re2c 是一个词法分析工具。目前支持 C, Go, Rust 三种目标语言。词法分析工具通过分析文本序列产生 Token,之后可以直接对 Token 进行分析或者将其输入到语法分析工具中。
介绍
re2c 以注释的形式写在目标语言源码中,然后通过 re2c/re2go/re2rust 进行预处理,将注释翻译成代码,就形成了目标产物:
fn lex(s: &[u8]) -> bool {
let mut cursor = 0;
/*!re2c
re2c:define:YYCTYPE = u8;
re2c:define:YYPEEK = "*s.get_unchecked(cursor)";
re2c:define:YYSKIP = "cursor += 1;";
re2c:yyfill:enable = 0;
number = [1-9][0-9]*;
number { return true; }
* { return false; }
*/
}
fn main() {
assert!(lex(b"1234\0"));
}
然后使用 re2rust 将注释转换成代码:
$ re2rust lexer.rs -o lexer.rs
生成后的代码如下:
/* Generated by re2c */
// re2rust $INPUT -o $OUTPUT
fn lex(s: &[u8]) -> bool {
let mut cursor = 0;
{
#[allow(unused_assignments)]
let mut yych : u8 = 0;
let mut yystate : usize = 0;
'yyl: loop {
match yystate {
0 => {
yych = unsafe {*s.get_unchecked(cursor)};
cursor += 1;
match yych {
0x31 ..= 0x39 => {
yystate = 2;
continue 'yyl;
}
_ => {
yystate = 1;
continue 'yyl;
}
}
}
1 => { return false; }
2 => {
yych = unsafe {*s.get_unchecked(cursor)};
match yych {
0x30 ..= 0x39 => {
cursor += 1;
yystate = 2;
continue 'yyl;
}
_ => {
yystate = 3;
continue 'yyl;
}
}
}
3 => { return true; }
_ => {
panic!("internal lexer error")
}
}
}
}
}
fn main() {
assert!(lex(b"1234\0"));
}
语法
re2c 的语句有三种:
<name> = <regular expression>; | 将 <name> 绑定到 regex 上。name 由英文字母、数字和下划线构成。定义后 name 可以在其它 regex 和 rule 中使用。 |
<configuration> = <value>; | 修改 re2c 的 配置。 |
<regular expression> { <code> } | 若 regex 匹配,则执行 code。如果多个 regex 都匹配,则选择最长的 token,若 token 一样长,则选择最早匹配上的。 |
!<directive>; | 指令是特殊的、预定义的语句。 |
程序接口
要使 re2c 能够正常工作,需要对 re2c 进行配置。re2c 支持两种风格:
指针 API:主要用于 C 语言这种有指针的语言中。使用更简单。
通用 API:用于无指针的语言中。
指针 API 实际上相当于预配置的通用 API:
/*!re2c
re2c:define:YYPEEK = "*buffer.get_unchecked(cursor)";
re2c:define:YYSKIP = "cursor += 1;";
re2c:define:YYBACKUP = "marker = cursor;";
re2c:define:YYRESTORE = "cursor = marker;";
re2c:define:YYBACKUPCTX = "ctxmarker = cursor;";
re2c:define:YYRESTORECTX = "cursor = ctxmarker;";
re2c:define:YYRESTORETAG = "cursor = @@{tag};";
re2c:define:YYLESSTHAN = "limit - cursor < @@{len}";
re2c:define:YYSTAGP = "@@{tag} = cursor;";
re2c:define:YYSTAGN = "@@{tag} = NONE;";
re2c:define:YYSHIFT = "cursor = (cursor as isize + @@{shift}) as usize;";
re2c:define:YYSHIFTSTAG = "@@{tag} = (@@{tag} as isize + @@{shift}) as usize;";
*/
当缺少配置时 re2c 不会报错,但是翻译后的代码一般无法编译。 |
API 原语
原语 | 作用 |
---|---|
YYCTYPE | 字符(code unit)的类型。ASCII, UTF-8 都是 u8 类型。 |
YYPEEK | 返回当前位置字符的值。 |
YYSKIP | 将当前指针进行自增。 |
处理流 EOF
解析过程中需要知道合适终止解析,一般有三种方法:
使用哨兵字符。
使用边界检查。
同时使用两者。
子模式提取
re2c 支持两种子模式提取方式:
使用 tag。
使用 posix captures。
tag 分为两种: @tag
形式的被称为 s-tag, #tag
的形式被成为 m-tag。其中 s-tag 代表了 tag 的单个值,而 m-tag 代表了匹配的整个历史。
下面演示了使用 s-tag 提取表达式的方式:
// re2rust $INPUT -o $OUTPUT
#[derive(Debug, PartialEq)]
struct SemVer(u32, u32, u32); // version: (major, minor, patch)
fn s2n(str: &[u8]) -> u32 { // convert a pre-parsed string to a number
let mut n = 0;
for i in str { n = n * 10 + *i as u32 - 48; }
return n;
}
fn parse(str: &[u8]) -> Option<SemVer> {
let (mut cur, mut mar) = (0, 0);
// User-defined tag variables that are available in semantic action.
let (t1, mut t2, t3, t4, t5);
// Autogenerated tag variables used by the lexer to track tag values.
const NONE: usize = std::usize::MAX;
/*!stags:re2c format = 'let mut @@{tag} = NONE;'; */
/*!re2c
re2c:define:YYCTYPE = u8;
re2c:define:YYPEEK = "*str.get_unchecked(cur)";
re2c:define:YYSKIP = "cur += 1;";
re2c:define:YYBACKUP = "mar = cur;";
re2c:define:YYRESTORE = "cur = mar;";
re2c:define:YYSTAGP = "@@{tag} = cur;";
re2c:define:YYSTAGN = "@@{tag} = NONE;";
re2c:define:YYSHIFTSTAG = "@@{tag} -= -@@{shift}isize as usize;";
re2c:yyfill:enable = 0;
re2c:tags = 1;
num = [0-9]+;
@t1 num @t2 "." @t3 num @t4 ("." @t5 num)? [\x00] {
let major = s2n(&str[t1..t2]);
let minor = s2n(&str[t3..t4]);
let patch = if t5 != NONE {s2n(&str[t5..cur - 1])} else {0};
return Some(SemVer(major, minor, patch));
}
* { return None; }
*/
}
fn main() {
assert_eq!(parse(b"23.34\0"), Some(SemVer(23, 34, 0)));
assert_eq!(parse(b"1.2.99999\0"), Some(SemVer(1, 2, 99999)));
assert_eq!(parse(b"1.a\0"), None);
}
故障处理
在解析过程中,YYPEEK 指向的实际上是需要解析的下一个字符,如果需要获取当前字符,需要对 cursor 进行偏移。此外,还需要谨慎处理终止条件:
Details
#[derive(Debug, Clone)]
enum WildChar {
RepeatAny,
Any,
Choose(bool, usize, usize),
Range(u8, u8),
Char(u8),
Eof,
}
/// # Errors
/// return error if pat is invalid.
pub fn sqlmatch(pat: &[u8], text: &[u8]) -> Result<bool, ()> {
let mut cursor = 0usize;
let mut text_cursor = 0usize;
loop {
let mut state = WildChar::Eof;
let mut marker = cursor;
let mut t1 = 0usize;
let mut t2 = 0usize;
/*!stags:re2c format = 'let mut @@{tag} = 0;'; */
/*!re2c
re2c:define:YYCTYPE = u8;
re2c:define:YYPEEK = "match cursor < pat.len() { true => pat[cursor], false => 0}";
re2c:define:YYSKIP = "cursor += 1;";
re2c:define:YYBACKUP = "marker = cursor;";
re2c:define:YYRESTORE = "cursor = marker;";
re2c:define:YYSTAGP = "@@{tag} = cursor;";
re2c:define:YYSHIFTSTAG = "@@{tag} -= -@@{shift}isize as usize;";
re2c:tags = 1;
re2c:yyfill:enable = 0;
re2c:define:YYLESSTHAN = "cursor >= pat.len()";
re2c:eof = 0;
neg_choose = "["[\\^!][^\]]*"]";
choose = "["[^\]]*"]";
alpha = [a-zA-Z];
escaped_ch = "\\" .;
$ {
state = WildChar::Eof;
break;
}
escaped_ch {
state = WildChar::Char(pat[cursor - 1]);
break;
}
"%" {
state = WildChar::RepeatAny;
break;
}
"_" {
state = WildChar::Any;
break;
}
@t1 neg_choose @t2 {
state = WildChar::Choose(true, t1 + 2, t2 - 1);
break;
}
@t1 choose @t2 {
state = WildChar::Choose(false, t1 + 1, t2 - 1);
break;
}
@t1 alpha "-" alpha @t2 {
state = WildChar::Range(pat[t1], pat[t2 - 1]);
break;
}
alpha {
state = WildChar::Char(pat[cursor - 1]);
break;
}
* {
unreachable!();
}
*/
macro_rules! YYPEEK {
() => {
if text_cursor == text.len() {
return Ok(false);
} else {
text[text_cursor]
}
};
}
match state {
WildChar::Choose(neg, s, e) => {
}
WildChar::Range(s, e) => {
}
WildChar::Char(ch) => {
}
WildChar::Eof => {
}
WildChar::Any => {
}
WildChar::RepeatAny => {
}
}
}
if text_cursor == text.len() {
return Ok(true);
}
Ok(false)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_sqlmatch(){
assert!(sqlmatch(b"abcdefg", b"abcdefg").unwrap());
assert!(sqlmatch(b"ab_", b"abc").unwrap());
assert!(sqlmatch(b"ab%", b"abcdefg").unwrap());
assert!(sqlmatch(b"%fg", b"%fg").unwrap());
assert!(sqlmatch(b"a-z", b"a").unwrap());
assert!(sqlmatch(b"[abc]", b"a").unwrap());
assert!(sqlmatch(b"[^abc]", b"d").unwrap());
assert!(sqlmatch(b"\\[[^abc]", b"[d").unwrap());
assert!(sqlmatch(b"\\[abc", b"[abc").unwrap());
assert!(sqlmatch(b"\\\\abc", b"\\abc").unwrap());
}
}
UTF8 支持
re2c 对字符集提供了天然的支持。因此可以配合 Rust 提供 UTF8 支持。Rust 主要用到的 API 有:
功能 | 方法 |
---|---|
获取字符的个数 |
|
获取第 n 个字符 |
|
re2c 中的 API 需要更改为:
/*!re2c
re2c:define:YYCTYPE = u32;
*/
最终效果如下:
Details
#[derive(Debug, Clone)]
enum WildCard {
RepeatAny,
Any,
Choose(bool, usize, usize),
Range(u32, u32),
Char(u32),
Eof,
}
#[inline(always)]
fn char_of(text: &str, pos: usize) -> u32 {
text.chars().nth(pos).unwrap() as u32
}
/// # Errors
/// return error if pat is invalid.
pub fn sqlmatch(pat: &str, text: &str) -> Result<bool, ()> {
let pat_len = pat.chars().count();
let text_len = text.chars().count();
let mut cursor = 0usize;
let mut text_cursor = 0usize;
loop {
let mut state = WildCard::Eof;
let mut marker = cursor;
let mut t1 = 0usize;
let mut t2 = 0usize;
/*!stags:re2c format = 'let mut @@{tag} = 0;'; */
/*!re2c
re2c:define:YYCTYPE = u32;
re2c:define:YYPEEK = "match cursor < pat_len { true => char_of(pat, cursor), false => 0}";
re2c:define:YYSKIP = "cursor += 1;";
re2c:define:YYBACKUP = "marker = cursor;";
re2c:define:YYRESTORE = "cursor = marker;";
re2c:define:YYSTAGP = "@@{tag} = cursor;";
re2c:define:YYSHIFTSTAG = "@@{tag} -= -@@{shift}isize as usize;";
re2c:tags = 1;
re2c:yyfill:enable = 0;
re2c:define:YYLESSTHAN = "cursor >= pat_len";
re2c:eof = 0;
neg_choose = "["[\\^!][^\]]*"]";
choose = "["[^\]]*"]";
word = [^\-];
escaped_ch = "\\" .;
$ {
state = WildCard::Eof;
break;
}
escaped_ch {
state = WildCard::Char(char_of(pat, cursor - 1));
break;
}
"%" {
state = WildCard::RepeatAny;
break;
}
"_" {
state = WildCard::Any;
break;
}
@t1 "[" word "-" word "]" @t2 {
state = WildCard::Range(char_of(pat, t1 + 1), char_of(pat, t2 - 2));
break;
}
@t1 neg_choose @t2 {
state = WildCard::Choose(true, t1 + 2, t2 - 1);
break;
}
@t1 choose @t2 {
state = WildCard::Choose(false, t1 + 1, t2 - 1);
break;
}
* {
state = WildCard::Char(char_of(pat, cursor - 1));
break;
}
*/
macro_rules! YYPEEK {
() => {
if text_cursor == text_len {
return Ok(false);
} else {
char_of(text, text_cursor)
}
};
}
match state {
WildCard::Choose(neg, s, e) => {
}
WildCard::Range(s, e) => {
}
WildCard::Char(ch) => {
}
WildCard::Eof => {
}
WildCard::Any => {
}
WildCard::RepeatAny => {
}
}
}
if text_cursor == text_len {
return Ok(true);
}
Ok(false)
}