From 4537f0ee694a0fe6a9bef1db47775a2e9c7d221f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20B=C3=A4rtschi?= Date: Tue, 21 Jan 2025 09:33:59 +0100 Subject: [PATCH] ttir emitter and tests --- ttir/emit.go | 25 +++++++- ttir/ttir_test.go | 126 +++++++++++++++++++++++++++++++++++++++++ typechecker/checker.go | 4 +- 3 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 ttir/ttir_test.go diff --git a/ttir/emit.go b/ttir/emit.go index 4c2c8ae..a40f7e4 100644 --- a/ttir/emit.go +++ b/ttir/emit.go @@ -3,13 +3,32 @@ package ttir import "robaertschi.xyz/robaertschi/tt/tast" func EmitProgram(program *tast.Program) *Program { + functions := make([]Function, 0) + for _, decl := range program.Declarations { + switch decl := decl.(type) { + case *tast.FunctionDeclaration: + functions = append(functions, *emitFunction(decl)) + } + } + return &Program{ + Functions: functions, + } } func emitFunction(function *tast.FunctionDeclaration) *Function { - + value, instructions := emitExpression(function.Body) + instructions = append(instructions, &Ret{op: value}) + return &Function{ + Name: function.Name, + Instructions: instructions, + } } -func emitExpression(expr *tast.Expression) (Operand, []Instruction) { - +func emitExpression(expr tast.Expression) (Operand, []Instruction) { + switch expr := expr.(type) { + case *tast.IntegerExpression: + return &Constant{Value: expr.Value}, []Instruction{} + } + panic("unhandled tast.Expression case in ir emitter") } diff --git a/ttir/ttir_test.go b/ttir/ttir_test.go new file mode 100644 index 0000000..2916617 --- /dev/null +++ b/ttir/ttir_test.go @@ -0,0 +1,126 @@ +package ttir + +import ( + "fmt" + "testing" + + "robaertschi.xyz/robaertschi/tt/lexer" + "robaertschi.xyz/robaertschi/tt/parser" + "robaertschi.xyz/robaertschi/tt/token" + "robaertschi.xyz/robaertschi/tt/typechecker" +) + +type ttirEmitterTest struct { + input string + expected Program +} + +func runTTIREmitterTest(t *testing.T, test ttirEmitterTest) { + t.Helper() + + l, err := lexer.New(test.input, "test.tt") + l.WithErrorCallback(func(l token.Loc, s string, a ...any) { + format := fmt.Sprintf(s, a...) + t.Errorf("Lexer error callback called: %s:%d:%d %s", l.File, l.Line, l.Col, format) + }) + if err != nil { + t.Fatalf("lexer error: %q", err) + } + + p := parser.New(l) + p.WithErrorCallback(func(tok token.Token, s string, a ...any) { + format := fmt.Sprintf(s, a...) + t.Errorf("Parser error callback called: %s:%d:%d %s", tok.Loc.File, tok.Loc.Line, tok.Loc.Col, format) + }) + program := p.ParseProgram() + tprogram, err := typechecker.New().CheckProgram(program) + + if err != nil { + t.Fatalf("typechecker error: %q", err) + } + + ttir := EmitProgram(tprogram) + + expectProgram(t, &test.expected, ttir) +} + +func expectProgram(t *testing.T, expected *Program, actual *Program) { + t.Helper() + + if len(expected.Functions) != len(actual.Functions) { + t.Errorf("expected %d functions , got %d", len(expected.Functions), len(actual.Functions)) + return + } + + for i, decl := range expected.Functions { + expectFunction(t, decl, actual.Functions[i]) + } +} + +func expectFunction(t *testing.T, expected Function, actual Function) { + t.Helper() + + if expected.Name != actual.Name { + t.Errorf("expected name %q, got %q", expected.Name, actual.Name) + } + + if len(expected.Instructions) != len(actual.Instructions) { + t.Errorf("expected %d instructions, got %d", len(expected.Instructions), len(actual.Instructions)) + return + } + + for i, inst := range expected.Instructions { + expectInstruction(t, inst, actual.Instructions[i]) + } +} + +func expectInstruction(t *testing.T, inst Instruction, actual Instruction) { + t.Helper() + switch inst := inst.(type) { + case *Ret: + ret, ok := actual.(*Ret) + + if !ok { + t.Errorf("expected inst to be *Ret, but got %T", actual) + return + } + + expectOperand(t, inst.op, ret.op) + } +} + +func expectOperand(t *testing.T, expected Operand, actual Operand) { + t.Helper() + + switch expected := expected.(type) { + case *Constant: + constant, ok := actual.(*Constant) + + if !ok { + t.Errorf("expected operand to be *Constant, but got %T", actual) + return + } + + if expected.Value != constant.Value { + t.Errorf("expected *Constant.Value to be %d, but got %d", expected.Value, constant.Value) + } + } +} + +func TestBasicFunction(t *testing.T) { + runTTIREmitterTest(t, ttirEmitterTest{ + input: "fn main() = 0;", + expected: Program{ + Functions: []Function{ + { + Name: "main", + Instructions: []Instruction{ + &Ret{ + op: &Constant{Value: 0}, + }, + }, + }, + }, + }, + }) +} diff --git a/typechecker/checker.go b/typechecker/checker.go index 940bf1f..5b8c0b9 100644 --- a/typechecker/checker.go +++ b/typechecker/checker.go @@ -19,7 +19,7 @@ func (c *Checker) error(t token.Token, format string, args ...any) error { return fmt.Errorf("%s:%d:%d %s", t.Loc.File, t.Loc.Line, t.Loc.Col, fmt.Sprintf(format, args...)) } -func (c *Checker) CheckProgram(program ast.Program) (tast.Program, error) { +func (c *Checker) CheckProgram(program *ast.Program) (*tast.Program, error) { decls := []tast.Declaration{} errs := []error{} @@ -32,7 +32,7 @@ func (c *Checker) CheckProgram(program ast.Program) (tast.Program, error) { } } - return tast.Program{Declarations: decls}, errors.Join(errs...) + return &tast.Program{Declarations: decls}, errors.Join(errs...) } func (c *Checker) checkDeclaration(decl ast.Declaration) (tast.Declaration, error) {