use crate::CompileOptions;
use crate::internal::as2::hir::{Class, Interface, StatementKind};
use crate::internal::as2_codegen::builder::CodeBuilder;
use crate::internal::as2_codegen::context::ScriptContext;
use crate::internal::as2_codegen::statement::{gen_expr, gen_function, gen_statements};
use crate::internal::as2_pcode::{Action, Actions, PushValue};
mod access;
mod builder;
mod constants;
mod context;
mod special_properties;
mod statement;
pub fn script_to_actions(statements: &[StatementKind]) -> Actions {
generate_actions(|context, builder| {
gen_statements(context, builder, statements);
})
}
pub fn interface_to_actions(interface: &Interface) -> Actions {
generate_actions(|context, builder| {
let segments: Vec<&str> = interface.name.split(".").collect();
for i in 1..=segments.len() {
create_if_not_exists(context, builder, &segments[0..i], |_context, builder| {
if i < segments.len() {
builder.push(0);
builder.push("Object");
builder.action(Action::NewObject);
builder.action(Action::SetMember);
} else {
builder.action(Action::DefineFunction {
name: "".to_string(),
params: vec![],
actions: Actions::empty(),
});
builder.action(Action::SetMember);
if let Some(extends) = &interface.extends {
get_type_path(builder, extends, true, true);
builder.push(1);
get_type_path(builder, &interface.name, true, true);
builder.action_with_stack_delta(Action::ImplementsOp, -3);
}
}
})
}
})
}
fn create_if_not_exists<F>(
context: &mut ScriptContext,
builder: &mut CodeBuilder,
path: &[&str],
generator: F,
) where
F: FnOnce(&mut ScriptContext, &mut CodeBuilder),
{
let end = context.create_label();
builder.push("_global");
builder.action(Action::GetVariable);
for segment in path {
builder.push(*segment);
builder.action(Action::GetMember);
}
builder.action(Action::Not);
builder.action(Action::Not);
builder.action(Action::If(end.clone()));
builder.push("_global");
builder.action(Action::GetVariable);
if path.len() > 1 {
for segment in &path[0..path.len() - 1] {
builder.push(*segment);
builder.action(Action::GetMember);
}
}
builder.push(path[path.len() - 1]);
generator(context, builder);
builder.mark_label(end);
builder.action(Action::Pop);
}
pub fn class_to_actions(compile_options: &CompileOptions, class: &Class) -> Actions {
generate_actions(|context, builder| {
let segments: Vec<&str> = class.name.split(".").collect();
for i in 1..=segments.len() {
create_if_not_exists(context, builder, &segments[0..i], |context, builder| {
if i < segments.len() {
builder.push(0);
builder.push("Object");
builder.action(Action::NewObject);
builder.action(Action::SetMember);
} else {
gen_function(context, builder, &class.constructor, false);
builder.action(Action::StoreRegister(1));
builder.action(Action::SetMember);
let needs_prototype_setup = if let Some(extends) = &class.extends {
if compile_options.swf_version >= 7 {
get_type_path(builder, &class.name, false, true);
get_type_path(builder, extends, false, true);
builder.action(Action::Extends);
true
} else {
get_type_path(builder, &class.name, true, true);
builder.push("prototype");
builder.push(0);
get_type_path(builder, extends, false, false);
builder.action(Action::NewObject);
builder.action(Action::StoreRegister(2));
builder.action(Action::SetMember);
false
}
} else {
true
};
if needs_prototype_setup {
builder.push(PushValue::Register(1));
builder.push("prototype");
builder.action(Action::GetMember);
builder.action(Action::StoreRegister(2));
builder.action(Action::Pop);
}
if !class.implements.is_empty() {
for name in &class.implements {
get_type_path(builder, name, true, true);
}
builder.push(class.implements.len() as i32);
get_type_path(builder, &class.name, true, true);
builder.action_with_stack_delta(
Action::ImplementsOp,
-2 - class.implements.len() as i32,
);
}
for (name, method) in &class.functions {
if method.is_static {
builder.push(PushValue::Register(1));
} else {
builder.push(PushValue::Register(2));
}
builder.push(name.as_str());
gen_function(context, builder, &method.function, false);
builder.action(Action::SetMember);
}
for (name, field) in &class.fields {
if let Some(value) = &field.value {
if field.is_static {
builder.push(PushValue::Register(1));
} else {
builder.push(PushValue::Register(2));
}
builder.push(name.as_str());
gen_expr(context, builder, value, false);
builder.action(Action::SetMember);
}
}
for (name, virtual_property) in &class.virtual_properties {
let owner = if virtual_property.is_static {
PushValue::Register(1)
} else {
PushValue::Register(2)
};
if let Some(setter) = &virtual_property.setter {
builder.push(owner.clone());
builder.push(setter.as_str());
builder.action(Action::GetMember);
} else {
builder.action(Action::DefineFunction {
name: "".to_string(),
params: vec![],
actions: Actions::empty(),
})
}
if let Some(getter) = &virtual_property.getter {
builder.push(owner.clone());
builder.push(getter.as_str());
builder.action(Action::GetMember);
} else {
builder.action(Action::DefineFunction {
name: "".to_string(),
params: vec![],
actions: Actions::empty(),
})
}
builder.push(name.as_str());
builder.push(3);
builder.push(owner);
builder.push("addProperty");
builder.action_with_stack_delta(Action::CallMethod, -5);
}
builder.push(1);
builder.push(PushValue::Null);
get_type_path(builder, &class.name, false, true);
builder.push("prototype");
builder.action(Action::GetMember);
builder.push(3);
builder.push("ASSetPropFlags");
builder.action_with_stack_delta(Action::CallFunction, -5);
}
})
}
})
}
fn get_type_path(
builder: &mut CodeBuilder,
type_name: &str,
from_global: bool,
and_get_value: bool,
) {
let segments: Vec<&str> = type_name.split(".").collect();
let mut first = true;
if from_global {
builder.push("_global");
builder.action(Action::GetVariable);
first = false;
}
for segment in segments {
builder.push(segment);
if first {
if and_get_value {
builder.action(Action::GetVariable);
}
first = false;
} else if and_get_value {
builder.action(Action::GetMember);
}
}
}
fn generate_actions<F>(f: F) -> Actions
where
F: FnOnce(&mut ScriptContext, &mut CodeBuilder),
{
let mut context = ScriptContext::new();
let mut builder = CodeBuilder::new();
builder.action(Action::ConstantPool(vec![])); f(&mut context, &mut builder);
let mut actions = builder.into_actions();
actions.replace_action(
0,
Action::ConstantPool(context.constants.into_iter().collect()),
);
actions
}