This commit is contained in:
Sasha Koshka 2023-10-24 01:34:58 -04:00
parent 5ac046d5b1
commit ee8eabcfc4
6 changed files with 160 additions and 61 deletions

View File

@ -13,6 +13,10 @@ func (this *Tree) canAssignStrict (
) error {
can := false
switch destination.(type) {
// yes if unspecified
case nil:
can = true
// named type
case *entity.TypeNamed:
// try to compare names first
@ -32,12 +36,9 @@ func (this *Tree) canAssignStrict (
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)
}
method, err := this.analyzeMethodOrBehavior (
pos, source, name)
if err != nil { return err }
// extract signature
var signature *entity.Signature
@ -48,7 +49,7 @@ func (this *Tree) canAssignStrict (
signature = method.(*entity.Method).Signature
default:
panic(fmt.Sprint (
"Tree.methodOrBehaviorOf returned ",
"Tree.analyzeMethodOrBehavior returned ",
method))
}

View File

@ -5,6 +5,7 @@ import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *Tree) analyzeExpression (
into entity.Type,
strict bool,
expression entity.Expression,
) (
entity.Expression,
@ -12,15 +13,15 @@ func (this *Tree) analyzeExpression (
) {
switch expression.(type) {
case *entity.Variable:
return this.analyzeVariable(into, expression.(*entity.Variable))
return this.analyzeVariable(into, strict, expression.(*entity.Variable))
case *entity.Declaration:
return this.analyzeDeclaration(into, expression.(*entity.Declaration))
return this.analyzeDeclaration(into, strict, expression.(*entity.Declaration))
case *entity.Call:
return this.analyzeCall(into, expression.(*entity.Call))
return this.analyzeCall(into, strict, expression.(*entity.Call))
case *entity.MethodCall:
return this.analyzeMethodCall(into, expression.(*entity.MethodCall))
// case *entity.Subscript:
// return this.analyzeSubscript(into, expression.(*entity.Subscript))
return this.analyzeMethodCall(into, strict, expression.(*entity.MethodCall))
case *entity.Subscript:
return this.analyzeSubscript(into, strict, expression.(*entity.Subscript))
// case *entity.Slice:
// return this.analyzeSlice(into, expression.(*entity.Slice))
// case *entity.Dereference:
@ -61,7 +62,7 @@ func (this *Tree) analyzeStatement (
if assignment, ok := statement.(*entity.Assignment); ok {
return this.analyzeAssignment(assignment)
} else if expression, ok := statement.(entity.Expression); ok {
expression, err := this.analyzeExpression(nil, expression)
expression, err := this.analyzeExpression(nil, false, expression)
return expression.(entity.Statement), err
} else {
panic(fmt.Sprint (
@ -69,4 +70,3 @@ func (this *Tree) analyzeStatement (
statement))
}
}

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"
@ -11,6 +11,7 @@ import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *Tree) analyzeVariable (
into entity.Type,
strict bool,
variable *entity.Variable,
) (
entity.Expression,
@ -32,6 +33,7 @@ func (this *Tree) analyzeVariable (
func (this *Tree) analyzeDeclaration (
into entity.Type,
strict bool,
declaration *entity.Declaration,
) (
entity.Expression,
@ -61,19 +63,23 @@ func (this *Tree) analyzeDeclaration (
}
func (this *Tree) analyzeCall (
into entity.Type,
call *entity.Call,
into entity.Type,
strict bool,
call *entity.Call,
) (
entity.Expression,
error,
) {
// get function
function, err := this.analyzeFunction(call.Pos, call.Name)
call.Function = function
if err != nil { return nil, err }
// check return result
err = this.canAssignStrict(call.Pos, into, function.Signature.Return)
if err != nil { return nil, err }
// check arg count
if len(call.Arguments) > len(function.Signature.Arguments) {
return nil, participle.Errorf (
call.Pos, "too many arguments in call to %s",
@ -84,13 +90,81 @@ func (this *Tree) analyzeCall (
call.Name)
}
// check arg types
for index, argument := range call.Arguments {
signature := function.Signature
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
argument, err := this.analyzeExpression(correct.Type, argument)
argument, err := this.analyzeExpression(correct.Type, true, argument)
if err != nil { return nil, err }
call.Arguments[index] = argument
}
return call, nil
}
func (this *Tree) analyzeMethodCall (
into entity.Type,
strict bool,
call *entity.MethodCall,
) (
entity.Expression,
error,
) {
// get method
sourceExpr, err := this.analyzeVariable(nil, false, call.Source)
source := sourceExpr.(*entity.Variable)
if err != nil { return nil, err }
method, err := this.analyzeMethodOrBehavior (
call.Pos, source.Declaration.Type, call.Name)
// extract signature
var signature *entity.Signature
switch method.(type) {
case *entity.Signature:
signature = method.(*entity.Signature)
call.Behavior = signature
case *entity.Method:
signature = method.(*entity.Method).Signature
call.Method = method.(*entity.Method)
default:
panic(fmt.Sprint (
"Tree.analyzeMethodOrBehavior returned ",
method))
}
// check return result
err = this.canAssignStrict(call.Pos, into, signature.Return)
if err != nil { return nil, err }
// check arg count
if len(call.Arguments) > len(signature.Arguments) {
return nil, participle.Errorf (
call.Pos, "too many arguments in call to %s",
call.Name)
} else if len(call.Arguments) < len(signature.Arguments) {
return nil, participle.Errorf (
call.Pos, "too few arguments in call to %s",
call.Name)
}
// check arg types
for index, argument := range call.Arguments {
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
argument, err := this.analyzeExpression(correct.Type, true, argument)
if err != nil { return nil, err }
call.Arguments[index] = argument
}
return call, nil
}
func (this *Tree) analyzeSubscript (
into entity.Type,
strict bool,
subscript *entity.Subscript,
) (
entity.Expression,
error,
) {
}

View File

@ -1,9 +1,11 @@
package analyzer
import "fmt"
import "github.com/alecthomas/participle/v2"
import "github.com/alecthomas/participle/v2/lexer"
import "git.tebibyte.media/sashakoshka/fspl/entity"
// analyzeMethod analyzes a method that is directly owned by the specified type.
func (this *Tree) analyzeMethod (
pos lexer.Position,
typeName string,
@ -56,3 +58,59 @@ func (this *Tree) analyzeMethod (
// TODO: analyze method body
return method, nil
}
func (this *Tree) methodExists (typeName, name string) bool {
_, exists := this.rawMethods[typeName + "." + name]
return exists
}
// analyzeMethodOrBehavior returns *entity.Signature if it found an interface
// behavior, and *entity.Method if it found a method.
func (this *Tree) analyzeMethodOrBehavior (
pos lexer.Position,
ty entity.Type,
name string,
) (
any,
error,
) {
switch ty.(type) {
case *entity.TypeNamed:
ty := ty.(*entity.TypeNamed)
if this.methodExists(ty.Name, name) {
method, err := this.analyzeMethod(pos, ty.Name, name)
if err != nil { return nil, err }
return method, nil
} else {
return this.analyzeMethodOrBehavior(pos, ty.Type, name)
}
case *entity.TypeInterface:
ty := ty.(*entity.TypeInterface)
if behavior, ok := ty.BehaviorMap[name]; ok {
return behavior, nil
} else {
return nil, participle.Errorf (
pos, "no behavior or method named %s",
name)
}
case *entity.TypePointer:
ty := ty.(*entity.TypePointer)
return this.analyzeMethodOrBehavior(pos, ty.Referenced, name)
case *entity.TypeVoid,
*entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
return nil, participle.Errorf (
pos, "no method named %s defined on this type",
name)
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
}
}

View File

@ -60,8 +60,9 @@ func (this *Tree) analyzeTypeInternal (
error,
) {
// edge cases
if ty == entity.Void() { return ty, nil }
if ty == nil { return entity.Void(), nil }
void := &entity.TypeVoid { }
if ty.Equals(void) { return ty, nil }
if ty == nil { return void, nil }
var err error
// if we are analyzing a typedef, we will need to update incomplete type
@ -200,43 +201,3 @@ 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

@ -80,10 +80,15 @@ func (this *Call) String () string {
// inherent type information, it may be directly assigned to an interface.
// A method call is never a valid location expression.
type MethodCall struct {
// Syntax
Pos lexer.Position
Source *Variable `parser:" @@ '.' "`
Name string `parser:" '[' @Ident "`
Arguments []Expression `parser:" @@* ']' "`
// Semantics
Method *Method
Behavior *Signature
}
func (*MethodCall) expression(){}
func (*MethodCall) statement(){}