we in era3 boys; overhauled expression system, and variables are now in maps

This commit is contained in:
Neemek 2026-07-09 23:10:49 +02:00
parent bf29e6c3dd
commit 43e450c207
Signed by: neemek
GPG key ID: 84FFE4D7D40AB25E
14 changed files with 945 additions and 452 deletions

View file

@ -8,107 +8,66 @@ import (
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)}},
&IntegerValue{new(big.Int).SetInt64(1)},
0,
},
}, },
}, },
"func": { "func": {
"fn sum(a: int, b: int) -> int {\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",
&IntegerSignature{},
},
{
"b",
&IntegerSignature{},
},
},
Chunk: &Chunk{
Bytecode: []Bytecode{
InstructionDescend,
InstructionGetLocal, 0,
InstructionGetLocal, 1,
InstructionAddInt,
InstructionReturn,
InstructionAscend,
},
Constants: []Value{&StringValue{"a"}, &StringValue{"b"}},
},
},
0,
},
},
}, },
"list": { "list": {
"a := [1.0, 2.0]", "a := [1.0, 2.0]\n{}",
[]Value{ []Value{&NilValue{}},
&VariableValue{ []map[string]Value{
"a", {"a": &ListValue{
&ListValue{
[]Value{ []Value{
&FloatValue{1}, &FloatValue{1},
&FloatValue{2}, &FloatValue{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)}, &IntegerValue{big.NewInt(1)},
&IntegerValue{big.NewInt(2)}, &IntegerValue{big.NewInt(2)},
&IntegerValue{big.NewInt(3)}, &IntegerValue{big.NewInt(3)},
}, },
}, }},
0,
},
}, },
}, },
"list_concat": { "list_concat": {
"a := [1.0, 2.0]\nb := a + [3.0]", "a := [1.0, 2.0]\nb := a + [3.0]\nnil",
[]Value{ []Value{&NilValue{}},
&VariableValue{ []map[string]Value{
"a", {
&ListValue{ "a": &ListValue{
[]Value{ []Value{
&FloatValue{1}, &FloatValue{1},
&FloatValue{2}, &FloatValue{2},
}, },
}, },
0, "b": &ListValue{
},
&VariableValue{
"b",
&ListValue{
[]Value{ []Value{
&FloatValue{1}, &FloatValue{1},
&FloatValue{2}, &FloatValue{2},
&FloatValue{3}, &FloatValue{3},
}, },
}, },
0,
}, },
}, },
}, },
@ -160,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

