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:
joeduffy 2017-01-27 16:42:32 -08:00
parent 5774999957
commit d685850800
5 changed files with 155 additions and 13 deletions

View file

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

View file

@ -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")
)

View file

@ -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) {

View file

@ -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")

View file

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