183 lines
5.1 KiB
Go
183 lines
5.1 KiB
Go
package analyzer
|
|
|
|
import "github.com/google/uuid"
|
|
import "git.tebibyte.media/fspl/fspl/errors"
|
|
import "git.tebibyte.media/fspl/fspl/entity"
|
|
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
|
|
|
// Tree is a semantic tree. It contains the same constructs as the syntax tree,
|
|
// but with their semantic information filled in.
|
|
type Tree struct {
|
|
Types map[entity.Key] *entity.Typedef
|
|
Functions map[entity.Key] *entity.Function
|
|
|
|
unit uuid.UUID
|
|
ast fsplParser.Tree
|
|
nicknames map[string] uuid.UUID
|
|
rawTypes map[entity.Key] *entity.Typedef
|
|
rawFunctions map[entity.Key] *entity.Function
|
|
rawMethods map[entity.Key] *entity.Method
|
|
|
|
scopeContextManager
|
|
incompleteTypes map[entity.Key] *entity.Typedef
|
|
}
|
|
|
|
// Analyze takes in an AST and analyzes its contents within the context of this
|
|
// semantic tree. Analyzed entities will be added to the tree.
|
|
func (this *Tree) Analyze (
|
|
unit uuid.UUID,
|
|
nicknames map[string] uuid.UUID,
|
|
ast fsplParser.Tree,
|
|
) error {
|
|
this.ensure()
|
|
this.resetRawMaps()
|
|
this.ast = ast
|
|
this.unit = unit
|
|
this.nicknames = nicknames
|
|
err := this.assembleRawMaps()
|
|
if err != nil { return err }
|
|
return this.analyzeDeclarations()
|
|
}
|
|
|
|
func (this *Tree) assembleRawMaps () error {
|
|
for _, declaration := range this.ast.Declarations {
|
|
switch declaration := declaration.(type) {
|
|
case *entity.Typedef:
|
|
key := entity.Key {
|
|
Unit: this.unit,
|
|
Name: declaration.Name,
|
|
}
|
|
err := this.topLevelNameAvailable(declaration.Position(), key)
|
|
if err != nil { return err }
|
|
declaration.Methods = make(map[string] *entity.Method)
|
|
this.rawTypes[key] = declaration
|
|
|
|
case *entity.Function:
|
|
key := entity.Key {
|
|
Unit: this.unit,
|
|
Name: declaration.Signature.Name,
|
|
}
|
|
err := this.topLevelNameAvailable(declaration.Position(), key)
|
|
if err != nil { return err }
|
|
this.rawFunctions[key] = declaration
|
|
|
|
case *entity.Method:
|
|
key := entity.Key {
|
|
Unit: this.unit,
|
|
Name: declaration.TypeName,
|
|
Method: declaration.Signature.Name,
|
|
}
|
|
err := this.topLevelNameAvailable(declaration.Position(), key)
|
|
if err != nil { return err }
|
|
this.rawMethods[key] = declaration
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, key entity.Key) error {
|
|
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
|
|
return errors.Errorf (
|
|
currentPos, "cannot shadow primitive %s",
|
|
key.Name)
|
|
}
|
|
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
|
|
return errors.Errorf (
|
|
currentPos, "cannot shadow builtin %s",
|
|
key.Name)
|
|
}
|
|
if ty, isType := this.rawTypes[key]; isType {
|
|
return errors.Errorf (
|
|
currentPos, "%s already declared at %v",
|
|
key.Name, ty.Position())
|
|
}
|
|
if function, isFunction := this.rawFunctions[key]; isFunction {
|
|
return errors.Errorf (
|
|
currentPos, "%s already declared at %v",
|
|
key.Name, function.Position())
|
|
}
|
|
if method, isMethod := this.rawMethods[key]; isMethod {
|
|
return errors.Errorf (
|
|
currentPos, "%s.%s already declared at %v",
|
|
key.Name, key.Method, method.Position())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *Tree) analyzeDeclarations () error {
|
|
for key, rawType := range this.rawTypes {
|
|
ty, err := this.analyzeTypedef(rawType.Position(), key, false)
|
|
if err != nil { return err }
|
|
this.Types[key] = ty
|
|
}
|
|
for key, rawFunction := range this.rawFunctions {
|
|
_, err := this.analyzeFunction(rawFunction.Position(), key)
|
|
if err != nil { return err }
|
|
}
|
|
for _, rawMethod := range this.rawMethods {
|
|
_, err := this.analyzeMethod (
|
|
rawMethod.Position(),
|
|
entity.Key {
|
|
Unit: this.unit,
|
|
Name: rawMethod.TypeName,
|
|
Method: rawMethod.Signature.Name,
|
|
})
|
|
if err != nil { return err }
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (this *Tree) resolveNickname (pos errors.Position, nickname string) (uuid.UUID, error) {
|
|
if nickname == "" { return this.unit, nil }
|
|
|
|
fail := func () (uuid.UUID, error) {
|
|
return uuid.UUID { }, errors.Errorf (
|
|
pos, "no unit named %s",
|
|
nickname)
|
|
}
|
|
|
|
if this.nicknames == nil { return fail() }
|
|
unit, ok := this.nicknames[nickname]
|
|
if !ok { return fail() }
|
|
|
|
return unit, nil
|
|
}
|
|
|
|
func (this *Tree) ensure () {
|
|
if this.rawTypes != nil { return }
|
|
this.resetRawMaps()
|
|
this.incompleteTypes = make(map[entity.Key] *entity.Typedef)
|
|
this.Types = make(map[entity.Key] *entity.Typedef)
|
|
this.Functions = make(map[entity.Key] *entity.Function)
|
|
|
|
for name, ty := range builtinTypes {
|
|
// builtin types have a zero UUID in their key
|
|
this.Types[entity.Key { Name: name }] = &entity.Typedef {
|
|
Acc: entity.AccessPublic,
|
|
Name: name,
|
|
Type: ty,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (this *Tree) resetRawMaps () {
|
|
if this.rawTypes != nil &&
|
|
this.rawFunctions != nil &&
|
|
this.rawMethods != nil {
|
|
|
|
if len(this.rawTypes) > 0 {
|
|
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
|
}
|
|
if len(this.rawFunctions) > 0 {
|
|
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
|
}
|
|
if len(this.rawMethods) > 0 {
|
|
this.rawMethods = make(map[entity.Key] *entity.Method)
|
|
}
|
|
} else {
|
|
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
|
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
|
this.rawMethods = make(map[entity.Key] *entity.Method)
|
|
}
|
|
}
|