diff --git a/asm/amd64/amd64.go b/asm/amd64/amd64.go index 009d674..785f784 100644 --- a/asm/amd64/amd64.go +++ b/asm/amd64/amd64.go @@ -51,8 +51,12 @@ func (f *Function) Emit() string { type Opcode string const ( - Mov Opcode = "mov" - Ret Opcode = "ret" + Mov Opcode = "mov" + Ret Opcode = "ret" + Add Opcode = "add" + Sub Opcode = "sub" + Imull Opcode = "imul" + Idiv Opcode = "idiv" ) type Instruction struct { @@ -113,3 +117,9 @@ type Imm int64 func (i Imm) OperandString(size OperandSize) string { return fmt.Sprintf("%d", i) } + +type Stack int64 + +func (s Stack) OperandString(size OperandSize) string { + return fmt.Sprintf("rbp(%d)", s) +} diff --git a/ast/ast.go b/ast/ast.go index a33dffe..a9f6fdb 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -73,3 +73,39 @@ type IntegerExpression struct { func (ie *IntegerExpression) expressionNode() {} func (ie *IntegerExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IntegerExpression) String() string { return ie.Token.Literal } + +//go:generate stringer -type=BinaryOperator +type BinaryOperator int + +const ( + Add BinaryOperator = iota + Subtract + Multiply + Divide +) + +func (bo BinaryOperator) SymbolString() string { + switch bo { + case Add: + return "+" + case Subtract: + return "-" + case Multiply: + return "*" + case Divide: + return "/" + } + return "" +} + +type BinaryExpression struct { + Token token.Token // The operator + Lhs, Rhs Expression + Operator BinaryOperator +} + +func (be *BinaryExpression) expressionNode() {} +func (be *BinaryExpression) TokenLiteral() string { return be.Token.Literal } +func (be *BinaryExpression) String() string { + return fmt.Sprintf("%s %s %s", be.Lhs, be.Operator, be.Rhs) +} diff --git a/ast/binaryoperator_string.go b/ast/binaryoperator_string.go new file mode 100644 index 0000000..58dbd4f --- /dev/null +++ b/ast/binaryoperator_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=BinaryOperator"; DO NOT EDIT. + +package ast + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Add-0] + _ = x[Subtract-1] + _ = x[Multiply-2] + _ = x[Divide-3] +} + +const _BinaryOperator_name = "AddSubtractMultiplyDivide" + +var _BinaryOperator_index = [...]uint8{0, 3, 11, 19, 25} + +func (i BinaryOperator) String() string { + if i < 0 || i >= BinaryOperator(len(_BinaryOperator_index)-1) { + return "BinaryOperator(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _BinaryOperator_name[_BinaryOperator_index[i]:_BinaryOperator_index[i+1]] +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 0cc79f0..8b3dfc8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -23,29 +23,28 @@ type Arguments struct { OnlyEmitAsm bool } -// Prefix writer writes a prefix befor a call to an other writer -// This Prefix is written befor each new line -type prefixWriter struct { +// Prefix writer writes a prefix before each new line from another io.Writer +type PrefixWriter struct { output io.Writer outputPrefix []byte outputPrefixWritten bool } -func NewPrefixWriter(output io.Writer, prefix []byte) *prefixWriter { - return &prefixWriter{ +func NewPrefixWriter(output io.Writer, prefix []byte) *PrefixWriter { + return &PrefixWriter{ output: output, outputPrefix: prefix, } } -func NewPrefixWriterString(output io.Writer, prefix string) *prefixWriter { - return &prefixWriter{ +func NewPrefixWriterString(output io.Writer, prefix string) *PrefixWriter { + return &PrefixWriter{ output: output, outputPrefix: []byte(prefix), } } -func (w *prefixWriter) Write(p []byte) (n int, err error) { +func (w *PrefixWriter) Write(p []byte) (n int, err error) { toWrites := bytes.SplitAfter(p, []byte{'\n'}) diff --git a/go.mod b/go.mod index 04dcae2..ac05136 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,6 @@ module robaertschi.xyz/robaertschi/tt go 1.23.4 + +require ( +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..978ae9d --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= diff --git a/lexer/lexer.go b/lexer/lexer.go index 8444ecc..649d1e8 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -71,6 +71,14 @@ func (l *Lexer) NextToken() token.Token { tok = l.newToken(token.OpenParen) case ')': tok = l.newToken(token.CloseParen) + case '+': + tok = l.newToken(token.Plus) + case '-': + tok = l.newToken(token.Minus) + case '*': + tok = l.newToken(token.Asterisk) + case '/': + tok = l.newToken(token.Slash) case -1: tok.Literal = "" tok.Type = token.Eof diff --git a/parser/parser.go b/parser/parser.go index eb80cce..a9c5be2 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -12,11 +12,18 @@ import ( type precedence int const ( - LOWEST precedence = iota - SUM - PRODUCT + PrecLowest precedence = iota + PrecSum + PrecProduct ) +var precedences = map[token.TokenType]precedence{ + token.Plus: PrecSum, + token.Minus: PrecSum, + token.Asterisk: PrecProduct, + token.Slash: PrecProduct, +} + type ErrorCallback func(token.Token, string, ...any) type prefixParseFn func() ast.Expression type infixParseFn func(ast.Expression) ast.Expression @@ -40,6 +47,10 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefixFn(token.Int, p.parseIntegerExpression) p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfixFn(token.Plus, p.parseBinaryExpression) + p.registerInfixFn(token.Minus, p.parseBinaryExpression) + p.registerInfixFn(token.Asterisk, p.parseBinaryExpression) + p.registerInfixFn(token.Slash, p.parseBinaryExpression) p.nextToken() p.nextToken() @@ -77,10 +88,14 @@ func (p *Parser) peekTokenIs(tt token.TokenType) bool { } func getPrecedence(tt token.TokenType) precedence { - switch tt { - default: - return LOWEST + if prec, ok := precedences[tt]; ok { + return prec } + return PrecLowest +} + +func (p *Parser) curPrecedence() precedence { + return getPrecedence(p.curToken.Type) } func (p *Parser) peekPrecedence() precedence { @@ -161,7 +176,7 @@ func (p *Parser) parseDeclaration() ast.Declaration { } p.nextToken() - expr := p.parseExpression(LOWEST) + expr := p.parseExpression(PrecLowest) if !p.expectPeek(token.Semicolon) { return nil } @@ -212,3 +227,26 @@ func (p *Parser) parseIntegerExpression() ast.Expression { int.Value = value return int } + +func (p *Parser) parseBinaryExpression(lhs ast.Expression) ast.Expression { + var op ast.BinaryOperator + switch p.curToken.Type { + case token.Plus: + op = ast.Add + case token.Minus: + op = ast.Subtract + case token.Asterisk: + op = ast.Multiply + case token.Slash: + op = ast.Divide + default: + return p.exprError(p.curToken, "invalid token for binary expression %s", p.curToken.Type) + } + tok := p.curToken + + precedence := p.curPrecedence() + p.nextToken() + rhs := p.parseExpression(precedence) + + return &ast.BinaryExpression{Lhs: lhs, Rhs: rhs, Operator: op, Token: tok} +} diff --git a/tast/tast.go b/tast/tast.go index 12e1620..c7ae9a2 100644 --- a/tast/tast.go +++ b/tast/tast.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/token" "robaertschi.xyz/robaertschi/tt/types" ) @@ -74,3 +75,19 @@ func (ie *IntegerExpression) Type() types.Type { } func (ie *IntegerExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IntegerExpression) String() string { return ie.Token.Literal } + +type BinaryExpression struct { + Token token.Token // The operator + Lhs, Rhs Expression + Operator ast.BinaryOperator + ResultType types.Type +} + +func (be *BinaryExpression) expressionNode() {} +func (be *BinaryExpression) Type() types.Type { + return be.ResultType +} +func (be *BinaryExpression) TokenLiteral() string { return be.Token.Literal } +func (be *BinaryExpression) String() string { + return fmt.Sprintf("%s %s %s", be.Lhs, be.Operator, be.Rhs) +} diff --git a/test.tt b/test.tt index 86e3e0d..1469b1c 100644 --- a/test.tt +++ b/test.tt @@ -1 +1 @@ -fn main() = 0; +fn main() = 35 + 34; diff --git a/token/token.go b/token/token.go index 97862cd..bc1c128 100644 --- a/token/token.go +++ b/token/token.go @@ -26,13 +26,17 @@ const ( Ident TokenType = "IDENT" Int TokenType = "INT" - Semicolon = ";" - Equal = "=" - OpenParen = "(" - CloseParen = ")" + Semicolon TokenType = ";" + Equal TokenType = "=" + OpenParen TokenType = "(" + CloseParen TokenType = ")" + Plus TokenType = "+" + Minus TokenType = "-" + Asterisk TokenType = "*" + Slash TokenType = "/" // Keywords - Fn = "FN" + Fn TokenType = "FN" ) func LookupKeyword(literal string) TokenType { diff --git a/ttir/emit.go b/ttir/emit.go index 37ce260..0c65b40 100644 --- a/ttir/emit.go +++ b/ttir/emit.go @@ -1,6 +1,17 @@ package ttir -import "robaertschi.xyz/robaertschi/tt/tast" +import ( + "fmt" + + "robaertschi.xyz/robaertschi/tt/tast" +) + +var uniqueId int64 + +func temp() string { + uniqueId += 1 + return fmt.Sprintf("temp.%d", uniqueId) +} func EmitProgram(program *tast.Program) *Program { functions := make([]Function, 0) @@ -29,6 +40,14 @@ func emitExpression(expr tast.Expression) (Operand, []Instruction) { switch expr := expr.(type) { case *tast.IntegerExpression: return &Constant{Value: expr.Value}, []Instruction{} + case *tast.BinaryExpression: + lhsDst, instructions := emitExpression(expr.Lhs) + rhsDst, rhsInstructions := emitExpression(expr.Rhs) + instructions = append(instructions, rhsInstructions...) + dst := &Var{Value: temp()} + instructions = append(instructions, &Binary{Operator: expr.Operator, Lhs: lhsDst, Rhs: rhsDst, Dst: dst}) + return dst, instructions + } panic("unhandled tast.Expression case in ir emitter") } diff --git a/ttir/ttir.go b/ttir/ttir.go index 8da3e3e..d4b930c 100644 --- a/ttir/ttir.go +++ b/ttir/ttir.go @@ -3,6 +3,8 @@ package ttir import ( "fmt" "strings" + + "robaertschi.xyz/robaertschi/tt/ast" ) type Program struct { @@ -38,6 +40,18 @@ func (r *Ret) String() string { } func (r *Ret) instruction() {} +type Binary struct { + Operator ast.BinaryOperator + Lhs Operand + Rhs Operand + Dst Operand +} + +func (b *Binary) String() string { + return fmt.Sprintf("%s = %s %s, %s", b.Dst, b.Operator, b.Lhs, b.Rhs) +} +func (b *Binary) instruction() {} + type Operand interface { String() string operand() @@ -51,3 +65,12 @@ func (c *Constant) String() string { return fmt.Sprintf("%d", c.Value) } func (c *Constant) operand() {} + +type Var struct { + Value string +} + +func (v *Var) String() string { + return v.Value +} +func (v *Var) operand() {} diff --git a/typechecker/checker.go b/typechecker/checker.go index e685355..ce0db8e 100644 --- a/typechecker/checker.go +++ b/typechecker/checker.go @@ -7,6 +7,7 @@ import ( "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/tast" "robaertschi.xyz/robaertschi/tt/token" + "robaertschi.xyz/robaertschi/tt/types" ) type Checker struct { @@ -66,6 +67,20 @@ func (c *Checker) checkExpression(expr ast.Expression) (tast.Expression, error) return &tast.IntegerExpression{Token: expr.Token, Value: expr.Value}, nil case *ast.ErrorExpression: return nil, c.error(expr.InvalidToken, "invalid expression") + case *ast.BinaryExpression: + lhs, lhsErr := c.checkExpression(expr.Lhs) + rhs, rhsErr := c.checkExpression(expr.Rhs) + var operandErr error + var resultType types.Type + if lhsErr == nil && rhsErr == nil { + if !lhs.Type().IsSameType(rhs.Type()) { + operandErr = fmt.Errorf("the lhs of the expression does not have the same type then the rhs, lhs=%q, rhs=%q", lhs.Type(), rhs.Type()) + } else { + resultType = lhs.Type() + } + } + + return &tast.BinaryExpression{Lhs: lhs, Rhs: rhs, Operator: expr.Operator, Token: expr.Token, ResultType: resultType}, errors.Join(lhsErr, rhsErr, operandErr) } return nil, fmt.Errorf("unhandled expression in type checker") }