Skip to content

Commit

Permalink
Merge pull request #117 from skx/113-range-strings
Browse files Browse the repository at this point in the history
113 range strings
  • Loading branch information
skx authored Jan 29, 2020
2 parents c763eb8 + 8a59274 commit 7f3a8a7
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 13 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,19 @@ Again as you'd expect the facilities are pretty normal/expected:
* There are series of built-in primitives which can be used by your scripts, and you can export your own host-specified functions easily.
* For example the `print` function to generate output from your script is just a simple function implemented in Golang and exported to the environment.

Our script allows looping over arrays, via the `foreach` function:
Our script allows looping over arrays and string, via the `foreach` function:

foreach item in [ "My", "name", "is", "Steve" ] {
print( item, "\t" );
}
return false;

len = 0;
foreach char in "狐犬" {
len++;
}
return( len == 2 );

And new arrays can be created with integers via the `..` helper:

sum = 0;
Expand Down
13 changes: 13 additions & 0 deletions evalfilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,19 @@ return( sum == 10 );
{Input: `sum = 0 ; foreach item in 1..4 {
sum = sum + item; }
return( sum == 10 );
`,
Result: true},
{Input: `
str = "狐犬"; len = 0;
foreach char in str { len++ }
return len == 2 ;
`,
Result: true},
{Input: `
str = "狐犬"; len = 0;
foreach char in str { len++ }
foreach char in str { len++ }
return len == 4 ;
`,
Result: true},
{Input: `return( len( 1..10 ) == 10);`,
Expand Down
11 changes: 11 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,14 @@ type Decrement interface {
// Decrease lowers the object's value by one.
Decrease()
}

// Iterable is the interface that any object must implement if
// it is to be iterated over with the `foreach` built-in.
type Iterable interface {

// Reset the state of any previous iteration.
Reset()

// Get the next "thing" from the object.
Next() (Object, int, bool)
}
23 changes: 21 additions & 2 deletions object/object_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ type Array struct {
// Elements holds the individual members of the array we're wrapping.
Elements []Object

// Offset is used for array walking.
Offset int
// offset holds our iteration-offset.
offset int
}

// Type returns the type of this object.
Expand Down Expand Up @@ -53,3 +53,22 @@ func (ao *Array) ToInterface() interface{} {

return res
}

// Reset implements the Iterable interface, and allows the contents
// of the array to be reset to allow re-iteration.
func (ao *Array) Reset() {
ao.offset = 0
}

// Next implements the Iterable interface, and allows the contents
// of our array to be iterated over.
func (ao *Array) Next() (Object, int, bool) {
if ao.offset < len(ao.Elements) {
ao.offset++

element := ao.Elements[ao.offset-1]
return element, ao.offset - 1, true
}

return nil, 0, false
}
30 changes: 30 additions & 0 deletions object/object_string.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package object

import "unicode/utf8"

// String wraps string and implements the Object interface.
type String struct {
// Value holds the string value this object wraps.
Value string

// Offset holds our iteration-offset
offset int
}

// Type returns the type of this object.
Expand All @@ -30,3 +35,28 @@ func (s *String) True() bool {
func (s *String) ToInterface() interface{} {
return s.Value
}

// Reset implements the Iterable interface, and allows the contents
// of the string to be reset to allow re-iteration.
func (s *String) Reset() {
s.offset = 0
}

// Next implements the Iterable interface, and allows the contents
// of our string to be iterated over.
func (s *String) Next() (Object, int, bool) {

if s.offset < utf8.RuneCountInString(s.Value) {
s.offset++

// Get the characters as an array of runes
chars := []rune(s.Value)

// Now index
val := String{Value: string(chars[s.offset-1])}

return &val, s.offset - 1, true
}

return nil, 0, false
}
20 changes: 10 additions & 10 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,11 @@ func (vm *VM) Run(obj interface{}) (object.Object, error) {
}

// Reset it.
obj, ok := out.(*object.Array)
helper, ok := out.(object.Iterable)
if !ok {
return nil, fmt.Errorf("object not an array:%v", obj)
return nil, fmt.Errorf("%s object doesn't implement the Iterable interface", out.Type())
}

obj.Offset = 0
helper.Reset()
vm.stack.Push(out)

case code.OpIterationNext:
Expand All @@ -430,15 +429,15 @@ func (vm *VM) Run(obj interface{}) (object.Object, error) {
return nil, err
}

obj, ok := out.(*object.Array)
helper, ok := out.(object.Iterable)
if !ok {
return nil, fmt.Errorf("object not an array:%v", obj)
return nil, fmt.Errorf("%s object doesn't implement the Iterable interface", out.Type())
}

if obj.Offset < len(obj.Elements) {
obj.Offset++
obj, _, ok := helper.Next()
if ok {
vm.stack.Push(out)
vm.stack.Push(obj)
vm.stack.Push(obj.Elements[obj.Offset-1])
vm.stack.Push(True)
} else {
vm.stack.Push(False)
Expand Down Expand Up @@ -496,6 +495,7 @@ func (vm *VM) Run(obj interface{}) (object.Object, error) {
helper.Increase()
vm.environment.Set(name, val)

vm.stack.Pop()
case code.OpDec:
// Get the name of the variable whos' contents
// we should decrement.
Expand All @@ -513,7 +513,7 @@ func (vm *VM) Run(obj interface{}) (object.Object, error) {
// Mutate & store
helper.Decrease()
vm.environment.Set(name, val)

vm.stack.Pop()
default:
return nil, fmt.Errorf("unhandled opcode: %v %s", op, code.String(op))
}
Expand Down

0 comments on commit 7f3a8a7

Please sign in to comment.