Merge branch 'types' into main
All checks were successful
/ test (push) Successful in 46s

This commit is contained in:
Neemek 2025-09-16 17:38:46 +00:00
commit f53ab30dbe
42 changed files with 3635 additions and 904 deletions

View file

@ -22,13 +22,22 @@ func GetAllTestCases() map[string]AllTestCase {
},
},
"func": {
"func sum(a, b) {\n\treturn a + b\n}\nsum(1, 2)",
"func sum(a: number, b: number) number {\n\treturn a + b\n}\n_ = sum(1, 2)",
[]Value{
&VariableValue{
"sum",
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: &Chunk{
Bytecode: []Bytecode{
InstructionDescend,
@ -45,6 +54,63 @@ func GetAllTestCases() map[string]AllTestCase {
},
},
},
"list": {
"a := [1, 2]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
},
},
},
"constant_list_concat": {
"a := [1, 2] + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
"list_concat": {
"a := [1, 2]\nb := a + [3]",
[]Value{
&VariableValue{
"a",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
0,
},
&VariableValue{
"b",
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
0,
},
},
},
}
}
@ -64,18 +130,18 @@ func TestAll(t *testing.T) {
}
t.Log("Initializing parser")
p := NewParser(tokens)
p := NewParser(tc.src, tokens)
t.Log("Parsing tokens")
tree, err := p.Parse()
tree, err := p.Parse(tc.src)
if err != nil {
print(err.(*ParsingError).Format([]rune(tc.src)))
print(err.(ParsingError).Format())
t.Fatalf("parser had an error")
}
t.Log("Initializing compiler")
c := NewCompiler()
c := NewCompiler([]rune(tc.src))
t.Log("Compiling parse tree")
err = c.Compile(tree)
@ -107,10 +173,10 @@ func BenchmarkAll(b *testing.B) {
l := NewLexer(tc.src)
tokens, _ := l.Tokenize()
p := NewParser(tokens)
tree, _ := p.Parse()
p := NewParser(tc.src, tokens)
tree, _ := p.Parse(tc.src)
c := NewCompiler()
c := NewCompiler([]rune(tc.src))
_ = c.Compile(tree)
vm := NewVM(c.Chunk, 256, 256)

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ import (
)
func TestNewCompiler(t *testing.T) {
c := NewCompiler()
c := NewCompiler([]rune{})
if c == nil {
t.Fatal("NewCompiler returned nil")
@ -23,54 +23,85 @@ func TestNewCompiler(t *testing.T) {
func BenchmarkNewCompiler(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewCompiler()
_ = NewCompiler([]rune{})
}
}
type CompileTestData struct {
tree Node
program *Program
expectedStack []Value
}
func GetCompileTestData() map[string]CompileTestData {
return map[string]CompileTestData{
"constant_string": {
&StringNode{
"Hello world!",
"\"Hello world!\"",
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&StringValue{"Hello world!"},
&VariableValue{
"a",
&StringValue{"Hello world!"},
0,
},
},
},
"conditional_false": {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
},
true,
},
&ConditionalNode{
&BooleanNode{
false,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
},
false,
},
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
nil,
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
@ -81,33 +112,45 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
"conditional_true": {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
},
true,
},
&ConditionalNode{
&BooleanNode{
true,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
},
false,
},
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
nil,
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
@ -118,43 +161,58 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
"conditional_else_false": {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
},
true,
},
&ConditionalNode{
&BooleanNode{
false,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
},
false,
},
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
2,
},
false,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
@ -165,43 +223,58 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
"conditional_else_true": {
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
},
true,
},
&ConditionalNode{
&BooleanNode{
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
},
false,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
2,
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
false,
},
0, 0,
},
&BlockNode{
[]Node{
&AssignNode{
"a",
&NumberNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
@ -212,49 +285,107 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
"addition": {
&BinaryNode{
BinaryAddition,
&NumberNode{
1,
},
&NumberNode{
2,
},
},
[]Value{
&NumberValue{3},
},
},
"sum_function": {&BlockNode{
[]Node{
&AssignNode{
"sum",
&FunctionNode{
"sum",
[]string{"a", "b"},
&BlockNode{
[]Node{
&ReturnNode{
&BinaryNode{
BinaryAddition,
&ReferenceNode{"a"},
&ReferenceNode{"b"},
},
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
&BinaryNode{
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
true,
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&NumberValue{3},
0,
},
},
},
"sum_function": {
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"sum",
&FunctionNode{
"sum",
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{
[]Node{
&ReturnNode{
&BinaryNode{
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"sum",
&FunctionValue{
"sum",
[]string{"a", "b"},
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
@ -275,43 +406,63 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
"remove_func_vars": {
&BlockNode{
[]Node{
&AssignNode{
"a",
&FunctionNode{
&Program{
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"a",
[]string{},
&BlockNode{
[]Node{
&AssignNode{
"b",
&NumberNode{1},
true,
},
&ReturnNode{
&ReferenceNode{"b"},
&FunctionNode{
"a",
[]FunctionParameter{},
&NumberSignature{},
&BlockNode{
[]Node{
&AssignNode{
"b",
&NumberNode{
1,
0, 0,
},
true,
0, 0,
},
&ReturnNode{
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
true,
},
&CallNode{
&ReferenceNode{
"a",
&CallNode{
&ReferenceNode{
"a",
0, 0,
},
[]Node{},
false,
0, 0,
},
[]Node{},
false,
},
0, 0,
},
"",
},
[]Value{
&VariableValue{
"a",
&FunctionValue{
"a",
[]string{},
[]FunctionParameter{},
&NumberSignature{},
NewChunk(
[]Bytecode{
InstructionDescend,
@ -331,6 +482,58 @@ func GetCompileTestData() map[string]CompileTestData {
},
},
},
"two_lists": {
program: &Program{
[]string{},
&BlockNode{
statements: []Node{
&AssignNode{
name: "a",
value: &ListNode{
items: []Node{
&NumberNode{value: 1},
&NumberNode{value: 2},
},
},
declare: true,
},
&AssignNode{
name: "b",
value: &ListNode{
items: []Node{
&StringNode{value: "Hello"},
&StringNode{value: "world"},
},
},
declare: true,
},
},
},
"",
},
expectedStack: []Value{
&VariableValue{
name: "a",
value: &ListValue{
Items: []Value{
&NumberValue{1},
&NumberValue{2},
},
},
scope: 0,
},
&VariableValue{
name: "b",
value: &ListValue{
Items: []Value{
&StringValue{"Hello"},
&StringValue{"world"},
},
},
scope: 0,
},
},
},
}
}
@ -343,7 +546,7 @@ func printChunk(t *testing.T, name string, chunk *Chunk) {
t.Logf("=-= constants =-=")
for i, ct := range chunk.Constants {
t.Logf("c=%d \t%s", i, ct)
t.Logf("c=%d \t%s", i, ct.DebugString())
f, ok := ct.(*FunctionValue)
if ok {
@ -360,10 +563,10 @@ func TestCompile(t *testing.T) {
for name, testCase := range data {
t.Run(name, func(t *testing.T) {
t.Log("Initializing compiler")
c := NewCompiler()
c := NewCompiler([]rune(testCase.program.String()))
t.Log("Compiling node tree")
err := c.Compile(testCase.tree)
err := c.Compile(testCase.program)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}
@ -389,8 +592,8 @@ func BenchmarkCompile(b *testing.B) {
for name, testCase := range data {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
c := NewCompiler()
_ = c.Compile(testCase.tree)
c := NewCompiler([]rune{})
_ = c.Compile(testCase.program)
}
})
}
@ -399,7 +602,7 @@ func BenchmarkCompile(b *testing.B) {
func TestCompiler_AddU16(t *testing.T) {
for i := 0; i <= 0xffff; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) {
c := NewCompiler()
c := NewCompiler([]rune{})
c.addU16(uint16(i))
if c.Chunk.Bytecode[0] != Bytecode(i>>8) {
@ -417,24 +620,9 @@ func TestCompiler_CleanStack(t *testing.T) {
cases := GetCompileTestData()
for name, tc := range cases {
switch tc.tree.Type() {
// skip all expected unclean nodes
case StringNodeType, NumberNodeType, ReferenceNodeType, BooleanNodeType, NilNodeType, BinaryNodeType, ReturnNodeType:
continue
case CallNodeType:
if tc.tree.(*CallNode).keep {
// if we know it should be unclean, skip it
continue
}
// clean statements
default:
}
t.Run(name, func(t *testing.T) {
c := NewCompiler()
err := c.Compile(tc.tree)
c := NewCompiler([]rune(tc.program.String()))
err := c.Compile(tc.program)
if err != nil {
t.Fatalf("Compiling failed: %v", err)
}

View file

@ -29,6 +29,7 @@ const (
TokenSemicolon
TokenNumber
TokenHexadecimal
TokenString
TokenName
@ -53,6 +54,7 @@ const (
TokenComma
TokenDot
TokenColon
TokenAssign
TokenDeclare
@ -64,6 +66,7 @@ const (
TokenLessThanOrEqual
TokenDoubleAmpersand
TokenPipe
TokenDoublePipe
TokenBreakpoint
@ -153,6 +156,12 @@ func (t TokenType) String() string {
return "close bracket"
case TokenImport:
return "import"
case TokenColon:
return "colon"
case TokenPipe:
return "pipe"
case TokenHexadecimal:
return "hexadecimal"
}
return "UNDEFINED TOKENTYPE STRING CONVERSION"
@ -234,11 +243,11 @@ func (l *Lexer) NextToken() (Token, error) {
case '.':
return l.makeToken(TokenDot), nil
case ':':
if !l.accept('=') {
return l.makeToken(TokenError), errors.New("malformed token (got ':', expected '=' to follow)")
if l.accept('=') {
return l.makeToken(TokenDeclare), nil
}
return l.makeToken(TokenDeclare), nil
return l.makeToken(TokenColon), nil
case '!':
if l.accept('=') {
return l.makeToken(TokenBangEquals), nil
@ -276,7 +285,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenDoublePipe), nil
}
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)")
return l.makeToken(TokenPipe), nil
case '"':
// include ending quote
@ -327,6 +336,19 @@ func (l *Lexer) NextToken() (Token, error) {
default:
return l.makeToken(TokenName), nil
}
} else if c == '0' && l.peek() != '.' {
if l.peek() == 'x' {
l.advance()
// hex
for unicode.In(l.peek(), unicode.Hex_Digit) {
l.advance()
}
return l.makeToken(TokenHexadecimal), nil
}
return l.makeToken(TokenNumber), nil
} else if unicode.IsDigit(c) {
for unicode.IsDigit(l.peek()) {
l.advance()

View file

@ -11,6 +11,8 @@ type NodeType int
type Node interface {
Type() NodeType
String() string
Bounds() (Pos, Pos)
}
const (
@ -21,6 +23,7 @@ const (
NilNodeType
ListNodeType
BinaryNodeType
UnaryNodeType
BlockNodeType
ConditionalNodeType
LoopNodeType
@ -29,7 +32,6 @@ const (
FunctionNodeType
ReturnNodeType
AccessNodeType
ImportNodeType
BreakpointNodeType
)
@ -67,8 +69,8 @@ func (n NodeType) String() string {
return "Access"
case BreakpointNodeType:
return "Breakpoint"
case ImportNodeType:
return "Import"
case UnaryNodeType:
return "Unary"
}
return "Invalid Node Type"
}
@ -76,6 +78,9 @@ func (n NodeType) String() string {
// ReferenceNode a reference to a variable on the stack
type ReferenceNode struct {
name string
start Pos
end Pos
}
func (n ReferenceNode) Type() NodeType {
@ -86,10 +91,17 @@ func (n ReferenceNode) String() string {
return n.name
}
func (n ReferenceNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// StringNode string/text values
type StringNode struct {
value string
quoted string
start Pos
end Pos
}
func (n StringNode) Type() NodeType {
@ -100,8 +112,15 @@ func (n StringNode) String() string {
return n.quoted
}
func (n StringNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type NumberNode struct {
value float64
start Pos
end Pos
}
func (n NumberNode) Type() NodeType {
@ -112,9 +131,17 @@ func (n NumberNode) String() string {
return strconv.FormatFloat(n.value, 'g', -1, NumberSize)
}
func (n NumberNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ListNode a list or sequence of values (items)
type ListNode struct {
items []Node
items []Node
content TypeSignature
start Pos
end Pos
}
func (n ListNode) Type() NodeType {
@ -125,18 +152,25 @@ func (n ListNode) String() string {
sb := strings.Builder{}
sb.WriteString("[")
for i, item := range n.items {
sb.WriteString(item.String())
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(item.String())
}
sb.WriteString("]")
return sb.String()
}
func (n ListNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type AccessNode struct {
source Node
property string
start Pos
end Pos
}
func (n AccessNode) Type() NodeType {
@ -147,6 +181,10 @@ func (n AccessNode) String() string {
return fmt.Sprintf("(%s from %s)", n.property, n.source)
}
func (n AccessNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BinaryOperation uint
func (n BinaryOperation) String() string {
@ -198,11 +236,45 @@ const (
BinaryGreaterEqual
)
func (n BinaryOperation) Symbol() string {
switch n {
case BinaryAddition:
return "+"
case BinarySubtraction:
return "-"
case BinaryMultiplication:
return "*"
case BinaryDivision:
return "/"
case BinaryEquality:
return "=="
case BinaryInequality:
return "!="
case BinaryLess:
return "<"
case BinaryGreater:
return ">"
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
case BinaryLessEqual:
return "<="
case BinaryGreaterEqual:
return ">="
}
panic("unsupported binary operation to symbol conversion for " + n.String())
}
// BinaryNode All operations which take 2 variables
type BinaryNode struct {
BinaryOperation
Left Node
Right Node
start Pos
end Pos
}
func (n BinaryNode) Type() NodeType {
@ -213,9 +285,65 @@ func (n BinaryNode) String() string {
return fmt.Sprintf("%s %s %s", n.Left.String(), n.BinaryOperation.String(), n.Right.String())
}
func (n BinaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type UnaryOperation int
const (
UnaryNegate UnaryOperation = iota
UnaryNot
)
func (op UnaryOperation) String() string {
switch op {
case UnaryNegate:
return "negate"
case UnaryNot:
return "not"
}
panic("unimplemented unary operation to string conversion")
}
func (op UnaryOperation) Symbol() string {
switch op {
case UnaryNegate:
return "-"
case UnaryNot:
return "!"
}
panic("unimplemented unary operation to symbol conversion")
}
type UnaryNode struct {
UnaryOperation
value Node
start Pos
end Pos
}
func (n UnaryNode) Type() NodeType {
return UnaryNodeType
}
func (n UnaryNode) String() string {
return fmt.Sprintf("%s %s", n.UnaryOperation.String(), n.value.String())
}
func (n UnaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BooleanNode boolean value
type BooleanNode struct {
value bool
start Pos
end Pos
}
func (n BooleanNode) Type() NodeType {
@ -226,8 +354,15 @@ func (n BooleanNode) String() string {
return strconv.FormatBool(n.value)
}
func (n BooleanNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// NilNode nil value
type NilNode struct{}
type NilNode struct {
start Pos
end Pos
}
func (n NilNode) Type() NodeType {
return NilNodeType
@ -237,9 +372,16 @@ func (n NilNode) String() string {
return "nil"
}
func (n NilNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BlockNode block node with statements
type BlockNode struct {
statements []Node
start Pos
end Pos
}
func (n BlockNode) Type() NodeType {
@ -257,16 +399,8 @@ func (n BlockNode) String() string {
return builder.String()
}
type ImportNode struct {
path string
}
func (n ImportNode) Type() NodeType {
return ImportNodeType
}
func (n ImportNode) String() string {
return fmt.Sprintf("import %s", n.path)
func (n BlockNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ConditionalNode conditionals (if statements)
@ -274,6 +408,9 @@ type ConditionalNode struct {
condition Node
do Node
otherwise Node
start Pos
end Pos
}
func (n ConditionalNode) Type() NodeType {
@ -281,13 +418,24 @@ func (n ConditionalNode) Type() NodeType {
}
func (n ConditionalNode) String() string {
if n.otherwise == nil {
return fmt.Sprintf("if %s then %s", n.condition.String(), n.do.String())
}
return fmt.Sprintf("if %s then %s otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String())
}
func (n ConditionalNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// LoopNode Loops (for/while)
type LoopNode struct {
condition Node
do Node
start Pos
end Pos
}
func (n LoopNode) Type() NodeType {
@ -298,11 +446,18 @@ func (n LoopNode) String() string {
return fmt.Sprintf("while %s loop %s", n.condition.String(), n.do.String())
}
func (n LoopNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// AssignNode assignment
type AssignNode struct {
name string
value Node
declare bool
start Pos
end Pos
}
func (n AssignNode) Type() NodeType {
@ -313,11 +468,18 @@ func (n AssignNode) String() string {
return fmt.Sprintf("set %s to %s", n.name, n.value)
}
func (n AssignNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// CallNode function call
type CallNode struct {
source Node
args []Node
keep bool
start Pos
end Pos
}
func (n CallNode) Type() NodeType {
@ -328,11 +490,24 @@ func (n CallNode) String() string {
return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args)
}
func (n CallNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// FunctionNode definition of function
type FunctionNode struct {
name string
params []string
logic Node
name string
parameters []FunctionParameter
yield TypeSignature
logic Node
start Pos
end Pos
}
type FunctionParameter struct {
Name string
Signature TypeSignature
}
func (n FunctionNode) Type() NodeType {
@ -343,9 +518,16 @@ func (n FunctionNode) String() string {
return fmt.Sprintf("definition of %s, do %s", n.name, n.logic.String())
}
func (n FunctionNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ReturnNode return a value out of this context
type ReturnNode struct {
value Node
start Pos
end Pos
}
func (n ReturnNode) Type() NodeType {
@ -356,7 +538,14 @@ func (n ReturnNode) String() string {
return fmt.Sprintf("return %s", n.value)
}
type BreakpointNode struct{}
func (n ReturnNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BreakpointNode struct {
start Pos
end Pos
}
func (n BreakpointNode) Type() NodeType {
return BreakpointNodeType
@ -365,3 +554,7 @@ func (n BreakpointNode) Type() NodeType {
func (n BreakpointNode) String() string {
return "breakpoint"
}
func (n BreakpointNode) Bounds() (Pos, Pos) {
return n.start, n.end
}

View file

@ -8,17 +8,24 @@ import (
"strings"
)
type FormatedError interface {
Error() string
Format() string
}
type ParsingError struct {
Description string
Causer *Token
Source string
}
func (p *ParsingError) Error() string {
func (p ParsingError) Error() string {
return p.Description
}
// Format Print a rich and informative error
func (p *ParsingError) Format(src []rune) string {
func (p ParsingError) Format() string {
src := []rune(p.Source)
builder := strings.Builder{}
lineNumber := 1
@ -38,13 +45,17 @@ func (p *ParsingError) Format(src []rune) string {
}
}
builder.WriteString(" \t v ")
descriptor := fmt.Sprintf("%d:%d", lineNumber, int(p.Causer.Start)-lineBeginning+1)
builder.WriteString(p.Description)
builder.WriteRune('\n')
builder.WriteString(fmt.Sprintf(" %d:%d\t | %s", lineNumber, int(p.Causer.Start)-lineBeginning+1, string(src[lineBeginning:lineEnd])))
builder.WriteString(descriptor)
builder.WriteString(" | ")
builder.WriteString(string(src[lineBeginning:lineEnd]))
builder.WriteString("\n\t ^")
builder.WriteString("\n")
builder.WriteString(strings.Repeat(" ", len(descriptor)))
builder.WriteString(" ")
for i := lineBeginning; i <= int(p.Causer.Start); i++ {
builder.WriteRune(' ')
}
@ -58,20 +69,45 @@ func (p *ParsingError) Format(src []rune) string {
}
type Parser struct {
source string
tokens []Token
prev *Token
curr *Token
pos Pos
}
func NewParser(tokens []Token) *Parser {
func NewParser(source string, tokens []Token) *Parser {
return &Parser{
source: source,
tokens: tokens,
pos: 0,
}
}
func (p *Parser) Parse() (Node, error) {
type Program struct {
Imports []string
Block *BlockNode
Path string
}
func (p *Program) String() string {
builder := strings.Builder{}
builder.WriteString("=== Imports ===\n")
for _, i := range p.Imports {
builder.WriteString(i)
builder.WriteString("\n")
}
builder.WriteString("===============\n")
builder.WriteString(p.Block.String())
return builder.String()
}
func (p *Parser) Parse(path string) (*Program, error) {
imports := make([]string, 0)
// top level statements
statements := make([]Node, 0)
@ -79,17 +115,33 @@ func (p *Parser) Parse() (Node, error) {
p.advance()
for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF {
if p.accept(TokenImport) {
if err := p.expect(TokenString, "import requires a path/name to import"); err != nil {
return nil, err
}
imports = append(imports, p.prev.Lexeme[1:len(p.prev.Lexeme)-1])
}
b, err := p.block(true)
if err != nil {
return nil, err
}
statements = append(statements, b)
if b != nil {
statements = append(statements, b)
}
}
return &BlockNode{
statements: statements,
return &Program{
imports,
&BlockNode{
statements,
0,
p.curr.Start + p.curr.Length,
},
path,
}, nil
}
@ -107,9 +159,9 @@ func (p *Parser) accept(tokenType TokenType) bool {
return false
}
func (p *Parser) expect(tokenType TokenType) error {
func (p *Parser) expect(tokenType TokenType, reason string) error {
if !p.accept(tokenType) {
return p.error("Expected token "+tokenType.String()+", got "+p.curr.Type.String(), p.curr)
return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.curr.Type, reason), p.curr)
}
return nil
}
@ -134,9 +186,10 @@ func (p *Parser) advance() {
}
func (p *Parser) error(error string, causer *Token) error {
return &ParsingError{
return ParsingError{
Description: error,
Causer: causer,
Source: p.source,
}
}
@ -147,6 +200,8 @@ func (p *Parser) factor() (Node, error) {
return &StringNode{
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenNumber:
@ -159,17 +214,37 @@ func (p *Parser) factor() (Node, error) {
return &NumberNode{
num,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, 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
}
return &NumberNode{
float64(num),
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenTrue:
p.advance()
return &BooleanNode{
true,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenFalse:
p.advance()
return &BooleanNode{
false,
p.prev.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenNil:
@ -178,11 +253,25 @@ func (p *Parser) factor() (Node, error) {
case TokenOpenBracket:
p.advance()
start := p.prev.Start
if p.accept(TokenCloseBracket) {
s, err := p.parseSignature()
if err != nil {
return nil, err
}
return &ListNode{
[]Node{},
s,
start,
p.prev.Start + p.prev.Length,
}, nil
}
var values []Node
for !p.accept(TokenCloseBracket) {
if len(values) > 0 {
if err := p.expect(TokenComma); err != nil {
if err := p.expect(TokenComma, "list values must be separated by a comma"); err != nil {
return nil, err
}
}
@ -198,24 +287,48 @@ func (p *Parser) factor() (Node, error) {
return &ListNode{
values,
nil,
start,
p.prev.Start + p.prev.Length,
}, nil
// unary minus
case TokenMinus:
p.advance()
first := p.prev
f, err := p.factor()
if err != nil {
return nil, err
}
return &BinaryNode{
BinarySubtraction,
&NumberNode{0},
return &UnaryNode{
UnaryNegate,
f,
first.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenBang:
p.advance()
start := p.prev.Start
v, err := p.factor()
if err != nil {
return nil, err
}
return &UnaryNode{
UnaryNot,
v,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenName:
p.advance()
name := (*p.prev).Lexeme
start := p.prev.Start
nameEnd := start + p.prev.Length
if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs()
@ -226,23 +339,39 @@ func (p *Parser) factor() (Node, error) {
return &CallNode{
&ReferenceNode{
name,
start,
nameEnd,
},
args,
true,
start,
p.prev.Start + p.prev.Length,
}, nil
}
return &ReferenceNode{
name,
start,
nameEnd,
}, nil
case TokenFunc:
p.advance()
start := p.prev.Start
params, err := p.parseParams()
if err != nil {
return nil, err
}
var sig TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
sig, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false)
if err != nil {
return nil, err
@ -251,7 +380,10 @@ func (p *Parser) factor() (Node, error) {
return &FunctionNode{
"*",
params,
sig,
b,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenOpenParenthesis:
@ -260,20 +392,20 @@ func (p *Parser) factor() (Node, error) {
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseParenthesis); err != nil {
if err := p.expect(TokenCloseParenthesis, "an opened parenthesis must be closed"); err != nil {
return nil, err
}
return v, nil
default:
err := p.error("invalid factor", p.curr)
p.advance()
return nil, err
return nil, p.error("invalid factor", p.curr)
}
}
func (p *Parser) prop() (Node, error) {
start := p.curr.Start
v, err := p.factor()
if err != nil {
return nil, err
@ -281,7 +413,7 @@ func (p *Parser) prop() (Node, error) {
// parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil {
if err := p.expect(TokenName, "property must be a name"); err != nil {
return nil, err
}
property := (*p.prev).Lexeme
@ -289,6 +421,8 @@ func (p *Parser) prop() (Node, error) {
v = &AccessNode{
v,
property,
start,
p.prev.Start + p.prev.Length,
}
// if called, also add
@ -302,6 +436,8 @@ func (p *Parser) prop() (Node, error) {
v,
args,
true,
start,
p.prev.Start + p.prev.Length,
}
}
}
@ -310,6 +446,7 @@ func (p *Parser) prop() (Node, error) {
}
func (p *Parser) product() (Node, error) {
start := p.curr.Start
left, err := p.prop()
if err != nil {
return nil, err
@ -331,6 +468,8 @@ func (p *Parser) product() (Node, error) {
op,
left,
f,
start,
p.prev.Start + p.prev.Length,
}
}
@ -338,6 +477,8 @@ func (p *Parser) product() (Node, error) {
}
func (p *Parser) term() (Node, error) {
start := p.curr.Start
left, err := p.product()
if err != nil {
return nil, err
@ -359,6 +500,8 @@ func (p *Parser) term() (Node, error) {
op,
left,
pr,
start,
p.prev.Start + p.prev.Length,
}
}
@ -366,6 +509,7 @@ func (p *Parser) term() (Node, error) {
}
func (p *Parser) comparison() (Node, error) {
start := p.curr.Start
left, err := p.term()
if err != nil {
@ -403,10 +547,13 @@ func (p *Parser) comparison() (Node, error) {
op,
left,
t,
start,
p.prev.Start + p.prev.Length,
}, nil
}
func (p *Parser) condition() (Node, error) {
start := p.curr.Start
left, err := p.comparison()
if err != nil {
return nil, err
@ -434,12 +581,15 @@ func (p *Parser) condition() (Node, error) {
op,
left,
c,
start,
p.prev.Start + p.prev.Length,
}, nil
}
func (p *Parser) statement() (Node, error) {
switch (*p.curr).Type {
case TokenIf:
start := p.curr.Start
p.advance()
condition, err := p.condition()
@ -470,20 +620,25 @@ func (p *Parser) statement() (Node, error) {
condition,
then,
otherwise,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenName:
p.advance()
start := p.prev.Start
name := (*p.prev).Lexeme
if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{
name,
start,
p.prev.Start + p.prev.Length,
}
// parse chains of prop-getting ( "".split().join().length.round() )
for p.accept(TokenDot) {
if err := p.expect(TokenName); err != nil {
if err := p.expect(TokenName, "property must be name"); err != nil {
return nil, err
}
property := (*p.prev).Lexeme
@ -491,6 +646,8 @@ func (p *Parser) statement() (Node, error) {
v = &AccessNode{
v,
property,
start,
p.prev.Start + p.prev.Length,
}
// if called, also add
@ -504,6 +661,8 @@ func (p *Parser) statement() (Node, error) {
v,
args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
start,
p.prev.Start + p.prev.Length,
}
}
}
@ -518,9 +677,13 @@ func (p *Parser) statement() (Node, error) {
return &CallNode{
&ReferenceNode{
name,
start,
start + Pos(len(name)),
},
args,
false,
start,
p.prev.Start + p.prev.Length,
}, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare
@ -533,28 +696,19 @@ func (p *Parser) statement() (Node, error) {
name,
c,
isDeclaration,
start,
p.prev.Start + p.prev.Length,
}, nil
} else {
return p.condition()
}
case TokenImport:
p.advance()
if err := p.expect(TokenString); err != nil {
return nil, err
}
path := p.prev.Lexeme[1 : len(p.prev.Lexeme)-1]
return &ImportNode{
path,
}, nil
return nil, p.error("invalid statement", p.curr)
case TokenFunc:
p.advance()
if err := p.expect(TokenName); err != nil {
funcStart := p.prev.Start
if err := p.expect(TokenName, "function must have a name"); err != nil {
return nil, err
}
name := p.prev.Lexeme
@ -564,6 +718,14 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
var yield TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
yield, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false)
if err != nil {
return nil, err
@ -574,13 +736,19 @@ func (p *Parser) statement() (Node, error) {
&FunctionNode{
name,
params,
yield,
b,
funcStart,
p.prev.Start + p.prev.Length,
},
true,
funcStart,
p.prev.Start + p.prev.Length,
}, nil
case TokenWhile:
p.advance()
start := p.prev.Start
c, err := p.condition()
if err != nil {
@ -595,10 +763,13 @@ func (p *Parser) statement() (Node, error) {
return &LoopNode{
c,
b,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenReturn:
p.advance()
start := p.prev.Start
c, err := p.condition()
if err != nil {
@ -607,6 +778,8 @@ func (p *Parser) statement() (Node, error) {
return &ReturnNode{
c,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenBreakpoint:
@ -614,24 +787,33 @@ func (p *Parser) statement() (Node, error) {
return &BreakpointNode{}, nil
case TokenImport:
defer p.advance()
return nil, p.error("import statements must be top-level", p.curr)
default:
err := p.error("invalid statement", p.curr)
p.advance()
return nil, err
defer p.advance()
return nil, p.error("invalid statement", p.curr)
}
}
func (p *Parser) block(canBeStatement bool) (Node, error) {
if canBeStatement {
if !p.accept(TokenOpenBrace) {
if p.curr.Type == TokenEOF {
return nil, nil
}
return p.statement()
}
} else {
if err := p.expect(TokenOpenBrace); err != nil {
if err := p.expect(TokenOpenBrace, "a block is required"); err != nil {
return nil, err
}
}
start := p.prev.Start
statements := make([]Node, 0)
for !p.accept(TokenCloseBrace) {
@ -646,13 +828,15 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
return &BlockNode{
statements,
start,
p.prev.Start + p.prev.Length,
}, nil
}
func (p *Parser) parseArgs() ([]Node, error) {
args := make([]Node, 0)
if err := p.expect(TokenOpenParenthesis); err != nil {
if err := p.expect(TokenOpenParenthesis, "arguments must be contained in parenthesis"); err != nil {
return nil, err
}
@ -663,7 +847,7 @@ func (p *Parser) parseArgs() ([]Node, error) {
}
args = append(args, c)
for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma); err != nil {
if err := p.expect(TokenComma, "arguments must be separated by comma"); err != nil {
return nil, err
}
c, err = p.condition()
@ -678,30 +862,142 @@ func (p *Parser) parseArgs() ([]Node, error) {
}
// parseParams parse parameters and parentheses
func (p *Parser) parseParams() ([]string, error) {
if err := p.expect(TokenOpenParenthesis); err != nil {
func (p *Parser) parseParams() ([]FunctionParameter, error) {
if err := p.expect(TokenOpenParenthesis, "parameters must be in parentheses"); err != nil {
return nil, err
}
params := make([]string, 0)
params := make([]FunctionParameter, 0)
if p.accept(TokenName) {
name := (*p.prev).Lexeme
params = append(params, name)
if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil {
return nil, err
}
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma); err != nil {
if err := p.expect(TokenComma, "parameters must be separated by comma"); err != nil {
return nil, err
}
if err := p.expect(TokenName); err != nil {
if err := p.expect(TokenName, "parameters must have a name (cannot have trailing comma)"); err != nil {
return nil, err
}
name = (*p.prev).Lexeme
params = append(params, name)
if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil {
return nil, err
}
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
}
} else {
if err := p.expect(TokenCloseParenthesis); err != nil {
if err := p.expect(TokenCloseParenthesis, "must close parameter list"); err != nil {
return nil, err
}
}
return params, nil
}
func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature
if p.accept(TokenFunc) {
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
return nil, err
}
var in []TypeSignature
for !p.accept(TokenCloseParenthesis) {
if len(in) > 0 {
if err := p.expect(TokenComma, "parameter types must be separated by a comma"); err != nil {
return nil, err
}
}
sig, err := p.parseSignature()
if err != nil {
return nil, err
}
in = append(in, sig)
}
out, err := p.parseSignature()
if err != nil {
return nil, err
}
s = &FunctionSignature{
in,
out,
}
} else {
if err := p.expect(TokenName, "type must be a name"); err != nil {
return nil, err
}
name := (*p.prev).Lexeme
switch name {
case "string":
s = &StringSignature{}
case "number":
s = &NumberSignature{}
case "boolean":
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket, "list must have content typed"); err != nil {
return nil, err
}
contents, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket, "list must close parameter list"); err != nil {
return nil, err
}
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
default:
return nil, p.error("unsupported type: "+name, p.prev)
}
}
if p.accept(TokenPipe) {
other, err := p.parseSignature()
if err != nil {
return nil, err
}
return &CompositeSignature{
s,
other,
}, nil
}
return s, nil
}

View file

@ -1,14 +1,16 @@
package core
import (
"fmt"
"strconv"
"strings"
"testing"
)
func TestNewParser(t *testing.T) {
tokens := make([]Token, 0)
p := NewParser(tokens)
p := NewParser("", tokens)
if p == nil {
t.Fatal("parser should not be nil")
@ -32,7 +34,7 @@ func TestNewParser(t *testing.T) {
func BenchmarkNewParser(b *testing.B) {
tokens := make([]Token, 0)
for i := 0; i < b.N; i++ {
_ = NewParser(tokens)
_ = NewParser("", tokens)
}
}
@ -66,14 +68,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"assignment": {
@ -90,10 +97,13 @@ func GetTokenTestData() map[string]TokenTestData {
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"declaration": {
@ -113,14 +123,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
@ -167,30 +182,63 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryMultiplication,
&BinaryNode{
BinaryAddition,
&NumberNode{2},
&NumberNode{1},
&NumberNode{
2,
0, 0,
},
&NumberNode{
1,
0, 0,
},
0, 0,
},
&NumberNode{5},
&NumberNode{
5,
0, 0,
},
0, 0,
},
&BinaryNode{
BinaryDivision,
&NumberNode{3},
&NumberNode{
3,
0, 0,
},
&BinaryNode{
BinarySubtraction,
&NumberNode{6},
&NumberNode{2},
&NumberNode{
6,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
0, 0,
},
0, 0,
},
&BinaryNode{
BinaryDivision,
&NumberNode{10},
&NumberNode{2},
&NumberNode{
10,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"condition_equal": {
@ -210,14 +258,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&NumberNode{
20,
0, 0,
},
&NumberNode{
15,
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"if_statement": {
@ -240,10 +293,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
0,
0, 0,
},
0, 0,
},
do: &BlockNode{
[]Node{
@ -251,13 +307,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
},
0, 0,
},
},
"if_else_statement": {
@ -286,10 +346,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
0,
0, 0,
},
0, 0,
},
do: &BlockNode{
[]Node{
@ -297,10 +360,13 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
otherwise: &BlockNode{
[]Node{
@ -308,13 +374,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
0,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
},
0, 0,
},
},
"empty_block": {
@ -327,8 +397,10 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{
&BlockNode{
[]Node{},
0, 0,
},
},
0, 0,
},
},
"lambda": { // a := func(a, b) { return a + b }
@ -338,9 +410,14 @@ func GetTokenTestData() map[string]TokenTestData {
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(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"),
@ -357,7 +434,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a",
&FunctionNode{
"*",
[]string{"a", "b"},
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{
[]Node{
&ReturnNode{
@ -365,18 +452,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"function_declaration": {
@ -404,7 +499,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a",
&FunctionNode{
"a",
[]string{"a", "b"},
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{
[]Node{
&ReturnNode{
@ -412,18 +517,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"prop_getting": {
@ -443,12 +556,16 @@ func GetTokenTestData() map[string]TokenTestData {
&AccessNode{
&ReferenceNode{
"a",
0, 0,
},
"b",
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"list_init": {
@ -488,27 +605,43 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
3.141,
0, 0,
},
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
&BooleanNode{
true,
0, 0,
},
&ListNode{
[]Node{
&NumberNode{2}, &NumberNode{3},
&NumberNode{
2,
0, 0,
}, &NumberNode{
3,
0, 0,
},
},
nil,
0, 0,
},
},
nil,
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
}
@ -642,15 +775,17 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("Function node names match (%s)", n.name)
}
if len(n.params) != len(m.params) {
t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.params), len(m.params))
if len(n.parameters) != len(m.parameters) {
t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.parameters), len(m.parameters))
} else {
t.Logf("Function node parameters count is equal (%d) ", len(n.params))
t.Logf("Function node parameters count is equal (%d) ", len(n.parameters))
}
for i, p := range m.params {
if n.params[i] != p {
t.Errorf("Function node parameter %d does not match: %s and %s", i, p, m.params)
for i, p := range m.parameters {
if !n.parameters[i].Signature.Matches(p.Signature) {
t.Errorf("Function node parameter signature %d does not match: %s and %s", i, p.Signature, n.parameters[i].Signature)
} else if n.parameters[i].Name != p.Name {
t.Errorf("Function node parameter name %d does not match: %s and %s", i, p.Name, n.parameters[i].Name)
} else {
t.Logf("Function node parameter %d matches (%s)", i, p)
}
@ -665,6 +800,105 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
}
}
func SerializeTokens(tokens []Token) string {
out := strings.Builder{}
level := 0
for _, token := range tokens {
switch token.Type {
case TokenPlus:
out.WriteString(" + ")
case TokenMinus:
out.WriteString(" - ")
case TokenStar:
out.WriteString("*")
case TokenSlash:
out.WriteString("/")
case TokenBang:
out.WriteString("!")
case TokenSemicolon:
out.WriteString(";")
case TokenNumber:
out.WriteString(token.Lexeme)
case TokenString:
out.WriteString(fmt.Sprintf("\"%s\"", token.Lexeme))
case TokenName:
out.WriteString(token.Lexeme)
case TokenOpenParenthesis:
out.WriteString("(")
case TokenCloseParenthesis:
out.WriteString(")")
case TokenOpenBracket:
out.WriteString("[")
case TokenCloseBracket:
out.WriteString("]")
case TokenOpenBrace:
out.WriteString("{")
level = level + 1
case TokenCloseBrace:
out.WriteString("}")
level = level - 1
case TokenTrue:
out.WriteString("true")
case TokenFalse:
out.WriteString("false")
case TokenNil:
out.WriteString("nil")
case TokenFunc:
out.WriteString("func")
case TokenReturn:
out.WriteString("return ")
case TokenWhile:
out.WriteString("while ")
case TokenVar:
out.WriteString("var ")
case TokenIf:
out.WriteString("if ")
case TokenElse:
out.WriteString(" else ")
case TokenImport:
out.WriteString("import ")
case TokenComma:
out.WriteString(", ")
case TokenDot:
out.WriteString(".")
case TokenColon:
out.WriteString(": ")
case TokenAssign:
out.WriteString(" = ")
case TokenDeclare:
out.WriteString(" := ")
case TokenBangEquals:
out.WriteString(" != ")
case TokenEquals:
out.WriteString(" == ")
case TokenGreaterThan:
out.WriteString(" > ")
case TokenLessThan:
out.WriteString(" < ")
case TokenGreaterThanOrEqual:
out.WriteString(" >= ")
case TokenLessThanOrEqual:
out.WriteString(" <= ")
case TokenDoubleAmpersand:
out.WriteString(" && ")
case TokenDoublePipe:
out.WriteString(" || ")
case TokenBreakpoint:
out.WriteString("breakpoint")
case TokenEOF:
out.WriteString(fmt.Sprintf("<error: \"%s\">", token.Lexeme))
case TokenHexadecimal:
out.WriteString(token.Lexeme)
case TokenPipe:
out.WriteString(" | ")
case TokenError:
}
}
return out.String()
}
func TestParser_Parse(t *testing.T) {
t.Logf("Getting test data")
tokenData := GetTokenTestData()
@ -676,17 +910,17 @@ func TestParser_Parse(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Logf("Initializing parser")
p := NewParser(data.tokens)
p := NewParser("", data.tokens)
t.Logf("Parsing main")
tree, err := p.Parse()
tree, err := p.Parse("")
if err != nil {
t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune{}))
t.Fatalf("Unexpected error(s): %s", err.(ParsingError).Format())
}
t.Logf("Checking parsed tree")
NodeEquality(t, tree, data.tree)
NodeEquality(t, tree.Block, data.tree)
})
}
}
@ -697,9 +931,9 @@ func BenchmarkParser_Parse(b *testing.B) {
for name, data := range tokenData {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
p := NewParser(data.tokens)
p := NewParser("", data.tokens)
_, _ = p.Parse()
_, _ = p.Parse("")
}
})
}

View file

@ -1,25 +1,28 @@
package core
type Stack[T any] struct {
Current Pos
Size Pos
Current Pos
Capacity Pos
items []T
}
func NewStack[T any](size Pos) *Stack[T] {
func NewStack[T any](capacity Pos) *Stack[T] {
return &Stack[T]{
items: make([]T, size),
Size: size,
Current: 0,
items: make([]T, 16),
Capacity: capacity,
Current: 0,
}
}
func (s *Stack[T]) Push(items ...T) {
for _, item := range items {
if s.Current >= s.Size {
if s.Current >= s.Capacity {
panic("stack overflow")
}
if int(s.Current) == len(s.items) {
s.items = append(s.items, item)
}
s.items[s.Current] = item
s.Current++
@ -45,7 +48,7 @@ func (s *Stack[T]) Peek() T {
// check whether the stack is invalid (stack over-/underflow)
func (s *Stack[T]) check() {
if s.Current >= s.Size {
if s.Current >= s.Capacity {
panic("stack underflow")
}

View file

@ -25,16 +25,10 @@ func TestNewStack(t *testing.T) {
s := NewStack[any](Pos(size))
if s.Size != Pos(size) {
t.Errorf("Stack size (%d) does not match expected size (%d)", s.Size, size)
if s.Capacity != Pos(size) {
t.Errorf("Stack size (%d) does not match expected size (%d)", s.Capacity, size)
} else {
t.Logf("Stack size is expected size (%d)", s.Size)
}
if len(s.items) != size {
t.Errorf("internal items slice size (%d) does not match expected size (%d)", len(s.items), size)
} else {
t.Logf("internal items slice size is as expected (%d)", len(s.items))
t.Logf("Stack size is expected size (%d)", s.Capacity)
}
if s.Current != 0 {

349
core/types.go Normal file
View file

@ -0,0 +1,349 @@
package core
import (
"fmt"
"strings"
)
type Type int
const (
TypeString Type = iota
TypeNumber
TypeBoolean
TypeNil
TypeList
TypeObject
TypeFunction
TypeAny
TypeComposite
TypeInner
)
func (t Type) String() string {
switch t {
case TypeString:
return "string"
case TypeNumber:
return "number"
case TypeBoolean:
return "boolean"
case TypeNil:
return "nil"
case TypeList:
return "list"
case TypeObject:
return "object"
case TypeFunction:
return "func"
case TypeAny:
return "any"
case TypeComposite:
return "composite"
case TypeInner:
return "inner"
}
panic(fmt.Sprintf("unsupported string conversion for type %v", int(t)))
}
func SignatureOf(v Value) TypeSignature {
switch t := v.(type) {
case *StringValue:
return &StringSignature{}
case *NumberValue:
return &NumberSignature{}
case *BoolValue:
return &BooleanSignature{}
case *ListValue:
// try to deduce contents type
var contains TypeSignature
for _, p := range t.Items {
sig := SignatureOf(p)
if contains == nil {
contains = sig
} else if !contains.Matches(sig) {
contains = &AnySignature{}
break
}
}
return &ListSignature{
contains,
}
case *ObjectValue:
return &ObjectSignature{}
case *FunctionValue:
params := make([]TypeSignature, len(t.Params))
for i, p := range t.Params {
params[i] = p.Signature
}
return &FunctionSignature{
params,
t.Yield,
}
case *BuiltinFunctionValue:
return t.Signature
}
panic(fmt.Sprintf("unknown value; cannot get signature of %s", v))
}
type TypeSignature interface {
Type() Type
// Matches check if this type signature matches another.
Matches(TypeSignature) bool
// String create a human-readable string version of the value type.
String() string
}
type NilSignature struct{}
func (*NilSignature) Type() Type {
return TypeNil
}
func (s *NilSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNil
}
func (*NilSignature) String() string {
return "nil"
}
type StringSignature struct{}
func (*StringSignature) Type() Type {
return TypeString
}
func (s *StringSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeString
}
func (*StringSignature) String() string {
return "string"
}
type NumberSignature struct{}
func (*NumberSignature) Type() Type {
return TypeNumber
}
func (s *NumberSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNumber
}
func (*NumberSignature) String() string {
return "number"
}
type BooleanSignature struct{}
func (*BooleanSignature) Type() Type {
return TypeBoolean
}
func (s *BooleanSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeBoolean
}
func (*BooleanSignature) String() string {
return "boolean"
}
type ListSignature struct {
Contents TypeSignature
}
func (*ListSignature) Type() Type {
return TypeList
}
func (s *ListSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || (other.Type() == TypeList && other.(*ListSignature).Contents.Matches(s.Contents))
}
func (s *ListSignature) String() string {
return fmt.Sprintf("list[%s]", s.Contents)
}
type ObjectSignature struct {
Members map[string]TypeSignature
}
func (*ObjectSignature) Type() Type {
return TypeObject
}
func (s *ObjectSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeObject {
return false
}
o := other.(*ObjectSignature)
if len(o.Members) != len(s.Members) {
return false
}
for name, member := range s.Members {
v, ok := o.Members[name]
if !ok {
return false
}
if !v.Matches(member) {
return false
}
}
return true
}
func (s *ObjectSignature) String() string {
panic("unimplemented")
}
type FunctionSignature struct {
In []TypeSignature
Out TypeSignature
}
func (*FunctionSignature) Type() Type {
return TypeFunction
}
func (s *FunctionSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeFunction {
return false
}
f := other.(*FunctionSignature)
if !s.Out.Matches(f.Out) {
return false
}
if len(f.In) != len(s.In) {
return false
}
for i, p := range s.In {
v := f.In[i]
if !p.Matches(v) {
return false
}
}
return true
}
func (s *FunctionSignature) String() string {
b := strings.Builder{}
b.WriteString("func(")
for i, t := range s.In {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(t.String())
}
b.WriteString(")")
if s.Out.Type() != TypeNil {
b.WriteString(" ")
b.WriteString(s.Out.String())
}
return b.String()
}
type AnySignature struct{}
func (*AnySignature) Type() Type {
return TypeAny
}
func (*AnySignature) Matches(_ TypeSignature) bool {
return true
}
func (*AnySignature) String() string {
return "any"
}
type CompositeSignature struct {
A TypeSignature
B TypeSignature
}
func (*CompositeSignature) Type() Type {
return TypeComposite
}
func (s *CompositeSignature) Matches(other TypeSignature) bool {
return s.A.Matches(other) || s.B.Matches(other)
}
func (s *CompositeSignature) String() string {
return fmt.Sprintf("%s|%s", s.A, s.B)
}
type InnerSignature struct{}
func (*InnerSignature) Type() Type {
return TypeInner
}
func (*InnerSignature) Matches(_ TypeSignature) bool {
return false
}
func (*InnerSignature) String() string {
return "inner"
}

View file

@ -68,15 +68,6 @@ func GoToValue(gov interface{}) Value {
return &StringValue{
v,
}
case []interface{}:
values := make([]Value, len(v))
for i, value := range v {
values[i] = GoToValue(value)
}
return &ListValue{
values,
}
case map[string]interface{}:
values := map[string]Value{}
for key, value := range v {
@ -86,9 +77,17 @@ func GoToValue(gov interface{}) Value {
return &ObjectValue{
values,
}
case Value:
return v
default:
if reflect.TypeOf(v).Kind() == reflect.Slice {
return &ListValue{
v.([]Value),
}
}
}
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov).Name()))
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov)))
}
type Value interface {
@ -106,6 +105,9 @@ type Value interface {
// Get a member from the value. An error is returned if the member does not exist
Get(string) (Value, error)
// Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value.
Clone() Value
}
type NilValue struct{}
@ -130,8 +132,12 @@ func (v *NilValue) Get(_ string) (Value, error) {
return nil, errors.New("nil has no properties")
}
func (v *NilValue) Clone() Value {
return &NilValue{}
}
type BoolValue struct {
bool
Boolean bool
}
func (v *BoolValue) Type() ValueType {
@ -139,7 +145,7 @@ func (v *BoolValue) Type() ValueType {
}
func (v *BoolValue) String() string {
if v.bool {
if v.Boolean {
return "true"
} else {
return "false"
@ -151,16 +157,22 @@ func (v *BoolValue) DebugString() string {
}
func (v *BoolValue) Equals(other Value) bool {
return other.Type() == BoolValueType && other.(*BoolValue).bool == v.bool
return other.Type() == BoolValueType && other.(*BoolValue).Boolean == v.Boolean
}
func (v *BoolValue) Get(_ string) (Value, error) {
return nil, errors.New("booleans have no properties")
}
func (v *BoolValue) Clone() Value {
return &BoolValue{
v.Boolean,
}
}
// ObjectValue An object with any number of members (key-value pairs)
type ObjectValue struct {
members map[string]Value
Members map[string]Value
}
func (v *ObjectValue) Type() ValueType {
@ -169,12 +181,12 @@ func (v *ObjectValue) Type() ValueType {
func (v *ObjectValue) String() string {
out := "{"
for key, value := range v.members {
for key, value := range v.Members {
if out != "{" {
out += ", "
}
out += fmt.Sprintf("%q=%s", key, value.String())
out += fmt.Sprintf("%q=%s", key, value.DebugString())
}
out += "}"
@ -191,8 +203,8 @@ func (v *ObjectValue) Equals(other Value) bool {
return false
}
for key, value := range v.members {
if !object.members[key].Equals(value) {
for key, value := range v.Members {
if !object.Members[key].Equals(value) {
return false
}
}
@ -203,26 +215,30 @@ func (v *ObjectValue) Equals(other Value) bool {
var ObjectPrototype = map[string]Value{
"set": &BuiltinFunctionValue{
"set",
[]string{"property", "value"},
func(vm *VM, _this Value, params map[string]Value) (Value, error) {
&FunctionSignature{
[]TypeSignature{&StringSignature{}, &ListSignature{}},
&NilSignature{},
},
func(vm *VM, _this Value, params []Value) (Value, error) {
this := _this.(*ObjectValue)
p := params["property"]
v, ok := params["value"].(*StringValue)
p := params[1]
v, ok := params[0].(*StringValue)
if !ok {
return nil, errors.New("property is not a string")
}
this.members[v.string] = p
this.Members[v.Text] = p
return &NilValue{}, nil
},
nil,
false,
},
}
func (v *ObjectValue) Get(key string) (Value, error) {
if member, ok := v.members[key]; ok {
if member, ok := v.Members[key]; ok {
return member, nil
} else if p, ok := ObjectPrototype[key]; ok {
return p, nil
@ -231,9 +247,21 @@ func (v *ObjectValue) Get(key string) (Value, error) {
}
}
func (v *ObjectValue) Clone() Value {
m := make(map[string]Value, len(v.Members))
for name, mem := range v.Members {
m[name] = mem.Clone()
}
return &ObjectValue{
m,
}
}
// NumberValue Integer or floating-point values
type NumberValue struct {
float64
Number float64
}
const NumberSize int = 64
@ -243,7 +271,7 @@ func (v *NumberValue) Type() ValueType {
}
func (v *NumberValue) String() string {
return strconv.FormatFloat(v.float64, 'g', -1, NumberSize)
return strconv.FormatFloat(v.Number, 'g', -1, NumberSize)
}
func (v *NumberValue) DebugString() string {
@ -251,7 +279,7 @@ func (v *NumberValue) DebugString() string {
}
func (v *NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && other.(*NumberValue).float64 == v.float64
return other.Type() == NumberValueType && other.(*NumberValue).Number == v.Number
}
func (v *NumberValue) Get(_ string) (Value, error) {
@ -259,8 +287,14 @@ func (v *NumberValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties")
}
func (v *NumberValue) Clone() Value {
return &NumberValue{
v.Number,
}
}
type StringValue struct {
string
Text string
}
func (v *StringValue) Type() ValueType {
@ -268,7 +302,7 @@ func (v *StringValue) Type() ValueType {
}
func (v *StringValue) String() string {
return v.string
return v.Text
}
func (v *StringValue) DebugString() string {
@ -276,31 +310,47 @@ func (v *StringValue) DebugString() string {
}
func (v *StringValue) Equals(other Value) bool {
return other.Type() == StringValueType && other.(*StringValue).string == v.string
return other.Type() == StringValueType && other.(*StringValue).Text == v.Text
}
var StringPrototype = map[string]*BuiltinFunctionValue{
"split": {
"split",
[]string{"seperator"},
func(vm *VM, this Value, m map[string]Value) (Value, error) {
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&ListSignature{
&StringSignature{},
},
},
func(vm *VM, this Value, v []Value) (Value, error) {
str := this.(*StringValue).String()
sep := m["seperator"].(*StringValue).String()
sep := v[0].(*StringValue).String()
var out []string
var out []Value
tmp := strings.Builder{}
for i := 0; i < len(str)-len(sep); i++ {
tmp.WriteRune([]rune(str)[i])
if str[i:i+len(sep)] == sep {
out = append(out, tmp.String())
out = append(out, &StringValue{tmp.String()})
tmp.Reset()
}
}
return GoToValue(out), nil
return &ListValue{out}, nil
},
nil,
true,
},
"length": {
Name: "length",
Signature: &FunctionSignature{
[]TypeSignature{},
&NumberSignature{},
},
F: func(vm *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*StringValue).Text)), nil
},
},
}
@ -312,9 +362,15 @@ func (v *StringValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
}
func (v *StringValue) Clone() Value {
return &StringValue{
v.Text,
}
}
// ListValue a dynamic list of values
type ListValue struct {
items []Value
Items []Value
}
func (v *ListValue) Type() ValueType {
@ -323,7 +379,7 @@ func (v *ListValue) Type() ValueType {
func (v *ListValue) String() string {
out := "["
for i, item := range v.items {
for i, item := range v.Items {
if i != 0 {
out += ", "
}
@ -345,12 +401,12 @@ func (v *ListValue) Equals(other Value) bool {
l := other.(*ListValue)
if len(v.items) != len(l.items) {
if len(v.Items) != len(l.Items) {
return false
}
for i, item := range l.items {
if !item.Equals(l.items[i]) {
for i, item := range v.Items {
if !item.Equals(l.Items[i]) {
return false
}
}
@ -361,19 +417,28 @@ func (v *ListValue) Equals(other Value) bool {
var ListPrototype = map[string]*BuiltinFunctionValue{
"append": {
"append",
[]string{"item"},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
this.(*ListValue).items = append(this.(*ListValue).items, p["item"])
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
this.(*ListValue).Items = append(this.(*ListValue).Items, v[0])
return &NilValue{}, nil
},
nil,
false,
},
"at": {
"at",
[]string{"index"},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
items := this.(*ListValue).items
index := int(p["index"].(*NumberValue).float64)
&FunctionSignature{
[]TypeSignature{
&NumberSignature{},
},
&InnerSignature{},
},
func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).Items
index := int(p[0].(*NumberValue).Number)
if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -382,56 +447,41 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
return items[index], nil
},
nil,
false,
},
"length": {
"length",
[]string{},
func(_ *VM, this Value, p map[string]Value) (Value, error) {
return GoToValue(len(this.(*ListValue).items)), nil
},
nil,
},
"map": {
"map",
[]string{"f"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
list := value.(*ListValue)
v := m["f"]
var f Value
f, ok := v.(*FunctionValue)
if !ok {
f, ok = v.(*BuiltinFunctionValue)
if !ok {
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
}
}
for i, item := range list.items {
var err error
list.items[i], err = vm.Call(f, []Value{
item,
})
if err != nil {
return nil, err
}
}
return list, nil
&FunctionSignature{
[]TypeSignature{},
&NumberSignature{},
},
func(_ *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*ListValue).Items)), nil
},
nil,
false,
},
"reduce": {
"reduce",
[]string{"f", "start"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
&FunctionSignature{
[]TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue)
f := m["f"]
sum := m["start"]
f := m[0]
sum := m[1]
for _, v := range list.items {
for _, v := range list.Items {
result, err := vm.Call(f, []Value{sum, v})
if err != nil {
return nil, err
@ -442,6 +492,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
return sum, nil
},
nil,
false,
},
}
@ -453,9 +504,22 @@ func (v *ListValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
}
func (v *ListValue) Clone() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &ListValue{
n,
}
}
type FunctionValue struct {
Name string
Params []string
Params []FunctionParameter
Yield TypeSignature
Chunk *Chunk
Parent Value
}
@ -482,11 +546,22 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
func (v *FunctionValue) Clone() Value {
return &FunctionValue{
v.Name,
v.Params,
v.Yield,
v.Chunk,
v.Parent,
}
}
type BuiltinFunctionValue struct {
Name string
Parameters []string
F func(*VM, Value, map[string]Value) (Value, error)
Parent Value
Name string
Signature *FunctionSignature
F func(*VM, Value, []Value) (Value, error)
Parent Value
Constant bool
}
func (v *BuiltinFunctionValue) Type() ValueType {
@ -510,6 +585,16 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
func (v *BuiltinFunctionValue) Clone() Value {
return &BuiltinFunctionValue{
v.Name,
v.Signature,
v.F,
v.Parent,
v.Constant,
}
}
// VariableValue a value wrapper for variables kept on the stack
type VariableValue struct {
name string
@ -541,3 +626,11 @@ func (v *VariableValue) Equals(other Value) bool {
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

@ -16,19 +16,19 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Logf("Both are nil")
return
case BoolValueType:
if got.(*BoolValue).bool != want.(*BoolValue).bool {
if got.(*BoolValue).Boolean != want.(*BoolValue).Boolean {
t.Errorf("bool value mismatch: got %v, want %v", got.(*BoolValue), want.(*BoolValue))
} else {
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
}
case NumberValueType:
if got.(*NumberValue).float64 != want.(*NumberValue).float64 {
if got.(*NumberValue).Number != want.(*NumberValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue))
} else {
t.Logf("Both are same number (%s)", got.(*NumberValue).String())
}
case StringValueType:
if got.(*StringValue).string != want.(*StringValue).string {
if got.(*StringValue).Text != want.(*StringValue).Text {
t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue))
} else {
t.Logf("Both are same string (%s)", got.(*StringValue).String())
@ -60,19 +60,10 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name)
}
if len(n.Parameters) != len(m.Parameters) {
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.Parameters, m.Parameters)
if !n.Signature.Matches(m.Signature) {
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n, m)
}
for i, v := range n.Parameters {
if v != m.Parameters[i] {
t.Errorf("builtin function parameter %d mismatch: got %v, want %v", i, v, m.Parameters[i])
}
}
if &n.F != &m.F {
t.Errorf("builtin function f mismatch: got %v, want %v", &n.F, &m.F)
}
case VariableValueType:
n := got.(*VariableValue)
m := want.(*VariableValue)
@ -87,6 +78,32 @@ func CompareValues(t *testing.T, got Value, want Value) {
CompareValues(t, n.value, m.value)
case ListValueType:
n := got.(*ListValue)
m := want.(*ListValue)
if len(n.Items) != len(m.Items) {
t.Fatalf("list items length mismatch: got %d, want %d", len(n.Items), len(m.Items))
}
for i, v := range n.Items {
t.Logf("comparing list items #%d: got %s, want %s", i, v, m.Items[i])
CompareValues(t, v, m.Items[i])
}
case ObjectValueType:
n := got.(*ObjectValue)
m := want.(*ObjectValue)
if len(n.Members) != len(m.Members) {
t.Fatalf("object members count mismatch: got %d, want %d", len(n.Members), len(m.Members))
}
for k, v := range n.Members {
t.Logf("comparing object member %s: got %s, want %s", k, v, m.Members[k])
CompareValues(t, v, m.Members[k])
}
default:
panic("unimplemented comparison")
}

View file

@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"log"
"math"
"os"
"strings"
)
@ -26,6 +28,8 @@ const (
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
// InstructionEquals whether the two top values on the stack are equal
InstructionEquals
// InstructionNotEqual whether the two top values on the stack are not equal
@ -99,6 +103,8 @@ const (
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) The order is reversed compared
// to on the stack; the top value on the stack is the last in the list.
InstructionFormList
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
InstructionConcatLists
// InstructionBreakpoint for debugging purposes
InstructionBreakpoint
@ -118,6 +124,8 @@ func (b Bytecode) String() string {
return "MUL"
case InstructionDiv:
return "DIV"
case InstructionNegate:
return "NEGATE"
case InstructionEquals:
return "EQUALS"
case InstructionNotEqual:
@ -182,6 +190,8 @@ func (b Bytecode) String() string {
return "APPEND"
case InstructionAccessProperty:
return "ACCESS_PROPERTY"
case InstructionConcatLists:
return "CONCAT_LISTS"
}
return "UNDEFINED"
}
@ -202,7 +212,7 @@ func (c Chunk) String() string {
b.WriteString("=-= constants =-=\n")
for i, ct := range c.Constants {
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct))
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct.DebugString()))
f, ok := ct.(*FunctionValue)
if ok {
@ -228,6 +238,16 @@ func RegisterGOBTypes() {
Params: nil,
Chunk: nil,
})
// Signatures
gob.Register(&NilSignature{})
gob.Register(&NumberSignature{})
gob.Register(&StringSignature{})
gob.Register(&FunctionSignature{})
gob.Register(&ListSignature{})
gob.Register(&ObjectSignature{})
gob.Register(&BooleanSignature{})
}
func (c Chunk) Serialize() []byte {
@ -287,38 +307,108 @@ type Call struct {
var DefaultGlobals = map[string]Value{
"write": &BuiltinFunctionValue{
"write", // always remember where you come from...
[]string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) {
println(v["value"].String())
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil
},
nil,
false,
},
"print": &BuiltinFunctionValue{
"print",
[]string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) {
print(v["value"].String())
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil
},
nil,
false,
},
"format": &BuiltinFunctionValue{
"format",
[]string{"format_string", "values"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
valuies := m["values"].(*ListValue).items
&FunctionSignature{
[]TypeSignature{
&StringSignature{},
&ListSignature{
&AnySignature{},
},
},
&StringSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
b := strings.Builder{}
template := m[0].(*StringValue).Text
valuies := m[1].(*ListValue).Items
return GoToValue(fmt.Sprintf(m["format_string"].String(), valuies)), nil
vi := 0
last := 0
for i := 0; i < len(template); i++ {
if template[i] == '%' {
b.WriteString(template[last:i])
b.WriteString(valuies[vi].String())
vi++
last = i + 1
}
}
b.WriteString(template[last:])
return GoToValue(b.String()), nil
},
nil,
true,
},
"char": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&StringSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
n := args[0].(*NumberValue).Number
b := byte(n)
return &StringValue{
string([]byte{b}),
}, nil
},
nil,
true,
},
"byte": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
s := args[0].(*StringValue).Text
b := []byte(s)[0]
return &NumberValue{float64(b)}, nil
},
nil,
true,
},
"assertEq": &BuiltinFunctionValue{
"assertEq",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
a := params["a"]
b := params["b"]
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if !a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -327,13 +417,20 @@ var DefaultGlobals = map[string]Value{
return &NilValue{}, nil
},
nil,
false,
},
"assertNotEq": &BuiltinFunctionValue{
"assertNotEq",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
a := params["a"]
b := params["b"]
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -342,6 +439,85 @@ var DefaultGlobals = map[string]Value{
return &NilValue{}, nil
},
nil,
false,
},
"str": &BuiltinFunctionValue{
"str",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&StringSignature{},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
return GoToValue(args[0].String()), nil
},
nil,
true,
},
"type": &BuiltinFunctionValue{
Name: "type",
Signature: &FunctionSignature{
In: []TypeSignature{&AnySignature{}},
Out: &StringSignature{},
},
F: func(vm *VM, this Value, args []Value) (Value, error) {
v := args[0]
sig := SignatureOf(v)
return GoToValue(sig.String()), nil
},
Constant: true,
},
"exit": &BuiltinFunctionValue{
"exit",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
os.Exit(int(args[0].(*NumberValue).Number))
return &NilValue{}, nil
},
nil,
false,
},
"floor": &BuiltinFunctionValue{
"floor",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Floor(args[0].(*NumberValue).Number)}, nil
},
nil,
true,
},
"ceil": &BuiltinFunctionValue{
"ceil",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
return &NumberValue{math.Ceil(args[0].(*NumberValue).Number)}, nil
},
nil,
true,
},
"roundd": &BuiltinFunctionValue{
"roundd",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}, &NumberSignature{}},
&NumberSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
x := args[0].(*NumberValue).Number
decimals := args[1].(*NumberValue).Number
multiplier := math.Pow(10, decimals)
return &NumberValue{math.Round(x*multiplier) / multiplier}, nil
},
nil,
true,
},
}
@ -393,29 +569,34 @@ func (vm *VM) Next() bool {
vm.stack.Push(vm.ReadConstant())
case InstructionAdd:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l + r})
case InstructionSub:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l - r})
case InstructionMul:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l * r})
case InstructionDiv:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l / r})
case InstructionNegate:
v := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{-v})
case InstructionEquals:
vm.stack.Push(
&BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
@ -427,40 +608,40 @@ func (vm *VM) Next() bool {
)
case InstructionNot:
b := vm.stack.Pop().(*BoolValue).bool
b := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{!b})
case InstructionAnd:
r := vm.stack.Pop().(*BoolValue).bool
l := vm.stack.Pop().(*BoolValue).bool
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).bool
l := vm.stack.Pop().(*BoolValue).bool
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).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l < r})
case InstructionLessOrEqual:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l <= r})
case InstructionGreater:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqual:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l >= r})
@ -479,7 +660,7 @@ func (vm *VM) Next() bool {
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],
f.Params[i].Name,
vm.stack.items[p],
vm.scope,
}
@ -494,10 +675,10 @@ func (vm *VM) Next() bool {
vm.chunk = f.Chunk
vm.ip = 0
case *BuiltinFunctionValue:
args := map[string]Value{}
args := make([]Value, len(f.Signature.In))
for i := len(f.Parameters) - 1; i >= 0; i-- {
args[f.Parameters[i]] = vm.stack.Pop()
for i := len(f.Signature.In) - 1; i >= 0; i-- {
args[i] = vm.stack.Pop()
}
v, err := f.F(vm, f.Parent, args)
@ -519,12 +700,12 @@ func (vm *VM) Next() bool {
case InstructionJumpFalse:
n := vm.NextU16()
if !vm.stack.Pop().(*BoolValue).bool {
if !vm.stack.Pop().(*BoolValue).Boolean {
vm.ip += Pos(n)
}
case InstructionGetLocal:
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
if v == nil {
@ -536,7 +717,7 @@ func (vm *VM) Next() bool {
case InstructionSetLocal:
value := vm.stack.Pop().(Value)
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
@ -544,19 +725,19 @@ func (vm *VM) Next() bool {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
}
v.value = value
v.value = value.Clone()
case InstructionDeclareLocal:
vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).string,
vm.stack.Pop().(Value),
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().Clone(),
)
case InstructionGetGlobal:
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string])
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
case InstructionSetGlobal:
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string] = vm.stack.Pop()
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.stack.Pop()
case InstructionTrue:
vm.stack.Push(&BoolValue{true})
@ -570,20 +751,32 @@ func (vm *VM) Next() bool {
case InstructionFormList:
n := int(vm.NextU16())
items := make([]Value, n+1)
for i := 0; i <= n; i++ {
items[n-i] = vm.stack.Pop()
items := make([]Value, n)
for i := n - 1; i >= 0; i-- {
items[i] = vm.stack.Pop()
}
vm.stack.Push(&ListValue{
items,
})
case InstructionNewList:
vm.stack.Push(&ListValue{[]Value{}})
case InstructionAppend:
value := vm.stack.Pop()
list := vm.stack.Pop().(*ListValue)
list.items = append(list.items, value)
list.Items = append(list.Items, value)
vm.stack.Push(list)
case InstructionConcatLists:
r := vm.stack.Pop().(*ListValue)
l := vm.stack.Pop().(*ListValue)
vm.stack.Push(&ListValue{
append(l.Items, r.Items...),
})
case InstructionDescend:
vm.descend()
@ -595,8 +788,8 @@ func (vm *VM) Next() bool {
vm.stack.Push(&StringValue{v.String()})
case InstructionStringConcatenation:
r := vm.stack.Pop().(*StringValue).string
l := vm.stack.Pop().(*StringValue).string
r := vm.stack.Pop().(*StringValue).Text
l := vm.stack.Pop().(*StringValue).Text
vm.stack.Push(&StringValue{l + r})
@ -645,7 +838,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
})
for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i], args[i])
vm.addVar(f.Params[i].Name, args[i])
}
if f.Parent != nil {
@ -660,25 +853,21 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
for vm.chunk.Bytecode[vm.ip] != InstructionReturn && vm.Next() {
}
if vm.HasNext() {
vm.Next()
}
vm.Next()
return vm.stack.Pop(), nil
case *BuiltinFunctionValue:
argies := map[string]Value{}
for i, arg := range args {
argies[f.Parameters[i]] = arg
}
return f.F(vm, f.Parent, argies)
return f.F(vm, f.Parent, args)
}
return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))
}
func (vm *VM) SetChunk(c *Chunk) {
vm.chunk = c
}
func (vm *VM) TryNextByte() (Bytecode, error) {
if !vm.HasNext() {
return 0, errors.New("there are no more instructions")

View file

@ -76,13 +76,13 @@ func TestNewVM(t *testing.T) {
}
// should have given stack size
if vm.stack.Size != stackSize {
t.Errorf("vm.stack.Size = %d, want %d", vm.stack.Size, stackSize)
if vm.stack.Capacity != stackSize {
t.Errorf("vm.stack.Capacity = %d, want %d", vm.stack.Capacity, stackSize)
}
// should have given call stack size
if vm.call.Size != callstackSize {
t.Errorf("vm.call.Size = %d, want %d", vm.call.Size, callstackSize)
if vm.call.Capacity != callstackSize {
t.Errorf("vm.call.Capacity = %d, want %d", vm.call.Capacity, callstackSize)
}
}
@ -441,8 +441,17 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{1},
&NumberValue{2},
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -475,8 +484,17 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{1},
&NumberValue{2},
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -492,8 +510,13 @@ func GetExecutionTestData() map[string]struct {
),
},
&FunctionValue{
Name: "square",
Params: []string{"n"},
Name: "square",
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -513,8 +536,13 @@ func GetExecutionTestData() map[string]struct {
&VariableValue{
"square",
&FunctionValue{
Name: "square",
Params: []string{"n"},
Name: "square",
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -532,6 +560,37 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{5},
},
},
"list_concat": {
NewChunk(
[]Bytecode{
InstructionConstant, 0,
InstructionConstant, 1,
InstructionConcatLists,
},
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
},
},
&ListValue{
[]Value{
&NumberValue{3},
},
},
},
),
[]Value{
&ListValue{
[]Value{
&NumberValue{1},
&NumberValue{2},
&NumberValue{3},
},
},
},
},
}
}