From 72941e1ab49e9fb29d6466bf491d80572c4b51c6 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 21 Nov 2023 15:04:01 -0500 Subject: [PATCH] Slight modifications to analyzer --- analyzer/assignment.go | 68 ++++++++++++++++++------------------- analyzer/assignment_test.go | 16 +++++++++ analyzer/expression.go | 22 ++++++------ analyzer/function.go | 10 +++--- analyzer/literal.go | 16 +++++---- analyzer/method.go | 36 ++++++++++++++++---- analyzer/name_test.go | 16 +++++++++ entity/scope.go | 10 ++++++ 8 files changed, 132 insertions(+), 62 deletions(-) diff --git a/analyzer/assignment.go b/analyzer/assignment.go index d79049c..4153527 100644 --- a/analyzer/assignment.go +++ b/analyzer/assignment.go @@ -71,15 +71,15 @@ func (this *Tree) canAssign ( } // 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 { + if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok { + if source, ok := 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) + destination = ReduceToBase(destination) + source = ReduceToBase(source) } switch destination.(type) { @@ -126,8 +126,8 @@ func (this *Tree) canAssignCoerce ( source, destination) } - destination = this.reduceToBase(destination) - source = this.reduceToBase(source) + destination = ReduceToBase(destination) + source = ReduceToBase(source) switch destination.(type) { // no type @@ -215,21 +215,11 @@ func (this *Tree) canAssignInterface ( 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) + left = ReduceToBase(left) + right = ReduceToBase(right) switch left.(type) { case nil: return false @@ -353,10 +343,20 @@ func (this *Tree) isLocationExpression (expression entity.Expression) error { } } +// ReduceToBase takes in an analyzed type and reduces it to its first non-name +// type. +func ReduceToBase (ty entity.Type) entity.Type { + if namedty, ok := ty.(*entity.TypeNamed); ok { + return ReduceToBase(namedty.Type) + } else { + return ty + } +} + // 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) + ty = ReduceToBase(ty) switch ty.(type) { case *entity.TypeInterface: return ty.(*entity.TypeInterface), true default: return nil, false @@ -364,8 +364,8 @@ func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) { } // isNumeric returns whether or not the specified type is a number. -func (this *Tree) isNumeric (ty entity.Type) bool { - ty = this.reduceToBase(ty) +func isNumeric (ty entity.Type) bool { + ty = ReduceToBase(ty) switch ty.(type) { case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true default: return false @@ -373,8 +373,8 @@ func (this *Tree) isNumeric (ty entity.Type) bool { } // isInteger returns whether or not the specified type is an integer. -func (this *Tree) isInteger (ty entity.Type) bool { - switch this.reduceToBase(ty).(type) { +func isInteger (ty entity.Type) bool { + switch ReduceToBase(ty).(type) { case *entity.TypeInt, *entity.TypeWord: return true default: return false } @@ -382,12 +382,12 @@ func (this *Tree) isInteger (ty entity.Type) bool { // 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) +func isOrdered (ty entity.Type) bool { + return isNumeric(ty) } // isBoolean returns whether or not the specified type is a boolean. -func (this *Tree) isBoolean (ty entity.Type) bool { +func isBoolean (ty entity.Type) bool { for { named, ok := ty.(*entity.TypeNamed) if !ok { return false } @@ -396,16 +396,16 @@ func (this *Tree) isBoolean (ty entity.Type) bool { } // isFloat returns whether or not the specified type is a float. -func (this *Tree) isFloat (ty entity.Type) bool { - switch this.reduceToBase(ty).(type) { +func isFloat (ty entity.Type) bool { + switch 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) { +func isUnsigned (ty entity.Type) bool { + switch ReduceToBase(ty).(type) { case *entity.TypeInt: return ty.(*entity.TypeInt).Signed case *entity.TypeWord: return ty.(*entity.TypeInt).Signed default: return false @@ -414,8 +414,8 @@ func (this *Tree) isUnsigned (ty entity.Type) bool { // 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) +func inRange (ty entity.Type, value int64) bool { + base := ReduceToBase(ty) switch base.(type) { case *entity.TypeInt: base := base.(*entity.TypeInt) @@ -423,7 +423,7 @@ func (this *Tree) inRange (ty entity.Type, value int64) bool { return value > integer.SignedMin(base.Width) && value < integer.SignedMax(base.Width) } else { - return value > 0 && + return value >= 0 && uint64(value) < integer.UnsignedMax(base.Width) } case *entity.TypeWord: @@ -431,7 +431,7 @@ func (this *Tree) inRange (ty entity.Type, value int64) bool { if base.Signed { return value > math.MinInt && value < math.MaxInt } else { - return value > 0 && uint64(value) < math.MaxUint + return value >= 0 && uint64(value) < math.MaxUint } default: return false } diff --git a/analyzer/assignment_test.go b/analyzer/assignment_test.go index f1d6916..fd26bab 100644 --- a/analyzer/assignment_test.go +++ b/analyzer/assignment_test.go @@ -206,6 +206,20 @@ BlueJay.[land] = { } `) } +func TestAssignmentInterfaceErrBadLayer (test *testing.T) { +testStringErr (test, +"no method named fly defined on this type", 7, 11, +` +Bird: ([fly distance:F64] [land]) +BlueJay: Int +BlueJay.[land] = { } +BlueJayRef: *BlueJay +[main] = { + b:Bird = a:BlueJayRef +} +`) +} + func TestAssignmentInterface (test *testing.T) { testString (test, ` @@ -215,6 +229,8 @@ BlueJay.[fly distance:F64] = { } BlueJay.[land] = { } [main] = { b:Bird = a:BlueJay + ref:*Bird = [@ a] + c:Bird = ref } `) } diff --git a/analyzer/expression.go b/analyzer/expression.go index ba12c4a..8058aaf 100644 --- a/analyzer/expression.go +++ b/analyzer/expression.go @@ -353,7 +353,7 @@ func (this *Tree) analyzeOperation ( comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) { if len(operation.Arguments) < 2 { return wrongArgCount() } - if !this.isBoolean(into) { return wrongInto() } + if !isBoolean(into) { return wrongInto() } operation.Ty = &entity.TypeNamed { Name: "Bool", Type: this.Types["Bool"].Type, @@ -404,30 +404,30 @@ func (this *Tree) analyzeOperation ( entity.OperatorDivide, entity.OperatorIncrement, entity.OperatorDecrement: - return nSameType(-1, this.isNumeric) + return nSameType(-1, isNumeric) case entity.OperatorModulo: - return nSameType(2, this.isNumeric) + return nSameType(2, isNumeric) // logic case entity.OperatorLogicalNot: - return nSameType(1, this.isBoolean) + return nSameType(1, isBoolean) case entity.OperatorLogicalOr, entity.OperatorLogicalAnd, entity.OperatorLogicalXor: - return nSameType(-1, this.isBoolean) + return nSameType(-1, isBoolean) // bit manipulation case entity.OperatorNot: - return nSameType(1, this.isInteger) + return nSameType(1, isInteger) case entity.OperatorOr, entity.OperatorAnd, entity.OperatorXor: - return nSameType(-1, this.isInteger) + return nSameType(-1, isInteger) case entity.OperatorLeftShift, entity.OperatorRightShift: if len(operation.Arguments) != 2 { return wrongArgCount() } - if !this.isInteger(into) { return wrongInto() } + if !isInteger(into) { return wrongInto() } arg, err := this.analyzeExpression(into, mode, operation.Arguments[0]) if err != nil { return nil, err } @@ -447,10 +447,10 @@ func (this *Tree) analyzeOperation ( entity.OperatorGreater, entity.OperatorLessEqual, entity.OperatorGreaterEqual: - return comparison(this.isOrdered) + return comparison(isOrdered) case entity.OperatorEqual: - return comparison(this.isOrdered) + return comparison(isOrdered) default: panic(fmt.Sprint ( @@ -510,7 +510,7 @@ func (this *Tree) analyzeMemberAccess ( if err != nil { return nil, err } access.Source = source.(*entity.Variable) - sourceType, ok := this.reduceToBase(source.Type()).(*entity.TypeStruct) + sourceType, ok := ReduceToBase(source.Type()).(*entity.TypeStruct) if !ok { return nil, participle.Errorf ( access.Pos, "cannot access members of %v", source) diff --git a/analyzer/function.go b/analyzer/function.go index 97039b4..7fa1344 100644 --- a/analyzer/function.go +++ b/analyzer/function.go @@ -49,9 +49,11 @@ func (this *Tree) analyzeFunction ( this.Functions[name] = function // analyze function body - body, err := this.analyzeExpression ( - function.Signature.Return, strict, function.Body) - if err != nil { return nil, err } - function.Body = body + if function.Body != nil { + body, err := this.analyzeExpression ( + function.Signature.Return, strict, function.Body) + if err != nil { return nil, err } + function.Body = body + } return function, nil } diff --git a/analyzer/literal.go b/analyzer/literal.go index e67bc72..9be6a24 100644 --- a/analyzer/literal.go +++ b/analyzer/literal.go @@ -12,13 +12,13 @@ func (this *Tree) analyzeLiteralInt ( entity.Expression, error, ) { - if !this.isNumeric(into) { + if !isNumeric(into) { return nil, participle.Errorf ( literal.Pos, "cannot use integer literal as %v", into) } - if this.isInteger(into) && !this.inRange(into, int64(literal.Value)) { + if isInteger(into) && !inRange(into, int64(literal.Value)) { return nil, participle.Errorf ( literal.Pos, "integer literal out of range for type %v", into) @@ -36,7 +36,7 @@ func (this *Tree) analyzeLiteralFloat ( entity.Expression, error, ) { - if !this.isFloat(into) { + if !isFloat(into) { return nil, participle.Errorf ( literal.Pos, "cannot use float literal as %v", into) @@ -54,7 +54,7 @@ func (this *Tree) analyzeLiteralArray ( entity.Expression, error, ) { - base := this.reduceToBase(into) + base := ReduceToBase(into) var elementType entity.Type switch base.(type) { case *entity.TypeArray: @@ -69,6 +69,10 @@ func (this *Tree) analyzeLiteralArray ( case *entity.TypeSlice: base := base.(*entity.TypeSlice) elementType = base.Element + + case *entity.TypePointer: + base := base.(*entity.TypePointer) + elementType = base.Referenced default: return nil, participle.Errorf ( literal.Pos, "cannot use array literal as %v", @@ -93,7 +97,7 @@ func (this *Tree) analyzeLiteralStruct ( entity.Expression, error, ) { - base, ok := this.reduceToBase(into).(*entity.TypeStruct) + base, ok := ReduceToBase(into).(*entity.TypeStruct) if !ok { return nil, participle.Errorf ( literal.Pos, "cannot use struct literal as %v", @@ -128,7 +132,7 @@ func (this *Tree) analyzeLiteralBoolean ( entity.Expression, error, ) { - if !this.isBoolean(into) { + if !isBoolean(into) { return nil, participle.Errorf ( literal.Pos, "cannot use boolean literal as %v", into) diff --git a/analyzer/method.go b/analyzer/method.go index de35342..3f4a777 100644 --- a/analyzer/method.go +++ b/analyzer/method.go @@ -56,10 +56,12 @@ func (this *Tree) analyzeMethod ( owner.Methods[name] = method // analyze method body - body, err := this.analyzeExpression ( - method.Signature.Return, strict, method.Body) - if err != nil { return nil, err } - method.Body = body + if method.Body != nil { + body, err := this.analyzeExpression ( + method.Signature.Return, strict, method.Body) + if err != nil { return nil, err } + method.Body = body + } return method, nil } @@ -77,6 +79,18 @@ func (this *Tree) analyzeMethodOrBehavior ( ) ( any, error, +) { + return this.analyzeMethodOrBehaviorInternal(pos, ty, name, true) +} + +func (this *Tree) analyzeMethodOrBehaviorInternal ( + pos lexer.Position, + ty entity.Type, + name string, + pierceReference bool, +) ( + any, + error, ) { switch ty.(type) { case *entity.TypeNamed: @@ -86,7 +100,8 @@ func (this *Tree) analyzeMethodOrBehavior ( if err != nil { return nil, err } return method, nil } else { - return this.analyzeMethodOrBehavior(pos, ty.Type, name) + return this.analyzeMethodOrBehaviorInternal ( + pos, ty.Type, name, false) } case *entity.TypeInterface: @@ -100,8 +115,15 @@ func (this *Tree) analyzeMethodOrBehavior ( } case *entity.TypePointer: - ty := ty.(*entity.TypePointer) - return this.analyzeMethodOrBehavior(pos, ty.Referenced, name) + if pierceReference { + ty := ty.(*entity.TypePointer) + return this.analyzeMethodOrBehaviorInternal ( + pos, ty.Referenced, name, false) + } else { + return nil, participle.Errorf ( + pos, "no method named %s defined on this type", + name) + } case *entity.TypeSlice, *entity.TypeArray, diff --git a/analyzer/name_test.go b/analyzer/name_test.go index b8116cd..cfed8ee 100644 --- a/analyzer/name_test.go +++ b/analyzer/name_test.go @@ -15,6 +15,20 @@ Type.[something] = { } `) } +func TestMethodNameErrBadLayer (test *testing.T) { +testStringErr (test, +"no method named something defined on this type", 7, 2, +` +Type: Int +Type.[something] = { } +RefType: *Type +[main] = { + instance:RefType + instance.[something] +} +`) +} + func TestMethodName (test *testing.T) { testString (test, ` @@ -23,6 +37,8 @@ Type.[world] = { } [main] = { instance:Type instance.[world] + ref:*Type = [@ instance] + ref.[world] } `) } diff --git a/entity/scope.go b/entity/scope.go index e1b2196..12cca52 100644 --- a/entity/scope.go +++ b/entity/scope.go @@ -9,6 +9,10 @@ type Scoped interface { // AddVariable adds a variable to this entity's scope. AddVariable (declaration *Declaration) + + // OverVariables calls callback for each defined variable, stopping if + // returns false. + OverVariables (callback func (*Declaration) bool) } // Scope implements a scope. @@ -29,3 +33,9 @@ func (this *Scope) AddVariable (declaration *Declaration) { } this.variables[declaration.Name] = declaration } + +func (this *Scope) OverVariables (callback func (*Declaration) bool) { + for _, declaration := range this.variables { + if callback(declaration) { break } + } +}