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
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.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
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/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/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=

View file

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

View file

@ -1,113 +1,73 @@
package core
import (
"math/big"
"testing"
)
type AllTestCase struct {
src string
expectedStack []Value
expectedScope []map[string]Value
}
func GetAllTestCases() map[string]AllTestCase {
return map[string]AllTestCase{
"constant_number": {
"a := 1",
[]Value{
&VariableValue{
"a",
&NumberValue{1},
0,
},
[]Value{&IntegerValue{new(big.Int).SetInt64(1)}},
[]map[string]Value{
{"a": &IntegerValue{new(big.Int).SetInt64(1)}},
},
},
"func": {
"func sum(a: number, b: number) number {\n\treturn a + b\n}\n_ = sum(1, 2)",
[]Value{
&VariableValue{
"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,
},
},
"fn sum(a: int, b: int) -> int {\n\treturn a + b\n}\nres := sum(1, 2)",
[]Value{&IntegerValue{new(big.Int).SetInt64(3)}},
[]map[string]Value{},
},
"list": {
"a := [1, 2]",
"a := [1.0, 2.0]\n{}",
[]Value{&NilValue{}},
[]map[string]Value{
{"a": &ListValue{
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
&FloatValue{1},
&FloatValue{2},
},
}},
},
},
"constant_list_concat": {
"a := [1, 2] + [3]",
"a := [1, 2] + [3]\n{}",
[]Value{&NilValue{}},
[]map[string]Value{
{"a": &ListValue{
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
&IntegerValue{big.NewInt(1)},
&IntegerValue{big.NewInt(2)},
&IntegerValue{big.NewInt(3)},
},
}},
},
},
"list_concat": {
"a := [1, 2]\nb := a + [3]",
"a := [1.0, 2.0]\nb := a + [3.0]\nnil",
[]Value{&NilValue{}},
[]map[string]Value{
{
"a": &ListValue{
[]Value{
&VariableValue{
"a",
&ListValue{
&FloatValue{1},
&FloatValue{2},
},
},
"b": &ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&FloatValue{1},
&FloatValue{2},
&FloatValue{3},
},
},
0,
},
&VariableValue{
"b",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
@ -159,7 +119,13 @@ func TestAll(t *testing.T) {
}
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 (
"fmt"
"math/big"
"strings"
)
@ -17,6 +18,9 @@ type Compiler struct {
source []rune
Warnings []CompilerError
// optimize Whether to attempt some optimization of the emitted bytecode
optimize bool
stack *Stack[LocalVariable]
}
@ -128,6 +132,7 @@ func NewCompiler(source []rune) *Compiler {
nil,
source,
[]CompilerError{},
false,
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 {
return err
}
if i != len(p.Block.statements)-1 {
c.add(InstructionPop)
}
}
c.fileStack.Pop()
@ -191,16 +200,20 @@ func (c *Compiler) compile(tree Node) error {
tree.(*StringNode).value,
})
case NumberNodeType:
case FloatNodeType:
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:
l := tree.(*ListNode)
if len(l.items) == 0 {
c.add(InstructionNewList)
} else if c.isTreeConstant(l) {
} else if c.optimize && c.isTreeConstant(l) {
v, err := c.compute(l)
if err != nil {
panic(err) // this shouldn't happen
@ -229,7 +242,7 @@ func (c *Compiler) compile(tree Node) error {
}
case UnaryNodeType:
if c.isTreeConstant(tree.(*UnaryNode).value) {
if c.optimize && c.isTreeConstant(tree.(*UnaryNode).value) {
v, err := c.compute(tree)
if err != nil {
return err
@ -243,9 +256,19 @@ func (c *Compiler) compile(tree Node) error {
return err
}
vt, err := c.deduceSignature(tree.(*UnaryNode).value)
if err != nil {
return err
}
switch tree.(*UnaryNode).UnaryOperation {
case UnaryNegate:
c.add(InstructionNegate)
if vt.Type() == TypeInteger {
c.add(InstructionNegateInt)
} else {
c.add(InstructionNegateFloat)
}
case UnaryNot:
c.add(InstructionNot)
}
@ -262,12 +285,21 @@ func (c *Compiler) compile(tree Node) error {
c.add(InstructionNil)
case BlockNodeType:
if len(tree.(*BlockNode).statements) == 0 {
c.add(InstructionNil)
return nil
}
c.addDescend()
for _, n := range tree.(*BlockNode).statements {
for i, n := range tree.(*BlockNode).statements {
err := c.compile(n)
if err != nil {
return err
}
if i != len(tree.(*BlockNode).statements)-1 {
c.add(InstructionPop)
}
}
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)
}
if c.isTreeConstant(n.condition) {
if c.optimize && c.isTreeConstant(n.condition) {
v, err := c.compute(n.condition)
if err != nil {
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
var jumpOverElse Pos
if n.otherwise != nil {
// this would jump over the else/otherwise block in the code
c.add(InstructionJump)
jumpOverElse = c.ip
jumpOverElse := c.ip
c.advance(2)
}
// put the u16 of where to jump if the condition was false
c.putU16(jumpByPos, uint16(c.ip-jumpByPos-2))
@ -337,9 +366,12 @@ func (c *Compiler) compile(tree Node) error {
if err != nil {
return err
}
c.putU16(jumpOverElse, uint16(c.ip-jumpOverElse-2))
} else {
c.add(InstructionNil)
}
c.putU16(jumpOverElse, uint16(c.ip-jumpOverElse-2))
case LoopNodeType:
n := tree.(*LoopNode)
@ -353,7 +385,7 @@ func (c *Compiler) compile(tree Node) error {
}
alwaysLoop := false
if c.isTreeConstant(n.condition) {
if c.optimize && c.isTreeConstant(n.condition) {
v, err := c.compute(n.condition)
if err != nil {
return err
@ -368,6 +400,8 @@ func (c *Compiler) compile(tree Node) error {
}
}
c.add(InstructionNil)
conditionPos := c.ip
jumpValuePos := Pos(0)
if !alwaysLoop {
@ -381,6 +415,8 @@ func (c *Compiler) compile(tree Node) error {
c.advance(2)
}
c.add(InstructionPop)
err = c.compile(n.do)
if err != nil {
return err
@ -397,6 +433,26 @@ func (c *Compiler) compile(tree Node) error {
case AssignNodeType:
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 == "_" {
// allow non-ish statements
err := c.compile(n.value)
@ -414,9 +470,10 @@ func (c *Compiler) compile(tree Node) error {
return err
}
}
*/
case CallNodeType:
n := tree.(*CallNode)
case InvokeNodeType:
n := tree.(*InvokeNode)
s, err := c.deduceSignature(n.source)
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)
}
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) {
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)
}
if c.isTreeConstant(arg) {
if c.optimize && c.isTreeConstant(arg) {
v, err := c.compute(arg)
if err != nil {
return err
@ -486,10 +539,6 @@ func (c *Compiler) compile(tree Node) error {
c.add(InstructionCall)
if !n.keep {
c.add(InstructionPop)
}
case FunctionNodeType:
n := tree.(*FunctionNode)
@ -538,6 +587,7 @@ func (c *Compiler) compile(tree Node) error {
n.yield,
c.Chunk,
nil,
nil,
}
// restore old chunk and ip
@ -573,7 +623,7 @@ func (c *Compiler) compile(tree Node) error {
}
func (c *Compiler) compileBinary(binary *BinaryNode) error {
if c.isTreeConstant(binary) {
if c.optimize && c.isTreeConstant(binary) {
v, err := c.compute(binary)
if err != nil {
return err
@ -593,38 +643,77 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
return err
}
switch binary.BinaryOperation {
case BinaryAddition:
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 {
case BinaryAddition:
if res.Type() == TypeString {
c.add(InstructionStringConcatenation)
} else if res.Type() == TypeList {
c.add(InstructionConcatLists)
} else if res.Type() == TypeFloat {
c.add(InstructionAddFloat)
} else if res.Type() == TypeInteger {
c.add(InstructionAddInt)
} else {
c.add(InstructionAdd)
return c.error("unimplemented binary compilation", binary)
}
case BinarySubtraction:
c.add(InstructionSub)
if res.Type() == TypeFloat {
c.add(InstructionSubFloat)
} else {
c.add(InstructionSubInt)
}
case BinaryMultiplication:
c.add(InstructionMul)
if res.Type() == TypeFloat {
c.add(InstructionMulFloat)
} else {
c.add(InstructionMulInt)
}
case BinaryDivision:
c.add(InstructionDiv)
if res.Type() == TypeFloat {
c.add(InstructionDivFloat)
} else {
c.add(InstructionDivInt)
}
case BinaryEquality:
c.add(InstructionEquals)
case BinaryInequality:
c.add(InstructionNotEqual)
case BinaryLess:
c.add(InstructionLess)
if inType.Type() == TypeFloat {
c.add(InstructionLessFloat)
} else {
c.add(InstructionLessInt)
}
case BinaryGreater:
c.add(InstructionGreater)
if inType.Type() == TypeFloat {
c.add(InstructionGreaterFloat)
} else {
c.add(InstructionGreaterInt)
}
case BinaryLessEqual:
c.add(InstructionLessOrEqual)
if inType.Type() == TypeFloat {
c.add(InstructionLessOrEqualFloat)
} else {
c.add(InstructionLessOrEqualInt)
}
case BinaryGreaterEqual:
c.add(InstructionGreaterOrEqual)
if inType.Type() == TypeFloat {
c.add(InstructionGreaterOrEqualFloat)
} else {
c.add(InstructionGreaterOrEqualInt)
}
case BinaryAnd:
c.add(InstructionAnd)
case BinaryOr:
@ -638,8 +727,10 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch tree.Type() {
case StringNodeType:
return &StringSignature{}, nil
case NumberNodeType:
return &NumberSignature{}, nil
case FloatNodeType:
return &FloatSignature{}, nil
case IntegerNodeType:
return &IntegerSignature{}, nil
case ReferenceNodeType:
n := tree.(*ReferenceNode)
sig, err := c.getVarSignature(n.name, n)
@ -695,17 +786,23 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch n.BinaryOperation {
case BinarySubtraction, BinaryMultiplication, BinaryDivision:
if l.Type() != TypeNumber {
return nil, c.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l), n)
if l.Type() == TypeInteger {
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:
switch l.Type() {
case TypeString:
return &StringSignature{}, nil
case TypeNumber:
return &NumberSignature{}, nil
case TypeInteger:
return &IntegerSignature{}, nil
case TypeFloat:
return &FloatSignature{}, nil
case TypeList:
return &ListSignature{
l.(*ListSignature).Contents,
@ -722,7 +819,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
case BinaryEquality, BinaryInequality:
return &BooleanSignature{}, nil
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)
}
@ -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)
}
case CallNodeType:
n := tree.(*CallNode)
case InvokeNodeType:
n := tree.(*InvokeNode)
sig, err := c.deduceSignature(n.source)
if err != nil {
return nil, err
@ -852,10 +949,13 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch n.UnaryOperation {
case UnaryNegate:
if sig.Type() != TypeNumber {
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be number)", n.UnaryOperation), n)
if sig.Type() == TypeFloat {
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:
if sig.Type() != TypeBoolean {
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)
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:
return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree)
}
@ -929,8 +1038,12 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
case AssignNodeType:
n := tree.(*AssignNode)
switch n.dest.Type() {
case ReferenceNodeType:
name := n.dest.(*ReferenceNode).name
if !n.declare {
prev, err := c.getVarSignature(n.name, n)
prev, err := c.getVarSignature(name, n)
if err != nil {
return err
}
@ -941,7 +1054,7 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
}
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
@ -951,7 +1064,11 @@ func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
if err != nil {
return err
}
c.registerVar(n.name, sig)
c.registerVar(name, sig)
default:
return c.error("can neither assign nor declare to", n.dest)
}
default:
}
@ -1040,7 +1157,7 @@ func (c *Compiler) isLocal(name string) bool {
// isTreeConstant check if a node tree is constant (predictable)
func (c *Compiler) isTreeConstant(tree Node) bool {
switch tree.Type() {
case StringNodeType, NumberNodeType, BooleanNodeType, NilNodeType:
case StringNodeType, FloatNodeType, IntegerNodeType, BooleanNodeType, NilNodeType:
return true
case ListNodeType:
for _, item := range tree.(*ListNode).items {
@ -1054,13 +1171,13 @@ func (c *Compiler) isTreeConstant(tree Node) bool {
return c.isTreeConstant(tree.(*UnaryNode).value)
case BinaryNodeType:
return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right)
case CallNodeType:
for _, arg := range tree.(*CallNode).args {
case InvokeNodeType:
for _, arg := range tree.(*InvokeNode).args {
if !c.isTreeConstant(arg) {
return false
}
}
return c.isTreeConstant(tree.(*CallNode).source)
return c.isTreeConstant(tree.(*InvokeNode).source)
case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, FunctionNodeType,
ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType:
return false
@ -1076,8 +1193,13 @@ func (c *Compiler) compute(tree Node) (Value, error) {
n.value,
}, nil
case *NumberNode:
return &NumberValue{
case *FloatNode:
return &FloatValue{
n.value,
}, nil
case *IntegerNode:
return &IntegerValue{
n.value,
}, nil
@ -1114,13 +1236,17 @@ func (c *Compiler) compute(tree Node) (Value, error) {
switch n.UnaryOperation {
case UnaryNegate:
if v.Type() != NumberValueType {
return nil, c.error(fmt.Sprintf("cannot negate %s value (not a number)", v.Type()), n)
if v.Type() == FloatValueType {
return &FloatValue{
-v.(*FloatValue).Number,
}, nil
} else if v.Type() == IntegerValueType {
return &IntegerValue{
new(big.Int).Neg(v.(*IntegerValue).Number),
}, nil
}
return &NumberValue{
-v.(*NumberValue).Number,
}, nil
return nil, c.error(fmt.Sprintf("cannot negate %s value (not a number)", v.Type()), n)
case UnaryNot:
if v.Type() != BoolValueType {
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)
case *CallNode:
case *InvokeNode:
source, err := c.compute(n.source)
if err != nil {
return nil, err
@ -1180,7 +1306,7 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
// perform type check
switch n.BinaryOperation {
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)
}
case BinaryAnd, BinaryOr:
@ -1196,37 +1322,67 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
switch n.BinaryOperation {
case BinaryAddition:
switch l.Type() {
case NumberValueType:
v = l.(*NumberValue).Number + r.(*NumberValue).Number
case FloatValueType:
v = l.(*FloatValue).Number + r.(*FloatValue).Number
case StringValueType:
v = l.(*StringValue).Text + r.(*StringValue).Text
case ListValueType:
v = append(l.(*ListValue).Items, r.(*ListValue).Items...)
case IntegerValueType:
v = new(big.Int).Add(l.(*IntegerValue).Number, r.(*IntegerValue).Number)
default:
return nil, c.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n)
}
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:
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:
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:
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean
case BinaryOr:
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean
v = l.(*BoolValue).Boolean || r.(*BoolValue).Boolean
case BinaryEquality:
v = l.Equals(r)
case BinaryInequality:
v = !l.Equals(r)
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:
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:
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:
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

View file

@ -30,6 +30,7 @@ func BenchmarkNewCompiler(b *testing.B) {
type CompileTestData struct {
program *Program
expectedStack []Value
expectedScope []map[string]Value
}
func GetCompileTestData() map[string]CompileTestData {
@ -40,7 +41,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&ReferenceNode{"a", 0, 0},
&StringNode{
"Hello world!",
"\"Hello world!\"",
@ -54,22 +55,22 @@ func GetCompileTestData() map[string]CompileTestData {
},
"",
},
[]Value{
&VariableValue{
"a",
&StringValue{"Hello world!"},
0,
[]Value{&StringValue{"Hello world!"}},
[]map[string]Value{
{
"a": &StringValue{"Hello world!"},
},
},
},
/* these tests are so fucking unmaintainable
"conditional_false": {
&Program{
[]Import{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
@ -84,8 +85,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
@ -106,7 +107,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{
&VariableValue{
"a",
&NumberValue{0},
&FloatValue{0},
0,
},
},
@ -117,8 +118,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
@ -133,8 +134,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
@ -155,7 +156,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{
&VariableValue{
"a",
&NumberValue{1},
&FloatValue{1},
0,
},
},
@ -166,8 +167,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
@ -182,8 +183,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
@ -196,8 +197,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
2,
0, 0,
},
@ -217,7 +218,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{
&VariableValue{
"a",
&NumberValue{2},
&FloatValue{2},
0,
},
},
@ -228,8 +229,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
0,
0, 0,
},
@ -244,8 +245,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
1,
0, 0,
},
@ -258,8 +259,8 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
&ReferenceNode{"a", 0, 0},
&FloatNode{
2,
0, 0,
},
@ -279,7 +280,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{
&VariableValue{
"a",
&NumberValue{1},
&FloatValue{1},
0,
},
},
@ -290,14 +291,14 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&ReferenceNode{"a", 0, 0},
&BinaryNode{
BinaryAddition,
&NumberNode{
&FloatNode{
1,
0, 0,
},
&NumberNode{
&FloatNode{
2,
0, 0,
},
@ -314,7 +315,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{
&VariableValue{
"a",
&NumberValue{3},
&FloatValue{3},
0,
},
},
@ -325,20 +326,20 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"sum",
&ReferenceNode{name: "sum"},
&FunctionNode{
"sum",
[]FunctionParameter{
{
"a",
&NumberSignature{},
&FloatSignature{},
},
{
"b",
&NumberSignature{},
&FloatSignature{},
},
},
&NumberSignature{},
&FloatSignature{},
&BlockNode{
[]Node{
&ReturnNode{
@ -378,20 +379,20 @@ func GetCompileTestData() map[string]CompileTestData {
[]FunctionParameter{
{
"a",
&NumberSignature{},
&FloatSignature{},
},
{
"b",
&NumberSignature{},
&FloatSignature{},
},
},
&NumberSignature{},
&FloatSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
InstructionGetLocal, 0,
InstructionGetLocal, 1,
InstructionAdd,
InstructionAddFloat,
InstructionReturn,
InstructionAscend,
},
@ -411,16 +412,16 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
"a",
&ReferenceNode{"a", 0, 0},
&FunctionNode{
"a",
[]FunctionParameter{},
&NumberSignature{},
&FloatSignature{},
&BlockNode{
[]Node{
&AssignNode{
"b",
&NumberNode{
&ReferenceNode{"b", 0, 0},
&FloatNode{
1,
0, 0,
},
@ -448,7 +449,6 @@ func GetCompileTestData() map[string]CompileTestData {
0, 0,
},
[]Node{},
false,
0, 0,
},
},
@ -462,7 +462,7 @@ func GetCompileTestData() map[string]CompileTestData {
&FunctionValue{
"a",
[]FunctionParameter{},
&NumberSignature{},
&FloatSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
@ -473,7 +473,7 @@ func GetCompileTestData() map[string]CompileTestData {
InstructionAscend,
},
[]Value{
&NumberValue{1}, &StringValue{"b"},
&FloatValue{1}, &StringValue{"b"},
},
),
nil,
@ -488,17 +488,17 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
statements: []Node{
&AssignNode{
name: "a",
dest: &ReferenceNode{"a", 0, 0},
value: &ListNode{
items: []Node{
&NumberNode{value: 1},
&NumberNode{value: 2},
&FloatNode{value: 1},
&FloatNode{value: 2},
},
},
declare: true,
},
&AssignNode{
name: "b",
dest: &ReferenceNode{"b", 0, 0},
value: &ListNode{
items: []Node{
&StringNode{value: "Hello"},
@ -516,8 +516,8 @@ func GetCompileTestData() map[string]CompileTestData {
name: "a",
value: &ListValue{
Items: []Value{
&NumberValue{1},
&NumberValue{2},
&FloatValue{1},
&FloatValue{2},
},
},
scope: 0,
@ -540,10 +540,10 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
name: "a",
dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{
UnaryNegate,
&NumberNode{
&FloatNode{
1,
0, 0,
},
@ -559,7 +559,7 @@ func GetCompileTestData() map[string]CompileTestData {
expectedStack: []Value{
&VariableValue{
name: "a",
value: &NumberValue{-1},
value: &FloatValue{-1},
scope: 0,
},
},
@ -570,7 +570,7 @@ func GetCompileTestData() map[string]CompileTestData {
&BlockNode{
[]Node{
&AssignNode{
name: "a",
dest: &ReferenceNode{"a", 0, 0},
value: &UnaryNode{
UnaryNot,
&BooleanNode{
@ -593,6 +593,7 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
},
*/
}
}
@ -641,7 +642,8 @@ func TestCompile(t *testing.T) {
}
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
for i := 0; i < int(vm.stack.Current); i++ {
v := vm.stack.items[i]
for i := 1; i < int(vm.Stack.Current); i++ {
v := vm.Stack.items[i]
if v == nil || v.Type() != VariableValueType {
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 TokenType
Start Pos
Length Pos
End Pos
Line Pos
Lexeme 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
@ -28,7 +28,8 @@ const (
TokenBang
TokenSemicolon
TokenNumber
TokenInteger
TokenFloat
TokenHexadecimal
TokenString
TokenName
@ -55,6 +56,7 @@ const (
TokenComma
TokenDot
TokenColon
TokenArrow
TokenAssign
TokenDeclare
@ -69,6 +71,7 @@ const (
TokenPipe
TokenDoublePipe
TokenNewLine
TokenBreakpoint
TokenEOF
TokenError
@ -86,8 +89,10 @@ func (t TokenType) String() string {
return "slash"
case TokenBang:
return "bang"
case TokenNumber:
return "number"
case TokenFloat:
return "float"
case TokenInteger:
return "integer"
case TokenString:
return "string"
case TokenTrue:
@ -135,7 +140,7 @@ func (t TokenType) String() string {
case TokenDeclare:
return "declare"
case TokenFunc:
return "func"
return "fn"
case TokenReturn:
return "return"
case TokenWhile:
@ -162,6 +167,10 @@ func (t TokenType) String() string {
return "pipe"
case TokenHexadecimal:
return "hexadecimal"
case TokenArrow:
return "arrow"
case TokenNewLine:
return "newline"
}
panic("UNDEFINED TOKENTYPE STRING CONVERSION")
@ -206,9 +215,14 @@ func (l *Lexer) NextToken() (Token, error) {
l.advance()
switch c {
case '\n':
return l.makeToken(TokenNewLine), nil
case '+':
return l.makeToken(TokenPlus), nil
case '-':
if l.accept('>') {
return l.makeToken(TokenArrow), nil
}
return l.makeToken(TokenMinus), nil
case '*':
return l.makeToken(TokenStar), nil
@ -339,7 +353,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenElse), nil
case "var":
return l.makeToken(TokenVar), nil
case "func":
case "fn":
return l.makeToken(TokenFunc), nil
case "while":
return l.makeToken(TokenWhile), nil
@ -364,7 +378,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenHexadecimal), nil
}
return l.makeToken(TokenNumber), nil
return l.makeToken(TokenInteger), nil
} else if unicode.IsDigit(c) {
for unicode.IsDigit(l.peek()) {
l.advance()
@ -375,20 +389,22 @@ func (l *Lexer) NextToken() (Token, error) {
for unicode.IsDigit(l.peek()) {
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))
}
}
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{
Type: t,
Start: start,
Length: length,
End: end,
Line: line,
Lexeme: lexeme,
}
@ -410,7 +426,7 @@ func (l *Lexer) Tokenize() ([]Token, error) {
}
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 {
@ -459,7 +475,7 @@ func (l *Lexer) isAtEnd() bool {
}
func (l *Lexer) skipWhitespace() {
for !l.isAtEnd() && unicode.IsSpace(l.peek()) {
for !l.isAtEnd() && unicode.IsSpace(l.peek()) && l.peek() != '\n' {
l.advance()
}
}

View file

@ -21,33 +21,33 @@ func GetLexerTestData() map[string]LexerTestData {
},
"simple number(1)": {
"1024",
[]TokenType{TokenNumber, TokenEOF},
[]TokenType{TokenInteger, TokenEOF},
},
"simple_arithmetics(7)": {
"1 + 23 / 4 * 3",
[]TokenType{
TokenNumber, TokenPlus, TokenNumber, TokenSlash,
TokenNumber, TokenStar, TokenNumber, TokenEOF,
TokenInteger, TokenPlus, TokenInteger, TokenSlash,
TokenInteger, TokenStar, TokenInteger, TokenEOF,
},
},
"condition(3)": {
"a <= 200",
[]TokenType{TokenName, TokenLessThanOrEqual, TokenNumber, TokenEOF},
[]TokenType{TokenName, TokenLessThanOrEqual, TokenInteger, TokenEOF},
},
"if_statement(10)": {
"if a >= 200 {\n write(\"Hello world!\")\n}",
[]TokenType{
TokenIf, TokenName, TokenGreaterThanOrEqual, TokenNumber, TokenOpenBrace,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace,
TokenEOF,
TokenIf, TokenName, TokenGreaterThanOrEqual, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine,
TokenCloseBrace, TokenEOF,
},
},
"if_else_statement(20)": {
"if 23 * 2/3 > 32 {\n write(\"It is larger!\")\n} else {\n write(\"It is lower!\")\n}",
[]TokenType{
TokenIf, TokenNumber, TokenStar, TokenNumber, TokenSlash, TokenNumber, TokenGreaterThan, TokenNumber, TokenOpenBrace,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace,
TokenElse, TokenOpenBrace, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenCloseBrace,
TokenIf, TokenInteger, TokenStar, TokenInteger, TokenSlash, TokenInteger, TokenGreaterThan, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine, TokenCloseBrace,
TokenElse, TokenOpenBrace, TokenNewLine, TokenName, TokenOpenParenthesis, TokenString, TokenCloseParenthesis, TokenNewLine, TokenCloseBrace,
TokenEOF,
},
},
@ -58,8 +58,8 @@ func GetLexerTestData() map[string]LexerTestData {
"full_arithmetic_equality": {
"a + 2 == 10 * 2 / 3",
[]TokenType{
TokenName, TokenPlus, TokenNumber, TokenEquals,
TokenNumber, TokenStar, TokenNumber, TokenSlash, TokenNumber,
TokenName, TokenPlus, TokenInteger, TokenEquals,
TokenInteger, TokenStar, TokenInteger, TokenSlash, TokenInteger,
TokenEOF,
},
},
@ -77,7 +77,7 @@ func GetLexerTestData() map[string]LexerTestData {
},
"space_before_string": {
"\n \"\"",
[]TokenType{TokenString, TokenEOF},
[]TokenType{TokenNewLine, TokenString, TokenEOF},
},
"write_call": {
"write(\"Hello world\")",
@ -86,46 +86,46 @@ func GetLexerTestData() map[string]LexerTestData {
"complex_comparison": {
"!(h__elo123 >= 1)",
[]TokenType{
TokenBang, TokenOpenParenthesis, TokenName, TokenGreaterThanOrEqual, TokenNumber, TokenCloseParenthesis,
TokenBang, TokenOpenParenthesis, TokenName, TokenGreaterThanOrEqual, TokenInteger, TokenCloseParenthesis,
TokenEOF,
},
},
"3assignments_1condition": {
"a = 8 * 32\nb = a > 256\nc = a <= 256\n!b == c",
[]TokenType{
TokenName, TokenAssign, TokenNumber, TokenStar, TokenNumber,
TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenNumber,
TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenNumber,
TokenName, TokenAssign, TokenInteger, TokenStar, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenGreaterThan, TokenInteger, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenLessThanOrEqual, TokenInteger, TokenNewLine,
TokenBang, TokenName, TokenEquals, TokenName, TokenEOF,
},
},
"func": {
"func sum(a, b) {\n return a + b\n}",
"function": {
"fn sum(a, b) {\n return a + b\n}",
[]TokenType{
TokenFunc, TokenName, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis,
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace,
TokenOpenBrace, TokenNewLine, TokenReturn, TokenName, TokenPlus, TokenName, TokenNewLine, TokenCloseBrace,
},
},
"while_loop": {
"while a < 5 {\n a = a + 1\n}",
[]TokenType{
TokenWhile, TokenName, TokenLessThan, TokenNumber, TokenOpenBrace,
TokenName, TokenAssign, TokenName, TokenPlus, TokenNumber, TokenCloseBrace,
TokenWhile, TokenName, TokenLessThan, TokenInteger, TokenOpenBrace, TokenNewLine,
TokenName, TokenAssign, TokenName, TokenPlus, TokenInteger, TokenNewLine, TokenCloseBrace, TokenEOF,
},
},
"lambda": {
"sum := func(a, b) {\n" +
"sum := fn(a, b) {\n" +
" return a + b\n" +
"}",
[]TokenType{
TokenName, TokenDeclare, TokenFunc, TokenOpenParenthesis, TokenName, TokenComma, TokenName, TokenCloseParenthesis,
TokenOpenBrace, TokenReturn, TokenName, TokenPlus, TokenName, TokenCloseBrace,
TokenOpenBrace, TokenNewLine, TokenReturn, TokenName, TokenPlus, TokenName, TokenNewLine, TokenCloseBrace,
},
},
"list": {
"data := [3, 1, 4, 1]",
[]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 (
"fmt"
"math/big"
"strconv"
"strings"
)
@ -21,17 +22,20 @@ type Boundary interface {
const (
StringNodeType NodeType = iota
NumberNodeType
FloatNodeType
IntegerNodeType
ReferenceNodeType
BooleanNodeType
NilNodeType
ListNodeType
TupleNodeType
BinaryNodeType
UnaryNodeType
BlockNodeType
ConditionalNodeType
LoopNodeType
AssignNodeType
InvokeNodeType
CallNodeType
FunctionNodeType
ReturnNodeType
@ -43,8 +47,10 @@ func (n NodeType) String() string {
switch n {
case StringNodeType:
return "String"
case NumberNodeType:
return "Number"
case FloatNodeType:
return "Float"
case IntegerNodeType:
return "Integer"
case ReferenceNodeType:
return "Reference"
case BinaryNodeType:
@ -61,7 +67,7 @@ func (n NodeType) String() string {
return "Loop"
case AssignNodeType:
return "Assign"
case CallNodeType:
case InvokeNodeType:
return "Call"
case FunctionNodeType:
return "Function"
@ -69,12 +75,16 @@ func (n NodeType) String() string {
return "Return"
case ListNodeType:
return "List"
case TupleNodeType:
return "Tuple"
case AccessNodeType:
return "Access"
case BreakpointNodeType:
return "Breakpoint"
case UnaryNodeType:
return "Unary"
case CallNodeType:
return "Call"
}
return "Invalid Node Type"
}
@ -120,22 +130,41 @@ func (n StringNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type NumberNode struct {
type FloatNode struct {
value float64
start Pos
end Pos
}
func (n NumberNode) Type() NodeType {
return NumberNodeType
func (n FloatNode) Type() NodeType {
return FloatNodeType
}
func (n NumberNode) String() string {
return strconv.FormatFloat(n.value, 'g', -1, NumberSize)
func (n FloatNode) String() string {
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
}
@ -169,6 +198,37 @@ func (n ListNode) Bounds() (Pos, Pos) {
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 {
source Node
property string
@ -258,14 +318,14 @@ func (n BinaryOperation) Symbol() string {
return "<"
case BinaryGreater:
return ">"
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
case BinaryLessEqual:
return "<="
case BinaryGreaterEqual:
return ">="
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
}
panic("unsupported binary operation to symbol conversion for " + n.String())
@ -456,7 +516,7 @@ func (n LoopNode) Bounds() (Pos, Pos) {
// AssignNode assignment
type AssignNode struct {
name string
dest Node
value Node
declare bool
@ -469,18 +529,39 @@ func (n AssignNode) Type() NodeType {
}
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) {
return n.start, n.end
}
// CallNode function call
type CallNode struct {
// InvokeNode function call
type InvokeNode struct {
source 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
end Pos
@ -491,7 +572,7 @@ func (n CallNode) Type() NodeType {
}
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) {

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"math/big"
"strconv"
"strings"
)
@ -61,7 +62,7 @@ func (p ParsingError) Format() string {
b.WriteRune(' ')
}
for i := 0; i < int(p.Causer.Length); i++ {
for i := 0; i < len(p.Causer.Lexeme); i++ {
b.WriteRune('^')
}
b.WriteRune('\n')
@ -81,6 +82,7 @@ type Parser struct {
prev *Token
curr *Token
pos Pos
ignoreNewLine bool
}
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{
p.prev.Lexeme[1 : len(p.prev.Lexeme)-1],
start,
p.prev.Start + p.prev.Length,
p.prev.End,
})
continue
}
b, err := p.block(true)
for p.accept(TokenNewLine) {
}
if p.curr.Type == TokenEOF {
break
}
b, err := p.expression(false)
if err != nil {
return nil, err
@ -163,7 +172,7 @@ func (p *Parser) Parse(path string) (*Program, error) {
&BlockNode{
statements,
0,
p.curr.Start + p.curr.Length,
p.curr.End,
},
path,
}, nil
@ -175,6 +184,12 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false
}
if p.ignoreNewLine && tokenType != TokenNewLine {
for p.curr.Type == TokenNewLine {
p.advance()
}
}
if (*p.curr).Type == tokenType {
p.advance()
return true
@ -203,10 +218,11 @@ func (p *Parser) advance() {
if p.pos < Pos(len(p.tokens)) {
p.curr = &p.tokens[p.pos]
p.pos++
} else {
panic("no more tokens")
p.curr = nil
}
p.pos++
}
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) {
switch (*p.curr).Type {
case TokenString:
@ -226,35 +606,49 @@ func (p *Parser) factor() (Node, error) {
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme,
p.prev.Start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenNumber:
case TokenInteger:
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 {
return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev)
}
return &NumberNode{
return &FloatNode{
num,
p.prev.Start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenHexadecimal:
p.advance()
start := (*p.prev).Start
num, err := strconv.ParseUint((*p.prev).Lexeme[2:], 16, NumberSize)
if err != nil {
return nil, err
num, ok := new(big.Int).SetString(p.prev.Lexeme[2:], 16)
if !ok {
return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.prev.Lexeme), p.prev)
}
return &NumberNode{
float64(num),
return &IntegerNode{
num,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenTrue:
@ -262,14 +656,14 @@ func (p *Parser) factor() (Node, error) {
return &BooleanNode{
true,
p.prev.Start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenFalse:
p.advance()
return &BooleanNode{
false,
p.prev.Start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenNil:
@ -280,6 +674,8 @@ func (p *Parser) factor() (Node, error) {
p.advance()
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) {
s, err := p.parseSignature()
if err != nil {
@ -290,10 +686,13 @@ func (p *Parser) factor() (Node, error) {
[]Node{},
s,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
}
oldIgnoreNewline := p.ignoreNewLine
p.ignoreNewLine = true
var values []Node
for !p.accept(TokenCloseBracket) {
if len(values) > 0 {
@ -310,11 +709,13 @@ func (p *Parser) factor() (Node, error) {
values = append(values, value)
}
p.ignoreNewLine = oldIgnoreNewline
return &ListNode{
values,
nil,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
// unary minus
@ -330,7 +731,7 @@ func (p *Parser) factor() (Node, error) {
UnaryNegate,
f,
first.Start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenBang:
@ -346,14 +747,14 @@ func (p *Parser) factor() (Node, error) {
UnaryNot,
v,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenName:
p.advance()
name := (*p.prev).Lexeme
start := p.prev.Start
nameEnd := start + p.prev.Length
nameEnd := p.prev.End
if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs()
@ -361,16 +762,15 @@ func (p *Parser) factor() (Node, error) {
return nil, err
}
return &CallNode{
return &InvokeNode{
&ReferenceNode{
name,
start,
nameEnd,
},
args,
true,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
}
@ -390,7 +790,7 @@ func (p *Parser) factor() (Node, error) {
}
var sig TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
if p.accept(TokenArrow) {
sig, err = p.parseSignature()
if err != nil {
return nil, err
@ -408,7 +808,7 @@ func (p *Parser) factor() (Node, error) {
sig,
b,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenOpenParenthesis:
@ -423,8 +823,16 @@ func (p *Parser) factor() (Node, error) {
return v, nil
case TokenBreakpoint:
p.advance()
return &BreakpointNode{
p.prev.Start,
p.prev.End,
}, nil
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,
property,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}
// if called, also add
@ -457,12 +865,11 @@ func (p *Parser) prop() (Node, error) {
return nil, err
}
v = &CallNode{
v = &InvokeNode{
v,
args,
true,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}
}
}
@ -494,7 +901,7 @@ func (p *Parser) product() (Node, error) {
left,
f,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}
}
@ -526,7 +933,7 @@ func (p *Parser) term() (Node, error) {
left,
pr,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}
}
@ -573,7 +980,7 @@ func (p *Parser) comparison() (Node, error) {
left,
t,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
}
@ -607,7 +1014,7 @@ func (p *Parser) condition() (Node, error) {
left,
c,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
}
@ -646,19 +1053,18 @@ func (p *Parser) statement() (Node, error) {
then,
otherwise,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenName:
p.advance()
start := p.prev.Start
name := (*p.prev).Lexeme
name := p.prev
if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{
name,
start,
p.prev.Start + p.prev.Length,
name.Lexeme,
name.Start,
name.End,
}
// parse chains of prop-getting ( "".split().join().length.round() )
@ -671,8 +1077,8 @@ func (p *Parser) statement() (Node, error) {
v = &AccessNode{
v,
property,
start,
p.prev.Start + p.prev.Length,
name.Start,
p.prev.End,
}
// if called, also add
@ -682,12 +1088,11 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
v = &CallNode{
v = &InvokeNode{
v,
args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
start,
p.prev.Start + p.prev.Length,
name.Start,
p.prev.End,
}
}
}
@ -699,16 +1104,15 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
return &CallNode{
return &InvokeNode{
&ReferenceNode{
name,
start,
start + Pos(len(name)),
name.Lexeme,
name.Start,
name.End,
},
args,
false,
start,
p.prev.Start + p.prev.Length,
name.Start,
p.prev.End,
}, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare
@ -717,12 +1121,16 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
return &AssignNode{
name,
return &AssignNode{ // THIS COULD BE MORE PERMISSIVE; its a new system
&ReferenceNode{
name.Lexeme,
name.Start,
name.End,
},
c,
isDeclaration,
start,
p.prev.Start + p.prev.Length,
name.Start,
p.prev.End,
}, nil
}
@ -736,7 +1144,7 @@ func (p *Parser) statement() (Node, error) {
if err := p.expect(TokenName, "function must have a name"); err != nil {
return nil, err
}
name := p.prev.Lexeme
name := p.prev
params, err := p.parseParams()
if err != nil {
@ -744,7 +1152,7 @@ func (p *Parser) statement() (Node, error) {
}
var yield TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
if p.accept(TokenArrow) {
yield, err = p.parseSignature()
if err != nil {
return nil, err
@ -757,18 +1165,22 @@ func (p *Parser) statement() (Node, error) {
}
return &AssignNode{
name,
&ReferenceNode{
name.Lexeme,
name.Start,
name.End,
},
&FunctionNode{
name,
name.Lexeme,
params,
yield,
b,
funcStart,
p.prev.Start + p.prev.Length,
p.prev.End,
},
true,
funcStart,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenWhile:
@ -789,7 +1201,7 @@ func (p *Parser) statement() (Node, error) {
c,
b,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenReturn:
@ -804,7 +1216,7 @@ func (p *Parser) statement() (Node, error) {
return &ReturnNode{
c,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
case TokenBreakpoint:
@ -854,7 +1266,7 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
return &BlockNode{
statements,
start,
p.prev.Start + p.prev.Length,
p.prev.End,
}, nil
}
@ -866,7 +1278,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
}
if !p.accept(TokenCloseParenthesis) {
c, err := p.condition()
c, err := p.expression(false)
if err != nil {
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 {
return nil, err
}
c, err = p.condition()
c, err = p.expression(false)
if err != nil {
return nil, err
}
@ -942,7 +1354,17 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) {
func (p *Parser) parseSignature() (TypeSignature, error) {
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 {
return nil, err
}
@ -965,15 +1387,31 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
in = append(in, sig)
}
out, err := p.parseSignature()
var out TypeSignature
var err error
if p.accept(TokenArrow) {
out, err = p.parseSignature()
if err != nil {
return nil, err
}
} else {
out = &NilSignature{}
}
s = &FunctionSignature{
in,
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 {
if err := p.expect(TokenName, "type must be a name"); err != nil {
return nil, err
@ -983,8 +1421,10 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
switch name {
case "string":
s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "int":
s = &IntegerSignature{}
case "float":
s = &FloatSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
@ -997,7 +1437,7 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
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
}

View file

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

View file

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

View file

@ -5,6 +5,30 @@ import (
"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]) {
if actual.Current != Pos(len(expected)) {
t.Errorf("Unexpected stack size. Expected %d, got %d", len(expected), actual.Current)

View file

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

View file

@ -3,7 +3,7 @@ package core
import (
"errors"
"fmt"
"math"
"math/big"
"reflect"
"strconv"
)
@ -13,7 +13,8 @@ type ValueType int
const (
NilValueType ValueType = iota
BoolValueType
NumberValueType
FloatValueType
IntegerValueType
StringValueType
ListValueType
ObjectValueType
@ -30,8 +31,10 @@ func (v ValueType) String() string {
return "bool"
case ObjectValueType:
return "object"
case NumberValueType:
return "number"
case FloatValueType:
return "float"
case IntegerValueType:
return "int"
case StringValueType:
return "string"
case ListValueType:
@ -57,11 +60,15 @@ func GoToValue(gov interface{}) Value {
v,
}
case int:
return &NumberValue{
float64(v),
return &IntegerValue{
new(big.Int).SetInt64(int64(v)),
}
case *big.Int:
return &IntegerValue{
v,
}
case float64:
return &NumberValue{
return &FloatValue{
v,
}
case string:
@ -259,40 +266,71 @@ func (v *ObjectValue) Clone() Value {
}
}
// NumberValue Integer or floating-point values
type NumberValue struct {
// FloatValue floating-point values
type FloatValue struct {
Number float64
}
const NumberSize int = 64
const FloatSize int = 64
func (v *NumberValue) Type() ValueType {
return NumberValueType
func (v *FloatValue) Type() ValueType {
return FloatValueType
}
func (v *NumberValue) String() string {
return strconv.FormatFloat(v.Number, 'g', -1, NumberSize)
func (v *FloatValue) String() string {
return strconv.FormatFloat(v.Number, 'g', -1, FloatSize)
}
func (v *NumberValue) DebugString() string {
func (v *FloatValue) DebugString() string {
return v.String()
}
func (v *NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && other.(*NumberValue).Number == v.Number
func (v *FloatValue) Equals(other Value) bool {
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?
return nil, errors.New("numbers have no properties")
}
func (v *NumberValue) Clone() Value {
return &NumberValue{
func (v *FloatValue) Clone() Value {
return &FloatValue{
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 {
Text string
}
@ -348,7 +386,7 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
Name: "length",
Signature: &FunctionSignature{
[]TypeSignature{},
&NumberSignature{},
&IntegerSignature{},
},
F: func(vm *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*StringValue).Text)), nil
@ -357,11 +395,11 @@ var StringPrototype = map[string]*BuiltinFunctionValue{
"at": {
Name: "at",
Signature: &FunctionSignature{
[]TypeSignature{&NumberSignature{}},
[]TypeSignature{&IntegerSignature{}},
&StringSignature{},
},
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) {
return nil, errors.New("index is out of range")
@ -450,13 +488,13 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"at",
&FunctionSignature{
[]TypeSignature{
&NumberSignature{},
&IntegerSignature{},
},
&InnerSignature{},
},
func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).Items
index := int(p[0].(*NumberValue).Number)
index := int(p[0].(*IntegerValue).Number.Int64())
if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -471,13 +509,13 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"put",
&FunctionSignature{
[]TypeSignature{
&NumberSignature{}, &InnerSignature{},
&FloatSignature{}, &InnerSignature{},
},
&NilSignature{},
},
func(_ *VM, this Value, args []Value) (Value, error) {
l := this.(*ListValue)
i := int(args[0].(*NumberValue).Number)
i := int(args[0].(*FloatValue).Number)
v := args[1]
// bounds check
@ -496,7 +534,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
"length",
&FunctionSignature{
[]TypeSignature{},
&NumberSignature{},
&IntegerSignature{},
},
func(_ *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*ListValue).Items)), nil
@ -565,6 +603,7 @@ type FunctionValue struct {
Yield TypeSignature
Chunk *Chunk
Parent Value
Scope *Scope
}
func (v *FunctionValue) Type() ValueType {
@ -572,7 +611,7 @@ func (v *FunctionValue) Type() ValueType {
}
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 {
@ -581,7 +620,6 @@ func (v *FunctionValue) DebugString() string {
func (v *FunctionValue) Equals(other Value) bool {
return other.Type() == FunctionValueType &&
v.Name == other.(*FunctionValue).Name &&
v.Chunk == other.(*FunctionValue).Chunk
}
@ -596,6 +634,7 @@ func (v *FunctionValue) Clone() Value {
v.Yield,
v.Chunk,
v.Parent,
v.Scope,
}
}
@ -637,43 +676,3 @@ func (v *BuiltinFunctionValue) Clone() Value {
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 {
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
}
case NumberValueType:
if got.(*NumberValue).Number != want.(*NumberValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue))
case FloatValueType:
if got.(*FloatValue).Number != want.(*FloatValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*FloatValue), want.(*FloatValue))
} 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:
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 {
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)
}
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:
n := got.(*ListValue)
m := want.(*ListValue)

View file

@ -7,7 +7,9 @@ import (
"fmt"
"log"
"math"
"math/big"
"os"
"strconv"
"strings"
)
@ -20,30 +22,52 @@ const (
// InstructionPop pop and delete the first item on the stack
InstructionPop
// InstructionAdd pop two and add them
InstructionAdd
// InstructionSub pop two and subtract the second from the first
InstructionSub
// InstructionMul pop two and multiply them
InstructionMul
// InstructionDiv pop two and divide the second by the first
InstructionDiv
// InstructionNegate negate the value; if it was positive, make it negative, and vice versa.
InstructionNegate
// InstructionAddFloat pop two floats and add them
InstructionAddFloat
// InstructionSubFloat pop two floats and subtract the second from the first
InstructionSubFloat
// InstructionMulFloat pop two floats and multiply them
InstructionMulFloat
// InstructionDivFloat pop two floats and divide the second by the first
InstructionDivFloat
// InstructionNegateFloat negate the float; if it was positive, make it negative, and vice versa.
InstructionNegateFloat
// InstructionAddInt pop two ints and add them
InstructionAddInt
// InstructionSubInt pop two ints and subtract the second from the first
InstructionSubInt
// InstructionMulInt pop two ints and multiply them
InstructionMulInt
// InstructionDivInt pop two ints and divide the second by the first
InstructionDivInt
// InstructionNegateInt negate the int; if it was positive, make it negative, and vice versa.
InstructionNegateInt
// InstructionEquals whether the two top values on the stack are equal
InstructionEquals
// InstructionNotEqual whether the two top values on the stack are not equal
InstructionNotEqual
// InstructionNot inverts boolean (true => false, false => true)
InstructionNot
// InstructionLess pops two from stack, pushes whether the lowest is less than the highest
InstructionLess
// InstructionLessOrEqual pops two from stack, pushes whether the lowest is less or equal than the highest
InstructionLessOrEqual
// InstructionGreater pops two from stack, pushes whether the lowest is greater than the highest
InstructionGreater
// InstructionGreaterOrEqual pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqual
// InstructionLessFloat pops two from stack, pushes whether the lowest is less than the highest
InstructionLessFloat
// InstructionLessOrEqualFloat pops two from stack, pushes whether the lowest is less or equal than the highest
InstructionLessOrEqualFloat
// InstructionGreaterFloat pops two from stack, pushes whether the lowest is greater than the highest
InstructionGreaterFloat
// InstructionGreaterOrEqualFloat pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqualFloat
// InstructionLessInt pops two from stack, pushes whether the lowest is less than the highest
InstructionLessInt
// InstructionLessOrEqualInt pops two from stack, pushes whether the lowest is less or equal than the highest
InstructionLessOrEqualInt
// InstructionGreaterInt pops two from stack, pushes whether the lowest is greater than the highest
InstructionGreaterInt
// InstructionGreaterOrEqualInt pops two from stack, pushes whether the lowest is greater or equal than the highest
InstructionGreaterOrEqualInt
// InstructionAccessProperty gets a property from a value, and pops it onto the stack
InstructionAccessProperty
@ -80,6 +104,8 @@ const (
// InstructionSwap swap the two top items on the stack (1, 2 -> 2, 1)
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
@ -116,30 +142,48 @@ func (b Bytecode) String() string {
return "RETURN"
case InstructionPop:
return "POP"
case InstructionAdd:
return "ADD"
case InstructionSub:
return "SUB"
case InstructionMul:
return "MUL"
case InstructionDiv:
return "DIV"
case InstructionNegate:
return "NEGATE"
case InstructionAddFloat:
return "ADD_FLOAT"
case InstructionSubFloat:
return "SUB_FLOAT"
case InstructionMulFloat:
return "MUL_FLOAT"
case InstructionDivFloat:
return "DIV_FLOAT"
case InstructionNegateFloat:
return "NEGATE_FLOAT"
case InstructionAddInt:
return "ADD_INT"
case InstructionSubInt:
return "SUB_INT"
case InstructionMulInt:
return "MUL_INT"
case InstructionDivInt:
return "DIV_INT"
case InstructionNegateInt:
return "NEGATE_INT"
case InstructionEquals:
return "EQUALS"
case InstructionNotEqual:
return "NOT_EQUALS"
case InstructionNot:
return "NOT"
case InstructionLess:
return "LESS"
case InstructionLessOrEqual:
return "LESS_OR_EQUAL"
case InstructionGreater:
return "GREATER_OR_EQUAL"
case InstructionGreaterOrEqual:
return "GREATER_OR_EQUAL"
case InstructionLessFloat:
return "LESS_FLOAT"
case InstructionLessOrEqualFloat:
return "LESS_OR_EQUAL_FLOAT"
case InstructionGreaterFloat:
return "GREATER_FLOAT"
case InstructionGreaterOrEqualFloat:
return "GREATER_OR_EQUAL_FLOAT"
case InstructionLessInt:
return "LESS_INT"
case InstructionLessOrEqualInt:
return "LESS_OR_EQUAL_INT"
case InstructionGreaterInt:
return "GREATER_INT"
case InstructionGreaterOrEqualInt:
return "GREATER_OR_EQUAL_INT"
case InstructionJump:
return "JUMP"
case InstructionJumpFalse:
@ -192,6 +236,8 @@ func (b Bytecode) String() string {
return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
case InstructionDuplicate:
return "DUPLICATE"
}
return "UNDEFINED"
}
@ -225,6 +271,30 @@ func (c Chunk) String() 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 {
return &Chunk{bytecode, constants}
}
@ -232,7 +302,7 @@ func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
func RegisterGOBTypes() {
gob.Register(&StringValue{""})
gob.Register(&BoolValue{false})
gob.Register(&NumberValue{0})
gob.Register(&FloatValue{0})
gob.Register(&FunctionValue{
Name: "",
Params: nil,
@ -241,7 +311,7 @@ func RegisterGOBTypes() {
// Signatures
gob.Register(&NilSignature{})
gob.Register(&NumberSignature{})
gob.Register(&FloatSignature{})
gob.Register(&StringSignature{})
gob.Register(&FunctionSignature{})
gob.Register(&ListSignature{})
@ -286,26 +356,29 @@ type VM struct {
// instruction pointer
ip Pos
scope Pos
// global variable storage
globals map[string]Value
variableEnd Pos
// local variable storage
scope *Scope
stack *Stack[Value]
Stack *Stack[Value]
call *Stack[Call]
}
type Scope struct {
current map[string]Value
parent *Scope
}
type Call struct {
chunk *Chunk
ip Pos
stackEnd Pos
variableEnd Pos
scope Pos
scope *Scope
}
var DefaultGlobals = map[string]Value{
"write": &BuiltinFunctionValue{
"println": &BuiltinFunctionValue{
"write", // always remember where you come from...
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
@ -313,7 +386,7 @@ var DefaultGlobals = map[string]Value{
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil
return &NilValue{}, nil
},
nil,
false,
@ -326,7 +399,7 @@ var DefaultGlobals = map[string]Value{
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil
return &NilValue{}, nil
},
nil,
false,
@ -368,12 +441,12 @@ var DefaultGlobals = map[string]Value{
"char": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
[]TypeSignature{&IntegerSignature{}},
&StringSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
n := args[0].(*NumberValue).Number
b := byte(n)
n := args[0].(*IntegerValue).Number
b := n.Bytes()[0]
return &StringValue{
string([]byte{b}),
@ -383,16 +456,16 @@ var DefaultGlobals = map[string]Value{
true,
},
"byte": &BuiltinFunctionValue{
"char",
"byte",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NumberSignature{},
&IntegerSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
s := args[0].(*StringValue).Text
b := []byte(s)[0]
n := new(big.Int).SetBytes([]byte(s))
return &NumberValue{float64(b)}, nil
return &IntegerValue{n}, nil
},
nil,
true,
@ -453,6 +526,66 @@ var DefaultGlobals = map[string]Value{
nil,
true,
},
"int": &BuiltinFunctionValue{
"int",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&CompositeSignature{
&IntegerSignature{},
&NilSignature{},
},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
switch v := args[0].(type) {
case *IntegerValue:
return &IntegerValue{v.Number}, nil // this might need to clone the value instead
case *FloatValue:
n := new(big.Int).SetInt64(int64(v.Number))
return &IntegerValue{n}, nil
case *StringValue:
n, success := new(big.Int).SetString(v.Text, 0) // determine base
if !success {
return &NilValue{}, nil
}
return &IntegerValue{n}, nil
default:
return nil, errors.New(fmt.Sprintf("%s cannot become an integer", v))
}
},
nil,
true,
},
"float": &BuiltinFunctionValue{
"float",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&CompositeSignature{
&FloatSignature{},
&NilSignature{},
},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
switch v := args[0].(type) {
case *IntegerValue:
n, _ := v.Number.Float64()
return &FloatValue{n}, nil // this might need to clone the value instead
case *FloatValue:
return &FloatValue{v.Number}, nil
case *StringValue:
num, err := strconv.ParseFloat(v.Text, FloatSize)
if err != nil {
return &NilValue{}, nil
}
return &FloatValue{num}, nil
default:
return nil, errors.New(fmt.Sprintf("%s cannot become an integer", v))
}
},
nil,
true,
},
"type": &BuiltinFunctionValue{
Name: "type",
Signature: &FunctionSignature{
@ -470,11 +603,11 @@ var DefaultGlobals = map[string]Value{
"exit": &BuiltinFunctionValue{
"exit",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
[]TypeSignature{&FloatSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
os.Exit(int(args[0].(*NumberValue).Number))
os.Exit(int(args[0].(*FloatValue).Number))
return &NilValue{}, nil
},
nil,
@ -483,11 +616,11 @@ var DefaultGlobals = map[string]Value{
"floor": &BuiltinFunctionValue{
"floor",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
[]TypeSignature{&FloatSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Floor(args[0].(*NumberValue).Number)}, nil
return &FloatValue{math.Floor(args[0].(*FloatValue).Number)}, nil
},
nil,
true,
@ -495,11 +628,11 @@ var DefaultGlobals = map[string]Value{
"ceil": &BuiltinFunctionValue{
"ceil",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
[]TypeSignature{&FloatSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Ceil(args[0].(*NumberValue).Number)}, nil
return &FloatValue{math.Ceil(args[0].(*FloatValue).Number)}, nil
},
nil,
true,
@ -507,14 +640,14 @@ var DefaultGlobals = map[string]Value{
"roundd": &BuiltinFunctionValue{
"roundd",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}, &NumberSignature{}},
&NumberSignature{},
[]TypeSignature{&FloatSignature{}, &FloatSignature{}},
&FloatSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
x := args[0].(*NumberValue).Number
decimals := args[1].(*NumberValue).Number
x := args[0].(*FloatValue).Number
decimals := args[1].(*FloatValue).Number
multiplier := math.Pow(10, decimals)
return &NumberValue{math.Round(x*multiplier) / multiplier}, nil
return &FloatValue{math.Round(x*multiplier) / multiplier}, nil
},
nil,
true,
@ -524,10 +657,13 @@ var DefaultGlobals = map[string]Value{
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
vm := &VM{
chunk: chunk,
stack: NewStack[Value](stackSize),
Stack: NewStack[Value](stackSize),
call: NewStack[Call](callstackSize),
globals: DefaultGlobals,
scope: &Scope{
current: map[string]Value{},
},
}
return vm
@ -544,141 +680,190 @@ func (vm *VM) Next() bool {
case InstructionReturn:
if vm.call.Current == 0 {
return false
} else {
v := vm.stack.Pop()
}
v := vm.Stack.Pop()
c := vm.call.Pop()
// reset stack current and variable end and scope
vm.variableEnd = c.variableEnd
vm.stack.Current = c.stackEnd
vm.scope = c.scope
// reset to calling position
vm.ip = c.ip
vm.chunk = c.chunk
vm.purgeVars()
vm.stack.Push(v)
}
vm.Stack.Push(v)
case InstructionPop:
vm.stack.Pop()
vm.Stack.Pop()
case InstructionConstant:
vm.stack.Push(vm.ReadConstant())
c := vm.ReadConstant()
case InstructionAdd:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
if c, ok := c.(*FunctionValue); ok {
c.Scope = vm.scope
}
vm.stack.Push(&NumberValue{l + r})
vm.Stack.Push(c)
case InstructionSub:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionAddFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&NumberValue{l - r})
vm.Stack.Push(&FloatValue{l + r})
case InstructionMul:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionSubFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&NumberValue{l * r})
vm.Stack.Push(&FloatValue{l - r})
case InstructionDiv:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionMulFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&NumberValue{l / r})
vm.Stack.Push(&FloatValue{l * r})
case InstructionNegate:
v := vm.stack.Pop().(*NumberValue).Number
case InstructionDivFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&NumberValue{-v})
vm.Stack.Push(&FloatValue{l / r})
case InstructionNegateFloat:
v := vm.Stack.Pop().(*FloatValue).Number
vm.Stack.Push(&FloatValue{-v})
case InstructionAddInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Add(l, r)})
case InstructionSubInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Sub(l, r)})
case InstructionMulInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Mul(l, r)})
case InstructionDivInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Div(l, r)})
case InstructionNegateInt:
v := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&IntegerValue{new(big.Int).Neg(v)})
case InstructionEquals:
vm.stack.Push(
&BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
vm.Stack.Push(
&BoolValue{vm.Stack.Pop().Equals(vm.Stack.Pop())},
)
case InstructionNotEqual:
vm.stack.Push(
&BoolValue{!vm.stack.Pop().Equals(vm.stack.Pop())},
vm.Stack.Push(
&BoolValue{!vm.Stack.Pop().Equals(vm.Stack.Pop())},
)
case InstructionNot:
b := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{!b})
b := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{!b})
case InstructionAnd:
r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l && r})
r := vm.Stack.Pop().(*BoolValue).Boolean
l := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{l && r})
case InstructionOr:
r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l || r})
r := vm.Stack.Pop().(*BoolValue).Boolean
l := vm.Stack.Pop().(*BoolValue).Boolean
vm.Stack.Push(&BoolValue{l || r})
case InstructionLess:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionLessFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&BoolValue{l < r})
vm.Stack.Push(&BoolValue{l < r})
case InstructionLessOrEqual:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionLessOrEqualFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&BoolValue{l <= r})
vm.Stack.Push(&BoolValue{l <= r})
case InstructionGreater:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionGreaterFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&BoolValue{l > r})
vm.Stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqual:
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
case InstructionGreaterOrEqualFloat:
r := vm.Stack.Pop().(*FloatValue).Number
l := vm.Stack.Pop().(*FloatValue).Number
vm.stack.Push(&BoolValue{l >= r})
vm.Stack.Push(&BoolValue{l >= r})
case InstructionLessInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) == -1})
case InstructionLessOrEqualInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) != 1})
case InstructionGreaterInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) == 1})
case InstructionGreaterOrEqualInt:
r := vm.Stack.Pop().(*IntegerValue).Number
l := vm.Stack.Pop().(*IntegerValue).Number
vm.Stack.Push(&BoolValue{l.Cmp(r) != -1})
case InstructionCall:
v := vm.stack.Pop()
v := vm.Stack.Pop()
switch f := v.(type) {
case *FunctionValue:
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
stackEnd: vm.stack.Current - Pos(len(f.Params)),
variableEnd: vm.variableEnd,
scope: vm.scope,
})
vm.scope = f.Scope
vm.descend()
for i := len(f.Params) - 1; i >= 0; i-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
vm.stack.items[p] = &VariableValue{
f.Params[i].Name,
vm.stack.items[p],
vm.scope,
}
vm.addVar(f.Params[i].Name, vm.Stack.Pop())
}
if f.Parent != nil {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk
vm.ip = 0
case *BuiltinFunctionValue:
args := make([]Value, len(f.Signature.In))
for i := len(f.Signature.In) - 1; i >= 0; i-- {
args[i] = vm.stack.Pop()
args[i] = vm.Stack.Pop()
}
v, err := f.F(vm, f.Parent, args)
@ -686,9 +871,13 @@ func (vm *VM) Next() bool {
vm.error(err.Error())
}
vm.stack.Push(v)
if v == nil {
v = &NilValue{}
}
vm.Stack.Push(v)
default:
vm.error(fmt.Sprintf("value called is not a function (%s, type %T)", v.DebugString(), v))
vm.error(fmt.Sprintf("%s (%s) is not callable ", v.DebugString(), v.Type()))
return false
}
@ -700,7 +889,7 @@ func (vm *VM) Next() bool {
case InstructionJumpFalse:
n := vm.NextU16()
if !vm.stack.Pop().(*BoolValue).Boolean {
if !vm.Stack.Pop().(*BoolValue).Boolean {
vm.ip += Pos(n)
}
@ -713,67 +902,61 @@ func (vm *VM) Next() bool {
return false
}
vm.stack.Push(v.value)
vm.Stack.Push(v)
case InstructionSetLocal:
value := vm.stack.Pop().(Value)
value := vm.Stack.Peek().Clone()
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
if v == nil {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
}
v.value = value.Clone()
vm.setVar(name, value)
case InstructionDeclareLocal:
vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().Clone(),
vm.Stack.Peek().Clone(),
)
case InstructionGetGlobal:
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
vm.Stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
case InstructionSetGlobal:
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.stack.Pop()
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.Stack.Pop()
case InstructionTrue:
vm.stack.Push(&BoolValue{true})
vm.Stack.Push(&BoolValue{true})
case InstructionFalse:
vm.stack.Push(&BoolValue{false})
vm.Stack.Push(&BoolValue{false})
case InstructionNil:
vm.stack.Push(&NilValue{})
vm.Stack.Push(&NilValue{})
case InstructionFormList:
n := int(vm.NextU16())
items := make([]Value, n)
for i := n - 1; i >= 0; i-- {
items[i] = vm.stack.Pop()
items[i] = vm.Stack.Pop()
}
vm.stack.Push(&ListValue{
vm.Stack.Push(&ListValue{
items,
})
case InstructionNewList:
vm.stack.Push(&ListValue{[]Value{}})
vm.Stack.Push(&ListValue{[]Value{}})
case InstructionAppend:
value := vm.stack.Pop()
list := vm.stack.Pop().(*ListValue)
value := vm.Stack.Pop()
list := vm.Stack.Pop().(*ListValue)
list.Items = append(list.Items, value)
vm.stack.Push(list)
vm.Stack.Push(list)
case InstructionConcatLists:
r := vm.stack.Pop().(*ListValue)
l := vm.stack.Pop().(*ListValue)
r := vm.Stack.Pop().(*ListValue)
l := vm.Stack.Pop().(*ListValue)
vm.stack.Push(&ListValue{
vm.Stack.Push(&ListValue{
append(l.Items, r.Items...),
})
@ -784,23 +967,26 @@ func (vm *VM) Next() bool {
vm.ascend()
case InstructionStringConversion:
v := vm.stack.Pop()
vm.stack.Push(&StringValue{v.String()})
v := vm.Stack.Pop()
vm.Stack.Push(&StringValue{v.String()})
case InstructionStringConcatenation:
r := vm.stack.Pop().(*StringValue).Text
l := vm.stack.Pop().(*StringValue).Text
r := vm.Stack.Pop().(*StringValue).Text
l := vm.Stack.Pop().(*StringValue).Text
vm.stack.Push(&StringValue{l + r})
vm.Stack.Push(&StringValue{l + r})
case InstructionSwap:
r := vm.stack.Pop()
l := vm.stack.Pop()
r := vm.Stack.Pop()
l := vm.Stack.Pop()
vm.stack.Push(r, l)
vm.Stack.Push(r, l)
case InstructionDuplicate:
vm.Stack.Push(vm.Stack.Peek().Clone())
case InstructionAccessProperty:
source := vm.stack.Pop()
source := vm.Stack.Pop()
property := vm.ReadConstant()
member, err := source.Get(property.(*StringValue).String())
@ -815,9 +1001,19 @@ func (vm *VM) Next() bool {
member.(*BuiltinFunctionValue).Parent = source
}
vm.stack.Push(member)
vm.Stack.Push(member)
case InstructionBreakpoint:
/*
// I'm keeping this
s := vm.scope
log.Printf("breakpoint %d", vm.ip)
for s != nil {
log.Printf("%s", s.current)
s = s.parent
}
*/
vm.Stack.Push(&NilValue{})
default:
panic("invalid byte code")
@ -832,11 +1028,12 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.call.Push(Call{
chunk: vm.chunk,
ip: vm.ip,
stackEnd: vm.stack.Current,
variableEnd: vm.variableEnd,
scope: vm.scope,
})
vm.scope = f.Scope
vm.descend()
for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i].Name, args[i])
}
@ -845,8 +1042,6 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.addVar("this", f.Parent)
}
vm.variableEnd = vm.stack.Current
vm.chunk = f.Chunk
vm.ip = 0
@ -855,7 +1050,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
vm.Next()
return vm.stack.Pop(), nil
return vm.Stack.Pop(), nil
case *BuiltinFunctionValue:
return f.F(vm, f.Parent, args)
@ -873,6 +1068,18 @@ func (vm *VM) TryNextByte() (Bytecode, error) {
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
}
if int(vm.ip) >= len(vm.chunk.Bytecode) {
return 0, errors.New("there are no more instructions")
}
v := vm.chunk.Bytecode[vm.ip]
vm.ip++
@ -890,57 +1097,59 @@ func (vm *VM) NextByte() Bytecode {
}
func (vm *VM) ascend() {
vm.scope--
if vm.scope < 0 {
if vm.scope.parent == nil {
panic("invalid scope")
}
vm.purgeVars()
}
// 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()
}
vm.scope = vm.scope.parent
}
func (vm *VM) descend() {
vm.scope++
old := vm.scope
vm.scope = &Scope{
map[string]Value{},
old,
}
}
func (vm *VM) addVar(name string, value Value) {
vm.variableEnd++
vm.stack.Push(&VariableValue{
name,
value,
vm.scope,
})
vm.scope.current[name] = value
}
func (vm *VM) getVar(name string) *VariableValue {
for i := vm.variableEnd - 1; i >= 0; i-- {
v, ok := vm.stack.items[i].(*VariableValue)
func (vm *VM) getVar(name string) Value {
s := vm.scope
if !ok {
continue
}
if v.name == name {
for s != nil {
if v, ok := s.current[name]; ok {
return v
}
s = s.parent
}
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 {
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 {
return vm.chunk.Constants[id]
return vm.chunk.Constants[id].Clone()
}
func (vm *VM) ReadConstant() Value {

View file

@ -49,7 +49,7 @@ func TestNewVM(t *testing.T) {
chunk := NewChunk([]Bytecode{
InstructionConstant, 0,
}, []Value{
&NumberValue{0},
&FloatValue{0},
})
stackSize := Pos(256)
callstackSize := Pos(256)
@ -76,8 +76,8 @@ func TestNewVM(t *testing.T) {
}
// should have given stack size
if vm.stack.Capacity != stackSize {
t.Errorf("vm.stack.Capacity = %d, want %d", vm.stack.Capacity, stackSize)
if vm.Stack.Capacity != stackSize {
t.Errorf("vm.stack.Capacity = %d, want %d", vm.Stack.Capacity, stackSize)
}
// should have given call stack size
@ -95,23 +95,26 @@ func BenchmarkNewVM(b *testing.B) {
func GetExecutionTestData() map[string]struct {
chunk *Chunk
resultingStack []Value
resultingScope []map[string]Value
} {
return map[string]struct {
chunk *Chunk
resultingStack []Value
resultingScope []map[string]Value
}{
"two_plus_one": {
NewChunk([]Bytecode{
InstructionConstant, 0,
InstructionConstant, 1,
InstructionAdd,
InstructionAddFloat,
},
[]Value{
&NumberValue{1}, &NumberValue{2},
&FloatValue{1}, &FloatValue{2},
}),
[]Value{
&NumberValue{3},
&FloatValue{3},
},
[]map[string]Value{},
},
"push_constant": {
NewChunk(
@ -119,12 +122,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 0,
},
[]Value{
&NumberValue{1},
&FloatValue{1},
},
),
[]Value{
&NumberValue{1},
&FloatValue{1},
},
[]map[string]Value{},
},
"push_true": {
NewChunk(
@ -136,6 +140,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&BoolValue{true},
},
[]map[string]Value{},
},
"push_false": {
NewChunk(
@ -147,6 +152,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&BoolValue{false},
},
[]map[string]Value{},
},
"push_nil": {
NewChunk(
@ -158,6 +164,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&NilValue{},
},
[]map[string]Value{},
},
"empty": {
NewChunk(
@ -165,6 +172,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{},
),
[]Value{},
[]map[string]Value{},
},
// (2 + 1) * 5 / (6 - 2)
"full_arithmetic": {
@ -172,21 +180,22 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{
InstructionConstant, 0,
InstructionConstant, 1,
InstructionAdd,
InstructionAddFloat,
InstructionConstant, 2,
InstructionMul,
InstructionMulFloat,
InstructionConstant, 3,
InstructionConstant, 0,
InstructionSub,
InstructionDiv,
InstructionSubFloat,
InstructionDivFloat,
},
[]Value{
&NumberValue{2}, &NumberValue{1}, &NumberValue{5}, &NumberValue{6},
&FloatValue{2}, &FloatValue{1}, &FloatValue{5}, &FloatValue{6},
},
),
[]Value{
&NumberValue{3.75},
&FloatValue{3.75},
},
[]map[string]Value{},
},
"equality_true": {
NewChunk(
@ -196,12 +205,13 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals,
},
[]Value{
&NumberValue{1},
&FloatValue{1},
},
),
[]Value{
&BoolValue{true},
},
[]map[string]Value{},
},
"equality_false": {
NewChunk(
@ -211,12 +221,13 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals,
},
[]Value{
&NumberValue{1}, &NumberValue{2},
&FloatValue{1}, &FloatValue{2},
},
),
[]Value{
&BoolValue{false},
},
[]map[string]Value{},
},
"inequality_false": {
NewChunk(
@ -226,12 +237,13 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual,
},
[]Value{
&NumberValue{1},
&FloatValue{1},
},
),
[]Value{
&BoolValue{false},
},
[]map[string]Value{},
},
"inequality_true": {
NewChunk(
@ -241,12 +253,13 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual,
},
[]Value{
&NumberValue{1}, &NumberValue{2},
&FloatValue{1}, &FloatValue{2},
},
),
[]Value{
&BoolValue{true},
},
[]map[string]Value{},
},
"not_true": {
NewChunk(
@ -259,6 +272,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&BoolValue{false},
},
[]map[string]Value{},
},
"not_false": {
NewChunk(
@ -271,6 +285,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&BoolValue{true},
},
[]map[string]Value{},
},
"jump": {
NewChunk(
@ -280,12 +295,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute
},
[]Value{
&NumberValue{0}, &NumberValue{1},
&FloatValue{0}, &FloatValue{1},
},
),
[]Value{
&NumberValue{1},
&FloatValue{1},
},
[]map[string]Value{},
},
"jump_false/false": {
NewChunk(
@ -296,12 +312,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute
},
[]Value{
&NumberValue{0}, &NumberValue{1},
&FloatValue{0}, &FloatValue{1},
},
),
[]Value{
&NumberValue{1},
&FloatValue{1},
},
[]map[string]Value{},
},
"jump_false/true": {
NewChunk(
@ -312,12 +329,13 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute
},
[]Value{
&NumberValue{0}, &NumberValue{1},
&FloatValue{0}, &FloatValue{1},
},
),
[]Value{
&NumberValue{0}, &NumberValue{1},
&FloatValue{0}, &FloatValue{1},
},
[]map[string]Value{},
},
"declare_local": {
NewChunk(
@ -326,14 +344,13 @@ func GetExecutionTestData() map[string]struct {
InstructionDeclareLocal, 1,
},
[]Value{
&NumberValue{0}, &StringValue{"a"},
&FloatValue{0}, &StringValue{"a"},
},
),
[]Value{
&VariableValue{
"a",
&NumberValue{0},
0,
[]Value{&FloatValue{0}},
[]map[string]Value{
{
"a": &FloatValue{0},
},
},
},
@ -342,18 +359,19 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{
InstructionConstant, 0,
InstructionDeclareLocal, 1,
InstructionPop,
InstructionConstant, 2,
InstructionSetLocal, 1, // reassign
InstructionPop,
},
[]Value{
&NumberValue{0}, &StringValue{"a"}, &NumberValue{1},
&FloatValue{0}, &StringValue{"a"}, &FloatValue{1},
},
),
[]Value{
&VariableValue{
"a",
&NumberValue{1},
0,
[]Value{},
[]map[string]Value{
{
"a": &FloatValue{1},
},
},
},
@ -362,19 +380,18 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{
InstructionConstant, 0,
InstructionDeclareLocal, 1,
InstructionGetLocal, 1, // reassign
},
[]Value{
&NumberValue{0}, &StringValue{"a"},
&FloatValue{0}, &StringValue{"a"},
},
),
[]Value{
&VariableValue{
"a",
&NumberValue{0},
0,
&FloatValue{0},
},
[]map[string]Value{
{
"a": &FloatValue{0},
},
&NumberValue{0},
},
},
"get_reassigned_local": {
@ -382,23 +399,25 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{
InstructionConstant, 0,
InstructionDeclareLocal, 1,
InstructionPop,
InstructionGetLocal, 1,
InstructionConstant, 2,
InstructionSetLocal, 1, // reassign
InstructionPop,
InstructionGetLocal, 1,
},
[]Value{
&NumberValue{0}, &StringValue{"a"}, &NumberValue{1},
&FloatValue{0}, &StringValue{"a"}, &FloatValue{1},
},
),
[]Value{
&VariableValue{
"a",
&NumberValue{1},
0,
&FloatValue{0},
&FloatValue{1},
},
[]map[string]Value{
{
"a": &FloatValue{1},
},
&NumberValue{0},
&NumberValue{1},
},
},
"variable_scope": {
@ -406,26 +425,28 @@ func GetExecutionTestData() map[string]struct {
[]Bytecode{
InstructionConstant, 0,
InstructionDeclareLocal, 1,
InstructionPop,
InstructionDescend,
InstructionConstant, 2,
InstructionDeclareLocal, 3,
InstructionPop,
InstructionDescend,
InstructionConstant, 4,
InstructionDeclareLocal, 5,
InstructionPop,
InstructionAscend,
InstructionAscend,
},
[]Value{
&NumberValue{0}, &StringValue{"a"},
&NumberValue{1}, &StringValue{"b"},
&NumberValue{2}, &StringValue{"c"},
&FloatValue{0}, &StringValue{"a"},
&FloatValue{1}, &StringValue{"b"},
&FloatValue{2}, &StringValue{"c"},
},
),
[]Value{
&VariableValue{
"a",
&NumberValue{0},
0,
[]Value{},
[]map[string]Value{
{
"a": &FloatValue{0},
},
},
},
@ -438,25 +459,25 @@ func GetExecutionTestData() map[string]struct {
InstructionCall,
},
[]Value{
&NumberValue{1},
&NumberValue{2},
&FloatValue{1},
&FloatValue{2},
&FunctionValue{
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
&FloatSignature{},
},
{
"b",
&NumberSignature{},
&FloatSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
InstructionGetLocal, 1,
InstructionAdd,
InstructionAddFloat,
InstructionReturn,
},
[]Value{
@ -467,32 +488,34 @@ func GetExecutionTestData() map[string]struct {
},
),
[]Value{
&NumberValue{3},
&FloatValue{3},
},
[]map[string]Value{},
},
"function_calling_function": {
NewChunk(
[]Bytecode{
InstructionConstant, 3,
InstructionDeclareLocal, 4,
InstructionPop,
InstructionConstant, 0,
InstructionConstant, 1,
InstructionConstant, 2,
InstructionCall,
},
[]Value{
&NumberValue{1},
&NumberValue{2},
&FloatValue{1},
&FloatValue{2},
&FunctionValue{
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
&FloatSignature{},
},
{
"b",
&NumberSignature{},
&FloatSignature{},
},
},
Chunk: NewChunk(
@ -501,7 +524,7 @@ func GetExecutionTestData() map[string]struct {
InstructionGetLocal, 2, InstructionCall, // square the number
InstructionGetLocal, 1,
InstructionGetLocal, 2, InstructionCall, // square the number
InstructionAdd,
InstructionAddFloat,
InstructionReturn,
},
[]Value{
@ -514,14 +537,14 @@ func GetExecutionTestData() map[string]struct {
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
&FloatSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
InstructionGetLocal, 0,
InstructionMul,
InstructionMulFloat,
InstructionReturn,
},
[]Value{
@ -533,21 +556,23 @@ func GetExecutionTestData() map[string]struct {
},
),
[]Value{
&VariableValue{
"square",
&FunctionValue{
&FloatValue{5},
},
[]map[string]Value{
{
"square": &FunctionValue{
Name: "square",
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
&FloatSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
InstructionGetLocal, 0,
InstructionMul,
InstructionMulFloat,
InstructionReturn,
},
[]Value{
@ -555,9 +580,7 @@ func GetExecutionTestData() map[string]struct {
},
),
},
0,
},
&NumberValue{5},
},
},
"list_concat": {
@ -570,13 +593,13 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&FloatValue{1},
&FloatValue{2},
},
},
&ListValue{
[]Value{
&NumberValue{3},
&FloatValue{3},
},
},
},
@ -584,12 +607,13 @@ func GetExecutionTestData() map[string]struct {
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
&FloatValue{1},
&FloatValue{2},
&FloatValue{3},
},
},
},
[]map[string]Value{},
},
}
}
@ -604,7 +628,7 @@ func TestVM_Execution(t *testing.T) {
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,
},
[]Value{
&NumberValue{0},
&FloatValue{0},
},
),
16,
@ -741,7 +765,7 @@ func TestVM_Jump(t *testing.T) {
InstructionConstant, 2,
},
[]Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2},
&FloatValue{0}, &FloatValue{1}, &FloatValue{2},
},
),
16,
@ -766,7 +790,7 @@ func TestVM_JumpFalse(t *testing.T) {
InstructionConstant, 2,
},
[]Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2},
&FloatValue{0}, &FloatValue{1}, &FloatValue{2},
},
),
16,
@ -792,7 +816,7 @@ func TestVM_DontJumpFalse(t *testing.T) {
InstructionConstant, 2,
},
[]Value{
&NumberValue{0}, &NumberValue{1}, &NumberValue{2},
&FloatValue{0}, &FloatValue{1}, &FloatValue{2},
},
),
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
# The running sum of terms
tot := 0
tot := 0.0
n := 1
while n <= terms {
tot = tot + 1 / (n*n)
tot = tot + 1.0/float(n*n)
n = n + 1
}
tot = tot * 6
tot = tot * 6.0
# get the absolute value of a number
func abs(x: number) number {
if x < 0 {
fn abs(x: float) -> float {
if x < 0.0 {
return -x
}
return x
@ -30,19 +30,19 @@ func abs(x: number) number {
# see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy
SQRT_ACC := 0.00000001
func sqrt(x: number) number {
pg := 0 # previous guess
g := 1 # current guess
fn sqrt(x: float) -> float {
pg := 0.0 # previous guess
g := 1.0 # current guess
while abs(pg - g) >= SQRT_ACC {
pg = g
g = (pg + tot/pg)/2
g = (pg + x/pg)/2.0
}
return g
}
tot = sqrt(tot)
pi := sqrt(tot)
# output the result
write(str(tot))
println(pi)

View file

@ -13,3 +13,5 @@ _slow_blink := CSI + "5m"
_rapid_blink := CSI + "6m"
_strike := CSI + "9m"
_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 := []
i := 0

View file

@ -7,7 +7,7 @@ E := 2.718281828459045235360287471352
# 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
# returned value is x.
func abs(x: number) number {
fn absf(x: float) -> float {
# if the number is negative
if x < 0 {
# negate it so it's positive
@ -17,15 +17,23 @@ func abs(x: number) number {
return x
}
fn absi(n: int) -> int {
if n < 0 {
-n
} else {
n
}
}
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
}
NEWTONS_ACC := 0.000000000001
func newtons(f: func(number)number) number {
pg := 0
g := 1
fn newtons(f: fn(float) -> float) -> float {
pg := 0.0
g := 1.0
while abs(g - pg) > NEWTONS_ACC {
pg = g
@ -41,9 +49,9 @@ MAX_SQRT_DX := 0.0000001
# x: number
# Calculate the approximate square root using newton's method until
# the accuracy has increased by less than the variable `MAX_SQRT_DX`.
func sqrt(x: number) number {
fn sqrt(x: float) -> float {
ng := x
g := 1
g := 1.0
while abs(g - ng) > MAX_SQRT_DX {
g = ng
@ -51,8 +59,6 @@ func sqrt(x: number) number {
# create new guess
ng = (g + x / g) / 2
}
return g
}
# floor(x)
@ -72,7 +78,7 @@ func sqrt(x: number) number {
# round(x)
# x: number
# Return the closest whole number to the value x.
func round(x: number) number {
fn round(x: float) -> float {
f := floor(x)
if x - f > 0.5 {
@ -86,7 +92,7 @@ func round(x: number) number {
# x: number; any number
# n: number; the number to divide by
# 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 {
return 0
}
@ -110,17 +116,18 @@ func mod(x: number, n: number) number {
# This value is only reasonable if 0<x<1.
# It is approximated using the taylor series of e**x.
SM_EXP_ACC := 0.00000000001
func sm_exp(x: number) number {
p_tot := 0
tot := 1
fn sm_exp(x: float) -> float {
p_tot := 0.0
tot := 1.0
n := 1
x_pow := x
f := 1
f := 1.0
while abs(tot - p_tot) > SM_EXP_ACC {
p_tot = tot
t := x_pow / f
tot = tot + t
f = f * (n+1)
f = f * float(n+1)
x_pow = x_pow * x
n = n + 1
}
@ -131,22 +138,22 @@ func sm_exp(x: number) number {
# exp(x)
# x: number; any number
# 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)
tot := 1
tot := 1.0
while n >= 1 {
tot = tot * E
n = n - 1
}
if n > 0 {
if n > 0.0 {
tot = tot * sm_exp(n)
}
if x < 0 {
return 1/tot
1.0/tot
} else {
return tot
tot
}
}
@ -155,9 +162,9 @@ func exp(x: number) number {
# Get the approximate value of the natural logarithm
# This function uses newton's method to approximate.
LN_ACC := 0.0000000001
func ln(x: number) number {
pg := 0
g := 1
fn ln(x: float) -> float {
pg := 0.0
g := 1.0
while abs(pg - g) > LN_ACC {
pg = g
@ -171,7 +178,7 @@ func ln(x: number) number {
# x: number; any number. The base
# p: number; the value of the exponent
# 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))
}
@ -181,15 +188,15 @@ func pow(x: number, p: number) number {
# Calculate the approximate value of the logarithm
# of a with b as base.
LOG_ACC := 0.0000001
func log(a: number, b: number) number {
fn log(a: float, b: float) -> float {
ln_b := ln(b)
pg := 0
g := 1
pg := 0.0
g := 1.0
while abs(g - pg) > LOG_ACC {
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
@ -199,9 +206,9 @@ func log(a: number, b: number) number {
# x: number; an angle in radians
# 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
func sin(x: number) number {
f := 1
x = mod(x, 2*PI)
fn sin(x: float) -> float {
f := 1.0
x = mod(x, 2.0*PI)
if x > PI {
x = PI - x
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)
tot := x
l := 1
i := 1
s := -1
l := 1.0
i := 1.0
s := -1.0
while i <= 19 {
i = i + 2
l = s * l * x / i / (i-1)
i = i + 2.0
l = s * l * x / i / (i-1.0)
tot = tot + l

View file

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

View file

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

View file

@ -1,20 +1,20 @@
list := []number
list := []int
x := 1
while x <= 1000 {
list.append(x)
assertEq(list.reduce(func(tot: number, a: number) number {
assertEq(list.reduce(fn(tot: int, a: int) -> int {
return tot + a
}, 0), x*(x + 1)/2)
x = x + 1
}
func sum(a: number, b: number) number {
fn sum(a: int, b: int) -> int {
return a + b
}
list = []number
list = []int
x = 1
while x <= 100 {
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
]
func fib(n: number) number {
fn fib(n: int) -> int {
if n < 2 {
return n
}
@ -29,4 +29,4 @@ while x < fibonacci_numbers.length() {
x = x + 1
}
write("")
println("")

View file

@ -1,5 +1,5 @@
list := []number
list := []int
list.append(1)
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
}
breakpoint
assertEq(sum(1, 2), 3)
breakpoint
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(true), "boolean")
# lists
assertEq(type(["Hello", "world"]), "list[string]")
assertEq(type([0, 1]), "list[number]")
assertEq(type([0, 1]), "list[int]")