1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct AgxCall {
6 pub name: String,
8 pub arity: usize,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ParsedExpression {
15 pub calls: Vec<AgxCall>,
17 pub references: Vec<Vec<String>>,
19}
20
21#[derive(Debug, Error)]
23#[error("{0}")]
24pub struct AgxError(pub String);
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27enum Token {
28 Name(String),
29 String,
30 Number,
31 True,
32 False,
33 Null,
34 Op(String),
35 LParen,
36 RParen,
37 LBracket,
38 RBracket,
39 Dot,
40 Comma,
41}
42
43fn tokenize(input: &str) -> Result<Vec<Token>, AgxError> {
44 let chars: Vec<char> = input.chars().collect();
45 let mut tokens = vec![];
46 let mut i = 0;
47 while i < chars.len() {
48 match chars[i] {
49 c if c.is_whitespace() => i += 1,
50 '(' => {
51 tokens.push(Token::LParen);
52 i += 1;
53 }
54 ')' => {
55 tokens.push(Token::RParen);
56 i += 1;
57 }
58 '[' => {
59 tokens.push(Token::LBracket);
60 i += 1;
61 }
62 ']' => {
63 tokens.push(Token::RBracket);
64 i += 1;
65 }
66 '.' => {
67 tokens.push(Token::Dot);
68 i += 1;
69 }
70 ',' => {
71 tokens.push(Token::Comma);
72 i += 1;
73 }
74 '\'' | '"' => {
75 let quote = chars[i];
76 i += 1;
77 let mut closed = false;
78 while i < chars.len() {
79 if chars[i] == '\\' {
80 i += 2;
81 continue;
82 }
83 if chars[i] == quote {
84 i += 1;
85 closed = true;
86 break;
87 }
88 i += 1;
89 }
90 if !closed {
91 return Err(AgxError("unterminated string".into()));
92 }
93 tokens.push(Token::String);
94 }
95 c if c.is_ascii_digit() => {
96 i += 1;
97 while i < chars.len()
98 && (chars[i].is_ascii_digit()
99 || matches!(chars[i], '.' | 'e' | 'E' | '+' | '-'))
100 {
101 i += 1;
102 }
103 tokens.push(Token::Number);
104 }
105 c if c.is_ascii_alphabetic() || c == '_' => {
106 let start = i;
107 i += 1;
108 while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
109 i += 1;
110 }
111 let name: String = chars[start..i].iter().collect();
112 tokens.push(match name.as_str() {
113 "true" => Token::True,
114 "false" => Token::False,
115 "null" => Token::Null,
116 "in" => Token::Op(name),
117 _ => Token::Name(name),
118 });
119 }
120 _ => {
121 let remaining: String = chars[i..].iter().collect();
122 let op = [
123 "&&", "||", "==", "!=", "<=", ">=", "+", "-", "*", "/", "%", "!", "<", ">",
124 ]
125 .into_iter()
126 .find(|candidate| remaining.starts_with(candidate))
127 .ok_or_else(|| AgxError(format!("unexpected character {:?}", chars[i])))?;
128 tokens.push(Token::Op(op.into()));
129 i += op.len();
130 }
131 }
132 }
133 Ok(tokens)
134}
135
136struct Parser {
137 tokens: Vec<Token>,
138 at: usize,
139 calls: Vec<AgxCall>,
140 references: Vec<Vec<String>>,
141}
142
143impl Parser {
144 fn peek(&self) -> Option<&Token> {
145 self.tokens.get(self.at)
146 }
147 fn take(&mut self) -> Result<Token, AgxError> {
148 let token = self
149 .peek()
150 .cloned()
151 .ok_or_else(|| AgxError("unexpected end of expression".into()))?;
152 self.at += 1;
153 Ok(token)
154 }
155 fn op(&mut self, wanted: &str) -> bool {
156 if matches!(self.peek(), Some(Token::Op(op)) if op == wanted) {
157 self.at += 1;
158 true
159 } else {
160 false
161 }
162 }
163 fn parse(&mut self) -> Result<(), AgxError> {
164 self.or()?;
165 if self.peek().is_some() {
166 return Err(AgxError("unexpected trailing token".into()));
167 }
168 Ok(())
169 }
170 fn or(&mut self) -> Result<(), AgxError> {
171 self.and()?;
172 while self.op("||") {
173 self.and()?;
174 }
175 Ok(())
176 }
177 fn and(&mut self) -> Result<(), AgxError> {
178 self.equality()?;
179 while self.op("&&") {
180 self.equality()?;
181 }
182 Ok(())
183 }
184 fn equality(&mut self) -> Result<(), AgxError> {
185 self.comparison()?;
186 loop {
187 if self.op("==") || self.op("!=") || self.op("in") {
188 self.comparison()?;
189 } else {
190 break;
191 }
192 }
193 Ok(())
194 }
195 fn comparison(&mut self) -> Result<(), AgxError> {
196 self.additive()?;
197 loop {
198 if self.op("<") || self.op("<=") || self.op(">") || self.op(">=") {
199 self.additive()?;
200 } else {
201 break;
202 }
203 }
204 Ok(())
205 }
206 fn additive(&mut self) -> Result<(), AgxError> {
207 self.product()?;
208 loop {
209 if self.op("+") || self.op("-") {
210 self.product()?;
211 } else {
212 break;
213 }
214 }
215 Ok(())
216 }
217 fn product(&mut self) -> Result<(), AgxError> {
218 self.unary()?;
219 loop {
220 if self.op("*") || self.op("/") || self.op("%") {
221 self.unary()?;
222 } else {
223 break;
224 }
225 }
226 Ok(())
227 }
228 fn unary(&mut self) -> Result<(), AgxError> {
229 if self.op("!") || self.op("-") {
230 self.unary()
231 } else {
232 self.primary()
233 }
234 }
235 fn primary(&mut self) -> Result<(), AgxError> {
236 match self.take()? {
237 Token::String | Token::Number | Token::True | Token::False | Token::Null => Ok(()),
238 Token::LParen => {
239 self.or()?;
240 if self.take()? != Token::RParen {
241 return Err(AgxError("expected ')'".into()));
242 }
243 Ok(())
244 }
245 Token::LBracket => {
246 if matches!(self.peek(), Some(Token::RBracket)) {
247 self.at += 1;
248 return Ok(());
249 }
250 loop {
251 self.or()?;
252 match self.take()? {
253 Token::Comma => continue,
254 Token::RBracket => break,
255 _ => return Err(AgxError("expected ',' or ']'".into())),
256 }
257 }
258 Ok(())
259 }
260 Token::Name(name) => {
261 if matches!(self.peek(), Some(Token::LParen)) {
262 self.at += 1;
263 let mut arity = 0;
264 if !matches!(self.peek(), Some(Token::RParen)) {
265 loop {
266 self.or()?;
267 arity += 1;
268 if matches!(self.peek(), Some(Token::Comma)) {
269 self.at += 1;
270 } else {
271 break;
272 }
273 }
274 }
275 if self.take()? != Token::RParen {
276 return Err(AgxError("expected ')'".into()));
277 }
278 self.calls.push(AgxCall { name, arity });
279 } else {
280 let mut reference = vec![name];
281 while matches!(self.peek(), Some(Token::Dot)) {
282 self.at += 1;
283 match self.take()? {
284 Token::Name(part) => reference.push(part),
285 _ => return Err(AgxError("expected name after '.'".into())),
286 }
287 }
288 self.references.push(reference);
289 }
290 Ok(())
291 }
292 _ => Err(AgxError("expected expression".into())),
293 }
294 }
295}
296
297pub fn parse_expression(input: &str) -> Result<ParsedExpression, AgxError> {
299 let mut parser = Parser {
300 tokens: tokenize(input)?,
301 at: 0,
302 calls: vec![],
303 references: vec![],
304 };
305 parser.parse()?;
306 Ok(ParsedExpression {
307 calls: parser.calls,
308 references: parser.references,
309 })
310}