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) } }