From 2e187585a151f56014c74e3f7da2009234f9f00d Mon Sep 17 00:00:00 2001 From: Pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 16 Aug 2023 13:14:20 +0100 Subject: [PATCH] actually run if(/else) substacks this runs the step after the last if(/else) twice for some reason (TODO) --- src/ir.rs | 226 ++++++++++++++++++++++++-------------------- src/sb3.rs | 54 +++++++++-- src/targets/wasm.rs | 74 +++------------ 3 files changed, 179 insertions(+), 175 deletions(-) diff --git a/src/ir.rs b/src/ir.rs index 7222c80..b0d1521 100644 --- a/src/ir.rs +++ b/src/ir.rs @@ -1,10 +1,11 @@ // intermediate representation use crate::sb3::{ - Block, BlockArray, BlockArrayOrId, BlockOpcode, Field, Input, Sb3Project, VarVal, VariableInfo, + Block, BlockArray, BlockArrayOrId, BlockOpcode, Field, Input, Sb3Project, VarVal, + VariableInfo, }; use alloc::collections::BTreeMap; use alloc::rc::Rc; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; use ordered_float::OrderedFloat; @@ -54,14 +55,14 @@ impl From for IrProject { .comments .clone() .iter() - .find(|(_id, comment)| { + .any(|(_id, comment)| { matches!(comment.block_id.clone(), Some(d) if &d == id) && comment.text.clone() == *"hq-dbg" - }) - .is_some(), + }), vars: Rc::clone(&vars), }); - let thread = Thread::from_hat(block.clone(), target.blocks.clone(), context); + let thread = + Thread::from_hat(block.clone(), target.blocks.clone(), context); threads.push(thread); } } @@ -140,6 +141,7 @@ pub enum IrOpcode { event_whenbackdropswitchesto, event_whengreaterthan, event_whenbroadcastreceived, + hq_drop(usize), hq_goto { step: Option>, does_yield: bool, @@ -405,6 +407,7 @@ impl IrOpcode { operator_length => BlockDescriptor::new(vec![Any], Number), hq_goto { .. } => BlockDescriptor::new(vec![], Stack), hq_goto_if { .. } => BlockDescriptor::new(vec![Boolean], Stack), + hq_drop(n) => BlockDescriptor::new(vec![Any; *n], Stack), _ => todo!("{:?}", &self), } } @@ -483,45 +486,90 @@ pub struct Thread { trait IrBlockVec { fn add_block( &mut self, - block: Block, + block_id: String, blocks: &BTreeMap, context: Rc, ); + fn add_block_arr(&mut self, block_arr: &BlockArray); } impl IrBlockVec for Vec { + fn add_block_arr(&mut self, block_arr: &BlockArray) { + self.push( + match block_arr { + BlockArray::NumberOrAngle(ty, value) => match ty { + 4 => IrOpcode::math_number { NUM: *value }, + 5 => IrOpcode::math_positive_number { NUM: *value }, + 6 => IrOpcode::math_whole_number { NUM: *value }, + 7 => IrOpcode::math_integer { NUM: *value }, + 8 => IrOpcode::math_angle { NUM: *value }, + _ => panic!("bad project json (block array of type ({}, u32))", ty), + }, + BlockArray::ColorOrString(ty, value) => match ty { + 4 => IrOpcode::math_number { + NUM: value.parse().unwrap(), + }, + 5 => IrOpcode::math_positive_number { + NUM: value.parse().unwrap(), + }, + 6 => IrOpcode::math_whole_number { + NUM: value.parse().unwrap(), + }, + 7 => IrOpcode::math_integer { + NUM: value.parse().unwrap(), + }, + 8 => IrOpcode::math_angle { + NUM: value.parse().unwrap(), + }, + 9 => todo!(), + 10 => IrOpcode::text { + TEXT: value.to_string(), + }, + _ => panic!("bad project json (block array of type ({}, string))", ty), + }, + BlockArray::Broadcast(ty, _name, id) => match ty { + 12 => IrOpcode::data_variable { + VARIABLE: id.to_string(), + }, + _ => todo!(), + }, + BlockArray::VariableOrList(ty, _name, id, _pos_x, _pos_y) => match ty { + 12 => IrOpcode::data_variable { + VARIABLE: id.to_string(), + }, + _ => todo!(), + }, + } + .into(), + ) + } fn add_block( &mut self, - block: Block, + block_id: String, blocks: &BTreeMap, context: Rc, ) { + let block = blocks.get(&block_id).unwrap(); match block { Block::Normal { block_info, .. } => { - for (name, input) in block_info.inputs.clone() { + //println!("{}: {:?}", &block_id, &block_info.opcode); + for (name, input) in &block_info.inputs { if matches!(name.as_str(), "SUBSTACK" | "SUBSTACK2") { continue; } match input { Input::Shadow(_, maybe_block, _) | Input::NoShadow(_, maybe_block) => { - if let Some(block) = maybe_block { - match block { - BlockArrayOrId::Id(id) => { - if let Some(actual_block) = blocks.get(&id) { - self.add_block( - actual_block.clone(), - blocks, - Rc::clone(&context), - ); - } - } - BlockArrayOrId::Array(arr) => { - self.add_block( - Block::Special(arr), - blocks, - Rc::clone(&context), - ); - } + let Some(block) = maybe_block else { panic!("block doest exist"); }; + match block { + BlockArrayOrId::Id(id) => { + self.add_block( + id.clone(), + blocks, + Rc::clone(&context), + ); + } + BlockArrayOrId::Array(arr) => { + self.add_block_arr(arr); } } } @@ -589,7 +637,7 @@ impl IrBlockVec for Vec { let id = maybe_id.clone().expect("invalid project.json - null id for VARIABLE field (E031)"); vec![ IrOpcode::data_variable { - VARIABLE: id.clone(), + VARIABLE: id.to_string(), }, IrOpcode::operator_add, IrOpcode::data_setvariableto { @@ -597,86 +645,54 @@ impl IrBlockVec for Vec { } ] }, - BlockOpcode::control_if => vec![IrOpcode::hq_goto_if { step: None, does_yield: false, }, IrOpcode::hq_goto { step: None, does_yield: false, }], - BlockOpcode::control_if_else => vec![IrOpcode::hq_goto_if { step: None, does_yield: false, }, IrOpcode::hq_goto { step: None, does_yield: false, }], + BlockOpcode::control_if => { + let substack_id = if let BlockArrayOrId::Id(id) = block_info.inputs.get("SUBSTACK").expect("missing SUBSTACK input for control_if").get_1().unwrap().clone().unwrap() { id } else { panic!("malformed SUBSTACK input") }; + vec![IrOpcode::hq_goto_if { step: Some(step_from_top_block(substack_id, block_info.next.clone(), blocks, Rc::clone(&context))), does_yield: false, }, IrOpcode::hq_goto { step: Some(step_from_top_block(block_info.next.clone().unwrap(), None, blocks, Rc::clone(&context))), does_yield: false, }] + } + BlockOpcode::control_if_else => { + let substack_id = if let BlockArrayOrId::Id(id) = block_info.inputs.get("SUBSTACK").expect("missing SUBSTACK input for control_if").get_1().unwrap().clone().unwrap() { id } else { panic!("malformed SUBSTACK input") }; + let substack2_id = if let BlockArrayOrId::Id(id) = block_info.inputs.get("SUBSTACK2").expect("missing SUBSTACK input for control_if").get_1().unwrap().clone().unwrap() { id } else { panic!("malformed SUBSTACK2 input") }; + vec![IrOpcode::hq_goto_if { step: Some(step_from_top_block(substack_id, block_info.next.clone(), blocks, Rc::clone(&context))), does_yield: false, }, IrOpcode::hq_goto { step: Some(step_from_top_block(substack2_id, block_info.next.clone(), blocks, Rc::clone(&context))), does_yield: false, }] + } _ => todo!(), }).into_iter().map(IrBlock::from).collect()); - - /*if let Some(next_id) = &block_info.next { - if let Some(next_block) = blocks.get(next_id) { - self.add_block(next_block.clone(), blocks, Rc::clone(&context)); - } - }*/ } - Block::Special(a) => self.push( - match a { - BlockArray::NumberOrAngle(ty, value) => match ty { - 4 => IrOpcode::math_number { NUM: value }, - 5 => IrOpcode::math_positive_number { NUM: value }, - 6 => IrOpcode::math_whole_number { NUM: value }, - 7 => IrOpcode::math_integer { NUM: value }, - 8 => IrOpcode::math_angle { NUM: value }, - _ => panic!("bad project json (block array of type ({}, u32))", ty), - }, - BlockArray::ColorOrString(ty, value) => match ty { - 4 => IrOpcode::math_number { - NUM: value.parse().unwrap(), - }, - 5 => IrOpcode::math_positive_number { - NUM: value.parse().unwrap(), - }, - 6 => IrOpcode::math_whole_number { - NUM: value.parse().unwrap(), - }, - 7 => IrOpcode::math_integer { - NUM: value.parse().unwrap(), - }, - 8 => IrOpcode::math_angle { - NUM: value.parse().unwrap(), - }, - 9 => todo!(), - 10 => IrOpcode::text { TEXT: value }, - _ => panic!("bad project json (block array of type ({}, string))", ty), - }, - BlockArray::Broadcast(ty, _name, id) => match ty { - 12 => IrOpcode::data_variable { VARIABLE: id }, - _ => todo!(), - }, - BlockArray::VariableOrList(ty, _name, id, _pos_x, _pos_y) => match ty { - 12 => IrOpcode::data_variable { VARIABLE: id }, - _ => todo!(), - }, - } - .into(), - ), + Block::Special(a) => self.add_block_arr(a), }; } } pub fn step_from_top_block( - top: Block, + top_id: String, + last_next: Option, blocks: &BTreeMap, context: Rc, ) -> Rc { let mut ops: Vec = vec![]; - let mut next_block = top; + let mut next_block = blocks.get(&top_id).unwrap(); + let mut next_id = Some(top_id); loop { - ops.add_block(next_block.clone(), blocks, Rc::clone(&context)); + ops.add_block(next_id.clone().unwrap(), blocks, Rc::clone(&context)); + if next_block.block_info().unwrap().next.is_none() { + next_id = last_next.clone(); + } else { + next_id = next_block.block_info().unwrap().next.clone(); + } assert!(!ops.is_empty()); - if let Some(last_block) = ops.last() { - if last_block.does_request_redraw() - && !(*last_block.opcode() == IrOpcode::looks_say && context.dbg) - { - break; - } + if matches!(ops.last().unwrap().opcode(), IrOpcode::hq_goto { .. }) { + next_id = None; } - if next_block.block_info().unwrap().next.is_none() { + if next_id.is_none() { + break; + } else { + next_block = blocks.get(&next_id.clone().unwrap()).unwrap(); + } + let Some(last_block) = ops.last() else { unreachable!() }; + if last_block.does_request_redraw() + && !(*last_block.opcode() == IrOpcode::looks_say && context.dbg) + { break; } - next_block = blocks - .get(&next_block.block_info().unwrap().next.clone().unwrap()) - .unwrap() - .clone(); } let mut type_stack: Vec<(usize, BlockType)> = vec![]; let mut expected_outputs: Vec<(usize, BlockType)> = vec![]; @@ -698,9 +714,9 @@ pub fn step_from_top_block( } } assert!( - type_stack.is_empty(), - "type stack too big (expected 0 items at end of step, got {})", - type_stack.len() + type_stack.is_empty(), + "type stack too big (expected 0 items at end of step, got {})", + type_stack.len() ); for (index, ty) in expected_outputs { ops.get_mut(index) @@ -708,16 +724,22 @@ pub fn step_from_top_block( .set_expected_output(ty.clone()); } let mut step = Step::new(ops.clone(), Rc::clone(&context)); - if let Some(ref next_id) = next_block.block_info().unwrap().next { - step.opcodes_mut().push(IrBlock::from(IrOpcode::hq_goto { + step.opcodes_mut().push(if let Some(ref id) = next_id.clone() { + IrBlock::from(IrOpcode::hq_goto { step: Some(Rc::clone(&step_from_top_block( - blocks.get(next_id).unwrap().clone(), + id.clone(), + if last_next != Some(id.clone()) { last_next.clone() } else { None }, blocks, Rc::clone(&context), ))), does_yield: true, - })); - } + }) + } else { + IrBlock::from(IrOpcode::hq_goto { + step: None, + does_yield: false, + }) + }); Rc::from(step) } @@ -741,11 +763,7 @@ impl Thread { ) -> Thread { let first_step = if let Block::Normal { block_info, .. } = &hat { if let Some(next_id) = &block_info.next { - if let Some(next_block) = blocks.get(next_id) { - step_from_top_block(next_block.clone(), &blocks, Rc::clone(&context)) - } else { - unreachable!(); - } + step_from_top_block(next_id.clone(), None, &blocks, Rc::clone(&context)) } else { unreachable!(); } diff --git a/src/sb3.rs b/src/sb3.rs index df76ee5..ed3d60c 100644 --- a/src/sb3.rs +++ b/src/sb3.rs @@ -219,6 +219,29 @@ pub enum Block { Special(BlockArray), } +/*impl Block { + pub fn fixup_nexts(&mut self, blocks: &mut BTreeMap, final_next: Option) { + use BlockOpcode::*; + let Some(bi) = self.block_info() else { + return; + }; + if matches!(bi.opcode, control_if | control_if_else | control_forever | control_repeat | control_repeat_until | control_while | control_for_each) { + for (name, input) in &bi.inputs { + if matches!(name.as_str(), "SUBSTACK" | "SUBSTACK2") { + if let BlockArrayOrId::Id(input_id) = input.get_1().unwrap().clone().unwrap() { + blocks.bottom_block_mut(&input_id).fixup_nexts(blocks, bi.next.clone());//.block_info_mut().unwrap().next = next; + } + } + } + } + if bi.next.is_none() { + self.block_info_mut().unwrap().next = final_next; + } else { + blocks.get_mut(&bi.next.clone().unwrap()).unwrap().fixup_nexts(blocks, final_next); + } + } +}*/ + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum BlockArray { @@ -235,14 +258,14 @@ pub enum BlockArrayOrId { Array(BlockArray), } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, EnumFieldGetter)] #[serde(untagged)] pub enum Input { Shadow(u32, Option, Option), NoShadow(u32, Option), } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, EnumFieldGetter)] #[serde(untagged)] pub enum Field { Value((Option,)), @@ -328,21 +351,21 @@ pub enum VariableInfo { CloudVar(String, VarVal, bool), LocalVar(String, VarVal), } - +/* pub trait BlockMap { - fn get_bottom_block(&self, id: &str) -> &Block; + fn bottom_block<'a>(&'a self, id: &'a str) -> String; } impl BlockMap for BTreeMap { - fn get_bottom_block(&self, id: &str) -> &Block { + fn bottom_block<'a>(&'a self, id: &'a str) -> String { let block = self.get(id).unwrap(); if let Some(next) = &block.block_info().unwrap().next { - self.get_bottom_block(next) + self.bottom_block(next) } else { - block + id.to_string() } } -} +}*/ #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -451,7 +474,20 @@ impl TryFrom<&str> for Sb3Project { use serde_json::error::Category; let sb3: Result = serde_json::from_str(string); match sb3 { - Ok(proj) => Ok(proj), + Ok(proj) => Ok(proj), /*{ + for target in &mut proj.targets { + for (id, _block) in target.blocks.iter().filter(|(_id, block)| block.block_info().is_some() && block.block_info().unwrap().parent.is_none()) { + target.blocks = target.blocks.fixup_nexts(id.to_string(), None); + } + //target.blocks.fixup_nexts(); + // for (_id, block) in &mut target.blocks { + // if block.block_info().is_some() && block.block_info().unwrap().parent.is_none() { + // block.fixup_nexts(&mut target.blocks, None); + // } + // } + } + Ok(proj) + },*/ Err(err) => Err(match err.classify() { Category::Syntax => "Invalid JSON syntax", Category::Data => "Invalid project.json", diff --git a/src/targets/wasm.rs b/src/targets/wasm.rs index 2d59461..e8b8b38 100644 --- a/src/targets/wasm.rs +++ b/src/targets/wasm.rs @@ -187,6 +187,7 @@ fn instructions( "10 ^" => vec![Call(func_indices::MATHOP_POW10)], _ => panic!("invalid OPERATOR field (E041)"), }, + hq_drop(n) => vec![Drop; 2 * *n], hq_goto { step, does_yield: true, @@ -350,7 +351,8 @@ fn instructions( .try_into() .expect("THREAD_INDICES out of bounds (E018)"); vec![ - If(WasmBlockType::FunctionType(types::NOPARAM_I32)), + I32WrapI64, + If(WasmBlockType::Empty), //WasmBlockType::FunctionType(types::NOPARAM_I32)), LocalGet(0), I32Const( next_step_index @@ -374,6 +376,8 @@ fn instructions( .expect("vars.len() out of bounds (E032)"); vec![ + I32WrapI64, + If(WasmBlockType::Empty), //WasmBlockType::FunctionType(types::NOPARAM_I32)), LocalGet(0), I32Const(byte_offset::THREADS), I32Add, // destination (= current thread pos in memory) @@ -418,6 +422,7 @@ fn instructions( }), I32Const(0), Return, + End, ] } } @@ -428,7 +433,8 @@ fn instructions( if let Some(next_step) = step { let next_step_index = next_step.compile_wasm(step_funcs, string_consts); vec![ - If(WasmBlockType::FunctionType(types::NOPARAM_I32)), + I32WrapI64, + If(WasmBlockType::Empty), //WasmBlockType::FunctionType(types::NOPARAM_I32)), LocalGet(step_func_locals::MEM_LOCATION), I32Const( next_step_index @@ -450,6 +456,8 @@ fn instructions( .expect("vars.len() out of bounds (E032)"); vec![ + I32WrapI64, + If(WasmBlockType::Empty), //WasmBlockType::FunctionType(types::NOPARAM_I32)), LocalGet(0), I32Const(byte_offset::THREADS), I32Add, // destination (= current thread pos in memory) @@ -494,6 +502,7 @@ fn instructions( }), I32Const(0), Return, + End, ] } } @@ -588,65 +597,6 @@ impl CompileToWasm for Rc { func.instruction(&instr); } } - let _thread_indices: u64 = byte_offset::THREADS - .try_into() - .expect("THREAD_INDICES out of bounds (E018)"); - let vars_num: i32 = self - .context() - .vars - .len() - .try_into() - .expect("vars.len() out of bounds (E032)"); - if !matches!( - self.opcodes().last().unwrap().opcode(), - IrOpcode::hq_goto { .. } - ) { - func.instruction(&Instruction::LocalGet(0)); - func.instruction(&Instruction::I32Const(byte_offset::THREADS)); - func.instruction(&Instruction::I32Add); // destination (= current thread pos in memory) - func.instruction(&Instruction::LocalGet(0)); - func.instruction(&Instruction::I32Const(byte_offset::THREADS + 4)); - func.instruction(&Instruction::I32Add); // source (= current thread pos + 4) - func.instruction(&Instruction::I32Const(0)); - func.instruction(&Instruction::I32Load(MemArg { - offset: byte_offset::THREAD_NUM - .try_into() - .expect("THREAD_NUM out of bounds (E009)"), - align: 2, - memory_index: 0, - })); - func.instruction(&Instruction::I32Const(4)); - func.instruction(&Instruction::I32Mul); - func.instruction(&Instruction::I32Const( - byte_offset::THREADS - 4 + vars_num * 12, - )); - func.instruction(&Instruction::I32Add); - func.instruction(&Instruction::LocalGet(0)); - func.instruction(&Instruction::I32Sub); // length (threadnum * 4 + THREADS offset - 4 + number of variables - current thread pos) - func.instruction(&Instruction::MemoryCopy { - src_mem: 0, - dst_mem: 0, - }); - func.instruction(&Instruction::I32Const(0)); - func.instruction(&Instruction::I32Const(0)); - func.instruction(&Instruction::I32Load(MemArg { - offset: byte_offset::THREAD_NUM - .try_into() - .expect("THREAD_NUM out of bounds (E010)"), - align: 2, - memory_index: 0, - })); - func.instruction(&Instruction::I32Const(1)); - func.instruction(&Instruction::I32Sub); - func.instruction(&Instruction::I32Store(MemArg { - offset: byte_offset::THREAD_NUM - .try_into() - .expect("THREAD_NUM out of bounds (E011)"), - align: 2, - memory_index: 0, - })); - func.instruction(&Instruction::I32Const(0)); - } func.instruction(&Instruction::End); step_funcs.insert(Some(Rc::clone(self)), func); (step_funcs.len() - 1) @@ -1522,7 +1472,7 @@ mod tests { } let output = Command::new("node") .arg("-e") - .arg(wasm.js_string()) + .arg(format!("({})()", wasm.js_string())) .stdout(Stdio::inherit()) .output() .expect("failed to execute process");