916 lines
No EOL
45 KiB
Rust
916 lines
No EOL
45 KiB
Rust
use std::{collections::HashMap, fs, process, vec};
|
|
use crate::{errors::{create_error, print_error, ErrorSubType, ErrorType}, lexer::lex, parser::{parse, ASTPart}, Context};
|
|
|
|
const ASXVERSION: [u8; 3] = [1,0,0];
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Operation {
|
|
pub opcode: u8,
|
|
pub arg1: Option<u8>,
|
|
pub arg2: Option<f64>,
|
|
pub arg3: Option<u8>,
|
|
pub pos: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct RegisterState {
|
|
id: u8,
|
|
used: bool,
|
|
variable: u32,
|
|
last_used: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct AllocateResult {
|
|
register: u8,
|
|
unbind_before: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct Compiled {
|
|
operations: Vec<Operation>,
|
|
variables: Vec<Variable>,
|
|
strings: HashMap<u32, String>,
|
|
functions: HashMap<u32, Compiled>,
|
|
try_catch: Option<u32>
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct Variable {
|
|
name: String,
|
|
id: u32,
|
|
start: usize,
|
|
end: usize,
|
|
no_end: bool
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PrevFunc {
|
|
variables: Vec<Variable>,
|
|
previous: Option<Box<PrevFunc>>,
|
|
}
|
|
|
|
fn get_register_by_id(registers: &Vec<RegisterState>, id: u8) -> RegisterState {
|
|
for register in registers {
|
|
if register.id == id {
|
|
return register.clone();
|
|
}
|
|
}
|
|
return RegisterState {
|
|
id: 0,
|
|
used: false,
|
|
variable: 0,
|
|
last_used: 0,
|
|
};
|
|
}
|
|
|
|
fn get_register_by_variable(registers: &Vec<RegisterState>, variable: Variable) -> RegisterState {
|
|
for register in registers {
|
|
if register.variable == variable.id {
|
|
return register.clone();
|
|
}
|
|
}
|
|
return RegisterState {
|
|
id: 0,
|
|
used: false,
|
|
variable: 0,
|
|
last_used: 0,
|
|
};
|
|
}
|
|
|
|
fn set_register(registers: &mut Vec<RegisterState>, new_state: RegisterState) {
|
|
if new_state.id == 0 {
|
|
return;
|
|
}
|
|
for i in 0..registers.len() {
|
|
if registers[i].id == new_state.id {
|
|
registers[i] = new_state;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn allocate_register(registers: &Vec<RegisterState>) -> AllocateResult {
|
|
for register in registers {
|
|
if register.id != 0 && !register.used {
|
|
return AllocateResult {
|
|
register: register.id,
|
|
unbind_before: false,
|
|
};
|
|
}
|
|
}
|
|
//Find the one that wasn't used for the longest time
|
|
let mut oldest: usize = 0;
|
|
let mut oldest_register: u8 = 0;
|
|
let mut oldest_temp: usize = 0;
|
|
let mut oldest_temp_register: u8 = 0;
|
|
let mut tempc = 0;
|
|
for register in registers {
|
|
if ((register.last_used < oldest) || (oldest == 0)) && register.id != 0 {
|
|
oldest = register.last_used;
|
|
oldest_register = register.id;
|
|
}
|
|
if ((register.last_used < oldest_temp) || (oldest_temp == 0)) && register.id != 0 && register.variable == 0 {
|
|
oldest_temp = register.last_used;
|
|
oldest_temp_register = register.id;
|
|
}
|
|
if register.id != 0 && register.variable == 0 {
|
|
tempc += 1;
|
|
}
|
|
}
|
|
if oldest_temp_register != 0 && tempc > 3 {
|
|
return AllocateResult {
|
|
register: oldest_temp_register,
|
|
unbind_before: false,
|
|
};
|
|
}
|
|
return AllocateResult {
|
|
register: oldest_register,
|
|
unbind_before: true,
|
|
};
|
|
}
|
|
|
|
fn garbage_collect_registers(_registers: &mut Vec<RegisterState>) {
|
|
return;
|
|
/*for register in registers {
|
|
if register.used && register.variable == 0 {
|
|
register.used = false;
|
|
register.last_used = 0;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
fn get_variable_by_name(variables: &Vec<Variable>, name: &str, position: usize, traceback: &PrevFunc) -> Option<Variable> {
|
|
for variable in variables {
|
|
if variable.name == name {
|
|
if variable.start <= position && (variable.no_end || variable.end >= position) {
|
|
return Some(variable.clone());
|
|
}
|
|
}
|
|
}
|
|
let mut cur = traceback.clone();
|
|
loop {
|
|
for variable in &cur.variables {
|
|
if variable.name == name {
|
|
return Some(variable.clone());
|
|
}
|
|
}
|
|
if let Some(prev) = &cur.previous {
|
|
cur = *prev.clone();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn do_ast_op(ast_op: ASTPart, op_count: &mut usize, ops: &mut Vec<Operation>, variables: &mut Vec<Variable>, next_var_id: &mut u32, strings: &mut HashMap<u32, String>, next_string_id: &mut u32, functions: &mut HashMap<u32, Compiled>, next_function_id: &mut u32, registers: &mut Vec<RegisterState>, ctx: &Context, traceback: &PrevFunc) -> u8 {
|
|
*op_count += 1;
|
|
match ast_op {
|
|
ASTPart::Number(num) => {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: num.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 3, arg1: Some(reg.register), arg2: Some(num.value), arg3: None, pos: num.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::String(str) => {
|
|
let str_id = *next_string_id;
|
|
strings.insert(str_id, str.value);
|
|
*next_string_id += 1;
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: str.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 1, arg1: Some(reg.register), arg2: Some(str_id as f64), arg3: None, pos: str.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::Boolean(bool) => {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: bool.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 4, arg1: Some(reg.register), arg2: Some((bool.value as i64) as f64), arg3: None, pos: bool.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::Null(nul) => {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: nul.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 6, arg1: Some(reg.register), arg2: Some(0 as f64), arg3: None, pos: nul.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::VarRead(var_read) => {
|
|
if get_variable_by_name(variables, &var_read.variable, ops.len(), traceback).is_none() {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: var_read.pos as u32 });
|
|
}
|
|
let str_id = *next_string_id;
|
|
strings.insert(str_id, var_read.variable.clone());
|
|
*next_string_id += 1;
|
|
ops.push(Operation { opcode: 1, arg1: Some(reg.register), arg2: Some(str_id as f64), arg3: None, pos: var_read.pos as u32 });
|
|
ops.push(Operation { opcode: 30, arg1: Some(0), arg2: Some(reg.register as f64), arg3: Some(reg.register), pos: var_read.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
}
|
|
let reg = get_register_by_variable(registers, get_variable_by_name(variables, &var_read.variable, ops.len(), traceback).expect("Variable should exist"));
|
|
if reg.id == 0 {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: var_read.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 2, arg1: Some(reg.register), arg2: Some(get_variable_by_name(variables, &var_read.variable, ops.len(), traceback).expect("Variable should exist").id as f64), arg3: None, pos: var_read.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: get_variable_by_name(variables, &var_read.variable, ops.len(), traceback).expect("Variable should exist").id, last_used: *op_count });
|
|
return reg.register;
|
|
} else {
|
|
return reg.id;
|
|
}
|
|
},
|
|
ASTPart::Function(func) => {
|
|
let func_id = *next_function_id;
|
|
let self_tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
functions.insert(func_id, compile_function(func.body, Some(func.args), registers, next_var_id, ctx, &self_tb, None));
|
|
*next_function_id += 1;
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: func.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 5, arg1: Some(reg.register), arg2: Some(func_id as f64), arg3: None, pos: func.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::Call(call) => {
|
|
for arg in call.args {
|
|
let arg_reg = do_ast_op(arg, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 28, arg1: Some(arg_reg), arg2: None, arg3: None, pos: call.pos as u32 });
|
|
let reg = get_register_by_id(registers, arg_reg);
|
|
if reg.variable == 0 {
|
|
set_register(registers, RegisterState { id: arg_reg, used: false, variable: 0, last_used: 0 });
|
|
}
|
|
}
|
|
let func = do_ast_op(*call.function, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let ret_reg = allocate_register(registers);
|
|
if ret_reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(ret_reg.register), arg2: None, arg3: None, pos: call.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 27, arg1: Some(func), arg2: Some(ret_reg.register as f64), arg3: None, pos: call.pos as u32 });
|
|
set_register(registers, RegisterState { id: ret_reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return ret_reg.register;
|
|
},
|
|
ASTPart::If(if_part) => {
|
|
let condition_reg = do_ast_op(*if_part.condition, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 24, arg1: Some(condition_reg), arg2: Some(condition_reg as f64), arg3: None, pos: if_part.pos as u32 });
|
|
|
|
//Update the lastif variable
|
|
if get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).is_none() {
|
|
variables.push(Variable { name: String::from("__LASTIF"), id: *next_var_id, start: ops.len(), end: 0, no_end: true });
|
|
*next_var_id += 1;
|
|
}
|
|
ops.push(Operation { opcode: 7, arg1: Some(condition_reg), arg2: Some(get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id as f64), arg3: None, pos: if_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: condition_reg, used: true, variable: get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id, last_used: *op_count });
|
|
|
|
let op_placeholder = ops.len();
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: if_part.pos as u32 });
|
|
let mut fake_vars: Vec<Variable> = vec![];
|
|
let tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
for if_op in if_part.body {
|
|
do_ast_op(if_op, op_count, ops, &mut fake_vars, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, &tb);
|
|
}
|
|
for fake_var in fake_vars {
|
|
variables.push(Variable { name: fake_var.name, id: fake_var.id, start: fake_var.start, end: ops.len()-1, no_end: false });
|
|
}
|
|
ops[op_placeholder] = Operation {
|
|
opcode: 26,
|
|
arg1: Some(condition_reg),
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: if_part.pos as u32,
|
|
};
|
|
|
|
//Unbind the lastif variable
|
|
if get_register_by_id(registers, condition_reg).variable == get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id {
|
|
ops.push(Operation { opcode: 8, arg1: Some(condition_reg), arg2: None, arg3: None, pos: if_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: condition_reg, used: false, variable: 0, last_used: 0 });
|
|
}
|
|
garbage_collect_registers(registers);
|
|
},
|
|
ASTPart::Else(else_part) => {
|
|
if get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).is_none() {
|
|
let err = create_error(&format!("Else used without an if statement before it"), else_part.pos, ErrorType::SemanticError, ErrorSubType::ElseWithoutIf, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
let reg = get_register_by_variable(registers, get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").clone());
|
|
let else_condition_reg;
|
|
if reg.id == 0 {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: else_part.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 2, arg1: Some(reg.register), arg2: Some(get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id as f64), arg3: None, pos: else_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id, last_used: *op_count });
|
|
else_condition_reg = reg.register;
|
|
} else {
|
|
else_condition_reg = reg.id;
|
|
}
|
|
ops.push(Operation { opcode: 24, arg1: Some(else_condition_reg), arg2: Some(else_condition_reg as f64), arg3: None, pos: else_part.pos as u32 });
|
|
|
|
let op_placeholder = ops.len();
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: else_part.pos as u32 });
|
|
let tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let mut fake_vars: Vec<Variable> = vec![];
|
|
for else_op in else_part.body {
|
|
do_ast_op(else_op, op_count, ops, &mut fake_vars, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, &tb);
|
|
}
|
|
for fake_var in fake_vars {
|
|
variables.push(Variable { name: fake_var.name, id: fake_var.id, start: fake_var.start, end: ops.len()-1, no_end: false });
|
|
}
|
|
ops[op_placeholder] = Operation {
|
|
opcode: 26,
|
|
arg1: Some(else_condition_reg),
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: else_part.pos as u32,
|
|
};
|
|
//Unbind the lastif variable
|
|
if get_register_by_id(registers, else_condition_reg).variable == get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id {
|
|
ops.push(Operation { opcode: 8, arg1: Some(else_condition_reg), arg2: None, arg3: None, pos: else_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: else_condition_reg, used: false, variable: 0, last_used: 0 });
|
|
}
|
|
garbage_collect_registers(registers);
|
|
},
|
|
ASTPart::ElseIf(elseif_part) => {
|
|
if get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).is_none() {
|
|
let err = create_error(&format!("Else if used without an if statement before it"), elseif_part.pos, ErrorType::SemanticError, ErrorSubType::ElseWithoutIf, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
let reg = get_register_by_variable(registers, get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").clone());
|
|
let else_condition_reg;
|
|
if reg.id == 0 {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: elseif_part.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 2, arg1: Some(reg.register), arg2: Some(get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id as f64), arg3: None, pos: elseif_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id, last_used: *op_count });
|
|
else_condition_reg = reg.register;
|
|
} else {
|
|
else_condition_reg = reg.id;
|
|
}
|
|
ops.push(Operation { opcode: 24, arg1: Some(else_condition_reg), arg2: Some(else_condition_reg as f64), arg3: None, pos: elseif_part.pos as u32 });
|
|
|
|
let condition_reg = do_ast_op(*elseif_part.condition, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 16, arg1: Some(else_condition_reg), arg2: Some(condition_reg as f64), arg3: Some(condition_reg), pos: elseif_part.pos as u32 });
|
|
ops.push(Operation { opcode: 24, arg1: Some(condition_reg), arg2: Some(condition_reg as f64), arg3: None, pos: elseif_part.pos as u32 });
|
|
|
|
//Update the lastif variable
|
|
ops.push(Operation { opcode: 7, arg1: Some(condition_reg), arg2: Some(get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id as f64), arg3: None, pos: elseif_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: condition_reg, used: true, variable: get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id, last_used: *op_count });
|
|
|
|
let op_placeholder = ops.len();
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: elseif_part.pos as u32 });
|
|
let tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let mut fake_vars: Vec<Variable> = vec![];
|
|
for elseif_op in elseif_part.body {
|
|
do_ast_op(elseif_op, op_count, ops, &mut fake_vars, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, &tb);
|
|
}
|
|
for fake_var in fake_vars {
|
|
variables.push(Variable { name: fake_var.name, id: fake_var.id, start: fake_var.start, end: ops.len()-1, no_end: false });
|
|
}
|
|
ops[op_placeholder] = Operation {
|
|
opcode: 26,
|
|
arg1: Some(condition_reg),
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: elseif_part.pos as u32
|
|
};
|
|
//Unbind the lastif variable
|
|
if get_register_by_id(registers, condition_reg).variable == get_variable_by_name(variables, "__LASTIF", ops.len(), traceback).expect("__LASTIF should exist").id {
|
|
ops.push(Operation { opcode: 8, arg1: Some(condition_reg), arg2: None, arg3: None, pos: elseif_part.pos as u32 });
|
|
set_register(registers, RegisterState { id: condition_reg, used: false, variable: 0, last_used: 0 });
|
|
}
|
|
garbage_collect_registers(registers);
|
|
},
|
|
ASTPart::While(while_part) => {
|
|
let start = ops.len();
|
|
let condition_reg = do_ast_op(*while_part.condition, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 24, arg1: Some(condition_reg), arg2: Some(condition_reg as f64), arg3: None, pos: while_part.pos as u32 });
|
|
|
|
let op_placeholder = ops.len();
|
|
let mut breaks: Vec<usize> = vec![];
|
|
let mut continues: Vec<usize> = vec![];
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: while_part.pos as u32 });
|
|
let tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let mut fake_vars: Vec<Variable> = vec![];
|
|
for while_op in while_part.body {
|
|
match while_op {
|
|
ASTPart::Break(_) => {
|
|
breaks.push(ops.len());
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: while_part.pos as u32 });
|
|
},
|
|
ASTPart::Continue(_) => {
|
|
continues.push(ops.len());
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: while_part.pos as u32 });
|
|
},
|
|
_ => {
|
|
do_ast_op(while_op, op_count, ops, &mut fake_vars, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, &tb);
|
|
}
|
|
}
|
|
}
|
|
for fake_var in fake_vars {
|
|
variables.push(Variable { name: fake_var.name, id: fake_var.id, start: fake_var.start, end: ops.len()-1, no_end: false });
|
|
}
|
|
ops.push(Operation { opcode: 25, arg1: None, arg2: Some(start as f64), arg3: None, pos: while_part.pos as u32 });
|
|
ops[op_placeholder] = Operation {
|
|
opcode: 26,
|
|
arg1: Some(condition_reg),
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: while_part.pos as u32
|
|
};
|
|
for brk in breaks {
|
|
ops[brk] = Operation {
|
|
opcode: 25,
|
|
arg1: None,
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: while_part.pos as u32
|
|
};
|
|
}
|
|
for cont in continues {
|
|
ops[cont] = Operation {
|
|
opcode: 25,
|
|
arg1: None,
|
|
arg2: Some(start as f64),
|
|
arg3: None,
|
|
pos: while_part.pos as u32
|
|
};
|
|
}
|
|
},
|
|
ASTPart::Break(brk) => {
|
|
let err = create_error(&format!("Unexpected break outside of loop"), brk.pos, ErrorType::SemanticError, ErrorSubType::BreakContinueWithoutLoop, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
},
|
|
ASTPart::Continue(cont) => {
|
|
let err = create_error(&format!("Unexpected continue outside of loop"), cont.pos, ErrorType::SemanticError, ErrorSubType::BreakContinueWithoutLoop, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
},
|
|
ASTPart::For(for_part) => {
|
|
do_ast_op(*for_part.init, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let start = ops.len();
|
|
let condition_reg = do_ast_op(*for_part.condition, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 24, arg1: Some(condition_reg), arg2: Some(condition_reg as f64), arg3: None, pos: for_part.pos as u32 });
|
|
|
|
let op_placeholder = ops.len();
|
|
let mut breaks: Vec<usize> = vec![];
|
|
let mut continues: Vec<usize> = vec![];
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: for_part.pos as u32 });
|
|
let tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let mut fake_vars: Vec<Variable> = vec![];
|
|
for for_op in for_part.body {
|
|
match for_op {
|
|
ASTPart::Break(_) => {
|
|
breaks.push(ops.len());
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: for_part.pos as u32 });
|
|
},
|
|
ASTPart::Continue(_) => {
|
|
continues.push(ops.len());
|
|
ops.push(Operation { opcode: 0, arg1: None, arg2: None, arg3: None, pos: for_part.pos as u32 });
|
|
},
|
|
_ => {
|
|
do_ast_op(for_op, op_count, ops, &mut fake_vars, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, &tb);
|
|
}
|
|
}
|
|
}
|
|
for fake_var in fake_vars {
|
|
variables.push(Variable { name: fake_var.name, id: fake_var.id, start: fake_var.start, end: ops.len()-1, no_end: false });
|
|
}
|
|
do_ast_op(*for_part.update, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
|
|
ops.push(Operation { opcode: 25, arg1: None, arg2: Some(start as f64), arg3: None, pos: for_part.pos as u32 });
|
|
ops[op_placeholder] = Operation {
|
|
opcode: 26,
|
|
arg1: Some(condition_reg),
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: for_part.pos as u32
|
|
};
|
|
for brk in breaks {
|
|
ops[brk] = Operation {
|
|
opcode: 25,
|
|
arg1: None,
|
|
arg2: Some(ops.len() as f64),
|
|
arg3: None,
|
|
pos: for_part.pos as u32
|
|
};
|
|
}
|
|
for cont in continues {
|
|
ops[cont] = Operation {
|
|
opcode: 25,
|
|
arg1: None,
|
|
arg2: Some(start as f64),
|
|
arg3: None,
|
|
pos: for_part.pos as u32
|
|
};
|
|
}
|
|
},
|
|
ASTPart::Return(ret) => {
|
|
let ret_reg = do_ast_op(*ret.value, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 29, arg1: Some(ret_reg), arg2: None, arg3: None, pos: ret.pos as u32 });
|
|
},
|
|
ASTPart::Operation(op) => {
|
|
let left_reg = do_ast_op(*op.left, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let right_reg = do_ast_op(*op.right, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: op.pos as u32 });
|
|
}
|
|
let opcode = match op.operator.as_str() {
|
|
"+" => 10,
|
|
"-" => 11,
|
|
"*" => 12,
|
|
"/" => 13,
|
|
"^" => 14,
|
|
"%" => 15,
|
|
"&" => 16,
|
|
"|" => 17,
|
|
"==" => 18,
|
|
"!=" => 19,
|
|
">" => 20,
|
|
">=" => 21,
|
|
"<" => 22,
|
|
"<=" => 23,
|
|
"!" => {
|
|
ops.push(Operation { opcode: 24, arg1: Some(left_reg), arg2: Some(reg.register as f64), arg3: None, pos: op.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
_ => {
|
|
let err = create_error(&format!("Unknown operator `{}`", op.operator), op.pos, ErrorType::SyntaxError, ErrorSubType::UnknownOperation, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
},
|
|
};
|
|
ops.push(Operation { opcode, arg1: Some(left_reg), arg2: Some(right_reg as f64), arg3: Some(reg.register), pos: op.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::VarUpdate(upd) => {
|
|
if get_variable_by_name(variables, &upd.variable, ops.len(), traceback).is_none() {
|
|
let err = create_error(&format!("Variable `{}` does not exist", upd.variable), upd.pos, ErrorType::SemanticError, ErrorSubType::VariableNotFound, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
let value_reg = do_ast_op(*upd.value, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let reg = get_register_by_variable(registers, get_variable_by_name(variables, &upd.variable, ops.len(), traceback).expect("Variable should exist").clone());
|
|
if reg.id != 0 {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.id), arg2: None, arg3: None, pos: upd.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.id, used: false, variable: 0, last_used: 0 });
|
|
}
|
|
ops.push(Operation { opcode: 7, arg1: Some(value_reg), arg2: Some(get_variable_by_name(variables, &upd.variable, ops.len(), traceback).expect("Variable should exist").id as f64), arg3: None, pos: upd.pos as u32 });
|
|
set_register(registers, RegisterState { id: value_reg, used: true, variable: get_variable_by_name(variables, &upd.variable, ops.len(), traceback).expect("Variable should exist").id, last_used: *op_count });
|
|
garbage_collect_registers(registers);
|
|
},
|
|
ASTPart::Assigment(asign) => {
|
|
if get_variable_by_name(variables, &asign.variable, ops.len(), traceback).is_some() {
|
|
let err = create_error(&format!("Variable `{}` already exists", asign.variable), asign.pos, ErrorType::SemanticError, ErrorSubType::VariableAlreadyExists, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
let reg = do_ast_op(*asign.value, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
variables.push(Variable { name: asign.variable.clone(), id: *next_var_id, start: ops.len(), end: 0, no_end: true });
|
|
*next_var_id += 1;
|
|
ops.push(Operation { opcode: 7, arg1: Some(reg), arg2: Some(get_variable_by_name(variables, &asign.variable, ops.len(), traceback).expect("Variable should exist").id as f64), arg3: None, pos: asign.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg, used: true, variable: get_variable_by_name(variables, &asign.variable, ops.len(), traceback).expect("Variable should exist").id, last_used: *op_count });
|
|
garbage_collect_registers(registers);
|
|
},
|
|
ASTPart::Table(tbl) => {
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: tbl.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 6, arg1: Some(reg.register), arg2: Some(1 as f64), arg3: None, pos: tbl.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
for val in tbl.values {
|
|
let key_reg = do_ast_op(val.key, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let value_reg = do_ast_op(val.value, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 31, arg1: Some(reg.register), arg2: Some(key_reg as f64), arg3: Some(value_reg), pos: tbl.pos as u32 });
|
|
}
|
|
return reg.register;
|
|
},
|
|
ASTPart::TableGet(tbl_get) => {
|
|
let table_reg = do_ast_op(*tbl_get.table, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let key_reg = do_ast_op(*tbl_get.key, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: tbl_get.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 30, arg1: Some(table_reg), arg2: Some(key_reg as f64), arg3: Some(reg.register), pos: tbl_get.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
ASTPart::TableSet(tbl_set) => {
|
|
let table_reg = do_ast_op(*tbl_set.table, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let key_reg = do_ast_op(*tbl_set.key, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
let value_reg = do_ast_op(*tbl_set.value, op_count, ops, variables, next_var_id, strings, next_string_id, functions, next_function_id, registers, ctx, traceback);
|
|
ops.push(Operation { opcode: 31, arg1: Some(table_reg), arg2: Some(key_reg as f64), arg3: Some(value_reg), pos: tbl_set.pos as u32 });
|
|
},
|
|
ASTPart::Import(impr) => {
|
|
let fi = fs::read_to_string(&impr.path);
|
|
match fi {
|
|
Ok(data) => {
|
|
let imp_ctx = Context {
|
|
file: String::from(&impr.path),
|
|
raw_file: data.clone(),
|
|
c_funcid: 0,
|
|
known: true
|
|
};
|
|
let self_tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let lexed = lex(data, &imp_ctx);
|
|
let ast = parse(lexed, &imp_ctx);
|
|
let compiled = compile_function(ast, None, registers, next_var_id, &imp_ctx, &self_tb, None);
|
|
functions.insert(*next_function_id, compiled);
|
|
*next_function_id += 1;
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: impr.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 5, arg1: Some(reg.register), arg2: Some((*next_function_id-1) as f64), arg3: None, pos: impr.pos as u32 });
|
|
ops.push(Operation { opcode: 27, arg1: Some(reg.register), arg2: Some(reg.register as f64), arg3: None, pos: impr.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
return reg.register;
|
|
},
|
|
Err(e) => {
|
|
let err = create_error(&format!("Failed to read file `{}`: {}", impr.path, e), impr.pos, ErrorType::IOError, ErrorSubType::FileError, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
}
|
|
},
|
|
ASTPart::TryCatch(tc) => {
|
|
let self_tb = PrevFunc {
|
|
variables: variables.clone(),
|
|
previous: Some(Box::new(traceback.clone())),
|
|
};
|
|
let catch_f = compile_function(tc.catch_block.body, Some(tc.catch_block.args), registers, next_var_id, ctx, &self_tb, None);
|
|
let mut try_f = compile_function(tc.try_block.body, None, registers, next_var_id, ctx, &self_tb, Some(0));
|
|
let mut nextt_f = 1;
|
|
for (fid, _) in &try_f.functions {
|
|
if fid >= &nextt_f {
|
|
nextt_f = fid+1;
|
|
}
|
|
}
|
|
try_f.functions.insert(nextt_f, catch_f);
|
|
try_f.try_catch = Some(nextt_f);
|
|
functions.insert(*next_function_id, try_f);
|
|
*next_function_id += 1;
|
|
let reg = allocate_register(registers);
|
|
if reg.unbind_before {
|
|
ops.push(Operation { opcode: 8, arg1: Some(reg.register), arg2: None, arg3: None, pos: tc.pos as u32 });
|
|
}
|
|
ops.push(Operation { opcode: 5, arg1: Some(reg.register), arg2: Some((*next_function_id-1) as f64), arg3: None, pos: tc.pos as u32 });
|
|
ops.push(Operation { opcode: 27, arg1: Some(reg.register), arg2: None, arg3: None, pos: tc.pos as u32 });
|
|
set_register(registers, RegisterState { id: reg.register, used: true, variable: 0, last_used: *op_count });
|
|
},
|
|
_ => {}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
fn compile_function(ast: Vec<ASTPart>, args: Option<Vec<String>>, registers: &mut Vec<RegisterState>, next_var_id: &mut u32, ctx: &Context, traceback: &PrevFunc, try_catch: Option<u32>) -> Compiled {
|
|
let mut ops: Vec<Operation> = vec![];
|
|
|
|
let mut variables: Vec<Variable> = vec![];
|
|
let mut strings: HashMap<u32, String> = HashMap::new();
|
|
let mut next_string_id: u32 = 1;
|
|
let mut functions: HashMap<u32, Compiled> = HashMap::new();
|
|
let mut next_function_id: u32 = 1;
|
|
|
|
let empty_tb = PrevFunc {
|
|
variables: vec![],
|
|
previous: None,
|
|
};
|
|
|
|
match args {
|
|
Some(arg_list) => {
|
|
for arg in arg_list {
|
|
if get_variable_by_name(&variables, &arg, 0, &empty_tb).is_some() {
|
|
let err = create_error(&format!("Argument `{}` already exists", arg), 0, ErrorType::SemanticError, ErrorSubType::ArgumentDuplication, ctx);
|
|
print_error(&err);
|
|
process::exit(1);
|
|
}
|
|
variables.push( Variable { name: arg, id: *next_var_id, start: 0, end: 0, no_end: true });
|
|
*next_var_id += 1;
|
|
}
|
|
},
|
|
None => {}
|
|
}
|
|
|
|
let mut op_count = 0;
|
|
for ast_op in ast {
|
|
do_ast_op(ast_op, &mut op_count, &mut ops, &mut variables, next_var_id, &mut strings, &mut next_string_id, &mut functions, &mut next_function_id, registers, ctx, traceback);
|
|
}
|
|
ops.push(Operation { opcode: 29, arg1: Some(0), arg2: None, arg3: None, pos: ctx.raw_file.len() as u32 });
|
|
for var in &mut variables {
|
|
if var.no_end {
|
|
var.end = ops.len() - 1;
|
|
}
|
|
}
|
|
|
|
return Compiled {
|
|
operations: ops,
|
|
variables,
|
|
strings,
|
|
functions,
|
|
try_catch
|
|
};
|
|
}
|
|
|
|
fn append_be_num(output: &mut Vec<u8>, size: usize, num: usize) { //appends a big-endian u number to the output
|
|
for i in (0..size).rev() {
|
|
output.push(((num >> (i * 8)) & 0xFF) as u8);
|
|
}
|
|
}
|
|
|
|
fn append_string(output: &mut Vec<u8>, input: String, add_len: bool) {
|
|
let bytes = input.as_bytes();
|
|
if add_len {
|
|
append_be_num(output, 4, bytes.len());
|
|
}
|
|
output.extend_from_slice(bytes);
|
|
}
|
|
|
|
fn append_binary(output: &mut Vec<u8>, input: String) {
|
|
let mut inp = input.clone();
|
|
while inp.len() >= 8 {
|
|
let byte = bin_to_num(inp[..8].to_string());
|
|
output.push(byte as u8);
|
|
inp = inp[8..].to_string();
|
|
}
|
|
}
|
|
|
|
fn num_to_bin(number: usize, size: usize) -> String {
|
|
let mut bin = String::new();
|
|
for i in (0..size).rev() {
|
|
if (number >> i) & 1 == 1 {
|
|
bin.push('1');
|
|
} else {
|
|
bin.push('0');
|
|
}
|
|
}
|
|
return bin;
|
|
}
|
|
|
|
fn bin_to_num(bin: String) -> usize {
|
|
let mut num = 0;
|
|
for (i, c) in bin.chars().rev().enumerate() {
|
|
if c == '1' {
|
|
num += 1 << i;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
fn compile_body(compiled: Compiled, fpos: &mut usize, ctx: &Context) -> (Vec<u8>, Vec<Context>) {
|
|
let mut output: Vec<u8> = vec![];
|
|
let mut additional: Vec<Vec<u8>> = vec![];
|
|
let mut contexts: Vec<Context> = vec![ctx.clone()];
|
|
//function
|
|
//function headers
|
|
append_be_num(&mut output, 4, compiled.variables.len());
|
|
for var in compiled.variables {
|
|
append_string(&mut output, var.name, true);
|
|
append_be_num(&mut output, 3, var.id as usize);
|
|
append_be_num(&mut output, 4, var.start);
|
|
append_be_num(&mut output, 4, var.end);
|
|
}
|
|
append_be_num(&mut output, 4, compiled.strings.len());
|
|
for strs in compiled.strings.keys() {
|
|
append_be_num(&mut output, 3, *strs as usize);
|
|
append_string(&mut output, compiled.strings[strs].clone(), true);
|
|
}
|
|
append_be_num(&mut output, 4, compiled.functions.len());
|
|
for funcs in compiled.functions.keys() {
|
|
*fpos += 1;
|
|
append_be_num(&mut output, 3, *funcs as usize);
|
|
append_be_num(&mut output, 4, *fpos);
|
|
let (compiled, mut context) = compile_body(compiled.functions[funcs].clone(), fpos, ctx);
|
|
for c in &mut context {
|
|
c.c_funcid = *fpos
|
|
}
|
|
contexts.extend_from_slice(&context);
|
|
additional.push(compiled);
|
|
}
|
|
match compiled.try_catch {
|
|
Some(tc) => {
|
|
append_be_num(&mut output, 4, tc as usize);
|
|
},
|
|
None => {
|
|
append_be_num(&mut output, 4, 0);
|
|
}
|
|
}
|
|
//function body
|
|
append_be_num(&mut output, 4, compiled.operations.len());
|
|
for ops in compiled.operations {
|
|
let mut op_bin = num_to_bin(ops.opcode.into(), 5);
|
|
if let Some(arg1) = ops.arg1 {
|
|
op_bin.push_str(&num_to_bin(arg1.into(), 4));
|
|
} else {
|
|
op_bin.push_str(&num_to_bin(0, 4));
|
|
}
|
|
if let Some(arg2) = ops.arg2 {
|
|
op_bin.push_str(&num_to_bin(arg2.to_bits() as usize, 64));
|
|
} else {
|
|
op_bin.push_str(&num_to_bin(0, 64));
|
|
}
|
|
if let Some(arg3) = ops.arg3 {
|
|
op_bin.push_str(&num_to_bin(arg3.into(), 4));
|
|
} else {
|
|
op_bin.push_str(&num_to_bin(0, 4));
|
|
}
|
|
op_bin.push_str(&num_to_bin(0, 3));
|
|
append_binary(&mut output, op_bin);
|
|
append_be_num(&mut output, 4, ops.pos as usize);
|
|
}
|
|
for addition in additional {
|
|
output.extend_from_slice(&addition);
|
|
}
|
|
|
|
return (output, contexts);
|
|
}
|
|
|
|
pub fn compile(ast: Vec<ASTPart>, ctx: &Context) -> (Vec<u8>, Vec<Context>) {
|
|
let mut next_var_id: u32 = 1;
|
|
let mut registers: Vec<RegisterState> = vec![];
|
|
for i in 0..16 {
|
|
registers.push(RegisterState {
|
|
id: i as u8,
|
|
used: false,
|
|
variable: 0,
|
|
last_used: 0,
|
|
});
|
|
}
|
|
|
|
let empty_tb = PrevFunc {
|
|
variables: vec![],
|
|
previous: None,
|
|
};
|
|
let compiled = compile_function(ast, None, &mut registers, &mut next_var_id, ctx, &empty_tb, None);
|
|
|
|
let mut output: Vec<u8> = vec![];
|
|
//doctype specifier
|
|
append_string(&mut output, String::from("ASX"), false);
|
|
//version
|
|
output.push(ASXVERSION[0]);
|
|
output.push(ASXVERSION[1]);
|
|
output.push(ASXVERSION[2]);
|
|
//number of functions
|
|
output.push(0);
|
|
output.push(0);
|
|
output.push(0);
|
|
output.push(0);
|
|
//functions
|
|
let mut fpos = 0;
|
|
let (body, contexts) = compile_body(compiled, &mut fpos, ctx);
|
|
output.extend_from_slice(&body);
|
|
|
|
//update function count
|
|
fpos += 1;
|
|
output[6] = ((fpos >> 24) & 0xFF) as u8;
|
|
output[7] = ((fpos >> 16) & 0xFF) as u8;
|
|
output[8] = ((fpos >> 8) & 0xFF) as u8;
|
|
output[9] = (fpos & 0xFF) as u8;
|
|
|
|
return (output, contexts);
|
|
} |