mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-15 21:43:30 +00:00
186 lines
5.1 KiB
Go
186 lines
5.1 KiB
Go
package typechecker
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"robaertschi.xyz/robaertschi/tt/ast"
|
|
"robaertschi.xyz/robaertschi/tt/tast"
|
|
"robaertschi.xyz/robaertschi/tt/token"
|
|
"robaertschi.xyz/robaertschi/tt/types"
|
|
)
|
|
|
|
type Variables map[string]types.Type
|
|
|
|
func copyVars(vars Variables) Variables {
|
|
newVars := make(Variables)
|
|
|
|
for k, v := range vars {
|
|
newVars[k] = v
|
|
}
|
|
|
|
return newVars
|
|
}
|
|
|
|
type Checker struct {
|
|
foundMain bool
|
|
functionVariables map[string]Variables
|
|
}
|
|
|
|
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) {
|
|
_, err := VarResolve(program)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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(c.functionVariables[decl.Name], 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(vars Variables, expr tast.Expression) error {
|
|
switch expr := expr.(type) {
|
|
case *tast.IntegerExpression:
|
|
return nil
|
|
case *tast.BooleanExpression:
|
|
return nil
|
|
case *tast.BinaryExpression:
|
|
lhsErr := c.checkExpression(vars, expr.Lhs)
|
|
rhsErr := c.checkExpression(vars, expr.Rhs)
|
|
var operandErr error
|
|
if lhsErr == nil && rhsErr == nil {
|
|
if !expr.Lhs.Type().IsSameType(expr.Rhs.Type()) {
|
|
operandErr = c.error(expr.Token, "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 = c.error(expr.Token, "the operator %q is not supported by the type %q", expr.Operator, expr.Lhs.Type().Name())
|
|
}
|
|
}
|
|
|
|
return errors.Join(lhsErr, rhsErr, operandErr)
|
|
case *tast.BlockExpression:
|
|
errs := []error{}
|
|
|
|
for _, expr := range expr.Expressions {
|
|
errs = append(errs, c.checkExpression(vars, expr))
|
|
}
|
|
if expr.ReturnExpression != nil {
|
|
errs = append(errs, c.checkExpression(vars, expr.ReturnExpression))
|
|
}
|
|
return errors.Join(errs...)
|
|
case *tast.IfExpression:
|
|
condErr := c.checkExpression(vars, expr.Condition)
|
|
if condErr == nil {
|
|
if !expr.Condition.Type().IsSameType(types.Bool) {
|
|
condErr = c.error(expr.Token, "the condition in the if should be a boolean, but got %q", expr.Condition.Type().Name())
|
|
}
|
|
}
|
|
thenErr := c.checkExpression(vars, expr.Then)
|
|
|
|
if expr.Else == nil {
|
|
return errors.Join(condErr, thenErr)
|
|
}
|
|
|
|
elseErr := c.checkExpression(vars, expr.Else)
|
|
if thenErr == nil && elseErr == nil {
|
|
if !expr.Then.Type().IsSameType(expr.Else.Type()) {
|
|
thenErr = c.error(expr.Token, "the then branch of type %q does not match with the else branch of type %q", expr.Then.Type().Name(), expr.Else.Type().Name())
|
|
}
|
|
}
|
|
return errors.Join(condErr, thenErr, elseErr)
|
|
case *tast.AssignmentExpression:
|
|
varRef, ok := expr.Lhs.(*tast.VariableReference)
|
|
if !ok {
|
|
return c.error(expr.Token, "not a valid assignment target")
|
|
}
|
|
|
|
if !expr.Lhs.Type().IsSameType(expr.Lhs.Type()) {
|
|
return c.error(
|
|
expr.Rhs.Tok(),
|
|
"the assignment rhs has the wrong type, variable %q has type %q but got %q",
|
|
varRef.Identifier,
|
|
varRef.Type().Name(),
|
|
expr.Rhs.String(),
|
|
)
|
|
}
|
|
return nil
|
|
case *tast.VariableDeclaration:
|
|
if !expr.VariableType.IsSameType(expr.InitializingExpression.Type()) {
|
|
return c.error(expr.InitializingExpression.Tok(),
|
|
"initializing expression for variable %q has wrong type, expected %q but got %q",
|
|
expr.Identifier,
|
|
expr.VariableType.Name(),
|
|
expr.InitializingExpression.Type().Name(),
|
|
)
|
|
}
|
|
return nil
|
|
case *tast.VariableReference:
|
|
return nil
|
|
case *tast.FunctionCall:
|
|
functionType := vars[expr.Identifier].(*types.FunctionType)
|
|
if len(expr.Arguments) != len(functionType.Parameters) {
|
|
return c.error(expr.Token, "invalid amount of arguments for function %q, expected %d but got %d", expr.Identifier, len(functionType.Parameters), len(expr.Arguments))
|
|
}
|
|
|
|
errs := []error{}
|
|
|
|
for i, param := range functionType.Parameters {
|
|
e := expr.Arguments[i]
|
|
if !e.Type().IsSameType(param) {
|
|
errs = append(errs, c.error(e.Tok(), "invalid type for parameter, expected %q but got %q", param.Name(), e.Type().Name()))
|
|
}
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
default:
|
|
panic(fmt.Sprintf("unexpected tast.Expression: %#v", expr))
|
|
}
|
|
return fmt.Errorf("unhandled expression %T in type checker", expr)
|
|
}
|