Rail开发日志_5

设定一个目标。在原有的框架上实现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
相关推荐
浪费笔墨1 天前
Rail开发日志_3
rail