@ -18,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]
} }
@ -129,6 +132,7 @@ func NewCompiler(source []rune) *Compiler {
nil, nil,
source, source,
[]CompilerError{}, []CompilerError{},
false,
NewStack[LocalVariable](256), NewStack[LocalVariable](256),
} }
@ -169,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()
@ -205,7 +213,7 @@ func (c *Compiler) compile(tree Node) error {
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
@ -234,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
@ -277,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()
@ -298,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
@ -336,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
if n.otherwise != nil {
// this would jump over the else/otherwise block in the code // this would jump over the else/otherwise block in the code
c.add(InstructionJump) c.add(InstructionJump)
jumpOverElse = c.ip jumpOverElse := c.ip
c.advance(2) 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))
@ -352,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)
@ -368,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
@ -383,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 {
@ -396,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
@ -412,6 +433,26 @@ func (c *Compiler) compile(tree Node) error {
case AssignNodeType: case AssignNodeType:
n := tree.(*AssignNode) n := tree.(*AssignNode)
switch n.dest.Type() {
case ReferenceNodeType:
d := n.dest.(*ReferenceNode)
if d.name == "_" {
return c.compile(n.value)
}
if n.declare && c.isVarDeclaredHere(d.name) {
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
}
default:
return c.error(fmt.Sprintf("cannot assign to %s", n.dest.Type()), n.dest)
}
/*
if n.name == "_" { if n.name == "_" {
// allow non-ish statements // allow non-ish statements
err := c.compile(n.value) err := c.compile(n.value)
@ -429,9 +470,10 @@ func (c *Compiler) compile(tree Node) error {
return err return err
} }
} }
*/
case CallNodeType: case InvokeNodeType:
n := tree.(*CallNode) n := tree.(*InvokeNode)
s, err := c.deduceSignature(n.source) s, err := c.deduceSignature(n.source)
if err != nil { if err != nil {
@ -443,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)
} }
@ -478,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
@ -501,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)
@ -588,7 +622,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
@ -825,8 +859,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
@ -989,8 +1023,12 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
case AssignNodeType: case AssignNodeType:
n := tree.(*AssignNode) n := tree.(*AssignNode)
switch n.dest.Type() {
case ReferenceNodeType:
name := n.dest.(*ReferenceNode).name
if !n.declare { if !n.declare {
prev, err := c.getVarSignature(n.name, n) prev, err := c.getVarSignature(name, n)
if err != nil { if err != nil {
return err return err
} }
@ -1001,7 +1039,7 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
} }
if !sig.Matches(prev) { if !sig.Matches(prev) {
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(fmt.Sprintf("cannot assign value of type %s to variable %s of type %s", sig, name, prev), n.value)
} }
return nil return nil
@ -1011,7 +1049,11 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
if err != nil { if err != nil {
return err return err
} }
c.registerVar(n.name, sig) c.registerVar(name, sig)
default:
return c.error("can neither assign nor declare to", n.dest)
}
default: default:
} }
@ -1114,13 +1156,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
@ -1202,7 +1244,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

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,21 +55,21 @@ 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},
&FloatNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
@ -84,7 +85,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -117,7 +118,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
@ -133,7 +134,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -166,7 +167,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
@ -182,7 +183,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -196,7 +197,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
@ -228,7 +229,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
@ -244,7 +245,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -258,7 +259,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FloatNode{ &FloatNode{
2, 2,
0, 0, 0, 0,
@ -290,7 +291,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&FloatNode{ &FloatNode{
@ -325,7 +326,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"sum", &ReferenceNode{name: "sum"},
&FunctionNode{ &FunctionNode{
"sum", "sum",
[]FunctionParameter{ []FunctionParameter{
@ -411,7 +412,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"a", "a",
[]FunctionParameter{}, []FunctionParameter{},
@ -419,7 +420,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&FloatNode{ &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,
}, },
}, },
@ -488,7 +488,7 @@ 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{
&FloatNode{value: 1}, &FloatNode{value: 1},
@ -498,7 +498,7 @@ func GetCompileTestData() map[string]CompileTestData {
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"},
@ -540,7 +540,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
name: "a", dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{ value: &UnaryNode{
UnaryNegate, UnaryNegate,
&FloatNode{ &FloatNode{
@ -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 {
}, },
}, },
}, },
*/
} }
} }
@ -642,6 +643,7 @@ 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,7 +693,7 @@ 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 {

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
@ -71,6 +71,7 @@ const (
TokenPipe TokenPipe
TokenDoublePipe TokenDoublePipe
TokenNewLine
TokenBreakpoint TokenBreakpoint
TokenEOF TokenEOF
TokenError TokenError
@ -168,6 +169,8 @@ func (t TokenType) String() string {
return "hexadecimal" return "hexadecimal"
case TokenArrow: case TokenArrow:
return "arrow" return "arrow"
case TokenNewLine:
return "newline"
} }
panic("UNDEFINED TOKENTYPE STRING CONVERSION") panic("UNDEFINED TOKENTYPE STRING CONVERSION")
@ -212,6 +215,8 @@ 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 '-':
@ -395,11 +400,11 @@ func (l *Lexer) NextToken() (Token, error) {
} }
} }
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,
} }
@ -421,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 {
@ -470,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

@ -37,17 +37,17 @@ func GetLexerTestData() map[string]LexerTestData {
"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, TokenInteger, 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, TokenInteger, TokenStar, TokenInteger, TokenSlash, TokenInteger, TokenGreaterThan, TokenInteger, 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,
}, },
}, },
@ -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\")",
@ -93,9 +93,9 @@ func GetLexerTestData() map[string]LexerTestData {
"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, TokenInteger, TokenStar, TokenInteger, TokenName, TokenAssign, TokenInteger, TokenStar, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenInteger, TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenInteger, TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenInteger, TokenNewLine,
TokenBang, TokenName, TokenEquals, TokenName, TokenEOF, TokenBang, TokenName, TokenEquals, TokenName, TokenEOF,
}, },
}, },
@ -103,14 +103,14 @@ func GetLexerTestData() map[string]LexerTestData {
"fn 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, TokenInteger, TokenOpenBrace, TokenWhile, TokenName, TokenLessThan, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenPlus, TokenInteger, TokenCloseBrace, TokenName, TokenAssign, TokenName, TokenPlus, TokenInteger, TokenNewLine, TokenCloseBrace, TokenEOF,
}, },
}, },
"lambda": { "lambda": {
@ -119,7 +119,7 @@ func GetLexerTestData() map[string]LexerTestData {
"}", "}",
[]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": {

View file

@ -28,12 +28,14 @@ const (
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
@ -65,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"
@ -73,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"
} }
@ -192,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
@ -281,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())
@ -479,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
@ -492,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
@ -514,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

@ -62,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')
@ -82,6 +82,7 @@ type Parser struct {
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 {
@ -143,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
@ -164,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
@ -176,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
@ -220,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,
start + p.prev.End,
}
if name != nil {
return &AssignNode{
&ReferenceNode{name.Lexeme, name.Start, name.End},
fn,
true,
start,
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 := l.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:
@ -228,7 +606,7 @@ 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 TokenInteger: case TokenInteger:
@ -242,7 +620,7 @@ func (p *Parser) factor() (Node, error) {
return &IntegerNode{ return &IntegerNode{
num, num,
p.prev.Start, p.prev.Start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenFloat: case TokenFloat:
@ -256,7 +634,7 @@ func (p *Parser) factor() (Node, error) {
return &FloatNode{ 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:
@ -270,7 +648,7 @@ func (p *Parser) factor() (Node, error) {
return &IntegerNode{ return &IntegerNode{
num, num,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
}, nil }, nil
case TokenTrue: case TokenTrue:
@ -278,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:
@ -308,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 {
@ -328,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
@ -348,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:
@ -364,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()
@ -379,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
} }
@ -426,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:
@ -441,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)
} }
} }
@ -465,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
@ -475,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,
} }
} }
} }
@ -512,7 +901,7 @@ func (p *Parser) product() (Node, error) {
left, left,
f, f,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
} }
@ -544,7 +933,7 @@ func (p *Parser) term() (Node, error) {
left, left,
pr, pr,
start, start,
p.prev.Start + p.prev.Length, p.prev.End,
} }
} }
@ -591,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
} }
@ -625,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
} }
@ -664,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() )
@ -689,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
@ -700,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,
} }
} }
} }
@ -717,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
@ -735,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
} }
@ -754,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 {
@ -775,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:
@ -807,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:
@ -822,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:
@ -872,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
} }
@ -884,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
} }
@ -893,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
} }
@ -1002,6 +1396,16 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
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

