trashcan/parser/
lit.rs

1//! trashcan's sub-parsers for literals
2
3use std::str;
4
5use super::{ParseErrorKind, CutParseResult};
6use super::bits::*;
7
8use ast::*;
9
10pub fn literal(input: &[u8]) -> CutParseResult<Literal> {
11    alt!(input,
12        literal_null(input)
13      ; literal_bool(input)
14      ; literal_currency(input)
15      ; literal_float(input)
16      ; literal_int(input)
17      ; literal_string(input)
18  //  TODO: "wacky" literal types
19  //  ; literal_date));
20    )
21}
22
23fn literal_null(input: &[u8]) -> CutParseResult<Literal> {
24    alt!(input,
25        keyword(input, b"nullptr") => |_| Literal::NullPtr
26      ; keyword(input, b"nullvar") => |_| Literal::NullVar
27      ; keyword(input, b"emptyvar") => |_| Literal::EmptyVar
28    )
29}
30
31fn literal_bool(input: &[u8]) -> CutParseResult<Literal> {
32    alt!(input,
33        keyword(input, b"true") => |_| Literal::Bool(true)
34      ; keyword(input, b"false") => |_| Literal::Bool(false)
35    )
36}
37
38fn literal_int(input: &[u8]) -> CutParseResult<Literal> {
39    let (i, _) = opt(input, multispace)?;
40    let (i, num) = require!(digits(i));
41    let (i, tag) = require!(opt!(alt!(i,
42        keyword_immediate(i, b"u8")
43      ; keyword_immediate(i, b"i16")
44      ; keyword_immediate(i, b"i32")
45      ; keyword_immediate(i, b"isize")
46    )));
47
48    let num = unsafe { str::from_utf8_unchecked(num) };
49    let parsed = match tag {
50        None => num.parse::<i32>().map(Literal::Int32),
51        Some(b"u8") => num.parse::<u8>().map(Literal::UInt8),
52        Some(b"i16") => num.parse::<i16>().map(Literal::Int16),
53        Some(b"i32") => num.parse::<i32>().map(Literal::Int32),
54        Some(b"isize") => num.parse::<i64>().map(Literal::IntPtr),
55        _ => panic!("dumpster fire: bad tag in int literal"),
56    };
57
58    match parsed {
59        Ok(lit) => ok!(i, lit),
60        // if we have numbers and a tag but fail the numeric parse,
61        //   that's an unrecoverable error
62        Err(_) => cut!(input, ParseErrorKind::InvalidLiteral),
63    }
64}
65
66fn literal_float(input: &[u8]) -> CutParseResult<Literal> {
67    let (i, _) = opt(input, multispace)?;
68    let (i, whole) = require!(digits(i));
69    // mandatory decimal point
70    let (i, _) = require!(byte(i, b'.'));
71    let (i, frac) = require!(opt(i, digits));
72    let (i, tag) = require!(opt!(alt!(i,
73        keyword_immediate(i, b"f32")
74      ; keyword_immediate(i, b"f64")
75    )));
76
77    let mut num = String::from(unsafe { str::from_utf8_unchecked(whole) });
78    match frac {
79        None => { },
80        Some(frac) => {
81            num.push_str(".");
82            num.push_str(unsafe { str::from_utf8_unchecked(frac) });
83        }
84    };
85
86    let parsed = match tag {
87        None => num.parse::<f64>().map(Literal::Float64),
88        Some(b"f32") => num.parse::<f32>().map(Literal::Float32),
89        Some(b"f64") => num.parse::<f64>().map(Literal::Float64),
90        _ => panic!("dumpster fire: bad tag in float literal"),
91    };
92
93    match parsed {
94        Ok(lit) => ok!(i, lit),
95        // if we have numbers and a tag but fail the numeric parse,
96        //   that's an unrecoverable error
97        Err(_) => cut!(input, ParseErrorKind::InvalidLiteral),
98    }
99}
100
101fn literal_currency(input: &[u8]) -> CutParseResult<Literal> {
102    let (i, _) = opt(input, multispace)?;
103
104    let (i, whole) = require!(digits(i));
105
106    let (i, frac) = require!(opt!(chain!(i,
107        |i| byte(i, b'.') =>
108        digits
109    )));
110
111    let (i, _) = require!(keyword_immediate(i, b"currency"));
112
113    let whole = unsafe { str::from_utf8_unchecked(whole) }.parse::<i64>();
114    let frac = match frac {
115        None => Ok(0i16),
116        Some(frac) => {
117            unsafe { str::from_utf8_unchecked(frac) }.parse::<i16>()
118        },
119    };
120
121    let (whole, frac) = match (whole, frac) {
122        (Err(_), _) => return cut!(input, ParseErrorKind::InvalidLiteral),
123        (_, Err(_)) => return cut!(input, ParseErrorKind::InvalidLiteral),
124        (Ok(whole), Ok(frac)) => (whole, frac),
125    };
126
127    ok!(i, make_currency(whole, frac))
128}
129
130fn literal_string(input: &[u8]) -> CutParseResult<Literal> {
131    let (i, _) = opt(input, multispace)?;
132    let (i, _) = require!(byte(i, b'"'));
133    let (i, escaped) = require!(escaped_string(i));
134    let (i, _) = require!(byte(i, b'"'));
135
136    match String::from_utf8(escaped) {
137        Ok(s) => ok!(i, Literal::String(s)),
138        Err(_) => cut!(input, ParseErrorKind::InvalidLiteral)
139    }
140}
141
142#[inline]
143fn make_currency(whole: i64, frac: i16) -> Literal {
144    let frac_digits = (frac as f32).log10().ceil() as i16;
145    let frac_scalar = f32::powf(10.0, (4 - frac_digits) as f32);
146    Literal::Currency(whole * 10000 + (frac as f32 * frac_scalar) as i64)
147}
148
149fn escaped_string(input: &[u8]) -> CutParseResult<Vec<u8>> {
150    let mut s = Vec::new();
151    let mut bytes_consumed = 0;
152    let mut bytes = input.iter();
153    while let Some(c) = bytes.next() {
154        if *c == b'"' {
155            break;
156        }
157
158        if *c == b'\\' {
159            match bytes.next() {
160                Some(&b'n') => s.push(b'\n'),
161                Some(&b't') => s.push(b'\t'),
162                Some(&b'"') => s.push(b'"'),
163                // TODO: more escapes here
164                _ => return cut!(&input[bytes_consumed..],
165                  ParseErrorKind::InvalidEscape)
166            }
167            bytes_consumed += 2;
168            continue;
169        }
170
171        // TODO: it'd be nice to allow rust style multiline strings
172        //   (or maybe C-style adjacent-literal concatenation)
173        // first option needs peek here; second just needs a change to the
174        // literal_string production
175
176        bytes_consumed += 1;
177        s.push(*c);
178    }
179
180    ok!(&input[bytes_consumed..], s)
181}
182
183#[cfg(test)]
184mod test {
185    use super::*;
186
187    #[test]
188    fn parse_literals() {
189        expect_parse!(literal(b"nullptr") => Literal::NullPtr);
190        expect_parse!(literal(b"123.45") => Literal::Float64(123.45));
191        expect_parse!(literal(b"\n123i16") => Literal::Int16(123i16));
192        expect_parse!(literal(b"\t123.45currency") =>
193          Literal::Currency(1234500i64));
194        expect_parse!(literal(b"\"hello\tworld\"") => Literal::String(_));
195        expect_parse_err!(literal(b"alskf") => ParseErrorKind::NoAltMatch);
196        expect_parse_cut!(literal(b"  123456789u8") =>
197          ParseErrorKind::InvalidLiteral);
198    }
199
200    #[test]
201    fn parse_nulls() {
202        expect_parse!(literal_null(b"nullptr") => Literal::NullPtr);
203        expect_parse!(literal_null(b" nullvar") => Literal::NullVar);
204        expect_parse!(literal_null(b"\nemptyvar") => Literal::EmptyVar);
205    }
206
207    #[test]
208    fn parse_bools() {
209        expect_parse!(literal_bool(b"   true") => Literal::Bool(true));
210        expect_parse!(literal_bool(b"false") => Literal::Bool(false));
211        expect_parse_err!(literal_bool(b"fake") => _);
212    }
213
214    #[test]
215    fn parse_ints() {
216        expect_parse!(literal_int(b"721") => Literal::Int32(721));
217        expect_parse!(literal_int(b"123u8") => Literal::UInt8(123u8));
218        expect_parse_err!(literal_int(b"alskf") =>
219          ParseErrorKind::ExpectedDigit);
220        expect_parse_cut!(literal_int(b"123456789u8") =>
221          ParseErrorKind::InvalidLiteral);
222    }
223
224    #[test]
225    fn parse_floats() {
226        expect_parse!(literal_float(b"124.5") => Literal::Float64(124.5));
227        expect_parse!(literal_float(b"1234.56f32") => Literal::Float32(1234.56f32));
228        expect_parse_err!(literal_float(b"x.12") => ParseErrorKind::ExpectedDigit);
229        /*
230        expect_parse_cut!(literal_float(b"9999999999999999999999999999.0f32") =>
231          ParseErrorKind::InvalidLiteral);
232        */
233    }
234
235    #[test]
236    fn parse_currency() {
237        expect_parse!(literal_currency(b" 12345.67currency") =>
238          Literal::Currency(123456700i64));
239        expect_parse_cut!(
240            literal_currency(b"999.9999999999999999999999999999currency") => _);
241    }
242
243    #[test]
244    fn parse_string() {
245        expect_parse!(literal_string(b"\"hello world\\nor whatever\"") =>
246          Literal::String(_));
247        expect_parse_err!(literal_string(b"\"unclosed") => _);
248        expect_parse_cut!(literal_string(b"   \"invalid \\x escape\"") => _);
249    }
250}