本文将封装一些 Token 与 Node 共用的类型界说, 为后续做准备
源码: src/syntax_kind.rs
剖析一下
咱们的需求是要完成表达式的核算以及格式化, 这个核算需求支撑正负数以及括号, 那么对咱们有用的信息只有以下几种
- 数字 1-9
- “+”
- “-“
- “(“
- “)”
那么咱们其实完全能够直接穷举出一切不同的 Token 类型, 而且实际上确实是应该直接穷举
此外也能够顺便把 AST 上的节点类型顺手写了, 咱们需求核算简略的四则运算, 那么就很想当然的需求数字节点和表达式节点, 而表达式节点中通过类型区别不同的核算即可, 总归, 咱们需求这些东西
- 数字节点类型
- 加法表达式节点类型
- 减法表达式节点类型
- 乘法表达式节点类型
- 除法表达式节点类型
写一下
SyntaxKind 结构体
这里就十分简略的界说了一个元组结构体, 结构体中只保存一个 16 位无符号整型, 以此来区别不同的类型即可
#[derive(Debug, PartialOrd, PartialEq, Eq, Copy, Clone)]
pub struct SyntaxKind(pub u16);
// node
pub const NUM: SyntaxKind = SyntaxKind(5);
pub const ADD_EXPR: SyntaxKind = SyntaxKind(6);
pub const SUB_EXPR: SyntaxKind = SyntaxKind(7);
pub const MUL_EXPR: SyntaxKind = SyntaxKind(8);
pub const DIV_EXPR: SyntaxKind = SyntaxKind(9);
// token
pub const OPEN_PAREN: SyntaxKind = SyntaxKind(100);
pub const CLOSE_PAREN: SyntaxKind = SyntaxKind(101);
pub const PLUS: SyntaxKind = SyntaxKind(102);
pub const MINUS: SyntaxKind = SyntaxKind(103);
pub const STAR: SyntaxKind = SyntaxKind(104);
pub const SLASH: SyntaxKind = SyntaxKind(105);
// other
pub const UNKNOW: SyntaxKind = SyntaxKind(65534);
如上咱们就快速界说出了咱们所需的节点类型, 最终的 UNKNOW
类型是本来认为会在某些地方用到的兜底类型, 可是貌似没什么大用
完成一些简略的办法
上面特意将 SyntaxKind
界说为一个结构体就是为了给他增加关联办法, 以下简略写几个会用得到的工具函数
from_operator
用来将字符串类型的操作符转化为 SyntaxKind
类型, 返回值是一个 Option
impl SyntaxKind {
pub fn from_operator(str: &str) -> Option<SyntaxKind> {
let op = match str {
"(" => OPEN_PAREN,
")" => CLOSE_PAREN,
"+" => PLUS,
"-" => MINUS,
"*" => STAR,
"/" => SLASH,
_ => return None,
};
Some(op)
}
}
get_op_priority
获取操作符的优先级, 值越大, 优先级越高
impl SyntaxKind {
pub fn get_op_priority(str: &str) -> usize {
match str {
"*" | "/" => 2,
"+" | "-" => 1,
_ => usize::MAX,
}
}
}
into_str
将一个拥有一切权的 SyntaxKind
类型转化为对应的操作符
impl SyntaxKind {
pub fn into_str<'a>(self) -> &'a str {
match self {
OPEN_PAREN => "(",
CLOSE_PAREN => ")",
PLUS => "+",
MINUS => "-",
STAR => "*",
SLASH => "/",
_ => "unknow",
}
}
}
一个简略的宏
这里是一个简略的宏, 用来直接根据字符串生成对应的操作符 SyntaxKind
类型, 仅仅偷闲用的, 详细原因见 Q&A
#[macro_export]
macro_rules! token {
["("] => { $crate::syntax_kind::OPEN_PAREN };
[")"] => { $crate::syntax_kind::CLOSE_PAREN };
["+"] => { $crate::syntax_kind::PLUS };
["-"] => { $crate::syntax_kind::MINUS };
["*"] => { $crate::syntax_kind::STAR };
["/"] => { $crate::syntax_kind::SLASH };
}
其运用方法如下
#[test]
fn test_marco() {
assert_eq!(token!["("], OPEN_PAREN);
assert_eq!(token![")"], CLOSE_PAREN);
assert_eq!(token!["+"], PLUS);
assert_eq!(token!["-"], MINUS);
assert_eq!(token!["*"], STAR);
assert_eq!(token!["/"], SLASH);
}
总结一下
这里咱们提早界说好了后边需求用到的一些类型, 十分简略, 下一篇文章进入本专栏第一个相对硬核的内容, 尽请期待
Q&A
Q: 为什么专门界说这个宏?
A: 一方面是不需求去记一些符号的命名, 这点在我写另一个项目的时分特别有用, 因为那里面有一大堆一大堆 Token, 另一个方面就是运用宏的话能够在实际编码的时分必定程度提高可读性, 比如 token!["+"]
和 PLUS
两者比较显然是运用宏的可读性更高, 再一个方面就是这个文件其实是我从我写的代码格式化工具里直接 copy 过来的, 改了改就直接用了, 就没留心发现能够去掉这个宏