///|
pub fn compound_assign_binary_op(op : @ast.CompoundOp) -> @ast.BinOp? {
match op {
AddAssign => Some(Add)
SubAssign => Some(Sub)
MulAssign => Some(Mul)
ExpAssign => Some(Exp)
DivAssign => Some(Div)
ModAssign => Some(Mod)
BitAndAssign => Some(BitAnd)
BitOrAssign => Some(BitOr)
BitXorAssign => Some(BitXor)
LShiftAssign => Some(LShift)
RShiftAssign => Some(RShift)
URShiftAssign => Some(URShift)
LogicalAndAssign | LogicalOrAssign | NullishAssign => None
}
}
///|
pub fn get_assignment_name_value(
ctx : ExecContext,
env : Environment,
name : String,
) -> Value raise Error {
@static_semantics.validate_strict_assignment_target_name(ctx.strict, name)
env.get(name)
}
///|
pub fn Interpreter::eval_update_property(
self : Interpreter,
ctx : ExecContext,
obj : Value,
prop : String,
op : @ast.UpdateOp,
prefix : Bool,
member_loc : @token.Loc,
loc : @token.Loc,
) -> Value raise Error {
let current = self.get_property(obj, prop, member_loc)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
let _ = self.set_property(obj, prop, new_val, loc, strict=ctx.strict)
if prefix {
new_val
} else {
Number(num)
}
}
///|
pub fn Interpreter::eval_update_computed_property(
self : Interpreter,
ctx : ExecContext,
obj : Value,
key : Value,
op : @ast.UpdateOp,
prefix : Bool,
member_loc : @token.Loc,
loc : @token.Loc,
) -> Value raise Error {
let prop_key = to_property_key(key, interp=Some(self))
let current = self.get_computed_property(obj, prop_key, member_loc)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
let _ = self.set_computed_property(
obj,
prop_key,
new_val,
loc,
strict=ctx.strict,
)
if prefix {
new_val
} else {
Number(num)
}
}
///|
fn unwrap_groupings(expr : @ast.Expr) -> @ast.Expr {
let mut current = expr
for ;; {
match current {
Grouping(inner, _) => current = inner
_ => return current
}
}
}
///|
fn Interpreter::eval_update(
self : Interpreter,
ctx : ExecContext,
op : @ast.UpdateOp,
operand : @ast.Expr,
prefix : Bool,
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
match operand {
Ident(name, _) => {
let current = get_assignment_name_value(ctx, env, name)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
if self.is_immutable_global(name) {
if ctx.strict {
raise @errors.TypeError(
message="Cannot assign to read only property '\{name}' of object '[object global]'",
)
}
} else {
env.assign_with_strict(name, new_val, ctx.strict)
}
if prefix {
new_val
} else {
Number(num)
}
}
Member(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
self.eval_update_property(ctx, obj, prop, op, prefix, mloc, loc)
}
ComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
self.eval_update_computed_property(ctx, obj, key, op, prefix, mloc, loc)
}
SuperMember(prop, mloc) => {
let this_val = eval_this_value(env)
let super_proto = self.require_super_target(env, "super.prop", mloc)
let current = self.get_property_from_prototype(
this_val, super_proto, prop, mloc,
)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
let _ = self.set_property(
super_proto,
prop,
new_val,
loc,
strict=ctx.strict,
receiver=this_val,
)
if prefix {
new_val
} else {
Number(num)
}
}
SuperComputedMember(key_expr, mloc) => {
let this_val = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
let super_proto = self.require_super_target(env, "super[expr]", mloc)
let prop_key = to_property_key(key, interp=Some(self))
let current = self.get_computed_property_from_prototype(
this_val, super_proto, prop_key, mloc,
)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
let _ = self.set_computed_property(
super_proto,
prop_key,
new_val,
loc,
strict=ctx.strict,
receiver=this_val,
)
if prefix {
new_val
} else {
Number(num)
}
}
PrivateMember(obj_expr, name, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let current = get_private_member(obj, name, env)
let num = self.to_number(current)
let new_val : Value = match op {
Increment => Number(num + 1.0)
Decrement => Number(num - 1.0)
}
let brand = resolve_private_brand(env, name)
set_private_field(obj, brand, name, new_val)
if prefix {
new_val
} else {
Number(num)
}
}
Call(_, _, _) => {
if ctx.strict {
raise @errors.SyntaxError(
message="Invalid update target at line \{loc.line}, col \{loc.col}",
)
}
let _ = self.eval_expr(ctx, operand, env)
raise @errors.ReferenceError(
message="Invalid left-hand side expression in prefix/postfix operation",
)
}
Grouping(_, _) => {
let inner = unwrap_groupings(operand)
match inner {
Call(_, _, _) => {
if ctx.strict {
raise @errors.SyntaxError(
message="Invalid update target at line \{loc.line}, col \{loc.col}",
)
}
let _ = self.eval_expr(ctx, inner, env)
raise @errors.ReferenceError(
message="Invalid left-hand side expression in prefix/postfix operation",
)
}
_ =>
raise @errors.SyntaxError(
message="Invalid update target at line \{loc.line}, col \{loc.col}",
)
}
}
_ =>
raise @errors.SyntaxError(
message="Invalid update target at line \{loc.line}, col \{loc.col}",
)
}
}
///|
fn Interpreter::eval_compound_assign(
self : Interpreter,
ctx : ExecContext,
op : @ast.CompoundOp,
target : @ast.Expr,
value_expr : @ast.Expr,
env : Environment,
loc : @token.Loc,
) -> Value raise Error {
// Handle logical assignment operators with short-circuit semantics
match op {
LogicalAndAssign =>
return self.eval_logical_assign(
ctx, target, value_expr, env, loc, is_truthy,
)
LogicalOrAssign =>
return self.eval_logical_assign(ctx, target, value_expr, env, loc, v => {
!is_truthy(v)
})
NullishAssign =>
return self.eval_logical_assign(ctx, target, value_expr, env, loc, v => {
v is (Null | Undefined)
})
_ => ()
}
let bin_op : @ast.BinOp = match compound_assign_binary_op(op) {
Some(bin_op) => bin_op
None =>
raise @errors.InternalError(
message="unexpected logical compound assignment mapping",
)
}
match target {
Ident(name, _) => {
let current = get_assignment_name_value(ctx, env, name)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
// Check if the target is a non-writable property on the global object
if self.is_immutable_global(name) {
if ctx.strict {
raise @errors.TypeError(
message="Cannot assign to read only property '\{name}' of object '[object global]'",
)
}
return result
}
env.assign_with_strict(name, result, ctx.strict)
result
}
Member(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let current = self.get_property(obj, prop, mloc)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
let _ = self.set_property(obj, prop, result, loc, strict=ctx.strict)
result
}
ComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
// Per ES5 11.2.1 step 5: CheckObjectCoercible(baseValue) before ToString
if obj is Null {
raise @errors.TypeError(message="Cannot read properties of null")
}
if obj is Undefined {
raise @errors.TypeError(message="Cannot read properties of undefined")
}
let prop_key = to_property_key(key, interp=Some(self))
let current = self.get_computed_property(obj, prop_key, mloc)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
let _ = self.set_computed_property(
obj,
prop_key,
result,
loc,
strict=ctx.strict,
)
result
}
SuperMember(prop, mloc) => {
let this_val = eval_this_value(env)
let super_proto = self.require_super_target(env, "super.prop", mloc)
let current = self.get_property_from_prototype(
this_val, super_proto, prop, mloc,
)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
let _ = self.set_property(
super_proto,
prop,
result,
loc,
strict=ctx.strict,
receiver=this_val,
)
result
}
SuperComputedMember(key_expr, mloc) => {
let this_val = eval_this_value(env)
let key = self.eval_expr(ctx, key_expr, env)
let super_proto = self.require_super_target(env, "super[expr]", mloc)
let prop_key = to_property_key(key, interp=Some(self))
let current = self.get_computed_property_from_prototype(
this_val, super_proto, prop_key, mloc,
)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
let _ = self.set_computed_property(
super_proto,
prop_key,
result,
loc,
strict=ctx.strict,
receiver=this_val,
)
result
}
PrivateMember(obj_expr, name, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let current = get_private_member(obj, name, env)
let rhs = self.eval_expr(ctx, value_expr, env)
let result = eval_binary_op(bin_op, current, rhs, loc, interp=Some(self))
let brand = resolve_private_brand(env, name)
set_private_field(obj, brand, name, result)
result
}
Call(_, _, _) => {
if ctx.strict {
raise @errors.SyntaxError(
message="Invalid compound assignment target at line \{loc.line}, col \{loc.col}",
)
}
let _ = self.eval_expr(ctx, target, env)
raise @errors.ReferenceError(
message="Invalid left-hand side in assignment",
)
}
Grouping(_, _) => {
let inner = unwrap_groupings(target)
match inner {
Call(_, _, _) => {
if ctx.strict {
raise @errors.SyntaxError(
message="Invalid compound assignment target at line \{loc.line}, col \{loc.col}",
)
}
let _ = self.eval_expr(ctx, inner, env)
raise @errors.ReferenceError(
message="Invalid left-hand side in assignment",
)
}
_ =>
raise @errors.SyntaxError(
message="Invalid compound assignment target at line \{loc.line}, col \{loc.col}",
)
}
}
_ =>
raise @errors.SyntaxError(
message="Invalid compound assignment target at line \{loc.line}, col \{loc.col}",
)
}
}
///|
/// Shared skeleton for `&&=`, `||=`, and `??=`.
/// `should_assign(current)` returns true when the operator requires a write.
fn Interpreter::eval_logical_assign(
self : Interpreter,
ctx : ExecContext,
target : @ast.Expr,
value_expr : @ast.Expr,
env : Environment,
loc : @token.Loc,
should_assign : (Value) -> Bool,
) -> Value raise Error {
match target {
Ident(name, _) => {
let current = get_assignment_name_value(ctx, env, name)
if should_assign(current) {
let rhs = self.eval_named_expr(ctx, value_expr, env, name)
if self.is_immutable_global(name) {
if ctx.strict {
raise @errors.TypeError(
message="Cannot assign to read only property '\{name}' of object '[object global]'",
)
}
return rhs
}
env.assign_with_strict(name, rhs, ctx.strict)
rhs
} else {
current
}
}
Member(obj_expr, prop, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let current = self.get_property(obj, prop, mloc)
if should_assign(current) {
let rhs = self.eval_expr(ctx, value_expr, env)
self.set_property(obj, prop, rhs, loc, strict=ctx.strict)
} else {
current
}
}
ComputedMember(obj_expr, key_expr, mloc) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let key = self.eval_expr(ctx, key_expr, env)
// Per ES5 11.2.1 step 5: CheckObjectCoercible(baseValue) before ToString
if obj is Null {
raise @errors.TypeError(message="Cannot read properties of null")
}
if obj is Undefined {
raise @errors.TypeError(message="Cannot read properties of undefined")
}
let prop_key = to_property_key(key, interp=Some(self))
let current = self.get_computed_property(obj, prop_key, mloc)
if should_assign(current) {
let rhs = self.eval_expr(ctx, value_expr, env)
self.set_computed_property(obj, prop_key, rhs, loc, strict=ctx.strict)
} else {
current
}
}
PrivateMember(obj_expr, name, _) => {
let obj = self.eval_expr(ctx, obj_expr, env)
let current = get_private_member(obj, name, env)
if should_assign(current) {
let rhs = self.eval_expr(ctx, value_expr, env)
let brand = resolve_private_brand(env, name)
set_private_field(obj, brand, name, rhs)
rhs
} else {
current
}
}
_ =>
raise @errors.SyntaxError(
message="Invalid assignment target at line \{loc.line}, col \{loc.col}",
)
}
}