rewrite, implement types
This commit is contained in:
parent
29707b4b02
commit
f1ac9b1657
43
README.md
43
README.md
@ -1,3 +1,44 @@
|
||||
# Mako
|
||||
|
||||
Scripting language!
|
||||
Lightweight, efficient Lua-like scripting language with a Go backend!
|
||||
|
||||
the go mod is `git.sharkk.net/Sharkk/Mako`
|
||||
|
||||
### Lexing & Parsing
|
||||
|
||||
Recursive descent + Pratt parsing.
|
||||
Scanner should move through tokens with virtually no allocations.
|
||||
Easy-to-consume AST for bytecode compilation.
|
||||
|
||||
### VM
|
||||
|
||||
Stack-based VM, register-based optimizations where it makes sense for performance, and a very minimal bytecode
|
||||
format to preserve space.
|
||||
|
||||
### Memory Management
|
||||
|
||||
Go's GC is robust and can be used for most needs; the internals can pool commonly used structures to reduce pressure.
|
||||
|
||||
|
||||
## Syntax
|
||||
|
||||
Lua-like, but with more minimalism and eventually some syntactic sugars.
|
||||
|
||||
```
|
||||
// C style comments
|
||||
// No local for variable definitions; the local behavior should be implicit
|
||||
x = "foo"
|
||||
y = "bar"
|
||||
fn add(a, b)
|
||||
return a + b
|
||||
end
|
||||
echo add(x, y) // Outputs: foobar
|
||||
|
||||
if expression and expression2 then
|
||||
res = add(1, 2)
|
||||
elseif otherExpression() then
|
||||
// do other stuff
|
||||
else
|
||||
// final thing
|
||||
end
|
||||
```
|
||||
|
@ -1,565 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.sharkk.net/Sharkk/Mako/parser"
|
||||
"git.sharkk.net/Sharkk/Mako/types"
|
||||
)
|
||||
|
||||
// Compile converts AST to bytecode
|
||||
func Compile(program *parser.Program) *types.Bytecode {
|
||||
c := &compiler{
|
||||
constants: []any{},
|
||||
instructions: []types.Instruction{},
|
||||
scopes: []scope{},
|
||||
currentFunction: nil,
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
c.exitScope()
|
||||
|
||||
return &types.Bytecode{
|
||||
Constants: c.constants,
|
||||
Instructions: c.instructions,
|
||||
}
|
||||
}
|
||||
|
||||
type scope struct {
|
||||
variables map[string]bool
|
||||
upvalues map[string]int
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
constants []any
|
||||
instructions []types.Instruction
|
||||
scopes []scope
|
||||
currentFunction *functionCompiler
|
||||
}
|
||||
|
||||
type functionCompiler struct {
|
||||
constants []any
|
||||
instructions []types.Instruction
|
||||
numParams int
|
||||
upvalues []upvalueInfo
|
||||
}
|
||||
|
||||
type upvalueInfo struct {
|
||||
index int // Index in the upvalue list
|
||||
isLocal bool // Whether this is a local variable or an upvalue from an outer scope
|
||||
capturedFrom int // The scope level where this variable was captured from
|
||||
}
|
||||
|
||||
func (c *compiler) enterScope() {
|
||||
c.scopes = append(c.scopes, scope{
|
||||
variables: make(map[string]bool),
|
||||
upvalues: make(map[string]int),
|
||||
})
|
||||
c.emit(types.OpEnterScope, 0)
|
||||
}
|
||||
|
||||
func (c *compiler) exitScope() {
|
||||
c.scopes = c.scopes[:len(c.scopes)-1]
|
||||
c.emit(types.OpExitScope, 0)
|
||||
}
|
||||
|
||||
func (c *compiler) declareVariable(name string) {
|
||||
if len(c.scopes) > 0 {
|
||||
c.scopes[len(c.scopes)-1].variables[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) isLocalVariable(name string) bool {
|
||||
for i := len(c.scopes) - 1; i >= 0; i-- {
|
||||
if _, ok := c.scopes[i].variables[name]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *compiler) compileStatement(stmt parser.Statement) {
|
||||
if stmt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch s := stmt.(type) {
|
||||
case *parser.VariableStatement:
|
||||
c.compileExpression(s.Value)
|
||||
nameIndex := c.addConstant(s.Name.Value)
|
||||
|
||||
// Use SetGlobal for top-level variables to persist between REPL lines
|
||||
if len(c.scopes) <= 1 {
|
||||
c.emit(types.OpSetGlobal, nameIndex)
|
||||
} else {
|
||||
c.declareVariable(s.Name.Value)
|
||||
c.emit(types.OpSetLocal, nameIndex)
|
||||
}
|
||||
|
||||
case *parser.IndexAssignmentStatement:
|
||||
c.compileExpression(s.Left)
|
||||
c.compileExpression(s.Index)
|
||||
c.compileExpression(s.Value)
|
||||
c.emit(types.OpSetIndex, 0)
|
||||
|
||||
case *parser.EchoStatement:
|
||||
c.compileExpression(s.Value)
|
||||
c.emit(types.OpEcho, 0)
|
||||
|
||||
case *parser.ReturnStatement:
|
||||
if s.Value != nil {
|
||||
c.compileExpression(s.Value)
|
||||
} else {
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
c.emit(types.OpReturn, 0)
|
||||
|
||||
case *parser.FunctionStatement:
|
||||
// Use the dedicated function for function statements
|
||||
c.compileFunctionDeclaration(s)
|
||||
|
||||
// BlockStatement now should only be used for keyword blocks like if-then-else-end
|
||||
case *parser.BlockStatement:
|
||||
for _, blockStmt := range s.Statements {
|
||||
c.compileStatement(blockStmt)
|
||||
}
|
||||
|
||||
case *parser.ExpressionStatement:
|
||||
c.compileExpression(s.Expression)
|
||||
// Pop the value since we're not using it
|
||||
c.emit(types.OpPop, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileExpression(expr parser.Expression) {
|
||||
switch e := expr.(type) {
|
||||
case *parser.StringLiteral:
|
||||
constIndex := c.addConstant(e.Value)
|
||||
c.emit(types.OpConstant, constIndex)
|
||||
|
||||
case *parser.NumberLiteral:
|
||||
constIndex := c.addConstant(e.Value)
|
||||
c.emit(types.OpConstant, constIndex)
|
||||
|
||||
case *parser.BooleanLiteral:
|
||||
constIndex := c.addConstant(e.Value)
|
||||
c.emit(types.OpConstant, constIndex)
|
||||
|
||||
case *parser.NilLiteral:
|
||||
constIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, constIndex)
|
||||
|
||||
case *parser.Identifier:
|
||||
nameIndex := c.addConstant(e.Value)
|
||||
|
||||
// Check if it's a local variable first
|
||||
if c.isLocalVariable(e.Value) {
|
||||
c.emit(types.OpGetLocal, nameIndex)
|
||||
} else {
|
||||
// Otherwise treat as global
|
||||
c.emit(types.OpGetGlobal, nameIndex)
|
||||
}
|
||||
|
||||
case *parser.TableLiteral:
|
||||
c.emit(types.OpNewTable, 0)
|
||||
|
||||
for key, value := range e.Pairs {
|
||||
c.emit(types.OpDup, 0)
|
||||
|
||||
// Special handling for identifier keys in tables
|
||||
if ident, ok := key.(*parser.Identifier); ok {
|
||||
// Treat identifiers as string literals in table keys
|
||||
strIndex := c.addConstant(ident.Value)
|
||||
c.emit(types.OpConstant, strIndex)
|
||||
} else {
|
||||
// For other expressions, compile normally
|
||||
c.compileExpression(key)
|
||||
}
|
||||
|
||||
c.compileExpression(value)
|
||||
c.emit(types.OpSetIndex, 0)
|
||||
c.emit(types.OpPop, 0)
|
||||
}
|
||||
|
||||
case *parser.IndexExpression:
|
||||
c.compileExpression(e.Left)
|
||||
c.compileExpression(e.Index)
|
||||
c.emit(types.OpGetIndex, 0)
|
||||
|
||||
case *parser.FunctionLiteral:
|
||||
c.compileFunctionLiteral(e)
|
||||
|
||||
case *parser.CallExpression:
|
||||
// Compile the function expression first
|
||||
c.compileExpression(e.Function)
|
||||
|
||||
// Then compile the arguments
|
||||
for _, arg := range e.Arguments {
|
||||
c.compileExpression(arg)
|
||||
}
|
||||
|
||||
// Emit the call instruction with the number of arguments
|
||||
c.emit(types.OpCall, len(e.Arguments))
|
||||
|
||||
// Arithmetic expressions
|
||||
case *parser.InfixExpression:
|
||||
switch e.Operator {
|
||||
case "and":
|
||||
// Compile left operand
|
||||
c.compileExpression(e.Left)
|
||||
|
||||
// Duplicate to check condition
|
||||
c.emit(types.OpDup, 0)
|
||||
|
||||
// Jump if false (short-circuit)
|
||||
jumpFalsePos := len(c.instructions)
|
||||
c.emit(types.OpJumpIfFalse, 0) // Will backpatch
|
||||
|
||||
// Pop the duplicate since we'll replace it
|
||||
c.emit(types.OpPop, 0)
|
||||
|
||||
// Compile right operand
|
||||
c.compileExpression(e.Right)
|
||||
|
||||
// Jump target for short-circuit
|
||||
endPos := len(c.instructions)
|
||||
c.instructions[jumpFalsePos].Operand = endPos
|
||||
|
||||
case "or":
|
||||
// Compile left operand
|
||||
c.compileExpression(e.Left)
|
||||
|
||||
// Duplicate to check condition
|
||||
c.emit(types.OpDup, 0)
|
||||
|
||||
// Need to check if it's truthy to short-circuit
|
||||
falseJumpPos := len(c.instructions)
|
||||
c.emit(types.OpJumpIfFalse, 0) // Jump to right eval if false
|
||||
|
||||
// If truthy, jump to end
|
||||
trueJumpPos := len(c.instructions)
|
||||
c.emit(types.OpJump, 0) // Jump to end if true
|
||||
|
||||
// Position for false case
|
||||
falsePos := len(c.instructions)
|
||||
c.instructions[falseJumpPos].Operand = falsePos
|
||||
|
||||
// Pop the duplicate since we'll replace it
|
||||
c.emit(types.OpPop, 0)
|
||||
|
||||
// Compile right operand
|
||||
c.compileExpression(e.Right)
|
||||
|
||||
// End position
|
||||
endPos := len(c.instructions)
|
||||
c.instructions[trueJumpPos].Operand = endPos
|
||||
|
||||
default:
|
||||
// Original infix expression compilation
|
||||
c.compileExpression(e.Left)
|
||||
c.compileExpression(e.Right)
|
||||
|
||||
// Generate the appropriate operation
|
||||
switch e.Operator {
|
||||
case "+":
|
||||
c.emit(types.OpAdd, 0)
|
||||
case "-":
|
||||
c.emit(types.OpSubtract, 0)
|
||||
case "*":
|
||||
c.emit(types.OpMultiply, 0)
|
||||
case "/":
|
||||
c.emit(types.OpDivide, 0)
|
||||
case "==":
|
||||
c.emit(types.OpEqual, 0)
|
||||
case "!=":
|
||||
c.emit(types.OpNotEqual, 0)
|
||||
case "<":
|
||||
c.emit(types.OpLessThan, 0)
|
||||
case ">":
|
||||
c.emit(types.OpGreaterThan, 0)
|
||||
case "<=":
|
||||
c.emit(types.OpLessEqual, 0)
|
||||
case ">=":
|
||||
c.emit(types.OpGreaterEqual, 0)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown infix operator: %s", e.Operator))
|
||||
}
|
||||
}
|
||||
|
||||
case *parser.PrefixExpression:
|
||||
// Compile the operand
|
||||
c.compileExpression(e.Right)
|
||||
|
||||
// Generate the appropriate operation
|
||||
switch e.Operator {
|
||||
case "-":
|
||||
c.emit(types.OpNegate, 0)
|
||||
case "not":
|
||||
c.emit(types.OpNot, 0)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown prefix operator: %s", e.Operator))
|
||||
}
|
||||
|
||||
case *parser.GroupedExpression:
|
||||
// Just compile the inner expression
|
||||
c.compileExpression(e.Expr)
|
||||
|
||||
case *parser.IfExpression:
|
||||
// Compile condition
|
||||
c.compileExpression(e.Condition)
|
||||
|
||||
// Emit jump-if-false with placeholder
|
||||
jumpNotTruePos := len(c.instructions)
|
||||
c.emit(types.OpJumpIfFalse, 0) // Will backpatch
|
||||
|
||||
// Compile consequence (then block)
|
||||
if e.Consequence != nil {
|
||||
lastStmtIndex := len(e.Consequence.Statements) - 1
|
||||
for i, stmt := range e.Consequence.Statements {
|
||||
if i == lastStmtIndex {
|
||||
// For the last statement, we need to ensure it leaves a value
|
||||
if exprStmt, ok := stmt.(*parser.ExpressionStatement); ok {
|
||||
c.compileExpression(exprStmt.Expression)
|
||||
} else {
|
||||
c.compileStatement(stmt)
|
||||
// Push null if not an expression statement
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
} else {
|
||||
c.compileStatement(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// If no statements, push null
|
||||
if len(e.Consequence.Statements) == 0 {
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
} else {
|
||||
// No consequence block, push null
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
|
||||
// Emit jump to skip else part
|
||||
jumpPos := len(c.instructions)
|
||||
c.emit(types.OpJump, 0) // Will backpatch
|
||||
|
||||
// Backpatch jump-if-false to point to else
|
||||
afterConsequencePos := len(c.instructions)
|
||||
c.instructions[jumpNotTruePos].Operand = afterConsequencePos
|
||||
|
||||
// Compile alternative (else block)
|
||||
if e.Alternative != nil {
|
||||
lastStmtIndex := len(e.Alternative.Statements) - 1
|
||||
for i, stmt := range e.Alternative.Statements {
|
||||
if i == lastStmtIndex {
|
||||
// For the last statement, we need to ensure it leaves a value
|
||||
if exprStmt, ok := stmt.(*parser.ExpressionStatement); ok {
|
||||
c.compileExpression(exprStmt.Expression)
|
||||
} else {
|
||||
c.compileStatement(stmt)
|
||||
// Push null if not an expression statement
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
} else {
|
||||
c.compileStatement(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// If no statements, push null
|
||||
if len(e.Alternative.Statements) == 0 {
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
} else {
|
||||
// No else - push null
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
}
|
||||
|
||||
// Backpatch jump to point after else
|
||||
afterAlternativePos := len(c.instructions)
|
||||
c.instructions[jumpPos].Operand = afterAlternativePos
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileFunctionLiteral(fn *parser.FunctionLiteral) {
|
||||
// Save the current compiler state
|
||||
parentCompiler := c.currentFunction
|
||||
|
||||
// Create a new function compiler
|
||||
fnCompiler := &functionCompiler{
|
||||
constants: []any{},
|
||||
instructions: []types.Instruction{},
|
||||
numParams: len(fn.Parameters),
|
||||
upvalues: []upvalueInfo{},
|
||||
}
|
||||
|
||||
c.currentFunction = fnCompiler
|
||||
|
||||
// Enter a new scope for the function body
|
||||
c.enterScope()
|
||||
|
||||
// Declare parameters as local variables
|
||||
for _, param := range fn.Parameters {
|
||||
c.declareVariable(param.Value)
|
||||
paramIndex := c.addConstant(param.Value)
|
||||
c.emit(types.OpSetLocal, paramIndex)
|
||||
}
|
||||
|
||||
// Compile the function body
|
||||
for _, stmt := range fn.Body.Statements {
|
||||
c.compileStatement(stmt)
|
||||
}
|
||||
|
||||
// Ensure the function always returns a value
|
||||
// If the last instruction is not a return, add one
|
||||
if len(fnCompiler.instructions) == 0 ||
|
||||
(len(fnCompiler.instructions) > 0 && fnCompiler.instructions[len(fnCompiler.instructions)-1].Opcode != types.OpReturn) {
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
c.emit(types.OpReturn, 0)
|
||||
}
|
||||
|
||||
// Exit the function scope
|
||||
c.exitScope()
|
||||
|
||||
// Restore the parent compiler
|
||||
c.currentFunction = parentCompiler
|
||||
|
||||
// Extract upvalue information for closure creation
|
||||
upvalueIndexes := make([]int, len(fnCompiler.upvalues))
|
||||
for i, upvalue := range fnCompiler.upvalues {
|
||||
upvalueIndexes[i] = upvalue.index
|
||||
}
|
||||
|
||||
// Create a Function object and add it to the constants
|
||||
function := types.NewFunction(
|
||||
fnCompiler.instructions,
|
||||
fnCompiler.numParams,
|
||||
fnCompiler.constants,
|
||||
upvalueIndexes,
|
||||
)
|
||||
|
||||
functionIndex := c.addConstant(function)
|
||||
c.emit(types.OpFunction, functionIndex)
|
||||
}
|
||||
|
||||
func (c *compiler) addConstant(value any) int {
|
||||
if c.currentFunction != nil {
|
||||
c.currentFunction.constants = append(c.currentFunction.constants, value)
|
||||
return len(c.currentFunction.constants) - 1
|
||||
}
|
||||
|
||||
c.constants = append(c.constants, value)
|
||||
return len(c.constants) - 1
|
||||
}
|
||||
|
||||
func (c *compiler) emit(op types.Opcode, operand int) {
|
||||
instruction := types.Instruction{
|
||||
Opcode: op,
|
||||
Operand: operand,
|
||||
}
|
||||
|
||||
if c.currentFunction != nil {
|
||||
c.currentFunction.instructions = append(c.currentFunction.instructions, instruction)
|
||||
} else {
|
||||
c.instructions = append(c.instructions, instruction)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) compileFunctionDeclaration(fn *parser.FunctionStatement) {
|
||||
// Save the current compiler state
|
||||
parentCompiler := c.currentFunction
|
||||
|
||||
// Create a new function compiler
|
||||
fnCompiler := &functionCompiler{
|
||||
constants: []any{},
|
||||
instructions: []types.Instruction{},
|
||||
numParams: len(fn.Parameters),
|
||||
upvalues: []upvalueInfo{},
|
||||
}
|
||||
|
||||
c.currentFunction = fnCompiler
|
||||
|
||||
// Enter a new scope for the function body
|
||||
c.enterScope()
|
||||
|
||||
// Declare parameters as local variables
|
||||
for _, param := range fn.Parameters {
|
||||
c.declareVariable(param.Value)
|
||||
paramIndex := c.addConstant(param.Value)
|
||||
c.emit(types.OpSetLocal, paramIndex)
|
||||
}
|
||||
|
||||
// Compile the function body
|
||||
for _, stmt := range fn.Body.Statements {
|
||||
c.compileStatement(stmt)
|
||||
}
|
||||
|
||||
// Ensure the function always returns a value
|
||||
// If the last instruction is not a return, add one
|
||||
if len(fnCompiler.instructions) == 0 || fnCompiler.instructions[len(fnCompiler.instructions)-1].Opcode != types.OpReturn {
|
||||
nullIndex := c.addConstant(nil)
|
||||
c.emit(types.OpConstant, nullIndex)
|
||||
c.emit(types.OpReturn, 0)
|
||||
}
|
||||
|
||||
// Exit the function scope
|
||||
c.exitScope()
|
||||
|
||||
// Restore the parent compiler
|
||||
c.currentFunction = parentCompiler
|
||||
|
||||
// Extract upvalue information for closure creation
|
||||
upvalueIndexes := make([]int, len(fnCompiler.upvalues))
|
||||
for i, upvalue := range fnCompiler.upvalues {
|
||||
upvalueIndexes[i] = upvalue.index
|
||||
}
|
||||
|
||||
// Create a Function object and add it to the constants
|
||||
function := types.NewFunction(
|
||||
fnCompiler.instructions,
|
||||
fnCompiler.numParams,
|
||||
fnCompiler.constants,
|
||||
upvalueIndexes,
|
||||
)
|
||||
|
||||
functionIndex := c.addConstant(function)
|
||||
c.emit(types.OpFunction, functionIndex)
|
||||
|
||||
// Store the function in a global variable
|
||||
nameIndex := c.addConstant(fn.Name.Value)
|
||||
|
||||
// Use SetGlobal for top-level variables to persist between REPL lines
|
||||
if len(c.scopes) <= 1 {
|
||||
c.emit(types.OpSetGlobal, nameIndex)
|
||||
} else {
|
||||
c.declareVariable(fn.Name.Value)
|
||||
c.emit(types.OpSetLocal, nameIndex)
|
||||
}
|
||||
}
|
256
lexer/lexer.go
256
lexer/lexer.go
@ -1,256 +0,0 @@
|
||||
package lexer
|
||||
|
||||
type TokenType byte
|
||||
|
||||
const (
|
||||
TokenEOF TokenType = iota
|
||||
TokenIdentifier
|
||||
TokenString
|
||||
TokenNumber
|
||||
TokenEqual
|
||||
TokenEcho
|
||||
TokenSemicolon
|
||||
TokenLeftBrace
|
||||
TokenRightBrace
|
||||
TokenLeftBracket
|
||||
TokenRightBracket
|
||||
TokenComma
|
||||
TokenPlus
|
||||
TokenMinus
|
||||
TokenStar
|
||||
TokenSlash
|
||||
TokenLeftParen
|
||||
TokenRightParen
|
||||
TokenIf
|
||||
TokenThen
|
||||
TokenElse
|
||||
TokenTrue
|
||||
TokenFalse
|
||||
TokenEqualEqual
|
||||
TokenNotEqual
|
||||
TokenLessThan
|
||||
TokenGreaterThan
|
||||
TokenLessEqual
|
||||
TokenGreaterEqual
|
||||
TokenEnd
|
||||
TokenAnd
|
||||
TokenOr
|
||||
TokenNot
|
||||
TokenElseIf
|
||||
TokenNil
|
||||
TokenFunction
|
||||
TokenReturn
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Value string
|
||||
Line int
|
||||
}
|
||||
|
||||
type Lexer struct {
|
||||
input string
|
||||
pos int
|
||||
readPos int
|
||||
ch byte
|
||||
line int
|
||||
}
|
||||
|
||||
func New(input string) *Lexer {
|
||||
l := &Lexer{input: input, line: 1}
|
||||
l.readChar()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) readChar() {
|
||||
if l.readPos >= len(l.input) {
|
||||
l.ch = 0
|
||||
} else {
|
||||
l.ch = l.input[l.readPos]
|
||||
}
|
||||
l.pos = l.readPos
|
||||
l.readPos++
|
||||
|
||||
if l.ch == '\n' {
|
||||
l.line++
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) NextToken() Token {
|
||||
var tok Token
|
||||
|
||||
l.skipWhitespace()
|
||||
l.skipComment()
|
||||
|
||||
switch l.ch {
|
||||
case '=':
|
||||
if l.peekChar() == '=' {
|
||||
l.readChar() // consume the current '='
|
||||
tok = Token{Type: TokenEqualEqual, Value: "==", Line: l.line}
|
||||
} else {
|
||||
tok = Token{Type: TokenEqual, Value: "=", Line: l.line}
|
||||
}
|
||||
case '!':
|
||||
if l.peekChar() == '=' {
|
||||
l.readChar() // consume the current '!'
|
||||
tok = Token{Type: TokenNotEqual, Value: "!=", Line: l.line}
|
||||
} else {
|
||||
tok = Token{Type: TokenEOF, Value: "", Line: l.line} // Not supported yet
|
||||
}
|
||||
case '<':
|
||||
if l.peekChar() == '=' {
|
||||
l.readChar() // consume the current '<'
|
||||
tok = Token{Type: TokenLessEqual, Value: "<=", Line: l.line}
|
||||
} else {
|
||||
tok = Token{Type: TokenLessThan, Value: "<", Line: l.line}
|
||||
}
|
||||
case '>':
|
||||
if l.peekChar() == '=' {
|
||||
l.readChar() // consume the current '>'
|
||||
tok = Token{Type: TokenGreaterEqual, Value: ">=", Line: l.line}
|
||||
} else {
|
||||
tok = Token{Type: TokenGreaterThan, Value: ">", Line: l.line}
|
||||
}
|
||||
case '"':
|
||||
tok = Token{Type: TokenString, Value: l.readString(), Line: l.line}
|
||||
return tok
|
||||
case '{':
|
||||
tok = Token{Type: TokenLeftBrace, Value: "{", Line: l.line}
|
||||
case '}':
|
||||
tok = Token{Type: TokenRightBrace, Value: "}", Line: l.line}
|
||||
case '[':
|
||||
tok = Token{Type: TokenLeftBracket, Value: "[", Line: l.line}
|
||||
case ']':
|
||||
tok = Token{Type: TokenRightBracket, Value: "]", Line: l.line}
|
||||
case ',':
|
||||
tok = Token{Type: TokenComma, Value: ",", Line: l.line}
|
||||
case '+':
|
||||
tok = Token{Type: TokenPlus, Value: "+", Line: l.line}
|
||||
case '-':
|
||||
tok = Token{Type: TokenMinus, Value: "-", Line: l.line}
|
||||
case '*':
|
||||
tok = Token{Type: TokenStar, Value: "*", Line: l.line}
|
||||
case '/':
|
||||
if l.peekChar() == '/' {
|
||||
// Comment line, skip it
|
||||
l.skipComment()
|
||||
return l.NextToken()
|
||||
}
|
||||
tok = Token{Type: TokenSlash, Value: "/", Line: l.line}
|
||||
case '(':
|
||||
tok = Token{Type: TokenLeftParen, Value: "(", Line: l.line}
|
||||
case ')':
|
||||
tok = Token{Type: TokenRightParen, Value: ")", Line: l.line}
|
||||
case 0:
|
||||
tok = Token{Type: TokenEOF, Value: "", Line: l.line}
|
||||
default:
|
||||
if isLetter(l.ch) {
|
||||
tok.Value = l.readIdentifier()
|
||||
switch tok.Value {
|
||||
case "echo":
|
||||
tok.Type = TokenEcho
|
||||
case "if":
|
||||
tok.Type = TokenIf
|
||||
case "then":
|
||||
tok.Type = TokenThen
|
||||
case "else":
|
||||
tok.Type = TokenElse
|
||||
case "elseif":
|
||||
tok.Type = TokenElseIf
|
||||
case "true":
|
||||
tok.Type = TokenTrue
|
||||
case "false":
|
||||
tok.Type = TokenFalse
|
||||
case "end":
|
||||
tok.Type = TokenEnd
|
||||
case "and":
|
||||
tok.Type = TokenAnd
|
||||
case "or":
|
||||
tok.Type = TokenOr
|
||||
case "not":
|
||||
tok.Type = TokenNot
|
||||
case "nil":
|
||||
tok.Type = TokenNil
|
||||
case "function":
|
||||
tok.Type = TokenFunction
|
||||
case "return":
|
||||
tok.Type = TokenReturn
|
||||
default:
|
||||
tok.Type = TokenIdentifier
|
||||
}
|
||||
return tok
|
||||
} else if isDigit(l.ch) {
|
||||
tok.Type = TokenNumber
|
||||
tok.Value = l.readNumber()
|
||||
return tok
|
||||
} else {
|
||||
tok = Token{Type: TokenEOF, Value: "", Line: l.line}
|
||||
}
|
||||
}
|
||||
|
||||
l.readChar()
|
||||
return tok
|
||||
}
|
||||
|
||||
func (l *Lexer) skipWhitespace() {
|
||||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
|
||||
l.readChar()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) readIdentifier() string {
|
||||
pos := l.pos
|
||||
for isLetter(l.ch) || isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[pos:l.pos]
|
||||
}
|
||||
|
||||
func (l *Lexer) readNumber() string {
|
||||
pos := l.pos
|
||||
for isDigit(l.ch) {
|
||||
l.readChar()
|
||||
}
|
||||
return l.input[pos:l.pos]
|
||||
}
|
||||
|
||||
func (l *Lexer) readString() string {
|
||||
pos := l.pos + 1
|
||||
for {
|
||||
l.readChar()
|
||||
if l.ch == '"' || l.ch == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
str := l.input[pos:l.pos]
|
||||
l.readChar() // Skip closing quote
|
||||
return str
|
||||
}
|
||||
|
||||
func isLetter(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
func (l *Lexer) peekChar() byte {
|
||||
if l.readPos >= len(l.input) {
|
||||
return 0
|
||||
}
|
||||
return l.input[l.readPos]
|
||||
}
|
||||
|
||||
func (l *Lexer) skipComment() {
|
||||
if l.ch == '/' && l.peekChar() == '/' {
|
||||
l.readChar() // Skip first '/'
|
||||
l.readChar() // Skip second '/'
|
||||
|
||||
for l.ch != '\n' && l.ch != 0 {
|
||||
l.readChar()
|
||||
}
|
||||
|
||||
l.skipWhitespace()
|
||||
}
|
||||
}
|
65
mako.go
65
mako.go
@ -1,68 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.sharkk.net/Sharkk/Mako/compiler"
|
||||
"git.sharkk.net/Sharkk/Mako/lexer"
|
||||
"git.sharkk.net/Sharkk/Mako/parser"
|
||||
"git.sharkk.net/Sharkk/Mako/vm"
|
||||
)
|
||||
|
||||
func RunRepl() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
virtualMachine := vm.New()
|
||||
|
||||
fmt.Println("Mako REPL (type 'exit' to quit)")
|
||||
for {
|
||||
fmt.Print(">> ")
|
||||
if !scanner.Scan() {
|
||||
break
|
||||
}
|
||||
|
||||
input := scanner.Text()
|
||||
if input == "exit" {
|
||||
break
|
||||
}
|
||||
|
||||
ExecuteCode(input, virtualMachine)
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteCode(code string, virtualMachine *vm.VM) {
|
||||
lex := lexer.New(code)
|
||||
p := parser.New(lex)
|
||||
program := p.ParseProgram()
|
||||
|
||||
if len(p.Errors()) > 0 {
|
||||
for _, err := range p.Errors() {
|
||||
fmt.Printf("Error: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
bytecode := compiler.Compile(program)
|
||||
virtualMachine.Run(bytecode)
|
||||
}
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
|
||||
// If there's a command line argument, try to execute it as a file
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
fileContent, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file %s: %v\n", filename, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Execute the file content
|
||||
ExecuteCode(string(fileContent), vm.New())
|
||||
} else {
|
||||
// No arguments, run the REPL
|
||||
RunRepl()
|
||||
}
|
||||
fmt.Println("Foo")
|
||||
}
|
||||
|
203
parser/ast.go
203
parser/ast.go
@ -1,203 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "git.sharkk.net/Sharkk/Mako/lexer"
|
||||
|
||||
type Node interface {
|
||||
TokenLiteral() string
|
||||
}
|
||||
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (p *Program) TokenLiteral() string {
|
||||
if len(p.Statements) > 0 {
|
||||
return p.Statements[0].TokenLiteral()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type VariableStatement struct {
|
||||
Token lexer.Token
|
||||
Name *Identifier
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (vs *VariableStatement) statementNode() {}
|
||||
func (vs *VariableStatement) TokenLiteral() string { return vs.Token.Value }
|
||||
|
||||
type EchoStatement struct {
|
||||
Token lexer.Token
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (es *EchoStatement) statementNode() {}
|
||||
func (es *EchoStatement) TokenLiteral() string { return es.Token.Value }
|
||||
|
||||
type Identifier struct {
|
||||
Token lexer.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (i *Identifier) expressionNode() {}
|
||||
func (i *Identifier) TokenLiteral() string { return i.Token.Value }
|
||||
|
||||
type StringLiteral struct {
|
||||
Token lexer.Token
|
||||
Value string
|
||||
}
|
||||
|
||||
func (sl *StringLiteral) expressionNode() {}
|
||||
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Value }
|
||||
|
||||
type NumberLiteral struct {
|
||||
Token lexer.Token
|
||||
Value float64
|
||||
}
|
||||
|
||||
func (nl *NumberLiteral) expressionNode() {}
|
||||
func (nl *NumberLiteral) TokenLiteral() string { return nl.Token.Value }
|
||||
|
||||
// TableLiteral represents a table: {key1 = val1, key2 = val2}
|
||||
type TableLiteral struct {
|
||||
Token lexer.Token
|
||||
Pairs map[Expression]Expression
|
||||
}
|
||||
|
||||
func (tl *TableLiteral) expressionNode() {}
|
||||
func (tl *TableLiteral) TokenLiteral() string { return tl.Token.Value }
|
||||
|
||||
// IndexExpression represents table access: table[key]
|
||||
type IndexExpression struct {
|
||||
Token lexer.Token
|
||||
Left Expression
|
||||
Index Expression
|
||||
}
|
||||
|
||||
func (ie *IndexExpression) expressionNode() {}
|
||||
func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Value }
|
||||
|
||||
// IndexAssignmentStatement represents: table[key] = value
|
||||
type IndexAssignmentStatement struct {
|
||||
Token lexer.Token
|
||||
Left Expression
|
||||
Index Expression
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (ias *IndexAssignmentStatement) statementNode() {}
|
||||
func (ias *IndexAssignmentStatement) TokenLiteral() string { return ias.Token.Value }
|
||||
|
||||
// BlockStatement represents a block of code enclosed in braces
|
||||
type BlockStatement struct {
|
||||
Token lexer.Token
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (bs *BlockStatement) statementNode() {}
|
||||
func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Value }
|
||||
|
||||
// New AST nodes for arithmetic expressions
|
||||
|
||||
// InfixExpression represents binary operations like: a + b
|
||||
type InfixExpression struct {
|
||||
Token lexer.Token // The operator token, e.g. +
|
||||
Left Expression
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (ie *InfixExpression) expressionNode() {}
|
||||
func (ie *InfixExpression) TokenLiteral() string { return ie.Token.Value }
|
||||
|
||||
// PrefixExpression represents unary operations like: -a
|
||||
type PrefixExpression struct {
|
||||
Token lexer.Token // The prefix token, e.g. -
|
||||
Operator string
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Value }
|
||||
|
||||
// GroupedExpression represents an expression in parentheses: (a + b)
|
||||
type GroupedExpression struct {
|
||||
Token lexer.Token // The '(' token
|
||||
Expr Expression
|
||||
}
|
||||
|
||||
func (ge *GroupedExpression) expressionNode() {}
|
||||
func (ge *GroupedExpression) TokenLiteral() string { return ge.Token.Value }
|
||||
|
||||
type IfExpression struct {
|
||||
Token lexer.Token // The 'if' token
|
||||
Condition Expression
|
||||
Consequence *BlockStatement
|
||||
Alternative *BlockStatement // nil if no 'else'
|
||||
}
|
||||
|
||||
func (ie *IfExpression) expressionNode() {}
|
||||
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Value }
|
||||
|
||||
type BooleanLiteral struct {
|
||||
Token lexer.Token
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (bl *BooleanLiteral) expressionNode() {}
|
||||
func (bl *BooleanLiteral) TokenLiteral() string { return bl.Token.Value }
|
||||
|
||||
type NilLiteral struct {
|
||||
Token lexer.Token
|
||||
}
|
||||
|
||||
func (nl *NilLiteral) expressionNode() {}
|
||||
func (nl *NilLiteral) TokenLiteral() string { return nl.Token.Value }
|
||||
|
||||
// Function-related AST nodes
|
||||
|
||||
type FunctionLiteral struct {
|
||||
Token lexer.Token // The 'function' token
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fl *FunctionLiteral) expressionNode() {}
|
||||
func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Value }
|
||||
|
||||
type CallExpression struct {
|
||||
Token lexer.Token // The '(' token
|
||||
Function Expression // Identifier or FunctionLiteral
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (ce *CallExpression) expressionNode() {}
|
||||
func (ce *CallExpression) TokenLiteral() string { return ce.Token.Value }
|
||||
|
||||
type ReturnStatement struct {
|
||||
Token lexer.Token // The 'return' token
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (rs *ReturnStatement) statementNode() {}
|
||||
func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Value }
|
||||
|
||||
type FunctionStatement struct {
|
||||
Token lexer.Token // The 'function' token
|
||||
Name *Identifier
|
||||
Parameters []*Identifier
|
||||
Body *BlockStatement
|
||||
}
|
||||
|
||||
func (fs *FunctionStatement) statementNode() {}
|
||||
func (fs *FunctionStatement) TokenLiteral() string { return fs.Token.Value }
|
846
parser/parser.go
846
parser/parser.go
@ -1,846 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.sharkk.net/Sharkk/Mako/lexer"
|
||||
)
|
||||
|
||||
// Precedence levels for expression parsing
|
||||
const (
|
||||
_ int = iota
|
||||
LOWEST
|
||||
LOGICAL_OR // or
|
||||
LOGICAL_AND // and
|
||||
EQUALITY // ==, !=
|
||||
COMPARISON // <, >, <=, >=
|
||||
SUM // +, -
|
||||
PRODUCT // *, /
|
||||
PREFIX // -X or !X
|
||||
CALL // myFunction(X)
|
||||
INDEX // array[index]
|
||||
)
|
||||
|
||||
var precedences = map[lexer.TokenType]int{
|
||||
lexer.TokenPlus: SUM,
|
||||
lexer.TokenMinus: SUM,
|
||||
lexer.TokenStar: PRODUCT,
|
||||
lexer.TokenSlash: PRODUCT,
|
||||
lexer.TokenLeftBracket: INDEX,
|
||||
lexer.TokenLeftParen: CALL, // Add precedence for function calls
|
||||
lexer.TokenEqualEqual: EQUALITY,
|
||||
lexer.TokenNotEqual: EQUALITY,
|
||||
lexer.TokenLessThan: COMPARISON,
|
||||
lexer.TokenGreaterThan: COMPARISON,
|
||||
lexer.TokenLessEqual: COMPARISON,
|
||||
lexer.TokenGreaterEqual: COMPARISON,
|
||||
lexer.TokenAnd: LOGICAL_AND,
|
||||
lexer.TokenOr: LOGICAL_OR,
|
||||
}
|
||||
|
||||
type (
|
||||
prefixParseFn func() Expression
|
||||
infixParseFn func(Expression) Expression
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
l *lexer.Lexer
|
||||
errors []string
|
||||
|
||||
curToken lexer.Token
|
||||
peekToken lexer.Token
|
||||
|
||||
prefixParseFns map[lexer.TokenType]prefixParseFn
|
||||
infixParseFns map[lexer.TokenType]infixParseFn
|
||||
}
|
||||
|
||||
func New(l *lexer.Lexer) *Parser {
|
||||
p := &Parser{
|
||||
l: l,
|
||||
errors: []string{},
|
||||
}
|
||||
|
||||
// Initialize prefix parse functions
|
||||
p.prefixParseFns = make(map[lexer.TokenType]prefixParseFn)
|
||||
p.registerPrefix(lexer.TokenIdentifier, p.parseIdentifier)
|
||||
p.registerPrefix(lexer.TokenString, p.parseStringLiteral)
|
||||
p.registerPrefix(lexer.TokenNumber, p.parseNumberLiteral)
|
||||
p.registerPrefix(lexer.TokenLeftBrace, p.parseTableLiteral)
|
||||
p.registerPrefix(lexer.TokenMinus, p.parsePrefixExpression)
|
||||
p.registerPrefix(lexer.TokenLeftParen, p.parseGroupedExpression)
|
||||
p.registerPrefix(lexer.TokenIf, p.parseIfExpression)
|
||||
p.registerPrefix(lexer.TokenElse, p.parseUnexpectedToken)
|
||||
p.registerPrefix(lexer.TokenEnd, p.parseUnexpectedToken)
|
||||
p.registerPrefix(lexer.TokenThen, p.parseUnexpectedToken)
|
||||
p.registerPrefix(lexer.TokenTrue, p.parseBooleanLiteral)
|
||||
p.registerPrefix(lexer.TokenFalse, p.parseBooleanLiteral)
|
||||
p.registerPrefix(lexer.TokenNot, p.parsePrefixExpression)
|
||||
p.registerPrefix(lexer.TokenNil, p.parseNilLiteral)
|
||||
p.registerPrefix(lexer.TokenFunction, p.parseFunctionLiteral)
|
||||
p.registerPrefix(lexer.TokenRightParen, p.parseUnexpectedToken)
|
||||
|
||||
// Initialize infix parse functions
|
||||
p.infixParseFns = make(map[lexer.TokenType]infixParseFn)
|
||||
p.registerInfix(lexer.TokenPlus, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenMinus, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenStar, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenSlash, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenLeftBracket, p.parseIndexExpression)
|
||||
p.registerInfix(lexer.TokenLeftParen, p.parseCallExpression)
|
||||
p.registerInfix(lexer.TokenAnd, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenOr, p.parseInfixExpression)
|
||||
|
||||
// Register comparison operators
|
||||
p.registerInfix(lexer.TokenEqualEqual, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenNotEqual, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenLessThan, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenGreaterThan, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenLessEqual, p.parseInfixExpression)
|
||||
p.registerInfix(lexer.TokenGreaterEqual, p.parseInfixExpression)
|
||||
|
||||
// Read two tokens, so curToken and peekToken are both set
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) registerPrefix(tokenType lexer.TokenType, fn prefixParseFn) {
|
||||
p.prefixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) registerInfix(tokenType lexer.TokenType, fn infixParseFn) {
|
||||
p.infixParseFns[tokenType] = fn
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.curToken = p.peekToken
|
||||
p.peekToken = p.l.NextToken()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t lexer.TokenType) bool {
|
||||
return p.curToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t lexer.TokenType) bool {
|
||||
return p.peekToken.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) expectPeek(t lexer.TokenType) bool {
|
||||
if p.peekTokenIs(t) {
|
||||
p.nextToken()
|
||||
return true
|
||||
}
|
||||
p.peekError(t)
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) peekError(t lexer.TokenType) {
|
||||
msg := fmt.Sprintf("line %d: expected next token to be %d, got %d instead",
|
||||
p.peekToken.Line, t, p.peekToken.Type)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []string {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() int {
|
||||
if p, ok := precedences[p.peekToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() int {
|
||||
if p, ok := precedences[p.curToken.Type]; ok {
|
||||
return p
|
||||
}
|
||||
return LOWEST
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProgram() *Program {
|
||||
program := &Program{Statements: []Statement{}}
|
||||
|
||||
for !p.curTokenIs(lexer.TokenEOF) {
|
||||
stmt := p.parseStatement()
|
||||
program.Statements = append(program.Statements, stmt)
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() Statement {
|
||||
switch p.curToken.Type {
|
||||
case lexer.TokenIdentifier:
|
||||
if p.peekTokenIs(lexer.TokenEqual) {
|
||||
return p.parseVariableStatement()
|
||||
} else if p.peekTokenIs(lexer.TokenLeftBracket) {
|
||||
return p.parseIndexAssignmentStatement()
|
||||
}
|
||||
return p.parseExpressionStatement()
|
||||
case lexer.TokenEcho:
|
||||
return p.parseEchoStatement()
|
||||
case lexer.TokenReturn:
|
||||
return p.parseReturnStatement()
|
||||
case lexer.TokenFunction:
|
||||
// If the next token is an identifier, it's a function declaration
|
||||
if p.peekTokenIs(lexer.TokenIdentifier) {
|
||||
return p.parseFunctionStatement()
|
||||
}
|
||||
// Otherwise, it's a function expression
|
||||
return p.parseExpressionStatement()
|
||||
default:
|
||||
return p.parseExpressionStatement()
|
||||
}
|
||||
}
|
||||
|
||||
// Parse return statements
|
||||
func (p *Parser) parseReturnStatement() *ReturnStatement {
|
||||
stmt := &ReturnStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken() // Skip 'return'
|
||||
|
||||
// If there's no expression after 'return', set value to nil
|
||||
if p.curTokenIs(lexer.TokenEnd) {
|
||||
stmt.Value = &NilLiteral{Token: p.curToken}
|
||||
return stmt
|
||||
}
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
return stmt
|
||||
}
|
||||
|
||||
// New method for expression statements
|
||||
func (p *Parser) parseExpressionStatement() *ExpressionStatement {
|
||||
stmt := &ExpressionStatement{Token: p.curToken}
|
||||
|
||||
stmt.Expression = p.parseExpression(LOWEST)
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
// Add ExpressionStatement to ast.go
|
||||
type ExpressionStatement struct {
|
||||
Token lexer.Token
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (es *ExpressionStatement) statementNode() {}
|
||||
func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Value }
|
||||
|
||||
func (p *Parser) parseVariableStatement() *VariableStatement {
|
||||
stmt := &VariableStatement{Token: p.curToken}
|
||||
|
||||
stmt.Name = &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
|
||||
if !p.expectPeek(lexer.TokenEqual) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip the equals sign
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseEchoStatement() *EchoStatement {
|
||||
stmt := &EchoStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexAssignmentStatement() *IndexAssignmentStatement {
|
||||
stmt := &IndexAssignmentStatement{
|
||||
Token: p.curToken,
|
||||
Left: &Identifier{Token: p.curToken, Value: p.curToken.Value},
|
||||
}
|
||||
|
||||
p.nextToken() // Skip identifier
|
||||
if !p.expectPeek(lexer.TokenLeftBracket) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '['
|
||||
stmt.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightBracket) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.expectPeek(lexer.TokenEqual) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '='
|
||||
stmt.Value = p.parseExpression(LOWEST)
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
// Core expression parser with precedence climbing
|
||||
func (p *Parser) parseExpression(precedence int) Expression {
|
||||
prefix := p.prefixParseFns[p.curToken.Type]
|
||||
if prefix == nil {
|
||||
p.noPrefixParseFnError(p.curToken.Type)
|
||||
return nil
|
||||
}
|
||||
leftExp := prefix()
|
||||
|
||||
// Continue while we have valid infix operators
|
||||
// and stop at special tokens that end expressions
|
||||
for !p.peekTokenIs(lexer.TokenEnd) &&
|
||||
!p.peekTokenIs(lexer.TokenThen) &&
|
||||
!p.peekTokenIs(lexer.TokenElse) &&
|
||||
precedence < p.peekPrecedence() {
|
||||
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
leftExp = infix(leftExp)
|
||||
}
|
||||
|
||||
return leftExp
|
||||
}
|
||||
|
||||
func (p *Parser) noPrefixParseFnError(t lexer.TokenType) {
|
||||
msg := fmt.Sprintf("line %d: no prefix parse function for %d found",
|
||||
p.curToken.Line, t)
|
||||
p.errors = append(p.errors, msg)
|
||||
}
|
||||
|
||||
// Expression parsing methods
|
||||
func (p *Parser) parseIdentifier() Expression {
|
||||
return &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
}
|
||||
|
||||
func (p *Parser) parseStringLiteral() Expression {
|
||||
return &StringLiteral{Token: p.curToken, Value: p.curToken.Value}
|
||||
}
|
||||
|
||||
func (p *Parser) parseNumberLiteral() Expression {
|
||||
lit := &NumberLiteral{Token: p.curToken}
|
||||
|
||||
value, err := strconv.ParseFloat(p.curToken.Value, 64)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("could not parse %q as float", p.curToken.Value)
|
||||
p.errors = append(p.errors, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Value = value
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseTableLiteral() Expression {
|
||||
table := &TableLiteral{
|
||||
Token: p.curToken, // This should be '{'
|
||||
Pairs: make(map[Expression]Expression),
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '{'
|
||||
|
||||
if p.curTokenIs(lexer.TokenRightBrace) {
|
||||
return table // Empty table
|
||||
}
|
||||
|
||||
// Parse the first key-value pair
|
||||
key := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(lexer.TokenEqual) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '='
|
||||
value := p.parseExpression(LOWEST)
|
||||
table.Pairs[key] = value
|
||||
|
||||
// Parse remaining key-value pairs
|
||||
for p.peekTokenIs(lexer.TokenComma) {
|
||||
p.nextToken() // Skip current value
|
||||
p.nextToken() // Skip comma
|
||||
|
||||
if p.curTokenIs(lexer.TokenRightBrace) {
|
||||
break // Allow trailing comma
|
||||
}
|
||||
|
||||
key = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(lexer.TokenEqual) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '='
|
||||
value = p.parseExpression(LOWEST)
|
||||
table.Pairs[key] = value
|
||||
}
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightBrace) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (p *Parser) parseIndexExpression(left Expression) Expression {
|
||||
exp := &IndexExpression{
|
||||
Token: p.curToken,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '['
|
||||
exp.Index = p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightBracket) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
// New methods for arithmetic expressions
|
||||
func (p *Parser) parsePrefixExpression() Expression {
|
||||
expression := &PrefixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Value,
|
||||
}
|
||||
|
||||
p.nextToken() // Skip the prefix token
|
||||
expression.Right = p.parseExpression(PREFIX)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseInfixExpression(left Expression) Expression {
|
||||
expression := &InfixExpression{
|
||||
Token: p.curToken,
|
||||
Operator: p.curToken.Value,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
precedence := p.curPrecedence()
|
||||
p.nextToken() // Skip the operator
|
||||
expression.Right = p.parseExpression(precedence)
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseGroupedExpression() Expression {
|
||||
p.nextToken() // Skip '('
|
||||
|
||||
exp := p.parseExpression(LOWEST)
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightParen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wrap in GroupedExpression to maintain the AST structure
|
||||
return &GroupedExpression{
|
||||
Token: p.curToken,
|
||||
Expr: exp,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseBooleanLiteral() Expression {
|
||||
return &BooleanLiteral{
|
||||
Token: p.curToken,
|
||||
Value: p.curTokenIs(lexer.TokenTrue),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseIfExpression() Expression {
|
||||
expression := &IfExpression{Token: p.curToken}
|
||||
|
||||
p.nextToken() // Skip 'if'
|
||||
|
||||
// Parse condition
|
||||
expression.Condition = p.parseExpression(LOWEST)
|
||||
|
||||
// Expect 'then' after condition
|
||||
if !p.expectPeek(lexer.TokenThen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip 'then'
|
||||
|
||||
// Create a block statement for the consequence
|
||||
consequence := &BlockStatement{Token: p.curToken}
|
||||
consequence.Statements = []Statement{}
|
||||
|
||||
// Parse statements until we hit 'else', 'elseif', or 'end'
|
||||
for !p.curTokenIs(lexer.TokenElse) && !p.curTokenIs(lexer.TokenElseIf) &&
|
||||
!p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) {
|
||||
stmt := p.parseStatement()
|
||||
consequence.Statements = append(consequence.Statements, stmt)
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
expression.Consequence = consequence
|
||||
|
||||
// Check for 'elseif'
|
||||
if p.curTokenIs(lexer.TokenElseIf) {
|
||||
// Create a block statement for the alternative
|
||||
alternative := &BlockStatement{Token: p.curToken}
|
||||
alternative.Statements = []Statement{}
|
||||
|
||||
// Parse the nested elseif as a new if expression
|
||||
nestedIf := p.parseElseIfExpression()
|
||||
|
||||
// Add it as an expression statement in the alternative block
|
||||
alternative.Statements = append(alternative.Statements,
|
||||
&ExpressionStatement{Token: p.curToken, Expression: nestedIf})
|
||||
|
||||
expression.Alternative = alternative
|
||||
return expression
|
||||
}
|
||||
|
||||
// Check for 'else'
|
||||
if p.curTokenIs(lexer.TokenElse) {
|
||||
p.nextToken() // Skip 'else'
|
||||
|
||||
// Create a block statement for the alternative
|
||||
alternative := &BlockStatement{Token: p.curToken}
|
||||
alternative.Statements = []Statement{}
|
||||
|
||||
// Parse statements until we hit 'end'
|
||||
for !p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) {
|
||||
stmt := p.parseStatement()
|
||||
alternative.Statements = append(alternative.Statements, stmt)
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
expression.Alternative = alternative
|
||||
}
|
||||
|
||||
// We should now be at the 'end' token
|
||||
if !p.curTokenIs(lexer.TokenEnd) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected 'end' to close if expression",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseElseIfExpression() Expression {
|
||||
expression := &IfExpression{Token: p.curToken}
|
||||
|
||||
p.nextToken() // Skip 'elseif'
|
||||
|
||||
// Parse condition
|
||||
expression.Condition = p.parseExpression(LOWEST)
|
||||
|
||||
// Expect 'then' after condition
|
||||
if !p.expectPeek(lexer.TokenThen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.nextToken() // Skip 'then'
|
||||
|
||||
// Create a block statement for the consequence
|
||||
consequence := &BlockStatement{Token: p.curToken}
|
||||
consequence.Statements = []Statement{}
|
||||
|
||||
// Parse statements until we hit 'else', 'elseif', or 'end'
|
||||
for !p.curTokenIs(lexer.TokenElse) && !p.curTokenIs(lexer.TokenElseIf) &&
|
||||
!p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) {
|
||||
stmt := p.parseStatement()
|
||||
consequence.Statements = append(consequence.Statements, stmt)
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
expression.Consequence = consequence
|
||||
|
||||
// Handle nested elseif
|
||||
if p.curTokenIs(lexer.TokenElseIf) {
|
||||
// Create a block statement for the alternative
|
||||
alternative := &BlockStatement{Token: p.curToken}
|
||||
alternative.Statements = []Statement{}
|
||||
|
||||
// Parse the nested elseif recursively
|
||||
nestedIf := p.parseElseIfExpression()
|
||||
|
||||
// Add it as an expression statement in the alternative block
|
||||
alternative.Statements = append(alternative.Statements,
|
||||
&ExpressionStatement{Token: p.curToken, Expression: nestedIf})
|
||||
|
||||
expression.Alternative = alternative
|
||||
return expression
|
||||
}
|
||||
|
||||
// Handle else
|
||||
if p.curTokenIs(lexer.TokenElse) {
|
||||
p.nextToken() // Skip 'else'
|
||||
|
||||
// Create a block statement for the alternative
|
||||
alternative := &BlockStatement{Token: p.curToken}
|
||||
alternative.Statements = []Statement{}
|
||||
|
||||
// Parse statements until we hit 'end'
|
||||
for !p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) {
|
||||
stmt := p.parseStatement()
|
||||
alternative.Statements = append(alternative.Statements, stmt)
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
expression.Alternative = alternative
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
func (p *Parser) parseUnexpectedToken() Expression {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: unexpected token: %s",
|
||||
p.curToken.Line, p.curToken.Value))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseNilLiteral() Expression {
|
||||
return &NilLiteral{Token: p.curToken}
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionLiteral() Expression {
|
||||
lit := &FunctionLiteral{Token: p.curToken}
|
||||
|
||||
// Check if next token is a left paren
|
||||
if !p.expectPeek(lexer.TokenLeftParen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the parameters
|
||||
lit.Parameters = []*Identifier{}
|
||||
|
||||
// Check for empty parameter list
|
||||
if p.peekTokenIs(lexer.TokenRightParen) {
|
||||
p.nextToken() // Skip to the right paren
|
||||
} else {
|
||||
p.nextToken() // Skip the left paren
|
||||
|
||||
// Parse first parameter
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name, got %s",
|
||||
p.curToken.Line, p.curToken.Value))
|
||||
return nil
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
lit.Parameters = append(lit.Parameters, ident)
|
||||
|
||||
// Parse additional parameters
|
||||
for p.peekTokenIs(lexer.TokenComma) {
|
||||
p.nextToken() // Skip current parameter
|
||||
p.nextToken() // Skip comma
|
||||
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name after comma",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
lit.Parameters = append(lit.Parameters, ident)
|
||||
}
|
||||
|
||||
// After parsing parameters, expect closing parenthesis
|
||||
if !p.expectPeek(lexer.TokenRightParen) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Parse function body
|
||||
bodyStmts := []Statement{}
|
||||
for p.nextToken(); !p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF); p.nextToken() {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
bodyStmts = append(bodyStmts, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Expect 'end' token
|
||||
if !p.curTokenIs(lexer.TokenEnd) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected 'end' to close function",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
lit.Body = &BlockStatement{
|
||||
Token: p.curToken,
|
||||
Statements: bodyStmts,
|
||||
}
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionParameters() []*Identifier {
|
||||
identifiers := []*Identifier{}
|
||||
|
||||
// Empty parameter list
|
||||
if p.peekTokenIs(lexer.TokenRightParen) {
|
||||
p.nextToken() // Skip to right paren
|
||||
p.nextToken() // Skip right paren
|
||||
return identifiers
|
||||
}
|
||||
|
||||
p.nextToken() // Skip left paren
|
||||
|
||||
// First parameter
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
// Expected identifier for parameter but didn't get one
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name, got %d",
|
||||
p.curToken.Line, p.curToken.Type))
|
||||
if p.expectPeek(lexer.TokenRightParen) {
|
||||
return identifiers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
identifiers = append(identifiers, ident)
|
||||
|
||||
// Additional parameters
|
||||
for p.peekTokenIs(lexer.TokenComma) {
|
||||
p.nextToken() // Skip current identifier
|
||||
p.nextToken() // Skip comma
|
||||
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name after comma",
|
||||
p.curToken.Line))
|
||||
break
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
identifiers = append(identifiers, ident)
|
||||
}
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightParen) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected ')' to close parameter list",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (p *Parser) parseCallExpression(function Expression) Expression {
|
||||
exp := &CallExpression{
|
||||
Token: p.curToken,
|
||||
Function: function,
|
||||
Arguments: []Expression{},
|
||||
}
|
||||
|
||||
// Empty argument list
|
||||
if p.peekTokenIs(lexer.TokenRightParen) {
|
||||
p.nextToken()
|
||||
return exp
|
||||
}
|
||||
|
||||
p.nextToken() // Skip '('
|
||||
|
||||
// First argument
|
||||
exp.Arguments = append(exp.Arguments, p.parseExpression(LOWEST))
|
||||
|
||||
// Additional arguments
|
||||
for p.peekTokenIs(lexer.TokenComma) {
|
||||
p.nextToken() // Skip current argument
|
||||
p.nextToken() // Skip comma
|
||||
exp.Arguments = append(exp.Arguments, p.parseExpression(LOWEST))
|
||||
}
|
||||
|
||||
if !p.expectPeek(lexer.TokenRightParen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionStatement() *FunctionStatement {
|
||||
stmt := &FunctionStatement{Token: p.curToken}
|
||||
|
||||
p.nextToken() // Skip 'function'
|
||||
|
||||
// Parse function name
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected function name", p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Name = &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
|
||||
// Check if next token is a left paren
|
||||
if !p.expectPeek(lexer.TokenLeftParen) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse parameters
|
||||
stmt.Parameters = []*Identifier{}
|
||||
|
||||
// Check for empty parameter list
|
||||
if p.peekTokenIs(lexer.TokenRightParen) {
|
||||
p.nextToken() // Skip to the right paren
|
||||
} else {
|
||||
p.nextToken() // Skip the left paren
|
||||
|
||||
// Parse first parameter
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name, got %s",
|
||||
p.curToken.Line, p.curToken.Value))
|
||||
return nil
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
stmt.Parameters = append(stmt.Parameters, ident)
|
||||
|
||||
// Parse additional parameters
|
||||
for p.peekTokenIs(lexer.TokenComma) {
|
||||
p.nextToken() // Skip current parameter
|
||||
p.nextToken() // Skip comma
|
||||
|
||||
if !p.curTokenIs(lexer.TokenIdentifier) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected parameter name after comma",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
ident := &Identifier{Token: p.curToken, Value: p.curToken.Value}
|
||||
stmt.Parameters = append(stmt.Parameters, ident)
|
||||
}
|
||||
|
||||
// After parsing parameters, expect closing parenthesis
|
||||
if !p.expectPeek(lexer.TokenRightParen) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Parse function body
|
||||
bodyStmts := []Statement{}
|
||||
for p.nextToken(); !p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF); p.nextToken() {
|
||||
stmt := p.parseStatement()
|
||||
if stmt != nil {
|
||||
bodyStmts = append(bodyStmts, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Expect 'end' token
|
||||
if !p.curTokenIs(lexer.TokenEnd) {
|
||||
p.errors = append(p.errors, fmt.Sprintf("line %d: expected 'end' to close function",
|
||||
p.curToken.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt.Body = &BlockStatement{
|
||||
Token: p.curToken,
|
||||
Statements: bodyStmts,
|
||||
}
|
||||
|
||||
return stmt
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// Assignment style
|
||||
add = function(a, b)
|
||||
return a + b
|
||||
end
|
||||
|
||||
// Direct declaration style
|
||||
function multiply(a, b)
|
||||
return a * b
|
||||
end
|
||||
|
||||
echo add(1, 2)
|
||||
echo multiply(2, 2)
|
@ -1,88 +0,0 @@
|
||||
x = 10
|
||||
result = if x > 5 then "greater" else "smaller" end
|
||||
echo result
|
||||
|
||||
if x == 10 and x < 20 then
|
||||
echo "x is ten and less than 20"
|
||||
end
|
||||
|
||||
if x < 20 or x == 5 then
|
||||
echo "x is either less than 20 or equals 5"
|
||||
end
|
||||
|
||||
if not (x < 5) then
|
||||
echo "x is not less than 5"
|
||||
end
|
||||
|
||||
if x < 5 then
|
||||
echo "x is small"
|
||||
elseif x < 15 then
|
||||
echo "x is medium"
|
||||
elseif x < 25 then
|
||||
echo "x is large"
|
||||
else
|
||||
echo "x is very large"
|
||||
end
|
||||
|
||||
if x < 20 and x > 5 then
|
||||
echo "x is between 5 and 20"
|
||||
x = x + 1
|
||||
else
|
||||
echo "x is not between 5 and 20"
|
||||
x = x - 1
|
||||
end
|
||||
|
||||
a = 5
|
||||
b = 10
|
||||
c = 15
|
||||
|
||||
if a < b and b < c then
|
||||
echo "a < b < c - values are in ascending order"
|
||||
end
|
||||
|
||||
if a > 10 or b > 5 then
|
||||
echo "either a > 10 or b > 5 (or both)"
|
||||
end
|
||||
|
||||
if not (a == b) then
|
||||
echo "a is not equal to b"
|
||||
end
|
||||
|
||||
if (a < b and b < c) or (a == 5 and c == 15) then
|
||||
echo "Complex condition is true"
|
||||
end
|
||||
|
||||
if a > b and b > c then
|
||||
echo "Descending order"
|
||||
elseif a < b and b < c then
|
||||
echo "Ascending order"
|
||||
elseif a == b and b == c then
|
||||
echo "All equal"
|
||||
else
|
||||
echo "No specific order"
|
||||
end
|
||||
|
||||
scores = {
|
||||
math = if x > 10 then "excellent" else "good" end,
|
||||
science = "passing"
|
||||
}
|
||||
|
||||
echo "math score: " + scores["math"]
|
||||
echo "science score: " + scores["science"]
|
||||
|
||||
if a < b and b < c then
|
||||
if a > 0 and c < 20 then
|
||||
echo "a is positive, less than b, and c is less than 20"
|
||||
else
|
||||
echo "a is less than b, but either a is not positive or c >= 20"
|
||||
end
|
||||
end
|
||||
|
||||
if not (a < 0) and not (b > 20) then
|
||||
echo "Both a is not negative and b is not greater than 20"
|
||||
end
|
||||
|
||||
test1 = if a > 0 and b < 5 then "yes" else "no" end // Should be "no" (short-circuits on second condition)
|
||||
test2 = if a < 0 or b > 5 then "yes" else "no" end // Should be "yes" (short-circuits on second condition)
|
||||
echo "Test1: " + test1
|
||||
echo "Test2: " + test2
|
167
types/ast.go
Normal file
167
types/ast.go
Normal file
@ -0,0 +1,167 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Node represents a node in the abstract syntax tree
|
||||
type Node interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// Statement represents any statement
|
||||
type Statement interface {
|
||||
Node
|
||||
statementNode()
|
||||
}
|
||||
|
||||
// Expression represents any expression
|
||||
type Expression interface {
|
||||
Node
|
||||
expressionNode()
|
||||
}
|
||||
|
||||
// --- Expressions ---
|
||||
|
||||
// LiteralExpr represents a literal value
|
||||
type LiteralExpr struct {
|
||||
Value any
|
||||
}
|
||||
|
||||
func (l LiteralExpr) expressionNode() {}
|
||||
func (l LiteralExpr) String() string { return fmt.Sprintf("%v", l.Value) }
|
||||
|
||||
// BinaryExpr represents a binary operation
|
||||
type BinaryExpr struct {
|
||||
Left Expression
|
||||
Operator Token
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (b BinaryExpr) expressionNode() {}
|
||||
func (b BinaryExpr) String() string {
|
||||
return fmt.Sprintf("(%s %s %s)", b.Left.String(), b.Operator.Lexeme, b.Right.String())
|
||||
}
|
||||
|
||||
// VariableExpr represents a variable reference
|
||||
type VariableExpr struct {
|
||||
Name Token
|
||||
}
|
||||
|
||||
func (v VariableExpr) expressionNode() {}
|
||||
func (v VariableExpr) String() string { return v.Name.Lexeme }
|
||||
|
||||
// CallExpr represents a function call
|
||||
type CallExpr struct {
|
||||
Callee Expression
|
||||
Paren Token
|
||||
Arguments []Expression
|
||||
}
|
||||
|
||||
func (c CallExpr) expressionNode() {}
|
||||
func (c CallExpr) String() string {
|
||||
args := ""
|
||||
for i, arg := range c.Arguments {
|
||||
if i > 0 {
|
||||
args += ", "
|
||||
}
|
||||
args += arg.String()
|
||||
}
|
||||
return fmt.Sprintf("%s(%s)", c.Callee.String(), args)
|
||||
}
|
||||
|
||||
// --- Statements ---
|
||||
|
||||
// ExpressionStmt represents an expression statement
|
||||
type ExpressionStmt struct {
|
||||
Expression Expression
|
||||
}
|
||||
|
||||
func (e ExpressionStmt) statementNode() {}
|
||||
func (e ExpressionStmt) String() string { return e.Expression.String() }
|
||||
|
||||
// AssignStmt represents a variable assignment
|
||||
type AssignStmt struct {
|
||||
Name Token
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (a AssignStmt) statementNode() {}
|
||||
func (a AssignStmt) String() string {
|
||||
return fmt.Sprintf("%s = %s", a.Name.Lexeme, a.Value.String())
|
||||
}
|
||||
|
||||
// FunctionStmt represents a function declaration
|
||||
type FunctionStmt struct {
|
||||
Name Token
|
||||
Params []Token
|
||||
Body []Statement
|
||||
}
|
||||
|
||||
func (f FunctionStmt) statementNode() {}
|
||||
func (f FunctionStmt) String() string {
|
||||
params := ""
|
||||
for i, param := range f.Params {
|
||||
if i > 0 {
|
||||
params += ", "
|
||||
}
|
||||
params += param.Lexeme
|
||||
}
|
||||
return fmt.Sprintf("fn %s(%s) ... end", f.Name.Lexeme, params)
|
||||
}
|
||||
|
||||
// ReturnStmt represents a return statement
|
||||
type ReturnStmt struct {
|
||||
Keyword Token
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (r ReturnStmt) statementNode() {}
|
||||
func (r ReturnStmt) String() string {
|
||||
if r.Value == nil {
|
||||
return "return"
|
||||
}
|
||||
return fmt.Sprintf("return %s", r.Value.String())
|
||||
}
|
||||
|
||||
// IfStmt represents an if statement
|
||||
type IfStmt struct {
|
||||
Condition Expression
|
||||
ThenBranch []Statement
|
||||
ElseIfs []struct {
|
||||
Condition Expression
|
||||
Body []Statement
|
||||
}
|
||||
ElseBranch []Statement
|
||||
}
|
||||
|
||||
func (i IfStmt) statementNode() {}
|
||||
func (i IfStmt) String() string { return "if ... then ... end" }
|
||||
|
||||
// EchoStmt represents an echo statement
|
||||
type EchoStmt struct {
|
||||
Keyword Token
|
||||
Value Expression
|
||||
}
|
||||
|
||||
func (e EchoStmt) statementNode() {}
|
||||
func (e EchoStmt) String() string { return fmt.Sprintf("echo %s", e.Value.String()) }
|
||||
|
||||
// UnaryExpr represents a unary operation
|
||||
type UnaryExpr struct {
|
||||
Operator Token
|
||||
Right Expression
|
||||
}
|
||||
|
||||
func (u UnaryExpr) expressionNode() {}
|
||||
func (u UnaryExpr) String() string {
|
||||
return fmt.Sprintf("(%s%s)", u.Operator.Lexeme, u.Right.String())
|
||||
}
|
||||
|
||||
// BlockStmt represents a block of statements
|
||||
type BlockStmt struct {
|
||||
Statements []Statement
|
||||
}
|
||||
|
||||
func (b BlockStmt) statementNode() {}
|
||||
func (b BlockStmt) String() string {
|
||||
return "{ ... }"
|
||||
}
|
86
types/bytecode.go
Normal file
86
types/bytecode.go
Normal file
@ -0,0 +1,86 @@
|
||||
// bytecode.go
|
||||
package types
|
||||
|
||||
// SourcePos represents a position in the source code
|
||||
type SourcePos struct {
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// OpCode represents a bytecode instruction
|
||||
type OpCode byte
|
||||
|
||||
const (
|
||||
OP_CONSTANT OpCode = iota
|
||||
OP_NIL
|
||||
OP_TRUE
|
||||
OP_FALSE
|
||||
OP_POP
|
||||
OP_GET_LOCAL
|
||||
OP_SET_LOCAL
|
||||
OP_GET_GLOBAL
|
||||
OP_SET_GLOBAL
|
||||
OP_EQUAL
|
||||
OP_GREATER
|
||||
OP_LESS
|
||||
OP_ADD
|
||||
OP_SUBTRACT
|
||||
OP_MULTIPLY
|
||||
OP_DIVIDE
|
||||
OP_NOT
|
||||
OP_NEGATE
|
||||
OP_PRINT
|
||||
OP_JUMP
|
||||
OP_JUMP_IF_FALSE
|
||||
OP_CALL
|
||||
OP_RETURN
|
||||
)
|
||||
|
||||
// String returns the string representation of an OpCode
|
||||
func (op OpCode) String() string {
|
||||
names := [...]string{
|
||||
"OP_CONSTANT", "OP_NIL", "OP_TRUE", "OP_FALSE",
|
||||
"OP_POP", "OP_GET_LOCAL", "OP_SET_LOCAL", "OP_GET_GLOBAL",
|
||||
"OP_SET_GLOBAL", "OP_EQUAL", "OP_GREATER", "OP_LESS",
|
||||
"OP_ADD", "OP_SUBTRACT", "OP_MULTIPLY", "OP_DIVIDE",
|
||||
"OP_NOT", "OP_NEGATE", "OP_PRINT", "OP_JUMP",
|
||||
"OP_JUMP_IF_FALSE", "OP_CALL", "OP_RETURN",
|
||||
}
|
||||
return names[op]
|
||||
}
|
||||
|
||||
// Instruction represents a single bytecode instruction
|
||||
type Instruction struct {
|
||||
Op OpCode
|
||||
Operands []byte
|
||||
Pos SourcePos // Source position for debugging
|
||||
}
|
||||
|
||||
// Chunk represents a chunk of bytecode
|
||||
type Chunk struct {
|
||||
Code []Instruction
|
||||
Constants []Value
|
||||
}
|
||||
|
||||
// Function represents a function in bytecode
|
||||
type Function struct {
|
||||
Name string
|
||||
Arity int
|
||||
Chunk *Chunk
|
||||
LocalCount int
|
||||
UpvalueCount int
|
||||
}
|
||||
|
||||
// Environment represents a variable scope
|
||||
type Environment struct {
|
||||
Values map[string]Value
|
||||
Enclosing *Environment
|
||||
}
|
||||
|
||||
// NewEnvironment creates a new environment
|
||||
func NewEnvironment(enclosing *Environment) *Environment {
|
||||
return &Environment{
|
||||
Values: make(map[string]Value),
|
||||
Enclosing: enclosing,
|
||||
}
|
||||
}
|
23
types/errors.go
Normal file
23
types/errors.go
Normal file
@ -0,0 +1,23 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MakoError represents an error in Mako code
|
||||
type MakoError struct {
|
||||
Message string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
func (e MakoError) Error() string {
|
||||
return fmt.Sprintf("[%d:%d] %s", e.Line, e.Column, e.Message)
|
||||
}
|
||||
|
||||
// NewError creates a new MakoError
|
||||
func NewError(message string, line, column int) *MakoError {
|
||||
return &MakoError{
|
||||
Message: message,
|
||||
Line: line,
|
||||
Column: column,
|
||||
}
|
||||
}
|
99
types/token.go
Normal file
99
types/token.go
Normal file
@ -0,0 +1,99 @@
|
||||
package types
|
||||
|
||||
// TokenType represents the type of a token
|
||||
type TokenType int
|
||||
|
||||
// Token represents a lexical token
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Lexeme string
|
||||
Literal any
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// All token types in the language
|
||||
const (
|
||||
// Special tokens
|
||||
EOF TokenType = iota
|
||||
ERROR
|
||||
|
||||
// Literals
|
||||
IDENTIFIER
|
||||
STRING
|
||||
NUMBER
|
||||
|
||||
// Operators
|
||||
PLUS // +
|
||||
MINUS // -
|
||||
STAR // *
|
||||
SLASH // /
|
||||
EQUAL // =
|
||||
EQUAL_EQUAL // ==
|
||||
BANG_EQUAL // !=
|
||||
LESS //
|
||||
LESS_EQUAL // <=
|
||||
GREATER // >
|
||||
GREATER_EQUAL // >=
|
||||
|
||||
// Keywords
|
||||
AND
|
||||
OR
|
||||
IF
|
||||
ELSEIF
|
||||
ELSE
|
||||
THEN
|
||||
END
|
||||
FN
|
||||
RETURN
|
||||
ECHO
|
||||
TRUE
|
||||
FALSE
|
||||
NIL
|
||||
|
||||
// Punctuation
|
||||
LEFT_PAREN // (
|
||||
RIGHT_PAREN // )
|
||||
COMMA // ,
|
||||
)
|
||||
|
||||
// String returns the string representation of the token type
|
||||
func (t TokenType) String() string {
|
||||
return tokenNames[t]
|
||||
}
|
||||
|
||||
// Token type string representations
|
||||
var tokenNames = [...]string{
|
||||
EOF: "EOF",
|
||||
ERROR: "ERROR",
|
||||
IDENTIFIER: "IDENTIFIER",
|
||||
STRING: "STRING",
|
||||
NUMBER: "NUMBER",
|
||||
PLUS: "PLUS",
|
||||
MINUS: "MINUS",
|
||||
STAR: "STAR",
|
||||
SLASH: "SLASH",
|
||||
EQUAL: "EQUAL",
|
||||
EQUAL_EQUAL: "EQUAL_EQUAL",
|
||||
BANG_EQUAL: "BANG_EQUAL",
|
||||
LESS: "LESS",
|
||||
LESS_EQUAL: "LESS_EQUAL",
|
||||
GREATER: "GREATER",
|
||||
GREATER_EQUAL: "GREATER_EQUAL",
|
||||
AND: "AND",
|
||||
OR: "OR",
|
||||
IF: "IF",
|
||||
ELSEIF: "ELSEIF",
|
||||
ELSE: "ELSE",
|
||||
THEN: "THEN",
|
||||
END: "END",
|
||||
FN: "FN",
|
||||
RETURN: "RETURN",
|
||||
ECHO: "ECHO",
|
||||
TRUE: "TRUE",
|
||||
FALSE: "FALSE",
|
||||
NIL: "NIL",
|
||||
LEFT_PAREN: "LEFT_PAREN",
|
||||
RIGHT_PAREN: "RIGHT_PAREN",
|
||||
COMMA: "COMMA",
|
||||
}
|
292
types/types.go
292
types/types.go
@ -1,292 +0,0 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ValueType represents the type of a value
|
||||
type ValueType byte
|
||||
|
||||
const (
|
||||
TypeNull ValueType = iota
|
||||
TypeNumber
|
||||
TypeString
|
||||
TypeBoolean
|
||||
TypeTable
|
||||
TypeFunction
|
||||
)
|
||||
|
||||
type Opcode byte
|
||||
|
||||
const (
|
||||
OpConstant Opcode = iota
|
||||
OpSetLocal
|
||||
OpGetLocal
|
||||
OpSetGlobal
|
||||
OpGetGlobal
|
||||
OpEcho
|
||||
OpNewTable
|
||||
OpSetIndex
|
||||
OpGetIndex
|
||||
OpDup
|
||||
OpPop
|
||||
OpEnterScope
|
||||
OpExitScope
|
||||
OpAdd
|
||||
OpSubtract
|
||||
OpMultiply
|
||||
OpDivide
|
||||
OpNegate
|
||||
OpJumpIfFalse
|
||||
OpJump
|
||||
OpEqual
|
||||
OpNotEqual
|
||||
OpLessThan
|
||||
OpGreaterThan
|
||||
OpLessEqual
|
||||
OpGreaterEqual
|
||||
OpNot
|
||||
OpFunction
|
||||
OpCall
|
||||
OpReturn
|
||||
OpClosure
|
||||
)
|
||||
|
||||
type Instruction struct {
|
||||
Opcode Opcode
|
||||
Operand int
|
||||
}
|
||||
|
||||
type Bytecode struct {
|
||||
Constants []any
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Type ValueType
|
||||
Data any
|
||||
}
|
||||
|
||||
// Equal checks if two values are equal
|
||||
func (v Value) Equal(other Value) bool {
|
||||
if v.Type != other.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case TypeNull:
|
||||
return true // null == null
|
||||
case TypeNumber:
|
||||
return v.Data.(float64) == other.Data.(float64)
|
||||
case TypeString:
|
||||
return v.Data.(string) == other.Data.(string)
|
||||
case TypeBoolean:
|
||||
return v.Data.(bool) == other.Data.(bool)
|
||||
case TypeTable:
|
||||
return v.Data.(*Table).Equal(other.Data.(*Table))
|
||||
case TypeFunction:
|
||||
// Two functions are equal if they point to the same function object
|
||||
return v.Data.(*Function) == other.Data.(*Function)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func NewNull() Value {
|
||||
return Value{Type: TypeNull, Data: nil}
|
||||
}
|
||||
|
||||
func NewString(s string) Value {
|
||||
return Value{Type: TypeString, Data: s}
|
||||
}
|
||||
|
||||
func NewNumber(n float64) Value {
|
||||
return Value{Type: TypeNumber, Data: n}
|
||||
}
|
||||
|
||||
func NewBoolean(b bool) Value {
|
||||
return Value{Type: TypeBoolean, Data: b}
|
||||
}
|
||||
|
||||
// Function represents a callable function
|
||||
type Function struct {
|
||||
Instructions []Instruction
|
||||
NumParams int
|
||||
NumLocals int
|
||||
NumConstants int
|
||||
Constants []any // Function's own constant pool
|
||||
UpvalueIndexes []int // Indexes of upvalues (for closures)
|
||||
}
|
||||
|
||||
func NewFunction(instructions []Instruction, numParams int, constants []any, upvalues []int) *Function {
|
||||
return &Function{
|
||||
Instructions: instructions,
|
||||
NumParams: numParams,
|
||||
NumConstants: len(constants),
|
||||
Constants: constants,
|
||||
UpvalueIndexes: upvalues,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFunctionValue(function *Function) Value {
|
||||
return Value{Type: TypeFunction, Data: function}
|
||||
}
|
||||
|
||||
// Upvalue represents a reference to a value that has been closed over
|
||||
type Upvalue struct {
|
||||
Value *Value // Pointer to the closed-over value
|
||||
}
|
||||
|
||||
// TableEntry maintains insertion order
|
||||
type TableEntry struct {
|
||||
Key Value
|
||||
Value Value
|
||||
}
|
||||
|
||||
// Table with ordered entries
|
||||
type Table struct {
|
||||
Entries []TableEntry // Preserves insertion order
|
||||
HashMap map[string]int // Fast lookups for string keys
|
||||
NumMap map[float64]int // Fast lookups for number keys
|
||||
BoolMap map[bool]int // Fast lookups for boolean keys
|
||||
// We can't have a map for table keys since they're not comparable in Go
|
||||
// We'll handle table keys by linear search in Entries
|
||||
}
|
||||
|
||||
// Equal compares two tables for equality
|
||||
func (t *Table) Equal(other *Table) bool {
|
||||
if len(t.Entries) != len(other.Entries) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if every key-value pair in t exists in other
|
||||
for _, entry := range t.Entries {
|
||||
found := false
|
||||
for _, otherEntry := range other.Entries {
|
||||
if entry.Key.Equal(otherEntry.Key) && entry.Value.Equal(otherEntry.Value) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func NewTable() *Table {
|
||||
return &Table{
|
||||
Entries: []TableEntry{},
|
||||
HashMap: make(map[string]int),
|
||||
NumMap: make(map[float64]int),
|
||||
BoolMap: make(map[bool]int),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTableValue() Value {
|
||||
return Value{Type: TypeTable, Data: NewTable()}
|
||||
}
|
||||
|
||||
// SetKey is a helper to generate a string representation of a key for maps
|
||||
func (t *Table) SetKey(key Value) string {
|
||||
switch key.Type {
|
||||
case TypeString:
|
||||
return "s:" + key.Data.(string)
|
||||
case TypeNumber:
|
||||
return "n:" + fmt.Sprintf("%v", key.Data.(float64))
|
||||
case TypeBoolean:
|
||||
if key.Data.(bool) {
|
||||
return "b:true"
|
||||
}
|
||||
return "b:false"
|
||||
case TypeNull:
|
||||
return "null"
|
||||
case TypeTable:
|
||||
// For tables, we can use a simple hash or just indicate it's a table
|
||||
return "t:table" // This won't distinguish between different tables
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// TableSet preserves insertion order
|
||||
func (t *Table) Set(key, value Value) {
|
||||
idx := -1
|
||||
|
||||
// Check if the key is a table
|
||||
if key.Type == TypeTable {
|
||||
// For table keys, we need to do a linear search
|
||||
for i, entry := range t.Entries {
|
||||
if entry.Key.Type == TypeTable && entry.Key.Data.(*Table).Equal(key.Data.(*Table)) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use the existing maps for other types
|
||||
switch key.Type {
|
||||
case TypeString:
|
||||
if i, ok := t.HashMap[key.Data.(string)]; ok {
|
||||
idx = i
|
||||
}
|
||||
case TypeNumber:
|
||||
if i, ok := t.NumMap[key.Data.(float64)]; ok {
|
||||
idx = i
|
||||
}
|
||||
case TypeBoolean:
|
||||
if i, ok := t.BoolMap[key.Data.(bool)]; ok {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idx >= 0 {
|
||||
// Update existing entry
|
||||
t.Entries[idx].Value = value
|
||||
} else {
|
||||
// Add new entry
|
||||
t.Entries = append(t.Entries, TableEntry{Key: key, Value: value})
|
||||
idx = len(t.Entries) - 1
|
||||
|
||||
// Update lookup maps
|
||||
switch key.Type {
|
||||
case TypeString:
|
||||
t.HashMap[key.Data.(string)] = idx
|
||||
case TypeNumber:
|
||||
t.NumMap[key.Data.(float64)] = idx
|
||||
case TypeBoolean:
|
||||
t.BoolMap[key.Data.(bool)] = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) Get(key Value) Value {
|
||||
// Check if the key is a table
|
||||
if key.Type == TypeTable {
|
||||
// For table keys, we need to do a linear search
|
||||
for _, entry := range t.Entries {
|
||||
if entry.Key.Type == TypeTable && entry.Key.Data.(*Table).Equal(key.Data.(*Table)) {
|
||||
return entry.Value
|
||||
}
|
||||
}
|
||||
return NewNull()
|
||||
}
|
||||
|
||||
// Use the existing maps for other types
|
||||
switch key.Type {
|
||||
case TypeString:
|
||||
if i, ok := t.HashMap[key.Data.(string)]; ok {
|
||||
return t.Entries[i].Value
|
||||
}
|
||||
case TypeNumber:
|
||||
if i, ok := t.NumMap[key.Data.(float64)]; ok {
|
||||
return t.Entries[i].Value
|
||||
}
|
||||
case TypeBoolean:
|
||||
if i, ok := t.BoolMap[key.Data.(bool)]; ok {
|
||||
return t.Entries[i].Value
|
||||
}
|
||||
}
|
||||
return NewNull()
|
||||
}
|
106
types/value.go
Normal file
106
types/value.go
Normal file
@ -0,0 +1,106 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ValueType represents the type of a runtime value
|
||||
type ValueType int
|
||||
|
||||
const (
|
||||
NIL_TYPE ValueType = iota
|
||||
BOOL_TYPE
|
||||
NUMBER_TYPE
|
||||
STRING_TYPE
|
||||
FUNCTION_TYPE
|
||||
TABLE_TYPE
|
||||
)
|
||||
|
||||
// Value interface represents any value in the language at runtime
|
||||
type Value interface {
|
||||
Type() ValueType
|
||||
String() string
|
||||
}
|
||||
|
||||
// NilValue represents a nil value
|
||||
type NilValue struct{}
|
||||
|
||||
func (n NilValue) Type() ValueType {
|
||||
return NIL_TYPE
|
||||
}
|
||||
|
||||
func (n NilValue) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
// BoolValue represents a boolean value
|
||||
type BoolValue struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
func (b BoolValue) Type() ValueType {
|
||||
return BOOL_TYPE
|
||||
}
|
||||
|
||||
func (b BoolValue) String() string {
|
||||
if b.Value {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
// NumberValue represents a numeric value
|
||||
type NumberValue struct {
|
||||
Value float64
|
||||
}
|
||||
|
||||
func (n NumberValue) Type() ValueType {
|
||||
return NUMBER_TYPE
|
||||
}
|
||||
|
||||
func (n NumberValue) String() string {
|
||||
return fmt.Sprintf("%g", n.Value)
|
||||
}
|
||||
|
||||
// StringValue represents a string value
|
||||
type StringValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (s StringValue) Type() ValueType {
|
||||
return STRING_TYPE
|
||||
}
|
||||
|
||||
func (s StringValue) String() string {
|
||||
return s.Value
|
||||
}
|
||||
|
||||
// FunctionValue represents a function value
|
||||
type FunctionValue struct {
|
||||
Name string
|
||||
Arity int
|
||||
// We'll add bytecode and other function data later
|
||||
}
|
||||
|
||||
func (f FunctionValue) Type() ValueType {
|
||||
return FUNCTION_TYPE
|
||||
}
|
||||
|
||||
func (f FunctionValue) String() string {
|
||||
if f.Name == "" {
|
||||
return "<anonymous function>"
|
||||
}
|
||||
return fmt.Sprintf("<function %s>", f.Name)
|
||||
}
|
||||
|
||||
// TableValue represents a table (map/dictionary/array)
|
||||
type TableValue struct {
|
||||
Data map[Value]Value
|
||||
MetaTable *TableValue
|
||||
}
|
||||
|
||||
func (t TableValue) Type() ValueType {
|
||||
return TABLE_TYPE
|
||||
}
|
||||
|
||||
func (t TableValue) String() string {
|
||||
return fmt.Sprintf("<table: %p>", &t)
|
||||
}
|
604
vm/vm.go
604
vm/vm.go
@ -1,604 +0,0 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.sharkk.net/Sharkk/Mako/types"
|
||||
)
|
||||
|
||||
// Scope represents a lexical scope
|
||||
type Scope struct {
|
||||
Variables map[string]types.Value
|
||||
}
|
||||
|
||||
// Frame represents a call frame on the call stack
|
||||
type Frame struct {
|
||||
Function *types.Function // The function being executed
|
||||
IP int // Instruction pointer
|
||||
BasePointer int // Base stack pointer for this frame
|
||||
ReturnAddr int // Return address in the caller
|
||||
Upvalues []*types.Upvalue // Closed-over variables
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
constants []any
|
||||
globals map[string]types.Value
|
||||
scopes []Scope // Stack of local scopes
|
||||
stack []types.Value
|
||||
sp int // Stack pointer
|
||||
frames []Frame // Call frames
|
||||
fp int // Frame pointer (index of current frame)
|
||||
upvalues []*types.Upvalue // Upvalues for closures
|
||||
}
|
||||
|
||||
func New() *VM {
|
||||
return &VM{
|
||||
globals: make(map[string]types.Value),
|
||||
scopes: []Scope{}, // Initially no scopes
|
||||
stack: make([]types.Value, 1024),
|
||||
sp: 0,
|
||||
frames: make([]Frame, 64), // Support up to 64 nested calls
|
||||
fp: -1, // No active frame yet
|
||||
upvalues: []*types.Upvalue{},
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the VM to its initial state
|
||||
// Can be called as vm.Reset() to reuse an existing VM instance
|
||||
func (vm *VM) Reset() *VM {
|
||||
vm.constants = nil
|
||||
vm.globals = make(map[string]types.Value)
|
||||
vm.scopes = []Scope{}
|
||||
vm.stack = make([]types.Value, 1024)
|
||||
vm.sp = 0
|
||||
vm.frames = make([]Frame, 64)
|
||||
vm.fp = -1
|
||||
vm.upvalues = []*types.Upvalue{}
|
||||
return vm
|
||||
}
|
||||
|
||||
// GetGlobal retrieves a global variable by name
|
||||
func (vm *VM) GetGlobal(name string) (types.Value, bool) {
|
||||
val, ok := vm.globals[name]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Global returns all global variables for testing purposes
|
||||
func (vm *VM) Globals() map[string]types.Value {
|
||||
return vm.globals
|
||||
}
|
||||
|
||||
// CurrentStack returns the current stack values for testing
|
||||
func (vm *VM) CurrentStack() []types.Value {
|
||||
return vm.stack[:vm.sp]
|
||||
}
|
||||
|
||||
func (vm *VM) Run(bytecode *types.Bytecode) {
|
||||
vm.constants = bytecode.Constants
|
||||
vm.runCode(bytecode.Instructions, 0)
|
||||
}
|
||||
|
||||
func (vm *VM) runCode(instructions []types.Instruction, basePointer int) types.Value {
|
||||
for ip := 0; ip < len(instructions); ip++ {
|
||||
instruction := instructions[ip]
|
||||
|
||||
switch instruction.Opcode {
|
||||
case types.OpConstant:
|
||||
constIndex := instruction.Operand
|
||||
constant := vm.constants[constIndex]
|
||||
|
||||
switch v := constant.(type) {
|
||||
case string:
|
||||
vm.push(types.NewString(v))
|
||||
case float64:
|
||||
vm.push(types.NewNumber(v))
|
||||
case bool:
|
||||
vm.push(types.NewBoolean(v))
|
||||
case nil:
|
||||
vm.push(types.NewNull())
|
||||
case *types.Function:
|
||||
vm.push(types.NewFunctionValue(v))
|
||||
}
|
||||
|
||||
case types.OpSetLocal:
|
||||
constIndex := instruction.Operand
|
||||
name := vm.constants[constIndex].(string)
|
||||
value := vm.pop()
|
||||
|
||||
// Set in current scope if it exists
|
||||
if len(vm.scopes) > 0 {
|
||||
vm.scopes[len(vm.scopes)-1].Variables[name] = value
|
||||
} else {
|
||||
// No scope, set as global
|
||||
vm.globals[name] = value
|
||||
}
|
||||
|
||||
case types.OpGetLocal:
|
||||
constIndex := instruction.Operand
|
||||
name := vm.constants[constIndex].(string)
|
||||
|
||||
// Check local scopes from innermost to outermost
|
||||
found := false
|
||||
for i := len(vm.scopes) - 1; i >= 0; i-- {
|
||||
if val, ok := vm.scopes[i].Variables[name]; ok {
|
||||
vm.push(val)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in locals, check globals
|
||||
if !found {
|
||||
if val, ok := vm.globals[name]; ok {
|
||||
vm.push(val)
|
||||
} else {
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
}
|
||||
|
||||
case types.OpSetGlobal:
|
||||
constIndex := instruction.Operand
|
||||
name := vm.constants[constIndex].(string)
|
||||
value := vm.pop()
|
||||
vm.globals[name] = value
|
||||
|
||||
case types.OpGetGlobal:
|
||||
constIndex := instruction.Operand
|
||||
name := vm.constants[constIndex].(string)
|
||||
if val, ok := vm.globals[name]; ok {
|
||||
vm.push(val)
|
||||
} else {
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpEnterScope:
|
||||
// Push a new scope
|
||||
vm.scopes = append(vm.scopes, Scope{
|
||||
Variables: make(map[string]types.Value),
|
||||
})
|
||||
|
||||
case types.OpExitScope:
|
||||
// Pop the current scope
|
||||
if len(vm.scopes) > 0 {
|
||||
vm.scopes = vm.scopes[:len(vm.scopes)-1]
|
||||
}
|
||||
|
||||
case types.OpNewTable:
|
||||
vm.push(types.NewTableValue())
|
||||
|
||||
case types.OpSetIndex:
|
||||
value := vm.pop()
|
||||
key := vm.pop()
|
||||
tableVal := vm.pop()
|
||||
|
||||
if tableVal.Type != types.TypeTable {
|
||||
fmt.Println("Error: attempt to index non-table value")
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
table := tableVal.Data.(*types.Table)
|
||||
table.Set(key, value)
|
||||
vm.push(tableVal)
|
||||
|
||||
case types.OpGetIndex:
|
||||
key := vm.pop()
|
||||
tableVal := vm.pop()
|
||||
|
||||
if tableVal.Type != types.TypeTable {
|
||||
fmt.Println("Error: attempt to index non-table value")
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
table := tableVal.Data.(*types.Table)
|
||||
value := table.Get(key)
|
||||
vm.push(value)
|
||||
|
||||
case types.OpDup:
|
||||
if vm.sp > 0 {
|
||||
vm.push(vm.stack[vm.sp-1])
|
||||
}
|
||||
|
||||
case types.OpPop:
|
||||
vm.pop()
|
||||
|
||||
case types.OpEcho:
|
||||
value := vm.pop()
|
||||
switch value.Type {
|
||||
case types.TypeString:
|
||||
fmt.Println(value.Data.(string))
|
||||
case types.TypeNumber:
|
||||
fmt.Println(value.Data.(float64))
|
||||
case types.TypeBoolean:
|
||||
fmt.Println(value.Data.(bool))
|
||||
case types.TypeNull:
|
||||
fmt.Println("nil")
|
||||
case types.TypeTable:
|
||||
fmt.Println(vm.formatTable(value.Data.(*types.Table)))
|
||||
case types.TypeFunction:
|
||||
fmt.Println("<function>")
|
||||
}
|
||||
|
||||
// Jump instructions
|
||||
case types.OpJumpIfFalse:
|
||||
condition := vm.pop()
|
||||
// Consider falsy: false, null, 0
|
||||
shouldJump := false
|
||||
|
||||
if condition.Type == types.TypeBoolean && !condition.Data.(bool) {
|
||||
shouldJump = true
|
||||
} else if condition.Type == types.TypeNull {
|
||||
shouldJump = true
|
||||
} else if condition.Type == types.TypeNumber && condition.Data.(float64) == 0 {
|
||||
shouldJump = true
|
||||
}
|
||||
|
||||
if shouldJump {
|
||||
ip = instruction.Operand - 1 // -1 because loop will increment
|
||||
}
|
||||
|
||||
case types.OpJump:
|
||||
ip = instruction.Operand - 1 // -1 because loop will increment
|
||||
|
||||
// Function instructions
|
||||
case types.OpFunction:
|
||||
constIndex := instruction.Operand
|
||||
function := vm.constants[constIndex].(*types.Function)
|
||||
// Use the helper function to create a proper function value
|
||||
vm.push(types.NewFunctionValue(function))
|
||||
|
||||
case types.OpCall:
|
||||
numArgs := instruction.Operand
|
||||
|
||||
// The function is at position sp-numArgs-1
|
||||
if vm.sp <= numArgs {
|
||||
fmt.Println("Error: stack underflow during function call")
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
fnVal := vm.stack[vm.sp-numArgs-1]
|
||||
|
||||
if fnVal.Type != types.TypeFunction {
|
||||
fmt.Printf("Error: attempt to call non-function value\n")
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
function, ok := fnVal.Data.(*types.Function)
|
||||
if !ok {
|
||||
fmt.Printf("Error: function data is invalid\n")
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we have the correct number of arguments
|
||||
if numArgs != function.NumParams {
|
||||
fmt.Printf("Error: function expects %d arguments, got %d\n",
|
||||
function.NumParams, numArgs)
|
||||
vm.push(types.NewNull())
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a new call frame
|
||||
frame := Frame{
|
||||
Function: function,
|
||||
IP: 0,
|
||||
BasePointer: vm.sp - numArgs - 1, // Below the function and args
|
||||
ReturnAddr: ip,
|
||||
Upvalues: make([]*types.Upvalue, len(function.UpvalueIndexes)),
|
||||
}
|
||||
|
||||
// Save the current frame
|
||||
vm.fp++
|
||||
if vm.fp >= len(vm.frames) {
|
||||
// Grow the frame stack if needed
|
||||
newFrames := make([]Frame, len(vm.frames)*2)
|
||||
copy(newFrames, vm.frames)
|
||||
vm.frames = newFrames
|
||||
}
|
||||
vm.frames[vm.fp] = frame
|
||||
|
||||
// Save the current constants
|
||||
oldConstants := vm.constants
|
||||
// Switch to function's constants
|
||||
vm.constants = function.Constants
|
||||
|
||||
// Run the function code
|
||||
returnValue := vm.runCode(function.Instructions, frame.BasePointer)
|
||||
|
||||
// Restore the old constants
|
||||
vm.constants = oldConstants
|
||||
|
||||
// Restore state
|
||||
vm.fp--
|
||||
|
||||
// Replace the function with the return value
|
||||
vm.stack[frame.BasePointer] = returnValue
|
||||
|
||||
// Adjust the stack pointer to remove the arguments
|
||||
vm.sp = frame.BasePointer + 1
|
||||
|
||||
case types.OpReturn:
|
||||
returnValue := vm.pop()
|
||||
|
||||
// If we're in a function call, return to the caller
|
||||
if vm.fp >= 0 {
|
||||
frame := vm.frames[vm.fp]
|
||||
|
||||
// Restore the stack to just below the function
|
||||
vm.sp = frame.BasePointer
|
||||
|
||||
// Push the return value
|
||||
vm.push(returnValue)
|
||||
|
||||
// Return to the caller
|
||||
return returnValue
|
||||
}
|
||||
|
||||
// Top-level return
|
||||
vm.push(returnValue)
|
||||
return returnValue
|
||||
|
||||
// Arithmetic operations
|
||||
case types.OpAdd:
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
result := left.Data.(float64) + right.Data.(float64)
|
||||
vm.push(types.NewNumber(result))
|
||||
} else if left.Type == types.TypeString && right.Type == types.TypeString {
|
||||
// String concatenation
|
||||
result := left.Data.(string) + right.Data.(string)
|
||||
vm.push(types.NewString(result))
|
||||
} else {
|
||||
fmt.Println("Error: cannot add values of different types")
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpSubtract:
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
result := left.Data.(float64) - right.Data.(float64)
|
||||
vm.push(types.NewNumber(result))
|
||||
} else {
|
||||
fmt.Println("Error: cannot subtract non-number values")
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpMultiply:
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
result := left.Data.(float64) * right.Data.(float64)
|
||||
vm.push(types.NewNumber(result))
|
||||
} else {
|
||||
fmt.Println("Error: cannot multiply non-number values")
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpDivide:
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
// Check for division by zero
|
||||
if right.Data.(float64) == 0 {
|
||||
fmt.Println("Error: division by zero")
|
||||
vm.push(types.NewNull())
|
||||
} else {
|
||||
result := left.Data.(float64) / right.Data.(float64)
|
||||
vm.push(types.NewNumber(result))
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Error: cannot divide non-number values")
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpNegate:
|
||||
operand := vm.pop()
|
||||
|
||||
if operand.Type == types.TypeNumber {
|
||||
result := -operand.Data.(float64)
|
||||
vm.push(types.NewNumber(result))
|
||||
} else {
|
||||
fmt.Println("Error: cannot negate non-number value")
|
||||
vm.push(types.NewNull())
|
||||
}
|
||||
|
||||
case types.OpEqual:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for equality comparison")
|
||||
vm.push(types.NewBoolean(false))
|
||||
continue
|
||||
}
|
||||
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
vm.push(types.NewBoolean(left.Equal(right)))
|
||||
|
||||
case types.OpNotEqual:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for inequality comparison")
|
||||
vm.push(types.NewBoolean(true))
|
||||
continue
|
||||
}
|
||||
|
||||
right := vm.pop()
|
||||
left := vm.pop()
|
||||
vm.push(types.NewBoolean(!left.Equal(right)))
|
||||
|
||||
case types.OpLessThan:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for less-than comparison")
|
||||
vm.push(types.NewBoolean(false))
|
||||
continue
|
||||
}
|
||||
|
||||
// Peek at values first before popping
|
||||
right := vm.stack[vm.sp-1]
|
||||
left := vm.stack[vm.sp-2]
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
// Now pop them
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
vm.push(types.NewBoolean(left.Data.(float64) < right.Data.(float64)))
|
||||
} else {
|
||||
// Pop the values to maintain stack balance
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
fmt.Println("Error: cannot compare non-number values with <")
|
||||
vm.push(types.NewBoolean(false))
|
||||
}
|
||||
|
||||
case types.OpGreaterThan:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for greater-than comparison")
|
||||
vm.push(types.NewBoolean(false))
|
||||
continue
|
||||
}
|
||||
|
||||
// Peek at values first before popping
|
||||
right := vm.stack[vm.sp-1]
|
||||
left := vm.stack[vm.sp-2]
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
// Now pop them
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
vm.push(types.NewBoolean(left.Data.(float64) > right.Data.(float64)))
|
||||
} else {
|
||||
// Pop the values to maintain stack balance
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
fmt.Println("Error: cannot compare non-number values with >")
|
||||
vm.push(types.NewBoolean(false))
|
||||
}
|
||||
|
||||
case types.OpLessEqual:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for less-equal comparison")
|
||||
vm.push(types.NewBoolean(false))
|
||||
continue
|
||||
}
|
||||
|
||||
// Peek at values first before popping
|
||||
right := vm.stack[vm.sp-1]
|
||||
left := vm.stack[vm.sp-2]
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
// Now pop them
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
vm.push(types.NewBoolean(left.Data.(float64) <= right.Data.(float64)))
|
||||
} else {
|
||||
// Pop the values to maintain stack balance
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
fmt.Println("Error: cannot compare non-number values with <=")
|
||||
vm.push(types.NewBoolean(false))
|
||||
}
|
||||
|
||||
case types.OpGreaterEqual:
|
||||
if vm.sp < 2 {
|
||||
fmt.Println("Error: not enough operands for greater-equal comparison")
|
||||
vm.push(types.NewBoolean(false))
|
||||
continue
|
||||
}
|
||||
|
||||
// Peek at values first before popping
|
||||
right := vm.stack[vm.sp-1]
|
||||
left := vm.stack[vm.sp-2]
|
||||
|
||||
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
||||
// Now pop them
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
vm.push(types.NewBoolean(left.Data.(float64) >= right.Data.(float64)))
|
||||
} else {
|
||||
// Pop the values to maintain stack balance
|
||||
vm.pop()
|
||||
vm.pop()
|
||||
fmt.Println("Error: cannot compare non-number values with >=")
|
||||
vm.push(types.NewBoolean(false))
|
||||
}
|
||||
|
||||
case types.OpNot:
|
||||
operand := vm.pop()
|
||||
|
||||
// Consider falsy: false, null, 0
|
||||
isFalsy := false
|
||||
|
||||
if operand.Type == types.TypeBoolean && !operand.Data.(bool) {
|
||||
isFalsy = true
|
||||
} else if operand.Type == types.TypeNull {
|
||||
isFalsy = true
|
||||
} else if operand.Type == types.TypeNumber && operand.Data.(float64) == 0 {
|
||||
isFalsy = true
|
||||
}
|
||||
|
||||
vm.push(types.NewBoolean(isFalsy))
|
||||
}
|
||||
}
|
||||
|
||||
// Return null for the top-level code when no explicit return is found
|
||||
return types.NewNull()
|
||||
}
|
||||
|
||||
func (vm *VM) push(value types.Value) {
|
||||
if vm.sp >= len(vm.stack) {
|
||||
// Grow stack if needed
|
||||
newStack := make([]types.Value, len(vm.stack)*2)
|
||||
copy(newStack, vm.stack)
|
||||
vm.stack = newStack
|
||||
}
|
||||
vm.stack[vm.sp] = value
|
||||
vm.sp++
|
||||
}
|
||||
|
||||
func (vm *VM) pop() types.Value {
|
||||
if vm.sp <= 0 {
|
||||
// Return null instead of causing a panic when trying to pop from an empty stack
|
||||
fmt.Println("Stack underflow error")
|
||||
return types.NewNull()
|
||||
}
|
||||
vm.sp--
|
||||
return vm.stack[vm.sp]
|
||||
}
|
||||
|
||||
func (vm *VM) formatTable(table *types.Table) string {
|
||||
result := "{"
|
||||
for i, entry := range table.Entries {
|
||||
result += vm.formatValue(entry.Key) + " = " + vm.formatValue(entry.Value)
|
||||
if i < len(table.Entries)-1 {
|
||||
result += ", "
|
||||
}
|
||||
}
|
||||
result += "}"
|
||||
return result
|
||||
}
|
||||
|
||||
func (vm *VM) formatValue(value types.Value) string {
|
||||
switch value.Type {
|
||||
case types.TypeString:
|
||||
return "\"" + value.Data.(string) + "\""
|
||||
case types.TypeNumber:
|
||||
return fmt.Sprintf("%v", value.Data.(float64))
|
||||
case types.TypeBoolean:
|
||||
return fmt.Sprintf("%v", value.Data.(bool))
|
||||
case types.TypeNull:
|
||||
return "nil"
|
||||
case types.TypeTable:
|
||||
return vm.formatTable(value.Data.(*types.Table))
|
||||
case types.TypeFunction:
|
||||
return "<function>"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user