Skip to content

Commit

Permalink
Allow to iterate over hashes, closes #139
Browse files Browse the repository at this point in the history
```
for k, v in {"a": 1, "b": 2, "c": 3} {
  echo(k, v)
}
```

This required a bit of a refactoring on the iterables,
but nothing too crazy.
  • Loading branch information
odino committed Feb 2, 2019
1 parent ac4e220 commit 2166053
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 23 deletions.
16 changes: 8 additions & 8 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,37 +768,37 @@ func evalForInExpression(
i.Reset()
}()

return loopIterable(i.Next, env, fie, 0)
return loopIterable(i.Next, env, fie)
case *object.Builtin:
if i.Next == nil {
return newError(fie.Token, "builtin function cannot be used in loop")
}

return loopIterable(i.Next, env, fie, 0)
return loopIterable(i.Next, env, fie)
default:
return newError(fie.Token, "'%s' is a %s, not an iterable, cannot be used in for loop", i.Inspect(), i.Type())
}
}

func loopIterable(next func(int) (int, object.Object), env *object.Environment, fie *ast.ForInExpression, pos int) object.Object {
k, v := next(pos)
func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fie *ast.ForInExpression) object.Object {
k, v := next()

if k < 0 || v == EOF {
if k == nil || v == EOF {
return NULL
}

// set the special k v variables in the
// environment
env.Set(fie.Key, &object.Number{Token: fie.Token, Value: float64(k)})
env.Set(fie.Key, k)
env.Set(fie.Value, v)
err := Eval(fie.Block, env)

if isError(err) {
return err
}

if k >= 0 {
return loopIterable(next, env, fie, pos+1)
if k != nil {
return loopIterable(next, env, fie)
}

return NULL
Expand Down
15 changes: 10 additions & 5 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ func TestForInExpressions(t *testing.T) {
{"a = 0; for k, x in 1..10 { a = a + 1}; a", 10},
{"a = 0; for x in 1 { a = a + 1}; a", "'1' is a NUMBER, not an iterable, cannot be used in for loop"},
{"a = 0; for x in 1..10 { a = a + 1}; a", 10},
{`a = 0; for k, v in {"a": 10} { a = v}; a`, 10},
{`a = ""; b = "abc"; for k, v in {"a": 1, "b": 2, "c": 3} { a += k}; a == b`, true},
{`a = 0; for k, v in ["x", "y", "z"] { a = a + k}; a`, 3},
{`for k, v in ["x", "y", "z"] {}; k`, "identifier not found: k"},
{`for k, v in ["x", "y", "z"] {}; v`, "identifier not found: v"},
Expand All @@ -309,16 +311,19 @@ func TestForInExpressions(t *testing.T) {

for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testNumberObject(t, evaluated, float64(integer))
} else {

switch ev := tt.expected.(type) {
case int:
testNumberObject(t, evaluated, float64(ev))
case bool:
testBooleanObject(t, evaluated, ev)
default:
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("no error object returned. got=%T(%+v)", evaluated, evaluated)
continue
}
logErrorWithPosition(t, errObj.Message, tt.expected)
logErrorWithPosition(t, errObj.Message, ev)
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions evaluator/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

var scanner *bufio.Scanner
var tok token.Token
var scannerPosition int

func init() {
scanner = bufio.NewScanner(os.Stdin)
Expand Down Expand Up @@ -507,14 +508,17 @@ func stdinFn(args ...object.Object) object.Object {

return &object.String{Token: tok, Value: scanner.Text()}
}
func stdinNextFn(pos int) (int, object.Object) {
func stdinNextFn() (object.Object, object.Object) {
v := scanner.Scan()

if !v {
return pos, EOF
return nil, EOF
}

return pos, &object.String{Token: tok, Value: scanner.Text()}
defer func() {
scannerPosition += 1
}()
return &object.Number{Value: float64(scannerPosition)}, &object.String{Token: tok, Value: scanner.Text()}
}

// env(variable:"PWD")
Expand Down
47 changes: 40 additions & 7 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Object interface {
}

type Iterable interface {
Next(int) (int, Object)
Next() (Object, Object)
Reset()
}

Expand Down Expand Up @@ -164,7 +164,7 @@ func (s *String) HashKey() HashKey {
type Builtin struct {
Token token.Token
Fn BuiltinFunction
Next func(int) (int, Object)
Next func() (Object, Object)
Types []string
Iterable bool
}
Expand All @@ -179,14 +179,14 @@ type Array struct {
}

func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
func (ao *Array) Next(pos int) (int, Object) {
func (ao *Array) Next() (Object, Object) {
position := ao.position
if len(ao.Elements) > position {
ao.position = position + 1
return position, ao.Elements[position]
return &Number{Value: float64(position)}, ao.Elements[position]
}

return -1, nil
return nil, nil
}
func (ao *Array) Reset() {
ao.position = 0
Expand Down Expand Up @@ -231,8 +231,9 @@ type HashPair struct {
}

type Hash struct {
Token token.Token
Pairs map[HashKey]HashPair
Token token.Token
Pairs map[HashKey]HashPair
Position int
}

func (h *Hash) Type() ObjectType { return HASH_OBJ }
Expand All @@ -252,3 +253,35 @@ func (h *Hash) Inspect() string {

return out.String()
}

// Pretty convoluted logic here we could
// refactor.
// First we sort the hash keys alphabetically
// and then we loop through them until
// we reach the required position within
// the loop.
func (h *Hash) Next() (Object, Object) {
curPosition := 0
pairs := make(map[string]HashPair)
var keys []string
for _, v := range h.Pairs {
pairs[v.Key.Inspect()] = v
keys = append(keys, v.Key.Inspect())
}

sort.Strings(keys)

for _, k := range keys {
if h.Position == curPosition {
h.Position += 1
return pairs[k].Key, pairs[k].Value
}

curPosition += 1
}

return nil, nil
}
func (h *Hash) Reset() {
h.Position = 0
}

0 comments on commit 2166053

Please sign in to comment.