Skip to content

Commit 24a00db

Browse files
committed
Add lambdas (still strings!)
1 parent d2371f1 commit 24a00db

File tree

12 files changed

+6621
-3965
lines changed

12 files changed

+6621
-3965
lines changed

ast/call.go

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package ast
22

3-
import "strings"
3+
import (
4+
"fmt"
5+
"strings"
6+
)
47

58
type Call struct {
6-
Fn Var
9+
Fn Expr
710
Args CallArgs
811
}
912

1013
func NewCall(f, as Attrib) (Expr, error) {
11-
fn := f.(Var)
14+
fn := f.(Expr)
1215
args := as.(CallArgs)
1316
return Call{Fn: fn, Args: args}, nil
1417
}
@@ -17,27 +20,58 @@ func (ca Call) Eval(c *Context) Val {
1720
return ""
1821
}
1922

20-
userFn, ok := c.UserFunctionMap[string(ca.Fn)]
21-
if !ok {
22-
fn, ok := c.FunctionMap[string(ca.Fn)]
23-
if !ok {
24-
panic("function '" + string(ca.Fn) + "' not found.")
23+
if fnVar, ok := ca.Fn.(Var); ok {
24+
userFn, ok := c.UserFunctionMap[string(fnVar)]
25+
if ok {
26+
vals := make([]Val, 0, len(ca.Args))
27+
for _, argExp := range ca.Args {
28+
v := argExp.Eval(c)
29+
vals = append(vals, v)
30+
}
31+
res := userFn.Call(c, vals)
32+
return res
2533
}
26-
vals := make([]string, 0, len(ca.Args))
27-
for _, argExp := range ca.Args {
28-
v := argExp.Eval(c)
29-
vals = append(vals, string(v))
34+
35+
fn, ok := c.FunctionMap[string(fnVar)]
36+
if ok {
37+
vals := make([]string, 0, len(ca.Args))
38+
for _, argExp := range ca.Args {
39+
v := argExp.Eval(c)
40+
vals = append(vals, string(v))
41+
}
42+
res := fn(vals)
43+
return Val(res)
3044
}
31-
res := fn(vals)
32-
return Val(res)
45+
// Treat as expression, fallthrough
46+
}
47+
48+
fnSource := ca.Fn.Eval(c)
49+
fnAst, err := c.parseFn([]byte(fnSource))
50+
if err != nil {
51+
fmt.Println("Error occurred:", err)
52+
return ""
53+
}
54+
fnProg := fnAst.(Program)
55+
if len(fnProg.Code) != 1 {
56+
// Must consist of exactly one lambda
57+
fmt.Println("Length of parsed program is not equal to 1")
58+
return ""
59+
}
60+
fst := fnProg.Code[0]
61+
lam, ok := fst.(Lambda)
62+
if !ok {
63+
// Must be lambda
64+
fmt.Println("Parsed program is not a Lambda")
65+
return ""
3366
}
3467

3568
vals := make([]Val, 0, len(ca.Args))
3669
for _, argExp := range ca.Args {
3770
v := argExp.Eval(c)
3871
vals = append(vals, v)
3972
}
40-
res := userFn.Call(c, vals)
73+
74+
res := lam.Call(c, vals)
4175
return res
4276
}
4377
func (ca Call) String() string {
@@ -95,6 +129,7 @@ func (f FuncDecl) Call(c *Context, args []Val) Val {
95129
MaxStackSize: c.MaxStackSize - CheckSize(c.VariableMap) - GoStackframeEstimate, // New context needs to account for Go stackframes
96130
limitStackSize: c.limitStackSize,
97131
exitChannel: c.exitChannel,
132+
parseFn: c.parseFn,
98133
}
99134
return f.Code.Eval(&cNew)
100135
}

ast/context.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package ast
22

3-
func NewContext(args []string, funcs map[string]func([]string) string) *Context {
3+
func NewContext(args []string, funcs map[string]func([]string) string, parseFn func([]byte) (Expr, error)) *Context {
44
return &Context{
55
Args: args,
66
VariableMap: make(map[Var]Val),
@@ -9,6 +9,7 @@ func NewContext(args []string, funcs map[string]func([]string) string) *Context
99
MaxStackSize: -1,
1010
limitStackSize: false,
1111
exitChannel: make(chan int, 1),
12+
parseFn: parseFn,
1213
}
1314
}
1415

@@ -20,6 +21,7 @@ type Context struct {
2021
MaxStackSize int64
2122
exitChannel chan int
2223
limitStackSize bool
24+
parseFn func([]byte) (Expr, error) /* Ugly hack to avoid illegal circular imports */
2325
}
2426

2527
func (c *Context) SetMaxStackSize(sz int64) {

ast/lambda.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ast
2+
3+
import "strings"
4+
5+
type Lambda struct {
6+
Params []string
7+
Code Block
8+
}
9+
10+
func NewLambda(ps, b Attrib) (Expr, error) {
11+
params := ps.([]string)
12+
code := b.(Block)
13+
return Lambda{Params: params, Code: code}, nil
14+
}
15+
16+
func (l Lambda) Eval(c *Context) Val {
17+
// Closure by value, copy the value of l's body's free vars in the current context into its body
18+
fv := FreeVars(l)
19+
for v := range fv {
20+
l.Code = append([]Expr{Assn{V: Var(v), E: c.VariableMap[Var(v)]}}, l.Code...)
21+
}
22+
return Val(l.String())
23+
}
24+
25+
func (l Lambda) String() string {
26+
res := "fun(" + strings.Join(l.Params, ", ") + ") {\n\t"
27+
codeLines := strings.SplitN(l.Code.String(), "\n", -1)
28+
res += strings.Join(codeLines, "\n\t")
29+
res += "\n}"
30+
//res = strings.ReplaceAll(res, "\"", "\\\"")
31+
return res
32+
}
33+
34+
func (l Lambda) Call(c *Context, args []Val) Val {
35+
// Construct corresponding FuncDecl and call that instead
36+
fDecl := FuncDecl{
37+
Params: l.Params,
38+
Code: l.Code,
39+
Identifier: "temp_lambda",
40+
}
41+
return fDecl.Call(c, args)
42+
}

ast/util.go

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ func HasSideEffects(e Expr) bool {
5656
return false
5757
}
5858

59+
func FreeVars(e Expr) map[string]struct{} {
60+
// I haven't decided yet what the difference should be to UsedVars
61+
used := make(map[string]struct{})
62+
setUsedVars(e, used)
63+
return used
64+
}
65+
5966
func UsedVars(b Block) map[string]struct{} {
6067
used := make(map[string]struct{})
6168
setUsedVars(b, used)
@@ -105,38 +112,27 @@ func setUsedVars(expr Expr, used map[string]struct{}) {
105112
for _, e := range val.Args {
106113
setUsedVars(e, used)
107114
}
115+
// Because we allow arbitrary sources for a call, we need to take those into account
116+
setUsedVars(val.Fn, used)
108117
case Index:
109118
setUsedVars(val.Source, used)
110119
setUsedVars(val.I, used)
120+
case Lambda:
121+
innerUsed := UsedVars(val.Code)
122+
// Lambda's used vars are "used \union (innerUsed \except params)
123+
for _, v := range val.Params {
124+
delete(innerUsed, v)
125+
}
126+
for v := range innerUsed {
127+
used[v] = struct{}{}
128+
}
111129
}
112130
return
113131
}
114132

115133
func attribToString(a Attrib) string {
116134
return string(a.(*token.Token).Lit)
117135
}
118-
func unescape(s string) string {
119-
in := []rune(s)
120-
out := make([]rune, 0, len(in))
121-
var escape bool
122-
for _, r := range in {
123-
switch {
124-
case escape:
125-
switch r {
126-
case 'n':
127-
out = append(out, '\n')
128-
default:
129-
out = append(out, r)
130-
}
131-
escape = false
132-
case r == '\\':
133-
escape = true
134-
default:
135-
out = append(out, r)
136-
}
137-
}
138-
return string(out)
139-
}
140136

141137
const (
142138
SigExternalExit = iota + 1
@@ -180,6 +176,7 @@ Complete switch of expressions
180176
case IfElse:
181177
case While:
182178
case Call:
179+
case Lambda:
183180
}
184181
185182
*/

ast/val.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package ast
22

3+
import "strconv"
4+
35
type Val string
46

57
func NewVal(a Attrib) (Expr, error) {
68
quoted := attribToString(a)
7-
unquoted := quoted[1 : len(quoted)-1]
8-
unescaped := unescape(unquoted)
9-
return Val(unescaped), nil
9+
res, err := strconv.Unquote(quoted)
10+
if err != nil {
11+
panic(err)
12+
}
13+
return Val(res), nil
1014
}
1115
func (v Val) Eval(c *Context) Val {
1216
return v
1317
}
1418
func (v Val) String() string {
15-
return "\"" + string(v) + "\""
19+
return strconv.Quote(string(v))
1620
}

0 commit comments

Comments
 (0)