设定一个目标。在原有的框架上实现Rail
类型。这是最最最基本的数据载体,基本得以至于拿它命名这个语言。
测试项
代码先这么写,想好期望的目标。
rust
fn main() {
test(
String::from(r"
a1 = b1
a2 = b2
+ c1
a3 = b3
a4 = b4
a5 = b5
+ c2
a6 = b6
+ c3
a7 = b7
+ c4
")
);
}
构建思路
在into_tokens()
之前不需要改动,因为词法分析和语言无关。
其实很简单,只要追踪Comment
就知道代码往哪加了。这是在Rail
之前构建Comment
得唯一原因。
无脑乱写,最终的测试代码如下。
rust
use std::mem;
const OMMISION: &str = "/";
const CATEGORY: &str = "+";
const RAIL: &str = "=";
#[derive(Debug, Clone)]
enum Line {
PlaceHolder,
Comment { content: String },
Rail { name: String, content: String },
Category { name: String },
}
#[derive(Debug)]
enum Node {
PlaceHolder,
Comment { content: String },
Rail { name: String, content: String },
Domain { category: Line, rails: Vec<Node> },
}
struct Root {
name: String,
rails: Vec<Node>,
}
fn into_tokens(lines_input: &str) -> (Vec<Vec<String>>, Vec<usize>) {
let mut word_current = String::new();
let mut line_current = Vec::new();
let mut lines_list_splited = Vec::new();
let mut indent_current = 0;
let mut indent_list = Vec::new();
let mut indent_state_in = true;
for char in lines_input.chars() {
match char {
'\n' => {
if !word_current.is_empty() {
line_current.push(word_current.clone());
word_current.clear();
}
if !line_current.is_empty() {
lines_list_splited.push(line_current.clone());
indent_list.push(indent_current);
}
line_current.clear();
indent_current = 0;
indent_state_in = true;
}
' ' => {
if indent_state_in {
indent_current += 1;
} else if !word_current.is_empty() {
line_current.push(word_current.clone());
word_current.clear();
}
}
other => {
indent_state_in = false;
word_current.push(other);
}
}
}
if !word_current.is_empty() {
line_current.push(word_current);
}
if !line_current.is_empty() {
lines_list_splited.push(line_current);
indent_list.push(indent_current);
}
let indent_list = indent_list.into_iter().map(|x| x / 2).collect();
(lines_list_splited, indent_list)
}
fn trim_lines(lines: &mut Vec<Vec<String>>) {
let trim = |s: &str| -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.peek() {
Some('_') => {
chars.next();
result.push(' ');
}
Some('\\') => {
chars.next();
result.push('\\');
}
_ => result.push(c),
}
} else {
result.push(c);
}
}
result
};
for line in lines {
for word in line.iter_mut() {
*word = trim(word);
}
}
}
fn into_lines(token_list: &Vec<Vec<String>>, range: usize) -> Vec<Line> {
let mut local = Vec::new();
for i in 0..range {
let token = &token_list[i];
let lenth = token.len();
let data = match lenth {
1 => match token[0].as_str() {
OMMISION => Line::PlaceHolder,
_ => continue,
},
2 => match token[0].as_str() {
OMMISION => Line::Comment { content: token[1].clone() },
CATEGORY => Line::Category { name: token[1].clone() },
_ => continue,
},
3 => match token[1].as_str() {
RAIL => Line::Rail { name: token[0].clone(), content: token[2].clone() },
_ => continue
}
_ => continue,
};
local.push(data);
}
local
}
fn into_nodes(domain_name: &str, indent_list: Vec<usize>, line_list: Vec<Line>, range: usize) -> Root {
let mut root = Root { name: domain_name.to_string(), rails: Vec::new() };
let mut stack: Vec<Vec<Node>> = vec![vec![]];
let mut context = 0;
for i in 0..range {
let indent = indent_list[i];
let indent_move = indent as isize - context as isize;
let line = &line_list[i];
if indent_move < 0 {
for _ in 0..-indent_move {
if let Some(child_layer) = stack.pop() {
if let Some(parent_layer) = stack.last_mut() {
if let Some(Node::Domain { rails, .. }) = parent_layer.last_mut() {
*rails = child_layer;
}
}
}
}
}
let node = match line {
Line::PlaceHolder => Node::PlaceHolder,
Line::Comment { content } => Node::Comment { content: content.clone() },
Line::Rail { name, content } => Node::Rail { name: name.clone(), content: content.clone()},
Line::Category { name } => Node::Domain {
category: Line::Category { name: name.clone() },
rails: Vec::new(),
},
};
if indent_move > 0 {
if let Some(current_layer) = stack.last_mut() {
if let Some(Node::Domain { rails, .. }) = current_layer.last_mut() {
let new_layer = mem::take(rails);
stack.push(new_layer);
}
}
}
if let Some(current_layer) = stack.last_mut() {
current_layer.push(node);
}
context = indent;
}
while let Some(child_layer) = stack.pop() {
if let Some(parent_layer) = stack.last_mut() {
if let Some(Node::Domain { rails, .. }) = parent_layer.last_mut() {
*rails = child_layer;
}
} else {
root.rails = child_layer;
}
}
root
}
fn print_node(node: &Node, indent: usize) {
let indent_str = "_".repeat(indent);
match node {
Node::PlaceHolder => println!("{indent_str}/"),
Node::Comment { content } => println!("{indent_str}/ {}", content),
Node::Rail { name, content } => println!("{indent_str}{} = {}", name, content),
Node::Domain { category, rails } => {
match category {
Line::Category { name } => println!("{indent_str}+ {}", name),
_ => {}
}
for child in rails {
print_node(child, indent + 2);
}
}
}
}
fn check_nodes(root: &Root) {
println!("[{}]", root.name);
for node in &root.rails {
print_node(node, 0);
}
}
fn main() {
test(
String::from(r"
a1 = b1
a2 = b2
+ c1
a3 = b3
a4 = b4
a5 = b5
+ c2
a6 = b6
+ c3
a7 = b7
+ c4
")
);
}
fn test(input: String) {
let (mut line_list, indent_list) = into_tokens(&input);
trim_lines(&mut line_list);
let range = line_list.len();
let lines = into_lines(&line_list, range);
let root = into_nodes("local", indent_list, lines, range);
check_nodes(&root);
}
测试结果与预期一致。
rust
[local]
a1 = b1
a2 = b2
+ c1
__a3 = b3
__a4 = b4
__a5 = b5
__+ c2
____a6 = b6
____+ c3
__a7 = b7
__+ c4