if expressions

This commit is contained in:
Robin 2025-02-02 14:15:16 +01:00
parent f98d0b8d18
commit 9e6e3bd1e4
12 changed files with 270 additions and 15 deletions

View File

@ -84,12 +84,11 @@ type Opcode string
const ( const (
// Two operands // Two operands
Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax
Add Opcode = "add" Add Opcode = "add"
Sub Opcode = "sub" Sub Opcode = "sub"
Imul Opcode = "imul" Imul Opcode = "imul"
Cmp Opcode = "cmp" Cmp Opcode = "cmp"
SetCC Opcode = "setcc"
// One operand // One operand
Idiv Opcode = "idiv" Idiv Opcode = "idiv"
@ -130,6 +129,27 @@ func (i *SimpleInstruction) InstructionString() string {
return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight)) return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight))
} }
type Label string
func (l Label) InstructionString() string {
return fmt.Sprintf("%s:", l)
}
type JumpCCInstruction struct {
Cond CondCode
Dst string
}
func (j *JumpCCInstruction) InstructionString() string {
return fmt.Sprintf("j%s %s", j.Cond, j.Dst)
}
type JmpInstruction string
func (j JmpInstruction) InstructionString() string {
return fmt.Sprintf("jmp %s", j)
}
type SetCCInstruction struct { type SetCCInstruction struct {
Cond CondCode Cond CondCode
Dst Operand Dst Operand

View File

@ -74,6 +74,36 @@ func cgInstruction(i ttir.Instruction) []Instruction {
} }
case *ttir.Binary: case *ttir.Binary:
return cgBinary(i) return cgBinary(i)
case ttir.Label:
return []Instruction{Label(i)}
case *ttir.JumpIfZero:
return []Instruction{
&SimpleInstruction{
Opcode: Cmp,
Lhs: toAsmOperand(i.Value),
Rhs: Imm(0),
},
&JumpCCInstruction{
Cond: Equal,
Dst: i.Label,
},
}
case *ttir.JumpIfNotZero:
return []Instruction{
&SimpleInstruction{
Opcode: Cmp,
Lhs: toAsmOperand(i.Value),
Rhs: Imm(0),
},
&JumpCCInstruction{
Cond: NotEqual,
Dst: i.Label,
},
}
case ttir.Jump:
return []Instruction{JmpInstruction(i)}
case *ttir.Copy:
return []Instruction{&SimpleInstruction{Opcode: Mov, Lhs: toAsmOperand(i.Dst), Rhs: toAsmOperand(i.Src)}}
} }
return []Instruction{} return []Instruction{}
@ -190,6 +220,8 @@ func rpInstruction(i Instruction, r *replacePseudoPass) Instruction {
Cond: i.Cond, Cond: i.Cond,
Dst: pseudoToStack(i.Dst, r), Dst: pseudoToStack(i.Dst, r),
} }
case *JumpCCInstruction, JmpInstruction, Label:
return i
} }
panic("invalid instruction") panic("invalid instruction")
@ -295,6 +327,8 @@ func fixupInstruction(i Instruction) []Instruction {
return []Instruction{i} return []Instruction{i}
case *SetCCInstruction: case *SetCCInstruction:
return []Instruction{i}
case *JumpCCInstruction, JmpInstruction, Label:
return []Instruction{i} return []Instruction{i}
} }

View File

@ -10,6 +10,13 @@ import (
_ "embed" _ "embed"
) )
var extraLabelId int64 = 0
func extraLabel() string {
extraLabelId += 1
return fmt.Sprintf("qbe.extra.%d", extraLabelId)
}
//go:embed qbe_stub.asm //go:embed qbe_stub.asm
var Stub string var Stub string
@ -118,7 +125,30 @@ func emitInstruction(w io.Writer, i ttir.Instruction) error {
if err := emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs)); err != nil { if err := emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs)); err != nil {
return err return err
} }
case *ttir.Copy:
if err := emitf(w, "\t%s =l copy %s\n", emitOperand(i.Dst), emitOperand(i.Src)); err != nil {
return err
}
case ttir.Label:
if err := emitf(w, "@%s\n", string(i)); err != nil {
return err
}
case ttir.Jump:
if err := emitf(w, "\tjmp @%s\n", string(i)); err != nil {
return err
}
case *ttir.JumpIfNotZero:
after := extraLabel()
if err := emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), i.Label, after, after); err != nil {
return err
}
case *ttir.JumpIfZero:
after := extraLabel()
if err := emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), after, i.Label, after); err != nil {
return err
}
default:
panic("unkown instruction")
} }
return nil return nil

View File

