Compare commits

...

8 commits

30 changed files with 1954 additions and 965 deletions

View file

@ -2,4 +2,4 @@ module neemek.com/anglais/cli
go 1.25 go 1.25
require github.com/alecthomas/kong v1.12.1 require github.com/alecthomas/kong v1.15.0

View file

@ -4,7 +4,10 @@ github.com/alecthomas/kong v1.5.1 h1:9quB93P2aNGXf5C1kWNei85vjBgITNJQA4dSwJQGCOY
github.com/alecthomas/kong v1.5.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/kong v1.5.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0= github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.15.0 h1:BVJstKbpO73zKpmIu+m/aLRrNmWwxXPIGTNin9VmLVI=
github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=

View file

@ -3,13 +3,14 @@ package main
import ( import (
"bufio" "bufio"
"errors" "errors"
"github.com/alecthomas/kong"
"log" "log"
"neemek.com/anglais/core"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/alecthomas/kong"
"neemek.com/anglais/core"
) )
type Context struct { type Context struct {
@ -306,6 +307,8 @@ func (cmd *ReplCmd) Run(ctx *Context) error {
vm.SetChunk(c.Chunk) vm.SetChunk(c.Chunk)
for vm.Next() { for vm.Next() {
} }
println(vm.Stack.Pop().DebugString())
} }
} }

View file

@ -1,113 +1,73 @@
package core package core
import ( import (
"math/big"
"testing" "testing"
) )
type AllTestCase struct { type AllTestCase struct {
src string src string
expectedStack []Value expectedStack []Value
expectedScope []map[string]Value
} }
func GetAllTestCases() map[string]AllTestCase { func GetAllTestCases() map[string]AllTestCase {
return map[string]AllTestCase{ return map[string]AllTestCase{
"constant_number": { "constant_number": {
"a := 1", "a := 1",
[]Value{ []Value{&IntegerValue{new(big.Int).SetInt64(1)}},
&VariableValue{ []map[string]Value{
"a", {"a": &IntegerValue{new(big.Int).SetInt64(1)}},
&NumberValue{1},
0,
},
}, },
}, },
"func": { "func": {
"func sum(a: number, b: number) number {\n\treturn a + b\n}\n_ = sum(1, 2)", "fn sum(a: int, b: int) -> int {\n\treturn a + b\n}\nres := sum(1, 2)",
[]Value{ []Value{&IntegerValue{new(big.Int).SetInt64(3)}},
&VariableValue{ []map[string]Value{},
"sum",
&FunctionValue{
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: &Chunk{
Bytecode: []Bytecode{
InstructionDescend,
InstructionGetLocal, 0,
InstructionGetLocal, 1,
InstructionAdd,
InstructionReturn,
InstructionAscend,
},
Constants: []Value{&StringValue{"a"}, &StringValue{"b"}},
},
},
0,
},
},
}, },
"list": { "list": {
"a := [1, 2]", "a := [1.0, 2.0]\n{}",
[]Value{ []Value{&NilValue{}},
&VariableValue{ []map[string]Value{
"a", {"a": &ListValue{
&ListValue{ []Value{
[]Value{ &FloatValue{1},
&NumberValue{1}, &FloatValue{2},
&NumberValue{2},
},
}, },
0, }},
},
}, },
}, },
"constant_list_concat": { "constant_list_concat": {
"a := [1, 2] + [3]", "a := [1, 2] + [3]\n{}",
[]Value{ []Value{&NilValue{}},
&VariableValue{ []map[string]Value{
"a", {"a": &ListValue{
&ListValue{ []Value{
[]Value{ &IntegerValue{big.NewInt(1)},
&NumberValue{1}, &IntegerValue{big.NewInt(2)},
&NumberValue{2}, &IntegerValue{big.NewInt(3)},
&NumberValue{3},
},
}, },
0, }},
},
}, },
}, },
"list_concat": { "list_concat": {
"a := [1, 2]\nb := a + [3]", "a := [1.0, 2.0]\nb := a + [3.0]\nnil",
[]Value{ []Value{&NilValue{}},
&VariableValue{ []map[string]Value{
"a", {
&ListValue{ "a": &ListValue{
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
}, },
}, },
0, "b": &ListValue{
},
&VariableValue{
"b",
&ListValue{
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
&NumberValue{3}, &FloatValue{3},
}, },
}, },
0,
}, },
}, },
}, },
@ -159,7 +119,13 @@ func TestAll(t *testing.T) {
} }
t.Log("Comparing stacks") t.Log("Comparing stacks")
CompareStacks(t, tc.expectedStack, vm.stack)
CompareStacks(t, tc.expectedStack, vm.Stack)
// expected scope == nil => we don't care
if tc.expectedScope != nil {
CompareScope(t, tc.expectedScope, vm.scope)
}
}) })
} }
} }

View file

