logic_eval/parse/
inner.rs

1use crate::{Error, Result};
2
3pub fn parse_str<T: Parse>(text: &str) -> Result<T> {
4    let mut buf = ParseBuffer::new(text);
5    T::parse(&mut buf)
6}
7
8pub trait Parse: Sized {
9    fn parse(buf: &mut ParseBuffer<'_>) -> Result<Self>;
10}
11
12#[derive(Clone, Copy)]
13pub struct ParseBuffer<'a> {
14    pub(crate) text: &'a str,
15    /// Inclusive
16    pub(crate) start: usize,
17    /// Exclusive
18    pub(crate) end: usize,
19}
20
21impl<'a> ParseBuffer<'a> {
22    pub const fn new(text: &'a str) -> Self {
23        Self {
24            text,
25            start: 0,
26            end: text.len(),
27        }
28    }
29
30    pub(crate) fn cur_text(&self) -> &str {
31        &self.text[self.start..self.end]
32    }
33
34    pub(crate) fn parse<T: Parse>(&mut self) -> Result<T> {
35        T::parse(self)
36    }
37
38    pub(crate) fn peek_parse<T: Parse>(&self) -> Option<(T, Self)> {
39        let mut peek = *self;
40        // FIXME: No need to create error messages, which may cause performance
41        // issue.
42        T::parse(&mut peek).ok().map(|t| (t, peek))
43    }
44}
45
46pub(crate) struct Ident(Location);
47
48impl Ident {
49    pub(crate) fn to_text<'a>(&self, whole_text: &'a str) -> &'a str {
50        &whole_text[self.0.left..self.0.right]
51    }
52}
53
54impl Parse for Ident {
55    fn parse(buf: &mut ParseBuffer<'_>) -> Result<Self> {
56        fn is_allowed_first(c: char) -> bool {
57            c.is_alphabetic() || !(c.is_whitespace() || RESERVED.contains(&c))
58        }
59
60        fn is_allowed_rest(c: char) -> bool {
61            c.is_alphanumeric() || !(c.is_whitespace() || RESERVED.contains(&c) || c == VAR_PREFIX)
62        }
63
64        let s = buf.cur_text();
65
66        let Some(l) = s.find(|c: char| !c.is_whitespace()) else {
67            return Err("expected an ident, but input is empty".into());
68        };
69
70        let mut r = l;
71
72        let first = s[l..].chars().next().unwrap();
73        if is_allowed_first(first) {
74            r += first.len_utf8();
75        } else {
76            return Err(format!("expected an ident from {}", s).into());
77        }
78
79        for rest in s[l..].chars().skip(1) {
80            if is_allowed_rest(rest) {
81                r += rest.len_utf8();
82            } else {
83                break;
84            }
85        }
86
87        let loc = Location {
88            left: buf.start + l,
89            right: buf.start + r,
90        };
91        buf.start += r;
92        Ok(Ident(loc))
93    }
94}
95
96macro_rules! impl_parse_for_string {
97    ($str:literal, $ty:ident) => {
98        impl Parse for $ty {
99            fn parse(buf: &mut ParseBuffer<'_>) -> Result<Self> {
100                let s = buf.cur_text();
101
102                let Some(l) = s.find(|c: char| !c.is_whitespace()) else {
103                    return Err(format!("expected `{}` from `{}`", $str, s).into());
104                };
105                let r = l + $str.len();
106
107                let substr = s
108                    .get(l..r)
109                    .ok_or(Error::from(format!("expected `{}` from `{s}`", $str)))?;
110
111                if substr == $str {
112                    let loc = Location {
113                        left: buf.start + l,
114                        right: buf.start + r,
115                    };
116                    buf.start += r;
117                    Ok($ty { _loc: loc })
118                } else {
119                    Err(format!("expected `{}` from `{s}`", $str).into())
120                }
121            }
122        }
123    };
124}
125
126pub const VAR_PREFIX: char = '$';
127
128pub(crate) const RESERVED: &[char] = &[
129    ',',  // And
130    ';',  // Or
131    '.',  // End of a clause
132    ':',  // Part of a 'is implied by'
133    '-',  // Part of a 'is implied by'
134    '\\', // Part of a not
135    '+',  // Part of a not
136    '(',  // Grouping terms
137    ')',  // Grouping terms
138    '[',  // List
139    ']',  // List
140    '|',  // Seperating head and tail in a list
141    '+',  // Arithmetic operator
142    '-',  // Arithmetic operator
143    '*',  // Arithmetic operator
144    '/',  // Arithmetic operator
145];
146
147pub(crate) struct CommaToken {
148    pub(crate) _loc: Location,
149}
150impl_parse_for_string!(",", CommaToken);
151
152pub(crate) struct SemiToken {
153    pub(crate) _loc: Location,
154}
155impl_parse_for_string!(";", SemiToken);
156
157pub(crate) struct DotToken {
158    pub(crate) _loc: Location,
159}
160impl_parse_for_string!(".", DotToken);
161
162pub(crate) struct HornToken {
163    pub(crate) _loc: Location,
164}
165impl_parse_for_string!(":-", HornToken);
166
167pub(crate) struct NegationToken {
168    pub(crate) _loc: Location,
169}
170impl_parse_for_string!("\\+", NegationToken);
171
172pub(crate) struct OpenParenToken {
173    pub(crate) _loc: Location,
174}
175impl_parse_for_string!("(", OpenParenToken);
176
177pub(crate) struct CloseParenToken {
178    pub(crate) _loc: Location,
179}
180impl_parse_for_string!(")", CloseParenToken);
181
182#[derive(Debug, Clone, Copy)]
183pub(crate) struct Location {
184    /// Inclusive
185    left: usize,
186    /// Exclusive
187    right: usize,
188}