mod data_types;
mod reader;
use std::fmt::Display;
use std::str::FromStr;
use crate::gml::assembly::assembler::data_types::DataTypes;
use crate::gml::assembly::assembler::reader::Reader;
use crate::gml::instruction::AssetReference;
use crate::gml::instruction::CodeVariable;
use crate::gml::instruction::ComparisonType;
use crate::gml::instruction::DataType;
use crate::gml::instruction::InstanceType;
use crate::gml::instruction::Instruction;
use crate::gml::instruction::PushValue;
use crate::gml::instruction::VariableType;
use crate::prelude::*;
use crate::util::fmt::typename;
use crate::wad::data::GMData;
use crate::wad::elements::GMNamedListChunk;
use crate::wad::elements::function::GMFunction;
use crate::wad::elements::function::GMFunctions;
use crate::wad::elements::game_object::GMGameObject;
use crate::wad::elements::variable::GMVariable;
use crate::wad::reference::GMRef;
pub fn assemble_instructions(assembly: &str, gm_data: &GMData) -> Result<Vec<Instruction>> {
let heuristic = assembly.lines().count();
let mut instructions: Vec<Instruction> = Vec::with_capacity(heuristic);
for line in assembly.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let instruction: Instruction = assemble_instruction(line, gm_data)
.with_context(|| format!("assembling instruction: {line}"))?;
instructions.push(instruction);
}
Ok(instructions)
}
pub fn assemble_instruction(line: &str, gm_data: &GMData) -> Result<Instruction> {
let mut reader = Reader::new(line.trim());
let mnemonic: &str;
let opcode_end: Option<usize> = reader.line.find(['.', ' ']);
if let Some(index) = opcode_end {
mnemonic = reader.consume_to(index);
} else {
mnemonic = reader.clear();
}
let mut types = DataTypes::new();
while reader.starts_with(".") {
reader.consume_dot()?;
let raw_type: char = reader
.consume_char()
.ok_or("Unexpected EOL when trying to parse instruction data type")?;
let data_type = DataType::from_char(raw_type)?;
types.push(data_type)?;
}
match reader.peek_char() {
Some(' ') => {
reader.consume_space()?;
}
None => {}
_ => bail!("Expected space; found remaining string {line:?}"),
}
let instruction = parse_instruction(&mut reader, mnemonic, types, gm_data)?;
if !reader.is_empty() {
bail!(
"Expected end of line; found remaining string {:?}",
reader.line
)
}
Ok(instruction)
}
#[allow(clippy::too_many_lines)] fn parse_instruction(
reader: &mut Reader,
mnemonic: &str,
types: DataTypes,
gm_data: &GMData,
) -> Result<Instruction> {
let instruction = match mnemonic {
"conv" => {
types.assert_count(2, mnemonic)?;
Instruction::Convert { from: types[0], to: types[1] }
}
"mul" => {
types.assert_count(2, mnemonic)?;
Instruction::Multiply {
multiplicand: types[1],
multiplier: types[0],
}
}
"div" => {
types.assert_count(2, mnemonic)?;
Instruction::Divide { dividend: types[1], divisor: types[0] }
}
"rem" => {
types.assert_count(2, mnemonic)?;
Instruction::Remainder { dividend: types[1], divisor: types[0] }
}
"mod" => {
types.assert_count(2, mnemonic)?;
Instruction::Modulus { dividend: types[1], divisor: types[0] }
}
"add" => {
types.assert_count(2, mnemonic)?;
Instruction::Add { augend: types[1], addend: types[0] }
}
"sub" => {
types.assert_count(2, mnemonic)?;
Instruction::Subtract { minuend: types[1], subtrahend: types[0] }
}
"and" => {
types.assert_count(2, mnemonic)?;
Instruction::And { lhs: types[1], rhs: types[0] }
}
"or" => {
types.assert_count(2, mnemonic)?;
Instruction::Or { lhs: types[1], rhs: types[0] }
}
"xor" => {
types.assert_count(2, mnemonic)?;
Instruction::Xor { lhs: types[1], rhs: types[0] }
}
"neg" => {
types.assert_count(1, mnemonic)?;
Instruction::Negate { data_type: types[0] }
}
"not" => {
types.assert_count(1, mnemonic)?;
Instruction::Not { data_type: types[0] }
}
"shl" => {
types.assert_count(2, mnemonic)?;
Instruction::ShiftLeft { value: types[1], shift_amount: types[0] }
}
"shr" => {
types.assert_count(2, mnemonic)?;
Instruction::ShiftRight { value: types[1], shift_amount: types[0] }
}
"cmp" => parse_comparison(types, reader)?,
"pop" => {
types.assert_count(2, mnemonic)?;
let variable: CodeVariable = parse_variable(reader, gm_data)?;
Instruction::Pop {
type1: types[0],
type2: types[1],
variable,
}
}
"popswap" => {
types.assert_count(0, mnemonic)?;
Instruction::PopSwap { is_array: false }
}
"popswaparr" => {
types.assert_count(0, mnemonic)?;
Instruction::PopSwap { is_array: true }
}
"dup" => parse_duplicate(types, reader)?,
"dupswap" => parse_duplicate_swap(types, reader)?,
"ret" => {
types.assert_count(0, mnemonic)?;
Instruction::Return
}
"exit" => {
types.assert_count(0, mnemonic)?;
Instruction::Exit
}
"popz" => {
types.assert_count(1, mnemonic)?;
Instruction::PopDiscard { data_type: types[0] }
}
"jmp" => {
types.assert_count(0, mnemonic)?;
let jump_offset: i32 = reader.parse_int()?;
Instruction::Branch { jump_offset }
}
"jt" => {
types.assert_count(0, mnemonic)?;
let jump_offset: i32 = reader.parse_int()?;
Instruction::BranchIf { jump_offset }
}
"jf" => {
types.assert_count(0, mnemonic)?;
let jump_offset: i32 = reader.parse_int()?;
Instruction::BranchUnless { jump_offset }
}
"pushenv" => {
types.assert_count(0, mnemonic)?;
let jump_offset: i32 = reader.parse_int()?;
Instruction::PushWithContext { jump_offset }
}
"popenv" => {
types.assert_count(0, mnemonic)?;
let jump_offset: i32 = reader.parse_int()?;
Instruction::PopWithContext { jump_offset }
}
"popenvexit" => {
types.assert_count(0, mnemonic)?;
Instruction::PopWithContextExit
}
"push" => Instruction::Push {
value: parse_push(types, reader, gm_data)?,
},
"pushloc" => {
types.assert_count(0, mnemonic)?;
let variable = parse_variable(reader, gm_data)?;
Instruction::PushLocal { variable }
}
"pushglb" => {
types.assert_count(0, mnemonic)?;
let variable = parse_variable(reader, gm_data)?;
Instruction::PushGlobal { variable }
}
"pushbltn" => {
types.assert_count(0, mnemonic)?;
let variable = parse_variable(reader, gm_data)?;
Instruction::PushBuiltin { variable }
}
"pushim" => {
types.assert_count(0, mnemonic)?;
let integer: i16 = reader.parse_int()?;
Instruction::PushImmediate { integer }
}
"call" => parse_call(types, reader, gm_data)?,
"callvar" => {
types.assert_count(0, mnemonic)?;
let argument_count: u16 = reader.parse_uint()?;
Instruction::CallVariable { argument_count }
}
"chkindex" => Instruction::CheckArrayIndex,
"pushaf" => Instruction::PushArrayFinal,
"popaf" => Instruction::PopArrayFinal,
"pushac" => Instruction::PushArrayContainer,
"setowner" => Instruction::SetArrayOwner,
"isstaticok" => Instruction::HasStaticInitialized,
"setstatic" => Instruction::SetStaticInitialized,
"savearef" => Instruction::SaveArrayReference,
"restorearef" => Instruction::RestoreArrayReference,
"isnullish" => Instruction::IsNullishValue,
"pushref" => {
types.assert_count(0, mnemonic)?;
let asset_reference = parse_asset_reference(reader, gm_data)?;
Instruction::PushReference { asset_reference }
}
_ => bail!("Invalid opcode mnemonic {mnemonic:?}"),
};
Ok(instruction)
}
fn parse_asset_reference(reader: &mut Reader, gm_data: &GMData) -> Result<AssetReference> {
let line = reader.line;
let asset_type = reader
.consume_round_brackets()?
.ok_or_else(|| format!("Expected asset type within round brackets; found {line:?}"))?;
#[rustfmt::skip]
let asset_reference = match asset_type {
"object" => AssetReference::Object(gm_data.game_objects.ref_by_name(reader.parse_identifier()?)?),
"sprite" => AssetReference::Sprite(gm_data.sprites.ref_by_name(reader.parse_identifier()?)?),
"sound" => AssetReference::Sound(gm_data.sounds.ref_by_name(reader.parse_identifier()?)?),
"room" => AssetReference::Room(gm_data.rooms.ref_by_name(reader.parse_identifier()?)?),
"background" => AssetReference::Background(gm_data.backgrounds.ref_by_name(reader.parse_identifier()?)?),
"path" => AssetReference::Path(gm_data.paths.ref_by_name(reader.parse_identifier()?)?),
"script" => AssetReference::Script(gm_data.scripts.ref_by_name(reader.parse_identifier()?)?),
"font" => AssetReference::Font(gm_data.fonts.ref_by_name(reader.parse_identifier()?)?),
"timeline" => AssetReference::Timeline(gm_data.timelines.ref_by_name(reader.parse_identifier()?)?),
"shader" => AssetReference::Shader(gm_data.shaders.ref_by_name(reader.parse_identifier()?)?),
"sequence" => AssetReference::Sequence(gm_data.sequences.ref_by_name(reader.parse_identifier()?)?),
"animcurve" => AssetReference::AnimCurve(gm_data.animation_curves.ref_by_name(reader.parse_identifier()?)?),
"particlesystem" => AssetReference::ParticleSystem(gm_data.particle_systems.ref_by_name(reader.parse_identifier()?)?),
"roominstance" => AssetReference::RoomInstance(reader.parse_int()?),
"function" => AssetReference::Function(parse_function(reader, &gm_data.functions)?),
_ => bail!("Invalid Type Cast to asset type {asset_type:?}"),
};
Ok(asset_reference)
}
fn parse_comparison(types: DataTypes, reader: &mut Reader) -> Result<Instruction> {
types.assert_count(2, "cmp")?;
let comparison_type: &str = reader.parse_identifier()?;
let comparison_type = match comparison_type {
"EQ" => ComparisonType::Equal,
"NEQ" => ComparisonType::NotEqual,
"LT" => ComparisonType::LessThan,
"LTE" => ComparisonType::LessOrEqual,
"GTE" => ComparisonType::GreaterOrEqual,
"GT" => ComparisonType::GreaterThan,
_ => bail!("Invalid Comparison Type {comparison_type:?}"),
};
Ok(Instruction::Compare {
lhs: types[1],
rhs: types[0],
comparison_type,
})
}
fn parse_duplicate(types: DataTypes, reader: &mut Reader) -> Result<Instruction> {
types.assert_count(1, "dup")?;
let size: u8 = reader.parse_uint()?;
Ok(Instruction::Duplicate { data_type: types[0], size })
}
fn parse_duplicate_swap(types: DataTypes, reader: &mut Reader) -> Result<Instruction> {
types.assert_count(1, "dupswap")?;
let size1: u8 = reader.parse_uint()?;
reader.consume_space()?;
let size2: u8 = reader.parse_uint()?;
Ok(Instruction::DuplicateSwap { data_type: types[0], size1, size2 })
}
fn parse_push(types: DataTypes, reader: &mut Reader, gm_data: &GMData) -> Result<PushValue> {
types.assert_count(1, "push")?;
let value: PushValue = match types[0] {
DataType::Int16 => PushValue::Int16(parse_int(reader.clear())?),
DataType::Int32 => {
if let Some(type_cast) = reader.consume_round_brackets()? {
match type_cast {
"function" => PushValue::Function(parse_function(reader, &gm_data.functions)?),
"variable" => {
let mut variable: CodeVariable = parse_variable(reader, gm_data)?;
variable.is_int32 = true;
PushValue::Variable(variable)
}
_ => bail!(
"Invalid type cast {type_cast:?}; expected \"function\" or \"variable\""
),
}
} else {
PushValue::Int32(parse_int(reader.clear())?)
}
}
DataType::Int64 => PushValue::Int64(parse_int(reader.clear())?),
DataType::Double => {
let line: &str = reader.clear();
let float: f64 = line
.parse()
.ok()
.ok_or_else(|| format!("Invalid float literal {line:?}"))?;
PushValue::Double(float)
}
DataType::Boolean => {
let line: &str = reader.clear();
let bool: bool = match line {
"true" => true,
"false" => false,
_ => bail!("Invalid boolean {line:?}"),
};
PushValue::Boolean(bool)
}
DataType::String => {
let string: String = parse_string_literal(reader)?;
PushValue::String(string)
}
DataType::Variable => PushValue::Variable(parse_variable(reader, gm_data)?),
};
Ok(value)
}
fn parse_call(types: DataTypes, reader: &mut Reader, gm_data: &GMData) -> Result<Instruction> {
types.assert_count(0, "call")?;
let function: GMRef<GMFunction> = parse_function(reader, &gm_data.functions)?;
let line = reader.line;
let argc_str: &str = reader.consume_round_brackets()?.ok_or_else(|| {
format!("Expected round brackets with argument count for function call; found {line:?}")
})?;
let argument_count: u16 = if let Some(str) = argc_str.strip_prefix("argc=") {
str.parse()
.map_err(|e| format!("Invalid argument count {str}: {e}"))?
} else {
bail!(
"Expected \"argc=\" for function call parameters; found {:?}",
reader.line
);
};
Ok(Instruction::Call { function, argument_count })
}
impl VariableType {
fn from_string(variable_type: &str) -> Result<Self> {
Ok(match variable_type {
"stacktop" => Self::StackTop,
"array" => Self::Array,
"roominstance" => Self::Instance,
"arraypushaf" => Self::MultiPush,
"arraypopaf" => Self::MultiPop,
_ => bail!("Invalid Variable Reference Type {variable_type:?}"),
})
}
}
fn parse_variable(reader: &mut Reader, gm_data: &GMData) -> Result<CodeVariable> {
let mut variable_type = if let Some(ty) = reader.consume_square_brackets()? {
VariableType::from_string(ty)?
} else {
VariableType::Normal
};
let instance_type_raw = reader.parse_identifier()?;
let instance_type_arg = reader.consume_angle_brackets()?.unwrap_or_default();
reader.consume_dot()?;
let mut variable_ref: Option<GMRef<GMVariable>> = None;
let instance_type: InstanceType = match instance_type_raw {
"self" => InstanceType::Self_,
"object" => {
let object_ref: GMRef<GMGameObject> =
gm_data.game_objects.ref_by_name(instance_type_arg)?;
InstanceType::GameObject(object_ref)
}
"roominstance" => {
variable_type = VariableType::Instance;
let instance_id: i16 = parse_int(instance_type_arg)?;
InstanceType::RoomInstance(instance_id)
}
"local" => {
let var_index: u32 = parse_int(instance_type_arg)?;
variable_ref = Some(GMRef::new(var_index));
InstanceType::Local
}
"stacktop" => InstanceType::StackTop,
"builtin" => InstanceType::Builtin,
"global" => InstanceType::Global,
"arg" => InstanceType::Argument,
"other" => InstanceType::Other,
"static" => InstanceType::Static,
"all" => InstanceType::All,
"none" => InstanceType::None,
_ => bail!("Invalid Instance Type {instance_type_raw:?}"),
};
let name: &str = parse_variable_identifier(reader)?;
if instance_type != InstanceType::Local {
let vari_instance_type: InstanceType = instance_type.as_vari();
for (i, var) in gm_data.variables.iter().enumerate() {
if var.name != name {
continue;
}
if let Some(data) = &var.modern_data
&& data.instance_type != vari_instance_type
{
continue;
}
variable_ref = Some(GMRef::new(i as u32));
break;
}
}
let Some(variable) = variable_ref else {
bail!("Cannot resolve variable with name {name:?}");
};
Ok(CodeVariable {
variable,
variable_type,
instance_type,
is_int32: false, })
}
fn parse_variable_identifier<'a>(reader: &'a mut Reader) -> Result<&'a str> {
if reader.consume_str("$$$$temp$$$$").is_some() {
Ok("$$$$temp$$$$")
} else {
reader.parse_identifier()
}
}
fn parse_function(reader: &mut Reader, gm_functions: &GMFunctions) -> Result<GMRef<GMFunction>> {
let identifier = reader.parse_identifier()?;
for (i, func) in gm_functions.iter().enumerate() {
if func.name == identifier {
return Ok(i.into());
}
}
bail!("Function {identifier:?} does not exist")
}
fn parse_int<T>(string: &str) -> Result<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
match string.parse() {
Ok(int) => Ok(int),
Err(err) => {
bail!("Invalid {} Integer {}: {}", typename::<T>(), string, err);
}
}
}
fn parse_string_literal(reader: &mut Reader) -> Result<String> {
let line = reader.line;
if reader.consume_char() != Some('"') {
bail!("Expected string literal; found {line:?}");
}
let mut escaping: bool = false;
let mut string: String = String::with_capacity(reader.line.len());
for (i, char) in reader.line.char_indices() {
if escaping {
let append_char = match char {
'\\' => '\\',
'"' => '"',
'a' => '\x07', 'b' => '\x08', 't' => '\t', 'n' => '\n', 'v' => '\x0B', 'f' => '\x0C', 'r' => '\r', _ => bail!("Invalid escape character '{char}'"),
};
string.push(append_char);
escaping = false;
} else if char == '"' {
reader.consume_to(i + 1);
return Ok(string);
} else if char == '\\' {
escaping = true;
} else {
string.push(char);
}
}
bail!("String literal's quotation marks were never closed")
}