hngnggg i forgor to commit

- implemented scope management
- finished function signature analyzing
- added method analyzing
  - had to restructure type analysis slightly to do this
This commit is contained in:
Sasha Koshka 2023-10-17 03:11:11 -04:00
parent 8b4244e3cb
commit decefce142
8 changed files with 207 additions and 42 deletions

View File

@ -23,13 +23,19 @@ func (this *Tree) analyzeFunction (
if !exists {
return nil, participle.Errorf(pos, "no function named %s", name)
}
// create a new scope context for this function
this.PushScopeContext()
this.PushScope(function)
defer this.PopScopeContext()
defer this.PopScope()
// analyze signature
// analyze signature and add arguments to root scope of function
function.Signature, err = this.assembleSignatureMap(function.Signature)
if err != nil { return function, err }
for name, argument := range function.Signature.ArgumentMap {
argument.Type, err = this.analyzeType(argument.Type, false)
// TODO add argument to scope
this.AddVariable(argument)
function.Signature.ArgumentMap[name] = argument
if err != nil { return function, err }
}
@ -37,6 +43,11 @@ func (this *Tree) analyzeFunction (
this.analyzeType(function.Signature.Return, false)
if err != nil { return function, err }
// add incomplete function to complete functions because there is enough
// information for it to be complete from the point of view of other
// parts of the code
this.Functions[name] = function
// TODO: analyze function body
return function, nil
}

59
analyzer/method.go Normal file
View File

@ -0,0 +1,59 @@
package analyzer
import "github.com/alecthomas/participle/v2"
import "github.com/alecthomas/participle/v2/lexer"
import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *Tree) analyzeMethod (
pos lexer.Position,
typeName string,
name string,
) (
*entity.Method,
error,
) {
// get parent typedef
owner, err := this.analyzeTypedef(pos, typeName, false)
if err != nil { return nil, err }
// return if exists already
if method, exists := owner.Methods[name]; exists {
return method, nil
}
// error if method is missing
method, exists := this.rawMethods[name]
if !exists {
return nil, participle.Errorf (
pos, "no method named %s.%s",
typeName, name)
}
// create a new scope context for this function
this.PushScopeContext()
this.PushScope(method)
defer this.PopScopeContext()
defer this.PopScope()
// analyze signature and add arguments to root scope of function
method.Signature, err = this.assembleSignatureMap(method.Signature)
if err != nil { return method, err }
for name, argument := range method.Signature.ArgumentMap {
argument.Type, err = this.analyzeType(argument.Type, false)
this.AddVariable(argument)
method.Signature.ArgumentMap[name] = argument
if err != nil { return method, err }
}
method.Signature.Return, err =
this.analyzeType(method.Signature.Return, false)
if err != nil { return method, err }
// add incomplete method to complete methods because there is enough
// information for it to be complete from the point of view of other
// parts of the code
owner.Methods[name] = method
// TODO: analyze method body
return method, nil
}

View File

