Skip to content

Commit ea03860

Browse files
committed
Fix pretty-printing to use correct precedence, refactor binops to BinOp
1 parent 43f0779 commit ea03860

19 files changed

+548
-294
lines changed

ast/and.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

ast/arg.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ func (a Arg) Eval(c *Context) Val {
1818
func (a Arg) String() string {
1919
return "$" + strconv.Itoa(int(a))
2020
}
21+
func (a Arg) Precedence() int {
22+
// Leaf, not operator
23+
return LeafPrecedence
24+
}

ast/assn.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ func (a Assn) Eval(c *Context) Val {
1818
func (a Assn) String() string {
1919
return a.V.String() + " = " + a.E.String()
2020
}
21+
func (a Assn) Precedence() int {
22+
return 0
23+
}

ast/ast.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ type Attrib interface{}
99
type Expr interface {
1010
Eval(*Context) Val
1111
String() string
12+
Precedence() int
13+
//IsRightAssociative() bool // Unnecessary, we only have left-associative expressions currently.
1214
}
1315

16+
const LeafPrecedence int = 100
17+
1418
type Program struct {
1519
Funcs []FuncDecl
1620
Code Block
@@ -40,6 +44,10 @@ func (p Program) String() string {
4044
result := strings.Join(funcdecls, "\n")
4145
return result + "\n" + p.Code.String()
4246
}
47+
func (p Program) Precedence() int {
48+
// Unused
49+
return -1
50+
}
4351

4452
type Block []Expr
4553

@@ -70,3 +78,7 @@ func (b Block) String() string {
7078
}
7179
return str
7280
}
81+
func (b Block) Precedence() int {
82+
// Unused
83+
return -1
84+
}

ast/binop.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package ast
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
)
7+
8+
type Op int
9+
10+
// Op are operators used for BinOp, encoded as their precedence
11+
const (
12+
OrOp Op = (iota + 1) * 10
13+
AndOp
14+
NotEqualsOp
15+
EqualsOp
16+
ConcatOp
17+
)
18+
19+
func (o Op) String() string {
20+
switch o {
21+
case OrOp:
22+
return "||"
23+
case AndOp:
24+
return "&&"
25+
case NotEqualsOp:
26+
return "!="
27+
case EqualsOp:
28+
return "=="
29+
case ConcatOp:
30+
return "+"
31+
}
32+
panic("Op not found:" + strconv.Itoa(int(o)))
33+
}
34+
35+
type BinOp struct {
36+
Lhs Expr
37+
Rhs Expr
38+
Op Op
39+
}
40+
41+
func NewOr(a, b Attrib) (Expr, error) {
42+
return BinOp{
43+
Lhs: a.(Expr),
44+
Rhs: b.(Expr),
45+
Op: OrOp,
46+
}, nil
47+
}
48+
func NewAnd(a, b Attrib) (Expr, error) {
49+
return BinOp{
50+
Lhs: a.(Expr),
51+
Rhs: b.(Expr),
52+
Op: AndOp,
53+
}, nil
54+
}
55+
func NewNotEquals(a, b Attrib) (Expr, error) {
56+
return BinOp{
57+
Lhs: a.(Expr),
58+
Rhs: b.(Expr),
59+
Op: NotEqualsOp,
60+
}, nil
61+
}
62+
func NewEquals(a, b Attrib) (Expr, error) {
63+
return BinOp{
64+
Lhs: a.(Expr),
65+
Rhs: b.(Expr),
66+
Op: EqualsOp,
67+
}, nil
68+
}
69+
func NewConcat(a, b Attrib) (Expr, error) {
70+
return BinOp{
71+
Lhs: a.(Expr),
72+
Rhs: b.(Expr),
73+
Op: ConcatOp,
74+
}, nil
75+
}
76+
77+
func (b BinOp) Eval(c *Context) Val {
78+
switch b.Op {
79+
case OrOp:
80+
return b.evalOr(c)
81+
case AndOp:
82+
return b.evalAnd(c)
83+
case NotEqualsOp:
84+
return b.evalNotEquals(c)
85+
case EqualsOp:
86+
return b.evalEquals(c)
87+
case ConcatOp:
88+
return b.evalConcat(c)
89+
}
90+
panic("BinOp Op not found")
91+
}
92+
func (b BinOp) String() string {
93+
lhs := b.Lhs.String()
94+
rhs := b.Rhs.String()
95+
96+
// Example:
97+
// Lhs: a == b
98+
// Op: +
99+
// Rhs: c
100+
// We can't just print "Lhs Op Rhs" <=> "a == b + c", because + has higher precedence, which would "capture" only the b:
101+
// Lhs: a !! INCORRECT
102+
// Op: == !! INCORRECT
103+
// Rhs: b + c !! INCORRECT
104+
// Hence we must add parentheses around Lhs
105+
106+
// For Lhs we need '>', because same precedence operators (i.e. the same operator) can be chained:
107+
// a + b + c <=> (a + b) + c !! Parentheses unnecessary
108+
if b.Precedence() > b.Lhs.Precedence() {
109+
lhs = "(" + lhs + ")"
110+
}
111+
// However, because we only have left-associative operators, for the Rhs we must treat the case of same precedence differently,
112+
// i.e. a + (b + c) may not be printed as a + b + c, because that is equal to (a + b) + c =/= a + (b + c)
113+
// (Note: If we had different associativity, see here: https://stackoverflow.com/questions/6277747/pretty-print-expression-with-as-few-parentheses-as-possible)
114+
if b.Precedence() >= b.Rhs.Precedence() {
115+
rhs = "(" + rhs + ")"
116+
}
117+
return fmt.Sprintf("%v %v %v", lhs, b.Op.String(), rhs)
118+
}
119+
func (b BinOp) Precedence() int {
120+
return int(b.Op)
121+
}
122+
123+
124+
func (b BinOp) evalOr(c *Context) Val {
125+
if BoolOf(b.Lhs.Eval(c)) || BoolOf(b.Rhs.Eval(c)) {
126+
return Val("true")
127+
} else {
128+
return Val("false")
129+
}
130+
}
131+
func (b BinOp) evalAnd(c *Context) Val {
132+
if BoolOf(b.Lhs.Eval(c)) && BoolOf(b.Rhs.Eval(c)) {
133+
return Val("true")
134+
} else {
135+
return Val("false")
136+
}
137+
}
138+
func (b BinOp) evalNotEquals(c *Context) Val {
139+
val := "false"
140+
if b.Lhs.Eval(c) != b.Rhs.Eval(c) {
141+
val = "true"
142+
}
143+
return Val(val)
144+
}
145+
func (b BinOp) evalEquals(c *Context) Val {
146+
val := "false"
147+
if b.Lhs.Eval(c) == b.Rhs.Eval(c) {
148+
val = "true"
149+
}
150+
return Val(val)
151+
}
152+
func (b BinOp) evalConcat(c *Context) Val {
153+
return b.Lhs.Eval(c) + b.Rhs.Eval(c)
154+
}

ast/call.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,27 @@ func (ca Call) Eval(c *Context) Val {
4949
fnAst, err := c.parseFn([]byte(fnSource))
5050
if err != nil {
5151
fmt.Println("Error occurred:", err)
52+
fmt.Println("Fn is:")
53+
fmt.Println(ca.Fn)
54+
fmt.Println("source is:")
55+
fmt.Println(string(fnSource))
5256
return ""
5357
}
5458
fnProg := fnAst.(Program)
5559
if len(fnProg.Code) != 1 {
5660
// Must consist of exactly one lambda
57-
fmt.Println("Length of parsed program is not equal to 1")
61+
fmt.Println("Length of parsed program is not equal to 1, Fn expression is:")
62+
fmt.Println(ca.Fn)
63+
fmt.Println("source is:")
64+
fmt.Println(string(fnSource))
5865
return ""
5966
}
6067
fst := fnProg.Code[0]
6168
lam, ok := fst.(Lambda)
6269
if !ok {
6370
// Must be lambda
64-
fmt.Println("Parsed program is not a Lambda")
71+
fmt.Println("Parsed program is not a Lambda:")
72+
fmt.Println(string(fnSource))
6573
return ""
6674
}
6775

@@ -80,7 +88,16 @@ func (ca Call) String() string {
8088
args = append(args, arg.String())
8189
}
8290

83-
return ca.Fn.String() + "(" + strings.Join(args, ", ") + ")"
91+
fnStr := ca.Fn.String()
92+
if ca.Fn.Precedence() < LeafPrecedence {
93+
fnStr = "(" + fnStr + ")"
94+
}
95+
96+
return fnStr + "(" + strings.Join(args, ", ") + ")"
97+
}
98+
func (ca Call) Precedence() int {
99+
// Leaf, not operator
100+
return LeafPrecedence
84101
}
85102

86103
// CallArgs is not an Expr, since it can never appear on its own

ast/concat.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

ast/context.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ func (c *Context) GetExitChannel() chan int {
3838
}
3939
return c.exitChannel
4040
}
41+
42+
func (c *Context) FuncNames() Set {
43+
names := make(Set, len(c.FunctionMap) + len(c.UserFunctionMap))
44+
for fId := range c.UserFunctionMap {
45+
names.Add(fId)
46+
}
47+
for fId := range c.FunctionMap {
48+
names.Add(fId)
49+
}
50+
return names
51+
}

