Skip to main content

logic_eval/parse/
inner.rs

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