diff --git a/main.go b/main.go index a7d48cf..f9f4505 100644 --- a/main.go +++ b/main.go @@ -38,4 +38,15 @@ func main() { } log.Printf("Parsed:\n%+#v\n\n", parsed) + + errors := validator(parsed) + if len(errors) != 0 { + for _, err = range errors { + if c, ok := err.(CompilerError); ok { + log.Fatalln(err, "\nv- here\n"+string([]rune(string(content))[c.Position:])) + } + + log.Fatalln(err) + } + } } diff --git a/parser.go b/parser.go index c1cb15a..8f8c46e 100644 --- a/parser.go +++ b/parser.go @@ -20,6 +20,10 @@ type Type struct { Value any } +const STRING_TYPE_NAME = "string" + +var STRING_TYPE = Type{Type: Type_Named, Value: STRING_TYPE_NAME} + type NamedType struct { TypeName string } @@ -46,6 +50,10 @@ type Statement struct { Value any } +type ExpressionStatement struct { + Expression Expression +} + type BlockStatement struct { Block Block } @@ -55,8 +63,9 @@ type ReturnStatement struct { } type DeclareLocalVariableStatement struct { - Variable string - Initializer *Expression + Variable string + VariableType Type + Initializer *Expression } type ExpressionType uint32 @@ -70,8 +79,9 @@ const ( ) type Expression struct { - Type ExpressionType - Value any + Type ExpressionType + Value any + ValueType Type } type AssignmentExpression struct { @@ -107,8 +117,14 @@ type TupleExpression struct { Members []Expression } +type Local struct { + Name string + Type Type +} + type Block struct { Statements []Statement + Locals map[string]Local } type ParsedParameter struct { @@ -474,7 +490,7 @@ func (p *Parser) tryDeclareLocalVariableStatement() (*Statement, error) { token := pCopy.nextToken() if token.Type == Type_Separator && token.Value.(Separator) == Separator_Semicolon { - return &Statement{Type: Statement_DeclareLocalVariable, Value: DeclareLocalVariableStatement{Variable: variableName, Initializer: nil}}, nil + return &Statement{Type: Statement_DeclareLocalVariable, Value: DeclareLocalVariableStatement{Variable: variableName, VariableType: *variableType, Initializer: nil}}, nil } if token.Type != Type_Operator || token.Value.(Operator) != Operator_Equals { @@ -494,7 +510,7 @@ func (p *Parser) tryDeclareLocalVariableStatement() (*Statement, error) { } *p = pCopy - return &Statement{Type: Statement_DeclareLocalVariable, Value: DeclareLocalVariableStatement{Variable: variableName, Initializer: initializer}}, nil + return &Statement{Type: Statement_DeclareLocalVariable, Value: DeclareLocalVariableStatement{Variable: variableName, VariableType: *variableType, Initializer: initializer}}, nil } func (p *Parser) expectStatement() (*Statement, error) { diff --git a/validator.go b/validator.go new file mode 100644 index 0000000..6bd412d --- /dev/null +++ b/validator.go @@ -0,0 +1,152 @@ +package main + +import ( + "errors" +) + +func createError(message string) error { + // TODO: pass token and get actual token position + return errors.New(message) +} + +func validateImport(imp *Import) []error { + // TODO + return nil +} + +func validateExpression(expr *Expression, block *Block) []error { + var errors []error + + switch expr.Type { + case Expression_Assignment: + assignment := expr.Value.(AssignmentExpression) + var local Local + var ok bool + if local, ok = block.Locals[assignment.Variable]; !ok { + errors = append(errors, createError("Assignment to undeclared variable "+assignment.Variable)) + return errors + } + + valueErrors := validateExpression(&assignment.Value, block) + if len(valueErrors) != 0 { + errors = append(errors, valueErrors...) + return errors + } + + // TODO: check if assignment is valid + expr.ValueType = local.Type + case Expression_Literal: + literal := expr.Value.(LiteralExpression) + + switch literal.Literal.Type { + case Literal_Boolean: + case Literal_Number: + expr.ValueType = Type{Type: Type_Primitive, Value: literal.Literal.Primitive} + case Literal_String: + expr.ValueType = STRING_TYPE + } + case Expression_VariableReference: + reference := expr.Value.(VariableReferenceExpression) + var local Local + var ok bool + if local, ok = block.Locals[reference.Variable]; !ok { + errors = append(errors, createError("Reference to undeclared variable "+reference.Variable)) + return errors + } + + expr.ValueType = local.Type + case Expression_Arithmetic: + arithmethic := expr.Value.(ArithmeticExpression) + + errors = append(errors, validateExpression(&arithmethic.Left, block)...) + errors = append(errors, validateExpression(&arithmethic.Right, block)...) + + if len(errors) != 0 { + return errors + } + + // TODO: validate types compatible and determine result type + case Expression_Tuple: + tuple := expr.Value.(TupleExpression) + + var types []Type + for _, member := range tuple.Members { + memberErrors := validateExpression(&member, block) + if len(memberErrors) != 0 { + errors = append(errors, memberErrors...) + continue + } + + types = append(types, member.ValueType) + } + + if len(errors) != 0 { + return errors + } + + expr.ValueType = Type{Type: Type_Tuple, Value: TupleType{Types: types}} + } + + return errors +} + +func validateStatement(stmt *Statement, block *Block) []error { + var errors []error + + switch stmt.Type { + case Statement_Expression: + expression := stmt.Value.(ExpressionStatement) + errors = append(errors, validateExpression(&expression.Expression, block)...) + case Statement_Block: + block := stmt.Value.(BlockStatement) + errors = append(errors, validateBlock(&block.Block)...) + case Statement_Return: + ret := stmt.Value.(ReturnStatement) + if ret.Value != nil { + errors = append(errors, validateExpression(ret.Value, block)...) + } + case Statement_DeclareLocalVariable: + dlv := stmt.Value.(DeclareLocalVariableStatement) + if dlv.Initializer != nil { + errors = append(errors, validateExpression(dlv.Initializer, block)...) + } + + block.Locals[dlv.Variable] = Local{Name: dlv.Variable, Type: dlv.VariableType} + } + + return errors +} + +func validateBlock(block *Block) []error { + var errors []error + + block.Locals = make(map[string]Local) + + for _, stmt := range block.Statements { + errors = append(errors, validateStatement(&stmt, block)...) + } + + return errors +} + +func validateFunction(function *ParsedFunction) []error { + var errors []error + + errors = append(errors, validateBlock(&function.Body)...) + + return errors +} + +func validator(file *ParsedFile) []error { + var errors []error + + for _, imp := range file.Imports { + errors = append(errors, validateImport(&imp)...) + } + + for _, function := range file.Functions { + errors = append(errors, validateFunction(&function)...) + } + + return errors +}