logical conditions, elseif

This commit is contained in:
Sky Johnson 2025-05-06 15:45:11 -05:00
parent 67d586c789
commit 1bc3357aff
5 changed files with 216 additions and 38 deletions

View File

@ -157,34 +157,87 @@ func (c *compiler) compileExpression(expr parser.Expression) {
// Arithmetic expressions // Arithmetic expressions
case *parser.InfixExpression: case *parser.InfixExpression:
// Compile left and right expressions
c.compileExpression(e.Left)
c.compileExpression(e.Right)
// Generate the appropriate operation
switch e.Operator { switch e.Operator {
case "+": case "and":
c.emit(types.OpAdd, 0) // Compile left operand
case "-": c.compileExpression(e.Left)
c.emit(types.OpSubtract, 0)
case "*": // Duplicate to check condition
c.emit(types.OpMultiply, 0) c.emit(types.OpDup, 0)
case "/":
c.emit(types.OpDivide, 0) // Jump if false (short-circuit)
case "==": jumpFalsePos := len(c.instructions)
c.emit(types.OpEqual, 0) c.emit(types.OpJumpIfFalse, 0) // Will backpatch
case "!=":
c.emit(types.OpNotEqual, 0) // Pop the duplicate since we'll replace it
case "<": c.emit(types.OpPop, 0)
c.emit(types.OpLessThan, 0)
case ">": // Compile right operand
c.emit(types.OpGreaterThan, 0) c.compileExpression(e.Right)
case "<=":
c.emit(types.OpLessEqual, 0) // Jump target for short-circuit
case ">=": endPos := len(c.instructions)
c.emit(types.OpGreaterEqual, 0) 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: default:
panic(fmt.Sprintf("Unknown infix operator: %s", e.Operator)) // 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: case *parser.PrefixExpression:
@ -195,6 +248,8 @@ func (c *compiler) compileExpression(expr parser.Expression) {
switch e.Operator { switch e.Operator {
case "-": case "-":
c.emit(types.OpNegate, 0) c.emit(types.OpNegate, 0)
case "not":
c.emit(types.OpNot, 0)
default: default:
panic(fmt.Sprintf("Unknown prefix operator: %s", e.Operator)) panic(fmt.Sprintf("Unknown prefix operator: %s", e.Operator))
} }

View File

@ -33,6 +33,10 @@ const (
TokenLessEqual TokenLessEqual
TokenGreaterEqual TokenGreaterEqual
TokenEnd TokenEnd
TokenAnd
TokenOr
TokenNot
TokenElseIf
) )
type Token struct { type Token struct {
@ -150,12 +154,20 @@ func (l *Lexer) NextToken() Token {
tok.Type = TokenThen tok.Type = TokenThen
case "else": case "else":
tok.Type = TokenElse tok.Type = TokenElse
case "elseif":
tok.Type = TokenElseIf
case "true": case "true":
tok.Type = TokenTrue tok.Type = TokenTrue
case "false": case "false":
tok.Type = TokenFalse tok.Type = TokenFalse
case "end": case "end":
tok.Type = TokenEnd tok.Type = TokenEnd
case "and":
tok.Type = TokenAnd
case "or":
tok.Type = TokenOr
case "not":
tok.Type = TokenNot
default: default:
tok.Type = TokenIdentifier tok.Type = TokenIdentifier
} }

View File

@ -11,10 +11,14 @@ import (
const ( const (
_ int = iota _ int = iota
LOWEST LOWEST
SUM // +, - LOGICAL_OR // or
PRODUCT // *, / LOGICAL_AND // and
PREFIX // -X or !X EQUALITY // ==, !=
INDEX // array[index] COMPARISON // <, >, <=, >=
SUM // +, -
PRODUCT // *, /
PREFIX // -X or !X
INDEX // array[index]
) )
var precedences = map[lexer.TokenType]int{ var precedences = map[lexer.TokenType]int{
@ -23,12 +27,14 @@ var precedences = map[lexer.TokenType]int{
lexer.TokenStar: PRODUCT, lexer.TokenStar: PRODUCT,
lexer.TokenSlash: PRODUCT, lexer.TokenSlash: PRODUCT,
lexer.TokenLeftBracket: INDEX, lexer.TokenLeftBracket: INDEX,
lexer.TokenEqualEqual: LOWEST + 1, lexer.TokenEqualEqual: EQUALITY,
lexer.TokenNotEqual: LOWEST + 1, lexer.TokenNotEqual: EQUALITY,
lexer.TokenLessThan: LOWEST + 1, lexer.TokenLessThan: COMPARISON,
lexer.TokenGreaterThan: LOWEST + 1, lexer.TokenGreaterThan: COMPARISON,
lexer.TokenLessEqual: LOWEST + 1, lexer.TokenLessEqual: COMPARISON,
lexer.TokenGreaterEqual: LOWEST + 1, lexer.TokenGreaterEqual: COMPARISON,
lexer.TokenAnd: LOGICAL_AND,
lexer.TokenOr: LOGICAL_OR,
} }
type ( type (
@ -67,6 +73,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(lexer.TokenThen, p.parseUnexpectedToken) p.registerPrefix(lexer.TokenThen, p.parseUnexpectedToken)
p.registerPrefix(lexer.TokenTrue, p.parseBooleanLiteral) p.registerPrefix(lexer.TokenTrue, p.parseBooleanLiteral)
p.registerPrefix(lexer.TokenFalse, p.parseBooleanLiteral) p.registerPrefix(lexer.TokenFalse, p.parseBooleanLiteral)
p.registerPrefix(lexer.TokenNot, p.parsePrefixExpression)
// Initialize infix parse functions // Initialize infix parse functions
p.infixParseFns = make(map[lexer.TokenType]infixParseFn) p.infixParseFns = make(map[lexer.TokenType]infixParseFn)
@ -75,6 +82,8 @@ func New(l *lexer.Lexer) *Parser {
p.registerInfix(lexer.TokenStar, p.parseInfixExpression) p.registerInfix(lexer.TokenStar, p.parseInfixExpression)
p.registerInfix(lexer.TokenSlash, p.parseInfixExpression) p.registerInfix(lexer.TokenSlash, p.parseInfixExpression)
p.registerInfix(lexer.TokenLeftBracket, p.parseIndexExpression) p.registerInfix(lexer.TokenLeftBracket, p.parseIndexExpression)
p.registerInfix(lexer.TokenAnd, p.parseInfixExpression)
p.registerInfix(lexer.TokenOr, p.parseInfixExpression)
// Register comparison operators // Register comparison operators
p.registerInfix(lexer.TokenEqualEqual, p.parseInfixExpression) p.registerInfix(lexer.TokenEqualEqual, p.parseInfixExpression)
@ -469,8 +478,9 @@ func (p *Parser) parseIfExpression() Expression {
consequence := &BlockStatement{Token: p.curToken} consequence := &BlockStatement{Token: p.curToken}
consequence.Statements = []Statement{} consequence.Statements = []Statement{}
// Parse statements until we hit 'else' or 'end' // Parse statements until we hit 'else', 'elseif', or 'end'
for !p.curTokenIs(lexer.TokenElse) && !p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) { for !p.curTokenIs(lexer.TokenElse) && !p.curTokenIs(lexer.TokenElseIf) &&
!p.curTokenIs(lexer.TokenEnd) && !p.curTokenIs(lexer.TokenEOF) {
stmt := p.parseStatement() stmt := p.parseStatement()
consequence.Statements = append(consequence.Statements, stmt) consequence.Statements = append(consequence.Statements, stmt)
p.nextToken() p.nextToken()
@ -478,6 +488,23 @@ func (p *Parser) parseIfExpression() Expression {
expression.Consequence = consequence 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' // Check for 'else'
if p.curTokenIs(lexer.TokenElse) { if p.curTokenIs(lexer.TokenElse) {
p.nextToken() // Skip 'else' p.nextToken() // Skip 'else'
@ -506,6 +533,73 @@ func (p *Parser) parseIfExpression() Expression {
return expression 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) parseErrorToken() Expression { func (p *Parser) parseErrorToken() Expression {
msg := fmt.Sprintf("unexpected token: %s", p.curToken.Value) msg := fmt.Sprintf("unexpected token: %s", p.curToken.Value)
p.errors = append(p.errors, msg) p.errors = append(p.errors, msg)

View File

@ -39,6 +39,7 @@ const (
OpGreaterThan OpGreaterThan
OpLessEqual OpLessEqual
OpGreaterEqual OpGreaterEqual
OpNot
) )
type Instruction struct { type Instruction struct {

View File

@ -409,6 +409,22 @@ func (vm *VM) Run(bytecode *types.Bytecode) {
fmt.Println("Error: cannot compare non-number values with >=") fmt.Println("Error: cannot compare non-number values with >=")
vm.push(types.NewBoolean(false)) 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))
} }
} }
} }