ast/equals.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

ast/ifelse.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ func (e IfElse) String() string {
3131
str := "if (" + e.Cond.String() + ") {\n\t" + thenStr + "\n} else {\n\t" + elseStr + "\n}"
3232
return str
3333
}
34+
func (e IfElse) Precedence() int {
35+
// Leaf, not operator
36+
return LeafPrecedence
37+
}

ast/index.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,13 @@ func (i Index) Eval(c *Context) Val {
2525
return Val(src[idx])
2626
}
2727
func (i Index) String() string {
28-
return i.Source.String() + "[" + i.I.String() + "]"
28+
srcStr := i.Source.String()
29+
if i.Source.Precedence() < LeafPrecedence {
30+
srcStr = "(" + srcStr + ")"
31+
}
32+
return srcStr + "[" + i.I.String() + "]"
33+
}
34+
func (i Index) Precedence() int {
35+
// Leaf, not operator
36+
return LeafPrecedence
2937
}

ast/lambda.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ func NewLambda(ps, b Attrib) (Expr, error) {
1717
}
1818

1919
func (l Lambda) Eval(c *Context) Val {
20-
// Closure by value, copy the value of l's body's free vars in the current context into its body
21-
fvMap := FreeVars(l)
20+
// Closure by value, copy the value of l's body's var that are used before defined in the current context into its body
21+
fvMap := UsedBeforeDefVars(l, c.FuncNames())
2222

2323
// Need to sort as slice because key traversal in maps is non-deterministic
2424
fv := make([]string, 0, len(fvMap))
@@ -46,6 +46,10 @@ func (l Lambda) String() string {
4646
return res
4747
}
4848

49+
func (l Lambda) Precedence() int {
50+
return LeafPrecedence
51+
}
52+
4953
func (l Lambda) Call(c *Context, args []Val) Val {
5054
// Construct corresponding FuncDecl and call that instead
5155
fDecl := FuncDecl{

ast/notequals.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)