439 lines
11 KiB
Go
439 lines
11 KiB
Go
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))
|
|
}
|