From e8d4bc98ae2f8752942b53af6a7bcd914df6b0a3 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Tue, 6 May 2025 17:21:58 -0500 Subject: [PATCH] remove semicolon parsing --- compiler/compiler.go | 18 ++ go.mod | 2 - lexer/lexer.go | 2 - parser/parser.go | 40 +--- tests/compiler_test.go | 394 ---------------------------------- tests/edge_test.go | 438 -------------------------------------- tests/integration_test.go | 408 ----------------------------------- tests/lexer_test.go | 237 --------------------- tests/parser_test.go | 345 ------------------------------ tests/vm_test.go | 354 ------------------------------ 10 files changed, 19 insertions(+), 2219 deletions(-) delete mode 100644 tests/compiler_test.go delete mode 100644 tests/edge_test.go delete mode 100644 tests/integration_test.go delete mode 100644 tests/lexer_test.go delete mode 100644 tests/parser_test.go delete mode 100644 tests/vm_test.go diff --git a/compiler/compiler.go b/compiler/compiler.go index e272cc3..37f8a1e 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -18,7 +18,21 @@ func Compile(program *parser.Program) *types.Bytecode { // Start in global scope c.enterScope() + // Add nil check for program + if program == nil { + c.exitScope() + return &types.Bytecode{ + Constants: c.constants, + Instructions: c.instructions, + } + } + + // Process each statement safely for _, stmt := range program.Statements { + // Skip nil statements + if stmt == nil { + continue + } c.compileStatement(stmt) } @@ -68,6 +82,10 @@ func (c *compiler) isLocalVariable(name string) bool { } func (c *compiler) compileStatement(stmt parser.Statement) { + if stmt == nil { + return + } + switch s := stmt.(type) { case *parser.VariableStatement: c.compileExpression(s.Value) diff --git a/go.mod b/go.mod index 66186be..9130d44 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module git.sharkk.net/Sharkk/Mako go 1.24.1 - -require git.sharkk.net/Go/Assert v0.0.0-20250426205601-1b0e5ea6e7ac diff --git a/lexer/lexer.go b/lexer/lexer.go index 1db41b7..aa6b4f7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -108,8 +108,6 @@ func (l *Lexer) NextToken() Token { } else { tok = Token{Type: TokenGreaterThan, Value: ">", Line: l.line} } - case ';': - tok = Token{Type: TokenSemicolon, Value: ";", Line: l.line} case '"': tok = Token{Type: TokenString, Value: l.readString(), Line: l.line} return tok diff --git a/parser/parser.go b/parser/parser.go index 31e344a..8f04bbd 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -188,10 +188,6 @@ func (p *Parser) parseExpressionStatement() *ExpressionStatement { stmt.Expression = p.parseExpression(LOWEST) - if p.peekTokenIs(lexer.TokenSemicolon) { - p.nextToken() - } - return stmt } @@ -204,21 +200,6 @@ type ExpressionStatement struct { func (es *ExpressionStatement) statementNode() {} func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Value } -func (p *Parser) parseBlockStatement() *BlockStatement { - block := &BlockStatement{Token: p.curToken} - block.Statements = []Statement{} - - p.nextToken() // Skip '{' - - for p.curToken.Type != lexer.TokenRightBrace && p.curToken.Type != lexer.TokenEOF { - stmt := p.parseStatement() - block.Statements = append(block.Statements, stmt) - p.nextToken() - } - - return block -} - func (p *Parser) parseVariableStatement() *VariableStatement { stmt := &VariableStatement{Token: p.curToken} @@ -232,10 +213,6 @@ func (p *Parser) parseVariableStatement() *VariableStatement { stmt.Value = p.parseExpression(LOWEST) - if p.peekTokenIs(lexer.TokenSemicolon) { - p.nextToken() - } - return stmt } @@ -246,10 +223,6 @@ func (p *Parser) parseEchoStatement() *EchoStatement { stmt.Value = p.parseExpression(LOWEST) - if p.peekTokenIs(lexer.TokenSemicolon) { - p.nextToken() - } - return stmt } @@ -278,10 +251,6 @@ func (p *Parser) parseIndexAssignmentStatement() *IndexAssignmentStatement { p.nextToken() // Skip '=' stmt.Value = p.parseExpression(LOWEST) - if p.peekTokenIs(lexer.TokenSemicolon) { - p.nextToken() - } - return stmt } @@ -296,8 +265,7 @@ func (p *Parser) parseExpression(precedence int) Expression { // Continue while we have valid infix operators // and stop at special tokens that end expressions - for !p.peekTokenIs(lexer.TokenSemicolon) && - !p.peekTokenIs(lexer.TokenEnd) && + for !p.peekTokenIs(lexer.TokenEnd) && !p.peekTokenIs(lexer.TokenThen) && !p.peekTokenIs(lexer.TokenElse) && precedence < p.peekPrecedence() { @@ -600,12 +568,6 @@ func (p *Parser) parseElseIfExpression() Expression { return expression } -func (p *Parser) parseErrorToken() Expression { - msg := fmt.Sprintf("unexpected token: %s", p.curToken.Value) - p.errors = append(p.errors, msg) - return nil -} - func (p *Parser) parseUnexpectedToken() Expression { p.errors = append(p.errors, fmt.Sprintf("line %d: unexpected token: %s", p.curToken.Line, p.curToken.Value)) diff --git a/tests/compiler_test.go b/tests/compiler_test.go deleted file mode 100644 index 89acbcd..0000000 --- a/tests/compiler_test.go +++ /dev/null @@ -1,394 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/compiler" - "git.sharkk.net/Sharkk/Mako/lexer" - "git.sharkk.net/Sharkk/Mako/parser" - "git.sharkk.net/Sharkk/Mako/types" -) - -func TestCompileConstants(t *testing.T) { - input := ` - 5; - "hello"; - true; - false; - ` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Check that we have the right constants - assert.Equal(t, 4, len(bytecode.Constants)) - - // Check constant values - assert.Equal(t, 5.0, bytecode.Constants[0]) - assert.Equal(t, "hello", bytecode.Constants[1]) - assert.Equal(t, true, bytecode.Constants[2]) - assert.Equal(t, false, bytecode.Constants[3]) - - // Check that we have the right instructions - assert.Equal(t, 10, len(bytecode.Instructions)) // 2 scope + 4 constants + 4 pops - - // Check instruction opcodes - first instruction is enter scope - assert.Equal(t, types.OpEnterScope, bytecode.Instructions[0].Opcode) - - // Check the constant loading and popping instructions - for i := 0; i < 4; i++ { - assert.Equal(t, types.OpConstant, bytecode.Instructions[1+i*2].Opcode) - assert.Equal(t, i, bytecode.Instructions[1+i*2].Operand) - assert.Equal(t, types.OpPop, bytecode.Instructions[2+i*2].Opcode) - } - - // Last instruction is exit scope - assert.Equal(t, types.OpExitScope, bytecode.Instructions[len(bytecode.Instructions)-1].Opcode) -} - -func TestCompileVariableAssignment(t *testing.T) { - input := `x = 5;` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Constants should be: 5, "x" - assert.Equal(t, 2, len(bytecode.Constants)) - assert.Equal(t, 5.0, bytecode.Constants[0]) - assert.Equal(t, "x", bytecode.Constants[1]) - - // Instructions should be: - // - OpEnterScope - // - OpConstant (5) - // - OpSetGlobal with operand pointing to "x" - // - OpExitScope - assert.Equal(t, 4, len(bytecode.Instructions)) - - assert.Equal(t, types.OpEnterScope, bytecode.Instructions[0].Opcode) - assert.Equal(t, types.OpConstant, bytecode.Instructions[1].Opcode) - assert.Equal(t, 0, bytecode.Instructions[1].Operand) - assert.Equal(t, types.OpSetGlobal, bytecode.Instructions[2].Opcode) - assert.Equal(t, 1, bytecode.Instructions[2].Operand) - assert.Equal(t, types.OpExitScope, bytecode.Instructions[3].Opcode) -} - -func TestCompileArithmeticExpressions(t *testing.T) { - tests := []struct { - input string - constants []any - operations []types.Opcode - }{ - { - "5 + 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpAdd, types.OpPop}, - }, - { - "5 - 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpSubtract, types.OpPop}, - }, - { - "5 * 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpMultiply, types.OpPop}, - }, - { - "5 / 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpDivide, types.OpPop}, - }, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Check constants - assert.Equal(t, len(tt.constants), len(bytecode.Constants)) - for i, c := range tt.constants { - assert.Equal(t, c, bytecode.Constants[i]) - } - - // Check operations, skipping the first and last (scope instructions) - assert.Equal(t, len(tt.operations)+2, len(bytecode.Instructions)) - for i, op := range tt.operations { - assert.Equal(t, op, bytecode.Instructions[i+1].Opcode) - } - } -} - -func TestCompileComparisonExpressions(t *testing.T) { - tests := []struct { - input string - constants []any - operations []types.Opcode - }{ - { - "5 == 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpEqual, types.OpPop}, - }, - { - "5 != 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpNotEqual, types.OpPop}, - }, - { - "5 < 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpLessThan, types.OpPop}, - }, - { - "5 > 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpGreaterThan, types.OpPop}, - }, - { - "5 <= 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpLessEqual, types.OpPop}, - }, - { - "5 >= 10;", - []any{5.0, 10.0}, - []types.Opcode{types.OpConstant, types.OpConstant, types.OpGreaterEqual, types.OpPop}, - }, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Check constants - assert.Equal(t, len(tt.constants), len(bytecode.Constants)) - for i, c := range tt.constants { - assert.Equal(t, c, bytecode.Constants[i]) - } - - // Check operations, skipping the first and last (scope instructions) - assert.Equal(t, len(tt.operations)+2, len(bytecode.Instructions)) - for i, op := range tt.operations { - assert.Equal(t, op, bytecode.Instructions[i+1].Opcode) - } - } -} - -func TestCompileTable(t *testing.T) { - input := `table = { name = "John", age = 30 };` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Instead of checking exact order, just verify the constants are present - assert.Equal(t, 5, len(bytecode.Constants)) - - // Check that all expected constants are present - foundName := false - foundJohn := false - foundAge := false - found30 := false - foundTable := false - - for _, constant := range bytecode.Constants { - switch v := constant.(type) { - case string: - if v == "name" { - foundName = true - } else if v == "John" { - foundJohn = true - } else if v == "age" { - foundAge = true - } else if v == "table" { - foundTable = true - } - case float64: - if v == 30.0 { - found30 = true - } - } - } - - assert.True(t, foundName) - assert.True(t, foundJohn) - assert.True(t, foundAge) - assert.True(t, found30) - assert.True(t, foundTable) - - // Check opcodes rather than exact operands which depend on constant order - expectedOpcodes := []types.Opcode{ - types.OpEnterScope, - types.OpNewTable, // Create table - types.OpDup, // Duplicate to set first property - types.OpConstant, // Load key - types.OpConstant, // Load value - types.OpSetIndex, // Set first property - types.OpPop, // Pop result of setindex - types.OpDup, // Duplicate for second property - types.OpConstant, // Load key - types.OpConstant, // Load value - types.OpSetIndex, // Set second property - types.OpPop, // Pop result of setindex - types.OpSetGlobal, // Set table variable - types.OpExitScope, - } - - assert.Equal(t, len(expectedOpcodes), len(bytecode.Instructions)) - for i, op := range expectedOpcodes { - assert.Equal(t, op, bytecode.Instructions[i].Opcode) - } -} - -func TestCompileIfStatement(t *testing.T) { - input := ` - if x < 10 then - echo "x is less than 10"; - else - echo "x is not less than 10"; - end - ` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - // Don't check exact count - just verify key constants exist - assert.True(t, len(bytecode.Constants) >= 5) - - // Check for key opcodes in the bytecode - ops := bytecode.Instructions - - // Check for variable lookup - foundGetGlobal := false - for _, op := range ops { - if op.Opcode == types.OpGetGlobal { - foundGetGlobal = true - break - } - } - assert.True(t, foundGetGlobal) - - // Check for comparison opcode - foundLessThan := false - for _, op := range ops { - if op.Opcode == types.OpLessThan { - foundLessThan = true - break - } - } - assert.True(t, foundLessThan) - - // Check for conditional jump - foundJumpIfFalse := false - for _, op := range ops { - if op.Opcode == types.OpJumpIfFalse { - foundJumpIfFalse = true - break - } - } - assert.True(t, foundJumpIfFalse) - - // Check for unconditional jump (for else clause) - foundJump := false - for _, op := range ops { - if op.Opcode == types.OpJump { - foundJump = true - break - } - } - assert.True(t, foundJump) - - // Check for echo statements - foundEcho := false - echoCount := 0 - for _, op := range ops { - if op.Opcode == types.OpEcho { - foundEcho = true - echoCount++ - } - } - assert.True(t, foundEcho) - assert.Equal(t, 2, echoCount) -} - -func TestCompileLogicalOperators(t *testing.T) { - tests := []struct { - input string - expectedOperator types.Opcode - }{ - {"not true;", types.OpNot}, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - bytecode := compiler.Compile(program) - - foundOp := false - for _, op := range bytecode.Instructions { - if op.Opcode == tt.expectedOperator { - foundOp = true - break - } - } - assert.True(t, foundOp) - } - - // AND and OR are special as they involve jumps - test separately - andInput := "true and false;" - lex := lexer.New(andInput) - p := parser.New(lex) - program := p.ParseProgram() - bytecode := compiler.Compile(program) - - // AND should involve a JumpIfFalse instruction - foundJumpIfFalse := false - for _, op := range bytecode.Instructions { - if op.Opcode == types.OpJumpIfFalse { - foundJumpIfFalse = true - break - } - } - assert.True(t, foundJumpIfFalse) - - // OR test - orInput := "true or false;" - lex = lexer.New(orInput) - p = parser.New(lex) - program = p.ParseProgram() - bytecode = compiler.Compile(program) - - // OR should involve both Jump and JumpIfFalse instructions - foundJump := false - foundJumpIfFalse = false - for _, op := range bytecode.Instructions { - if op.Opcode == types.OpJump { - foundJump = true - } - if op.Opcode == types.OpJumpIfFalse { - foundJumpIfFalse = true - } - } - assert.True(t, foundJump) - assert.True(t, foundJumpIfFalse) -} diff --git a/tests/edge_test.go b/tests/edge_test.go deleted file mode 100644 index 67eac84..0000000 --- a/tests/edge_test.go +++ /dev/null @@ -1,438 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/compiler" - "git.sharkk.net/Sharkk/Mako/lexer" - "git.sharkk.net/Sharkk/Mako/parser" - "git.sharkk.net/Sharkk/Mako/types" - "git.sharkk.net/Sharkk/Mako/vm" -) - -// Helper function to execute Mako code -func executeMakoWithErrors(code string) (*vm.VM, []string) { - lex := lexer.New(code) - p := parser.New(lex) - program := p.ParseProgram() - errors := p.Errors() - bytecode := compiler.Compile(program) - virtualMachine := vm.New() - virtualMachine.Run(bytecode) - return virtualMachine, errors -} - -func TestTypeConversions(t *testing.T) { - // Test automatic type conversions in operations - vm, errors := executeMakoWithErrors(` - // String concatenation - result1 = "Hello " + "World"; // Should work - - // Using boolean in a condition - if true then - result2 = "Boolean works in condition"; - end - - // Numeric conditions - if 1 then - result3 = "Numeric 1 is truthy"; - end - - if 0 then - result4 = "This should not execute"; - else - result4 = "Numeric 0 is falsy"; - end - `) - - assert.Equal(t, 0, len(errors)) - - // Verify results - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "Hello World", result1.Data.(string)) - - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeString, result2.Type) - assert.Equal(t, "Boolean works in condition", result2.Data.(string)) - - result3, found := vm.GetGlobal("result3") - assert.True(t, found) - assert.Equal(t, types.TypeString, result3.Type) - assert.Equal(t, "Numeric 1 is truthy", result3.Data.(string)) - - result4, found := vm.GetGlobal("result4") - assert.True(t, found) - assert.Equal(t, types.TypeString, result4.Type) - assert.Equal(t, "Numeric 0 is falsy", result4.Data.(string)) -} - -func TestEdgeCases(t *testing.T) { - // Test edge cases that might cause issues - vm, errors := executeMakoWithErrors(` - // Deep nesting - table = { - level1 = { - level2 = { - level3 = { - level4 = { - value = "Deep nesting" - } - } - } - } - }; - - result1 = table["level1"]["level2"]["level3"]["level4"]["value"]; - - // Empty tables - emptyTable = {}; - result2 = emptyTable["nonexistent"]; // Should return null - - // Table with invalid access - someTable = { key = "value" }; - someTable[123] = "numeric key"; // Should work - result3 = someTable[123]; // Should retrieve the value - `) - - assert.Equal(t, 0, len(errors)) - - // Verify deep nesting result - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "Deep nesting", result1.Data.(string)) - - // Verify empty table result - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeNull, result2.Type) - - // Verify numeric key result - result3, found := vm.GetGlobal("result3") - assert.True(t, found) - assert.Equal(t, types.TypeString, result3.Type) - assert.Equal(t, "numeric key", result3.Data.(string)) -} - -func TestErrorHandling(t *testing.T) { - // Test error handling in the parser - _, errors := executeMakoWithErrors(` - // Invalid syntax - missing semicolon - x = 5 - y = 10; - `) - - // Should have at least one error - assert.True(t, len(errors) > 0) - - // Test parser recovery - _, errors = executeMakoWithErrors(` - // Missing end keyword - if x < 10 then - echo "x is less than 10"; - // end - missing - `) - - // Should have at least one error - assert.True(t, len(errors) > 0) -} - -func TestNestedScopes(t *testing.T) { - // Test nested scopes and variable shadowing - vm, errors := executeMakoWithErrors(` - x = "global"; - - { - echo x; // Should be "global" - x = "outer"; - echo x; // Should be "outer" - - { - echo x; // Should be "outer" - x = "inner"; - echo x; // Should be "inner" - - { - echo x; // Should be "inner" - x = "innermost"; - echo x; // Should be "innermost" - } - - echo x; // Should be "inner" again - } - - echo x; // Should be "outer" again - } - - echo x; // Should be "global" again - result = x; - `) - - assert.Equal(t, 0, len(errors)) - - // Verify that x returns to its global value - result, found := vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeString, result.Type) - assert.Equal(t, "global", result.Data.(string)) - - // Also verify x directly - x, found := vm.GetGlobal("x") - assert.True(t, found) - assert.Equal(t, types.TypeString, x.Type) - assert.Equal(t, "global", x.Data.(string)) -} - -func TestComplexExpressions(t *testing.T) { - // Test complex expressions with multiple operators - vm, errors := executeMakoWithErrors(` - // Arithmetic precedence - result1 = 5 + 10 * 2; // Should be 25, not 30 - - // Parentheses override precedence - result2 = (5 + 10) * 2; // Should be 30 - - // Combined comparison and logical operators - x = 5; - y = 10; - z = 15; - - result3 = x < y and y < z; // Should be true - result4 = x > y or y < z; // Should be true - result5 = not (x > y); // Should be true - - // Complex conditional - if x < y and y < z then - result6 = "Condition passed"; - else - result6 = "Condition failed"; - end - `) - - assert.Equal(t, 0, len(errors)) - - // Verify arithmetic precedence - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, result1.Type) - assert.Equal(t, 25.0, result1.Data.(float64)) - - // Verify parentheses precedence - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, result2.Type) - assert.Equal(t, 30.0, result2.Data.(float64)) - - // Verify logical operators - result3, found := vm.GetGlobal("result3") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result3.Type) - assert.Equal(t, true, result3.Data.(bool)) - - result4, found := vm.GetGlobal("result4") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result4.Type) - assert.Equal(t, true, result4.Data.(bool)) - - result5, found := vm.GetGlobal("result5") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result5.Type) - assert.Equal(t, true, result5.Data.(bool)) - - // Verify complex conditional - result6, found := vm.GetGlobal("result6") - assert.True(t, found) - assert.Equal(t, types.TypeString, result6.Type) - assert.Equal(t, "Condition passed", result6.Data.(string)) -} - -func TestNestedTables(t *testing.T) { - // Test nested tables and complex access patterns - vm, errors := executeMakoWithErrors(` - // Define a nested table - config = { - server = { - host = "localhost", - port = 8080, - settings = { - timeout = 30, - retries = 3 - } - }, - database = { - host = "db.example.com", - port = 5432, - credentials = { - username = "admin", - password = "secret" - } - } - }; - - // Access nested values - result1 = config["server"]["host"]; - result2 = config["server"]["settings"]["timeout"]; - result3 = config["database"]["credentials"]["username"]; - - // Update nested values - config["server"]["settings"]["timeout"] = 60; - result4 = config["server"]["settings"]["timeout"]; - - // Add new nested values - config["logging"] = { - level = "info", - file = "app.log" - }; - result5 = config["logging"]["level"]; - `) - - assert.Equal(t, 0, len(errors)) - - // Verify nested table access - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "localhost", result1.Data.(string)) - - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, result2.Type) - assert.Equal(t, 30.0, result2.Data.(float64)) - - result3, found := vm.GetGlobal("result3") - assert.True(t, found) - assert.Equal(t, types.TypeString, result3.Type) - assert.Equal(t, "admin", result3.Data.(string)) - - // Verify nested table update - result4, found := vm.GetGlobal("result4") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, result4.Type) - assert.Equal(t, 60.0, result4.Data.(float64)) - - // Verify adding new nested values - result5, found := vm.GetGlobal("result5") - assert.True(t, found) - assert.Equal(t, types.TypeString, result5.Type) - assert.Equal(t, "info", result5.Data.(string)) -} - -func TestTableAsArguments(t *testing.T) { - // Test using tables as arguments - vm, errors := executeMakoWithErrors(` - // Define a table - person = { - name = "John", - age = 30 - }; - - // Use as index - lookup = { - John = "Developer", - Jane = "Designer" - }; - - // Access using value from another table - role = lookup[person["name"]]; - result1 = role; // Should print "Developer" - `) - - assert.Equal(t, 0, len(errors)) - - // Verify table as argument - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "Developer", result1.Data.(string)) - - // Test complex table indexing - this is more advanced and might not work yet - complexVM, complexErrors := executeMakoWithErrors(` - // Test with table as key - might not work in current implementation - matrix = {}; - - // Instead use a string representation - matrix["0,0"] = "origin"; - result2 = matrix["0,0"]; - `) - - // For now, we expect no errors with the string-based approach - assert.Equal(t, 0, len(complexErrors)) - - result2, found := complexVM.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeString, result2.Type) - assert.Equal(t, "origin", result2.Data.(string)) -} - -func TestTableEquality(t *testing.T) { - // Test table equality and using tables as keys - vm, errors := executeMakoWithErrors(` - // Test table equality - t1 = { x = 1, y = 2 }; - t2 = { x = 1, y = 2 }; - t3 = { x = 1, y = 3 }; - - // Test basic equality - eq_result1 = t1 == t2; // Should be true - eq_result2 = t1 == t3; // Should be false - eq_result3 = t2 == t3; // Should be false - - // Test using tables as keys - lookup = {}; - lookup[t1] = "first table"; - lookup[t3] = "third table"; - - // Try to retrieve values using table keys - result1 = lookup[t1]; // Should be "first table" - result2 = lookup[t2]; // Should also be "first table" because t1 and t2 are equal - result3 = lookup[t3]; // Should be "third table" - - // Test with nested tables - nested1 = { table = t1, name = "nested1" }; - nested2 = { table = t2, name = "nested2" }; - nested3 = { table = t3, name = "nested3" }; - - // Check equality with nested tables - neq_result1 = nested1 == nested2; // Should be false (different names) - neq_result2 = nested1 == nested3; // Should be false (different tables and names) - `) - - assert.Equal(t, 0, len(errors)) - - // Verify basic table equality results - eq_result1, found := vm.GetGlobal("eq_result1") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, eq_result1.Type) - assert.Equal(t, true, eq_result1.Data.(bool)) - - eq_result2, found := vm.GetGlobal("eq_result2") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, eq_result2.Type) - assert.Equal(t, false, eq_result2.Data.(bool)) - - // Verify table as key results - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "first table", result1.Data.(string)) - - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeString, result2.Type) - assert.Equal(t, "first table", result2.Data.(string)) - - result3, found := vm.GetGlobal("result3") - assert.True(t, found) - assert.Equal(t, types.TypeString, result3.Type) - assert.Equal(t, "third table", result3.Data.(string)) - - // Verify nested table equality - neq_result1, found := vm.GetGlobal("neq_result1") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, neq_result1.Type) - assert.Equal(t, false, neq_result1.Data.(bool)) -} diff --git a/tests/integration_test.go b/tests/integration_test.go deleted file mode 100644 index 202d186..0000000 --- a/tests/integration_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/compiler" - "git.sharkk.net/Sharkk/Mako/lexer" - "git.sharkk.net/Sharkk/Mako/parser" - "git.sharkk.net/Sharkk/Mako/types" - "git.sharkk.net/Sharkk/Mako/vm" -) - -// Helper function to execute Mako code -func executeMako(code string) *vm.VM { - lex := lexer.New(code) - p := parser.New(lex) - program := p.ParseProgram() - bytecode := compiler.Compile(program) - virtualMachine := vm.New() - virtualMachine.Run(bytecode) - return virtualMachine -} - -func TestBasicExecution(t *testing.T) { - vm := executeMako(` - // Variables and echo - x = 5; - y = 10; - sum = x + y; - `) - - // Verify the sum variable - sum, found := vm.GetGlobal("sum") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, sum.Type) - assert.Equal(t, 15.0, sum.Data.(float64)) - - // Verify x and y variables - x, found := vm.GetGlobal("x") - assert.True(t, found) - assert.Equal(t, 5.0, x.Data.(float64)) - - y, found := vm.GetGlobal("y") - assert.True(t, found) - assert.Equal(t, 10.0, y.Data.(float64)) -} - -func TestTableOperations(t *testing.T) { - vm := executeMako(` - // Table creation and access - person = { - name = "John", - age = 30, - isActive = true - }; - - nameValue = person["name"]; - person["location"] = "New York"; - locationValue = person["location"]; - `) - - // Verify table operations - nameValue, found := vm.GetGlobal("nameValue") - assert.True(t, found) - assert.Equal(t, types.TypeString, nameValue.Type) - assert.Equal(t, "John", nameValue.Data.(string)) - - locationValue, found := vm.GetGlobal("locationValue") - assert.True(t, found) - assert.Equal(t, types.TypeString, locationValue.Type) - assert.Equal(t, "New York", locationValue.Data.(string)) - - // Verify the person table exists - person, found := vm.GetGlobal("person") - assert.True(t, found) - assert.Equal(t, types.TypeTable, person.Type) -} - -func TestConditionalExecution(t *testing.T) { - vm := executeMako(` - // If-else statements - x = 5; - - if x < 10 then - result1 = "x is less than 10"; - else - result1 = "x is not less than 10"; - end - - // Nested if-else - y = 20; - - if x > y then - result2 = "x is greater than y"; - elseif x < y then - result2 = "x is less than y"; - else - result2 = "x equals y"; - end - `) - - // Verify conditional results - result1, found := vm.GetGlobal("result1") - assert.True(t, found) - assert.Equal(t, types.TypeString, result1.Type) - assert.Equal(t, "x is less than 10", result1.Data.(string)) - - result2, found := vm.GetGlobal("result2") - assert.True(t, found) - assert.Equal(t, types.TypeString, result2.Type) - assert.Equal(t, "x is less than y", result2.Data.(string)) -} - -func TestScopes(t *testing.T) { - vm := executeMako(` - // Global scope - x = 10; - globalX = x; - - // Enter a new scope - { - // Local scope - variable shadowing - x = 20; - localX = x; - - // New local variable - y = 30; - localY = y; - } - - // Back to global scope - afterScopeX = x; - afterScopeY = y; // This should be null since y was only defined in local scope - `) - - // Verify global scope values - globalX, found := vm.GetGlobal("globalX") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, globalX.Type) - assert.Equal(t, 10.0, globalX.Data.(float64)) - - // Verify local scope values while in scope - localX, found := vm.GetGlobal("localX") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, localX.Type) - assert.Equal(t, 20.0, localX.Data.(float64)) - - localY, found := vm.GetGlobal("localY") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, localY.Type) - assert.Equal(t, 30.0, localY.Data.(float64)) - - // Verify variables after scope exit - afterScopeX, found := vm.GetGlobal("afterScopeX") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, afterScopeX.Type) - assert.Equal(t, 10.0, afterScopeX.Data.(float64)) - - // y should be null since it was only defined in local scope - afterScopeY, found := vm.GetGlobal("afterScopeY") - assert.True(t, found) - assert.Equal(t, types.TypeNull, afterScopeY.Type) -} - -func TestArithmeticOperations(t *testing.T) { - vm := executeMako(` - // Basic arithmetic - add_result = 5 + 10; - sub_result = 20 - 5; - mul_result = 4 * 5; - div_result = 20 / 4; - - // Compound expressions - expr1 = (5 + 10) * 2; - expr2 = 5 + 10 * 2; - expr3 = -5 + 10; - `) - - // Verify arithmetic results - add_result, found := vm.GetGlobal("add_result") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, add_result.Type) - assert.Equal(t, 15.0, add_result.Data.(float64)) - - sub_result, found := vm.GetGlobal("sub_result") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, sub_result.Type) - assert.Equal(t, 15.0, sub_result.Data.(float64)) - - mul_result, found := vm.GetGlobal("mul_result") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, mul_result.Type) - assert.Equal(t, 20.0, mul_result.Data.(float64)) - - div_result, found := vm.GetGlobal("div_result") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, div_result.Type) - assert.Equal(t, 5.0, div_result.Data.(float64)) - - // Verify compound expressions - expr1, found := vm.GetGlobal("expr1") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, expr1.Type) - assert.Equal(t, 30.0, expr1.Data.(float64)) - - expr2, found := vm.GetGlobal("expr2") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, expr2.Type) - assert.Equal(t, 25.0, expr2.Data.(float64)) - - expr3, found := vm.GetGlobal("expr3") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, expr3.Type) - assert.Equal(t, 5.0, expr3.Data.(float64)) -} - -func TestComparisonOperations(t *testing.T) { - vm := executeMako(` - // Basic comparisons - eq_result = 5 == 5; - neq_result = 5 != 10; - lt_result = 5 < 10; - gt_result = 10 > 5; - lte_result = 5 <= 5; - gte_result = 5 >= 5; - - // Compound comparisons - comp1 = 5 + 5 == 10; - comp2 = 5 * 2 != 15; - `) - - // Verify comparison results - eq_result, found := vm.GetGlobal("eq_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, eq_result.Type) - assert.Equal(t, true, eq_result.Data.(bool)) - - neq_result, found := vm.GetGlobal("neq_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, neq_result.Type) - assert.Equal(t, true, neq_result.Data.(bool)) - - lt_result, found := vm.GetGlobal("lt_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, lt_result.Type) - assert.Equal(t, true, lt_result.Data.(bool)) - - gt_result, found := vm.GetGlobal("gt_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, gt_result.Type) - assert.Equal(t, true, gt_result.Data.(bool)) - - lte_result, found := vm.GetGlobal("lte_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, lte_result.Type) - assert.Equal(t, true, lte_result.Data.(bool)) - - gte_result, found := vm.GetGlobal("gte_result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, gte_result.Type) - assert.Equal(t, true, gte_result.Data.(bool)) - - // Verify compound comparison results - comp1, found := vm.GetGlobal("comp1") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, comp1.Type) - assert.Equal(t, true, comp1.Data.(bool)) - - comp2, found := vm.GetGlobal("comp2") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, comp2.Type) - assert.Equal(t, true, comp2.Data.(bool)) -} - -func TestLogicalOperations(t *testing.T) { - vm := executeMako(` - // Logical operators - and_tt = true and true; - and_tf = true and false; - or_tf = true or false; - or_ff = false or false; - not_t = not true; - not_f = not false; - - // Short-circuit evaluation - x = 5; - true_or_effect = true or (x = 10); // Should not change x - x_after_or = x; // Should still be 5 - - false_and_effect = false and (x = 15); // Should not change x - x_after_and = x; // Should still be 5 - `) - - // Verify logical operation results - and_tt, found := vm.GetGlobal("and_tt") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, and_tt.Type) - assert.Equal(t, true, and_tt.Data.(bool)) - - and_tf, found := vm.GetGlobal("and_tf") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, and_tf.Type) - assert.Equal(t, false, and_tf.Data.(bool)) - - or_tf, found := vm.GetGlobal("or_tf") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, or_tf.Type) - assert.Equal(t, true, or_tf.Data.(bool)) - - or_ff, found := vm.GetGlobal("or_ff") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, or_ff.Type) - assert.Equal(t, false, or_ff.Data.(bool)) - - not_t, found := vm.GetGlobal("not_t") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, not_t.Type) - assert.Equal(t, false, not_t.Data.(bool)) - - not_f, found := vm.GetGlobal("not_f") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, not_f.Type) - assert.Equal(t, true, not_f.Data.(bool)) - - // Verify short-circuit behavior - x_after_or, found := vm.GetGlobal("x_after_or") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, x_after_or.Type) - assert.Equal(t, 5.0, x_after_or.Data.(float64)) - - x_after_and, found := vm.GetGlobal("x_after_and") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, x_after_and.Type) - assert.Equal(t, 5.0, x_after_and.Data.(float64)) -} - -func TestComplexProgram(t *testing.T) { - vm := executeMako(` - // Define a table to store data - data = { - users = { - admin = { - name = "Admin User", - access = "full", - active = true - }, - guest = { - name = "Guest User", - access = "limited", - active = true - }, - blocked = { - name = "Blocked User", - access = "none", - active = false - } - }, - settings = { - theme = "dark", - notifications = true, - language = "en" - } - }; - - // Get the user type from input (simulated) - userType = "admin"; - - // Check access and print message - if data["users"][userType]["active"] then - access = data["users"][userType]["access"]; - - if access == "full" then - message = "Welcome, Administrator!"; - elseif access == "limited" then - message = "Welcome, Guest!"; - else - message = "Access denied."; - end - else - message = "User account is inactive."; - end - - // Update a setting - data["settings"]["theme"] = "light"; - theme = data["settings"]["theme"]; - - // Toggle notifications - data["settings"]["notifications"] = not data["settings"]["notifications"]; - notif_status = data["settings"]["notifications"]; - `) - - // Verify complex program results - message, found := vm.GetGlobal("message") - assert.True(t, found) - assert.Equal(t, types.TypeString, message.Type) - assert.Equal(t, "Welcome, Administrator!", message.Data.(string)) - - theme, found := vm.GetGlobal("theme") - assert.True(t, found) - assert.Equal(t, types.TypeString, theme.Type) - assert.Equal(t, "light", theme.Data.(string)) - - notif_status, found := vm.GetGlobal("notif_status") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, notif_status.Type) - assert.Equal(t, false, notif_status.Data.(bool)) -} diff --git a/tests/lexer_test.go b/tests/lexer_test.go deleted file mode 100644 index 504ca2d..0000000 --- a/tests/lexer_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/lexer" -) - -func TestLexerSimpleTokens(t *testing.T) { - input := `= + - * / ( ) { } [ ] , ; "hello" 123 if then else end true false` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenEqual, "="}, - {lexer.TokenPlus, "+"}, - {lexer.TokenMinus, "-"}, - {lexer.TokenStar, "*"}, - {lexer.TokenSlash, "/"}, - {lexer.TokenLeftParen, "("}, - {lexer.TokenRightParen, ")"}, - {lexer.TokenLeftBrace, "{"}, - {lexer.TokenRightBrace, "}"}, - {lexer.TokenLeftBracket, "["}, - {lexer.TokenRightBracket, "]"}, - {lexer.TokenComma, ","}, - {lexer.TokenSemicolon, ";"}, - {lexer.TokenString, "hello"}, - {lexer.TokenNumber, "123"}, - {lexer.TokenIf, "if"}, - {lexer.TokenThen, "then"}, - {lexer.TokenElse, "else"}, - {lexer.TokenEnd, "end"}, - {lexer.TokenTrue, "true"}, - {lexer.TokenFalse, "false"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerCompoundTokens(t *testing.T) { - input := `== != < > <= >= and or not elseif` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenEqualEqual, "=="}, - {lexer.TokenNotEqual, "!="}, - {lexer.TokenLessThan, "<"}, - {lexer.TokenGreaterThan, ">"}, - {lexer.TokenLessEqual, "<="}, - {lexer.TokenGreaterEqual, ">="}, - {lexer.TokenAnd, "and"}, - {lexer.TokenOr, "or"}, - {lexer.TokenNot, "not"}, - {lexer.TokenElseIf, "elseif"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerIdentifiersAndKeywords(t *testing.T) { - input := `variable echo if then else end true false and or not x y_1 _var UPPERCASE` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenIdentifier, "variable"}, - {lexer.TokenEcho, "echo"}, - {lexer.TokenIf, "if"}, - {lexer.TokenThen, "then"}, - {lexer.TokenElse, "else"}, - {lexer.TokenEnd, "end"}, - {lexer.TokenTrue, "true"}, - {lexer.TokenFalse, "false"}, - {lexer.TokenAnd, "and"}, - {lexer.TokenOr, "or"}, - {lexer.TokenNot, "not"}, - {lexer.TokenIdentifier, "x"}, - {lexer.TokenIdentifier, "y_1"}, - {lexer.TokenIdentifier, "_var"}, - {lexer.TokenIdentifier, "UPPERCASE"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerNumbers(t *testing.T) { - input := `0 123 999999` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenNumber, "0"}, - {lexer.TokenNumber, "123"}, - {lexer.TokenNumber, "999999"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerStrings(t *testing.T) { - input := `"" "hello" "one two three" "special chars: !@#$%^&*()"` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenString, ""}, - {lexer.TokenString, "hello"}, - {lexer.TokenString, "one two three"}, - {lexer.TokenString, "special chars: !@#$%^&*()"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerComments(t *testing.T) { - input := `x = 5 // This is a comment - y = 10 // Another comment` - - lex := lexer.New(input) - - expected := []struct { - expectedType lexer.TokenType - expectedValue string - }{ - {lexer.TokenIdentifier, "x"}, - {lexer.TokenEqual, "="}, - {lexer.TokenNumber, "5"}, - {lexer.TokenIdentifier, "y"}, - {lexer.TokenEqual, "="}, - {lexer.TokenNumber, "10"}, - {lexer.TokenEOF, ""}, - } - - for _, exp := range expected { - tok := lex.NextToken() - assert.Equal(t, exp.expectedType, tok.Type) - assert.Equal(t, exp.expectedValue, tok.Value) - } -} - -func TestLexerCompositeCode(t *testing.T) { - input := ` - // Sample Mako code - x = 10; - y = 20; - if x < y then - echo "x is less than y"; - else - echo "x is not less than y"; - end - - // Table example - table = { - name = "John", - age = 30, - isActive = true - }; - - echo table["name"]; - ` - - lex := lexer.New(input) - - // Spot check some tokens to avoid an overly verbose test - assert.Equal(t, lexer.TokenIdentifier, lex.NextToken().Type) // x - assert.Equal(t, lexer.TokenEqual, lex.NextToken().Type) // = - assert.Equal(t, lexer.TokenNumber, lex.NextToken().Type) // 10 - assert.Equal(t, lexer.TokenSemicolon, lex.NextToken().Type) // ; - - // Skip ahead to check table creation - for i := 0; i < 13; i++ { - lex.NextToken() - } - - assert.Equal(t, lexer.TokenIdentifier, lex.NextToken().Type) // table - assert.Equal(t, lexer.TokenEqual, lex.NextToken().Type) // = - assert.Equal(t, lexer.TokenLeftBrace, lex.NextToken().Type) // { - - // Check echo table["name"] - for i := 0; i < 15; i++ { - lex.NextToken() - } - - tok := lex.NextToken() - assert.Equal(t, lexer.TokenEcho, tok.Type) - tok = lex.NextToken() - assert.Equal(t, lexer.TokenIdentifier, tok.Type) - assert.Equal(t, "table", tok.Value) - tok = lex.NextToken() - assert.Equal(t, lexer.TokenLeftBracket, tok.Type) - tok = lex.NextToken() - assert.Equal(t, lexer.TokenString, tok.Type) - assert.Equal(t, "name", tok.Value) -} diff --git a/tests/parser_test.go b/tests/parser_test.go deleted file mode 100644 index e2174dd..0000000 --- a/tests/parser_test.go +++ /dev/null @@ -1,345 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/lexer" - "git.sharkk.net/Sharkk/Mako/parser" -) - -func TestParseVariableStatement(t *testing.T) { - input := `x = 5;` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - stmt, ok := program.Statements[0].(*parser.VariableStatement) - assert.True(t, ok) - assert.Equal(t, "x", stmt.Name.Value) - - // Test the expression value - numLit, ok := stmt.Value.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, 5.0, numLit.Value) -} - -func TestParseEchoStatement(t *testing.T) { - input := `echo "hello";` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - stmt, ok := program.Statements[0].(*parser.EchoStatement) - assert.True(t, ok) - - strLit, ok := stmt.Value.(*parser.StringLiteral) - assert.True(t, ok) - assert.Equal(t, "hello", strLit.Value) -} - -func TestParseTableLiteral(t *testing.T) { - input := `table = { name = "John", age = 30 };` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - stmt, ok := program.Statements[0].(*parser.VariableStatement) - assert.True(t, ok) - assert.Equal(t, "table", stmt.Name.Value) - - tableLit, ok := stmt.Value.(*parser.TableLiteral) - assert.True(t, ok) - assert.Equal(t, 2, len(tableLit.Pairs)) - - // Check that the table has the expected keys and values - // We need to find the entries in the pairs map - foundName := false - foundAge := false - - for key, value := range tableLit.Pairs { - if ident, ok := key.(*parser.Identifier); ok && ident.Value == "name" { - foundName = true - strLit, ok := value.(*parser.StringLiteral) - assert.True(t, ok) - assert.Equal(t, "John", strLit.Value) - } - - if ident, ok := key.(*parser.Identifier); ok && ident.Value == "age" { - foundAge = true - numLit, ok := value.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, 30.0, numLit.Value) - } - } - - assert.True(t, foundName) - assert.True(t, foundAge) -} - -func TestParseIndexExpression(t *testing.T) { - input := `table["key"];` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - indexExpr, ok := exprStmt.Expression.(*parser.IndexExpression) - assert.True(t, ok) - - ident, ok := indexExpr.Left.(*parser.Identifier) - assert.True(t, ok) - assert.Equal(t, "table", ident.Value) - - strLit, ok := indexExpr.Index.(*parser.StringLiteral) - assert.True(t, ok) - assert.Equal(t, "key", strLit.Value) -} - -func TestParseInfixExpression(t *testing.T) { - tests := []struct { - input string - leftValue float64 - operator string - rightValue float64 - }{ - {"5 + 5;", 5, "+", 5}, - {"5 - 5;", 5, "-", 5}, - {"5 * 5;", 5, "*", 5}, - {"5 / 5;", 5, "/", 5}, - {"5 < 5;", 5, "<", 5}, - {"5 > 5;", 5, ">", 5}, - {"5 == 5;", 5, "==", 5}, - {"5 != 5;", 5, "!=", 5}, - {"5 <= 5;", 5, "<=", 5}, - {"5 >= 5;", 5, ">=", 5}, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - infixExpr, ok := exprStmt.Expression.(*parser.InfixExpression) - assert.True(t, ok) - - leftLit, ok := infixExpr.Left.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, tt.leftValue, leftLit.Value) - - assert.Equal(t, tt.operator, infixExpr.Operator) - - rightLit, ok := infixExpr.Right.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, tt.rightValue, rightLit.Value) - } -} - -func TestParsePrefixExpression(t *testing.T) { - tests := []struct { - input string - operator string - value float64 - }{ - {"-5;", "-", 5}, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - prefixExpr, ok := exprStmt.Expression.(*parser.PrefixExpression) - assert.True(t, ok) - - assert.Equal(t, tt.operator, prefixExpr.Operator) - - rightLit, ok := prefixExpr.Right.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, tt.value, rightLit.Value) - } -} - -func TestParseBooleanLiteral(t *testing.T) { - tests := []struct { - input string - value bool - }{ - {"true;", true}, - {"false;", false}, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - boolLit, ok := exprStmt.Expression.(*parser.BooleanLiteral) - assert.True(t, ok) - assert.Equal(t, tt.value, boolLit.Value) - } -} - -func TestParseIfExpression(t *testing.T) { - input := `if x < 10 then - echo "x is less than 10"; - else - echo "x is not less than 10"; - end` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - ifExpr, ok := exprStmt.Expression.(*parser.IfExpression) - assert.True(t, ok) - - // Check condition - condition, ok := ifExpr.Condition.(*parser.InfixExpression) - assert.True(t, ok) - assert.Equal(t, "<", condition.Operator) - - // Check consequence - assert.Equal(t, 1, len(ifExpr.Consequence.Statements)) - - consEchoStmt, ok := ifExpr.Consequence.Statements[0].(*parser.EchoStatement) - assert.True(t, ok) - - consStrLit, ok := consEchoStmt.Value.(*parser.StringLiteral) - assert.True(t, ok) - assert.Equal(t, "x is less than 10", consStrLit.Value) - - // Check alternative - assert.NotNil(t, ifExpr.Alternative) - assert.Equal(t, 1, len(ifExpr.Alternative.Statements)) - - altEchoStmt, ok := ifExpr.Alternative.Statements[0].(*parser.EchoStatement) - assert.True(t, ok) - - altStrLit, ok := altEchoStmt.Value.(*parser.StringLiteral) - assert.True(t, ok) - assert.Equal(t, "x is not less than 10", altStrLit.Value) -} - -func TestParseElseIfExpression(t *testing.T) { - input := `if x < 10 then - echo "x is less than 10"; - elseif x < 20 then - echo "x is less than 20"; - else - echo "x is not less than 20"; - end` - - lex := lexer.New(input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - ifExpr, ok := exprStmt.Expression.(*parser.IfExpression) - assert.True(t, ok) - - // Check that we have an alternative block - assert.NotNil(t, ifExpr.Alternative) - assert.Equal(t, 1, len(ifExpr.Alternative.Statements)) - - // The alternative should contain another IfExpression (the elseif) - altExprStmt, ok := ifExpr.Alternative.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - nestedIfExpr, ok := altExprStmt.Expression.(*parser.IfExpression) - assert.True(t, ok) - - // Check nested if condition - condition, ok := nestedIfExpr.Condition.(*parser.InfixExpression) - assert.True(t, ok) - assert.Equal(t, "<", condition.Operator) - - rightLit, ok := condition.Right.(*parser.NumberLiteral) - assert.True(t, ok) - assert.Equal(t, 20.0, rightLit.Value) - - // Check that the nested if has an alternative (the else) - assert.NotNil(t, nestedIfExpr.Alternative) -} - -func TestParseLogicalOperators(t *testing.T) { - tests := []struct { - input string - operator string - }{ - {"true and false;", "and"}, - {"true or false;", "or"}, - {"not true;", "not"}, - } - - for _, tt := range tests { - lex := lexer.New(tt.input) - p := parser.New(lex) - program := p.ParseProgram() - - assert.Equal(t, 0, len(p.Errors())) - assert.Equal(t, 1, len(program.Statements)) - - exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement) - assert.True(t, ok) - - if tt.operator == "not" { - // Test prefix NOT - prefixExpr, ok := exprStmt.Expression.(*parser.PrefixExpression) - assert.True(t, ok) - assert.Equal(t, tt.operator, prefixExpr.Operator) - } else { - // Test infix AND/OR - infixExpr, ok := exprStmt.Expression.(*parser.InfixExpression) - assert.True(t, ok) - assert.Equal(t, tt.operator, infixExpr.Operator) - } - } -} diff --git a/tests/vm_test.go b/tests/vm_test.go deleted file mode 100644 index 0af00b6..0000000 --- a/tests/vm_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package tests - -import ( - "testing" - - assert "git.sharkk.net/Go/Assert" - "git.sharkk.net/Sharkk/Mako/types" - "git.sharkk.net/Sharkk/Mako/vm" -) - -func TestVMPushPop(t *testing.T) { - vm := vm.New() - - // Create basic bytecode that pushes constants and then pops - bytecode := &types.Bytecode{ - Constants: []any{5.0, "hello", true}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push "hello" - {Opcode: types.OpConstant, Operand: 2}, // Push true - {Opcode: types.OpPop, Operand: 0}, // Pop true - {Opcode: types.OpPop, Operand: 0}, // Pop "hello" - {Opcode: types.OpPop, Operand: 0}, // Pop 5.0 - }, - } - - // Run the VM - vm.Run(bytecode) - - // Check stack is empty - assert.Equal(t, 0, len(vm.CurrentStack())) -} - -func TestVMArithmetic(t *testing.T) { - tests := []struct { - constants []any - instructions []types.Instruction - expected float64 - }{ - { - // 5 + 10 - []any{5.0, 10.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 10.0 - {Opcode: types.OpAdd, Operand: 0}, // Add - }, - 15.0, - }, - { - // 5 - 10 - []any{5.0, 10.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 10.0 - {Opcode: types.OpSubtract, Operand: 0}, // Subtract - }, - -5.0, - }, - { - // 5 * 10 - []any{5.0, 10.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 10.0 - {Opcode: types.OpMultiply, Operand: 0}, // Multiply - }, - 50.0, - }, - { - // 10 / 5 - []any{10.0, 5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 10.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpDivide, Operand: 0}, // Divide - }, - 2.0, - }, - { - // -5 - []any{5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpNegate, Operand: 0}, // Negate - }, - -5.0, - }, - } - - for _, tt := range tests { - vm := vm.New() - bytecode := &types.Bytecode{ - Constants: tt.constants, - Instructions: tt.instructions, - } - - vm.Run(bytecode) - - // Check the result on the stack - stack := vm.CurrentStack() - assert.Equal(t, 1, len(stack)) - assert.Equal(t, types.TypeNumber, stack[0].Type) - assert.Equal(t, tt.expected, stack[0].Data.(float64)) - } -} - -func TestVMComparisons(t *testing.T) { - tests := []struct { - constants []any - instructions []types.Instruction - expected bool - }{ - { - // 5 == 5 - []any{5.0, 5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpEqual, Operand: 0}, // Equal - }, - true, - }, - { - // 5 != 10 - []any{5.0, 10.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 10.0 - {Opcode: types.OpNotEqual, Operand: 0}, // Not Equal - }, - true, - }, - { - // 5 < 10 - []any{5.0, 10.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 10.0 - {Opcode: types.OpLessThan, Operand: 0}, // Less Than - }, - true, - }, - { - // 10 > 5 - []any{10.0, 5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 10.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpGreaterThan, Operand: 0}, // Greater Than - }, - true, - }, - { - // 5 <= 5 - []any{5.0, 5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpLessEqual, Operand: 0}, // Less Than or Equal - }, - true, - }, - { - // 5 >= 5 - []any{5.0, 5.0}, - []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpGreaterEqual, Operand: 0}, // Greater Than or Equal - }, - true, - }, - } - - for _, tt := range tests { - vm := vm.New() - bytecode := &types.Bytecode{ - Constants: tt.constants, - Instructions: tt.instructions, - } - - vm.Run(bytecode) - - // Check the result on the stack - stack := vm.CurrentStack() - assert.Equal(t, 1, len(stack)) - assert.Equal(t, types.TypeBoolean, stack[0].Type) - assert.Equal(t, tt.expected, stack[0].Data.(bool)) - } -} - -func TestVMTableOperations(t *testing.T) { - // Create bytecode for: table = {}; table["key"] = "value"; result = table["key"] - bytecode := &types.Bytecode{ - Constants: []any{"table", "key", "value", "result"}, - Instructions: []types.Instruction{ - {Opcode: types.OpNewTable, Operand: 0}, // Create new table - {Opcode: types.OpSetGlobal, Operand: 0}, // Store in global "table" - {Opcode: types.OpGetGlobal, Operand: 0}, // Push table onto stack - {Opcode: types.OpConstant, Operand: 1}, // Push "key" - {Opcode: types.OpConstant, Operand: 2}, // Push "value" - {Opcode: types.OpSetIndex, Operand: 0}, // Set table["key"] = "value" - {Opcode: types.OpPop, Operand: 0}, // Pop the result of SetIndex - {Opcode: types.OpGetGlobal, Operand: 0}, // Push table onto stack again - {Opcode: types.OpConstant, Operand: 1}, // Push "key" - {Opcode: types.OpGetIndex, Operand: 0}, // Get table["key"] - {Opcode: types.OpSetGlobal, Operand: 3}, // Store in global "result" - }, - } - - vm := vm.New() - vm.Run(bytecode) - - // Now check if "result" contains "value" - result, found := vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeString, result.Type) - assert.Equal(t, "value", result.Data.(string)) -} - -func TestVMConditionalJumps(t *testing.T) { - // Create bytecode for: if true then result = "true" else result = "false" end - bytecode := &types.Bytecode{ - Constants: []any{true, "result", "true", "false"}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push true - {Opcode: types.OpJumpIfFalse, Operand: 6}, // Jump to else branch if false - {Opcode: types.OpConstant, Operand: 2}, // Push "true" - {Opcode: types.OpSetGlobal, Operand: 1}, // Set result = "true" - {Opcode: types.OpJump, Operand: 8}, // Jump to end - {Opcode: types.OpConstant, Operand: 3}, // Push "false" - {Opcode: types.OpSetGlobal, Operand: 1}, // Set result = "false" - }, - } - - vm := vm.New() - vm.Run(bytecode) - - // Check result == "true" - result, found := vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeString, result.Type) - assert.Equal(t, "true", result.Data.(string)) -} - -func TestVMScopes(t *testing.T) { - // Create bytecode for: - // x = 5 - // { - // x = 10 - // y = 20 - // } - // result = x - bytecode := &types.Bytecode{ - Constants: []any{5.0, "x", 10.0, 20.0, "y", "result"}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push 5.0 - {Opcode: types.OpSetGlobal, Operand: 1}, // Set global x = 5 - {Opcode: types.OpEnterScope, Operand: 0}, // Enter new scope - {Opcode: types.OpConstant, Operand: 2}, // Push 10.0 - {Opcode: types.OpSetLocal, Operand: 1}, // Set local x = 10 - {Opcode: types.OpConstant, Operand: 3}, // Push 20.0 - {Opcode: types.OpSetLocal, Operand: 4}, // Set local y = 20 - {Opcode: types.OpExitScope, Operand: 0}, // Exit scope - {Opcode: types.OpGetGlobal, Operand: 1}, // Get global x - {Opcode: types.OpSetGlobal, Operand: 5}, // Set result = x - }, - } - - vm := vm.New() - vm.Run(bytecode) - - // Check result == 5.0 (global x, not the shadowed local x) - result, found := vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeNumber, result.Type) - assert.Equal(t, 5.0, result.Data.(float64)) -} - -func TestVMLogicalOperators(t *testing.T) { - // Test NOT operator - notBytecode := &types.Bytecode{ - Constants: []any{true, "result"}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 0}, // Push true - {Opcode: types.OpNot, Operand: 0}, // NOT operation - {Opcode: types.OpSetGlobal, Operand: 1}, // Set result - }, - } - - vm := vm.New() - vm.Run(notBytecode) - - // Check result == false - result, found := vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result.Type) - assert.Equal(t, false, result.Data.(bool)) - - // Test AND with short-circuit - andBytecode := &types.Bytecode{ - Constants: []any{false, 5.0, "x", true, "result"}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpSetGlobal, Operand: 2}, // Set x = 5.0 - {Opcode: types.OpConstant, Operand: 0}, // Push false - {Opcode: types.OpDup, Operand: 0}, // Duplicate false for condition - {Opcode: types.OpJumpIfFalse, Operand: 9}, // Jump if false (short-circuit) - {Opcode: types.OpPop, Operand: 0}, // Pop the duplicate - {Opcode: types.OpGetGlobal, Operand: 2}, // Get x - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpAdd, Operand: 0}, // Add x + 5 (should be skipped) - {Opcode: types.OpSetGlobal, Operand: 4}, // Set result (this is where we jump to) - }, - } - - vm = vm.Reset() - vm.Run(andBytecode) - - // Check result == false (the first operand, since AND short-circuits) - result, found = vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result.Type) - assert.Equal(t, false, result.Data.(bool)) - - // Test OR with short-circuit - orBytecode := &types.Bytecode{ - Constants: []any{true, 5.0, "x", false, "result"}, - Instructions: []types.Instruction{ - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 - {Opcode: types.OpSetGlobal, Operand: 2}, // Set x = 5.0 - {Opcode: types.OpConstant, Operand: 0}, // Push true - {Opcode: types.OpDup, Operand: 0}, // Duplicate true for condition - {Opcode: types.OpJumpIfFalse, Operand: 7}, // Jump if false (not taken) - {Opcode: types.OpConstant, Operand: 0}, // Push true again - {Opcode: types.OpSetGlobal, Operand: 4}, // Set result (with actual value) - {Opcode: types.OpJump, Operand: 11}, // Jump to end (short-circuit) - {Opcode: types.OpPop, Operand: 0}, // Pop the duplicate (not executed) - {Opcode: types.OpGetGlobal, Operand: 2}, // Get x (not executed) - {Opcode: types.OpConstant, Operand: 1}, // Push 5.0 (not executed) - {Opcode: types.OpAdd, Operand: 0}, // Add x + 5 (not executed) - }, - } - - vm = vm.Reset() - vm.Run(orBytecode) - - // Check result == true (the first operand, since OR short-circuits) - result, found = vm.GetGlobal("result") - assert.True(t, found) - assert.Equal(t, types.TypeBoolean, result.Type) - assert.Equal(t, true, result.Data.(bool)) -}