1use std::collections::BTreeMap;
2
3use combine::combinator::{choice, many, many1, optional, position};
4use combine::easy::Error;
5use combine::error::StreamError;
6use combine::{parser, ParseResult, Parser};
7
8use crate::helpers::{ident, kind, name, punct};
9use crate::position::Pos;
10use crate::tokenizer::{Kind as T, Token, TokenStream};
11
12pub type Name = String;
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct Directive {
17 pub position: Pos,
18 pub name: Name,
19 pub arguments: Vec<(Name, Value)>,
20}
21
22#[derive(Debug, Clone, PartialEq)]
29pub struct Number(pub(crate) i64);
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum Value {
35 Variable(Name),
36 Int(Number),
37 Float(f64),
38 String(String),
39 Boolean(bool),
40 Null,
41 Enum(Name),
42 List(Vec<Value>),
43 Object(BTreeMap<Name, Value>),
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub enum Type {
48 NamedType(Name),
49 ListType(Box<Type>),
50 NonNullType(Box<Type>),
51}
52
53impl Number {
54 pub fn as_i64(&self) -> Option<i64> {
56 Some(self.0)
57 }
58}
59
60impl From<i32> for Number {
61 fn from(i: i32) -> Self {
62 Number(i as i64)
63 }
64}
65
66pub fn directives<'a>(input: &mut TokenStream<'a>) -> ParseResult<Vec<Directive>, TokenStream<'a>> {
67 many(
68 position()
69 .skip(punct("@"))
70 .and(name())
71 .and(parser(arguments))
72 .map(|((position, name), arguments)| Directive {
73 position,
74 name,
75 arguments,
76 }),
77 )
78 .parse_stream(input)
79}
80
81pub fn arguments<'a>(
82 input: &mut TokenStream<'a>,
83) -> ParseResult<Vec<(String, Value)>, TokenStream<'a>> {
84 optional(
85 punct("(")
86 .with(many1(name().skip(punct(":")).and(parser(value))))
87 .skip(punct(")")),
88 )
89 .map(|opt| opt.unwrap_or_else(Vec::new))
90 .parse_stream(input)
91}
92
93pub fn int_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
94 kind(T::IntValue)
95 .and_then(|tok| tok.value.parse())
96 .map(Number)
97 .map(Value::Int)
98 .parse_stream(input)
99}
100
101pub fn float_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
102 kind(T::FloatValue)
103 .and_then(|tok| tok.value.parse())
104 .map(Value::Float)
105 .parse_stream(input)
106}
107
108fn unquote_block_string(src: &str) -> Result<String, Error<Token, Token>> {
109 debug_assert!(src.starts_with("\"\"\"") && src.ends_with("\"\"\""));
110 let indent = src[3..src.len() - 3]
111 .lines()
112 .skip(1)
113 .filter_map(|line| {
114 let trimmed = line.trim_start().len();
115 if trimmed > 0 {
116 Some(line.len() - trimmed)
117 } else {
118 None }
120 })
121 .min()
122 .unwrap_or(0);
123 let mut result = String::with_capacity(src.len() - 6);
124 let mut lines = src[3..src.len() - 3].lines();
125 if let Some(first) = lines.next() {
126 let stripped = first.trim();
127 if !stripped.is_empty() {
128 result.push_str(stripped);
129 result.push('\n');
130 }
131 }
132 let mut last_line = 0;
133 for line in lines {
134 last_line = result.len();
135 if line.len() > indent {
136 result.push_str(&line[indent..].replace(r#"\""""#, r#"""""#));
137 }
138 result.push('\n');
139 }
140 if result[last_line..].trim().is_empty() {
141 result.truncate(last_line);
142 }
143
144 Ok(result)
145}
146
147fn unquote_string(s: &str) -> Result<String, Error<Token, Token>> {
148 let mut res = String::with_capacity(s.len());
149 debug_assert!(s.starts_with('"') && s.ends_with('"'));
150 let mut chars = s[1..s.len() - 1].chars();
151 let mut temp_code_point = String::with_capacity(4);
152 while let Some(c) = chars.next() {
153 match c {
154 '\\' => {
155 match chars.next().expect("slash cant be at the end") {
156 c @ '"' | c @ '\\' | c @ '/' => res.push(c),
157 'b' => res.push('\u{0010}'),
158 'f' => res.push('\u{000C}'),
159 'n' => res.push('\n'),
160 'r' => res.push('\r'),
161 't' => res.push('\t'),
162 'u' => {
163 temp_code_point.clear();
164 for _ in 0..4 {
165 match chars.next() {
166 Some(inner_c) => temp_code_point.push(inner_c),
167 None => {
168 return Err(Error::unexpected_message(format_args!(
169 "\\u must have 4 characters after it, only found '{}'",
170 temp_code_point
171 )))
172 }
173 }
174 }
175
176 match u32::from_str_radix(&temp_code_point, 16).map(std::char::from_u32) {
178 Ok(Some(unicode_char)) => res.push(unicode_char),
179 _ => {
180 return Err(Error::unexpected_message(format_args!(
181 "{} is not a valid unicode code point",
182 temp_code_point
183 )))
184 }
185 }
186 }
187 c => {
188 return Err(Error::unexpected_message(format_args!(
189 "bad escaped char {:?}",
190 c
191 )));
192 }
193 }
194 }
195 c => res.push(c),
196 }
197 }
198
199 Ok(res)
200}
201
202pub fn string<'a>(input: &mut TokenStream<'a>) -> ParseResult<String, TokenStream<'a>> {
203 choice((
204 kind(T::StringValue).and_then(|tok| unquote_string(tok.value)),
205 kind(T::BlockString).and_then(|tok| unquote_block_string(tok.value)),
206 ))
207 .parse_stream(input)
208}
209
210pub fn string_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
211 kind(T::StringValue)
212 .and_then(|tok| unquote_string(tok.value))
213 .map(Value::String)
214 .parse_stream(input)
215}
216
217pub fn block_string_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
218 kind(T::BlockString)
219 .and_then(|tok| unquote_block_string(tok.value))
220 .map(Value::String)
221 .parse_stream(input)
222}
223
224pub fn plain_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
225 ident("true")
226 .map(|_| Value::Boolean(true))
227 .or(ident("false").map(|_| Value::Boolean(false)))
228 .or(ident("null").map(|_| Value::Null))
229 .or(name().map(Value::Enum))
230 .or(parser(int_value))
231 .or(parser(float_value))
232 .or(parser(string_value))
233 .or(parser(block_string_value))
234 .parse_stream(input)
235}
236
237pub fn value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
238 parser(plain_value)
239 .or(punct("$").with(name()).map(Value::Variable))
240 .or(punct("[")
241 .with(many(parser(value)))
242 .skip(punct("]"))
243 .map(Value::List))
244 .or(punct("{")
245 .with(many(name().skip(punct(":")).and(parser(value))))
246 .skip(punct("}"))
247 .map(Value::Object))
248 .parse_stream(input)
249}
250
251pub fn default_value<'a>(input: &mut TokenStream<'a>) -> ParseResult<Value, TokenStream<'a>> {
252 parser(plain_value)
253 .or(punct("[")
254 .with(many(parser(default_value)))
255 .skip(punct("]"))
256 .map(Value::List))
257 .or(punct("{")
258 .with(many(name().skip(punct(":")).and(parser(default_value))))
259 .skip(punct("}"))
260 .map(Value::Object))
261 .parse_stream(input)
262}
263
264pub fn parse_type<'a>(input: &mut TokenStream<'a>) -> ParseResult<Type, TokenStream<'a>> {
265 name()
266 .map(Type::NamedType)
267 .or(punct("[")
268 .with(parser(parse_type))
269 .skip(punct("]"))
270 .map(Box::new)
271 .map(Type::ListType))
272 .and(optional(punct("!")).map(|v| v.is_some()))
273 .map(|(typ, strict)| {
274 if strict {
275 Type::NonNullType(Box::new(typ))
276 } else {
277 typ
278 }
279 })
280 .parse_stream(input)
281}
282
283#[cfg(test)]
284mod tests {
285 use super::unquote_string;
286 use super::Number;
287
288 #[test]
289 fn number_from_i32_and_to_i64_conversion() {
290 assert_eq!(Number::from(1).as_i64(), Some(1));
291 assert_eq!(Number::from(584).as_i64(), Some(584));
292 assert_eq!(
293 Number::from(i32::min_value()).as_i64(),
294 Some(i32::min_value() as i64)
295 );
296 assert_eq!(
297 Number::from(i32::max_value()).as_i64(),
298 Some(i32::max_value() as i64)
299 );
300 }
301
302 #[test]
303 fn unquote_unicode_string() {
304 assert_eq!(unquote_string(r#""\u0009""#).expect(""), "\u{0009}");
306 assert_eq!(unquote_string(r#""\u000A""#).expect(""), "\u{000A}");
307 assert_eq!(unquote_string(r#""\u000D""#).expect(""), "\u{000D}");
308 assert_eq!(unquote_string(r#""\u0020""#).expect(""), "\u{0020}");
309 assert_eq!(unquote_string(r#""\uFFFF""#).expect(""), "\u{FFFF}");
310
311 assert_eq!(
313 unquote_string(r#""\u0009 hello \u000A there""#).expect(""),
314 "\u{0009} hello \u{000A} there"
315 );
316 }
317}