acme/semanticAnalyzer.go
2020-04-28 16:54:39 +02:00

1358 lines
40 KiB
Go

package main
import (
"fmt"
)
// getVar goes through all symbol varTables recursively and looks for an entry for the given variable name v
func (s *SymbolTable) getVar(v string) (SymbolVarEntry, bool) {
if s == nil {
return SymbolVarEntry{}, false
}
if variable, ok := s.varTable[v]; ok {
return variable, true
}
return s.parent.getVar(v)
}
// isLocalVar only searches the immediate local symbol varTable
func (s *SymbolTable) isLocalVar(v string) bool {
if s == nil {
return false
}
_, ok := s.varTable[v]
return ok
}
func (s *SymbolTable) getLocalVars() (keys []string) {
for k := range s.varTable {
keys = append(keys, k)
}
return
}
func (s *SymbolTable) setVar(v string, t ComplexType, isIndexed bool) {
s.varTable[v] = SymbolVarEntry{t, "", 0, isIndexed}
}
func (s *SymbolTable) setType(v string, members []StructMem) {
// Only set type definitions in the upper-most symbol table!
if s != nil && s.parent == nil {
s.typeTable[v] = SymbolTypeEntry{members, 0}
return
}
s.parent.setType(v, members)
}
func (s *SymbolTable) getType(v string) (SymbolTypeEntry, bool) {
// Only query type definitions from the upper-most symbol table!
if s != nil && s.parent == nil {
entry, ok := s.typeTable[v]
return entry, ok
}
return s.parent.getType(v)
}
func (s *SymbolTable) setFun(name string, argTypes, returnTypes []ComplexType, inline bool) {
l := s.funTable[name]
l = append(l, SymbolFunEntry{argTypes, returnTypes, "", "", 0, inline, false})
s.funTable[name] = l
}
func (s *SymbolTable) getLocalFun(name string, paramTypes []ComplexType, strict bool) (SymbolFunEntry, bool) {
if s == nil {
return SymbolFunEntry{}, false
}
l, ok := s.funTable[name]
if !ok {
return SymbolFunEntry{}, false
}
for _, le := range l {
if equalTypes(le.paramTypes, paramTypes, strict) {
return le, true
}
}
return SymbolFunEntry{}, false
}
func (s *SymbolTable) isLocalFun(name string, paramTypes []ComplexType) bool {
_, ok := s.getLocalFun(name, paramTypes, true)
return ok
}
func (s *SymbolTable) getFun(name string, paramTypes []ComplexType, strict bool) (SymbolFunEntry, bool) {
if s == nil {
return SymbolFunEntry{}, false
}
if localFun, ok := s.getLocalFun(name, paramTypes, strict); ok {
return localFun, true
}
return s.parent.getFun(name, paramTypes, strict)
}
func (s *SymbolTable) setVarAsmName(v string, asmName string) {
if s == nil {
panic("Could not set asm variable name in symbol table!")
return
}
if _, ok := s.varTable[v]; ok {
tmp := s.varTable[v]
tmp.varName = asmName
s.varTable[v] = tmp
return
}
s.parent.setVarAsmName(v, asmName)
}
func (s *SymbolTable) setVarAsmOffset(v string, offset int) {
if s == nil {
panic("Could not set asm variable name in symbol table!")
return
}
if _, ok := s.varTable[v]; ok {
tmp := s.varTable[v]
tmp.offset = offset
s.varTable[v] = tmp
return
}
s.parent.setVarAsmOffset(v, offset)
}
func (s *SymbolTable) setLoopJumpLabels(br, cont string) {
s.activeLoopBreakLabel = br
s.activeLoopContinueLabel = cont
}
func (s *SymbolTable) getLoopBreakLabel() string {
if s.activeLoopBreakLabel != "" {
return s.activeLoopBreakLabel
}
return s.parent.getLoopBreakLabel()
}
func (s *SymbolTable) getLoopContinueLabel() string {
if s.activeLoopContinueLabel != "" {
return s.activeLoopContinueLabel
}
return s.parent.getLoopContinueLabel()
}
func (s *SymbolTable) setFunAsmName(v string, asmName string, paramTypes []ComplexType, strict bool) {
if s == nil {
panic("Could not set asm function name in symbol table!")
}
l, ok := s.funTable[v]
if !ok {
panic("No such function in funTable. Can not set asm name")
}
for i, le := range l {
if equalTypes(le.paramTypes, paramTypes, strict) {
le.jumpLabel = asmName
l[i] = le
s.funTable[v] = l
return
}
}
s.parent.setFunAsmName(v, asmName, paramTypes, strict)
}
func (s *SymbolTable) funIsInline(name string, paramTypes []ComplexType, strict bool) bool {
if e, ok := s.getFun(name, paramTypes, strict); ok {
return e.inline
}
panic("No such function in funTable. Cannot query used status")
}
func (s *SymbolTable) funIsUsed(name string, paramTypes []ComplexType, strict bool) bool {
if e, ok := s.getFun(name, paramTypes, strict); ok {
return e.isUsed
}
panic("No such function in funTable. Cannot query used status")
}
func (s *SymbolTable) setFunEpilogueLabel(v string, label string, paramTypes []ComplexType) {
if s == nil {
panic("Could not set asm epilogue label in symbol table!")
return
}
l, ok := s.funTable[v]
if !ok {
panic("No such function in funTable. Can not set epilogue label.")
}
for i, le := range l {
if equalTypes(le.paramTypes, paramTypes, true) {
le.epilogueLabel = label
l[i] = le
s.funTable[v] = l
return
}
}
s.parent.setFunEpilogueLabel(v, label, paramTypes)
}
func (s *SymbolTable) setFunIsUsed(v string, paramTypes []ComplexType, isUsed bool) {
if s == nil {
panic("Could not set isUsed flag in symbol table!")
return
}
if l, ok := s.funTable[v]; ok {
for i, le := range l {
if equalTypes(le.paramTypes, paramTypes, false) {
le.isUsed = isUsed
l[i] = le
s.funTable[v] = l
return
}
}
}
s.parent.setFunIsUsed(v, paramTypes, isUsed)
}
func (s *SymbolTable) setFunReturnStackPointer(v string, offset int, paramTypes []ComplexType) {
if s == nil {
panic("Could not set asm return stack pointer in symbol table!")
return
}
l, ok := s.funTable[v]
if !ok {
panic("No such function in funTable. Can not set return stack pointer.")
}
for i, le := range l {
if equalTypes(le.paramTypes, paramTypes, true) {
le.returnStackPointerOffset = offset
l[i] = le
s.funTable[v] = l
return
}
}
s.parent.setFunReturnStackPointer(v, offset, paramTypes)
}
func analyzeUnaryOp(unaryOp UnaryOp, symbolTable *SymbolTable) (Expression, error) {
// Re-order expression, if the expression is not fixed and the priority is of the operator is not according to the priority
// The priority of an operator must be equal or higher in (right) sub-trees (as they are evaluated first).
if tmpE, ok := unaryOp.expr.(BinaryOp); ok {
if unaryOp.operator.priority() < tmpE.operator.priority() && !tmpE.fixed {
newChild := unaryOp
newChild.expr = tmpE.leftExpr
tmpE.leftExpr = newChild
var newRoot Expression
newRoot = tmpE
// The unary expression is now a binary, so we have to start over. The following checks won't be working any more.
expression, err := analyzeExpression(newRoot, symbolTable)
if err != nil {
return newRoot, err
}
newRoot = expression
return newRoot, nil
}
}
expression, err := analyzeExpression(unaryOp.expr, symbolTable)
if err != nil {
return unaryOp, err
}
unaryOp.expr = expression
if expression.getResultCount() != 1 {
return nil, fmt.Errorf("%w[%v:%v] - Unary expression can only handle one result", ErrCritical, unaryOp.line, unaryOp.column)
}
t := expression.getExpressionTypes(symbolTable)[0]
switch unaryOp.operator {
case OP_NEGATIVE:
if t.t != TYPE_FLOAT && t.t != TYPE_INT {
return nil, fmt.Errorf("%w[%v:%v] - Unary '-' expression must be float or int, but is: %v", ErrCritical, unaryOp.line, unaryOp.column, unaryOp)
}
unaryOp.opType = t
return unaryOp, nil
case OP_NOT:
if t.t != TYPE_BOOL {
return nil, fmt.Errorf("%w[%v:%v] - Unary '!' expression must be bool, but is: %v", ErrCritical, unaryOp.line, unaryOp.column, unaryOp)
}
unaryOp.opType = ComplexType{TYPE_BOOL, "", nil}
return unaryOp, nil
}
return nil, fmt.Errorf("%w[%v:%v] - Unknown unary expression: %v", ErrCritical, unaryOp.line, unaryOp.column, unaryOp)
}
func analyzeBinaryOp(binaryOp BinaryOp, symbolTable *SymbolTable) (Expression, error) {
// Re-order expression, if the expression is not fixed and the priority is of the operator is not according to the priority
// The priority of an operator must be equal or higher in (right) sub-trees (as they are evaluated first).
if tmpE, ok := binaryOp.rightExpr.(BinaryOp); ok {
if binaryOp.operator.priority() < tmpE.operator.priority() && !tmpE.fixed {
newChild := binaryOp
newChild.rightExpr = tmpE.leftExpr
tmpE.leftExpr = newChild
binaryOp = tmpE
}
}
leftExpression, err := analyzeExpression(binaryOp.leftExpr, symbolTable)
if err != nil {
return binaryOp, err
}
binaryOp.leftExpr = leftExpression
rightExpression, err := analyzeExpression(binaryOp.rightExpr, symbolTable)
if err != nil {
return binaryOp, err
}
binaryOp.rightExpr = rightExpression
if binaryOp.leftExpr.getResultCount() != binaryOp.rightExpr.getResultCount() || binaryOp.leftExpr.getResultCount() != 1 {
return nil, fmt.Errorf("%w[%v:%v] - BinaryOp %v expected two values, got %v and %v",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator,
binaryOp.leftExpr.getResultCount(), binaryOp.rightExpr.getResultCount(),
)
}
tLeft := binaryOp.leftExpr.getExpressionTypes(symbolTable)[0]
tRight := binaryOp.rightExpr.getExpressionTypes(symbolTable)[0]
// Check types only after we possibly rearranged the expression!
if binaryOp.leftExpr.getExpressionTypes(symbolTable)[0] != binaryOp.rightExpr.getExpressionTypes(symbolTable)[0] {
return binaryOp, fmt.Errorf(
"%w[%v:%v] - BinaryOp '%v' expected same type, got: '%v', '%v'",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator, tLeft, tRight,
)
}
// We match all types explicitely to make sure that this still works or creates an error when we introduce new types
// that are not considered yet!
switch binaryOp.operator {
case OP_AND, OP_OR:
binaryOp.opType = ComplexType{TYPE_BOOL, "", nil}
// We know left and right are the same type, so only compare left here.
if tLeft.t != TYPE_BOOL {
return binaryOp, fmt.Errorf(
"%w[%v:%v] - BinaryOp '%v' needs bool, got: '%v'",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator, tLeft,
)
}
//return binaryOp, TYPE_BOOL, nil
case OP_MOD:
binaryOp.opType = ComplexType{TYPE_INT, "", nil}
if tLeft.t != TYPE_INT {
return binaryOp, fmt.Errorf(
"%w[%v:%v] - BinaryOp '%v' only works for int",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator,
)
}
case OP_PLUS, OP_MINUS, OP_MULT, OP_DIV:
if tLeft.t == TYPE_FLOAT {
binaryOp.opType = ComplexType{TYPE_FLOAT, "", nil}
} else {
binaryOp.opType = ComplexType{TYPE_INT, "", nil}
}
if tLeft.t != TYPE_FLOAT && tLeft.t != TYPE_INT {
return binaryOp, fmt.Errorf(
"%w[%v:%v] - BinaryOp '%v' needs int/float, got: '%v'",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator, tLeft,
)
}
//return binaryOp, tLeft, nil
case OP_LE, OP_GE, OP_LESS, OP_GREATER:
binaryOp.opType.t = TYPE_BOOL
if tLeft.t != TYPE_FLOAT && tLeft.t != TYPE_INT && tLeft.t != TYPE_STRING {
return binaryOp, fmt.Errorf(
"%w[%v:%v] - BinaryOp '%v' needs int/float/string, got: '%v'",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator, tLeft,
)
}
//return binaryOp, TYPE_BOOL, nil
case OP_EQ, OP_NE:
binaryOp.opType = ComplexType{TYPE_BOOL, "", nil}
// We can actually compare all data types. So there will be no missmatch in general!
default:
return binaryOp, fmt.Errorf(
"%w[%v:%v] - Invalid binary operator: '%v' for type '%v'",
ErrCritical, binaryOp.line, binaryOp.column, binaryOp.operator, tLeft,
)
}
return binaryOp, nil
}
func analyzeDirectAccess(t ComplexType, directAccess []DirectAccess, symbolTable *SymbolTable) ([]DirectAccess, error) {
// If any of the accesses are an Array, we switch to heap memory and have to negate the offset in memory!
isHeapMemory := false
for i, access := range directAccess {
if t.t == TYPE_ARRAY {
isHeapMemory = true
}
if access.indexed {
if t.t != TYPE_ARRAY {
row, col := access.indexExpression.startPos()
return nil, fmt.Errorf("%w[%v:%v] - Indexing only works on arrays, ... got %v",
ErrCritical, row, col, t.t,
)
}
if t.subType == nil {
row, col := access.indexExpression.startPos()
return nil, fmt.Errorf("%w[%v:%v] - Indexing only works on arrays, got %v",
ErrCritical, row, col, t.t,
)
}
newIndexExpression, tmpE := analyzeExpression(access.indexExpression, symbolTable)
if tmpE != nil {
return nil, tmpE
}
directAccess[i].indexExpression = newIndexExpression
ts := newIndexExpression.getExpressionTypes(symbolTable)
if len(ts) != 1 || ts[0].getMemCount(symbolTable) != 1 {
row, col := access.indexExpression.startPos()
return nil, fmt.Errorf("%w[%v:%v] - Index expression can only have one value",
ErrCritical, row, col,
)
}
if ts[0].t != TYPE_INT {
row, col := access.indexExpression.startPos()
return nil, fmt.Errorf("%w[%v:%v] - Index expression must be int",
ErrCritical, row, col,
)
}
t = *t.subType
} else {
if t.t != TYPE_STRUCT {
return nil, fmt.Errorf("%w[%v:%v] - Qualified name access only works on structs",
ErrCritical, access.line, access.column,
)
}
entry, ok := symbolTable.getType(t.tName)
if !ok {
return nil, fmt.Errorf("%w[%v:%v] - Struct type '%v' used before declaration",
ErrCritical, access.line, access.column, t.tName,
)
}
memberIndex := -1
memberOffset := 0
for j, m := range entry.members {
if m.memName == access.accessName {
memberIndex = j
// For stack memory, the offset is negative!
memberOffset = -m.offset
break
}
}
if memberIndex == -1 {
return nil, fmt.Errorf("%w[%v:%v] - '%v' is not a member of struct '%v'",
ErrCritical, access.line, access.column, access.accessName, t.tName,
)
}
if isHeapMemory {
memberOffset = -memberOffset
}
directAccess[i].structOffset = memberOffset
t = entry.members[memberIndex].memType
}
}
return directAccess, nil
}
// Sometimes we need a bit of special handling to work with system-side generic functions
// like 'append()', to keep our type system going on the programmer and semantic side ...
func analyzeFunCallReturnTypes(fun FunCall, args []ComplexType, symbolTable *SymbolTable) ([]ComplexType, error) {
funEntry, _ := symbolTable.getFun(fun.funName, args, false)
types := funEntry.returnTypes
if len(types) == 1 && types[0].typeIsGeneric() {
arrayType := ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}
switch fun.funName {
case "extend":
if len(args) != 2 {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' needs two parameters",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
if !equalType(args[0], arrayType, false) {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' needs array as first parameter",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
// Both parameters to extend should be equal.
if !equalType(args[0], args[1], true) {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' can only be called with arrays (of the same type)",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
// Whatever we dump into append, we get out again!
return []ComplexType{args[0]}, nil
case "append":
if len(args) != 2 {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' needs two parameters",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
if !equalType(args[0], arrayType, false) {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' needs array as first parameter",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
// We expect the append element function
// The element must have the same type as the array.
if !equalType(*args[0].subType, args[1], true) {
return nil, fmt.Errorf("%w[%v:%v] - Function '%v' type mismatch between array type and given element to append",
ErrCritical, fun.line, fun.column, fun.funName,
)
}
// Whatever we dump into append, we get out again!
return []ComplexType{args[0]}, nil
default:
return nil, fmt.Errorf("%w[%v:%v] - Unknown generic function %v", ErrCritical, fun.line, fun.column, fun.funName)
}
}
return types, nil
}
func structMemToTypeList(mems []StructMem) (types []ComplexType) {
for _, m := range mems {
types = append(types, m.memType)
}
return
}
func analyzeFunCall(fun FunCall, symbolTable *SymbolTable) (FunCall, error) {
// The parameters must be analyzed before we query the symbol table for the actual
// function because the argument types must be analyzed/determined by then!
// Basically unpacking expression list before providing it to the function
expressionTypes := []ComplexType{}
for i, e := range fun.args {
newE, parseErr := analyzeExpression(e, symbolTable)
if parseErr != nil {
return fun, parseErr
}
fun.args[i] = newE
expressionTypes = append(expressionTypes, newE.getExpressionTypes(symbolTable)...)
}
funEntry, funOk := symbolTable.getFun(fun.funName, expressionsToTypes(fun.args, symbolTable), false)
typeEntry, typeOk := symbolTable.getType(fun.funName)
if funOk && typeOk {
return fun, fmt.Errorf("%w[%v:%v] - Function name '%v' already in use as a Type", ErrCritical, fun.line, fun.column, fun.funName)
}
var paramTypes []ComplexType
switch {
case funOk:
paramTypes = funEntry.paramTypes
symbolTable.setFunIsUsed(fun.funName, expressionsToTypes(fun.args, symbolTable), true)
returnTypes, err := analyzeFunCallReturnTypes(fun, expressionsToTypes(fun.args, symbolTable), symbolTable)
if err != nil {
return fun, err
}
fun.retTypes = returnTypes
fun.createStruct = false
case typeOk:
paramTypes = structMemToTypeList(typeEntry.members)
// One well defined return type. That is this struct we expect!
fun.retTypes = []ComplexType{ComplexType{TYPE_STRUCT, fun.funName, nil}}
fun.createStruct = true
default:
return fun, fmt.Errorf("%w[%v:%v] - Function '%v' called before declaration", ErrCritical, fun.line, fun.column, fun.funName)
}
if len(expressionTypes) != len(paramTypes) {
return fun, fmt.Errorf("%w[%v:%v] - Function call to '%v' has %v parameters, but needs %v",
ErrCritical, fun.line, fun.column, fun.funName, len(expressionTypes), len(funEntry.paramTypes),
)
}
for i, t := range expressionTypes {
if !equalType(t, paramTypes[i], false) {
return fun, fmt.Errorf("%w[%v:%v] - Function call to '%v' got type %v as %v. parameter, but needs %v",
ErrCritical, fun.line, fun.column, fun.funName, t, i+1, paramTypes[i],
)
}
}
if len(fun.directAccess) > 0 {
if fun.getResultCount() != 1 {
return fun, fmt.Errorf("%w[%v:%v] - Indexing is only allowed for single return functions",
ErrCritical, fun.line, fun.column,
)
}
newDirectAccess, err := analyzeDirectAccess(fun.retTypes[0], fun.directAccess, symbolTable)
if err != nil {
return fun, err
}
fun.directAccess = newDirectAccess
}
return fun, nil
}
func analyzeArrayDecl(a Array, symbolTable *SymbolTable) (Array, error) {
var arrayType ComplexType = ComplexType{TYPE_UNKNOWN, "", nil}
arraySize := 0
for i, e := range a.aExpressions {
newE, err := analyzeExpression(e, symbolTable)
if err != nil {
return a, err
}
a.aExpressions[i] = newE
if i == 0 && a.aType.t == TYPE_UNKNOWN {
arrayType = ComplexType{TYPE_ARRAY, "", &newE.getExpressionTypes(symbolTable)[0]}
}
if !equalType(newE.getExpressionTypes(symbolTable)[0], *arrayType.subType, true) {
return a, fmt.Errorf("%w[%v:%v] - Not all expressions in array declaration have the same type",
ErrCritical, a.line, a.column,
)
}
arraySize += newE.getResultCount()
}
// If aCount is already set to something, we know, this value is correct!!!
if a.aCount == 0 && arraySize > 0 {
a.aCount = arraySize
}
// For an empty array declaration, the type is set explicitely!
if a.aType.t == TYPE_UNKNOWN {
a.aType = arrayType
}
if e := analyzeType(a.aType, symbolTable); e != nil {
return a, fmt.Errorf("%w[%v:%v] - %v", ErrCritical, a.line, a.column, e.Error())
}
if a.isDirectlyAccessed() {
newDirectAccess, err := analyzeDirectAccess(a.aType, a.directAccess, symbolTable)
if err != nil {
return a, err
}
a.directAccess = newDirectAccess
}
return a, nil
}
func analyzeVariable(e Variable, symbolTable *SymbolTable) (Variable, error) {
// Lookup variable type and annotate node.
if vTable, ok := symbolTable.getVar(e.vName); ok {
e.vType = vTable.sType
if e.isDirectlyAccessed() {
newDirectAccess, err := analyzeDirectAccess(e.vType, e.directAccess, symbolTable)
if err != nil {
return e, err
}
e.directAccess = newDirectAccess
}
} else {
return e, fmt.Errorf("%w[%v:%v] - Variable '%v' referenced before declaration", ErrCritical, e.line, e.column, e.vName)
}
// Always access the very last entry for variables!
return e, nil
}
func analyzeExpression(expression Expression, symbolTable *SymbolTable) (Expression, error) {
switch e := expression.(type) {
case Constant:
return e, nil
case Variable:
v, err := analyzeVariable(e, symbolTable)
if err == nil && v.vShadow {
err = fmt.Errorf("%w[%v:%v] - Variable used as expression can not use 'shadow' keyword", ErrCritical, v.line, v.column)
}
return v, err
case UnaryOp:
return analyzeUnaryOp(e, symbolTable)
case BinaryOp:
return analyzeBinaryOp(e, symbolTable)
case FunCall:
return analyzeFunCall(e, symbolTable)
case Array:
return analyzeArrayDecl(e, symbolTable)
default:
row, col := expression.startPos()
return expression, fmt.Errorf("%w[%v:%v] - Unknown type for expression '%v'", ErrCritical, row, col, expression)
}
}
// Returns newly created variables and variables that should shadow others!
// This is just for housekeeping and removing them later!!!!
// All new variables (and shadow ones) are updated/written to the symbol varTable
func analyzeAssignment(assignment Assignment, symbolTable *SymbolTable) (Assignment, error) {
expressionTypes := make([]ComplexType, 0)
// We need this temporary array to hold information about sub-types for arrays, so we can
// pass this information on for later usage in indexed variables!
for i, e := range assignment.expressions {
expression, err := analyzeExpression(e, symbolTable)
if err != nil {
return assignment, err
}
tmpTypes := expression.getExpressionTypes(symbolTable)
expressionTypes = append(expressionTypes, tmpTypes...)
assignment.expressions[i] = expression
}
// Populate/overwrite the dictionary of variables for futher statements :)
if len(assignment.variables) != len(expressionTypes) {
row, col := assignment.startPos()
if len(assignment.variables) > 0 {
row, col = assignment.variables[0].line, assignment.variables[0].column
}
return assignment, fmt.Errorf(
"%w[%v:%v] - Variables and expression count need to match", ErrCritical, row, col,
)
}
for i, v := range assignment.variables {
expressionType := expressionTypes[i]
// Shadowing is only allowed in a different block, not right after the first variable, to avoid confusion and complicated
// variable handling
if symbolTable.isLocalVar(v.vName) && v.vShadow {
return assignment, fmt.Errorf(
"%w[%v:%v] - Variable %v is shadowing another variable in the same block. This is not allowed",
ErrCritical, v.line, v.column, v.vName,
)
}
// Only, if the variable already exists and we're not trying to shadow it!
if vTable, ok := symbolTable.getVar(v.vName); ok {
if !v.vShadow {
variableType := getAccessedType(vTable.sType, v.directAccess, symbolTable)
if !equalType(variableType, expressionType, true) {
return assignment, fmt.Errorf(
"%w[%v:%v] - Assignment type missmatch between variable %v and expression %v",
ErrCritical, v.line, v.column, v.vType, expressionType,
)
}
} else {
if v.isDirectlyAccessed() {
return assignment, fmt.Errorf("%w[%v:%v] - An indexed array write can not shadow its source",
ErrCritical, v.line, v.column,
)
}
symbolTable.setVar(v.vName, expressionType, false)
}
} else {
symbolTable.setVar(v.vName, expressionType, v.isDirectlyAccessed())
}
assignment.variables[i].vType = expressionType
if v.isDirectlyAccessed() {
entry, _ := symbolTable.getVar(v.vName)
newDirectAccess, err := analyzeDirectAccess(entry.sType, v.directAccess, symbolTable)
if err != nil {
return assignment, err
}
assignment.variables[i].directAccess = newDirectAccess
}
}
return assignment, nil
}
func analyzeCondition(condition Condition, symbolTable *SymbolTable) (Condition, error) {
// This expression MUST come out as boolean!
e, err := analyzeExpression(condition.expression, symbolTable)
if err != nil {
return condition, err
}
if e.getResultCount() != 1 {
row, col := e.startPos()
return condition, fmt.Errorf("%w[%v:%v] - Condition accepts only one expression, got %v",
ErrCritical, row, col, e.getResultCount(),
)
}
t := e.getExpressionTypes(symbolTable)[0]
if t.t != TYPE_BOOL {
row, col := e.startPos()
return condition, fmt.Errorf(
"%w[%v:%v] - If expression expected boolean, got: %v --> <<%v>>",
ErrCritical, row, col, t, condition.expression,
)
}
condition.expression = e
block, err := analyzeBlock(condition.block, symbolTable, nil)
if err != nil {
return condition, err
}
condition.block = block
elseBlock, err := analyzeBlock(condition.elseBlock, symbolTable, nil)
if err != nil {
return condition, err
}
condition.elseBlock = elseBlock
return condition, nil
}
func analyzeSwitch(sc Switch, symbolTable *SymbolTable) (Switch, error) {
valueSwitch := sc.expression != nil
if valueSwitch {
e, err := analyzeExpression(sc.expression, symbolTable)
if err != nil {
return sc, err
}
sc.expression = e
}
for i, c := range sc.cases {
// Empty case is permitted (behaves just like a default) as the last case only!
// And only, if we have a value-switch! Otherwise the default will be 'true'
if len(c.expressions) == 0 && i != len(sc.cases)-1 && sc.expression != nil {
row, col := sc.startPos()
return sc, fmt.Errorf("%w[%v:%v] - empty/default case must be the last case or have an expression to match",
ErrCritical, row, col,
)
}
for j, ce := range c.expressions {
e, err := analyzeExpression(ce, symbolTable)
if err != nil {
return sc, err
}
if e.getResultCount() != 1 {
row, col := e.startPos()
return sc, fmt.Errorf("%w[%v:%v] - case expression can only have one result value",
ErrCritical, row, col,
)
}
if valueSwitch {
if e.getExpressionTypes(symbolTable)[0].t != TYPE_INT {
row, col := e.startPos()
return sc, fmt.Errorf("%w[%v:%v] - value switch only accepts integers as cases",
ErrCritical, row, col,
)
}
} else {
if e.getExpressionTypes(symbolTable)[0].t != TYPE_BOOL {
row, col := e.startPos()
return sc, fmt.Errorf("%w[%v:%v] - general switch only accepts bools as cases",
ErrCritical, row, col,
)
}
}
c.expressions[j] = e
}
block, err := analyzeBlock(c.block, symbolTable, nil)
if err != nil {
return sc, err
}
c.block = block
sc.cases[i] = c
}
return sc, nil
}
func analyzeLoop(loop Loop, symbolTable *SymbolTable) (Loop, error) {
nextSymbolTable := &SymbolTable{
make(map[string]SymbolVarEntry, 0),
make(map[string][]SymbolFunEntry, 0),
make(map[string]SymbolTypeEntry, 0),
symbolTable.activeFunctionName,
symbolTable.activeFunctionParams,
symbolTable.activeFunctionReturn,
true,
"",
"",
symbolTable,
}
assignment, err := analyzeAssignment(loop.assignment, nextSymbolTable)
if err != nil {
return loop, err
}
loop.assignment = assignment
for i, e := range loop.expressions {
expression, err := analyzeExpression(e, nextSymbolTable)
if err != nil {
return loop, err
}
for _, t := range expression.getExpressionTypes(symbolTable) {
if t.t != TYPE_BOOL {
row, col := expression.startPos()
return loop, fmt.Errorf(
"%w[%v:%v] - Loop expression expected boolean, got: %v (%v)",
ErrCritical, row, col, t, expression,
)
}
}
loop.expressions[i] = expression
}
incrAssignment, err := analyzeAssignment(loop.incrAssignment, nextSymbolTable)
if err != nil {
return loop, err
}
loop.incrAssignment = incrAssignment
statements, err := analyzeBlock(loop.block, symbolTable, nextSymbolTable)
if err != nil {
return loop, err
}
loop.block = statements
loop.block.symbolTable = nextSymbolTable
return loop, nil
}
func analyzeRangedLoop(loop RangedLoop, symbolTable *SymbolTable) (RangedLoop, error) {
nextSymbolTable := &SymbolTable{
make(map[string]SymbolVarEntry, 0),
make(map[string][]SymbolFunEntry, 0),
make(map[string]SymbolTypeEntry, 0),
symbolTable.activeFunctionName,
symbolTable.activeFunctionParams,
symbolTable.activeFunctionReturn,
true,
"",
"",
symbolTable,
}
rangeExpression, err := analyzeExpression(loop.rangeExpression, nextSymbolTable)
if err != nil {
return loop, err
}
if rangeExpression.getResultCount() != 1 {
return loop, fmt.Errorf("%w[%v:%v] - RangedLoop can only iterate one array at a time", ErrCritical, loop.line, loop.column)
}
rangeType := rangeExpression.getExpressionTypes(symbolTable)[0]
if !equalType(rangeType, ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}, false) {
return loop, fmt.Errorf("%w[%v:%v] - RangedLoop can only iterate arrays", ErrCritical, loop.line, loop.column)
}
loop.elem.vType = *rangeType.subType
// As we don't have an assignment (that does it for us), we have to register the variables ourself
nextSymbolTable.setVar(loop.elem.vName, loop.elem.vType, false)
nextSymbolTable.setVar(loop.counter.vName, loop.counter.vType, false)
statements, err := analyzeBlock(loop.block, symbolTable, nextSymbolTable)
if err != nil {
return loop, err
}
loop.rangeExpression = rangeExpression
loop.block = statements
loop.block.symbolTable = nextSymbolTable
return loop, nil
}
func analyzeFunction(fun Function, symbolTable *SymbolTable) (Function, error) {
functionSymbolTable := &SymbolTable{
make(map[string]SymbolVarEntry, 0),
make(map[string][]SymbolFunEntry, 0),
make(map[string]SymbolTypeEntry, 0),
fun.fName,
variablesToTypes(fun.parameters),
fun.returnTypes,
false,
"",
"",
symbolTable,
}
for _, v := range fun.parameters {
if v.vType.t == TYPE_UNKNOWN {
return fun, fmt.Errorf("%w[%v:%v] - Function parameter %v has invalid type", ErrCritical, v.line, v.column, v)
}
if _, ok := functionSymbolTable.getVar(v.vName); ok {
return fun, fmt.Errorf("%w[%v:%v] - Function parameter %v already exists", ErrCritical, v.line, v.column, v)
}
if e := analyzeType(v.vType, symbolTable); e != nil {
return fun, fmt.Errorf("%w[%v:%v] - %v", ErrCritical, v.line, v.column, e.Error())
}
functionSymbolTable.setVar(v.vName, v.vType, false)
}
if symbolTable.isLocalFun(fun.fName, variablesToTypes(fun.parameters)) {
return fun, fmt.Errorf("%w[%v:%v] - Function with the same name already exists in this scope", ErrCritical, fun.line, fun.column)
}
symbolTable.setFun(fun.fName, variablesToTypes(fun.parameters), fun.returnTypes, false)
newBlock, err := analyzeBlock(fun.block, symbolTable, functionSymbolTable)
if err != nil {
return fun, err
}
fun.block = newBlock
// Checks, that every single path has one return statement so we don't have undefined function returns,
// unassigned registers or no expected values on stack
if len(fun.returnTypes) > 0 {
if err = fun.block.functionReturnAnalysis(); err != nil {
return fun, fmt.Errorf("%w[%v:%v] - %v", ErrCritical, fun.line, fun.column, err.Error())
}
}
return fun, nil
}
func analyzeReturn(ret Return, symbolTable *SymbolTable) (Return, error) {
if symbolTable.activeFunctionName == "" {
return ret, fmt.Errorf("%w[%v:%v] - Return called outside of a function",
ErrCritical, ret.line, ret.column,
)
}
typeIndex := 0
for i, e := range ret.expressions {
newE, err := analyzeExpression(e, symbolTable)
if err != nil {
return ret, err
}
row, col := e.startPos()
for _, t := range newE.getExpressionTypes(symbolTable) {
if typeIndex >= len(symbolTable.activeFunctionReturn) {
return ret, fmt.Errorf("%w[%v:%v] - Too many expressions returned. Expected %v",
ErrCritical, row, col, len(symbolTable.activeFunctionReturn),
)
}
if !equalType(t, symbolTable.activeFunctionReturn[typeIndex], true) {
return ret, fmt.Errorf("%w[%v:%v] - Function return type does not match definition. Expected %v, got %v",
ErrCritical, row, col, symbolTable.activeFunctionReturn[typeIndex], t,
)
}
typeIndex++
}
ret.expressions[i] = newE
}
return ret, nil
}
func analyzeBreak(br Break, symbolTable *SymbolTable) (Break, error) {
if !symbolTable.activeLoop {
return br, fmt.Errorf("%w[%v:%v] - Break statement outside of loop block", ErrCritical, br.line, br.column)
}
return br, nil
}
func analyzeContinue(cont Continue, symbolTable *SymbolTable) (Continue, error) {
if !symbolTable.activeLoop {
return cont, fmt.Errorf("%w[%v:%v] - Continue statement outside of loop block", ErrCritical, cont.line, cont.column)
}
return cont, nil
}
// analyzeType recursively goes through the type hierarchy and checks, if all types are well defined.
func analyzeType(t ComplexType, symbolTable *SymbolTable) error {
// Needs to be defined!
if t.t == TYPE_STRUCT {
if _, ok := symbolTable.getType(t.tName); !ok {
return fmt.Errorf("Struct name '%v' undefined", t.tName)
}
return nil
}
if t.t == TYPE_ARRAY {
return analyzeType(*t.subType, symbolTable)
}
if t.t == TYPE_UNKNOWN {
return fmt.Errorf("Type '%v' is unknown", t.t)
}
return nil
}
func analyzeStructDef(st StructDef, symbolTable *SymbolTable) (StructDef, error) {
if _, ok := symbolTable.getType(st.name); ok {
return st, fmt.Errorf("%w[%v:%v] - Struct name '%v' already used", ErrCritical, st.line, st.column, st.name)
}
if len(st.members) == 0 {
return st, fmt.Errorf("%w[%v:%v] - A Struct can not have zero members", ErrCritical, st.line, st.column)
}
offset := 0
// Go through the complex type of each member and check, that all types are well defined.
for i, m := range st.members {
if e := analyzeType(m.memType, symbolTable); e != nil {
return st, fmt.Errorf("%w[%v:%v] - %v", ErrCritical, st.line, st.column, e.Error())
}
// Check against the remaining members, that each member name is unique!
for j := i + 1; j < len(st.members); j++ {
if m.memName == st.members[j].memName {
return st, fmt.Errorf("%w[%v:%v] - The struct member '%v' is defined multiple times", ErrCritical, st.line, st.column, m.memName)
}
}
st.members[i].offset = offset
offset += m.memType.getMemCount(symbolTable)
}
symbolTable.setType(st.name, st.members)
return st, nil
}
func analyzeStatement(statement Statement, symbolTable *SymbolTable) (Statement, error) {
switch st := statement.(type) {
case StructDef:
return analyzeStructDef(st, symbolTable)
case Condition:
return analyzeCondition(st, symbolTable)
case Switch:
return analyzeSwitch(st, symbolTable)
case Loop:
return analyzeLoop(st, symbolTable)
case RangedLoop:
return analyzeRangedLoop(st, symbolTable)
case Assignment:
return analyzeAssignment(st, symbolTable)
case Function:
return analyzeFunction(st, symbolTable)
case Return:
return analyzeReturn(st, symbolTable)
case FunCall:
return analyzeFunCall(st, symbolTable)
case Break:
return analyzeBreak(st, symbolTable)
case Continue:
return analyzeContinue(st, symbolTable)
}
row, col := statement.startPos()
return statement, fmt.Errorf("%w[%v:%v] - Unexpected statement: %v", ErrCritical, row, col, statement)
}
// analyzeBlock gets a reference to the current (now parent) symbol varTable
// Additionally, it might get a pre-filled symbol varTable for the new scope to use!
// This might be the case for function arguments or in a for-loop, where variables belong to the
// coming block only but are parsed in the TreeNode before.
func analyzeBlock(block Block, symbolTable, newBlockSymbolTable *SymbolTable) (Block, error) {
if newBlockSymbolTable != nil {
block.symbolTable = newBlockSymbolTable
} else {
block.symbolTable = &SymbolTable{
make(map[string]SymbolVarEntry, 0),
make(map[string][]SymbolFunEntry, 0),
make(map[string]SymbolTypeEntry, 0),
symbolTable.activeFunctionName,
symbolTable.activeFunctionParams,
symbolTable.activeFunctionReturn,
symbolTable.activeLoop,
symbolTable.activeLoopBreakLabel,
symbolTable.activeLoopContinueLabel,
symbolTable,
}
}
for i, s := range block.statements {
statement, err := analyzeStatement(s, block.symbolTable)
if err != nil {
return block, err
}
block.statements[i] = statement
}
return block, nil
}
// Set usage status for system the functions above. This can be made nicer and general, but the effort is not
// worth it right now. Maybe at a later point.
func setSystemFunctionUsage(s *SymbolTable) {
iArg := []ComplexType{ComplexType{TYPE_INT, "", nil}}
fArg := []ComplexType{ComplexType{TYPE_FLOAT, "", nil}}
aArg := []ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}}
aeArg := []ComplexType{
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
}
aaArg := []ComplexType{
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
ComplexType{TYPE_WHATEVER, "", nil},
}
plnI := s.funIsUsed("println", iArg, true)
pI := s.funIsUsed("print", iArg, true)
plnF := s.funIsUsed("println", fArg, true)
pF := s.funIsUsed("print", fArg, true)
s.setFunIsUsed("print", fArg, pF || plnF)
s.setFunIsUsed("print", iArg, pI || plnI || s.funIsUsed("print", fArg, true))
// We need to re-query them because we just possibly changed their state.
s.setFunIsUsed("printChar", iArg, s.funIsUsed("print", iArg, true) || s.funIsUsed("print", fArg, true))
s.setFunIsUsed("free", aArg,
s.funIsUsed("free", aArg, false) ||
s.funIsUsed("extend", aeArg, false) ||
s.funIsUsed("append", aaArg, false),
)
}
// analyzeTypes traverses the tree and analyzes variables with their corresponding type recursively from expressions!
// returns an error if we have a type missmatch anywhere!
func semanticAnalysis(ast AST) (AST, error) {
ast.globalSymbolTable = &SymbolTable{
make(map[string]SymbolVarEntry, 0),
make(map[string][]SymbolFunEntry, 0),
make(map[string]SymbolTypeEntry, 0),
"",
nil,
nil,
false,
"",
"",
nil,
}
ast.globalSymbolTable.setFun("printChar", []ComplexType{ComplexType{TYPE_INT, "", nil}}, []ComplexType{}, false)
ast.globalSymbolTable.setFun("print", []ComplexType{ComplexType{TYPE_INT, "", nil}}, []ComplexType{}, false)
ast.globalSymbolTable.setFun("println", []ComplexType{ComplexType{TYPE_INT, "", nil}}, []ComplexType{}, false)
ast.globalSymbolTable.setFun("print", []ComplexType{ComplexType{TYPE_FLOAT, "", nil}}, []ComplexType{}, false)
ast.globalSymbolTable.setFun("println", []ComplexType{ComplexType{TYPE_FLOAT, "", nil}}, []ComplexType{}, false)
ast.globalSymbolTable.setFun("int",
[]ComplexType{ComplexType{TYPE_FLOAT, "", nil}},
[]ComplexType{ComplexType{TYPE_INT, "", nil}}, true,
)
ast.globalSymbolTable.setFun("float",
[]ComplexType{ComplexType{TYPE_INT, "", nil}},
[]ComplexType{ComplexType{TYPE_FLOAT, "", nil}}, true,
)
ast.globalSymbolTable.setFun("cap",
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
[]ComplexType{ComplexType{TYPE_INT, "", nil}}, true,
)
ast.globalSymbolTable.setFun("len",
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
[]ComplexType{ComplexType{TYPE_INT, "", nil}}, true,
)
// free can not be set as inline, as we have to call it explicitely from assembly in append()
ast.globalSymbolTable.setFun("free",
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
[]ComplexType{}, false,
)
ast.globalSymbolTable.setFun("reset",
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
[]ComplexType{}, true,
)
ast.globalSymbolTable.setFun("clear",
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
[]ComplexType{}, true,
)
ast.globalSymbolTable.setFun("extend",
[]ComplexType{
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
},
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
false,
)
ast.globalSymbolTable.setFun("append",
[]ComplexType{
ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}},
ComplexType{TYPE_WHATEVER, "", nil},
},
[]ComplexType{ComplexType{TYPE_ARRAY, "", &ComplexType{TYPE_WHATEVER, "", nil}}},
false,
)
block, err := analyzeBlock(ast.block, nil, ast.globalSymbolTable)
if err != nil {
ast.globalSymbolTable = &SymbolTable{}
return ast, err
}
ast.block = block
setSystemFunctionUsage(ast.globalSymbolTable)
return ast, nil
}