1115 lines
25 KiB
Go
1115 lines
25 KiB
Go
package core
|
|
|
|
import (
|
|
"math/big"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewParser(t *testing.T) {
|
|
tokens := make([]Token, 0)
|
|
|
|
p := NewParser("", []string{}, tokens)
|
|
|
|
if p == nil {
|
|
t.Fatal("parser should not be nil")
|
|
}
|
|
|
|
if p.state.pos != 0 {
|
|
t.Error("parser should initialize position at 0")
|
|
}
|
|
|
|
if len(p.tokens) != len(tokens) {
|
|
t.Error("parser should have the passed token list")
|
|
}
|
|
|
|
for i, v := range tokens {
|
|
if p.tokens[i] != v {
|
|
t.Error("parser should have the passed token list")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkNewParser(b *testing.B) {
|
|
tokens := make([]Token, 0)
|
|
for i := 0; i < b.N; i++ {
|
|
_ = NewParser("", []string{}, tokens)
|
|
}
|
|
}
|
|
|
|
type TokenTestData struct {
|
|
tokens []Token
|
|
tree Node
|
|
}
|
|
|
|
func GetTokenTestData() map[string]TokenTestData {
|
|
return map[string]TokenTestData{
|
|
"empty": {
|
|
[]Token{
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{},
|
|
},
|
|
"addition": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 1, 0, "_"),
|
|
NewToken(TokenAssign, 1, 1, 0, "="),
|
|
NewToken(TokenFloat, 3, 1, 0, "1"),
|
|
NewToken(TokenPlus, 4, 1, 0, "+"),
|
|
NewToken(TokenFloat, 5, 1, 0, "2"),
|
|
NewToken(TokenEOF, 6, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"_", 0, 0},
|
|
&BinaryNode{
|
|
BinaryAddition,
|
|
&FloatNode{
|
|
1,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
2,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"assignment": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 5, 0, "hello"),
|
|
NewToken(TokenAssign, 5, 1, 0, "="),
|
|
NewToken(TokenString, 6, 12, 0, "\"Hello world!\""),
|
|
NewToken(TokenEOF, 18, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"hello", 0, 0},
|
|
&StringNode{
|
|
"Hello world!",
|
|
"\"Hello world!\"",
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"declaration": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 1, 0, "a"),
|
|
NewToken(TokenDeclare, 1, 2, 0, ":="),
|
|
NewToken(TokenFloat, 3, 1, 0, "1"),
|
|
NewToken(TokenPlus, 4, 1, 0, "+"),
|
|
NewToken(TokenName, 5, 1, 0, "b"),
|
|
NewToken(TokenEOF, 6, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"a", 0, 0},
|
|
&BinaryNode{
|
|
BinaryAddition,
|
|
&FloatNode{
|
|
1,
|
|
0, 0,
|
|
},
|
|
&ReferenceNode{
|
|
"b",
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
true,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
|
|
"arithmetic_order": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 1, 0, "_"),
|
|
NewToken(TokenAssign, 1, 2, 0, "="),
|
|
|
|
NewToken(TokenOpenParenthesis, 3, 1, 0, "("),
|
|
NewToken(TokenFloat, 4, 1, 0, "2"),
|
|
NewToken(TokenPlus, 5, 1, 0, "+"),
|
|
NewToken(TokenFloat, 6, 1, 0, "1"),
|
|
NewToken(TokenCloseParenthesis, 7, 1, 0, ")"),
|
|
NewToken(TokenStar, 8, 1, 0, "*"),
|
|
NewToken(TokenFloat, 9, 1, 0, "5"),
|
|
|
|
NewToken(TokenPlus, 10, 1, 0, "+"),
|
|
|
|
NewToken(TokenFloat, 11, 1, 0, "3"),
|
|
NewToken(TokenSlash, 12, 1, 0, "/"),
|
|
NewToken(TokenOpenParenthesis, 13, 1, 0, "("),
|
|
NewToken(TokenFloat, 14, 1, 0, "6"),
|
|
NewToken(TokenMinus, 15, 1, 0, "-"),
|
|
NewToken(TokenFloat, 16, 1, 0, "2"),
|
|
NewToken(TokenCloseParenthesis, 17, 1, 0, ")"),
|
|
|
|
NewToken(TokenMinus, 18, 1, 0, "-"),
|
|
|
|
NewToken(TokenFloat, 19, 2, 0, "10"),
|
|
NewToken(TokenSlash, 20, 1, 0, "/"),
|
|
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{
|
|
BinaryAddition,
|
|
&BinaryNode{
|
|
BinaryMultiplication,
|
|
&BinaryNode{
|
|
BinaryAddition,
|
|
&FloatNode{
|
|
2,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
1,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
5,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
&BinaryNode{
|
|
BinaryDivision,
|
|
&FloatNode{
|
|
3,
|
|
0, 0,
|
|
},
|
|
&BinaryNode{
|
|
BinarySubtraction,
|
|
&FloatNode{
|
|
6,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
2,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
&BinaryNode{
|
|
BinaryDivision,
|
|
&FloatNode{
|
|
10,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
2,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"condition_equal": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 1, 0, "_"),
|
|
NewToken(TokenAssign, 1, 1, 0, "="),
|
|
NewToken(TokenFloat, 2, 2, 0, "20"),
|
|
NewToken(TokenEquals, 4, 2, 0, "=="),
|
|
NewToken(TokenFloat, 6, 2, 0, "15"),
|
|
NewToken(TokenEOF, 8, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"_", 0, 0},
|
|
&BinaryNode{
|
|
BinaryEquality,
|
|
&FloatNode{
|
|
20,
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
15,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"if_statement": {
|
|
[]Token{
|
|
NewToken(TokenIf, 0, 2, 0, "if"),
|
|
NewToken(TokenName, 2, 1, 0, "a"),
|
|
NewToken(TokenEquals, 3, 2, 0, "=="),
|
|
NewToken(TokenFloat, 5, 1, 0, "0"),
|
|
NewToken(TokenOpenBrace, 6, 1, 0, "{"),
|
|
NewToken(TokenName, 7, 1, 1, "b"),
|
|
NewToken(TokenAssign, 8, 1, 1, "="),
|
|
NewToken(TokenFloat, 9, 1, 1, "1"),
|
|
NewToken(TokenCloseBrace, 10, 1, 2, "}"),
|
|
NewToken(TokenEOF, 11, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&ConditionalNode{
|
|
condition: &BinaryNode{
|
|
BinaryEquality,
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
0,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
do: &BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"b", 0, 0},
|
|
&FloatNode{
|
|
1,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"if_else_statement": {
|
|
[]Token{
|
|
NewToken(TokenIf, 0, 2, 0, "if"),
|
|
NewToken(TokenName, 2, 1, 0, "a"),
|
|
NewToken(TokenEquals, 3, 2, 0, "=="),
|
|
NewToken(TokenFloat, 5, 1, 0, "0"),
|
|
NewToken(TokenOpenBrace, 6, 1, 0, "{"),
|
|
NewToken(TokenName, 7, 1, 1, "b"),
|
|
NewToken(TokenAssign, 8, 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(TokenFloat, 18, 1, 2, "0"),
|
|
NewToken(TokenCloseBrace, 19, 1, 2, "}"),
|
|
NewToken(TokenEOF, 20, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&ConditionalNode{
|
|
condition: &BinaryNode{
|
|
BinaryEquality,
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
0,
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
do: &BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"b", 0, 0},
|
|
&FloatNode{
|
|
1,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
otherwise: &BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"b", 0, 0},
|
|
&FloatNode{
|
|
0,
|
|
0, 0,
|
|
},
|
|
false,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"empty_block": {
|
|
[]Token{
|
|
NewToken(TokenOpenBrace, 0, 1, 0, "{"),
|
|
NewToken(TokenCloseBrace, 1, 1, 0, "}"),
|
|
NewToken(TokenEOF, 2, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&BlockNode{
|
|
[]Node{},
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"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, 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, "float"),
|
|
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
|
|
NewToken(TokenArrow, 12, 1, 0, "->"),
|
|
NewToken(TokenName, 10, 5, 0, "float"),
|
|
|
|
NewToken(TokenOpenBrace, 12, 1, 1, "{"),
|
|
NewToken(TokenReturn, 13, 6, 1, "return"),
|
|
NewToken(TokenName, 19, 1, 1, "a"),
|
|
NewToken(TokenPlus, 20, 1, 1, "+"),
|
|
NewToken(TokenName, 21, 1, 1, "b"),
|
|
NewToken(TokenCloseBrace, 22, 1, 2, "}"),
|
|
|
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"a", 0, 0},
|
|
&FunctionNode{
|
|
"*",
|
|
[]FunctionParameter{
|
|
{
|
|
"a",
|
|
&FloatSignature{},
|
|
},
|
|
{
|
|
"b",
|
|
&FloatSignature{},
|
|
},
|
|
},
|
|
&FloatSignature{},
|
|
&BlockNode{
|
|
[]Node{
|
|
&ReturnNode{
|
|
&BinaryNode{
|
|
BinaryAddition,
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&ReferenceNode{
|
|
"b",
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
0, 0,
|
|
},
|
|
true,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"function_declaration": {
|
|
[]Token{
|
|
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"),
|
|
NewToken(TokenName, 19, 1, 1, "a"),
|
|
NewToken(TokenPlus, 20, 1, 1, "+"),
|
|
NewToken(TokenName, 21, 1, 1, "b"),
|
|
NewToken(TokenCloseBrace, 22, 1, 2, "}"),
|
|
|
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"a", 0, 0},
|
|
&FunctionNode{
|
|
"a",
|
|
[]FunctionParameter{
|
|
{
|
|
"a",
|
|
&FloatSignature{},
|
|
},
|
|
{
|
|
"b",
|
|
&FloatSignature{},
|
|
},
|
|
},
|
|
&FloatSignature{},
|
|
&BlockNode{
|
|
[]Node{
|
|
&ReturnNode{
|
|
&BinaryNode{
|
|
BinaryAddition,
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&ReferenceNode{
|
|
"b",
|
|
0, 0,
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
0, 0,
|
|
},
|
|
true,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"prop_getting": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 1, 0, "p"),
|
|
NewToken(TokenDeclare, 1, 2, 0, ":="),
|
|
NewToken(TokenName, 3, 1, 0, "a"),
|
|
NewToken(TokenDot, 4, 1, 0, "."),
|
|
NewToken(TokenName, 5, 1, 0, "b"),
|
|
|
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"p", 0, 0},
|
|
&AccessNode{
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&Token{TokenName, 0, 1, 0, "b"},
|
|
0, 0,
|
|
},
|
|
true,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"list_init": {
|
|
[]Token{
|
|
NewToken(TokenName, 0, 4, 0, "data"),
|
|
NewToken(TokenDeclare, 4, 2, 0, ":="),
|
|
|
|
NewToken(TokenOpenBracket, 6, 1, 0, "["),
|
|
NewToken(TokenName, 8, 1, 0, "a"),
|
|
NewToken(TokenComma, 11, 1, 0, ","),
|
|
|
|
NewToken(TokenFloat, 8, 1, 0, "3.141"),
|
|
NewToken(TokenComma, 11, 1, 0, ","),
|
|
|
|
NewToken(TokenString, 6, 1, 0, "\"Hello world!\""),
|
|
NewToken(TokenComma, 11, 1, 0, ","),
|
|
|
|
NewToken(TokenTrue, 6, 1, 0, "true"),
|
|
NewToken(TokenComma, 11, 1, 0, ","),
|
|
|
|
NewToken(TokenOpenBracket, 6, 1, 0, "["),
|
|
NewToken(TokenFloat, 6, 1, 0, "2"),
|
|
NewToken(TokenComma, 11, 1, 0, ","),
|
|
|
|
NewToken(TokenFloat, 6, 1, 0, "3"),
|
|
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
|
|
|
|
NewToken(TokenCloseBracket, 6, 1, 0, "]"),
|
|
|
|
NewToken(TokenEOF, 23, 0, 2, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&AssignNode{
|
|
&ReferenceNode{"data", 0, 0},
|
|
&ListNode{
|
|
[]Node{
|
|
&ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
&FloatNode{
|
|
3.141,
|
|
0, 0,
|
|
},
|
|
&StringNode{
|
|
"Hello world!",
|
|
"\"Hello world!\"",
|
|
0, 0,
|
|
},
|
|
&BooleanNode{
|
|
true,
|
|
0, 0,
|
|
},
|
|
&ListNode{
|
|
[]Node{
|
|
&FloatNode{
|
|
2,
|
|
0, 0,
|
|
}, &FloatNode{
|
|
3,
|
|
0, 0,
|
|
},
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
},
|
|
nil,
|
|
0, 0,
|
|
},
|
|
true,
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"single_tuple": {
|
|
[]Token{
|
|
NewToken(TokenOpenParenthesis, 0, 0, 0, "("),
|
|
NewToken(TokenInteger, 0, 0, 0, "1"),
|
|
NewToken(TokenComma, 0, 0, 0, ","),
|
|
NewToken(TokenCloseParenthesis, 0, 0, 0, ")"),
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&TupleNode{
|
|
[]Node{
|
|
&IntegerNode{
|
|
big.NewInt(1),
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"tuple": {
|
|
[]Token{
|
|
NewToken(TokenOpenParenthesis, 0, 0, 0, "("),
|
|
NewToken(TokenInteger, 0, 0, 0, "1"),
|
|
NewToken(TokenComma, 0, 0, 0, ","),
|
|
NewToken(TokenInteger, 0, 0, 0, "2"),
|
|
NewToken(TokenCloseParenthesis, 0, 0, 0, ")"),
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&TupleNode{
|
|
[]Node{
|
|
&IntegerNode{
|
|
big.NewInt(1),
|
|
0, 0,
|
|
},
|
|
&IntegerNode{
|
|
big.NewInt(2),
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"record/single": {
|
|
[]Token{
|
|
NewToken(TokenOpenParenthesis, 0, 0, 0, "("),
|
|
NewToken(TokenName, 0, 0, 1, "a"),
|
|
NewToken(TokenColon, 0, 0, 0, ":"),
|
|
NewToken(TokenInteger, 0, 0, 0, "2"),
|
|
NewToken(TokenCloseParenthesis, 0, 0, 0, ")"),
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&RecordNode{
|
|
map[string]Node{
|
|
"a": &IntegerNode{
|
|
big.NewInt(2),
|
|
0, 0,
|
|
},
|
|
},
|
|
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"record/multiple": {
|
|
[]Token{
|
|
NewToken(TokenOpenParenthesis, 0, 0, 0, "("),
|
|
NewToken(TokenName, 0, 0, 1, "a"),
|
|
NewToken(TokenColon, 0, 0, 0, ":"),
|
|
NewToken(TokenInteger, 0, 0, 0, "2"),
|
|
NewToken(TokenComma, 0, 0, 0, ","),
|
|
NewToken(TokenName, 0, 0, 1, "b"),
|
|
NewToken(TokenColon, 0, 0, 0, ":"),
|
|
NewToken(TokenInteger, 0, 0, 0, "4"),
|
|
NewToken(TokenCloseParenthesis, 0, 0, 0, ")"),
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&RecordNode{
|
|
map[string]Node{
|
|
"a": &IntegerNode{
|
|
big.NewInt(2),
|
|
0, 0,
|
|
},
|
|
"b": &IntegerNode{
|
|
big.NewInt(4),
|
|
0, 0,
|
|
},
|
|
},
|
|
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
"record/shorthand": {
|
|
[]Token{
|
|
NewToken(TokenOpenParenthesis, 0, 0, 0, "("),
|
|
NewToken(TokenName, 0, 0, 1, "a"),
|
|
NewToken(TokenColon, 0, 0, 0, ":"),
|
|
NewToken(TokenComma, 0, 0, 0, ","),
|
|
NewToken(TokenName, 0, 0, 1, "b"),
|
|
NewToken(TokenColon, 0, 0, 0, ":"),
|
|
NewToken(TokenCloseParenthesis, 0, 0, 0, ")"),
|
|
NewToken(TokenEOF, 0, 0, 0, ""),
|
|
},
|
|
&BlockNode{
|
|
[]Node{
|
|
&RecordNode{
|
|
map[string]Node{
|
|
"a": &ReferenceNode{
|
|
"a",
|
|
0, 0,
|
|
},
|
|
"b": &ReferenceNode{
|
|
"b",
|
|
0, 0,
|
|
},
|
|
},
|
|
|
|
0, 0,
|
|
},
|
|
},
|
|
0, 0,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func NodeEquality(t *testing.T, n1 Node, n2 Node) {
|
|
if n1 == n2 {
|
|
return
|
|
}
|
|
|
|
if n1 == nil || n2 == nil {
|
|
t.Fatalf("one of the nodes are nil (1: %s; 2: %s)", n1, n2)
|
|
}
|
|
|
|
if n1.Type() != n2.Type() {
|
|
t.Fatalf("node types (%s and %s) don't match", n1.Type(), n2.Type())
|
|
}
|
|
|
|
t.Logf("Nodes have same non-nil type (%s)", n1.Type())
|
|
|
|
switch n1.Type() {
|
|
case NilNodeType:
|
|
case StringNodeType:
|
|
if n1.(*StringNode).value != n2.(*StringNode).value {
|
|
t.Errorf("String node values don't match (%s and %s)", n1.(*StringNode).value, n2.(*StringNode).value)
|
|
} else {
|
|
t.Logf("String node values match (%s)", n1.(*StringNode).value)
|
|
}
|
|
|
|
if n1.(*StringNode).quoted != n2.(*StringNode).quoted {
|
|
t.Errorf("String node quoted values don't match (%s and %s)", n1.(*StringNode).quoted, n2.(*StringNode).quoted)
|
|
} else {
|
|
t.Logf("String node quoted values match (%s)", n1.(*StringNode).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("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)
|
|
} else {
|
|
t.Logf("Reference node values match (%s)", n1.(*ReferenceNode).name)
|
|
}
|
|
case BinaryNodeType:
|
|
if n1.(*BinaryNode).BinaryOperation != n2.(*BinaryNode).BinaryOperation {
|
|
t.Errorf("Binary node operation not same (%s and %s)", n1.(*BinaryNode).BinaryOperation, n2.(*BinaryNode).BinaryOperation)
|
|
} else {
|
|
t.Logf("Binary node operation matches (%s)", n1.(*BinaryNode).BinaryOperation)
|
|
}
|
|
|
|
t.Log("Checking equality of binary left side")
|
|
NodeEquality(t, n1.(*BinaryNode).Left, n2.(*BinaryNode).Left)
|
|
t.Log("Checking equality of binary right side")
|
|
NodeEquality(t, n1.(*BinaryNode).Right, n2.(*BinaryNode).Right)
|
|
|
|
case BooleanNodeType:
|
|
if n1.(*BooleanNode).Boolean != n2.(*BooleanNode).Boolean {
|
|
t.Errorf("Boolean node values don't match (%s and %s)", strconv.FormatBool(n1.(*BooleanNode).Boolean), strconv.FormatBool(n2.(*BooleanNode).Boolean))
|
|
} else {
|
|
t.Logf("Boolean node values match (%s)", strconv.FormatBool(n1.(*BooleanNode).Boolean))
|
|
}
|
|
case BlockNodeType:
|
|
if len(n1.(*BlockNode).statements) != len(n2.(*BlockNode).statements) {
|
|
t.Errorf("Block node statement count is not equal (%d and %d)", len(n1.(*BlockNode).statements), len(n2.(*BlockNode).statements))
|
|
} else {
|
|
t.Logf("Block node statement count is equal (%d) ", len(n1.(*BlockNode).statements))
|
|
}
|
|
|
|
for i, n := range n1.(*BlockNode).statements {
|
|
t.Logf("Checking equality of statements at %d", i)
|
|
NodeEquality(t, n, n2.(*BlockNode).statements[i])
|
|
}
|
|
|
|
case ConditionalNodeType:
|
|
t.Log("Checking equality of conditions")
|
|
NodeEquality(t, n1.(*ConditionalNode).condition, n2.(*ConditionalNode).condition)
|
|
t.Log("Checking equality of do statement(s)")
|
|
NodeEquality(t, n1.(*ConditionalNode).do, n2.(*ConditionalNode).do)
|
|
t.Log("Checking equality of else statement(s)")
|
|
NodeEquality(t, n1.(*ConditionalNode).otherwise, n2.(*ConditionalNode).otherwise)
|
|
|
|
case LoopNodeType:
|
|
t.Log("Checking equality of loop conditions")
|
|
NodeEquality(t, n1.(*LoopNode).condition, n2.(*LoopNode).condition)
|
|
t.Log("Checking equality of do loop statement(s)")
|
|
NodeEquality(t, n1.(*LoopNode).do, n2.(*LoopNode).do)
|
|
|
|
case AssignNodeType:
|
|
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)
|
|
}
|
|
|
|
t.Logf("Checking equality of assignment values")
|
|
NodeEquality(t, n1.(*AssignNode).value, n2.(*AssignNode).value)
|
|
|
|
case InvokeNodeType:
|
|
n := n1.(*InvokeNode)
|
|
m := n2.(*InvokeNode)
|
|
|
|
NodeEquality(t, n.source, m.source)
|
|
|
|
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.(*InvokeNode).args[i], arg)
|
|
}
|
|
|
|
case FunctionNodeType:
|
|
n := n1.(*FunctionNode)
|
|
m := n2.(*FunctionNode)
|
|
|
|
if n.name != m.name {
|
|
t.Errorf("Function node names don't match (%s and %s)", n.name, m.name)
|
|
} else {
|
|
t.Logf("Function node names match (%s)", n.name)
|
|
}
|
|
|
|
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.parameters))
|
|
}
|
|
|
|
for i, p := range m.parameters {
|
|
if !n.parameters[i].Signature.Contains(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)
|
|
}
|
|
}
|
|
|
|
NodeEquality(t, n.logic, m.logic)
|
|
|
|
case ReturnNodeType:
|
|
NodeEquality(t, n1.(*ReturnNode).value, n2.(*ReturnNode).value)
|
|
|
|
case AccessNodeType:
|
|
a1 := n1.(*AccessNode)
|
|
a2 := n2.(*AccessNode)
|
|
|
|
// only care about lexeme; the rest is debug info
|
|
if a1.property.Lexeme != a2.property.Lexeme {
|
|
t.Errorf("Access node property does not match: .%s != .%s", a1.property.Lexeme, a2.property.Lexeme)
|
|
} else {
|
|
t.Logf("Access node property matches: .%s", a1.property.Lexeme)
|
|
}
|
|
|
|
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.Contains(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])
|
|
}
|
|
|
|
case TupleNodeType:
|
|
t1 := n1.(*TupleNode)
|
|
t2 := n2.(*TupleNode)
|
|
|
|
if len(t1.items) != len(t2.items) {
|
|
t.Fatalf("tuple item count does not match")
|
|
}
|
|
|
|
for i, v1 := range t1.items {
|
|
t.Logf("Checking item %d", i)
|
|
NodeEquality(t, v1, t2.items[i])
|
|
}
|
|
|
|
case RecordNodeType:
|
|
r1 := n1.(*RecordNode)
|
|
r2 := n2.(*RecordNode)
|
|
|
|
if len(r1.entries) != len(r2.entries) {
|
|
t.Fatalf("Record node entries count does not match")
|
|
}
|
|
|
|
for i, v1 := range r1.entries {
|
|
t.Logf("Checking item %s", i)
|
|
if v2, ok := r2.entries[i]; ok {
|
|
NodeEquality(t, v1, v2)
|
|
} else {
|
|
t.Errorf("Record node entry %s from first does not exist in other", i)
|
|
}
|
|
}
|
|
|
|
default:
|
|
panic("unimplemented node equality")
|
|
}
|
|
}
|
|
|
|
func TestParser_Parse(t *testing.T) {
|
|
t.Logf("Getting test data")
|
|
tokenData := GetTokenTestData()
|
|
|
|
for name, data := range tokenData {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Logf("Initializing parser")
|
|
p := NewParser("", []string{}, data.tokens)
|
|
|
|
t.Logf("Parsing main")
|
|
tree, err := p.Parse("")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error(s): %s", err.(ParsingError).Format())
|
|
}
|
|
|
|
t.Logf("Checking parsed tree")
|
|
NodeEquality(t, tree.Block, data.tree)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParser_AcceptSeq(t *testing.T) {
|
|
p := NewParser("a:", []string{}, []Token{
|
|
NewToken(TokenName, 0, 1, 0, "a"),
|
|
NewToken(TokenColon, 1, 2, 0, ":"),
|
|
NewToken(TokenEOF, 2, 2, 0, ""),
|
|
})
|
|
|
|
// initialize
|
|
p.advance()
|
|
|
|
if !p.acceptSeq(TokenName, TokenColon) {
|
|
t.Fatalf("tokens were not accepted")
|
|
}
|
|
|
|
t.Logf("tokens were accepted")
|
|
}
|
|
|
|
func TestParser_AcceptSeqAndAfter(t *testing.T) {
|
|
p := NewParser("a: 1", []string{}, []Token{
|
|
NewToken(TokenName, 0, 1, 0, "a"),
|
|
NewToken(TokenColon, 1, 2, 0, ":"),
|
|
NewToken(TokenInteger, 3, 4, 0, "1"),
|
|
NewToken(TokenEOF, 4, 4, 0, ""),
|
|
})
|
|
|
|
// initialize
|
|
p.advance()
|
|
|
|
if !p.acceptSeq(TokenName, TokenColon) {
|
|
t.Fatalf("seq tokens were not accepted")
|
|
}
|
|
|
|
if !p.accept(TokenInteger) {
|
|
t.Fatalf("integer was not accepted")
|
|
}
|
|
|
|
t.Logf("tokens were accepted")
|
|
}
|
|
|
|
func TestParser_AcceptSeq_TooFew(t *testing.T) {
|
|
p := NewParser("a", []string{}, []Token{
|
|
NewToken(TokenName, 0, 1, 0, "a"),
|
|
NewToken(TokenEOF, 1, 1, 0, ""),
|
|
})
|
|
|
|
// initialize
|
|
p.advance()
|
|
|
|
if p.acceptSeq(TokenName, TokenColon) {
|
|
t.Fatalf("tokens were incorrectly accepted")
|
|
}
|
|
|
|
t.Logf("tokens were, as expected, not accepted")
|
|
}
|
|
|
|
func BenchmarkParser_Parse(b *testing.B) {
|
|
tokenData := GetTokenTestData()
|
|
|
|
for name, data := range tokenData {
|
|
b.Run(name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
p := NewParser("", []string{}, data.tokens)
|
|
|
|
_, _ = p.Parse("")
|
|
}
|
|
})
|
|
}
|
|
}
|