2 个版本 (1 个稳定版)
1.0.0 | 2020年9月27日 |
---|---|
0.1.0 | 2020年5月23日 |
#363 在 编程语言 中
130KB
2K SLoC
dpr
...的演变 dynparser
基本执行流程
Text -> Parsing -> Transform -> Text
有关 peg
语法的更多信息见下文。
使用方法
添加到 cargo.toml
[dependencies]
dpr = "0.1.0"
# dpr = {git = "https://github.com/jleahred/dpr" }
以下是一些示例
修改
0.1.0 First version
待办事项
- 添加外部函数
- 不需要是 multiexpr pub(crate)struct Transf2Expr { pub(crate)mexpr: MultiExpr,
- 当只有一个选项时删除和/或 multiexpr(and/or)
关于
给定一个扩展的 peg
语法,它将验证输入并根据转换规则生成输出
但让我们通过示例来看一下
简单示例
从下面的这个 peg
开始
Peg
main = char+
char = 'a' -> A
/ 'b' -> B
/ .
给定这个 input
输入
aaacbbabdef
我们得到的结果
输出
AAAcBBABdef
加法计算器示例
Peg
main = expr
expr = num:num -> PUSH $(num)$(:endl)
(op:op expr:expr)? -> $(expr)EXEC $(op)$(:endl)
op = '+' -> ADD
/ '-' -> SUB
num = [0-9]+ ('.' [0-9])?
输入
1+2-3
输出
PUSH 1
PUSH 2
PUSH 3
EXEC SUB
EXEC ADD
执行流程
基本文本转换流程。
DSL flow
.--------.
| peg |
| user |
'--------'
|
v
.--------.
| GEN |
| rules |
'--------'
| .----------.
| | input |
| | user |
| '----------'
| |
| v
| .----------.
| | parse |
'--------------->| |
'----------'
|
v
.---------.
| replace |
| |
'---------'
|
v
.--------.
| OUTPUT |
| |
'--------'
第一个示例的 Rust 代码...
extern crate dpr;
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
"
main = char+
char = 'a' -> A
/ 'b' -> B
/ .
",
)
.gen_rules()?
.parse("aaacbbabdef")?
.replace()?
// ...
;
println!("{:#?}", result);
Ok(())
}
PEG 规则语法
您看到了一些示例,让我们更详细地看看
令牌 | 描述 |
---|---|
= |
在左边,符号,在右边定义符号的表达式 |
符号 |
它是一个不带引号的字符串,没有空格和 ASCII 字符 |
. |
任何字符 |
"..." |
由引号分隔的文本 |
<空格> |
分隔令牌和规则连接(and 操作) |
/ |
或操作 |
(...) |
由子表达式组成的表达式 |
? |
可选的 |
* |
重复 0 或更多次 |
+ |
重复 1 或更多次 |
! |
否定表达式,如果没有跟随则继续,不消耗 |
& |
验证它跟随...但不消耗 |
[...] |
匹配字符。它是一个列表或范围(或两者都是) |
-> |
箭头后面是转换规则 |
: |
命名,以便在转换中使用 |
error(...) | 这允许您在满足此规则时定义错误消息 |
下面是定义有效 peg
输入的 grammar
。顺便说一下,这个 grammar
已被解析以生成解析此代码的代码;;-)
让我们通过示例来查看
示例规则
一个简单的文本字符串。
main = "Hello world"
连接(与)
main = "Hello " "world"
引用符号
符号
main = hi
hi = "Hello world"
或条件 /
main = "hello" / "hi"
或多行
main
= "hello"
/ "hi"
/ "hola"
或多行 2
main = "hello"
/ "hi"
/ "hola"
或无序
main = "hello"
/ "hi" / "hola"
括号
main = ("hello" / "hi") " world"
仅多行
多行1
main
= ("hello" / "hi") " world"
多行2
main
= ("hello" / "hi")
" world"
多行3
main = ("hello" / "hi")
" world"
建议在每行使用或操作符 /
,并在第一行使用 =
,如下所示:
多行有序
main = ("hello" / "hi") " world"
/ "bye"
可选的
main = ("hello" / "hi") " world"?
重复
main = one_or_more_a / zero_or_many_b
one_or_more = "a"+
zero_or_many = "b"*
否定不会移动当前位置
下一个示例将消耗所有字符直到得到一个 "a" 字符
否定
main = (!"a" .)* "a"
消耗到
comment = "//" (!"\n" .)*
/ "/*" (!"*/" .)* "*/"
匹配一组字符。字符可以由范围定义。
number = digit+ ("." digit+)?
digit = [0-9]
a_or_b = [ab]
id = [_a-zA-Z][_a-zA-Z0-9]*
a_or_b_or_digit = [ab0-9]
简单递归
一个或多个 "a" 递归
as = "a" as
/ "a"
// simplified with `+`
ak = "a"+
递归匹配括号
递归匹配 par
match_par = "(" match_par ")"
/ "(" ")"
为了产生自定义错误,您必须使用 error(...)
构造函数
在下一个示例中,如果括号不平衡,系统将报错
parenth = '(' _ expr _ ( ')'
/ error("unbalanced parethesis: missing ')'")
)
如您所见,如果您可以正确关闭括号,则一切正常,否则将产生自定义错误消息
替换
您可以使用 ->
设置替换规则
op = '+' -> ADD
/ '-' -> SUB
当发现并验证 +
时,它将被替换为 ADD
expr = num:num -> PUSH $(num)$(:endl)
(op:op expr:expr)? -> $(expr)EXEC $(op)$(:endl)
要引用解析的块,您可以给它命名,使用 :
当引用一个 symbol
时,您不需要给出一个名称
以下示例是等效的
expr = num:num -> PUSH $(num)$(:endl)
(op:op expr:expr)? -> $(expr)EXEC $(op)$(:endl)
expr = num -> PUSH $(num)$(:endl)
(op expr)? -> $(expr)EXEC $(op)$(:endl)
箭头将在当前行上工作。如果您需要在多行上使用转换,您将不得不使用 (...)
解析 peg 语法有一个语法,可以在文件 gcode/peg2code.rs
中找到一个示例
在箭头之后,您将得到转换规则。
替换令牌
:$
内部的内容将被替换。它外部的内容将被原样写入
替换令牌
可以通过名称或位置引用解析文本
-> $(num)
这将查找左侧定义的名为 num
的名称,并将其写入输出
下一行也将查找名称,但不会在 rep_symbol
上报错,如果它不存在的话
rep_or_unary = atom_or_par rep_symbol? -> $(?rep_symbol)$(atom_or_par)
您也可以通过位置引用一个元素
-> $(.1)
您也可以通过在 replacing token
开头使用 :
来引用 functions
expr = num -> $(:endl)
预定义函数是...
(查看 replace.rs
以查看完整的替换函数)
"endl" => "\n",
"spc" => " ",
"_" => " ",
"tab" => "\t",
"(" => "\t",
// "now" => "pending",
_ => "?unknown_fn?",
示例
expr = num -> PUSH $(num)$(:endl)
(op expr)? -> $(.2)EXEC $(.1)$(:endl)
您可以定义自己的 functions
(即 external functions
)
在下一个示例中,我们创建了替换令牌 el
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
"
main = char+
char = 'a' -> $(:el)A
/ 'b' -> $(:el)B
/ ch:. -> $(:el)$(ch)
",
)
.gen_rules()?
.parse("aaacbbabdef")?
.replace(Some(&dpr::FnCallBack(custom_funtions)))?
// ...
;
println!("{:#?}", result);
println!("{}", result.str());
Ok(())
}
fn custom_funtions(fn_txt: &str) -> Option<String> {
match fn_txt {
"el" => Some("\n".to_string()),
_ => None,
}
}
完整的数学表达式编译器示例
没有数学表达式计算器的解析器是什么?
显然,必须考虑运算符优先级、运算符结合性和括号、负数和负表达式
extern crate dpr;
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
r#"
main = expr
expr = term (
_ add_op _ term ->$(term)$(add_op)
)*
term = factor (
_ mult_op _ factor ->$(factor)$(mult_op)
)*
factor = pow (
_ pow_op _ subexpr ->$(subexpr)$(pow_op)
)*
pow = subexpr (
_ pow_op _ pow ->$(pow)$(pow_op)
)*
subexpr = '(' _ expr _ ->$(expr)
( ')' ->$(:none)
/ error("parenthesis error")
)
/ number ->PUSH $(number)$(:endl)
/ '-' _ subexpr ->PUSH 0$(:endl)$(subexpr)SUB$(:endl)
number = ([0-9]+ ('.' [0-9])?)
add_op = '+' ->EXEC ADD$(:endl)
/ '-' ->EXEC SUB$(:endl)
mult_op = '*' ->EXEC MUL$(:endl)
/ '/' ->EXEC DIV$(:endl)
pow_op = '^' ->EXEC POW$(:endl)
_ = ' '*
"#,
)
.gen_rules()?
.parse("-(-1+2* 3^5 ^(- 2 ) -7)+8")?
.replace()?
// ...
;
println!("{:#?}", result);
println!("{}", result.str());
Ok(())
}
输出是用于堆栈机的程序,由一个带有参数的命令组成...
PUSH 0
PUSH 0
PUSH 1
EXEC SUB
PUSH 2
PUSH 3
PUSH 5
PUSH 0
PUSH 2
EXEC SUB
EXEC POW
EXEC POW
EXEC MUL
EXEC ADD
PUSH 7
EXEC SUB
EXEC SUB
PUSH 8
EXEC ADD
完整的 peg 语法文档规范
目前是...
(要查看更新后的参考,请打开 peg2code.rs 文件 :-)
fn text_peg2code() -> &'static str {
r#"
/* A peg grammar to parse peg grammars
*
*/
main = grammar -> $(grammar)EOP
grammar = rule+
symbol = [_a-zA-Z0-9] [_'"a-zA-Z0-9]*
rule = _ rule_name _ '=' _ expr _eol _ -> RULE$(:endl)$(rule_name)$(:endl)$(expr)
rule_name = symbol
expr = or -> OR$(:endl)$(or)CLOSE_MEXPR$(:endl)
or = _ and -> AND$(:endl)$(and)CLOSE_MEXPR$(:endl)
( _ '/' _ or )? -> $(or)
and = error
/ (andline transf2 and:(
_ ->$(:none)
!(rule_name _ ('=' / '{')) and )?) -> TRANSF2$(:endl)$(transf2)EOTRANSF2$(:endl)AND$(:endl)$(andline)CLOSE_MEXPR$(:endl)$(and)
/ andline (
( ' ' / comment )* eol+ _ -> $(:none)
!( rule_name _ ('=' / '{') ) and
)?
error = 'error' _ '(' _ literal _ ')' -> ERROR$(:endl)$(literal)$(:endl)
andline = andchunk (
' '+ ->$(:none)
( error / andchunk )
)*
andchunk = name e:rep_or_unary -> NAMED$(:endl)$(name)$(:endl)$(e)
/ rep_or_unary
// this is the and separator
_1 = ' ' / eol -> $(:none)
// repetitions or unary operator
rep_or_unary = atom_or_par rep_symbol? -> $(?rep_symbol)$(atom_or_par)
// atom_or_par -> $(atom_or_par)
/ '!' atom_or_par -> NEGATE$(:endl)$(atom_or_par)
/ '&' atom_or_par -> PEEK$(:endl)$(atom_or_par)
rep_symbol = '*' -> REPEAT$(:endl)0$(:endl)inf$(:endl)
/ '+' -> REPEAT$(:endl)1$(:endl)inf$(:endl)
/ '?' -> REPEAT$(:endl)0$(:endl)1$(:endl)
atom_or_par = atom / parenth
parenth = '(' _ expr _ -> $(expr)
( ')' -> $(:none)
/ error("unbalanced parethesis: missing ')'")
)
atom = a:literal -> ATOM$(:endl)LIT$(:endl)$(a)$(:endl)
/ a:match -> MATCH$(:endl)$(a)
/ a:rule_name -> ATOM$(:endl)RULREF$(:endl)$(a)$(:endl)
/ dot -> ATOM$(:endl)DOT$(:endl)
// as rule_name can start with a '.', dot has to be after rule_name
literal = lit_noesc / lit_esc
lit_noesc = _' l:( !_' . )* _' -> $(l)
_' = "'"
lit_esc = (_"
l:( esc_char
/ hex_char
/ !_" .
)*
_") -> $(l)
_" = '"'
esc_char = '\r'
/ '\n'
/ '\t'
/ '\\'
/ '\\"'
hex_char = '\0x' [0-9A-F] [0-9A-F]
eol = "\r\n" / "\n" / "\r"
_eol = (' ' / comment)* eol
match = '[' -> $(:none)
(
mchars b:(mbetween*) -> CHARS$(:endl)$(mchars)$(:endl)BETW$(:endl)$(b)EOBETW$(:endl)
/ b:(mbetween+) -> BETW$(:endl)$(b)EOBETW$(:endl)
)
']' -> $(:none)
mchars = (!']' !(. '-') .)+
mbetween = f:. '-' s:. -> $(f)$(:endl)$(s)$(:endl)
dot = '.'
_ = (
( ' '
/ eol
/ comment
)*
) -> $(:none)
comment = ( line_comment
/ mline_comment
) -> $(:none)
line_comment = '//' (!eol .)*
mline_comment = '/*' (!'*/' .)* '*/'
name = symbol ":" -> $(symbol)
transf2 = _1 _ '->' ' '* -> $(:none)
transf_rule -> $(transf_rule)
&eol
transf_rule = ( tmpl_text / tmpl_rule )+
tmpl_text = t:( (!("$(" / eol) .)+ ) -> TEXT$(:endl)$(t)$(:endl)
tmpl_rule = "$(" -> $(:none)
(
// by name optional
'?' symbol ->NAMED_OPT$(:endl)$(symbol)$(:endl)
// by name
/ symbol ->NAMED$(:endl)$(symbol)$(:endl)
// by pos
/ "." pos:([0-9]+) ->POS$(:endl)$(symbol)$(pos)$(:endl)
// by function
/ ":" ->$(:none)
fn:((!(")" / eol) .)+) ->FUNCT$(:endl)$(fn)$(:endl)
)
")" ->$(:none)
"#
}
代码黑客
正如您所看到的,开始解析 peg
输入的代码,是写在文本 peg
文件中的
这是怎么做到的?
目前,rules_for_peg
代码是...
r#"symbol"# => or!(and!(ematch!(chlist r#"_"# , from 'a', to 'z' , from 'A', to 'Z' , from '0', to '9' ), rep!(ematch!(chlist r#"_'""# , from 'a', to 'z' , from 'A', to 'Z' , from '0', to '9' ), 0)))
, r#"transf_rule"# => or!(and!(rep!(or!(and!(ref_rule!(r#"tmpl_text"#)), and!(ref_rule!(r#"tmpl_rule"#))), 1)))
, r#"mbetween"# => or!(and!(transf2!( and!( and!(named!("f", dot!()), lit!("-"), named!("s", dot!())) ) , t2rules!(t2_byname!("f"), t2_funct!("endl"), t2_byname!("s"), t2_funct!("endl"), ) )))
, r#"line_comment"# => or!(and!(lit!("//"), rep!(or!(and!(not!(ref_rule!(r#"eol"#)), dot!())), 0)))
, r#"lit_noesc"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"_'"#), named!("l", rep!(or!(and!(not!(ref_rule!(r#"_'"#)), dot!())), 0)), ref_rule!(r#"_'"#)) ) , t2rules!(t2_byname!("l"), ) )))
, r#"error"# => or!(and!(transf2!( and!( and!(lit!("error"), ref_rule!(r#"_"#), lit!("("), ref_rule!(r#"_"#), ref_rule!(r#"literal"#), ref_rule!(r#"_"#), lit!(")")) ) , t2rules!(t2_text!("ERROR"), t2_funct!("endl"), t2_byname!("literal"), t2_funct!("endl"), ) )))
, r#"atom_or_par"# => or!(and!(ref_rule!(r#"atom"#)), and!(ref_rule!(r#"parenth"#)))
, r#"tmpl_text"# => or!(and!(transf2!( and!( and!(named!("t", or!(and!(rep!(or!(and!(not!(or!(and!(lit!("$(")), and!(ref_rule!(r#"eol"#)))), dot!())), 1))))) ) , t2rules!(t2_text!("TEXT"), t2_funct!("endl"), t2_byname!("t"), t2_funct!("endl"), ) )))
, r#"atom"# => or!(and!(transf2!( and!( and!(named!("a", ref_rule!(r#"literal"#))) ) , t2rules!(t2_text!("ATOM"), t2_funct!("endl"), t2_text!("LIT"), t2_funct!("endl"), t2_byname!("a"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(named!("a", ref_rule!(r#"match"#))) ) , t2rules!(t2_text!("MATCH"), t2_funct!("endl"), t2_byname!("a"), ) )), and!(transf2!( and!( and!(named!("a", ref_rule!(r#"rule_name"#))) ) , t2rules!(t2_text!("ATOM"), t2_funct!("endl"), t2_text!("RULREF"), t2_funct!("endl"), t2_byname!("a"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(ref_rule!(r#"dot"#)) ) , t2rules!(t2_text!("ATOM"), t2_funct!("endl"), t2_text!("DOT"), t2_funct!("endl"), ) )))
, r#"grammar"# => or!(and!(rep!(ref_rule!(r#"rule"#), 1)))
, r#"dot"# => or!(and!(lit!(".")))
, r#"eol"# => or!(and!(lit!("\r\n")), and!(lit!("\n")), and!(lit!("\r")))
, r#"expr"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"or"#)) ) , t2rules!(t2_text!("OR"), t2_funct!("endl"), t2_byname!("or"), t2_text!("CLOSE_MEXPR"), t2_funct!("endl"), ) )))
, r#"name"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"symbol"#), lit!(":")) ) , t2rules!(t2_byname!("symbol"), ) )))
, r#"literal"# => or!(and!(ref_rule!(r#"lit_noesc"#)), and!(ref_rule!(r#"lit_esc"#)))
, r#"rule"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"_"#), ref_rule!(r#"rule_name"#), ref_rule!(r#"_"#), lit!("="), ref_rule!(r#"_"#), ref_rule!(r#"expr"#), ref_rule!(r#"_eol"#), ref_rule!(r#"_"#)) ) , t2rules!(t2_text!("RULE"), t2_funct!("endl"), t2_byname!("rule_name"), t2_funct!("endl"), t2_byname!("expr"), ) )))
, r#"andchunk"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"name"#), named!("e", ref_rule!(r#"rep_or_unary"#))) ) , t2rules!(t2_text!("NAMED"), t2_funct!("endl"), t2_byname!("name"), t2_funct!("endl"), t2_byname!("e"), ) )), and!(ref_rule!(r#"rep_or_unary"#)))
, r#"_""# => or!(and!(lit!("\"")))
, r#"mline_comment"# => or!(and!(lit!("/*"), rep!(or!(and!(not!(lit!("*/")), dot!())), 0), lit!("*/")))
, r#"andline"# => or!(and!(ref_rule!(r#"andchunk"#), rep!(or!(and!(transf2!( and!( and!(rep!(lit!(" "), 1)) ) , t2rules!(t2_funct!("none"), ) ), or!(and!(ref_rule!(r#"error"#)), and!(ref_rule!(r#"andchunk"#))))), 0)))
, r#"hex_char"# => or!(and!(lit!("\0x"), ematch!(chlist r#""# , from '0', to '9' , from 'A', to 'F' ), ematch!(chlist r#""# , from '0', to '9' , from 'A', to 'F' )))
, r#"mchars"# => or!(and!(rep!(or!(and!(not!(lit!("]")), not!(or!(and!(dot!(), lit!("-")))), dot!())), 1)))
, r#"transf2"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"_1"#), ref_rule!(r#"_"#), lit!("->"), rep!(lit!(" "), 0)) ) , t2rules!(t2_funct!("none"), ) ), transf2!( and!( and!(ref_rule!(r#"transf_rule"#)) ) , t2rules!(t2_byname!("transf_rule"), ) ), peek!(ref_rule!(r#"eol"#))))
, r#"_'"# => or!(and!(lit!("'")))
, r#"and"# => or!(and!(ref_rule!(r#"error"#)), and!(transf2!( and!( and!(or!(and!(ref_rule!(r#"andline"#), ref_rule!(r#"transf2"#), named!("and", rep!(or!(and!(transf2!( and!( and!(ref_rule!(r#"_"#)) ) , t2rules!(t2_funct!("none"), ) ), not!(or!(and!(ref_rule!(r#"rule_name"#), ref_rule!(r#"_"#), or!(and!(lit!("=")), and!(lit!("{")))))), ref_rule!(r#"and"#))), 0, 1))))) ) , t2rules!(t2_text!("TRANSF2"), t2_funct!("endl"), t2_byname!("transf2"), t2_text!("EOTRANSF2"), t2_funct!("endl"), t2_text!("AND"), t2_funct!("endl"), t2_byname!("andline"), t2_text!("CLOSE_MEXPR"), t2_funct!("endl"), t2_byname!("and"), ) )), and!(ref_rule!(r#"andline"#), rep!(or!(and!(transf2!( and!( and!(rep!(or!(and!(lit!(" ")), and!(ref_rule!(r#"comment"#))), 0), rep!(ref_rule!(r#"eol"#), 1), ref_rule!(r#"_"#)) ) , t2rules!(t2_funct!("none"), ) ), not!(or!(and!(ref_rule!(r#"rule_name"#), ref_rule!(r#"_"#), or!(and!(lit!("=")), and!(lit!("{")))))), ref_rule!(r#"and"#))), 0, 1)))
, r#"_"# => or!(and!(transf2!( and!( and!(or!(and!(rep!(or!(and!(lit!(" ")), and!(ref_rule!(r#"eol"#)), and!(ref_rule!(r#"comment"#))), 0)))) ) , t2rules!(t2_funct!("none"), ) )))
, r#"or"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"_"#), ref_rule!(r#"and"#)) ) , t2rules!(t2_text!("AND"), t2_funct!("endl"), t2_byname!("and"), t2_text!("CLOSE_MEXPR"), t2_funct!("endl"), ) ), transf2!( and!( and!(rep!(or!(and!(ref_rule!(r#"_"#), lit!("/"), ref_rule!(r#"_"#), ref_rule!(r#"or"#))), 0, 1)) ) , t2rules!(t2_byname!("or"), ) )))
, r#"_1"# => or!(and!(lit!(" ")), and!(transf2!( and!( and!(ref_rule!(r#"eol"#)) ) , t2rules!(t2_funct!("none"), ) )))
, r#"lit_esc"# => or!(and!(transf2!( and!( and!(or!(and!(ref_rule!(r#"_""#), named!("l", rep!(or!(and!(ref_rule!(r#"esc_char"#)), and!(ref_rule!(r#"hex_char"#)), and!(not!(ref_rule!(r#"_""#)), dot!())), 0)), ref_rule!(r#"_""#)))) ) , t2rules!(t2_byname!("l"), ) )))
, r#"rep_symbol"# => or!(and!(transf2!( and!( and!(lit!("*")) ) , t2rules!(t2_text!("REPEAT"), t2_funct!("endl"), t2_text!("0"), t2_funct!("endl"), t2_text!("inf"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(lit!("+")) ) , t2rules!(t2_text!("REPEAT"), t2_funct!("endl"), t2_text!("1"), t2_funct!("endl"), t2_text!("inf"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(lit!("?")) ) , t2rules!(t2_text!("REPEAT"), t2_funct!("endl"), t2_text!("0"), t2_funct!("endl"), t2_text!("1"), t2_funct!("endl"), ) )))
, r#"parenth"# => or!(and!(transf2!( and!( and!(lit!("("), ref_rule!(r#"_"#), ref_rule!(r#"expr"#), ref_rule!(r#"_"#)) ) , t2rules!(t2_byname!("expr"), ) ), or!(and!(transf2!( and!( and!(lit!(")")) ) , t2rules!(t2_funct!("none"), ) )), and!(error!("unbalanced parethesis: missing ')'")))))
, r#"comment"# => or!(and!(transf2!( and!( and!(or!(and!(ref_rule!(r#"line_comment"#)), and!(ref_rule!(r#"mline_comment"#)))) ) , t2rules!(t2_funct!("none"), ) )))
, r#"_eol"# => or!(and!(rep!(or!(and!(lit!(" ")), and!(ref_rule!(r#"comment"#))), 0), ref_rule!(r#"eol"#)))
, r#"esc_char"# => or!(and!(lit!("\r")), and!(lit!("\n")), and!(lit!("\t")), and!(lit!("\\")), and!(lit!("\\\"")))
, r#"match"# => or!(and!(transf2!( and!( and!(lit!("[")) ) , t2rules!(t2_funct!("none"), ) ), or!(and!(transf2!( and!( and!(ref_rule!(r#"mchars"#), named!("b", or!(and!(rep!(ref_rule!(r#"mbetween"#), 0))))) ) , t2rules!(t2_text!("CHARS"), t2_funct!("endl"), t2_byname!("mchars"), t2_funct!("endl"), t2_text!("BETW"), t2_funct!("endl"), t2_byname!("b"), t2_text!("EOBETW"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(named!("b", or!(and!(rep!(ref_rule!(r#"mbetween"#), 1))))) ) , t2rules!(t2_text!("BETW"), t2_funct!("endl"), t2_byname!("b"), t2_text!("EOBETW"), t2_funct!("endl"), ) ))), transf2!( and!( and!(lit!("]")) ) , t2rules!(t2_funct!("none"), ) )))
, r#"rep_or_unary"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"atom_or_par"#), rep!(ref_rule!(r#"rep_symbol"#), 0, 1)) ) , t2rules!(t2_byname_opt!("rep_symbol"), t2_byname!("atom_or_par"), ) )), and!(transf2!( and!( and!(lit!("!"), ref_rule!(r#"atom_or_par"#)) ) , t2rules!(t2_text!("NEGATE"), t2_funct!("endl"), t2_byname!("atom_or_par"), ) )), and!(transf2!( and!( and!(lit!("&"), ref_rule!(r#"atom_or_par"#)) ) , t2rules!(t2_text!("PEEK"), t2_funct!("endl"), t2_byname!("atom_or_par"), ) )))
, r#"tmpl_rule"# => or!(and!(transf2!( and!( and!(lit!("$(")) ) , t2rules!(t2_funct!("none"), ) ), or!(and!(transf2!( and!( and!(lit!("?"), ref_rule!(r#"symbol"#)) ) , t2rules!(t2_text!("NAMED_OPT"), t2_funct!("endl"), t2_byname!("symbol"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(ref_rule!(r#"symbol"#)) ) , t2rules!(t2_text!("NAMED"), t2_funct!("endl"), t2_byname!("symbol"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(lit!("."), named!("pos", or!(and!(rep!(ematch!(chlist r#""# , from '0', to '9' ), 1))))) ) , t2rules!(t2_text!("POS"), t2_funct!("endl"), t2_byname!("symbol"), t2_byname!("pos"), t2_funct!("endl"), ) )), and!(transf2!( and!( and!(lit!(":")) ) , t2rules!(t2_funct!("none"), ) ), transf2!( and!( and!(named!("fn", or!(and!(rep!(or!(and!(not!(or!(and!(lit!(")")), and!(ref_rule!(r#"eol"#)))), dot!())), 1))))) ) , t2rules!(t2_text!("FUNCT"), t2_funct!("endl"), t2_byname!("fn"), t2_funct!("endl"), ) ))), transf2!( and!( and!(lit!(")")) ) , t2rules!(t2_funct!("none"), ) )))
, r#"main"# => or!(and!(transf2!( and!( and!(ref_rule!(r#"grammar"#)) ) , t2rules!(t2_byname!("grammar"), t2_text!("EOP"), ) )))
, r#"rule_name"# => or!(and!(ref_rule!(r#"symbol"#)))
)
}
手动编写它是困难的。
这个程序不是设计用来接收一个文本 peg
语法和一个文本输入,然后生成一个文本输出吗?
IR
IR
是从中间表示(Intermediate Representation)来的。
为什么????
一旦我们解析了输入,我们就有一个 AST
。我们可以处理 AST
,但...
AST
与语法紧密耦合。大多数时候我们修改语法,我们还需要修改处理 AST
的代码。
有时语法修改可能是一个语法修改,或者添加一些需要语法修改的功能,因此需要不同的 AST
,但所有或几乎所有概念保持相同。
想象一下,如果我们想在数学表达式编译器中添加 sqrt
函数。我们需要修改规则生成器以处理新的 AST
。
为了将 peg
语法从解析 AST
中解耦,我们将创建一个 IR
(中间表示)。
如何获取 IR
将在自带的 peg
语法中定义为转换规则。
IR
的解释器将生成内存中的规则。然后,我们可以从生成的规则中生成 rust
代码,或者我们可以有一个特定的解释器来生成它们,但从 rust 数据结构中获得它们是很好的。
为了开发这个功能...我们需要一个解析器和代码生成器... 嘿!!!我做到了。 dpr
就是这样!!!
如何生成 IR
peg_grammar()
.parse(peg_grammar())
.gen_rules()
.replace()
peg_grammar
将在 转换规则
中包含生成 IR
的指令。
多亏了 IR
,修改这个程序变得容易,我们不需要处理与 peg-grammar
紧耦合的 AST
。
让我们一步步来看
创建规则...
extern crate dpr;
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
"
main = char+
char = 'a' -> A
/ 'b' -> B
/ .
",
)
.gen_rules()?
// .parse("aaacbbabdef")?
// .replace()?
// ...
;
println!("{:#?}", result);
Ok(())
}
生成一组规则,如...
SetOfRules(
{
"main": And(
MultiExpr(
[
Repeat(
RepInfo {
expression: RuleName(
"char",
),
min: NRep(
1,
),
max: None,
},
),
],
),
),
"char": Or(
MultiExpr(
[
And(
MultiExpr(
[
MetaExpr(
Transf2(
Transf2Expr {
mexpr: MultiExpr(
[
Simple(
Literal(
"a",
),
),
],
),
transf2_rules: "A",
},
),
),
],
),
),
And(
MultiExpr(
[
MetaExpr(
Transf2(
Transf2Expr {
mexpr: MultiExpr(
[
Simple(
Literal(
"b",
),
),
],
),
transf2_rules: "B",
},
),
),
],
),
),
And(
MultiExpr(
[
Simple(
Dot,
),
],
),
),
],
),
),
},
)
这组规则将使我们能够使用 parse
和生成任何 input
的 AST
。
下一步,使用生成的规则 parsing
input
...
创建规则...(使用简化的输入以减少 output
的大小)
extern crate dpr;
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
"
main = char+
char = 'a' -> A
/ 'b' -> B
/ .
",
)
.gen_rules()?
.parse("acb")?
// .replace()?
// ...
;
println!("{:#?}", result);
Ok(())
}
现在您可以看到生成的 AST
Rule(
(
"main",
[
Rule(
(
"char",
[
Transf2(
(
"A",
[
Val(
"a",
),
],
),
),
],
),
),
Rule(
(
"char",
[
Val(
"c",
),
],
),
),
Rule(
(
"char",
[
Transf2(
(
"B",
[
Val(
"b",
),
],
),
),
],
),
),
],
),
)
然后运行转换...
extern crate dpr;
fn main() -> Result<(), dpr::Error> {
let result = dpr::Peg::new(
"
main = char+
char = 'a' -> A
/ 'b' -> B
/ .
",
)
.gen_rules()?
.parse("acb")?
.replace()?
// ...
;
println!("{:#?}", result);
Ok(())
}
"AcB"
依赖关系
~1MB
~25K SLoC