comments, if then else
This commit is contained in:
parent
7c24e21453
commit
3a323cd45a
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
/test.mako
|
@ -110,6 +110,10 @@ func (c *compiler) compileExpression(expr parser.Expression) {
|
|||||||
constIndex := c.addConstant(e.Value)
|
constIndex := c.addConstant(e.Value)
|
||||||
c.emit(types.OpConstant, constIndex)
|
c.emit(types.OpConstant, constIndex)
|
||||||
|
|
||||||
|
case *parser.BooleanLiteral:
|
||||||
|
constIndex := c.addConstant(e.Value)
|
||||||
|
c.emit(types.OpConstant, constIndex)
|
||||||
|
|
||||||
case *parser.Identifier:
|
case *parser.Identifier:
|
||||||
nameIndex := c.addConstant(e.Value)
|
nameIndex := c.addConstant(e.Value)
|
||||||
|
|
||||||
@ -147,7 +151,7 @@ func (c *compiler) compileExpression(expr parser.Expression) {
|
|||||||
c.compileExpression(e.Index)
|
c.compileExpression(e.Index)
|
||||||
c.emit(types.OpGetIndex, 0)
|
c.emit(types.OpGetIndex, 0)
|
||||||
|
|
||||||
// New expression types for arithmetic
|
// Arithmetic expressions
|
||||||
case *parser.InfixExpression:
|
case *parser.InfixExpression:
|
||||||
// Compile left and right expressions
|
// Compile left and right expressions
|
||||||
c.compileExpression(e.Left)
|
c.compileExpression(e.Left)
|
||||||
@ -163,6 +167,18 @@ func (c *compiler) compileExpression(expr parser.Expression) {
|
|||||||
c.emit(types.OpMultiply, 0)
|
c.emit(types.OpMultiply, 0)
|
||||||
case "/":
|
case "/":
|
||||||
c.emit(types.OpDivide, 0)
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("Unknown infix operator: %s", e.Operator))
|
panic(fmt.Sprintf("Unknown infix operator: %s", e.Operator))
|
||||||
}
|
}
|
||||||
@ -182,6 +198,80 @@ func (c *compiler) compileExpression(expr parser.Expression) {
|
|||||||
case *parser.GroupedExpression:
|
case *parser.GroupedExpression:
|
||||||
// Just compile the inner expression
|
// Just compile the inner expression
|
||||||
c.compileExpression(e.Expr)
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,18 @@ const (
|
|||||||
TokenSlash
|
TokenSlash
|
||||||
TokenLeftParen
|
TokenLeftParen
|
||||||
TokenRightParen
|
TokenRightParen
|
||||||
|
TokenIf
|
||||||
|
TokenThen
|
||||||
|
TokenElse
|
||||||
|
TokenTrue
|
||||||
|
TokenFalse
|
||||||
|
TokenEqualEqual
|
||||||
|
TokenNotEqual
|
||||||
|
TokenLessThan
|
||||||
|
TokenGreaterThan
|
||||||
|
TokenLessEqual
|
||||||
|
TokenGreaterEqual
|
||||||
|
TokenEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
@ -55,10 +67,37 @@ func (l *Lexer) NextToken() Token {
|
|||||||
var tok Token
|
var tok Token
|
||||||
|
|
||||||
l.skipWhitespace()
|
l.skipWhitespace()
|
||||||
|
l.skipComment()
|
||||||
|
|
||||||
switch l.ch {
|
switch l.ch {
|
||||||
case '=':
|
case '=':
|
||||||
tok = Token{Type: TokenEqual, Value: "="}
|
if l.peekChar() == '=' {
|
||||||
|
l.readChar() // consume the current '='
|
||||||
|
tok = Token{Type: TokenEqualEqual, Value: "=="}
|
||||||
|
} else {
|
||||||
|
tok = Token{Type: TokenEqual, Value: "="}
|
||||||
|
}
|
||||||
|
case '!':
|
||||||
|
if l.peekChar() == '=' {
|
||||||
|
l.readChar() // consume the current '!'
|
||||||
|
tok = Token{Type: TokenNotEqual, Value: "!="}
|
||||||
|
} else {
|
||||||
|
tok = Token{Type: TokenEOF, Value: ""} // Not supported yet
|
||||||
|
}
|
||||||
|
case '<':
|
||||||
|
if l.peekChar() == '=' {
|
||||||
|
l.readChar() // consume the current '<'
|
||||||
|
tok = Token{Type: TokenLessEqual, Value: "<="}
|
||||||
|
} else {
|
||||||
|
tok = Token{Type: TokenLessThan, Value: "<"}
|
||||||
|
}
|
||||||
|
case '>':
|
||||||
|
if l.peekChar() == '=' {
|
||||||
|
l.readChar() // consume the current '>'
|
||||||
|
tok = Token{Type: TokenGreaterEqual, Value: ">="}
|
||||||
|
} else {
|
||||||
|
tok = Token{Type: TokenGreaterThan, Value: ">"}
|
||||||
|
}
|
||||||
case ';':
|
case ';':
|
||||||
tok = Token{Type: TokenSemicolon, Value: ";"}
|
tok = Token{Type: TokenSemicolon, Value: ";"}
|
||||||
case '"':
|
case '"':
|
||||||
@ -74,7 +113,6 @@ func (l *Lexer) NextToken() Token {
|
|||||||
tok = Token{Type: TokenRightBracket, Value: "]"}
|
tok = Token{Type: TokenRightBracket, Value: "]"}
|
||||||
case ',':
|
case ',':
|
||||||
tok = Token{Type: TokenComma, Value: ","}
|
tok = Token{Type: TokenComma, Value: ","}
|
||||||
// New arithmetic operators
|
|
||||||
case '+':
|
case '+':
|
||||||
tok = Token{Type: TokenPlus, Value: "+"}
|
tok = Token{Type: TokenPlus, Value: "+"}
|
||||||
case '-':
|
case '-':
|
||||||
@ -92,9 +130,22 @@ func (l *Lexer) NextToken() Token {
|
|||||||
default:
|
default:
|
||||||
if isLetter(l.ch) {
|
if isLetter(l.ch) {
|
||||||
tok.Value = l.readIdentifier()
|
tok.Value = l.readIdentifier()
|
||||||
if tok.Value == "echo" {
|
switch tok.Value {
|
||||||
|
case "echo":
|
||||||
tok.Type = TokenEcho
|
tok.Type = TokenEcho
|
||||||
} else {
|
case "if":
|
||||||
|
tok.Type = TokenIf
|
||||||
|
case "then":
|
||||||
|
tok.Type = TokenThen
|
||||||
|
case "else":
|
||||||
|
tok.Type = TokenElse
|
||||||
|
case "true":
|
||||||
|
tok.Type = TokenTrue
|
||||||
|
case "false":
|
||||||
|
tok.Type = TokenFalse
|
||||||
|
case "end":
|
||||||
|
tok.Type = TokenEnd
|
||||||
|
default:
|
||||||
tok.Type = TokenIdentifier
|
tok.Type = TokenIdentifier
|
||||||
}
|
}
|
||||||
return tok
|
return tok
|
||||||
@ -153,3 +204,23 @@ func isLetter(ch byte) bool {
|
|||||||
func isDigit(ch byte) bool {
|
func isDigit(ch byte) bool {
|
||||||
return '0' <= ch && ch <= '9'
|
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()
|
||||||
|
l.readChar()
|
||||||
|
|
||||||
|
for l.ch != '\n' && l.ch != 0 {
|
||||||
|
l.readChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.skipWhitespace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
68
mako.go
Normal file
68
mako.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
@ -138,3 +138,21 @@ type GroupedExpression struct {
|
|||||||
|
|
||||||
func (ge *GroupedExpression) expressionNode() {}
|
func (ge *GroupedExpression) expressionNode() {}
|
||||||
func (ge *GroupedExpression) TokenLiteral() string { return ge.Token.Value }
|
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 }
|
||||||
|
@ -18,11 +18,17 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var precedences = map[lexer.TokenType]int{
|
var precedences = map[lexer.TokenType]int{
|
||||||
lexer.TokenPlus: SUM,
|
lexer.TokenPlus: SUM,
|
||||||
lexer.TokenMinus: SUM,
|
lexer.TokenMinus: SUM,
|
||||||
lexer.TokenStar: PRODUCT,
|
lexer.TokenStar: PRODUCT,
|
||||||
lexer.TokenSlash: PRODUCT,
|
lexer.TokenSlash: PRODUCT,
|
||||||
lexer.TokenLeftBracket: INDEX,
|
lexer.TokenLeftBracket: INDEX,
|
||||||
|
lexer.TokenEqualEqual: LOWEST + 1,
|
||||||
|
lexer.TokenNotEqual: LOWEST + 1,
|
||||||
|
lexer.TokenLessThan: LOWEST + 1,
|
||||||
|
lexer.TokenGreaterThan: LOWEST + 1,
|
||||||
|
lexer.TokenLessEqual: LOWEST + 1,
|
||||||
|
lexer.TokenGreaterEqual: LOWEST + 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -55,6 +61,9 @@ func New(l *lexer.Lexer) *Parser {
|
|||||||
p.registerPrefix(lexer.TokenLeftBrace, p.parseTableLiteral)
|
p.registerPrefix(lexer.TokenLeftBrace, p.parseTableLiteral)
|
||||||
p.registerPrefix(lexer.TokenMinus, p.parsePrefixExpression)
|
p.registerPrefix(lexer.TokenMinus, p.parsePrefixExpression)
|
||||||
p.registerPrefix(lexer.TokenLeftParen, p.parseGroupedExpression)
|
p.registerPrefix(lexer.TokenLeftParen, p.parseGroupedExpression)
|
||||||
|
p.registerPrefix(lexer.TokenIf, p.parseIfExpression) // New
|
||||||
|
p.registerPrefix(lexer.TokenTrue, p.parseBooleanLiteral) // New
|
||||||
|
p.registerPrefix(lexer.TokenFalse, p.parseBooleanLiteral) // New
|
||||||
|
|
||||||
// Initialize infix parse functions
|
// Initialize infix parse functions
|
||||||
p.infixParseFns = make(map[lexer.TokenType]infixParseFn)
|
p.infixParseFns = make(map[lexer.TokenType]infixParseFn)
|
||||||
@ -64,6 +73,14 @@ func New(l *lexer.Lexer) *Parser {
|
|||||||
p.registerInfix(lexer.TokenSlash, p.parseInfixExpression)
|
p.registerInfix(lexer.TokenSlash, p.parseInfixExpression)
|
||||||
p.registerInfix(lexer.TokenLeftBracket, p.parseIndexExpression)
|
p.registerInfix(lexer.TokenLeftBracket, p.parseIndexExpression)
|
||||||
|
|
||||||
|
// 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
|
// Read two tokens, so curToken and peekToken are both set
|
||||||
p.nextToken()
|
p.nextToken()
|
||||||
p.nextToken()
|
p.nextToken()
|
||||||
@ -415,3 +432,58 @@ func (p *Parser) parseGroupedExpression() Expression {
|
|||||||
Expr: exp,
|
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)
|
||||||
|
|
||||||
|
if !p.expectPeek(lexer.TokenThen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nextToken() // Skip 'then'
|
||||||
|
|
||||||
|
// Parse consequence (then block)
|
||||||
|
if p.curTokenIs(lexer.TokenLeftBrace) {
|
||||||
|
expression.Consequence = p.parseBlockStatement()
|
||||||
|
} else {
|
||||||
|
// For single statement without braces
|
||||||
|
stmt := &BlockStatement{Token: p.curToken}
|
||||||
|
stmt.Statements = []Statement{p.parseStatement()}
|
||||||
|
expression.Consequence = stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 'else'
|
||||||
|
if p.peekTokenIs(lexer.TokenElse) {
|
||||||
|
p.nextToken() // Skip current token
|
||||||
|
p.nextToken() // Skip 'else'
|
||||||
|
|
||||||
|
// Parse alternative (else block)
|
||||||
|
if p.curTokenIs(lexer.TokenLeftBrace) {
|
||||||
|
expression.Alternative = p.parseBlockStatement()
|
||||||
|
} else {
|
||||||
|
// For single statement without braces
|
||||||
|
stmt := &BlockStatement{Token: p.curToken}
|
||||||
|
stmt.Statements = []Statement{p.parseStatement()}
|
||||||
|
expression.Alternative = stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for 'end' if we had a then block without braces
|
||||||
|
if !p.curTokenIs(lexer.TokenRightBrace) && p.peekTokenIs(lexer.TokenEnd) {
|
||||||
|
p.nextToken() // Consume 'end'
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
45
repl.go
45
repl.go
@ -1,45 +0,0 @@
|
|||||||
// File: cmd/main.go
|
|
||||||
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 main() {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
lex := lexer.New(input)
|
|
||||||
p := parser.New(lex)
|
|
||||||
program := p.ParseProgram()
|
|
||||||
|
|
||||||
if len(p.Errors()) > 0 {
|
|
||||||
for _, err := range p.Errors() {
|
|
||||||
fmt.Printf("Error: %s\n", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bytecode := compiler.Compile(program)
|
|
||||||
virtualMachine.Run(bytecode)
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,6 +31,14 @@ const (
|
|||||||
OpMultiply
|
OpMultiply
|
||||||
OpDivide
|
OpDivide
|
||||||
OpNegate
|
OpNegate
|
||||||
|
OpJumpIfFalse
|
||||||
|
OpJump
|
||||||
|
OpEqual
|
||||||
|
OpNotEqual
|
||||||
|
OpLessThan
|
||||||
|
OpGreaterThan
|
||||||
|
OpLessEqual
|
||||||
|
OpGreaterEqual
|
||||||
)
|
)
|
||||||
|
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
@ -60,6 +68,10 @@ func NewNumber(n float64) Value {
|
|||||||
return Value{Type: TypeNumber, Data: n}
|
return Value{Type: TypeNumber, Data: n}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewBoolean(b bool) Value {
|
||||||
|
return Value{Type: TypeBoolean, Data: b}
|
||||||
|
}
|
||||||
|
|
||||||
// TableEntry maintains insertion order
|
// TableEntry maintains insertion order
|
||||||
type TableEntry struct {
|
type TableEntry struct {
|
||||||
Key Value
|
Key Value
|
||||||
|
187
vm/vm.go
187
vm/vm.go
@ -44,6 +44,10 @@ func (vm *VM) Run(bytecode *types.Bytecode) {
|
|||||||
vm.push(types.NewString(v))
|
vm.push(types.NewString(v))
|
||||||
case float64:
|
case float64:
|
||||||
vm.push(types.NewNumber(v))
|
vm.push(types.NewNumber(v))
|
||||||
|
case bool:
|
||||||
|
vm.push(types.NewBoolean(v))
|
||||||
|
case nil:
|
||||||
|
vm.push(types.NewNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.OpSetLocal:
|
case types.OpSetLocal:
|
||||||
@ -164,6 +168,27 @@ func (vm *VM) Run(bytecode *types.Bytecode) {
|
|||||||
fmt.Println(vm.formatTable(value.Data.(*types.Table)))
|
fmt.Println(vm.formatTable(value.Data.(*types.Table)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
// Arithmetic operations
|
// Arithmetic operations
|
||||||
case types.OpAdd:
|
case types.OpAdd:
|
||||||
right := vm.pop()
|
right := vm.pop()
|
||||||
@ -233,16 +258,178 @@ func (vm *VM) Run(bytecode *types.Bytecode) {
|
|||||||
fmt.Println("Error: cannot negate non-number value")
|
fmt.Println("Error: cannot negate non-number value")
|
||||||
vm.push(types.NewNull())
|
vm.push(types.NewNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comparison operators with safer implementation
|
||||||
|
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()
|
||||||
|
|
||||||
|
if left.Type != right.Type {
|
||||||
|
vm.push(types.NewBoolean(false))
|
||||||
|
} else {
|
||||||
|
switch left.Type {
|
||||||
|
case types.TypeNumber:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(float64) == right.Data.(float64)))
|
||||||
|
case types.TypeString:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(string) == right.Data.(string)))
|
||||||
|
case types.TypeBoolean:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(bool) == right.Data.(bool)))
|
||||||
|
case types.TypeNull:
|
||||||
|
vm.push(types.NewBoolean(true)) // null == null
|
||||||
|
default:
|
||||||
|
vm.push(types.NewBoolean(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
if left.Type != right.Type {
|
||||||
|
vm.push(types.NewBoolean(true))
|
||||||
|
} else {
|
||||||
|
switch left.Type {
|
||||||
|
case types.TypeNumber:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(float64) != right.Data.(float64)))
|
||||||
|
case types.TypeString:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(string) != right.Data.(string)))
|
||||||
|
case types.TypeBoolean:
|
||||||
|
vm.push(types.NewBoolean(left.Data.(bool) != right.Data.(bool)))
|
||||||
|
case types.TypeNull:
|
||||||
|
vm.push(types.NewBoolean(false)) // null != null is false
|
||||||
|
default:
|
||||||
|
vm.push(types.NewBoolean(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) push(value types.Value) {
|
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.stack[vm.sp] = value
|
||||||
vm.sp++
|
vm.sp++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VM) pop() types.Value {
|
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--
|
vm.sp--
|
||||||
return vm.stack[vm.sp]
|
return vm.stack[vm.sp]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user