@ -1,7 +1,82 @@
package analyzer
// type ScopeManager struct {
// stacks []
// }
//
// type scopeStack []entity.Scoped
import "git.tebibyte.media/sashakoshka/fspl/entity"
// scopeContextManager is a stack of scopeContexts allowing multiple stacks of
// scopes to be managed at the same time in case the analysis of one scoped
// entity causes the analysis of another.
type scopeContextManager []scopeContext
func (this *scopeContextManager) PushScopeContext () {
*this = append(*this, nil)
}
func (this *scopeContextManager) PopScopeContext () {
this.assertPopulated()
*this = (*this)[:len(*this) - 1]
}
func (this *scopeContextManager) PushScope (scope entity.Scoped) {
this.assertPopulated()
(*this)[len(*this) - 1].PushScope(scope)
}
func (this *scopeContextManager) PopScope () {
this.assertPopulated()
(*this)[len(*this) - 1].PopScope()
}
func (this *scopeContextManager) Variable (name string) *entity.Declaration {
this.assertPopulated()
return (*this)[len(*this) - 1].TopScope().Variable(name)
}
func (this *scopeContextManager) AddVariable (declaration *entity.Declaration) {
this.assertPopulated()
(*this)[len(*this) - 1].TopScope().AddVariable(declaration)
}
func (this *scopeContextManager) assertPopulated () {
if len(*this) < 1 {
panic("scopeContextManager must have at least 1 scope context")
}
}
// scopeContext is a stack of scopes used when analyzing scoped entity.
type scopeContext []entity.Scoped
func (this *scopeContext) PushScope (scope entity.Scoped) {
*this = append(*this, scope)
}
func (this *scopeContext) PopScope () {
this.assertPopulated()
*this = (*this)[:len(*this) - 1]
}
func (this *scopeContext) TopScope () entity.Scoped {
this.assertPopulated()
return (*this)[len(*this) - 1]
}
func (this *scopeContext) Variable (name string) *entity.Declaration {
this.assertPopulated()
for index := len(*this) - 1; index >= 0; index -- {
variable := (*this)[index].Variable(name)
if variable != nil {
return variable
}
}
return nil
}
func (this *scopeContext) AddVariable (declaration *entity.Declaration) {
this.assertPopulated()
(*this)[len(*this) - 1].AddVariable(declaration)
}
func (this *scopeContext) assertPopulated () {
if len(*this) < 1 {
panic("scopeContext must have at least 1 scope")
}
}

View File

@ -8,15 +8,16 @@ import "git.tebibyte.media/sashakoshka/fspl/parser"
// Tree is a semantic tree. It contains the same constructs as the syntax tree,
// but with their semantic information filled in.
type Tree struct {
scopeContextManager
rawTypes map[string] *entity.Typedef
rawFunctions map[string] *entity.Function
rawMethods map[string] *entity.Method
ast parser.Tree
incompleteTypes map[string] *entity.Typedef
incompleteTypes map[string] entity.Type
Types map[string] entity.Type
Types map[string] *entity.Typedef
Functions map[string] *entity.Function
}
@ -97,11 +98,16 @@ func (this *Tree) analyzeDeclarations () error {
this.Types[name] = ty
}
for name, rawFunction := range this.rawFunctions {
function, err := this.analyzeFunction(rawFunction.Pos, name)
_, err := this.analyzeFunction(rawFunction.Pos, name)
if err != nil { return err }
this.Functions[name] = function
}
// TODO methods
// for _, rawMethod := range this.rawMethods {
// _, err := this.analyzeMethod (
// rawMethod.Pos,
// rawMethod.TypeName,
// rawMethod.Signature.Name)
// if err != nil { return err }
// }
return nil
}
@ -110,11 +116,15 @@ func (this *Tree) ensure () {
this.rawTypes = make(map[string] *entity.Typedef)
this.rawFunctions = make(map[string] *entity.Function)
this.rawMethods = make(map[string] *entity.Method)
this.incompleteTypes = make(map[string] entity.Type)
this.Types = make(map[string] entity.Type)
this.incompleteTypes = make(map[string] *entity.Typedef)
this.Types = make(map[string] *entity.Typedef)
this.Functions = make(map[string] *entity.Function)
for name, ty := range builtinTypes {
this.Types[name] = ty
this.Types[name] = &entity.Typedef {
Public: true,
Name: name,
Type: ty,
}
}
}

View File

