mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-18 23:13:29 +00:00
begin ir
This commit is contained in:
parent
6f9d64b2bf
commit
c8d4e1c0a7
13
Architecture.md
Normal file
13
Architecture.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
tt Programming Language Backend Architecture
|
||||||
|
|
||||||
|
# Goals
|
||||||
|
- Easy support for different architectures and OSs
|
||||||
|
- Easily Optimisable on most levels
|
||||||
|
- Good Performance
|
||||||
|
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
AST --> Type Checking --> TAST --> IR Emission --> TTIR --> Codegen --> TTASM --> Emit --> FASM -> Binary
|
||||||
|
|
||||||
|
TTIR: TT Intermediate Representation is the Representation that the AST gets turned into. This will be mostly be used for optimissing and abstracting away from Assembly
|
||||||
|
TAST: Typed Ast
|
@ -63,7 +63,7 @@ type ErrorExpression struct {
|
|||||||
|
|
||||||
func (e *ErrorExpression) expressionNode() {}
|
func (e *ErrorExpression) expressionNode() {}
|
||||||
func (e *ErrorExpression) TokenLiteral() string { return e.InvalidToken.Literal }
|
func (e *ErrorExpression) TokenLiteral() string { return e.InvalidToken.Literal }
|
||||||
func (e *ErrorExpression) String() string { return "<ERROR>" }
|
func (e *ErrorExpression) String() string { return "<ERROR EXPR>" }
|
||||||
|
|
||||||
type IntegerExpression struct {
|
type IntegerExpression struct {
|
||||||
Token token.Token // The token.INT
|
Token token.Token // The token.INT
|
||||||
|
@ -64,20 +64,20 @@ func (l *Lexer) NextToken() token.Token {
|
|||||||
|
|
||||||
switch l.ch {
|
switch l.ch {
|
||||||
case ';':
|
case ';':
|
||||||
tok = l.newToken(token.SEMICOLON)
|
tok = l.newToken(token.Semicolon)
|
||||||
case '=':
|
case '=':
|
||||||
tok = l.newToken(token.EQUAL)
|
tok = l.newToken(token.Equal)
|
||||||
case '(':
|
case '(':
|
||||||
tok = l.newToken(token.OPEN_PAREN)
|
tok = l.newToken(token.OpenParen)
|
||||||
case ')':
|
case ')':
|
||||||
tok = l.newToken(token.CLOSE_PAREN)
|
tok = l.newToken(token.CloseParen)
|
||||||
case -1:
|
case -1:
|
||||||
tok.Literal = ""
|
tok.Literal = ""
|
||||||
tok.Type = token.EOF
|
tok.Type = token.Eof
|
||||||
default:
|
default:
|
||||||
if isNumber(l.ch) {
|
if isNumber(l.ch) {
|
||||||
tok.Literal = l.readInteger()
|
tok.Literal = l.readInteger()
|
||||||
tok.Type = token.INT
|
tok.Type = token.Int
|
||||||
return tok
|
return tok
|
||||||
} else if unicode.IsLetter(l.ch) {
|
} else if unicode.IsLetter(l.ch) {
|
||||||
tok.Literal = l.readIdentifier()
|
tok.Literal = l.readIdentifier()
|
||||||
@ -87,7 +87,7 @@ func (l *Lexer) NextToken() token.Token {
|
|||||||
if l.errorCallback != nil {
|
if l.errorCallback != nil {
|
||||||
l.errorCallback(tok.Loc, "Unknown character %r", l.ch)
|
l.errorCallback(tok.Loc, "Unknown character %r", l.ch)
|
||||||
}
|
}
|
||||||
tok = l.newToken(token.ILLEGAL)
|
tok = l.newToken(token.Illegal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := l.readChar(); err != nil {
|
if err := l.readChar(); err != nil {
|
||||||
@ -164,7 +164,7 @@ func (l *Lexer) skipWhitespace() {
|
|||||||
|
|
||||||
func (l *Lexer) error(loc token.Loc, format string, args ...any) {
|
func (l *Lexer) error(loc token.Loc, format string, args ...any) {
|
||||||
if l.errorCallback != nil {
|
if l.errorCallback != nil {
|
||||||
l.errorCallback(loc, format, args)
|
l.errorCallback(loc, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.errors += 1
|
l.errors += 1
|
||||||
|
@ -17,7 +17,7 @@ func runLexerTest(t *testing.T, test lexerTest) {
|
|||||||
|
|
||||||
l, err := New(test.input, "test.tt")
|
l, err := New(test.input, "test.tt")
|
||||||
l.WithErrorCallback(func(l token.Loc, s string, a ...any) {
|
l.WithErrorCallback(func(l token.Loc, s string, a ...any) {
|
||||||
format := fmt.Sprintf(s, a)
|
format := fmt.Sprintf(s, a...)
|
||||||
t.Errorf("Lexer error callback called: %s:%d:%d %s", l.File, l.Line, l.Col, format)
|
t.Errorf("Lexer error callback called: %s:%d:%d %s", l.File, l.Line, l.Col, format)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -42,14 +42,14 @@ func TestBasicFunctionality(t *testing.T) {
|
|||||||
runLexerTest(t, lexerTest{
|
runLexerTest(t, lexerTest{
|
||||||
input: "fn main() = 0;",
|
input: "fn main() = 0;",
|
||||||
expectedToken: []token.Token{
|
expectedToken: []token.Token{
|
||||||
{Type: token.FN, Literal: "fn"},
|
{Type: token.Fn, Literal: "fn"},
|
||||||
{Type: token.IDENT, Literal: "main"},
|
{Type: token.Ident, Literal: "main"},
|
||||||
{Type: token.OPEN_PAREN, Literal: "("},
|
{Type: token.OpenParen, Literal: "("},
|
||||||
{Type: token.CLOSE_PAREN, Literal: ")"},
|
{Type: token.CloseParen, Literal: ")"},
|
||||||
{Type: token.EQUAL, Literal: "="},
|
{Type: token.Equal, Literal: "="},
|
||||||
{Type: token.INT, Literal: "0"},
|
{Type: token.Int, Literal: "0"},
|
||||||
{Type: token.SEMICOLON, Literal: ";"},
|
{Type: token.Semicolon, Literal: ";"},
|
||||||
{Type: token.EOF, Literal: ""},
|
{Type: token.Eof, Literal: ""},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
197
parser/parser.go
197
parser/parser.go
@ -1,18 +1,211 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"robaertschi.xyz/robaertschi/tt/ast"
|
||||||
"robaertschi.xyz/robaertschi/tt/lexer"
|
"robaertschi.xyz/robaertschi/tt/lexer"
|
||||||
"robaertschi.xyz/robaertschi/tt/token"
|
"robaertschi.xyz/robaertschi/tt/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type precedence int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LOWEST precedence = iota
|
||||||
|
SUM
|
||||||
|
PRODUCT
|
||||||
|
)
|
||||||
|
|
||||||
type ErrorCallback func(token.Token, string, ...any)
|
type ErrorCallback func(token.Token, string, ...any)
|
||||||
|
type prefixParseFn func() ast.Expression
|
||||||
|
type infixParseFn func(ast.Expression) ast.Expression
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
lexer lexer.Lexer
|
|
||||||
|
|
||||||
curToken token.Token
|
curToken token.Token
|
||||||
peekToken token.Token
|
peekToken token.Token
|
||||||
|
|
||||||
errors int
|
errors int
|
||||||
errorCallback ErrorCallback
|
errorCallback ErrorCallback
|
||||||
|
|
||||||
|
l *lexer.Lexer
|
||||||
|
prefixParseFns map[token.TokenType]prefixParseFn
|
||||||
|
infixParseFns map[token.TokenType]infixParseFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(l *lexer.Lexer) *Parser {
|
||||||
|
p := &Parser{l: l}
|
||||||
|
|
||||||
|
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
|
||||||
|
p.registerPrefixFn(token.Int, p.parseIntegerExpression)
|
||||||
|
|
||||||
|
p.infixParseFns = make(map[token.TokenType]infixParseFn)
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
p.nextToken()
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) WithErrorCallback(errorCallback ErrorCallback) {
|
||||||
|
p.errorCallback = errorCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) registerInfixFn(tt token.TokenType, infix infixParseFn) {
|
||||||
|
p.infixParseFns[tt] = infix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) registerPrefixFn(tt token.TokenType, fn prefixParseFn) {
|
||||||
|
p.prefixParseFns[tt] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) nextToken() {
|
||||||
|
p.curToken = p.peekToken
|
||||||
|
p.peekToken = p.l.NextToken()
|
||||||
|
fmt.Printf("curToken: %q, peekToken: %q\n", p.curToken.Type, p.peekToken.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) curTokenIs(tt token.TokenType) bool {
|
||||||
|
return p.curToken.Type == tt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) peekTokenIs(tt token.TokenType) bool {
|
||||||
|
return p.peekToken.Type == tt
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrecedence(tt token.TokenType) precedence {
|
||||||
|
switch tt {
|
||||||
|
default:
|
||||||
|
return LOWEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) peekPrecedence() precedence {
|
||||||
|
return getPrecedence(p.peekToken.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) error(t token.Token, format string, args ...any) {
|
||||||
|
if p.errorCallback != nil {
|
||||||
|
p.errorCallback(t, format, args...)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s:%d:%d ", t.Loc.File, t.Loc.Line, t.Loc.Col)
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.errors += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) exprError(invalidToken token.Token, format string, args ...any) ast.Expression {
|
||||||
|
p.error(invalidToken, format, args...)
|
||||||
|
return &ast.ErrorExpression{
|
||||||
|
InvalidToken: invalidToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) expect(tt token.TokenType) bool {
|
||||||
|
if p.curToken.Type != tt {
|
||||||
|
p.error(p.curToken, "expected %q, got %q", tt, p.curToken.Type)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) expectPeek(tt token.TokenType) bool {
|
||||||
|
if p.peekToken.Type != tt {
|
||||||
|
p.error(p.peekToken, "expected %q, got %q", tt, p.peekToken.Type)
|
||||||
|
p.nextToken()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
p.nextToken()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) ParseProgram() *ast.Program {
|
||||||
|
decls := []ast.Declaration{}
|
||||||
|
|
||||||
|
for p.curToken.Type != token.Eof {
|
||||||
|
decl := p.parseDeclaration()
|
||||||
|
if decl != nil {
|
||||||
|
decls = append(decls, decl)
|
||||||
|
}
|
||||||
|
p.nextToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ast.Program{
|
||||||
|
Declarations: decls,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseDeclaration() ast.Declaration {
|
||||||
|
if !p.expect(token.Fn) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tok := p.curToken
|
||||||
|
if !p.expectPeek(token.Ident) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := p.curToken.Literal
|
||||||
|
if !p.expectPeek(token.OpenParen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !p.expectPeek(token.CloseParen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !p.expectPeek(token.Equal) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
expr := p.parseExpression(LOWEST)
|
||||||
|
if !p.expectPeek(token.Semicolon) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ast.FunctionDeclaration{
|
||||||
|
Token: tok,
|
||||||
|
Name: name,
|
||||||
|
Body: expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseExpression(precedence precedence) ast.Expression {
|
||||||
|
prefix := p.prefixParseFns[p.curToken.Type]
|
||||||
|
if prefix == nil {
|
||||||
|
return p.exprError(p.curToken, "could not parse invalid token in expression %s", p.curToken.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
leftExpr := prefix()
|
||||||
|
|
||||||
|
for !p.peekTokenIs(token.Semicolon) && precedence < p.peekPrecedence() {
|
||||||
|
infix := p.infixParseFns[p.peekToken.Type]
|
||||||
|
if infix == nil {
|
||||||
|
return leftExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
p.nextToken()
|
||||||
|
|
||||||
|
leftExpr = infix(leftExpr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return leftExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseIntegerExpression() ast.Expression {
|
||||||
|
if !p.expect(token.Int) {
|
||||||
|
return &ast.ErrorExpression{InvalidToken: p.curToken}
|
||||||
|
}
|
||||||
|
|
||||||
|
int := &ast.IntegerExpression{
|
||||||
|
Token: p.curToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseInt(int.Token.Literal, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return p.exprError(int.Token, "invalid integer literal: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
int.Value = value
|
||||||
|
return int
|
||||||
}
|
}
|
||||||
|
106
parser/parser_test.go
Normal file
106
parser/parser_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"robaertschi.xyz/robaertschi/tt/ast"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/lexer"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parserTest struct {
|
||||||
|
input string
|
||||||
|
expectedProgram ast.Program
|
||||||
|
}
|
||||||
|
|
||||||
|
func runParserTest(test parserTest, t *testing.T) {
|
||||||
|
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.Errorf("creating lexer failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := 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)
|
||||||
|
})
|
||||||
|
|
||||||
|
actual := p.ParseProgram()
|
||||||
|
|
||||||
|
if p.errors > 0 {
|
||||||
|
t.Fatalf("parser errors: %d", p.errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actual.Declarations) != len(test.expectedProgram.Declarations) {
|
||||||
|
t.Fatalf("expected %d declarations, got %d", len(test.expectedProgram.Declarations), len(actual.Declarations))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, decl := range test.expectedProgram.Declarations {
|
||||||
|
expectDeclarationSame(t, decl, actual.Declarations[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectDeclarationSame(t *testing.T, expected ast.Declaration, actual ast.Declaration) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
switch expected := expected.(type) {
|
||||||
|
case *ast.FunctionDeclaration:
|
||||||
|
actual, ok := actual.(*ast.FunctionDeclaration)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected function declaration, got %T", actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if actual.Name != expected.Name {
|
||||||
|
t.Errorf("expected function name %s, got %s", expected.Name, actual.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectExpression(t, expected.Body, actual.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectExpression(t *testing.T, expected ast.Expression, actual ast.Expression) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
switch expected := expected.(type) {
|
||||||
|
case *ast.ErrorExpression:
|
||||||
|
actual, ok := actual.(*ast.ErrorExpression)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected error expression, got %T", actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if actual.InvalidToken != expected.InvalidToken {
|
||||||
|
t.Errorf("expected invalid token %v, got %v", expected.InvalidToken, actual.InvalidToken)
|
||||||
|
}
|
||||||
|
case *ast.IntegerExpression:
|
||||||
|
integerExpr, ok := actual.(*ast.IntegerExpression)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected *ast.IntegerExpression, got %T", actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if integerExpr.Value != expected.Value {
|
||||||
|
t.Errorf("expected integer value %d, got %d", expected.Value, integerExpr.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionDeclaration(t *testing.T) {
|
||||||
|
test := parserTest{
|
||||||
|
input: "fn main() = 0;",
|
||||||
|
expectedProgram: ast.Program{
|
||||||
|
Declarations: []ast.Declaration{
|
||||||
|
&ast.FunctionDeclaration{
|
||||||
|
Name: "main",
|
||||||
|
Body: &ast.IntegerExpression{Value: 0, Token: token.Token{Type: token.Int, Literal: "0"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runParserTest(test, t)
|
||||||
|
}
|
76
tast/tast.go
Normal file
76
tast/tast.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Typed AST
|
||||||
|
// Almost identical to the AST, but contains types and contains only correct types.
|
||||||
|
// Also, it does not contain a Error Expression, because, that an previous error
|
||||||
|
|
||||||
|
package tast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"robaertschi.xyz/robaertschi/tt/token"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
TokenLiteral() string
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Declaration interface {
|
||||||
|
Node
|
||||||
|
declarationNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expression interface {
|
||||||
|
Node
|
||||||
|
expressionNode()
|
||||||
|
Type() types.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
Declarations []Declaration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) TokenLiteral() string {
|
||||||
|
if len(p.Declarations) > 0 {
|
||||||
|
return p.Declarations[0].TokenLiteral()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) String() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
for _, decl := range p.Declarations {
|
||||||
|
builder.WriteString(decl.String())
|
||||||
|
builder.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionDeclaration struct {
|
||||||
|
Token token.Token // The token.FN
|
||||||
|
Body Expression
|
||||||
|
Name string
|
||||||
|
ReturnType types.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd *FunctionDeclaration) declarationNode() {}
|
||||||
|
func (fd *FunctionDeclaration) TokenLiteral() string { return fd.Token.Literal }
|
||||||
|
func (fd *FunctionDeclaration) String() string {
|
||||||
|
return fmt.Sprintf("fn %v(): %v = %v;", fd.Name, fd.ReturnType.Name(), fd.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegerExpression struct {
|
||||||
|
Token token.Token // The token.INT
|
||||||
|
Value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ie *IntegerExpression) expressionNode() {}
|
||||||
|
func (ie *IntegerExpression) Type() types.Type {
|
||||||
|
return types.I64
|
||||||
|
}
|
||||||
|
func (ie *IntegerExpression) TokenLiteral() string { return ie.Token.Literal }
|
||||||
|
func (ie *IntegerExpression) String() string { return ie.Token.Literal }
|
@ -16,28 +16,28 @@ type Token struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keywords = map[string]TokenType{
|
var keywords = map[string]TokenType{
|
||||||
"fn": FN,
|
"fn": Fn,
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ILLEGAL TokenType = "ILLEGAL"
|
Illegal TokenType = "ILLEGAL"
|
||||||
EOF TokenType = "EOF"
|
Eof TokenType = "EOF"
|
||||||
|
|
||||||
IDENT TokenType = "IDENT"
|
Ident TokenType = "IDENT"
|
||||||
INT TokenType = "INT"
|
Int TokenType = "INT"
|
||||||
|
|
||||||
SEMICOLON = ";"
|
Semicolon = ";"
|
||||||
EQUAL = "="
|
Equal = "="
|
||||||
OPEN_PAREN = "("
|
OpenParen = "("
|
||||||
CLOSE_PAREN = ")"
|
CloseParen = ")"
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
FN = "FN"
|
Fn = "FN"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LookupKeyword(literal string) TokenType {
|
func LookupKeyword(literal string) TokenType {
|
||||||
if value, ok := keywords[literal]; ok {
|
if value, ok := keywords[literal]; ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return IDENT
|
return Ident
|
||||||
}
|
}
|
||||||
|
33
ttir/ttir.go
Normal file
33
ttir/ttir.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package ttir
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
Functions []Function
|
||||||
|
}
|
||||||
|
|
||||||
|
type Function struct {
|
||||||
|
Name string
|
||||||
|
Instructions []Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
type Instruction interface {
|
||||||
|
String() string
|
||||||
|
instruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ret struct {
|
||||||
|
op Operand
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Ret) String() {}
|
||||||
|
func (r *Ret) instruction() {}
|
||||||
|
|
||||||
|
type Operand interface {
|
||||||
|
String() string
|
||||||
|
operand()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Constant struct {
|
||||||
|
Value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Constant) operand() {}
|
60
typechecker/checker.go
Normal file
60
typechecker/checker.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package typechecker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"robaertschi.xyz/robaertschi/tt/ast"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/tast"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Checker struct{}
|
||||||
|
|
||||||
|
func New() *Checker {
|
||||||
|
return &Checker{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
decls := []tast.Declaration{}
|
||||||
|
errs := []error{}
|
||||||
|
|
||||||
|
for _, decl := range program.Declarations {
|
||||||
|
decl, err := c.checkDeclaration(decl)
|
||||||
|
if err == nil {
|
||||||
|
decls = append(decls, decl)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tast.Program{Declarations: decls}, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) checkDeclaration(decl ast.Declaration) (tast.Declaration, error) {
|
||||||
|
switch decl := decl.(type) {
|
||||||
|
case *ast.FunctionDeclaration:
|
||||||
|
body, err := c.checkExpression(decl.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tast.FunctionDeclaration{Token: decl.Token, Body: body, ReturnType: body.Type(), Name: decl.Name}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unhandled declaration in type checker")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) checkExpression(expr ast.Expression) (tast.Expression, error) {
|
||||||
|
switch expr := expr.(type) {
|
||||||
|
case *ast.IntegerExpression:
|
||||||
|
return &tast.IntegerExpression{Token: expr.Token, Value: expr.Value}, nil
|
||||||
|
case *ast.ErrorExpression:
|
||||||
|
return nil, c.error(expr.InvalidToken, "invalid expression")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unhandled expression in type checker")
|
||||||
|
}
|
36
types/types.go
Normal file
36
types/types.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Type interface {
|
||||||
|
// Checks if the two types are the same
|
||||||
|
IsSameType(Type) bool
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeId struct {
|
||||||
|
id int64
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
I64Id int64 = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
I64 = New(I64Id, "i64")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ti *TypeId) IsSameType(t Type) bool {
|
||||||
|
if ti2, ok := t.(*TypeId); ok {
|
||||||
|
return ti.id == ti2.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ti *TypeId) Name() string {
|
||||||
|
return ti.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(id int64, name string) Type {
|
||||||
|
return &TypeId{id: id, name: name}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user