@ -165,3 +165,28 @@ func (be *BlockExpression) String() string {
return builder.String() return builder.String()
} }
type IfExpression struct {
Token token.Token // The 'if' token
Condition Expression
Then Expression
// Can be nil
Else Expression
}
func (ie *IfExpression) expressionNode() {}
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
func (ie *IfExpression) String() string {
var builder strings.Builder
builder.WriteString("(if\n\t")
builder.WriteString(ie.Then.String())
if ie.Else != nil {
builder.WriteString(" else in ")
builder.WriteString(ie.Else.String())
}
builder.WriteString(")")
return builder.String()
}

View File

@ -56,6 +56,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefixFn(token.False, p.parseBooleanExpression) p.registerPrefixFn(token.False, p.parseBooleanExpression)
p.registerPrefixFn(token.OpenParen, p.parseGroupedExpression) p.registerPrefixFn(token.OpenParen, p.parseGroupedExpression)
p.registerPrefixFn(token.OpenBrack, p.parseBlockExpression) p.registerPrefixFn(token.OpenBrack, p.parseBlockExpression)
p.registerPrefixFn(token.If, p.parseIfExpression)
p.infixParseFns = make(map[token.TokenType]infixParseFn) p.infixParseFns = make(map[token.TokenType]infixParseFn)
p.registerInfixFn(token.Plus, p.parseBinaryExpression) p.registerInfixFn(token.Plus, p.parseBinaryExpression)
@ -334,3 +335,36 @@ func (p *Parser) parseBlockExpression() ast.Expression {
return block return block
} }
func (p *Parser) parseIfExpression() ast.Expression {
if ok, errExpr := p.expect(token.If); !ok {
return errExpr
}
ifExpr := &ast.IfExpression{Token: p.curToken}
p.nextToken()
ifExpr.Condition = p.parseExpression(PrecLowest)
if p.peekTokenIs(token.OpenBrack) {
p.nextToken()
ifExpr.Then = p.parseBlockExpression()
} else {
if ok, errExpr := p.expectPeek(token.In); !ok {
return errExpr
}
p.nextToken()
ifExpr.Then = p.parseExpression(PrecLowest)
}
if p.peekTokenIs(token.Else) {
p.nextToken()
p.nextToken()
ifExpr.Else = p.parseExpression(PrecLowest)
} else {
ifExpr.Else = nil
}
return ifExpr
}

View File

@ -140,3 +140,32 @@ func (be *BlockExpression) String() string {
return builder.String() return builder.String()
} }
type IfExpression struct {
Token token.Token // The 'if' token
Condition Expression
Then Expression
// Can be nil
Else Expression
ReturnType types.Type
}
func (ie *IfExpression) expressionNode() {}
func (ie *IfExpression) Type() types.Type {
return ie.ReturnType
}
func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal }
func (ie *IfExpression) String() string {
var builder strings.Builder
builder.WriteString("(if\n\t")
builder.WriteString(ie.Then.String())
if ie.Else != nil {
builder.WriteString(" else in ")
builder.WriteString(ie.Else.String())
}
builder.WriteString(")")
return builder.String()
}

View File

@ -1,3 +1,5 @@
fn main() = { fn main() = {
5 == 3 if 3 == 3
in 4
else 3
}; };

View File

