Skip to content

Commit e564415

Browse files
committed
evaluator: Add index and dot expression evaluation
Add index and dot expression evaluation in evaluator, similar to binary expression evaluation.
1 parent a5b48db commit e564415

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

pkg/evaluator/evaluator.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func (e *Evaluator) Eval(scope *scope, node parser.Node) Value {
6767
return e.evalUnaryExpr(scope, node)
6868
case *parser.BinaryExpression:
6969
return e.evalBinaryExpr(scope, node)
70+
case *parser.IndexExpression:
71+
return e.evalIndexExpr(scope, node)
72+
case *parser.DotExpression:
73+
return e.evalDotExpr(scope, node)
7074
}
7175
return nil
7276
}
@@ -341,3 +345,40 @@ func evalBinaryBoolExpr(op parser.Operator, left, right *Bool) Value {
341345
}
342346
return newError("unknown bool operation: " + op.String())
343347
}
348+
349+
func (e *Evaluator) evalIndexExpr(scope *scope, expr *parser.IndexExpression) Value {
350+
left := e.Eval(scope, expr.Left)
351+
if isError(left) {
352+
return left
353+
}
354+
index := e.Eval(scope, expr.Index)
355+
if isError(index) {
356+
return index
357+
}
358+
359+
switch left := left.(type) {
360+
case *Array:
361+
return left.Index(index)
362+
case *String:
363+
return left.Index(index)
364+
case *Map:
365+
index, ok := index.(*String)
366+
if !ok {
367+
return newError("expected string for map index, found " + index.String())
368+
}
369+
return left.Get(index.Val)
370+
}
371+
return nil
372+
}
373+
374+
func (e *Evaluator) evalDotExpr(scope *scope, expr *parser.DotExpression) Value {
375+
left := e.Eval(scope, expr.Left)
376+
if isError(left) {
377+
return left
378+
}
379+
m, ok := left.(*Map)
380+
if !ok {
381+
return newError("expected map before '.', found " + left.String())
382+
}
383+
return m.Get(expr.Key)
384+
}

pkg/evaluator/evaluator_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,60 @@ a := [1 1+1 (three)]`: "[1 2 3]",
317317
}
318318
}
319319

320+
func TestIndex(t *testing.T) {
321+
tests := map[string]string{
322+
// x := ["a","b","c"]; x = "abc"
323+
"print x[0]": "a",
324+
"print x[1]": "b",
325+
"print x[2]": "c",
326+
"print x[-1]": "c",
327+
"print x[-2]": "b",
328+
"print x[-3]": "a",
329+
`
330+
n1 := 1
331+
print x[n1 - 1] x[1 + n1]
332+
`: "a c",
333+
`
334+
m := {a: "bingo"}
335+
print m[x[0]]
336+
`: "bingo",
337+
}
338+
for in, want := range tests {
339+
in, want := in, want
340+
for _, decl := range []string{`x := ["a" "b" "c"]`, `x := "abc"`} {
341+
input := decl + "\n" + in
342+
t.Run(input, func(t *testing.T) {
343+
b := bytes.Buffer{}
344+
fn := func(s string) { b.WriteString(s) }
345+
Run(input, fn)
346+
assert.Equal(t, want+"\n", b.String())
347+
})
348+
}
349+
}
350+
}
351+
352+
func TestIndexErr(t *testing.T) {
353+
tests := map[string]string{
354+
// x := ["a","b","c"]; x = "abc"
355+
"print x[3]": "ERROR: index 3 out of bounds, should be between -3 and 2",
356+
"print x[-4]": "ERROR: index -4 out of bounds, should be between -3 and 2",
357+
`m := {}
358+
print m[x[1]]`: "ERROR: no value for key b",
359+
}
360+
for in, want := range tests {
361+
in, want := in, want
362+
for _, decl := range []string{`x := ["a" "b" "c"]`, `x := "abc"`} {
363+
input := decl + "\n" + in
364+
t.Run(input, func(t *testing.T) {
365+
b := bytes.Buffer{}
366+
fn := func(s string) { b.WriteString(s) }
367+
Run(input, fn)
368+
assert.Equal(t, want, b.String())
369+
})
370+
}
371+
}
372+
}
373+
320374
func TestMapLit(t *testing.T) {
321375
tests := map[string]string{
322376
"a := {n:1}": "{n:1}",
@@ -344,6 +398,26 @@ a := {name:"fox" age:39+(three)}`: "{name:fox age:42}",
344398
}
345399
}
346400