View file

@ -63,7 +63,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&FloatNode{ &FloatNode{
@ -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!\"",
@ -118,7 +118,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&FloatNode{ &FloatNode{
@ -173,7 +173,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinarySubtraction, BinarySubtraction,
&BinaryNode{ &BinaryNode{
@ -253,7 +253,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"_", &ReferenceNode{"_", 0, 0},
&BinaryNode{ &BinaryNode{
BinaryEquality, BinaryEquality,
&FloatNode{ &FloatNode{
@ -304,7 +304,7 @@ func GetTokenTestData() map[string]TokenTestData {
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -357,7 +357,7 @@ func GetTokenTestData() map[string]TokenTestData {
do: &BlockNode{ do: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&FloatNode{ &FloatNode{
1, 1,
0, 0, 0, 0,
@ -371,7 +371,7 @@ func GetTokenTestData() map[string]TokenTestData {
otherwise: &BlockNode{ otherwise: &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"b", &ReferenceNode{"b", 0, 0},
&FloatNode{ &FloatNode{
0, 0,
0, 0, 0, 0,
@ -432,7 +432,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"*", "*",
[]FunctionParameter{ []FunctionParameter{
@ -503,7 +503,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"a", &ReferenceNode{"a", 0, 0},
&FunctionNode{ &FunctionNode{
"a", "a",
[]FunctionParameter{ []FunctionParameter{
@ -559,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",
@ -607,7 +607,7 @@ func GetTokenTestData() map[string]TokenTestData {
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
"data", &ReferenceNode{"data", 0, 0},
&ListNode{ &ListNode{
[]Node{ []Node{
&ReferenceNode{ &ReferenceNode{
@ -749,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)
@ -762,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:
@ -958,7 +951,7 @@ func TestParser_Parse(t *testing.T) {
tree, err := p.Parse("") tree, err := p.Parse("")
if err != nil { if err != nil {
t.Fatalf("Unexpected error(s): %s", err.(ParsingError).Description) t.Fatalf("Unexpected error(s): %s", err.(ParsingError).Format())
} }
t.Logf("Checking parsed tree") t.Logf("Checking parsed tree")

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

@ -610,7 +610,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 {
@ -619,7 +619,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
} }
@ -675,43 +674,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

@ -70,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)

View file

@ -104,6 +104,8 @@ const (
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1) // InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
InstructionSwap InstructionSwap
// InstructionDuplicate push a copy of the item on top of the stack (1 -> 1, 1)
InstructionDuplicate
// InstructionAnd pop two booleans and push true if both are true // InstructionAnd pop two booleans and push true if both are true
InstructionAnd InstructionAnd
@ -234,6 +236,8 @@ func (b Bytecode) String() string {
return "ACCESS_PROPERTY" return "ACCESS_PROPERTY"
case InstructionConcatLists: case InstructionConcatLists:
return "CONCAT_LISTS" return "CONCAT_LISTS"
case InstructionDuplicate:
return "DUPLICATE"
} }
return "UNDEFINED" return "UNDEFINED"
} }
@ -267,6 +271,30 @@ func (c Chunk) String() string {
return b.String() return b.String()
} }
func (c *Chunk) Equals(other *Chunk) bool {
if len(c.Bytecode) != len(other.Bytecode) {
return false
}
for i, bc := range c.Bytecode {
if other.Bytecode[i] != bc {
return false
}
}
if len(c.Constants) != len(other.Constants) {
return false
}
for i := 0; i < len(c.Constants); i++ {
if other.Constants[i] != c.Constants[i] {
return false
}
}
return true
}
func NewChunk(bytecode []Bytecode, constants []Value) *Chunk { func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
return &Chunk{bytecode, constants} return &Chunk{bytecode, constants}
} }
@ -328,22 +356,25 @@ type VM struct {
// instruction pointer // instruction pointer
ip Pos ip Pos
scope Pos
// global variable storage // global variable storage
globals map[string]Value globals map[string]Value
variableEnd Pos // local variable storage
scope *Scope
stack *Stack[Value] stack *Stack[Value]
call *Stack[Call] call *Stack[Call]
} }
type Scope struct {
current map[string]Value
parent *Scope
}
type Call struct { type Call struct {
chunk *Chunk chunk *Chunk
ip Pos ip Pos
stackEnd Pos scope *Scope
variableEnd Pos
scope Pos
} }
var DefaultGlobals = map[string]Value{ var DefaultGlobals = map[string]Value{
@ -355,7 +386,7 @@ var DefaultGlobals = map[string]Value{
}, },
func(_ *VM, this Value, v []Value) (Value, error) { func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String()) println(v[0].String())
return nil, nil return &NilValue{}, nil
}, },
nil, nil,
false, false,
@ -368,7 +399,7 @@ var DefaultGlobals = map[string]Value{
}, },
func(_ *VM, this Value, v []Value) (Value, error) { func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String()) print(v[0].String())
return nil, nil return &NilValue{}, nil
}, },
nil, nil,
false, false,
@ -630,6 +661,9 @@ func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
call: NewStack[Call](callstackSize), call: NewStack[Call](callstackSize),
globals: DefaultGlobals, globals: DefaultGlobals,
scope: &Scope{
current: map[string]Value{},
},
} }
return vm return vm
@ -646,23 +680,19 @@ func (vm *VM) Next() bool {
case InstructionReturn: case InstructionReturn:
if vm.call.Current == 0 { if vm.call.Current == 0 {
return false return false
} else { }
v := vm.stack.Pop() v := vm.stack.Pop()
c := vm.call.Pop() c := vm.call.Pop()
// reset stack current and variable end and scope // reset stack current and variable end and scope
vm.variableEnd = c.variableEnd
vm.stack.Current = c.stackEnd
vm.scope = c.scope vm.scope = c.scope
// reset to calling position // reset to calling position
vm.ip = c.ip vm.ip = c.ip
vm.chunk = c.chunk vm.chunk = c.chunk
vm.purgeVars()
vm.stack.Push(v) vm.stack.Push(v)
}
case InstructionPop: case InstructionPop:
vm.stack.Pop() vm.stack.Pop()
@ -807,26 +837,19 @@ func (vm *VM) Next() bool {
vm.call.Push(Call{ vm.call.Push(Call{
chunk: vm.chunk, chunk: vm.chunk,
ip: vm.ip, ip: vm.ip,
stackEnd: vm.stack.Current - Pos(len(f.Params)),
variableEnd: vm.variableEnd,
scope: vm.scope, scope: vm.scope,
}) })
vm.descend()
for i := len(f.Params) - 1; i >= 0; i-- { for i := len(f.Params) - 1; i >= 0; i-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i) vm.addVar(f.Params[i].Name, vm.stack.Pop())
vm.stack.items[p] = &VariableValue{
f.Params[i].Name,
vm.stack.items[p],
vm.scope,
}
} }
if f.Parent != nil { if f.Parent != nil {
vm.addVar("this", f.Parent) vm.addVar("this", f.Parent)
} }
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk vm.chunk = f.Chunk
vm.ip = 0 vm.ip = 0
case *BuiltinFunctionValue: case *BuiltinFunctionValue:
@ -868,24 +891,18 @@ func (vm *VM) Next() bool {
return false return false
} }
vm.stack.Push(v.value) vm.stack.Push(v)
case InstructionSetLocal: case InstructionSetLocal:
value := vm.stack.Pop().(Value) value := vm.stack.Peek().Clone()
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name) vm.setVar(name, value)
if v == nil {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
}
v.value = value.Clone()
case InstructionDeclareLocal: case InstructionDeclareLocal:
vm.addVar( vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).Text, vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().Clone(), vm.stack.Peek().Clone(),
) )
case InstructionGetGlobal: case InstructionGetGlobal:
@ -954,6 +971,9 @@ func (vm *VM) Next() bool {
vm.stack.Push(r, l) vm.stack.Push(r, l)
case InstructionDuplicate:
vm.stack.Push(vm.stack.Peek().Clone())
case InstructionAccessProperty: case InstructionAccessProperty:
source := vm.stack.Pop() source := vm.stack.Pop()
property := vm.ReadConstant() property := vm.ReadConstant()
@ -973,6 +993,7 @@ func (vm *VM) Next() bool {
vm.stack.Push(member) vm.stack.Push(member)
case InstructionBreakpoint: case InstructionBreakpoint:
vm.stack.Push(&NilValue{})
default: default:
panic("invalid byte code") panic("invalid byte code")
@ -987,8 +1008,6 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.call.Push(Call{ vm.call.Push(Call{
chunk: vm.chunk, chunk: vm.chunk,
ip: vm.ip, ip: vm.ip,
stackEnd: vm.stack.Current,
variableEnd: vm.variableEnd,
scope: vm.scope, scope: vm.scope,
}) })
@ -1000,8 +1019,6 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.addVar("this", f.Parent) vm.addVar("this", f.Parent)
} }
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk vm.chunk = f.Chunk
vm.ip = 0 vm.ip = 0
@ -1028,6 +1045,14 @@ func (vm *VM) TryNextByte() (Bytecode, error) {
return 0, errors.New("there are no more instructions") return 0, errors.New("there are no more instructions")
} }
for int(vm.ip) >= len(vm.chunk.Bytecode) && vm.call.Current > 0 {
c := vm.call.Pop()
vm.ip = c.ip
vm.chunk = c.chunk
vm.scope = c.scope
}
v := vm.chunk.Bytecode[vm.ip] v := vm.chunk.Bytecode[vm.ip]
vm.ip++ vm.ip++
@ -1045,53 +1070,55 @@ func (vm *VM) NextByte() Bytecode {
} }
func (vm *VM) ascend() { func (vm *VM) ascend() {
vm.scope-- if vm.scope.parent == nil {
if vm.scope < 0 {
panic("invalid scope") panic("invalid scope")
} }
vm.purgeVars() vm.scope = vm.scope.parent
}
// purgeVars remove all variables not within scope
func (vm *VM) purgeVars() {
for ; vm.variableEnd > 0 && vm.stack.items[vm.variableEnd-1].(*VariableValue).scope > vm.scope; vm.variableEnd-- {
vm.stack.Pop()
}
} }
func (vm *VM) descend() { func (vm *VM) descend() {
vm.scope++ old := vm.scope
vm.scope = &Scope{
map[string]Value{},
old,
}
} }
func (vm *VM) addVar(name string, value Value) { func (vm *VM) addVar(name string, value Value) {
vm.variableEnd++ vm.scope.current[name] = value
vm.stack.Push(&VariableValue{
name,
value,
vm.scope,
})
} }
func (vm *VM) getVar(name string) *VariableValue { func (vm *VM) getVar(name string) Value {
for i := vm.variableEnd - 1; i >= 0; i-- { s := vm.scope
v, ok := vm.stack.items[i].(*VariableValue)
if !ok { for s != nil {
continue if v, ok := s.current[name]; ok {
}
if v.name == name {
return v return v
} }
s = s.parent
} }
return nil return nil
} }
func (vm *VM) setVar(name string, v Value) {
s := vm.scope
for s != nil {
if _, ok := s.current[name]; ok {
s.current[name] = v
break
}
s = s.parent
}
}
func (vm *VM) HasNext() bool { func (vm *VM) HasNext() bool {
return vm.ip < Pos(len(vm.chunk.Bytecode)) return vm.ip < Pos(len(vm.chunk.Bytecode)) || vm.call.Current > 0
} }
func (vm *VM) GetConstant(id Bytecode) Value { func (vm *VM) GetConstant(id Bytecode) Value {

View file

@ -95,10 +95,12 @@ 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{
@ -112,6 +114,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{3}, &FloatValue{3},
}, },
[]map[string]Value{},
}, },
"push_constant": { "push_constant": {
NewChunk( NewChunk(
@ -125,6 +128,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{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": {
@ -187,6 +195,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{3.75}, &FloatValue{3.75},
}, },
[]map[string]Value{},
}, },
"equality_true": { "equality_true": {
NewChunk( NewChunk(
@ -202,6 +211,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{true}, &BoolValue{true},
}, },
[]map[string]Value{},
}, },
"equality_false": { "equality_false": {
NewChunk( NewChunk(
@ -217,6 +227,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"inequality_false": { "inequality_false": {
NewChunk( NewChunk(
@ -232,6 +243,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&BoolValue{false}, &BoolValue{false},
}, },
[]map[string]Value{},
}, },
"inequality_true": { "inequality_true": {
NewChunk( NewChunk(
@ -247,6 +259,7 @@ func GetExecutionTestData() map[string]struct {
[]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(
@ -286,6 +301,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{1}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"jump_false/false": { "jump_false/false": {
NewChunk( NewChunk(
@ -302,6 +318,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{1}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"jump_false/true": { "jump_false/true": {
NewChunk( NewChunk(
@ -318,6 +335,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{0}, &FloatValue{1}, &FloatValue{0}, &FloatValue{1},
}, },
[]map[string]Value{},
}, },
"declare_local": { "declare_local": {
NewChunk( NewChunk(
@ -329,11 +347,10 @@ func GetExecutionTestData() map[string]struct {
&FloatValue{0}, &StringValue{"a"}, &FloatValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{&FloatValue{0}},
&VariableValue{ []map[string]Value{
"a", {
&FloatValue{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{
&FloatValue{0}, &StringValue{"a"}, &FloatValue{1}, &FloatValue{0}, &StringValue{"a"}, &FloatValue{1},
}, },
), ),
[]Value{ []Value{},
&VariableValue{ []map[string]Value{
"a", {
&FloatValue{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{
&FloatValue{0}, &StringValue{"a"}, &FloatValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{
&VariableValue{
"a",
&FloatValue{0}, &FloatValue{0},
0,
}, },
&FloatValue{0}, []map[string]Value{
{
"a": &FloatValue{0},
},
}, },
}, },
"get_reassigned_local": { "get_reassigned_local": {
@ -382,9 +399,11 @@ 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{
@ -392,26 +411,29 @@ func GetExecutionTestData() map[string]struct {
}, },
), ),
[]Value{ []Value{
&VariableValue{
"a",
&FloatValue{1},
0,
},
&FloatValue{0}, &FloatValue{0},
&FloatValue{1}, &FloatValue{1},
}, },
[]map[string]Value{
{
"a": &FloatValue{1},
},
},
}, },
"variable_scope": { "variable_scope": {
NewChunk( NewChunk(
[]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,
}, },
@ -421,11 +443,10 @@ func GetExecutionTestData() map[string]struct {
&FloatValue{2}, &StringValue{"c"}, &FloatValue{2}, &StringValue{"c"},
}, },
), ),
[]Value{ []Value{},
&VariableValue{ []map[string]Value{
"a", {
&FloatValue{0}, "a": &FloatValue{0},
0,
}, },
}, },
}, },
@ -469,12 +490,14 @@ func GetExecutionTestData() map[string]struct {
[]Value{ []Value{
&FloatValue{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,
@ -533,9 +556,11 @@ 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{
{ {
@ -555,9 +580,7 @@ func GetExecutionTestData() map[string]struct {
}, },
), ),
}, },
0,
}, },
&FloatValue{5},
}, },
}, },
"list_concat": { "list_concat": {
@ -590,6 +613,7 @@ func GetExecutionTestData() map[string]struct {
}, },
}, },
}, },
[]map[string]Value{},
}, },
} }
} }

View file

@ -1,6 +1,10 @@
fn double(a: int) -> int { fn double(a: int) -> int {
return 2*a 2*a
} }
a := 1
a = 2
println(double(2) == 4) println(double(2) == 4)