mod constants;
use std::borrow::Cow;
use std::cell::Cell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::vec;
use constants::tokens::get_code_token;
use crate::datex_values::SlotIdentifier;
use crate::parser::header::has_dxb_magic_number;
use crate::utils::color::AnsiCodes;
use crate::utils::color::Color;
use crate::utils::logger::LoggerContext;
use lazy_static::lazy_static;
use regex::Regex;
use crate::datex_values::Value;
use crate::global::binary_codes::BinaryCode;
use crate::parser::header;
use crate::parser::body;
use self::constants::tokens::get_code_color;
lazy_static!{
static ref NEW_LINE:Regex = Regex::new(r"\r\n").unwrap();
static ref LAST_LINE:Regex = Regex::new(r" (.)$").unwrap();
static ref INDENT:String ="\r\n ".to_string();
}
pub fn decompile(ctx: &LoggerContext, dxb:&[u8], formatted:bool, colorized:bool, resolve_slots:bool) -> String {
let mut body = dxb;
if has_dxb_magic_number(dxb) {
let (_header, _body) = header::parse_dxb_header(dxb);
body = _body;
}
return decompile_body(ctx, body, formatted, colorized, resolve_slots);
}
pub fn decompile_body(ctx: &LoggerContext, dxb_body:&[u8], formatted:bool, colorized:bool, resolve_slots:bool) -> String {
let mut initial_state = DecompilerGlobalState {
ctx,
dxb_body,
index: &Cell::from(0),
is_end_instruction: &Cell::from(false),
formatted,
colorized,
resolve_slots,
current_label: 0,
labels: HashMap::new(),
inserted_labels: HashSet::new(),
variables: HashMap::new(),
};
return decompile_loop(&mut initial_state);
}
fn int_to_label(n: i32) -> String {
let mut label = String::new();
let mut n = n;
while n > 0 {
let r = n % 26;
label.insert(0, (r as u8 + b'a') as char);
n /= 26;
}
if label.is_empty() {
label = "a".to_string();
}
label
}
struct DecompilerGlobalState<'a> {
ctx: &'a LoggerContext,
dxb_body:&'a [u8],
index: &'a Cell<usize>,
is_end_instruction: &'a Cell<bool>,
formatted: bool,
colorized: bool,
resolve_slots: bool, current_label: i32,
labels: HashMap<usize, String>,
inserted_labels: HashSet<usize>,
variables: HashMap<u16, String>
}
impl DecompilerGlobalState<'_> {
fn get_insert_label(&mut self, index:usize) -> String {
if self.labels.contains_key(&index) {
return self.labels.get(&index).or(Some(&"?invalid?".to_string())).unwrap().to_string();
}
else {
let name = self.current_label.to_string();
self.current_label += 1;
self.labels.insert(index, name.clone());
return name;
}
}
fn get_variable_name(&mut self, slot:&SlotIdentifier) -> (String, String) {
if slot.is_reserved() || slot.is_object_slot() || !self.resolve_slots {
return (slot.to_string(), "".to_string());
}
if self.variables.contains_key(&slot.index) {
return (self.variables.get(&slot.index).or(Some(&"?invalid?".to_string())).unwrap().to_string(), "".to_string())
}
else {
let name = int_to_label(self.current_label);
self.current_label += 1;
self.variables.insert(slot.index, name.clone());
return (name, "var".to_string());
}
}
}
fn decompile_loop(state: &mut DecompilerGlobalState) -> String {
let mut out:String = "".to_string();
let instruction_iterator = body::iterate_instructions(state.dxb_body, state.index, state.is_end_instruction);
let mut open_element_comma = false;
let mut last_was_value = false;
let mut last_was_property_access = false;
let mut is_indexed_element = false;
let mut next_assign_action: Option<u8> = None;
let mut connective_size_stack: Vec<usize> = vec![];
let mut connective_type_stack:Vec<BinaryCode> = vec![];
for instruction in instruction_iterator {
let code = instruction.code;
let is_new_element = match code {
BinaryCode::ELEMENT => true,
BinaryCode::ELEMENT_WITH_KEY => true,
BinaryCode::ELEMENT_WITH_DYNAMIC_KEY => true,
BinaryCode::ELEMENT_WITH_INT_KEY => true,
BinaryCode::INTERNAL_OBJECT_SLOT => true,
_ => false
};
let is_closing = match code {
BinaryCode::CLOSE_AND_STORE => true,
BinaryCode::SUBSCOPE_END => true,
BinaryCode::ARRAY_END => true,
BinaryCode::OBJECT_END => true,
BinaryCode::TUPLE_END => true,
_ => false
};
let no_space_around = match code {
BinaryCode::CLOSE_AND_STORE => true,
BinaryCode::CHILD_ACTION => true,
BinaryCode::CHILD_GET => true,
BinaryCode::CHILD_GET_REF => true,
BinaryCode::CHILD_SET => true,
BinaryCode::CHILD_SET_REFERENCE => true,
_ => false
};
let add_comma = open_element_comma && is_new_element; if state.formatted && last_was_value && !add_comma && !no_space_around && !is_indexed_element && !is_closing {
out += " ";
}
last_was_value = true;
is_indexed_element = false; if add_comma {
open_element_comma = false;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();} out += if state.formatted {",\r\n"} else {","}
}
let has_slot = instruction.slot.is_some();
let slot = instruction.slot.unwrap_or_default();
let has_primitive_value = instruction.primitive_value.is_some();
let primitive_value = instruction.primitive_value.unwrap_or_default();
let mut custom_primitive_color = false;
let variable_info = if has_slot { state.get_variable_name(&slot)} else {("".to_string(),"".to_string())};
let variable_name = variable_info.0;
let variable_prefix = variable_info.1;
if state.colorized {
if last_was_property_access && (code == BinaryCode::TEXT || code == BinaryCode::SHORT_TEXT) && primitive_value.can_omit_quotes() {
out += &get_code_color(&BinaryCode::ELEMENT_WITH_KEY).as_ansi_rgb(); }
else if code != BinaryCode::CLOSE_AND_STORE { let color = get_code_color(&code);
if color == Color::_UNKNOWN && has_primitive_value {
custom_primitive_color = true;
}
else {
out += &color.as_ansi_rgb();
}
}
}
match code {
BinaryCode::INTERNAL_VAR => out += &format!("{variable_name}"),
BinaryCode::LABEL => out += &format!("$_{variable_name}"),
BinaryCode::SET_INTERNAL_VAR => {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += &variable_prefix;
if variable_prefix.len()!=0 {out += " "};
if state.colorized {out += &get_code_color(&code).as_ansi_rgb();}
out += &variable_name;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += " = ";
},
BinaryCode::INIT_INTERNAL_VAR => {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += &variable_prefix;
if variable_prefix.len()!=0 {out += " "};
if state.colorized {out += &get_code_color(&code).as_ansi_rgb();}
out += &variable_name;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += " := ";
},
BinaryCode::SET_INTERNAL_VAR_REFERENCE => {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += &variable_prefix;
if variable_prefix.len()!=0 {out += " "};
if state.colorized {out += &get_code_color(&code).as_ansi_rgb();}
out += &variable_name;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += " $= ";
},
BinaryCode::INIT_POINTER => {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += &instruction.value.unwrap().to_string();
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += " := ";
},
BinaryCode::SET_POINTER => {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += &instruction.value.unwrap().to_string();
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += " =";
},
BinaryCode::CHILD_ACTION => out += &get_code_token(&BinaryCode::CHILD_ACTION, state.formatted),
BinaryCode::ELEMENT_WITH_KEY => out += &format!("{}:", primitive_value.to_key_string()),
BinaryCode::ELEMENT_WITH_INT_KEY => out += &format!("{}:", primitive_value.to_key_string()),
BinaryCode::INTERNAL_OBJECT_SLOT => out += &format!("{}:", SlotIdentifier::new(primitive_value.get_as_unsigned_integer() as u16)),
BinaryCode::RESOLVE_RELATIVE_PATH => out += primitive_value.get_as_text(),
BinaryCode::ELEMENT => {
is_indexed_element = true; },
BinaryCode::CONJUNCTION => {
out += "(";
connective_type_stack.push(BinaryCode::CONJUNCTION);
connective_size_stack.push(primitive_value.get_as_unsigned_integer());
},
BinaryCode::DISJUNCTION => {
out += "(";
connective_type_stack.push(BinaryCode::DISJUNCTION);
connective_size_stack.push(primitive_value.get_as_unsigned_integer());
},
BinaryCode::JMP => {
let label = state.get_insert_label(primitive_value.get_as_unsigned_integer());
out += &format!("jmp {}", label);
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += ";";
},
BinaryCode::JTR => {
let label = state.get_insert_label(primitive_value.get_as_unsigned_integer());
out += &format!("jtr {}", label)
},
BinaryCode::JFA => {
let label = state.get_insert_label(primitive_value.get_as_unsigned_integer());
out += &format!("jfa {}", label)
},
BinaryCode::SCOPE_BLOCK_START => {
let scope = &mut decompile_body(&state.ctx, &primitive_value.get_as_buffer(), state.formatted, state.colorized, state.resolve_slots);
if true {
*scope += ")";
out += "(";
if state.formatted {
out += &INDENT;
out += &NEW_LINE.replace_all( &scope,
&INDENT.to_string()
);
};
}
else {
scope.pop(); scope.pop();
scope.pop();
out += scope;
}
},
BinaryCode::CLOSE_AND_STORE => {
let empty: &[_] = &['\r', '\n', ' '];
out = out.trim_end_matches(empty).to_string();
if state.colorized {out += &get_code_color(&code).as_ansi_rgb()}
out += &get_code_token(&code, state.formatted);
if state.formatted && state.index.get() < state.dxb_body.len() {
out += "\r\n";
}
}
_ => {
if has_primitive_value {
if last_was_property_access {out += &primitive_value.to_key_string()}
else if custom_primitive_color {out += &primitive_value.to_string_colorized()}
else {out += &Value::to_string(&primitive_value)}
}
else if instruction.value.is_some() {
out += &instruction.value.unwrap().to_string();
}
else {
out += &get_code_token(&code, state.formatted)
}
}
}
if instruction.subscope_continue {
let inner = Cow::from(decompile_loop(state));
let is_empty = inner.len() == 0;
let newline_count = inner.chars().filter(|c| *c == '\n').count();
if state.formatted && !is_empty && newline_count>0 {
out += &INDENT;
out += &NEW_LINE.replace_all( &inner,
&INDENT.to_string()
);
out += "\r\n";
}
else {
out += &NEW_LINE.replace_all(&inner, "").trim_end(); }
}
if next_assign_action.is_some() {
if state.colorized {
out += &Color::DEFAULT.as_ansi_rgb();
}
out += " ";
let assign_type = next_assign_action.unwrap();
match assign_type {
1 => out += "$",
2 => out += "",
_ => out += &get_code_token(&BinaryCode::try_from(assign_type).expect("enum conversion error"), false)
}
out += "= ";
last_was_value = false; next_assign_action = None; }
match code {
BinaryCode::CHILD_ACTION => next_assign_action = Some(primitive_value.get_as_integer() as u8),
BinaryCode::CHILD_SET_REFERENCE => next_assign_action = Some(1),
BinaryCode::CHILD_SET => next_assign_action = Some(2),
_ => ()
}
last_was_property_access = false;
if is_new_element {open_element_comma = true} if is_closing {
last_was_value = false; }
if no_space_around {
last_was_value = false }
match code {
BinaryCode::SET_INTERNAL_VAR => {last_was_value = false}, BinaryCode::SET_INTERNAL_VAR_REFERENCE => {last_was_value = false}, BinaryCode::INIT_INTERNAL_VAR => {last_was_value = false}, BinaryCode::INIT_POINTER => {last_was_value = false}, BinaryCode::NOT => {last_was_value = false}, BinaryCode::CHILD_GET => {last_was_property_access = true}, BinaryCode::CHILD_GET_REF => {last_was_property_access = true}, BinaryCode::CHILD_ACTION => {last_was_property_access = true}, BinaryCode::CHILD_SET => {last_was_property_access = true}, BinaryCode::CHILD_SET_REFERENCE => {last_was_property_access = true}, BinaryCode::CONJUNCTION => {last_was_value = false}, BinaryCode::DISJUNCTION => {last_was_value = false}, _ => ()
}
for label in &mut state.labels {
if *label.0 == state.index.get() && !state.inserted_labels.contains(label.0) {
if state.colorized {out += &Color::RESERVED.as_ansi_rgb();}
out += "\r\nlbl ";
out += &label.1;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb();}
out += ";";
state.inserted_labels.insert(*label.0);
}
}
while last_was_value && connective_size_stack.len()!=0 {
let len = connective_size_stack.len()-1;
connective_size_stack[len] -= 1;
if state.colorized {out += &Color::DEFAULT.as_ansi_rgb()};
if connective_size_stack[len] == 0 {
connective_size_stack.pop();
connective_type_stack.pop();
out += ")";
}
else {
out += if connective_type_stack[connective_type_stack.len()-1] == BinaryCode::CONJUNCTION {" &"} else {" |"};
break; }
}
}
if state.colorized {out += AnsiCodes::RESET};
return out;
}