@ -16,9 +16,12 @@ type Token struct {
} }
var keywords = map[string]TokenType{ var keywords = map[string]TokenType{
"fn": Fn, "else": Else,
"true": True,
"false": False, "false": False,
"fn": Fn,
"if": If,
"in": In,
"true": True,
} }
const ( const (
@ -48,9 +51,12 @@ const (
GreaterThanEqual TokenType = ">=" GreaterThanEqual TokenType = ">="
// Keywords // Keywords
Fn TokenType = "FN" Else TokenType = "ELSE"
True TokenType = "TRUE"
False TokenType = "FALSE" False TokenType = "FALSE"
Fn TokenType = "FN"
If TokenType = "IF"
In TokenType = "IN"
True TokenType = "TRUE"
) )
func LookupKeyword(literal string) TokenType { func LookupKeyword(literal string) TokenType {

View File

@ -7,11 +7,18 @@ import (
"robaertschi.xyz/robaertschi/tt/types" "robaertschi.xyz/robaertschi/tt/types"
) )
var uniqueId int64 var uniqueTempId int64
func temp() string { func temp() string {
uniqueId += 1 uniqueTempId += 1
return fmt.Sprintf("temp.%d", uniqueId) return fmt.Sprintf("temp.%d", uniqueTempId)
}
var uniqueLabelId int64
func tempLabel() string {
uniqueLabelId += 1
return fmt.Sprintf("lbl.%d", uniqueLabelId)
} }
func EmitProgram(program *tast.Program) *Program { func EmitProgram(program *tast.Program) *Program {
@ -82,6 +89,32 @@ func emitExpression(expr tast.Expression) (Operand, []Instruction) {
} }
return value, instructions return value, instructions
case *tast.IfExpression:
// if (cond -> false jump to "else") {
// ...
// } jump to end of if
// else: else {
// ...
// } endOfIf:
elseLabel := tempLabel()
endOfIfLabel := tempLabel()
dst := &Var{Value: temp()}
condDst, instructions := emitExpression(expr.Condition)
instructions = append(instructions, &JumpIfZero{Value: condDst, Label: elseLabel})
thenDst, thenInstructions := emitExpression(expr.Then)
instructions = append(instructions, thenInstructions...)
instructions = append(instructions, &Copy{Src: thenDst, Dst: dst}, Jump(endOfIfLabel))
instructions = append(instructions, Label(elseLabel))
if expr.Else != nil {
elseDst, elseInstructions := emitExpression(expr.Else)
instructions = append(instructions, elseInstructions...)
instructions = append(instructions, &Copy{Src: elseDst, Dst: dst})
}
instructions = append(instructions, Label(endOfIfLabel))
return dst, instructions
} }
panic("unhandled tast.Expression case in ir emitter") panic("unhandled tast.Expression case in ir emitter")
} }

View File

@ -67,6 +67,16 @@ func (b *Binary) String() string {
} }
func (b *Binary) instruction() {} func (b *Binary) instruction() {}
type Copy struct {
Src Operand
Dst Operand
}
func (c *Copy) String() string {
return fmt.Sprintf("%s = copy %s\n", c.Dst, c.Src)
}
func (c *Copy) instruction() {}
type JumpIfZero struct { type JumpIfZero struct {
Value Operand Value Operand
Label string Label string

View File

@ -7,6 +7,7 @@ import (
"robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/ast"
"robaertschi.xyz/robaertschi/tt/tast" "robaertschi.xyz/robaertschi/tt/tast"
"robaertschi.xyz/robaertschi/tt/token" "robaertschi.xyz/robaertschi/tt/token"
"robaertschi.xyz/robaertschi/tt/types"
) )
type Checker struct { type Checker struct {
@ -95,6 +96,26 @@ func (c *Checker) checkExpression(expr tast.Expression) error {
errs = append(errs, c.checkExpression(expr.ReturnExpression)) errs = append(errs, c.checkExpression(expr.ReturnExpression))
} }
return errors.Join(errs...) return errors.Join(errs...)
case *tast.IfExpression:
condErr := c.checkExpression(expr.Condition)
if condErr == nil {
if !expr.Condition.Type().IsSameType(types.Bool) {
condErr = c.error(expr.Token, "the condition in the if should be a boolean, but got %q", expr.Condition.Type().Name())
}
}
thenErr := c.checkExpression(expr.Then)
if expr.Else == nil {
return errors.Join(condErr, thenErr)
}
elseErr := c.checkExpression(expr.Else)
if thenErr == nil && elseErr == nil {
if !expr.Then.Type().IsSameType(expr.Else.Type()) {
thenErr = c.error(expr.Token, "the then branch of type %q does not match with the else branch of type %q", expr.Then.Type().Name(), expr.Else.Type().Name())
}
}
return errors.Join(condErr, thenErr, elseErr)
} }
return fmt.Errorf("unhandled expression %T in type checker", expr) return fmt.Errorf("unhandled expression %T in type checker", expr)
} }

View File

@ -93,6 +93,17 @@ func (c *Checker) inferExpression(expr ast.Expression) (tast.Expression, error)
ReturnType: returnType, ReturnType: returnType,
ReturnExpression: returnExpr, ReturnExpression: returnExpr,
}, errors.Join(errs...) }, errors.Join(errs...)
case *ast.IfExpression:
cond, condErr := c.inferExpression(expr.Condition)
then, thenErr := c.inferExpression(expr.Then)
if expr.Else != nil {
elseExpr, elseErr := c.inferExpression(expr.Else)
return &tast.IfExpression{Token: expr.Token, Condition: cond, Then: then, Else: elseExpr, ReturnType: then.Type()}, errors.Join(condErr, thenErr, elseErr)
}
return &tast.IfExpression{Token: expr.Token, Condition: cond, Then: then, Else: nil, ReturnType: types.Unit}, errors.Join(condErr, thenErr)
} }
return nil, fmt.Errorf("unhandled expression in type inferer") return nil, fmt.Errorf("unhandled expression in type inferer")
} }