@ -2,6 +2,7 @@ package core
import ( import (
"fmt" "fmt"
"math/big"
"strings" "strings"
) )
@ -17,6 +18,9 @@ type Compiler struct {
source []rune source []rune
Warnings []CompilerError Warnings []CompilerError
// optimize Whether to attempt some optimization of the emitted bytecode
optimize bool
stack *Stack[LocalVariable] stack *Stack[LocalVariable]
} }
@ -128,6 +132,7 @@ func NewCompiler(source []rune) *Compiler {
nil, nil,
source, source,
[]CompilerError{}, []CompilerError{},
false,
NewStack[LocalVariable](256), NewStack[LocalVariable](256),
} }
@ -168,10 +173,14 @@ func (c *Compiler) Compile(p *Program) error {
} }
} }
for _, s := range p.Block.statements { for i, s := range p.Block.statements {
if err := c.compile(s); err != nil { if err := c.compile(s); err != nil {
return err return err
} }
if i != len(p.Block.statements)-1 {
c.add(InstructionPop)
}
} }
c.fileStack.Pop() c.fileStack.Pop()
@ -191,16 +200,20 @@ func (c *Compiler) compile(tree Node) error {
tree.(*StringNode).value, tree.(*StringNode).value,
}) })
case NumberNodeType: case FloatNodeType:
c.add(InstructionConstant) c.add(InstructionConstant)
c.addConstant(&NumberValue{tree.(*NumberNode).value}) c.addConstant(&FloatValue{tree.(*FloatNode).value})
case IntegerNodeType:
c.add(InstructionConstant)
c.addConstant(&IntegerValue{tree.(*IntegerNode).value})
case ListNodeType: case ListNodeType:
l := tree.(*ListNode) l := tree.(*ListNode)
if len(l.items) == 0 { if len(l.items) == 0 {
c.add(InstructionNewList) c.add(InstructionNewList)
} else if c.isTreeConstant(l) { } else if c.optimize && c.isTreeConstant(l) {
v, err := c.compute(l) v, err := c.compute(l)
if err != nil { if err != nil {
panic(err) // this shouldn't happen panic(err) // this shouldn't happen
@ -229,7 +242,7 @@ func (c *Compiler) compile(tree Node) error {
} }
case UnaryNodeType: case UnaryNodeType:
if c.isTreeConstant(tree.(*UnaryNode).value) { if c.optimize && c.isTreeConstant(tree.(*UnaryNode).value) {
v, err := c.compute(tree) v, err := c.compute(tree)
if err != nil { if err != nil {
return err return err
@ -243,9 +256,19 @@ func (c *Compiler) compile(tree Node) error {
return err return err
} }
vt, err := c.deduceSignature(tree.(*UnaryNode).value)
if err != nil {
return err
}
switch tree.(*UnaryNode).UnaryOperation { switch tree.(*UnaryNode).UnaryOperation {
case UnaryNegate: case UnaryNegate:
c.add(InstructionNegate) if vt.Type() == TypeInteger {
c.add(InstructionNegateInt)
} else {
c.add(InstructionNegateFloat)
}
case UnaryNot: case UnaryNot:
c.add(InstructionNot) c.add(InstructionNot)
} }
@ -262,12 +285,21 @@ func (c *Compiler) compile(tree Node) error {
c.add(InstructionNil) c.add(InstructionNil)
case BlockNodeType: case BlockNodeType:
if len(tree.(*BlockNode).statements) == 0 {
c.add(InstructionNil)
return nil
}
c.addDescend() c.addDescend()
for _, n := range tree.(*BlockNode).statements { for i, n := range tree.(*BlockNode).statements {
err := c.compile(n) err := c.compile(n)
if err != nil { if err != nil {
return err return err
} }
if i != len(tree.(*BlockNode).statements)-1 {
c.add(InstructionPop)
}
} }
c.addAscend() c.addAscend()
@ -283,7 +315,7 @@ func (c *Compiler) compile(tree Node) error {
return c.error(fmt.Sprintf("conditional requires boolean; cannot use non-boolean type %s", sig), n.condition) return c.error(fmt.Sprintf("conditional requires boolean; cannot use non-boolean type %s", sig), n.condition)
} }
if c.isTreeConstant(n.condition) { if c.optimize && c.isTreeConstant(n.condition) {
v, err := c.compute(n.condition) v, err := c.compute(n.condition)
if err != nil { if err != nil {
return err return err
@ -321,13 +353,10 @@ func (c *Compiler) compile(tree Node) error {
} }
// we store the position of the jump over the else code here // we store the position of the jump over the else code here
var jumpOverElse Pos // this would jump over the else/otherwise block in the code
if n.otherwise != nil { c.add(InstructionJump)
// this would jump over the else/otherwise block in the code jumpOverElse := c.ip
c.add(InstructionJump) c.advance(2)
jumpOverElse = c.ip
c.advance(2)
}
// put the u16 of where to jump if the condition was false // put the u16 of where to jump if the condition was false
c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2)) c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2))
@ -337,9 +366,12 @@ func (c *Compiler) compile(tree Node) error {
if err != nil { if err != nil {
return err return err
} }
c.putU16(jumpOverElse, uint16(c.ip-jumpOverElse-2)) } else {
c.add(InstructionNil)
} }
c.putU16(jumpOverElse, uint16(c.ip-jumpOverElse-2))
case LoopNodeType: case LoopNodeType:
n := tree.(*LoopNode) n := tree.(*LoopNode)
@ -353,7 +385,7 @@ func (c *Compiler) compile(tree Node) error {
} }
alwaysLoop := false alwaysLoop := false
if c.isTreeConstant(n.condition) { if c.optimize && c.isTreeConstant(n.condition) {
v, err := c.compute(n.condition) v, err := c.compute(n.condition)
if err != nil { if err != nil {
return err return err
@ -368,6 +400,8 @@ func (c *Compiler) compile(tree Node) error {
} }
} }
c.add(InstructionNil)
conditionPos := c.ip conditionPos := c.ip
jumpValuePos := Pos(0) jumpValuePos := Pos(0)
if !alwaysLoop { if !alwaysLoop {
@ -381,6 +415,8 @@ func (c *Compiler) compile(tree Node) error {
c.advance(2) c.advance(2)
} }
c.add(InstructionPop)
err = c.compile(n.do) err = c.compile(n.do)
if err != nil { if err != nil {
return err return err
@ -397,26 +433,47 @@ func (c *Compiler) compile(tree Node) error {
case AssignNodeType: case AssignNodeType:
n := tree.(*AssignNode) n := tree.(*AssignNode)
if n.name == "_" { switch n.dest.Type() {
// allow non-ish statements case ReferenceNodeType:
err := c.compile(n.value) d := n.dest.(*ReferenceNode)
if err != nil {
return err if d.name == "_" {
} return c.compile(n.value)
c.add(InstructionPop)
} else {
if n.declare && c.isVarDeclaredHere(n.name) {
return c.error(fmt.Sprintf("%s is already declared in this scope", n.name), n)
} }
err := c.addSetVar(n.name, n.value, n.declare) if n.declare && c.isVarDeclaredHere(d.name) {
if err != nil { return c.error(fmt.Sprintf("%s is already declared in this scope", d.name), n)
}
if err := c.addSetVar(d.name, n.value, n.declare); err != nil {
return err return err
} }
default:
return c.error(fmt.Sprintf("cannot assign to %s", n.dest.Type()), n.dest)
} }
case CallNodeType: /*
n := tree.(*CallNode) if n.name == "_" {
// allow non-ish statements
err := c.compile(n.value)
if err != nil {
return err
}
c.add(InstructionPop)
} else {
if n.declare && c.isVarDeclaredHere(n.name) {
return c.error(fmt.Sprintf("%s is already declared in this scope", n.name), n)
}
err := c.addSetVar(n.name, n.value, n.declare)
if err != nil {
return err
}
}
*/
case InvokeNodeType:
n := tree.(*InvokeNode)
s, err := c.deduceSignature(n.source) s, err := c.deduceSignature(n.source)
if err != nil { if err != nil {
@ -428,10 +485,6 @@ func (c *Compiler) compile(tree Node) error {
return c.error(fmt.Sprintf("cannot call non-function value of type %s", s), n) return c.error(fmt.Sprintf("cannot call non-function value of type %s", s), n)
} }
if !n.keep && f.Out.Type() != TypeNil {
c.warn(fmt.Sprintf("shouldn't void result of function call (output is non-nil %s)", f.Out), n)
}
if len(n.args) != len(f.In) { if len(n.args) != len(f.In) {
return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n) return c.error(fmt.Sprintf("wrong argument count: function of signature %s got %d, requires %d", f, len(n.args), len(f.In)), n)
} }
@ -463,7 +516,7 @@ func (c *Compiler) compile(tree Node) error {
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg) return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.In[i]), arg)
} }
if c.isTreeConstant(arg) { if c.optimize && c.isTreeConstant(arg) {
v, err := c.compute(arg) v, err := c.compute(arg)
if err != nil { if err != nil {
return err return err
@ -486,10 +539,6 @@ func (c *Compiler) compile(tree Node) error {
c.add(InstructionCall) c.add(InstructionCall)
if !n.keep {
c.add(InstructionPop)
}
case FunctionNodeType: case FunctionNodeType:
n := tree.(*FunctionNode) n := tree.(*FunctionNode)
@ -538,6 +587,7 @@ func (c *Compiler) compile(tree Node) error {
n.yield, n.yield,
c.Chunk, c.Chunk,
nil, nil,
nil,
} }
// restore old chunk and ip // restore old chunk and ip
@ -573,7 +623,7 @@ func (c *Compiler) compile(tree Node) error {
} }
func (c *Compiler) compileBinary(binary *BinaryNode) error { func (c *Compiler) compileBinary(binary *BinaryNode) error {
if c.isTreeConstant(binary) { if c.optimize && c.isTreeConstant(binary) {
v, err := c.compute(binary) v, err := c.compute(binary)
if err != nil { if err != nil {
return err return err
@ -593,38 +643,77 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
return err return err
} }
res, err := c.deduceSignature(binary)
if err != nil {
return err
}
inType, err := c.deduceSignature(binary.Left) // type(left) == type(right) because res is valid
if err != nil {
return err
}
switch binary.BinaryOperation { switch binary.BinaryOperation {
case BinaryAddition: case BinaryAddition:
res, err := c.deduceSignature(binary)
if err != nil {
return err
}
if res.Type() == TypeString { if res.Type() == TypeString {
c.add(InstructionStringConcatenation) c.add(InstructionStringConcatenation)
} else if res.Type() == TypeList { } else if res.Type() == TypeList {
c.add(InstructionConcatLists) c.add(InstructionConcatLists)
} else if res.Type() == TypeFloat {
c.add(InstructionAddFloat)
} else if res.Type() == TypeInteger {
c.add(InstructionAddInt)
} else { } else {
c.add(InstructionAdd) return c.error("unimplemented binary compilation", binary)
} }
case BinarySubtraction: case BinarySubtraction:
c.add(InstructionSub) if res.Type() == TypeFloat {
c.add(InstructionSubFloat)
} else {
c.add(InstructionSubInt)
}
case BinaryMultiplication: case BinaryMultiplication:
c.add(InstructionMul) if res.Type() == TypeFloat {
c.add(InstructionMulFloat)
} else {
c.add(InstructionMulInt)
}
case BinaryDivision: case BinaryDivision:
c.add(InstructionDiv) if res.Type() == TypeFloat {
c.add(InstructionDivFloat)
} else {
c.add(InstructionDivInt)
}
case BinaryEquality: case BinaryEquality:
c.add(InstructionEquals) c.add(InstructionEquals)
case BinaryInequality: case BinaryInequality:
c.add(InstructionNotEqual) c.add(InstructionNotEqual)
case BinaryLess: case BinaryLess:
c.add(InstructionLess) if inType.Type() == TypeFloat {
c.add(InstructionLessFloat)
} else {
c.add(InstructionLessInt)
}
case BinaryGreater: case BinaryGreater:
c.add(InstructionGreater) if inType.Type() == TypeFloat {
c.add(InstructionGreaterFloat)
} else {
c.add(InstructionGreaterInt)
}
case BinaryLessEqual: case BinaryLessEqual:
c.add(InstructionLessOrEqual) if inType.Type() == TypeFloat {
c.add(InstructionLessOrEqualFloat)
} else {
c.add(InstructionLessOrEqualInt)
}
case BinaryGreaterEqual: case BinaryGreaterEqual:
c.add(InstructionGreaterOrEqual) if inType.Type() == TypeFloat {
c.add(InstructionGreaterOrEqualFloat)
} else {
c.add(InstructionGreaterOrEqualInt)
}
case BinaryAnd: case BinaryAnd:
c.add(InstructionAnd) c.add(InstructionAnd)
case BinaryOr: case BinaryOr:
@ -638,8 +727,10 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch tree.Type() { switch tree.Type() {
case StringNodeType: case StringNodeType:
return &StringSignature{}, nil return &StringSignature{}, nil
case NumberNodeType: case FloatNodeType:
return &NumberSignature{}, nil return &FloatSignature{}, nil
case IntegerNodeType:
return &IntegerSignature{}, nil
case ReferenceNodeType: case ReferenceNodeType:
n := tree.(*ReferenceNode) n := tree.(*ReferenceNode)
sig, err := c.getVarSignature(n.name, n) sig, err := c.getVarSignature(n.name, n)
@ -695,17 +786,23 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch n.BinaryOperation { switch n.BinaryOperation {
case BinarySubtraction, BinaryMultiplication, BinaryDivision: case BinarySubtraction, BinaryMultiplication, BinaryDivision:
if l.Type() != TypeNumber { if l.Type() == TypeInteger {
return nil, c.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l), n) return &IntegerSignature{}, nil
} }
return &NumberSignature{}, nil if l.Type() == TypeFloat {
return &FloatSignature{}, nil
}
return nil, c.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l), n)
case BinaryAddition: case BinaryAddition:
switch l.Type() { switch l.Type() {
case TypeString: case TypeString:
return &StringSignature{}, nil return &StringSignature{}, nil
case TypeNumber: case TypeInteger:
return &NumberSignature{}, nil return &IntegerSignature{}, nil
case TypeFloat:
return &FloatSignature{}, nil
case TypeList: case TypeList:
return &ListSignature{ return &ListSignature{
l.(*ListSignature).Contents, l.(*ListSignature).Contents,
@ -722,7 +819,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
case BinaryEquality, BinaryInequality: case BinaryEquality, BinaryInequality:
return &BooleanSignature{}, nil return &BooleanSignature{}, nil
case BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual: case BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual:
if l.Type() != TypeNumber { if l.Type() != TypeInteger && l.Type() != TypeFloat {
return nil, c.error(fmt.Sprintf("cannot perform number comparison (%s) on non-number type %s", n.BinaryOperation, l), n) return nil, c.error(fmt.Sprintf("cannot perform number comparison (%s) on non-number type %s", n.BinaryOperation, l), n)
} }
@ -768,8 +865,8 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return nil, c.error(fmt.Sprintf("cannot access property from value of type %s", sig), n) return nil, c.error(fmt.Sprintf("cannot access property from value of type %s", sig), n)
} }
case CallNodeType: case InvokeNodeType:
n := tree.(*CallNode) n := tree.(*InvokeNode)
sig, err := c.deduceSignature(n.source) sig, err := c.deduceSignature(n.source)
if err != nil { if err != nil {
return nil, err return nil, err
@ -852,10 +949,13 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch n.UnaryOperation { switch n.UnaryOperation {
case UnaryNegate: case UnaryNegate:
if sig.Type() != TypeNumber { if sig.Type() == TypeFloat {
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be number)", n.UnaryOperation), n) return &FloatSignature{}, nil
} else if sig.Type() == TypeInteger {
return &IntegerSignature{}, nil
} }
return &NumberSignature{}, nil
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be number)", n.UnaryOperation), n)
case UnaryNot: case UnaryNot:
if sig.Type() != TypeBoolean { if sig.Type() != TypeBoolean {
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be boolean)", n.UnaryOperation), n) return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be boolean)", n.UnaryOperation), n)
@ -864,6 +964,15 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
} }
return nil, c.error(fmt.Sprintf("unimplemented result type deduction for unary %s", n.UnaryOperation), n) return nil, c.error(fmt.Sprintf("unimplemented result type deduction for unary %s", n.UnaryOperation), n)
case BlockNodeType:
n := tree.(*BlockNode)
if len(n.statements) == 0 {
return &NilSignature{}, nil
}
return c.deduceSignature(n.statements[len(n.statements)-1])
case AssignNodeType:
return c.deduceSignature(tree.(*AssignNode).value)
default: default:
return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree) return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree)
} }
@ -929,29 +1038,37 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
case AssignNodeType: case AssignNodeType:
n := tree.(*AssignNode) n := tree.(*AssignNode)
if !n.declare { switch n.dest.Type() {
prev, err := c.getVarSignature(n.name, n) case ReferenceNodeType:
if err != nil { name := n.dest.(*ReferenceNode).name
return err
if !n.declare {
prev, err := c.getVarSignature(name, n)
if err != nil {
return err
}
sig, err := c.deduceSignature(n.value)
if err != nil {
return err
}
if !sig.Matches(prev) {
return c.error(fmt.Sprintf("cannot assign value of type %s to variable %s of type %s", sig, name, prev), n.value)
}
return nil
} }
sig, err := c.deduceSignature(n.value) sig, err := c.deduceSignature(n.value)
if err != nil { if err != nil {
return err return err
} }
c.registerVar(name, sig)
if !sig.Matches(prev) { default:
return c.error(fmt.Sprintf("cannot assign value of type %s to variable %s of type %s", sig, n.name, prev), n.value) return c.error("can neither assign nor declare to", n.dest)
}
return nil
} }
sig, err := c.deduceSignature(n.value)
if err != nil {
return err
}
c.registerVar(n.name, sig)
default: default:
} }
@ -1040,7 +1157,7 @@ func (c *Compiler) isLocal(name string) bool {
// isTreeConstant check if a node tree is constant (predictable) // isTreeConstant check if a node tree is constant (predictable)
func (c *Compiler) isTreeConstant(tree Node) bool { func (c *Compiler) isTreeConstant(tree Node) bool {
switch tree.Type() { switch tree.Type() {
case StringNodeType, NumberNodeType, BooleanNodeType, NilNodeType: case StringNodeType, FloatNodeType, IntegerNodeType, BooleanNodeType, NilNodeType:
return true return true
case ListNodeType: case ListNodeType:
for _, item := range tree.(*ListNode).items { for _, item := range tree.(*ListNode).items {
@ -1054,13 +1171,13 @@ func (c *Compiler) isTreeConstant(tree Node) bool {
return c.isTreeConstant(tree.(*UnaryNode).value) return c.isTreeConstant(tree.(*UnaryNode).value)
case BinaryNodeType: case BinaryNodeType:
return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right) return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right)
case CallNodeType: case InvokeNodeType:
for _, arg := range tree.(*CallNode).args { for _, arg := range tree.(*InvokeNode).args {
if !c.isTreeConstant(arg) { if !c.isTreeConstant(arg) {
return false return false
} }
} }
return c.isTreeConstant(tree.(*CallNode).source) return c.isTreeConstant(tree.(*InvokeNode).source)
case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, FunctionNodeType, case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, FunctionNodeType,
ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType: ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType:
return false return false
@ -1076,8 +1193,13 @@ func (c *Compiler) compute(tree Node) (Value, error) {
n.value, n.value,
}, nil }, nil
case *NumberNode: case *FloatNode:
return &NumberValue{ return &FloatValue{
n.value,
}, nil
case *IntegerNode:
return &IntegerValue{
n.value, n.value,
}, nil }, nil
@ -1114,13 +1236,17 @@ func (c *Compiler) compute(tree Node) (Value, error) {
switch n.UnaryOperation { switch n.UnaryOperation {
case UnaryNegate: case UnaryNegate:
if v.Type() != NumberValueType { if v.Type() == FloatValueType {
return nil, c.error(fmt.Sprintf("cannot negate %s value (not a number)", v.Type()), n) return &FloatValue{
-v.(*FloatValue).Number,
}, nil
} else if v.Type() == IntegerValueType {
return &IntegerValue{
new(big.Int).Neg(v.(*IntegerValue).Number),
}, nil
} }
return &NumberValue{ return nil, c.error(fmt.Sprintf("cannot negate %s value (not a number)", v.Type()), n)
-v.(*NumberValue).Number,
}, nil
case UnaryNot: case UnaryNot:
if v.Type() != BoolValueType { if v.Type() != BoolValueType {
return nil, c.error(fmt.Sprintf("cannot invert %s value (not a boolean)", v.Type()), n) return nil, c.error(fmt.Sprintf("cannot invert %s value (not a boolean)", v.Type()), n)
@ -1133,7 +1259,7 @@ func (c *Compiler) compute(tree Node) (Value, error) {
return nil, c.error(fmt.Sprintf("unimplemented unary %s", v.Type()), n) return nil, c.error(fmt.Sprintf("unimplemented unary %s", v.Type()), n)
case *CallNode: case *InvokeNode:
source, err := c.compute(n.source) source, err := c.compute(n.source)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1180,7 +1306,7 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
// perform type check // perform type check
switch n.BinaryOperation { switch n.BinaryOperation {
case BinarySubtraction, BinaryMultiplication, BinaryDivision, BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual: case BinarySubtraction, BinaryMultiplication, BinaryDivision, BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual:
if l.Type() != NumberValueType { if l.Type() != FloatValueType && l.Type() != IntegerValueType {
return nil, c.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l.Type()), n) return nil, c.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l.Type()), n)
} }
case BinaryAnd, BinaryOr: case BinaryAnd, BinaryOr:
@ -1196,37 +1322,67 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
switch n.BinaryOperation { switch n.BinaryOperation {
case BinaryAddition: case BinaryAddition:
switch l.Type() { switch l.Type() {
case NumberValueType: case FloatValueType:
v = l.(*NumberValue).Number + r.(*NumberValue).Number v = l.(*FloatValue).Number + r.(*FloatValue).Number
case StringValueType: case StringValueType:
v = l.(*StringValue).Text + r.(*StringValue).Text v = l.(*StringValue).Text + r.(*StringValue).Text
case ListValueType: case ListValueType:
v = append(l.(*ListValue).Items, r.(*ListValue).Items...) v = append(l.(*ListValue).Items, r.(*ListValue).Items...)
case IntegerValueType:
v = new(big.Int).Add(l.(*IntegerValue).Number, r.(*IntegerValue).Number)
default: default:
return nil, c.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n) return nil, c.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n)
} }
case BinarySubtraction: case BinarySubtraction:
v = l.(*NumberValue).Number - r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number - r.(*FloatValue).Number
} else {
v = new(big.Int).Sub(l.(*IntegerValue).Number, r.(*IntegerValue).Number)
}
case BinaryMultiplication: case BinaryMultiplication:
v = l.(*NumberValue).Number * r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number * r.(*FloatValue).Number
} else {
v = new(big.Int).Mul(l.(*IntegerValue).Number, r.(*IntegerValue).Number)
}
case BinaryDivision: case BinaryDivision:
v = l.(*NumberValue).Number / r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number / r.(*FloatValue).Number
} else {
v = new(big.Int).Div(l.(*IntegerValue).Number, r.(*IntegerValue).Number)
}
case BinaryAnd: case BinaryAnd:
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean
case BinaryOr: case BinaryOr:
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean v = l.(*BoolValue).Boolean || r.(*BoolValue).Boolean
case BinaryEquality: case BinaryEquality:
v = l.Equals(r) v = l.Equals(r)
case BinaryInequality: case BinaryInequality:
v = !l.Equals(r) v = !l.Equals(r)
case BinaryLess: case BinaryLess:
v = l.(*NumberValue).Number < r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number < r.(*FloatValue).Number
} else {
v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) == -1
}
case BinaryGreater: case BinaryGreater:
v = l.(*NumberValue).Number > r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number > r.(*FloatValue).Number
} else {
v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) == 1
}
case BinaryLessEqual: case BinaryLessEqual:
v = l.(*NumberValue).Number <= r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number <= r.(*FloatValue).Number
} else {
v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) != 1
}
case BinaryGreaterEqual: case BinaryGreaterEqual:
v = l.(*NumberValue).Number >= r.(*NumberValue).Number if l.Type() == FloatValueType {
v = l.(*FloatValue).Number >= r.(*FloatValue).Number
} else {
v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) != 1
}
} }
return GoToValue(v), nil return GoToValue(v), nil

View file

@ -30,6 +30,7 @@ func BenchmarkNewCompiler(b *testing.B) {
type CompileTestData struct { type CompileTestData struct {
program *Program program *Program
expectedStack []Value expectedStack []Value
expectedScope []map[string]Value
} }
func GetCompileTestData() map[string]CompileTestData { func GetCompileTestData() map[string]CompileTestData {
@ -40,7 +41,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&StringNode{ &StringNode{
"Hello world!", "Hello world!",
"\"Hello world!\"", "\"Hello world!\"",
@ -54,22 +55,22 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
"", "",
}, },
[]Value{ []Value{&StringValue{"Hello world!"}},
&VariableValue{ []map[string]Value{
"a", {
&StringValue{"Hello world!"}, "a": &StringValue{"Hello world!"},
0,
}, },
}, },
}, },
/* these tests are so fucking unmaintainable
"conditional_false": { "conditional_false": {
&Program{ &Program{
[]Import{}, []Import{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -84,8 +85,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -106,7 +107,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&NumberValue{0}, &FloatValue{0},
0, 0,
}, },
}, },
@ -117,8 +118,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -133,8 +134,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -155,7 +156,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&NumberValue{1}, &FloatValue{1},
0, 0,
}, },
}, },
@ -166,8 +167,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -182,8 +183,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -196,8 +197,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -217,7 +218,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&NumberValue{2}, &FloatValue{2},
0, 0,
}, },
}, },
@ -228,8 +229,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -244,8 +245,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -258,8 +259,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -279,7 +280,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&NumberValue{1}, &FloatValue{1},
0, 0,
}, },
}, },
@ -290,14 +291,14 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -314,7 +315,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
&NumberValue{3}, &FloatValue{3},
0, 0,
}, },
}, },
@ -325,20 +326,20 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"sum", &ReferenceNode{name: "sum"},
&FunctionNode{ &FunctionNode{
"sum", "sum",
[]FunctionParameter{ []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
&NumberSignature{}, &FloatSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -378,20 +379,20 @@ func GetCompileTestData() map[string]CompileTestData {
[]FunctionParameter{ []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
&NumberSignature{}, &FloatSignature{},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionGetLocal, 1, InstructionGetLocal, 1,
InstructionAdd, InstructionAddFloat,
InstructionReturn, InstructionReturn,
InstructionAscend, InstructionAscend,
}, },
@ -411,16 +412,16 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"a", "a",
[]FunctionParameter{}, []FunctionParameter{},
&NumberSignature{}, &FloatSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -448,7 +449,6 @@ func GetCompileTestData() map[string]CompileTestData {
0, 0, 0, 0,
}, },
[]Node{}, []Node{},
false,
0, 0, 0, 0,
}, },
}, },
@ -462,7 +462,7 @@ func GetCompileTestData() map[string]CompileTestData {
&FunctionValue{ &FunctionValue{
"a", "a",
[]FunctionParameter{}, []FunctionParameter{},
&NumberSignature{}, &FloatSignature{},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,
@ -473,7 +473,7 @@ func GetCompileTestData() map[string]CompileTestData {
InstructionAscend, InstructionAscend,
}, },
[]Value{ []Value{
&NumberValue{1}, &StringValue{"b"}, &FloatValue{1}, &StringValue{"b"},
}, },
), ),
nil, nil,
@ -488,17 +488,17 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
statements: []Node{ statements: []Node{
&AssignNode{ &AssignNode{
name: "a", dest: &ReferenceNode{"a", 0, 0},
value: &ListNode{ value: &ListNode{
items: []Node{ items: []Node{
&NumberNode{value: 1}, &FloatNode{value: 1},
&NumberNode{value: 2}, &FloatNode{value: 2},
}, },
}, },
declare: true, declare: true,
}, },
&AssignNode{ &AssignNode{
name: "b", dest: &ReferenceNode{"b", 0, 0},
value: &ListNode{ value: &ListNode{
items: []Node{ items: []Node{
&StringNode{value: "Hello"}, &StringNode{value: "Hello"},
@ -516,8 +516,8 @@ func GetCompileTestData() map[string]CompileTestData {
name: "a", name: "a",
value: &ListValue{ value: &ListValue{
Items: []Value{ Items: []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
}, },
}, },
scope: 0, scope: 0,
@ -540,10 +540,10 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
name: "a", dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{ value: &UnaryNode{
UnaryNegate, UnaryNegate,
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -559,7 +559,7 @@ func GetCompileTestData() map[string]CompileTestData {
expectedStack: []Value{ expectedStack: []Value{
&VariableValue{ &VariableValue{
name: "a", name: "a",
value: &NumberValue{-1}, value: &FloatValue{-1},
scope: 0, scope: 0,
}, },
}, },
@ -570,7 +570,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
name: "a", dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{ value: &UnaryNode{
UnaryNot, UnaryNot,
&BooleanNode{ &BooleanNode{
@ -593,6 +593,7 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
}, },
*/
} }
} }
@ -641,7 +642,8 @@ func TestCompile(t *testing.T) {
} }
t.Log("Executed bytecode") t.Log("Executed bytecode")
CompareStacks(t, testCase.expectedStack, vm.stack) CompareStacks(t, testCase.expectedStack, vm.Stack)
CompareScope(t, testCase.expectedScope, vm.scope)
}) })
} }
} }
@ -691,8 +693,8 @@ func TestCompiler_CleanStack(t *testing.T) {
} }
// make sure stack has only assigned values // make sure stack has only assigned values
for i := 0; i < int(vm.stack.Current); i++ { for i := 1; i < int(vm.Stack.Current); i++ {
v := vm.stack.items[i] v := vm.Stack.items[i]
if v == nil || v.Type() != VariableValueType { if v == nil || v.Type() != VariableValueType {
t.Errorf("Unclean stack! value %v at %d on the stack is intermediary", v.String(), i) t.Errorf("Unclean stack! value %v at %d on the stack is intermediary", v.String(), i)

View file

@ -9,13 +9,13 @@ import (
type Token struct { type Token struct {
Type TokenType Type TokenType
Start Pos Start Pos
Length Pos End Pos
Line Pos Line Pos
Lexeme string Lexeme string
} }
func (t Token) String() string { func (t Token) String() string {
return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Type.String(), t.Lexeme, t.Start, t.Length, t.Line) return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Type.String(), t.Lexeme, t.Start, t.End, t.Line)
} }
type TokenType uint64 type TokenType uint64
@ -28,7 +28,8 @@ const (
TokenBang TokenBang
TokenSemicolon TokenSemicolon
TokenNumber TokenInteger
TokenFloat
TokenHexadecimal TokenHexadecimal
TokenString TokenString
TokenName TokenName
@ -55,6 +56,7 @@ const (
TokenComma TokenComma
TokenDot TokenDot
TokenColon TokenColon
TokenArrow
TokenAssign TokenAssign
TokenDeclare TokenDeclare
@ -69,6 +71,7 @@ const (
TokenPipe TokenPipe
TokenDoublePipe TokenDoublePipe
TokenNewLine
TokenBreakpoint TokenBreakpoint
TokenEOF TokenEOF
TokenError TokenError
@ -86,8 +89,10 @@ func (t TokenType) String() string {
return "slash" return "slash"
case TokenBang: case TokenBang:
return "bang" return "bang"
case TokenNumber: case TokenFloat:
return "number" return "float"
case TokenInteger:
return "integer"
case TokenString: case TokenString:
return "string" return "string"
case TokenTrue: case TokenTrue:
@ -135,7 +140,7 @@ func (t TokenType) String() string {
case TokenDeclare: case TokenDeclare:
return "declare" return "declare"
case TokenFunc: case TokenFunc:
return "func" return "fn"
case TokenReturn: case TokenReturn:
return "return" return "return"
case TokenWhile: case TokenWhile:
@ -162,6 +167,10 @@ func (t TokenType) String() string {
return "pipe" return "pipe"
case TokenHexadecimal: case TokenHexadecimal:
return "hexadecimal" return "hexadecimal"
case TokenArrow:
return "arrow"
case TokenNewLine:
return "newline"
} }
panic("UNDEFINED TOKENTYPE STRING CONVERSION") panic("UNDEFINED TOKENTYPE STRING CONVERSION")
@ -206,9 +215,14 @@ func (l *Lexer) NextToken() (Token, error) {
l.advance() l.advance()
switch c { switch c {
case '\n':
return l.makeToken(TokenNewLine), nil
case '+': case '+':
return l.makeToken(TokenPlus), nil return l.makeToken(TokenPlus), nil
case '-': case '-':
if l.accept('>') {
return l.makeToken(TokenArrow), nil
}
return l.makeToken(TokenMinus), nil return l.makeToken(TokenMinus), nil
case '*': case '*':
return l.makeToken(TokenStar), nil return l.makeToken(TokenStar), nil
@ -339,7 +353,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenElse), nil return l.makeToken(TokenElse), nil
case "var": case "var":
return l.makeToken(TokenVar), nil return l.makeToken(TokenVar), nil
case "func": case "fn":
return l.makeToken(TokenFunc), nil return l.makeToken(TokenFunc), nil
case "while": case "while":
return l.makeToken(TokenWhile), nil return l.makeToken(TokenWhile), nil
@ -364,7 +378,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenHexadecimal), nil return l.makeToken(TokenHexadecimal), nil
} }
return l.makeToken(TokenNumber), nil return l.makeToken(TokenInteger), nil
} else if unicode.IsDigit(c) { } else if unicode.IsDigit(c) {
for unicode.IsDigit(l.peek()) { for unicode.IsDigit(l.peek()) {
l.advance() l.advance()
@ -375,20 +389,22 @@ func (l *Lexer) NextToken() (Token, error) {
for unicode.IsDigit(l.peek()) { for unicode.IsDigit(l.peek()) {
l.advance() l.advance()
} }
return l.makeToken(TokenFloat), nil
} }
return l.makeToken(TokenNumber), nil return l.makeToken(TokenInteger), nil
} }
return l.makeToken(TokenError), errors.New(fmt.Sprintf("invalid token %c", c)) return l.makeToken(TokenError), errors.New(fmt.Sprintf("invalid token %c", c))
} }
} }
func NewToken(t TokenType, start Pos, length Pos, line Pos, lexeme string) Token { func NewToken(t TokenType, start Pos, end Pos, line Pos, lexeme string) Token {
return Token{ return Token{
Type: t, Type: t,
Start: start, Start: start,
Length: length, End: end,
Line: line, Line: line,
Lexeme: lexeme, Lexeme: lexeme,
} }
@ -410,7 +426,7 @@ func (l *Lexer) Tokenize() ([]Token, error) {
} }
func (l *Lexer) makeToken(t TokenType) Token { func (l *Lexer) makeToken(t TokenType) Token {
return NewToken(t, l.start, l.current-l.start, l.line, string(l.src[l.start:l.current])) return NewToken(t, l.start, l.current, l.line, string(l.src[l.start:l.current]))
} }
func (l *Lexer) peek() rune { func (l *Lexer) peek() rune {
@ -459,7 +475,7 @@ func (l *Lexer) isAtEnd() bool {
} }
func (l *Lexer) skipWhitespace() { func (l *Lexer) skipWhitespace() {
for !l.isAtEnd() && unicode.IsSpace(l.peek()) { for !l.isAtEnd() && unicode.IsSpace(l.peek()) && l.peek() != '\n' {
l.advance() l.advance()
} }
} }

View file

@ -21,33 +21,33 @@ func GetLexerTestData() map[string]LexerTestData {
}, },
"simple number(1)": { "simple number(1)": {
"1024", "1024",
[]TokenType{TokenNumber, TokenEOF}, []TokenType{TokenInteger, TokenEOF},
}, },
"simple_arithmetics(7)": { "simple_arithmetics(7)": {
"1 + 23 / 4 * 3", "1 + 23 / 4 * 3",
[]TokenType{ []TokenType{
TokenNumber, TokenPlus, TokenNumber, TokenSlash, TokenInteger, TokenPlus, TokenInteger, TokenSlash,
TokenNumber, TokenStar, TokenNumber, TokenEOF, TokenInteger, TokenStar, TokenInteger, TokenEOF,
}, },
}, },
"condition(3)": { "condition(3)": {
"a <= 200", "a <= 200",
[]TokenType{TokenName, TokenLessThanOrEqual, TokenNumber, TokenEOF}, []TokenType{TokenName, TokenLessThanOrEqual, TokenInteger, TokenEOF},
}, },
"if_statement(10)": { "if_statement(10)": {
"if a >= 200 {\n write(\"Hello world!\")\n}", "if a >= 200 {\n write(\"Hello world!\")\n}",
[]TokenType{ []TokenType{
TokenIf, TokenName, TokenGreaterThanOrEqual, TokenNumber, TokenOpenBrace, TokenIf, TokenName, TokenGreaterThanOrEqual, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine,
TokenEOF, TokenCloseBrace, TokenEOF,
}, },
}, },
"if_else_statement(20)": { "if_else_statement(20)": {
"if 23 * 2/3 > 32 {\n write(\"It is larger!\")\n} else {\n write(\"It is lower!\")\n}", "if 23 * 2/3 > 32 {\n write(\"It is larger!\")\n} else {\n write(\"It is lower!\")\n}",
[]TokenType{ []TokenType{
TokenIf, TokenNumber, TokenStar, TokenNumber, TokenSlash, TokenNumber, TokenGreaterThan, TokenNumber, TokenOpenBrace, TokenIf, TokenInteger, TokenStar, TokenInteger, TokenSlash, TokenInteger, TokenGreaterThan, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine, TokenCloseBrace,
TokenElse, TokenOpenBrace, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace, TokenElse, TokenOpenBrace, TokenNewLine, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine, TokenCloseBrace,
TokenEOF, TokenEOF,
}, },
}, },
@ -58,8 +58,8 @@ func GetLexerTestData() map[string]LexerTestData {
"full_arithmetic_equality": { "full_arithmetic_equality": {
"a + 2 == 10 * 2 / 3", "a + 2 == 10 * 2 / 3",
[]TokenType{ []TokenType{
TokenName, TokenPlus, TokenNumber, TokenEquals, TokenName, TokenPlus, TokenInteger, TokenEquals,
TokenNumber, TokenStar, TokenNumber, TokenSlash, TokenNumber, TokenInteger, TokenStar, TokenInteger, TokenSlash, TokenInteger,
TokenEOF, TokenEOF,
}, },
}, },
@ -77,7 +77,7 @@ func GetLexerTestData() map[string]LexerTestData {
}, },
"space_before_string": { "space_before_string": {
"\n \"\"", "\n \"\"",
[]TokenType{TokenString, TokenEOF}, []TokenType{TokenNewLine, TokenString, TokenEOF},
}, },
"write_call": { "write_call": {
"write(\"Hello world\")", "write(\"Hello world\")",
@ -86,46 +86,46 @@ func GetLexerTestData() map[string]LexerTestData {
"complex_comparison": { "complex_comparison": {
"!(h__elo123 >= 1)", "!(h__elo123 >= 1)",
[]TokenType{ []TokenType{
TokenBang, TokenOpenParenthesis, TokenName, TokenGreaterThanOrEqual, TokenNumber, TokenCloseParenthesis, TokenBang, TokenOpenParenthesis, TokenName, TokenGreaterThanOrEqual, TokenInteger, TokenCloseParenthesis,
TokenEOF, TokenEOF,
}, },
}, },
"3assignments_1condition": { "3assignments_1condition": {
"a = 8 * 32\nb = a > 256\nc = a <= 256\n!b == c", "a = 8 * 32\nb = a > 256\nc = a <= 256\n!b == c",
[]TokenType{ []TokenType{
TokenName, TokenAssign, TokenNumber, TokenStar, TokenNumber, TokenName, TokenAssign, TokenInteger, TokenStar, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenNumber, TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenNumber, TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenInteger, TokenNewLine,
TokenBang, TokenName, TokenEquals, TokenName, TokenEOF, TokenBang, TokenName, TokenEquals, TokenName, TokenEOF,
}, },
}, },
"func": { "function": {
"func sum(a, b) {\n return a + b\n}", "fn sum(a, b) {\n return a + b\n}",
[]TokenType{ []TokenType{
TokenFunc, TokenName, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis, TokenFunc, TokenName, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis,
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace, TokenOpenBrace, TokenNewLine, TokenReturn, TokenName, TokenPlus, TokenName, TokenNewLine, TokenCloseBrace,
}, },
}, },
"while_loop": { "while_loop": {
"while a < 5 {\n a = a + 1\n}", "while a < 5 {\n a = a + 1\n}",
[]TokenType{ []TokenType{
TokenWhile, TokenName, TokenLessThan, TokenNumber, TokenOpenBrace, TokenWhile, TokenName, TokenLessThan, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenPlus, TokenNumber, TokenCloseBrace, TokenName, TokenAssign, TokenName, TokenPlus, TokenInteger, TokenNewLine, TokenCloseBrace, TokenEOF,
}, },
}, },
"lambda": { "lambda": {
"sum := func(a, b) {\n" + "sum := fn(a, b) {\n" +
" return a + b\n" + " return a + b\n" +
"}", "}",
[]TokenType{ []TokenType{
TokenName, TokenDeclare, TokenFunc, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis, TokenName, TokenDeclare, TokenFunc, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis,
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace, TokenOpenBrace, TokenNewLine, TokenReturn, TokenName, TokenPlus, TokenName, TokenNewLine, TokenCloseBrace,
}, },
}, },
"list": { "list": {
"data := [3, 1, 4, 1]", "data := [3, 1, 4, 1]",
[]TokenType{ []TokenType{
TokenName, TokenDeclare, TokenOpenBracket, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenComma, TokenNumber, TokenCloseBracket, TokenName, TokenDeclare, TokenOpenBracket, TokenInteger, TokenComma, TokenInteger, TokenComma, TokenInteger, TokenComma, TokenInteger, TokenCloseBracket,
}, },
}, },
} }

View file

@ -2,6 +2,7 @@ package core
import ( import (
"fmt" "fmt"
"math/big"
"strconv" "strconv"
"strings" "strings"
) )
@ -21,17 +22,20 @@ type Boundary interface {
const ( const (
StringNodeType NodeType = iota StringNodeType NodeType = iota
NumberNodeType FloatNodeType
IntegerNodeType
ReferenceNodeType ReferenceNodeType
BooleanNodeType BooleanNodeType
NilNodeType NilNodeType
ListNodeType ListNodeType
TupleNodeType
BinaryNodeType BinaryNodeType
UnaryNodeType UnaryNodeType
BlockNodeType BlockNodeType
ConditionalNodeType ConditionalNodeType
LoopNodeType LoopNodeType
AssignNodeType AssignNodeType
InvokeNodeType
CallNodeType CallNodeType
FunctionNodeType FunctionNodeType
ReturnNodeType ReturnNodeType
@ -43,8 +47,10 @@ func (n NodeType) String() string {
switch n { switch n {
case StringNodeType: case StringNodeType:
return "String" return "String"
case NumberNodeType: case FloatNodeType:
return "Number" return "Float"
case IntegerNodeType:
return "Integer"
case ReferenceNodeType: case ReferenceNodeType:
return "Reference" return "Reference"
case BinaryNodeType: case BinaryNodeType:
@ -61,7 +67,7 @@ func (n NodeType) String() string {
return "Loop" return "Loop"
case AssignNodeType: case AssignNodeType:
return "Assign" return "Assign"
case CallNodeType: case InvokeNodeType:
return "Call" return "Call"
case FunctionNodeType: case FunctionNodeType:
return "Function" return "Function"
@ -69,12 +75,16 @@ func (n NodeType) String() string {
return "Return" return "Return"
case ListNodeType: case ListNodeType:
return "List" return "List"
case TupleNodeType:
return "Tuple"
case AccessNodeType: case AccessNodeType:
return "Access" return "Access"
case BreakpointNodeType: case BreakpointNodeType:
return "Breakpoint" return "Breakpoint"
case UnaryNodeType: case UnaryNodeType:
return "Unary" return "Unary"
case CallNodeType:
return "Call"
} }
return "Invalid Node Type" return "Invalid Node Type"
} }
@ -120,22 +130,41 @@ func (n StringNode) Bounds() (Pos, Pos) {
return n.start, n.end return n.start, n.end
} }
type NumberNode struct { type FloatNode struct {
value float64 value float64
start Pos start Pos
end Pos end Pos
} }
func (n NumberNode) Type() NodeType { func (n FloatNode) Type() NodeType {
return NumberNodeType return FloatNodeType
} }
func (n NumberNode) String() string { func (n FloatNode) String() string {
return strconv.FormatFloat(n.value, 'g', -1, NumberSize) return strconv.FormatFloat(n.value, 'g', -1, FloatSize)
} }
func (n NumberNode) Bounds() (Pos, Pos) { func (n FloatNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type IntegerNode struct {
value *big.Int
start Pos
end Pos
}
func (n IntegerNode) Type() NodeType {
return IntegerNodeType
}
func (n IntegerNode) String() string {
return n.value.String()
}
func (n IntegerNode) Bounds() (Pos, Pos) {
return n.start, n.end return n.start, n.end
} }
@ -169,6 +198,37 @@ func (n ListNode) Bounds() (Pos, Pos) {
return n.start, n.end return n.start, n.end
} }
type TupleNode struct {
items []Node
start Pos
end Pos
}
func (n TupleNode) Type() NodeType {
return TupleNodeType
}
func (n TupleNode) String() string {
sb := strings.Builder{}
sb.WriteString("(")
for i, item := range n.items {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(item.String())
}
sb.WriteString(")")
return sb.String()
}
func (n TupleNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type AccessNode struct { type AccessNode struct {
source Node source Node
property string property string
@ -258,14 +318,14 @@ func (n BinaryOperation) Symbol() string {
return "<" return "<"
case BinaryGreater: case BinaryGreater:
return ">" return ">"
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
case BinaryLessEqual: case BinaryLessEqual:
return "<=" return "<="
case BinaryGreaterEqual: case BinaryGreaterEqual:
return ">=" return ">="
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
} }
panic("unsupported binary operation to symbol conversion for " + n.String()) panic("unsupported binary operation to symbol conversion for " + n.String())
@ -456,7 +516,7 @@ func (n LoopNode) Bounds() (Pos, Pos) {
// AssignNode assignment // AssignNode assignment
type AssignNode struct { type AssignNode struct {
name string dest Node
value Node value Node
declare bool declare bool
@ -469,18 +529,39 @@ func (n AssignNode) Type() NodeType {
} }
func (n AssignNode) String() string { func (n AssignNode) String() string {
return fmt.Sprintf("set %s to %s", n.name, n.value) return fmt.Sprintf("set %s to %s", n.dest, n.value)
} }
func (n AssignNode) Bounds() (Pos, Pos) { func (n AssignNode) Bounds() (Pos, Pos) {
return n.start, n.end return n.start, n.end
} }
// CallNode function call // InvokeNode function call
type CallNode struct { type InvokeNode struct {
source Node source Node
args []Node args []Node
keep bool
start Pos
end Pos
}
func (n InvokeNode) Type() NodeType {
return InvokeNodeType
}
func (n InvokeNode) String() string {
return fmt.Sprintf("invoke %s with args (%s)", n.source.String(), n.args)
}
func (n InvokeNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// CallNode call a function of a value
type CallNode struct {
source Node
name Token
args []Node
start Pos start Pos
end Pos end Pos
@ -491,7 +572,7 @@ func (n CallNode) Type() NodeType {
} }
func (n CallNode) String() string { func (n CallNode) String() string {
return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args) return fmt.Sprintf("call %s on %s with args (%s)", n.name, n.source.String(), n.args)
} }
func (n CallNode) Bounds() (Pos, Pos) { func (n CallNode) Bounds() (Pos, Pos) {

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"math/big"
"strconv" "strconv"
"strings" "strings"
) )
@ -61,7 +62,7 @@ func (p ParsingError) Format() string {
b.WriteRune(' ') b.WriteRune(' ')
} }
for i := 0; i < int(p.Causer.Length); i++ { for i := 0; i < len(p.Causer.Lexeme); i++ {
b.WriteRune('^') b.WriteRune('^')
} }
b.WriteRune('\n') b.WriteRune('\n')
@ -75,12 +76,13 @@ func (p ParsingError) Format() string {
} }
type Parser struct { type Parser struct {
source string source string
trace []string trace []string
tokens []Token tokens []Token
prev *Token prev *Token
curr *Token curr *Token
pos Pos pos Pos
ignoreNewLine bool
} }
func NewParser(source string, trace []string, tokens []Token) *Parser { func NewParser(source string, trace []string, tokens []Token) *Parser {
@ -142,12 +144,19 @@ func (p *Parser) Parse(path string) (*Program, error) {
imports = append(imports, Import{ imports = append(imports, Import{
p.prev.Lexeme[1 : len(p.prev.Lexeme)-1], p.prev.Lexeme[1 : len(p.prev.Lexeme)-1],
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}) })
continue continue
} }
b, err := p.block(true) for p.accept(TokenNewLine) {
}
if p.curr.Type == TokenEOF {
break
}
b, err := p.expression(false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -163,7 +172,7 @@ func (p *Parser) Parse(path string) (*Program, error) {
&BlockNode{ &BlockNode{
statements, statements,
0, 0,
p.curr.Start + p.curr.Length, p.curr.End,
}, },
path, path,
}, nil }, nil
@ -175,6 +184,12 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false return false
} }
if p.ignoreNewLine && tokenType != TokenNewLine {
for p.curr.Type == TokenNewLine {
p.advance()
}
}
if (*p.curr).Type == tokenType { if (*p.curr).Type == tokenType {
p.advance() p.advance()
return true return true
@ -203,10 +218,11 @@ func (p *Parser) advance() {
if p.pos < Pos(len(p.tokens)) { if p.pos < Pos(len(p.tokens)) {
p.curr = &p.tokens[p.pos] p.curr = &p.tokens[p.pos]
p.pos++
} else { } else {
panic("no more tokens") p.curr = nil
} }
p.pos++
} }
func (p *Parser) error(error string, causer *Token) error { func (p *Parser) error(error string, causer *Token) error {
@ -218,6 +234,370 @@ func (p *Parser) error(error string, causer *Token) error {
} }
} }
func (p *Parser) expression(mustBeBlock bool) (Node, error) {
if mustBeBlock || p.accept(TokenOpenBrace) {
if mustBeBlock {
if err := p.expect(TokenOpenBrace, "expected block"); err != nil {
return nil, err
}
}
oldIgnoreNewline := p.ignoreNewLine
p.ignoreNewLine = false
start := p.prev.Start
var statements []Node
for !p.accept(TokenCloseBrace) {
if p.accept(TokenNewLine) {
continue
}
s, err := p.expression(false)
if err != nil {
return nil, err
}
statements = append(statements, s)
if !p.accept(TokenNewLine) {
if err := p.expect(TokenCloseBrace, "blocks must be closed"); err != nil {
return nil, err
}
break
}
}
p.ignoreNewLine = oldIgnoreNewline
return &BlockNode{statements, start, p.prev.End}, nil
}
t := p.curr
switch t.Type {
case TokenIf:
p.advance()
cond, err := p.expression(false)
if err != nil {
return nil, err
}
do, err := p.expression(true)
if err != nil {
return nil, err
}
var otherwise Node
if p.accept(TokenElse) {
otherwise, err = p.expression(p.curr.Type != TokenIf)
if err != nil {
return nil, err
}
}
return &ConditionalNode{
cond,
do,
otherwise,
t.Start,
t.End,
}, nil
case TokenFunc:
p.advance()
start := p.prev.Start
var name *Token
if p.accept(TokenName) { // can be unnamed, but accept name if it is named
name = p.prev
}
params, err := p.parseParams()
if err != nil {
return nil, err
}
var yield TypeSignature
if p.accept(TokenArrow) {
yield, err = p.parseSignature()
if err != nil {
return nil, err
}
}
logic, err := p.expression(true)
if err != nil {
return nil, err
}
names := "*"
if name != nil {
names = name.Lexeme
}
fn := &FunctionNode{
names,
params,
yield,
logic,
start,
p.prev.End,
}
if name != nil {
return &AssignNode{
&ReferenceNode{name.Lexeme, name.Start, name.End},
fn,
true,
start,
p.prev.End,
}, nil
}
return fn, nil
case TokenReturn:
p.advance()
start := p.prev.Start
v, err := p.expression(false)
if err != nil {
return nil, err
}
return &ReturnNode{
v,
start,
p.prev.End,
}, nil
case TokenWhile:
p.advance()
start := p.prev.Start
cond, err := p.expression(false)
if err != nil {
return nil, err
}
logic, err := p.expression(true)
if err != nil {
return nil, err
}
return &LoopNode{
cond,
logic,
start,
p.prev.End,
}, nil
default:
s, err := p.binary()
if err != nil {
return nil, err
}
if p.accept(TokenDeclare) || p.accept(TokenAssign) {
isDeclaration := p.prev.Type == TokenDeclare
// possibly assign tuples; not implemented yet
v, err := p.expression(false)
if err != nil {
return nil, err
}
start, _ := s.Bounds()
_, end := v.Bounds()
return &AssignNode{
s,
v,
isDeclaration,
start,
end,
}, nil
}
return s, nil
}
}
func isBinaryOperator(tokenType TokenType) bool {
switch tokenType {
case TokenPlus, TokenMinus, TokenStar, TokenSlash, TokenPipe, TokenDoubleAmpersand, TokenDoublePipe, TokenEquals, TokenBangEquals, TokenLessThan, TokenLessThanOrEqual, TokenGreaterThan, TokenGreaterThanOrEqual:
return true
default:
return false
}
}
func binaryPrecedence(op TokenType) int {
switch op {
case TokenDoubleAmpersand, TokenDoublePipe:
return 1
case TokenEquals, TokenBangEquals, TokenLessThan, TokenGreaterThan, TokenLessThanOrEqual, TokenGreaterThanOrEqual:
return 2
case TokenPlus, TokenMinus, TokenPipe:
return 3
case TokenStar, TokenSlash:
return 5
default:
panic("unimplemented")
}
}
func tokenToBinaryOperation(tokenType TokenType) BinaryOperation {
switch tokenType {
case TokenPlus:
return BinaryAddition
case TokenMinus:
return BinarySubtraction
case TokenStar:
return BinaryMultiplication
case TokenSlash:
return BinaryDivision
case TokenPipe:
panic("unimplemented bitwise ops")
case TokenDoubleAmpersand:
return BinaryAnd
case TokenDoublePipe:
return BinaryOr
case TokenEquals:
return BinaryEquality
case TokenBangEquals:
return BinaryInequality
case TokenLessThan:
return BinaryLess
case TokenLessThanOrEqual:
return BinaryLessEqual
case TokenGreaterThan:
return BinaryGreater
case TokenGreaterThanOrEqual:
return BinaryGreaterEqual
default:
panic("unimplemented")
}
}
func (p *Parser) binary() (Node, error) {
t, err := p.chain()
if err != nil {
return nil, err
}
ops := NewStack[*Token](128)
values := NewStack[Node](256)
values.pushItem(t)
for isBinaryOperator(p.curr.Type) {
for ops.Current > 0 && binaryPrecedence(p.curr.Type) <= binaryPrecedence(ops.Peek().Type) {
r := values.Pop()
l := values.Pop()
op := tokenToBinaryOperation(ops.Pop().Type)
start, _ := l.Bounds()
_, end := r.Bounds()
values.Push(&BinaryNode{
op,
l,
r,
start,
end,
})
}
ops.Push(p.curr)
p.advance()
v, err := p.chain()
if err != nil {
return nil, err
}
values.Push(v)
}
for ops.Current > 0 {
r := values.Pop()
l := values.Pop()
op := tokenToBinaryOperation(ops.Pop().Type)
start, _ := l.Bounds()
_, end := l.Bounds()
values.Push(&BinaryNode{
op,
l,
r,
start,
end,
})
}
return values.Pop(), nil
}
func (p *Parser) chain() (Node, error) {
f, err := p.factor()
if err != nil {
return nil, err
}
for {
if p.accept(TokenDot) {
if err = p.expect(TokenName, "can only access properties by name"); err != nil {
return nil, err
}
name := p.prev
f = &AccessNode{
f,
p.prev.Lexeme,
name.Start,
name.End,
}
if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs()
if err != nil {
return nil, err
}
f = &InvokeNode{
f,
args,
name.Start,
p.prev.End,
}
}
} else if p.curr.Type == TokenOpenParenthesis {
start := p.curr.Start
args, err := p.parseArgs()
if err != nil {
return nil, err
}
f = &InvokeNode{
f,
args,
start,
p.prev.End,
}
} else {
break
}
}
return f, nil
}
func (p *Parser) factor() (Node, error) { func (p *Parser) factor() (Node, error) {
switch (*p.curr).Type { switch (*p.curr).Type {
case TokenString: case TokenString:
@ -226,35 +606,49 @@ func (p *Parser) factor() (Node, error) {
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1], (*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme, (*p.prev).Lexeme,
p.prev.Start, p.prev.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenNumber: case TokenInteger:
p.advance() p.advance()
num, err := strconv.ParseFloat((*p.prev).Lexeme, NumberSize)
num, success := new(big.Int).SetString(p.prev.Lexeme, 10)
if !success {
return nil, p.error(fmt.Sprintf("cannot parse integer base 10: %s", p.prev.Lexeme), p.prev)
}
return &IntegerNode{
num,
p.prev.Start,
p.prev.End,
}, nil
case TokenFloat:
p.advance()
num, err := strconv.ParseFloat((*p.prev).Lexeme, FloatSize)
if err != nil { if err != nil {
return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev) return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev)
} }
return &NumberNode{ return &FloatNode{
num, num,
p.prev.Start, p.prev.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenHexadecimal: case TokenHexadecimal:
p.advance() p.advance()
start := (*p.prev).Start start := (*p.prev).Start
num, err := strconv.ParseUint((*p.prev).Lexeme[2:], 16, NumberSize) num, ok := new(big.Int).SetString(p.prev.Lexeme[2:], 16)
if err != nil { if !ok {
return nil, err return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.prev.Lexeme), p.prev)
} }
return &NumberNode{ return &IntegerNode{
float64(num), num,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenTrue: case TokenTrue:
@ -262,14 +656,14 @@ func (p *Parser) factor() (Node, error) {
return &BooleanNode{ return &BooleanNode{
true, true,
p.prev.Start, p.prev.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenFalse: case TokenFalse:
p.advance() p.advance()
return &BooleanNode{ return &BooleanNode{
false, false,
p.prev.Start, p.prev.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenNil: case TokenNil:
@ -280,6 +674,8 @@ func (p *Parser) factor() (Node, error) {
p.advance() p.advance()
start := p.prev.Start start := p.prev.Start
// TODO: find better solution; current one is messy
// Maybe perform better analysis to determine the kind of the list...
if p.accept(TokenCloseBracket) { if p.accept(TokenCloseBracket) {
s, err := p.parseSignature() s, err := p.parseSignature()
if err != nil { if err != nil {
@ -290,10 +686,13 @@ func (p *Parser) factor() (Node, error) {
[]Node{}, []Node{},
s, s,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
oldIgnoreNewline := p.ignoreNewLine
p.ignoreNewLine = true
var values []Node var values []Node
for !p.accept(TokenCloseBracket) { for !p.accept(TokenCloseBracket) {
if len(values) > 0 { if len(values) > 0 {
@ -310,11 +709,13 @@ func (p *Parser) factor() (Node, error) {
values = append(values, value) values = append(values, value)
} }
p.ignoreNewLine = oldIgnoreNewline
return &ListNode{ return &ListNode{
values, values,
nil, nil,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
// unary minus // unary minus
@ -330,7 +731,7 @@ func (p *Parser) factor() (Node, error) {
UnaryNegate, UnaryNegate,
f, f,
first.Start, first.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenBang: case TokenBang:
@ -346,14 +747,14 @@ func (p *Parser) factor() (Node, error) {
UnaryNot, UnaryNot,
v, v,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenName: case TokenName:
p.advance() p.advance()
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
start := p.prev.Start start := p.prev.Start
nameEnd := start + p.prev.Length nameEnd := p.prev.End
if p.curr.Type == TokenOpenParenthesis { if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs() args, err := p.parseArgs()
@ -361,16 +762,15 @@ func (p *Parser) factor() (Node, error) {
return nil, err return nil, err
} }
return &CallNode{ return &InvokeNode{
&ReferenceNode{ &ReferenceNode{
name, name,
start, start,
nameEnd, nameEnd,
}, },
args, args,
true,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
@ -390,7 +790,7 @@ func (p *Parser) factor() (Node, error) {
} }
var sig TypeSignature = &NilSignature{} var sig TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace { if p.accept(TokenArrow) {
sig, err = p.parseSignature() sig, err = p.parseSignature()
if err != nil { if err != nil {
return nil, err return nil, err
@ -408,7 +808,7 @@ func (p *Parser) factor() (Node, error) {
sig, sig,
b, b,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenOpenParenthesis: case TokenOpenParenthesis:
@ -423,8 +823,16 @@ func (p *Parser) factor() (Node, error) {
return v, nil return v, nil
case TokenBreakpoint:
p.advance()
return &BreakpointNode{
p.prev.Start,
p.prev.End,
}, nil
default: default:
return nil, p.error("invalid factor", p.curr) return nil, p.error(fmt.Sprintf("invalid factor %s", p.curr), p.curr)
} }
} }
@ -447,7 +855,7 @@ func (p *Parser) prop() (Node, error) {
v, v,
property, property,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
// if called, also add // if called, also add
@ -457,12 +865,11 @@ func (p *Parser) prop() (Node, error) {
return nil, err return nil, err
} }
v = &CallNode{ v = &InvokeNode{
v, v,
args, args,
true,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
} }
} }
@ -494,7 +901,7 @@ func (p *Parser) product() (Node, error) {
left, left,
f, f,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
} }
@ -526,7 +933,7 @@ func (p *Parser) term() (Node, error) {
left, left,
pr, pr,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
} }
@ -573,7 +980,7 @@ func (p *Parser) comparison() (Node, error) {
left, left,
t, t,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
@ -607,7 +1014,7 @@ func (p *Parser) condition() (Node, error) {
left, left,
c, c,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
@ -646,19 +1053,18 @@ func (p *Parser) statement() (Node, error) {
then, then,
otherwise, otherwise,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenName: case TokenName:
p.advance() p.advance()
start := p.prev.Start name := p.prev
name := (*p.prev).Lexeme
if (*p.curr).Type == TokenDot { if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{ var v Node = &ReferenceNode{
name, name.Lexeme,
start, name.Start,
p.prev.Start + p.prev.Length, name.End,
} }
// parse chains of prop-getting ( "".split().join().length.round() ) // parse chains of prop-getting ( "".split().join().length.round() )
@ -671,8 +1077,8 @@ func (p *Parser) statement() (Node, error) {
v = &AccessNode{ v = &AccessNode{
v, v,
property, property,
start, name.Start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
// if called, also add // if called, also add
@ -682,12 +1088,11 @@ func (p *Parser) statement() (Node, error) {
return nil, err return nil, err
} }
v = &CallNode{ v = &InvokeNode{
v, v,
args, args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value. name.Start,
start, p.prev.End,
p.prev.Start + p.prev.Length,
} }
} }
} }
@ -699,16 +1104,15 @@ func (p *Parser) statement() (Node, error) {
return nil, err return nil, err
} }
return &CallNode{ return &InvokeNode{
&ReferenceNode{ &ReferenceNode{
name, name.Lexeme,
start, name.Start,
start + Pos(len(name)), name.End,
}, },
args, args,
false, name.Start,
start, p.prev.End,
p.prev.Start + p.prev.Length,
}, nil }, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) { } else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare isDeclaration := p.prev.Type == TokenDeclare
@ -717,12 +1121,16 @@ func (p *Parser) statement() (Node, error) {
return nil, err return nil, err
} }
return &AssignNode{ return &AssignNode{ // THIS COULD BE MORE PERMISSIVE; its a new system
name, &ReferenceNode{
name.Lexeme,
name.Start,
name.End,
},
c, c,
isDeclaration, isDeclaration,
start, name.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
@ -736,7 +1144,7 @@ func (p *Parser) statement() (Node, error) {
if err := p.expect(TokenName, "function must have a name"); err != nil { if err := p.expect(TokenName, "function must have a name"); err != nil {
return nil, err return nil, err
} }
name := p.prev.Lexeme name := p.prev
params, err := p.parseParams() params, err := p.parseParams()
if err != nil { if err != nil {
@ -744,7 +1152,7 @@ func (p *Parser) statement() (Node, error) {
} }
var yield TypeSignature = &NilSignature{} var yield TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace { if p.accept(TokenArrow) {
yield, err = p.parseSignature() yield, err = p.parseSignature()
if err != nil { if err != nil {
return nil, err return nil, err
@ -757,18 +1165,22 @@ func (p *Parser) statement() (Node, error) {
} }
return &AssignNode{ return &AssignNode{
name, &ReferenceNode{
name.Lexeme,
name.Start,
name.End,
},
&FunctionNode{ &FunctionNode{
name, name.Lexeme,
params, params,
yield, yield,
b, b,
funcStart, funcStart,
p.prev.Start + p.prev.Length, p.prev.End,
}, },
true, true,
funcStart, funcStart,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenWhile: case TokenWhile:
@ -789,7 +1201,7 @@ func (p *Parser) statement() (Node, error) {
c, c,
b, b,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenReturn: case TokenReturn:
@ -804,7 +1216,7 @@ func (p *Parser) statement() (Node, error) {
return &ReturnNode{ return &ReturnNode{
c, c,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenBreakpoint: case TokenBreakpoint:
@ -854,7 +1266,7 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
return &BlockNode{ return &BlockNode{
statements, statements,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
} }
@ -866,7 +1278,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
} }
if !p.accept(TokenCloseParenthesis) { if !p.accept(TokenCloseParenthesis) {
c, err := p.condition() c, err := p.expression(false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -875,7 +1287,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
if err := p.expect(TokenComma, "arguments must be separated by comma"); err != nil { if err := p.expect(TokenComma, "arguments must be separated by comma"); err != nil {
return nil, err return nil, err
} }
c, err = p.condition() c, err = p.expression(false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -942,7 +1354,17 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) {
func (p *Parser) parseSignature() (TypeSignature, error) { func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature var s TypeSignature
if p.accept(TokenFunc) { if p.accept(TokenOpenParenthesis) {
is, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseParenthesis, "expected closing parenthesis"); err != nil {
return nil, err
}
s = is
} else if p.accept(TokenFunc) {
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil { if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
return nil, err return nil, err
} }
@ -965,15 +1387,31 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in = append(in, sig) in = append(in, sig)
} }
out, err := p.parseSignature() var out TypeSignature
if err != nil { var err error
return nil, err if p.accept(TokenArrow) {
out, err = p.parseSignature()
if err != nil {
return nil, err
}
} else {
out = &NilSignature{}
} }
s = &FunctionSignature{ s = &FunctionSignature{
in, in,
out, out,
} }
} else if p.accept(TokenOpenBracket) {
inner, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket, "list type must be enclosed in brackets"); err != nil {
return nil, err
}
return &ListSignature{inner}, nil
} else { } else {
if err := p.expect(TokenName, "type must be a name"); err != nil { if err := p.expect(TokenName, "type must be a name"); err != nil {
return nil, err return nil, err
@ -983,8 +1421,10 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
switch name { switch name {
case "string": case "string":
s = &StringSignature{} s = &StringSignature{}
case "number": case "int":
s = &NumberSignature{} s = &IntegerSignature{}
case "float":
s = &FloatSignature{}
case "boolean": case "boolean":
s = &BooleanSignature{} s = &BooleanSignature{}
case "list": case "list":
@ -997,7 +1437,7 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
return nil, err return nil, err
} }
if err := p.expect(TokenCloseBracket, "list must close parameter list"); err != nil { if err := p.expect(TokenCloseBracket, "square brackets enclose list content signature"); err != nil {
return nil, err return nil, err
} }

View file

@ -55,22 +55,22 @@ func GetTokenTestData() map[string]TokenTestData {
[]Token{ []Token{
NewToken(TokenName, 0, 1, 0, "_"), NewToken(TokenName, 0, 1, 0, "_"),
NewToken(TokenAssign, 1, 1, 0, "="), NewToken(TokenAssign, 1, 1, 0, "="),
NewToken(TokenNumber, 3, 1, 0, "1"), NewToken(TokenFloat, 3, 1, 0, "1"),
NewToken(TokenPlus, 4, 1, 0, "+"), NewToken(TokenPlus, 4, 1, 0, "+"),
NewToken(TokenNumber, 5, 1, 0, "2"), NewToken(TokenFloat, 5, 1, 0, "2"),
NewToken(TokenEOF, 6, 0, 0, ""), NewToken(TokenEOF, 6, 0, 0, ""),
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -93,7 +93,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"hello", &ReferenceNode{"hello", 0, 0},
&StringNode{ &StringNode{
"Hello world!", "Hello world!",
"\"Hello world!\"", "\"Hello world!\"",
@ -110,7 +110,7 @@ func GetTokenTestData() map[string]TokenTestData {
[]Token{ []Token{
NewToken(TokenName, 0, 1, 0, "a"), NewToken(TokenName, 0, 1, 0, "a"),
NewToken(TokenDeclare, 1, 2, 0, ":="), NewToken(TokenDeclare, 1, 2, 0, ":="),
NewToken(TokenNumber, 3, 1, 0, "1"), NewToken(TokenFloat, 3, 1, 0, "1"),
NewToken(TokenPlus, 4, 1, 0, "+"), NewToken(TokenPlus, 4, 1, 0, "+"),
NewToken(TokenName, 5, 1, 0, "b"), NewToken(TokenName, 5, 1, 0, "b"),
NewToken(TokenEOF, 6, 0, 0, ""), NewToken(TokenEOF, 6, 0, 0, ""),
@ -118,10 +118,10 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -145,35 +145,35 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenAssign, 1, 2, 0, "="), NewToken(TokenAssign, 1, 2, 0, "="),
NewToken(TokenOpenParenthesis, 3, 1, 0, "("), NewToken(TokenOpenParenthesis, 3, 1, 0, "("),
NewToken(TokenNumber, 4, 1, 0, "2"), NewToken(TokenFloat, 4, 1, 0, "2"),
NewToken(TokenPlus, 5, 1, 0, "+"), NewToken(TokenPlus, 5, 1, 0, "+"),
NewToken(TokenNumber, 6, 1, 0, "1"), NewToken(TokenFloat, 6, 1, 0, "1"),
NewToken(TokenCloseParenthesis, 7, 1, 0, ")"), NewToken(TokenCloseParenthesis, 7, 1, 0, ")"),
NewToken(TokenStar, 8, 1, 0, "*"), NewToken(TokenStar, 8, 1, 0, "*"),
NewToken(TokenNumber, 9, 1, 0, "5"), NewToken(TokenFloat, 9, 1, 0, "5"),
NewToken(TokenPlus, 10, 1, 0, "+"), NewToken(TokenPlus, 10, 1, 0, "+"),
NewToken(TokenNumber, 11, 1, 0, "3"), NewToken(TokenFloat, 11, 1, 0, "3"),
NewToken(TokenSlash, 12, 1, 0, "/"), NewToken(TokenSlash, 12, 1, 0, "/"),
NewToken(TokenOpenParenthesis, 13, 1, 0, "("), NewToken(TokenOpenParenthesis, 13, 1, 0, "("),
NewToken(TokenNumber, 14, 1, 0, "6"), NewToken(TokenFloat, 14, 1, 0, "6"),
NewToken(TokenMinus, 15, 1, 0, "-"), NewToken(TokenMinus, 15, 1, 0, "-"),
NewToken(TokenNumber, 16, 1, 0, "2"), NewToken(TokenFloat, 16, 1, 0, "2"),
NewToken(TokenCloseParenthesis, 17, 1, 0, ")"), NewToken(TokenCloseParenthesis, 17, 1, 0, ")"),
NewToken(TokenMinus, 18, 1, 0, "-"), NewToken(TokenMinus, 18, 1, 0, "-"),
NewToken(TokenNumber, 19, 2, 0, "10"), NewToken(TokenFloat, 19, 2, 0, "10"),
NewToken(TokenSlash, 20, 1, 0, "/"), NewToken(TokenSlash, 20, 1, 0, "/"),
NewToken(TokenNumber, 21, 1, 0, "2"), NewToken(TokenFloat, 21, 1, 0, "2"),
NewToken(TokenEOF, 22, 0, 0, ""), NewToken(TokenEOF, 22, 0, 0, ""),
}, },
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2 // (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinarySubtraction, BinarySubtraction,
&BinaryNode{ &BinaryNode{
@ -182,17 +182,17 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryMultiplication, BinaryMultiplication,
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
5, 5,
0, 0, 0, 0,
}, },
@ -200,17 +200,17 @@ func GetTokenTestData() map[string]TokenTestData {
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{ &FloatNode{
3, 3,
0, 0, 0, 0,
}, },
&BinaryNode{ &BinaryNode{
BinarySubtraction, BinarySubtraction,
&NumberNode{ &FloatNode{
6, 6,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -222,11 +222,11 @@ func GetTokenTestData() map[string]TokenTestData {
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{ &FloatNode{
10, 10,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, },
@ -245,22 +245,22 @@ func GetTokenTestData() map[string]TokenTestData {
[]Token{ []Token{
NewToken(TokenName, 0, 1, 0, "_"), NewToken(TokenName, 0, 1, 0, "_"),
NewToken(TokenAssign, 1, 1, 0, "="), NewToken(TokenAssign, 1, 1, 0, "="),
NewToken(TokenNumber, 2, 2, 0, "20"), NewToken(TokenFloat, 2, 2, 0, "20"),
NewToken(TokenEquals, 4, 2, 0, "=="), NewToken(TokenEquals, 4, 2, 0, "=="),
NewToken(TokenNumber, 6, 2, 0, "15"), NewToken(TokenFloat, 6, 2, 0, "15"),
NewToken(TokenEOF, 8, 0, 0, ""), NewToken(TokenEOF, 8, 0, 0, ""),
}, },
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryEquality, BinaryEquality,
&NumberNode{ &FloatNode{
20, 20,
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
15, 15,
0, 0, 0, 0,
}, },
@ -278,11 +278,11 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenIf, 0, 2, 0, "if"), NewToken(TokenIf, 0, 2, 0, "if"),
NewToken(TokenName, 2, 1, 0, "a"), NewToken(TokenName, 2, 1, 0, "a"),
NewToken(TokenEquals, 3, 2, 0, "=="), NewToken(TokenEquals, 3, 2, 0, "=="),
NewToken(TokenNumber, 5, 1, 0, "0"), NewToken(TokenFloat, 5, 1, 0, "0"),
NewToken(TokenOpenBrace, 6, 1, 0, "{"), NewToken(TokenOpenBrace, 6, 1, 0, "{"),
NewToken(TokenName, 7, 1, 1, "b"), NewToken(TokenName, 7, 1, 1, "b"),
NewToken(TokenAssign, 8, 1, 1, "="), NewToken(TokenAssign, 8, 1, 1, "="),
NewToken(TokenNumber, 9, 1, 1, "1"), NewToken(TokenFloat, 9, 1, 1, "1"),
NewToken(TokenCloseBrace, 10, 1, 2, "}"), NewToken(TokenCloseBrace, 10, 1, 2, "}"),
NewToken(TokenEOF, 11, 0, 2, ""), NewToken(TokenEOF, 11, 0, 2, ""),
}, },
@ -295,7 +295,7 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -304,8 +304,8 @@ func GetTokenTestData() map[string]TokenTestData {
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -325,17 +325,17 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenIf, 0, 2, 0, "if"), NewToken(TokenIf, 0, 2, 0, "if"),
NewToken(TokenName, 2, 1, 0, "a"), NewToken(TokenName, 2, 1, 0, "a"),
NewToken(TokenEquals, 3, 2, 0, "=="), NewToken(TokenEquals, 3, 2, 0, "=="),
NewToken(TokenNumber, 5, 1, 0, "0"), NewToken(TokenFloat, 5, 1, 0, "0"),
NewToken(TokenOpenBrace, 6, 1, 0, "{"), NewToken(TokenOpenBrace, 6, 1, 0, "{"),
NewToken(TokenName, 7, 1, 1, "b"), NewToken(TokenName, 7, 1, 1, "b"),
NewToken(TokenAssign, 8, 1, 1, "="), NewToken(TokenAssign, 8, 1, 1, "="),
NewToken(TokenNumber, 9, 1, 1, "1"), NewToken(TokenFloat, 9, 1, 1, "1"),
NewToken(TokenCloseBrace, 10, 1, 2, "}"), NewToken(TokenCloseBrace, 10, 1, 2, "}"),
NewToken(TokenElse, 11, 4, 2, "else"), NewToken(TokenElse, 11, 4, 2, "else"),
NewToken(TokenOpenBrace, 15, 1, 2, "{"), NewToken(TokenOpenBrace, 15, 1, 2, "{"),
NewToken(TokenName, 16, 1, 2, "b"), NewToken(TokenName, 16, 1, 2, "b"),
NewToken(TokenAssign, 17, 1, 2, "="), NewToken(TokenAssign, 17, 1, 2, "="),
NewToken(TokenNumber, 18, 1, 2, "0"), NewToken(TokenFloat, 18, 1, 2, "0"),
NewToken(TokenCloseBrace, 19, 1, 2, "}"), NewToken(TokenCloseBrace, 19, 1, 2, "}"),
NewToken(TokenEOF, 20, 0, 2, ""), NewToken(TokenEOF, 20, 0, 2, ""),
}, },
@ -348,7 +348,7 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -357,8 +357,8 @@ func GetTokenTestData() map[string]TokenTestData {
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&NumberNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
}, },
@ -371,8 +371,8 @@ func GetTokenTestData() map[string]TokenTestData {
otherwise: &BlockNode{ otherwise: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&NumberNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
}, },
@ -403,21 +403,22 @@ func GetTokenTestData() map[string]TokenTestData {
0, 0, 0, 0,
}, },
}, },
"lambda": { // a := func(a, b) { return a + b } "lambda": { // a := fn(a: float, b: float) -> float { return a + b }
[]Token{ []Token{
NewToken(TokenName, 0, 1, 0, "a"), NewToken(TokenName, 0, 1, 0, "a"),
NewToken(TokenDeclare, 1, 2, 0, ":="), NewToken(TokenDeclare, 1, 2, 0, ":="),
NewToken(TokenFunc, 3, 4, 0, "func"), NewToken(TokenFunc, 3, 2, 0, "fn"),
NewToken(TokenOpenParenthesis, 7, 1, 0, "("), NewToken(TokenOpenParenthesis, 5, 1, 0, "("),
NewToken(TokenName, 8, 1, 0, "a"), NewToken(TokenName, 6, 1, 0, "a"),
NewToken(TokenColon, 9, 1, 0, ":"), NewToken(TokenColon, 6, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"), NewToken(TokenName, 10, 5, 0, "float"),
NewToken(TokenComma, 9, 1, 0, ","), NewToken(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"), NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"), NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"), NewToken(TokenName, 10, 5, 0, "float"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"), NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenName, 10, 5, 0, "number"), NewToken(TokenArrow, 12, 1, 0, "->"),
NewToken(TokenName, 10, 5, 0, "float"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"), NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"), NewToken(TokenReturn, 13, 6, 1, "return"),
@ -431,20 +432,20 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"*", "*",
[]FunctionParameter{ []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
&NumberSignature{}, &FloatSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -476,13 +477,19 @@ func GetTokenTestData() map[string]TokenTestData {
}, },
"function_declaration": { "function_declaration": {
[]Token{ []Token{
NewToken(TokenFunc, 0, 4, 0, "func"), NewToken(TokenFunc, 0, 2, 0, "fn"),
NewToken(TokenName, 4, 3, 0, "a"), NewToken(TokenName, 4, 3, 0, "a"),
NewToken(TokenOpenParenthesis, 7, 1, 0, "("), NewToken(TokenOpenParenthesis, 7, 1, 0, "("),
NewToken(TokenName, 8, 1, 0, "a"), NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 8, 1, 0, "float"),
NewToken(TokenComma, 9, 1, 0, ","), NewToken(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"), NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 8, 1, 0, "float"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"), NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenArrow, 9, 1, 0, "->"),
NewToken(TokenName, 8, 1, 0, "float"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"), NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"), NewToken(TokenReturn, 13, 6, 1, "return"),
@ -496,20 +503,20 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"a", "a",
[]FunctionParameter{ []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
&NumberSignature{}, &FloatSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -552,7 +559,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"p", &ReferenceNode{"p", 0, 0},
&AccessNode{ &AccessNode{
&ReferenceNode{ &ReferenceNode{
"a", "a",
@ -577,7 +584,7 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenName, 8, 1, 0, "a"), NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenComma, 11, 1, 0, ","), NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenNumber, 8, 1, 0, "3.141"), NewToken(TokenFloat, 8, 1, 0, "3.141"),
NewToken(TokenComma, 11, 1, 0, ","), NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenString, 6, 1, 0, "\"Hello world!\""), NewToken(TokenString, 6, 1, 0, "\"Hello world!\""),
@ -587,10 +594,10 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenComma, 11, 1, 0, ","), NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenOpenBracket, 6, 1, 0, "["), NewToken(TokenOpenBracket, 6, 1, 0, "["),
NewToken(TokenNumber, 6, 1, 0, "2"), NewToken(TokenFloat, 6, 1, 0, "2"),
NewToken(TokenComma, 11, 1, 0, ","), NewToken(TokenComma, 11, 1, 0, ","),
NewToken(TokenNumber, 6, 1, 0, "3"), NewToken(TokenFloat, 6, 1, 0, "3"),
NewToken(TokenCloseBracket, 6, 1, 0, "]"), NewToken(TokenCloseBracket, 6, 1, 0, "]"),
NewToken(TokenCloseBracket, 6, 1, 0, "]"), NewToken(TokenCloseBracket, 6, 1, 0, "]"),
@ -600,14 +607,14 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"data", &ReferenceNode{"data", 0, 0},
&ListNode{ &ListNode{
[]Node{ []Node{
&ReferenceNode{ &ReferenceNode{
"a", "a",
0, 0, 0, 0,
}, },
&NumberNode{ &FloatNode{
3.141, 3.141,
0, 0, 0, 0,
}, },
@ -622,10 +629,10 @@ func GetTokenTestData() map[string]TokenTestData {
}, },
&ListNode{ &ListNode{
[]Node{ []Node{
&NumberNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
}, &NumberNode{ }, &FloatNode{
3, 3,
0, 0, 0, 0,
}, },
@ -677,12 +684,20 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("String node quoted values match (%s)", n1.(*StringNode).value) t.Logf("String node quoted values match (%s)", n1.(*StringNode).value)
} }
case NumberNodeType: case FloatNodeType:
if n1.(*NumberNode).value != n2.(*NumberNode).value { if n1.(*FloatNode).value != n2.(*FloatNode).value {
t.Errorf("Number node values don't match (%f and %f)", n1.(*NumberNode).value, n2.(*NumberNode).value) t.Errorf("Float node values don't match (%f and %f)", n1.(*FloatNode).value, n2.(*FloatNode).value)
} else { } else {
t.Logf("Number node values match (%f)", n1.(*NumberNode).value) t.Logf("Float node values match (%f)", n1.(*FloatNode).value)
} }
case IntegerNodeType:
if n1.(*IntegerNode).value.Cmp(n2.(*IntegerNode).value) != 0 {
t.Errorf("Integer node values don't match (%d and %d)", n1.(*IntegerNode).value, n2.(*IntegerNode).value)
} else {
t.Logf("Integer node values match (%d)", n1.(*IntegerNode).value)
}
case ReferenceNodeType: case ReferenceNodeType:
if n1.(*ReferenceNode).name != n2.(*ReferenceNode).name { if n1.(*ReferenceNode).name != n2.(*ReferenceNode).name {
t.Errorf("Reference node values don't match (%s and %s)", n1.(*ReferenceNode).name, n2.(*ReferenceNode).name) t.Errorf("Reference node values don't match (%s and %s)", n1.(*ReferenceNode).name, n2.(*ReferenceNode).name)
@ -734,11 +749,8 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
NodeEquality(t, n1.(*LoopNode).do, n2.(*LoopNode).do) NodeEquality(t, n1.(*LoopNode).do, n2.(*LoopNode).do)
case AssignNodeType: case AssignNodeType:
if n1.(*AssignNode).name != n2.(*AssignNode).name { t.Logf("Checking if value destination matches")
t.Errorf("Assigned value name is not the same (%s and %s)", n1.(*AssignNode).name, n2.(*AssignNode).name) NodeEquality(t, n1.(*AssignNode).dest, n2.(*AssignNode).dest)
} else {
t.Logf("Assigned value name matches (%s)", n1.(*AssignNode).name)
}
if n1.(*AssignNode).declare != n2.(*AssignNode).declare { if n1.(*AssignNode).declare != n2.(*AssignNode).declare {
t.Errorf("Not same type of assigning (1: %v; 2: %v)", n1.(*AssignNode).declare, n2.(*AssignNode).declare) t.Errorf("Not same type of assigning (1: %v; 2: %v)", n1.(*AssignNode).declare, n2.(*AssignNode).declare)
@ -747,22 +759,18 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("Checking equality of assignment values") t.Logf("Checking equality of assignment values")
NodeEquality(t, n1.(*AssignNode).value, n2.(*AssignNode).value) NodeEquality(t, n1.(*AssignNode).value, n2.(*AssignNode).value)
case CallNodeType: case InvokeNodeType:
n := n1.(*CallNode) n := n1.(*InvokeNode)
m := n2.(*CallNode) m := n2.(*InvokeNode)
NodeEquality(t, n.source, m.source) NodeEquality(t, n.source, m.source)
if n.keep == m.keep {
t.Logf("Call node keep modifier doesn't match (%v and %v)", n.keep, m.keep)
}
if len(n.args) != len(m.args) { if len(n.args) != len(m.args) {
t.Fatalf("Call node arguments count does not match (%d and %d)", len(n.args), m.args) t.Fatalf("Call node arguments count does not match (%d and %d)", len(n.args), m.args)
} }
for i, arg := range m.args { for i, arg := range m.args {
NodeEquality(t, n1.(*CallNode).args[i], arg) NodeEquality(t, n1.(*InvokeNode).args[i], arg)
} }
case FunctionNodeType: case FunctionNodeType:
@ -795,6 +803,37 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
case ReturnNodeType: case ReturnNodeType:
NodeEquality(t, n1.(*ReturnNode).value, n2.(*ReturnNode).value) NodeEquality(t, n1.(*ReturnNode).value, n2.(*ReturnNode).value)
case AccessNodeType:
a1 := n1.(*AccessNode)
a2 := n2.(*AccessNode)
if a1.property != a2.property {
t.Errorf("Access node property does not match: .%s != .%s", a1.property, a2.property)
} else {
t.Logf("Access node property matches: .%s", a1.property)
}
NodeEquality(t, a1.source, a2.source)
case ListNodeType:
l1 := n1.(*ListNode)
l2 := n2.(*ListNode)
if l1.content == nil && l2.content == nil {
// fine
t.Logf("Both content types are yet to be determined")
} else if l1.content != nil || l2.content != nil {
t.Errorf("one is nil, one is not")
} else if !l1.content.Matches(l2.content) {
t.Errorf("signature doesn't match")
}
for i, v1 := range l1.items {
t.Logf("Checking item %d", i)
NodeEquality(t, v1, l2.items[i])
}
default: default:
panic("unimplemented node equality") panic("unimplemented node equality")
} }
@ -818,7 +857,7 @@ func SerializeTokens(tokens []Token) string {
out.WriteString("!") out.WriteString("!")
case TokenSemicolon: case TokenSemicolon:
out.WriteString(";") out.WriteString(";")
case TokenNumber: case TokenFloat:
out.WriteString(token.Lexeme) out.WriteString(token.Lexeme)
case TokenString: case TokenString:
out.WriteString(fmt.Sprintf("\"%s\"", token.Lexeme)) out.WriteString(fmt.Sprintf("\"%s\"", token.Lexeme))
@ -845,7 +884,7 @@ func SerializeTokens(tokens []Token) string {
case TokenNil: case TokenNil:
out.WriteString("nil") out.WriteString("nil")
case TokenFunc: case TokenFunc:
out.WriteString("func") out.WriteString("fn")
case TokenReturn: case TokenReturn:
out.WriteString("return ") out.WriteString("return ")
case TokenWhile: case TokenWhile:
@ -904,10 +943,6 @@ func TestParser_Parse(t *testing.T) {
tokenData := GetTokenTestData() tokenData := GetTokenTestData()
for name, data := range tokenData { for name, data := range tokenData {
if name != "empty_block" && name != "lambda" {
continue
}
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Logf("Initializing parser") t.Logf("Initializing parser")
p := NewParser("", []string{}, data.tokens) p := NewParser("", []string{}, data.tokens)

View file

@ -7,10 +7,10 @@ type Stack[T any] struct {
items []T items []T
} }
func NewStack[T any](capacity Pos) *Stack[T] { func NewStack[T any](maxCapacity Pos) *Stack[T] {
return &Stack[T]{ return &Stack[T]{
items: make([]T, 16), items: make([]T, min(maxCapacity, 16)),
Capacity: capacity, Capacity: maxCapacity,
Current: 0, Current: 0,
} }
} }

View file

@ -5,6 +5,30 @@ import (
"testing" "testing"
) )
func CompareScope(t *testing.T, expectedScope []map[string]Value, actualScope *Scope) {
s := actualScope
for i := len(expectedScope) - 1; i >= 0; i-- {
if s == nil {
t.Fatal("scope cut unexpecetantly short")
}
for name, value := range expectedScope[i] {
v, ok := s.current[name]
if !ok {
t.Errorf("variable %s not found in correct scope", name)
continue
}
if !v.Equals(value) {
t.Errorf("variable %s has value %s but expected %s", name, v.String(), value.String())
} else {
t.Logf("variable %s has expected value %s", name, v.String())
}
}
}
}
func CompareStacks[T Value](t *testing.T, expected []T, actual *Stack[T]) { func CompareStacks[T Value](t *testing.T, expected []T, actual *Stack[T]) {
if actual.Current != Pos(len(expected)) { if actual.Current != Pos(len(expected)) {
t.Errorf("Unexpected stack size. Expected %d, got %d", len(expected), actual.Current) t.Errorf("Unexpected stack size. Expected %d, got %d", len(expected), actual.Current)

View file

@ -9,7 +9,8 @@ type Type int
const ( const (
TypeString Type = iota TypeString Type = iota
TypeNumber TypeInteger
TypeFloat
TypeBoolean TypeBoolean
TypeNil TypeNil
TypeList TypeList
@ -24,8 +25,10 @@ func (t Type) String() string {
switch t { switch t {
case TypeString: case TypeString:
return "string" return "string"
case TypeNumber: case TypeInteger:
return "number" return "integer"
case TypeFloat:
return "float"
case TypeBoolean: case TypeBoolean:
return "boolean" return "boolean"
case TypeNil: case TypeNil:
@ -51,8 +54,10 @@ func SignatureOf(v Value) TypeSignature {
switch t := v.(type) { switch t := v.(type) {
case *StringValue: case *StringValue:
return &StringSignature{} return &StringSignature{}
case *NumberValue: case *FloatValue:
return &NumberSignature{} return &FloatSignature{}
case *IntegerValue:
return &IntegerSignature{}
case *BoolValue: case *BoolValue:
return &BooleanSignature{} return &BooleanSignature{}
case *ListValue: case *ListValue:
@ -138,22 +143,40 @@ func (*StringSignature) String() string {
return "string" return "string"
} }
type NumberSignature struct{} type FloatSignature struct{}
func (*NumberSignature) Type() Type { func (*FloatSignature) Type() Type {
return TypeNumber return TypeFloat
} }
func (s *NumberSignature) Matches(other TypeSignature) bool { func (s *FloatSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite { if other.Type() == TypeComposite {
return other.Matches(s) return other.Matches(s)
} }
return other.Type() == TypeAny || other.Type() == TypeNumber return other.Type() == TypeAny || other.Type() == TypeFloat
} }
func (*NumberSignature) String() string { func (*FloatSignature) String() string {
return "number" return "float"
}
type IntegerSignature struct{}
func (*IntegerSignature) Type() Type {
return TypeInteger
}
func (s *IntegerSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeInteger
}
func (*IntegerSignature) String() string {
return "int"
} }
type BooleanSignature struct{} type BooleanSignature struct{}

View file

@ -3,7 +3,7 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"math" "math/big"
"reflect" "reflect"
"strconv" "strconv"
) )
@ -13,7 +13,8 @@ type ValueType int
const ( const (
NilValueType ValueType = iota NilValueType ValueType = iota
BoolValueType BoolValueType
NumberValueType FloatValueType
IntegerValueType
StringValueType StringValueType
ListValueType ListValueType
ObjectValueType ObjectValueType
@ -30,8 +31,10 @@ func (v ValueType) String() string {
return "bool" return "bool"
case ObjectValueType: case ObjectValueType:
return "object" return "object"
case NumberValueType: case FloatValueType:
return "number" return "float"
case IntegerValueType:
return "int"
case StringValueType: case StringValueType:
return "string" return "string"
case ListValueType: case ListValueType:
@ -57,11 +60,15 @@ func GoToValue(gov interface{}) Value {
v, v,
} }
case int: case int:
return &NumberValue{ return &IntegerValue{
float64(v), new(big.Int).SetInt64(int64(v)),
}
case *big.Int:
return &IntegerValue{
v,
} }
case float64: case float64:
return &NumberValue{ return &FloatValue{
v, v,
} }
case string: case string:
@ -259,40 +266,71 @@ func (v *ObjectValue) Clone() Value {
} }
} }
// NumberValue Integer or floating-point values // FloatValue floating-point values
type NumberValue struct { type FloatValue struct {
Number float64 Number float64
} }
const NumberSize int = 64 const FloatSize int = 64
func (v *NumberValue) Type() ValueType { func (v *FloatValue) Type() ValueType {
return NumberValueType return FloatValueType
} }
func (v *NumberValue) String() string { func (v *FloatValue) String() string {
return strconv.FormatFloat(v.Number, 'g', -1, NumberSize) return strconv.FormatFloat(v.Number, 'g', -1, FloatSize)
} }
func (v *NumberValue) DebugString() string { func (v *FloatValue) DebugString() string {
return v.String() return v.String()
} }
func (v *NumberValue) Equals(other Value) bool { func (v *FloatValue) Equals(other Value) bool {
return other.Type() == NumberValueType && other.(*NumberValue).Number == v.Number return other.Type() == FloatValueType && other.(*FloatValue).Number == v.Number
} }
func (v *NumberValue) Get(_ string) (Value, error) { func (v *FloatValue) Get(_ string) (Value, error) {
// TODO maybe add standard functions for number values? // TODO maybe add standard functions for number values?
return nil, errors.New("numbers have no properties") return nil, errors.New("numbers have no properties")
} }
func (v *NumberValue) Clone() Value { func (v *FloatValue) Clone() Value {
return &NumberValue{ return &FloatValue{
v.Number, v.Number,
} }
} }
// IntegerValue whole number/integer values
type IntegerValue struct {
Number *big.Int
}
func (v *IntegerValue) Type() ValueType {
return IntegerValueType
}
func (v *IntegerValue) String() string {
return v.Number.String()
}
func (v *IntegerValue) DebugString() string {
return v.String()
}
func (v *IntegerValue) Equals(other Value) bool {
return other.Type() == IntegerValueType && other.(*IntegerValue).Number.Cmp(v.Number) == 0
}
func (v *IntegerValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties")
}
func (v *IntegerValue) Clone() Value {
return &IntegerValue{
new(big.Int).Set(v.Number),
}
}
type StringValue struct { type StringValue struct {
Text string Text string
} }
@ -348,7 +386,7 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
Name: "length", Name: "length",
Signature: &FunctionSignature{ Signature: &FunctionSignature{
[]TypeSignature{}, []TypeSignature{},
&NumberSignature{}, &IntegerSignature{},
}, },
F: func(vm *VM, this Value, _ []Value) (Value, error) { F: func(vm *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*StringValue).Text)), nil return GoToValue(len(this.(*StringValue).Text)), nil
@ -357,11 +395,11 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
"at": { "at": {
Name: "at", Name: "at",
Signature: &FunctionSignature{ Signature: &FunctionSignature{
[]TypeSignature{&NumberSignature{}}, []TypeSignature{&IntegerSignature{}},
&StringSignature{}, &StringSignature{},
}, },
F: func(vm *VM, this Value, args []Value) (Value, error) { F: func(vm *VM, this Value, args []Value) (Value, error) {
i := int(math.Floor(args[0].(*NumberValue).Number)) i := int(args[0].(*IntegerValue).Number.Int64())
if i < 0 || i >= len(this.(*StringValue).Text) { if i < 0 || i >= len(this.(*StringValue).Text) {
return nil, errors.New("index is out of range") return nil, errors.New("index is out of range")
@ -450,13 +488,13 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"at", "at",
&FunctionSignature{ &FunctionSignature{
[]TypeSignature{ []TypeSignature{
&NumberSignature{}, &IntegerSignature{},
}, },
&InnerSignature{}, &InnerSignature{},
}, },
func(_ *VM, this Value, p []Value) (Value, error) { func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).Items items := this.(*ListValue).Items
index := int(p[0].(*NumberValue).Number) index := int(p[0].(*IntegerValue).Number.Int64())
if index >= len(items) { if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index)) return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -471,13 +509,13 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"put", "put",
&FunctionSignature{ &FunctionSignature{
[]TypeSignature{ []TypeSignature{
&NumberSignature{}, &InnerSignature{}, &FloatSignature{}, &InnerSignature{},
}, },
&NilSignature{}, &NilSignature{},
}, },
func(_ *VM, this Value, args []Value) (Value, error) { func(_ *VM, this Value, args []Value) (Value, error) {
l := this.(*ListValue) l := this.(*ListValue)
i := int(args[0].(*NumberValue).Number) i := int(args[0].(*FloatValue).Number)
v := args[1] v := args[1]
// bounds check // bounds check
@ -496,7 +534,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"length", "length",
&FunctionSignature{ &FunctionSignature{
[]TypeSignature{}, []TypeSignature{},
&NumberSignature{}, &IntegerSignature{},
}, },
func(_ *VM, this Value, _ []Value) (Value, error) { func(_ *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*ListValue).Items)), nil return GoToValue(len(this.(*ListValue).Items)), nil
@ -565,6 +603,7 @@ type FunctionValue struct {
Yield TypeSignature Yield TypeSignature
Chunk *Chunk Chunk *Chunk
Parent Value Parent Value
Scope *Scope
} }
func (v *FunctionValue) Type() ValueType { func (v *FunctionValue) Type() ValueType {
@ -572,7 +611,7 @@ func (v *FunctionValue) Type() ValueType {
} }
func (v *FunctionValue) String() string { func (v *FunctionValue) String() string {
return fmt.Sprintf("<function name=%s>", v.Name) return fmt.Sprintf("<function name=%s block=%p>", v.Name, v.Chunk)
} }
func (v *FunctionValue) DebugString() string { func (v *FunctionValue) DebugString() string {
@ -581,7 +620,6 @@ func (v *FunctionValue) DebugString() string {
func (v *FunctionValue) Equals(other Value) bool { func (v *FunctionValue) Equals(other Value) bool {
return other.Type() == FunctionValueType && return other.Type() == FunctionValueType &&
v.Name == other.(*FunctionValue).Name &&
v.Chunk == other.(*FunctionValue).Chunk v.Chunk == other.(*FunctionValue).Chunk
} }
@ -596,6 +634,7 @@ func (v *FunctionValue) Clone() Value {
v.Yield, v.Yield,
v.Chunk, v.Chunk,
v.Parent, v.Parent,
v.Scope,
} }
} }
@ -637,43 +676,3 @@ func (v *BuiltinFunctionValue) Clone() Value {
v.Constant, v.Constant,
} }
} }
// VariableValue a value wrapper for variables kept on the stack
type VariableValue struct {
name string
value Value
scope Pos
}
func (v *VariableValue) Type() ValueType {
return VariableValueType
}
func (v *VariableValue) String() string {
return fmt.Sprintf("<variable name=%s value=%s scope=%d>", v.name, v.value, v.scope)
// variables should not be accessed on the stack; normal values should be pushed and popped predictably
//panic("tried getting string value of a unreachable value")
}
func (v *VariableValue) DebugString() string {
return v.String()
}
func (v *VariableValue) Equals(other Value) bool {
return other.Type() == VariableValueType &&
v.name == other.(*VariableValue).name &&
v.value.Equals(other.(*VariableValue).value)
}
func (v *VariableValue) Get(_ string) (Value, error) {
return nil, errors.New("variables have no properties")
}
func (v *VariableValue) Clone() Value {
return &VariableValue{
v.name,
v.value.Clone(),
v.scope,
}
}

View file

@ -21,15 +21,21 @@ func CompareValues(t *testing.T, got Value, want Value) {
} else { } else {
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String()) t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
} }
case NumberValueType: case FloatValueType:
if got.(*NumberValue).Number != want.(*NumberValue).Number { if got.(*FloatValue).Number != want.(*FloatValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue)) t.Errorf("number value mismatch: got %v, want %v", got.(*FloatValue), want.(*FloatValue))
} else { } else {
t.Logf("Both are same number (%s)", got.(*NumberValue).String()) t.Logf("Both are same number (%s)", got.(*FloatValue).String())
}
case IntegerValueType:
if got.(*IntegerValue).Number.String() != want.(*IntegerValue).Number.String() {
t.Errorf("number value mismatch: got %v, want %v", got.(*IntegerValue), want.(*IntegerValue))
} else {
t.Logf("Both are same number (%s)", got.(*IntegerValue).String())
} }
case StringValueType: case StringValueType:
if got.(*StringValue).Text != want.(*StringValue).Text { if got.(*StringValue).Text != want.(*StringValue).Text {
t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue)) t.Errorf("string value mismatch: got %s, want %s", got.(*StringValue), want.(*StringValue))
} else { } else {
t.Logf("Both are same string (%s)", got.(*StringValue).String()) t.Logf("Both are same string (%s)", got.(*StringValue).String())
} }
@ -64,20 +70,6 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n, m) t.Errorf("builtin function parameter count mismatch: got %v, want %v", n, m)
} }
case VariableValueType:
n := got.(*VariableValue)
m := want.(*VariableValue)
if n.name != m.name {
t.Errorf("variable name mismatch: got %v, want %v", n.name, m.name)
}
if n.scope != m.scope {
t.Errorf("variable scope mismatch: got %v, want %v", n.scope, m.scope)
}
CompareValues(t, n.value, m.value)
case ListValueType: case ListValueType:
n := got.(*ListValue) n := got.(*ListValue)
m := want.(*ListValue) m := want.(*ListValue)

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@ func TestNewVM(t *testing.T) {
chunk := NewChunk([]Bytecode{ chunk := NewChunk([]Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
}, []Value{ }, []Value{
&NumberValue{0}, &FloatValue{0},
}) })
stackSize := Pos(256) stackSize := Pos(256)
callstackSize := Pos(256) callstackSize := Pos(256)
@ -76,8 +76,8 @@ func TestNewVM(t *testing.T) {
} }
// should have given stack size // should have given stack size
if vm.stack.Capacity != stackSize { if vm.Stack.Capacity != stackSize {
t.Errorf("vm.stack.Capacity = %d, want %d", vm.stack.Capacity, stackSize) t.Errorf("vm.stack.Capacity = %d, want %d", vm.Stack.Capacity, stackSize)
} }
// should have given call stack size // should have given call stack size
@ -95,23 +95,26 @@ func BenchmarkNewVM(b *testing.B) {
func GetExecutionTestData() map[string]struct { func GetExecutionTestData() map[string]struct {
chunk *Chunk chunk *Chunk
resultingStack []Value resultingStack []Value
resultingScope []map[string]Value
} { } {
return map[string]struct { return map[string]struct {
chunk *Chunk chunk *Chunk
resultingStack []Value resultingStack []Value
resultingScope []map[string]Value
}{ }{
"two_plus_one": { "two_plus_one": {
NewChunk([]Bytecode{ NewChunk([]Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionConstant, 1, InstructionConstant, 1,
InstructionAdd, InstructionAddFloat,
}, },
[]Value{ []Value{
&NumberValue{1}, &NumberValue{2}, &FloatValue{1}, &FloatValue{2},
}), }),
[]Value{ []Value{
&NumberValue{3}, &FloatValue{3},
}, },
[]map[string]Value{},
}, },
"push_constant": { "push_constant": {
NewChunk( NewChunk(
@ -119,12 +122,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 0, InstructionConstant, 0,
}, },
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"push_true": { "push_true": {
NewChunk( NewChunk(
@ -136,6 +140,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{true}, &BoolValue{true},
}, },
[]map[string]Value{},
}, },
"push_false": { "push_false": {
NewChunk( NewChunk(
@ -147,6 +152,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"push_nil": { "push_nil": {
NewChunk( NewChunk(
@ -158,6 +164,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&NilValue{}, &NilValue{},
}, },
[]map[string]Value{},
}, },
"empty": { "empty": {
NewChunk( NewChunk(
@ -165,6 +172,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{}, []Value{},
[]map[string]Value{},
}, },
// (2 + 1) * 5 / (6 - 2) // (2 + 1) * 5 / (6 - 2)
"full_arithmetic": { "full_arithmetic": {
@ -172,21 +180,22 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{ []Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionConstant, 1, InstructionConstant, 1,
InstructionAdd, InstructionAddFloat,
InstructionConstant, 2, InstructionConstant, 2,
InstructionMul, InstructionMulFloat,
InstructionConstant, 3, InstructionConstant, 3,
InstructionConstant, 0, InstructionConstant, 0,
InstructionSub, InstructionSubFloat,
InstructionDiv, InstructionDivFloat,
}, },
[]Value{ []Value{
&NumberValue{2}, &NumberValue{1}, &NumberValue{5}, &NumberValue{6}, &FloatValue{2}, &FloatValue{1}, &FloatValue{5}, &FloatValue{6},
}, },
), ),
[]Value{ []Value{
&NumberValue{3.75}, &FloatValue{3.75},
}, },
[]map[string]Value{},
}, },
"equality_true": { "equality_true": {
NewChunk( NewChunk(
@ -196,12 +205,13 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals, InstructionEquals,
}, },
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&BoolValue{true}, &BoolValue{true},
}, },
[]map[string]Value{},
}, },
"equality_false": { "equality_false": {
NewChunk( NewChunk(
@ -211,12 +221,13 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals, InstructionEquals,
}, },
[]Value{ []Value{
&NumberValue{1}, &NumberValue{2}, &FloatValue{1}, &FloatValue{2},
}, },
), ),
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"inequality_false": { "inequality_false": {
NewChunk( NewChunk(
@ -226,12 +237,13 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual, InstructionNotEqual,
}, },
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"inequality_true": { "inequality_true": {
NewChunk( NewChunk(
@ -241,12 +253,13 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual, InstructionNotEqual,
}, },
[]Value{ []Value{
&NumberValue{1}, &NumberValue{2}, &FloatValue{1}, &FloatValue{2},
}, },
), ),
[]Value{ []Value{
&BoolValue{true}, &BoolValue{true},
}, },
[]map[string]Value{},
}, },
"not_true": { "not_true": {
NewChunk( NewChunk(
@ -259,6 +272,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"not_false": { "not_false": {
NewChunk( NewChunk(
@ -271,6 +285,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{true}, &BoolValue{true},
}, },
[]map[string]Value{},
}, },
"jump": { "jump": {
NewChunk( NewChunk(
@ -280,12 +295,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &FloatValue{0}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"jump_false/false": { "jump_false/false": {
NewChunk( NewChunk(
@ -296,12 +312,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &FloatValue{0}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"jump_false/true": { "jump_false/true": {
NewChunk( NewChunk(
@ -312,12 +329,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &FloatValue{0}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &FloatValue{0}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"declare_local": { "declare_local": {
NewChunk( NewChunk(
@ -326,14 +344,13 @@ func GetExecutionTestData() map[string]struct {
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
}, },
[]Value{ []Value{
&NumberValue{0}, &StringValue{"a"}, &FloatValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{&FloatValue{0}},
&VariableValue{ []map[string]Value{
"a", {
&NumberValue{0}, "a": &FloatValue{0},
0,
}, },
}, },
}, },
@ -342,18 +359,19 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{ []Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
InstructionPop,
InstructionConstant, 2, InstructionConstant, 2,
InstructionSetLocal, 1, // reassign InstructionSetLocal, 1, // reassign
InstructionPop,
}, },
[]Value{ []Value{
&NumberValue{0}, &StringValue{"a"}, &NumberValue{1}, &FloatValue{0}, &StringValue{"a"}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{},
&VariableValue{ []map[string]Value{
"a", {
&NumberValue{1}, "a": &FloatValue{1},
0,
}, },
}, },
}, },
@ -362,19 +380,18 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{ []Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
InstructionGetLocal, 1, // reassign
}, },
[]Value{ []Value{
&NumberValue{0}, &StringValue{"a"}, &FloatValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &FloatValue{0},
"a", },
&NumberValue{0}, []map[string]Value{
0, {
"a": &FloatValue{0},
}, },
&NumberValue{0},
}, },
}, },
"get_reassigned_local": { "get_reassigned_local": {
@ -382,23 +399,25 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{ []Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
InstructionPop,
InstructionGetLocal, 1, InstructionGetLocal, 1,
InstructionConstant, 2, InstructionConstant, 2,
InstructionSetLocal, 1, // reassign InstructionSetLocal, 1, // reassign
InstructionPop,
InstructionGetLocal, 1, InstructionGetLocal, 1,
}, },
[]Value{ []Value{
&NumberValue{0}, &StringValue{"a"}, &NumberValue{1}, &FloatValue{0}, &StringValue{"a"}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &FloatValue{0},
"a", &FloatValue{1},
&NumberValue{1}, },
0, []map[string]Value{
{
"a": &FloatValue{1},
}, },
&NumberValue{0},
&NumberValue{1},
}, },
}, },
"variable_scope": { "variable_scope": {
@ -406,26 +425,28 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{ []Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
InstructionPop,
InstructionDescend, InstructionDescend,
InstructionConstant, 2, InstructionConstant, 2,
InstructionDeclareLocal, 3, InstructionDeclareLocal, 3,
InstructionPop,
InstructionDescend, InstructionDescend,
InstructionConstant, 4, InstructionConstant, 4,
InstructionDeclareLocal, 5, InstructionDeclareLocal, 5,
InstructionPop,
InstructionAscend, InstructionAscend,
InstructionAscend, InstructionAscend,
}, },
[]Value{ []Value{
&NumberValue{0}, &StringValue{"a"}, &FloatValue{0}, &StringValue{"a"},
&NumberValue{1}, &StringValue{"b"}, &FloatValue{1}, &StringValue{"b"},
&NumberValue{2}, &StringValue{"c"}, &FloatValue{2}, &StringValue{"c"},
}, },
), ),
[]Value{ []Value{},
&VariableValue{ []map[string]Value{
"a", {
&NumberValue{0}, "a": &FloatValue{0},
0,
}, },
}, },
}, },
@ -438,25 +459,25 @@ func GetExecutionTestData() map[string]struct {
InstructionCall, InstructionCall,
}, },
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []FunctionParameter{ Params: []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionGetLocal, 1, InstructionGetLocal, 1,
InstructionAdd, InstructionAddFloat,
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
@ -467,32 +488,34 @@ func GetExecutionTestData() map[string]struct {
}, },
), ),
[]Value{ []Value{
&NumberValue{3}, &FloatValue{3},
}, },
[]map[string]Value{},
}, },
"function_calling_function": { "function_calling_function": {
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionConstant, 3, InstructionConstant, 3,
InstructionDeclareLocal, 4, InstructionDeclareLocal, 4,
InstructionPop,
InstructionConstant, 0, InstructionConstant, 0,
InstructionConstant, 1, InstructionConstant, 1,
InstructionConstant, 2, InstructionConstant, 2,
InstructionCall, InstructionCall,
}, },
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []FunctionParameter{ Params: []FunctionParameter{
{ {
"a", "a",
&NumberSignature{}, &FloatSignature{},
}, },
{ {
"b", "b",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
Chunk: NewChunk( Chunk: NewChunk(
@ -501,7 +524,7 @@ func GetExecutionTestData() map[string]struct {
InstructionGetLocal, 2, InstructionCall, // square the number InstructionGetLocal, 2, InstructionCall, // square the number
InstructionGetLocal, 1, InstructionGetLocal, 1,
InstructionGetLocal, 2, InstructionCall, // square the number InstructionGetLocal, 2, InstructionCall, // square the number
InstructionAdd, InstructionAddFloat,
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
@ -514,14 +537,14 @@ func GetExecutionTestData() map[string]struct {
Params: []FunctionParameter{ Params: []FunctionParameter{
{ {
"n", "n",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionMul, InstructionMulFloat,
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
@ -533,21 +556,23 @@ func GetExecutionTestData() map[string]struct {
}, },
), ),
[]Value{ []Value{
&VariableValue{ &FloatValue{5},
"square", },
&FunctionValue{ []map[string]Value{
{
"square": &FunctionValue{
Name: "square", Name: "square",
Params: []FunctionParameter{ Params: []FunctionParameter{
{ {
"n", "n",
&NumberSignature{}, &FloatSignature{},
}, },
}, },
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionGetLocal, 0, InstructionGetLocal, 0,
InstructionMul, InstructionMulFloat,
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
@ -555,9 +580,7 @@ func GetExecutionTestData() map[string]struct {
}, },
), ),
}, },
0,
}, },
&NumberValue{5},
}, },
}, },
"list_concat": { "list_concat": {
@ -570,13 +593,13 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&ListValue{ &ListValue{
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
}, },
}, },
&ListValue{ &ListValue{
[]Value{ []Value{
&NumberValue{3}, &FloatValue{3},
}, },
}, },
}, },
@ -584,12 +607,13 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&ListValue{ &ListValue{
[]Value{ []Value{
&NumberValue{1}, &FloatValue{1},
&NumberValue{2}, &FloatValue{2},
&NumberValue{3}, &FloatValue{3},
}, },
}, },
}, },
[]map[string]Value{},
}, },
} }
} }
@ -604,7 +628,7 @@ func TestVM_Execution(t *testing.T) {
for vm.Next() { for vm.Next() {
} }
CompareStacks(t, test.resultingStack, vm.stack) CompareStacks(t, test.resultingStack, vm.Stack)
}) })
} }
} }
@ -630,7 +654,7 @@ func TestVM_NextByte(t *testing.T) {
InstructionConstant, 0, InstructionConstant, 0,
}, },
[]Value{ []Value{
&NumberValue{0}, &FloatValue{0},
}, },
), ),
16, 16,
@ -741,7 +765,7 @@ func TestVM_Jump(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2}, &FloatValue{0}, &FloatValue{1}, &FloatValue{2},
}, },
), ),
16, 16,
@ -766,7 +790,7 @@ func TestVM_JumpFalse(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2}, &FloatValue{0}, &FloatValue{1}, &FloatValue{2},
}, },
), ),
16, 16,
@ -792,7 +816,7 @@ func TestVM_DontJumpFalse(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2}, &FloatValue{0}, &FloatValue{1}, &FloatValue{2},
}, },
), ),
16, 16,

11
era3.ang Normal file
View file

@ -0,0 +1,11 @@
fn counter() -> (fn() -> int) {
i := 0
fn() -> int { i = i + 1 }
}
next := counter()
println(next())
println(next())
println(next())

View file

@ -7,19 +7,19 @@
terms := 1000000 terms := 1000000
# The running sum of terms # The running sum of terms
tot := 0 tot := 0.0
n := 1 n := 1
while n <= terms { while n <= terms {
tot = tot + 1 / (n*n) tot = tot + 1.0/float(n*n)
n = n + 1 n = n + 1
} }
tot = tot * 6 tot = tot * 6.0
# get the absolute value of a number # get the absolute value of a number
func abs(x: number) number { fn abs(x: float) -> float {
if x < 0 { if x < 0.0 {
return -x return -x
} }
return x return x
@ -30,19 +30,19 @@ func abs(x: number) number {
# see: https://en.wikipedia.org/wiki/Newton's_method # see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy # The required accuracy
SQRT_ACC := 0.00000001 SQRT_ACC := 0.00000001
func sqrt(x: number) number { fn sqrt(x: float) -> float {
pg := 0 # previous guess pg := 0.0 # previous guess
g := 1 # current guess g := 1.0 # current guess
while abs(pg - g) >= SQRT_ACC { while abs(pg - g) >= SQRT_ACC {
pg = g pg = g
g = (pg + tot/pg)/2 g = (pg + x/pg)/2.0
} }
return g return g
} }
tot = sqrt(tot) pi := sqrt(tot)
# output the result # output the result
write(str(tot)) println(pi)

View file

@ -13,3 +13,5 @@ _slow_blink := CSI + "5m"
_rapid_blink := CSI + "6m" _rapid_blink := CSI + "6m"
_strike := CSI + "9m" _strike := CSI + "9m"
_primary_font := CSI + "10m" _primary_font := CSI + "10m"
fn red(s: string) -> string {}

View file

@ -1,5 +1,5 @@
func map(list: list[any], f: func(any)any) list[any] { fn map(list: [any], f: fn(any) -> any) -> [any] {
out := [] out := []
i := 0 i := 0

View file

@ -7,7 +7,7 @@ E := 2.718281828459045235360287471352
# Get the absolute value of a number. If x is negative, the returned # Get the absolute value of a number. If x is negative, the returned
# value is positive and equal to `-x`. If x is positive or zero, the # value is positive and equal to `-x`. If x is positive or zero, the
# returned value is x. # returned value is x.
func abs(x: number) number { fn absf(x: float) -> float {
# if the number is negative # if the number is negative
if x < 0 { if x < 0 {
# negate it so it's positive # negate it so it's positive
@ -17,15 +17,23 @@ func abs(x: number) number {
return x return x
} }
fn absi(n: int) -> int {
if n < 0 {
-n
} else {
n
}
}
DERIVE_DX := 0.00000001 DERIVE_DX := 0.00000001
func derive(f: func(number)number, x: number) number { fn derive(f: fn(float) -> float, x: float) float {
return (f(x + DERIVE_DX) - f(x))/DERIVE_DX return (f(x + DERIVE_DX) - f(x))/DERIVE_DX
} }
NEWTONS_ACC := 0.000000000001 NEWTONS_ACC := 0.000000000001
func newtons(f: func(number)number) number { fn newtons(f: fn(float) -> float) -> float {
pg := 0 pg := 0.0
g := 1 g := 1.0
while abs(g - pg) > NEWTONS_ACC { while abs(g - pg) > NEWTONS_ACC {
pg = g pg = g
@ -41,9 +49,9 @@ MAX_SQRT_DX := 0.0000001
# x: number # x: number
# Calculate the approximate square root using newton's method until # Calculate the approximate square root using newton's method until
# the accuracy has increased by less than the variable `MAX_SQRT_DX`. # the accuracy has increased by less than the variable `MAX_SQRT_DX`.
func sqrt(x: number) number { fn sqrt(x: float) -> float {
ng := x ng := x
g := 1 g := 1.0
while abs(g - ng) > MAX_SQRT_DX { while abs(g - ng) > MAX_SQRT_DX {
g = ng g = ng
@ -51,8 +59,6 @@ func sqrt(x: number) number {
# create new guess # create new guess
ng = (g + x / g) / 2 ng = (g + x / g) / 2
} }
return g
} }
# floor(x) # floor(x)
@ -72,7 +78,7 @@ func sqrt(x: number) number {
# round(x) # round(x)
# x: number # x: number
# Return the closest whole number to the value x. # Return the closest whole number to the value x.
func round(x: number) number { fn round(x: float) -> float {
f := floor(x) f := floor(x)
if x - f > 0.5 { if x - f > 0.5 {
@ -86,7 +92,7 @@ func round(x: number) number {
# x: number; any number # x: number; any number
# n: number; the number to divide by # n: number; the number to divide by
# Return the rest from a division of x by n. # Return the rest from a division of x by n.
func mod(x: number, n: number) number { fn mod(x: float, n: float) -> float {
if x == 0 { if x == 0 {
return 0 return 0
} }
@ -110,17 +116,18 @@ func mod(x: number, n: number) number {
# This value is only reasonable if 0<x<1. # This value is only reasonable if 0<x<1.
# It is approximated using the taylor series of e**x. # It is approximated using the taylor series of e**x.
SM_EXP_ACC := 0.00000000001 SM_EXP_ACC := 0.00000000001
func sm_exp(x: number) number { fn sm_exp(x: float) -> float {
p_tot := 0 p_tot := 0.0
tot := 1 tot := 1.0
n := 1 n := 1
x_pow := x x_pow := x
f := 1 f := 1.0
while abs(tot - p_tot) > SM_EXP_ACC { while abs(tot - p_tot) > SM_EXP_ACC {
p_tot = tot p_tot = tot
t := x_pow / f t := x_pow / f
tot = tot + t tot = tot + t
f = f * (n+1) f = f * float(n+1)
x_pow = x_pow * x x_pow = x_pow * x
n = n + 1 n = n + 1
} }
@ -131,22 +138,22 @@ func sm_exp(x: number) number {
# exp(x) # exp(x)
# x: number; any number # x: number; any number
# Get an approximate value of e raised to the power of x. # Get an approximate value of e raised to the power of x.
func exp(x: number) number { fn exp(x: float) -> float {
n := abs(x) n := abs(x)
tot := 1 tot := 1.0
while n >= 1 { while n >= 1 {
tot = tot * E tot = tot * E
n = n - 1 n = n - 1
} }
if n > 0 { if n > 0.0 {
tot = tot * sm_exp(n) tot = tot * sm_exp(n)
} }
if x < 0 { if x < 0 {
return 1/tot 1.0/tot
} else { } else {
return tot tot
} }
} }
@ -155,9 +162,9 @@ func exp(x: number) number {
# Get the approximate value of the natural logarithm # Get the approximate value of the natural logarithm
# This function uses newton's method to approximate. # This function uses newton's method to approximate.
LN_ACC := 0.0000000001 LN_ACC := 0.0000000001
func ln(x: number) number { fn ln(x: float) -> float {
pg := 0 pg := 0.0
g := 1 g := 1.0
while abs(pg - g) > LN_ACC { while abs(pg - g) > LN_ACC {
pg = g pg = g
@ -171,7 +178,7 @@ func ln(x: number) number {
# x: number; any number. The base # x: number; any number. The base
# p: number; the value of the exponent # p: number; the value of the exponent
# Raise any number to any power (x^p) # Raise any number to any power (x^p)
func pow(x: number, p: number) number { fn pow(x: float, p: float) -> float {
return exp(p*ln(x)) return exp(p*ln(x))
} }
@ -181,15 +188,15 @@ func pow(x: number, p: number) number {
# Calculate the approximate value of the logarithm # Calculate the approximate value of the logarithm
# of a with b as base. # of a with b as base.
LOG_ACC := 0.0000001 LOG_ACC := 0.0000001
func log(a: number, b: number) number { fn log(a: float, b: float) -> float {
ln_b := ln(b) ln_b := ln(b)
pg := 0 pg := 0.0
g := 1 g := 1.0
while abs(g - pg) > LOG_ACC { while abs(g - pg) > LOG_ACC {
pg = g pg = g
g = pg - 1/ln_b - a/(ln_b*pow(b, pg)) g = pg - 1.0/ln_b - a/(ln_b*pow(b, pg))
} }
return g return g
@ -199,9 +206,9 @@ func log(a: number, b: number) number {
# x: number; an angle in radians # x: number; an angle in radians
# Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine # Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
# TODO: use hashmap with precomputed values and linear interpolation # TODO: use hashmap with precomputed values and linear interpolation
func sin(x: number) number { fn sin(x: float) -> float {
f := 1 f := 1.0
x = mod(x, 2*PI) x = mod(x, 2.0*PI)
if x > PI { if x > PI {
x = PI - x x = PI - x
f = -1 f = -1
@ -209,13 +216,13 @@ func sin(x: number) number {
# compute sine with a taylor series mock function of sine (valid between -pi and +pi) # compute sine with a taylor series mock function of sine (valid between -pi and +pi)
tot := x tot := x
l := 1 l := 1.0
i := 1 i := 1.0
s := -1 s := -1.0
while i <= 19 { while i <= 19 {
i = i + 2 i = i + 2.0
l = s * l * x / i / (i-1) l = s * l * x / i / (i-1.0)
tot = tot + l tot = tot + l

View file

@ -1,5 +1,5 @@
assertEq(1+1, 2) assertEq(1+1, 2)
assertEq(3*2, 6) assertEq(3*2, 6)
assertEq(3/4, 0.75) assertEq(3.0/4.0, 0.75)
assertEq(2 - 5, -3) assertEq(2 - 5, -3)

View file

@ -3,7 +3,7 @@ assertEq(1, 1)
assertEq(0, 0) assertEq(0, 0)
assertEq("", "") assertEq("", "")
assertEq([]number, []number) assertEq([]int, []int)
assertEq([3, 1, 4, 1], [3, 1, 4, 1]) assertEq([3, 1, 4, 1], [3, 1, 4, 1])
# Inequality # Inequality

View file

@ -1,20 +1,20 @@
list := []number list := []int
x := 1 x := 1
while x <= 1000 { while x <= 1000 {
list.append(x) list.append(x)
assertEq(list.reduce(func(tot: number, a: number) number { assertEq(list.reduce(fn(tot: int, a: int) -> int {
return tot + a return tot + a
}, 0), x*(x + 1)/2) }, 0), x*(x + 1)/2)
x = x + 1 x = x + 1
} }
func sum(a: number, b: number) number { fn sum(a: int, b: int) -> int {
return a + b return a + b
} }
list = []number list = []int
x = 1 x = 1
while x <= 100 { while x <= 100 {
list.append(2*x - 1) list.append(2*x - 1)

View file

@ -3,7 +3,7 @@ fibonacci_numbers := [
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377
] ]
func fib(n: number) number { fn fib(n: int) -> int {
if n < 2 { if n < 2 {
return n return n
} }
@ -29,4 +29,4 @@ while x < fibonacci_numbers.length() {
x = x + 1 x = x + 1
} }
write("") println("")

View file

@ -1,5 +1,5 @@
list := []number list := []int
list.append(1) list.append(1)
list.append(2) list.append(2)

View file

@ -1,12 +1,8 @@
func sum(a: number, b: number) number { fn sum(a: int, b: int) -> int {
return a + b return a + b
} }
breakpoint
assertEq(sum(1, 2), 3) assertEq(sum(1, 2), 3)
breakpoint
assertEq(sum(3, 3), 6) assertEq(sum(3, 3), 6)
breakpoint

View file

@ -1,8 +1,8 @@
assertEq(type(1), "number") assertEq(type(1), "int")
assertEq(type("Hello"), "string") assertEq(type("Hello"), "string")
assertEq(type(true), "boolean") assertEq(type(true), "boolean")
# lists # lists
assertEq(type(["Hello", "world"]), "list[string]") assertEq(type(["Hello", "world"]), "list[string]")
assertEq(type([0, 1]), "list[number]") assertEq(type([0, 1]), "list[int]")