diff --git a/Architecture.md b/Architecture.md index 1903bb8..245aca6 100644 --- a/Architecture.md +++ b/Architecture.md @@ -11,3 +11,8 @@ AST --> Type Checking --> TAST --> IR Emission --> TTIR --> Codegen --> TTASM -- 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 + +## Type Checking +Passes: +- Type Inference +- Type Checking diff --git a/ast/ast.go b/ast/ast.go index 6a287ab..f5f6021 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -74,6 +74,15 @@ func (ie *IntegerExpression) expressionNode() {} func (ie *IntegerExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IntegerExpression) String() string { return ie.Token.Literal } +type BooleanExpression struct { + Token token.Token // The token.TRUE or token.FALSE + Value bool +} + +func (be *BooleanExpression) expressionNode() {} +func (be *BooleanExpression) TokenLiteral() string { return be.Token.Literal } +func (be *BooleanExpression) String() string { return be.Token.Literal } + //go:generate stringer -type=BinaryOperator type BinaryOperator int @@ -86,6 +95,10 @@ const ( NotEqual ) +func (bo BinaryOperator) IsBooleanOperator() bool { + return bo == Equal || bo == NotEqual +} + func (bo BinaryOperator) SymbolString() string { switch bo { case Add: @@ -113,5 +126,5 @@ type BinaryExpression struct { 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.SymbolString(), be.Rhs) + return fmt.Sprintf("(%s %s %s)", be.Lhs, be.Operator.SymbolString(), be.Rhs) } diff --git a/cmd/cmd.go b/cmd/cmd.go index 8563baf..3605e6f 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -128,7 +128,7 @@ func Compile(args Arguments) { tprogram, err := typechecker.New().CheckProgram(program) if err != nil { - fmt.Printf("Typechecker failed with %e\n", err) + fmt.Printf("Typechecker failed with: %v\n", err) os.Exit(1) } if (args.ToPrint & PrintTAst) != 0 { diff --git a/parser/parser.go b/parser/parser.go index ab1bdbd..14e1522 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -48,6 +48,8 @@ func New(l *lexer.Lexer) *Parser { p.prefixParseFns = make(map[token.TokenType]prefixParseFn) p.registerPrefixFn(token.Int, p.parseIntegerExpression) + p.registerPrefixFn(token.True, p.parseBooleanExpression) + p.registerPrefixFn(token.False, p.parseBooleanExpression) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfixFn(token.Plus, p.parseBinaryExpression) @@ -234,6 +236,23 @@ func (p *Parser) parseIntegerExpression() ast.Expression { return int } +func (p *Parser) parseBooleanExpression() ast.Expression { + var value bool + switch p.curToken.Type { + case token.True: + value = true + case token.False: + value = false + default: + return p.exprError(p.curToken, "invalid token for boolean expression %s", p.curToken.Type) + } + + return &ast.BooleanExpression{ + Token: p.curToken, + Value: value, + } +} + func (p *Parser) parseBinaryExpression(lhs ast.Expression) ast.Expression { var op ast.BinaryOperator switch p.curToken.Type { diff --git a/tast/tast.go b/tast/tast.go index 92a8b7c..6cb2451 100644 --- a/tast/tast.go +++ b/tast/tast.go @@ -58,6 +58,8 @@ type FunctionDeclaration struct { ReturnType types.Type } +var _ Declaration = &FunctionDeclaration{} + func (fd *FunctionDeclaration) declarationNode() {} func (fd *FunctionDeclaration) TokenLiteral() string { return fd.Token.Literal } func (fd *FunctionDeclaration) String() string { @@ -69,6 +71,8 @@ type IntegerExpression struct { Value int64 } +var _ Expression = &IntegerExpression{} + func (ie *IntegerExpression) expressionNode() {} func (ie *IntegerExpression) Type() types.Type { return types.I64 @@ -76,6 +80,20 @@ 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 BooleanExpression struct { + Token token.Token // The token.TRUE or token.FALSE + Value bool +} + +var _ Expression = &BooleanExpression{} + +func (be *BooleanExpression) expressionNode() {} +func (ie *BooleanExpression) Type() types.Type { + return types.Bool +} +func (be *BooleanExpression) TokenLiteral() string { return be.Token.Literal } +func (be *BooleanExpression) String() string { return be.Token.Literal } + type BinaryExpression struct { Token token.Token // The operator Lhs, Rhs Expression @@ -83,6 +101,8 @@ type BinaryExpression struct { ResultType types.Type } +var _ Expression = &BinaryExpression{} + func (be *BinaryExpression) expressionNode() {} func (be *BinaryExpression) Type() types.Type { return be.ResultType diff --git a/test b/test new file mode 100755 index 0000000..a637ba9 Binary files /dev/null and b/test differ diff --git a/test.tt b/test.tt index 2f2abbe..2ff1b9d 100644 --- a/test.tt +++ b/test.tt @@ -1 +1 @@ -fn main() = 3 != 3; +fn main() = true != true; diff --git a/token/token.go b/token/token.go index 85d31b7..c2c2494 100644 --- a/token/token.go +++ b/token/token.go @@ -16,7 +16,9 @@ type Token struct { } var keywords = map[string]TokenType{ - "fn": Fn, + "fn": Fn, + "true": True, + "false": False, } const ( @@ -40,7 +42,9 @@ const ( NotEqual TokenType = "!=" // Keywords - Fn TokenType = "FN" + Fn TokenType = "FN" + True TokenType = "TRUE" + False TokenType = "FALSE" ) func LookupKeyword(literal string) TokenType { diff --git a/ttir/emit.go b/ttir/emit.go index 0d36c93..09724c9 100644 --- a/ttir/emit.go +++ b/ttir/emit.go @@ -40,6 +40,12 @@ func emitExpression(expr tast.Expression) (Operand, []Instruction) { switch expr := expr.(type) { case *tast.IntegerExpression: return &Constant{Value: expr.Value}, []Instruction{} + case *tast.BooleanExpression: + value := int64(0) + if expr.Value { + value = 1 + } + return &Constant{Value: value}, []Instruction{} case *tast.BinaryExpression: switch expr.Operator { default: diff --git a/typechecker/check.go b/typechecker/check.go index e69de29..6dfa2c6 100644 --- a/typechecker/check.go +++ b/typechecker/check.go @@ -0,0 +1,90 @@ +package typechecker + +import ( + "errors" + "fmt" + + "robaertschi.xyz/robaertschi/tt/ast" + "robaertschi.xyz/robaertschi/tt/tast" + "robaertschi.xyz/robaertschi/tt/token" +) + +type Checker struct { + foundMain bool +} + +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) { + newProgram, err := c.inferTypes(program) + if err != nil { + return nil, err + } + + errs := []error{} + + for _, decl := range newProgram.Declarations { + err := c.checkDeclaration(decl) + if err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + + if !c.foundMain { + // TODO(Robin): Add support for libraries + errs = append(errs, errors.New("no function called 'main' found")) + } + + return newProgram, errors.Join(errs...) +} + +func (c *Checker) checkDeclaration(decl tast.Declaration) error { + switch decl := decl.(type) { + case *tast.FunctionDeclaration: + err := c.checkExpression(decl.Body) + + if err != nil { + return err + } + + if decl.Name == "main" { + c.foundMain = true + } + + return nil + } + return errors.New("unhandled declaration in type checker") +} + +func (c *Checker) checkExpression(expr tast.Expression) error { + switch expr := expr.(type) { + case *tast.IntegerExpression: + return nil + case *tast.BooleanExpression: + return nil + case *tast.BinaryExpression: + lhsErr := c.checkExpression(expr.Lhs) + rhsErr := c.checkExpression(expr.Rhs) + var operandErr error + if lhsErr == nil && rhsErr == nil { + if !expr.Lhs.Type().IsSameType(expr.Rhs.Type()) { + operandErr = fmt.Errorf("the lhs of the expression does not have the same type then the rhs, lhs=%q, rhs=%q", expr.Lhs.Type().Name(), expr.Rhs.Type().Name()) + } else if !expr.Lhs.Type().SupportsBinaryOperator(expr.Operator) { + operandErr = fmt.Errorf("the operator %q is not supported by the type %q", expr.Operator, expr.Lhs.Type().Name()) + } + } + + return errors.Join(lhsErr, rhsErr, operandErr) + } + return fmt.Errorf("unhandled expression in type checker") +} diff --git a/typechecker/infer.go b/typechecker/infer.go index e98b0ba..edb9a7d 100644 --- a/typechecker/infer.go +++ b/typechecker/infer.go @@ -6,28 +6,15 @@ import ( "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/tast" - "robaertschi.xyz/robaertschi/tt/token" "robaertschi.xyz/robaertschi/tt/types" ) -type Checker struct { - foundMain bool -} - -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) { +func (c *Checker) inferTypes(program *ast.Program) (*tast.Program, error) { decls := []tast.Declaration{} errs := []error{} for _, decl := range program.Declarations { - decl, err := c.checkDeclaration(decl) + decl, err := c.inferDeclaration(decl) if err == nil { decls = append(decls, decl) } else { @@ -35,52 +22,44 @@ func (c *Checker) CheckProgram(program *ast.Program) (*tast.Program, error) { } } - if !c.foundMain { - // TODO(Robin): Add support for libraries - errs = append(errs, errors.New("no function called 'main' found")) - } - return &tast.Program{Declarations: decls}, errors.Join(errs...) } -func (c *Checker) checkDeclaration(decl ast.Declaration) (tast.Declaration, error) { +func (c *Checker) inferDeclaration(decl ast.Declaration) (tast.Declaration, error) { switch decl := decl.(type) { case *ast.FunctionDeclaration: - body, err := c.checkExpression(decl.Body) + body, err := c.inferExpression(decl.Body) if err != nil { return nil, err } - if decl.Name == "main" { - c.foundMain = true - } - return &tast.FunctionDeclaration{Token: decl.Token, Body: body, ReturnType: body.Type(), Name: decl.Name}, nil } - return nil, errors.New("unhandled declaration in type checker") + return nil, errors.New("unhandled declaration in type inferer") } -func (c *Checker) checkExpression(expr ast.Expression) (tast.Expression, error) { +func (c *Checker) inferExpression(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.BooleanExpression: + return &tast.BooleanExpression{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 + lhs, lhsErr := c.inferExpression(expr.Lhs) + rhs, rhsErr := c.inferExpression(expr.Rhs) 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()) + if expr.Operator.IsBooleanOperator() { + resultType = types.Bool } 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 &tast.BinaryExpression{Lhs: lhs, Rhs: rhs, Operator: expr.Operator, Token: expr.Token, ResultType: resultType}, errors.Join(lhsErr, rhsErr) } - return nil, fmt.Errorf("unhandled expression in type checker") + return nil, fmt.Errorf("unhandled expression in type inferer") } diff --git a/types/types.go b/types/types.go index 995a1a2..322314a 100644 --- a/types/types.go +++ b/types/types.go @@ -1,8 +1,11 @@ package types +import "robaertschi.xyz/robaertschi/tt/ast" + type Type interface { // Checks if the two types are the same IsSameType(Type) bool + SupportsBinaryOperator(op ast.BinaryOperator) bool Name() string } @@ -13,12 +16,21 @@ type TypeId struct { const ( I64Id int64 = iota + BoolId ) var ( - I64 = New(I64Id, "i64") + I64 = New(I64Id, "i64") + Bool = New(BoolId, "bool") ) +func (ti *TypeId) SupportsBinaryOperator(op ast.BinaryOperator) bool { + if ti == Bool && !op.IsBooleanOperator() { + return false + } + return true +} + func (ti *TypeId) IsSameType(t Type) bool { if ti2, ok := t.(*TypeId); ok { return ti.id == ti2.id