use crate::internal::as2::hir::{
Affix, BinaryOperator, ConstantKind, Declaration, Expr, ExprKind, ForCondition, Function,
FunctionSignature, GetUrlMethod, StatementKind, SwitchElement, TryCatch, UnaryOperator,
};
use crate::internal::as2_codegen::access::VariableAccess;
use crate::internal::as2_codegen::builder::CodeBuilder;
use crate::internal::as2_codegen::context::ScriptContext;
use crate::internal::as2_pcode::{Action, CatchTarget, PCode, PushValue};
pub(crate) fn gen_statements(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
statements: &[StatementKind],
) {
let mut hoisted = vec![];
let mut regular = vec![];
for statement in statements {
if matches!(
statement,
StatementKind::Expr(Expr {
value: ExprKind::Function(Function {
signature: FunctionSignature { name: Some(_), .. },
..
}),
..
})
) {
hoisted.push(statement);
} else {
regular.push(statement);
}
}
for statement in hoisted {
gen_statement(context, builder, statement);
}
for statement in regular {
gen_statement(context, builder, statement);
}
}
pub(crate) fn gen_statement(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
statement: &StatementKind,
) {
match statement {
StatementKind::Declare(declarations) => {
gen_declarations(context, builder, declarations);
}
StatementKind::Return(exprs) => {
for expr in exprs {
gen_expr(context, builder, expr, false);
}
if exprs.is_empty() {
builder.push(PushValue::Undefined);
}
builder.action(Action::Return);
}
StatementKind::Throw(exprs) => {
for expr in exprs {
gen_expr(context, builder, expr, false);
}
if exprs.is_empty() {
builder.push(PushValue::Undefined);
}
builder.action(Action::Throw);
}
StatementKind::Expr(expr) => {
let stack_size = builder.stack_size();
gen_expr(context, builder, expr, true);
builder.truncate_stack(stack_size);
}
StatementKind::Block(statements) => {
let stack_size = builder.stack_size();
gen_statements(context, builder, statements);
builder.truncate_stack(stack_size);
}
StatementKind::ForIn { condition, body } => gen_for_loop(context, builder, condition, body),
StatementKind::If { condition, yes, no } => gen_if(context, builder, condition, yes, no),
StatementKind::Break => {
if let Some(label) = context.break_label() {
builder.action(Action::Jump(label));
}
}
StatementKind::Continue => {
if let Some(label) = context.continue_label() {
builder.action(Action::Jump(label));
}
}
StatementKind::Try(try_catch) => {
gen_try_catch(context, builder, try_catch);
}
StatementKind::WaitForFrame {
frame,
scene: _, if_loaded,
} => gen_wait_for_frame(context, builder, frame, if_loaded),
StatementKind::TellTarget { target, body } => {
gen_tell_target(context, builder, target, body)
}
StatementKind::While { condition, body } => {
gen_while_loop(context, builder, condition, body)
}
StatementKind::InlinePCode(pcode) => {
let source = PCode::new("inline pcode", pcode);
let actions = source
.to_actions()
.unwrap_or_else(|e| panic!("{}", e.to_string()));
builder.append(actions);
}
StatementKind::With { target, body } => gen_with(context, builder, target, body),
StatementKind::Switch { target, elements } => {
gen_switch(context, builder, target, elements)
}
}
}
fn gen_tell_target(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
target: &Expr,
body: &StatementKind,
) {
let was_in_tell_target = context.is_in_tell_target();
if was_in_tell_target {
builder.push(""); builder.push(11); builder.action(Action::GetProperty);
}
match &target.value {
ExprKind::Constant(ConstantKind::String(name)) => {
builder.action(Action::SetTarget(name.to_string()));
}
_ => {
gen_expr(context, builder, target, false);
builder.action(Action::SetTarget2);
}
}
context.set_is_in_tell_target(true);
gen_statement(context, builder, body);
context.set_is_in_tell_target(was_in_tell_target);
builder.action(Action::SetTarget("".to_string()));
if was_in_tell_target {
builder.action(Action::SetTarget2);
}
}
fn gen_switch(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
target: &Expr,
elements: &[SwitchElement],
) {
let break_label = context.create_label();
let mut conditionals = vec![];
let mut default_label = break_label.clone();
for element in elements {
match element {
SwitchElement::Case(value) => {
conditionals.push((context.create_label(), value));
}
SwitchElement::Default => {
default_label = context.create_label();
}
_ => {}
}
}
if conditionals.is_empty() && default_label == break_label {
return;
}
if !conditionals.is_empty() {
gen_expr(context, builder, target, false);
builder.action(Action::StoreRegister(0));
for (i, (label, value)) in conditionals.iter().enumerate() {
if i > 0 {
builder.push(PushValue::Register(0));
}
gen_expr(context, builder, value, false);
builder.action(Action::StrictEquals);
builder.action(Action::If(label.clone()));
}
}
builder.action(Action::Jump(default_label.clone()));
let mut i = 0;
let mut has_case = false;
let old_break_label = context.set_break_label(Some(break_label.clone()));
for element in elements {
match element {
SwitchElement::Case(_) => {
builder.mark_label(conditionals[i].0.clone());
i += 1;
has_case = true;
}
SwitchElement::Default => {
builder.mark_label(default_label.clone());
has_case = true;
}
SwitchElement::Statement(statement) => {
if has_case {
gen_statement(context, builder, statement);
}
}
}
}
context.set_break_label(old_break_label);
builder.mark_label(break_label);
}
fn gen_with(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
target: &Expr,
body: &StatementKind,
) {
let could_use_special_properties = context.can_use_special_properties();
let mut sub_builder = CodeBuilder::new();
context.set_can_use_special_properties(false);
gen_statement(context, &mut sub_builder, body);
context.set_can_use_special_properties(could_use_special_properties);
let actions = sub_builder.into_actions();
gen_expr(context, builder, target, false);
builder.action(Action::With(actions));
}
fn gen_wait_for_frame(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
frame: &Expr,
if_loaded: &StatementKind,
) {
let mut sub_builder = CodeBuilder::new();
gen_statement(context, &mut sub_builder, if_loaded);
let actions = sub_builder.into_actions();
let num_actions = actions.actions().len();
match frame.value {
ExprKind::Constant(ConstantKind::Integer(frame)) => {
let mut frame_number = frame;
if frame_number != 0 {
frame_number = frame_number.wrapping_sub(1);
}
builder.action(Action::WaitForFrame {
frame: (frame_number & 0xFFFF) as u16,
skip_count: num_actions as u8,
});
}
_ => {
gen_expr(context, builder, frame, false);
builder.action(Action::WaitForFrame2 {
skip_count: num_actions as u8,
});
}
}
builder.append(actions);
}
fn gen_while_loop(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
condition: &Expr,
body: &StatementKind,
) {
let start = context.create_label();
let end = context.create_label();
let old_break = context.set_break_label(Some(end.clone()));
let old_continue = context.set_continue_label(Some(start.clone()));
builder.mark_label(start.clone());
gen_expr(context, builder, condition, false);
builder.action(Action::Not);
builder.action(Action::If(end.clone()));
gen_statement(context, builder, body);
builder.action(Action::Jump(start));
builder.mark_label(end);
context.set_break_label(old_break);
context.set_continue_label(old_continue);
}
fn gen_for_loop(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
condition: &ForCondition,
body: &StatementKind,
) {
let start_stack_size = builder.stack_size();
let end_label = context.create_label();
let continue_label = context.create_label();
let old_break = context.set_break_label(Some(end_label.clone()));
let old_continue = context.set_continue_label(Some(continue_label.clone()));
match condition {
ForCondition::Enumerate {
variable,
declare,
object,
} => {
gen_expr(context, builder, object, false);
builder.action(Action::Enumerate2);
builder.mark_label(continue_label.clone());
builder.action(Action::StoreRegister(0));
builder.push(PushValue::Null);
builder.action(Action::Equals2);
builder.action(Action::If(end_label.clone()));
if *declare {
let value = context.constants.add(variable);
builder.push(value);
builder.push(PushValue::Register(0));
builder.action(Action::DefineLocal);
} else {
let access = VariableAccess::for_identifier(context, builder, variable);
builder.push(PushValue::Register(0));
access.set_value(builder);
}
gen_statement(context, builder, body);
builder.action(Action::Jump(continue_label));
}
ForCondition::Classic {
initialize,
condition,
update,
} => {
if let Some(initialize) = initialize {
gen_statement(context, builder, initialize);
}
let start_label = context.create_label();
builder.mark_label(start_label.clone());
if !condition.is_empty() {
let last_cond = condition.len() - 1;
for (i, expr) in condition.iter().enumerate() {
let stack_size = builder.stack_size();
gen_expr(context, builder, expr, i != last_cond);
if i != last_cond {
builder.truncate_stack(stack_size);
}
}
builder.action(Action::Not);
builder.action(Action::If(end_label.clone()));
}
gen_statement(context, builder, body);
builder.mark_label(continue_label.clone());
for expr in update {
let stack_size = builder.stack_size();
gen_expr(context, builder, expr, true);
builder.truncate_stack(stack_size);
}
builder.action(Action::Jump(start_label));
}
}
builder.truncate_stack(start_stack_size);
builder.mark_label(end_label);
context.set_break_label(old_break);
context.set_continue_label(old_continue);
}
fn gen_if(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
condition: &Expr,
yes: &StatementKind,
no: &Option<Box<StatementKind>>,
) {
let end_label = context.create_label();
gen_expr(context, builder, condition, false);
builder.action(Action::Not);
if let Some(no) = no {
let no_label = context.create_label();
builder.action(Action::If(no_label.clone()));
gen_statement(context, builder, yes);
builder.action(Action::Jump(end_label.clone()));
builder.mark_label(no_label);
gen_statement(context, builder, no);
} else {
builder.action(Action::If(end_label.clone()));
gen_statement(context, builder, yes);
}
builder.mark_label(end_label);
}
fn gen_ternary(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
condition: &Expr,
yes: &Expr,
no: &Expr,
) {
let end_label = context.create_label();
let no_label = context.create_label();
gen_expr(context, builder, condition, false);
builder.action(Action::Not);
builder.action(Action::If(no_label.clone()));
gen_expr(context, builder, yes, false);
builder.action(Action::Jump(end_label.clone()));
builder.mark_label(no_label);
gen_expr(context, builder, no, false);
builder.mark_label(end_label);
builder.assume_stack_delta(-1);
}
fn gen_declarations(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
declarations: &[Declaration],
) {
for declaration in declarations {
let value = context.constants.add(&declaration.name);
builder.push(value);
if let Some(value) = &declaration.value {
gen_expr(context, builder, value, false);
builder.action(Action::DefineLocal);
} else {
builder.action(Action::DefineLocal2);
}
}
}
pub fn gen_expr(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
expr: &Expr,
will_discard_result: bool,
) {
let (_span, kind) = (expr.span, &expr.value);
match kind {
ExprKind::Constant(constant) => {
VariableAccess::for_constant(context, builder, constant).get_value(builder)
}
ExprKind::Call { name, args } => gen_call(context, builder, name, args),
ExprKind::New { name, args } => gen_new(context, builder, name, args),
ExprKind::BinaryOperator(op, left, right) => {
gen_binary_op(context, builder, *op, left, right, will_discard_result)
}
ExprKind::UnaryOperator(op, exr) => {
gen_unary_op(context, builder, *op, exr, will_discard_result)
}
ExprKind::Ternary { condition, yes, no } => {
gen_ternary(context, builder, condition, yes, no)
}
ExprKind::InitObject(values) => gen_init_object(context, builder, values),
ExprKind::InitArray(values) => gen_init_array(context, builder, values),
ExprKind::Field(_, _) => {
let access = VariableAccess::for_expr(context, builder, expr);
access.get_value(builder);
}
ExprKind::TypeOf(expr) => {
gen_expr(context, builder, expr, false);
builder.action(Action::TypeOf);
}
ExprKind::Delete(expr) => {
VariableAccess::for_expr(context, builder, expr).delete(builder);
}
ExprKind::Void(_) => {}
ExprKind::Function(function) => gen_function(context, builder, function, true),
ExprKind::GetVariable(name) => {
gen_expr(context, builder, name, false);
builder.action(Action::GetVariable);
}
ExprKind::SetVariable(name, value) => {
gen_expr(context, builder, name, false);
gen_expr(context, builder, value, false);
builder.action(Action::SetVariable);
}
ExprKind::CallFrame(frame) => {
gen_expr(context, builder, frame, false);
builder.action(Action::Call);
}
ExprKind::AsciiToChar(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::AsciiToChar);
}
ExprKind::MBAsciiToChar(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::MBAsciiToChar);
}
ExprKind::DuplicateMovieClip {
source,
target,
depth,
} => {
gen_expr(context, builder, source, false);
gen_expr(context, builder, target, false);
builder.push(16384);
gen_expr(context, builder, depth, false);
builder.action(Action::Add2);
builder.action(Action::CloneSprite);
}
ExprKind::GetTime => {
builder.action(Action::GetTime);
}
ExprKind::GotoFrame(frame, play) => match &frame.value {
ExprKind::Constant(ConstantKind::String(label)) => {
builder.action(Action::GotoLabel(label.to_string()));
if *play {
builder.action(Action::Play);
}
}
ExprKind::Constant(ConstantKind::Integer(frame_number)) => {
let mut frame_number = *frame_number;
if frame_number != 0 {
frame_number = frame_number.wrapping_sub(1);
}
builder.action(Action::GotoFrame((frame_number & 0xFFFF) as u16));
if *play {
builder.action(Action::Play);
}
}
_ => {
gen_expr(context, builder, frame, false);
builder.action(Action::GotoFrame2 {
scene_bias: 0,
play: *play,
});
}
},
ExprKind::GetUrl {
target,
url,
load_variables,
load_target,
method,
} => {
gen_get_url(
context,
builder,
target,
url,
*load_variables,
*load_target,
*method,
);
}
ExprKind::CastToInteger(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::ToInteger);
}
ExprKind::CastToNumber(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::ToNumber);
}
ExprKind::CastToString(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::ToString);
}
ExprKind::StringLength(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::StringLength);
}
ExprKind::MBStringLength(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::MBStringLength);
}
ExprKind::CharToAscii(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::CharToAscii);
}
ExprKind::MBCharToAscii(value) => {
gen_expr(context, builder, value, false);
builder.action(Action::MBCharToAscii);
}
ExprKind::Substring {
string,
start,
length,
} => {
gen_expr(context, builder, string, false);
gen_expr(context, builder, start, false);
gen_expr(context, builder, length, false);
builder.action(Action::StringExtract);
}
ExprKind::MBSubstring {
string,
start,
length,
} => {
gen_expr(context, builder, string, false);
gen_expr(context, builder, start, false);
gen_expr(context, builder, length, false);
builder.action(Action::MBStringExtract);
}
ExprKind::NextFrame => builder.action(Action::NextFrame),
ExprKind::PreviousFrame => builder.action(Action::PrevFrame),
ExprKind::Play => builder.action(Action::Play),
ExprKind::Stop => builder.action(Action::Stop),
ExprKind::StartDrag {
target,
lock,
constraints,
} => {
let stack_delta = if let Some((a, b, c, d)) = constraints {
gen_expr(context, builder, a, false);
gen_expr(context, builder, b, false);
gen_expr(context, builder, c, false);
gen_expr(context, builder, d, false);
builder.push(1);
-7
} else {
builder.push(0);
-3
};
builder.push(if *lock { 1 } else { 0 });
gen_expr(context, builder, target, false);
builder.action_with_stack_delta(Action::StartDrag, stack_delta);
}
ExprKind::StopSounds => builder.action(Action::StopSounds),
ExprKind::EndDrag => builder.action(Action::EndDrag),
ExprKind::ToggleQuality => builder.action(Action::ToggleQuality),
ExprKind::GetTargetPath(expr) => {
gen_expr(context, builder, expr, false);
builder.action(Action::TargetPath);
}
ExprKind::Trace(expr) => {
gen_expr(context, builder, expr, false);
builder.action(Action::Trace);
}
ExprKind::RemoveSprite(expr) => {
gen_expr(context, builder, expr, false);
builder.action(Action::RemoveSprite);
}
ExprKind::GetRandomNumber(expr) => {
gen_expr(context, builder, expr, false);
builder.action(Action::RandomNumber);
}
ExprKind::GetProperty(target, property) => {
gen_expr(context, builder, target, false);
builder.push(*property);
builder.action(Action::GetProperty);
}
ExprKind::SetProperty(target, property, value) => {
gen_expr(context, builder, target, false);
builder.push(*property);
gen_expr(context, builder, value, false);
builder.action(Action::SetProperty);
}
}
}
fn gen_get_url(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
target: &Expr,
url: &Expr,
load_variables: bool,
load_target: bool,
method: GetUrlMethod,
) {
if !load_target
&& !load_variables
&& method == GetUrlMethod::None
&& let Expr {
value: ExprKind::Constant(ConstantKind::String(url)),
..
} = url
&& let Expr {
value: ExprKind::Constant(ConstantKind::String(target)),
..
} = target
{
builder.action(Action::GetUrl {
url: url.to_string(),
target: target.to_string(),
});
return;
}
gen_expr(context, builder, url, false);
gen_expr(context, builder, target, false);
builder.action(Action::GetUrl2 {
load_variables,
load_target,
method: match method {
GetUrlMethod::None => 0,
GetUrlMethod::Get => 1,
GetUrlMethod::Post => 2,
},
});
}
fn gen_try_catch(context: &mut ScriptContext, builder: &mut CodeBuilder, try_catch: &TryCatch) {
let mut end_label: Option<String> = None;
let mut try_builder = CodeBuilder::new();
gen_statements(context, &mut try_builder, &try_catch.try_body);
let try_body = try_builder.into_actions();
let catch_body = if try_catch.typed_catches.is_empty() && try_catch.catch_all.is_none() {
None
} else if let Some(catch_all) = &try_catch.catch_all
&& try_catch.typed_catches.is_empty()
{
let mut catch_builder = CodeBuilder::new();
gen_statements(context, &mut catch_builder, &catch_all.body);
let catch_body = catch_builder.into_actions();
Some((
CatchTarget::Variable(catch_all.name.value.to_owned()),
catch_body,
))
} else {
let mut catch_builder = CodeBuilder::new();
let catch_target = CatchTarget::Register(0);
end_label = Some(context.create_label());
let mut first = true;
for (type_name, catch) in &try_catch.typed_catches {
let next_label = context.create_label();
if !first {
catch_builder.action(Action::Pop);
}
first = false;
catch_builder.push(type_name.value.to_owned());
catch_builder.action(Action::GetVariable);
catch_builder.push(PushValue::Register(0));
catch_builder.action(Action::CastOp);
catch_builder.action(Action::PushDuplicate);
catch_builder.push(PushValue::Null);
catch_builder.action(Action::Equals2);
catch_builder.action(Action::If(next_label.clone()));
catch_builder.push(catch.name.value.to_owned());
catch_builder.action(Action::StackSwap);
catch_builder.action(Action::DefineLocal);
gen_statements(context, &mut catch_builder, &catch.body);
catch_builder.action(Action::Jump(end_label.clone().unwrap()));
catch_builder.mark_label(next_label);
}
if let Some(catch) = &try_catch.catch_all {
catch_builder.action(Action::Pop);
catch_builder.push(PushValue::Register(0));
catch_builder.push(catch.name.value.to_owned());
catch_builder.action(Action::StackSwap);
catch_builder.action(Action::DefineLocal);
gen_statements(context, &mut catch_builder, &catch.body);
} else {
catch_builder.action(Action::Pop);
catch_builder.push(PushValue::Register(0));
catch_builder.action(Action::Throw);
}
let catch_body = catch_builder.into_actions();
Some((catch_target, catch_body))
};
let finally_body = if try_catch.finally.is_empty() {
None
} else {
let mut finally_builder = CodeBuilder::new();
if let Some(end_label) = end_label.take() {
finally_builder.mark_label(end_label);
}
gen_statements(context, &mut finally_builder, &try_catch.finally);
let finally_body = finally_builder.into_actions();
Some(finally_body)
};
builder.action(Action::Try {
try_body,
catch_body,
finally_body,
});
if let Some(end_label) = end_label.take() {
builder.mark_label(end_label);
}
}
pub(crate) fn gen_function(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
function: &Function,
with_name: bool,
) {
let mut fun_builder = CodeBuilder::new();
let old_break = context.set_break_label(None);
let old_continue = context.set_continue_label(None);
gen_statements(context, &mut fun_builder, &function.body);
let actions = fun_builder.into_actions();
let name = if with_name {
function.signature.name.clone().map(|s| s.value)
} else {
None
};
builder.action(Action::DefineFunction {
name: name.unwrap_or_default(),
params: function
.signature
.args
.iter()
.map(|arg| arg.name.to_string())
.collect(),
actions,
});
context.set_break_label(old_break);
context.set_continue_label(old_continue);
}
fn gen_init_object(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
values: &[(String, Expr)],
) {
let num_fields = values.len() as i32;
for (key, value) in values.iter() {
let push_value = context.constants.add(key);
builder.push(push_value);
gen_expr(context, builder, value, false);
}
builder.push(num_fields);
builder.action_with_stack_delta(Action::InitObject, -num_fields * 2);
}
fn gen_init_array(context: &mut ScriptContext, builder: &mut CodeBuilder, values: &[Expr]) {
let num_values = values.len() as i32;
for value in values.iter().rev() {
gen_expr(context, builder, value, false);
}
builder.push(num_values);
builder.action_with_stack_delta(Action::InitArray, -num_values);
}
fn gen_unary_op(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
op: UnaryOperator,
expr: &Expr,
will_discard_result: bool,
) {
let adjust_in_place =
|context: &mut ScriptContext, builder: &mut CodeBuilder, action, affix| {
if !will_discard_result && affix == Affix::Postfix {
VariableAccess::for_expr(context, builder, expr).get_value(builder);
}
let _ = VariableAccess::for_expr(context, builder, expr);
let access = VariableAccess::for_expr(context, builder, expr);
access.get_value(builder);
builder.action(action);
if !will_discard_result && affix == Affix::Prefix {
access.get_and_set_value(builder);
} else {
access.set_value(builder);
}
};
match op {
UnaryOperator::Sub => match expr {
Expr {
value: ExprKind::Constant(ConstantKind::Integer(value)),
..
} => {
VariableAccess::for_constant(context, builder, &ConstantKind::Integer(-*value));
}
Expr {
value: ExprKind::Constant(ConstantKind::Float(value)),
..
} => {
VariableAccess::for_constant(context, builder, &ConstantKind::Float(-*value));
}
_ => {
VariableAccess::for_constant(context, builder, &ConstantKind::Integer(0));
gen_expr(context, builder, expr, false);
builder.action(Action::Subtract);
}
},
UnaryOperator::BitNot => {
gen_expr(context, builder, expr, false);
builder.push(-1);
builder.action(Action::BitXor);
}
UnaryOperator::Increment(affix) => {
adjust_in_place(context, builder, Action::Increment, affix)
}
UnaryOperator::Decrement(affix) => {
adjust_in_place(context, builder, Action::Decrement, affix)
}
UnaryOperator::LogicalNot => {
gen_expr(context, builder, expr, false);
builder.action(Action::Not);
}
}
}
fn gen_binary_op(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
op: BinaryOperator,
left: &Expr,
right: &Expr,
will_discard_result: bool, ) {
let trivial = |context: &mut ScriptContext, builder: &mut CodeBuilder, action: Action| {
gen_expr(context, builder, left, false);
gen_expr(context, builder, right, false);
builder.action(action);
};
let trivial_assign =
|context: &mut ScriptContext, builder: &mut CodeBuilder, action: Action| {
let access = VariableAccess::for_expr(context, builder, left);
trivial(context, builder, action);
if will_discard_result {
access.set_value(builder);
} else {
access.get_and_set_value(builder);
}
};
match op {
BinaryOperator::Add => trivial(context, builder, Action::Add2),
BinaryOperator::Assign => {
let access = VariableAccess::for_expr(context, builder, left);
gen_expr(context, builder, right, false);
if will_discard_result {
access.set_value(builder);
} else {
access.get_and_set_value(builder);
}
}
BinaryOperator::AddAssign => trivial_assign(context, builder, Action::Add2),
BinaryOperator::Sub => trivial(context, builder, Action::Subtract),
BinaryOperator::SubAssign => trivial_assign(context, builder, Action::Subtract),
BinaryOperator::Divide => trivial(context, builder, Action::Divide),
BinaryOperator::DivideAssign => trivial_assign(context, builder, Action::Divide),
BinaryOperator::Multiply => trivial(context, builder, Action::Multiply),
BinaryOperator::MultiplyAssign => trivial_assign(context, builder, Action::Multiply),
BinaryOperator::Modulo => trivial(context, builder, Action::Modulo),
BinaryOperator::ModuloAssign => trivial_assign(context, builder, Action::Modulo),
BinaryOperator::BitAnd => trivial(context, builder, Action::BitAnd),
BinaryOperator::BitAndAssign => trivial_assign(context, builder, Action::BitAnd),
BinaryOperator::BitOr => trivial(context, builder, Action::BitOr),
BinaryOperator::BitOrAssign => trivial_assign(context, builder, Action::BitOr),
BinaryOperator::BitXor => trivial(context, builder, Action::BitXor),
BinaryOperator::BitXorAssign => trivial_assign(context, builder, Action::BitXor),
BinaryOperator::BitShiftLeft => trivial(context, builder, Action::BitLShift),
BinaryOperator::BitShiftLeftAssign => trivial_assign(context, builder, Action::BitLShift),
BinaryOperator::BitShiftRight => trivial(context, builder, Action::BitRShift),
BinaryOperator::BitShiftRightAssign => trivial_assign(context, builder, Action::BitRShift),
BinaryOperator::BitShiftRightUnsigned => trivial(context, builder, Action::BitURShift),
BinaryOperator::BitShiftRightUnsignedAssign => {
trivial_assign(context, builder, Action::BitURShift)
}
BinaryOperator::Equal => trivial(context, builder, Action::Equals2),
BinaryOperator::StrictEqual => trivial(context, builder, Action::StrictEquals),
BinaryOperator::NotEqual => {
trivial(context, builder, Action::Equals2);
builder.action(Action::Not)
}
BinaryOperator::StrictNotEqual => {
trivial(context, builder, Action::StrictEquals);
builder.action(Action::Not)
}
BinaryOperator::LessThan => trivial(context, builder, Action::Less2),
BinaryOperator::LessThanEqual => {
trivial(context, builder, Action::Greater);
builder.action(Action::Not)
}
BinaryOperator::GreaterThan => trivial(context, builder, Action::Greater),
BinaryOperator::GreaterThanEqual => {
trivial(context, builder, Action::Less2);
builder.action(Action::Not)
}
BinaryOperator::LogicalAnd => {
let end_label = context.create_label();
gen_expr(context, builder, left, false);
builder.action(Action::PushDuplicate);
builder.action(Action::Not);
builder.action(Action::If(end_label.clone()));
builder.action(Action::Pop);
gen_expr(context, builder, right, false);
builder.mark_label(end_label);
}
BinaryOperator::LogicalOr => {
let end_label = context.create_label();
gen_expr(context, builder, left, false);
builder.action(Action::PushDuplicate);
builder.action(Action::If(end_label.clone()));
builder.action(Action::Pop);
gen_expr(context, builder, right, false);
builder.mark_label(end_label);
}
BinaryOperator::InstanceOf => trivial(context, builder, Action::InstanceOf),
BinaryOperator::StringEqual => trivial(context, builder, Action::StringEquals),
BinaryOperator::StringGreaterThan => trivial(context, builder, Action::StringGreater),
BinaryOperator::StringGreaterThanEqual => {
trivial(context, builder, Action::StringLess);
builder.action(Action::Not);
}
BinaryOperator::StringLessThan => trivial(context, builder, Action::StringLess),
BinaryOperator::StringLessThanEqual => {
trivial(context, builder, Action::StringGreater);
builder.action(Action::Not);
}
BinaryOperator::StringNotEqual => {
trivial(context, builder, Action::StringEquals);
builder.action(Action::Not);
}
BinaryOperator::BooleanAnd => trivial(context, builder, Action::And),
BinaryOperator::BooleanOr => trivial(context, builder, Action::Or),
BinaryOperator::StringAdd => trivial(context, builder, Action::StringAdd),
}
}
fn gen_call(context: &mut ScriptContext, builder: &mut CodeBuilder, name: &Expr, args: &[Expr]) {
for arg in args.iter().rev() {
gen_expr(context, builder, arg, false);
}
let num_args = args.len() as i32;
builder.push(num_args);
VariableAccess::for_expr(context, builder, name).call(builder, num_args);
}
fn gen_new(context: &mut ScriptContext, builder: &mut CodeBuilder, name: &Expr, args: &[Expr]) {
for arg in args.iter().rev() {
gen_expr(context, builder, arg, false);
}
let num_args = args.len() as i32;
builder.push(num_args);
let access = VariableAccess::for_expr(context, builder, name);
access.call_new(builder, num_args);
}