├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── earlgrey ├── Cargo.toml ├── README.md ├── examples │ ├── arith.rs │ ├── ebnftree.rs │ └── minisum.rs ├── refs │ ├── A Pint-sized Earley Parser.pdf │ ├── Earley Parsing by Loup.pdf │ ├── Ohm semantic actions.pdf │ ├── earley-doc.pdf │ └── sppf.html.pdf └── src │ ├── earley │ ├── README.md │ ├── grammar.rs │ ├── mod.rs │ ├── parser.rs │ ├── parser_test.rs │ ├── spans.rs │ └── trees.rs │ ├── ebnf.rs │ ├── ebnf_test.rs │ ├── ebnf_tokenizer.rs │ ├── lib.rs │ └── parsers.rs ├── fluxcap ├── Cargo.toml ├── pcfgs.pdf ├── src │ ├── constants.rs │ ├── lib.rs │ ├── main.rs │ ├── time_parser.rs │ ├── time_semantics.rs │ └── time_test.rs └── time_examples ├── kronos ├── Cargo.toml ├── README.md ├── ladz-acl.2014.pdf └── src │ ├── lib.rs │ ├── mixed_tests.rs │ ├── seq_except.rs │ ├── seq_func.rs │ ├── seq_grain.rs │ ├── seq_intersect.rs │ ├── seq_interval.rs │ ├── seq_lastof.rs │ ├── seq_mgrain.rs │ ├── seq_named.rs │ ├── seq_nthof.rs │ ├── seq_seasons.rs │ ├── seq_shim.rs │ ├── seq_union.rs │ ├── types.rs │ └── utils.rs ├── lexers ├── Cargo.toml ├── README.md └── src │ ├── delim_tokenizer.rs │ ├── ebnf_tokenizer.rs │ ├── helpers.rs │ ├── helpers_test.rs │ ├── lib.rs │ ├── lisp_tokenizer.rs │ ├── math_tokenizer.rs │ ├── scanner.rs │ └── scanner_test.rs ├── lisp ├── Cargo.toml ├── README.md └── src │ ├── TODO │ ├── bin │ └── lisp.rs │ ├── builtin.rs │ ├── eval.rs │ ├── lib.rs │ ├── parser.rs │ ├── parser_test.rs │ └── procedure.rs ├── lox ├── Cargo.toml └── src │ ├── lox_environment.rs │ ├── lox_interpreter.rs │ ├── lox_native.rs │ ├── lox_parser.rs │ ├── lox_resolver.rs │ ├── lox_scanner.rs │ ├── main.rs │ └── tests │ ├── break-depth.lox │ ├── break-loops.lox │ ├── nested-scopes.lox │ └── self-reference.lox ├── numerica ├── Cargo.toml └── src │ ├── context.rs │ ├── expr │ ├── arithmetic.rs │ ├── distribution.rs │ ├── find_root.rs │ ├── listops.rs │ ├── mod.rs │ ├── replace_all.rs │ ├── sum.rs │ ├── table.rs │ └── transcendental.rs │ ├── findroot.rs │ ├── itertools.rs │ ├── lib.rs │ ├── matrix.rs │ ├── parser.rs │ ├── plot.rs │ ├── tests.rs │ └── tokenizer.rs ├── shunting ├── Cargo.toml ├── README.md ├── examples │ └── tox.rs └── src │ ├── lib.rs │ ├── parser.rs │ ├── parser_test.rs │ ├── rpneval.rs │ ├── rpneval_test.rs │ └── rpnprint.rs ├── tools ├── Cargo.toml └── src │ └── bin │ └── numerica.rs └── unidades ├── Cargo.toml └── src ├── lib.rs └── si_units.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "earlgrey", 5 | "fluxcap", 6 | "kronos", 7 | "lexers", 8 | "lisp", 9 | "lox", 10 | "shunting", 11 | "numerica", 12 | "unidades", 13 | "tools", 14 | ] 15 | 16 | resolver = "2" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust crates for parsing stuff 2 | 3 | ## lexers 4 | Tokenizers for math expressions, splitting text, lexing lisp-like stuff, etc. 5 | 6 | ## earlgrey 7 | An **Early** CFG parser which can extract all trees and handle ambiguous grammars. 8 | 9 | ## shunting 10 | A tiny *math* parser that understands prefix/infix/postfix operators and functions. 11 | 12 | 13 | ## kronos 14 | A library to compute *time expressions* (eg: the 3rd monday of the week). 15 | 16 | ## fluxcap 17 | A binding of **kronos** and **earlgrey** to parse time expressions from text. 18 | 19 | 20 | ## misc 21 | - lisp: a partial rust clone of lispy. 22 | -------------------------------------------------------------------------------- /earlgrey/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "earlgrey" 3 | version = "0.4.1" 4 | edition = "2024" 5 | license = "MIT" 6 | authors = ["Rodolfo Granata "] 7 | description = "A library for parsing context-free grammars using Earley algorithm" 8 | repository = "https://github.com/rodolf0/tox/tree/master/earlgrey" 9 | readme = "README.md" 10 | keywords = ["parser", "earley", "grammar", "ast", "ebnf"] 11 | categories = ["parsing", "text-processing"] 12 | 13 | # The library crate has no dependencies. Optionals are for tools 14 | [dev-dependencies] 15 | lexers = { version = "0.1", path = "../lexers" } 16 | rustyline = { version = "14.0.0" } 17 | 18 | [features] 19 | # Allow building with 'debug' feature to de verbose printing of internal parsing state. 20 | debug = [] 21 | -------------------------------------------------------------------------------- /earlgrey/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | ```rust 4 | // Full code at examples/ebnftree.rs 5 | fn main() { 6 | let grammar = r#" 7 | expr := expr ('+'|'-') term | term ; 8 | term := term ('*'|'/') factor | factor ; 9 | factor := '-' factor | power ; 10 | power := ufact '^' factor | ufact ; 11 | ufact := ufact '!' | group ; 12 | group := num | '(' expr ')' ; 13 | "#; 14 | 15 | use std::str::FromStr; 16 | let grammar = earlgrey::EbnfGrammarParser::new(grammar, "expr") 17 | .plug_terminal("num", |n| f64::from_str(n).is_ok()) 18 | .into_grammar() 19 | .unwrap(); 20 | 21 | let parser = earlgrey::sexpr_parser(grammar).unwrap(); 22 | 23 | for tree in parser(tokenizer(input.chars()))? { 24 | println!("{}", tree.print()); 25 | } 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /earlgrey/examples/arith.rs: -------------------------------------------------------------------------------- 1 | fn build_grammar() -> earlgrey::Grammar { 2 | use std::str::FromStr; 3 | earlgrey::GrammarBuilder::default() 4 | .nonterm("expr") 5 | .nonterm("term") 6 | .nonterm("factor") 7 | .nonterm("power") 8 | .nonterm("ufact") 9 | .nonterm("group") 10 | .nonterm("func") 11 | .nonterm("args") 12 | .terminal("[n]", |n| f64::from_str(n).is_ok()) 13 | .terminal("+", |n| n == "+") 14 | .terminal("-", |n| n == "-") 15 | .terminal("*", |n| n == "*") 16 | .terminal("/", |n| n == "/") 17 | .terminal("%", |n| n == "%") 18 | .terminal("^", |n| n == "^") 19 | .terminal("!", |n| n == "!") 20 | .terminal("(", |n| n == "(") 21 | .terminal(")", |n| n == ")") 22 | .rule("expr", &["term"]) 23 | .rule("expr", &["expr", "+", "term"]) 24 | .rule("expr", &["expr", "-", "term"]) 25 | .rule("term", &["factor"]) 26 | .rule("term", &["term", "*", "factor"]) 27 | .rule("term", &["term", "/", "factor"]) 28 | .rule("term", &["term", "%", "factor"]) 29 | .rule("factor", &["power"]) 30 | .rule("factor", &["-", "factor"]) 31 | .rule("power", &["ufact"]) 32 | .rule("power", &["ufact", "^", "factor"]) 33 | .rule("ufact", &["group"]) 34 | .rule("ufact", &["ufact", "!"]) 35 | .rule("group", &["[n]"]) 36 | .rule("group", &["(", "expr", ")"]) 37 | .into_grammar("expr") 38 | .expect("Bad Gramar") 39 | } 40 | 41 | struct Tokenizer>(lexers::Scanner); 42 | 43 | impl> Iterator for Tokenizer { 44 | type Item = String; 45 | fn next(&mut self) -> Option { 46 | self.0.scan_whitespace(); 47 | self.0 48 | .scan_math_op() 49 | .or_else(|| self.0.scan_number()) 50 | .or_else(|| self.0.scan_identifier()) 51 | } 52 | } 53 | 54 | fn tokenizer>(input: I) -> Tokenizer { 55 | Tokenizer(lexers::Scanner::new(input)) 56 | } 57 | 58 | fn gamma(x: f64) -> f64 { 59 | #[link(name = "m")] 60 | unsafe extern "C" { 61 | fn tgamma(x: f64) -> f64; 62 | } 63 | unsafe { tgamma(x) } 64 | } 65 | 66 | fn semanter<'a>() -> earlgrey::EarleyForest<'a, f64> { 67 | use std::str::FromStr; 68 | let mut ev = earlgrey::EarleyForest::new(|symbol, token| match symbol { 69 | "[n]" => f64::from_str(token).unwrap(), 70 | _ => 0.0, 71 | }); 72 | ev.action("expr -> term", |n| n[0]); 73 | ev.action("expr -> expr + term", |n| n[0] + n[2]); 74 | ev.action("expr -> expr - term", |n| n[0] - n[2]); 75 | ev.action("term -> factor", |n| n[0]); 76 | ev.action("term -> term * factor", |n| n[0] * n[2]); 77 | ev.action("term -> term / factor", |n| n[0] / n[2]); 78 | ev.action("term -> term % factor", |n| n[0] % n[2]); 79 | ev.action("factor -> power", |n| n[0]); 80 | ev.action("factor -> - factor", |n| -n[1]); 81 | ev.action("power -> ufact", |n| n[0]); 82 | ev.action("power -> ufact ^ factor", |n| n[0].powf(n[2])); 83 | ev.action("ufact -> group", |n| n[0]); 84 | ev.action("ufact -> ufact !", |n| gamma(n[0] + 1.0)); 85 | ev.action("group -> [n]", |n| n[0]); 86 | ev.action("group -> ( expr )", |n| n[1]); 87 | ev 88 | } 89 | 90 | fn main() -> Result<(), String> { 91 | let grammar = build_grammar(); 92 | let parser = earlgrey::EarleyParser::new(grammar.clone()); 93 | let evaler = semanter(); 94 | 95 | if std::env::args().len() > 1 { 96 | let input = std::env::args().skip(1).collect::>().join(" "); 97 | match parser.parse(tokenizer(input.chars())) { 98 | Err(e) => println!("Parse err: {:?}", e), 99 | Ok(state) => println!("{:?}", evaler.eval(&state)), 100 | } 101 | return Ok(()); 102 | } 103 | 104 | println!("{:?}", grammar); 105 | use rustyline::error::ReadlineError; 106 | 107 | let mut rl = rustyline::DefaultEditor::new().map_err(|e| e.to_string())?; 108 | loop { 109 | match rl.readline("~> ") { 110 | Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => return Ok(()), 111 | Err(e) => return Err(format!("Readline err: {:?}", e)), 112 | Ok(line) => match parser.parse(&mut tokenizer(line.chars())) { 113 | Err(e) => println!("Parse err: {:?}", e), 114 | Ok(state) => { 115 | let _ = rl.add_history_entry(&line); 116 | println!("{:?}", evaler.eval(&state)); 117 | } 118 | }, 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /earlgrey/examples/ebnftree.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | struct Tokenizer>(lexers::Scanner); 4 | 5 | impl> Iterator for Tokenizer { 6 | type Item = String; 7 | fn next(&mut self) -> Option { 8 | self.0.scan_whitespace(); 9 | self.0 10 | .scan_math_op() 11 | .or_else(|| self.0.scan_number()) 12 | .or_else(|| self.0.scan_identifier()) 13 | } 14 | } 15 | 16 | fn tokenizer>(input: I) -> Tokenizer { 17 | Tokenizer(lexers::Scanner::new(input)) 18 | } 19 | 20 | fn main() -> Result<(), String> { 21 | let grammar = r#" 22 | expr := expr ('+'|'-') term | term ; 23 | term := term ('*'|'/') factor | factor ; 24 | factor := '-' factor | power ; 25 | power := ufact '^' factor | ufact ; 26 | ufact := ufact '!' | group ; 27 | group := num | '(' expr ')' ; 28 | "#; 29 | 30 | let input = std::env::args().skip(1).collect::>().join(" "); 31 | 32 | use std::str::FromStr; 33 | let grammar = earlgrey::EbnfGrammarParser::new(grammar, "expr") 34 | .plug_terminal("num", |n| f64::from_str(n).is_ok()) 35 | .into_grammar()?; 36 | let parser = earlgrey::sexpr_parser(grammar)?; 37 | 38 | for tree in parser(tokenizer(input.chars()))? { 39 | println!("{}", tree.print()); 40 | } 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /earlgrey/examples/minisum.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Gramar: S -> S + N | N; N -> [0-9]; 3 | let grammar = earlgrey::GrammarBuilder::default() 4 | .nonterm("S") 5 | .nonterm("N") 6 | .terminal("[+]", |c| c == "+") 7 | .terminal("[0-9]", |n| "1234567890".contains(n)) 8 | .rule("S", &["S", "[+]", "N"]) 9 | .rule("S", &["N"]) 10 | .rule("N", &["[0-9]"]) 11 | .into_grammar("S") 12 | .unwrap(); 13 | 14 | // Parse some sum 15 | let input = "1 + 2 + 3".split_whitespace(); 16 | let trees = earlgrey::EarleyParser::new(grammar).parse(input).unwrap(); 17 | 18 | // Evaluate the results 19 | // Describe what to do when we find a Terminal 20 | let mut ev = earlgrey::EarleyForest::new(|symbol, token| match symbol { 21 | "[0-9]" => token.parse().unwrap(), 22 | _ => 0.0, 23 | }); 24 | 25 | // Describe how to execute grammar rules 26 | ev.action("S -> S [+] N", |n| n[0] + n[2]); 27 | ev.action("S -> N", |n| n[0]); 28 | ev.action("N -> [0-9]", |n| n[0]); 29 | 30 | println!("{}", ev.eval(&trees).unwrap()); 31 | } 32 | -------------------------------------------------------------------------------- /earlgrey/refs/A Pint-sized Earley Parser.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/earlgrey/refs/A Pint-sized Earley Parser.pdf -------------------------------------------------------------------------------- /earlgrey/refs/Earley Parsing by Loup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/earlgrey/refs/Earley Parsing by Loup.pdf -------------------------------------------------------------------------------- /earlgrey/refs/Ohm semantic actions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/earlgrey/refs/Ohm semantic actions.pdf -------------------------------------------------------------------------------- /earlgrey/refs/earley-doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/earlgrey/refs/earley-doc.pdf -------------------------------------------------------------------------------- /earlgrey/refs/sppf.html.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/earlgrey/refs/sppf.html.pdf -------------------------------------------------------------------------------- /earlgrey/src/earley/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Earlgrey is a crate for building parsers that can understand context-free grammars. 4 | 5 | ## How to use it 6 | 7 | Parsing stage: 8 | 9 | - First you need to define a grammar using `GrammarBuilder` to define terminals and rules. 10 | - Then build an `EarleyParser` for that grammar and call `parse` on some input. 11 | 12 | Invoking the parser on some input returns an opaque type (list of Earley items) that encodes all possible trees. If the grammar is unambiguous this should represent a single tree. 13 | 14 | Evaluating the result: 15 | 16 | You need an `EarleyForest` that will walk through all resulting parse trees and act on them. 17 | - To build this you provide a function that given a terminal produces an AST node. 18 | - Then you define semantic actions to evaluate how to interpret each rule in the grammar. 19 | 20 | ## Example 21 | 22 | A toy parser that can understand sums. 23 | 24 | ```rust 25 | fn main() { 26 | // Gramar: S -> S + N | N; N -> [0-9]; 27 | let g = earlgrey::GrammarBuilder::default() 28 | .nonterm("S") 29 | .nonterm("N") 30 | .terminal("[+]", |c| c == "+") 31 | .terminal("[0-9]", |n| "1234567890".contains(n)) 32 | .rule("S", &["S", "[+]", "N"]) 33 | .rule("S", &["N"]) 34 | .rule("N", &["[0-9]"]) 35 | .into_grammar("S") 36 | .unwrap(); 37 | 38 | // Parse some sum 39 | let input = "1 + 2 + 3".split_whitespace(); 40 | let trees = earlgrey::EarleyParser::new(g) 41 | .parse(input) 42 | .unwrap(); 43 | 44 | // Evaluate the results 45 | // Describe what to do when we find a Terminal 46 | let mut ev = earlgrey::EarleyForest::new( 47 | |symbol, token| match symbol { 48 | "[0-9]" => token.parse().unwrap(), 49 | _ => 0.0, 50 | }); 51 | 52 | // Describe how to execute grammar rules 53 | ev.action("S -> S [+] N", |n| n[0] + n[2]); 54 | ev.action("S -> N", |n| n[0]); 55 | ev.action("N -> [0-9]", |n| n[0]); 56 | 57 | println!("{}", ev.eval(&trees).unwrap()); 58 | } 59 | ``` 60 | 61 | 62 | #### References for Earley's algorithm 63 | * http://loup-vaillant.fr/tutorials/earley-parsing/ 64 | * https://user.phil-fak.uni-duesseldorf.de/~kallmeyer/Parsing/earley.pdf 65 | * http://joshuagrams.github.io/pep/ 66 | * https://github.com/tomerfiliba/tau/blob/master/earley3.py 67 | -------------------------------------------------------------------------------- /earlgrey/src/earley/mod.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod grammar; 4 | pub use grammar::{Grammar, GrammarBuilder}; 5 | 6 | mod parser; 7 | mod spans; 8 | pub use parser::EarleyParser; 9 | 10 | mod trees; 11 | pub use trees::EarleyForest; 12 | 13 | #[cfg(test)] 14 | mod parser_test; 15 | -------------------------------------------------------------------------------- /earlgrey/src/ebnf_tokenizer.rs: -------------------------------------------------------------------------------- 1 | pub struct EbnfTokenizer> { 2 | input: std::iter::Peekable, 3 | buff: Vec, 4 | } 5 | 6 | impl> EbnfTokenizer { 7 | pub fn new(input: I) -> Self { 8 | Self { 9 | input: input.peekable(), 10 | buff: Vec::new(), 11 | } 12 | } 13 | 14 | fn next_result(&mut self) -> Result, String> { 15 | if !self.buff.is_empty() { 16 | return Ok(Some(self.buff.remove(0))); 17 | } 18 | match self.input.next() { 19 | // Various single char tokens. 20 | Some(x) if "[]{}()|;".contains(x) => Ok(Some(x.to_string())), 21 | // Assignment operator. 22 | Some(':') => match self.input.next() { 23 | Some('=') => Ok(Some(":=".to_string())), 24 | _ => Err("Incomplete := operator".to_string()), 25 | }, 26 | // Tokenize Strings checking for escapes. 27 | Some(open) if open == '"' || open == '\'' => { 28 | self.buff.push(open.to_string()); 29 | let mut quoted_string = String::new(); 30 | let mut escaped = false; 31 | while let Some(ch) = self.input.next() { 32 | // Swallow escape char '\' and prevent string closure 33 | if !escaped && ch == '\\' { 34 | escaped = true; 35 | continue; 36 | } 37 | if escaped { 38 | quoted_string.push('\\') 39 | } 40 | // Found close token. Check it wasn't escaped. 41 | if !escaped && open == ch { 42 | self.buff.push(quoted_string); 43 | self.buff.push(ch.to_string()); 44 | return self.next_result(); 45 | } 46 | quoted_string.push(ch); 47 | escaped = false; 48 | } 49 | Err("Unfinished string missing close quote".to_string()) 50 | } 51 | // Swallow comments until EOL. 52 | Some('#') => { 53 | while let Some(nl) = self.input.next() { 54 | if nl == '\n' { 55 | return self.next_result(); 56 | } 57 | } 58 | Err("Unfinished comment missing EOL".to_string()) 59 | } 60 | // Tags (starts with '@') and identifiers. 61 | Some(x) if x.is_ascii_alphabetic() || x == '@' || x == '_' => { 62 | let mut id = x.to_string(); 63 | while let Some(ch) = self.input.peek() { 64 | if !ch.is_ascii_alphanumeric() && *ch != '_' { 65 | break; 66 | } 67 | id.push(self.input.next().unwrap()); 68 | } 69 | Ok(Some(id)) 70 | } 71 | // Swallow whitespace. 72 | Some(x) if x.is_whitespace() => { 73 | while let Some(ws) = self.input.peek() { 74 | if !ws.is_whitespace() { 75 | break; 76 | } 77 | self.input.next(); // consume whitespace 78 | } 79 | self.next_result() 80 | } 81 | Some(ch) => Err(format!("Unexpected char: {}", ch)), 82 | None => Ok(None), 83 | } 84 | } 85 | } 86 | 87 | impl> Iterator for EbnfTokenizer { 88 | type Item = String; 89 | 90 | fn next(&mut self) -> Option { 91 | match self.next_result() { 92 | Err(e) => { 93 | eprintln!("EbnfTokenizer error: {}", e); 94 | None 95 | } 96 | Ok(v) => v, 97 | } 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::EbnfTokenizer; 104 | 105 | #[test] 106 | fn simple() { 107 | let input = r#" 108 | "hello" world @tag | [ foo ]; 109 | {x} # comment 110 | a:=(y) 111 | "escapedstring\"" 112 | "#; 113 | let expected = vec![ 114 | "\"", 115 | "hello", 116 | "\"", 117 | "world", 118 | "@tag", 119 | "|", 120 | "[", 121 | "foo", 122 | "]", 123 | ";", 124 | "{", 125 | "x", 126 | "}", 127 | "a", 128 | ":=", 129 | "(", 130 | "y", 131 | ")", 132 | "\"", 133 | "escapedstring\\\"", 134 | "\"", 135 | ]; 136 | for (idx, token) in EbnfTokenizer::new(input.chars()).enumerate() { 137 | assert_eq!(token, expected[idx]); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /earlgrey/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod earley; 4 | pub use earley::{EarleyForest, EarleyParser, Grammar, GrammarBuilder}; 5 | 6 | mod ebnf; 7 | mod ebnf_tokenizer; 8 | pub use ebnf::EbnfGrammarParser; 9 | 10 | mod parsers; 11 | pub use parsers::{sexpr_parser, Sexpr}; 12 | 13 | #[cfg(test)] 14 | mod ebnf_test; 15 | -------------------------------------------------------------------------------- /earlgrey/src/parsers.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::earley::{EarleyForest, EarleyParser, Grammar}; 4 | use std::fmt::Debug; 5 | 6 | #[derive(Clone, Debug)] 7 | pub enum Sexpr { 8 | Atom(String), 9 | List(Vec), 10 | } 11 | 12 | impl Sexpr { 13 | pub fn print(&self) -> String { 14 | let mut out = String::new(); 15 | self.print_helper("", &mut out); 16 | out 17 | } 18 | 19 | fn print_helper(&self, indent: &str, out: &mut String) { 20 | match *self { 21 | Sexpr::Atom(ref lexeme) => *out += &format!("\u{2500} {}\n", lexeme), 22 | Sexpr::List(ref subn) => { 23 | if let Some((first, rest)) = subn.split_first() { 24 | if let Some((last, rest)) = rest.split_last() { 25 | *out += "\u{252c}"; 26 | first.print_helper(&format!("{}\u{2502}", indent), out); 27 | for mid in rest { 28 | *out += &format!("{}\u{251c}", indent); 29 | mid.print_helper(&format!("{}\u{2502}", indent), out); 30 | } 31 | *out += &format!("{}\u{2570}", indent); 32 | last.print_helper(&format!("{} ", indent), out); 33 | } else { 34 | *out += "\u{2500} \u{03b5}\n"; 35 | } 36 | } else { 37 | *out += "\u{2500} \u{03b5}\n"; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | pub fn sexpr_parser( 45 | grammar: Grammar, 46 | ) -> Result Result, String>, String> 47 | where 48 | InputIter: Iterator, 49 | InputIter::Item: AsRef + std::fmt::Debug, 50 | { 51 | let mut tree_builder = EarleyForest::new(|_, tok| Sexpr::Atom(tok.to_string())); 52 | 53 | for rule in &grammar.rules { 54 | tree_builder.action(&rule.to_string(), move |mut nodes| match nodes.len() { 55 | 1 => nodes.swap_remove(0), 56 | _ => Sexpr::List(nodes), 57 | }); 58 | } 59 | 60 | let parser = EarleyParser::new(grammar); 61 | Ok(move |tokenizer| tree_builder.eval_all(&parser.parse(tokenizer)?)) 62 | } 63 | -------------------------------------------------------------------------------- /fluxcap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fluxcap" 3 | version = "0.1.2" 4 | edition = "2021" 5 | authors = ["Rodolfo Granata "] 6 | description = "time expression parsing inspired on duckling" 7 | license = "MIT" 8 | repository = "https://github.com/rodolf0/tox" 9 | 10 | [dependencies] 11 | chrono = "0.4" 12 | earlgrey = { version = "0.4", path = "../earlgrey" } 13 | kronos = { version = "0.1", path = "../kronos" } 14 | -------------------------------------------------------------------------------- /fluxcap/pcfgs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/fluxcap/pcfgs.pdf -------------------------------------------------------------------------------- /fluxcap/src/constants.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | pub fn weekday(d: &str) -> Option { 4 | match d { 5 | "sunday" | "sundays" | "sun" => Some(0), 6 | "monday" | "mondays" | "mon" => Some(1), 7 | "tuesday" | "tuesdays" | "tue" => Some(2), 8 | "wednesday" | "wednesdays" | "wed" => Some(3), 9 | "thursday" | "thursdays" | "thu" => Some(4), 10 | "friday" | "fridays" | "fri" => Some(5), 11 | "saturday" | "saturdays" | "sat" => Some(6), 12 | _ => None 13 | } 14 | } 15 | 16 | pub fn month(m: &str) -> Option { 17 | match m { 18 | "january" | "jan" => Some(1), 19 | "february" | "feb" => Some(2), 20 | "march" | "mar" => Some(3), 21 | "april" | "apr" => Some(4), 22 | "may" => Some(5), 23 | "june" | "jun" => Some(6), 24 | "july" | "jul" => Some(7), 25 | "august" | "aug" => Some(8), 26 | "september" | "sep" => Some(9), 27 | "october" | "oct" => Some(10), 28 | "november" | "nov" => Some(11), 29 | "december" | "dec" => Some(12), 30 | _ => None 31 | } 32 | } 33 | 34 | pub fn ordinal(n: &str) -> Option { 35 | static ORD: [&str;31] = [ 36 | "first", "second", "third", "fourth", "fifth", "sixth", "seventh", 37 | "eigth", "ninth", "thenth", "eleventh", "twelveth", "thirteenth", 38 | "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", 39 | "nineteenth", "twentieth", "twenty-first", "twenty-second", 40 | "twenty-third", "twenty-fourth", "twenty-fith", "twenty-sixth", 41 | "twenty-seventh", "twenty-eigth", "twenty-ninth", "thirtieth", 42 | "thirty-first", 43 | ]; 44 | ORD.iter() 45 | .enumerate() 46 | .filter_map(|(i, txt)| if *txt == n { Some((i+1) as u32) } else { None }) 47 | .next() 48 | } 49 | 50 | pub fn short_ordinal(n: &str) -> Option { 51 | use std::str::FromStr; 52 | let num = n.chars().take_while(|d| d.is_numeric()).collect::(); 53 | match &n[num.len()..] { 54 | "st"|"nd"|"rd"|"th" => u32::from_str(&num).ok(), 55 | _ => None 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::{ordinal, short_ordinal}; 62 | #[test] 63 | fn test_short_ordinal() { 64 | assert_eq!(short_ordinal("22nd"), Some(22)); 65 | assert_eq!(short_ordinal("43rd"), Some(43)); 66 | assert_eq!(short_ordinal("5ht"), None); 67 | } 68 | #[test] 69 | fn test_ordinal() { 70 | assert_eq!(ordinal("twenty-fourth"), Some(24)); 71 | assert_eq!(ordinal("twelveth"), Some(12)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /fluxcap/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod constants; 4 | mod time_parser; 5 | pub use time_parser::time_grammar; 6 | pub use time_parser::debug_time_expression; 7 | 8 | mod time_semantics; 9 | pub use time_semantics::{TimeMachine, TimeEl}; 10 | 11 | #[cfg(test)] 12 | mod time_test; 13 | -------------------------------------------------------------------------------- /fluxcap/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | extern crate fluxcap; 3 | extern crate kronos; 4 | 5 | use std::io; 6 | 7 | fn main() -> Result<(), String> { 8 | let input = if std::env::args().len() <= 1 { 9 | let mut buffer = String::new(); 10 | io::stdin().read_line(&mut buffer).ok(); 11 | buffer.pop(); 12 | buffer 13 | } else { 14 | std::env::args() 15 | .skip(1) 16 | .filter(|arg| arg != "-v") 17 | .collect::>() 18 | .join(" ") 19 | }; 20 | 21 | let reftime = chrono::Local::now().naive_local(); 22 | let tm = fluxcap::TimeMachine::new(reftime); 23 | 24 | fn fmt(grain: kronos::Grain) -> &'static str { 25 | use kronos::Grain::*; 26 | match grain { 27 | Second => "%A, %e %B %Y %H:%M:%S", 28 | Minute => "%A, %e %B %Y %H:%M", 29 | Hour => "%A, %e %B %Y %Hhs", 30 | Day | Week => "%A, %e %B %Y", 31 | Month | Quarter | Half => "%B %Y", 32 | Year | Lustrum | Decade | Century | Millenium => "%Y", 33 | } 34 | } 35 | 36 | for r in tm.eval(&input)? { 37 | match r { 38 | fluxcap::TimeEl::Time(ref r) if r.grain <= kronos::Grain::Day => 39 | println!("({:?}) {}", r.grain, r.start.format(fmt(r.grain))), 40 | fluxcap::TimeEl::Time(ref r) => 41 | println!("({:?}) {} - {}", r.grain, 42 | r.start.format(fmt(r.grain)), 43 | r.end.format(fmt(r.grain))), 44 | _ => println!("{:?}", r), 45 | } 46 | } 47 | 48 | let verbose = std::env::args().any(|arg| arg == "-v"); 49 | if verbose { 50 | match fluxcap::debug_time_expression(&input) { 51 | Err(error) => eprintln!("{}", error), 52 | Ok(trees) => for t in trees { 53 | println!("{}", t.print()); 54 | } 55 | } 56 | } 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /fluxcap/src/time_parser.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | // https://github.com/wit-ai/duckling_old/blob/master/resources/languages/en/corpus/time.clj 4 | // https://github.com/wit-ai/duckling_old/blob/master/resources/languages/en/rules/time.clj 5 | 6 | pub fn time_grammar() -> &'static str { 7 | r#" 8 | named_seq := day_ordinal 9 | | weekday 10 | | month 11 | | day_ordinal 'of' month 12 | | month day_ordinal 13 | | weekday day_ordinal 14 | | weekday day_ordinal 'of' month 15 | | weekday month day_ordinal 16 | | 'weekend' | 'weekends' 17 | | year 18 | ; 19 | 20 | sequence := named_seq | grain; 21 | 22 | comp_seq := ordinal sequence 'of' ['the'] @opt_the comp_seq 23 | | 'last' sequence 'of' ['the'] @opt_the comp_seq 24 | | sequence 25 | ; 26 | 27 | comp_grain := small_int grain 28 | | 'a' grain 29 | | comp_grain 'and' small_int grain 30 | | comp_grain 'and' 'a' grain 31 | ; 32 | 33 | time := 'today' 34 | | 'tomorrow' 35 | | 'yesterday' 36 | | 'on' weekday 37 | | named_seq 38 | 39 | | 'the' comp_seq 40 | | 'this' comp_seq 41 | | 'next' comp_seq 42 | | 'last' comp_seq 43 | 44 | | comp_seq 'after' 'next' 45 | | comp_seq 'before' 'last' 46 | 47 | | 'a' named_seq 'ago' 48 | | small_int named_seq 'ago' 49 | | 'in' small_int named_seq 50 | 51 | | comp_grain 'ago' 52 | | 'in' comp_grain 53 | 54 | | month year 55 | | month day_ordinal year 56 | 57 | | comp_grain 'after' time 58 | | comp_grain 'before' time 59 | 60 | | sequence 'until' time 61 | | sequence 'since' time 62 | | sequence 'between' time 'and' time 63 | ; 64 | "# 65 | } 66 | 67 | fn _grammar() -> Result { 68 | use std::str::FromStr; 69 | use crate::constants::*; 70 | earlgrey::EbnfGrammarParser::new(time_grammar(), "time") 71 | .plug_terminal("ordinal", |d| ordinal(d).or_else(|| short_ordinal(d)).is_some()) 72 | .plug_terminal("day_ordinal", |d| ordinal(d).or_else(|| short_ordinal(d)).is_some()) 73 | .plug_terminal("weekday", |d| weekday(d).is_some()) 74 | .plug_terminal("month", |d| month(d).is_some()) 75 | .plug_terminal("grain", |g| kronos::Grain::from_str(g).is_ok()) 76 | .plug_terminal("year", |y| if let Ok(year) = i32::from_str(y) 77 | { year > 999 && year < 2200 } else { false }) 78 | .plug_terminal("small_int", |u| if let Ok(u) = usize::from_str(u) 79 | { u < 100 } else { false }) 80 | .into_grammar() 81 | } 82 | 83 | pub fn time_parser() -> earlgrey::EarleyParser { 84 | earlgrey::EarleyParser::new( 85 | _grammar() 86 | .unwrap_or_else(|e| panic!("TimeMachine grammar BUG: {:?}", e)) 87 | ) 88 | } 89 | 90 | pub fn debug_time_expression(time: &str) -> Result, String> { 91 | let parser = earlgrey::sexpr_parser( 92 | _grammar() 93 | .unwrap_or_else(|e| panic!("TimeMachine grammar BUG: {:?}", e)) 94 | )?; 95 | parser(time.split(&[' ', ','][..]).filter(|w| !w.is_empty())) 96 | } 97 | -------------------------------------------------------------------------------- /fluxcap/src/time_test.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | type DateTime = chrono::NaiveDateTime; 4 | 5 | use crate::time_semantics::{TimeEl, TimeMachine}; 6 | use kronos::Grain as g; 7 | 8 | fn d(year: i32, month: u32, day: u32) -> DateTime { 9 | use chrono::naive::NaiveDate as Date; 10 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 11 | } 12 | 13 | fn r(s: DateTime, e: DateTime, gr: kronos::Grain) -> Vec { 14 | vec![TimeEl::Time(kronos::Range { 15 | start: s, 16 | end: e, 17 | grain: gr, 18 | })] 19 | } 20 | 21 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 22 | chrono::NaiveDate::from_ymd(year, month, day).and_hms(h, m, s) 23 | } 24 | 25 | #[test] 26 | fn t_thisnext() -> Result<(), String> { 27 | let tm = TimeMachine::new(d(2016, 9, 5)); 28 | assert_eq!(tm.eval("next monday")?, r(d(2016, 9, 12), d(2016, 9, 13), g::Day)); 29 | assert_eq!(tm.eval("this monday")?, r(d(2016, 9, 5), d(2016, 9, 6), g::Day)); 30 | assert_eq!(tm.eval("next march")?, r(d(2017, 3, 1), d(2017, 4, 1), g::Month)); 31 | assert_eq!(tm.eval("this march")?, r(d(2017, 3, 1), d(2017, 4, 1), g::Month)); 32 | assert_eq!(tm.eval("next month")?, r(d(2016, 10, 1), d(2016, 11, 1), g::Month)); 33 | assert_eq!(tm.eval("tue after next")?, r(d(2016, 9, 13), d(2016, 9, 14), g::Day)); 34 | 35 | let tm = TimeMachine::new(d(2016, 3, 5)); 36 | assert_eq!(tm.eval("this march")?, r(d(2016, 3, 1), d(2016, 4, 1), g::Month)); 37 | assert_eq!(tm.eval("next year")?, r(d(2017, 1, 1), d(2018, 1, 1), g::Year)); 38 | assert_eq!(tm.eval("next week")?, r(d(2016, 3, 6), d(2016, 3, 13), g::Week)); 39 | Ok(()) 40 | } 41 | 42 | #[test] 43 | fn t_direct() -> Result<(), String> { 44 | let tm = TimeMachine::new(d(2016, 9, 5)); 45 | assert_eq!(tm.eval("2002")?, r(d(2002, 1, 1), d(2003, 1, 1), g::Year)); 46 | assert_eq!(tm.eval("the 12th")?, r(d(2016, 9, 12), d(2016, 9, 13), g::Day)); 47 | 48 | let tm = TimeMachine::new(d(2016, 10, 26)); 49 | assert_eq!(tm.eval("monday")?, r(d(2016, 10, 31), d(2016, 11, 1), g::Day)); 50 | assert_eq!(tm.eval("today")?, r(d(2016, 10, 26), d(2016, 10, 27), g::Day)); 51 | assert_eq!(tm.eval("tomorrow")?, r(d(2016, 10, 27), d(2016, 10, 28), g::Day)); 52 | 53 | let tm = TimeMachine::new(d(2016, 9, 12)); 54 | assert_eq!(tm.eval("the 12th")?, r(d(2016, 9, 12), d(2016, 9, 13), g::Day)); 55 | Ok(()) 56 | } 57 | 58 | #[test] 59 | fn t_nthof() -> Result<(), String> { 60 | let tm = TimeMachine::new(d(2016, 9, 5)); 61 | assert_eq!(tm.eval("the 3rd mon of june")?, r(d(2017, 6, 19), d(2017, 6, 20), g::Day)); 62 | assert_eq!(tm.eval("the 3rd day of the month")?, r(d(2016, 9, 3), d(2016, 9, 4), g::Day)); 63 | assert_eq!(tm.eval("the 2nd week of august")?, r(d(2017, 8, 6), d(2017, 8, 13), g::Week)); 64 | assert_eq!(tm.eval("the 3rd day of the 2nd week of may")?, r(d(2017, 5, 9), d(2017, 5, 10), g::Day)); 65 | assert_eq!(tm.eval("2nd week of june after next")?, r(d(2018, 6, 3), d(2018, 6, 10), g::Week)); 66 | assert_eq!(tm.eval("third tuesday of the month after next")?, r(d(2016, 10, 18), d(2016, 10, 19), g::Day)); 67 | 68 | let tm = TimeMachine::new(d(2017, 1, 1)); 69 | assert_eq!(tm.eval("the 8th fri of the year")?, r(d(2017, 2, 24), d(2017, 2, 25), g::Day)); 70 | 71 | let tm = TimeMachine::new(d(2020, 1, 1)); 72 | assert_eq!(tm.eval("the last day of feb")?, r(d(2020, 2, 29), d(2020, 3, 1), g::Day)); 73 | Ok(()) 74 | } 75 | 76 | #[test] 77 | fn t_intersect() -> Result<(), String> { 78 | let tm = TimeMachine::new(d(2016, 9, 5)); 79 | assert_eq!(tm.eval("feb 27th 1984")?, r(d(1984, 2, 27), d(1984, 2, 28), g::Day)); 80 | assert_eq!(tm.eval("mon feb 28th")?, r(d(2022, 2, 28), d(2022, 3, 1), g::Day)); 81 | 82 | let tm = TimeMachine::new(d(2016, 10, 24)); 83 | assert_eq!(tm.eval("friday 18th")?, r(d(2016, 11, 18), d(2016, 11, 19), g::Day)); 84 | assert_eq!(tm.eval("18th of june")?, r(d(2017, 6, 18), d(2017, 6, 19), g::Day)); 85 | assert_eq!(tm.eval("feb 27th")?, r(d(2017, 2, 27), d(2017, 2, 28), g::Day)); 86 | Ok(()) 87 | } 88 | 89 | #[test] 90 | fn t_anchored() -> Result<(), String> { 91 | let tm = TimeMachine::new(d(2016, 9, 5)); 92 | assert_eq!(tm.eval("the 10th week of 1984")?, r(d(1984, 3, 4), d(1984, 3, 11), g::Week)); 93 | assert_eq!(tm.eval("the 2nd day of the 3rd week of 1987")?, r(d(1987, 1, 12), d(1987, 1, 13), g::Day)); 94 | Ok(()) 95 | } 96 | 97 | #[test] 98 | fn t_timediff() -> Result<(), String> { 99 | let tm = TimeMachine::new(d(2016, 9, 5)); 100 | // until 101 | assert_eq!(tm.eval("days until tomorrow")?, vec![TimeEl::Count(1)]); 102 | assert_eq!(tm.eval("months until 2018")?, vec![TimeEl::Count(16)]); 103 | assert_eq!(tm.eval("weeks until dec")?, vec![TimeEl::Count(12)]); 104 | // since 105 | assert_eq!(tm.eval("feb 29th since 2000")?, vec![TimeEl::Count(5)]); 106 | assert_eq!(tm.eval("years since 2000")?, vec![TimeEl::Count(16)]); 107 | assert_eq!(tm.eval("days since sep")?, vec![TimeEl::Count(4)]); 108 | // between 109 | assert_eq!(tm.eval("days between mar and apr")?, vec![TimeEl::Count(31)]); 110 | 111 | let tm = TimeMachine::new(d(2016, 10, 25)); 112 | assert_eq!(tm.eval("mon until nov 14th")?, vec![TimeEl::Count(2)]); 113 | 114 | let tm = TimeMachine::new(d(2023, 3, 13)); 115 | assert_eq!(tm.eval("years since mar 13th 2005")?, vec![TimeEl::Count(18)]); 116 | Ok(()) 117 | } 118 | 119 | #[test] 120 | fn t_shifts() -> Result<(), String> { 121 | let tm = TimeMachine::new(d(2016, 10, 26)); 122 | assert_eq!(tm.eval("2 weeks ago")?, r(d(2016, 10, 12), dttm(2016, 10, 12, 0, 0, 1), g::Second)); 123 | assert_eq!(tm.eval("a week after feb 14th")?, r(d(2017, 2, 21), d(2017, 2, 22), g::Day)); 124 | assert_eq!(tm.eval("a week before feb 28th")?, r(d(2017, 2, 21), d(2017, 2, 22), g::Day)); 125 | assert_eq!(tm.eval("in a year")?, r(d(2017, 10, 26), dttm(2017, 10, 26, 0, 0, 1), g::Second)); 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /fluxcap/time_examples: -------------------------------------------------------------------------------- 1 | - the 1st mon jan 1st of the 1st year of the century 2 | -------------------------------------------------------------------------------- /kronos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kronos" 3 | version = "0.1.5" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Rodolfo Granata "] 7 | description = "A tool to calculate complex time expressions" 8 | repository = "https://github.com/rodolf0/tox/tree/master/kronos" 9 | readme = "README.md" 10 | keywords = ["time", "date", "calendar", "chrono", "sequence"] 11 | categories = ["date-and-time"] 12 | 13 | [dependencies] 14 | chrono = "=0.4.22" 15 | -------------------------------------------------------------------------------- /kronos/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Kronos is a tool for calculating date/times. It is meant to give a concrete date for questions like *"When is the 2nd Monday of June?"*, or *"What was the 3rd to last day-of-the-week of past February?"*. 4 | 5 | To answer these questions kronos composes `TimeSequence` iterators. These are infinite sequences into the past and the future which you can pin to a particular instant and get resulting time `Range`s. 6 | 7 | ## Example 8 | 9 | Lets first define a `TimeSequence` that represents any and all *Monday*s. Then use it to get an iterator of all future Mondays from a specific **t0** instant onward. 10 | ```rust 11 | // Reference time: Tuesday, 5th Feb 2019 12 | let t0 = chrono::NaiveDate::from_ymd(2019, 2, 5) 13 | .and_hms(0, 0, 0); 14 | 15 | // A sequence for *Mondays* 16 | let mondays = kronos::Weekday(1); 17 | 18 | // First Monday after t0 reference time 19 | mondays.future(&t0).next() 20 | ``` 21 | 22 | ## Ranges 23 | 24 | The previous example would return a `Range` which represents an open-ended time interval `[start, end)`. Ranges also have a `Grain` that specifies the resolution of the start and end instants. 25 | 26 | Examples of a Range could be Aug 26th 2018. 27 | - It has a resolution of Grain::Day, 28 | - starts on 2018/08/26 00:00:00, 29 | - ends on 2018/08/27 00:00:00 30 | 31 | ## Composing `TimeSequence`s 32 | 33 | ### Basic sequences 34 | 35 | Some simple TimeSequences can be built almost out of thin air. For example: 36 | - `Weekday(2)` a sequence for Tuesdays. 37 | - `Month(6)` a sequence for all June months. 38 | - `Grains(Grain::Day)` a sequence to iterate over days. 39 | 40 | ### Composite sequences 41 | 42 | More comples TimeSequences can be created by combining other sequences. For example: 43 | - `NthOf(2, Weekday(1), Month(6))` creates a sequence for "the second Mondays of June". 44 | - `LastOf(1, Weekend, Grains(Grain::Year))` for "last weekend of the year". 45 | - `Intersect(Weekday(1), NthOf(28, Grains(Grain::Day), Grains(Grains::Month)))` for "all Monday 28th". 46 | 47 | Other compositions allow unions, intersections, intervals, exceptions, etc. Please check each module's tests for [examples](https://github.com/rodolf0/tox/tree/master/kronos/src) on how to use them. 48 | 49 | 50 | #### References 51 | * http://homes.cs.washington.edu/~kentonl/pub/ladz-acl.2014.pdf 52 | - https://github.com/wit-ai/duckling_old/blob/6b7e2e1bdbd50299cee4075ff48d7323c05758bc/src/duckling/time/pred.clj#L57-L72 53 | - https://github.com/wit-ai/duckling_old/blob/6b7e2e1bdbd50299cee4075ff48d7323c05758bc/src/duckling/time/pred.clj#L333 54 | -------------------------------------------------------------------------------- /kronos/ladz-acl.2014.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodolf0/tox/00b39db6e88e6fcc1e6c3d394b9f9d56423a763e/kronos/ladz-acl.2014.pdf -------------------------------------------------------------------------------- /kronos/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod types; 4 | pub use crate::types::{Grain, TimeSequence, Range, Season}; 5 | 6 | mod utils; 7 | 8 | mod seq_named; 9 | pub use crate::seq_named::{Weekday, Month, Weekend, Year}; 10 | 11 | mod seq_grain; 12 | pub use crate::seq_grain::Grains; 13 | 14 | mod seq_nthof; 15 | pub use crate::seq_nthof::NthOf; 16 | 17 | mod seq_lastof; 18 | pub use crate::seq_lastof::LastOf; 19 | 20 | mod seq_union; 21 | pub use crate::seq_union::Union; 22 | 23 | mod seq_intersect; 24 | pub use crate::seq_intersect::Intersect; 25 | 26 | mod seq_except; 27 | pub use crate::seq_except::Except; 28 | 29 | mod seq_interval; 30 | pub use crate::seq_interval::Interval; 31 | 32 | mod seq_seasons; 33 | pub use crate::seq_seasons::Seasons; 34 | 35 | mod seq_mgrain; 36 | pub use crate::seq_mgrain::MGrain; 37 | 38 | mod seq_func; 39 | pub use crate::seq_func::{Map, shift, step_by}; 40 | 41 | mod seq_shim; 42 | pub use crate::seq_shim::Shim; 43 | 44 | #[cfg(test)] 45 | mod mixed_tests; 46 | -------------------------------------------------------------------------------- /kronos/src/mixed_tests.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Date, Grain, Range, TimeSequence}; 4 | 5 | use crate::seq_nthof::*; 6 | use crate::seq_intersect::*; 7 | use crate::seq_grain::*; 8 | use crate::seq_named::*; 9 | use crate::seq_func::*; 10 | 11 | 12 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 13 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 14 | } 15 | 16 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 17 | Date::from_ymd(year, month, day).and_hms(h, m, s) 18 | } 19 | 20 | 21 | #[test] 22 | fn test_multi() { 23 | // 3 days after mon feb 28th 24 | let seq = NthOf(28, Grains(Grain::Day), Grains(Grain::Month)); 25 | let seq = Intersect(seq, Weekday(1)); 26 | let seq = Intersect(seq, Month(2)); 27 | let seq3dafter = shift(seq.clone(), Grain::Day, 3); 28 | 29 | let mut iter = seq3dafter.future(&dt(2021, 9, 5)); 30 | assert_eq!(iter.next().unwrap(), 31 | Range{start: dt(2022, 3, 3), end: dt(2022, 3, 4), grain: Grain::Day}); 32 | assert_eq!(iter.next().unwrap(), 33 | Range{start: dt(2028, 3, 2), end: dt(2028, 3, 3), grain: Grain::Day}); 34 | 35 | // backward: 3 days after monday feb 28th 36 | let mut iter = seq3dafter.past(&dt(2021, 9, 5)); 37 | assert_eq!(iter.next().unwrap(), 38 | Range{start: dt(2011, 3, 3), end: dt(2011, 3, 4), grain: Grain::Day}); 39 | assert_eq!(iter.next().unwrap(), 40 | Range{start: dt(2005, 3, 3), end: dt(2005, 3, 4), grain: Grain::Day}); 41 | 42 | // edge cases, first end-of-range <= reftime 43 | let mut iter = seq.past(&dttm(2022, 2, 28, 1, 0, 0)); 44 | assert_eq!(iter.next().unwrap(), 45 | Range{start: dt(2011, 2, 28), end: dt(2011, 3, 1), grain: Grain::Day}); 46 | 47 | let mut iter = seq.past(&dttm(2028, 2, 29, 0, 0, 0)); 48 | assert_eq!(iter.next().unwrap(), 49 | Range{start: dt(2028, 2, 28), end: dt(2028, 2, 29), grain: Grain::Day}); 50 | assert_eq!(iter.next().unwrap(), 51 | Range{start: dt(2022, 2, 28), end: dt(2022, 3, 1), grain: Grain::Day}); 52 | } 53 | 54 | #[test] 55 | fn test_every_nmonths_from_offset() { 56 | // Ref: https://github.com/rodolf0/tox/pull/8/commits 57 | let t0_april = dt(2018, 4, 3); 58 | 59 | // Filtering once the iterator has been triggered 60 | let mut every_3months_iter = Grains(Grain::Month) 61 | .future(&t0_april) 62 | .step_by(3); 63 | 64 | assert_eq!(every_3months_iter.next().unwrap(), Range{ 65 | start: dt(2018, 4, 1), end: dt(2018, 5, 1), grain: Grain::Month}); 66 | 67 | // Specifying the template 68 | let seq = step_by(Grains(Grain::Month), 3); 69 | assert_eq!(seq.future(&t0_april).next().unwrap(), Range{ 70 | start: dt(2018, 4, 1), end: dt(2018, 5, 1), grain: Grain::Month}); 71 | 72 | // Every n-months but using an offset 73 | use chrono::Datelike; 74 | let mut every_3months_from_next_march_iter = Grains(Grain::Month) 75 | .future(&t0_april) 76 | .skip_while(|r| r.start.date().month() != 3) 77 | .step_by(3); 78 | assert_eq!(every_3months_from_next_march_iter.next().unwrap(), Range{ 79 | start: dt(2019, 3, 1), end: dt(2019, 4, 1), grain: Grain::Month}); 80 | } 81 | -------------------------------------------------------------------------------- /kronos/src/seq_except.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Range, TimeSequence}; 4 | 5 | // |------a------| 6 | // |------b------| 7 | // 8 | // |----a----| 9 | // |-----b-----| 10 | // 11 | // |---------a---------| 12 | // |-----b-----| 13 | // 14 | // Exapmles: 15 | // - Everyday except Fridays 16 | // - Mondays except March 17 | // - March except mondays (never happens: a march without mondays) 18 | 19 | #[derive(Clone)] 20 | pub struct Except(pub SeqA, pub SeqB) 21 | where SeqA: TimeSequence, 22 | SeqB: TimeSequence; 23 | 24 | impl Except 25 | where SeqA: TimeSequence, 26 | SeqB: TimeSequence 27 | { 28 | fn _base(&self, t0: &DateTime, future: bool) -> Box + '_> { 29 | let (stream, mut except) = if future { 30 | (self.0._future_raw(t0), self.1._future_raw(t0)) 31 | } else { 32 | (self.0._past_raw(t0), self.1._past_raw(t0)) 33 | }; 34 | let mut nexcept = except.next().unwrap(); 35 | Box::new(stream.filter(move |range| { 36 | // advance exception filter up to current range 37 | while (nexcept.end < range.start && future) || 38 | (nexcept.start >= range.end && !future) { 39 | nexcept = except.next().unwrap(); 40 | } 41 | range.intersect(&nexcept).is_none() 42 | })) 43 | } 44 | } 45 | 46 | impl TimeSequence for Except 47 | where SeqA: TimeSequence, 48 | SeqB: TimeSequence 49 | { 50 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 51 | self._base(t0, true) 52 | } 53 | 54 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 55 | self._base(t0, false) 56 | } 57 | } 58 | 59 | 60 | #[cfg(test)] 61 | mod test { 62 | use super::*; 63 | use crate::types::{Date, Grain}; 64 | use crate::seq_grain::Grains; 65 | use crate::seq_named::{Weekday, Month}; 66 | 67 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 68 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 69 | } 70 | 71 | #[test] 72 | fn except_basic() { 73 | // days except Friday and thursdays 74 | let except = Except(Except(Grains(Grain::Day), Weekday(5)), Weekday(4)); 75 | let mut fut = except.future(&dt(2018, 8, 22)); 76 | assert_eq!(fut.next().unwrap(), 77 | Range{start: dt(2018, 8, 22), end: dt(2018, 8, 23), grain: Grain::Day}); 78 | assert_eq!(fut.next().unwrap(), 79 | Range{start: dt(2018, 8, 25), end: dt(2018, 8, 26), grain: Grain::Day}); 80 | assert_eq!(fut.next().unwrap(), 81 | Range{start: dt(2018, 8, 26), end: dt(2018, 8, 27), grain: Grain::Day}); 82 | 83 | let mut past = except.past(&dt(2018, 8, 19)); 84 | assert_eq!(past.next().unwrap(), 85 | Range{start: dt(2018, 8, 18), end: dt(2018, 8, 19), grain: Grain::Day}); 86 | assert_eq!(past.next().unwrap(), 87 | Range{start: dt(2018, 8, 15), end: dt(2018, 8, 16), grain: Grain::Day}); 88 | 89 | let mut past = except.past(&dt(2018, 8, 17)); 90 | assert_eq!(past.next().unwrap(), 91 | Range{start: dt(2018, 8, 15), end: dt(2018, 8, 16), grain: Grain::Day}); 92 | } 93 | 94 | 95 | #[test] 96 | fn except_diff_grains() { 97 | // mondays except september 98 | let except = Except(Weekday(1), Month(9)); 99 | let mut fut = except.future(&dt(2018, 8, 22)); 100 | assert_eq!(fut.next().unwrap(), 101 | Range{start: dt(2018, 8, 27), end: dt(2018, 8, 28), grain: Grain::Day}); 102 | assert_eq!(fut.next().unwrap(), 103 | Range{start: dt(2018, 10, 1), end: dt(2018, 10, 2), grain: Grain::Day}); 104 | 105 | // mondays except August - past 106 | let except = Except(Weekday(1), Month(8)); 107 | let mut past = except.past(&dt(2018, 8, 22)); 108 | assert_eq!(past.next().unwrap(), 109 | Range{start: dt(2018, 7, 30), end: dt(2018, 7, 31), grain: Grain::Day}); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /kronos/src/seq_func.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Range, Grain, TimeSequence}; 4 | 5 | #[derive(Clone)] 6 | pub struct Map(pub Seq, pub RangeMapper) 7 | where Seq: TimeSequence, 8 | RangeMapper: FnMut(Range)->Option + Clone; 9 | 10 | impl TimeSequence for Map 11 | where Seq: TimeSequence, 12 | RangeMapper: FnMut(Range)->Option + Clone, 13 | 14 | { 15 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 16 | let mut f = self.1.clone(); 17 | Box::new(self.0._future_raw(t0).filter_map(move |x| f(x))) 18 | } 19 | 20 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 21 | let mut f = self.1.clone(); 22 | Box::new(self.0._past_raw(t0).filter_map(move |x| f(x))) 23 | } 24 | } 25 | 26 | 27 | pub fn shift(seq: Seq, grain: Grain, n: i32) -> impl TimeSequence 28 | where Seq: TimeSequence 29 | { 30 | use crate::utils; 31 | Map(seq, move |x| Some(Range{ 32 | start: utils::shift_datetime(x.start, grain, n), 33 | end: utils::shift_datetime(x.end, grain, n), 34 | grain: x.grain})) 35 | } 36 | 37 | 38 | pub fn step_by(seq: Seq, n: usize) -> impl TimeSequence 39 | where Seq: TimeSequence 40 | { 41 | let mut counter = 0; 42 | Map(seq, move |x| { 43 | counter += 1; 44 | if (counter - 1) % n == 0 { 45 | Some(x) 46 | } else { 47 | None 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /kronos/src/seq_grain.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::utils; 4 | use crate::types::{DateTime, Range, Grain, TimeSequence}; 5 | 6 | 7 | #[derive(Clone)] 8 | pub struct Grains(pub Grain); 9 | 10 | impl Grains { 11 | fn _base(&self, t0: &DateTime, future: bool) -> Box> { 12 | let base = utils::truncate(*t0, self.0); 13 | let sign = if future { 1 } else { -1 }; 14 | let grain = self.0; 15 | Box::new((0..).map(move |x| Range{ 16 | start: utils::shift_datetime(base, grain, sign * x), 17 | end: utils::shift_datetime(base, grain, sign * x + 1), 18 | grain 19 | })) 20 | } 21 | } 22 | 23 | impl TimeSequence for Grains { 24 | fn _future_raw(&self, t0: &DateTime) -> Box> { 25 | self._base(t0, true) 26 | } 27 | 28 | fn _past_raw(&self, t0: &DateTime) -> Box> { 29 | self._base(t0, false) 30 | } 31 | } 32 | 33 | 34 | #[cfg(test)] 35 | mod test { 36 | use super::*; 37 | use crate::types::{Date, Grain}; 38 | 39 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 40 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 41 | } 42 | 43 | #[test] 44 | fn grain_basic() { 45 | let t0_27feb = dt(2015, 2, 27); 46 | let t0_1jan = dt(2016, 1, 1); 47 | 48 | let mut days = Grains(Grain::Day).future(&t0_27feb); 49 | assert_eq!(days.next().unwrap(), 50 | Range{start: dt(2015, 2, 27), end: dt(2015, 2, 28), grain: Grain::Day}); 51 | assert_eq!(days.next().unwrap(), 52 | Range{start: dt(2015, 2, 28), end: dt(2015, 3, 1), grain: Grain::Day}); 53 | 54 | // check "future" englobes date 55 | let mut weeks = Grains(Grain::Week).future(&t0_1jan); 56 | assert_eq!(weeks.next().unwrap(), 57 | Range{start: dt(2015, 12, 27), end: dt(2016, 1, 3), grain: Grain::Week}); 58 | assert_eq!(weeks.next().unwrap(), 59 | Range{start: dt(2016, 1, 3), end: dt(2016, 1, 10), grain: Grain::Week}); 60 | 61 | let mut months = Grains(Grain::Month).future(&t0_27feb); 62 | assert_eq!(months.next().unwrap(), 63 | Range{start: dt(2015, 2, 1), end: dt(2015, 3, 1), grain: Grain::Month}); 64 | assert_eq!(months.next().unwrap(), 65 | Range{start: dt(2015, 3, 1), end: dt(2015, 4, 1), grain: Grain::Month}); 66 | 67 | // backward iteration 68 | let mut years = Grains(Grain::Year).past(&t0_27feb); 69 | assert_eq!(years.next().unwrap(), 70 | Range{start: dt(2014, 1, 1), end: dt(2015, 1, 1), grain: Grain::Year}); 71 | assert_eq!(years.next().unwrap(), 72 | Range{start: dt(2013, 1, 1), end: dt(2014, 1, 1), grain: Grain::Year}); 73 | // if inclusive, _past_raw renders same year 74 | let mut years = Grains(Grain::Year)._past_raw(&t0_27feb); 75 | assert_eq!(years.next().unwrap(), 76 | Range{start: dt(2015, 1, 1), end: dt(2016, 1, 1), grain: Grain::Year}); 77 | } 78 | 79 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 80 | Date::from_ymd(year, month, day).and_hms(h, m, s) 81 | } 82 | 83 | #[test] 84 | fn smaller_grains() { 85 | let mut minute = Grains(Grain::Minute).future(&dt(2015, 2, 27)); 86 | assert_eq!(minute.next().unwrap(), 87 | Range{start: dttm(2015, 2, 27, 0, 0, 0), 88 | end: dttm(2015, 2, 27, 0, 1, 0), grain: Grain::Minute}); 89 | 90 | let mut min = Grains(Grain::Minute).past(&dttm(2015, 2, 27, 23, 20, 0)); 91 | assert_eq!(min.next().unwrap(), 92 | Range{start: dttm(2015, 2, 27, 23, 19, 0), 93 | end: dttm(2015, 2, 27, 23, 20, 0), grain: Grain::Minute}); 94 | let mut min = 95 | Grains(Grain::Minute)._past_raw(&dttm(2015, 2, 27, 23, 20, 0)); 96 | assert_eq!(min.next().unwrap(), 97 | Range{start: dttm(2015, 2, 27, 23, 20, 0), 98 | end: dttm(2015, 2, 27, 23, 21, 0), grain: Grain::Minute}); 99 | 100 | // non-inclusive past (default) 101 | let mut min = Grains(Grain::Minute).past(&dttm(2015, 2, 27, 23, 20, 25)); 102 | assert_eq!(min.next().unwrap(), 103 | Range{start: dttm(2015, 2, 27, 23, 19, 0), 104 | end: dttm(2015, 2, 27, 23, 20, 0), grain: Grain::Minute}); 105 | // inclusive past 106 | let mut min = 107 | Grains(Grain::Minute)._past_raw(&dttm(2015, 2, 27, 23, 20, 25)); 108 | assert_eq!(min.next().unwrap(), 109 | Range{start: dttm(2015, 2, 27, 23, 20, 0), 110 | end: dttm(2015, 2, 27, 23, 21, 0), grain: Grain::Minute}); 111 | 112 | let mut minute = Grains(Grain::Minute).past(&dt(2015, 2, 27)); 113 | assert_eq!(minute.next().unwrap(), 114 | Range{start: dttm(2015, 2, 26, 23, 59, 0), 115 | end: dttm(2015, 2, 27, 0, 0, 0), grain: Grain::Minute}); 116 | } 117 | 118 | #[test] 119 | fn virtual_grains() { 120 | let mut quarters = Grains(Grain::Quarter).future(&dt(2015, 2, 27)); 121 | assert_eq!(quarters.next().unwrap(), 122 | Range{start: dt(2015, 1, 1), end: dt(2015, 4, 1), grain: Grain::Quarter}); 123 | assert_eq!(quarters.next().unwrap(), 124 | Range{start: dt(2015, 4, 1), end: dt(2015, 7, 1), grain: Grain::Quarter}); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /kronos/src/seq_intersect.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Range, TimeSequence}; 4 | 5 | // Guard against impossible intersections 6 | const INFINITE_FUSE: usize = 1000; 7 | 8 | // Return intersections/overlaps of SeqA with SeqB 9 | // 10 | // |------a------| 11 | // |------b------| 12 | // 13 | // |----a----| 14 | // |-----b-----| 15 | // 16 | // |---------a---------| 17 | // |-----b-----| 18 | // 19 | // Exapmles: 20 | // - Mondays of February 21 | // - Monday 28th 22 | 23 | #[derive(Clone)] 24 | pub struct Intersect(pub SeqA, pub SeqB) 25 | where SeqA: TimeSequence, 26 | SeqB: TimeSequence; 27 | 28 | impl Intersect 29 | where SeqA: TimeSequence, 30 | SeqB: TimeSequence 31 | { 32 | fn _base(&self, t0: &DateTime, future: bool) -> Box + '_> { 33 | let (mut astream, mut bstream) = if future { 34 | (self.0._future_raw(t0), self.1._future_raw(t0)) 35 | } else { 36 | (self.0._past_raw(t0), self.1._past_raw(t0)) 37 | }; 38 | let mut anext = astream.next().unwrap(); 39 | let mut bnext = bstream.next().unwrap(); 40 | Box::new((0..).map(move |_| { 41 | for _ in 0..INFINITE_FUSE { 42 | let overlap = anext.intersect(&bnext); 43 | if (anext.end <= bnext.end && future) || 44 | (anext.start >= bnext.start && !future) { 45 | anext = astream.next().unwrap(); 46 | } else { 47 | bnext = bstream.next().unwrap(); 48 | } 49 | if let Some(overlap) = overlap { 50 | return overlap; 51 | } 52 | } 53 | panic!("Intersect INFINITE_FUSE blown"); 54 | })) 55 | } 56 | } 57 | 58 | impl TimeSequence for Intersect 59 | where SeqA: TimeSequence, 60 | SeqB: TimeSequence 61 | { 62 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 63 | self._base(t0, true) 64 | } 65 | 66 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 67 | self._base(t0, false) 68 | } 69 | } 70 | 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use super::*; 75 | use crate::types::{Date, Grain}; 76 | use crate::seq_named::{Weekday, Month}; 77 | use crate::seq_nthof::NthOf; 78 | use crate::seq_grain::Grains; 79 | use crate::seq_union::Union; 80 | 81 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 82 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 83 | } 84 | 85 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 86 | Date::from_ymd(year, month, day).and_hms(h, m, s) 87 | } 88 | 89 | #[test] 90 | fn intersect_basic() { 91 | // monday 28th 92 | let twenty8th = NthOf(28, Grains(Grain::Day), Grains(Grain::Month)); 93 | let mon28th = Intersect(Weekday(1), twenty8th); 94 | 95 | let mut it_mon28th = mon28th.future(&dt(2016, 2, 25)); 96 | assert_eq!(it_mon28th.next().unwrap(), 97 | Range{start: dt(2016, 3, 28), end: dt(2016, 3, 29), grain: Grain::Day}); 98 | assert_eq!(it_mon28th.next().unwrap(), 99 | Range{start: dt(2016, 11, 28), end: dt(2016, 11, 29), grain: Grain::Day}); 100 | assert_eq!(it_mon28th.next().unwrap(), 101 | Range{start: dt(2017, 8, 28), end: dt(2017, 8, 29), grain: Grain::Day}); 102 | 103 | // backward: monday 28th 104 | let mut it_mon28th = mon28th.past(&dt(2016, 2, 25)); 105 | assert_eq!(it_mon28th.next().unwrap(), 106 | Range{start: dt(2015, 12, 28), end: dt(2015, 12, 29), grain: Grain::Day}); 107 | assert_eq!(it_mon28th.next().unwrap(), 108 | Range{start: dt(2015, 9, 28), end: dt(2015, 9, 29), grain: Grain::Day}); 109 | 110 | // past-non-inclusive and range-end <= t0 .. so can't be 2015-12-28 111 | let mut it_mon28th = mon28th.past(&dttm(2015, 12, 28, 1, 0, 0)); 112 | assert_eq!(it_mon28th.next().unwrap(), 113 | Range{start: dt(2015, 9, 28), end: dt(2015, 9, 29), grain: Grain::Day}); 114 | // past-inclusive, should include 2015-12-28 cause range-start <= t0 < end 115 | let mut it_mon28th = mon28th._past_raw(&dttm(2015, 12, 28, 1, 0, 0)); 116 | assert_eq!(it_mon28th.next().unwrap(), 117 | Range{start: dt(2015, 12, 28), end: dt(2015, 12, 29), grain: Grain::Day}); 118 | } 119 | 120 | #[test] 121 | fn intersect2() { 122 | // tuesdays 3pm 123 | let mut tue3pm = Intersect(Weekday(2), 124 | NthOf(16, Grains(Grain::Hour), Grains(Grain::Day))) 125 | .future(&dt(2016, 2, 25)); 126 | assert_eq!(tue3pm.next().unwrap(), 127 | Range{start: dttm(2016, 3, 1, 15, 0, 0), 128 | end: dttm(2016, 3, 1, 16, 0, 0), grain: Grain::Hour}); 129 | assert_eq!(tue3pm.next().unwrap(), 130 | Range{start: dttm(2016, 3, 8, 15, 0, 0), 131 | end: dttm(2016, 3, 8, 16, 0, 0), grain: Grain::Hour}); 132 | assert_eq!(tue3pm.next().unwrap(), 133 | Range{start: dttm(2016, 3, 15, 15, 0, 0), 134 | end: dttm(2016, 3, 15, 16, 0, 0), grain: Grain::Hour}); 135 | 136 | // thursdays of june 137 | let mut junthurs = Intersect(Weekday(4), Month(6)).future(&dt(2016, 2, 25)); 138 | assert_eq!(junthurs.next().unwrap(), 139 | Range{start: dt(2016, 6, 2), end: dt(2016, 6, 3), grain: Grain::Day}); 140 | assert_eq!(junthurs.next().unwrap(), 141 | Range{start: dt(2016, 6, 9), end: dt(2016, 6, 10), grain: Grain::Day}); 142 | assert_eq!(junthurs.next().unwrap(), 143 | Range{start: dt(2016, 6, 16), end: dt(2016, 6, 17), grain: Grain::Day}); 144 | assert_eq!(junthurs.next().unwrap(), 145 | Range{start: dt(2016, 6, 23), end: dt(2016, 6, 24), grain: Grain::Day}); 146 | assert_eq!(junthurs.next().unwrap(), 147 | Range{start: dt(2016, 6, 30), end: dt(2016, 7, 1), grain: Grain::Day}); 148 | assert_eq!(junthurs.next().unwrap(), 149 | Range{start: dt(2017, 6, 1), end: dt(2017, 6, 2), grain: Grain::Day}); 150 | } 151 | 152 | 153 | #[test] 154 | fn intersect_union() { 155 | // mondays + wednesdays of June 156 | let monwedjune = Intersect(Union(Weekday(1), Weekday(3)), Month(6)); 157 | let mut fut = monwedjune.future(&dt(2016, 2, 25)); 158 | assert_eq!(fut.next().unwrap(), 159 | Range{start: dt(2016, 6, 1), end: dt(2016, 6, 2), grain: Grain::Day}); 160 | assert_eq!(fut.next().unwrap(), 161 | Range{start: dt(2016, 6, 6), end: dt(2016, 6, 7), grain: Grain::Day}); 162 | assert_eq!(fut.next().unwrap(), 163 | Range{start: dt(2016, 6, 8), end: dt(2016, 6, 9), grain: Grain::Day}); 164 | let mut fut = fut.skip(6); 165 | assert_eq!(fut.next().unwrap(), 166 | Range{start: dt(2017, 6, 5), end: dt(2017, 6, 6), grain: Grain::Day}); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /kronos/src/seq_interval.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::utils; 4 | use crate::types::{DateTime, Range, TimeSequence}; 5 | 6 | // example duckling intervals http://tinyurl.com/hk2vu34 7 | 8 | #[derive(Clone)] 9 | pub struct Interval 10 | where SeqA: TimeSequence, 11 | SeqB: TimeSequence + Clone 12 | { 13 | start: SeqA, 14 | end: SeqB, 15 | inclusive: bool, 16 | } 17 | 18 | impl Interval 19 | where SeqA: TimeSequence, 20 | SeqB: TimeSequence + Clone 21 | { 22 | fn _base(&self, t0: &DateTime, future: bool) -> Box + '_> { 23 | let endseq = self.end.clone(); 24 | let inclusive = self.inclusive; 25 | 26 | // interval generator 27 | let interval = move |istart: Range| { 28 | use std::cmp; 29 | let iend = endseq._future_raw(&istart.start).next().unwrap(); 30 | Range{ 31 | start: istart.start, 32 | end: if inclusive { iend.end } else { iend.start }, 33 | grain: cmp::min(istart.grain, iend.grain) 34 | } 35 | }; 36 | 37 | // guesstimate resolution for framing/truncating reftime so that 38 | // initial interval can contain t0 even if end-of start element past 39 | let probe = interval(self.start._future_raw(t0).next().unwrap()); 40 | // estimate grain from interval length 41 | let trunc_grain = utils::enclosing_grain_from_duration(probe.duration()); 42 | // choose a time of reference aligned to interval on enclosing grain 43 | let t0 = utils::truncate(*t0, trunc_grain); 44 | let t0 = self.start._future_raw(&t0).next().unwrap().start; 45 | 46 | Box::new(if future { 47 | self.start._future_raw(&t0) 48 | } else { 49 | self.start._past_raw(&t0) 50 | }.map(interval)) 51 | } 52 | } 53 | 54 | impl TimeSequence for Interval 55 | where SeqA: TimeSequence, 56 | SeqB: TimeSequence + Clone 57 | { 58 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 59 | self._base(t0, true) 60 | } 61 | 62 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 63 | self._base(t0, false) 64 | } 65 | } 66 | 67 | 68 | #[cfg(test)] 69 | mod test { 70 | use super::*; 71 | use crate::types::{Date, Grain}; 72 | use crate::seq_named::{Weekday, Month}; 73 | use crate::seq_nthof::NthOf; 74 | use crate::seq_grain::Grains; 75 | 76 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 77 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 78 | } 79 | 80 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 81 | Date::from_ymd(year, month, day).and_hms(h, m, s) 82 | } 83 | 84 | #[test] 85 | fn interval_basic() { 86 | // monday to friday 87 | let mon2fri = Interval{start: Weekday(1), end: Weekday(5), inclusive: true}; 88 | 89 | let mut fut = mon2fri.future(&dt(2016, 2, 25)); 90 | assert_eq!(fut.next().unwrap(), 91 | Range{start: dt(2016, 2, 22), end: dt(2016, 2, 27), grain: Grain::Day}); 92 | assert_eq!(fut.next().unwrap(), 93 | Range{start: dt(2016, 2, 29), end: dt(2016, 3, 5), grain: Grain::Day}); 94 | 95 | // past non-inclusive 96 | let mut past = mon2fri.past(&dt(2016, 2, 25)); 97 | assert_eq!(past.next().unwrap(), 98 | Range{start: dt(2016, 2, 15), end: dt(2016, 2, 20), grain: Grain::Day}); 99 | assert_eq!(past.next().unwrap(), 100 | Range{start: dt(2016, 2, 8), end: dt(2016, 2, 13), grain: Grain::Day}); 101 | 102 | // past inclusive 103 | let mut past = mon2fri._past_raw(&dt(2016, 2, 25)); 104 | assert_eq!(past.next().unwrap(), 105 | Range{start: dt(2016, 2, 22), end: dt(2016, 2, 27), grain: Grain::Day}); 106 | assert_eq!(past.next().unwrap(), 107 | Range{start: dt(2016, 2, 15), end: dt(2016, 2, 20), grain: Grain::Day}); 108 | assert_eq!(past.next().unwrap(), 109 | Range{start: dt(2016, 2, 8), end: dt(2016, 2, 13), grain: Grain::Day}); 110 | } 111 | 112 | #[test] 113 | fn interval_afternoon() { 114 | let afternoon = Interval{ 115 | start: NthOf(13, Grains(Grain::Hour), Grains(Grain::Day)), 116 | end: NthOf(19, Grains(Grain::Hour), Grains(Grain::Day)), 117 | inclusive: false}; 118 | 119 | let mut iter = afternoon.future(&dt(2016, 2, 25)); 120 | assert_eq!(iter.next().unwrap(), 121 | Range{start: dttm(2016, 2, 25, 12, 0, 0), 122 | end: dttm(2016, 2, 25, 18, 0, 0), grain: Grain::Hour}); 123 | assert_eq!(iter.next().unwrap(), 124 | Range{start: dttm(2016, 2, 26, 12, 0, 0), 125 | end: dttm(2016, 2, 26, 18, 0, 0), grain: Grain::Hour}); 126 | 127 | // past non-inclusive 128 | let mut iter = afternoon.past(&dttm(2016, 2, 25, 14, 0, 0)); 129 | assert_eq!(iter.next().unwrap(), 130 | Range{start: dttm(2016, 2, 24, 12, 0, 0), 131 | end: dttm(2016, 2, 24, 18, 0, 0), grain: Grain::Hour}); 132 | assert_eq!(iter.next().unwrap(), 133 | Range{start: dttm(2016, 2, 23, 12, 0, 0), 134 | end: dttm(2016, 2, 23, 18, 0, 0), grain: Grain::Hour}); 135 | 136 | // past inclusive 137 | let mut iter = afternoon._past_raw(&dttm(2016, 2, 25, 14, 0, 0)); 138 | assert_eq!(iter.next().unwrap(), 139 | Range{start: dttm(2016, 2, 25, 12, 0, 0), 140 | end: dttm(2016, 2, 25, 18, 0, 0), grain: Grain::Hour}); 141 | assert_eq!(iter.next().unwrap(), 142 | Range{start: dttm(2016, 2, 24, 12, 0, 0), 143 | end: dttm(2016, 2, 24, 18, 0, 0), grain: Grain::Hour}); 144 | assert_eq!(iter.next().unwrap(), 145 | Range{start: dttm(2016, 2, 23, 12, 0, 0), 146 | end: dttm(2016, 2, 23, 18, 0, 0), grain: Grain::Hour}); 147 | } 148 | 149 | #[test] 150 | fn interval_mixed() { 151 | let june2ndtileom = Interval{ 152 | start: NthOf(2, Grains(Grain::Day), Month(6)), 153 | end: Month(6), inclusive: true}; 154 | 155 | let mut iter = june2ndtileom.future(&dt(2016, 6, 25)); 156 | assert_eq!(iter.next().unwrap(), 157 | Range{start: dt(2016, 6, 2), end: dt(2016, 7, 1), grain: Grain::Day}); 158 | assert_eq!(iter.next().unwrap(), 159 | Range{start: dt(2017, 6, 2), end: dt(2017, 7, 1), grain: Grain::Day}); 160 | 161 | // past non-inclusive 162 | let mut iter = june2ndtileom.past(&dt(2016, 6, 25)); 163 | assert_eq!(iter.next().unwrap(), 164 | Range{start: dt(2015, 6, 2), end: dt(2015, 7, 1), grain: Grain::Day}); 165 | assert_eq!(iter.next().unwrap(), 166 | Range{start: dt(2014, 6, 2), end: dt(2014, 7, 1), grain: Grain::Day}); 167 | 168 | // past inclusive 169 | let mut iter = june2ndtileom._past_raw(&dt(2016, 6, 25)); 170 | assert_eq!(iter.next().unwrap(), 171 | Range{start: dt(2016, 6, 2), end: dt(2016, 7, 1), grain: Grain::Day}); 172 | assert_eq!(iter.next().unwrap(), 173 | Range{start: dt(2015, 6, 2), end: dt(2015, 7, 1), grain: Grain::Day}); 174 | assert_eq!(iter.next().unwrap(), 175 | Range{start: dt(2014, 6, 2), end: dt(2014, 7, 1), grain: Grain::Day}); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /kronos/src/seq_lastof.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::collections::VecDeque; 4 | use crate::types::{DateTime, Range, TimeSequence}; 5 | 6 | // Guard against impossible sequences, eg: 32nd day of the month 7 | const INFINITE_FUSE: usize = 1000; 8 | 9 | #[derive(Clone)] 10 | pub struct LastOf(pub usize, pub Win, pub Frame) 11 | where Frame: TimeSequence, 12 | Win: TimeSequence + Clone; 13 | 14 | 15 | impl LastOf 16 | where Frame: TimeSequence, 17 | Win: TimeSequence + Clone 18 | { 19 | fn _base(&self, t0: &DateTime, future: bool) 20 | -> Box + '_> 21 | { 22 | let win = self.1.clone(); 23 | let nth = self.0; 24 | let frame = if future { 25 | self.2._future_raw(t0) 26 | } else { 27 | self.2._past_raw(t0) 28 | }; 29 | Box::new(frame 30 | .map(move |outer| { 31 | let mut buf = VecDeque::new(); 32 | for inner in win._future_raw(&outer.start) { 33 | if inner.start >= outer.end { 34 | return buf.remove(nth-1); 35 | } 36 | buf.push_front(inner); 37 | buf.truncate(nth); 38 | } 39 | None 40 | }) 41 | .enumerate() 42 | .filter_map(|(i, elem)| { assert!(i <= INFINITE_FUSE); elem }) 43 | ) 44 | } 45 | } 46 | 47 | impl TimeSequence for LastOf 48 | where Frame: TimeSequence, 49 | Win: TimeSequence + Clone 50 | { 51 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 52 | self._base(t0, true) 53 | } 54 | 55 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 56 | self._base(t0, false) 57 | } 58 | } 59 | 60 | 61 | #[cfg(test)] 62 | mod test { 63 | use super::*; 64 | use crate::types::{Date, Grain}; 65 | use crate::seq_grain::Grains; 66 | use crate::seq_named::{Weekend, Month}; 67 | 68 | 69 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 70 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 71 | } 72 | 73 | #[test] 74 | #[should_panic] 75 | fn lastof_fuse() { 76 | let badlastof = LastOf(32, Grains(Grain::Day), Grains(Grain::Month)); 77 | badlastof.future(&dt(2015, 2, 25)).next(); 78 | } 79 | 80 | #[test] 81 | fn lastof() { 82 | // last weekend of the year 83 | let weekendofyear = LastOf(1, Weekend, Grains(Grain::Year)); 84 | let mut weekendofyear = weekendofyear.future(&dt(2015, 2, 25)); 85 | assert_eq!(weekendofyear.next().unwrap(), 86 | Range{start: dt(2015, 12, 26), end: dt(2015, 12, 28), grain: Grain::Day}); 87 | assert_eq!(weekendofyear.next().unwrap(), 88 | Range{start: dt(2016, 12, 31), end: dt(2017, 1, 2), grain: Grain::Day}); 89 | 90 | // 2nd-to-last day of february 91 | let daybeforelastfeb = LastOf(2, Grains(Grain::Day), Month(2)); 92 | let mut daybeforelastfeb = daybeforelastfeb.future(&dt(2015, 2, 25)); 93 | assert_eq!(daybeforelastfeb.next().unwrap(), 94 | Range{start: dt(2015, 2, 27), end: dt(2015, 2, 28), grain: Grain::Day}); 95 | assert_eq!(daybeforelastfeb.next().unwrap(), 96 | Range{start: dt(2016, 2, 28), end: dt(2016, 2, 29), grain: Grain::Day}); 97 | 98 | // 29th-to-last day of feb 99 | let t29th_before_last = LastOf(29, Grains(Grain::Day), Month(2)); 100 | let mut t29th_before_last = t29th_before_last.future(&dt(2015, 2, 25)); 101 | assert_eq!(t29th_before_last.next().unwrap(), 102 | Range{start: dt(2016, 2, 1), end: dt(2016, 2, 2), grain: Grain::Day}); 103 | assert_eq!(t29th_before_last.next().unwrap(), 104 | Range{start: dt(2020, 2, 1), end: dt(2020, 2, 2), grain: Grain::Day}); 105 | 106 | // backward: 2nd-to-last day of february 107 | let daybeforelastfeb = LastOf(2, Grains(Grain::Day), Month(2)); 108 | let mut daybeforelastfeb = daybeforelastfeb.past(&dt(2015, 2, 25)); 109 | assert_eq!(daybeforelastfeb.next().unwrap(), 110 | Range{start: dt(2014, 2, 27), end: dt(2014, 2, 28), grain: Grain::Day}); 111 | assert_eq!(daybeforelastfeb.next().unwrap(), 112 | Range{start: dt(2013, 2, 27), end: dt(2013, 2, 28), grain: Grain::Day}); 113 | assert_eq!(daybeforelastfeb.next().unwrap(), 114 | Range{start: dt(2012, 2, 28), end: dt(2012, 2, 29), grain: Grain::Day}); 115 | 116 | // backward: 5th-to-last day of february 117 | let fithbeforelastfeb = LastOf(5, Grains(Grain::Day), Month(2)); 118 | let mut fithbeforelastfeb = fithbeforelastfeb.past(&dt(2015, 2, 26)); 119 | assert_eq!(fithbeforelastfeb.next().unwrap(), 120 | Range{start: dt(2015, 2, 24), end: dt(2015, 2, 25), grain: Grain::Day}); 121 | 122 | // backward: 5th-to-last day of february starting that day - inclusive/raw 123 | let fithbeforelastfeb = LastOf(5, Grains(Grain::Day), Month(2)); 124 | let mut fithbeforelastfeb = fithbeforelastfeb._past_raw(&dt(2015, 2, 24)); 125 | assert_eq!(fithbeforelastfeb.next().unwrap(), 126 | Range{start: dt(2015, 2, 24), end: dt(2015, 2, 25), grain: Grain::Day}); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /kronos/src/seq_mgrain.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::utils; 4 | use crate::types::{DateTime, Grain, Range, TimeSequence, Duration}; 5 | 6 | 7 | #[derive(Clone)] 8 | pub struct MGrain { 9 | duration: Duration, 10 | resolution: Grain, 11 | } 12 | 13 | impl MGrain { 14 | pub fn new(duration: Duration) -> MGrain { 15 | MGrain{duration, resolution: utils::grain_from_duration(duration)} 16 | } 17 | 18 | pub fn new2(duration: Duration, resolution: Grain) -> MGrain { 19 | MGrain{duration, resolution} 20 | } 21 | 22 | fn _base(&self, t0: &DateTime, future: bool) -> Box> { 23 | let base = utils::truncate(*t0, self.resolution); 24 | let hop = if future { self.duration } else { - self.duration }; 25 | let duration = self.duration; 26 | let grain = self.resolution; 27 | Box::new((0..).map(move |x| Range{ 28 | start: base + hop * x, 29 | end: base + hop * x + duration, 30 | grain 31 | })) 32 | } 33 | } 34 | 35 | impl TimeSequence for MGrain { 36 | fn _future_raw(&self, t0: &DateTime) -> Box> { 37 | self._base(t0, true) 38 | } 39 | 40 | fn _past_raw(&self, t0: &DateTime) -> Box> { 41 | self._base(t0, false) 42 | } 43 | } 44 | 45 | 46 | #[cfg(test)] 47 | mod test { 48 | use super::*; 49 | use crate::types::{Date, Grain}; 50 | 51 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 52 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 53 | } 54 | 55 | fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime { 56 | Date::from_ymd(year, month, day).and_hms(h, m, s) 57 | } 58 | 59 | #[test] 60 | fn grain_basic() { 61 | let twodays = MGrain::new(Duration::days(2)); 62 | let mut iter = twodays.future(&dt(2015, 2, 28)); 63 | assert_eq!(iter.next().unwrap(), 64 | Range{start: dt(2015, 2, 28), end: dt(2015, 3, 2), grain: Grain::Day}); 65 | assert_eq!(iter.next().unwrap(), 66 | Range{start: dt(2015, 3, 2), end: dt(2015, 3, 4), grain: Grain::Day}); 67 | 68 | let mut iter = twodays.past(&dt(2015, 2, 28)); 69 | assert_eq!(iter.next().unwrap(), 70 | Range{start: dt(2015, 2, 26), end: dt(2015, 2, 28), grain: Grain::Day}); 71 | assert_eq!(iter.next().unwrap(), 72 | Range{start: dt(2015, 2, 24), end: dt(2015, 2, 26), grain: Grain::Day}); 73 | 74 | // past inclusive 75 | let mut iter = twodays._past_raw(&dt(2015, 2, 28)); 76 | assert_eq!(iter.next().unwrap(), 77 | Range{start: dt(2015, 2, 28), end: dt(2015, 3, 2), grain: Grain::Day}); 78 | assert_eq!(iter.next().unwrap(), 79 | Range{start: dt(2015, 2, 26), end: dt(2015, 2, 28), grain: Grain::Day}); 80 | } 81 | 82 | #[test] 83 | fn smaller_grains() { 84 | let twohs30m = MGrain::new(Duration::minutes(2*60+30)); 85 | 86 | let mut iter = twohs30m.future(&dt(2015, 2, 27)); 87 | assert_eq!(iter.next().unwrap(), 88 | Range{start: dttm(2015, 2, 27, 0, 0, 0), 89 | end: dttm(2015, 2, 27, 2, 30, 0), grain: Grain::Minute}); 90 | assert_eq!(iter.next().unwrap(), 91 | Range{start: dttm(2015, 2, 27, 2, 30, 0), 92 | end: dttm(2015, 2, 27, 5, 00, 0), grain: Grain::Minute}); 93 | 94 | let mut iter = twohs30m.past(&dttm(2015, 2, 27, 3, 0, 0)); 95 | assert_eq!(iter.next().unwrap(), 96 | Range{start: dttm(2015, 2, 27, 0, 30, 0), 97 | end: dttm(2015, 2, 27, 3, 0, 0), grain: Grain::Minute}); 98 | assert_eq!(iter.next().unwrap(), 99 | Range{start: dttm(2015, 2, 26, 22, 00, 0), 100 | end: dttm(2015, 2, 27, 0, 30, 0), grain: Grain::Minute}); 101 | } 102 | 103 | #[test] 104 | fn more_mgrain() { 105 | let twoweeks = MGrain::new(Duration::weeks(2)); 106 | let mut twoweeks = twoweeks.future(&dt(2015, 2, 27)); 107 | assert_eq!(twoweeks.next().unwrap(), 108 | Range{start: dt(2015, 2, 22), end: dt(2015, 3, 8), grain: Grain::Week}); 109 | assert_eq!(twoweeks.next().unwrap(), 110 | Range{start: dt(2015, 3, 8), end: dt(2015, 3, 22), grain: Grain::Week}); 111 | 112 | let threedays = MGrain::new(Duration::days(3)); 113 | let mut iter = threedays.future(&dt(2015, 2, 27)); 114 | assert_eq!(iter.next().unwrap(), 115 | Range{start: dt(2015, 2, 27), end: dt(2015, 3, 2), grain: Grain::Day}); 116 | assert_eq!(iter.next().unwrap(), 117 | Range{start: dt(2015, 3, 2), end: dt(2015, 3, 5), grain: Grain::Day}); 118 | assert_eq!(iter.next().unwrap(), 119 | Range{start: dt(2015, 3, 5), end: dt(2015, 3, 8), grain: Grain::Day}); 120 | 121 | let mut iter = threedays.past(&dt(2015, 2, 17)); 122 | assert_eq!(iter.next().unwrap(), 123 | Range{start: dt(2015, 2, 14), end: dt(2015, 2, 17), grain: Grain::Day}); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /kronos/src/seq_seasons.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::utils; 4 | use crate::types::{Grain, DateTime, Range, TimeSequence, Season}; 5 | 6 | 7 | #[derive(Clone)] 8 | pub struct Seasons(pub Season, pub bool); // north hemisphere 9 | 10 | impl Seasons { 11 | fn _base(&self, t0: &DateTime, future: bool) -> Box> { 12 | let (s0, s1) = utils::find_season(t0.date(), self.0, future, self.1); 13 | let s0 = s0.and_hms(0, 0, 0); 14 | let s1 = s1.and_hms(0, 0, 0); 15 | let sign = if future { 1 } else { -1 }; 16 | Box::new((0..).map(move |x| Range{ 17 | start: utils::shift_datetime(s0, Grain::Year, sign * x), 18 | end: utils::shift_datetime(s1, Grain::Year, sign * x), 19 | grain: Grain::Day 20 | })) 21 | } 22 | } 23 | 24 | impl TimeSequence for Seasons { 25 | fn _future_raw(&self, t0: &DateTime) -> Box> { 26 | self._base(t0, true) 27 | } 28 | 29 | fn _past_raw(&self, t0: &DateTime) -> Box> { 30 | self._base(t0, false) 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod test { 36 | use super::*; 37 | use crate::types::{Date, Grain}; 38 | 39 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 40 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 41 | } 42 | 43 | #[test] 44 | fn summer() { 45 | let summer = Seasons(Season::Summer, true); 46 | 47 | let mut iter = summer.future(&dt(2015, 9, 22)); 48 | assert_eq!(iter.next().unwrap(), 49 | Range{start: dt(2016, 6, 21), end: dt(2016, 9, 21), grain: Grain::Day}); 50 | assert_eq!(iter.next().unwrap(), 51 | Range{start: dt(2017, 6, 21), end: dt(2017, 9, 21), grain: Grain::Day}); 52 | 53 | // past non-inclusive 54 | let mut iter = summer.past(&dt(2015, 9, 22)); 55 | assert_eq!(iter.next().unwrap(), 56 | Range{start: dt(2015, 6, 21), end: dt(2015, 9, 21), grain: Grain::Day}); 57 | assert_eq!(iter.next().unwrap(), 58 | Range{start: dt(2014, 6, 21), end: dt(2014, 9, 21), grain: Grain::Day}); 59 | } 60 | 61 | #[test] 62 | fn winter() { 63 | let winter = Seasons(Season::Winter, true); 64 | // future 65 | let mut iter = winter.future(&dt(2015, 1, 22)); 66 | assert_eq!(iter.next().unwrap(), 67 | Range{start: dt(2014, 12, 21), end: dt(2015, 3, 21), grain: Grain::Day}); 68 | assert_eq!(iter.next().unwrap(), 69 | Range{start: dt(2015, 12, 21), end: dt(2016, 3, 21), grain: Grain::Day}); 70 | 71 | // past non-inclusive 72 | let mut iter = winter.past(&dt(2015, 1, 22)); 73 | assert_eq!(iter.next().unwrap(), 74 | Range{start: dt(2013, 12, 21), end: dt(2014, 3, 21), grain: Grain::Day}); 75 | assert_eq!(iter.next().unwrap(), 76 | Range{start: dt(2012, 12, 21), end: dt(2013, 3, 21), grain: Grain::Day}); 77 | 78 | // past inclusive 79 | let mut iter = winter._past_raw(&dt(2015, 1, 22)); 80 | assert_eq!(iter.next().unwrap(), 81 | Range{start: dt(2014, 12, 21), end: dt(2015, 3, 21), grain: Grain::Day}); 82 | assert_eq!(iter.next().unwrap(), 83 | Range{start: dt(2013, 12, 21), end: dt(2014, 3, 21), grain: Grain::Day}); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /kronos/src/seq_shim.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Range, TimeSequence}; 4 | use std::rc::Rc; 5 | 6 | // seq_*.rs hold many different types that implement TimeSequence, Shim 7 | // is a helper to allow different sequence types to be used as if they were one 8 | 9 | #[derive(Clone)] 10 | pub struct Shim<'a>(pub Rc); 11 | 12 | impl<'a> Shim<'a> { 13 | pub fn new(seq: impl TimeSequence + 'a) -> Shim<'a> { 14 | Shim(Rc::new(seq)) 15 | } 16 | } 17 | 18 | impl<'a> TimeSequence for Shim<'a> { 19 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 20 | self.0._future_raw(t0) 21 | } 22 | 23 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 24 | self.0._past_raw(t0) 25 | } 26 | 27 | fn future(&self, t0: &DateTime) -> Box + '_> { 28 | self.0.future(t0) 29 | } 30 | 31 | fn past(&self, t0: &DateTime) -> Box + '_> { 32 | self.0.past(t0) 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod test { 38 | use super::*; 39 | use crate::seq_grain::Grains; 40 | use crate::seq_named::Weekday; 41 | use crate::seq_nthof::NthOf; 42 | use crate::types::{Date, Grain}; 43 | 44 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 45 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 46 | } 47 | 48 | #[test] 49 | fn try_shim() { 50 | let weekday2 = Rc::new(Weekday(2)); 51 | let tue3mo = NthOf(3, Shim(weekday2), Grains(Grain::Month)); 52 | let mut tue3mo = tue3mo.future(&dt(2019, 1, 12)); 53 | assert_eq!( 54 | tue3mo.next().unwrap(), 55 | Range { 56 | start: dt(2019, 1, 15), 57 | end: dt(2019, 1, 16), 58 | grain: Grain::Day 59 | } 60 | ); 61 | } 62 | 63 | #[test] 64 | fn any_container() { 65 | let abunch = vec![ 66 | Shim::new(Weekday(3)), 67 | Shim::new(NthOf(3, Weekday(2), Grains(Grain::Month))), 68 | ]; 69 | assert_eq!(abunch.len(), 2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /kronos/src/seq_union.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::types::{DateTime, Range, TimeSequence}; 4 | 5 | // Alternates SeqA and SeqB depending on what happens first 6 | // Union skips over Ranges totally contained by other sequence 7 | // |------a------| 8 | // |------b------| 9 | // 10 | // |----a----| 11 | // |-----b-----| 12 | // 13 | // |---------a---------| 14 | // |-----b-----| 15 | // 16 | // Exapmles: 17 | // - Mondays and Fridays 18 | // - Weekends and Tuesdays 19 | // - overlapping (2pm to 3pm) and (1pm to 5pm) 20 | 21 | #[derive(Clone)] 22 | pub struct Union(pub SeqA, pub SeqB) 23 | where SeqA: TimeSequence, 24 | SeqB: TimeSequence; 25 | 26 | impl Union 27 | where SeqA: TimeSequence, 28 | SeqB: TimeSequence 29 | { 30 | fn _base(&self, t0: &DateTime, future: bool) -> Box + '_> { 31 | let (mut astream, mut bstream) = if future { 32 | (self.0._future_raw(t0), self.1._future_raw(t0)) 33 | } else { 34 | (self.0._past_raw(t0), self.1._past_raw(t0)) 35 | }; 36 | let mut anext = astream.next().unwrap(); 37 | let mut bnext = bstream.next().unwrap(); 38 | Box::new((0..).map(move |_| { 39 | if (anext.start <= bnext.start && future) || 40 | (anext.start > bnext.start && !future) { 41 | // advance included bstream until out of shadow of astream 42 | while (bnext.end <= anext.end && future) || 43 | (bnext.start >= anext.start && !future) { 44 | bnext = bstream.next().unwrap(); 45 | } 46 | let unionret = anext.clone(); 47 | anext = astream.next().unwrap(); 48 | unionret 49 | } else { 50 | // advance included astream until out of shadow of bstream 51 | while (anext.end <= bnext.end && future) || 52 | (anext.start >= bnext.start && !future) { 53 | anext = astream.next().unwrap(); 54 | } 55 | let unionret = bnext.clone(); 56 | bnext = bstream.next().unwrap(); 57 | unionret 58 | } 59 | })) 60 | } 61 | } 62 | 63 | impl TimeSequence for Union 64 | where SeqA: TimeSequence, 65 | SeqB: TimeSequence 66 | { 67 | fn _future_raw(&self, t0: &DateTime) -> Box + '_> { 68 | self._base(t0, true) 69 | } 70 | 71 | fn _past_raw(&self, t0: &DateTime) -> Box + '_> { 72 | self._base(t0, false) 73 | } 74 | } 75 | 76 | 77 | #[cfg(test)] 78 | mod test { 79 | use super::*; 80 | use crate::types::Grain; 81 | 82 | fn dt(year: i32, month: u32, day: u32) -> DateTime { 83 | use crate::types::Date; 84 | Date::from_ymd(year, month, day).and_hms(0, 0, 0) 85 | } 86 | 87 | #[test] 88 | fn test_union() { 89 | use crate::seq_named::Weekday; 90 | 91 | let mut monwed = Union(Weekday(1), Weekday(3)).future(&dt(2015, 2, 27)); 92 | assert_eq!(monwed.next().unwrap(), 93 | Range{start: dt(2015, 3, 2), end: dt(2015, 3, 3), grain: Grain::Day}); 94 | assert_eq!(monwed.next().unwrap(), 95 | Range{start: dt(2015, 3, 4), end: dt(2015, 3, 5), grain: Grain::Day}); 96 | assert_eq!(monwed.next().unwrap(), 97 | Range{start: dt(2015, 3, 9), end: dt(2015, 3, 10), grain: Grain::Day}); 98 | 99 | let monwed = Union(Weekday(1), Weekday(3)); 100 | let monwedfri = Union(monwed, Weekday(5)); 101 | let mut monwedfri = monwedfri.future(&dt(2015, 2, 27)); 102 | assert_eq!(monwedfri.next().unwrap(), 103 | Range{start: dt(2015, 2, 27), end: dt(2015, 2, 28), grain: Grain::Day}); 104 | assert_eq!(monwedfri.next().unwrap(), 105 | Range{start: dt(2015, 3, 2), end: dt(2015, 3, 3), grain: Grain::Day}); 106 | assert_eq!(monwedfri.next().unwrap(), 107 | Range{start: dt(2015, 3, 4), end: dt(2015, 3, 5), grain: Grain::Day}); 108 | } 109 | 110 | #[test] 111 | fn test_union_past() { 112 | use crate::seq_named::Weekday; 113 | 114 | let mut monwed = Union(Weekday(1), Weekday(3)).past(&dt(2015, 2, 27)); 115 | assert_eq!(monwed.next().unwrap(), 116 | Range{start: dt(2015, 2, 25), end: dt(2015, 2, 26), grain: Grain::Day}); 117 | assert_eq!(monwed.next().unwrap(), 118 | Range{start: dt(2015, 2, 23), end: dt(2015, 2, 24), grain: Grain::Day}); 119 | 120 | let monwed = Union(Weekday(1), Weekday(3)); 121 | let monwedfri = Union(monwed, Weekday(5)); 122 | let mut monwedfri = monwedfri.past(&dt(2015, 2, 27)); 123 | assert_eq!(monwedfri.next().unwrap(), 124 | Range{start: dt(2015, 2, 25), end: dt(2015, 2, 26), grain: Grain::Day}); 125 | assert_eq!(monwedfri.next().unwrap(), 126 | Range{start: dt(2015, 2, 23), end: dt(2015, 2, 24), grain: Grain::Day}); 127 | assert_eq!(monwedfri.next().unwrap(), 128 | Range{start: dt(2015, 2, 20), end: dt(2015, 2, 21), grain: Grain::Day}); 129 | 130 | // past-inclusive/raw 131 | let monwed = Union(Weekday(1), Weekday(3)); 132 | let monwedfri = Union(monwed, Weekday(5)); 133 | let mut monwedfri = monwedfri._past_raw(&dt(2015, 2, 27)); 134 | assert_eq!(monwedfri.next().unwrap(), 135 | Range{start: dt(2015, 2, 27), end: dt(2015, 2, 28), grain: Grain::Day}); 136 | } 137 | 138 | #[test] 139 | fn test_diff_resolution() { 140 | use crate::seq_named::{Month, Weekday}; 141 | 142 | let mut mon_or_march = Union(Weekday(1), Month(3)).future(&dt(2015, 2, 27)); 143 | assert_eq!(mon_or_march.next().unwrap(), 144 | Range{start: dt(2015, 3, 1), end: dt(2015, 4, 1), grain: Grain::Month}); 145 | assert_eq!(mon_or_march.next().unwrap(), 146 | Range{start: dt(2015, 4, 6), end: dt(2015, 4, 7), grain: Grain::Day}); 147 | assert_eq!(mon_or_march.next().unwrap(), 148 | Range{start: dt(2015, 4, 13), end: dt(2015, 4, 14), grain: Grain::Day}); 149 | let mut mon_or_march = mon_or_march.skip(46); 150 | assert_eq!(mon_or_march.next().unwrap(), 151 | Range{start: dt(2016, 3, 1), end: dt(2016, 4, 1), grain: Grain::Month}); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /kronos/src/types.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | pub type DateTime = chrono::NaiveDateTime; 4 | pub type Date = chrono::NaiveDate; 5 | pub type Duration = chrono::Duration; 6 | 7 | use std::str::FromStr; 8 | 9 | 10 | #[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Clone,Copy)] 11 | pub enum Grain { 12 | Second, 13 | Minute, 14 | Hour, 15 | Day, 16 | Week, 17 | Month, 18 | Quarter, 19 | Half, 20 | Year, 21 | Lustrum, 22 | Decade, 23 | Century, 24 | Millenium, 25 | } 26 | 27 | impl FromStr for Grain { 28 | type Err = String; 29 | fn from_str(s: &str) -> Result { 30 | match s.to_lowercase().as_ref() { 31 | "second" | "seconds" => Ok(Grain::Second), 32 | "minute" | "minutes" => Ok(Grain::Minute), 33 | "hour" | "hours" => Ok(Grain::Hour), 34 | "day" | "days" => Ok(Grain::Day), 35 | "week" | "weeks" => Ok(Grain::Week), 36 | "month" | "months" => Ok(Grain::Month), 37 | "quarter" | "quarters" => Ok(Grain::Quarter), 38 | "half" | "halfs" => Ok(Grain::Half), 39 | "year" | "years" => Ok(Grain::Year), 40 | "lustrum" | "lustrums" => Ok(Grain::Lustrum), 41 | "decade" | "decades" => Ok(Grain::Decade), 42 | "century" | "centuries" => Ok(Grain::Century), 43 | "millenium" | "millenia" | "milleniums" => Ok(Grain::Millenium), 44 | _ => Err(format!("Can't build Grain from {}", s)) 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug,PartialEq,Eq,Clone,Copy)] 50 | pub enum Season { 51 | Spring, 52 | Summer, 53 | Autumn, 54 | Winter, 55 | } 56 | 57 | impl FromStr for Season { 58 | type Err = String; 59 | fn from_str(s: &str) -> Result { 60 | match s.to_lowercase().as_ref() { 61 | "spring" | "springs" => Ok(Season::Spring), 62 | "summer" | "summers" => Ok(Season::Summer), 63 | "autumn" | "autumns" => Ok(Season::Autumn), 64 | "winter" | "winters" => Ok(Season::Winter), 65 | _ => Err(format!("Can't build Season from {}", s)) 66 | } 67 | } 68 | } 69 | 70 | // Ranges are right-open intervals of time, ie: [start, end) 71 | #[derive(Clone,Debug,PartialEq)] 72 | pub struct Range { 73 | pub start: DateTime, // included 74 | pub end: DateTime, // excluded 75 | pub grain: Grain, // resolution of start/end 76 | } 77 | 78 | impl Range { 79 | pub fn intersect(&self, other: &Range) -> Option { 80 | use std::cmp; 81 | if self.start < other.end && self.end > other.start { 82 | return Some(Range{ 83 | start: cmp::max(self.start, other.start), 84 | end: cmp::min(self.end, other.end), 85 | grain: cmp::min(self.grain, other.grain) 86 | }); 87 | } 88 | None 89 | } 90 | 91 | pub fn duration(&self) -> Duration { 92 | self.end.signed_duration_since(self.start) 93 | } 94 | } 95 | 96 | 97 | // TimeSequence is a floating description of a set of time Ranges. 98 | // They can be evaluated in the context of an instant to produce time Ranges. 99 | 100 | pub trait TimeSequence { 101 | // Yield instances of this sequence into the future. 102 | // End-time of Ranges must be greater than reference t0 DateTime. 103 | // NOTE: First Range may start after t0 if for example discontinuous. 104 | fn _future_raw(&self, t0: &DateTime) -> Box + '_>; 105 | 106 | // Yield instances of this sequence into the past 107 | // Start-time of emited Ranges must be less-or-equal than reference t0. 108 | fn _past_raw(&self, t0: &DateTime) -> Box + '_>; 109 | 110 | // NOTE: past_raw and future_raw are mainly used internaly. 111 | // Their first elements may overlap and are needed for composing NthOf. 112 | // End-user wants future + past which have no overlap in emitted Ranges 113 | 114 | fn future(&self, t0: &DateTime) -> Box + '_> { 115 | let t0 = *t0; 116 | Box::new(self._future_raw(&t0) 117 | .skip_while(move |range| range.end <= t0)) 118 | } 119 | 120 | // End-time of emited Ranges must be less-or-equal than reference DateTime. 121 | // Complement of "future" where end-time must be greater than t0. 122 | fn past(&self, t0: &DateTime) -> Box + '_> { 123 | let t0 = *t0; 124 | Box::new(self._past_raw(&t0) 125 | .skip_while(move |range| range.end > t0)) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lexers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lexers" 3 | version = "0.1.4" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Rodolfo Granata "] 7 | description = "Tools for tokenizing and scanning" 8 | repository = "https://github.com/rodolf0/tox/tree/master/lexers" 9 | readme = "README.md" 10 | keywords = ["lexer", "tokenizer", "scanner", "ebnf"] 11 | categories = ["parsing", "text-processing"] 12 | -------------------------------------------------------------------------------- /lexers/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Tokenizers 4 | This crate provides multiple tokenizers built on top of `Scanner`. 5 | 6 | - **EbnfTokenizer**: A tokenizing an EBNF grammar. 7 | ```rust 8 | let grammar = r#" 9 | expr := expr ('+'|'-') term | term ; 10 | term := term ('*'|'/') factor | factor ; 11 | factor := '-' factor | power ; 12 | power := ufact '^' factor | ufact ; 13 | ufact := ufact '!' | group ; 14 | group := num | '(' expr ')' ; 15 | "#; 16 | let mut tok = EbnfTokenizer::new(grammar.chars()) 17 | ``` 18 | - **LispTokenizer**: for tokenizing lisp like input. 19 | ```rust 20 | LispTokenizer::new("(+ 3 4 5)".chars()); 21 | ``` 22 | - **MathTokenizer**: emits `MathToken` tokens. 23 | ```rust 24 | MathTokenizer::new("3.4e-2 * sin(x)/(7! % -4)".chars()); 25 | ``` 26 | - **DelimTokenizer**: emits tokens split by some delimiter. 27 | 28 | 29 | ## Scanner 30 | `Scanner` is the building block for implementing tokenizers. You can build one from an Iterator and use it to extract tokens. Check the above mentioned tokenizers for examples. 31 | 32 | ### Example 33 | 34 | ```rust 35 | // Define a Tokenizer 36 | struct Tokenizer>(lexers::Scanner); 37 | 38 | impl> Iterator for Tokenizer { 39 | type Item = String; 40 | fn next(&mut self) -> Option { 41 | self.0.scan_whitespace(); 42 | self.0.scan_math_op() 43 | .or_else(|| self.0.scan_number()) 44 | .or_else(|| self.0.scan_identifier()) 45 | } 46 | } 47 | 48 | fn tokenizer>(input: I) -> Tokenizer { 49 | Tokenizer(lexers::Scanner::new(input)) 50 | } 51 | 52 | // Use it to tokenize a math expression 53 | let mut lx = tokenizer("3+4*2/-(1-5)^2^3".chars()); 54 | let token = lex.next(); 55 | ``` 56 | 57 | ### Tips 58 | 59 | - `scan_X` functions try to consume some text-object out of the scanner. For example numbers, identifiers, quoted strings, etc. 60 | 61 | - `buffer_pos` and `set_buffer_pos` are used for back-tracking as long as the Scanner's buffer still has the data you need. That means you haven't consumed or discarded it. 62 | -------------------------------------------------------------------------------- /lexers/src/delim_tokenizer.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::scanner::Scanner; 4 | 5 | // A tokenizer that splits input on each delimiter 6 | pub struct DelimTokenizer> { 7 | src: Scanner, 8 | delims: Vec, 9 | remove: bool, // drop the delimiters ? 10 | } 11 | 12 | impl> DelimTokenizer { 13 | pub fn new(src: I, delims: &str, remove: bool) -> Self { 14 | DelimTokenizer { 15 | src: Scanner::new(src), 16 | delims: delims.chars().collect(), 17 | remove, 18 | } 19 | } 20 | } 21 | 22 | impl> Iterator for DelimTokenizer { 23 | type Item = String; 24 | fn next(&mut self) -> Option { 25 | if self.src.until_any(&self.delims) { 26 | Some(self.src.extract_string()) 27 | } else if let Some(c) = self.src.accept_any(&self.delims) { 28 | self.src.extract(); // ignore 29 | if self.remove { 30 | self.next() 31 | } else { 32 | Some(c.to_string()) 33 | } 34 | } else { 35 | None 36 | } 37 | } 38 | } 39 | 40 | /////////////////////////////////////////////////////////////////////////////// 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::DelimTokenizer; 45 | 46 | #[test] 47 | fn delim_tokenizer() { 48 | let inputs = vec![ 49 | ("this is a test ", " ", true), 50 | ("just,more,tests,hi", ",", true), 51 | ("another, test, here,going on", " ,", true), 52 | ("1+2*3/5", "/+*", false), 53 | ]; 54 | let expect = vec![ 55 | vec!["this", "is", "a", "test"], 56 | vec!["just", "more", "tests", "hi"], 57 | vec!["another", "test", "here", "going", "on"], 58 | vec!["1", "+", "2", "*", "3", "/", "5"], 59 | ]; 60 | for (input, expected) in inputs.iter().zip(expect.iter()) { 61 | let mut lx = DelimTokenizer::new(input.0.chars(), &input.1, input.2); 62 | for exp in expected.iter() { 63 | assert_eq!(*exp, lx.next().unwrap()); 64 | } 65 | assert_eq!(lx.next(), None); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lexers/src/ebnf_tokenizer.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::scanner::Scanner; 4 | 5 | pub struct EbnfTokenizer> { 6 | input: Scanner, 7 | lookahead: Vec, 8 | } 9 | 10 | impl> EbnfTokenizer { 11 | pub fn new(source: I) -> Self { 12 | EbnfTokenizer { 13 | input: Scanner::new(source), 14 | lookahead: Vec::new(), 15 | } 16 | } 17 | 18 | pub fn scanner(source: I) -> Scanner { 19 | Scanner::new(Self::new(source)) 20 | } 21 | } 22 | 23 | impl> Iterator for EbnfTokenizer { 24 | type Item = String; 25 | fn next(&mut self) -> Option { 26 | // used for accumulating string parts 27 | if !self.lookahead.is_empty() { 28 | return self.lookahead.pop(); 29 | } 30 | let s = &mut self.input; 31 | s.scan_whitespace(); 32 | // discard comments starting with '#' until new-line 33 | if s.accept(&'#').is_some() { 34 | while let Some(nl) = s.next() { 35 | if nl == '\n' { 36 | s.extract(); // ignore comment 37 | // discard comment and allow more by restarting 38 | return self.next(); 39 | } 40 | } 41 | } 42 | if s.accept_any(&['[', ']', '{', '}', '(', ')', '|', ';']) 43 | .is_some() 44 | { 45 | return Some(s.extract_string()); 46 | } 47 | let backtrack = s.buffer_pos(); 48 | if s.accept(&':').is_some() { 49 | if s.accept(&'=').is_some() { 50 | return Some(s.extract_string()); 51 | } 52 | s.set_buffer_pos(backtrack); 53 | } 54 | let backtrack = s.buffer_pos(); 55 | if let Some(q) = s.accept_any(&['"', '\'']) { 56 | while let Some(n) = s.next() { 57 | if n == q { 58 | // store closing quote 59 | self.lookahead.push(n.to_string()); 60 | // store string content 61 | let v = s.extract_string(); 62 | self.lookahead.push(v[1..v.len() - 1].to_string()); 63 | // return opening quote 64 | return Some(q.to_string()); 65 | } 66 | } 67 | s.set_buffer_pos(backtrack); 68 | } 69 | let backtrack = s.buffer_pos(); 70 | s.accept(&'@'); 71 | // NOTE: scan_identifier limits the valid options 72 | if let Some(id) = s.scan_identifier() { 73 | return Some(id); 74 | } 75 | // backtrack possible '@' 76 | s.set_buffer_pos(backtrack); 77 | None 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lexers/src/helpers.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::scanner::Scanner; 4 | 5 | static WHITE: &[char] = &[' ', '\n', '\r', '\t']; 6 | static DIGITS: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; 7 | static HEXDIGITS: &[char] = &[ 8 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 9 | 'D', 'E', 'F', 10 | ]; 11 | static ALPHA: &[char] = &[ 12 | '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 13 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 14 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 15 | ]; 16 | static ALNUM: &[char] = &[ 17 | '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 18 | 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 19 | 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 20 | 'U', 'V', 'W', 'X', 'Y', 'Z', 21 | ]; 22 | 23 | impl> Scanner { 24 | pub fn extract_string(&mut self) -> String { 25 | self.extract().into_iter().collect() 26 | } 27 | 28 | pub fn scan_whitespace(&mut self) -> Option { 29 | self.skip_all(WHITE); 30 | Some(self.extract_string()) 31 | } 32 | 33 | // scan numbers like -?[0-9]+(\.[0-9]+)?([eE][+-][0-9]+)? 34 | pub fn scan_number(&mut self) -> Option { 35 | let backtrack = self.buffer_pos(); 36 | // optional sign 37 | self.accept_any(&['+', '-']); 38 | // require integer part 39 | if !self.skip_all(DIGITS) { 40 | self.set_buffer_pos(backtrack); 41 | return None; 42 | } 43 | // check for fractional part, else it's just an integer 44 | let backtrack = self.buffer_pos(); 45 | if self.accept(&'.').is_some() && !self.skip_all(DIGITS) { 46 | self.set_buffer_pos(backtrack); 47 | return Some(self.extract_string()); // integer 48 | } 49 | // check for exponent part 50 | let backtrack = self.buffer_pos(); 51 | if self.accept_any(&['e', 'E']).is_some() { 52 | self.accept_any(&['+', '-']); // exponent sign is optional 53 | if !self.skip_all(DIGITS) { 54 | self.set_buffer_pos(backtrack); 55 | return Some(self.extract_string()); //float 56 | } 57 | } 58 | self.accept(&'i'); // accept imaginary numbers 59 | Some(self.extract_string()) 60 | } 61 | 62 | pub fn scan_math_op(&mut self) -> Option { 63 | const OPS: &[char] = &['+', '-', '*', '/', '%', '^', '!', '(', ')', ',']; 64 | if self.accept_any(&['>', '=', '<']).is_some() { 65 | // accept '<', '>', '=', '<=', '>=', '==' 66 | self.accept(&'='); 67 | Some(self.extract_string()) 68 | } else if self.accept(&':').is_some() && self.accept(&'=').is_some() { 69 | // accept ':='. Set delayed to avoid immediate eval of rhs. 70 | Some(self.extract_string()) 71 | } else if self.accept(&'*').is_some() { 72 | // accept '*', '**' 73 | self.accept(&'*'); 74 | Some(self.extract_string()) 75 | } else if self.accept_any(OPS).is_some() { 76 | Some(self.extract_string()) 77 | } else { 78 | None 79 | } 80 | } 81 | 82 | // scan integers like 0x34 0b10101 0o657 83 | pub fn scan_integer(&mut self) -> Option { 84 | let backtrack = self.buffer_pos(); 85 | if self.accept(&'0').is_some() 86 | && match self.accept_any(&['x', 'o', 'b']) { 87 | Some('x') => self.skip_all(HEXDIGITS), 88 | Some('o') => self.skip_all(&HEXDIGITS[..8]), 89 | Some('b') => self.skip_all(&HEXDIGITS[..2]), 90 | _ => false, 91 | } 92 | { 93 | return Some(self.extract_string()); 94 | } 95 | self.set_buffer_pos(backtrack); 96 | None 97 | } 98 | 99 | // scan a quoted string like "this is \"an\" example" 100 | pub fn scan_quoted_string(&mut self, q: char) -> Option { 101 | let backtrack = self.buffer_pos(); 102 | self.accept(&q)?; 103 | while let Some(n) = self.next() { 104 | if n == '\\' { 105 | self.next(); 106 | continue; 107 | } 108 | if n == q { 109 | return Some(self.extract_string()); 110 | } 111 | } 112 | self.set_buffer_pos(backtrack); 113 | None 114 | } 115 | 116 | // scan [a-zA-Z_][a-zA-Z0-9_]+ 117 | pub fn scan_identifier(&mut self) -> Option { 118 | self.accept_any(ALPHA)?; 119 | self.skip_all(ALNUM); 120 | Some(self.extract_string()) 121 | } 122 | 123 | // scan an optional prefix (unit multiplier) and unit 124 | pub fn scan_unit(&mut self) -> Option<(String, String)> { 125 | static PFX: &[&str] = &[ 126 | "da", "h", "k", "M", "G", "T", "P", "E", "Z", "Y", 127 | "y", "z", "a", "f", "p", "n", "µ", "m", "c", "d", 128 | "", // no multiplier prefix, raw unit 129 | ]; 130 | // NOTE: longest prefix first for longest match (ie: 'da') 131 | assert_eq!(PFX[0], "da"); 132 | static BARE_UNITS: &[&str] = &[ 133 | "kat", "mol", "rad", 134 | "Bq", "cd", "Gy", "Hz", "lm", "lx", "Pa", "sr", "Sv", "Wb", 135 | "A", "°C", "C", "F", "g", "H", "J", "K", "m", "N", "s", "S", 136 | "T", "V", "W", "Ω", 137 | ]; 138 | assert_eq!(BARE_UNITS[0].len(), 3); 139 | for prefix in PFX { 140 | let pfx_backtrack = self.buffer_pos(); 141 | if self.accept_all(prefix.chars()) { 142 | for unit in BARE_UNITS { 143 | if self.accept_all(unit.chars()) { 144 | self.extract_string(); // ignore 145 | return Some((prefix.to_string(), unit.to_string())) 146 | } 147 | } 148 | } 149 | self.set_buffer_pos(pfx_backtrack); 150 | } 151 | None 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lexers/src/helpers_test.rs: -------------------------------------------------------------------------------- 1 | use crate::scanner::Scanner; 2 | 3 | #[test] 4 | fn scan_number() { 5 | let tests = vec![ 6 | "987", 7 | "-543", 8 | "435i", 9 | "41.98", 10 | "-83.5", 11 | "-54.3i", 12 | "28e3", 13 | "54E+2", 14 | "54e-33", 15 | "43e0i", 16 | "3E8i", 17 | "-38e3", 18 | "-53e+5", 19 | "-65E-4", 20 | "-32E-4i", 21 | "-33e+2i", 22 | "85.365e3", 23 | "54.234E+2", 24 | "54.849e-33", 25 | "1.4e+2i", 26 | "3.14e-5i", 27 | "-38.657e3", 28 | "53.845e+5", 29 | "65.987E-4", 30 | "-4.4e+2i", 31 | "-6.14e-5i", 32 | ]; 33 | for t in tests.iter() { 34 | let result = Scanner::new(t.chars()).scan_number(); 35 | assert_eq!(Some(t.to_string()), result); 36 | } 37 | } 38 | 39 | #[test] 40 | fn scan_math_ops() { 41 | let tests = vec![ 42 | "<", "<=", "=", "==", ">=", ">", "(", ")", ",", "*", 43 | "**", "^", "!", "+", "-", "/", "%", ":=", 44 | ]; 45 | for t in tests.iter() { 46 | let result = Scanner::new(t.chars()).scan_math_op(); 47 | assert_eq!(Some(t.to_string()), result); 48 | } 49 | // Negative tests 50 | let result = Scanner::new(":".chars()).scan_math_op(); 51 | assert_eq!(result, None); 52 | } 53 | 54 | #[test] 55 | fn scan_identifiers() { 56 | let tests = vec!["id1", "func", "anyword", "_00", "bla23"]; 57 | for t in tests.iter() { 58 | let result = Scanner::new(t.chars()).scan_identifier(); 59 | assert_eq!(Some(t.to_string()), result); 60 | } 61 | } 62 | 63 | #[test] 64 | fn scan_string() { 65 | let tests = vec![ 66 | r"'this is a test'", 67 | r"'another test \' with an escaped quote'", 68 | ]; 69 | for t in tests.iter() { 70 | let result = Scanner::new(t.chars()).scan_quoted_string('\''); 71 | assert_eq!(Some(t.to_string()), result); 72 | } 73 | } 74 | 75 | #[test] 76 | fn scan_units() { 77 | static PFX: &[&str] = &[ 78 | "y", "z", "a", "f", "p", "n", "µ", "m", "c", "d", 79 | "", // no multiplier prefix, raw unit 80 | "da", "h", "k", "M", "G", "T", "P", "E", "Z", "Y" 81 | ]; 82 | static UNITS: &[&str] = &[ 83 | "s", "m", "g", "A", "K", "mol", "cd", 84 | "rad", "sr", "Hz", "N", "Pa", "J", "W", "C", "V", "F", "Ω", "S", 85 | "Wb", "T", "H", "°C", "lm", "lx", "Bq", "Gy", "Sv", "kat", 86 | ]; 87 | for prefix in PFX { 88 | for unit_base in UNITS { 89 | let unit = format!("{}{}", prefix, unit_base); 90 | let result = Scanner::new(unit.chars()).scan_unit(); 91 | assert_eq!(result, Some((prefix.to_string(), unit_base.to_string()))); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lexers/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod helpers; 4 | mod scanner; 5 | pub use crate::scanner::Scanner; 6 | 7 | mod ebnf_tokenizer; 8 | pub use crate::ebnf_tokenizer::EbnfTokenizer; 9 | 10 | mod math_tokenizer; 11 | pub use crate::math_tokenizer::{MathToken, MathTokenizer}; 12 | 13 | mod delim_tokenizer; 14 | pub use crate::delim_tokenizer::DelimTokenizer; 15 | 16 | mod lisp_tokenizer; 17 | pub use crate::lisp_tokenizer::{LispToken, LispTokenizer}; 18 | 19 | #[cfg(test)] 20 | mod scanner_test; 21 | #[cfg(test)] 22 | mod helpers_test; 23 | -------------------------------------------------------------------------------- /lexers/src/lisp_tokenizer.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::scanner::Scanner; 4 | 5 | #[derive(Clone, PartialEq, Debug)] 6 | pub enum LispToken { 7 | OParen, 8 | CParen, 9 | Quote, 10 | QuasiQuote, 11 | UnQuote, 12 | UnQSplice, 13 | True, 14 | False, 15 | Symbol(String), 16 | Number(f64), 17 | String(String), 18 | } 19 | 20 | pub struct LispTokenizer>(Scanner); 21 | 22 | impl> LispTokenizer { 23 | pub fn new(source: I) -> Self { 24 | LispTokenizer(Scanner::new(source)) 25 | } 26 | 27 | pub fn scanner(source: I) -> Scanner { 28 | Scanner::new(Self::new(source)) 29 | } 30 | } 31 | 32 | impl> Iterator for LispTokenizer { 33 | type Item = LispToken; 34 | fn next(&mut self) -> Option { 35 | self.0.scan_whitespace(); 36 | if let Some(s) = self.0.scan_quoted_string('"') { 37 | return Some(LispToken::String(s)); 38 | } 39 | if let Some(lexeme) = self.0.accept_any(&[')', '(', '\'', '`', ',']) { 40 | let token = match lexeme { 41 | '(' => LispToken::OParen, 42 | ')' => LispToken::CParen, 43 | '\'' => LispToken::Quote, 44 | '`' => LispToken::QuasiQuote, 45 | ',' => { 46 | if self.0.accept(&'@').is_some() { 47 | LispToken::UnQSplice 48 | } else { 49 | LispToken::UnQuote 50 | } 51 | } 52 | _ => unreachable!(), 53 | }; 54 | self.0.extract(); // ignore 55 | return Some(token); 56 | } 57 | if self.0.until_any(&[')', ' ', '\n', '\r', '\t']) { 58 | use std::str::FromStr; 59 | let lexeme = self.0.extract_string(); 60 | return match &lexeme[..] { 61 | "#t" => Some(LispToken::True), 62 | "#f" => Some(LispToken::False), 63 | num => match f64::from_str(num) { 64 | Ok(n) => Some(LispToken::Number(n)), 65 | _ => Some(LispToken::Symbol(lexeme)), 66 | }, 67 | }; 68 | } 69 | None 70 | } 71 | } 72 | 73 | /////////////////////////////////////////////////////////////////////////////// 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::{LispToken, LispTokenizer}; 78 | 79 | #[test] 80 | fn lisp_tokenizer() { 81 | use LispToken::*; 82 | let inputs = vec!["(+ 3 4 5)", "(max 'a \"hello\")"]; 83 | let expect = vec![ 84 | vec![ 85 | OParen, 86 | Symbol(format!("+")), 87 | Number(3.0), 88 | Number(4.0), 89 | Number(5.0), 90 | CParen, 91 | ], 92 | vec![ 93 | OParen, 94 | Symbol(format!("max")), 95 | Quote, 96 | Symbol(format!("a")), 97 | String(format!("\"hello\"")), 98 | CParen, 99 | ], 100 | ]; 101 | for (input, expected) in inputs.iter().zip(expect.iter()) { 102 | let mut lx = LispTokenizer::new(input.chars()); 103 | for exp in expected.iter() { 104 | assert_eq!(*exp, lx.next().unwrap()); 105 | } 106 | assert_eq!(lx.next(), None); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lexers/src/math_tokenizer.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::scanner::Scanner; 4 | 5 | #[derive(Clone, PartialEq, Debug)] 6 | pub enum MathToken { 7 | Unknown(String), 8 | Number(f64), 9 | Quantity(f64, String, String), 10 | Variable(String), 11 | Function(String, usize), // arity 12 | UOp(String), 13 | BOp(String), 14 | OParen, 15 | CParen, 16 | Comma, 17 | } 18 | 19 | pub struct MathTokenizer> { 20 | src: Scanner, 21 | prev: Option, 22 | } 23 | 24 | impl> MathTokenizer { 25 | pub fn new(source: I) -> Self { 26 | MathTokenizer { 27 | src: Scanner::new(source), 28 | prev: None, 29 | } 30 | } 31 | 32 | pub fn scanner(source: I) -> Scanner { 33 | Scanner::new(Self::new(source)) 34 | } 35 | 36 | // when would a minus be unary? we need to know the prev token 37 | fn makes_unary(prev: &Option) -> bool { 38 | !matches!(*prev, 39 | Some(MathToken::Number(_)) | 40 | Some(MathToken::Variable(_)) | 41 | Some(MathToken::CParen)) 42 | } 43 | 44 | fn get_token(&mut self) -> Option { 45 | self.src.scan_whitespace(); // discard whatever came before + and spaces 46 | if let Some(op) = self.src.scan_math_op() { 47 | return match op.as_ref() { 48 | "(" => Some(MathToken::OParen), 49 | ")" => Some(MathToken::CParen), 50 | "," => Some(MathToken::Comma), 51 | "!" => Some(MathToken::UOp(op)), 52 | "-" if Self::makes_unary(&self.prev) => Some(MathToken::UOp(op)), 53 | _ => Some(MathToken::BOp(op)), 54 | }; 55 | } 56 | if let Some(id) = self.src.scan_identifier() { 57 | return match self.src.peek() { 58 | Some('(') => Some(MathToken::Function(id, 0)), 59 | _ => Some(MathToken::Variable(id)), 60 | }; 61 | } 62 | if let Some(num) = self.src.scan_number() { 63 | self.src.scan_whitespace(); // discard whatever came before + and spaces 64 | use std::str::FromStr; 65 | let value = f64::from_str(&num).unwrap(); 66 | if let Some((prefix, unit)) = self.src.scan_unit() { 67 | return Some(MathToken::Quantity(value, prefix, unit)); 68 | } 69 | return Some(MathToken::Number(value)); 70 | } 71 | if self.src.next().is_some() { 72 | return Some(MathToken::Unknown(self.src.extract_string())); 73 | } 74 | None 75 | } 76 | } 77 | 78 | impl> Iterator for MathTokenizer { 79 | type Item = MathToken; 80 | fn next(&mut self) -> Option { 81 | let token = self.get_token(); 82 | self.prev = token.clone(); 83 | token 84 | } 85 | } 86 | 87 | /////////////////////////////////////////////////////////////////////////////// 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::{MathToken::*, MathTokenizer}; 92 | 93 | #[test] 94 | fn basic_ops() { 95 | let mut lx = MathTokenizer::new("3+4*2/-(1-5)^2^3".chars()); 96 | let expect = [ 97 | Number(3.0), 98 | BOp("+".to_string()), 99 | Number(4.0), 100 | BOp("*".to_string()), 101 | Number(2.0), 102 | BOp("/".to_string()), 103 | UOp("-".to_string()), 104 | OParen, 105 | Number(1.0), 106 | BOp("-".to_string()), 107 | Number(5.0), 108 | CParen, 109 | BOp("^".to_string()), 110 | Number(2.0), 111 | BOp("^".to_string()), 112 | Number(3.0), 113 | ]; 114 | for exp_token in expect.iter() { 115 | let token = lx.next().unwrap(); 116 | assert_eq!(*exp_token, token); 117 | } 118 | assert_eq!(lx.next(), None); 119 | 120 | let mut lx = MathTokenizer::new("x := a + b".chars()); 121 | let expect = [ 122 | Variable("x".to_string()), 123 | BOp(":=".to_string()), 124 | Variable("a".to_string()), 125 | BOp("+".to_string()), 126 | Variable("b".to_string()), 127 | ]; 128 | for exp_token in expect.iter() { 129 | let token = lx.next().unwrap(); 130 | assert_eq!(*exp_token, token); 131 | } 132 | assert_eq!(lx.next(), None); 133 | } 134 | 135 | #[test] 136 | fn mixed_ops() { 137 | let mut lx = MathTokenizer::new("3.4e-2 * sin(x)/(7! % -4) * max(2, x)".chars()); 138 | let expect = [ 139 | Number(3.4e-2), 140 | BOp("*".to_string()), 141 | Function("sin".to_string(), 0), 142 | OParen, 143 | Variable("x".to_string()), 144 | CParen, 145 | BOp("/".to_string()), 146 | OParen, 147 | Number(7.0), 148 | UOp("!".to_string()), 149 | BOp("%".to_string()), 150 | UOp("-".to_string()), 151 | Number(4.0), 152 | CParen, 153 | BOp("*".to_string()), 154 | Function("max".to_string(), 0), 155 | OParen, 156 | Number(2.0), 157 | Comma, 158 | Variable("x".to_string()), 159 | CParen, 160 | ]; 161 | for exp_token in expect.iter() { 162 | let token = lx.next().unwrap(); 163 | assert_eq!(*exp_token, token); 164 | } 165 | assert_eq!(lx.next(), None); 166 | } 167 | 168 | #[test] 169 | fn unary_ops() { 170 | let mut lx = MathTokenizer::new("x---y".chars()); 171 | let expect = [ 172 | Variable("x".to_string()), 173 | BOp("-".to_string()), 174 | UOp("-".to_string()), 175 | UOp("-".to_string()), 176 | Variable("y".to_string()), 177 | ]; 178 | for exp_token in expect.iter() { 179 | let token = lx.next().unwrap(); 180 | assert_eq!(*exp_token, token); 181 | } 182 | assert_eq!(lx.next(), None); 183 | } 184 | 185 | #[test] 186 | fn quantity() { 187 | let mut lx = MathTokenizer::new("30km / (10 s) * 20g * 3 GHz".chars()); 188 | let expect = [ 189 | Quantity(30.0, "k".to_string(), "m".to_string()), 190 | BOp("/".to_string()), 191 | OParen, 192 | Quantity(10.0, "".to_string(), "s".to_string()), 193 | CParen, 194 | BOp("*".to_string()), 195 | Quantity(20.0, "".to_string(), "g".to_string()), 196 | BOp("*".to_string()), 197 | Quantity(3.0, "G".to_string(), "Hz".to_string()), 198 | ]; 199 | for exp_token in expect.iter() { 200 | let token = lx.next().unwrap(); 201 | assert_eq!(*exp_token, token); 202 | } 203 | assert_eq!(lx.next(), None); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /lexers/src/scanner.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | pub struct Scanner 4 | where 5 | I::Item: Clone, 6 | { 7 | src: I, 8 | buf: Vec, 9 | pos: isize, 10 | } 11 | 12 | // Scanners are Iterators 13 | impl Iterator for Scanner 14 | where 15 | I: Iterator, 16 | I::Item: Clone, 17 | { 18 | type Item = I::Item; 19 | fn next(&mut self) -> Option { 20 | self.pos += 1; 21 | // Check if we need to fill the buffer 22 | let lacking = self.pos - (self.buf.len() as isize) + 1; 23 | if lacking > 0 { 24 | self.buf.extend(self.src.by_ref().take(lacking as usize)); 25 | } 26 | // limit the buffer position to the buffer length at most 27 | self.pos = std::cmp::min(self.pos, self.buf.len() as isize); 28 | self.current() 29 | } 30 | } 31 | 32 | impl Scanner 33 | where 34 | I: Iterator, 35 | I::Item: Clone, 36 | { 37 | pub fn new(source: I) -> Scanner { 38 | Scanner { 39 | src: source, 40 | buf: Vec::new(), 41 | pos: -1, 42 | } 43 | } 44 | 45 | // Allows getting current buffer position to backtrack 46 | pub fn buffer_pos(&self) -> isize { 47 | self.pos 48 | } 49 | 50 | // Reset buffer position, normally used for backtracking 51 | // If position is out of bounds set_buffer_pos returns false 52 | pub fn set_buffer_pos(&mut self, pos: isize) -> bool { 53 | if pos < -1 || pos > (self.buf.len() as isize) { 54 | return false; 55 | } 56 | self.pos = pos; 57 | true 58 | } 59 | 60 | // Returns the current token on which the scanner is positioned 61 | pub fn current(&self) -> Option { 62 | let pos = self.pos as usize; 63 | if self.pos < 0 || pos >= self.buf.len() { 64 | return None; 65 | } 66 | Some(self.buf[pos].clone()) 67 | } 68 | 69 | // Steps the scanner back and returns the token at that position 70 | pub fn prev(&mut self) -> Option { 71 | if self.pos >= 0 { 72 | self.pos -= 1; 73 | } 74 | self.current() 75 | } 76 | 77 | // Returns the token ahead without actually advancing the scanner 78 | pub fn peek(&mut self) -> Option { 79 | let backtrack = self.pos; 80 | let peeked = self.next(); 81 | self.pos = backtrack; 82 | peeked 83 | } 84 | 85 | // Returns the previous token without actually backtracking the scanner 86 | pub fn peek_prev(&mut self) -> Option { 87 | let backtrack = self.pos; 88 | let peeked = self.prev(); 89 | self.pos = backtrack; 90 | peeked 91 | } 92 | 93 | // Returns a view of the current underlying buffer 94 | pub fn view(&self) -> &[I::Item] { 95 | let n = (self.pos + 1) as usize; 96 | &self.buf[..n] 97 | } 98 | 99 | // Consumes the buffer into a new token (which can be ignored) 100 | pub fn extract(&mut self) -> Vec { 101 | // Check where to shift buffer 102 | let split_point = std::cmp::min(self.pos + 1, self.buf.len() as isize); 103 | assert!(split_point >= 0); 104 | // Reset buffer cursor 105 | self.pos = -1; 106 | // Split buffer and keep the remainder 107 | let mut remaining = self.buf.split_off(split_point as usize); 108 | std::mem::swap(&mut self.buf, &mut remaining); 109 | remaining 110 | } 111 | } 112 | 113 | impl Scanner 114 | where 115 | I: Iterator, 116 | I::Item: Clone + PartialEq, 117 | { 118 | // Advance the scanner only if the next char is the expected one 119 | // self.current() will return the matched char if accept matched 120 | pub fn accept(&mut self, what: &I::Item) -> Option { 121 | let backtrack = self.buffer_pos(); 122 | if let Some(next) = self.next() { 123 | if &next == what { 124 | return Some(next); 125 | } 126 | } 127 | self.set_buffer_pos(backtrack); 128 | None 129 | } 130 | 131 | // Advance the scanner only if the next char is in the 'any' set, 132 | // self.current() will return the matched char if accept matched any 133 | pub fn accept_any(&mut self, any: &[I::Item]) -> Option { 134 | let backtrack = self.buffer_pos(); 135 | if let Some(next) = self.next() { 136 | if any.contains(&next) { 137 | return Some(next); 138 | } 139 | } 140 | self.set_buffer_pos(backtrack); 141 | None 142 | } 143 | 144 | // Advance the scanner only if a full match for items form 'what'. 145 | // self.current() will return the last item from 'what' 146 | pub fn accept_all(&mut self, what: impl Iterator) -> bool { 147 | let backtrack = self.buffer_pos(); 148 | for item in what { 149 | if self.accept(&item).is_none() { 150 | self.set_buffer_pos(backtrack); 151 | return false; 152 | } 153 | } 154 | true 155 | } 156 | 157 | // Skip over the 'over' set, result is if the scanner was advanced, 158 | // self.current() will return the last matching char 159 | pub fn skip_all(&mut self, over: &[I::Item]) -> bool { 160 | let mut advanced = false; 161 | while self.accept_any(over).is_some() { 162 | advanced = true; 163 | } 164 | advanced 165 | } 166 | 167 | // Find an element in the 'any' set or EOF, return if the scanner advanced, 168 | // self.current() returns the last non-matching char 169 | pub fn until_any(&mut self, any: &[I::Item]) -> bool { 170 | let mut advanced = false; 171 | while let Some(next) = self.peek() { 172 | if any.contains(&next) { 173 | break; 174 | } 175 | self.next(); 176 | advanced = true; 177 | } 178 | advanced 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lexers/src/scanner_test.rs: -------------------------------------------------------------------------------- 1 | use crate::scanner::Scanner; 2 | 3 | #[test] 4 | fn extremes() { 5 | let mut s = Scanner::new("just a test buffer@".chars()); 6 | assert_eq!(s.prev(), None); 7 | assert_eq!(s.peek_prev(), None); 8 | assert_eq!(s.next(), Some('j')); 9 | assert_eq!(s.prev(), None); 10 | while s.next() != Some('@') {} 11 | assert_eq!(s.current(), Some('@')); 12 | assert_eq!(s.peek_prev(), Some('r')); 13 | assert_eq!(s.prev(), Some('r')); 14 | assert_eq!(s.prev(), Some('e')); 15 | assert_eq!(s.next(), Some('r')); 16 | assert_eq!(s.next(), Some('@')); 17 | assert_eq!(s.next(), None); 18 | } 19 | 20 | #[test] 21 | fn extract() { 22 | let mut s = Scanner::new("just a test buffer@".chars()); 23 | assert_eq!(s.extract(), Vec::new()); 24 | for _ in 0..4 { 25 | assert!(s.next().is_some()); 26 | } 27 | assert_eq!(s.extract().into_iter().collect::(), "just"); 28 | assert_eq!(s.peek(), Some(' ')); 29 | assert_eq!(s.prev(), None); 30 | assert_eq!(s.next(), Some(' ')); 31 | for _ in 0..6 { 32 | assert!(s.next().is_some()); 33 | } 34 | assert_eq!(s.extract_string(), " a test"); 35 | assert_eq!(s.next(), Some(' ')); 36 | assert_eq!(s.peek_prev(), None); 37 | for _ in 0..7 { 38 | assert!(s.next().is_some()); 39 | } 40 | assert_eq!(s.extract_string(), " buffer@"); 41 | s.next(); 42 | assert_eq!(s.extract(), Vec::new()); 43 | } 44 | 45 | #[test] 46 | fn accept() { 47 | static WHITE: &[char] = &[' ', '\n', '\r', '\t']; 48 | let mut s = Scanner::new("heey you!".chars()); 49 | assert!(!s.skip_all(WHITE)); 50 | assert_eq!(s.prev(), None); 51 | assert_eq!(s.accept_any(&['h', 'e']), Some('h')); 52 | assert_eq!(s.current(), Some('h')); 53 | assert_eq!(s.accept_any(&['h', 'e']), Some('e')); 54 | assert_eq!(s.current(), Some('e')); 55 | assert_eq!(s.accept_any(&['h', 'y', 'e']), Some('e')); 56 | assert_eq!(s.accept_any(&['e']), None); 57 | assert_eq!(s.accept_any(&['h', 'e', 'y']), Some('y')); 58 | assert!(s.skip_all(WHITE)); 59 | assert!(!s.skip_all(WHITE)); 60 | assert_eq!(s.current(), Some(' ')); 61 | assert_eq!(s.peek(), Some('y')); 62 | assert_eq!(s.next(), Some('y')); 63 | assert_eq!(s.next(), Some('o')); 64 | } 65 | 66 | #[test] 67 | fn accept_all() { 68 | let mut s = Scanner::new("12.3 hPa".chars()); 69 | assert!(s.accept_all("12.3".chars())); 70 | assert_eq!(s.current(), Some('3')); 71 | assert_eq!(s.next(), Some(' ')); 72 | s.extract(); 73 | assert!(!s.accept_all("hXa".chars())); 74 | assert!(s.accept_all("hPa".chars())); 75 | assert_eq!(s.current(), Some('a')); 76 | assert_eq!(s.extract_string(), "hPa"); 77 | } 78 | 79 | #[test] 80 | fn skips() { 81 | let mut s = Scanner::new("heey you!".chars()); 82 | assert_eq!(s.accept_any(&['h']), Some('h')); 83 | assert!(s.skip_all(&['h', 'e', 'y'])); 84 | assert!(!s.skip_all(&['h', 'e', 'y'])); 85 | assert_eq!(s.current(), Some('y')); 86 | assert!(s.until_any(&['!'])); 87 | assert!(!s.until_any(&['!'])); 88 | assert_eq!(s.accept_any(&['!']), Some('!')); 89 | assert_eq!(s.next(), None); 90 | assert_eq!(s.current(), None); 91 | } 92 | -------------------------------------------------------------------------------- /lisp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lisp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Rodolfo Granata "] 6 | description = "A lisp like thingy" 7 | 8 | [dependencies] 9 | lexers = { version = "0.1", path = "../lexers" } 10 | rustyline = "9.1" 11 | -------------------------------------------------------------------------------- /lisp/README.md: -------------------------------------------------------------------------------- 1 | # lisp 2 | A lisp-like interpreter following norvig's lispy notes. 3 | 4 | Try out the **lisp** binary. 5 | 6 | ### lisp references 7 | * http://norvig.com/lispy.html 8 | * http://norvig.com/lispy2.html 9 | -------------------------------------------------------------------------------- /lisp/src/TODO: -------------------------------------------------------------------------------- 1 | - check visibility (pub) 2 | - check builtins 3 | - all cloning should be done on the caller if necesary 4 | - remove all cloning -> see were we can move / refer 5 | - implement quoting 6 | - Check for NotImplemented / TODOs 7 | -------------------------------------------------------------------------------- /lisp/src/bin/lisp.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(test))] 2 | fn main() { 3 | use std::rc::Rc; 4 | use lisp::{LispContext, Parser}; 5 | let cx = Rc::new(LispContext::new()); 6 | let mut rl = rustyline::Editor::<()>::new(); 7 | while let Ok(input) = rl.readline("~> ") { 8 | rl.add_history_entry(&input); 9 | match Parser::parse_str(&input[..]) { 10 | Err(e) => println!("Parse error: {:?}", e), 11 | Ok(exp) => match LispContext::eval(&exp, &cx) { 12 | Err(e) => println!("Eval error: {:?}", e), 13 | Ok(res) => println!("{}", res.to_string()) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lisp/src/builtin.rs: -------------------------------------------------------------------------------- 1 | use crate::eval::EvalErr; 2 | use crate::parser::LispExpr; 3 | use crate::procedure::Procedure; 4 | use std::collections::HashMap; 5 | use std::{ops, cmp}; 6 | use std::rc::Rc; 7 | 8 | macro_rules! builtin { 9 | ($fnexpr:expr) => { 10 | LispExpr::Proc(Rc::new(Procedure::builtin(Rc::new($fnexpr)))) 11 | } 12 | } 13 | 14 | fn foldop(op: T, args: &Vec) -> Result 15 | where T: Fn(f64, f64) -> f64 { 16 | let base = match args.first() { 17 | Some(&LispExpr::Number(n)) => n, 18 | _ => return Err(EvalErr::InvalidExpr) 19 | }; 20 | let mut rest = Vec::new(); 21 | for arg in args.iter().skip(1) { 22 | match arg { 23 | &LispExpr::Number(n) => rest.push(n), 24 | _ => return Err(EvalErr::InvalidExpr) 25 | } 26 | } 27 | Ok(LispExpr::Number(rest.iter().fold(base, |ac, &item| op(ac, item)))) 28 | } 29 | 30 | fn foldcmp(op: T, args: &Vec) -> Result 31 | where T: Fn(&LispExpr, &LispExpr) -> bool { 32 | if args.len() < 2 { 33 | return Err(EvalErr::InvalidExpr); 34 | } 35 | match args[..].windows(2).all(|win| op(&win[0], &win[1])) { 36 | true => Ok(LispExpr::True), 37 | false => Ok(LispExpr::False), 38 | } 39 | } 40 | 41 | fn first(args: &Vec) -> Result { 42 | match args.first() { 43 | Some(&LispExpr::List(ref l)) if l.len() > 0 => 44 | Ok(l.first().unwrap().clone()), 45 | _ => Err(EvalErr::InvalidExpr) 46 | } 47 | } 48 | 49 | fn tail(args: &Vec) -> Result { 50 | match args.first() { 51 | Some(&LispExpr::List(ref l)) => 52 | Ok(LispExpr::List(l.iter().skip(1).cloned().collect())), 53 | _ => Err(EvalErr::InvalidExpr) 54 | } 55 | } 56 | 57 | fn cons(args: &Vec) -> Result { 58 | if args.len() != 2 { return Err(EvalErr::InvalidExpr); } 59 | match args[1] { 60 | LispExpr::List(ref b) => { 61 | let mut a = vec![args[0].clone()]; 62 | a.extend(b.clone()); 63 | Ok(LispExpr::List(a)) 64 | }, 65 | _ => Ok(LispExpr::List(vec![args[0].clone(), args[1].clone()])) 66 | } 67 | } 68 | 69 | pub fn builtins() -> HashMap { 70 | let mut builtins: HashMap = HashMap::new(); 71 | 72 | builtins.insert(format!("+"), builtin!(|args| foldop(ops::Add::add, &args))); 73 | builtins.insert(format!("-"), builtin!(|args| match args.len() { 74 | 1 => match args.first() { // special handling of negation op 75 | Some(&LispExpr::Number(n)) => Ok(LispExpr::Number(-n)), 76 | _ => Err(EvalErr::InvalidExpr) 77 | }, 78 | _ => foldop(ops::Sub::sub, &args) 79 | })); 80 | builtins.insert(format!("*"), builtin!(|args| foldop(ops::Mul::mul, &args))); 81 | builtins.insert(format!("/"), builtin!(|args| foldop(ops::Div::div, &args))); 82 | builtins.insert(format!("%"), builtin!(|args| foldop(ops::Rem::rem, &args))); 83 | builtins.insert(format!("<"), builtin!(|args| foldcmp(cmp::PartialOrd::lt, &args))); 84 | builtins.insert(format!("<="), builtin!(|args| foldcmp(cmp::PartialOrd::le, &args))); 85 | builtins.insert(format!(">"), builtin!(|args| foldcmp(cmp::PartialOrd::gt, &args))); 86 | builtins.insert(format!(">="), builtin!(|args| foldcmp(cmp::PartialOrd::ge, &args))); 87 | builtins.insert(format!("="), builtin!(|args| foldcmp(cmp::PartialEq::eq, &args))); 88 | builtins.insert(format!("!="), builtin!(|args| foldcmp(cmp::PartialEq::ne, &args))); 89 | builtins.insert(format!("first"), builtin!(|args| first(&args))); 90 | builtins.insert(format!("tail"), builtin!(|args| tail(&args))); 91 | builtins.insert(format!("cons"), builtin!(|args| cons(&args))); 92 | builtins.insert(format!("list"), builtin!(|args| Ok(LispExpr::List(args.clone())))); 93 | builtins.insert(format!("length"), builtin!(|args| match args.first() { 94 | Some(&LispExpr::String(ref s)) => Ok(LispExpr::Number(s.len() as f64)), 95 | Some(&LispExpr::List(ref list)) => Ok(LispExpr::Number(list.len() as f64)), 96 | _ => Err(EvalErr::InvalidExpr) 97 | })); 98 | builtins.insert(format!("number?"), builtin!(|args| match args.first() { 99 | Some(&LispExpr::Number(_)) => Ok(LispExpr::True), _ => Ok(LispExpr::False) 100 | })); 101 | builtins.insert(format!("list?"), builtin!(|args| match args.first() { 102 | Some(&LispExpr::List(_)) => Ok(LispExpr::True), _ => Ok(LispExpr::False) 103 | })); 104 | builtins.insert(format!("symbol?"), builtin!(|args| match args.first() { 105 | Some(&LispExpr::Symbol(_)) => Ok(LispExpr::True), _ => Ok(LispExpr::False) 106 | })); 107 | builtins.insert(format!("procedure?"), builtin!(|args| match args.first() { 108 | Some(&LispExpr::Proc(_)) => Ok(LispExpr::True), _ => Ok(LispExpr::False) 109 | })); 110 | builtins.insert(format!("null?"), builtin!(|args| match args.first() { 111 | Some(&LispExpr::List(ref list)) if list.len() == 0 => Ok(LispExpr::True), 112 | _ => Ok(LispExpr::False) 113 | })); 114 | builtins.insert(format!("begin"), builtin!(|args| match args.last() { 115 | Some(expr) => Ok(expr.clone()), 116 | _ => Err(EvalErr::InvalidExpr) 117 | })); 118 | builtins 119 | } 120 | -------------------------------------------------------------------------------- /lisp/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod eval; 2 | mod procedure; 3 | mod builtin; 4 | 5 | pub use crate::parser::{Parser, LispExpr, ParseError}; 6 | pub use crate::eval::{LispContext, EvalErr}; 7 | pub use crate::procedure::Procedure; 8 | pub use crate::builtin::builtins; 9 | 10 | mod parser; 11 | #[cfg(test)] 12 | mod parser_test; 13 | -------------------------------------------------------------------------------- /lisp/src/parser.rs: -------------------------------------------------------------------------------- 1 | use lexers::{Scanner, LispToken, LispTokenizer}; 2 | use crate::procedure::Procedure; 3 | use std::string; 4 | use std::rc::Rc; 5 | 6 | #[derive(PartialEq, Debug)] 7 | pub enum ParseError { 8 | UnexpectedCParen, 9 | UnexpectedEOF, 10 | NotImplemented, 11 | } 12 | 13 | #[derive(Clone, PartialEq, PartialOrd, Debug)] 14 | pub enum LispExpr { 15 | List(Vec), 16 | String(String), 17 | Symbol(String), 18 | Number(f64), 19 | True, False, 20 | Proc(Rc), 21 | Quote(Box), 22 | QuasiQuote(Box), 23 | UnQuote(Box), 24 | UnQSplice(Box), 25 | } 26 | 27 | impl string::ToString for LispExpr { 28 | fn to_string(&self) -> String { 29 | match self { 30 | &LispExpr::Symbol(ref s) => s.clone(), 31 | &LispExpr::String(ref s) => s.clone(), 32 | &LispExpr::Number(n) => format!("{}", n), 33 | &LispExpr::List(ref v) => { 34 | let base = match v.first() { 35 | Some(expr) => expr.to_string(), 36 | None => String::new() 37 | }; 38 | format!("({})", v.iter().skip(1) 39 | .fold(base, |a, ref it| 40 | format!("{} {}", a, it.to_string()))) 41 | }, 42 | &LispExpr::True => format!("#t"), 43 | &LispExpr::False => format!("#f"), 44 | &LispExpr::Proc(ref p) => format!("{:?}", *p), 45 | &LispExpr::Quote(ref e) => format!("'{}", e.to_string()), 46 | &LispExpr::QuasiQuote(ref e) => format!("`{}", e.to_string()), 47 | &LispExpr::UnQuote(ref e) => format!(",{}", e.to_string()), 48 | &LispExpr::UnQSplice(ref e) => format!(",@{}", e.to_string()), 49 | } 50 | } 51 | } 52 | 53 | 54 | pub struct Parser; 55 | 56 | impl Parser { 57 | pub fn parse_str(expr: &str) -> Result { 58 | Self::parse(&mut LispTokenizer::scanner(expr.chars())) 59 | } 60 | 61 | fn parse(lex: &mut Scanner>) 62 | -> Result 63 | where I: Iterator 64 | { 65 | match lex.next() { 66 | None => Err(ParseError::UnexpectedEOF), 67 | Some(LispToken::CParen) => Err(ParseError::UnexpectedCParen), 68 | Some(LispToken::True) => Ok(LispExpr::True), 69 | Some(LispToken::False) => Ok(LispExpr::False), 70 | Some(LispToken::String(n)) => Ok(LispExpr::String(n)), 71 | Some(LispToken::Number(n)) => Ok(LispExpr::Number(n)), 72 | Some(LispToken::Symbol(s)) => Ok(LispExpr::Symbol(s)), 73 | Some(LispToken::OParen) => { 74 | let mut list = Vec::new(); 75 | while lex.peek() != Some(LispToken::CParen) { // even when != None 76 | match Parser::parse(lex) { 77 | Err(err) => return Err(err), 78 | Ok(expr) => list.push(expr), 79 | } 80 | } 81 | lex.next(); // get over that CParen 82 | Ok(LispExpr::List(list)) 83 | }, 84 | Some(LispToken::Quote) => { 85 | let expr = Parser::parse(lex)?; 86 | Ok(LispExpr::Quote(Box::new(expr))) 87 | }, 88 | Some(LispToken::QuasiQuote) => { 89 | let expr = Parser::parse(lex)?; 90 | Ok(LispExpr::QuasiQuote(Box::new(expr))) 91 | }, 92 | Some(LispToken::UnQuote) => { 93 | let expr = Parser::parse(lex)?; 94 | Ok(LispExpr::UnQuote(Box::new(expr))) 95 | }, 96 | Some(LispToken::UnQSplice) => { 97 | let expr = Parser::parse(lex)?; 98 | Ok(LispExpr::UnQSplice(Box::new(expr))) 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lisp/src/parser_test.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{Parser, LispExpr}; 2 | 3 | #[test] 4 | fn test_lisp1() { 5 | let p = Parser::parse_str("(begin (define r 10) (* pi (* r r)))"); 6 | let r = LispExpr::List(vec![ 7 | LispExpr::Symbol(format!("begin")), 8 | LispExpr::List(vec![ 9 | LispExpr::Symbol(format!("define")), 10 | LispExpr::Symbol(format!("r")), 11 | LispExpr::Number(10.0), 12 | ]), 13 | LispExpr::List(vec![ 14 | LispExpr::Symbol(format!("*")), 15 | LispExpr::Symbol(format!("pi")), 16 | LispExpr::List(vec![ 17 | LispExpr::Symbol(format!("*")), 18 | LispExpr::Symbol(format!("r")), 19 | LispExpr::Symbol(format!("r")), 20 | ]), 21 | ]), 22 | ]); 23 | assert_eq!(p.unwrap(), r); 24 | } 25 | -------------------------------------------------------------------------------- /lisp/src/procedure.rs: -------------------------------------------------------------------------------- 1 | use crate::eval::{EvalErr, LispContext}; 2 | use crate::parser::LispExpr; 3 | use std::{fmt, cmp}; 4 | use std::rc::Rc; 5 | 6 | pub type Fp = Rc) -> Result>; 7 | 8 | enum Body { 9 | Lisp(LispExpr), 10 | Builtin(Fp), 11 | } 12 | 13 | pub struct Procedure { 14 | params: Vec, 15 | body: Body, 16 | env: Option>, 17 | } 18 | 19 | impl cmp::PartialEq for Procedure { 20 | fn eq(&self, _other: &Procedure) -> bool { false } 21 | } 22 | 23 | impl cmp::PartialOrd for Procedure { 24 | fn partial_cmp(&self, _other: &Procedure) -> Option { None } 25 | } 26 | 27 | impl fmt::Debug for Procedure { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | write!(f, "(lambda ({:?}) ...)", self.params) 30 | } 31 | } 32 | 33 | impl Procedure { 34 | pub fn new(params: Vec, body: LispExpr, env: Rc) -> Procedure { 35 | Procedure{params: params, body: Body::Lisp(body), env: Some(env)} 36 | } 37 | 38 | pub fn builtin(fp: Fp) -> Procedure { 39 | Procedure{params: Vec::new(), body: Body::Builtin(fp), env: None} 40 | } 41 | 42 | pub fn call(&self, args: Vec) -> Result { 43 | match self.body { 44 | Body::Builtin(ref fp) => fp(&args), 45 | Body::Lisp(ref expr) => { 46 | let env = LispContext::nested(self.params.clone(), args, self.env.clone()); 47 | LispContext::eval(expr, &Rc::new(env)) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lox" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Rodolfo Granata "] 6 | 7 | [dependencies] 8 | lexers = { version = "0.1", path = "../lexers" } 9 | time = "0.3" 10 | -------------------------------------------------------------------------------- /lox/src/lox_environment.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::collections::HashMap; 4 | use crate::lox_interpreter::V; 5 | use std::cell::RefCell; 6 | use std::rc::Rc; 7 | 8 | 9 | pub struct Environment { 10 | values: HashMap, 11 | parent: Option>>, 12 | } 13 | 14 | impl Environment { 15 | pub fn new(parent: Option>>) -> Self { 16 | Environment{values: HashMap::new(), parent} 17 | } 18 | 19 | fn ancestor(&self, depth: usize) -> Option>> { 20 | let mut ancestor = self.parent.clone(); 21 | for _ in 1..depth { 22 | ancestor = match ancestor { 23 | Some(a) => a.borrow().parent.clone(), 24 | None => return None 25 | } 26 | } 27 | ancestor 28 | } 29 | 30 | pub fn define>(&mut self, name: S, val: V) { 31 | self.values.insert(name.into(), val); 32 | } 33 | 34 | pub fn get(&self, name: &str) -> Result { 35 | if self.values.contains_key(name) { 36 | return Ok(self.values.get(name).unwrap().clone()); 37 | } else if let Some(ref enc) = self.parent { 38 | return enc.borrow().get(name); 39 | } 40 | Err(format!("Environment get - undefined entity '{}'", name)) 41 | } 42 | 43 | pub fn get_at(&self, depth: usize, name: &str) -> Result { 44 | match depth > 0 { 45 | false => Ok(self.values.get(name).unwrap().clone()), 46 | true => match self.ancestor(depth) { 47 | None => panic!("Resolver Bug! wrong env depth {}", depth), 48 | Some(env) => match env.borrow().values.get(name) { 49 | Some(value) => Ok(value.clone()), 50 | _ => Err(format!( 51 | "Environment get_at - undefined entity '{}' depth {}", 52 | name, depth)) 53 | } 54 | } 55 | } 56 | } 57 | 58 | pub fn assign(&mut self, name: String, val: V) -> Result { 59 | if self.values.contains_key(&name) { 60 | self.values.insert(name, val.clone()); 61 | return Ok(val) 62 | } else if let Some(ref mut enc) = self.parent { 63 | return enc.borrow_mut().assign(name, val); 64 | } 65 | Err(format!("Environment assign - undefined entity '{}'", name)) 66 | } 67 | 68 | pub fn assign_at(&mut self, depth: usize, 69 | name: String, val: V) -> Result { 70 | match depth > 0 { 71 | false => if self.values.contains_key(&name) { 72 | self.values.insert(name, val.clone()); 73 | return Ok(val); 74 | }, 75 | true => match self.ancestor(depth) { 76 | None => panic!("Resolver Bug! wrong env depth {}", depth), 77 | Some(env) => if env.borrow().values.contains_key(&name) { 78 | env.borrow_mut().values.insert(name, val.clone()); 79 | return Ok(val); 80 | } 81 | } 82 | } 83 | Err(format!("Environment assign_at - undefined entity '{}'", name)) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lox/src/lox_native.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::lox_environment::Environment; 4 | use crate::lox_interpreter::{Callable, LoxInterpreter, V}; 5 | use std::rc::Rc; 6 | 7 | pub struct Clock; 8 | 9 | impl Callable for Clock { 10 | fn call(&self, _: &mut LoxInterpreter, _: &[V]) -> Result { 11 | Ok(V::Num( 12 | (time::OffsetDateTime::now_utc() - time::OffsetDateTime::UNIX_EPOCH) 13 | .whole_nanoseconds() as f64)) 14 | } 15 | fn arity(&self) -> usize { 0 } 16 | fn id(&self) -> String { "clock".to_string() } 17 | } 18 | 19 | pub fn native_fn_env() -> Environment { 20 | let mut environment = Environment::new(None); 21 | environment.define("clock", V::Callable(Rc::new(Clock))); 22 | environment 23 | } 24 | -------------------------------------------------------------------------------- /lox/src/lox_resolver.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use crate::lox_interpreter::LoxInterpreter; 4 | use crate::lox_parser::{Expr, Stmt}; 5 | use std::collections::HashMap; 6 | 7 | type ResolveResult = Result<(), String>; 8 | 9 | pub struct Resolver<'a> { 10 | interpreter: &'a mut LoxInterpreter, 11 | // tracks if variable name is defined or just declared 12 | scopes: Vec>, 13 | } 14 | 15 | impl<'a> Resolver<'a> { 16 | pub fn new(interp: &mut LoxInterpreter) -> Resolver { 17 | Resolver { 18 | interpreter: interp, 19 | scopes: Vec::new(), 20 | } 21 | } 22 | 23 | pub fn resolve(&mut self, stmts: &[Stmt]) -> ResolveResult { 24 | stmts 25 | .iter() 26 | .map(|stmt| self.resolve_stmt(stmt)) 27 | .find(|stmt| stmt.is_err()) 28 | .unwrap_or(Ok(())) 29 | } 30 | 31 | fn begin_scope(&mut self) { 32 | self.scopes.push(HashMap::new()); 33 | } 34 | 35 | fn end_scope(&mut self) { 36 | self.scopes.pop(); 37 | } 38 | 39 | fn declare(&mut self, token: String) -> ResolveResult { 40 | use std::collections::hash_map::Entry::*; 41 | if let Some(scope) = self.scopes.last_mut() { 42 | match scope.entry(token.clone()) { 43 | Occupied(_) => return Err(format!("Var {} already declared in scope", token)), 44 | Vacant(spot) => { 45 | spot.insert(false); 46 | } 47 | } 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn define(&mut self, token: String) -> ResolveResult { 53 | if let Some(scope) = self.scopes.last_mut() { 54 | scope.insert(token, true); 55 | } 56 | Ok(()) 57 | } 58 | 59 | fn resolve_local(&mut self, expr: &Expr, name: &str) -> ResolveResult { 60 | // find the scope that contains the name 61 | let scope = self 62 | .scopes 63 | .iter() 64 | .rev() 65 | .enumerate() 66 | .find(|&(_, scope)| scope.contains_key(name)); 67 | // bind the interpreter's reference to that scope 68 | if let Some((idx, _)) = scope { 69 | self.interpreter.resolve(expr.id(), idx); 70 | } 71 | Ok(()) 72 | } 73 | 74 | fn resolve_function(&mut self, params: &[String], body: &[Stmt]) -> ResolveResult { 75 | self.begin_scope(); 76 | for param in params { 77 | self.declare(param.clone())?; 78 | self.define(param.clone())?; 79 | } 80 | self.resolve(body)?; 81 | self.end_scope(); 82 | Ok(()) 83 | } 84 | 85 | fn resolve_expr(&mut self, expr: &Expr) -> ResolveResult { 86 | match expr { 87 | &Expr::Logical(ref left, _, ref right) => { 88 | self.resolve_expr(left)?; 89 | self.resolve_expr(right) 90 | } 91 | &Expr::Binary(ref left, _, ref right) => { 92 | self.resolve_expr(left)?; 93 | self.resolve_expr(right) 94 | } 95 | &Expr::Unary(_, ref unary) => self.resolve_expr(unary), 96 | &Expr::Nil | &Expr::Bool(_) | &Expr::Num(_) | &Expr::Str(_) => Ok(()), 97 | &Expr::Grouping(ref gexpr) => self.resolve_expr(gexpr), 98 | &Expr::Var(ref token) => { 99 | if let Some(scope) = self.scopes.last() { 100 | if scope.get(&token.lexeme) == Some(&false) { 101 | return Err(format!("Can't read var in initializer {:?}", token)); 102 | } 103 | } 104 | self.resolve_local(expr, &token.lexeme) 105 | } 106 | &Expr::Assign(ref token, ref asigex) => { 107 | self.resolve_expr(asigex)?; 108 | self.resolve_local(expr, &token.lexeme) 109 | } 110 | &Expr::Call(ref callee, ref args) => { 111 | self.resolve_expr(callee)?; 112 | args.iter() 113 | .map(|arg| self.resolve_expr(arg)) 114 | .find(|arg| arg.is_err()) 115 | .unwrap_or(Ok(())) 116 | } 117 | } 118 | } 119 | 120 | fn resolve_stmt(&mut self, stmt: &Stmt) -> ResolveResult { 121 | match stmt { 122 | Stmt::Print(ref expr) => self.resolve_expr(expr), 123 | Stmt::Expr(ref expr) => self.resolve_expr(expr), 124 | Stmt::Var(ref name, ref init) => { 125 | // split binding in declare/define to disallow self reference 126 | self.declare(name.clone())?; 127 | match init { 128 | Expr::Nil => (), 129 | _ => self.resolve_expr(init)?, 130 | } 131 | self.define(name.clone()) 132 | } 133 | Stmt::Block(ref stmts) => { 134 | self.begin_scope(); 135 | self.resolve(stmts)?; 136 | self.end_scope(); 137 | Ok(()) 138 | } 139 | Stmt::If(ref cond, ref then, ref elseb) => { 140 | self.resolve_expr(cond)?; 141 | self.resolve_stmt(then)?; 142 | if let Some(ref elseb) = elseb { 143 | self.resolve_stmt(elseb)?; 144 | } 145 | Ok(()) 146 | } 147 | Stmt::While(ref cond, ref body) => { 148 | self.resolve_expr(cond)?; 149 | self.resolve_stmt(body) 150 | } 151 | Stmt::Break(_) => Ok(()), 152 | Stmt::Function(ref name, ref parameters, ref body) => { 153 | self.declare(name.clone())?; 154 | self.define(name.clone())?; 155 | self.resolve_function(parameters, body) 156 | } 157 | Stmt::Return(ref expr) => self.resolve_expr(expr), 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lox/src/lox_scanner.rs: -------------------------------------------------------------------------------- 1 | use lexers::Scanner; 2 | 3 | #[derive(Clone,Debug,PartialEq)] 4 | pub enum TT { 5 | // single char tokens 6 | OPAREN, CPAREN, OBRACE, CBRACE, COMMA, DOT, 7 | MINUS, PLUS, SEMICOLON, SLASH, STAR, DOLLAR, 8 | BANG, ASSIGN, NE, EQ, GT, GE, LT, LE, 9 | // literals 10 | Id(String), Str(String), Num(f64), 11 | // keywords 12 | AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, BREAK, 13 | PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, EOF, 14 | } 15 | 16 | #[derive(Clone,Debug)] 17 | pub struct Token { 18 | pub line: usize, 19 | pub token: TT, 20 | pub lexeme: String, 21 | } 22 | 23 | pub struct LoxScanner> { 24 | src: Scanner, 25 | line: usize, 26 | errors: bool, 27 | } 28 | 29 | 30 | impl> LoxScanner { 31 | pub fn scanner(source: I) -> Scanner { 32 | Scanner::new( 33 | LoxScanner{ 34 | src: Scanner::new(source), 35 | line: 1, 36 | errors: false}) 37 | } 38 | 39 | fn tokenize(&mut self, literal: TT) -> Option { 40 | let lexeme = match literal { 41 | TT::EOF => String::new(), 42 | _ => self.src.extract_string() 43 | }; 44 | let literal = match literal { 45 | TT::Str(_) => TT::Str(lexeme.trim_matches('"').to_string()), 46 | other => other 47 | }; 48 | Some(Token{line: self.line, token: literal, lexeme}) 49 | } 50 | 51 | fn error>(&mut self, err: T) { 52 | eprintln!("LoxScanner error: {}", err.as_ref()); 53 | self.errors = true; 54 | } 55 | 56 | fn scan_restof_string(&mut self, q: char) -> bool { 57 | let backtrack = self.src.buffer_pos(); 58 | let orig_line = self.line; 59 | while let Some(n) = self.src.next() { 60 | if n == '\n' { self.line += 1; } 61 | if n == '\\' { self.src.next(); continue; } 62 | if n == q { return true; } 63 | } 64 | self.src.set_buffer_pos(backtrack); 65 | self.line = orig_line; 66 | false 67 | } 68 | 69 | fn id_or_keyword(&mut self, keyword: String) -> Option { 70 | let key2 = keyword.clone(); 71 | let tok = |literal: TT| -> Option { 72 | Some(Token{line: self.line, token: literal, lexeme: key2}) 73 | }; 74 | match keyword.as_ref() { 75 | "and" => tok(TT::AND), 76 | "class" => tok(TT::CLASS), 77 | "else" => tok(TT::ELSE), 78 | "false" => tok(TT::FALSE), 79 | "fun" => tok(TT::FUN), 80 | "for" => tok(TT::FOR), 81 | "if" => tok(TT::IF), 82 | "nil" => tok(TT::NIL), 83 | "or" => tok(TT::OR), 84 | "break" => tok(TT::BREAK), 85 | "print" => tok(TT::PRINT), 86 | "return" => tok(TT::RETURN), 87 | "super" => tok(TT::SUPER), 88 | "this" => tok(TT::THIS), 89 | "true" => tok(TT::TRUE), 90 | "var" => tok(TT::VAR), 91 | "while" => tok(TT::WHILE), 92 | _ => Some(Token{line: self.line, 93 | token: TT::Id(keyword.clone()), lexeme: keyword}) 94 | } 95 | } 96 | 97 | fn scan_token(&mut self) -> Option { 98 | let token = match self.src.next() { 99 | Some('(') => self.tokenize(TT::OPAREN), 100 | Some(')') => self.tokenize(TT::CPAREN), 101 | Some('{') => self.tokenize(TT::OBRACE), 102 | Some('}') => self.tokenize(TT::CBRACE), 103 | Some(',') => self.tokenize(TT::COMMA), 104 | Some('.') => self.tokenize(TT::DOT), 105 | Some('-') => self.tokenize(TT::MINUS), 106 | Some('+') => self.tokenize(TT::PLUS), 107 | Some(';') => self.tokenize(TT::SEMICOLON), 108 | Some('*') => self.tokenize(TT::STAR), 109 | Some('$') => self.tokenize(TT::DOLLAR), 110 | Some('!') => if self.src.accept(&'=').is_some() { 111 | self.tokenize(TT::NE) 112 | } else { 113 | self.tokenize(TT::BANG) 114 | }, 115 | Some('=') => if self.src.accept(&'=').is_some() { 116 | self.tokenize(TT::EQ) 117 | } else { 118 | self.tokenize(TT::ASSIGN) 119 | }, 120 | Some('<') => if self.src.accept(&'=').is_some() { 121 | self.tokenize(TT::LE) 122 | } else { 123 | self.tokenize(TT::LT) 124 | }, 125 | Some('>') => if self.src.accept(&'=').is_some() { 126 | self.tokenize(TT::GE) 127 | } else { 128 | self.tokenize(TT::GT) 129 | }, 130 | Some('/') => if self.src.accept(&'/').is_some() { 131 | // skip comment 132 | self.src.until_any(&['\n']); 133 | None 134 | } else { 135 | self.tokenize(TT::SLASH) 136 | }, 137 | Some(' ') | Some('\t') | Some('\r') => None, 138 | Some('\n') => { self.line += 1; None }, // track current line 139 | Some('"') => match self.scan_restof_string('"') { 140 | true => self.tokenize(TT::Str(String::new())), 141 | false => { self.error("unterminated string"); None } 142 | }, 143 | Some(d) if d.is_digit(10) => { 144 | self.src.prev(); // hacky but works 145 | let num = self.src.scan_number().unwrap(); 146 | use std::str::FromStr; 147 | Some(Token{line: self.line, 148 | token: TT::Num(f64::from_str(&num).unwrap()), lexeme: num}) 149 | }, 150 | Some(a) if a.is_alphabetic() => { 151 | self.src.prev(); // hacky but works 152 | let id = self.src.scan_identifier().unwrap(); 153 | self.id_or_keyword(id) 154 | }, 155 | Some(c) => { 156 | let err = format!("bad char '{}' at line {}", c, self.line); 157 | self.error(err); 158 | None 159 | }, 160 | None => self.tokenize(TT::EOF) 161 | }; 162 | self.src.extract(); // ignore what we didn't harvest 163 | token 164 | } 165 | } 166 | 167 | impl> Iterator for LoxScanner { 168 | type Item = Token; 169 | fn next(&mut self) -> Option { 170 | loop { // consume all white space and errors 171 | if let Some(token) = self.scan_token() { 172 | match token.token { 173 | TT::EOF => return None, 174 | _ => return Some(token) 175 | } 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lox/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use std::env; 4 | use std::fs::File; 5 | use std::io::{self, Read, Write}; 6 | 7 | mod lox_scanner; 8 | mod lox_parser; 9 | mod lox_interpreter; 10 | mod lox_environment; 11 | mod lox_native; 12 | mod lox_resolver; 13 | 14 | use crate::lox_scanner::LoxScanner; 15 | use crate::lox_parser::LoxParser; 16 | use crate::lox_interpreter::LoxInterpreter; 17 | use crate::lox_resolver::Resolver; 18 | 19 | 20 | fn main() { 21 | if env::args().len() > 2 { 22 | eprintln!("usage: lox [script]"); 23 | return; 24 | } 25 | 26 | let run = |source: String, interpreter: &mut LoxInterpreter| { 27 | let scanner = LoxScanner::scanner(source.chars()); 28 | let mut parser = LoxParser::new(scanner); 29 | match parser.parse() { 30 | Ok(stmts) => { 31 | match Resolver::new(interpreter).resolve(&stmts) { 32 | Ok(_) => if let Err(error) = interpreter.interpret(&stmts) { 33 | eprintln!("LoxInterpreter error: {}", error) 34 | }, 35 | Err(error) => eprintln!("Resolve error: {}", error) 36 | } 37 | } 38 | Err(errors) => for e in errors { eprintln!("{}", e); } 39 | } 40 | }; 41 | 42 | let mut interpreter = LoxInterpreter::new(); 43 | if env::args().len() == 2 { 44 | let sourcefile = env::args().nth(1).unwrap(); 45 | if let Ok(mut f) = File::open(&sourcefile) { 46 | let mut source = String::new(); 47 | if f.read_to_string(&mut source).is_ok() { 48 | return run(source, &mut interpreter); 49 | } 50 | } 51 | eprintln!("lox: failed to read source file {}", sourcefile); 52 | std::process::exit(1); 53 | } else { 54 | loop { 55 | let mut input = String::new(); 56 | io::stdout().write_all(b"~> ").unwrap(); 57 | io::stdout().flush().unwrap(); 58 | match io::stdin().read_line(&mut input) { 59 | Ok(_) => run(input, &mut interpreter), 60 | Err(e) => eprintln!("lox read_line error: {:?}", e) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lox/src/tests/break-depth.lox: -------------------------------------------------------------------------------- 1 | var start = clock(); 2 | 3 | while (true) { 4 | if ((clock() - start)/1e+9 > 1) { 5 | break 2; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lox/src/tests/break-loops.lox: -------------------------------------------------------------------------------- 1 | for (var y = 0; y < 10; y = y + 1) 2 | for (var x = 0; x < 10; x = x + 1) { 3 | if (x > 5 or y >= 3) break 2; 4 | print y + " " + x; 5 | } 6 | -------------------------------------------------------------------------------- /lox/src/tests/nested-scopes.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | fun showA() { 4 | print a; 5 | } 6 | 7 | showA(); 8 | var a = "block"; 9 | showA(); 10 | print a; 11 | } 12 | 13 | print a; 14 | -------------------------------------------------------------------------------- /lox/src/tests/self-reference.lox: -------------------------------------------------------------------------------- 1 | { 2 | var x = x; 3 | print x; 4 | } 5 | -------------------------------------------------------------------------------- /numerica/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "numerica" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | earlgrey = { version = "0.4", path = "../earlgrey" } 8 | rand_distr = "0.5" 9 | rand = "0.9" 10 | -------------------------------------------------------------------------------- /numerica/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Expr; 2 | 3 | use std::collections::HashMap; 4 | 5 | pub struct Context { 6 | bindings: HashMap, 7 | } 8 | 9 | impl Context { 10 | pub fn new() -> Self { 11 | Self { 12 | bindings: HashMap::new(), 13 | } 14 | } 15 | 16 | pub fn extend(&self) -> Self { 17 | Self { 18 | bindings: self.bindings.clone(), 19 | } 20 | } 21 | 22 | pub fn set(&mut self, sym: String, expr: Expr) { 23 | self.bindings.insert(sym, expr); 24 | } 25 | 26 | pub fn get(&self, sym: &str) -> Option { 27 | self.bindings.get(sym).cloned() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /numerica/src/expr/arithmetic.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Expr; 2 | 3 | fn fold_bin_op_commutative( 4 | args: Vec, 5 | reduce_op: fn(f64, f64) -> f64, 6 | lassoc: bool, 7 | ) -> (Option, Option>) { 8 | let reduce = |acc: (Option, Option>), x| { 9 | let (num_acc, other_acc) = acc; 10 | match x { 11 | Expr::Number(n) => match num_acc { 12 | None => (Some(n), other_acc), 13 | Some(lhs) => (Some(reduce_op(lhs, n)), other_acc), 14 | }, 15 | other => match other_acc { 16 | None => (num_acc, Some(vec![other])), 17 | Some(lhs) => ( 18 | num_acc, 19 | Some(lhs.into_iter().chain(std::iter::once(other)).collect()), 20 | ), 21 | }, 22 | } 23 | }; 24 | if lassoc { 25 | args.into_iter().fold((None, None), reduce) 26 | } else { 27 | args.into_iter().rfold((None, None), reduce) 28 | } 29 | } 30 | 31 | fn eval_commutative_binop( 32 | args: Vec, 33 | head_name: &str, 34 | reduce_op: fn(f64, f64) -> f64, 35 | lassoc: bool, 36 | ) -> Result { 37 | match fold_bin_op_commutative(args, reduce_op, lassoc) { 38 | (None, None) => Err(format!("Missing arguments for {}", head_name)), 39 | (Some(n), None) => Ok(Expr::Number(n)), 40 | (None, Some(exprs)) => Ok(Expr::from_head(head_name, exprs)), 41 | (Some(num), Some(exprs)) => Ok(Expr::from_head( 42 | head_name, 43 | std::iter::once(Expr::Number(num)) 44 | .chain(exprs.into_iter()) 45 | .collect(), 46 | )), 47 | } 48 | } 49 | 50 | fn eval_non_commutative_binop( 51 | args: Vec, 52 | head_name: &str, 53 | reduce_op: fn(f64, f64) -> f64, 54 | ) -> Result { 55 | let [lhs, rhs]: [Expr; 2] = args 56 | .try_into() 57 | .map_err(|e| format!("{} must have 2 arguments. {:?}", head_name, e))?; 58 | match (lhs, rhs) { 59 | (Expr::Number(lhs), Expr::Number(rhs)) => Ok(Expr::Number(reduce_op(lhs, rhs))), 60 | (lhs, rhs) => Ok(Expr::from_head(head_name, vec![lhs, rhs])), 61 | } 62 | } 63 | 64 | pub(crate) fn eval_plus(args: Vec) -> Result { 65 | eval_commutative_binop(args, "Plus", |acc, x| acc + x, true) 66 | } 67 | 68 | pub(crate) fn eval_minus(args: Vec) -> Result { 69 | eval_non_commutative_binop(args, "Minus", |l, r| l - r) 70 | } 71 | 72 | pub(crate) fn eval_divide(args: Vec) -> Result { 73 | eval_non_commutative_binop(args, "Divide", |l, r| l / r) 74 | } 75 | 76 | pub(crate) fn eval_times(args: Vec) -> Result { 77 | eval_commutative_binop(args, "Times", |acc, x| acc * x, true) 78 | } 79 | 80 | pub(crate) fn eval_power(args: Vec) -> Result { 81 | eval_non_commutative_binop(args, "Power", |l, r| l.powf(r)) 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use crate::context::Context; 87 | use crate::expr::Expr; 88 | 89 | fn eval(expr: &str) -> Result { 90 | use crate::expr::evaluate; 91 | use crate::parser::parser; 92 | evaluate(parser()?(expr)?, &mut Context::new()) 93 | } 94 | 95 | #[test] 96 | fn arith_ops() -> Result<(), String> { 97 | assert_eq!(eval(r#"1 + 2"#)?, Expr::Number(3.0)); 98 | assert_eq!(eval(r#"1 + 2 - 3"#)?, Expr::Number(0.0)); 99 | assert_eq!(eval(r#"1 - 2 + 3"#)?, Expr::Number(2.0)); 100 | assert_eq!(eval(r#"1 - 2 - 3"#)?, Expr::Number(-4.0)); 101 | assert_eq!(eval(r#"1 + 2 * 3"#)?, Expr::Number(7.0)); 102 | assert_eq!(eval(r#"2 ^ 2 ^ 3"#)?, Expr::Number(256.0)); 103 | assert_eq!(eval(r#"1 + 2 ^ 3"#)?, Expr::Number(9.0)); 104 | assert_eq!(eval(r#"3 / 2 / 4"#)?, Expr::Number(0.375)); 105 | assert_eq!(eval(r#"-3"#)?, Expr::Number(-3.0)); 106 | assert_eq!(eval(r#"--3"#)?, Expr::Number(3.0)); 107 | assert_eq!(eval(r#"4--3"#)?, Expr::Number(7.0)); 108 | assert_eq!(eval(r#"-4--3"#)?, Expr::Number(-1.0)); 109 | assert_eq!(eval(r#"-4-3"#)?, Expr::Number(-7.0)); 110 | Ok(()) 111 | } 112 | 113 | #[test] 114 | fn arith_ops_commutative() -> Result<(), String> { 115 | // Check there's no re-ordering leading to a different expression 116 | assert_eq!( 117 | eval(r#"x ^ 2"#)?, 118 | Expr::from_head( 119 | "Power", 120 | vec![Expr::Symbol("x".to_string()), Expr::Number(2.0)] 121 | ) 122 | ); 123 | Ok(()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /numerica/src/expr/distribution.rs: -------------------------------------------------------------------------------- 1 | use super::Expr; 2 | use crate::context::Context; 3 | use rand_distr::Distribution; 4 | use std::rc::Rc; 5 | 6 | #[derive(Debug, PartialEq)] 7 | pub enum Distr { 8 | Normal(rand_distr::Normal), 9 | Poisson(rand_distr::Poisson), 10 | Beta(rand_distr::Beta), 11 | } 12 | 13 | impl Distr { 14 | pub fn sample(&self) -> f64 { 15 | match self { 16 | Distr::Normal(d) => d.sample(&mut rand::rng()), 17 | Distr::Poisson(d) => d.sample(&mut rand::rng()), 18 | Distr::Beta(d) => d.sample(&mut rand::rng()), 19 | } 20 | } 21 | } 22 | 23 | pub(crate) fn eval_normal_dist(args: Vec) -> Result { 24 | let [mu, sigma]: [Expr; 2] = args 25 | .try_into() 26 | .map_err(|e| format!("NormalDist must have 2 arguments. {:?}", e))?; 27 | match (mu, sigma) { 28 | (Expr::Number(mu), Expr::Number(sigma)) => Ok(Expr::Distribution(Rc::new(Distr::Normal( 29 | rand_distr::Normal::new(mu, sigma).map_err(|e| e.to_string())?, 30 | )))), 31 | other => Err(format!("NormalDist params must be number. {:?}", other)), 32 | } 33 | } 34 | 35 | pub(crate) fn eval_beta_dist(args: Vec) -> Result { 36 | let [alpha, beta]: [f64; 2] = args 37 | .into_iter() 38 | .map(|a| match a { 39 | Expr::Number(n) => Ok(n), 40 | other => Err(format!("BetaDist params must be number. {:?}", other)), 41 | }) 42 | .collect::, _>>()? 43 | .try_into() 44 | .map_err(|e| format!("BetaDist error: {:?}", e))?; 45 | Ok(Expr::Distribution(Rc::new(Distr::Beta( 46 | rand_distr::Beta::new(alpha, beta).map_err(|e| e.to_string())?, 47 | )))) 48 | } 49 | 50 | pub(crate) fn eval_poisson_dist(args: Vec) -> Result { 51 | let [lambda]: [f64; 1] = args 52 | .into_iter() 53 | .map(|a| match a { 54 | Expr::Number(n) => Ok(n), 55 | other => Err(format!("PoissonDist params must be number. {:?}", other)), 56 | }) 57 | .collect::, _>>()? 58 | .try_into() 59 | .map_err(|e| format!("PoissonDist error: {:?}", e))?; 60 | Ok(Expr::Distribution(Rc::new(Distr::Poisson( 61 | rand_distr::Poisson::new(lambda).map_err(|e| e.to_string())?, 62 | )))) 63 | } 64 | 65 | pub(crate) fn eval_unsure(args: Vec) -> Result { 66 | let [low, high]: [Expr; 2] = args 67 | .try_into() 68 | .map_err(|e| format!("Unsure needs 2 arguments. {:?}", e))?; 69 | match (low, high) { 70 | (Expr::Number(low), Expr::Number(high)) => { 71 | let mu = Expr::Number((high + low) / 2.0); 72 | let sigma = Expr::Number((high - low).abs() / 3.92); // 2x z-score 95% 73 | eval_normal_dist(vec![mu, sigma]) 74 | } 75 | _ => Err(format!("Unsure params must be numbers.")), 76 | } 77 | } 78 | 79 | pub fn is_stochastic(expr: &Expr) -> bool { 80 | match expr { 81 | Expr::Distribution(_) => true, 82 | Expr::Head(h, args) => is_stochastic(&*h) || args.iter().any(is_stochastic), 83 | Expr::Function(_, body) => is_stochastic(body), 84 | _ => false, 85 | } 86 | } 87 | 88 | fn sample_expr(expr: &Expr) -> Expr { 89 | match expr { 90 | Expr::Distribution(d) => Expr::Number(d.sample()), 91 | Expr::Head(h, args) => Expr::Head( 92 | Box::new(sample_expr(h.as_ref())), 93 | args.iter().map(sample_expr).collect(), 94 | ), 95 | Expr::Function(p, body) => Expr::Function(p.clone(), Box::new(sample_expr(body))), 96 | o => o.clone(), 97 | } 98 | } 99 | 100 | pub(crate) fn eval_sample(args: Vec, ctx: &mut Context) -> Result { 101 | let [expr]: [Expr; 1] = args 102 | .try_into() 103 | .map_err(|e| format!("Sample must have an expr. {:?}", e))?; 104 | // expr has already been evaluated, here we pick samples from nested 105 | // distributions and then re-evaluate expr with concrete values. 106 | crate::evaluate(sample_expr(&expr), ctx) 107 | } 108 | 109 | pub(crate) fn eval_histogram(args: Vec, ctx: &mut Context) -> Result { 110 | let [expr, nsamples, nbuckets]: [Expr; 3] = args 111 | .try_into() 112 | .map_err(|e| format!("Histogram needs expr, num-samples, num-buckets. {:?}", e))?; 113 | let Expr::Number(nsamples) = nsamples else { 114 | return Err(format!("Histogram num-samples must be a number.")); 115 | }; 116 | let Expr::Number(nbuckets) = nbuckets else { 117 | return Err(format!("Histogram num-buckets must be a number.")); 118 | }; 119 | // expr has already been evaluated, here we pick samples from nested 120 | // distributions and then re-evaluate expr with concrete values. 121 | let samples = (0..nsamples as u32) 122 | .map(|_| match crate::evaluate(sample_expr(&expr), ctx) { 123 | Ok(Expr::Number(n)) => Ok(n), 124 | o => Err(format!("Histogram samples must be numbers. Got {:?}", o)), 125 | }) 126 | .collect::, _>>()?; 127 | 128 | let max = samples.iter().cloned().reduce(f64::max).unwrap_or(f64::MIN); 129 | let min = samples.iter().cloned().reduce(f64::min).unwrap_or(f64::MAX); 130 | 131 | let bucket_width = (max - min) / nbuckets; 132 | let mut histogram = vec![0.0; nbuckets as usize]; 133 | for sample in &samples { 134 | let idx = ((sample - min) / bucket_width) as usize; 135 | let idx = idx.min(histogram.len() - 1); 136 | histogram[idx] += 1.0; 137 | } 138 | 139 | Ok(Expr::from_head( 140 | "List", 141 | histogram 142 | .into_iter() 143 | .enumerate() 144 | .map(|(idx, n)| { 145 | Expr::from_head( 146 | "List", 147 | vec![ 148 | Expr::Number(min + (0.5 + (idx as f64)) * bucket_width), 149 | Expr::Number(n), 150 | ], 151 | ) 152 | }) 153 | .collect(), 154 | )) 155 | } 156 | -------------------------------------------------------------------------------- /numerica/src/expr/find_root.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::expr::Expr; 3 | use crate::{find_root_vec, findroot}; 4 | 5 | struct VarSpec { 6 | sym: String, 7 | start: f64, 8 | range: Option<(f64, f64)>, 9 | } 10 | 11 | fn parse_varspec(varspec: Expr) -> Result, String> { 12 | use super::Expr::*; 13 | // Normalize single and multiple variable specifications. 14 | // Single: {x0, x0_start, x0_range}, Multiple: {{x0, x0_start, x0_range}, ...} 15 | let varspec = match varspec { 16 | Head(h, spec) if *h == Symbol("List".into()) && spec.len() > 0 => match &spec[0] { 17 | // List of Lists implies multiple variables. Return the full spec. 18 | Head(h, _) if **h == Symbol("List".into()) => spec, 19 | // Single variable specification. Wrap in a list for uniformity. 20 | Symbol(_) => vec![Expr::from_head("List", spec)], 21 | o => return Err(format!("Unexpected var spec for FindRoot: {}", o)), 22 | }, 23 | _ => return Err(format!("Unexpected var spec for FindRoot: {}", varspec)), 24 | }; 25 | 26 | varspec 27 | .into_iter() 28 | .map(|vs| { 29 | let Head(_, vs) = vs else { 30 | return Err(format!("Unexpected var spec for FindRoot: {:?}", vs)); 31 | }; 32 | // Match individual var spec {x0, x0_start, x0_lo, x0_hi} 33 | match vs.as_slice() { 34 | [Symbol(sym), Number(start)] => Ok(VarSpec { 35 | sym: sym.clone(), 36 | start: *start, 37 | range: None, 38 | }), 39 | [Symbol(sym), Number(start), Number(lo), Number(hi)] => Ok(VarSpec { 40 | sym: sym.clone(), 41 | start: *start, 42 | range: Some((*lo, *hi)), 43 | }), 44 | _ => Err(format!("Unexpected var spec for FindRoot: {:?}", vs)), 45 | } 46 | }) 47 | .collect::, _>>() 48 | } 49 | 50 | fn nonlinear_root(fexpr: Expr, varspec: VarSpec) -> Result, String> { 51 | use super::Expr::*; 52 | let ctx = &mut Context::new(); 53 | // Wrap the expression we're finding the root of in a Function 54 | let fexpr = crate::evaluate( 55 | Expr::from_head("Function", vec![Symbol(varspec.sym), fexpr]), 56 | ctx, 57 | )?; 58 | let f = |xi: f64| match super::apply(fexpr.clone(), vec![Number(xi)], &mut Context::new()) { 59 | Ok(Number(x)) => Ok(x), 60 | o => Err(format!("FindRoot didn't return Number: {:?}", o)), 61 | }; 62 | match varspec.range { 63 | Some((lo, hi)) => Ok(vec![findroot::regula_falsi(f, (lo, hi))?]), 64 | None => findroot::find_roots(f, varspec.start), 65 | } 66 | } 67 | 68 | fn nonlinear_system(fexprs: Vec, varspecs: Vec) -> Result, String> { 69 | use super::Expr::*; 70 | let ctx = &mut Context::new(); 71 | let fexprs = fexprs 72 | .into_iter() 73 | .map(|fexpr| { 74 | crate::evaluate( 75 | Expr::from_head( 76 | "Function", 77 | vec![ 78 | Expr::from_head( 79 | "List", 80 | varspecs.iter().map(|vs| Symbol(vs.sym.clone())).collect(), 81 | ), 82 | fexpr, 83 | ], 84 | ), 85 | ctx, 86 | ) 87 | }) 88 | .collect::, _>>()?; 89 | 90 | let funcs = fexprs 91 | .into_iter() 92 | .map(|fexpr| { 93 | move |xi: &Vec| match super::apply( 94 | fexpr.clone(), 95 | xi.iter().map(|&x| Number(x)).collect(), 96 | &mut Context::new(), 97 | ) { 98 | Ok(Number(x)) => Ok(x), 99 | o => Err(format!("FindRoot didn't return Number: {:?}", o)), 100 | } 101 | }) 102 | .collect(); 103 | 104 | find_root_vec(funcs, varspecs.iter().map(|vi| vi.start).collect()) 105 | } 106 | 107 | pub(crate) fn eval_find_root(args: Vec) -> Result { 108 | let [fexpr, varspec]: [Expr; 2] = args 109 | .try_into() 110 | .map_err(|e| format!("FindRoot must have 2 arguments. {:?}", e))?; 111 | 112 | // Pull out variables specs to find roots for. 113 | let mut varspec = parse_varspec(varspec)?; 114 | 115 | // Adapt to single or multiple functions 116 | let mut fexpr = match fexpr { 117 | Expr::Head(h, a) if *h == Expr::Symbol("List".into()) => a, 118 | expr => vec![expr], 119 | }; 120 | 121 | if varspec.len() != fexpr.len() { 122 | return Err(format!( 123 | "Findroot unknowns != func-count, {} vs {}", 124 | varspec.len(), 125 | fexpr.len() 126 | )); 127 | } 128 | 129 | if fexpr.len() == 1 { 130 | let roots = nonlinear_root(fexpr.swap_remove(0), varspec.swap_remove(0))?; 131 | return Ok(Expr::from_head( 132 | "List", 133 | roots.into_iter().map(|ri| Expr::Number(ri)).collect(), 134 | )); 135 | } else { 136 | let roots = nonlinear_system(fexpr, varspec)?; 137 | return Ok(Expr::from_head( 138 | "List", 139 | roots.into_iter().map(|ri| Expr::Number(ri)).collect(), 140 | )); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /numerica/src/expr/sum.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::expr::Expr; 3 | 4 | // Parse iteration arguments, Eg: {var, 0, end} or {var, start, end} 5 | fn parse_sum_args(sum_args: &Expr) -> Result<(String, i32, i32), String> { 6 | use super::Expr::*; 7 | match sum_args { 8 | Head(h, args) if **h == Symbol("List".into()) => match args.as_slice() { 9 | [Symbol(x), Number(xn)] => Ok((x.clone(), 0 as i32, *xn as i32)), 10 | [Symbol(x), Number(x0), Number(xn)] => Ok((x.clone(), *x0 as i32, *xn as i32)), 11 | other => Err(format!("Sum unexpected arg1: {:?}", other)), 12 | }, 13 | other => Err(format!("Sum unexpected arg1: {:?}", other)), 14 | } 15 | } 16 | 17 | pub(crate) fn eval_sum(args: Vec, ctx: &mut Context) -> Result { 18 | let [sum_expr, sum_args]: [Expr; 2] = args 19 | .try_into() 20 | .map_err(|e| format!("Sum expected: {{expr, args}}. Got {:?}", e))?; 21 | let (x, x0, xn) = parse_sum_args(&sum_args)?; 22 | // Build sum function out of sum expression and iteration variable 23 | let sum_func = super::evaluate( 24 | Expr::from_head("Function", vec![Expr::Symbol(x), sum_expr.clone()]), 25 | ctx, 26 | )?; 27 | let sum = (x0..=xn).try_fold(0.0, |sum, xi| { 28 | match super::apply(sum_func.clone(), vec![Expr::Number(xi as f64)], ctx) { 29 | Ok(Expr::Number(n)) => Ok(sum + n), 30 | Ok(other) => Err(Ok(other)), // Short-circuit eval but no failure 31 | Err(err) => Err(Err(err)), 32 | } 33 | }); 34 | match sum { 35 | Ok(sum) => Ok(Expr::Number(sum)), 36 | Err(Ok(_)) => Ok(Expr::from_head("Sum", vec![sum_expr, sum_args])), 37 | Err(Err(e)) => Err(e), 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use crate::context::Context; 44 | use crate::expr::{Expr, evaluate}; 45 | use crate::parser::parser; 46 | 47 | fn eval(expr: Expr) -> Result { 48 | evaluate(expr, &mut Context::new()) 49 | } 50 | 51 | #[test] 52 | fn sum_expr() -> Result<(), String> { 53 | let p = parser()?; 54 | assert_eq!(eval(p(r#"Sum[x^2, {x, 3}]"#)?)?, Expr::Number(14.0)); 55 | assert_eq!(eval(p(r#"Sum[x^2, {x, 2, 4}]"#)?)?, Expr::Number(29.0)); 56 | assert_eq!( 57 | eval(p(r#"Sum[x^i, {i, 4}]"#)?)?, 58 | Expr::from_head( 59 | "Sum", 60 | vec![ 61 | Expr::from_head( 62 | "Power", 63 | vec![Expr::Symbol("x".into()), Expr::Symbol("i".into())] 64 | ), 65 | Expr::from_head("List", vec![Expr::Symbol("i".into()), Expr::Number(4.0)]) 66 | ] 67 | ) 68 | ); 69 | assert_eq!( 70 | eval(p(r#"ReplaceAll[Sum[x^i, {i, 4}], x -> 2]"#)?)?, 71 | Expr::Number(1.0 + 2.0 + 4.0 + 8.0 + 16.0) 72 | ); 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /numerica/src/expr/table.rs: -------------------------------------------------------------------------------- 1 | use super::replace_all::replace_all; 2 | use super::{Expr, evaluate}; 3 | use crate::context::Context; 4 | 5 | pub(crate) fn eval_table(mut args: Vec, ctx: &mut Context) -> Result { 6 | // first arg is the expression that will be evaluated for each table element 7 | let expr = args.remove(0); 8 | // Figure out iteration dimensions 9 | let idxs: Vec<(Expr, f64, f64, f64)> = args 10 | .into_iter() 11 | .map(|spec| evaluate(spec, ctx)) 12 | .map(|spec| match spec { 13 | Ok(Expr::Head(h, spec)) if *h == Expr::Symbol("List".into()) => match spec.as_slice() { 14 | [i, Expr::Number(imax)] => Ok((i.clone(), 1.0, *imax, 1.0)), 15 | [i, Expr::Number(imin), Expr::Number(imax)] => Ok((i.clone(), *imin, *imax, 1.0)), 16 | [i, Expr::Number(imin), Expr::Number(imax), Expr::Number(di)] => { 17 | Ok((i.clone(), *imin, *imax, *di)) 18 | } 19 | other => Err(format!("Table spec not supported. {:?}", other)), 20 | }, 21 | other => Err(format!("Table spec not supported. {:?}", other)), 22 | }) 23 | .collect::>()?; 24 | // Function to step the cursor across all dimensions. Returns bumped dimension 25 | fn cursor_step( 26 | spec: &[(Expr, f64, f64, f64)], 27 | cursor: Option>, 28 | ) -> Option<(usize, Vec)> { 29 | // Init cursor 30 | let Some(mut cursor) = cursor else { 31 | return Some((0, spec.iter().map(|spec| spec.1).collect())); 32 | }; 33 | // Increment cursor values, rippling carries as needed 34 | let mut idx = cursor.len() - 1; 35 | loop { 36 | cursor[idx] += spec[idx].3; // Add step size 37 | if cursor[idx] <= spec[idx].2 { 38 | return Some((idx, cursor)); // No carry needed 39 | } 40 | cursor[idx] = spec[idx].1; // Reset to min 41 | if idx == 0 { 42 | return None; // Done if we carried past first position 43 | } 44 | idx -= 1; // Move to next position 45 | } 46 | } 47 | 48 | // Generate the table 49 | let mut cursor = None; 50 | let mut table = Expr::from_head("List", Vec::new()); 51 | 52 | while let Some((bumped_dim, c)) = cursor_step(&idxs, cursor) { 53 | let mut inserter = match table { 54 | Expr::Head(_, ref mut a) => a, 55 | _ => panic!(), 56 | }; 57 | for dim in 0..idxs.len() - 1 { 58 | if dim >= bumped_dim { 59 | inserter.push(Expr::from_head("List", Vec::new())); 60 | } 61 | inserter = match inserter.last_mut().unwrap() { 62 | Expr::Head(h, a) if **h == Expr::Symbol("List".into()) => a, 63 | _ => panic!(), 64 | }; 65 | } 66 | 67 | let rexpr = replace_all( 68 | expr.clone(), 69 | &idxs 70 | .iter() 71 | .zip(&c) 72 | .map(|(spec, ci)| (spec.0.clone(), Expr::Number(*ci))) 73 | .collect::>(), 74 | )?; 75 | inserter.push(evaluate(rexpr, ctx)?); 76 | cursor = Some(c); // get next iteration 77 | } 78 | Ok(table) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use crate::expr::Expr; 84 | 85 | fn eval(expr: &str) -> Result { 86 | use crate::context::Context; 87 | use crate::expr::evaluate; 88 | use crate::parser::parser; 89 | evaluate(parser()?(expr)?, &mut Context::new()) 90 | } 91 | 92 | #[test] 93 | fn table() -> Result<(), String> { 94 | assert_eq!( 95 | eval(r#"Table[i, {i, 3}]"#)?, 96 | Expr::from_head( 97 | "List", 98 | vec![Expr::Number(1.0), Expr::Number(2.0), Expr::Number(3.0),] 99 | ) 100 | ); 101 | assert_eq!( 102 | eval(r#"Table[i+j, {i, 2}, {j, 3}]"#)?, 103 | Expr::from_head( 104 | "List", 105 | vec![ 106 | Expr::from_head( 107 | "List", 108 | vec![Expr::Number(2.0), Expr::Number(3.0), Expr::Number(4.0)] 109 | ), 110 | Expr::from_head( 111 | "List", 112 | vec![Expr::Number(3.0), Expr::Number(4.0), Expr::Number(5.0)] 113 | ), 114 | ] 115 | ) 116 | ); 117 | assert_eq!( 118 | eval(r#"Table[i+j+k, {i, 2}, {j, 2+1}, {k, 2}]"#)?, 119 | Expr::from_head( 120 | "List", 121 | vec![ 122 | Expr::from_head( 123 | "List", 124 | vec![ 125 | Expr::from_head("List", vec![Expr::Number(3.0), Expr::Number(4.0)]), 126 | Expr::from_head("List", vec![Expr::Number(4.0), Expr::Number(5.0)]), 127 | Expr::from_head("List", vec![Expr::Number(5.0), Expr::Number(6.0)]), 128 | ] 129 | ), 130 | Expr::from_head( 131 | "List", 132 | vec![ 133 | Expr::from_head("List", vec![Expr::Number(4.0), Expr::Number(5.0)]), 134 | Expr::from_head("List", vec![Expr::Number(5.0), Expr::Number(6.0)]), 135 | Expr::from_head("List", vec![Expr::Number(6.0), Expr::Number(7.0)]), 136 | ] 137 | ), 138 | ] 139 | ) 140 | ); 141 | Ok(()) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /numerica/src/expr/transcendental.rs: -------------------------------------------------------------------------------- 1 | use super::Expr; 2 | 3 | fn apply_op(args: Vec, op_name: &str, op: fn(f64) -> f64) -> Result { 4 | let [arg]: [Expr; 1] = args 5 | .try_into() 6 | .map_err(|e| format!("Expected single arg. {:?}", e))?; 7 | match arg { 8 | Expr::Number(n) => Ok(Expr::Number(op(n))), 9 | // Handle list of args, apply op to each 10 | Expr::Head(h, a) if *h == Expr::Symbol("List".into()) => { 11 | let args = a 12 | .into_iter() 13 | .map(|x| match x { 14 | Expr::Number(n) => Expr::Number(op(n)), 15 | o => Expr::from_head(op_name, vec![o]), 16 | }) 17 | .collect(); 18 | Ok(Expr::from_head("List", args)) 19 | } 20 | o => Ok(Expr::from_head(op_name, vec![o])), 21 | } 22 | } 23 | 24 | fn gamma(x: f64) -> f64 { 25 | #[link(name = "m")] 26 | unsafe extern "C" { 27 | fn tgamma(x: f64) -> f64; 28 | } 29 | unsafe { tgamma(x) } 30 | } 31 | 32 | pub(crate) fn eval_gamma(args: Vec) -> Result { 33 | apply_op(args, "Gamma", gamma) 34 | } 35 | 36 | pub(crate) fn eval_sin(args: Vec) -> Result { 37 | apply_op(args, "Sin", |x| x.sin()) 38 | } 39 | 40 | pub(crate) fn eval_cos(args: Vec) -> Result { 41 | apply_op(args, "Cos", |x| x.cos()) 42 | } 43 | 44 | pub(crate) fn eval_exp(args: Vec) -> Result { 45 | apply_op(args, "Exp", |x| x.exp()) 46 | } 47 | 48 | pub(crate) fn eval_abs(args: Vec) -> Result { 49 | apply_op(args, "Abs", |x| x.abs()) 50 | } 51 | -------------------------------------------------------------------------------- /numerica/src/itertools.rs: -------------------------------------------------------------------------------- 1 | pub struct ProductIterator { 2 | idx: Vec, 3 | lengths: Vec, 4 | } 5 | 6 | impl ProductIterator { 7 | pub fn new(lengths: Vec) -> Self { 8 | ProductIterator { 9 | idx: Vec::new(), 10 | lengths, 11 | } 12 | } 13 | } 14 | 15 | impl Iterator for ProductIterator { 16 | // Tuple of output dimensions 17 | type Item = Vec; 18 | 19 | fn next(&mut self) -> Option { 20 | if self.idx.len() != self.lengths.len() { 21 | self.idx = vec![0; self.lengths.len()]; 22 | return Some(self.idx.clone()); 23 | } 24 | for id in (0..self.lengths.len()).rev() { 25 | if self.idx[id] + 1 >= self.lengths[id] { 26 | self.idx[id] = 0; 27 | } else { 28 | self.idx[id] += 1; 29 | return Some(self.idx.clone()); 30 | } 31 | } 32 | None 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn product_iterator() { 42 | let items: Vec<_> = ProductIterator::new(vec![3, 2, 3]).collect(); 43 | assert_eq!( 44 | items, 45 | vec![ 46 | vec![0, 0, 0], 47 | vec![0, 0, 1], 48 | vec![0, 0, 2], 49 | vec![0, 1, 0], 50 | vec![0, 1, 1], 51 | vec![0, 1, 2], 52 | vec![1, 0, 0], 53 | vec![1, 0, 1], 54 | vec![1, 0, 2], 55 | vec![1, 1, 0], 56 | vec![1, 1, 1], 57 | vec![1, 1, 2], 58 | vec![2, 0, 0], 59 | vec![2, 0, 1], 60 | vec![2, 0, 2], 61 | vec![2, 1, 0], 62 | vec![2, 1, 1], 63 | vec![2, 1, 2] 64 | ] 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /numerica/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod tokenizer; 4 | 5 | mod context; 6 | pub use context::Context; 7 | mod expr; 8 | pub use expr::{Expr, evaluate, is_stochastic}; 9 | mod parser; 10 | pub use parser::{expr_tree, parser}; 11 | 12 | mod itertools; 13 | 14 | // TODO: clean this up 15 | mod findroot; 16 | pub use findroot::{ 17 | bisection, explore_domain, find_root_vec, find_roots, gauss_seidel, newton_raphson, nsolve, 18 | regula_falsi, 19 | }; 20 | 21 | mod matrix; 22 | pub use matrix::{dot_product, gram_schmidt_orthonorm, outer_product, qr_decompose}; 23 | 24 | mod plot; 25 | pub use plot::plot_histogram; 26 | 27 | #[cfg(test)] 28 | mod tests; 29 | -------------------------------------------------------------------------------- /numerica/src/plot.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, Expr}; 2 | 3 | pub fn plot_histogram(expr: &Expr, ctx: &mut Context) -> Result<(), String> { 4 | const SAMPLES: f64 = 100000.0; 5 | const BINS: f64 = 31.0; 6 | let r = crate::evaluate( 7 | crate::Expr::from_head( 8 | "Histogram", 9 | vec![expr.clone(), Expr::Number(SAMPLES), Expr::Number(BINS)], 10 | ), 11 | ctx, 12 | )?; 13 | // pull out the list 14 | let Expr::Head(_, buckets) = r else { 15 | panic!("Expected Expr::Head('List', ...)"); 16 | }; 17 | let mut max_freq: f64 = 0.0; 18 | for bucket in &buckets { 19 | let Expr::Head(_, bucket_def) = bucket else { 20 | panic!("Expected Expr::Head('List', ...)"); 21 | }; 22 | let Expr::Number(freq) = bucket_def[1] else { 23 | panic!("Expected Expr::Number(freq)"); 24 | }; 25 | max_freq = max_freq.max(freq); 26 | } 27 | for bucket in buckets { 28 | let Expr::Head(_, bucket_def) = bucket else { 29 | panic!("Expected Expr::Head('List', ...)"); 30 | }; 31 | let Expr::Number(center) = bucket_def[0] else { 32 | panic!("Expected Expr::Number(center)"); 33 | }; 34 | let Expr::Number(freq) = bucket_def[1] else { 35 | panic!("Expected Expr::Number(freq)"); 36 | }; 37 | println!( 38 | "{:10.2} {}", 39 | center, 40 | "\u{2b24}".repeat((50.0 * freq / max_freq) as usize) 41 | ); 42 | } 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /numerica/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::expr::Expr; 3 | 4 | fn eval(expr: &str) -> Result { 5 | eval_ctx(expr, &mut Context::new()) 6 | } 7 | 8 | fn eval_ctx(expr: &str, ctx: &mut Context) -> Result { 9 | use crate::expr::evaluate; 10 | use crate::parser::parser; 11 | evaluate(parser()?(expr)?, ctx) 12 | } 13 | 14 | #[test] 15 | fn set_delayed() -> Result<(), String> { 16 | let mut ctx = Context::new(); 17 | assert_eq!(eval_ctx(r#"x := 1"#, &mut ctx)?, Expr::Number(1.0)); 18 | assert_eq!( 19 | eval_ctx(r#"f := x + 1"#, &mut ctx)?, 20 | Expr::Head( 21 | Box::new(Expr::Symbol("Plus".into())), 22 | vec![Expr::Symbol("x".into()), Expr::Number(1.0)] 23 | ) 24 | ); 25 | assert_eq!(eval_ctx(r#"g = x + 1"#, &mut ctx)?, Expr::Number(2.0)); 26 | assert_eq!(eval_ctx(r#"f"#, &mut ctx)?, Expr::Number(2.0)); 27 | Ok(()) 28 | } 29 | 30 | #[test] 31 | fn composite_expr() -> Result<(), String> { 32 | assert_eq!( 33 | eval(r#"ReplaceAll[Times, Rule[Times, Plus]][3, 4]"#)?, 34 | Expr::Number(7.0) 35 | ); 36 | Ok(()) 37 | } 38 | 39 | #[test] 40 | fn empty_arglist() -> Result<(), String> { 41 | let rand_num = eval(r#"NormalDist[0, 2][]"#)?; 42 | assert!(matches!(rand_num, Expr::Number(_))); 43 | Ok(()) 44 | } 45 | 46 | #[test] 47 | fn functions() -> Result<(), String> { 48 | assert_eq!(eval(r#"Function[x, x + 1][3]"#)?, Expr::Number(4.0)); 49 | 50 | // Bind a function to a variable and call it 51 | let mut ctx = Context::new(); 52 | assert_eq!( 53 | eval_ctx(r#"f = Function[y, y * 2]"#, &mut ctx)?, 54 | Expr::Function( 55 | vec!["y".into()], 56 | Box::new(Expr::from_head( 57 | "Times", 58 | vec![Expr::Symbol("y".into()), Expr::Number(2.0)] 59 | )) 60 | ) 61 | ); 62 | assert_eq!(eval_ctx(r#"f[7]"#, &mut ctx)?, Expr::Number(14.0)); 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /shunting/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shunting" 3 | version = "0.1.2" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Rodolfo Granata "] 7 | description = "A library for evaluating math expressions" 8 | repository = "https://github.com/rodolf0/tox/tree/master/shunting" 9 | readme = "README.md" 10 | keywords = ["shunting", "math", "parser", "expression", "eval"] 11 | categories = ["parsing", "algorithms"] 12 | 13 | [dependencies] 14 | lexers = { version = "0.1", path = "../lexers" } 15 | rand = "0.8" 16 | rand_distr = "0.4" 17 | libm = "0.2" 18 | 19 | [dev-dependencies] 20 | home = "0.5" 21 | rustyline = "9.1" 22 | -------------------------------------------------------------------------------- /shunting/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | A library for evaluating math expressions. 4 | 5 | ## Using the library 6 | 7 | ```rust 8 | fn main() { 9 | let input = "sin(0.2)^2 + cos(0.2)^2"; 10 | let expr = ShuntingParser::parse_str(input).unwrap(); 11 | let result = MathContext::new().eval(&expr).unwrap(); 12 | println!("{} = {}", expr, result); 13 | } 14 | ``` 15 | 16 | ## A MathContext 17 | 18 | `MathContext` allows keeping context across multiple invocations to parse and evaluate. You can do this via the `setvar` method. 19 | 20 | 21 | ## The tool in the crate 22 | 23 | The crate also ship with the `tox` binary with a math repl. 24 | ``` 25 | $ tox 26 | >> 4! 27 | 24 28 | >> a = sin(0.2)^2 + cos(0.2)^2 29 | >> a 30 | 1 31 | >> (-3)! 32 | NaN 33 | >> (84 % (5/2)) ! 34 | 1.32934 35 | >> pi * 2.1^2 / cbrt(-(6+3)) 36 | -6.660512 37 | ``` 38 | -------------------------------------------------------------------------------- /shunting/examples/tox.rs: -------------------------------------------------------------------------------- 1 | mod repl { 2 | use lexers::{MathToken, MathTokenizer}; 3 | use shunting::{MathContext, ShuntingParser, MathOp}; 4 | 5 | pub fn evalexpr(input: &str) { 6 | match ShuntingParser::parse_str(input) { 7 | Err(e) => println!("Parse error: {:?}", e), 8 | Ok(expr) => match MathContext::new().eval(&expr) { 9 | Err(e) => println!("Eval error: {:?}", e), 10 | Ok(r) => println!("{} -> {}", expr, r), 11 | }, 12 | }; 13 | } 14 | 15 | pub fn parse_statement(cx: &MathContext, input: &str) { 16 | let mut ml = MathTokenizer::scanner(input.chars()); 17 | let backtrack = ml.buffer_pos(); 18 | if let (Some(MathToken::Variable(var)), Some(op)) = (ml.next(), ml.next()) { 19 | if op == MathToken::BOp(":=".to_string()) { 20 | match ShuntingParser::parse(&mut ml) { 21 | Err(e) => println!("Parse error: {:?}", e), 22 | Ok(expr) => match cx.compile(&expr) { 23 | Err(e) => println!("Compile error: {:?}", e), 24 | Ok(code) => cx.setvar(&var, code), 25 | } 26 | } 27 | return; 28 | } 29 | } 30 | // wasn't assignment... try evaluating expression 31 | ml.set_buffer_pos(backtrack); 32 | match ShuntingParser::parse(&mut ml) { 33 | Err(e) => println!("Parse error: {:?}", e), 34 | Ok(expr) => match cx.compile(&expr) { 35 | Err(e) => println!("Compile error: {:?}", e), 36 | Ok(MathOp::Number(n)) => println!("{}", n), 37 | Ok(x) => println!("{:?}", x.histogram::<15>(2000)), 38 | } 39 | }; 40 | } 41 | } 42 | 43 | fn main() { 44 | if std::env::args().len() > 1 { 45 | let input = std::env::args().skip(1).collect::>().join(" "); 46 | repl::evalexpr(&input[..]); 47 | } else { 48 | use shunting::MathContext; 49 | let cx = MathContext::new(); 50 | let histpath = home::home_dir().map(|h| h.join(".tox_history")).unwrap(); 51 | let mut rl = rustyline::Editor::<()>::new(); 52 | if rl.load_history(&histpath).is_err() { 53 | println!("No history yet"); 54 | } 55 | while let Ok(input) = rl.readline(">> ") { 56 | rl.add_history_entry(input.as_str()); 57 | repl::parse_statement(&cx, &input[..]); 58 | } 59 | rl.save_history(&histpath).unwrap(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /shunting/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | mod rpneval; 3 | mod rpnprint; 4 | 5 | pub use crate::parser::{RPNExpr, ShuntingParser}; 6 | pub use crate::rpneval::{MathOp, MathContext}; 7 | 8 | #[cfg(test)] 9 | mod parser_test; 10 | #[cfg(test)] 11 | mod rpneval_test; 12 | -------------------------------------------------------------------------------- /shunting/src/parser.rs: -------------------------------------------------------------------------------- 1 | use lexers::{MathToken, MathTokenizer}; 2 | 3 | #[derive(PartialEq, Debug)] 4 | pub enum Assoc { 5 | Left, 6 | Right, 7 | } 8 | 9 | pub fn op_precedence(mt: &MathToken) -> Result<(usize, Assoc), String> { 10 | // NOTE: This can't encode relations between all tokens, just Ops. 11 | // For example: 12 | // In https://github.com/rodolf0/natools/blob/master/libparser/parser.c#L56-L94 13 | // - unary-minus has to be < than Numbers and OParen 14 | // - but OParen has to be < than unary-minus too! 15 | // - At the same time, unary-minus has to be > than bin-ops (eg: +) 16 | Ok(match mt { 17 | MathToken::BOp(o) if o == "+" => (2, Assoc::Left), 18 | MathToken::BOp(o) if o == "-" => (2, Assoc::Left), 19 | MathToken::BOp(o) if o == "*" => (3, Assoc::Left), 20 | MathToken::BOp(o) if o == "/" => (3, Assoc::Left), 21 | MathToken::BOp(o) if o == "%" => (3, Assoc::Left), 22 | MathToken::BOp(o) if o == "^" || o == "**" => (4, Assoc::Right), 23 | MathToken::UOp(o) if o == "-" => (5, Assoc::Right), // unary minus 24 | MathToken::UOp(o) if o == "!" => (6, Assoc::Left), // factorial 25 | _ => return Err(format!("Undefined precedence for {:?}", mt)), 26 | }) 27 | } 28 | 29 | #[derive(PartialEq, Debug, Clone)] 30 | pub struct RPNExpr(pub Vec); 31 | 32 | pub struct ShuntingParser; 33 | 34 | impl ShuntingParser { 35 | pub fn parse_str(expr: &str) -> Result { 36 | Self::parse(&mut MathTokenizer::new(expr.chars())) 37 | } 38 | 39 | pub fn parse(lexer: &mut impl Iterator) -> Result { 40 | let mut out = Vec::new(); 41 | let mut stack = Vec::new(); 42 | let mut arity = Vec::::new(); 43 | 44 | for token in lexer { 45 | match token { 46 | MathToken::Number(_) => out.push(token), 47 | MathToken::Variable(_) => out.push(token), 48 | MathToken::OParen => stack.push(token), 49 | MathToken::Function(_, _) => { 50 | stack.push(token); 51 | arity.push(1); 52 | } 53 | MathToken::Comma | MathToken::CParen => { 54 | // Flush stack to output queue until open paren 55 | loop { 56 | match stack.pop() { 57 | // Only advance until we find the matching open paren 58 | Some(MathToken::OParen) => break, 59 | Some(any) => out.push(any), 60 | None => return Err("Missing Opening Paren".to_string()), 61 | } 62 | } 63 | if token == MathToken::Comma { 64 | // Keep track of function arity based on number of commas 65 | stack.push(MathToken::OParen); // put back OParen if reading Comma 66 | match arity.last_mut() { 67 | Some(a) => *a += 1, 68 | None => return Err("Comma outside function arglist".to_string()) 69 | } 70 | } else if let Some(MathToken::Function(fname, _)) = stack.last() { 71 | // token is CParen. Popped everything up to OParen. Check fn call. 72 | out.push(MathToken::Function(fname.clone(), arity.pop().unwrap())); 73 | stack.pop(); // pop the function we just shifted out 74 | } 75 | } 76 | MathToken::UOp(_) | MathToken::BOp(_) => { 77 | let (input_token_prec, input_token_assoc) = op_precedence(&token)?; 78 | // Flush stack while its precedence is lower than input or reach OParen 79 | while let Some(stack_top) = stack.last() { 80 | if stack_top == &MathToken::OParen { 81 | break; 82 | } 83 | let (stack_top_prec, _) = op_precedence(stack_top)?; 84 | if stack_top_prec < input_token_prec || ( 85 | stack_top_prec == input_token_prec && 86 | input_token_assoc == Assoc::Right) { 87 | break; 88 | } 89 | out.push(stack.pop().unwrap()); 90 | } 91 | stack.push(token); 92 | } 93 | MathToken::Quantity(_, _, _) => return Err("Can't handle quantities".to_string()), 94 | MathToken::Unknown(lexeme) => return Err(format!("Bad token: {}", lexeme)), 95 | } 96 | } 97 | while let Some(top) = stack.pop() { 98 | match top { 99 | MathToken::OParen => return Err("Missing Closing Paren".to_string()), 100 | token => out.push(token), 101 | } 102 | } 103 | Ok(RPNExpr(out)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /shunting/src/rpneval_test.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::ShuntingParser; 2 | use crate::rpneval::MathContext; 3 | 4 | macro_rules! fuzzy_eq { 5 | ($lhs:expr, $rhs:expr) => { 6 | assert!(($lhs - $rhs).abs() < 1.0e-10) 7 | }; 8 | } 9 | 10 | #[test] 11 | fn test_eval1() { 12 | let expr = ShuntingParser::parse_str("3+4*2/-(1-5)^2^3").unwrap(); 13 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 3.000122070313); 14 | } 15 | 16 | #[test] 17 | fn test_eval2() { 18 | let expr = ShuntingParser::parse_str("3.4e-2 * sin(pi/3)/(541 % -4) * max(2, -7)").unwrap(); 19 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 0.058889727457341); 20 | } 21 | 22 | #[test] 23 | fn test_eval3() { 24 | let expr = ShuntingParser::parse_str("(-(1-9^2) / (1 + 6^2))^0.5").unwrap(); 25 | fuzzy_eq!( 26 | MathContext::new().eval(&expr).unwrap(), 27 | 1.470429244187615496759 28 | ); 29 | } 30 | 31 | #[test] 32 | fn test_eval4() { 33 | let expr = ShuntingParser::parse_str("sin(0.345)^2 + cos(0.345)^2").unwrap(); 34 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 1.0); 35 | } 36 | 37 | #[test] 38 | fn test_eval5() { 39 | let expr = ShuntingParser::parse_str("sin(e)/cos(e)").unwrap(); 40 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), -0.4505495340698074); 41 | } 42 | 43 | #[test] 44 | fn test_eval6() { 45 | let expr = ShuntingParser::parse_str("(3+4)*3").unwrap(); 46 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 21.0); 47 | } 48 | 49 | #[test] 50 | fn test_eval7() { 51 | let expr = ShuntingParser::parse_str("(3+4)*3").unwrap(); 52 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 21.0); 53 | } 54 | 55 | #[test] 56 | fn test_eval8() { 57 | let expr = ShuntingParser::parse_str("2^3").unwrap(); 58 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 8.0); 59 | let expr = ShuntingParser::parse_str("2^-3").unwrap(); 60 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), 0.125); 61 | let expr = ShuntingParser::parse_str("-2^3").unwrap(); 62 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), -8.0); 63 | let expr = ShuntingParser::parse_str("-2^-3").unwrap(); 64 | fuzzy_eq!(MathContext::new().eval(&expr).unwrap(), -0.125); 65 | } 66 | -------------------------------------------------------------------------------- /shunting/src/rpnprint.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::RPNExpr; 2 | use lexers::MathToken; 3 | use std::fmt; 4 | 5 | #[derive(Debug, Clone)] 6 | enum AST<'a> { 7 | Leaf(&'a MathToken), 8 | Node(&'a MathToken, Vec>), 9 | } 10 | 11 | impl RPNExpr { 12 | fn build_ast(&self) -> AST { 13 | let mut ops = Vec::new(); 14 | for token in &self.0 { 15 | match token { 16 | MathToken::Number(_) | MathToken::Variable(_) => 17 | ops.push(AST::Leaf(token)), 18 | MathToken::Function(_, arity) => { 19 | let children = ops.split_off(ops.len() - arity); 20 | ops.push(AST::Node(token, children)); 21 | }, 22 | MathToken::BOp(_) => { 23 | let children = ops.split_off(ops.len() - 2); 24 | ops.push(AST::Node(token, children)); 25 | }, 26 | MathToken::UOp(_) => { 27 | let children = ops.split_off(ops.len() - 1); 28 | ops.push(AST::Node(token, children)); 29 | }, 30 | _ => unreachable!(), 31 | } 32 | } 33 | ops.pop().unwrap() 34 | } 35 | } 36 | 37 | impl fmt::Display for RPNExpr { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | fn print_helper(root: &AST, indent: &str, out: &mut String) { 40 | match root { 41 | AST::Leaf(tok) => *out += &format!("\u{2500}{:?}\n", tok), 42 | AST::Node(tok, children) => { 43 | // Print current node 44 | *out += &format!("\u{252c}{:?}\n", tok); 45 | // Print its children 46 | if let Some((last_node, rest)) = children.split_last() { 47 | for mid_node in rest { 48 | *out += &format!("{}\u{251c}", indent); 49 | print_helper(mid_node, &format!("{}\u{2502}", indent), out); 50 | } 51 | *out += &format!("{}\u{2570}", indent); 52 | print_helper(last_node, &format!("{} ", indent), out); 53 | } 54 | } 55 | } 56 | } 57 | let mut output = String::new(); 58 | print_helper(&self.build_ast(), "", &mut output); 59 | write!(f, "{}", output) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toxtools" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | rustyline = { version = "14.0.0" } 8 | numerica = { version = "0.1", path = "../numerica" } 9 | 10 | # [[bin]] 11 | # name = "numerica" 12 | -------------------------------------------------------------------------------- /tools/src/bin/numerica.rs: -------------------------------------------------------------------------------- 1 | extern crate numerica; 2 | 3 | fn main() -> Result<(), String> { 4 | let parser = numerica::parser()?; 5 | 6 | if std::env::args().len() > 1 { 7 | let input = std::env::args().skip(1).collect::>().join(" "); 8 | match parser(input.as_str()) { 9 | Err(e) => println!("Parse err: {:?}", e), 10 | Ok(expr) => { 11 | if numerica::is_stochastic(&expr) { 12 | let mut ctx = numerica::Context::new(); 13 | if let Err(_) = numerica::plot_histogram(&expr, &mut ctx) { 14 | println!("{}", expr); 15 | } 16 | } else { 17 | println!("{}", expr); 18 | }; 19 | } 20 | } 21 | return Ok(()); 22 | } 23 | 24 | use rustyline::error::ReadlineError; 25 | let mut rl = rustyline::DefaultEditor::new().map_err(|e| e.to_string())?; 26 | let mut ctx = numerica::Context::new(); 27 | loop { 28 | match rl.readline("~> ") { 29 | Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => return Ok(()), 30 | Err(e) => return Err(format!("Readline err: {:?}", e)), 31 | Ok(line) => match parser(line.as_str()) { 32 | Err(e) => println!("Parse err: {:?}", e), 33 | Ok(expr) => { 34 | // Debug 35 | // let _ = numerica::expr_tree(line.as_str()); 36 | let _ = rl.add_history_entry(&line); 37 | match numerica::evaluate(expr, &mut ctx) { 38 | Err(e) => println!("Eval err: {:?}", e), 39 | Ok(expr) => { 40 | if numerica::is_stochastic(&expr) { 41 | let mut ctx = numerica::Context::new(); 42 | if let Err(_) = numerica::plot_histogram(&expr, &mut ctx) { 43 | println!("{}", expr); 44 | } 45 | } else { 46 | println!("{}", expr); 47 | }; 48 | } 49 | } 50 | } 51 | }, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /unidades/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unidades" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Rodolfo Granata "] 7 | description = "A library for playing with physical units" 8 | repository = "https://github.com/rodolf0/tox/tree/master/unidades" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /unidades/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod si_units; 4 | pub use si_units::units; 5 | --------------------------------------------------------------------------------