fspl/analyzer/assignment.go
2023-10-31 21:52:47 -04:00

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