Function call analysis

This commit is contained in:
Sasha Koshka 2023-10-20 13:48:05 -04:00
parent 99c09d9b07
commit 5ac046d5b1
6 changed files with 148 additions and 71 deletions

View File

@ -1,48 +1,84 @@
package analyzer
import "fmt"
import "github.com/alecthomas/participle/v2"
import "github.com/alecthomas/participle/v2/lexer"
import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *Tree) canAssignStrict (
pos lexer.Position,
// assuming both are analyzed already
ty entity.Type,
source entity.Type
destination entity.Type,
source entity.Type,
) error {
can := false
switch ty.(type) {
switch destination.(type) {
// named type
case *entity.TypeNamed:
// try to compare names first
ty := ty.(*entity.TypeNamed)
destination := destination.(*entity.TypeNamed)
if source, ok := source.(*entity.TypeNamed); ok {
if ty.Name == source.Name {
if destination.Name == source.Name {
can = true
break
}
}
// if not, see if the first is an interface and the second can
// be assigned to it TODO
// if not, types may be assignmable further down:
return this.canAssignStrict(pos, destination.Type, source)
// interface tyoe
case *entity.TypeInterface:
destination := destination.(*entity.TypeInterface)
for name, behavior := range destination.BehaviorMap {
// get method
method := this.methodOrBehaviorOf(source, name)
if method == nil {
return participle.Errorf (
pos, "%v is missing method %v",
source, name)
}
// extract signature
var signature *entity.Signature
switch method.(type) {
case *entity.Signature:
signature = method.(*entity.Signature)
case *entity.Method:
signature = method.(*entity.Method).Signature
default:
panic(fmt.Sprint (
"Tree.methodOrBehaviorOf returned ",
method))
}
// if ty is not named or not an interface, we can test for structural
// check equivalence
if !signature.Equals(behavior) {
return participle.Errorf (
pos, "%v has wrong signature for method %v",
source, name)
}
}
// if ty is not named nor an interface, we can test for structural
// equivalence:
case *entity.TypeVoid,
*entity.TypePointer,
*entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
// TODO have equals methods
case *entity.TypePointer:
case *entity.TypeSlice:
case *entity.TypeArray:
case *entity.TypeStruct:
case *entity.TypeInt:
case *entity.TypeFloat, *entity.TypeWord:
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
can = destination.Equals(source)
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", destination))
}
if can {
return nil
} else {
return participle.Errorf (
call.Position, "expected %v",
destination)
return participle.Errorf(pos, "expected %v", destination)
}
}

View File

@ -19,32 +19,32 @@ func (this *Tree) analyzeExpression (
return this.analyzeCall(into, expression.(*entity.Call))
case *entity.MethodCall:
return this.analyzeMethodCall(into, expression.(*entity.MethodCall))
case *entity.Subscript:
return this.analyzeSubscript(into, expression.(*entity.Subscript))
case *entity.Slice:
return this.analyzeSlice(into, expression.(*entity.Slice))
case *entity.Dereference:
return this.analyzeDereference(into, expression.(*entity.Dereference))
case *entity.Reference:
return this.analyzeReference(into, expression.(*entity.Reference))
case *entity.ValueCast:
return this.analyzeValueCast(into, expression.(*entity.ValueCast))
case *entity.BitCast:
return this.analyzeBitCast(into, expression.(*entity.BitCast))
case *entity.Operation:
return this.analyzeOperation(into, expression.(*entity.Operation))
case *entity.Block:
return this.analyzeBlock(into, expression.(*entity.Block))
case *entity.MemberAccess:
return this.analyzeMemberAccess(into, expression.(*entity.MemberAccess))
case *entity.IfElse:
return this.analyzeIfElse(into, expression.(*entity.IfElse))
case *entity.Loop:
return this.analyzeLoop(into, expression.(*entity.Loop))
case *entity.Break:
return this.analyzeBreak(into, expression.(*entity.Break))
case *entity.Return:
return this.analyzeReturn(into, expression.(*entity.Return))
// case *entity.Subscript:
// return this.analyzeSubscript(into, expression.(*entity.Subscript))
// case *entity.Slice:
// return this.analyzeSlice(into, expression.(*entity.Slice))
// case *entity.Dereference:
// return this.analyzeDereference(into, expression.(*entity.Dereference))
// case *entity.Reference:
// return this.analyzeReference(into, expression.(*entity.Reference))
// case *entity.ValueCast:
// return this.analyzeValueCast(into, expression.(*entity.ValueCast))
// case *entity.BitCast:
// return this.analyzeBitCast(into, expression.(*entity.BitCast))
// case *entity.Operation:
// return this.analyzeOperation(into, expression.(*entity.Operation))
// case *entity.Block:
// return this.analyzeBlock(into, expression.(*entity.Block))
// case *entity.MemberAccess:
// return this.analyzeMemberAccess(into, expression.(*entity.MemberAccess))
// case *entity.IfElse:
// return this.analyzeIfElse(into, expression.(*entity.IfElse))
// case *entity.Loop:
// return this.analyzeLoop(into, expression.(*entity.Loop))
// case *entity.Break:
// return this.analyzeBreak(into, expression.(*entity.Break))
// case *entity.Return:
// return this.analyzeReturn(into, expression.(*entity.Return))
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about expression ",

View File

@ -1,6 +1,6 @@
package analyzer
import "fmt"
// import "fmt"
import "github.com/alecthomas/participle/v2"
import "git.tebibyte.media/sashakoshka/fspl/entity"
@ -19,11 +19,11 @@ func (this *Tree) analyzeVariable (
declaration := this.Variable(variable.Name)
if declaration == nil {
return nil, participle.Errorf (
variable.Position, "no variable named %s",
variable.Pos, "no variable named %s",
variable.Name)
}
err := this.canAssignStrict(variable.Pos, into, variable.Type)
err := this.canAssignStrict(variable.Pos, into, declaration.Type)
if err != nil { return nil, err }
variable.Declaration = declaration
@ -40,16 +40,16 @@ func (this *Tree) analyzeDeclaration (
existing := this.TopScope().Variable(declaration.Name)
if existing != nil {
return nil, participle.Errorf (
declaration.Position,
declaration.Pos,
"%s already declared in block at %v",
declaration.Name, exising.Pos)
declaration.Name, existing.Pos)
}
ty, err := this.analyzeType(declaration.Type)
declaration.ty = ty
ty, err := this.analyzeType(declaration.Type, false)
declaration.Type = ty
if err != nil { return nil, err }
err := this.canAssignStrict(declaration.Pos, into, declaration.Type)
err = this.canAssignStrict(declaration.Pos, into, declaration.Type)
if err != nil { return nil, err }
this.AddVariable(declaration)
@ -71,22 +71,26 @@ func (this *Tree) analyzeCall (
call.Function = function
if err != nil { return nil, err }
err := this.canAssignStrict(call.Pos, into, function.Type)
err = this.canAssignStrict(call.Pos, into, function.Signature.Return)
if err != nil { return nil, err }
if len(call.Arguments) > len(function.Signature.Arguments) {
return nil, participle.Errorf (
call.Position, "too many arguments in call to %s",
call.Pos, "too many arguments in call to %s",
call.Name)
} else if len(call.Arguments) < len(function.Signature.Arguments) {
return nil, participle.Errorf (
call.Position, "too few arguments in call to %s",
call.Pos, "too few arguments in call to %s",
call.Name)
}
for index, argument := range call.Arguments {
correct := function.ArgumentMap[function.ArgumentOrder[index]]
err := this.analyzeExpression(correct.Type, argument)
signature := function.Signature
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
argument, err := this.analyzeExpression(correct.Type, argument)
if err != nil { return nil, err }
call.Arguments[index] = argument
}
return call, nil
}

View File

@ -14,8 +14,7 @@ func (this *Tree) assembleSignatureMap (signature *entity.Signature) (*entity.Si
}
signature.ArgumentMap [member.Name] = member
signature.ArgumentOrder[index] = member.Name
signature.Arguments [index] = member
}
signature.Arguments = nil
return signature, nil
}

View File

@ -169,9 +169,8 @@ func (this *Tree) assembleStructMap (ty *entity.TypeStruct) (*entity.TypeStruct,
}
ty.MemberMap [member.Name] = member
ty.MemberOrder[index] = member.Name
ty.Members [index] = member
}
ty.Members = nil
return ty, nil
}
@ -186,9 +185,8 @@ func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeI
}
ty.BehaviorMap [method.Name] = method
ty.BehaviorOrder[index] = method.Name
ty.Behaviors [index] = method
}
ty.Behaviors = nil
return ty, nil
}
@ -202,3 +200,43 @@ func (this *Tree) analyzeBehavior (behavior *entity.Signature) (*entity.Signatur
behavior.Return, err = this.analyzeType(behavior.Return, false)
return behavior, err
}
// methodOrBehaviorOf returns *entity.Signature if it found an interface
// behavior, and *entity.Method if it found a method.
func (this *Tree) methodOrBehaviorOf (ty entity.Type, name string) any {
switch destination.(type) {
case *entity.TypeNamed:
ty := ty.(*entity.TypeNamed)
method, _ := this.analyzeMethod(pos, ty.Name, name)
if method != nil {
return method
} else {
return this.methodOrBehaviorOf(ty.Type, name)
}
case *entity.TypeInterface:
ty := ty.(*entity.TypeInterface)
if behavior, ok := ty.BehaviorMap[name]; ok {
return behavior
} else {
return nil
}
case *entity.TypePointer:
ty := ty.(*entity.TypePointer)
return this.methodOrBehaviorOf(ty.Referenced)
case *entity.TypeVoid,
*entity.TypePointer,
*entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
return nil
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
}
}

View File

@ -11,14 +11,14 @@ type Type interface {
ty ()
}
// Void represents the absence of a type.
type Void struct { }
func (*Void) ty(){}
func (*Void) Equals (ty Type) bool {
_, equals := ty.(*Void)
// TypeVoid represents the absence of a type.
type TypeVoid struct { }
func (*TypeVoid) ty(){}
func (*TypeVoid) Equals (ty Type) bool {
_, equals := ty.(*TypeVoid)
return equals
}
func (*Void) String () string { return "Void" }
func (*TypeVoid) String () string { return "Void" }
// TypeNamed refers to a user-defined or built in named type.
type TypeNamed struct {