@ -10,18 +10,18 @@ func (this *Tree) analyzeTypedef (
name string,
acceptIncomplete bool,
) (
entity.Type,
*entity.Typedef,
error,
) {
// return if exists already
if ty, exists := this.Types[name]; exists {
return ty, nil
if definition, exists := this.Types[name]; exists {
return definition, nil
}
// deal with incomplete types
if ty, incomplete := this. incompleteTypes[name]; incomplete {
if definition, incomplete := this. incompleteTypes[name]; incomplete {
if acceptIncomplete {
return ty, nil
return definition, nil
} else {
return nil, participle.Errorf (
pos, "type %s cannot be used in this context",
@ -34,8 +34,11 @@ func (this *Tree) analyzeTypedef (
if !exists {
return nil, participle.Errorf(pos, "no type named %s", name)
}
return this.analyzeTypeInternal(definition.Type, name, false)
var err error
definition.Type, err = this.analyzeTypeInternal (
definition.Type, definition, false)
return definition, err
}
func (this *Tree) analyzeType (
@ -45,12 +48,12 @@ func (this *Tree) analyzeType (
entity.Type,
error,
) {
return this.analyzeTypeInternal (ty, "", acceptIncomplete)
return this.analyzeTypeInternal (ty, nil, acceptIncomplete)
}
func (this *Tree) analyzeTypeInternal (
ty entity.Type,
name string,
root *entity.Typedef,
acceptIncomplete bool,
) (
entity.Type,
@ -61,14 +64,16 @@ func (this *Tree) analyzeTypeInternal (
if ty == nil { return entity.Void(), nil }
var err error
// manage incomplete type information
// if we are analyzing a typedef, we will need to update incomplete type
// information as we go in order for recursive type definitions to work
updateIncompleteInfo := func () {
if name != "" { this.incompleteTypes[name] = ty }
if root != nil { this.incompleteTypes[root.Name] = root }
}
updatePseudoCompleteInfo := func () {
if name != "" { this.Types[name] = ty }
if root != nil { this.Types[root.Name] = root }
}
if name != "" { defer delete(this.incompleteTypes, name) }
if root != nil { defer delete(this.incompleteTypes, root.Name) }
// ---------
switch ty.(type) {
// named type
@ -78,7 +83,11 @@ func (this *Tree) analyzeTypeInternal (
if primitive, isPrimitive := primitiveTypes[ty.Name]; isPrimitive {
return primitive, nil
}
ty.Type, err = this.analyzeTypedef(ty.Pos, ty.Name, acceptIncomplete)
var def *entity.Typedef
def, err = this.analyzeTypedef(ty.Pos, ty.Name, acceptIncomplete)
if err == nil {
ty.Type = def.Type
}
return ty, err
// pointer type

View File

@ -43,14 +43,6 @@ world: (x:Int y:Int)
`)
}
func TestTypeNamedErrMissing (test *testing.T) {
testStringErr (test,
"no type named Missing", 2, 10,
`
present: Missing
`)
}
func TestTypedefRecursiveErr (test *testing.T) {
testStringErr (test,
"type List cannot be used in this context", 4, 9,
@ -70,6 +62,14 @@ List: (
`)
}
func TestTypeNamedErrMissing (test *testing.T) {
testStringErr (test,
"no type named Missing", 2, 10,
`
present: Missing
`)
}
func TestTypeNamed (test *testing.T) {
testString (test,
`

View File

@ -8,7 +8,7 @@ type Scoped interface {
Variable (name string) *Declaration
// AddVariable adds a variable to this entity's scope.
AddVariable (name string, declaration *Declaration)
AddVariable (declaration *Declaration)
}
// Scope implements a scope.
@ -23,9 +23,9 @@ func (this *Scope) Variable (name string) *Declaration {
return this.variables[name]
}
func (this *Scope) AddVariable (name string, declaration *Declaration) {
func (this *Scope) AddVariable (declaration *Declaration) {
if this.variables == nil {
this.variables = make(map[string] *Declaration)
}
this.variables[name] = declaration
this.variables[declaration.Name] = declaration
}

View File

@ -17,7 +17,7 @@ type Typedef struct {
Type Type `parser:" ':' @@ "`
// Semantics
Methods map[string] Method
Methods map[string] *Method
}
func (*Typedef) topLevel(){}
func (this *Typedef) String () string {
@ -68,6 +68,7 @@ type Method struct {
// Semantics
Scope
Type Type
}
func (*Method) topLevel(){}
func (this *Method) String () string {