From bc554cdedf89fcfeeb963a76aab851edcc2d871c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20B=C3=A4rtschi?= Date: Mon, 27 Jan 2025 10:08:23 +0100 Subject: [PATCH] added new equality operators --- asm/amd64/amd64.go | 8 +++-- asm/amd64/amd64_test.go | 68 ++++++++++++++++++++++++++++++++++--- asm/amd64/codegen.go | 12 +++++-- asm/amd64/equality_test.txt | 33 ++++++++++++++++++ ast/ast.go | 14 +++++++- language.md | 1 - lexer/lexer.go | 20 +++++++++++ parser/parser.go | 28 +++++++++++---- test.tt | 7 ++-- token/token.go | 16 +++++---- 10 files changed, 181 insertions(+), 26 deletions(-) create mode 100644 asm/amd64/equality_test.txt diff --git a/asm/amd64/amd64.go b/asm/amd64/amd64.go index 6881e9f..a05389c 100644 --- a/asm/amd64/amd64.go +++ b/asm/amd64/amd64.go @@ -71,8 +71,12 @@ func (f *Function) Emit() string { type CondCode string const ( - Equal CondCode = "e" - NotEqual CondCode = "ne" + Equal CondCode = "e" + NotEqual CondCode = "ne" + Greater CondCode = "g" + GreaterEqual CondCode = "ge" + Less CondCode = "l" + LessEqual CondCode = "le" ) type Opcode string diff --git a/asm/amd64/amd64_test.go b/asm/amd64/amd64_test.go index 55ff2d3..797ba98 100644 --- a/asm/amd64/amd64_test.go +++ b/asm/amd64/amd64_test.go @@ -124,6 +124,10 @@ func expectOperand(t *testing.T, expected Operand, actual Operand) { } } +func trim(s string) string { + return strings.Trim(s, " \n\t") +} + func TestOperands(t *testing.T) { var op Operand @@ -145,7 +149,8 @@ func TestCodegen(t *testing.T) { program := &ttir.Program{ Functions: []ttir.Function{ { - Name: "main", + Name: "main", + HasReturnValue: true, Instructions: []ttir.Instruction{ &ttir.Ret{Op: &ttir.Constant{Value: 0}}, }, @@ -154,6 +159,7 @@ func TestCodegen(t *testing.T) { } expectedProgram := Program{ + Functions: []Function{ { Name: "main", @@ -167,6 +173,7 @@ func TestCodegen(t *testing.T) { Opcode: Ret, }, }, + HasReturnValue: true, }, }, } @@ -176,8 +183,8 @@ func TestCodegen(t *testing.T) { actual := actualProgram.Emit() expected := basicTest - if strings.Trim(actual, " \n\t") != strings.Trim(expected, " \n\t") { - t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", expected, actual) + if trim(actual) != trim(expected) { + t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", trim(expected), trim(actual)) } } @@ -198,12 +205,63 @@ func TestBinary(t *testing.T) { }, &ttir.Ret{Op: &ttir.Var{Value: "temp.1"}}, }, + HasReturnValue: true, }, }, } actual := CgProgram(program).Emit() - if strings.Trim(actual, " \n\t") != strings.Trim(binaryTest, " \n\t") { - t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", binaryTest, actual) + if trim(actual) != trim(binaryTest) { + t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", trim(binaryTest), trim(actual)) + } +} + +//go:embed equality_test.txt +var equalityTest string + +// There was once a bug with how the cmp instructions were generated, this check should fail if it happens again +func TestEqualityOperators(t *testing.T) { + program := ttir.Program{ + Functions: []ttir.Function{ + { + Name: "main", + HasReturnValue: false, + Instructions: []ttir.Instruction{ + &ttir.Binary{ + Lhs: &ttir.Constant{Value: 5}, + Rhs: &ttir.Constant{Value: 4}, + Dst: &ttir.Var{Value: "temp.1"}, + Operator: ast.LessThanEqual, + }, + &ttir.Binary{ + Lhs: &ttir.Constant{Value: 5}, + Rhs: &ttir.Constant{Value: 4}, + Dst: &ttir.Var{Value: "temp.2"}, + Operator: ast.LessThan, + }, + &ttir.Binary{ + Lhs: &ttir.Constant{Value: 5}, + Rhs: &ttir.Constant{Value: 4}, + Dst: &ttir.Var{Value: "temp.3"}, + Operator: ast.GreaterThanEqual, + }, + &ttir.Binary{ + Lhs: &ttir.Constant{Value: 5}, + Rhs: &ttir.Constant{Value: 4}, + Dst: &ttir.Var{Value: "temp.4"}, + Operator: ast.GreaterThan, + }, + &ttir.Ret{}, + }, + }, + }, + } + + actual := CgProgram(&program).Emit() + actualTrimmed := trim(actual) + expectedTrimmed := trim(actualTrimmed) + + if expectedTrimmed != actualTrimmed { + t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", expectedTrimmed, actualTrimmed) } } diff --git a/asm/amd64/codegen.go b/asm/amd64/codegen.go index 117733c..a7bc120 100644 --- a/asm/amd64/codegen.go +++ b/asm/amd64/codegen.go @@ -81,7 +81,7 @@ func cgInstruction(i ttir.Instruction) []Instruction { func cgBinary(b *ttir.Binary) []Instruction { switch b.Operator { - case ast.Equal, ast.NotEqual: + case ast.Equal, ast.NotEqual, ast.GreaterThan, ast.GreaterThanEqual, ast.LessThan, ast.LessThanEqual: var condCode CondCode switch b.Operator { @@ -89,6 +89,14 @@ func cgBinary(b *ttir.Binary) []Instruction { condCode = Equal case ast.NotEqual: condCode = NotEqual + case ast.GreaterThan: + condCode = Greater + case ast.GreaterThanEqual: + condCode = GreaterEqual + case ast.LessThan: + condCode = Less + case ast.LessThanEqual: + condCode = LessEqual } return []Instruction{ @@ -278,7 +286,7 @@ func fixupInstruction(i Instruction) []Instruction { &SimpleInstruction{ Opcode: Cmp, Lhs: Register(R11), - Rhs: i.Lhs, + Rhs: i.Rhs, }, } } diff --git a/asm/amd64/equality_test.txt b/asm/amd64/equality_test.txt new file mode 100644 index 0000000..fbd6c47 --- /dev/null +++ b/asm/amd64/equality_test.txt @@ -0,0 +1,33 @@ +format ELF64 executable +segment readable executable +entry _start +_start: + call main + mov rdi, 0 + mov rax, 60 + syscall +main: + push rbp + mov rbp, rsp + add rsp, -16 + mov r11, 5 + cmp r11, 4 + mov qword [rsp -4], 0 + setle byte [rsp -4] + mov r11, 5 + cmp r11, 4 + mov qword [rsp -8], 0 + setl byte [rsp -8] + mov r11, 5 + cmp r11, 4 + mov qword [rsp -12], 0 + setge byte [rsp -12] + mov r11, 5 + cmp r11, 4 + mov qword [rsp -16], 0 + setg byte [rsp -16] + mov rsp, rbp + pop rbp + ret + + diff --git a/ast/ast.go b/ast/ast.go index fe190f6..01cdc5d 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -93,10 +93,14 @@ const ( Divide Equal NotEqual + LessThan + LessThanEqual + GreaterThan + GreaterThanEqual ) func (bo BinaryOperator) IsBooleanOperator() bool { - return bo == Equal || bo == NotEqual + return bo == Equal || bo == NotEqual || bo == LessThan || bo == LessThanEqual || bo == GreaterThan || bo == GreaterThanEqual } func (bo BinaryOperator) SymbolString() string { @@ -113,6 +117,14 @@ func (bo BinaryOperator) SymbolString() string { return "==" case NotEqual: return "!=" + case LessThan: + return "<" + case LessThanEqual: + return "<=" + case GreaterThan: + return ">" + case GreaterThanEqual: + return ">=" } return "" } diff --git a/language.md b/language.md index 980ad74..b96f49c 100644 --- a/language.md +++ b/language.md @@ -3,7 +3,6 @@ ## Syntax ```tt - // Return type is i64 fn main() = { let i = 34; diff --git a/lexer/lexer.go b/lexer/lexer.go index d45dd83..d405a45 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -75,6 +75,26 @@ func (l *Lexer) NextToken() token.Token { return tok } tok = l.newToken(token.Equal) + case '<': + if l.peekByte() == '=' { + pos := l.position + l.readChar() + l.readChar() + tok.Type = token.LessThanEqual + tok.Literal = l.input[pos:l.position] + return tok + } + tok = l.newToken(token.LessThan) + case '>': + if l.peekByte() == '=' { + pos := l.position + l.readChar() + l.readChar() + tok.Type = token.GreaterThanEqual + tok.Literal = l.input[pos:l.position] + return tok + } + tok = l.newToken(token.GreaterThan) case '(': tok = l.newToken(token.OpenParen) case ')': diff --git a/parser/parser.go b/parser/parser.go index 1d4b04b..02955a1 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -19,12 +19,16 @@ const ( ) var precedences = map[token.TokenType]precedence{ - token.Plus: PrecSum, - token.Minus: PrecSum, - token.Asterisk: PrecProduct, - token.Slash: PrecProduct, - token.DoubleEqual: PrecComparison, - token.NotEqual: PrecComparison, + token.Plus: PrecSum, + token.Minus: PrecSum, + token.Asterisk: PrecProduct, + token.Slash: PrecProduct, + token.DoubleEqual: PrecComparison, + token.NotEqual: PrecComparison, + token.GreaterThan: PrecComparison, + token.GreaterThanEqual: PrecComparison, + token.LessThan: PrecComparison, + token.LessThanEqual: PrecComparison, } type ErrorCallback func(token.Token, string, ...any) @@ -60,6 +64,10 @@ func New(l *lexer.Lexer) *Parser { p.registerInfixFn(token.Slash, p.parseBinaryExpression) p.registerInfixFn(token.DoubleEqual, p.parseBinaryExpression) p.registerInfixFn(token.NotEqual, p.parseBinaryExpression) + p.registerInfixFn(token.GreaterThan, p.parseBinaryExpression) + p.registerInfixFn(token.GreaterThanEqual, p.parseBinaryExpression) + p.registerInfixFn(token.LessThan, p.parseBinaryExpression) + p.registerInfixFn(token.LessThanEqual, p.parseBinaryExpression) p.nextToken() p.nextToken() @@ -270,6 +278,14 @@ func (p *Parser) parseBinaryExpression(lhs ast.Expression) ast.Expression { op = ast.Equal case token.NotEqual: op = ast.NotEqual + case token.LessThan: + op = ast.LessThan + case token.LessThanEqual: + op = ast.LessThanEqual + case token.GreaterThan: + op = ast.GreaterThan + case token.GreaterThanEqual: + op = ast.GreaterThanEqual default: return p.exprError(p.curToken, "invalid token for binary expression %s", p.curToken.Type) } diff --git a/test.tt b/test.tt index 3e261c8..516ad28 100644 --- a/test.tt +++ b/test.tt @@ -1,5 +1,6 @@ fn main() = { - 3 + 3; - 3 * 43 * 34 / 34; - 3 + 5 <= 4; + 5 < 4; + 5 >= 4; + 5 > 4; }; diff --git a/token/token.go b/token/token.go index a4053f8..0df8713 100644 --- a/token/token.go +++ b/token/token.go @@ -36,12 +36,16 @@ const ( CloseBrack TokenType = "}" // Binary Operators - Plus TokenType = "+" - Minus TokenType = "-" - Asterisk TokenType = "*" - Slash TokenType = "/" - DoubleEqual TokenType = "==" - NotEqual TokenType = "!=" + Plus TokenType = "+" + Minus TokenType = "-" + Asterisk TokenType = "*" + Slash TokenType = "/" + DoubleEqual TokenType = "==" + NotEqual TokenType = "!=" + LessThan TokenType = "<" + LessThanEqual TokenType = "<=" + GreaterThan TokenType = ">" + GreaterThanEqual TokenType = ">=" // Keywords Fn TokenType = "FN"