elysium/validator.go

574 lines
16 KiB
Go
Raw Normal View History

2024-03-16 20:12:00 +01:00
package main
import (
2024-03-28 15:48:50 +01:00
"fmt"
"strconv"
2024-03-16 20:12:00 +01:00
)
type Validator struct {
Files []*ParsedFile
2024-04-02 19:43:05 +02:00
Wasm64 bool
AllFunctions map[string]*ParsedFunction
2024-03-24 15:19:45 +01:00
CurrentBlock *Block
CurrentFunction *ParsedFunction
}
func isSameType(a Type, b Type) bool {
if a.Type != b.Type {
return false
}
switch a.Type {
case Type_Primitive:
return a.Value.(PrimitiveType) == b.Value.(PrimitiveType)
case Type_Named:
return a.Value.(NamedType).TypeName == b.Value.(NamedType).TypeName
case Type_Array:
return isSameType(a.Value.(ArrayType).ElementType, b.Value.(ArrayType).ElementType)
case Type_Tuple:
aTuple := a.Value.(TupleType)
bTuple := b.Value.(TupleType)
if len(aTuple.Types) != len(bTuple.Types) {
return false
}
for i := 0; i < len(aTuple.Types); i++ {
if !isSameType(aTuple.Types[i], bTuple.Types[i]) {
return false
}
}
return true
}
panic("type not implemented")
2024-03-16 20:12:00 +01:00
}
func isTypeExpandableTo(from Type, to Type) bool {
2024-03-24 15:19:45 +01:00
if from.Type != to.Type {
// cannot convert between primitive, named, array and tuple types
return false
}
if from.Type == Type_Primitive {
return isPrimitiveTypeExpandableTo(from.Value.(PrimitiveType), to.Value.(PrimitiveType))
}
2024-03-24 15:19:45 +01:00
if from.Type == Type_Tuple {
fromT := from.Value.(TupleType)
toT := to.Value.(TupleType)
if len(fromT.Types) != len(toT.Types) {
return false
}
for i := 0; i < len(fromT.Types); i++ {
if !isTypeExpandableTo(fromT.Types[i], toT.Types[i]) {
return false
}
}
return true
}
if from.Type == Type_Array {
return isSameType(from.Value.(ArrayType).ElementType, to.Value.(ArrayType).ElementType)
}
panic("not implemented")
2024-03-16 20:12:00 +01:00
}
func expandExpressionToType(expr *Expression, to Type) {
// TODO: merge with isTypeExpandableTo?
if isSameType(*expr.ValueType, to) {
return
}
if expr.Type == Expression_Tuple {
tupleExpr := expr.Value.(TupleExpression)
tupleType := to.Value.(TupleType)
for i := 0; i < len(tupleType.Types); i++ {
expandExpressionToType(&tupleExpr.Members[i], tupleType.Types[i])
}
expr.Value = tupleExpr
return
}
*expr = Expression{Type: Expression_Cast, Value: CastExpression{Type: to, Value: *expr}, ValueType: &to, Position: expr.Position}
}
func isPrimitiveTypeExpandableTo(from PrimitiveType, to PrimitiveType) bool {
2024-03-17 19:55:28 +01:00
if from == to {
return true
}
switch from {
2024-03-19 12:19:19 +01:00
case Primitive_I8, Primitive_U8:
2024-03-17 19:55:28 +01:00
if to == Primitive_I16 || to == Primitive_U16 {
return true
}
fallthrough
2024-03-19 12:19:19 +01:00
case Primitive_I16, Primitive_U16:
2024-03-17 19:55:28 +01:00
if to == Primitive_I32 || to == Primitive_U32 {
return true
}
fallthrough
2024-03-19 12:19:19 +01:00
case Primitive_I32, Primitive_U32:
2024-03-17 19:55:28 +01:00
if to == Primitive_I64 || to == Primitive_U64 {
return true
}
case Primitive_F32:
if to == Primitive_F64 {
return true
}
}
return false
}
func (v *Validator) createError(message string, position TokenPosition) error {
2024-03-21 19:55:05 +01:00
return CompilerError{Position: position, Message: message}
}
func (v *Validator) validateImport(imp *Import) []error {
2024-03-28 15:48:50 +01:00
// TODO imports
return nil
}
2024-03-24 14:01:23 +01:00
func (v *Validator) getArithmeticResultType(expr *Expression, left PrimitiveType, right PrimitiveType, operation Operation) (PrimitiveType, error) {
2024-03-17 19:55:28 +01:00
if left == Primitive_Bool || right == Primitive_Bool {
2024-03-21 19:55:05 +01:00
return InvalidValue, v.createError("bool type cannot be used in arithmetic expressions", expr.Position)
2024-03-17 19:55:28 +01:00
}
if isPrimitiveTypeExpandableTo(left, right) {
2024-03-17 19:55:28 +01:00
return right, nil
}
if isPrimitiveTypeExpandableTo(right, left) {
2024-03-17 19:55:28 +01:00
return left, nil
}
2024-04-09 13:57:17 +02:00
return InvalidValue, v.createError(fmt.Sprintf("cannot use the types [%s, %s] in an arithmetic expression without an explicit cast", left, right), expr.Position)
2024-03-17 19:55:28 +01:00
}
2024-03-24 20:53:34 +01:00
func getLocal(block *Block, variable string) *Local {
if local, ok := block.Locals[variable]; ok {
return &local
}
if block.Parent == nil {
return nil
}
return getLocal(block.Parent, variable)
}
2024-03-24 15:19:45 +01:00
func (v *Validator) validatePotentiallyVoidExpression(expr *Expression) []error {
2024-03-16 20:12:00 +01:00
var errors []error
switch expr.Type {
case Expression_Assignment:
assignment := expr.Value.(AssignmentExpression)
2024-04-02 19:43:05 +02:00
errors = append(errors, v.validateExpression(&assignment.Lhs)...)
2024-03-16 20:12:00 +01:00
2024-03-24 15:19:45 +01:00
valueErrors := v.validateExpression(&assignment.Value)
2024-03-16 20:12:00 +01:00
if len(valueErrors) != 0 {
errors = append(errors, valueErrors...)
return errors
}
2024-04-02 19:43:05 +02:00
if !isSameType(*assignment.Value.ValueType, *assignment.Lhs.ValueType) {
if !isTypeExpandableTo(*assignment.Value.ValueType, *assignment.Lhs.ValueType) {
errors = append(errors, v.createError(fmt.Sprintf("cannot assign expression of type %s to variable of type %s", *assignment.Value.ValueType, *assignment.Lhs.ValueType), expr.Position))
}
2024-04-02 19:43:05 +02:00
expandExpressionToType(&assignment.Value, *assignment.Lhs.ValueType)
2024-03-24 21:36:34 +01:00
}
2024-04-02 19:43:05 +02:00
expr.ValueType = assignment.Lhs.ValueType
2024-03-19 10:54:21 +01:00
expr.Value = assignment
2024-03-16 20:12:00 +01:00
case Expression_Literal:
literal := expr.Value.(LiteralExpression)
switch literal.Literal.Type {
2024-03-19 12:48:06 +01:00
case Literal_Boolean, Literal_Number:
2024-03-21 19:55:05 +01:00
expr.ValueType = &Type{Type: Type_Primitive, Value: literal.Literal.Primitive}
2024-03-16 20:12:00 +01:00
case Literal_String:
2024-03-21 19:55:05 +01:00
expr.ValueType = &STRING_TYPE
2024-03-16 20:12:00 +01:00
}
case Expression_VariableReference:
reference := expr.Value.(VariableReferenceExpression)
local := getLocal(v.CurrentBlock, reference.Variable)
2024-03-24 20:53:34 +01:00
if local == nil {
2024-03-21 19:55:05 +01:00
errors = append(errors, v.createError("Reference to undeclared variable "+reference.Variable, expr.Position))
2024-03-16 20:12:00 +01:00
return errors
}
2024-03-21 19:55:05 +01:00
expr.ValueType = &local.Type
2024-03-23 14:03:20 +01:00
case Expression_Binary:
2024-03-24 14:01:23 +01:00
binary := expr.Value.(BinaryExpression)
2024-03-16 20:12:00 +01:00
2024-03-24 15:19:45 +01:00
errors = append(errors, v.validateExpression(&binary.Left)...)
errors = append(errors, v.validateExpression(&binary.Right)...)
2024-03-16 20:12:00 +01:00
if len(errors) != 0 {
return errors
}
2024-03-24 14:01:23 +01:00
if isBooleanOperation(binary.Operation) {
if binary.Left.ValueType.Type != Type_Primitive || binary.Right.ValueType.Type != Type_Primitive {
errors = append(errors, v.createError("cannot compare non-primitive types", expr.Position))
return errors
}
var operandType Type
if isTypeExpandableTo(*binary.Left.ValueType, *binary.Right.ValueType) {
operandType = *binary.Right.ValueType
} else if isTypeExpandableTo(*binary.Right.ValueType, *binary.Left.ValueType) {
operandType = *binary.Left.ValueType
} else {
errors = append(errors, v.createError(fmt.Sprintf("cannot compare the types %s and %s without an explicit cast", binary.Left.ValueType.Value.(PrimitiveType), binary.Right.ValueType.Value.(PrimitiveType)), expr.Position))
2024-03-24 14:01:23 +01:00
return errors
}
expandExpressionToType(&binary.Left, operandType)
expandExpressionToType(&binary.Right, operandType)
2024-03-24 14:01:23 +01:00
expr.ValueType = &Type{Type: Type_Primitive, Value: Primitive_Bool}
2024-03-17 19:55:28 +01:00
}
2024-03-24 14:01:23 +01:00
if isArithmeticOperation(binary.Operation) {
if binary.Left.ValueType.Type != Type_Primitive || binary.Right.ValueType.Type != Type_Primitive {
2024-03-28 15:48:50 +01:00
errors = append(errors, v.createError("both sides of an arithmetic expression must evaluate to a primitive type", expr.Position))
2024-03-24 14:01:23 +01:00
return errors
}
leftType := binary.Left.ValueType.Value.(PrimitiveType)
rightType := binary.Right.ValueType.Value.(PrimitiveType)
result, err := v.getArithmeticResultType(expr, leftType, rightType, binary.Operation)
if err != nil {
errors = append(errors, err)
return errors
}
expr.ValueType = &Type{Type: Type_Primitive, Value: result}
2024-03-17 19:55:28 +01:00
}
2024-03-24 14:01:23 +01:00
expr.Value = binary
2024-03-16 20:12:00 +01:00
case Expression_Tuple:
tuple := expr.Value.(TupleExpression)
var types []Type
2024-03-17 19:55:28 +01:00
for i := range tuple.Members {
member := &tuple.Members[i]
2024-03-24 15:19:45 +01:00
memberErrors := v.validateExpression(member)
2024-03-16 20:12:00 +01:00
if len(memberErrors) != 0 {
errors = append(errors, memberErrors...)
continue
}
2024-03-21 19:55:05 +01:00
types = append(types, *member.ValueType)
2024-03-16 20:12:00 +01:00
}
if len(errors) != 0 {
return errors
}
2024-03-21 19:55:05 +01:00
expr.ValueType = &Type{Type: Type_Tuple, Value: TupleType{Types: types}}
2024-03-19 10:54:21 +01:00
expr.Value = tuple
case Expression_FunctionCall:
fc := expr.Value.(FunctionCallExpression)
calledFunc, ok := v.AllFunctions[fc.Function]
if !ok {
2024-03-21 19:55:05 +01:00
errors = append(errors, v.createError("call to undefined function '"+fc.Function+"'", expr.Position))
return errors
}
if fc.Parameters != nil {
2024-03-24 15:19:45 +01:00
paramsErrors := v.validateExpression(fc.Parameters)
2024-03-21 20:37:21 +01:00
if len(paramsErrors) != 0 {
2024-03-21 19:55:05 +01:00
errors = append(errors, paramsErrors...)
return errors
}
params := []*Expression{fc.Parameters}
if fc.Parameters.Type == Expression_Tuple {
for i := 0; i < len(fc.Parameters.Value.(TupleExpression).Members); i++ {
params[i] = &fc.Parameters.Value.(TupleExpression).Members[i]
}
}
if len(params) != len(calledFunc.Parameters) {
2024-03-21 19:55:05 +01:00
errors = append(errors, v.createError("wrong number of arguments in function call: expected "+strconv.Itoa(len(calledFunc.Parameters))+", got "+strconv.Itoa(len(params)), expr.Position))
}
for i := 0; i < min(len(params), len(calledFunc.Parameters)); i++ {
typeGiven := params[i]
typeExpected := calledFunc.Parameters[i]
2024-03-21 19:55:05 +01:00
if !isTypeExpandableTo(*typeGiven.ValueType, typeExpected.Type) {
errors = append(errors, v.createError("invalid type for parameter "+strconv.Itoa(i), expr.Position))
}
expandExpressionToType(typeGiven, typeExpected.Type)
}
}
expr.ValueType = calledFunc.ReturnType
expr.Value = fc
2024-04-09 13:57:17 +02:00
case Expression_Unary:
unary := expr.Value.(UnaryExpression)
2024-04-09 13:57:17 +02:00
valErrors := v.validateExpression(&unary.Value)
2024-03-21 20:37:21 +01:00
if len(valErrors) != 0 {
2024-03-21 19:55:05 +01:00
errors = append(errors, valErrors...)
return errors
}
2024-04-09 13:57:17 +02:00
if unary.Value.ValueType.Type != Type_Primitive {
errors = append(errors, v.createError("cannot operate on non-primitive types", expr.Position))
} else {
primitive := unary.Value.ValueType.Value.(PrimitiveType)
if (unary.Operation == UnaryOperation_Negate || unary.Operation == UnaryOperation_Nop /* + sign */) && !isSignedInt(primitive) && !isFloatingPoint(primitive) {
errors = append(errors, v.createError("can only perform negation/unary plus on signed types", expr.Position))
}
if unary.Operation == UnaryOperation_LogicalNot && primitive != Primitive_Bool {
errors = append(errors, v.createError("cannot perform logical not on non-bool type", expr.Position))
}
if unary.Operation == UnaryOperation_BitwiseNot && !isUnsignedInt(primitive) && !isSignedInt(primitive) {
errors = append(errors, v.createError("cannot perform bitwise not on non-integer type", expr.Position))
}
}
2024-04-09 13:57:17 +02:00
expr.ValueType = unary.Value.ValueType
expr.Value = unary
2024-04-02 19:43:05 +02:00
case Expression_RawMemoryReference:
raw := expr.Value.(RawMemoryReferenceExpression)
addrErrors := v.validateExpression(&raw.Address)
if len(addrErrors) != 0 {
errors = append(errors, addrErrors...)
return errors
}
if raw.Address.ValueType.Type != Type_Primitive || raw.Address.ValueType.Value.(PrimitiveType) != Primitive_U64 {
errors = append(errors, v.createError("address must evaluate to a u64 value", expr.Position))
return errors
}
if !v.Wasm64 {
castTo := Type{Type: Type_Primitive, Value: Primitive_U32}
raw.Address = Expression{Type: Expression_Cast, Value: CastExpression{Type: castTo, Value: raw.Address}, ValueType: &castTo, Position: raw.Address.Position}
}
expr.ValueType = &raw.Type
expr.Value = raw
2024-03-24 14:01:23 +01:00
default:
panic("expr not implemented")
2024-03-16 20:12:00 +01:00
}
return errors
}
2024-03-24 15:19:45 +01:00
func (v *Validator) validateExpression(expr *Expression) []error {
errors := v.validatePotentiallyVoidExpression(expr)
2024-03-21 20:37:21 +01:00
if len(errors) != 0 {
return errors
}
2024-03-21 19:55:05 +01:00
if expr.ValueType == nil {
errors = append(errors, v.createError("expression must not evaluate to void", expr.Position))
}
return errors
}
2024-03-24 15:19:45 +01:00
func (v *Validator) validateStatement(stmt *Statement, functionLocals *[]Local) []error {
2024-03-16 20:12:00 +01:00
var errors []error
switch stmt.Type {
case Statement_Expression:
expression := stmt.Value.(ExpressionStatement)
2024-03-24 15:19:45 +01:00
errors = append(errors, v.validatePotentiallyVoidExpression(&expression.Expression)...)
2024-03-21 19:55:05 +01:00
stmt.Value = expression
2024-03-16 20:12:00 +01:00
case Statement_Block:
block := stmt.Value.(BlockStatement)
2024-03-24 20:53:34 +01:00
errors = append(errors, v.validateBlock(block.Block, functionLocals)...)
2024-03-21 19:55:05 +01:00
stmt.Value = block
2024-03-16 20:12:00 +01:00
case Statement_Return:
ret := stmt.Value.(ReturnStatement)
if ret.Value != nil {
if v.CurrentFunction.ReturnType == nil {
2024-03-24 15:19:45 +01:00
errors = append(errors, v.createError("cannot return value from void function", stmt.Position))
return errors
}
errors = append(errors, v.validateExpression(ret.Value)...)
if len(errors) != 0 {
return errors
}
if !isTypeExpandableTo(*ret.Value.ValueType, *v.CurrentFunction.ReturnType) {
errors = append(errors, v.createError(fmt.Sprintf("cannot return value of type %s from function returning %s", *ret.Value.ValueType, *v.CurrentFunction.ReturnType), ret.Value.Position))
2024-03-24 15:19:45 +01:00
}
expandExpressionToType(ret.Value, *v.CurrentFunction.ReturnType)
} else if v.CurrentFunction.ReturnType != nil {
2024-03-24 15:19:45 +01:00
errors = append(errors, v.createError("missing return value", stmt.Position))
2024-03-16 20:12:00 +01:00
}
2024-03-24 20:53:34 +01:00
stmt.Value = ret
2024-03-16 20:12:00 +01:00
case Statement_DeclareLocalVariable:
dlv := stmt.Value.(DeclareLocalVariableStatement)
if dlv.Initializer != nil {
2024-03-24 15:19:45 +01:00
errors = append(errors, v.validateExpression(dlv.Initializer)...)
2024-03-24 21:36:34 +01:00
if errors != nil {
return errors
}
if !isTypeExpandableTo(*dlv.Initializer.ValueType, dlv.VariableType) {
2024-03-28 15:48:50 +01:00
errors = append(errors, v.createError(fmt.Sprintf("cannot assign expression of type %s to variable of type %s", *dlv.Initializer.ValueType, dlv.VariableType), stmt.Position))
2024-03-24 21:36:34 +01:00
}
expandExpressionToType(dlv.Initializer, dlv.VariableType)
2024-03-16 20:12:00 +01:00
}
if getLocal(v.CurrentBlock, dlv.Variable) != nil {
2024-03-21 19:55:05 +01:00
errors = append(errors, v.createError("redeclaration of variable '"+dlv.Variable+"'", stmt.Position))
2024-03-17 19:55:28 +01:00
}
2024-03-18 21:14:28 +01:00
local := Local{Name: dlv.Variable, Type: dlv.VariableType, IsParameter: false, Index: len(*functionLocals)}
v.CurrentBlock.Locals[dlv.Variable] = local
2024-03-18 21:14:28 +01:00
*functionLocals = append(*functionLocals, local)
2024-03-19 12:48:06 +01:00
2024-03-24 14:01:23 +01:00
stmt.Value = dlv
case Statement_If:
ifS := stmt.Value.(IfStatement)
2024-03-24 15:19:45 +01:00
errors = append(errors, v.validateExpression(&ifS.Condition)...)
2024-03-24 20:53:34 +01:00
errors = append(errors, v.validateBlock(ifS.ConditionalBlock, functionLocals)...)
2024-03-24 14:01:23 +01:00
if ifS.ElseBlock != nil {
errors = append(errors, v.validateBlock(ifS.ElseBlock, functionLocals)...)
}
if len(errors) != 0 {
return errors
}
if ifS.Condition.ValueType.Type != Type_Primitive || ifS.Condition.ValueType.Value.(PrimitiveType) != Primitive_Bool {
errors = append(errors, v.createError("condition must evaluate to boolean", ifS.Condition.Position))
}
stmt.Value = ifS
case Statement_WhileLoop:
while := stmt.Value.(WhileLoopStatement)
errors = append(errors, v.validateExpression(&while.Condition)...)
errors = append(errors, v.validateBlock(while.Body, functionLocals)...)
if len(errors) != 0 {
return errors
}
if while.Condition.ValueType.Type != Type_Primitive || while.Condition.ValueType.Value.(PrimitiveType) != Primitive_Bool {
errors = append(errors, v.createError("condition must evaluate to boolean", while.Condition.Position))
}
stmt.Value = while
2024-03-24 14:01:23 +01:00
default:
panic("stmt not implemented")
2024-03-16 20:12:00 +01:00
}
return errors
}
func (v *Validator) validateBlock(block *Block, functionLocals *[]Local) []error {
2024-03-16 20:12:00 +01:00
var errors []error
2024-03-17 19:55:28 +01:00
if block.Locals == nil {
block.Locals = make(map[string]Local)
}
2024-03-16 20:12:00 +01:00
2024-03-17 19:55:28 +01:00
for i := range block.Statements {
v.CurrentBlock = block
2024-03-17 19:55:28 +01:00
stmt := &block.Statements[i]
2024-03-24 15:19:45 +01:00
errors = append(errors, v.validateStatement(stmt, functionLocals)...)
2024-03-16 20:12:00 +01:00
}
return errors
}
func (v *Validator) validateFunction(function *ParsedFunction) []error {
2024-03-16 20:12:00 +01:00
var errors []error
2024-03-18 21:14:28 +01:00
var locals []Local
v.CurrentFunction = function
2024-03-24 15:19:45 +01:00
2024-03-24 20:53:34 +01:00
body := function.Body
2024-03-17 19:55:28 +01:00
body.Locals = make(map[string]Local)
for _, param := range function.Parameters {
2024-03-18 21:14:28 +01:00
local := Local{Name: param.Name, Type: param.Type, IsParameter: true, Index: len(locals)}
locals = append(locals, local)
body.Locals[param.Name] = local
2024-03-17 19:55:28 +01:00
}
errors = append(errors, v.validateBlock(body, &locals)...)
2024-03-17 19:55:28 +01:00
2024-03-24 21:36:34 +01:00
// TODO: validate that function returns return value
2024-03-18 21:14:28 +01:00
function.Locals = locals
2024-03-16 20:12:00 +01:00
return errors
}
func (v *Validator) validate() []error {
2024-03-16 20:12:00 +01:00
var errors []error
v.AllFunctions = make(map[string]*ParsedFunction)
for _, file := range v.Files {
for i := range file.Functions {
function := &file.Functions[i]
fullFunctionName := function.Name
if file.Module != "" {
fullFunctionName = file.Module + "." + fullFunctionName
}
function.FullName = fullFunctionName
if _, exists := v.AllFunctions[fullFunctionName]; exists {
errors = append(errors, v.createError("duplicate function "+fullFunctionName, function.ReturnType.Position))
}
v.AllFunctions[fullFunctionName] = function
}
2024-03-16 20:12:00 +01:00
}
for _, file := range v.Files {
for i := range file.Imports {
errors = append(errors, v.validateImport(&file.Imports[i])...)
}
for i := range file.Functions {
errors = append(errors, v.validateFunction(&file.Functions[i])...)
}
2024-03-16 20:12:00 +01:00
}
return errors
}