logic_eval/parse/
inner.rs

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