From 1f2de4d0a7094b33ba5b6018fe6dbe185bc19c89 Mon Sep 17 00:00:00 2001 From: Missing Date: Mon, 22 Dec 2025 17:42:38 -0600 Subject: [PATCH 1/8] String Literal output is now optimized better --- compiler/src/frontend/frontend.rs | 911 +++++++++++++++--------------- compiler/src/misc.rs | 2 +- 2 files changed, 457 insertions(+), 456 deletions(-) diff --git a/compiler/src/frontend/frontend.rs b/compiler/src/frontend/frontend.rs index e312a9b..9866b77 100644 --- a/compiler/src/frontend/frontend.rs +++ b/compiler/src/frontend/frontend.rs @@ -27,15 +27,15 @@ impl MastermindContext { outer_scope: Option<&'a ScopeBuilder>, ) -> Result, String> where - BrainfuckBuilderData: BrainfuckBuilder, - CellAllocatorData: CellAllocator, + BrainfuckBuilderData: BrainfuckBuilder, + CellAllocatorData: CellAllocator, { let mut scope = if let Some(outer) = outer_scope { outer.open_inner() } else { ScopeBuilder::new() }; - + // TODO: fix unnecessary clones, and reimplement this with iterators somehow // hoist structs, then functions to top let mut filtered_clauses_1 = vec![]; @@ -67,7 +67,7 @@ impl MastermindContext { } } } - + for clause in filtered_clauses_2 { match clause { Clause::DeclareVariable { var } => { @@ -77,29 +77,29 @@ impl MastermindContext { Clause::DefineVariable { var, value } => { // same as above except we initialise the variable let absolute_type = scope.allocate_variable(var.clone())?; - + match (absolute_type, &value) { ( ValueType::Cell, - Expression::NaturalNumber(_) - | Expression::SumExpression { - sign: _, - summands: _, - } - | Expression::VariableReference(_), + Expression::NaturalNumber(_) + | Expression::SumExpression { + sign: _, + summands: _, + } + | Expression::VariableReference(_), ) => { let cell = scope.get_cell(&VariableTarget::from_definition(&var))?; scope._add_expr_to_cell(&value, cell)?; } - + // multi-cell arrays and (array literals or strings) (ValueType::Array(_, _), Expression::ArrayLiteral(expressions)) => { let cells = - scope.get_array_cells(&VariableTarget::from_definition(&var))?; + scope.get_array_cells(&VariableTarget::from_definition(&var))?; r_assert!( expressions.len() == cells.len(), - "Variable \"{var}\" cannot be initialised to array of length {}", - expressions.len() + "Variable \"{var}\" cannot be initialised to array of length {}", + expressions.len() ); for (cell, expr) in zip(cells, expressions) { scope._add_expr_to_cell(expr, cell)?; @@ -107,47 +107,47 @@ impl MastermindContext { } (ValueType::Array(_, _), Expression::StringLiteral(s)) => { let cells = - scope.get_array_cells(&VariableTarget::from_definition(&var))?; + scope.get_array_cells(&VariableTarget::from_definition(&var))?; r_assert!( s.len() == cells.len(), - "Variable \"{var}\" cannot be initialised to string of length {}", - s.len() + "Variable \"{var}\" cannot be initialised to string of length {}", + s.len() ); for (cell, chr) in zip(cells, s.bytes()) { scope.push_instruction(Instruction::AddToCell(cell, chr)); } } - + ( ValueType::Array(_, _), - Expression::VariableReference(variable_target), + Expression::VariableReference(variable_target), ) => r_panic!( "Cannot assign array \"{var}\" from variable reference \ \"{variable_target}\". Unimplemented." ), ( ValueType::Array(_, _), - Expression::NaturalNumber(_) - | Expression::SumExpression { - sign: _, - summands: _, - }, + Expression::NaturalNumber(_) + | Expression::SumExpression { + sign: _, + summands: _, + }, ) => r_panic!("Cannot assign single value to array \"{var}\"."), - + ( ValueType::DictStruct(_), - Expression::SumExpression { - sign: _, - summands: _, - } - | Expression::NaturalNumber(_) - | Expression::VariableReference(_) - | Expression::ArrayLiteral(_) - | Expression::StringLiteral(_), + Expression::SumExpression { + sign: _, + summands: _, + } + | Expression::NaturalNumber(_) + | Expression::VariableReference(_) + | Expression::ArrayLiteral(_) + | Expression::StringLiteral(_), ) => r_panic!( "Cannot assign value to struct type \"{var}\", initialise it instead." ), - + (ValueType::Cell, Expression::ArrayLiteral(_)) => { r_panic!("Cannot assign array to single-cell variable \"{var}\".") } @@ -203,18 +203,18 @@ impl MastermindContext { match value { Some(expr) => { let (imm, adds, subs) = expr.flatten()?; - + r_assert!( adds.len() == 0 && subs.len() == 0, - "Expected compile-time constant expression in assertion for {var}" + "Expected compile-time constant expression in assertion for {var}" ); - + Some(imm) } None => None, } }; - + match var.is_spread { false => { let cell = scope.get_cell(&var)?; @@ -269,11 +269,11 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + scope._add_expr_to_cell(&value, cell)?; scope.push_instruction(Instruction::OutputCell(cell)); scope.push_instruction(Instruction::ClearCell(cell)); - + scope.push_instruction(Instruction::Free(temp_mem_id)); } Expression::ArrayLiteral(expressions) => { @@ -287,13 +287,13 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + for value in expressions { scope._add_expr_to_cell(&value, cell)?; scope.push_instruction(Instruction::OutputCell(cell)); scope.push_instruction(Instruction::ClearCell(cell)); } - + scope.push_instruction(Instruction::Free(temp_mem_id)); } Expression::StringLiteral(s) => { @@ -307,28 +307,29 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + + let mut prev = 0; for c in s.bytes() { - scope.push_instruction(Instruction::AddToCell(cell, c)); + scope.push_instruction(Instruction::AddToCell(cell, c.wrapping_sub(prev))); scope.push_instruction(Instruction::OutputCell(cell)); - scope.push_instruction(Instruction::ClearCell(cell)); + prev = c; } - + scope.push_instruction(Instruction::ClearCell(cell)); scope.push_instruction(Instruction::Free(temp_mem_id)); } } } Clause::While { var, block } => { let cell = scope.get_cell(&var)?; - + // open loop on variable scope.push_instruction(Instruction::OpenLoop(cell)); - + // recursively compile instructions // TODO: when recursively compiling, check which things changed based on a return info value let loop_scope = self.create_ir_scope(&block, Some(&scope))?; scope.instructions.extend(loop_scope.build_ir(true)); - + // close the loop scope.push_instruction(Instruction::CloseLoop(cell)); } @@ -348,7 +349,7 @@ impl MastermindContext { // any other kind of expression, allocate memory for it automatically let id = scope.push_memory_id(); scope - .push_instruction(Instruction::Allocate(Memory::Cell { id }, None)); + .push_instruction(Instruction::Allocate(Memory::Cell { id }, None)); let new_cell = CellReference { memory_id: id, index: None, @@ -358,20 +359,20 @@ impl MastermindContext { } (true, Expression::VariableReference(var)) => { let cell = scope.get_cell(var)?; - + let new_mem_id = scope.push_memory_id(); scope.push_instruction(Instruction::Allocate( Memory::Cell { id: new_mem_id }, None, )); - + let new_cell = CellReference { memory_id: new_mem_id, index: None, }; - + scope._copy_cell(cell, new_cell, 1); - + (new_cell, true) } (true, _) => { @@ -379,14 +380,14 @@ impl MastermindContext { } }; scope.push_instruction(Instruction::OpenLoop(source_cell)); - + // recurse if let Some(block) = block { let loop_scope = self.create_ir_scope(&block, Some(&scope))?; // TODO: refactor, make a function in scope trait to do this automatically scope.instructions.extend(loop_scope.build_ir(true)); } - + // copy into each target and decrement the source for target in targets { match target.is_spread { @@ -402,10 +403,10 @@ impl MastermindContext { } } } - + scope.push_instruction(Instruction::AddToCell(source_cell, -1i8 as u8)); // 255 scope.push_instruction(Instruction::CloseLoop(source_cell)); - + // free the source cell if it was a expression we just created if free_source_cell { scope.push_instruction(Instruction::Free(source_cell.memory_id)); @@ -452,12 +453,12 @@ impl MastermindContext { _ => unreachable!(), }; // end patch // - + if if_block.is_none() && else_block.is_none() { panic!("Expected block in if/else statement"); }; let mut new_scope = scope.open_inner(); - + let condition_mem_id = new_scope.push_memory_id(); new_scope.push_instruction(Instruction::Allocate( Memory::Cell { @@ -469,7 +470,7 @@ impl MastermindContext { memory_id: condition_mem_id, index: None, }; - + let else_condition_cell = match else_block { Some(_) => { let else_mem_id = new_scope.push_memory_id(); @@ -486,46 +487,46 @@ impl MastermindContext { } None => None, }; - + // copy the condition expression to the temporary condition cell new_scope._add_expr_to_cell(&condition, condition_cell)?; - + new_scope.push_instruction(Instruction::OpenLoop(condition_cell)); // TODO: think about optimisations for clearing this variable, as the builder won't shorten it for safety as it doesn't know this loop is special new_scope.push_instruction(Instruction::ClearCell(condition_cell)); - + // set the else condition cell // above comment about optimisations also applies here if let Some(cell) = else_condition_cell { new_scope.push_instruction(Instruction::ClearCell(cell)); }; - + // recursively compile if block if let Some(block) = if_block { let if_scope = self.create_ir_scope(&block, Some(&new_scope))?; new_scope.instructions.extend(if_scope.build_ir(true)); }; - + // close if block new_scope.push_instruction(Instruction::CloseLoop(condition_cell)); new_scope.push_instruction(Instruction::Free(condition_cell.memory_id)); - + // else block: if let Some(cell) = else_condition_cell { new_scope.push_instruction(Instruction::OpenLoop(cell)); // again think about how to optimise this clear in the build step new_scope.push_instruction(Instruction::ClearCell(cell)); - + // recursively compile else block // TODO: fix this bad practice unwrap let block = else_block.unwrap(); let else_scope = self.create_ir_scope(&block, Some(&new_scope))?; new_scope.instructions.extend(else_scope.build_ir(true)); - + new_scope.push_instruction(Instruction::CloseLoop(cell)); new_scope.push_instruction(Instruction::Free(cell.memory_id)); } - + // extend the inner scopes instructions onto the outer one scope.instructions.extend(new_scope.build_ir(true)); } @@ -547,19 +548,19 @@ impl MastermindContext { let functions_scope = scope.open_inner_templates_only(); // compile the block and extend the operations let instructions = self - .create_ir_scope(&mm_clauses, Some(&functions_scope))? - // compile without cleaning up top level variables, this is the brainfuck programmer's responsibility - .build_ir(false); - + .create_ir_scope(&mm_clauses, Some(&functions_scope))? + // compile without cleaning up top level variables, this is the brainfuck programmer's responsibility + .build_ir(false); + // it is also the brainfuck programmer's responsibility to return to the start position let bf_code = - self.ir_to_bf(instructions, Some(TC::origin_cell()))?; + self.ir_to_bf(instructions, Some(TC::origin_cell()))?; expanded_bf.extend(bf_code); } ExtendedOpcode::Opcode(opcode) => expanded_bf.push(opcode), } } - + // handle the location specifier let location = match location_specifier { LocationSpecifier::None => CellLocation::Unspecified, @@ -568,7 +569,7 @@ impl MastermindContext { CellLocation::MemoryCell(scope.get_target_cell_reference(&var)?) } }; - + scope.push_instruction(Instruction::InsertBrainfuckAtCell( expanded_bf, location, @@ -585,7 +586,7 @@ impl MastermindContext { let cells = scope.get_array_cells(&var)?; for cell in cells { scope - .push_instruction(Instruction::AssertCellValue(cell, None)); + .push_instruction(Instruction::AssertCellValue(cell, None)); } } } @@ -596,48 +597,48 @@ impl MastermindContext { arguments, } => { // create variable translations and recursively compile the inner variable block - + // get the calling arguments' types let calling_argument_types: Vec = arguments - .iter() - .map(|arg| scope.get_expression_type(arg)) - .collect::, String>>()?; - + .iter() + .map(|arg| scope.get_expression_type(arg)) + .collect::, String>>()?; + // find the function based on name * types let function_definition = - scope.get_function(&function_name, &calling_argument_types)?; - + scope.get_function(&function_name, &calling_argument_types)?; + // create mappings in a new translation scope, so mappings will be removed once scope closes let mut argument_translation_scope = scope.open_inner(); assert_eq!(arguments.len(), function_definition.arguments.len()); for (calling_expr, (arg_name, _)) in zip(arguments, function_definition.arguments) - { - // TODO: allow expressions as arguments: create a new variable instead of mapping when a value needs to be computed - let calling_arg = match calling_expr { - Expression::VariableReference(var) => var, - expr => r_panic!( - "Expected variable target in function call argument, \ + { + // TODO: allow expressions as arguments: create a new variable instead of mapping when a value needs to be computed + let calling_arg = match calling_expr { + Expression::VariableReference(var) => var, + expr => r_panic!( + "Expected variable target in function call argument, \ found expression `{expr}`. General expressions as \ function arguments are not supported." - ), - }; - argument_translation_scope + ), + }; + argument_translation_scope .create_mapped_variable(arg_name, &calling_arg)?; - } - - // recursively compile the function block - let function_scope = self.create_ir_scope( - &function_definition.block, - Some(&argument_translation_scope), - )?; - argument_translation_scope + } + + // recursively compile the function block + let function_scope = self.create_ir_scope( + &function_definition.block, + Some(&argument_translation_scope), + )?; + argument_translation_scope .instructions .extend(function_scope.build_ir(true)); - - // add the recursively compiled instructions to the current scope's built instructions - // TODO: figure out why this .build_ir() call uses clean_up_variables = false - scope + + // add the recursively compiled instructions to the current scope's built instructions + // TODO: figure out why this .build_ir() call uses clean_up_variables = false + scope .instructions .extend(argument_translation_scope.build_ir(false)); } @@ -650,7 +651,7 @@ function arguments are not supported." | Clause::None => unreachable!(), } } - + Ok(scope) } } @@ -664,26 +665,26 @@ pub struct ScopeBuilder<'a, TC, OC> { /// If true, scope is not able to access variables from outer scope. /// Used for embedded mm so that the inner mm can use outer functions but not variables. types_only: bool, - + /// Number of memory allocations in current scope allocations: usize, - + /// Mappings for variable names to memory allocation IDs in current scope variable_memory: HashMap, - + /// Functions accessible by any code within or in the current scope functions: Vec<(String, Vec<(String, ValueType)>, Vec>)>, /// Struct types definitions structs: HashMap, - + /// Intermediate instructions generated by the compiler instructions: Vec>, } impl ScopeBuilder<'_, TC, OC> where - TC: Display + Clone, - OC: Clone, +TC: Display + Clone, +OC: Clone, { pub fn new() -> ScopeBuilder<'static, TC, OC> { ScopeBuilder { @@ -696,7 +697,7 @@ where instructions: Vec::new(), } } - + // regarding `clean_up_variables`: // I don't love this system of deciding what to clean up at the end in this specific function, but I'm not sure what the best way to achieve this would be // this used to be called "get_instructions" but I think this more implies things are being modified @@ -704,10 +705,10 @@ where if !clean_up_variables { return self.instructions; } - + // optimisations could go here? // TODO: add some optimisations from the builder to here - + // create instructions to free cells let mut clear_instructions = vec![]; for (_var_name, (_var_type, memory)) in self.variable_memory.iter() { @@ -739,14 +740,14 @@ where for instr in clear_instructions { self.push_instruction(instr); } - + self.instructions } - + fn push_instruction(&mut self, instruction: Instruction) { self.instructions.push(instruction); } - + /// Open a scope within the current one, any time there is a {} in Mastermind, this is called fn open_inner(&self) -> ScopeBuilder { ScopeBuilder { @@ -759,7 +760,7 @@ where instructions: Vec::new(), } } - + // syntactic context instead of normal context // used for embedded mm so that the inner mm can use outer functions fn open_inner_templates_only(&self) -> ScopeBuilder { @@ -773,14 +774,14 @@ where instructions: Vec::new(), } } - + /// Get the correct variable type and allocate the right amount of cells for it fn allocate_variable(&mut self, var: VariableTypeDefinition) -> Result<&ValueType, String> { r_assert!( !self.variable_memory.contains_key(&var.name), - "Cannot allocate variable {var} twice in the same scope" + "Cannot allocate variable {var} twice in the same scope" ); - + // get absolute type let full_type = self.create_absolute_type(&var.var_type)?; // get absolute type size @@ -794,12 +795,12 @@ where }; // save variable in scope memory let None = self - .variable_memory - .insert(var.name.clone(), (full_type, memory.clone())) + .variable_memory + .insert(var.name.clone(), (full_type, memory.clone())) else { r_panic!("Unreachable error occurred when allocating {var}"); }; - + // verify location specifier let location = match var.location_specifier { LocationSpecifier::None => None, @@ -808,25 +809,25 @@ where "Cannot use variable as location specifier target when allocating variable: {var}" ), }; - + // allocate self.push_instruction(Instruction::Allocate(memory.clone(), location)); - + // return a reference to the created full type Ok(&self.variable_memory.get(&var.name).unwrap().0) } - + // fn allocate_unnamed_cell(&mut self) -> Memory { // let mem_id = self.create_memory_id(); // Memory::Cell { id: mem_id } // } - + fn push_memory_id(&mut self) -> MemoryId { let current_scope_relative = self.allocations; self.allocations += 1; current_scope_relative + self.allocation_offset() } - + /// recursively tally the allocation stack size of the outer scope, does not include this scope fn allocation_offset(&self) -> usize { // little bit of a hack but works for now @@ -839,7 +840,7 @@ where 0 } } - + /// find a function definition based on name and argument types (unaffected by the self.fn_only flag) fn get_function( &self, @@ -861,19 +862,19 @@ where let (_, arguments, block) = func; return Ok(Function { arguments: arguments.clone(), - block: block.clone(), + block: block.clone(), }); } - + if let Some(outer_scope) = self.outer_scope { return outer_scope.get_function(calling_name, calling_arg_types); } - + r_panic!( "Could not find function \"{calling_name}\" with correct arguments in current scope" ); } - + /// Define a struct in this scope fn register_struct_definition( &mut self, @@ -881,7 +882,7 @@ where fields: Vec, ) -> Result<(), String> { let mut absolute_fields = vec![]; - + for field_def in fields { let absolute_type = self.create_absolute_type(&field_def.field_type)?; absolute_fields.push(( @@ -890,17 +891,17 @@ where field_def.location_offset_specifier, )); } - + let None = self - .structs - .insert(struct_name.to_string(), DictStructType(absolute_fields)) + .structs + .insert(struct_name.to_string(), DictStructType(absolute_fields)) else { r_panic!("Cannot define struct {struct_name} more than once in same scope."); }; - + Ok(()) } - + /// Define a function in this scope fn register_function_definition( &mut self, @@ -909,15 +910,15 @@ where new_block: Vec>, ) -> Result<(), String> { let absolute_arguments: Vec<(String, ValueType)> = new_arguments - .into_iter() - .map(|f| { - let LocationSpecifier::None = f.location_specifier else { - r_panic!("Cannot specify variable location in function argument \"{f}\"."); - }; - Ok((f.name, self.create_absolute_type(&f.var_type)?)) - }) - .collect::, String>>()?; - + .into_iter() + .map(|f| { + let LocationSpecifier::None = f.location_specifier else { + r_panic!("Cannot specify variable location in function argument \"{f}\"."); + }; + Ok((f.name, self.create_absolute_type(&f.var_type)?)) + }) + .collect::, String>>()?; + // TODO: refactor this: // This is some fucked C-style loop break logic, basically GOTOs // basically it only gets to the panic if the functions have identical signature (except argument names) @@ -933,13 +934,13 @@ where } r_panic!("Cannot define a function with the same signature more than once in the same scope: \"{new_function_name}\""); } - + self.functions - .push((new_function_name.to_string(), absolute_arguments, new_block)); - + .push((new_function_name.to_string(), absolute_arguments, new_block)); + Ok(()) } - + /// Recursively find the definition of a struct type by searching up the scope call stack fn get_struct_definition(&self, struct_name: &str) -> Result<&DictStructType, String> { if let Some(struct_def) = self.structs.get(struct_name) { @@ -951,21 +952,21 @@ where r_panic!("No definition found for struct \"{struct_name}\"."); } } - + /// Construct an absolute type from a type reference fn create_absolute_type(&self, type_ref: &VariableTypeReference) -> Result { Ok(match type_ref { VariableTypeReference::Cell => ValueType::Cell, - VariableTypeReference::Struct(struct_type_name) => { - ValueType::from_struct(self.get_struct_definition(struct_type_name)?.clone()) - } - VariableTypeReference::Array(variable_type_reference, len) => ValueType::Array( - *len, - Box::new(self.create_absolute_type(variable_type_reference)?), - ), + VariableTypeReference::Struct(struct_type_name) => { + ValueType::from_struct(self.get_struct_definition(struct_type_name)?.clone()) + } + VariableTypeReference::Array(variable_type_reference, len) => ValueType::Array( + *len, + Box::new(self.create_absolute_type(variable_type_reference)?), + ), }) } - + /// Return a cell reference for a variable target fn get_cell(&self, target: &VariableTarget) -> Result { // get the absolute type of the variable, as well as the memory allocations @@ -982,13 +983,13 @@ where }), ( Some(subfield_chain), - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len } - | Memory::MappedCells { - id, - start_index: _, - len, - }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len } + | Memory::MappedCells { + id, + start_index: _, + len, + }, ) => { let (subfield_type, cell_index) = full_type.get_subfield(&subfield_chain)?; let ValueType::Cell = subfield_type else { @@ -997,32 +998,32 @@ where r_assert!(cell_index < *len, "Cell reference out of bounds on variable target: {target}. This should not occur."); Ok(CellReference { memory_id: *id, - index: Some(match memory { - Memory::Cells { id: _, len: _ } => cell_index, - Memory::MappedCells { - id: _, - start_index, - len: _, - } => *start_index + cell_index, - _ => unreachable!(), - }), + index: Some(match memory { + Memory::Cells { id: _, len: _ } => cell_index, + Memory::MappedCells { + id: _, + start_index, + len: _, + } => *start_index + cell_index, + _ => unreachable!(), + }), }) } // valid states, user error ( Some(_), - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!("Cannot get subfields of cell type: {target}"), ( None, ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) => r_panic!("Expected single cell reference in target: {target}"), // invalid states, indicating an internal compiler issue (akin to 5xx error) ( @@ -1038,26 +1039,26 @@ where | ( _, ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!( "Invalid memory for value type in target: {target}. This should not occur." ), } } - + /// Return a list of cell references for an array of cells (not an array of structs) fn get_array_cells(&self, target: &VariableTarget) -> Result, String> { let (full_type, memory) = self.get_base_variable_memory(&target.name)?; Ok(match (&target.subfields, full_type, memory) { ( None, - ValueType::Array(arr_len, element_type), - Memory::Cells { id, len } - | Memory::MappedCells { - id, - start_index: _, - len, - }, + ValueType::Array(arr_len, element_type), + Memory::Cells { id, len } + | Memory::MappedCells { + id, + start_index: _, + len, + }, ) => { let ValueType::Cell = **element_type else { r_panic!("Cannot get array cells of struct array: {target}"); @@ -1068,28 +1069,28 @@ where ); (match memory { Memory::Cells { id: _, len } => 0..*len, - Memory::MappedCells { - id: _, - start_index, - len, - } => *start_index..(*start_index + *len), - _ => unreachable!(), + Memory::MappedCells { + id: _, + start_index, + len, + } => *start_index..(*start_index + *len), + _ => unreachable!(), }) .map(|i| CellReference { memory_id: *id, - index: Some(i), + index: Some(i), }) .collect() } ( Some(subfields), - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len: _ } - | Memory::MappedCells { - id, - start_index: _, - len: _, - }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len: _ } + | Memory::MappedCells { + id, + start_index: _, + len: _, + }, ) => { let (subfield_type, offset_index) = full_type.get_subfield(subfields)?; let ValueType::Array(arr_len, element_type) = subfield_type else { @@ -1098,66 +1099,66 @@ where let ValueType::Cell = **element_type else { r_panic!("Expected cell array in subfield variable target \"{target}\"."); }; - + (match memory { Memory::Cells { id: _, len: _ } => offset_index..(offset_index + *arr_len), - Memory::MappedCells { - id: _, - start_index, - len: _, - } => (*start_index + offset_index)..(*start_index + offset_index + *arr_len), - _ => unreachable!(), + Memory::MappedCells { + id: _, + start_index, + len: _, + } => (*start_index + offset_index)..(*start_index + offset_index + *arr_len), + _ => unreachable!(), }) .map(|i| CellReference { memory_id: *id, - index: Some(i), + index: Some(i), }) .collect() } ( None, - ValueType::DictStruct(_), - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + ValueType::DictStruct(_), + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) | ( None, - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => { r_panic!("Expected cell array type in variable target: {target}") } ( Some(_), - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => { r_panic!("Attempted to retrieve array subfield from cell type: {target}") } ( _, - ValueType::Cell, - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + ValueType::Cell, + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) | ( _, - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!( "Invalid memory for value type in target: {target}. This should not occur." ), }) } - + /// Return the first memory cell of a target allocation, used for location specifiers fn get_target_cell_reference(&self, target: &VariableTarget) -> Result { let (full_type, memory) = self.get_base_variable_memory(&target.name)?; @@ -1184,24 +1185,24 @@ where index: Some(*start_index), }, }, - Some(subfield_chain) => { - let (_subfield_type, offset_index) = full_type.get_subfield(&subfield_chain)?; - match memory { - Memory::Cell { id: _ } |Memory::MappedCell { id: _, index: _ } => r_panic!("Attempted to get cell reference of subfield of single cell memory: {target}"), - Memory::Cells { id, len } | Memory::MappedCells { id, start_index: _, len } => { - r_assert!(offset_index < *len, "Subfield memory index out of allocation range. This should not occur. ({target})"); - let index = match memory { - Memory::Cells { id: _, len: _ } => offset_index, - Memory::MappedCells { id: _, start_index, len: _ } => *start_index + offset_index, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ } => unreachable!() - }; - CellReference {memory_id: *id, index: Some(index)} - } - } - } + Some(subfield_chain) => { + let (_subfield_type, offset_index) = full_type.get_subfield(&subfield_chain)?; + match memory { + Memory::Cell { id: _ } |Memory::MappedCell { id: _, index: _ } => r_panic!("Attempted to get cell reference of subfield of single cell memory: {target}"), + Memory::Cells { id, len } | Memory::MappedCells { id, start_index: _, len } => { + r_assert!(offset_index < *len, "Subfield memory index out of allocation range. This should not occur. ({target})"); + let index = match memory { + Memory::Cells { id: _, len: _ } => offset_index, + Memory::MappedCells { id: _, start_index, len: _ } => *start_index + offset_index, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ } => unreachable!() + }; + CellReference {memory_id: *id, index: Some(index)} + } + } + } }) } - + /// Return the absolute type and memory allocation for a variable name fn get_base_variable_memory(&self, var_name: &str) -> Result<(&ValueType, &Memory), String> { match ( @@ -1216,19 +1217,19 @@ where } } } - + /// Get the absolute type of a full variable target, not just a name like `get_base_variable_memory` fn get_target_type(&self, target: &VariableTarget) -> Result<&ValueType, String> { let (var_type, _memory) = self.get_base_variable_memory(&target.name)?; Ok(match &target.subfields { None => var_type, - Some(subfields) => { - let (subfield_type, _memory_index) = var_type.get_subfield(subfields)?; - subfield_type - } + Some(subfields) => { + let (subfield_type, _memory_index) = var_type.get_subfield(subfields)?; + subfield_type + } }) } - + /// Create memory mapping between a pre-existing variable and a new one, used for function arguments. /// This could be used for copy by reference of subfields in future. fn create_mapped_variable( @@ -1270,118 +1271,118 @@ where // let subfield_size = subfield_type.size(); ( subfield_type, - match (subfield_type, base_var_memory) { - (ValueType::Cell, Memory::Cells { id, len: _ }) => { - // r_assert!((offset_index + subfield_size) <= *len, "Subfield \"{target}\" size and offset out of memory bounds. This should never occur."); - Memory::MappedCell { - id: *id, - index: Some(offset_index), - } - } - ( - ValueType::Cell, - Memory::MappedCells { - id, - start_index, - len: _, - }, - ) => Memory::MappedCell { - id: *id, - index: Some(*start_index + offset_index), - }, - ( - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len: _ }, - ) => Memory::MappedCells { - id: *id, - start_index: offset_index, - len: subfield_type.size()?, - }, - ( - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::MappedCells { - id, - start_index, - len: _, - }, - ) => Memory::MappedCells { - id: *id, - start_index: *start_index + offset_index, - len: subfield_type.size()?, - }, - (_, Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }) => { - r_panic!( - "Attempted to map a subfield of a single cell in \ + match (subfield_type, base_var_memory) { + (ValueType::Cell, Memory::Cells { id, len: _ }) => { + // r_assert!((offset_index + subfield_size) <= *len, "Subfield \"{target}\" size and offset out of memory bounds. This should never occur."); + Memory::MappedCell { + id: *id, + index: Some(offset_index), + } + } + ( + ValueType::Cell, + Memory::MappedCells { + id, + start_index, + len: _, + }, + ) => Memory::MappedCell { + id: *id, + index: Some(*start_index + offset_index), + }, + ( + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len: _ }, + ) => Memory::MappedCells { + id: *id, + start_index: offset_index, + len: subfield_type.size()?, + }, + ( + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::MappedCells { + id, + start_index, + len: _, + }, + ) => Memory::MappedCells { + id: *id, + start_index: *start_index + offset_index, + len: subfield_type.size()?, + }, + (_, Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }) => { + r_panic!( + "Attempted to map a subfield of a single cell in \ mapping: {mapped_var_name} -> {target}" - ) - } - }, + ) + } + }, ) } }; - + self.variable_memory - .insert(mapped_var_name, (var_type.clone(), mapped_memory)); + .insert(mapped_var_name, (var_type.clone(), mapped_memory)); Ok(()) } - + /// Get the final type of an expression. /// (technically unnecessary right now, but can be used to implement expressions as function arguments in future) fn get_expression_type(&self, expr: &Expression) -> Result { Ok(match expr { Expression::NaturalNumber(_) => ValueType::Cell, - Expression::SumExpression { sign: _, summands } => { - let Some(_) = summands.first() else { - r_panic!( - "Cannot infer expression type because sum \ + Expression::SumExpression { sign: _, summands } => { + let Some(_) = summands.first() else { + r_panic!( + "Cannot infer expression type because sum \ expression has no elements: `{expr}`." - ); - }; - // TODO: decide if the summands' types should be verified here or not - for summand in summands { - match self.get_expression_type(summand)? { - ValueType::Cell => (), - summand_type => { - r_panic!( - "Sum expressions must be comprised of cell-types: \ + ); + }; + // TODO: decide if the summands' types should be verified here or not + for summand in summands { + match self.get_expression_type(summand)? { + ValueType::Cell => (), + summand_type => { + r_panic!( + "Sum expressions must be comprised of cell-types: \ found `{summand_type}` in `{expr}`" - ); - } - }; - } - ValueType::Cell - } - Expression::VariableReference(var) => self.get_target_type(var)?.clone(), - Expression::ArrayLiteral(elements) => { - let mut elements_iter = elements.iter(); - let Some(first_element) = elements_iter.next() else { - r_panic!( - "Cannot infer expression type because \ + ); + } + }; + } + ValueType::Cell + } + Expression::VariableReference(var) => self.get_target_type(var)?.clone(), + Expression::ArrayLiteral(elements) => { + let mut elements_iter = elements.iter(); + let Some(first_element) = elements_iter.next() else { + r_panic!( + "Cannot infer expression type because \ array literal has no elements: `{expr}`." - ); - }; - let first_element_type = self.get_expression_type(first_element)?; - for element in elements_iter { - let element_type = self.get_expression_type(element)?; - r_assert!( - element_type == first_element_type, - "All elements in array expressions must have the \ + ); + }; + let first_element_type = self.get_expression_type(first_element)?; + for element in elements_iter { + let element_type = self.get_expression_type(element)?; + r_assert!( + element_type == first_element_type, + "All elements in array expressions must have the \ same type: found `{element_type}` in `{expr}`" - ); - } - ValueType::Array(elements.len(), Box::new(first_element_type)) - } - Expression::StringLiteral(s) => ValueType::Array(s.len(), Box::new(ValueType::Cell)), + ); + } + ValueType::Array(elements.len(), Box::new(first_element_type)) + } + Expression::StringLiteral(s) => ValueType::Array(s.len(), Box::new(ValueType::Cell)), }) } - + /// helper function for a common use-case: /// flatten an expression and add it to a specific cell (using copies and adds, etc) fn _add_expr_to_cell(&mut self, expr: &Expression, cell: CellReference) -> Result<(), String> { let (imm, adds, subs) = expr.flatten()?; - + self.push_instruction(Instruction::AddToCell(cell.clone(), imm)); - + let mut adds_set = HashMap::new(); for var in adds { let n = adds_set.remove(&var).unwrap_or(0); @@ -1391,15 +1392,15 @@ same type: found `{element_type}` in `{expr}`" let n = adds_set.remove(&var).unwrap_or(0); adds_set.insert(var, n - 1); } - + for (source, constant) in adds_set { let source_cell = self.get_cell(&source)?; self._copy_cell(source_cell, cell.clone(), constant); } - + Ok(()) } - + /// helper function to add a self-referencing expression to a cell /// this is separated because it requires another copy ontop of normal expressions // TODO: refactor/fix underlying logic for this @@ -1426,11 +1427,11 @@ same type: found `{element_type}` in `{expr}`" if pre_clear { self.push_instruction(Instruction::ClearCell(cell.clone())); } - + let (imm, adds, subs) = expr.flatten()?; - + self.push_instruction(Instruction::AddToCell(cell.clone(), imm)); - + let mut adds_set = HashMap::new(); for var in adds { let n = adds_set.remove(&var).unwrap_or(0); @@ -1440,7 +1441,7 @@ same type: found `{element_type}` in `{expr}`" let n = adds_set.remove(&var).unwrap_or(0); adds_set.insert(var, n - 1); } - + for (source, constant) in adds_set { let source_cell = self.get_cell(&source)?; //If we have an instance of the original cell being added simply use our temp cell value @@ -1454,10 +1455,10 @@ same type: found `{element_type}` in `{expr}`" //Cleanup self.push_instruction(Instruction::ClearCell(temp_cell)); self.push_instruction(Instruction::Free(temp_mem_id)); - + Ok(()) } - + /// Helper function to copy a cell from one to another, leaving the original unaffected // TODO: make one for draining a cell fn _copy_cell( @@ -1501,156 +1502,156 @@ mod scope_builder_tests { backend::bf::{Opcode, TapeCell}, parser::expressions::Sign, }; - + use super::*; - + #[test] fn variable_allocation_1() { let mut scope = ScopeBuilder::::new(); let allocated_type = scope.allocate_variable(VariableTypeDefinition { name: String::from("var"), - var_type: VariableTypeReference::Cell, - location_specifier: LocationSpecifier::None, + var_type: VariableTypeReference::Cell, + location_specifier: LocationSpecifier::None, }); assert_eq!(allocated_type, Ok(&ValueType::Cell)); } - + #[test] fn get_expression_type_numbers_1() { let scope = ScopeBuilder::::new(); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(0)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(0)) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(1)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(1)) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(345678)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(345678)) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_sums_1() { let scope = ScopeBuilder::::new(); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![Expression::NaturalNumber(0)] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![Expression::NaturalNumber(0)] + }) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Negative, - summands: vec![ - Expression::NaturalNumber(345678), - Expression::NaturalNumber(2) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Negative, + summands: vec![ + Expression::NaturalNumber(345678), + Expression::NaturalNumber(2) + ] + }) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![ - Expression::SumExpression { - sign: Sign::Negative, - summands: vec![ - Expression::NaturalNumber(1), - Expression::NaturalNumber(2) - ] - }, - Expression::NaturalNumber(2) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![ + Expression::SumExpression { + sign: Sign::Negative, + summands: vec![ + Expression::NaturalNumber(1), + Expression::NaturalNumber(2) + ] + }, + Expression::NaturalNumber(2) + ] + }) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_variables_1() { let mut scope = ScopeBuilder::::new(); scope - .allocate_variable(VariableTypeDefinition { - name: String::from("var"), - var_type: VariableTypeReference::Cell, - location_specifier: LocationSpecifier::None, - }) - .unwrap(); + .allocate_variable(VariableTypeDefinition { + name: String::from("var"), + var_type: VariableTypeReference::Cell, + location_specifier: LocationSpecifier::None, + }) + .unwrap(); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("var"), - subfields: None, - is_spread: false - })) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("var"), + subfields: None, + is_spread: false + })) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![ - Expression::VariableReference(VariableTarget { - name: String::from("var"), - subfields: None, - is_spread: false - }), - Expression::NaturalNumber(123) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![ + Expression::VariableReference(VariableTarget { + name: String::from("var"), + subfields: None, + is_spread: false + }), + Expression::NaturalNumber(123) + ] + }) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_arrays_1() { let mut scope = ScopeBuilder::::new(); scope - .allocate_variable(VariableTypeDefinition { - name: String::from("arr"), - var_type: VariableTypeReference::Array(Box::new(VariableTypeReference::Cell), 3), - location_specifier: LocationSpecifier::None, - }) - .unwrap(); + .allocate_variable(VariableTypeDefinition { + name: String::from("arr"), + var_type: VariableTypeReference::Array(Box::new(VariableTypeReference::Cell), 3), + location_specifier: LocationSpecifier::None, + }) + .unwrap(); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("arr"), - subfields: None, - is_spread: false - })) - .unwrap(), - ValueType::Array(3, Box::new(ValueType::Cell)) + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("arr"), + subfields: None, + is_spread: false + })) + .unwrap(), + ValueType::Array(3, Box::new(ValueType::Cell)) ); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("arr"), - subfields: Some(VariableTargetReferenceChain(vec![Reference::Index(0)])), - is_spread: false - })) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("arr"), + subfields: Some(VariableTargetReferenceChain(vec![Reference::Index(0)])), + is_spread: false + })) + .unwrap(), + ValueType::Cell ); } - + // TODO: make failure tests for expression types } diff --git a/compiler/src/misc.rs b/compiler/src/misc.rs index bdbf7c2..a6fa773 100644 --- a/compiler/src/misc.rs +++ b/compiler/src/misc.rs @@ -55,7 +55,7 @@ impl MastermindConfig { optimise_unreachable_loops: (optimise_bitmask & 0b00000100) > 0, // optimise_variable_usage: false, // optimise_memory_allocation: false, - optimise_constants: false, + optimise_constants: (optimise_bitmask & 0b00001000) > 0, optimise_empty_blocks: false, memory_allocation_method: 0, enable_2d_grid: false, From 657407812f14db2717a3bd8c22a29b824e6b507b Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Wed, 24 Dec 2025 02:28:30 +1100 Subject: [PATCH 2/8] Restructure optimisation code, refactor opt tests --- compiler/src/backend/bf2d.rs | 24 +- compiler/src/backend/common.rs | 2 +- compiler/src/backend/mod.rs | 1 + compiler/src/backend/optimiser/bf.rs | 216 +++++++++++++++ compiler/src/backend/optimiser/bf2d.rs | 207 +++++++++++++++ compiler/src/backend/optimiser/mod.rs | 4 + compiler/src/backend/optimiser/tests.rs | 340 ++++++++++++++++++++++++ compiler/src/brainfuck_optimiser.rs | 323 ---------------------- compiler/src/lib.rs | 19 +- compiler/src/main.rs | 19 +- 10 files changed, 801 insertions(+), 354 deletions(-) create mode 100644 compiler/src/backend/optimiser/bf.rs create mode 100644 compiler/src/backend/optimiser/bf2d.rs create mode 100644 compiler/src/backend/optimiser/mod.rs create mode 100644 compiler/src/backend/optimiser/tests.rs diff --git a/compiler/src/backend/bf2d.rs b/compiler/src/backend/bf2d.rs index f099f2c..0ffb1a3 100644 --- a/compiler/src/backend/bf2d.rs +++ b/compiler/src/backend/bf2d.rs @@ -81,18 +81,18 @@ impl BrainfuckProgram for Vec { ops.push(Opcode2D::Clear); i += 3; } else { - match substr.chars().next().unwrap() { - '+' => ops.push(Opcode2D::Add), - '-' => ops.push(Opcode2D::Subtract), - '>' => ops.push(Opcode2D::Right), - '<' => ops.push(Opcode2D::Left), - '[' => ops.push(Opcode2D::OpenLoop), - ']' => ops.push(Opcode2D::CloseLoop), - '.' => ops.push(Opcode2D::Output), - ',' => ops.push(Opcode2D::Input), - '^' => ops.push(Opcode2D::Up), - 'v' => ops.push(Opcode2D::Down), - _ => (), // could put a little special opcode in for other characters + match substr.chars().next() { + Some('+') => ops.push(Opcode2D::Add), + Some('-') => ops.push(Opcode2D::Subtract), + Some('>') => ops.push(Opcode2D::Right), + Some('<') => ops.push(Opcode2D::Left), + Some('[') => ops.push(Opcode2D::OpenLoop), + Some(']') => ops.push(Opcode2D::CloseLoop), + Some('.') => ops.push(Opcode2D::Output), + Some(',') => ops.push(Opcode2D::Input), + Some('^') => ops.push(Opcode2D::Up), + Some('v') => ops.push(Opcode2D::Down), + _ => (), } i += 1; } diff --git a/compiler/src/backend/common.rs b/compiler/src/backend/common.rs index f772389..821d9d4 100644 --- a/compiler/src/backend/common.rs +++ b/compiler/src/backend/common.rs @@ -459,6 +459,6 @@ pub trait BrainfuckBuilder { } pub trait BrainfuckProgram { - fn to_string(self) -> String; fn from_str(s: &str) -> Self; + fn to_string(self) -> String; } diff --git a/compiler/src/backend/mod.rs b/compiler/src/backend/mod.rs index 8f79770..6e26c5e 100644 --- a/compiler/src/backend/mod.rs +++ b/compiler/src/backend/mod.rs @@ -4,3 +4,4 @@ pub mod bf; pub mod bf2d; mod constants_optimiser; +mod optimiser; diff --git a/compiler/src/backend/optimiser/bf.rs b/compiler/src/backend/optimiser/bf.rs new file mode 100644 index 0000000..2aea5c4 --- /dev/null +++ b/compiler/src/backend/optimiser/bf.rs @@ -0,0 +1,216 @@ +use crate::{backend::bf::*, misc::MastermindContext}; +use std::{collections::HashMap, num::Wrapping}; + +impl MastermindContext { + pub fn optimise_bf(&self, ops: Vec) -> Vec { + let mut output = Vec::new(); + + // get stretch of characters to optimise (+-<>) + let mut i = 0; + let mut subset = Vec::new(); + while i < ops.len() { + let op = ops[i]; + match op { + Opcode::Add | Opcode::Subtract | Opcode::Right | Opcode::Left | Opcode::Clear => { + subset.push(op); + } + Opcode::OpenLoop | Opcode::CloseLoop | Opcode::Input | Opcode::Output => { + // optimise subset and push + let optimised_subset = self.optimise_bf_subset(subset); + output.extend(optimised_subset); + + subset = Vec::new(); + output.push(op); + } + } + i += 1; + } + + output + } + + fn optimise_bf_subset(&self, run: Vec) -> Vec { + #[derive(Clone)] + enum Change { + Add(Wrapping), + Set(Wrapping), + } + let mut tape: HashMap = HashMap::new(); + let mut head: i32 = 0; + + let mut i = 0; + while i < run.len() { + let op = run[i]; + match op { + Opcode::Clear => { + tape.insert(head, Change::Set(Wrapping(0i8))); + } + Opcode::Subtract | Opcode::Add => { + let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8))); + + let (Change::Add(val) | Change::Set(val)) = &mut change; + *val += match op { + Opcode::Add => 1, + Opcode::Subtract => -1, + _ => 0, + }; + + match &change { + Change::Add(val) => { + if *val != Wrapping(0i8) { + tape.insert(head, change); + } + } + Change::Set(_) => { + tape.insert(head, change); + } + } + } + Opcode::Right => { + head += 1; + } + Opcode::Left => { + head -= 1; + } + _ => (), + } + i += 1; + } + // always have a start and end cell + if !tape.contains_key(&0) { + tape.insert(0, Change::Add(Wrapping(0i8))); + } + if !tape.contains_key(&head) { + tape.insert(head, Change::Add(Wrapping(0i8))); + } + + // This whole algorithm is probably really efficient and I reckon there's almost certainly a better way + // It's also just really poorly done in general, I don't understand what everything does and I wrote the damned thing + // TODO: refactor this properly + // convert hashmap to array + // start by making a negative and positive array + let mut pos_arr = Vec::new(); + let mut neg_arr = Vec::new(); + for (cell, value) in tape.into_iter() { + let i: usize; + let arr: &mut Vec; + if cell < 0 { + i = (-(cell + 1)) as usize; + arr = &mut neg_arr; + } else { + i = cell as usize; + arr = &mut pos_arr; + } + + if i >= arr.len() { + arr.resize(i + 1, Change::Add(Wrapping(0i8))); + } + arr[i] = value; + } + let start_index = neg_arr.len(); + // now combine the two arrays + let mut tape_arr: Vec = Vec::new(); + tape_arr.extend(neg_arr.into_iter().rev()); + tape_arr.extend(pos_arr.into_iter()); + + if ((start_index) + 1) >= (tape_arr.len()) { + tape_arr.resize(start_index + 1, Change::Add(Wrapping(0i8))); + } + let final_index = ((start_index as i32) + head) as usize; + + let mut output = Vec::new(); + + // Also this following algorithm for zig-zagging around the tape is pretty poor as well, there has to be a nicer way of doing it + + // if final cell is to the right of the start cell then we need to go to the left first, and vice-versa + // 1. go to the furthest point on the tape (opposite of direction to final cell) + // 2. go from that end of the tape to the opposite end of the tape + // 3. return to the final cell + let mut idx = start_index; + let d2 = final_index >= start_index; + let d1 = !d2; + let d3 = !d2; + + //1 + match d1 { + true => { + for _ in start_index..(tape_arr.len() - 1) { + output.push(Opcode::Right); + idx += 1; + } + } + false => { + for _ in (1..=start_index).rev() { + output.push(Opcode::Left); + idx -= 1; + } + } + } + + //2 + match d2 { + true => { + for cell in idx..tape_arr.len() { + let change = &tape_arr[cell]; + if let Change::Set(_) = change { + output.push(Opcode::Clear); + } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + + for _ in 0..(v as i32).abs() { + output.push(match v > 0 { + true => Opcode::Add, + false => Opcode::Subtract, + }); + } + + if cell < (tape_arr.len() - 1) { + output.push(Opcode::Right); + idx += 1; + } + } + } + false => { + for cell in (0..=idx).rev() { + let change = &tape_arr[cell]; + if let Change::Set(_) = change { + output.push(Opcode::Clear); + } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + + for _ in 0..(v as i32).abs() { + output.push(match v > 0 { + true => Opcode::Add, + false => Opcode::Subtract, + }); + } + + if cell > 0 { + output.push(Opcode::Left); + idx -= 1; + } + } + } + } + + //3 + match d3 { + true => { + for _ in idx..final_index { + output.push(Opcode::Right); + idx += 1; + } + } + false => { + for _ in final_index..idx { + output.push(Opcode::Left); + idx -= 1; + } + } + } + + output + } +} diff --git a/compiler/src/backend/optimiser/bf2d.rs b/compiler/src/backend/optimiser/bf2d.rs new file mode 100644 index 0000000..18ad2a0 --- /dev/null +++ b/compiler/src/backend/optimiser/bf2d.rs @@ -0,0 +1,207 @@ +use crate::{backend::bf2d::*, misc::MastermindContext}; +use itertools::Itertools; +use std::{collections::HashMap, num::Wrapping}; + +impl MastermindContext { + // TODO: make deterministic! + pub fn optimise_bf2d(&self, program: Vec) -> Vec { + let mut output = Vec::new(); + + // get stretch of characters to optimise (+-<>) + let mut i = 0; + let mut subset = Vec::new(); + while i < program.len() { + let op = program[i]; + match op { + Opcode2D::Add + | Opcode2D::Subtract + | Opcode2D::Right + | Opcode2D::Left + | Opcode2D::Clear + | Opcode2D::Up + | Opcode2D::Down => { + subset.push(op); + } + Opcode2D::OpenLoop | Opcode2D::CloseLoop | Opcode2D::Input | Opcode2D::Output => { + // optimise subset and push + let optimised_subset = optimise_bf2d_subset( + subset, + // TODO: make this automatically decide rather than configuring + self.config.optimise_generated_all_permutations, + ); + output.extend(optimised_subset); + + subset = Vec::new(); + output.push(op); + } + } + i += 1; + } + + output + } +} + +fn optimise_bf2d_subset(run: Vec, all_perms: bool) -> Vec { + #[derive(Clone)] + enum Change { + Add(Wrapping), + Set(Wrapping), + } + let mut tape: HashMap = HashMap::new(); + let start = TapeCell2D(0, 0); + let mut head = TapeCell2D(0, 0); + let mut i = 0; + // simulate the subprogram to find the exact changes made to the tape + while i < run.len() { + let op = run[i]; + match op { + Opcode2D::Clear => { + tape.insert(head, Change::Set(Wrapping(0i8))); + } + Opcode2D::Subtract | Opcode2D::Add => { + let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8))); + + let (Change::Add(val) | Change::Set(val)) = &mut change; + *val += match op { + Opcode2D::Add => 1, + Opcode2D::Subtract => -1, + _ => 0, + }; + + match &change { + Change::Add(val) => { + if *val != Wrapping(0i8) { + tape.insert(head, change); + } + } + Change::Set(_) => { + tape.insert(head, change); + } + } + } + Opcode2D::Right => { + head.0 += 1; + } + Opcode2D::Left => { + head.0 -= 1; + } + Opcode2D::Up => { + head.1 += 1; + } + Opcode2D::Down => { + head.1 -= 1; + } + _ => (), + } + i += 1; + } + let mut output = Vec::new(); + if all_perms { + //Exhaustive approach checks all permutations + let mut output_length = i32::MAX; + let mut best_permutation = Vec::new(); + for perm in tape.iter().permutations(tape.len()) { + let mut position = start; + let mut current_output_length = 0; + //Calculate the distance of this + for (cell, _) in &perm { + current_output_length += (cell.0 - position.0).abs(); + current_output_length += (cell.1 - position.1).abs(); + position = **cell; + if current_output_length > output_length { + break; + } + } + if current_output_length > output_length { + continue; + } + //Add the distance to the finishing location + current_output_length += (head.0 - position.0).abs(); + current_output_length += (head.1 - position.1).abs(); + if current_output_length < output_length { + best_permutation = perm; + output_length = current_output_length; + } + } + let mut position = start; + for (cell, change) in best_permutation { + output = _move_position(output, &position, cell); + position = *cell; + if let Change::Set(_) = change { + output.push(Opcode2D::Clear); + } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + for _ in 0..(v as i32).abs() { + output.push(match v == -128 || v > 0 { + true => Opcode2D::Add, + false => Opcode2D::Subtract, + }); + } + } + output = _move_position(output, &position, &head); + } else { + //Greedy approach faster for bigger datasets + let mut position = start; + //For the number of cells navigate to the nearest cell + for _ in 0..tape.len() { + if !tape.is_empty() { + let mut min_distance = i32::MAX; + let mut next_position = TapeCell2D(0, 0); + for (cell, _value) in tape.iter() { + if (cell.0 - position.0).abs() + (cell.1 - position.1).abs() < min_distance { + min_distance = (cell.0 - position.0).abs() + (cell.1 - position.1).abs(); + next_position = *cell; + } + } + // Move to next position + output = _move_position(output, &position, &next_position); + position = next_position; + //Now Update the output with correct opcodes + let change = tape.remove(&next_position).unwrap(); + if let Change::Set(_) = change { + output.push(Opcode2D::Clear); + } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + for _ in 0..(v as i32).abs() { + output.push(match v == -128 || v > 0 { + true => Opcode2D::Add, + false => Opcode2D::Subtract, + }); + } + } + } + output = _move_position(output, &position, &head); + } + output +} + +fn _move_position( + mut program: Vec, + old_position: &TapeCell2D, + new_position: &TapeCell2D, +) -> Vec { + if old_position != new_position { + if old_position.0 < new_position.0 { + for _ in 0..(new_position.0 - old_position.0) { + program.push(Opcode2D::Right); + } + } else { + for _ in 0..(old_position.0 - new_position.0) { + program.push(Opcode2D::Left); + } + } + if old_position.1 < new_position.1 { + for _ in 0..(new_position.1 - old_position.1) { + program.push(Opcode2D::Up); + } + } else { + for _ in 0..(old_position.1 - new_position.1) { + program.push(Opcode2D::Down); + } + } + } + program +} diff --git a/compiler/src/backend/optimiser/mod.rs b/compiler/src/backend/optimiser/mod.rs new file mode 100644 index 0000000..f76b889 --- /dev/null +++ b/compiler/src/backend/optimiser/mod.rs @@ -0,0 +1,4 @@ +pub mod bf; +pub mod bf2d; + +mod tests; diff --git a/compiler/src/backend/optimiser/tests.rs b/compiler/src/backend/optimiser/tests.rs new file mode 100644 index 0000000..f1efb66 --- /dev/null +++ b/compiler/src/backend/optimiser/tests.rs @@ -0,0 +1,340 @@ +#![cfg(test)] + +use crate::{ + backend::{bf::*, bf2d::*, common::BrainfuckProgram}, + misc::{MastermindConfig, MastermindContext}, +}; + +const CTX_OPT: MastermindContext = MastermindContext { + config: MastermindConfig { + optimise_generated_code: true, + optimise_generated_all_permutations: false, + optimise_cell_clearing: false, + optimise_unreachable_loops: false, + + // optimise_variable_usage: false, + + // optimise_memory_allocation: false, + optimise_constants: false, + optimise_empty_blocks: false, + memory_allocation_method: 0, + enable_2d_grid: false, + }, +}; + +const CTX_OPT_EXHAUSTIVE: MastermindContext = MastermindContext { + config: MastermindConfig { + optimise_generated_code: true, + optimise_generated_all_permutations: true, + optimise_cell_clearing: false, + optimise_unreachable_loops: false, + + // optimise_variable_usage: false, + + // optimise_memory_allocation: false, + optimise_constants: false, + optimise_empty_blocks: false, + memory_allocation_method: 0, + enable_2d_grid: false, + }, +}; + +// TODO: +// fn _characteristic_test() + +#[test] +fn standard_0() { + let v: Vec = BrainfuckProgram::from_str("+++>><<++>--->+++<><><><><<<<<+++[>>>]"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), "+++++>--->+++<<<<<+++[>>>]".len()); +} + +#[test] +fn standard_1() { + let v: Vec = BrainfuckProgram::from_str("<><><>++<+[--++>>+<<-]"); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "++<+[->>+<<]"); +} + +#[test] +fn standard_2() { + let v: Vec = BrainfuckProgram::from_str( + "+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "+++++++++>>+++++++>---->>>++<<<<[>++<]"); +} + +#[test] +fn standard_3() { + let v: Vec = BrainfuckProgram::from_str(">><."); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, ">."); +} + +#[test] +fn standard_4() { + let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[>.<+]"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), "[-]+++<+++>[>.<+]".len()); +} + +#[test] +fn standard_5() { + let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[-]<[-]--+>-[>,]"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), "[-]-<[-]->[>,]".len()); +} + +#[test] +fn standard_6() { + let v: Vec = BrainfuckProgram::from_str( + "+++++[-]+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "[-]+++++++++>>+++++++>---->>>++<<<<[[-]+>++<]"); +} + +#[test] +fn greedy_2d_0() { + let v: Vec = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++[>>>>>>>]"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "+++++^---^+++vvvvv+++[>>>>>>>]"); +} + +#[test] +fn greedy_2d_1() { + let v: Vec = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "++v+[-^^+vv]"); +} + +#[test] +fn greedy_2d_2() { + let v: Vec = BrainfuckProgram::from_str( + "+++++++++^^+++^----^^^++++--v--++vvhellov++++[-v+^^++v+v-^]++---^+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "+++++++++^^+++++++^----^^^++vvvv[^++v]"); +} + +#[test] +fn greedy_2d_3() { + let v: Vec = BrainfuckProgram::from_str("^^v."); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "^."); +} + +#[test] +fn greedy_2d_4() { + let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++,"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "[-]+++v+++^,"); +} + +#[test] +fn greedy_2d_5() { + let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-,,,,..."); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "[-]-v[-]-^,,,,..."); +} + +#[test] +fn greedy_2d_6() { + let v: Vec = BrainfuckProgram::from_str( + "+++++[-]+++++++++^^+++^----^^^++++--v--++vvhellov++++[[-]v+^^++v+v-^]++---^+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o, "[-]+++++++++^^+++++++^----^^^++vvvv[[-]+^++v]"); +} + +#[test] +fn exhaustive_2d_0() { + let v: Vec = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++,"); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), "^^+++v---v+++++vvv+++,".len()); +} + +#[test] +fn exhaustive_2d_1() { + let v: Vec = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); + let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), "++v+[^^+vv-]".len()); +} + +#[test] +fn exhaustive_2d_2() { + let v: Vec = BrainfuckProgram::from_str( + "+++++++++^^+++^----^^^++++--v--++vvhellov++++[-v+^^++v+v-^]++---^+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), "+++++++++^^+++++++^----^^^++vvvv[^++v]".len()); +} + +#[test] +fn exhaustive_2d_3() { + let v: Vec = BrainfuckProgram::from_str("^^v."); + let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o, "^."); +} + +#[test] +fn exhaustive_2d_4() { + let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++."); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), "[-]+++v+++^.".len()); +} + +#[test] +fn exhaustive_2d_5() { + let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-."); + //(3) 0 0 [5] -3 3 + let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), "[-]-v[-]-^.".len()); +} + +#[test] +fn exhaustive_2d_6() { + let v: Vec = BrainfuckProgram::from_str( + "+++++[-]+++++++++^^+++^----^^^++++--v--++vvhellov++++[[-]v+^^++v+v-^]++---^+", + ); + // [9] 0 (7) -4 0 0 2 + // [(0)] 2 + // -1 1 + let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); + assert_eq!( + o.len(), + "[-]+++++++++^^^^^^++vvv----v+++++++[^++v[-]+]".len() + ); +} + +#[test] +fn wrapping_0() { + let v: Vec = BrainfuckProgram::from_str( + "-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.", + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_1() { + let v: Vec = BrainfuckProgram::from_str( + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++,", + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 128 + 1); +} + +#[test] +fn wrapping_2() { + let v: Vec = BrainfuckProgram::from_str( + "+--------------------------------------------------------------------------------------------------------------------------------." + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_3() { + let v: Vec = BrainfuckProgram::from_str( + "--------------------------------------------------------------------------------------------------------------------------------," + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 128 + 1); +} + +#[test] +fn wrapping_3a() { + let v: Vec = BrainfuckProgram::from_str( + "- --------------------------------------------------------------------------------------------------------------------------------." + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_4() { + let v: Vec = BrainfuckProgram::from_str( + "[-]--------------------------------------------------------------------------------------------------------------------------------." + ); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o.len(), 131 + 1); +} + +#[test] +fn wrapping_0_2d() { + let v: Vec = BrainfuckProgram::from_str( + "-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++,", + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_1_2d() { + let v: Vec = BrainfuckProgram::from_str( + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.", + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 128 + 1); +} + +#[test] +fn wrapping_2_2d() { + let v: Vec = BrainfuckProgram::from_str( + "+--------------------------------------------------------------------------------------------------------------------------------," + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_3_2d() { + let v: Vec = BrainfuckProgram::from_str( + "--------------------------------------------------------------------------------------------------------------------------------." + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 128 + 1); +} + +#[test] +fn wrapping_3a_2d() { + let v: Vec = BrainfuckProgram::from_str( + "- --------------------------------------------------------------------------------------------------------------------------------," + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 127 + 1); +} + +#[test] +fn wrapping_4_2d() { + let v: Vec = BrainfuckProgram::from_str( + "[-]--------------------------------------------------------------------------------------------------------------------------------." + ); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + assert_eq!(o.len(), 131 + 1); +} diff --git a/compiler/src/brainfuck_optimiser.rs b/compiler/src/brainfuck_optimiser.rs index 9b5d153..5d73e47 100644 --- a/compiler/src/brainfuck_optimiser.rs +++ b/compiler/src/brainfuck_optimiser.rs @@ -208,326 +208,3 @@ fn _move_position( } program } - -#[cfg(test)] -mod bf_optimiser_tests { - use crate::{ - backend::common::BrainfuckProgram, - misc::{MastermindConfig, MastermindContext}, - }; - - const CTX_OPT: MastermindContext = MastermindContext { - config: MastermindConfig { - optimise_generated_code: true, - optimise_generated_all_permutations: false, - optimise_cell_clearing: false, - optimise_unreachable_loops: false, - // optimise_variable_usage: false, - // optimise_memory_allocation: false, - optimise_constants: false, - optimise_empty_blocks: false, - memory_allocation_method: 0, - enable_2d_grid: false, - }, - }; - - const CTX_OPT_EXHAUSTIVE: MastermindContext = MastermindContext { - config: MastermindConfig { - optimise_generated_code: true, - optimise_generated_all_permutations: true, - optimise_cell_clearing: false, - optimise_unreachable_loops: false, - // optimise_variable_usage: false, - // optimise_memory_allocation: false, - optimise_constants: false, - optimise_empty_blocks: false, - memory_allocation_method: 0, - enable_2d_grid: false, - }, - }; - - #[test] - fn greedy_subset_equivalence_test_0() { - let v = BrainfuckProgram::from_str("+++>><<++>--->+++<><><><><<<<<+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "+++++>--->+++<<<<<+++"); - } - - #[test] - fn greedy_program_equivalence_test_0() { - let v = BrainfuckProgram::from_str("<><><>++<+[--++>>+<<-]"); - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "++<+[->>+<<]"); - } - - #[test] - fn greedy_program_equivalence_test_1() { - let v = BrainfuckProgram::from_str( - "+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "+++++++++>>+++++++>---->>>++<<<<[>++<]"); - } - - #[test] - fn greedy_program_equivalence_test_2() { - let v = BrainfuckProgram::from_str(">><."); - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, ">."); - } - - #[test] - fn greedy_subset_equivalence_test_1() { - let v = BrainfuckProgram::from_str("+++<+++>[-]+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "[-]+++<+++>"); - } - - #[test] - fn greedy_subset_equivalence_test_2() { - let v = BrainfuckProgram::from_str("+++<+++>[-]+++[-]<[-]--+>-"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "[-]-<[-]->"); - } - - #[test] - fn greedy_program_equivalence_test_3() { - let v = BrainfuckProgram::from_str( - "+++++[-]+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "[-]+++++++++>>+++++++>---->>>++<<<<[[-]+>++<]"); - } - - #[test] - fn greedy_two_dimensional_subset_equivalence_test_0() { - let v = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "+++++^---^+++vvvvv+++"); - } - - #[test] - fn greedy_two_dimensional_program_equivalence_test_0() { - let v = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "++v+[-^^+vv]"); - } - - #[test] - fn greedy_two_dimensional_program_equivalence_test_1() { - let v = BrainfuckProgram::from_str( - "+++++++++^^+++^----^^^++++--v--++vvhellov++++[-v+^^++v+v-^]++---^+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "+++++++++^^+++++++^----^^^++vvvv[^++v]"); - } - - #[test] - fn greedy_two_dimensional_program_equivalence_test_2() { - let v = BrainfuckProgram::from_str("^^v."); - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "^."); - } - - #[test] - fn greedy_two_dimensional_subset_equivalence_test_1() { - let v = BrainfuckProgram::from_str("+++v+++^[-]+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "[-]+++v+++^"); - } - - #[test] - fn greedy_two_dimensional_subset_equivalence_test_2() { - let v = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT.optimise_subset(v).to_string(); - assert_eq!(o, "[-]-v[-]-^"); - } - - #[test] - fn greedy_two_dimensional_program_equivalence_test_3() { - let v = BrainfuckProgram::from_str( - "+++++[-]+++++++++^^+++^----^^^++++--v--++vvhellov++++[[-]v+^^++v+v-^]++---^+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT.optimise_bf_code(v).to_string(); - assert_eq!(o, "[-]+++++++++^^+++++++^----^^^++vvvv[[-]+^++v]"); - } - - #[test] - #[ignore] - fn exhaustive_subset_equivalence_test_0() { - let v = BrainfuckProgram::from_str("+++>><<++>--->+++<><><><><<<<<+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, ">--->+++<<+++++<<<+++"); - } - - #[test] - #[ignore] - fn exhaustive_program_equivalence_test_0() { - let v = BrainfuckProgram::from_str("<><><>++<+[--++>>+<<-]"); - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "++<+[>>+<<-]"); - } - - #[test] - #[ignore] - fn exhaustive_program_equivalence_test_1() { - let v = BrainfuckProgram::from_str( - "+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "+++++++++>>+++++++>>>>++<<<----<[>++<]"); - } - - #[test] - #[ignore] - fn exhaustive_program_equivalence_test_2() { - let v = BrainfuckProgram::from_str(">><."); - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, ">."); - } - - #[test] - #[ignore] - fn exhaustive_subset_equivalence_test_1() { - let v = BrainfuckProgram::from_str("+++<+++>[-]+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, "[-]+++<+++>"); - } - - #[test] - #[ignore] - fn exhaustive_subset_equivalence_test_2() { - let v = BrainfuckProgram::from_str("+++<+++>[-]+++[-]<[-]--+>-"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, "[-]-<[-]->"); - } - - #[test] - #[ignore] - fn exhaustive_program_equivalence_test_3() { - let v = BrainfuckProgram::from_str( - "+++++[-]+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "[-]+++++++++>>+++++++>---->>>++<<<<[[-]+>++<]"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_subset_equivalence_test_0() { - let v = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, "^^+++v---v+++++vvv+++"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_program_equivalence_test_0() { - let v = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "++v+[^^+vv-]"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_program_equivalence_test_1() { - let v = BrainfuckProgram::from_str( - "+++++++++^^+++^----^^^++++--v--++vvhellov++++[-v+^^++v+v-^]++---^+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "+++++++++^^+++++++^----^^^++vvvv[^++v]"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_program_equivalence_test_2() { - let v = BrainfuckProgram::from_str("^^v."); - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "^."); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_subset_equivalence_test_1() { - let v = BrainfuckProgram::from_str("+++v+++^[-]+++"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, "[-]+++v+++^"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_subset_equivalence_test_2() { - let v = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-"); //(3) 0 0 [5] -3 3 - let o = CTX_OPT_EXHAUSTIVE.optimise_subset(v).to_string(); - assert_eq!(o, "[-]-v[-]-^"); - } - - #[test] - #[ignore] - fn exhaustive_two_dimensional_program_equivalence_test_3() { - let v = BrainfuckProgram::from_str( - "+++++[-]+++++++++^^+++^----^^^++++--v--++vvhellov++++[[-]v+^^++v+v-^]++---^+", - ); // [9] 0 (7) -4 0 0 2 // [(0)] 2 // -1 1 - let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf_code(v).to_string(); - assert_eq!(o, "[-]+++++++++^^^^^^++vvv----v+++++++[^++v[-]+]"); - } - - fn subset_edge_case_0() { - let v = BrainfuckProgram::from_str( - "-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 127); - } - - #[test] - fn subset_edge_case_1() { - let v = BrainfuckProgram::from_str( - "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 128); - } - - #[test] - fn subset_edge_case_2() { - let v = BrainfuckProgram::from_str( - "+--------------------------------------------------------------------------------------------------------------------------------" - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 127); - } - - #[test] - fn subset_edge_case_3() { - let v = BrainfuckProgram::from_str( - "--------------------------------------------------------------------------------------------------------------------------------" - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 128); - } - - #[test] - fn subset_edge_case_3a() { - let v = BrainfuckProgram::from_str( - "- --------------------------------------------------------------------------------------------------------------------------------" - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 127); - } - - #[test] - fn subset_edge_case_4() { - let v = BrainfuckProgram::from_str( - "[-]--------------------------------------------------------------------------------------------------------------------------------" - ); - let o: String = CTX_OPT.optimise_subset(v).to_string(); - println!("{o}"); - assert_eq!(o.len(), 131); - } -} diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 66dfd01..8a4fca6 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -4,7 +4,6 @@ // project dependencies: mod backend; mod brainfuck; -mod brainfuck_optimiser; mod frontend; mod macros; mod misc; @@ -56,19 +55,21 @@ pub fn wasm_compile( let parsed_syntax = parse_program::(&stripped_file)?; let instructions = ctx.create_ir_scope(&parsed_syntax, None)?.build_ir(false); let bf_code = ctx.ir_to_bf(instructions, None)?; - Ok(bf_code.to_string()) + Ok(match ctx.config.optimise_generated_code { + true => ctx.optimise_bf2d(bf_code), + false => bf_code, + } + .to_string()) } else { let parsed_syntax = parse_program::(&stripped_file)?; let instructions = ctx.create_ir_scope(&parsed_syntax, None)?.build_ir(false); let bf_code = ctx.ir_to_bf(instructions, None)?; - Ok(bf_code.to_string()) + Ok(match ctx.config.optimise_generated_code { + true => ctx.optimise_bf(bf_code), + false => bf_code, + } + .to_string()) } - - // TODO: fix optimisations - // Ok(match ctx.config.optimise_generated_code { - // true => ctx.optimise_bf_code(bf_code).to_string(), - // false => bf_code.to_string(), - // }) } #[wasm_bindgen] diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 685201f..5b1a810 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -4,7 +4,6 @@ // project dependencies: mod backend; mod brainfuck; -mod brainfuck_optimiser; mod frontend; #[macro_use] mod macros; @@ -100,19 +99,21 @@ fn main() -> Result<(), String> { let parsed_syntax = parse_program::(&stripped_program)?; let instructions = ctx.create_ir_scope(&parsed_syntax, None)?.build_ir(false); let bf_code = ctx.ir_to_bf(instructions, None)?; - bf_code.to_string() + match ctx.config.optimise_generated_code { + true => ctx.optimise_bf2d(bf_code), + false => bf_code, + } + .to_string() } else { let parsed_syntax = parse_program::(&stripped_program)?; let instructions = ctx.create_ir_scope(&parsed_syntax, None)?.build_ir(false); let bf_code = ctx.ir_to_bf(instructions, None)?; - bf_code.to_string() + match ctx.config.optimise_generated_code { + true => ctx.optimise_bf(bf_code), + false => bf_code, + } + .to_string() } - - // TODO: fix optimisations - // match ctx.config.optimise_generated_code { - // true => ctx.optimise_bf_code(bf_code).to_string(), - // false => bf_code.to_string(), - // } } false => program, }; From 3db1e8c2cbca03996b3eaae9729e19cc4a0329f8 Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Wed, 24 Dec 2025 02:59:12 +1100 Subject: [PATCH 3/8] Add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63f0275 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Heathcorp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 3637800075cead1cf7632cbeb8b5527e494d2417 Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Wed, 24 Dec 2025 03:11:23 +1100 Subject: [PATCH 4/8] Add top level movement tests for bf optimiser --- compiler/src/backend/optimiser/tests.rs | 65 ++++++++++++------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/compiler/src/backend/optimiser/tests.rs b/compiler/src/backend/optimiser/tests.rs index f1efb66..bdbd453 100644 --- a/compiler/src/backend/optimiser/tests.rs +++ b/compiler/src/backend/optimiser/tests.rs @@ -49,14 +49,12 @@ fn standard_0() { let o = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), "+++++>--->+++<<<<<+++[>>>]".len()); } - #[test] fn standard_1() { let v: Vec = BrainfuckProgram::from_str("<><><>++<+[--++>>+<<-]"); let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o, "++<+[->>+<<]"); } - #[test] fn standard_2() { let v: Vec = BrainfuckProgram::from_str( @@ -68,14 +66,12 @@ fn standard_2() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o, "+++++++++>>+++++++>---->>>++<<<<[>++<]"); } - #[test] fn standard_3() { let v: Vec = BrainfuckProgram::from_str(">><."); let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o, ">."); } - #[test] fn standard_4() { let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[>.<+]"); @@ -83,7 +79,6 @@ fn standard_4() { let o = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), "[-]+++<+++>[>.<+]".len()); } - #[test] fn standard_5() { let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[-]<[-]--+>-[>,]"); @@ -91,7 +86,6 @@ fn standard_5() { let o = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), "[-]-<[-]->[>,]".len()); } - #[test] fn standard_6() { let v: Vec = BrainfuckProgram::from_str( @@ -111,14 +105,12 @@ fn greedy_2d_0() { let o = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "+++++^---^+++vvvvv+++[>>>>>>>]"); } - #[test] fn greedy_2d_1() { let v: Vec = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "++v+[-^^+vv]"); } - #[test] fn greedy_2d_2() { let v: Vec = BrainfuckProgram::from_str( @@ -130,14 +122,12 @@ fn greedy_2d_2() { let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "+++++++++^^+++++++^----^^^++vvvv[^++v]"); } - #[test] fn greedy_2d_3() { let v: Vec = BrainfuckProgram::from_str("^^v."); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "^."); } - #[test] fn greedy_2d_4() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++,"); @@ -145,7 +135,6 @@ fn greedy_2d_4() { let o = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "[-]+++v+++^,"); } - #[test] fn greedy_2d_5() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-,,,,..."); @@ -153,7 +142,6 @@ fn greedy_2d_5() { let o = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o, "[-]-v[-]-^,,,,..."); } - #[test] fn greedy_2d_6() { let v: Vec = BrainfuckProgram::from_str( @@ -173,14 +161,12 @@ fn exhaustive_2d_0() { let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "^^+++v---v+++++vvv+++,".len()); } - #[test] fn exhaustive_2d_1() { let v: Vec = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "++v+[^^+vv-]".len()); } - #[test] fn exhaustive_2d_2() { let v: Vec = BrainfuckProgram::from_str( @@ -192,14 +178,12 @@ fn exhaustive_2d_2() { let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "+++++++++^^+++++++^----^^^++vvvv[^++v]".len()); } - #[test] fn exhaustive_2d_3() { let v: Vec = BrainfuckProgram::from_str("^^v."); let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o, "^."); } - #[test] fn exhaustive_2d_4() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++."); @@ -207,7 +191,6 @@ fn exhaustive_2d_4() { let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "[-]+++v+++^.".len()); } - #[test] fn exhaustive_2d_5() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-."); @@ -215,7 +198,6 @@ fn exhaustive_2d_5() { let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "[-]-v[-]-^.".len()); } - #[test] fn exhaustive_2d_6() { let v: Vec = BrainfuckProgram::from_str( @@ -239,7 +221,6 @@ fn wrapping_0() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] fn wrapping_1() { let v: Vec = BrainfuckProgram::from_str( @@ -248,7 +229,6 @@ fn wrapping_1() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), 128 + 1); } - #[test] fn wrapping_2() { let v: Vec = BrainfuckProgram::from_str( @@ -257,7 +237,6 @@ fn wrapping_2() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] fn wrapping_3() { let v: Vec = BrainfuckProgram::from_str( @@ -266,7 +245,6 @@ fn wrapping_3() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), 128 + 1); } - #[test] fn wrapping_3a() { let v: Vec = BrainfuckProgram::from_str( @@ -275,7 +253,6 @@ fn wrapping_3a() { let o: String = CTX_OPT.optimise_bf(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] fn wrapping_4() { let v: Vec = BrainfuckProgram::from_str( @@ -286,55 +263,75 @@ fn wrapping_4() { } #[test] -fn wrapping_0_2d() { +fn wrapping_2d_0() { let v: Vec = BrainfuckProgram::from_str( "-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++,", ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] -fn wrapping_1_2d() { +fn wrapping_2d_1() { let v: Vec = BrainfuckProgram::from_str( "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.", ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 128 + 1); } - #[test] -fn wrapping_2_2d() { +fn wrapping_2d_2() { let v: Vec = BrainfuckProgram::from_str( "+--------------------------------------------------------------------------------------------------------------------------------," ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] -fn wrapping_3_2d() { +fn wrapping_2d_3() { let v: Vec = BrainfuckProgram::from_str( "--------------------------------------------------------------------------------------------------------------------------------." ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 128 + 1); } - #[test] -fn wrapping_3a_2d() { +fn wrapping_2d_3a() { let v: Vec = BrainfuckProgram::from_str( "- --------------------------------------------------------------------------------------------------------------------------------," ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 127 + 1); } - #[test] -fn wrapping_4_2d() { +fn wrapping_2d_4() { let v: Vec = BrainfuckProgram::from_str( "[-]--------------------------------------------------------------------------------------------------------------------------------." ); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); assert_eq!(o.len(), 131 + 1); } + +#[test] +fn offset_toplevel_0() { + let v: Vec = BrainfuckProgram::from_str("++>>>++++<<<-->>>."); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "++++."); +} +#[test] +fn offset_toplevel_0a() { + let v: Vec = BrainfuckProgram::from_str("[++>>>++++<<<-->>>.]"); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "[>>>++++.]"); +} +#[test] +fn offset_toplevel_1() { + let v: Vec = BrainfuckProgram::from_str(">>++>-+<++<[++>>>++++<<<-->>>.]<<"); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "++++<[>>>++++.]"); +} +#[test] +fn offset_toplevel_2() { + let v: Vec = BrainfuckProgram::from_str("[++>>>++++<<<-->>>.]>>>+++<<"); + let o: String = CTX_OPT.optimise_bf(v).to_string(); + assert_eq!(o, "[>>>++++.]"); +} From 690154e9a0028af758576f6b792d2c0fb1628778 Mon Sep 17 00:00:00 2001 From: Missing Date: Tue, 23 Dec 2025 12:29:05 -0600 Subject: [PATCH 5/8] Used rustfmt on frontend.rs --- compiler/src/frontend/frontend.rs | 885 +++++++++++++++--------------- 1 file changed, 444 insertions(+), 441 deletions(-) diff --git a/compiler/src/frontend/frontend.rs b/compiler/src/frontend/frontend.rs index 9866b77..8936d3b 100644 --- a/compiler/src/frontend/frontend.rs +++ b/compiler/src/frontend/frontend.rs @@ -27,15 +27,15 @@ impl MastermindContext { outer_scope: Option<&'a ScopeBuilder>, ) -> Result, String> where - BrainfuckBuilderData: BrainfuckBuilder, - CellAllocatorData: CellAllocator, + BrainfuckBuilderData: BrainfuckBuilder, + CellAllocatorData: CellAllocator, { let mut scope = if let Some(outer) = outer_scope { outer.open_inner() } else { ScopeBuilder::new() }; - + // TODO: fix unnecessary clones, and reimplement this with iterators somehow // hoist structs, then functions to top let mut filtered_clauses_1 = vec![]; @@ -67,7 +67,7 @@ impl MastermindContext { } } } - + for clause in filtered_clauses_2 { match clause { Clause::DeclareVariable { var } => { @@ -77,29 +77,29 @@ impl MastermindContext { Clause::DefineVariable { var, value } => { // same as above except we initialise the variable let absolute_type = scope.allocate_variable(var.clone())?; - + match (absolute_type, &value) { ( ValueType::Cell, - Expression::NaturalNumber(_) - | Expression::SumExpression { - sign: _, - summands: _, - } - | Expression::VariableReference(_), + Expression::NaturalNumber(_) + | Expression::SumExpression { + sign: _, + summands: _, + } + | Expression::VariableReference(_), ) => { let cell = scope.get_cell(&VariableTarget::from_definition(&var))?; scope._add_expr_to_cell(&value, cell)?; } - + // multi-cell arrays and (array literals or strings) (ValueType::Array(_, _), Expression::ArrayLiteral(expressions)) => { let cells = - scope.get_array_cells(&VariableTarget::from_definition(&var))?; + scope.get_array_cells(&VariableTarget::from_definition(&var))?; r_assert!( expressions.len() == cells.len(), - "Variable \"{var}\" cannot be initialised to array of length {}", - expressions.len() + "Variable \"{var}\" cannot be initialised to array of length {}", + expressions.len() ); for (cell, expr) in zip(cells, expressions) { scope._add_expr_to_cell(expr, cell)?; @@ -107,47 +107,47 @@ impl MastermindContext { } (ValueType::Array(_, _), Expression::StringLiteral(s)) => { let cells = - scope.get_array_cells(&VariableTarget::from_definition(&var))?; + scope.get_array_cells(&VariableTarget::from_definition(&var))?; r_assert!( s.len() == cells.len(), - "Variable \"{var}\" cannot be initialised to string of length {}", - s.len() + "Variable \"{var}\" cannot be initialised to string of length {}", + s.len() ); for (cell, chr) in zip(cells, s.bytes()) { scope.push_instruction(Instruction::AddToCell(cell, chr)); } } - + ( ValueType::Array(_, _), - Expression::VariableReference(variable_target), + Expression::VariableReference(variable_target), ) => r_panic!( "Cannot assign array \"{var}\" from variable reference \ \"{variable_target}\". Unimplemented." ), ( ValueType::Array(_, _), - Expression::NaturalNumber(_) - | Expression::SumExpression { - sign: _, - summands: _, - }, + Expression::NaturalNumber(_) + | Expression::SumExpression { + sign: _, + summands: _, + }, ) => r_panic!("Cannot assign single value to array \"{var}\"."), - + ( ValueType::DictStruct(_), - Expression::SumExpression { - sign: _, - summands: _, - } - | Expression::NaturalNumber(_) - | Expression::VariableReference(_) - | Expression::ArrayLiteral(_) - | Expression::StringLiteral(_), + Expression::SumExpression { + sign: _, + summands: _, + } + | Expression::NaturalNumber(_) + | Expression::VariableReference(_) + | Expression::ArrayLiteral(_) + | Expression::StringLiteral(_), ) => r_panic!( "Cannot assign value to struct type \"{var}\", initialise it instead." ), - + (ValueType::Cell, Expression::ArrayLiteral(_)) => { r_panic!("Cannot assign array to single-cell variable \"{var}\".") } @@ -203,18 +203,18 @@ impl MastermindContext { match value { Some(expr) => { let (imm, adds, subs) = expr.flatten()?; - + r_assert!( adds.len() == 0 && subs.len() == 0, "Expected compile-time constant expression in assertion for {var}" ); - + Some(imm) } None => None, } }; - + match var.is_spread { false => { let cell = scope.get_cell(&var)?; @@ -269,11 +269,11 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + scope._add_expr_to_cell(&value, cell)?; scope.push_instruction(Instruction::OutputCell(cell)); scope.push_instruction(Instruction::ClearCell(cell)); - + scope.push_instruction(Instruction::Free(temp_mem_id)); } Expression::ArrayLiteral(expressions) => { @@ -287,13 +287,13 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + for value in expressions { scope._add_expr_to_cell(&value, cell)?; scope.push_instruction(Instruction::OutputCell(cell)); scope.push_instruction(Instruction::ClearCell(cell)); } - + scope.push_instruction(Instruction::Free(temp_mem_id)); } Expression::StringLiteral(s) => { @@ -307,10 +307,13 @@ impl MastermindContext { memory_id: temp_mem_id, index: None, }; - + let mut prev = 0; for c in s.bytes() { - scope.push_instruction(Instruction::AddToCell(cell, c.wrapping_sub(prev))); + scope.push_instruction(Instruction::AddToCell( + cell, + c.wrapping_sub(prev), + )); scope.push_instruction(Instruction::OutputCell(cell)); prev = c; } @@ -321,15 +324,15 @@ impl MastermindContext { } Clause::While { var, block } => { let cell = scope.get_cell(&var)?; - + // open loop on variable scope.push_instruction(Instruction::OpenLoop(cell)); - + // recursively compile instructions // TODO: when recursively compiling, check which things changed based on a return info value let loop_scope = self.create_ir_scope(&block, Some(&scope))?; scope.instructions.extend(loop_scope.build_ir(true)); - + // close the loop scope.push_instruction(Instruction::CloseLoop(cell)); } @@ -349,7 +352,7 @@ impl MastermindContext { // any other kind of expression, allocate memory for it automatically let id = scope.push_memory_id(); scope - .push_instruction(Instruction::Allocate(Memory::Cell { id }, None)); + .push_instruction(Instruction::Allocate(Memory::Cell { id }, None)); let new_cell = CellReference { memory_id: id, index: None, @@ -359,20 +362,20 @@ impl MastermindContext { } (true, Expression::VariableReference(var)) => { let cell = scope.get_cell(var)?; - + let new_mem_id = scope.push_memory_id(); scope.push_instruction(Instruction::Allocate( Memory::Cell { id: new_mem_id }, None, )); - + let new_cell = CellReference { memory_id: new_mem_id, index: None, }; - + scope._copy_cell(cell, new_cell, 1); - + (new_cell, true) } (true, _) => { @@ -380,14 +383,14 @@ impl MastermindContext { } }; scope.push_instruction(Instruction::OpenLoop(source_cell)); - + // recurse if let Some(block) = block { let loop_scope = self.create_ir_scope(&block, Some(&scope))?; // TODO: refactor, make a function in scope trait to do this automatically scope.instructions.extend(loop_scope.build_ir(true)); } - + // copy into each target and decrement the source for target in targets { match target.is_spread { @@ -403,10 +406,10 @@ impl MastermindContext { } } } - + scope.push_instruction(Instruction::AddToCell(source_cell, -1i8 as u8)); // 255 scope.push_instruction(Instruction::CloseLoop(source_cell)); - + // free the source cell if it was a expression we just created if free_source_cell { scope.push_instruction(Instruction::Free(source_cell.memory_id)); @@ -453,12 +456,12 @@ impl MastermindContext { _ => unreachable!(), }; // end patch // - + if if_block.is_none() && else_block.is_none() { panic!("Expected block in if/else statement"); }; let mut new_scope = scope.open_inner(); - + let condition_mem_id = new_scope.push_memory_id(); new_scope.push_instruction(Instruction::Allocate( Memory::Cell { @@ -470,7 +473,7 @@ impl MastermindContext { memory_id: condition_mem_id, index: None, }; - + let else_condition_cell = match else_block { Some(_) => { let else_mem_id = new_scope.push_memory_id(); @@ -487,46 +490,46 @@ impl MastermindContext { } None => None, }; - + // copy the condition expression to the temporary condition cell new_scope._add_expr_to_cell(&condition, condition_cell)?; - + new_scope.push_instruction(Instruction::OpenLoop(condition_cell)); // TODO: think about optimisations for clearing this variable, as the builder won't shorten it for safety as it doesn't know this loop is special new_scope.push_instruction(Instruction::ClearCell(condition_cell)); - + // set the else condition cell // above comment about optimisations also applies here if let Some(cell) = else_condition_cell { new_scope.push_instruction(Instruction::ClearCell(cell)); }; - + // recursively compile if block if let Some(block) = if_block { let if_scope = self.create_ir_scope(&block, Some(&new_scope))?; new_scope.instructions.extend(if_scope.build_ir(true)); }; - + // close if block new_scope.push_instruction(Instruction::CloseLoop(condition_cell)); new_scope.push_instruction(Instruction::Free(condition_cell.memory_id)); - + // else block: if let Some(cell) = else_condition_cell { new_scope.push_instruction(Instruction::OpenLoop(cell)); // again think about how to optimise this clear in the build step new_scope.push_instruction(Instruction::ClearCell(cell)); - + // recursively compile else block // TODO: fix this bad practice unwrap let block = else_block.unwrap(); let else_scope = self.create_ir_scope(&block, Some(&new_scope))?; new_scope.instructions.extend(else_scope.build_ir(true)); - + new_scope.push_instruction(Instruction::CloseLoop(cell)); new_scope.push_instruction(Instruction::Free(cell.memory_id)); } - + // extend the inner scopes instructions onto the outer one scope.instructions.extend(new_scope.build_ir(true)); } @@ -548,19 +551,19 @@ impl MastermindContext { let functions_scope = scope.open_inner_templates_only(); // compile the block and extend the operations let instructions = self - .create_ir_scope(&mm_clauses, Some(&functions_scope))? - // compile without cleaning up top level variables, this is the brainfuck programmer's responsibility - .build_ir(false); - + .create_ir_scope(&mm_clauses, Some(&functions_scope))? + // compile without cleaning up top level variables, this is the brainfuck programmer's responsibility + .build_ir(false); + // it is also the brainfuck programmer's responsibility to return to the start position let bf_code = - self.ir_to_bf(instructions, Some(TC::origin_cell()))?; + self.ir_to_bf(instructions, Some(TC::origin_cell()))?; expanded_bf.extend(bf_code); } ExtendedOpcode::Opcode(opcode) => expanded_bf.push(opcode), } } - + // handle the location specifier let location = match location_specifier { LocationSpecifier::None => CellLocation::Unspecified, @@ -569,7 +572,7 @@ impl MastermindContext { CellLocation::MemoryCell(scope.get_target_cell_reference(&var)?) } }; - + scope.push_instruction(Instruction::InsertBrainfuckAtCell( expanded_bf, location, @@ -586,7 +589,7 @@ impl MastermindContext { let cells = scope.get_array_cells(&var)?; for cell in cells { scope - .push_instruction(Instruction::AssertCellValue(cell, None)); + .push_instruction(Instruction::AssertCellValue(cell, None)); } } } @@ -597,48 +600,48 @@ impl MastermindContext { arguments, } => { // create variable translations and recursively compile the inner variable block - + // get the calling arguments' types let calling_argument_types: Vec = arguments - .iter() - .map(|arg| scope.get_expression_type(arg)) - .collect::, String>>()?; - + .iter() + .map(|arg| scope.get_expression_type(arg)) + .collect::, String>>()?; + // find the function based on name * types let function_definition = - scope.get_function(&function_name, &calling_argument_types)?; - + scope.get_function(&function_name, &calling_argument_types)?; + // create mappings in a new translation scope, so mappings will be removed once scope closes let mut argument_translation_scope = scope.open_inner(); assert_eq!(arguments.len(), function_definition.arguments.len()); for (calling_expr, (arg_name, _)) in zip(arguments, function_definition.arguments) - { - // TODO: allow expressions as arguments: create a new variable instead of mapping when a value needs to be computed - let calling_arg = match calling_expr { - Expression::VariableReference(var) => var, - expr => r_panic!( - "Expected variable target in function call argument, \ + { + // TODO: allow expressions as arguments: create a new variable instead of mapping when a value needs to be computed + let calling_arg = match calling_expr { + Expression::VariableReference(var) => var, + expr => r_panic!( + "Expected variable target in function call argument, \ found expression `{expr}`. General expressions as \ function arguments are not supported." - ), - }; - argument_translation_scope - .create_mapped_variable(arg_name, &calling_arg)?; - } - - // recursively compile the function block - let function_scope = self.create_ir_scope( - &function_definition.block, - Some(&argument_translation_scope), - )?; + ), + }; argument_translation_scope + .create_mapped_variable(arg_name, &calling_arg)?; + } + + // recursively compile the function block + let function_scope = self.create_ir_scope( + &function_definition.block, + Some(&argument_translation_scope), + )?; + argument_translation_scope .instructions .extend(function_scope.build_ir(true)); - - // add the recursively compiled instructions to the current scope's built instructions - // TODO: figure out why this .build_ir() call uses clean_up_variables = false - scope + + // add the recursively compiled instructions to the current scope's built instructions + // TODO: figure out why this .build_ir() call uses clean_up_variables = false + scope .instructions .extend(argument_translation_scope.build_ir(false)); } @@ -651,7 +654,7 @@ function arguments are not supported." | Clause::None => unreachable!(), } } - + Ok(scope) } } @@ -665,26 +668,26 @@ pub struct ScopeBuilder<'a, TC, OC> { /// If true, scope is not able to access variables from outer scope. /// Used for embedded mm so that the inner mm can use outer functions but not variables. types_only: bool, - + /// Number of memory allocations in current scope allocations: usize, - + /// Mappings for variable names to memory allocation IDs in current scope variable_memory: HashMap, - + /// Functions accessible by any code within or in the current scope functions: Vec<(String, Vec<(String, ValueType)>, Vec>)>, /// Struct types definitions structs: HashMap, - + /// Intermediate instructions generated by the compiler instructions: Vec>, } impl ScopeBuilder<'_, TC, OC> where -TC: Display + Clone, -OC: Clone, + TC: Display + Clone, + OC: Clone, { pub fn new() -> ScopeBuilder<'static, TC, OC> { ScopeBuilder { @@ -697,7 +700,7 @@ OC: Clone, instructions: Vec::new(), } } - + // regarding `clean_up_variables`: // I don't love this system of deciding what to clean up at the end in this specific function, but I'm not sure what the best way to achieve this would be // this used to be called "get_instructions" but I think this more implies things are being modified @@ -705,10 +708,10 @@ OC: Clone, if !clean_up_variables { return self.instructions; } - + // optimisations could go here? // TODO: add some optimisations from the builder to here - + // create instructions to free cells let mut clear_instructions = vec![]; for (_var_name, (_var_type, memory)) in self.variable_memory.iter() { @@ -740,14 +743,14 @@ OC: Clone, for instr in clear_instructions { self.push_instruction(instr); } - + self.instructions } - + fn push_instruction(&mut self, instruction: Instruction) { self.instructions.push(instruction); } - + /// Open a scope within the current one, any time there is a {} in Mastermind, this is called fn open_inner(&self) -> ScopeBuilder { ScopeBuilder { @@ -760,7 +763,7 @@ OC: Clone, instructions: Vec::new(), } } - + // syntactic context instead of normal context // used for embedded mm so that the inner mm can use outer functions fn open_inner_templates_only(&self) -> ScopeBuilder { @@ -774,14 +777,14 @@ OC: Clone, instructions: Vec::new(), } } - + /// Get the correct variable type and allocate the right amount of cells for it fn allocate_variable(&mut self, var: VariableTypeDefinition) -> Result<&ValueType, String> { r_assert!( !self.variable_memory.contains_key(&var.name), - "Cannot allocate variable {var} twice in the same scope" + "Cannot allocate variable {var} twice in the same scope" ); - + // get absolute type let full_type = self.create_absolute_type(&var.var_type)?; // get absolute type size @@ -795,12 +798,12 @@ OC: Clone, }; // save variable in scope memory let None = self - .variable_memory - .insert(var.name.clone(), (full_type, memory.clone())) + .variable_memory + .insert(var.name.clone(), (full_type, memory.clone())) else { r_panic!("Unreachable error occurred when allocating {var}"); }; - + // verify location specifier let location = match var.location_specifier { LocationSpecifier::None => None, @@ -809,25 +812,25 @@ OC: Clone, "Cannot use variable as location specifier target when allocating variable: {var}" ), }; - + // allocate self.push_instruction(Instruction::Allocate(memory.clone(), location)); - + // return a reference to the created full type Ok(&self.variable_memory.get(&var.name).unwrap().0) } - + // fn allocate_unnamed_cell(&mut self) -> Memory { // let mem_id = self.create_memory_id(); // Memory::Cell { id: mem_id } // } - + fn push_memory_id(&mut self) -> MemoryId { let current_scope_relative = self.allocations; self.allocations += 1; current_scope_relative + self.allocation_offset() } - + /// recursively tally the allocation stack size of the outer scope, does not include this scope fn allocation_offset(&self) -> usize { // little bit of a hack but works for now @@ -840,7 +843,7 @@ OC: Clone, 0 } } - + /// find a function definition based on name and argument types (unaffected by the self.fn_only flag) fn get_function( &self, @@ -862,19 +865,19 @@ OC: Clone, let (_, arguments, block) = func; return Ok(Function { arguments: arguments.clone(), - block: block.clone(), + block: block.clone(), }); } - + if let Some(outer_scope) = self.outer_scope { return outer_scope.get_function(calling_name, calling_arg_types); } - + r_panic!( "Could not find function \"{calling_name}\" with correct arguments in current scope" ); } - + /// Define a struct in this scope fn register_struct_definition( &mut self, @@ -882,7 +885,7 @@ OC: Clone, fields: Vec, ) -> Result<(), String> { let mut absolute_fields = vec![]; - + for field_def in fields { let absolute_type = self.create_absolute_type(&field_def.field_type)?; absolute_fields.push(( @@ -891,17 +894,17 @@ OC: Clone, field_def.location_offset_specifier, )); } - + let None = self - .structs - .insert(struct_name.to_string(), DictStructType(absolute_fields)) + .structs + .insert(struct_name.to_string(), DictStructType(absolute_fields)) else { r_panic!("Cannot define struct {struct_name} more than once in same scope."); }; - + Ok(()) } - + /// Define a function in this scope fn register_function_definition( &mut self, @@ -910,15 +913,15 @@ OC: Clone, new_block: Vec>, ) -> Result<(), String> { let absolute_arguments: Vec<(String, ValueType)> = new_arguments - .into_iter() - .map(|f| { - let LocationSpecifier::None = f.location_specifier else { - r_panic!("Cannot specify variable location in function argument \"{f}\"."); - }; - Ok((f.name, self.create_absolute_type(&f.var_type)?)) - }) - .collect::, String>>()?; - + .into_iter() + .map(|f| { + let LocationSpecifier::None = f.location_specifier else { + r_panic!("Cannot specify variable location in function argument \"{f}\"."); + }; + Ok((f.name, self.create_absolute_type(&f.var_type)?)) + }) + .collect::, String>>()?; + // TODO: refactor this: // This is some fucked C-style loop break logic, basically GOTOs // basically it only gets to the panic if the functions have identical signature (except argument names) @@ -934,13 +937,13 @@ OC: Clone, } r_panic!("Cannot define a function with the same signature more than once in the same scope: \"{new_function_name}\""); } - + self.functions - .push((new_function_name.to_string(), absolute_arguments, new_block)); - + .push((new_function_name.to_string(), absolute_arguments, new_block)); + Ok(()) } - + /// Recursively find the definition of a struct type by searching up the scope call stack fn get_struct_definition(&self, struct_name: &str) -> Result<&DictStructType, String> { if let Some(struct_def) = self.structs.get(struct_name) { @@ -952,21 +955,21 @@ OC: Clone, r_panic!("No definition found for struct \"{struct_name}\"."); } } - + /// Construct an absolute type from a type reference fn create_absolute_type(&self, type_ref: &VariableTypeReference) -> Result { Ok(match type_ref { VariableTypeReference::Cell => ValueType::Cell, - VariableTypeReference::Struct(struct_type_name) => { - ValueType::from_struct(self.get_struct_definition(struct_type_name)?.clone()) - } - VariableTypeReference::Array(variable_type_reference, len) => ValueType::Array( - *len, - Box::new(self.create_absolute_type(variable_type_reference)?), - ), + VariableTypeReference::Struct(struct_type_name) => { + ValueType::from_struct(self.get_struct_definition(struct_type_name)?.clone()) + } + VariableTypeReference::Array(variable_type_reference, len) => ValueType::Array( + *len, + Box::new(self.create_absolute_type(variable_type_reference)?), + ), }) } - + /// Return a cell reference for a variable target fn get_cell(&self, target: &VariableTarget) -> Result { // get the absolute type of the variable, as well as the memory allocations @@ -983,13 +986,13 @@ OC: Clone, }), ( Some(subfield_chain), - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len } - | Memory::MappedCells { - id, - start_index: _, - len, - }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len } + | Memory::MappedCells { + id, + start_index: _, + len, + }, ) => { let (subfield_type, cell_index) = full_type.get_subfield(&subfield_chain)?; let ValueType::Cell = subfield_type else { @@ -998,32 +1001,32 @@ OC: Clone, r_assert!(cell_index < *len, "Cell reference out of bounds on variable target: {target}. This should not occur."); Ok(CellReference { memory_id: *id, - index: Some(match memory { - Memory::Cells { id: _, len: _ } => cell_index, - Memory::MappedCells { - id: _, - start_index, - len: _, - } => *start_index + cell_index, - _ => unreachable!(), - }), + index: Some(match memory { + Memory::Cells { id: _, len: _ } => cell_index, + Memory::MappedCells { + id: _, + start_index, + len: _, + } => *start_index + cell_index, + _ => unreachable!(), + }), }) } // valid states, user error ( Some(_), - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!("Cannot get subfields of cell type: {target}"), ( None, ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) => r_panic!("Expected single cell reference in target: {target}"), // invalid states, indicating an internal compiler issue (akin to 5xx error) ( @@ -1039,26 +1042,26 @@ OC: Clone, | ( _, ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!( "Invalid memory for value type in target: {target}. This should not occur." ), } } - + /// Return a list of cell references for an array of cells (not an array of structs) fn get_array_cells(&self, target: &VariableTarget) -> Result, String> { let (full_type, memory) = self.get_base_variable_memory(&target.name)?; Ok(match (&target.subfields, full_type, memory) { ( None, - ValueType::Array(arr_len, element_type), - Memory::Cells { id, len } - | Memory::MappedCells { - id, - start_index: _, - len, - }, + ValueType::Array(arr_len, element_type), + Memory::Cells { id, len } + | Memory::MappedCells { + id, + start_index: _, + len, + }, ) => { let ValueType::Cell = **element_type else { r_panic!("Cannot get array cells of struct array: {target}"); @@ -1069,28 +1072,28 @@ OC: Clone, ); (match memory { Memory::Cells { id: _, len } => 0..*len, - Memory::MappedCells { - id: _, - start_index, - len, - } => *start_index..(*start_index + *len), - _ => unreachable!(), + Memory::MappedCells { + id: _, + start_index, + len, + } => *start_index..(*start_index + *len), + _ => unreachable!(), }) .map(|i| CellReference { memory_id: *id, - index: Some(i), + index: Some(i), }) .collect() } ( Some(subfields), - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len: _ } - | Memory::MappedCells { - id, - start_index: _, - len: _, - }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len: _ } + | Memory::MappedCells { + id, + start_index: _, + len: _, + }, ) => { let (subfield_type, offset_index) = full_type.get_subfield(subfields)?; let ValueType::Array(arr_len, element_type) = subfield_type else { @@ -1099,66 +1102,66 @@ OC: Clone, let ValueType::Cell = **element_type else { r_panic!("Expected cell array in subfield variable target \"{target}\"."); }; - + (match memory { Memory::Cells { id: _, len: _ } => offset_index..(offset_index + *arr_len), - Memory::MappedCells { - id: _, - start_index, - len: _, - } => (*start_index + offset_index)..(*start_index + offset_index + *arr_len), - _ => unreachable!(), + Memory::MappedCells { + id: _, + start_index, + len: _, + } => (*start_index + offset_index)..(*start_index + offset_index + *arr_len), + _ => unreachable!(), }) .map(|i| CellReference { memory_id: *id, - index: Some(i), + index: Some(i), }) .collect() } ( None, - ValueType::DictStruct(_), - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + ValueType::DictStruct(_), + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) | ( None, - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => { r_panic!("Expected cell array type in variable target: {target}") } ( Some(_), - ValueType::Cell, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Cell, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => { r_panic!("Attempted to retrieve array subfield from cell type: {target}") } ( _, - ValueType::Cell, - Memory::Cells { id: _, len: _ } - | Memory::MappedCells { - id: _, - start_index: _, - len: _, - }, + ValueType::Cell, + Memory::Cells { id: _, len: _ } + | Memory::MappedCells { + id: _, + start_index: _, + len: _, + }, ) | ( _, - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }, ) => r_panic!( "Invalid memory for value type in target: {target}. This should not occur." ), }) } - + /// Return the first memory cell of a target allocation, used for location specifiers fn get_target_cell_reference(&self, target: &VariableTarget) -> Result { let (full_type, memory) = self.get_base_variable_memory(&target.name)?; @@ -1185,9 +1188,9 @@ OC: Clone, index: Some(*start_index), }, }, - Some(subfield_chain) => { - let (_subfield_type, offset_index) = full_type.get_subfield(&subfield_chain)?; - match memory { + Some(subfield_chain) => { + let (_subfield_type, offset_index) = full_type.get_subfield(&subfield_chain)?; + match memory { Memory::Cell { id: _ } |Memory::MappedCell { id: _, index: _ } => r_panic!("Attempted to get cell reference of subfield of single cell memory: {target}"), Memory::Cells { id, len } | Memory::MappedCells { id, start_index: _, len } => { r_assert!(offset_index < *len, "Subfield memory index out of allocation range. This should not occur. ({target})"); @@ -1199,10 +1202,10 @@ OC: Clone, CellReference {memory_id: *id, index: Some(index)} } } - } + } }) } - + /// Return the absolute type and memory allocation for a variable name fn get_base_variable_memory(&self, var_name: &str) -> Result<(&ValueType, &Memory), String> { match ( @@ -1217,19 +1220,19 @@ OC: Clone, } } } - + /// Get the absolute type of a full variable target, not just a name like `get_base_variable_memory` fn get_target_type(&self, target: &VariableTarget) -> Result<&ValueType, String> { let (var_type, _memory) = self.get_base_variable_memory(&target.name)?; Ok(match &target.subfields { None => var_type, - Some(subfields) => { - let (subfield_type, _memory_index) = var_type.get_subfield(subfields)?; - subfield_type - } + Some(subfields) => { + let (subfield_type, _memory_index) = var_type.get_subfield(subfields)?; + subfield_type + } }) } - + /// Create memory mapping between a pre-existing variable and a new one, used for function arguments. /// This could be used for copy by reference of subfields in future. fn create_mapped_variable( @@ -1271,118 +1274,118 @@ OC: Clone, // let subfield_size = subfield_type.size(); ( subfield_type, - match (subfield_type, base_var_memory) { - (ValueType::Cell, Memory::Cells { id, len: _ }) => { - // r_assert!((offset_index + subfield_size) <= *len, "Subfield \"{target}\" size and offset out of memory bounds. This should never occur."); - Memory::MappedCell { - id: *id, - index: Some(offset_index), - } - } - ( - ValueType::Cell, - Memory::MappedCells { - id, - start_index, - len: _, - }, - ) => Memory::MappedCell { - id: *id, - index: Some(*start_index + offset_index), - }, - ( - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::Cells { id, len: _ }, - ) => Memory::MappedCells { - id: *id, - start_index: offset_index, - len: subfield_type.size()?, - }, - ( - ValueType::Array(_, _) | ValueType::DictStruct(_), - Memory::MappedCells { - id, - start_index, - len: _, - }, - ) => Memory::MappedCells { - id: *id, - start_index: *start_index + offset_index, - len: subfield_type.size()?, - }, - (_, Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }) => { - r_panic!( - "Attempted to map a subfield of a single cell in \ + match (subfield_type, base_var_memory) { + (ValueType::Cell, Memory::Cells { id, len: _ }) => { + // r_assert!((offset_index + subfield_size) <= *len, "Subfield \"{target}\" size and offset out of memory bounds. This should never occur."); + Memory::MappedCell { + id: *id, + index: Some(offset_index), + } + } + ( + ValueType::Cell, + Memory::MappedCells { + id, + start_index, + len: _, + }, + ) => Memory::MappedCell { + id: *id, + index: Some(*start_index + offset_index), + }, + ( + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::Cells { id, len: _ }, + ) => Memory::MappedCells { + id: *id, + start_index: offset_index, + len: subfield_type.size()?, + }, + ( + ValueType::Array(_, _) | ValueType::DictStruct(_), + Memory::MappedCells { + id, + start_index, + len: _, + }, + ) => Memory::MappedCells { + id: *id, + start_index: *start_index + offset_index, + len: subfield_type.size()?, + }, + (_, Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ }) => { + r_panic!( + "Attempted to map a subfield of a single cell in \ mapping: {mapped_var_name} -> {target}" - ) - } - }, + ) + } + }, ) } }; - + self.variable_memory - .insert(mapped_var_name, (var_type.clone(), mapped_memory)); + .insert(mapped_var_name, (var_type.clone(), mapped_memory)); Ok(()) } - + /// Get the final type of an expression. /// (technically unnecessary right now, but can be used to implement expressions as function arguments in future) fn get_expression_type(&self, expr: &Expression) -> Result { Ok(match expr { Expression::NaturalNumber(_) => ValueType::Cell, - Expression::SumExpression { sign: _, summands } => { - let Some(_) = summands.first() else { - r_panic!( - "Cannot infer expression type because sum \ + Expression::SumExpression { sign: _, summands } => { + let Some(_) = summands.first() else { + r_panic!( + "Cannot infer expression type because sum \ expression has no elements: `{expr}`." - ); - }; - // TODO: decide if the summands' types should be verified here or not - for summand in summands { - match self.get_expression_type(summand)? { - ValueType::Cell => (), - summand_type => { - r_panic!( - "Sum expressions must be comprised of cell-types: \ + ); + }; + // TODO: decide if the summands' types should be verified here or not + for summand in summands { + match self.get_expression_type(summand)? { + ValueType::Cell => (), + summand_type => { + r_panic!( + "Sum expressions must be comprised of cell-types: \ found `{summand_type}` in `{expr}`" - ); - } - }; - } - ValueType::Cell - } - Expression::VariableReference(var) => self.get_target_type(var)?.clone(), - Expression::ArrayLiteral(elements) => { - let mut elements_iter = elements.iter(); - let Some(first_element) = elements_iter.next() else { - r_panic!( - "Cannot infer expression type because \ + ); + } + }; + } + ValueType::Cell + } + Expression::VariableReference(var) => self.get_target_type(var)?.clone(), + Expression::ArrayLiteral(elements) => { + let mut elements_iter = elements.iter(); + let Some(first_element) = elements_iter.next() else { + r_panic!( + "Cannot infer expression type because \ array literal has no elements: `{expr}`." - ); - }; - let first_element_type = self.get_expression_type(first_element)?; - for element in elements_iter { - let element_type = self.get_expression_type(element)?; - r_assert!( - element_type == first_element_type, - "All elements in array expressions must have the \ + ); + }; + let first_element_type = self.get_expression_type(first_element)?; + for element in elements_iter { + let element_type = self.get_expression_type(element)?; + r_assert!( + element_type == first_element_type, + "All elements in array expressions must have the \ same type: found `{element_type}` in `{expr}`" - ); - } - ValueType::Array(elements.len(), Box::new(first_element_type)) - } - Expression::StringLiteral(s) => ValueType::Array(s.len(), Box::new(ValueType::Cell)), + ); + } + ValueType::Array(elements.len(), Box::new(first_element_type)) + } + Expression::StringLiteral(s) => ValueType::Array(s.len(), Box::new(ValueType::Cell)), }) } - + /// helper function for a common use-case: /// flatten an expression and add it to a specific cell (using copies and adds, etc) fn _add_expr_to_cell(&mut self, expr: &Expression, cell: CellReference) -> Result<(), String> { let (imm, adds, subs) = expr.flatten()?; - + self.push_instruction(Instruction::AddToCell(cell.clone(), imm)); - + let mut adds_set = HashMap::new(); for var in adds { let n = adds_set.remove(&var).unwrap_or(0); @@ -1392,15 +1395,15 @@ same type: found `{element_type}` in `{expr}`" let n = adds_set.remove(&var).unwrap_or(0); adds_set.insert(var, n - 1); } - + for (source, constant) in adds_set { let source_cell = self.get_cell(&source)?; self._copy_cell(source_cell, cell.clone(), constant); } - + Ok(()) } - + /// helper function to add a self-referencing expression to a cell /// this is separated because it requires another copy ontop of normal expressions // TODO: refactor/fix underlying logic for this @@ -1427,11 +1430,11 @@ same type: found `{element_type}` in `{expr}`" if pre_clear { self.push_instruction(Instruction::ClearCell(cell.clone())); } - + let (imm, adds, subs) = expr.flatten()?; - + self.push_instruction(Instruction::AddToCell(cell.clone(), imm)); - + let mut adds_set = HashMap::new(); for var in adds { let n = adds_set.remove(&var).unwrap_or(0); @@ -1441,7 +1444,7 @@ same type: found `{element_type}` in `{expr}`" let n = adds_set.remove(&var).unwrap_or(0); adds_set.insert(var, n - 1); } - + for (source, constant) in adds_set { let source_cell = self.get_cell(&source)?; //If we have an instance of the original cell being added simply use our temp cell value @@ -1455,10 +1458,10 @@ same type: found `{element_type}` in `{expr}`" //Cleanup self.push_instruction(Instruction::ClearCell(temp_cell)); self.push_instruction(Instruction::Free(temp_mem_id)); - + Ok(()) } - + /// Helper function to copy a cell from one to another, leaving the original unaffected // TODO: make one for draining a cell fn _copy_cell( @@ -1502,156 +1505,156 @@ mod scope_builder_tests { backend::bf::{Opcode, TapeCell}, parser::expressions::Sign, }; - + use super::*; - + #[test] fn variable_allocation_1() { let mut scope = ScopeBuilder::::new(); let allocated_type = scope.allocate_variable(VariableTypeDefinition { name: String::from("var"), - var_type: VariableTypeReference::Cell, - location_specifier: LocationSpecifier::None, + var_type: VariableTypeReference::Cell, + location_specifier: LocationSpecifier::None, }); assert_eq!(allocated_type, Ok(&ValueType::Cell)); } - + #[test] fn get_expression_type_numbers_1() { let scope = ScopeBuilder::::new(); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(0)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(0)) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(1)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(1)) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::NaturalNumber(345678)) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::NaturalNumber(345678)) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_sums_1() { let scope = ScopeBuilder::::new(); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![Expression::NaturalNumber(0)] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![Expression::NaturalNumber(0)] + }) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Negative, - summands: vec![ - Expression::NaturalNumber(345678), - Expression::NaturalNumber(2) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Negative, + summands: vec![ + Expression::NaturalNumber(345678), + Expression::NaturalNumber(2) + ] + }) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![ - Expression::SumExpression { - sign: Sign::Negative, - summands: vec![ - Expression::NaturalNumber(1), - Expression::NaturalNumber(2) - ] - }, - Expression::NaturalNumber(2) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![ + Expression::SumExpression { + sign: Sign::Negative, + summands: vec![ + Expression::NaturalNumber(1), + Expression::NaturalNumber(2) + ] + }, + Expression::NaturalNumber(2) + ] + }) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_variables_1() { let mut scope = ScopeBuilder::::new(); scope - .allocate_variable(VariableTypeDefinition { - name: String::from("var"), - var_type: VariableTypeReference::Cell, - location_specifier: LocationSpecifier::None, - }) - .unwrap(); + .allocate_variable(VariableTypeDefinition { + name: String::from("var"), + var_type: VariableTypeReference::Cell, + location_specifier: LocationSpecifier::None, + }) + .unwrap(); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("var"), - subfields: None, - is_spread: false - })) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("var"), + subfields: None, + is_spread: false + })) + .unwrap(), + ValueType::Cell ); assert_eq!( scope - .get_expression_type(&Expression::SumExpression { - sign: Sign::Positive, - summands: vec![ - Expression::VariableReference(VariableTarget { - name: String::from("var"), - subfields: None, - is_spread: false - }), - Expression::NaturalNumber(123) - ] - }) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::SumExpression { + sign: Sign::Positive, + summands: vec![ + Expression::VariableReference(VariableTarget { + name: String::from("var"), + subfields: None, + is_spread: false + }), + Expression::NaturalNumber(123) + ] + }) + .unwrap(), + ValueType::Cell ); } - + #[test] fn get_expression_type_arrays_1() { let mut scope = ScopeBuilder::::new(); scope - .allocate_variable(VariableTypeDefinition { - name: String::from("arr"), - var_type: VariableTypeReference::Array(Box::new(VariableTypeReference::Cell), 3), - location_specifier: LocationSpecifier::None, - }) - .unwrap(); + .allocate_variable(VariableTypeDefinition { + name: String::from("arr"), + var_type: VariableTypeReference::Array(Box::new(VariableTypeReference::Cell), 3), + location_specifier: LocationSpecifier::None, + }) + .unwrap(); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("arr"), - subfields: None, - is_spread: false - })) - .unwrap(), - ValueType::Array(3, Box::new(ValueType::Cell)) + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("arr"), + subfields: None, + is_spread: false + })) + .unwrap(), + ValueType::Array(3, Box::new(ValueType::Cell)) ); assert_eq!( scope - .get_expression_type(&Expression::VariableReference(VariableTarget { - name: String::from("arr"), - subfields: Some(VariableTargetReferenceChain(vec![Reference::Index(0)])), - is_spread: false - })) - .unwrap(), - ValueType::Cell + .get_expression_type(&Expression::VariableReference(VariableTarget { + name: String::from("arr"), + subfields: Some(VariableTargetReferenceChain(vec![Reference::Index(0)])), + is_spread: false + })) + .unwrap(), + ValueType::Cell ); } - + // TODO: make failure tests for expression types } From be1c9a368a98b2e6babfc658285f04d3e9f95af7 Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Wed, 24 Dec 2025 17:13:09 +1100 Subject: [PATCH 6/8] Fix issues with post optimiser toplevel offsets --- compiler/src/backend/optimiser/bf.rs | 333 ++++++++++++------------ compiler/src/backend/optimiser/bf2d.rs | 16 +- compiler/src/backend/optimiser/tests.rs | 172 +++++++++--- compiler/src/brainfuck_optimiser.rs | 210 --------------- 4 files changed, 315 insertions(+), 416 deletions(-) delete mode 100644 compiler/src/brainfuck_optimiser.rs diff --git a/compiler/src/backend/optimiser/bf.rs b/compiler/src/backend/optimiser/bf.rs index 2aea5c4..7467b6e 100644 --- a/compiler/src/backend/optimiser/bf.rs +++ b/compiler/src/backend/optimiser/bf.rs @@ -6,211 +6,216 @@ impl MastermindContext { let mut output = Vec::new(); // get stretch of characters to optimise (+-<>) - let mut i = 0; let mut subset = Vec::new(); - while i < ops.len() { - let op = ops[i]; + for op in ops { match op { Opcode::Add | Opcode::Subtract | Opcode::Right | Opcode::Left | Opcode::Clear => { subset.push(op); } Opcode::OpenLoop | Opcode::CloseLoop | Opcode::Input | Opcode::Output => { // optimise subset and push - let optimised_subset = self.optimise_bf_subset(subset); - output.extend(optimised_subset); - - subset = Vec::new(); + let optimised_subset = optimise_bf_subset(subset); + subset = vec![]; + + // remove any redundant movement at the beginning + // (this shouldn't really be in the loop, + // but it's tested and works, and compiler code isn't performance critical) + for subset_op in optimised_subset { + if let (0, Opcode::Left | Opcode::Right) = (output.len(), subset_op) { + continue; + } + output.push(subset_op); + } output.push(op); } } - i += 1; } output } +} - fn optimise_bf_subset(&self, run: Vec) -> Vec { - #[derive(Clone)] - enum Change { - Add(Wrapping), - Set(Wrapping), - } - let mut tape: HashMap = HashMap::new(); - let mut head: i32 = 0; - - let mut i = 0; - while i < run.len() { - let op = run[i]; - match op { - Opcode::Clear => { - tape.insert(head, Change::Set(Wrapping(0i8))); - } - Opcode::Subtract | Opcode::Add => { - let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8))); - - let (Change::Add(val) | Change::Set(val)) = &mut change; - *val += match op { - Opcode::Add => 1, - Opcode::Subtract => -1, - _ => 0, - }; - - match &change { - Change::Add(val) => { - if *val != Wrapping(0i8) { - tape.insert(head, change); - } - } - Change::Set(_) => { +fn optimise_bf_subset(run: Vec) -> Vec { + #[derive(Clone)] + enum Change { + Add(Wrapping), + Set(Wrapping), + } + let mut tape: HashMap = HashMap::new(); + let mut head: i32 = 0; + + let mut i = 0; + while i < run.len() { + let op = run[i]; + match op { + Opcode::Clear => { + tape.insert(head, Change::Set(Wrapping(0i8))); + } + Opcode::Subtract | Opcode::Add => { + let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8))); + + let (Change::Add(val) | Change::Set(val)) = &mut change; + *val += match op { + Opcode::Add => 1, + Opcode::Subtract => -1, + _ => 0, + }; + + match &change { + Change::Add(val) => { + if *val != Wrapping(0i8) { tape.insert(head, change); } } + Change::Set(_) => { + tape.insert(head, change); + } } - Opcode::Right => { - head += 1; - } - Opcode::Left => { - head -= 1; - } - _ => (), } - i += 1; - } - // always have a start and end cell - if !tape.contains_key(&0) { - tape.insert(0, Change::Add(Wrapping(0i8))); - } - if !tape.contains_key(&head) { - tape.insert(head, Change::Add(Wrapping(0i8))); - } - - // This whole algorithm is probably really efficient and I reckon there's almost certainly a better way - // It's also just really poorly done in general, I don't understand what everything does and I wrote the damned thing - // TODO: refactor this properly - // convert hashmap to array - // start by making a negative and positive array - let mut pos_arr = Vec::new(); - let mut neg_arr = Vec::new(); - for (cell, value) in tape.into_iter() { - let i: usize; - let arr: &mut Vec; - if cell < 0 { - i = (-(cell + 1)) as usize; - arr = &mut neg_arr; - } else { - i = cell as usize; - arr = &mut pos_arr; + Opcode::Right => { + head += 1; } - - if i >= arr.len() { - arr.resize(i + 1, Change::Add(Wrapping(0i8))); + Opcode::Left => { + head -= 1; } - arr[i] = value; + _ => (), } - let start_index = neg_arr.len(); - // now combine the two arrays - let mut tape_arr: Vec = Vec::new(); - tape_arr.extend(neg_arr.into_iter().rev()); - tape_arr.extend(pos_arr.into_iter()); - - if ((start_index) + 1) >= (tape_arr.len()) { - tape_arr.resize(start_index + 1, Change::Add(Wrapping(0i8))); - } - let final_index = ((start_index as i32) + head) as usize; + i += 1; + } + // always have a start and end cell + if !tape.contains_key(&0) { + tape.insert(0, Change::Add(Wrapping(0i8))); + } + if !tape.contains_key(&head) { + tape.insert(head, Change::Add(Wrapping(0i8))); + } - let mut output = Vec::new(); + // This whole algorithm is probably really efficient and I reckon there's almost certainly a better way + // It's also just really poorly done in general, I don't understand what everything does and I wrote the damned thing + // TODO: refactor this properly + // convert hashmap to array + // start by making a negative and positive array + let mut pos_arr = Vec::new(); + let mut neg_arr = Vec::new(); + for (cell, value) in tape.into_iter() { + let i: usize; + let arr: &mut Vec; + if cell < 0 { + i = (-(cell + 1)) as usize; + arr = &mut neg_arr; + } else { + i = cell as usize; + arr = &mut pos_arr; + } - // Also this following algorithm for zig-zagging around the tape is pretty poor as well, there has to be a nicer way of doing it - - // if final cell is to the right of the start cell then we need to go to the left first, and vice-versa - // 1. go to the furthest point on the tape (opposite of direction to final cell) - // 2. go from that end of the tape to the opposite end of the tape - // 3. return to the final cell - let mut idx = start_index; - let d2 = final_index >= start_index; - let d1 = !d2; - let d3 = !d2; - - //1 - match d1 { - true => { - for _ in start_index..(tape_arr.len() - 1) { - output.push(Opcode::Right); - idx += 1; - } + if i >= arr.len() { + arr.resize(i + 1, Change::Add(Wrapping(0i8))); + } + arr[i] = value; + } + let start_index = neg_arr.len(); + // now combine the two arrays + let mut tape_arr: Vec = Vec::new(); + tape_arr.extend(neg_arr.into_iter().rev()); + tape_arr.extend(pos_arr.into_iter()); + + if ((start_index) + 1) >= (tape_arr.len()) { + tape_arr.resize(start_index + 1, Change::Add(Wrapping(0i8))); + } + let final_index = ((start_index as i32) + head) as usize; + + let mut output = Vec::new(); + + // Also this following algorithm for zig-zagging around the tape is pretty poor as well, there has to be a nicer way of doing it + + // if final cell is to the right of the start cell then we need to go to the left first, and vice-versa + // 1. go to the furthest point on the tape (opposite of direction to final cell) + // 2. go from that end of the tape to the opposite end of the tape + // 3. return to the final cell + let mut idx = start_index; + let d2 = final_index >= start_index; + let d1 = !d2; + let d3 = !d2; + + //1 + match d1 { + true => { + for _ in start_index..(tape_arr.len() - 1) { + output.push(Opcode::Right); + idx += 1; } - false => { - for _ in (1..=start_index).rev() { - output.push(Opcode::Left); - idx -= 1; - } + } + false => { + for _ in (1..=start_index).rev() { + output.push(Opcode::Left); + idx -= 1; } } + } - //2 - match d2 { - true => { - for cell in idx..tape_arr.len() { - let change = &tape_arr[cell]; - if let Change::Set(_) = change { - output.push(Opcode::Clear); - } - let (Change::Add(v) | Change::Set(v)) = change; - let v = v.0; - - for _ in 0..(v as i32).abs() { - output.push(match v > 0 { - true => Opcode::Add, - false => Opcode::Subtract, - }); - } - - if cell < (tape_arr.len() - 1) { - output.push(Opcode::Right); - idx += 1; - } + //2 + match d2 { + true => { + for cell in idx..tape_arr.len() { + let change = &tape_arr[cell]; + if let Change::Set(_) = change { + output.push(Opcode::Clear); } - } - false => { - for cell in (0..=idx).rev() { - let change = &tape_arr[cell]; - if let Change::Set(_) = change { - output.push(Opcode::Clear); - } - let (Change::Add(v) | Change::Set(v)) = change; - let v = v.0; - - for _ in 0..(v as i32).abs() { - output.push(match v > 0 { - true => Opcode::Add, - false => Opcode::Subtract, - }); - } - - if cell > 0 { - output.push(Opcode::Left); - idx -= 1; - } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + + for _ in 0..(v as i32).abs() { + output.push(match v > 0 { + true => Opcode::Add, + false => Opcode::Subtract, + }); } - } - } - //3 - match d3 { - true => { - for _ in idx..final_index { + if cell < (tape_arr.len() - 1) { output.push(Opcode::Right); idx += 1; } } - false => { - for _ in final_index..idx { + } + false => { + for cell in (0..=idx).rev() { + let change = &tape_arr[cell]; + if let Change::Set(_) = change { + output.push(Opcode::Clear); + } + let (Change::Add(v) | Change::Set(v)) = change; + let v = v.0; + + for _ in 0..(v as i32).abs() { + output.push(match v > 0 { + true => Opcode::Add, + false => Opcode::Subtract, + }); + } + + if cell > 0 { output.push(Opcode::Left); idx -= 1; } } } + } - output + //3 + match d3 { + true => { + for _ in idx..final_index { + output.push(Opcode::Right); + idx += 1; + } + } + false => { + for _ in final_index..idx { + output.push(Opcode::Left); + idx -= 1; + } + } } + + output } diff --git a/compiler/src/backend/optimiser/bf2d.rs b/compiler/src/backend/optimiser/bf2d.rs index 18ad2a0..4d6ca1b 100644 --- a/compiler/src/backend/optimiser/bf2d.rs +++ b/compiler/src/backend/optimiser/bf2d.rs @@ -29,9 +29,21 @@ impl MastermindContext { // TODO: make this automatically decide rather than configuring self.config.optimise_generated_all_permutations, ); - output.extend(optimised_subset); + subset = vec![]; - subset = Vec::new(); + // remove any redundant movement at the beginning + // (this shouldn't really be in the loop, + // but it's tested and works, and compiler code isn't performance critical) + for subset_op in optimised_subset { + if let ( + 0, + Opcode2D::Left | Opcode2D::Right | Opcode2D::Up | Opcode2D::Down, + ) = (output.len(), subset_op) + { + continue; + } + output.push(subset_op); + } output.push(op); } } diff --git a/compiler/src/backend/optimiser/tests.rs b/compiler/src/backend/optimiser/tests.rs index bdbd453..d0404df 100644 --- a/compiler/src/backend/optimiser/tests.rs +++ b/compiler/src/backend/optimiser/tests.rs @@ -1,7 +1,10 @@ #![cfg(test)] +use std::io::Cursor; + use crate::{ backend::{bf::*, bf2d::*, common::BrainfuckProgram}, + brainfuck::{BrainfuckConfig, BrainfuckContext}, misc::{MastermindConfig, MastermindContext}, }; @@ -39,21 +42,76 @@ const CTX_OPT_EXHAUSTIVE: MastermindContext = MastermindContext { }, }; -// TODO: -// fn _characteristic_test() +// TODO: implement this, would have to refactor the BVM +// fn get_tape_changes(code: &str) { +// let ctx = BrainfuckContext { +// config: BrainfuckConfig { +// enable_debug_symbols: false, +// enable_2d_grid: false, +// }, +// }; + +// let input_bytes: Vec = vec![]; +// let mut input_stream = Cursor::new(input_bytes); +// let mut output_stream = Cursor::new(vec![]); + +// ctx.run( +// code.chars().collect(), +// &mut input_stream, +// &mut output_stream, +// Some(1000), +// ) +// .unwrap(); +// } + +/// Count each type of opcode, for naive equivalence tests +fn tally_opcodes(input: &str) -> [usize; 11] { + let mut t = [0; 11]; + for c in input.chars() { + *match c { + '+' => &mut t[0], + '-' => &mut t[1], + '>' => &mut t[2], + '<' => &mut t[3], + '[' => &mut t[4], + ']' => &mut t[5], + '.' => &mut t[6], + ',' => &mut t[7], + '^' => &mut t[8], + 'v' => &mut t[9], + _ => &mut t[10], + } += 1; + } + t +} + +fn _characteristic_test(input: &str, expected: &str) { + let ops: Vec = BrainfuckProgram::from_str(input); + let optimised = CTX_OPT.optimise_bf(ops).to_string(); + println!("OPTIMISED ({}): {}", optimised.len(), optimised); + println!("EXPECTED ({}): {}", expected.len(), expected); + assert_eq!(optimised.len(), expected.len()); + // TODO: implement actually running both codes, would require refactoring BVM + assert_eq!(tally_opcodes(&optimised), tally_opcodes(&expected)); +} +fn _characteristic_test_2d(ctx: MastermindContext, input: &str, expected: &str) { + todo!(); +} #[test] fn standard_0() { let v: Vec = BrainfuckProgram::from_str("+++>><<++>--->+++<><><><><<<<<+++[>>>]"); //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o.len(), "+++++>--->+++<<<<<+++[>>>]".len()); + let e = "+++<---<+++++<<<+++[>>>]"; + assert_eq!(o, e); } #[test] fn standard_1() { let v: Vec = BrainfuckProgram::from_str("<><><>++<+[--++>>+<<-]"); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "++<+[->>+<<]"); + let e = "++<+[->>+<<]"; + assert_eq!(o, e); } #[test] fn standard_2() { @@ -64,38 +122,36 @@ fn standard_2() { // [(0)] 2 // -1 1 let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "+++++++++>>+++++++>---->>>++<<<<[>++<]"); + let e = "+++++++++>>+++++++>---->>>++<<<<[>++<]"; + assert_eq!(o, e); } #[test] fn standard_3() { - let v: Vec = BrainfuckProgram::from_str(">><."); + let v: Vec = BrainfuckProgram::from_str(".>><."); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, ">."); + let e = ".>."; + assert_eq!(o, e); } #[test] fn standard_4() { let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[>.<+]"); - //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o.len(), "[-]+++<+++>[>.<+]".len()); + assert_eq!(o.len(), "+++>[-]+++[>.<+]".len()); } #[test] fn standard_5() { let v: Vec = BrainfuckProgram::from_str("+++<+++>[-]+++[-]<[-]--+>-[>,]"); - //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o.len(), "[-]-<[-]->[>,]".len()); + assert_eq!(o.len(), "[-]->[-]-[>,]".len()); } #[test] fn standard_6() { let v: Vec = BrainfuckProgram::from_str( "+++++[-]+++++++++>>+++>---->>>++++--<--++<>++<+<->]++--->+", ); - // [9] 0 (7) -4 0 0 2 - // [(0)] 2 - // -1 1 let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "[-]+++++++++>>+++++++>---->>>++<<<<[[-]+>++<]"); + let e = "[-]+++++++++>>+++++++>---->>>++<<<<[[-]+>++<]"; + assert_eq!(o, e); } #[test] @@ -103,13 +159,15 @@ fn greedy_2d_0() { let v: Vec = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++[>>>>>>>]"); //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "+++++^---^+++vvvvv+++[>>>>>>>]"); + let e = "+++++^---^+++vvvvv+++[>>>>>>>]"; + assert_eq!(o, e); } #[test] fn greedy_2d_1() { let v: Vec = BrainfuckProgram::from_str("v^v^v^++v+[--++^^+vv-]"); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "++v+[-^^+vv]"); + let e = "++v+[-^^+vv]"; + assert_eq!(o, e); } #[test] fn greedy_2d_2() { @@ -120,27 +178,31 @@ fn greedy_2d_2() { // [(0)] 2 // -1 1 let o: String = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "+++++++++^^+++++++^----^^^++vvvv[^++v]"); + let e = "+++++++++^^+++++++^----^^^++vvvv[^++v]"; + assert_eq!(o, e); } #[test] fn greedy_2d_3() { - let v: Vec = BrainfuckProgram::from_str("^^v."); + let v: Vec = BrainfuckProgram::from_str(",^^v."); let o: String = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "^."); + let e = ",^."; + assert_eq!(o, e); } #[test] fn greedy_2d_4() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++,"); //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "[-]+++v+++^,"); + let e = "[-]+++v+++^,"; + assert_eq!(o, e); } #[test] fn greedy_2d_5() { let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-,,,,..."); //(3) 0 0 [5] -3 3 let o = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "[-]-v[-]-^,,,,..."); + let e = "[-]-v[-]-^,,,,..."; + assert_eq!(o, e); } #[test] fn greedy_2d_6() { @@ -151,15 +213,15 @@ fn greedy_2d_6() { // [(0)] 2 // -1 1 let o: String = CTX_OPT.optimise_bf2d(v).to_string(); - assert_eq!(o, "[-]+++++++++^^+++++++^----^^^++vvvv[[-]+^++v]"); + let e = "[-]+++++++++^^+++++++^----^^^++vvvv[[-]+^++v]"; + assert_eq!(o, e); } #[test] fn exhaustive_2d_0() { - let v: Vec = BrainfuckProgram::from_str("+++^^vv++^---^+++v^v^v^v^vvvvv+++,"); - //(3) 0 0 [5] -3 3 + let v: Vec = BrainfuckProgram::from_str(",+++^^vv++^---^+++v^v^v^v^vvvvv+++,"); let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); - assert_eq!(o.len(), "^^+++v---v+++++vvv+++,".len()); + assert_eq!(o.len(), ",^^+++v---v+++++vvv+++,".len()); } #[test] fn exhaustive_2d_1() { @@ -172,31 +234,28 @@ fn exhaustive_2d_2() { let v: Vec = BrainfuckProgram::from_str( "+++++++++^^+++^----^^^++++--v--++vvhellov++++[-v+^^++v+v-^]++---^+", ); - // [9] 0 (7) -4 0 0 2 - // [(0)] 2 - // -1 1 let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); assert_eq!(o.len(), "+++++++++^^+++++++^----^^^++vvvv[^++v]".len()); } #[test] fn exhaustive_2d_3() { - let v: Vec = BrainfuckProgram::from_str("^^v."); + let v: Vec = BrainfuckProgram::from_str(".^^v."); let o: String = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); - assert_eq!(o, "^."); + let e = ".^."; + assert_eq!(o, e); } #[test] fn exhaustive_2d_4() { - let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++."); - //(3) 0 0 [5] -3 3 + let v: Vec = BrainfuckProgram::from_str(",+++v+++^[-]+++."); let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); - assert_eq!(o.len(), "[-]+++v+++^.".len()); + assert_eq!(o.len(), ",[-]+++v+++^.".len()); } #[test] fn exhaustive_2d_5() { - let v: Vec = BrainfuckProgram::from_str("+++v+++^[-]+++[-]v[-]--+^-."); + let v: Vec = BrainfuckProgram::from_str(",+++v+++^[-]+++[-]v[-]--+^-."); //(3) 0 0 [5] -3 3 let o = CTX_OPT_EXHAUSTIVE.optimise_bf2d(v).to_string(); - assert_eq!(o.len(), "[-]-v[-]-^.".len()); + assert_eq!(o.len(), ",[-]-v[-]-^.".len()); } #[test] fn exhaustive_2d_6() { @@ -315,23 +374,56 @@ fn wrapping_2d_4() { fn offset_toplevel_0() { let v: Vec = BrainfuckProgram::from_str("++>>>++++<<<-->>>."); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "++++."); + let e = "++++."; + assert_eq!(o, e); } #[test] fn offset_toplevel_0a() { let v: Vec = BrainfuckProgram::from_str("[++>>>++++<<<-->>>.]"); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "[>>>++++.]"); + let e = "[>>>++++.]"; + assert_eq!(o, e); } #[test] fn offset_toplevel_1() { let v: Vec = BrainfuckProgram::from_str(">>++>-+<++<[++>>>++++<<<-->>>.]<<"); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "++++<[>>>++++.]"); + let e = "++++<[>>>++++.]"; + assert_eq!(o, e); } #[test] fn offset_toplevel_2() { let v: Vec = BrainfuckProgram::from_str("[++>>>++++<<<-->>>.]>>>+++<<"); let o: String = CTX_OPT.optimise_bf(v).to_string(); - assert_eq!(o, "[>>>++++.]"); + let e = "[>>>++++.]"; + assert_eq!(o, e); +} + +#[test] +fn offset_toplevel_2d_0() { + let v: Vec = BrainfuckProgram::from_str("++>>>vvv++++<<^^^<--vv>."); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + let e = "++++<<^."; + assert_eq!(o, e); +} +#[test] +fn offset_toplevel_2d_0a() { + let v: Vec = BrainfuckProgram::from_str("[++v>>vv>++++<<^^<^--vv>.]"); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + let e = "[>>>vvv++++<<^.]"; + assert_eq!(o, e); +} +#[test] +fn offset_toplevel_2d_1() { + let v: Vec = + BrainfuckProgram::from_str(">vv>++>^-+v<++<[++>vv>>++++<^^<<-->^^^>>.]<<"); + let o: String = CTX_OPT.optimise_bf2d(v).to_string(); + let e = "++++<[>>>vv++++^^^^^.]"; + assert_eq!(o, e); +} +#[test] +fn offset_toplevel_2d_2() { + let v: Vec = BrainfuckProgram::from_str("[++>v>>++++<^<<-->v>>.]^^>>>+++>>++++.]".len()); } diff --git a/compiler/src/brainfuck_optimiser.rs b/compiler/src/brainfuck_optimiser.rs deleted file mode 100644 index 5d73e47..0000000 --- a/compiler/src/brainfuck_optimiser.rs +++ /dev/null @@ -1,210 +0,0 @@ -use itertools::Itertools; -use std::{collections::HashMap, num::Wrapping}; - -use crate::{ - backend::bf2d::{Opcode2D, TapeCell2D}, - misc::MastermindContext, -}; - -// originally trivial post-compilation brainfuck optimisations -// extended to 2D which makes it more difficult -impl MastermindContext { - pub fn optimise_bf_code(&self, program: Vec) -> Vec { - let mut output = Vec::new(); - - // get stretch of characters to optimise (+-<>) - let mut i = 0; - let mut subset = Vec::new(); - while i < program.len() { - let op = program[i]; - match op { - Opcode2D::Add - | Opcode2D::Subtract - | Opcode2D::Right - | Opcode2D::Left - | Opcode2D::Clear - | Opcode2D::Up - | Opcode2D::Down => { - subset.push(op); - } - Opcode2D::OpenLoop | Opcode2D::CloseLoop | Opcode2D::Input | Opcode2D::Output => { - // optimise subset and push - let optimised_subset = self.optimise_subset(subset); - output.extend(optimised_subset); - - subset = Vec::new(); - output.push(op); - } - } - i += 1; - } - - output - } - - fn optimise_subset(&self, run: Vec) -> Vec { - #[derive(Clone)] - enum Change { - Add(Wrapping), - Set(Wrapping), - } - let mut tape: HashMap = HashMap::new(); - let start = TapeCell2D(0, 0); - let mut head = TapeCell2D(0, 0); - let mut i = 0; - // simulate the subprogram to find the exact changes made to the tape - while i < run.len() { - let op = run[i]; - match op { - Opcode2D::Clear => { - tape.insert(head, Change::Set(Wrapping(0i8))); - } - Opcode2D::Subtract | Opcode2D::Add => { - let mut change = tape.remove(&head).unwrap_or(Change::Add(Wrapping(0i8))); - - let (Change::Add(val) | Change::Set(val)) = &mut change; - *val += match op { - Opcode2D::Add => 1, - Opcode2D::Subtract => -1, - _ => 0, - }; - - match &change { - Change::Add(val) => { - if *val != Wrapping(0i8) { - tape.insert(head, change); - } - } - Change::Set(_) => { - tape.insert(head, change); - } - } - } - Opcode2D::Right => { - head.0 += 1; - } - Opcode2D::Left => { - head.0 -= 1; - } - Opcode2D::Up => { - head.1 += 1; - } - Opcode2D::Down => { - head.1 -= 1; - } - _ => (), - } - i += 1; - } - let mut output = Vec::new(); - if self.config.optimise_generated_all_permutations { - //Exhaustive approach checks all permutations - let mut output_length = i32::MAX; - let mut best_permutation = Vec::new(); - for perm in tape.iter().permutations(tape.len()) { - let mut position = start; - let mut current_output_length = 0; - //Calculate the distance of this - for (cell, _) in &perm { - current_output_length += (cell.0 - position.0).abs(); - current_output_length += (cell.1 - position.1).abs(); - position = **cell; - if current_output_length > output_length { - break; - } - } - if current_output_length > output_length { - continue; - } - //Add the distance to the finishing location - current_output_length += (head.0 - position.0).abs(); - current_output_length += (head.1 - position.1).abs(); - if current_output_length < output_length { - best_permutation = perm; - output_length = current_output_length; - } - } - let mut position = start; - for (cell, change) in best_permutation { - output = _move_position(output, &position, cell); - position = *cell; - if let Change::Set(_) = change { - output.push(Opcode2D::Clear); - } - let (Change::Add(v) | Change::Set(v)) = change; - let v = v.0; - for _ in 0..(v as i32).abs() { - output.push(match v == -128 || v > 0 { - true => Opcode2D::Add, - false => Opcode2D::Subtract, - }); - } - } - output = _move_position(output, &position, &head); - } else { - //Greedy approach faster for bigger datasets - let mut position = start; - //For the number of cells navigate to the nearest cell - for _ in 0..tape.len() { - if !tape.is_empty() { - let mut min_distance = i32::MAX; - let mut next_position = TapeCell2D(0, 0); - for (cell, _value) in tape.iter() { - if (cell.0 - position.0).abs() + (cell.1 - position.1).abs() < min_distance - { - min_distance = - (cell.0 - position.0).abs() + (cell.1 - position.1).abs(); - next_position = *cell; - } - } - // Move to next position - output = _move_position(output, &position, &next_position); - position = next_position; - //Now Update the output with correct opcodes - let change = tape.remove(&next_position).unwrap(); - if let Change::Set(_) = change { - output.push(Opcode2D::Clear); - } - let (Change::Add(v) | Change::Set(v)) = change; - let v = v.0; - for _ in 0..(v as i32).abs() { - output.push(match v == -128 || v > 0 { - true => Opcode2D::Add, - false => Opcode2D::Subtract, - }); - } - } - } - output = _move_position(output, &position, &head); - } - output - } -} - -fn _move_position( - mut program: Vec, - old_position: &TapeCell2D, - new_position: &TapeCell2D, -) -> Vec { - if old_position != new_position { - if old_position.0 < new_position.0 { - for _ in 0..(new_position.0 - old_position.0) { - program.push(Opcode2D::Right); - } - } else { - for _ in 0..(old_position.0 - new_position.0) { - program.push(Opcode2D::Left); - } - } - if old_position.1 < new_position.1 { - for _ in 0..(new_position.1 - old_position.1) { - program.push(Opcode2D::Up); - } - } else { - for _ in 0..(old_position.1 - new_position.1) { - program.push(Opcode2D::Down); - } - } - } - program -} From 4463178946ec143701271399dddb3a76d804bb1f Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Wed, 24 Dec 2025 17:27:56 +1100 Subject: [PATCH 7/8] Add ignored test for included generated code optimisaiont --- compiler/src/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/compiler/src/tests.rs b/compiler/src/tests.rs index ddc5706..92f5246 100644 --- a/compiler/src/tests.rs +++ b/compiler/src/tests.rs @@ -2584,6 +2584,29 @@ output a + 3; assert_eq!(run_code(BVM_CONFIG_1D, &code, "", None).unwrap(), "tIJ"); } + #[test] + #[ignore] + fn generated_code_optimisations() { + let program = r#" +cell x; +cell y; +cell z; +z += 4; +x += 3; +z += 4; +x += 3; +z += 4; +y = 7; +input x; +input y; +input z; +"#; + let code = compile_program::(program, Some(OPT_ALL)).unwrap(); + println!("{code}"); + assert!(code.len() < 30); + assert_eq!(run_code(BVM_CONFIG_1D, &code, " ", None).unwrap(), ""); + } + // TODO: remove the need for this #[test] fn unimplemented_memory_allocation() { From c18e661acd16a7d9fdf4652185db8263878d8730 Mon Sep 17 00:00:00 2001 From: Heath Manning Date: Thu, 25 Dec 2025 11:57:42 +1100 Subject: [PATCH 8/8] Fix rustfmt long string issues --- compiler/src/frontend/frontend.rs | 76 ++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/compiler/src/frontend/frontend.rs b/compiler/src/frontend/frontend.rs index 8936d3b..ba7159f 100644 --- a/compiler/src/frontend/frontend.rs +++ b/compiler/src/frontend/frontend.rs @@ -1,7 +1,5 @@ // compile syntax tree into low-level instructions -use std::{collections::HashMap, fmt::Display, iter::zip}; - use super::types::*; use crate::{ backend::common::{ @@ -13,12 +11,12 @@ use crate::{ parser::{ expressions::Expression, types::{ - Clause, ExtendedOpcode, LocationSpecifier, Reference, StructFieldTypeDefinition, - VariableTarget, VariableTargetReferenceChain, VariableTypeDefinition, - VariableTypeReference, + Clause, ExtendedOpcode, LocationSpecifier, StructFieldTypeDefinition, VariableTarget, + VariableTypeDefinition, VariableTypeReference, }, }, }; +use std::{collections::HashMap, fmt::Display, iter::zip}; impl MastermindContext { pub fn create_ir_scope<'a, TC: 'static + TapeCellVariant, OC: 'static + OpcodeVariant>( @@ -206,7 +204,8 @@ impl MastermindContext { r_assert!( adds.len() == 0 && subs.len() == 0, - "Expected compile-time constant expression in assertion for {var}" + "Expected compile-time constant expression \ +in assertion for {var}" ); Some(imm) @@ -809,7 +808,8 @@ where LocationSpecifier::None => None, LocationSpecifier::Cell(cell) => Some(cell), LocationSpecifier::Variable(_) => r_panic!( - "Cannot use variable as location specifier target when allocating variable: {var}" + "Cannot use variable as location specifier \ +target when allocating variable: {var}" ), }; @@ -874,7 +874,8 @@ where } r_panic!( - "Could not find function \"{calling_name}\" with correct arguments in current scope" + "Could not find function \"{calling_name}\" \ +with correct arguments in current scope" ); } @@ -935,7 +936,10 @@ where continue 'func_loop; } } - r_panic!("Cannot define a function with the same signature more than once in the same scope: \"{new_function_name}\""); + r_panic!( + "Cannot define a function with the same signature \ +more than once in the same scope: \"{new_function_name}\"" + ); } self.functions @@ -998,7 +1002,11 @@ where let ValueType::Cell = subfield_type else { r_panic!("Expected cell type in variable target: {target}"); }; - r_assert!(cell_index < *len, "Cell reference out of bounds on variable target: {target}. This should not occur."); + r_assert!( + cell_index < *len, + "Cell reference out of bounds on variable target: {target}. \ +This should not occur." + ); Ok(CellReference { memory_id: *id, index: Some(match memory { @@ -1191,17 +1199,38 @@ where Some(subfield_chain) => { let (_subfield_type, offset_index) = full_type.get_subfield(&subfield_chain)?; match memory { - Memory::Cell { id: _ } |Memory::MappedCell { id: _, index: _ } => r_panic!("Attempted to get cell reference of subfield of single cell memory: {target}"), - Memory::Cells { id, len } | Memory::MappedCells { id, start_index: _, len } => { - r_assert!(offset_index < *len, "Subfield memory index out of allocation range. This should not occur. ({target})"); - let index = match memory { - Memory::Cells { id: _, len: _ } => offset_index, - Memory::MappedCells { id: _, start_index, len: _ } => *start_index + offset_index, - Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ } => unreachable!() - }; - CellReference {memory_id: *id, index: Some(index)} - } - } + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ } => r_panic!( + "Attempted to get cell reference of \ +subfield of single cell memory: {target}" + ), + Memory::Cells { id, len } + | Memory::MappedCells { + id, + start_index: _, + len, + } => { + r_assert!( + offset_index < *len, + "Subfield memory index out of allocation range. \ +This should not occur. ({target})" + ); + let index = match memory { + Memory::Cells { id: _, len: _ } => offset_index, + Memory::MappedCells { + id: _, + start_index, + len: _, + } => *start_index + offset_index, + Memory::Cell { id: _ } | Memory::MappedCell { id: _, index: _ } => { + unreachable!() + } + }; + CellReference { + memory_id: *id, + index: Some(index), + } + } + } } }) } @@ -1503,7 +1532,10 @@ same type: found `{element_type}` in `{expr}`" mod scope_builder_tests { use crate::{ backend::bf::{Opcode, TapeCell}, - parser::expressions::Sign, + parser::{ + expressions::Sign, + types::{Reference, VariableTargetReferenceChain}, + }, }; use super::*;