Implement unary operator evaluation; and l-values
This change implements unary operator evaluation, including prefix/postfix assignment operators. Doing this required implementing l-values properly in the interpreter; namely, load location and dereference, when used in an l-value position, result in a pointer to the location, while otherwise they (implicitly, in load location's case) deference the location and yield a value.
This commit is contained in:
parent
5774999957
commit
d685850800
5 changed files with 155 additions and 13 deletions
|
@ -388,7 +388,7 @@ func (a *astBinder) checkUnaryOperatorExpression(node *ast.UnaryOperatorExpressi
|
|||
node.Operand.GetKind(), "load location")
|
||||
}
|
||||
a.b.ctx.RegisterType(node, symbols.NewPointerType(opty))
|
||||
case ast.OpUnaryPlus, ast.OpUnaryMinus, ast.OpBitwiseNot, ast.OpPlusPlus, ast.OpMinusMinus:
|
||||
case ast.OpUnaryPlus, ast.OpUnaryMinus, ast.OpBitwiseNot:
|
||||
// The target must be a number:
|
||||
if opty != types.Number {
|
||||
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, opty, types.Number)
|
||||
|
@ -400,6 +400,14 @@ func (a *astBinder) checkUnaryOperatorExpression(node *ast.UnaryOperatorExpressi
|
|||
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, opty, types.Bool)
|
||||
}
|
||||
a.b.ctx.RegisterType(node, types.Bool)
|
||||
case ast.OpPlusPlus, ast.OpMinusMinus:
|
||||
// The target must be a numeric l-value.
|
||||
if !a.isLValue(node.Operand) {
|
||||
a.b.Diag().Errorf(errors.ErrorIllegalAssignmentLValue.At(node))
|
||||
} else if opty != types.Number {
|
||||
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, opty, types.Number)
|
||||
}
|
||||
a.b.ctx.RegisterType(node, types.Number)
|
||||
default:
|
||||
contract.Failf("Unrecognized unary operator: %v", node.Operator)
|
||||
}
|
||||
|
|
|
@ -12,4 +12,5 @@ var (
|
|||
ErrorFunctionArgIncorrectType = newError(1005, "Function argument has an incorrect type; expected %v, got %v")
|
||||
ErrorFunctionArgNotFound = newError(1006, "Function argument '%v' was not supplied")
|
||||
ErrorFunctionArgUnknown = newError(1007, "Function argument '%v' was not recognized")
|
||||
ErrorIllegalReadonlyLValue = newError(1008, "A readonly target cannot be used as an assignment target")
|
||||
)
|
||||
|
|
|
@ -586,6 +586,22 @@ func (e *evaluator) evalExpression(node ast.Expression) (*Object, *Unwind) {
|
|||
}
|
||||
}
|
||||
|
||||
// evalLValueExpression evaluates an expression for use as an l-value; in particular, this loads the target as a
|
||||
// pointer/reference object, rather than as an ordinary value, so that it can be used in an assignment. This is only
|
||||
// valid on the subset of AST nodes that are legal l-values (very few of them, it turns out).
|
||||
func (e *evaluator) evalLValueExpression(node ast.Expression) (*Object, *Unwind) {
|
||||
switch n := node.(type) {
|
||||
case *ast.LoadLocationExpression:
|
||||
return e.evalLoadLocationExpressionFor(n, true)
|
||||
case *ast.UnaryOperatorExpression:
|
||||
contract.Assert(n.Operator == ast.OpDereference)
|
||||
return e.evalUnaryOperatorExpressionFor(n, true)
|
||||
default:
|
||||
contract.Failf("Unrecognized l-value expression type: %v", node.GetKind())
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evalNullLiteral(node *ast.NullLiteral) (*Object, *Unwind) {
|
||||
return NewPrimitiveObject(types.Null, nil), nil
|
||||
}
|
||||
|
@ -675,6 +691,10 @@ func (e *evaluator) evalObjectLiteral(node *ast.ObjectLiteral) (*Object, *Unwind
|
|||
}
|
||||
|
||||
func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression) (*Object, *Unwind) {
|
||||
return e.evalLoadLocationExpressionFor(node, false)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLoadLocationExpressionFor(node *ast.LoadLocationExpression, lval bool) (*Object, *Unwind) {
|
||||
// If there's a 'this', evaluate it.
|
||||
var this *Object
|
||||
if node.Object != nil {
|
||||
|
@ -735,7 +755,15 @@ func (e *evaluator) evalLoadLocationExpression(node *ast.LoadLocationExpression)
|
|||
}
|
||||
}
|
||||
|
||||
return NewReferenceObject(ty, pv), nil
|
||||
// If this isn't for an l-value, return the raw object. Otherwise, make sure it's not readonly, and return it.
|
||||
if lval {
|
||||
// A readonly reference cannot be used as an l-value.
|
||||
if pv.Readonly() {
|
||||
e.Diag().Errorf(errors.ErrorIllegalReadonlyLValue.At(node))
|
||||
}
|
||||
return NewReferenceObject(ty, pv), nil
|
||||
}
|
||||
return pv.Obj(), nil
|
||||
}
|
||||
|
||||
func (e *evaluator) evalLoadDynamicExpression(node *ast.LoadDynamicExpression) (*Object, *Unwind) {
|
||||
|
@ -789,9 +817,83 @@ func (e *evaluator) evalLambdaExpression(node *ast.LambdaExpression) (*Object, *
|
|||
}
|
||||
|
||||
func (e *evaluator) evalUnaryOperatorExpression(node *ast.UnaryOperatorExpression) (*Object, *Unwind) {
|
||||
// TODO: perform the unary operator's behavior.
|
||||
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
|
||||
return nil, nil
|
||||
return e.evalUnaryOperatorExpressionFor(node, false)
|
||||
}
|
||||
|
||||
func (e *evaluator) evalUnaryOperatorExpressionFor(node *ast.UnaryOperatorExpression, lval bool) (*Object, *Unwind) {
|
||||
contract.Assertf(!lval || node.Operator == ast.OpDereference, "Only dereference unary ops support l-values")
|
||||
|
||||
// Evaluate the operand and prepare to use it.
|
||||
var opand *Object
|
||||
if node.Operator == ast.OpAddressof ||
|
||||
node.Operator == ast.OpPlusPlus || node.Operator == ast.OpMinusMinus {
|
||||
// These operators require an l-value; so we bind the expression a bit differently.
|
||||
var uw *Unwind
|
||||
if opand, uw = e.evalLValueExpression(node.Operand); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we just need to evaluate the operand as usual.
|
||||
var uw *Unwind
|
||||
if opand, uw = e.evalExpression(node.Operand); uw != nil {
|
||||
return nil, uw
|
||||
}
|
||||
}
|
||||
|
||||
// Now switch on the operator and perform its specific operation.
|
||||
switch node.Operator {
|
||||
case ast.OpDereference:
|
||||
// The target is a pointer. If this is for an l-value, just return it as-is; otherwise, dereference it.
|
||||
ref := opand.Reference()
|
||||
contract.Assert(ref != nil)
|
||||
if lval {
|
||||
return opand, nil
|
||||
}
|
||||
return ref.Obj(), nil
|
||||
case ast.OpAddressof:
|
||||
// The target is an l-value, load its address.
|
||||
contract.Assert(opand.Reference() != nil)
|
||||
return opand, nil
|
||||
case ast.OpUnaryPlus:
|
||||
// The target is a number; simply fetch it (asserting its value), and + it.
|
||||
return NewNumberObject(+opand.Number()), nil
|
||||
case ast.OpUnaryMinus:
|
||||
// The target is a number; simply fetch it (asserting its value), and - it.
|
||||
return NewNumberObject(-opand.Number()), nil
|
||||
case ast.OpLogicalNot:
|
||||
// The target is a boolean; simply fetch it (asserting its value), and ! it.
|
||||
return NewBoolObject(!opand.Bool()), nil
|
||||
case ast.OpBitwiseNot:
|
||||
// The target is a number; simply fetch it (asserting its value), and ^ it (similar to C's ~ operator).
|
||||
return NewNumberObject(float64(^int64(opand.Number()))), nil
|
||||
case ast.OpPlusPlus:
|
||||
// The target is an l-value; we must load it, ++ it, and return the appropriate prefix/postfix value.
|
||||
ref := opand.Reference()
|
||||
old := ref.Obj()
|
||||
val := old.Number()
|
||||
new := NewNumberObject(val + 1)
|
||||
ref.Set(new)
|
||||
if node.Postfix {
|
||||
return old, nil
|
||||
} else {
|
||||
return new, nil
|
||||
}
|
||||
case ast.OpMinusMinus:
|
||||
// The target is an l-value; we must load it, -- it, and return the appropriate prefix/postfix value.
|
||||
ref := opand.Reference()
|
||||
old := ref.Obj()
|
||||
val := old.Number()
|
||||
new := NewNumberObject(val - 1)
|
||||
ref.Set(new)
|
||||
if node.Postfix {
|
||||
return old, nil
|
||||
} else {
|
||||
return new, nil
|
||||
}
|
||||
default:
|
||||
contract.Failf("Unrecognized unary operator: %v", node.Operator)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpression) (*Object, *Unwind) {
|
||||
|
|
|
@ -38,6 +38,21 @@ func NewPrimitiveObject(t symbols.Type, data interface{}) *Object {
|
|||
}
|
||||
}
|
||||
|
||||
// NewBoolObject creates a new primitive number object.
|
||||
func NewBoolObject(v bool) *Object {
|
||||
return NewPrimitiveObject(types.Bool, v)
|
||||
}
|
||||
|
||||
// NewNumberObject creates a new primitive number object.
|
||||
func NewNumberObject(v float64) *Object {
|
||||
return NewPrimitiveObject(types.Number, v)
|
||||
}
|
||||
|
||||
// NewStringObject creates a new primitive number object.
|
||||
func NewStringObject(v string) *Object {
|
||||
return NewPrimitiveObject(types.String, v)
|
||||
}
|
||||
|
||||
// NewFunctionObject creates a new function object that can be invoked, with the given symbol.
|
||||
func NewFunctionObject(fnc symbols.Function, this *Object) *Object {
|
||||
return &Object{
|
||||
|
@ -87,21 +102,38 @@ func NewConstantObject(v interface{}) *Object {
|
|||
// Bool asserts that the target is a boolean literal and returns its value.
|
||||
func (o *Object) Bool() bool {
|
||||
contract.Assertf(o.Type == types.Bool, "Expected object type to be Bool; got %v", o.Type)
|
||||
contract.Assertf(o.Data != nil, "Expected boolean literal to carry a Data payload; got nil")
|
||||
contract.Assertf(o.Data != nil, "Expected Bool object to carry a Data payload; got nil")
|
||||
b, ok := o.Data.(bool)
|
||||
contract.Assertf(ok, "Expected a boolean literal value for condition expr; conversion failed")
|
||||
contract.Assertf(ok, "Expected Bool object's Data payload to be boolean literal")
|
||||
return b
|
||||
}
|
||||
|
||||
// Number asserts that the target is a numeric literal and returns its value.
|
||||
func (o *Object) Number() float64 {
|
||||
contract.Assertf(o.Type == types.Number, "Expected object type to be Number; got %v", o.Type)
|
||||
contract.Assertf(o.Data != nil, "Expected numeric literal to carry a Data payload; got nil")
|
||||
contract.Assertf(o.Data != nil, "Expected Number object to carry a Data payload; got nil")
|
||||
n, ok := o.Data.(float64)
|
||||
contract.Assertf(ok, "Expected a numeric literal value for condition expr; conversion failed")
|
||||
contract.Assertf(ok, "Expected Number object's Data payload to be numeric literal")
|
||||
return n
|
||||
}
|
||||
|
||||
// String asserts that the target is a string and returns its value.
|
||||
func (o *Object) String() string {
|
||||
contract.Assertf(o.Type == types.String, "Expected object type to be String; got %v", o.Type)
|
||||
contract.Assertf(o.Data != nil, "Expected String object to carry a Data payload; got nil")
|
||||
s, ok := o.Data.(string)
|
||||
contract.Assertf(ok, "Expected String object's Data payload to be string")
|
||||
return s
|
||||
}
|
||||
|
||||
// Reference asserts that the target is a reference and returns its value.
|
||||
func (o *Object) Reference() *Reference {
|
||||
contract.Assertf(o.Data != nil, "Expected Reference object to carry a Data payload; got nil")
|
||||
r, ok := o.Data.(*Reference)
|
||||
contract.Assertf(ok, "Expected Reference object's Data payload to be a Reference")
|
||||
return r
|
||||
}
|
||||
|
||||
// GetPropertyReference returns the reference to an object's property, lazily initializing if 'init' is true, or
|
||||
// returning nil otherwise.
|
||||
func (o *Object) GetPropertyReference(nm tokens.Name, init bool) *Reference {
|
||||
|
@ -119,9 +151,8 @@ type Reference struct {
|
|||
readonly bool // true prevents writes to this slot (by abandoning).
|
||||
}
|
||||
|
||||
func (ref *Reference) Get() *Object {
|
||||
return ref.obj
|
||||
}
|
||||
func (ref *Reference) Readonly() bool { return ref.readonly }
|
||||
func (ref *Reference) Obj() *Object { return ref.obj }
|
||||
|
||||
func (ref *Reference) Set(obj *Object) {
|
||||
contract.Assertf(!ref.readonly, "Unexpected write to readonly reference")
|
||||
|
|
|
@ -47,7 +47,7 @@ func (s *localScope) Pop() {
|
|||
// GetValue returns the object value for the given symbol.
|
||||
func (s *localScope) GetValue(sym *symbols.LocalVariable) *Object {
|
||||
if ref := s.GetValueReference(sym, false); ref != nil {
|
||||
return ref.Get()
|
||||
return ref.Obj()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue