-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathmod.rs
More file actions
175 lines (154 loc) · 6.73 KB
/
mod.rs
File metadata and controls
175 lines (154 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use pest::iterators::Pair as PestPair;
use std::borrow::Cow;
use crate::{error::Result, Obj, PartialVdf as Vdf, Value};
// the parser code is formatted using `prettyplease` which differs slightly from `rustfmt`. this
// indirection is enough to keep `rustfmt` from formatting the generated code until either the
// `ignore` config option is stable for `rustfmt` _or_ we switch to a different parser library with
// a less hacky setup :D
mod escaped {
include!("escaped.rs");
}
mod raw {
include!("raw.rs");
}
// unfortunate hack to re-use most of the code that consumes the pest parser produced by our two
// separate grammars :/
macro_rules! common_parsing {
($parser:ty, $rule:ty, $parse_escaped:expr) => {
/// Attempts to parse VDF text to a [`Vdf`]
pub fn parse(s: &str) -> Result<Vdf<'_>> {
let mut full_grammar = <$parser>::parse(<$rule>::vdf, s)?;
// There can be multiple base macros before the initial pair
let mut bases = Vec::new();
loop {
let pair = full_grammar.next().unwrap();
if let <$rule>::base_macro = pair.as_rule() {
let base_path_string = pair.into_inner().next().unwrap();
let base_path = match base_path_string.as_rule() {
<$rule>::quoted_raw_string => base_path_string.into_inner().next().unwrap(),
<$rule>::unquoted_string => base_path_string,
_ => unreachable!("Prevented by grammar"),
}
.as_str();
bases.push(Cow::from(base_path));
} else {
let (key, value) = parse_pair(pair);
return Ok(Vdf { key, value, bases });
}
}
}
fn parse_pair(grammar_pair: PestPair<'_, $rule>) -> (Cow<'_, str>, Value<'_>) {
// Structure: pair
// \ key <- Desired
// \ value <- Desired
if let <$rule>::pair = grammar_pair.as_rule() {
// Parse out the key and value
let mut grammar_pair_innards = grammar_pair.into_inner();
let grammar_string = grammar_pair_innards.next().unwrap();
let key = parse_string(grammar_string);
let grammar_value = grammar_pair_innards.next().unwrap();
let value = Value::from(grammar_value);
(key, value)
} else {
unreachable!("Prevented by grammar");
}
}
fn parse_string(grammar_string: PestPair<'_, $rule>) -> Cow<'_, str> {
match grammar_string.as_rule() {
// Structure: quoted_string
// \ "
// \ quoted_inner <- Desired
// \ "
<$rule>::quoted_string => {
let quoted_inner = grammar_string.into_inner().next().unwrap();
if $parse_escaped {
parse_escaped_string(quoted_inner)
} else {
Cow::from(quoted_inner.as_str())
}
}
// Structure: unquoted_string <- Desired
<$rule>::unquoted_string => {
let s = grammar_string.as_str();
Cow::from(s)
}
_ => unreachable!("Prevented by grammar"),
}
}
// Note: there can be a slight performance win here by having the grammar skip capturing
// quoted_inner and instead just slice off the starting and ending '"', but I'm going to pass since
// it seems like a hack for a ~4% improvement
fn parse_escaped_string(inner: PestPair<'_, $rule>) -> Cow<'_, str> {
let s = inner.as_str();
if s.contains('\\') {
// Escaped version won't be quite as long, but it will likely be close
let mut escaped = String::with_capacity(s.len());
let mut it = s.chars();
while let Some(ch) = it.next() {
if ch == '\\' {
// Character is escaped so check the next character to figure out the full
// character
match it.next() {
Some('n') => escaped.push('\n'),
Some('r') => escaped.push('\r'),
Some('t') => escaped.push('\t'),
Some('\\') => escaped.push('\\'),
Some('\"') => escaped.push('\"'),
_ => unreachable!("Prevented by grammar"),
}
} else {
escaped.push(ch)
}
}
Cow::from(escaped)
} else {
Cow::from(s)
}
}
impl<'a> From<PestPair<'a, $rule>> for Value<'a> {
fn from(grammar_value: PestPair<'a, $rule>) -> Self {
// Structure: value is ( obj | quoted_string | unquoted_string )
match grammar_value.as_rule() {
// Structure: ( quoted_string | unquoted_string )
<$rule>::quoted_string | <$rule>::unquoted_string => {
Self::Str(parse_string(grammar_value))
}
// Structure: obj
// \ pair* <- Desired
<$rule>::obj => {
let mut obj = Obj::new();
for grammar_pair in grammar_value.into_inner() {
let (key, value) = parse_pair(grammar_pair);
let entry = obj.entry(key).or_default();
(*entry).push(value);
}
Self::Obj(obj)
}
_ => unreachable!("Prevented by grammar"),
}
}
}
};
}
// expose ^^ macro to the rest of the crate
pub(crate) use common_parsing;
pub use escaped::{parse as escaped_parse, PestError as EscapedPestError};
pub use raw::{parse as raw_parse, PestError as RawPestError};
impl<'a> Vdf<'a> {
/// Attempts to parse VDF text to a [`Vdf`]
pub fn parse(s: &'a str) -> Result<Self> {
escaped_parse(s)
}
pub fn parse_raw(s: &'a str) -> Result<Self> {
raw_parse(s)
}
}
impl<'a> crate::Vdf<'a> {
/// Attempts to parse VDF text to a [`Vdf`]
pub fn parse(s: &'a str) -> Result<Self> {
Ok(crate::Vdf::from(Vdf::parse(s)?))
}
pub fn parse_raw(s: &'a str) -> Result<Self> {
Ok(crate::Vdf::from(Vdf::parse_raw(s)?))
}
}