Skip to main content

rink_core/loader/
gnu_units.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::ast::*;
6use crate::types::Numeric;
7use std::collections::BTreeMap;
8use std::iter::Peekable;
9use std::rc::Rc;
10use std::str::Chars;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Token {
14    Eof,
15    Newline,
16    Doc(String),
17    Ident(String),
18    Number(String, Option<String>, Option<String>),
19    LPar,
20    RPar,
21    Bang,
22    Slash,
23    Pipe,
24    Caret,
25    Plus,
26    Dash,
27    Asterisk,
28    Question,
29    LeftBrace,
30    RightBrace,
31    Error(String),
32}
33
34#[derive(Clone)]
35pub struct TokenIterator<'a>(Peekable<Chars<'a>>);
36
37impl<'a> TokenIterator<'a> {
38    pub fn new(input: &'a str) -> TokenIterator<'a> {
39        TokenIterator(input.chars().peekable())
40    }
41}
42
43fn is_ident(c: char) -> bool {
44    match c {
45        //c if c.is_alphabetic() => true,
46        //'_' | '$' | '-' | '\'' | '"' | '%' | ',' => true,
47        ' ' | '\t' | '\n' | '\r' | '(' | ')' | '/' | '|' | '^' | '+' | '*' | '\\' | '#' => false,
48        _ => true,
49    }
50}
51
52impl<'a> Iterator for TokenIterator<'a> {
53    type Item = Token;
54
55    fn next(&mut self) -> Option<Token> {
56        if self.0.peek().is_none() {
57            return Some(Token::Eof);
58        }
59        let res = match self.0.next().unwrap() {
60            ' ' | '\t' => return self.next(),
61            '\r' => {
62                if self.0.peek() == Some(&'\n') {
63                    self.0.next();
64                    Token::Newline
65                } else {
66                    Token::Newline
67                }
68            }
69            '\n' => Token::Newline,
70            '!' => Token::Bang,
71            '(' => Token::LPar,
72            ')' => Token::RPar,
73            '/' => Token::Slash,
74            '|' => Token::Pipe,
75            '^' => Token::Caret,
76            '-' => Token::Dash,
77            '+' => Token::Plus,
78            '*' => Token::Asterisk,
79            '{' => Token::LeftBrace,
80            '}' => Token::RightBrace,
81            '?' => {
82                if self.0.peek() == Some(&'?') {
83                    self.0.next();
84                    let mut out = String::new();
85                    loop {
86                        match self.0.next() {
87                            Some('\n') | None => break,
88                            Some(x) => out.push(x),
89                        }
90                    }
91                    Token::Doc(out)
92                } else {
93                    Token::Question
94                }
95            }
96            '\\' => match self.0.next() {
97                Some('\r') => match self.0.next() {
98                    Some('\n') => self.next().unwrap(),
99                    _ => Token::Error("Expected LF or CRLF line endings".to_string()),
100                },
101                Some('\n') => self.next().unwrap(),
102                Some(x) => Token::Error(format!("Invalid escape: \\{}", x)),
103                None => Token::Error("Unexpected EOF".to_string()),
104            },
105            '#' => {
106                for c in self.0.by_ref() {
107                    if c == '\n' {
108                        break;
109                    }
110                }
111                Token::Newline
112            }
113            x @ '0'..='9' | x @ '.' => {
114                let mut integer = String::new();
115                let mut frac = None;
116                let mut exp = None;
117
118                // integer component
119                if x != '.' {
120                    integer.push(x);
121                    while let Some('0'..='9') = self.0.peek().cloned() {
122                        integer.push(self.0.next().unwrap());
123                    }
124                } else {
125                    integer.push('0');
126                }
127                // fractional component
128                if x == '.' || Some('.') == self.0.peek().cloned() {
129                    let mut buf = String::new();
130                    if x != '.' {
131                        self.0.next();
132                    }
133                    while let Some('0'..='9') = self.0.peek().cloned() {
134                        buf.push(self.0.next().unwrap());
135                    }
136                    if !buf.is_empty() {
137                        frac = Some(buf)
138                    }
139                }
140                // exponent
141                if let Some('e') = self.0.peek().cloned().map(|x| x.to_ascii_lowercase()) {
142                    let mut buf = String::new();
143                    self.0.next();
144                    if let Some(c) = self.0.peek().cloned() {
145                        match c {
146                            '-' => {
147                                buf.push(self.0.next().unwrap());
148                            }
149                            '+' => {
150                                self.0.next();
151                            }
152                            _ => (),
153                        }
154                    }
155                    while let Some('0'..='9') = self.0.peek().cloned() {
156                        buf.push(self.0.next().unwrap());
157                    }
158                    if !buf.is_empty() {
159                        exp = Some(buf)
160                    }
161                }
162                Token::Number(integer, frac, exp)
163            }
164            '"' => {
165                let mut buf = String::new();
166                while let Some(c) = self.0.next() {
167                    if c == '\\' {
168                        if let Some(c) = self.0.next() {
169                            buf.push(c);
170                        }
171                    } else if c == '"' {
172                        break;
173                    } else {
174                        buf.push(c);
175                    }
176                }
177                Token::Ident(buf)
178            }
179            x => {
180                // All of the is_ident==false patterns should have already been handled. This can
181                // be verified by adding all of the is_ident=false patterns as a branch and seeing
182                // that it is unreachable.
183                assert!(is_ident(x));
184                let mut buf = String::new();
185                buf.push(x);
186                while let Some(c) = self.0.peek().cloned() {
187                    if is_ident(c) || c.is_numeric() {
188                        buf.push(self.0.next().unwrap());
189                    } else {
190                        break;
191                    }
192                }
193                Token::Ident(buf)
194            }
195        };
196        Some(res)
197    }
198}
199
200pub type Iter<'a> = Peekable<TokenIterator<'a>>;
201
202fn parse_term(iter: &mut Iter<'_>) -> Expr {
203    match iter.next().unwrap() {
204        Token::Ident(name) => match iter.peek().cloned().unwrap() {
205            Token::Ident(ref s) if s == "of" => {
206                iter.next();
207                Expr::Of {
208                    property: name,
209                    expr: Box::new(parse_mul(iter)),
210                }
211            }
212            _ => Expr::new_unit(name),
213        },
214        Token::Number(num, frac, exp) => Numeric::from_parts(
215            &*num,
216            frac.as_ref().map(|x| &**x),
217            exp.as_ref().map(|x| &**x),
218        )
219        .map(Expr::new_const)
220        .unwrap_or_else(Expr::new_error),
221        Token::Plus => Expr::new_plus(parse_term(iter)),
222        Token::Dash => Expr::new_negate(parse_term(iter)),
223        Token::Slash => Expr::new_frac(Expr::new_const(Numeric::one()), parse_term(iter)),
224        Token::LPar => {
225            let res = parse_expr(iter);
226            match iter.next().unwrap() {
227                Token::RPar => res,
228                x => Expr::new_error(format!("Expected ), got {:?}", x)),
229            }
230        }
231        x => Expr::new_error(format!("Expected term, got {:?}", x)),
232    }
233}
234
235fn parse_pow(iter: &mut Iter<'_>) -> Expr {
236    let left = parse_term(iter);
237    match *iter.peek().unwrap() {
238        Token::Caret => {
239            iter.next();
240            let right = parse_pow(iter);
241            Expr::BinOp(BinOpExpr {
242                op: BinOpType::Pow,
243                left: Box::new(left),
244                right: Box::new(right),
245            })
246        }
247        Token::Pipe => {
248            iter.next();
249            let right = parse_pow(iter);
250            Expr::BinOp(BinOpExpr {
251                op: BinOpType::Frac,
252                left: Box::new(left),
253                right: Box::new(right),
254            })
255        }
256        _ => left,
257    }
258}
259
260fn parse_mul(iter: &mut Iter<'_>) -> Expr {
261    let mut terms = vec![parse_pow(iter)];
262    loop {
263        match iter.peek().cloned().unwrap() {
264            Token::Slash
265            | Token::Plus
266            | Token::Dash
267            | Token::RPar
268            | Token::Newline
269            | Token::Eof => break,
270            Token::Asterisk => {
271                iter.next();
272            }
273            _ => terms.push(parse_pow(iter)),
274        }
275    }
276    if terms.len() == 1 {
277        terms.pop().unwrap()
278    } else {
279        Expr::new_mul(terms)
280    }
281}
282
283fn parse_div(iter: &mut Iter<'_>) -> Expr {
284    let mut left = parse_mul(iter);
285    while let Token::Slash = *iter.peek().unwrap() {
286        iter.next();
287        let right = parse_mul(iter);
288        left = Expr::new_frac(left, right);
289    }
290    left
291}
292
293fn parse_add(iter: &mut Iter<'_>) -> Expr {
294    let left = parse_div(iter);
295    match *iter.peek().unwrap() {
296        Token::Plus => {
297            iter.next();
298            let right = parse_add(iter);
299            Expr::new_add(left, right)
300        }
301        Token::Dash => {
302            iter.next();
303            let right = parse_add(iter);
304            Expr::new_sub(left, right)
305        }
306        _ => left,
307    }
308}
309
310pub fn parse_expr(iter: &mut Iter<'_>) -> Expr {
311    parse_add(iter)
312}
313
314pub fn parse(iter: &mut Iter<'_>) -> (Defs, Vec<String>) {
315    let mut map = vec![];
316    let mut line = 1;
317    let mut doc: Option<String> = None;
318    let mut category: Option<String> = None;
319    let mut symbols = BTreeMap::new();
320    let mut errors = vec![];
321    loop {
322        match iter.next().unwrap() {
323            Token::Newline => line += 1,
324            Token::Eof => break,
325            Token::Bang => match iter.next().unwrap() {
326                Token::Ident(ref s) if s == "category" => {
327                    match (iter.next().unwrap(), iter.next().unwrap()) {
328                        (Token::Ident(short), Token::Ident(display_name)) => {
329                            map.push(DefEntry {
330                                name: short.clone(),
331                                def: Rc::new(Def::Category { display_name }),
332                                doc: None,
333                                category: None,
334                            });
335                            category = Some(short);
336                        }
337                        _ => errors.push(format!("Malformed category directive")),
338                    }
339                }
340                Token::Ident(ref s) if s == "endcategory" => {
341                    if category.is_none() {
342                        errors.push(format!("Stray endcategory directive"));
343                    }
344                    category = None
345                }
346                Token::Ident(ref s) if s == "symbol" => {
347                    match (iter.next().unwrap(), iter.next().unwrap()) {
348                        (Token::Ident(subst), Token::Ident(sym)) => {
349                            symbols.insert(subst, sym);
350                        }
351                        _ => errors.push(format!("Malformed symbol directive")),
352                    }
353                }
354                Token::Ident(ref s) if s == "dependency" => {
355                    let name = if let Some(Token::Ident(ref name)) = iter.next() {
356                        name.clone()
357                    } else {
358                        errors.push(format!("Expected ident after `!dependency`"));
359                        continue;
360                    };
361
362                    map.push(DefEntry {
363                        name: name.clone(),
364                        def: Rc::new(Def::Dependency { name }),
365                        doc: doc.take(),
366                        category: category.clone(),
367                    });
368                }
369                Token::Ident(ref s) => {
370                    errors.push(format!("Unknown directive !{s}"));
371                    loop {
372                        match iter.peek().cloned().unwrap() {
373                            Token::Newline | Token::Eof => break,
374                            _ => {
375                                iter.next();
376                            }
377                        }
378                    }
379                }
380                _ => {
381                    errors.push(format!("syntax error: expected ident after !"));
382                    loop {
383                        match iter.peek().cloned().unwrap() {
384                            Token::Newline | Token::Eof => break,
385                            _ => {
386                                iter.next();
387                            }
388                        }
389                    }
390                }
391            },
392            Token::Doc(line) => {
393                doc = match doc.take() {
394                    None => Some(line.trim().to_owned()),
395                    Some(old) => Some(format!("{} {}", old.trim(), line.trim())),
396                };
397            }
398            Token::Ident(name) => {
399                if name.ends_with('-') {
400                    // prefix
401                    let expr = parse_expr(iter);
402                    let mut name = name;
403                    name.pop();
404                    if name.ends_with('-') {
405                        name.pop();
406                        map.push(DefEntry {
407                            name,
408                            def: Rc::new(Def::Prefix {
409                                expr: ExprString(expr),
410                                is_long: false,
411                            }),
412                            doc: doc.take(),
413                            category: category.clone(),
414                        });
415                    } else {
416                        map.push(DefEntry {
417                            name,
418                            def: Rc::new(Def::Prefix {
419                                expr: ExprString(expr),
420                                is_long: true,
421                            }),
422                            doc: doc.take(),
423                            category: category.clone(),
424                        });
425                    }
426                } else {
427                    // unit
428                    if let Some(&Token::Bang) = iter.peek() {
429                        // base unit
430                        iter.next();
431
432                        let long_name = if let Some(Token::Ident(ref long)) = iter.peek().cloned() {
433                            iter.next();
434                            Some(long.clone())
435                        } else {
436                            None
437                        };
438
439                        map.push(DefEntry {
440                            name: name.clone(),
441                            def: Rc::new(Def::BaseUnit { long_name }),
442                            doc: doc.take(),
443                            category: category.clone(),
444                        });
445                    } else if let Some(&Token::Question) = iter.peek() {
446                        // quantity
447                        iter.next();
448                        let expr = parse_expr(iter);
449                        map.push(DefEntry {
450                            name,
451                            def: Rc::new(Def::Quantity {
452                                expr: ExprString(expr),
453                            }),
454                            doc: doc.take(),
455                            category: category.clone(),
456                        });
457                    } else if let Some(&Token::LeftBrace) = iter.peek() {
458                        // substance
459                        iter.next();
460                        let mut props = vec![];
461                        let mut prop_doc = None;
462                        loop {
463                            let name = match iter.next().unwrap() {
464                                Token::Ident(name) => name,
465                                Token::Newline => {
466                                    line += 1;
467                                    continue;
468                                }
469                                Token::Eof => break,
470                                Token::Doc(line) => {
471                                    prop_doc = match prop_doc.take() {
472                                        None => Some(line.trim().to_owned()),
473                                        Some(old) => {
474                                            Some(format!("{} {}", old.trim(), line.trim()))
475                                        }
476                                    };
477                                    continue;
478                                }
479                                Token::RightBrace => break,
480                                x => {
481                                    errors.push(format!("Expected property, got {:?}", x));
482                                    break;
483                                }
484                            };
485                            let output_name = match iter.next().unwrap() {
486                                Token::Ident(ref s) if s == "const" => {
487                                    let input_name = match iter.next().unwrap() {
488                                        Token::Ident(name) => name,
489                                        x => {
490                                            errors.push(format!(
491                                                "Expected property input \
492                                                 name, got {:?}",
493                                                x
494                                            ));
495                                            break;
496                                        }
497                                    };
498                                    let output = parse_div(iter);
499                                    props.push(Property {
500                                        output_name: name.clone(),
501                                        name,
502                                        input: ExprString(Expr::new_const(Numeric::one())),
503                                        input_name,
504                                        output: ExprString(output),
505                                        doc: prop_doc.take(),
506                                    });
507                                    continue;
508                                }
509                                Token::Ident(name) => name,
510                                x => {
511                                    errors
512                                        .push(format!("Expected property input name, got {:?}", x));
513                                    break;
514                                }
515                            };
516                            let output = parse_mul(iter);
517                            match iter.next().unwrap() {
518                                Token::Slash => (),
519                                x => {
520                                    errors.push(format!("Expected /, got {:?}", x));
521                                    break;
522                                }
523                            }
524                            let input_name = match iter.next().unwrap() {
525                                Token::Ident(name) => name,
526                                x => {
527                                    errors
528                                        .push(format!("Expected property input name, got {:?}", x));
529                                    break;
530                                }
531                            };
532                            let input = parse_mul(iter);
533                            props.push(Property {
534                                name,
535                                input: ExprString(input),
536                                input_name,
537                                output: ExprString(output),
538                                output_name,
539                                doc: prop_doc.take(),
540                            });
541                        }
542                        map.push(DefEntry {
543                            name,
544                            def: Rc::new(Def::Substance {
545                                symbol: None,
546                                properties: props,
547                            }),
548                            doc: doc.take(),
549                            category: category.clone(),
550                        });
551                    } else {
552                        // derived
553                        let expr = parse_expr(iter);
554                        map.push(DefEntry {
555                            name,
556                            def: Rc::new(Def::Unit {
557                                expr: ExprString(expr),
558                            }),
559                            doc: doc.take(),
560                            category: category.clone(),
561                        });
562                    }
563                }
564            }
565            x => errors.push(format!("Expected definition on line {}, got {:?}", line, x)),
566        };
567    }
568
569    for entry in map.iter_mut() {
570        if let Def::Substance { ref mut symbol, .. } = *Rc::get_mut(&mut entry.def).unwrap() {
571            *symbol = symbols.get(&entry.name).map(|x| x.to_owned())
572        }
573    }
574
575    (Defs { defs: map }, errors)
576}
577
578pub fn parse_str(input: &str) -> Defs {
579    let mut iter = TokenIterator::new(&*input).peekable();
580    let (defs, errors) = parse(&mut iter);
581    for error in errors {
582        println!("{}", error);
583    }
584    defs
585}
586
587pub fn tokens(iter: &mut Iter<'_>) -> Vec<Token> {
588    let mut out = vec![];
589    loop {
590        match iter.next().unwrap() {
591            Token::Eof => break,
592            x => out.push(x),
593        }
594    }
595    out
596}