diff --git a/src/blocklang.rs b/src/blocklang.rs new file mode 100644 index 0000000..c3be6c8 --- /dev/null +++ b/src/blocklang.rs @@ -0,0 +1,1583 @@ +// Copyright (c) 2022 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +use std::cell::RefCell; +use std::rc::Rc; + +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +pub trait BlockView { + fn rows(&self) -> usize; + fn contains(&self, idx: usize) -> Option; + fn expanded(&self) -> bool; + fn label(&self, buf: &mut [u8]) -> usize; + fn has_input(&self, idx: usize) -> bool; + fn has_output(&self, idx: usize) -> bool; + fn input_label(&self, idx: usize, buf: &mut [u8]) -> usize; + fn output_label(&self, idx: usize, buf: &mut [u8]) -> usize; + fn custom_color(&self) -> Option; +} + +pub trait BlockCodeView { + fn area_header(&self, id: usize) -> Option<&str>; + fn area_size(&self, id: usize) -> (usize, usize); + fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView>; + fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)>; + fn generation(&self) -> u64; +} + +#[derive(Debug, Clone)] +pub struct BlockIDGenerator { + counter: Rc>, +} + +impl BlockIDGenerator { + pub fn new() -> Self { + Self { counter: Rc::new(RefCell::new(0)) } + } + + pub fn new_with_id(id: usize) -> Self { + Self { counter: Rc::new(RefCell::new(id)) } + } + + pub fn current(&self) -> usize { + *self.counter.borrow_mut() + } + + pub fn next(&self) -> usize { + let mut c = self.counter.borrow_mut(); + *c += 1; + *c + } +} + +/// This structure represents a block inside the [BlockArea] of a [BlockFun]. +/// It stores everything required for calculating a node of the AST. +/// +/// A [BlockType::instanciate_block] is used to create a new instance of this +/// structure. +/// +/// You usually don't use this structure directly, but you use the +/// position of it inside the [BlockFun]. The position of a block +/// is specified by the `area_id`, and the `x` and `y` coordinates. +#[derive(Debug, Clone)] +pub struct Block { + /// An ID to track this block. + id: usize, + /// How many rows this block spans. A [Block] can only be 1 cell wide. + rows: usize, + /// Up to two sub [BlockArea] can be specified here by their ID. + contains: (Option, Option), + /// Whether the sub areas are visible/drawn. + expanded: bool, + /// The type of this block. It's just a string set by the [BlockType] + /// and it should be everything that determines what this block is + /// going to end up as in the AST. + typ: String, + /// The label of the block. + lbl: String, + /// The input ports, the index into the [Vec] is the row. The [String] + /// is the label of the input port. + inputs: Vec>, + /// The output ports, the index into the [Vec] is the row. The [String] + /// is the label of the output port. + outputs: Vec>, + /// The color index of this block. + color: usize, +} + +impl Block { + pub fn clone_with_new_id(&self, new_id: usize) -> Self { + Self { + id: new_id, + rows: self.rows, + contains: self.contains.clone(), + expanded: self.expanded, + typ: self.typ.clone(), + lbl: self.lbl.clone(), + inputs: self.inputs.clone(), + outputs: self.outputs.clone(), + color: self.color, + } + } + + /// Takes the (input) port at row `idx` and pushed it one row further + /// down, wrapping around at the end. If `output` is true, the + /// output port at `idx` is shifted. + pub fn shift_port(&mut self, idx: usize, output: bool) { + if self.rows <= 1 { + return; + } + + let v = if output { &mut self.outputs } else { &mut self.inputs }; + + if v.len() < self.rows { + v.resize(self.rows, None); + } + + let idx_from = idx; + let idx_to = (idx + 1) % v.len(); + let elem = v.remove(idx_from); + v.insert(idx_to, elem); + } + + /// Calls `f` for every output port that is available. + /// `f` gets passed the row index. + pub fn for_output_ports(&self, mut f: F) { + for i in 0..self.rows { + if let Some(p) = self.outputs.get(i) { + if let Some(p) = p { + f(i, p); + } + } + } + } + + /// Returns the number of output ports of this [Block]. + pub fn count_outputs(&self) -> usize { + let mut count = 0; + + for i in 0..self.rows { + if let Some(o) = self.outputs.get(i) { + if o.is_some() { + count += 1; + } + } + } + + count + } + + /// Calls `f` for every input port that is available. + /// `f` gets passed the row index. + pub fn for_input_ports(&self, mut f: F) { + for i in 0..self.rows { + if let Some(p) = self.inputs.get(i) { + if let Some(p) = p { + f(i, p); + } + } + } + } + + /// Calls `f` for every input port that is available. + /// `f` gets passed the row index. + pub fn for_input_ports_reverse(&self, mut f: F) { + for i in 1..=self.rows { + let i = self.rows - i; + if let Some(p) = self.inputs.get(i) { + if let Some(p) = p { + f(i, p); + } + } + } + } +} + +impl BlockView for Block { + fn rows(&self) -> usize { + self.rows + } + fn contains(&self, idx: usize) -> Option { + if idx == 0 { + self.contains.0 + } else { + self.contains.1 + } + } + fn expanded(&self) -> bool { + true + } + fn label(&self, buf: &mut [u8]) -> usize { + use std::io::Write; + let mut bw = std::io::BufWriter::new(buf); + match write!(bw, "{}", self.lbl) { + Ok(_) => bw.buffer().len(), + _ => 0, + } + } + fn has_input(&self, idx: usize) -> bool { + self.inputs.get(idx).map(|s| s.is_some()).unwrap_or(false) + } + fn has_output(&self, idx: usize) -> bool { + self.outputs.get(idx).map(|s| s.is_some()).unwrap_or(false) + } + fn input_label(&self, idx: usize, buf: &mut [u8]) -> usize { + use std::io::Write; + if let Some(lbl_opt) = self.inputs.get(idx) { + if let Some(lbl) = lbl_opt { + let mut bw = std::io::BufWriter::new(buf); + match write!(bw, "{}", lbl) { + Ok(_) => bw.buffer().len(), + _ => 0, + } + } else { + 0 + } + } else { + 0 + } + } + fn output_label(&self, idx: usize, buf: &mut [u8]) -> usize { + use std::io::Write; + if let Some(lbl_opt) = self.outputs.get(idx) { + if let Some(lbl) = lbl_opt { + let mut bw = std::io::BufWriter::new(buf); + match write!(bw, "{}", lbl) { + Ok(_) => bw.buffer().len(), + _ => 0, + } + } else { + 0 + } + } else { + 0 + } + } + fn custom_color(&self) -> Option { + Some(self.color) + } +} + +/// Represents a connected collection of blocks. Is created by +/// [BlockFun::retrieve_block_chain_at] or [BlockArea::chain_at]. +/// +/// After creating a [BlockChain] structure you can decide to +/// clone the blocks from the [BlockArea] with [BlockChain::clone_load] +/// or remove the blocks from the [BlockArea] and store them +/// inside this [BlockChain] via [BlockChain::remove_load]. +/// +/// The original positions of the _loaded_ blocks is stored too. +/// If you want to move the whole chain in the coordinate system +/// to the upper left most corner, you can use [BlockChain::normalize_load_pos]. +#[derive(Debug)] +pub struct BlockChain { + /// The area ID this BlockChain was created from. + area_id: usize, + /// Stores the positions of the blocks of the chain inside the [BlockArea]. + blocks: HashSet<(i64, i64)>, + /// Stores the positions of blocks that only have output ports. + sources: HashSet<(i64, i64)>, + /// Stores the positions of blocks that only have input ports. + sinks: HashSet<(i64, i64)>, + /// This field stores _loaded_ blocks from the [BlockArea] + /// into this [BlockChain] for inserting or analyzing them. + /// + /// Stores the blocks themself, with their position in the [BlockArea], + /// which can be normalized (moved to the upper left) with + /// [BlockChain::normalize_load_pos]. + /// + /// The blocks in this [Vec] are stored in sorted order. + /// They are stored in ascending order of their `x` coordinate, + /// and for the same `x` coordinate in + /// ascending order of their `y` coordinate. + load: Vec<(Box, i64, i64)>, +} + +impl BlockChain { + pub fn move_by_offs(&mut self, xo: i64, yo: i64) { + for (_, x, y) in &mut self.load { + *x += xo; + *y += yo; + //d// println!("MOVE_BY_OFFS TO x={:3} y={:3}", *x, *y); + } + } + + /// Normalizes the position of all loaded blocks and returns + /// the original top left most position of the chain. + pub fn normalize_load_pos(&mut self) -> (i64, i64) { + let mut min_x = 100000000; + let mut min_y = 100000000; + + for (_, xo, yo) in &self.load { + min_x = min_x.min(*xo); + min_y = min_y.min(*yo); + } + + for (_, xo, yo) in &mut self.load { + *xo -= min_x; + *yo -= min_y; + } + + self.sort_load_pos(); + + (min_x, min_y) + } + + fn sort_load_pos(&mut self) { + self.load.sort_by(|&(_, x0, y0), &(_, x1, y1)| x0.cmp(&x1).then(y0.cmp(&y1))); + } + + pub fn get_connected_inputs_from_load_at_x(&self, x_split: i64) -> Vec<(i64, i64)> { + let mut output_points = vec![]; + for (block, x, y) in &self.load { + if *x == x_split { + block.for_output_ports(|row, _| { + output_points.push(y + (row as i64)); + }); + } + } + + let mut connection_pos = vec![]; + + for (block, x, y) in &self.load { + if *x == (x_split + 1) { + block.for_input_ports(|row, _| { + if output_points.iter().find(|&&out_y| out_y == (y + (row as i64))).is_some() { + connection_pos.push((*x, y + (row as i64))); + } + }); + } + } + + connection_pos + } + + // pub fn join_load_after_x(&mut self, x_join: i64, y_split: i64) -> bool { + // let filler_pos : Vec<(i64, i64)> = + // self.get_connected_inputs_from_load_at_x(x_split); + // if filler_pos.len() > 1 + // || (filler_pos.len() == 1 && filler_pos[0] != (x_join, y_split) + // } + + pub fn split_load_after_x( + &mut self, + x_split: i64, + y_split: i64, + filler: Option<&BlockType>, + id_gen: BlockIDGenerator, + ) { + let filler_pos: Vec<(i64, i64)> = self.get_connected_inputs_from_load_at_x(x_split); + + for (_block, x, _y) in &mut self.load { + if *x > x_split { + *x += 1; + } + } + + if let Some(filler) = filler { + for (x, y) in filler_pos { + if y == y_split { + continue; + } + let filler_block = filler.instanciate_block(None, id_gen.clone()); + + self.load.push((filler_block, x, y)); + } + } + + self.sort_load_pos(); + } + + pub fn clone_load(&mut self, area: &mut BlockArea, id_gen: BlockIDGenerator) { + self.load.clear(); + + for b in &self.blocks { + if let Some((block, xo, yo)) = area.ref_at_origin(b.0, b.1) { + self.load.push((Box::new(block.clone_with_new_id(id_gen.next())), xo, yo)); + } + } + + self.sort_load_pos(); + } + + pub fn remove_load(&mut self, area: &mut BlockArea) { + self.load.clear(); + + for b in &self.blocks { + if let Some((block, xo, yo)) = area.remove_at(b.0, b.1) { + self.load.push((block, xo, yo)); + } + } + + self.sort_load_pos(); + } + + pub fn place_load(&mut self, area: &mut BlockArea) { + let load = std::mem::replace(&mut self.load, vec![]); + area.set_blocks_from(load); + } + + pub fn try_fit_load_into_space(&mut self, area: &mut BlockArea) -> bool { + for (xo, yo) in &[ + (0, 0), // where it currently is + (0, -1), + (0, -2), + (0, -3), + (-1, 0), + (-1, -1), + (-1, -2), + (-1, -3), + (1, 0), + (1, -1), + (1, -2), + (1, -3), + (0, 1), + (0, 2), + (0, 3), + (-1, 1), + (-1, 2), + (-1, 3), + (1, 1), + (1, 2), + (1, 3), + ] { + println!("TRY {},{}", *xo, *yo); + if self.area_has_space_for_load(area, *xo, *yo) { + self.move_by_offs(*xo, *yo); + return true; + } + + //d// println!("RETRY xo={}, yo={}", *xo, *yo); + } + + return false; + } + + pub fn area_has_space_for_load( + &mut self, + area: &mut BlockArea, + xoffs: i64, + yoffs: i64, + ) -> bool { + for (block, x, y) in self.load.iter() { + if !area.check_space_at(*x + xoffs, *y + yoffs, block.rows) { + return false; + } + } + + true + } + + pub fn area_is_subarea_of_loaded(&mut self, area: usize, fun: &mut BlockFun) -> bool { + let mut areas = vec![]; + + for (block, _, _) in self.load.iter() { + fun.all_sub_areas_of(block.as_ref(), &mut areas); + } + + for a_id in areas.iter() { + if *a_id == area { + return true; + } + } + + return false; + } +} + +#[derive(Debug, Clone)] +pub struct BlockArea { + blocks: HashMap<(i64, i64), Box>, + origin_map: HashMap<(i64, i64), (i64, i64)>, + size: (usize, usize), + auto_shrink: bool, + header: String, +} + +impl BlockArea { + fn new(w: usize, h: usize) -> Self { + Self { + blocks: HashMap::new(), + origin_map: HashMap::new(), + size: (w, h), + auto_shrink: false, + header: "".to_string(), + } + } + + pub fn set_header(&mut self, header: String) { + self.header = header; + } + + pub fn set_auto_shrink(&mut self, shrink: bool) { + self.auto_shrink = shrink; + } + + pub fn auto_shrink(&self) -> bool { + self.auto_shrink + } + + pub fn chain_at(&self, x: i64, y: i64) -> Option> { + let (_block, xo, yo) = self.ref_at_origin(x, y)?; + + let mut dq: VecDeque<(i64, i64)> = VecDeque::new(); + dq.push_back((xo, yo)); + + let mut blocks: HashSet<(i64, i64)> = HashSet::new(); + let mut sources: HashSet<(i64, i64)> = HashSet::new(); + let mut sinks: HashSet<(i64, i64)> = HashSet::new(); + + let mut check_port_conns = vec![]; + + while let Some((x, y)) = dq.pop_front() { + check_port_conns.clear(); + + // First we find all adjacent output/input port positions + // and collect them in `check_port_conns`. + // + // While are at it, we also record which blocks are only + // sinks and which are only sources. Might be important for + // other algorithms that do things with this. + if let Some((block, xo, yo)) = self.ref_at_origin(x, y) { + if blocks.contains(&(xo, yo)) { + continue; + } + + blocks.insert((xo, yo)); + + let mut has_input = false; + let mut has_output = false; + + block.for_input_ports(|idx, _| { + check_port_conns.push((xo - 1, yo + (idx as i64), true)); + has_input = true; + }); + + block.for_output_ports(|idx, _| { + check_port_conns.push((xo + 1, yo + (idx as i64), false)); + has_output = true; + }); + + if !has_input { + sources.insert((xo, yo)); + } + + if !has_output { + sinks.insert((xo, yo)); + } + } + + // Then we look if there is a block at that position, with + // a corresponding input or output port at the right + // row inside the block. + for (x, y, is_output) in &check_port_conns { + if let Some((_block, xo, yo, _row)) = self.find_port_at(*x, *y, *is_output) { + dq.push_back((xo, yo)); + } + } + } + + Some(Box::new(BlockChain { area_id: 0, blocks, sources, sinks, load: vec![] })) + } + + pub fn find_last_unconnected_output(&self) -> Option<(i64, i64, String)> { + let mut max_x = 0; + let mut max_y = 0; + let mut port: Option<(i64, i64, String)> = None; + + for ((x, y), block) in &self.blocks { + let (x, y) = (*x, *y); + + block.for_output_ports(|row, _| { + let y = y + (row as i64); + + if self.find_port_at(x + 1, y, false).is_none() { + if y > max_y { + max_y = y; + max_x = x; + + port = Some(( + max_x, + max_y, + block + .outputs + .get(row) + .cloned() + .flatten() + .unwrap_or_else(|| "".to_string()), + )); + } else if y == max_y && x > max_x { + max_x = x; + + port = Some(( + max_x, + max_y, + block + .outputs + .get(row) + .cloned() + .flatten() + .unwrap_or_else(|| "".to_string()), + )); + } + } + }) + } + + port + } + + /// Collects the sinks in this area. + /// It returns a list of [Block] positions inside the + /// area. For unconnected outputs, which are also evaluated + /// and returned as possible last value of an [BlockArea], + /// the output row is also given. + /// + /// The result is sorted so, that the bottom right most element + /// is the first one in the result list. + pub fn collect_sinks(&self) -> Vec<(i64, i64, Option)> { + let mut sinks_out = vec![]; + + for ((x, y), block) in &self.blocks { + if block.count_outputs() == 0 { + sinks_out.push((*x, *y, None)); + } else { + block.for_output_ports(|row, _| { + if self.find_port_at(*x + 1, *y + (row as i64), false).is_none() { + sinks_out.push((*x, *y + (row as i64), Some(row))); + } + }); + } + } + + sinks_out.sort_by(|&(x0, y0, _), &(x1, y1, _)| y1.cmp(&y0).then(x1.cmp(&x0))); + + sinks_out + } + + fn ref_at(&self, x: i64, y: i64) -> Option<&Block> { + let (xo, yo) = self.origin_map.get(&(x, y))?; + self.blocks.get(&(*xo, *yo)).map(|b| b.as_ref()) + } + + fn ref_at_origin(&self, x: i64, y: i64) -> Option<(&Block, i64, i64)> { + let (xo, yo) = self.origin_map.get(&(x, y))?; + let (xo, yo) = (*xo, *yo); + self.blocks.get(&(xo, yo)).map(|b| (b.as_ref(), xo, yo)) + } + + fn ref_mut_at(&mut self, x: i64, y: i64) -> Option<&mut Block> { + let (xo, yo) = self.origin_map.get(&(x, y))?; + self.blocks.get_mut(&(*xo, *yo)).map(|b| b.as_mut()) + } + + fn ref_mut_at_origin(&mut self, x: i64, y: i64) -> Option<(&mut Block, i64, i64)> { + let (xo, yo) = self.origin_map.get(&(x, y))?; + let (xo, yo) = (*xo, *yo); + self.blocks.get_mut(&(xo, yo)).map(|b| (b.as_mut(), xo, yo)) + } + + fn find_port_at( + &self, + x: i64, + y: i64, + expect_output: bool, + ) -> Option<(&Block, i64, i64, usize)> { + let (block, xo, yo) = self.ref_at_origin(x, y)?; + + let port_y = (y - yo).max(0) as usize; + + if expect_output { + if let Some(o) = block.outputs.get(port_y) { + if o.is_some() { + return Some((block, xo, yo, port_y)); + } + } + } else { + if let Some(i) = block.inputs.get(port_y) { + if i.is_some() { + return Some((block, xo, yo, port_y)); + } + } + } + + None + } + + fn set_blocks_from(&mut self, list: Vec<(Box, i64, i64)>) { + for (block, x, y) in list.into_iter() { + self.blocks.insert((x, y), block); + } + + self.update_origin_map(); + } + + fn set_block_at(&mut self, x: i64, y: i64, block: Box) { + self.blocks.insert((x, y), block); + self.update_origin_map(); + } + + fn remove_at(&mut self, x: i64, y: i64) -> Option<(Box, i64, i64)> { + let (xo, yo) = self.origin_map.get(&(x, y))?; + if let Some(block) = self.blocks.remove(&(*xo, *yo)) { + let (xo, yo) = (*xo, *yo); + self.update_origin_map(); + Some((block, xo, yo)) + } else { + None + } + } + + fn set_size(&mut self, w: usize, h: usize) { + self.size = (w, h); + } + + fn get_direct_sub_areas(&self, out: &mut Vec) { + for ((_x, _y), block) in &self.blocks { + if let Some(sub_area) = block.contains.0 { + out.push(sub_area); + } + + if let Some(sub_area) = block.contains.1 { + out.push(sub_area); + } + } + } + + /// Calculates only the size of the area in the +x/+y quadrant. + /// The negative areas are not counted in. + fn resolve_size (usize, usize)>(&self, resolve_sub_areas: F) -> (usize, usize) { + let mut min_w = 1; + let mut min_h = 1; + + for ((ox, oy), _) in &self.origin_map { + let (ox, oy) = ((*ox).max(0) as usize, (*oy).max(0) as usize); + + if min_w < (ox + 1) { + min_w = ox + 1; + } + if min_h < (oy + 1) { + min_h = oy + 1; + } + } + + for ((x, y), block) in &self.blocks { + let (x, y) = ((*x).max(0) as usize, (*y).max(0) as usize); + + let mut prev_h = 1; // one for the top block + + if let Some(sub_area) = block.contains.0 { + let (sub_w, mut sub_h) = resolve_sub_areas(sub_area); + sub_h += prev_h; + prev_h += sub_h; + if min_w < (x + sub_w + 1) { + min_w = x + sub_w + 1; + } + if min_h < (y + sub_h + 1) { + min_h = y + sub_h + 1; + } + } + + if let Some(sub_area) = block.contains.1 { + let (sub_w, mut sub_h) = resolve_sub_areas(sub_area); + sub_h += prev_h; + if min_w < (x + sub_w + 1) { + min_w = x + sub_w + 1; + } + if min_h < (y + sub_h + 1) { + min_h = y + sub_h + 1; + } + } + } + + if self.auto_shrink { + (min_w, min_h) + } else { + ( + if self.size.0 < min_w { min_w } else { self.size.0 }, + if self.size.1 < min_h { min_h } else { self.size.1 }, + ) + } + } + + fn update_origin_map(&mut self) { + self.origin_map.clear(); + + for ((ox, oy), block) in &self.blocks { + for r in 0..block.rows { + self.origin_map.insert((*ox, *oy + (r as i64)), (*ox, *oy)); + } + } + } + + fn check_space_at(&self, x: i64, y: i64, rows: usize) -> bool { + for i in 0..rows { + let yo = y + (i as i64); + + if self.origin_map.get(&(x, yo)).is_some() { + return false; + } + } + + true + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlockUserInput { + None, + Float, + Integer, + Identifier, + ClientDecision, +} + +impl Default for BlockUserInput { + fn default() -> Self { + Self::None + } +} + +impl BlockUserInput { + pub fn needs_input(&self) -> bool { + *self != BlockUserInput::None + } +} + +#[derive(Debug, Clone, Default)] +pub struct BlockType { + pub category: String, + pub name: String, + pub rows: usize, + pub inputs: Vec>, + pub outputs: Vec>, + pub area_count: usize, + pub user_input: BlockUserInput, + pub description: String, + pub color: usize, +} + +impl BlockType { + fn touch_contains(&self, block: &mut Block) { + block.contains = match self.area_count { + 0 => (None, None), + 1 => (Some(1), None), + 2 => (Some(1), Some(1)), + _ => (None, None), + }; + } + + pub fn instanciate_block( + &self, + user_input: Option, + id_gen: BlockIDGenerator, + ) -> Box { + let mut block = Box::new(Block { + id: id_gen.next(), + rows: self.rows, + contains: (None, None), + expanded: true, + typ: self.name.clone(), + lbl: if let Some(inp) = user_input { inp } else { self.name.clone() }, + inputs: self.inputs.clone(), + outputs: self.outputs.clone(), + color: self.color, + }); + self.touch_contains(&mut *block); + block + } +} + +#[derive(Debug, Clone)] +pub struct BlockLanguage { + types: HashMap, + identifiers: HashMap, +} + +impl BlockLanguage { + pub fn new() -> Self { + Self { types: HashMap::new(), identifiers: HashMap::new() } + } + + pub fn define_identifier(&mut self, id: &str) { + let v = id.to_string(); + self.identifiers.insert(id.to_string(), v); + } + + pub fn define(&mut self, typ: BlockType) { + self.types.insert(typ.name.clone(), typ); + } + + pub fn is_identifier(&self, id: &str) -> bool { + self.identifiers.get(id).is_some() + } + + pub fn list_identifiers(&self) -> Vec { + let mut identifiers: Vec = self.identifiers.keys().cloned().collect(); + identifiers.sort(); + identifiers + } + + pub fn get_type_list(&self) -> Vec<(String, String, BlockUserInput)> { + let mut out = vec![]; + for (_, typ) in &self.types { + out.push((typ.category.clone(), typ.name.clone(), typ.user_input)); + } + out + } +} + +pub trait BlockASTNode: std::fmt::Debug + Clone { + fn from(id: usize, typ: &str, lbl: &str) -> Self; + fn add_node(&self, in_port: String, out_port: String, node: Self); + fn add_structural_node(&self, node: Self) { + self.add_node("".to_string(), "".to_string(), node); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BlockDSPError { + UnknownArea(usize), + UnknownLanguageType(String), + NoBlockAt(usize, i64, i64), + CircularAction(usize, usize), + NoSpaceAvailable(usize, i64, i64, usize), +} + +#[derive(Debug, Clone)] +pub struct BlockFunSnapshot { + areas: Vec>, + cur_id: usize, +} + +#[derive(Debug, Clone)] +pub struct BlockFun { + language: Rc>, + areas: Vec>, + size_work_dq: VecDeque, + area_work_dq: VecDeque, + id_gen: BlockIDGenerator, + generation: u64, +} + +#[derive(Debug)] +enum GenTreeJob { + Node { node: N, out: N }, + Output { area_id: usize, x: i64, y: i64, in_port: String, out: N }, + Sink { area_id: usize, x: i64, y: i64, out: N }, + Area { area_id: usize, out: N }, +} + +impl BlockFun { + pub fn new(lang: Rc>) -> Self { + Self { + language: lang, + areas: vec![Box::new(BlockArea::new(16, 16))], + size_work_dq: VecDeque::new(), + area_work_dq: VecDeque::new(), + id_gen: BlockIDGenerator::new(), + generation: 0, + } + } + + pub fn block_ref(&self, id: usize, x: i64, y: i64) -> Option<&Block> { + let area = self.areas.get(id)?; + area.ref_at(x, y) + } + + pub fn block_ref_mut(&mut self, id: usize, x: i64, y: i64) -> Option<&mut Block> { + let area = self.areas.get_mut(id)?; + area.ref_mut_at(x, y) + } + + pub fn shift_port(&mut self, id: usize, x: i64, y: i64, row: usize, output: bool) { + if let Some(block) = self.block_ref_mut(id, x, y) { + block.shift_port(row, output); + self.generation += 1; + } + } + + pub fn save_snapshot(&self) -> BlockFunSnapshot { + BlockFunSnapshot { + areas: self.areas.iter().cloned().collect(), + cur_id: self.id_gen.current(), + } + } + + pub fn load_snapshot(&mut self, repr: &BlockFunSnapshot) { + self.areas = repr.areas.iter().cloned().collect(); + self.id_gen = BlockIDGenerator::new_with_id(repr.cur_id); + self.recalculate_area_sizes(); + self.generation += 1; + } + + pub fn generate_tree(&self, null_typ: &str) -> Result { + // This is a type for filling in unfilled outputs: + let lang = self.language.borrow(); + let null_typ = lang + .types + .get(null_typ) + .ok_or(BlockDSPError::UnknownLanguageType(null_typ.to_string()))? + .name + .to_string(); + + // Next we build the root AST node set: + let mut tree_builder: Vec> = vec![]; + + let main_node = Node::from(0, "", ""); + + tree_builder.push(GenTreeJob::::Area { area_id: 0, out: main_node.clone() }); + + // A HashMap to store those blocks, that have multiple outputs. + // Their AST nodes need to be shared to multiple parent nodes. + let mut multi_outs: HashMap<(usize, i64, i64), Node> = HashMap::new(); + + // We do a depth first search here: + while let Some(job) = tree_builder.pop() { + match job { + GenTreeJob::::Area { area_id, out } => { + let area = + self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; + + let sinks = area.collect_sinks(); + + let area_node = Node::from(0, "", ""); + out.add_structural_node(area_node.clone()); + + for (x, y, uncon_out_row) in sinks { + if let Some(_row) = uncon_out_row { + let result_node = Node::from(0, "", ""); + + tree_builder.push(GenTreeJob::::Output { + area_id, + x, + y, + in_port: "".to_string(), + out: result_node.clone(), + }); + + tree_builder.push(GenTreeJob::::Node { + node: result_node, + out: area_node.clone(), + }); + } else { + tree_builder.push(GenTreeJob::::Sink { + area_id, + x, + y, + out: area_node.clone(), + }); + } + } + } + GenTreeJob::::Node { node, out } => { + out.add_structural_node(node); + } + GenTreeJob::::Sink { area_id, x, y, out } => { + let area = + self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; + + if let Some((block, xo, yo)) = area.ref_at_origin(x, y) { + let (node, needs_init) = + if let Some(node) = multi_outs.get(&(area_id, xo, yo)) { + (node.clone(), false) + } else { + (Node::from(block.id, &block.typ, &block.lbl), true) + }; + + out.add_structural_node(node.clone()); + + if needs_init { + multi_outs.insert((area_id, xo, yo), node.clone()); + + if let Some(cont_area_id) = block.contains.1 { + tree_builder.push(GenTreeJob::::Area { + area_id: cont_area_id, + out: node.clone(), + }); + } + + if let Some(cont_area_id) = block.contains.0 { + tree_builder.push(GenTreeJob::::Area { + area_id: cont_area_id, + out: node.clone(), + }); + } + + block.for_input_ports_reverse(|row, port_name| { + tree_builder.push(GenTreeJob::::Output { + area_id, + x: xo - 1, + y: yo + (row as i64), + in_port: port_name.to_string(), + out: node.clone(), + }); + }); + } + } + } + GenTreeJob::::Output { area_id, x, y, in_port, out } => { + let area = + self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; + + if let Some((block, xo, yo)) = area.ref_at_origin(x, y) { + let row = y - yo; + + let (node, needs_init) = + if let Some(node) = multi_outs.get(&(area_id, xo, yo)) { + (node.clone(), false) + } else { + (Node::from(block.id, &block.typ, &block.lbl), true) + }; + + if let Some(out_name) = block.outputs.get(row as usize).cloned().flatten() { + out.add_node(in_port, out_name, node.clone()); + } else { + let node = Node::from(0, &null_typ, ""); + out.add_node(in_port, "".to_string(), node.clone()); + } + + if needs_init { + multi_outs.insert((area_id, xo, yo), node.clone()); + + if let Some(cont_area_id) = block.contains.1 { + tree_builder.push(GenTreeJob::::Area { + area_id: cont_area_id, + out: node.clone(), + }); + } + + if let Some(cont_area_id) = block.contains.0 { + tree_builder.push(GenTreeJob::::Area { + area_id: cont_area_id, + out: node.clone(), + }); + } + + block.for_input_ports_reverse(|row, port_name| { + tree_builder.push(GenTreeJob::::Output { + area_id, + x: xo - 1, + y: yo + (row as i64), + in_port: port_name.to_string(), + out: node.clone(), + }); + }); + } + } else { + let node = Node::from(0, &null_typ, ""); + out.add_node(in_port, "".to_string(), node.clone()); + } + } + } + } + + Ok(main_node) + } + + pub fn recalculate_area_sizes(&mut self) { + let mut parents = vec![0; self.areas.len()]; + let mut sizes = vec![(0, 0); self.areas.len()]; + + // First we dive downwards, to record all the parents + // and get the sizes of the (leafs). + + self.area_work_dq.clear(); + self.size_work_dq.clear(); + + let parents_work_list = &mut self.area_work_dq; + let size_work_list = &mut self.size_work_dq; + + // Push the root area: + parents_work_list.push_back(0); + + let mut cur_sub = vec![]; + while let Some(area_idx) = parents_work_list.pop_back() { + cur_sub.clear(); + + self.areas[area_idx].get_direct_sub_areas(&mut cur_sub); + + // XXX: The resolver gets (0, 0), thats wrong for the + // areas with sub areas. But it resolves the leaf area + // sizes already correctly! + let (w, h) = self.areas[area_idx].resolve_size(|_id| (0, 0)); + sizes[area_idx] = (w, h); + + if cur_sub.len() == 0 { + size_work_list.push_front(area_idx); + } else { + for sub_idx in &cur_sub { + // XXX: Record the parent: + parents[*sub_idx] = area_idx; + parents_work_list.push_back(*sub_idx); + } + } + } + + // XXX: Invariant now is: + // - `parents` contains all the parent area IDs. + // - `size_work_list` contains all the leaf area IDs. + // - `sizes` contains correct sizes for the leafs + // (but wrong for the non leafs). + + // Next we need to work through the size_work_list upwards. + // That means, for each leaf in front of the Deque, + // we push the parent to the back. + while let Some(area_idx) = size_work_list.pop_front() { + // XXX: The invariant as we walk upwards is, that once we + // encounter a parent area ID in the size_work_list, + // we know that all sub areas already have been computed. + let (w, h) = self.areas[area_idx].resolve_size(|id| sizes[id]); + sizes[area_idx] = (w, h); + self.areas[area_idx].set_size(w, h); + + // XXX: area_idx == 0 is the root area, so skip that + // when pushing further parents! + if area_idx > 0 { + size_work_list.push_back(parents[area_idx]); + } + } + } + + pub fn area_is_subarea_of(&mut self, area_id: usize, a_id: usize, x: i64, y: i64) -> bool { + let mut areas = vec![]; + + let block = if let Some(block) = self.block_ref(a_id, x, y) { + block.clone() + } else { + return false; + }; + + self.all_sub_areas_of(&block, &mut areas); + + for a_id in &areas { + if area_id == *a_id { + return true; + } + } + + return false; + } + + pub fn all_sub_areas_of(&mut self, block: &Block, areas: &mut Vec) { + let contains = block.contains.clone(); + + let area_work_list = &mut self.area_work_dq; + area_work_list.clear(); + + if let Some(area_id) = contains.0 { + area_work_list.push_back(area_id); + } + if let Some(area_id) = contains.1 { + area_work_list.push_back(area_id); + } + + if area_work_list.len() <= 0 { + return; + } + + let mut cur_sub = vec![]; + while let Some(area_idx) = area_work_list.pop_front() { + areas.push(area_idx); + + cur_sub.clear(); + self.areas[area_idx].get_direct_sub_areas(&mut cur_sub); + + for sub_idx in &cur_sub { + area_work_list.push_back(*sub_idx); + } + } + } + + pub fn retrieve_block_chain_at( + &mut self, + id: usize, + x: i64, + y: i64, + remove_blocks: bool, + ) -> Option> { + let area = self.areas.get_mut(id)?; + let mut chain = area.chain_at(x, y)?; + + if remove_blocks { + chain.remove_load(area); + } else { + chain.clone_load(area, self.id_gen.clone()); + } + + Some(chain) + } + + pub fn clone_block_from_to( + &mut self, + id: usize, + x: i64, + y: i64, + id2: usize, + x2: i64, + mut y2: i64, + ) -> Result<(), BlockDSPError> { + let lang = self.language.clone(); + + let (mut block, _xo, yo) = if let Some(area) = self.areas.get_mut(id) { + let (block, xo, yo) = + area.ref_mut_at_origin(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; + + let mut new_block = Box::new(block.clone_with_new_id(self.id_gen.next())); + if let Some(typ) = lang.borrow().types.get(&new_block.typ) { + typ.touch_contains(new_block.as_mut()); + } + + (new_block, xo, yo) + } else { + return Err(BlockDSPError::UnknownArea(id)); + }; + + self.create_areas_for_block(block.as_mut()); + + // check if the user grabbed at a different row than the top row: + if y > yo { + // if so, adjust the destination: + let offs = y - yo; + y2 = (y2 - offs).max(0); + } + + let area2 = self.areas.get_mut(id2).ok_or(BlockDSPError::UnknownArea(id2))?; + let rows = block.rows; + + if area2.check_space_at(x2, y2, block.rows) { + area2.set_block_at(x2, y2, block); + self.generation += 1; + Ok(()) + } else { + Err(BlockDSPError::NoSpaceAvailable(id2, x2, y2, rows)) + } + } + + pub fn split_block_chain_after( + &mut self, + id: usize, + x: i64, + y: i64, + filler_type: Option<&str>, + ) -> Result<(), BlockDSPError> { + let mut area_clone = self.areas.get(id).ok_or(BlockDSPError::UnknownArea(id))?.clone(); + + let mut chain = area_clone.chain_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; + + chain.remove_load(area_clone.as_mut()); + + let lang = self.language.borrow(); + let typ: Option<&BlockType> = if let Some(filler_type) = filler_type { + Some( + lang.types + .get(filler_type) + .ok_or(BlockDSPError::UnknownLanguageType(filler_type.to_string()))?, + ) + } else { + None + }; + + chain.split_load_after_x(x, y, typ, self.id_gen.clone()); + + if !chain.area_has_space_for_load(&mut area_clone, 0, 0) { + return Err(BlockDSPError::NoSpaceAvailable(id, x, y, 0)); + } + + chain.place_load(&mut area_clone); + self.generation += 1; + + self.areas[id] = area_clone; + + Ok(()) + } + + pub fn move_block_chain_from_to( + &mut self, + id: usize, + x: i64, + y: i64, + id2: usize, + x2: i64, + y2: i64, + ) -> Result<(), BlockDSPError> { + let mut area_clone = self.areas.get(id).ok_or(BlockDSPError::UnknownArea(id))?.clone(); + + let mut chain = area_clone.chain_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; + + chain.remove_load(area_clone.as_mut()); + self.generation += 1; + + if id2 == id { + let move_x_offs = x2 - x; + let move_y_offs = y2 - y; + chain.move_by_offs(move_x_offs, move_y_offs); + + if !chain.try_fit_load_into_space(&mut area_clone) { + return Err(BlockDSPError::NoSpaceAvailable(id, x2, y2, 0)); + } + + chain.place_load(&mut area_clone); + self.areas[id] = area_clone; + } else { + // id2 != id + if chain.area_is_subarea_of_loaded(id2, self) { + return Err(BlockDSPError::CircularAction(id, id2)); + } + + let (xo, yo) = chain.normalize_load_pos(); + let (grab_x_offs, grab_y_offs) = (xo - x, yo - y); + + // println!("xo={}, yo={}, grab_x={}, grab_y={}, x2={}, y2={}", + // xo, yo, grab_x_offs, grab_y_offs, x2, y2); + + // XXX: .max(0) prevents us from moving the + // chain outside the subarea accendentally! + chain.move_by_offs((grab_x_offs + x2).max(0), (grab_y_offs + y2).max(0)); + + let mut area2_clone = + self.areas.get(id2).ok_or(BlockDSPError::UnknownArea(id))?.clone(); + + if !chain.try_fit_load_into_space(&mut area2_clone) { + return Err(BlockDSPError::NoSpaceAvailable(id, x2, y2, 1)); + } + + chain.place_load(&mut area2_clone); + + self.areas[id] = area_clone; + self.areas[id2] = area2_clone; + } + + self.generation += 1; + + // let mut chain = + // self.retrieve_block_chain_at(id, x, y, true) + // .ok_or(BlockDSPError::NoBlockAt(id, x, y))?; + // + // chain.normalize_load_pos(); + + Ok(()) + } + + pub fn move_block_from_to( + &mut self, + id: usize, + x: i64, + y: i64, + id2: usize, + x2: i64, + mut y2: i64, + ) -> Result<(), BlockDSPError> { + if self.area_is_subarea_of(id2, id, x, y) { + return Err(BlockDSPError::CircularAction(id, id2)); + } + + let (block, xo, yo) = if let Some(area) = self.areas.get_mut(id) { + area.remove_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))? + } else { + return Err(BlockDSPError::UnknownArea(id)); + }; + + // check if the user grabbed at a different row than the top row: + if y > yo { + // if so, adjust the destination: + let offs = y - yo; + y2 = (y2 - offs).max(0); + } + + let area2 = self.areas.get_mut(id2).ok_or(BlockDSPError::UnknownArea(id2))?; + let rows = block.rows; + + self.generation += 1; + + if area2.check_space_at(x2, y2, block.rows) { + area2.set_block_at(x2, y2, block); + Ok(()) + } else { + if let Some(area) = self.areas.get_mut(id) { + area.set_block_at(xo, yo, block); + } + Err(BlockDSPError::NoSpaceAvailable(id2, x2, y2, rows)) + } + } + + fn create_areas_for_block(&mut self, block: &mut Block) { + if let Some(area_id) = &mut block.contains.0 { + let mut area = Box::new(BlockArea::new(1, 1)); + area.set_auto_shrink(true); + self.areas.push(area); + *area_id = self.areas.len() - 1; + } + + if let Some(area_id) = &mut block.contains.1 { + let mut area = Box::new(BlockArea::new(1, 1)); + area.set_auto_shrink(true); + self.areas.push(area); + *area_id = self.areas.len() - 1; + } + } + + pub fn instanciate_at( + &mut self, + id: usize, + x: i64, + y: i64, + typ: &str, + user_input: Option, + ) -> Result<(), BlockDSPError> { + let mut block = { + let lang = self.language.borrow(); + + if let Some(area) = self.areas.get_mut(id) { + if let Some(typ) = lang.types.get(typ) { + if !area.check_space_at(x, y, typ.rows) { + return Err(BlockDSPError::NoSpaceAvailable(id, x, y, typ.rows)); + } + } + } else { + return Err(BlockDSPError::UnknownArea(id)); + } + + let typ = + lang.types.get(typ).ok_or(BlockDSPError::UnknownLanguageType(typ.to_string()))?; + + typ.instanciate_block(user_input, self.id_gen.clone()) + }; + + self.create_areas_for_block(block.as_mut()); + + self.generation += 1; + + if let Some(area) = self.areas.get_mut(id) { + area.set_block_at(x, y, block); + } + + Ok(()) + } + + pub fn remove_at(&mut self, id: usize, x: i64, y: i64) -> Result<(), BlockDSPError> { + let area = self.areas.get_mut(id).ok_or(BlockDSPError::UnknownArea(id))?; + area.remove_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; + self.generation += 1; + Ok(()) + } + + pub fn area_size(&self, id: usize) -> (usize, usize) { + self.areas.get(id).map(|a| a.size).unwrap_or((0, 0)) + } + + pub fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView> { + let area = self.areas.get(id)?; + Some(area.blocks.get(&(x, y))?.as_ref()) + } + + pub fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)> { + self.areas.get(id).map(|a| a.origin_map.get(&(x, y)).copied()).flatten() + } +} + +impl BlockCodeView for BlockFun { + fn area_header(&self, id: usize) -> Option<&str> { + self.areas.get(id).map(|a| &a.header[..]) + } + + fn area_size(&self, id: usize) -> (usize, usize) { + self.area_size(id) + } + + fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView> { + self.block_at(id, x, y) + } + + fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)> { + self.origin_at(id, x, y) + } + + fn generation(&self) -> u64 { + self.generation + } +} diff --git a/src/lib.rs b/src/lib.rs index eab30ed..6e671bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,6 +316,7 @@ pub mod sample_lib; pub mod scope_handle; #[cfg(feature="wblockdsp")] pub mod wblockdsp; +pub mod blocklang; mod util; pub use cell_dir::CellDir;