logic_eval/parse/
inner.rs1use 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 pub(crate) start: usize,
21 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 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 ',', ';', '.', ':', '-', '\\', '+', '(', ')', '[', ']', '|', '+', '-', '*', '/', ];
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 left: usize,
192 right: usize,
194}