trees_lang/
compile.rs

1/// Module containing error types for the compiler.
2pub mod errors;
3
4use std::cmp::Ordering;
5
6use errors::CompileError;
7use unicode_width::UnicodeWidthStr;
8
9/// Stores settings used during code compilation.
10///
11/// This struct is used to configure how character widths are interpreted during the
12/// compilation process for now.
13///
14/// # Example
15///
16/// ```rust
17/// use trees_lang::compile::{CompileConfig, CharWidthMode};
18///
19/// let config = CompileConfig {
20///     char_width: CharWidthMode::Full,
21/// };
22/// ```
23///
24/// This configuration can then be passed to compilation functions such as `split_code`
25/// or `find_blocks` to control how character positions and widths are calculated.
26#[derive(Debug, Clone)]
27pub struct CompileConfig {
28  /// Character width mode used during compilation.
29  pub char_width: CharWidthMode,
30}
31
32impl CompileConfig {
33  /// Default setup for compile
34  pub const DEFAULT: CompileConfig = CompileConfig {
35    char_width: CharWidthMode::Mono,
36  };
37}
38
39/// Determines how character widths are calculated during code parsing.
40///
41/// This enum controls how ambiguous-width characters (such as those in East Asian scripts)
42/// are interpreted during layout calculations. It affects how each character contributes to
43/// the horizontal spacing of visual elements.
44#[derive(Debug, Clone)]
45pub enum CharWidthMode {
46  /// Treat all characters as width 1.
47  Mono,
48  /// Treat ambiguous-width characters as half-width.
49  Half,
50  /// Treat ambiguous-width characters as full-width.
51  Full,
52}
53
54/// A single character in the source code with layout metadata.
55///
56/// Used internally to track the character, its x-position, and width in a parsed line.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct CodeCharacter {
59  /// The character itself.
60  pub char: String,
61  /// The x position (column) of the character.
62  pub x: usize,
63  /// The display width of the character.
64  pub len: usize,
65}
66
67/// A code that has been split into lines and characters for processing
68///
69/// # Example
70/// ```
71/// use trees_lang::compile::{SplitedCode, split_code, CharWidthMode, CompileConfig, CodeCharacter};
72///
73/// // Split each character
74/// let mut splited_code: SplitedCode = split_code(
75///   &vec![" ┌─".to_owned()],
76///   &CompileConfig {
77///     char_width: CharWidthMode::Mono
78///   }
79/// );
80///
81/// // Get each characters' position
82/// // (It is useful if char_width is not Mono)
83/// assert_eq!(splited_code.enumurate_x(0).collect::<Vec<_>>(), vec![0, 1, 2]);
84///
85/// // Get char of target position
86/// assert_eq!(splited_code.get(1, 0), Some(CodeCharacter {
87///   char: "┌".to_owned(), x: 1, len: 1
88/// }));
89/// ```
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct SplitedCode {
92  body: Vec<Vec<CodeCharacter>>,
93}
94
95impl SplitedCode {
96  /// Retrieves a specific character at position `(x, y)` from the split code.
97  ///
98  /// If the position is out of bounds or does not contain a character, returns `None`.
99  pub fn get(&self, x: usize, y: usize) -> Option<CodeCharacter> {
100    self.body.get(y)?.iter().find(|cc| cc.x == x).cloned()
101  }
102
103  /// Retrieves a slice of characters from the specified line between `x_min_exclusive` and `x_max_exclusive`.
104  ///
105  /// If the range is invalid or out of bounds, returns `None`.
106  pub fn get_slice_of_line(&self, x_min_exclusive: usize, x_max_exclusive: usize, y: usize) -> Option<String> {
107    let (mut index, _) = self.body.get(y)?.iter().enumerate().find(|(_index, cc)| cc.x == x_min_exclusive)?;
108    let mut return_str = "".to_string();
109
110    index += 1;
111
112    while let Some(cc) = self.body.get(y)?.get(index) {
113      match cc.x.cmp(&x_max_exclusive) {
114        Ordering::Equal => break,
115        Ordering::Greater => return None,
116        Ordering::Less => {}
117      }
118      return_str += &cc.char;
119
120      index += 1;
121    }
122
123    Some(return_str.to_string())
124  }
125
126  /// Retrieves the position of the character to the left of the given `(x, y)` position.
127  ///
128  /// If there is no character to the left, returns `None`.
129  pub fn left_x(&self, x: usize, y: usize) -> Option<usize> {
130    let index =
131      self.body.get(y)?.iter().enumerate().find_map(|(index, cc)| if cc.x == x { Some(index) } else { None })?;
132    self.body.get(y)?.get(index - 1).map(|cc| cc.x)
133  }
134
135  /// Retrieves the position of the character to the right of the given `(x, y)` position.
136  ///
137  /// If there is no character to the right, returns `None`.
138  pub fn right_x(&self, x: usize, y: usize) -> Option<usize> {
139    let index =
140      self.body.get(y)?.iter().enumerate().find_map(|(index, cc)| if cc.x == x { Some(index) } else { None })?;
141    self.body.get(y)?.get(index + 1).map(|cc| cc.x)
142  }
143
144  fn new() -> Self {
145    SplitedCode { body: vec![vec![]] }
146  }
147
148  fn append(&mut self, char: &str, char_width: &CharWidthMode) {
149    let now_line = self.body.last_mut().unwrap();
150
151    let x = if now_line.is_empty() {
152      0
153    } else {
154      now_line.last().unwrap().x + now_line.last().unwrap().len
155    };
156
157    let width = char.width();
158    let width_cjk = char.width_cjk();
159
160    now_line.push(CodeCharacter {
161      char: char.to_string(),
162      x,
163      len: match char_width {
164        CharWidthMode::Mono => 1,
165        CharWidthMode::Half => width,
166        CharWidthMode::Full => width_cjk,
167      },
168    });
169  }
170  fn new_line(&mut self) {
171    self.body.push(vec![]);
172  }
173
174  /// Returns the number of lines in the `SplitedCode`.
175  pub fn len_y(&self) -> usize {
176    self.body.len()
177  }
178
179  /// Enumerates the x-positions of all characters in the given line `y`.\
180  pub fn enumurate_x(&self, y: usize) -> Box<dyn std::iter::Iterator<Item = usize> + '_> {
181    Box::new(self.body[y].iter().map(|cc| cc.x))
182  }
183}
184
185/// A parsed visual block in the code, including its position, size, and connections.
186///
187/// This is an intermediate representation used during compilation before converting to a `Block`.
188///
189/// # Example
190/// ```rust
191/// use trees_lang::compile::{CompilingBlock, ArgPlug, BlockPlug, Orientation,
192///                             CompileConfig, CharWidthMode, split_code, find_blocks, connect_blocks};
193///
194/// let code = vec![
195///     "    ".to_owned(),
196///     "    ┌───────┐".to_owned(),
197///     "    │ abc   │    ".to_owned(),
198///     "    └───┬───┘   ".to_owned(),
199///     "    ┌───┴──┐".to_owned(),
200///     "    │ def  │    ".to_owned(),
201///     "    └──────┘   ".to_owned(),
202/// ];
203///
204/// let config = CompileConfig {
205///   char_width: CharWidthMode::Mono
206/// };
207/// let splited_code = split_code(&code, &config);
208/// let mut blocks = find_blocks(&splited_code, &config);
209/// let head_block: CompilingBlock = connect_blocks(&splited_code, &mut blocks, &config).unwrap();
210///
211/// assert_eq!(head_block.proc_name, "abc");
212/// assert_eq!(head_block.arg_plugs.len(), 1);
213/// assert_eq!(head_block.args.len(), 1);
214/// ```
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct CompilingBlock {
217  /// Procedure name in the block.
218  pub proc_name: String,
219  /// X position (column) of the top-left of the block.
220  pub x: usize,
221  /// Y position (row) of the top-left of the block.
222  pub y: usize,
223  /// Width of the block.
224  pub width: usize,
225  /// Height of the block.
226  pub height: usize,
227  /// Optional block plug for connecting this block to others.
228  pub block_plug: Option<BlockPlug>,
229  /// Edge connecting block-plug of this block to another block.
230  ///
231  /// This is setted by `connect_blocks` function. Before that, it is empty.
232  pub connect_from: Option<Edge>,
233  /// Argument plugs for this block.
234  pub arg_plugs: Vec<ArgPlug>,
235  /// Edges (connections) representing the arguments passed to this block.
236  ///
237  /// This is setted by `connect_blocks` function. Before that, it is empty.
238  pub args: Vec<Edge>,
239}
240
241/// An argument plug in a `CompilingBlock`, indicating where an argument can be connected.
242///
243/// Used for tracking the connection points for arguments in a visual block, including the position and orientation.
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct ArgPlug {
246  /// X position of the plug.
247  pub x: usize,
248  /// Y position of the plug.
249  pub y: usize,
250  /// Whether the argument plug is expand or not. (Typically "@").
251  pub expand: bool,
252  /// Orientation of the plug (direction from which argument connects).
253  pub ori: Orientation,
254}
255
256/// A fragment of an edge in the code's flow, with position and direction.
257///
258/// Tracks a specific piece of an edge connection, indicating the direction and location of a flow path.
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct EdgeFragment {
261  /// X coordinate of the edge fragment.
262  pub x: usize,
263  /// Y coordinate of the edge fragment.
264  pub y: usize,
265  /// Orientation of this edge fragment.
266  pub ori: Orientation,
267}
268
269/// An edge connecting two blocks, including fragments and plug information.
270///
271/// Used to describe the connections between blocks and their arguments in a visual code flow.
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct Edge {
274  /// Index of the block that owns the argument plug.
275  pub block_index_of_arg_plug: usize,
276  /// Information about the argument plug.
277  pub arg_plug_info: ArgPlug,
278  /// Sequence of fragments composing this edge.
279  pub fragments: Vec<EdgeFragment>,
280  /// Index of the block that the argument plug is connected to.
281  pub block_index_of_block_plug: usize,
282}
283
284/// A plug point for a block, where it can be connected to other blocks.
285///
286/// Tracks the position of a plug and its associated quote style for connection.
287#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct BlockPlug {
289  /// X position of the plug.
290  pub x: usize,
291  /// Y position of the plug.
292  pub y: usize,
293  /// The quoting style used at this plug.
294  pub quote: QuoteStyle,
295}
296
297/// Direction/orientation used for argument and edge routing.
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum Orientation {
300  /// The upward direction.
301  Up,
302  /// The leftward direction.
303  Left,
304  /// The rightward direction.
305  Right,
306  /// The downward direction.
307  Down,
308}
309
310/// Quote style of block.
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub enum QuoteStyle {
313  /// Quote
314  Quote,
315  /// Closure
316  Closure,
317  /// No quote style applied
318  None,
319}
320
321fn find_a_block(code: &SplitedCode, x: usize, y: usize, _config: &CompileConfig) -> Option<CompilingBlock> {
322  let cc = |dx: usize, dy: usize| -> Option<CodeCharacter> { code.get(x + dx, y + dy) };
323  let char = |dx: usize, dy: usize| -> Option<String> { code.get(x + dx, y + dy).map(|x| x.char.clone()) };
324
325  let char_is_in = |dx: usize, dy: usize, targets: &[&str]| -> Option<bool> {
326    let c = char(dx, dy)?;
327
328    let matched = targets.iter().any(|t| *t == c);
329
330    Some(matched)
331  };
332
333  let mut up_plug = None;
334  let mut arg_plugs: Vec<_> = vec![];
335
336  if char(0, 0)? != "┌" {
337    return None;
338  };
339  // 右回り
340  // 1から始める
341  let mut width1 = code.right_x(x, y)? - x;
342  while char_is_in(width1, 0, &["─", "┴", "•", "/"])? {
343    if char_is_in(width1, 0, &["┴", "•", "/"])? {
344      if up_plug.is_some() {
345        return None;
346      }
347      match char(width1, 0)?.as_str() {
348        "┴" => {
349          up_plug = Some(BlockPlug {
350            x: x + width1,
351            y,
352            quote: QuoteStyle::None,
353          });
354        }
355        "•" => {
356          up_plug = Some(BlockPlug {
357            x: x + width1,
358            y,
359            quote: QuoteStyle::Quote,
360          });
361        }
362        "/" => {
363          up_plug = Some(BlockPlug {
364            x: x + width1,
365            y,
366            quote: QuoteStyle::Closure,
367          });
368        }
369        _ => {}
370      }
371    }
372
373    width1 += cc(width1, 0)?.len;
374  }
375  if char(width1, 0)? != "┐" {
376    return None;
377  };
378
379  let mut height1 = 1;
380  while char_is_in(width1, height1, &["│", "├", "@"])? {
381    match char(width1, height1)?.as_str() {
382      "├" => {
383        arg_plugs.push(ArgPlug {
384          x: x + width1,
385          y: y + height1,
386          expand: false,
387          ori: Orientation::Right,
388        });
389      }
390      "@" => {
391        arg_plugs.push(ArgPlug {
392          x: x + width1,
393          y: y + height1,
394          expand: true,
395          ori: Orientation::Right,
396        });
397      }
398      _ => {}
399    }
400    height1 += 1;
401  }
402  if char(width1, height1)? != "┘" {
403    return None;
404  };
405
406  let mut under_width1 = code.right_x(x, y + height1)? - x;
407  while char_is_in(under_width1, height1, &["─", "┬", "@"])? {
408    match char(under_width1, height1)?.as_str() {
409      "┬" => {
410        arg_plugs.push(ArgPlug {
411          x: x + under_width1,
412          y: y + height1,
413          expand: false,
414          ori: Orientation::Down,
415        });
416      }
417      "@" => {
418        arg_plugs.push(ArgPlug {
419          x: x + under_width1,
420          y: y + height1,
421          expand: true,
422          ori: Orientation::Down,
423        });
424      }
425      _ => {}
426    }
427    under_width1 += cc(under_width1, height1)?.len;
428  }
429  if char(0, height1)? != "└" || under_width1 != width1 {
430    return None;
431  };
432
433  let mut under_height1 = 1;
434  while char_is_in(0, under_height1, &["│", "┤", "@"])? {
435    if char(0, under_height1)? == "┤" {
436      arg_plugs.push(ArgPlug {
437        x,
438        y: y + under_height1,
439        expand: false,
440        ori: Orientation::Left,
441      });
442    } else if char(0, under_height1)? == "@" {
443      arg_plugs.push(ArgPlug {
444        x,
445        y: y + under_height1,
446        expand: true,
447        ori: Orientation::Left,
448      });
449    }
450    under_height1 += 1;
451  }
452  if under_height1 != height1 {
453    return None;
454  };
455
456  let mut proc_name = "".to_owned();
457
458  for inside_y in 1..height1 {
459    proc_name += code.get_slice_of_line(x, x + width1, y + inside_y)?.trim();
460    proc_name += "\n";
461  }
462
463  arg_plugs.sort_by(|a, b| {
464    if a.x != b.x {
465      a.x.cmp(&b.x)
466    } else if a.x == x {
467      a.y.cmp(&b.y)
468    } else if a.x == x + width1 {
469      b.y.cmp(&a.y)
470    } else {
471      Ordering::Equal
472    }
473  });
474
475  Some(CompilingBlock {
476    proc_name: proc_name.trim().to_owned(),
477    args: vec![],
478    x,
479    y,
480    width: width1 + cc(width1, 0)?.len,
481    height: height1 + 1,
482    block_plug: up_plug,
483    connect_from: None,
484    arg_plugs,
485  })
486}
487
488/// Finds all the blocks in a given split code according to configuration.
489///
490/// Returns a vector of detected `CompilingBlock`s.
491pub fn find_blocks(splited_code: &SplitedCode, config: &CompileConfig) -> Vec<CompilingBlock> {
492  let mut blocks: Vec<CompilingBlock> = vec![];
493
494  for y in 0..splited_code.len_y() {
495    for x in splited_code.enumurate_x(y) {
496      if let Some(b) = find_a_block(splited_code, x, y, config) {
497        blocks.push(b);
498      }
499    }
500  }
501
502  blocks
503}
504
505fn find_next_edge(code: &SplitedCode, x: &usize, y: &usize, ori: &Orientation) -> Result<EdgeFragment, EdgeFragment> {
506  let update_and_check =
507    |new_x: usize, new_y: usize, up: &str, left: &str, right: &str, down: &str| -> Result<EdgeFragment, EdgeFragment> {
508      let cc = code.get(new_x, new_y).ok_or(EdgeFragment {
509        x: new_x,
510        y: new_y,
511        ori: *ori,
512      })?;
513
514      let t = cc.char;
515      if t == up {
516        Ok(EdgeFragment {
517          x: new_x,
518          y: new_y,
519          ori: Orientation::Up,
520        })
521      } else if t == left {
522        Ok(EdgeFragment {
523          x: new_x,
524          y: new_y,
525          ori: Orientation::Left,
526        })
527      } else if t == right {
528        Ok(EdgeFragment {
529          x: new_x,
530          y: new_y,
531          ori: Orientation::Right,
532        })
533      } else if t == down {
534        Ok(EdgeFragment {
535          x: new_x,
536          y: new_y,
537          ori: Orientation::Down,
538        })
539      } else {
540        Err(EdgeFragment {
541          x: new_x,
542          y: new_y,
543          ori: *ori,
544        })
545      }
546    };
547
548  match ori {
549    Orientation::Up => update_and_check(*x, y - 1, "│", "┐", "┌", ""),
550    Orientation::Left => update_and_check(code.left_x(*x, *y).unwrap_or(*x - 1), *y, "└", "─", "", "┌"),
551    Orientation::Right => update_and_check(
552      code.right_x(*x, *y).unwrap_or(*x + code.get(*x, *y).unwrap().len),
553      *y,
554      "┘",
555      "",
556      "─",
557      "┐",
558    ),
559    Orientation::Down => update_and_check(*x, y + 1, "", "┘", "└", "│"),
560  }
561}
562
563/// Connects the blocks by resolving edges between argument plugs and block plugs.
564///
565/// Returns the root `CompilingBlock` if successful.
566pub fn connect_blocks(
567  code: &SplitedCode,
568  blocks: &mut [CompilingBlock],
569  config: &CompileConfig,
570) -> Result<CompilingBlock, CompileError> {
571  let blocks_cloned = blocks.to_owned();
572
573  let head_candinates: Vec<usize> = blocks
574    .iter()
575    .enumerate()
576    .filter_map(|(i, block)| if block.block_plug.is_some() { None } else { Some(i) })
577    .collect();
578
579  if head_candinates.len() != 1 {
580    return Err(CompileError::NonUniqueStartBlock(Box::new(
581      errors::NonUniqueStartBlockError {
582        candinates: head_candinates.iter().map(|i| blocks[*i].clone()).collect(),
583      },
584    )));
585  }
586  let head = head_candinates[0];
587
588  // 借用権をかわすため、connect_fromは後から入れる。
589  let mut deferred_connections = Vec::new();
590
591  for (block_index, block) in blocks.iter_mut().enumerate() {
592    for arg_plug in block.arg_plugs.iter() {
593      let ArgPlug { x, y, ori, .. } = arg_plug;
594
595      let mut mut_x = *x;
596      let mut mut_y = *y;
597      let mut mut_ori = *ori;
598
599      // Edge構成用
600      let mut fragments = Vec::new();
601
602      loop {
603        match find_next_edge(code, &mut_x, &mut_y, &mut_ori) {
604          Ok(edge) => {
605            mut_x = edge.x;
606            mut_y = edge.y;
607            mut_ori = edge.ori;
608            fragments.push(edge);
609          }
610          Err(edge) => {
611            mut_x = edge.x;
612            mut_y = edge.y;
613            break;
614          }
615        }
616      }
617
618      let (arg_block_index, _) = blocks_cloned
619        .iter()
620        .enumerate()
621        .find(|(_, b)| {
622          if let Some(p) = &b.block_plug {
623            p.x == mut_x && p.y == mut_y
624          } else {
625            false
626          }
627        })
628        .ok_or(CompileError::DanglingArgEdge(Box::new(errors::DanglingArgEdgeError {
629          block_of_arg_plug: block.clone(),
630          arg_plug: arg_plug.clone(),
631          edge_fragments: fragments.clone(),
632          dangling_position: (mut_x, mut_y),
633        })))?;
634
635      let connect_edge = Edge {
636        block_index_of_arg_plug: block_index,
637        arg_plug_info: arg_plug.clone(),
638        fragments,
639        block_index_of_block_plug: arg_block_index,
640      };
641
642      block.args.push(connect_edge.clone());
643
644      // connect_fromをセット
645      deferred_connections.push((arg_block_index, connect_edge.clone()));
646    }
647  }
648
649  for (arg_block_index, connect_edge) in deferred_connections {
650    let block = &mut blocks[arg_block_index];
651    block.connect_from = Some(connect_edge);
652  }
653
654  Ok(blocks[head].clone())
655}
656
657/// Splits a list of code lines into a `SplitedCode` representation.
658///
659/// Each character is measured based on `CharWidthMode`.
660pub fn split_code(code: &Vec<String>, config: &CompileConfig) -> SplitedCode {
661  let mut splited_code = SplitedCode::new();
662
663  for line in code {
664    for char in line.split("") {
665      if !char.is_empty() {
666        splited_code.append(char, &config.char_width);
667      }
668    }
669
670    splited_code.new_line();
671  }
672
673  splited_code
674}
675
676#[cfg(test)]
677mod tests {
678  use crate::compile::{
679    ArgPlug, BlockPlug, CodeCharacter, CompileConfig, CompilingBlock, Edge, EdgeFragment, Orientation, QuoteStyle,
680    SplitedCode,
681    errors::{self, CompileError},
682    find_a_block, find_blocks,
683  };
684
685  use super::{connect_blocks, split_code};
686
687  #[test]
688  fn test_split_code() {
689    let code = vec![" ┌┐".to_owned()];
690    let splited = split_code(&code, &CompileConfig::DEFAULT);
691    let target = SplitedCode {
692      body: vec![
693        vec![
694          CodeCharacter {
695            char: " ".to_owned(),
696            x: 0,
697            len: 1,
698          },
699          CodeCharacter {
700            char: "┌".to_owned(),
701            x: 1,
702            len: 1,
703          },
704          CodeCharacter {
705            char: "┐".to_owned(),
706            x: 2,
707            len: 1,
708          },
709        ],
710        vec![],
711      ],
712    };
713    assert_eq!(splited, target);
714  }
715  #[test]
716  fn test_split_code_cjk() {
717    let mut config = CompileConfig::DEFAULT.clone();
718    config.char_width = crate::compile::CharWidthMode::Full;
719
720    let code = vec![" ┌┐".to_owned()];
721    let splited = split_code(&code, &config);
722    let target = SplitedCode {
723      body: vec![
724        vec![
725          CodeCharacter {
726            char: " ".to_owned(),
727            x: 0,
728            len: 1,
729          },
730          CodeCharacter {
731            char: "┌".to_owned(),
732            x: 1,
733            len: 2,
734          },
735          CodeCharacter {
736            char: "┐".to_owned(),
737            x: 3,
738            len: 2,
739          },
740        ],
741        vec![],
742      ],
743    };
744    assert_eq!(splited, target);
745  }
746
747  #[test]
748  fn test_find_a_block() {
749    let config = CompileConfig::DEFAULT;
750
751    let block = find_a_block(
752      &split_code(
753        &vec![
754          "               ".to_owned(),
755          "    ┌─────┐    ".to_owned(),
756          "    │ abc │    ".to_owned(),
757          "    └─────┘    ".to_owned(),
758          "               ".to_owned(),
759        ],
760        &config,
761      ),
762      4,
763      1,
764      &config,
765    );
766
767    assert_eq!(
768      Some(CompilingBlock {
769        proc_name: "abc".to_string(),
770        x: 4,
771        y: 1,
772        width: 7,
773        height: 3,
774        block_plug: None,
775        connect_from: None,
776        arg_plugs: vec![],
777        args: vec![]
778      }),
779      block
780    );
781  }
782
783  #[test]
784  fn test_find_a_block_cjk() {
785    let mut config = CompileConfig::DEFAULT.clone();
786    config.char_width = crate::compile::CharWidthMode::Full;
787
788    let block = find_a_block(
789      &split_code(
790        &vec![
791          "               ".to_owned(),
792          "    ┌───┐    ".to_owned(),
793          "    │ abc  │    ".to_owned(),
794          "    └───┘    ".to_owned(),
795          "               ".to_owned(),
796        ],
797        &config,
798      ),
799      4,
800      1,
801      &config,
802    );
803
804    assert_eq!(
805      Some(CompilingBlock {
806        proc_name: "abc".to_string(),
807        x: 4,
808        y: 1,
809        width: 10,
810        height: 3,
811        block_plug: None,
812        connect_from: None,
813        arg_plugs: vec![],
814        args: vec![]
815      }),
816      block
817    );
818  }
819
820  #[test]
821  fn check_find_blocks() {
822    let config = CompileConfig::DEFAULT;
823
824    let blocks = find_blocks(
825      &split_code(
826        &vec![
827          "    ".to_owned(),
828          "    ┌───────┐".to_owned(),
829          "    │ abc   │    ".to_owned(),
830          "    └───┬───┘   ".to_owned(),
831          "    ┌───┴──┐".to_owned(),
832          "    │ def  │    ".to_owned(),
833          "    └──────┘   ".to_owned(),
834        ],
835        &config,
836      ),
837      &config,
838    );
839
840    assert_eq!(
841      vec![
842        CompilingBlock {
843          proc_name: "abc".to_owned(),
844          x: 4,
845          y: 1,
846          width: 9,
847          height: 3,
848          block_plug: None,
849          connect_from: None,
850          arg_plugs: vec![ArgPlug {
851            x: 8,
852            y: 3,
853            expand: false,
854            ori: Orientation::Down
855          }],
856          args: vec![]
857        },
858        CompilingBlock {
859          proc_name: "def".to_owned(),
860          x: 4,
861          y: 4,
862          width: 8,
863          height: 3,
864          block_plug: Some(BlockPlug {
865            x: 8,
866            y: 4,
867            quote: QuoteStyle::None
868          }),
869          connect_from: None,
870          arg_plugs: vec![],
871          args: vec![]
872        }
873      ],
874      blocks
875    );
876  }
877
878  #[test]
879  fn check_find_blocks_half() {
880    let mut config = CompileConfig::DEFAULT.clone();
881    config.char_width = crate::compile::CharWidthMode::Half;
882
883    let blocks = find_blocks(
884      &split_code(
885        &vec![
886          "    ".to_owned(),
887          "    ┌───────┐".to_owned(),
888          "    │ あc   │    ".to_owned(),
889          "    └───┬───┘   ".to_owned(),
890          "    ┌───┴──┐".to_owned(),
891          "    │ いf  │    ".to_owned(),
892          "    └──────┘   ".to_owned(),
893        ],
894        &config,
895      ),
896      &config,
897    );
898
899    assert_eq!(
900      vec![
901        CompilingBlock {
902          proc_name: "あc".to_owned(),
903          x: 4,
904          y: 1,
905          width: 9,
906          height: 3,
907          block_plug: None,
908          connect_from: None,
909          arg_plugs: vec![ArgPlug {
910            x: 8,
911            y: 3,
912            expand: false,
913            ori: Orientation::Down
914          }],
915          args: vec![]
916        },
917        CompilingBlock {
918          proc_name: "いf".to_owned(),
919          x: 4,
920          y: 4,
921          width: 8,
922          height: 3,
923          block_plug: Some(BlockPlug {
924            x: 8,
925            y: 4,
926            quote: QuoteStyle::None
927          }),
928          connect_from: None,
929          arg_plugs: vec![],
930          args: vec![]
931        }
932      ],
933      blocks
934    );
935  }
936
937  #[test]
938  fn check_find_blocks_cjk() {
939    let mut config = CompileConfig::DEFAULT.clone();
940    config.char_width = crate::compile::CharWidthMode::Full;
941
942    let blocks = find_blocks(
943      &split_code(
944        &vec![
945          "    ".to_owned(),
946          "    ┌────┐".to_owned(),
947          "    │ abc    │    ".to_owned(),
948          "    └─┬──┘   ".to_owned(),
949          "    ┌─┴─┐".to_owned(),
950          "    │ def  │    ".to_owned(),
951          "    └───┘   ".to_owned(),
952        ],
953        &config,
954      ),
955      &config,
956    );
957
958    assert_eq!(
959      vec![
960        CompilingBlock {
961          proc_name: "abc".to_owned(),
962          x: 4,
963          y: 1,
964          width: 12,
965          height: 3,
966          block_plug: None,
967          connect_from: None,
968          arg_plugs: vec![ArgPlug {
969            x: 8,
970            y: 3,
971            expand: false,
972            ori: Orientation::Down
973          }],
974          args: vec![]
975        },
976        CompilingBlock {
977          proc_name: "def".to_owned(),
978          x: 4,
979          y: 4,
980          width: 10,
981          height: 3,
982          block_plug: Some(BlockPlug {
983            x: 8,
984            y: 4,
985            quote: QuoteStyle::None
986          }),
987          connect_from: None,
988          arg_plugs: vec![],
989          args: vec![]
990        }
991      ],
992      blocks
993    );
994  }
995
996  #[test]
997  fn two_connect() {
998    let splited_code = split_code(
999      &vec![
1000        "    ".to_owned(),
1001        "    ┌───────┐".to_owned(),
1002        "    │ abc   │    ".to_owned(),
1003        "    └───┬───┘   ".to_owned(),
1004        "        │   ".to_owned(),
1005        "    ┌───┴──┐".to_owned(),
1006        "    │ def  │    ".to_owned(),
1007        "    └──────┘   ".to_owned(),
1008      ],
1009      &CompileConfig::DEFAULT,
1010    );
1011
1012    let mut blocks = find_blocks(&splited_code, &CompileConfig::DEFAULT);
1013    let head = connect_blocks(&splited_code, &mut blocks, &CompileConfig::DEFAULT).unwrap();
1014
1015    let arg_edge = Edge {
1016      block_index_of_arg_plug: 0,
1017      arg_plug_info: ArgPlug {
1018        x: 8,
1019        y: 3,
1020        expand: false,
1021        ori: Orientation::Down,
1022      },
1023      fragments: vec![EdgeFragment {
1024        x: 8,
1025        y: 4,
1026        ori: Orientation::Down,
1027      }],
1028      block_index_of_block_plug: 1,
1029    };
1030
1031    assert_eq!(
1032      head,
1033      CompilingBlock {
1034        proc_name: "abc".to_owned(),
1035        x: 4,
1036        y: 1,
1037        width: 9,
1038        height: 3,
1039        block_plug: None,
1040        connect_from: None,
1041        arg_plugs: vec![ArgPlug {
1042          x: 8,
1043          y: 3,
1044          expand: false,
1045          ori: Orientation::Down
1046        }],
1047        args: vec![arg_edge.clone()]
1048      }
1049    );
1050
1051    assert_eq!(
1052      blocks[1],
1053      CompilingBlock {
1054        proc_name: "def".to_owned(),
1055        x: 4,
1056        y: 5,
1057        width: 8,
1058        height: 3,
1059        block_plug: Some(BlockPlug {
1060          x: 8,
1061          y: 5,
1062          quote: QuoteStyle::None
1063        }),
1064        connect_from: Some(arg_edge),
1065        arg_plugs: vec![],
1066        args: vec![]
1067      }
1068    );
1069  }
1070
1071  #[test]
1072  fn error_non_unique_start_block() {
1073    let code = vec![
1074      "    ".to_owned(),
1075      "    ┌───────┐".to_owned(),
1076      "    │ abc   │    ".to_owned(),
1077      "    └───────┘   ".to_owned(),
1078      "    ┌──────┐".to_owned(),
1079      "    │ def  │    ".to_owned(),
1080      "    └──────┘   ".to_owned(),
1081    ];
1082
1083    let splited_code = split_code(&code, &CompileConfig::DEFAULT);
1084    let mut blocks = find_blocks(&splited_code, &CompileConfig::DEFAULT);
1085
1086    let result = connect_blocks(&splited_code, &mut blocks, &CompileConfig::DEFAULT);
1087
1088    assert_eq!(
1089      result,
1090      Err(CompileError::NonUniqueStartBlock(Box::new(
1091        errors::NonUniqueStartBlockError {
1092          candinates: vec![blocks[0].clone(), blocks[1].clone()],
1093        }
1094      )))
1095    );
1096  }
1097
1098  #[test]
1099  fn error_dangling_arg_edge() {
1100    let code = vec![
1101      "    ".to_owned(),
1102      "    ┌───────┐".to_owned(),
1103      "    │ abc   │    ".to_owned(),
1104      "    └───┬───┘   ".to_owned(),
1105      "        │   ".to_owned(),
1106      "               ".to_owned(),
1107      "    ┌───┴──┐".to_owned(),
1108      "    │ def  │    ".to_owned(),
1109      "    └──────┘   ".to_owned(),
1110    ];
1111
1112    let splited_code = split_code(&code, &CompileConfig::DEFAULT);
1113    let mut blocks = find_blocks(&splited_code, &CompileConfig::DEFAULT);
1114
1115    let result = connect_blocks(&splited_code, &mut blocks, &CompileConfig::DEFAULT);
1116
1117    assert_eq!(
1118      result,
1119      Err(CompileError::DanglingArgEdge(Box::new(errors::DanglingArgEdgeError {
1120        block_of_arg_plug: blocks[0].clone(),
1121        arg_plug: blocks[0].arg_plugs[0].clone(),
1122        edge_fragments: vec![EdgeFragment {
1123          x: 8,
1124          y: 4,
1125          ori: Orientation::Down
1126        }],
1127        dangling_position: (8, 5)
1128      })))
1129    );
1130  }
1131
1132  #[test]
1133  fn ignore_two_block_plug() {
1134    let code = vec![
1135      "    ".to_owned(),
1136      "    ┌───────┐".to_owned(),
1137      "    │ abc   │    ".to_owned(),
1138      "    └───────┘   ".to_owned(),
1139      "           ".to_owned(),
1140      "    ┌──┴┴──┐".to_owned(),
1141      "    │ def  │    ".to_owned(),
1142      "    └──────┘   ".to_owned(),
1143    ];
1144
1145    let splited_code = split_code(&code, &CompileConfig::DEFAULT);
1146    let blocks = find_blocks(&splited_code, &CompileConfig::DEFAULT);
1147
1148    assert_eq!(blocks.len(), 1);
1149  }
1150}