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