diff --git a/README.md b/README.md index a002562..525544d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ # Mako -Scripting language! \ No newline at end of file +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 +``` diff --git a/compiler/compiler.go b/compiler/compiler.go deleted file mode 100644 index 9213b6d..0000000 --- a/compiler/compiler.go +++ /dev/null @@ -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) - } -} diff --git a/lexer/lexer.go b/lexer/lexer.go deleted file mode 100644 index 8b64b8a..0000000 --- a/lexer/lexer.go +++ /dev/null @@ -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() - } -} diff --git a/mako.go b/mako.go index 8371d38..3ed0ebe 100644 --- a/mako.go +++ b/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") } diff --git a/parser/ast.go b/parser/ast.go deleted file mode 100644 index 815e55f..0000000 --- a/parser/ast.go +++ /dev/null @@ -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 } diff --git a/parser/parser.go b/parser/parser.go deleted file mode 100644 index 266f464..0000000 --- a/parser/parser.go +++ /dev/null @@ -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 -} diff --git a/tests/funcs.mako b/tests/funcs.mako deleted file mode 100644 index 2cc77ba..0000000 --- a/tests/funcs.mako +++ /dev/null @@ -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) \ No newline at end of file diff --git a/tests/test.mako b/tests/test.mako deleted file mode 100644 index bc6114f..0000000 --- a/tests/test.mako +++ /dev/null @@ -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 \ No newline at end of file diff --git a/types/ast.go b/types/ast.go new file mode 100644 index 0000000..0c721c1 --- /dev/null +++ b/types/ast.go @@ -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 "{ ... }" +} diff --git a/types/bytecode.go b/types/bytecode.go new file mode 100644 index 0000000..f97dc8d --- /dev/null +++ b/types/bytecode.go @@ -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, + } +} diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 0000000..23256e5 --- /dev/null +++ b/types/errors.go @@ -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, + } +} diff --git a/types/token.go b/types/token.go new file mode 100644 index 0000000..d7e59d6 --- /dev/null +++ b/types/token.go @@ -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", +} diff --git a/types/types.go b/types/types.go deleted file mode 100644 index 7a6a5fc..0000000 --- a/types/types.go +++ /dev/null @@ -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() -} diff --git a/types/value.go b/types/value.go new file mode 100644 index 0000000..1b914bf --- /dev/null +++ b/types/value.go @@ -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 "" + } + return fmt.Sprintf("", 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("", &t) +} diff --git a/vm/vm.go b/vm/vm.go deleted file mode 100644 index 6a25214..0000000 --- a/vm/vm.go +++ /dev/null @@ -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("") - } - - // 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 "" - default: - return "unknown" - } -}