370 lines
10 KiB
Go
370 lines
10 KiB
Go
package analyzer
|
|
|
|
import "fmt"
|
|
import "math"
|
|
import "github.com/alecthomas/participle/v2"
|
|
import "github.com/alecthomas/participle/v2/lexer"
|
|
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
|
import "git.tebibyte.media/sashakoshka/fspl/integer"
|
|
|
|
type strictness int; const (
|
|
// name equivalence
|
|
strict strictness = iota
|
|
// structural equivalence up until the first base type, then name
|
|
// equivalence applies to the parts of the type
|
|
weak
|
|
// structural equivalence
|
|
structural
|
|
// assignment rules are completely ignored and everything is accepted
|
|
force
|
|
)
|
|
|
|
// canAssign takes in an analyzed destination type and an analyzed source type,
|
|
// and determines whether source can be assigned to destination.
|
|
func (this *Tree) canAssign (
|
|
pos lexer.Position,
|
|
// assuming both are analyzed already
|
|
destination entity.Type,
|
|
mode strictness,
|
|
source entity.Type,
|
|
) error {
|
|
// short circuit if force is selected
|
|
if mode == force { return nil }
|
|
|
|
// do structural equivalence if structural is selected
|
|
if mode == structural {
|
|
if this.areStructurallyEquivalent(destination, source) {
|
|
return nil
|
|
} else {
|
|
return participle.Errorf(pos, "expected %v", destination)
|
|
}
|
|
}
|
|
|
|
// first, check if this is interface assignment
|
|
if destination, ok := this.isInterface(destination); ok {
|
|
return this.canAssignInterface(pos, destination, source)
|
|
}
|
|
|
|
// then, check for array to slice assignment
|
|
if destination, ok := this.reduceToBase(destination).(*entity.TypeSlice); ok {
|
|
if source, ok := this.reduceToBase(source). (*entity.TypeArray); ok {
|
|
return this.canAssignSliceArray(pos, destination, source)
|
|
}}
|
|
|
|
// if weak, only compare the first base types
|
|
if mode == weak {
|
|
destination = this.reduceToBase(destination)
|
|
source = this.reduceToBase(source)
|
|
}
|
|
|
|
switch destination.(type) {
|
|
// no type
|
|
case nil:
|
|
return nil
|
|
|
|
// named type
|
|
case *entity.TypeNamed:
|
|
destination := destination.(*entity.TypeNamed)
|
|
if source, ok := source.(*entity.TypeNamed); ok {
|
|
if destination.Name == source.Name {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// base type
|
|
case *entity.TypePointer,
|
|
*entity.TypeSlice,
|
|
*entity.TypeArray,
|
|
*entity.TypeStruct,
|
|
*entity.TypeInt,
|
|
*entity.TypeFloat,
|
|
*entity.TypeWord,
|
|
*entity.TypeBool:
|
|
|
|
if !destination.Equals(source) {
|
|
return participle.Errorf(pos, "expected %v", destination)
|
|
}
|
|
|
|
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", destination))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
|
|
// type, and determines whether the array can be assigned to the slice.
|
|
func (this *Tree) canAssignSliceArray (
|
|
pos lexer.Position,
|
|
destination *entity.TypeSlice,
|
|
source *entity.TypeArray,
|
|
) error {
|
|
err := this.canAssign(pos, destination.Element, strict, source.Element)
|
|
if err != nil {
|
|
return participle.Errorf(pos, "expected %v", destination)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// canAssignInterface takes in an analyzed interface destination type and an
|
|
// analyzed source type, and determines whether source can be assigned to
|
|
// destination.
|
|
func (this *Tree) canAssignInterface (
|
|
pos lexer.Position,
|
|
destination *entity.TypeInterface,
|
|
source entity.Type,
|
|
) error {
|
|
for name, behavior := range destination.BehaviorMap {
|
|
// get method
|
|
method, err := this.analyzeMethodOrBehavior (
|
|
pos, source, name)
|
|
if err != nil { return err }
|
|
|
|
// 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.analyzeMethodOrBehavior returned ",
|
|
method))
|
|
}
|
|
|
|
fmt.Println(signature, behavior)
|
|
|
|
// check equivalence
|
|
if !signature.Equals(behavior) {
|
|
return participle.Errorf (
|
|
pos, "%v has wrong signature for method %v",
|
|
source, name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// reduceToBase takes in an analyzed type and reduces it to its first non-name
|
|
// type.
|
|
func (this *Tree) reduceToBase (ty entity.Type) entity.Type {
|
|
if namedty, ok := ty.(*entity.TypeNamed); ok {
|
|
return this.reduceToBase(namedty.Type)
|
|
} else {
|
|
return ty
|
|
}
|
|
}
|
|
|
|
// areStructurallyEquivalent tests whether two types are structurally equivalent
|
|
// to eachother.
|
|
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
|
|
left = this.reduceToBase(left)
|
|
right = this.reduceToBase(right)
|
|
|
|
switch left.(type) {
|
|
case nil: return false
|
|
|
|
// composite
|
|
case *entity.TypePointer:
|
|
left := left.(*entity.TypePointer)
|
|
right, ok := right.(*entity.TypePointer)
|
|
if !ok { return false }
|
|
return this.areStructurallyEquivalent(left.Referenced, right.Referenced)
|
|
|
|
case *entity.TypeSlice:
|
|
left := left.(*entity.TypeSlice)
|
|
right, ok := right.(*entity.TypeSlice)
|
|
if !ok { return false }
|
|
return this.areStructurallyEquivalent(left.Element, right.Element)
|
|
|
|
case *entity.TypeArray:
|
|
left := left.(*entity.TypeArray)
|
|
right, ok := right.(*entity.TypeArray)
|
|
if !ok { return false }
|
|
return left.Length == right.Length &&
|
|
this.areStructurallyEquivalent(left.Element, right.Element)
|
|
|
|
case *entity.TypeStruct:
|
|
left := left.(*entity.TypeStruct)
|
|
right, ok := right.(*entity.TypeStruct)
|
|
if !ok { return false }
|
|
if len(left.MemberMap) != len(right.MemberMap) { return false }
|
|
for name, member := range left.MemberMap {
|
|
other, ok := right.MemberMap[name]
|
|
if !ok { return false }
|
|
if !this.areStructurallyEquivalent(member.Type(), other.Type()) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// terminals
|
|
case *entity.TypeInt,
|
|
*entity.TypeFloat,
|
|
*entity.TypeWord,
|
|
*entity.TypeBool:
|
|
return left.Equals(right)
|
|
|
|
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", left))
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isLocationExpression returns whether or not an expression is a valid location
|
|
// expression.
|
|
func (this *Tree) isLocationExpression (expression entity.Expression) error {
|
|
switch expression.(type) {
|
|
case *entity.Variable:
|
|
return nil
|
|
case *entity.Declaration:
|
|
return nil
|
|
case *entity.Call:
|
|
return participle.Errorf (
|
|
expression.(*entity.Call).Pos,
|
|
"cannot assign to function call")
|
|
case *entity.MethodCall:
|
|
return participle.Errorf (
|
|
expression.(*entity.MethodCall).Pos,
|
|
"cannot assign to method call")
|
|
case *entity.Subscript:
|
|
return this.isLocationExpression (
|
|
expression.(*entity.Subscript).Slice)
|
|
case *entity.Slice:
|
|
return participle.Errorf (
|
|
expression.(*entity.MethodCall).Pos,
|
|
"cannot assign to slice operation")
|
|
case *entity.Dereference:
|
|
return this.isLocationExpression (
|
|
expression.(*entity.Dereference).Pointer)
|
|
case *entity.Reference:
|
|
return participle.Errorf (
|
|
expression.(*entity.Reference).Pos,
|
|
"cannot assign to reference operation")
|
|
case *entity.ValueCast:
|
|
return participle.Errorf (
|
|
expression.(*entity.ValueCast).Pos,
|
|
"cannot assign to value cast")
|
|
case *entity.BitCast:
|
|
return participle.Errorf (
|
|
expression.(*entity.BitCast).Pos,
|
|
"cannot assign to bit cast")
|
|
case *entity.Operation:
|
|
expression := expression.(*entity.Operation)
|
|
return participle.Errorf (
|
|
expression.Pos,
|
|
"cannot assign to %v operation",
|
|
expression.Operator)
|
|
case *entity.Block:
|
|
return participle.Errorf (
|
|
expression.(*entity.Block).Pos,
|
|
"cannot assign to block")
|
|
case *entity.MemberAccess:
|
|
return this.isLocationExpression (
|
|
expression.(*entity.MemberAccess).Source)
|
|
case *entity.IfElse:
|
|
return participle.Errorf (
|
|
expression.(*entity.Block).Pos,
|
|
"cannot assign to if/else")
|
|
case *entity.Loop:
|
|
return participle.Errorf (
|
|
expression.(*entity.Block).Pos,
|
|
"cannot assign to loop")
|
|
case *entity.Break:
|
|
return participle.Errorf (
|
|
expression.(*entity.Block).Pos,
|
|
"cannot assign to break statement")
|
|
case *entity.Return:
|
|
return participle.Errorf (
|
|
expression.(*entity.Block).Pos,
|
|
"cannot assign to return statement")
|
|
default:
|
|
panic(fmt.Sprint (
|
|
"BUG: analyzer doesnt know about expression ",
|
|
expression))
|
|
}
|
|
}
|
|
|
|
// isInterface takes in an analyzed type and returns true and an interface if
|
|
// that type refers to an interface. If not, it returns nil, false.
|
|
func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
|
|
ty = this.reduceToBase(ty)
|
|
switch ty.(type) {
|
|
case *entity.TypeInterface: return ty.(*entity.TypeInterface), true
|
|
default: return nil, false
|
|
}
|
|
}
|
|
|
|
// isNumeric returns whether or not the specified type is a number.
|
|
func (this *Tree) isNumeric (ty entity.Type) bool {
|
|
ty = this.reduceToBase(ty)
|
|
switch ty.(type) {
|
|
case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// isInteger returns whether or not the specified type is an integer.
|
|
func (this *Tree) isInteger (ty entity.Type) bool {
|
|
switch this.reduceToBase(ty).(type) {
|
|
case *entity.TypeInt, *entity.TypeWord: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// isOrdered returns whether or not the values of the specified type can be
|
|
// ordered.
|
|
func (this *Tree) isOrdered (ty entity.Type) bool {
|
|
return this.isNumeric(ty)
|
|
}
|
|
|
|
// isBoolean returns whether or not the specified type is a boolean.
|
|
func (this *Tree) isBoolean (ty entity.Type) bool {
|
|
switch this.reduceToBase(ty).(type) {
|
|
case *entity.TypeBool: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// isFloat returns whether or not the specified type is a float.
|
|
func (this *Tree) isFloat (ty entity.Type) bool {
|
|
switch this.reduceToBase(ty).(type) {
|
|
case *entity.TypeFloat: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// isUnsigned returns whether or not the specified type is an unsigned integer.
|
|
func (this *Tree) isUnsigned (ty entity.Type) bool {
|
|
switch this.reduceToBase(ty).(type) {
|
|
case *entity.TypeInt: return ty.(*entity.TypeInt).Signed
|
|
case *entity.TypeWord: return ty.(*entity.TypeInt).Signed
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// inRange returns whether the specified value can fit within the given integer
|
|
// type.
|
|
func (this *Tree) inRange (ty entity.Type, value int64) bool {
|
|
base := this.reduceToBase(ty)
|
|
switch base.(type) {
|
|
case *entity.TypeInt:
|
|
base := base.(*entity.TypeInt)
|
|
if base.Signed {
|
|
return value > integer.SignedMin(base.Width) &&
|
|
value < integer.SignedMax(base.Width)
|
|
} else {
|
|
return value > 0 &&
|
|
uint64(value) < integer.UnsignedMax(base.Width)
|
|
}
|
|
case *entity.TypeWord:
|
|
base := base.(*entity.TypeWord)
|
|
if base.Signed {
|
|
return value > math.MinInt && value < math.MaxInt
|
|
} else {
|
|
return value > 0 && uint64(value) < math.MaxUint
|
|
}
|
|
default: return false
|
|
}
|
|
}
|