From 2b3fd4f6aa0f4536e084befcd1a005c9eeeca997 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Sun, 4 Feb 2024 13:39:53 -0500 Subject: [PATCH] Fixed use of local variables --- pkg/compiler/compiler_for_test.go | 9 +++ pkg/compiler/compiler_range_test.go | 23 +++++--- pkg/compiler/visitor.go | 53 +++++++++++------ pkg/runtime/globals.go | 1 - pkg/runtime/heap.go | 1 - pkg/runtime/operators/range.go | 6 +- pkg/runtime/stack.go | 42 +++++++++---- pkg/runtime/values/range.go | 91 +++++++++++++++++++++++------ pkg/runtime/values/range_iter.go | 21 +++++-- pkg/runtime/vm.go | 45 +++++++------- 10 files changed, 202 insertions(+), 90 deletions(-) delete mode 100644 pkg/runtime/globals.go delete mode 100644 pkg/runtime/heap.go diff --git a/pkg/compiler/compiler_for_test.go b/pkg/compiler/compiler_for_test.go index b074a35f..1a5a83c9 100644 --- a/pkg/compiler/compiler_for_test.go +++ b/pkg/compiler/compiler_for_test.go @@ -15,5 +15,14 @@ func TestFor(t *testing.T) { []any{1, 2, 3, 4, 5}, ShouldEqualJSON, }, + { + `FOR i IN 1..5 + LET x = i + PRINT(x) + RETURN i +`, + []any{1, 2, 3, 4, 5}, + ShouldEqualJSON, + }, }) } diff --git a/pkg/compiler/compiler_range_test.go b/pkg/compiler/compiler_range_test.go index 3d89da37..0f62eef9 100644 --- a/pkg/compiler/compiler_range_test.go +++ b/pkg/compiler/compiler_range_test.go @@ -13,23 +13,28 @@ func TestRange(t *testing.T) { []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, ShouldEqualJSON, }, + { + "RETURN 10..1", + []any{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + ShouldEqualJSON, + }, { ` -LET start = 1 -LET end = 10 -RETURN start..end -`, + LET start = 1 + LET end = 10 + RETURN start..end + `, []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, ShouldEqualJSON, }, - // { - // ` + //{ + // ` //LET start = @start //LET end = @end //RETURN start..end //`, - // []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - // ShouldEqualJSON, - // }, + // []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + // ShouldEqualJSON, + //}, }) } diff --git a/pkg/compiler/visitor.go b/pkg/compiler/visitor.go index 965a8c48..e471507b 100644 --- a/pkg/compiler/visitor.go +++ b/pkg/compiler/visitor.go @@ -36,6 +36,7 @@ type ( const ( jumpPlaceholder = -1 + undefinedVariable = -1 ignorePseudoVariable = "_" pseudoVariable = "CURRENT" waitScope = "waitfor" @@ -108,22 +109,23 @@ func (v *visitor) VisitHead(_ *fql.HeadContext) interface{} { func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} { v.beginScope() v.emit(runtime.OpArray) + resArrayIndex := len(v.bytecode) - 1 - var loopStart, exitJump int + var loopJump, exitJump int // identify whether it's WHILE or FOR loop isForInLoop := ctx.While() == nil if isForInLoop { - loopStart = len(v.bytecode) - // Loop data source to iterate over if c := ctx.ForExpressionSource(); c != nil { c.Accept(v) } v.emit(runtime.OpLoopInit) + loopJump = len(v.bytecode) v.emit(runtime.OpLoopHasNext) exitJump = v.emitJump(runtime.OpJumpIfFalse) + // pop the boolean value from the stack v.emitPop() valVar := ctx.GetValueVariable().GetText() @@ -138,7 +140,22 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} hasCounterVar = true } + var valVarIndex int + + // declare value variable + if hasValVar { + valVarIndex = v.declareVariable(valVar) + } + + var counterVarIndex int + + if hasCounterVar { + // declare counter variable + counterVarIndex = v.declareVariable(counterVar) + } + if hasValVar && hasCounterVar { + // we will calculate the index of the counter variable v.emit(runtime.OpLoopNext) } else if hasValVar { v.emit(runtime.OpLoopNextValue) @@ -148,32 +165,29 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} panic(core.Error(ErrUnexpectedToken, ctx.GetText())) } - // declare value variable - valVarIndex := v.declareVariable(valVar) - - var counterVarIndex int - - if hasCounterVar { - // declare counter variable - counterVarIndex = v.declareVariable(counterVar) + if hasValVar { + v.defineVariable(valVarIndex) + // remove the value from the stack + v.emitPop() } - v.defineVariable(valVarIndex) - if hasCounterVar { v.defineVariable(counterVarIndex) + // remove the counter from the stack + v.emitPop() } } else { // Create initial value for the loop counter v.emitConstant(runtime.OpPush, values.NewInt(0)) - loopStart = len(v.bytecode) + loopJump = len(v.bytecode) // Condition expression ctx.Expression().Accept(v) // Condition check exitJump = v.emitJump(runtime.OpJumpIfFalse) + // pop the boolean value from the stack v.emitPop() counterVar := ctx.GetCounterVariable().GetText() @@ -212,13 +226,18 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} // return if c := ctx.ForExpressionReturn(); c != nil { + returnIdx := len(v.bytecode) c.Accept(v) + + // patch return in order to access the resulting array + v.arguments[returnIdx-1] = resArrayIndex } - v.emitLoop(loopStart) + v.emitLoop(loopJump) v.patchJump(exitJump) // pop loop counter v.emitPop() + v.emitPop() v.endScope() return nil @@ -811,7 +830,7 @@ func (v *visitor) declareVariable(name string) int { } } - v.locals = append(v.locals, variable{name, v.scope}) + v.locals = append(v.locals, variable{name, undefinedVariable}) return len(v.locals) - 1 } @@ -835,7 +854,7 @@ func (v *visitor) emitConstant(op runtime.Opcode, constant core.Value) { // emitLoop emits a loop instruction. func (v *visitor) emitLoop(loopStart int) { pos := v.emitJump(runtime.OpLoop) - jump := pos - loopStart + 2 + jump := pos - loopStart v.arguments[pos-1] = jump } diff --git a/pkg/runtime/globals.go b/pkg/runtime/globals.go deleted file mode 100644 index 7ccdf5f6..00000000 --- a/pkg/runtime/globals.go +++ /dev/null @@ -1 +0,0 @@ -package runtime diff --git a/pkg/runtime/heap.go b/pkg/runtime/heap.go deleted file mode 100644 index 7ccdf5f6..00000000 --- a/pkg/runtime/heap.go +++ /dev/null @@ -1 +0,0 @@ -package runtime diff --git a/pkg/runtime/operators/range.go b/pkg/runtime/operators/range.go index 8ab6c6ba..222716bd 100644 --- a/pkg/runtime/operators/range.go +++ b/pkg/runtime/operators/range.go @@ -9,9 +9,5 @@ func Range(left, right core.Value) (core.Value, error) { start := values.ToInt(left) end := values.ToInt(right) - if start > end { - return values.None, nil - } - - return values.NewRange(uint64(start), uint64(end)), nil + return values.NewRange(int64(start), int64(end)), nil } diff --git a/pkg/runtime/stack.go b/pkg/runtime/stack.go index 3dcf47af..262a6145 100644 --- a/pkg/runtime/stack.go +++ b/pkg/runtime/stack.go @@ -3,35 +3,57 @@ package runtime import "github.com/MontFerret/ferret/pkg/runtime/core" type Stack struct { - values []core.Value + operands []core.Value + variables []core.Value } -func NewStack(cap int) *Stack { - return &Stack{make([]core.Value, 0, cap)} +func NewStack(operands, variables int) *Stack { + return &Stack{ + make([]core.Value, 0, operands), + make([]core.Value, 0, variables), + } } func (s *Stack) Len() int { - return len(s.values) + return len(s.operands) } func (s *Stack) Peek() core.Value { - return s.values[len(s.values)-1] + return s.operands[len(s.operands)-1] } func (s *Stack) Push(value core.Value) { - s.values = append(s.values, value) + s.operands = append(s.operands, value) } func (s *Stack) Pop() core.Value { - value := s.values[len(s.values)-1] - s.values = s.values[:len(s.values)-1] + value := s.operands[len(s.operands)-1] + s.operands = s.operands[:len(s.operands)-1] return value } func (s *Stack) Get(index int) core.Value { - return s.values[index] + return s.operands[index] } func (s *Stack) Set(index int, value core.Value) { - s.values[index] = value + s.operands[index] = value +} + +func (s *Stack) GetVariable(index int) core.Value { + return s.variables[index] +} + +func (s *Stack) SetVariable(index int, value core.Value) { + // TODO: Calculate in advance the number of variables + if index >= len(s.variables) { + s.variables = append(s.variables, value) + return + } + + s.variables[index] = value +} + +func (s *Stack) PushVariable(value core.Value) { + s.variables = append(s.variables, value) } diff --git a/pkg/runtime/values/range.go b/pkg/runtime/values/range.go index c2a3d118..2633dbc4 100644 --- a/pkg/runtime/values/range.go +++ b/pkg/runtime/values/range.go @@ -11,31 +11,22 @@ import ( ) type Range struct { - start uint64 - end uint64 + start int64 + end int64 } -func NewRange(start, end uint64) *Range { +func NewRange(start, end int64) *Range { return &Range{start, end} } -func (r *Range) Start() uint64 { + +func (r *Range) Start() int64 { return r.start } -func (r *Range) End() uint64 { +func (r *Range) End() int64 { return r.end } -func (r *Range) MarshalJSON() ([]byte, error) { - arr := make([]uint64, r.end-r.start+1) - - for i := r.start; i <= r.end; i++ { - arr[i-r.start] = i - } - - return jettison.MarshalOpts(arr, jettison.NoHTMLEscaping()) -} - func (r *Range) String() string { return fmt.Sprintf("%d..%d", r.start, r.end) } @@ -57,7 +48,7 @@ func (r *Range) Compare(other core.Value) int64 { } func (r *Range) Unwrap() interface{} { - return []uint64{r.start, r.end} + return []int64{r.start, r.end} } func (r *Range) Hash() uint64 { @@ -65,11 +56,25 @@ func (r *Range) Hash() uint64 { h.Write([]byte(types.Range.String())) h.Write([]byte(":")) + + startMultiplier := 1 + if r.start < 0 { + h.Write([]byte("-")) + startMultiplier = -1 + } + bytes := make([]byte, 8) - binary.LittleEndian.PutUint64(bytes, r.start) + binary.LittleEndian.PutUint64(bytes, uint64(r.start*int64(startMultiplier))) h.Write(bytes) h.Write([]byte("..")) - binary.LittleEndian.PutUint64(bytes, r.end) + + endMultiplier := 1 + if r.start < 0 { + h.Write([]byte("-")) + endMultiplier = -1 + } + + binary.LittleEndian.PutUint64(bytes, uint64(r.end*int64(endMultiplier))) h.Write(bytes) return h.Sum64() @@ -82,3 +87,53 @@ func (r *Range) Copy() core.Value { func (r *Range) Iterate(_ context.Context) (core.Iterator, error) { return NewRangeIterator(r), nil } + +func (r *Range) MarshalJSON() ([]byte, error) { + start := r.start + end := r.end + + var arr []int64 + + if start <= end { + arr = r.populateArray(start, end, r.calculateCapacity(start, end), true) + } else { + arr = r.populateArray(start, end, r.calculateCapacity(start, end), false) + } + + return jettison.MarshalOpts(arr, jettison.NoHTMLEscaping()) +} + +func (r *Range) calculateCapacity(start int64, end int64) int64 { + var capacity int64 + if start <= end { + if end < 0 { + capacity = start + (end * -1) + 1 + } else { + capacity = end - start + 1 + } + } else { + if start < 0 { + capacity = end + (start * -1) + 1 + } else { + capacity = start - end + 1 + } + } + return capacity +} + +func (r *Range) populateArray(start int64, end int64, capacity int64, ascending bool) []int64 { + arr := make([]int64, 0, capacity) + + if ascending { + // start to end + for i := start; i <= end; i++ { + arr = append(arr, i) + } + } else { + // end to start + for i := start; i >= end; i-- { + arr = append(arr, i) + } + } + return arr +} diff --git a/pkg/runtime/values/range_iter.go b/pkg/runtime/values/range_iter.go index d5d1875c..69fd9499 100644 --- a/pkg/runtime/values/range_iter.go +++ b/pkg/runtime/values/range_iter.go @@ -7,21 +7,30 @@ import ( type RangeIterator struct { values *Range - pos uint64 + dir int64 + pos int64 } func NewRangeIterator(values *Range) core.Iterator { - return &RangeIterator{values: values, pos: values.start} + if values.start > values.end { + return &RangeIterator{values: values, dir: -1, pos: values.start} + } + + return &RangeIterator{values: values, dir: 1, pos: values.start} } -func (iterator *RangeIterator) HasNext(ctx context.Context) (bool, error) { - return iterator.values.end > iterator.pos, nil +func (iterator *RangeIterator) HasNext(_ context.Context) (bool, error) { + if iterator.dir == 1 { + return iterator.values.end > (iterator.pos - 1), nil + } + + return iterator.values.start > iterator.pos, nil } func (iterator *RangeIterator) Next(_ context.Context) (value core.Value, key core.Value, err error) { - val := NewInt64(int64(iterator.pos)) + val := NewInt64(iterator.pos) - iterator.pos++ + iterator.pos += iterator.dir return val, val, nil } diff --git a/pkg/runtime/vm.go b/pkg/runtime/vm.go index 64159d7f..ce7b26b3 100644 --- a/pkg/runtime/vm.go +++ b/pkg/runtime/vm.go @@ -9,8 +9,6 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values/types" ) -const DefaultStackSize = 128 - type VM struct { ip int stack *Stack @@ -41,7 +39,8 @@ func (vm *VM) Run(ctx context.Context, program *Program) (res []byte, err error) } }() - stack := NewStack(DefaultStackSize) + // TODO: Add code analysis to calculate the number of operands and variables + stack := NewStack(len(program.Bytecode), 8) vm.stack = stack vm.globals = make(map[string]core.Value) vm.ip = 0 @@ -53,6 +52,24 @@ loop: vm.ip++ switch op { + case OpPush: + stack.Push(program.Constants[arg]) + + case OpPop: + stack.Pop() + + case OpStoreGlobal: + vm.globals[program.Constants[arg].String()] = stack.Pop() + + case OpLoadGlobal: + stack.Push(vm.globals[program.Constants[arg].String()]) + + case OpStoreLocal: + stack.SetVariable(arg, stack.Peek()) + + case OpLoadLocal: + stack.Push(stack.GetVariable(arg)) + case OpNone: stack.Push(values.None) @@ -92,24 +109,6 @@ loop: stack.Push(obj) - case OpPush: - stack.Push(program.Constants[arg]) - - case OpPop: - stack.Pop() - - case OpStoreGlobal: - vm.globals[program.Constants[arg].String()] = stack.Pop() - - case OpLoadGlobal: - stack.Push(vm.globals[program.Constants[arg].String()]) - - case OpStoreLocal: - stack.Set(arg, stack.Peek()) - - case OpLoadLocal: - stack.Push(stack.Get(arg)) - case OpLoadProperty, OpLoadPropertyOptional: prop := stack.Pop() val := stack.Pop() @@ -435,8 +434,8 @@ loop: case OpLoopPush: // pop the return value from the stack res := stack.Pop() - arr := stack.Peek() - arr.(*values.Array).Push(res) + arr := stack.Get(arg).(*values.Array) + arr.Push(res) case OpReturn: break loop