401+
func TestDot(t *testing.T) {
402+
tests := map[string]string{
403+
// m := {name: "Greta"}
404+
"print m.name": "Greta",
405+
`print m["name"]`: "Greta",
406+
`s := "name"
407+
print m[s]`: "Greta",
408+
}
409+
for in, want := range tests {
410+
in, want := in, want
411+
input := `m := {name: "Greta"}` + "\n" + in
412+
t.Run(input, func(t *testing.T) {
413+
b := bytes.Buffer{}
414+
fn := func(s string) { b.WriteString(s) }
415+
Run(input, fn)
416+
assert.Equal(t, want+"\n", b.String())
417+
})
418+
}
419+
}
420+
347421
func TestDemo(t *testing.T) {
348422
prog := `
349423
move 10 10

pkg/evaluator/value.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ type Bool struct {
6161
}
6262

6363
type String struct {
64-
Val string
64+
Val string
65+
runes []rune
6566
}
6667

6768
type Any struct {
@@ -115,9 +116,21 @@ func (s *String) Equals(v Value) bool {
115116
func (s *String) Set(v Value) {
116117
if s2, ok := v.(*String); ok {
117118
s.Val = s2.Val
119+
s.runes = nil
118120
}
119121
}
120122

123+
func (s *String) Index(idx Value) Value {
124+
if s.runes == nil {
125+
s.runes = []rune(s.Val)
126+
}
127+
i, err := normalizeIndex(idx, len(s.runes))
128+
if err != nil {
129+
return err
130+
}
131+
return &String{Val: string(s.runes[i])}
132+
}
133+
121134
func (*Bool) Type() ValueType { return BOOL }
122135
func (b *Bool) String() string {
123136
return strconv.FormatBool(b.Val)
@@ -196,6 +209,15 @@ func (a *Array) Set(v Value) {
196209
}
197210
}
198211

212+
func (a *Array) Index(idx Value) Value {
213+
i, err := normalizeIndex(idx, len(*a.Elements))
214+
if err != nil {
215+
return err
216+
}
217+
elements := *a.Elements
218+
return elements[i]
219+
}
220+
199221
func (m *Map) Type() ValueType { return MAP }
200222
func (m *Map) String() string {
201223
pairs := make([]string, 0, len(m.Pairs))
@@ -228,6 +250,14 @@ func (m *Map) Set(v Value) {
228250
}
229251
}
230252

253+
func (m *Map) Get(key string) Value {
254+
val, ok := m.Pairs[key]
255+
if !ok {
256+
return newError("no value for key " + key)
257+
}
258+
return val
259+
}
260+
231261
func isError(val Value) bool { // TODO: replace with panic flow
232262
return val != nil && val.Type() == ERROR
233263
}
@@ -243,3 +273,20 @@ func isBreak(val Value) bool {
243273
func newError(msg string) *Error {
244274
return &Error{Message: msg}
245275
}
276+
277+
func normalizeIndex(idx Value, length int) (int, Value) {
278+
index, ok := idx.(*Num)
279+
if !ok {
280+
return 0, newError("expected index of type num, found " + idx.Type().String())
281+
}
282+
i := int(index.Val)
283+
if i < -length || i >= length {
284+
boundsStr := strconv.Itoa(-length) + " and " + strconv.Itoa(length-1)
285+
msg := "index " + strconv.Itoa(i) + " out of bounds, should be between " + boundsStr
286+
return 0, newError(msg)
287+
}
288+
if i < 0 {
289+
return length + i, nil // -1 references len-1 i.e. last element
290+
}
291+
return i, nil
292+
}

0 commit comments

Comments
 (0)