├── .dinghy.toml ├── .github └── issue_template.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── example.rs ├── build.rs ├── cli-debug ├── Cargo.toml └── src │ └── main.rs ├── cli ├── Cargo.toml └── src │ └── main.rs ├── data ├── de.json ├── en.json ├── fr.json └── ko.json ├── grammar ├── Cargo.toml ├── de │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules.rs │ │ └── training.rs ├── en │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules_amount.rs │ │ ├── rules_celebrations.rs │ │ ├── rules_datetime.rs │ │ ├── rules_duration.rs │ │ ├── rules_number.rs │ │ └── training.rs ├── es │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules_amount.rs │ │ ├── rules_celebrations.rs │ │ ├── rules_datetime.rs │ │ ├── rules_duration.rs │ │ ├── rules_number.rs │ │ └── training.rs ├── fr │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules_amount.rs │ │ ├── rules_celebrations.rs │ │ ├── rules_datetime.rs │ │ ├── rules_duration.rs │ │ ├── rules_number.rs │ │ └── training.rs ├── it │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules_amount.rs │ │ ├── rules_celebrations.rs │ │ ├── rules_datetime.rs │ │ ├── rules_duration.rs │ │ ├── rules_number.rs │ │ └── training.rs ├── ja │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules.rs │ │ └── training.rs ├── ko │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules.rs │ │ └── training.rs ├── pt │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── rules_amount.rs │ │ ├── rules_celebrations.rs │ │ ├── rules_datetime.rs │ │ ├── rules_duration.rs │ │ ├── rules_number.rs │ │ └── training.rs ├── src │ └── lib.rs └── zh │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── rules.rs │ └── training.rs ├── json-utils ├── Cargo.toml └── src │ └── lib.rs ├── moment ├── Cargo.toml ├── benches │ └── example.rs └── src │ ├── bidirectional_walker.rs │ ├── interval_constraints.rs │ ├── lib.rs │ ├── period.rs │ └── walker.rs ├── src ├── lib.rs ├── mapper.rs ├── parser.rs └── tagger.rs ├── tests ├── de.rs ├── en.rs ├── es.rs ├── fr.rs ├── it.rs ├── ja.rs ├── pt.rs └── utils │ └── mod.rs ├── update_version.sh └── values ├── Cargo.toml └── src ├── check.rs ├── context.rs ├── dimension.rs ├── helpers.rs ├── lib.rs ├── macros_rules.rs ├── macros_training.rs └── output.rs /.dinghy.toml: -------------------------------------------------------------------------------- 1 | [test_data] 2 | data = "./data" 3 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | # Parsing Error 3 | 4 | ## Version 5 | 6 | 7 | `your.tag.number` 8 | 9 | ## Language 10 | 11 | `your_language_code` 12 | 13 | ## Parser input 14 | 15 | `your input here` 16 | 17 | ## Parser output 18 | 19 | ``` 20 | Paste the output of the `rustling-cli` here 21 | ``` 22 | 23 | ## Parser expected output (Optional) 24 | 25 | ``` 26 | Paste the expected output of the `rustling-cli` here 27 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .cargo/* 4 | *.rustfmt 5 | .idea/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | os: 5 | - linux 6 | - windows 7 | script: 8 | - cargo test --all --verbose 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unrelease] 5 | 6 | ### Changed 7 | - Rename `ResolverContext::new` by `ResolverContext::for_reference` 8 | 9 | ### Added 10 | - `ResolverContext::new` to build a context with a reference, min and max interval. 11 | 12 | ## [0.19.3] 13 | ### Fixed 14 | - Remove Chinese training examples causing issues on raspbian and windows [#205](https://github.com/snipsco/rustling-ontology/pull/205) 15 | - Fix float parsing for all languages [#203](https://github.com/snipsco/rustling-ontology/pull/203) 16 | - Add various improvements in all languages [#203](https://github.com/snipsco/rustling-ontology/pull/203) 17 | 18 | ## [0.19.2] 19 | ### Fixed 20 | - Fix chrono issue and move to rust edition 2018 [#194](https://github.com/snipsco/rustling-ontology/pull/194) 21 | - Bump rustling to `0.9.1` [#195](https://github.com/snipsco/rustling-ontology/pull/195) 22 | 23 | ## [0.19.1] 24 | ### Added 25 | - [Fr] Implement datetime subtypes in French [#191](https://github.com/snipsco/rustling-ontology/pull/191) 26 | - [Fr] Improve grammars [#191](https://github.com/snipsco/rustling-ontology/pull/191) 27 | 28 | ## [0.19.0] 29 | ### Added 30 | - [All] Added new datetime subtypes [#167](https://github.com/snipsco/rustling-ontology/pull/167) 31 | 32 | ### Fixed 33 | - [All] Include prefix + in numbers [#186](https://github.com/snipsco/rustling-ontology/pull/186) 34 | - [All] Set boundaries for quarters in datetimes [#185](https://github.com/snipsco/rustling-ontology/pull/185) 35 | - [En] En moneys: add "centime" (request from PM team) [#183](https://github.com/snipsco/rustling-ontology/pull/183) 36 | - [Fr] Fix/fr add duration vocab 2 [#182](https://github.com/snipsco/rustling-ontology/pull/182) 37 | - [Fr] Fr "sept" abbreviation (for "september") removed if no following dot [#178](https://github.com/snipsco/rustling-ontology/pull/178) 38 | - [Es] Misc. fixes for Spanish. [#177](https://github.com/snipsco/rustling-ontology/pull/177) 39 | - [Ja] Delete rule that accepts numbers followed by quantifiers for cardinal [#176](https://github.com/snipsco/rustling-ontology/pull/176) 40 | - [Fr] Fix some interval rules in Fr and switched Duration/Datetime priority [#173](https://github.com/snipsco/rustling-ontology/pull/173) 41 | - [En] Typo in English training [#168](https://github.com/snipsco/rustling-ontology/pull/168) 42 | 43 | ## [0.18.1] 44 | ### Fixed 45 | - [Es] Various fixes 46 | - [Ja] Remove quantifiers in Japanese cardinals 47 | - [Fr] Fixed some interval rules and switched Duration/Datetime priority 48 | - [En] Fixed typos in training examples 49 | 50 | ### Added 51 | - [Pt] Improved all entities 52 | 53 | ## [0.18.0] 54 | ### Changed 55 | - [Pt] Add Portuguese V0 56 | 57 | ### Fixed 58 | - Crash when attempting to parse wrong month and day. 59 | - Fix and adjust date written abbreviations in all languages. 60 | - [De] Change end of time span setting to get the right intervals. 61 | - [De] Fix relative minute for value=1. 62 | - [It] Fix financial rule with Rubles. 63 | - [Es] Fix percentage pattern and other typos. 64 | 65 | ## [0.17.7] - 2019-01-17 66 | ### Changed 67 | - Fix resolution of decimal numbers in textual form. 68 | 69 | ## [0.17.6] - 2018-12-13 70 | ### Changed 71 | - Fuller coverage of Spanish and Italian 72 | 73 | [0.19.3]: https://github.com/snipsco/rustling-ontology/compare/0.19.2...0.19.3 74 | [0.19.2]: https://github.com/snipsco/rustling-ontology/compare/0.19.1...0.19.2 75 | [0.19.1]: https://github.com/snipsco/rustling-ontology/compare/0.19.0...0.19.1 76 | [0.19.0]: https://github.com/snipsco/rustling-ontology/compare/0.18.1...0.19.0 77 | [0.18.1]: https://github.com/snipsco/rustling-ontology/compare/0.18.0...0.18.1 78 | [0.18.0]: https://github.com/snipsco/rustling-ontology/compare/0.17.7...0.18.0 79 | [0.17.7]: https://github.com/snipsco/rustling-ontology/compare/0.17.6...0.17.7 80 | [0.17.6]: https://github.com/snipsco/rustling-ontology/compare/0.17.5...0.17.6 81 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | build = "build.rs" 6 | edition = "2018" 7 | 8 | [workspace] 9 | members=[ 10 | "cli", 11 | "cli-debug", 12 | "grammar", 13 | "grammar/de", 14 | "grammar/en", 15 | "grammar/es", 16 | "grammar/fr", 17 | "grammar/it", 18 | "grammar/ja", 19 | "grammar/ko", 20 | "grammar/pt", 21 | "grammar/zh", 22 | "json-utils", 23 | "moment", 24 | "values", 25 | ] 26 | 27 | [dependencies] 28 | rmp-serde = "0.14" 29 | serde = { version = "1", features = ["derive"] } 30 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 31 | rustling-ontology-moment = { path = "moment" } 32 | rustling-ontology-values = { path = "values" } 33 | rustling-ontology-grammar = { path = "grammar" } 34 | 35 | [dev-dependencies] 36 | bencher = { git = "https://github.com/snipsco/bencher", rev="63910ace" } 37 | rustling-ontology-json-utils = { path = "json-utils" } 38 | serde_json = "1" 39 | 40 | [build-dependencies] 41 | rmp-serde = "0.14" 42 | serde = { version = "1", features = ["derive"] } 43 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 44 | rustling-ontology-values = { path = "values" } 45 | rustling-ontology-moment = { path = "moment" } 46 | rustling-ontology-grammar = { path = "grammar" } 47 | 48 | [[bench]] 49 | name = "example" 50 | harness = false 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | RUN apt-get update && apt-get upgrade -y && apt-get clean 3 | RUN apt-get update && apt-get install -y python-setuptools python-wheel 4 | RUN apt-get install -y gcc g++ curl wget unzip git pkg-config libssl-dev libssh2-1-dev cmake\ 5 | && apt-get clean 6 | 7 | # Add user jenkins to the image 8 | RUN adduser --quiet --home /build/ build 9 | 10 | RUN git clone https://github.com/raspberrypi/tools /opt/pitools --depth 1 11 | 12 | USER build 13 | WORKDIR /build 14 | 15 | #install rust 16 | RUN HOME=/build curl https://sh.rustup.rs -sSf | HOME=/build sh -s -- -y 17 | 18 | #setup path with cargo & protoc 19 | RUN echo 'PATH=/build/.cargo/bin:$PATH' >> /build/.bashrc 20 | 21 | RUN /build/.cargo/bin/cargo install dinghy 22 | 23 | RUN /build/.cargo/bin/rustup target install armv7-unknown-linux-gnueabihf \ 24 | && /build/.cargo/bin/rustup target install arm-unknown-linux-gnueabihf 25 | 26 | RUN echo '[target.arm-unknown-linux-gnueabihf]\n\ 27 | linker = "/opt/pitools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc"\n\ 28 | \n\ 29 | [target.armv7-unknown-linux-gnueabihf]\n\ 30 | linker = "/opt/pitools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc"\n\ 31 | '>> /build/.cargo/config 32 | 33 | 34 | RUN echo 'export TARGET_CC=/opt/pitools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc' >> /build/.bashrc 35 | 36 | RUN ln -s /pypirc /build/.pypirc 37 | RUN ln -s /dinghy.toml /build/.dinghy.toml 38 | RUN ln -s /ssh-conf /build/.ssh 39 | 40 | WORKDIR /build/workdir 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Licensed under either of 4 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 5 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 6 | at your option. 7 | 8 | ### Contribution 9 | 10 | Unless you explicitly state otherwise, any contribution intentionally submitted 11 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 12 | be dual licensed as above, without any additional terms or conditions. 13 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustling-ontology 2 | [![Build Status](https://travis-ci.org/snipsco/rustling-ontology.svg?branch=master)](https://travis-ci.org/snipsco/rustling-ontology) 3 | [![Build Status](https://ci.appveyor.com/api/projects/status/github/snipsco/rustling-ontology?branch=master&svg=true)](https://ci.appveyor.com/project/snipsco/rustling-ontology) 4 | 5 | Probabilistic parser for entity detection based on Rustling (https://github.com/snipsco/rustling) 6 | 7 | Rustling is a rust port of https://github.com/facebookincubator/duckling 8 | 9 | 10 | 11 | ## Supported Output 12 | 13 | | Output | OutputKind | 14 | | --------- | ------------- | 15 | | Integer | Number | 16 | | Float | Number | 17 | | Ordinal | Ordinal | 18 | | Temperature | Temperature | 19 | | Time | Time | 20 | | TimeInterval | Time | 21 | | AmountOfMoney | AmountOfMoney | 22 | | Duration | Duration | 23 | 24 | 25 | ## Benches 26 | 27 | If you want to bench the project you will need to an environment variable named `SNIPS_RUSTLING_BENCH_INPUT` with one of these values: 28 | 29 | | Language | File | 30 | | -------- | ---- | 31 | | English | en.json | 32 | | French | fr.json | 33 | | Korean | ko.json | 34 | | German | de.json | 35 | 36 | ## Get started 37 | 38 | ### Install 39 | 40 | - Open a terminal 41 | 42 | - Install rust 43 | 44 | ``` 45 | curl https://sh.rustup.rs -sSf | sh 46 | ``` 47 | 48 | Select the default installation and add cargo to your source path with `source $HOME/.cargo/env`. You can also add this line 49 | `export PATH=$PATH:$HOME/.cargo/bin` to your shell configuration `.bashrc` or `zshrc` (depending on your terminal) 50 | 51 | - Clone this repository: 52 | 53 | ``` 54 | git clone git@github.com:snipsco/rustling-ontology.git 55 | ``` 56 | 57 | ### Build the library 58 | 59 | ``` 60 | cd rustling-ontology 61 | cargo build 62 | ``` 63 | 64 | It can take a while because the training for all languages takes time. 65 | 66 | ### Use the command line to run Rustling 67 | 68 | First, go to the cli folder 69 | ``` 70 | cd cli 71 | ``` 72 | 73 | Second, run this command 74 | 75 | ``` 76 | cargo run -- --lang en parse "tomorrow morning" 77 | ``` 78 | 79 | If you want to reduce the scope of rustling, you can run: 80 | 81 | ``` 82 | cargo run -- --lang fr parse "reserve un restaurant demain matin pour cinq personnes" -k Time,Number 83 | ``` 84 | 85 | If you want to see how the sentence has been parsed by rustling, you can run: 86 | 87 | ``` 88 | cargo run -- --lang en play "monday september the twenty sixth" 89 | ``` 90 | 91 | In this mode, the reference date used is the current date 92 | 93 | ### Use the command line to debug Rustling 94 | 95 | go to the cli-debug folder 96 | ``` 97 | cd cli-debug 98 | ``` 99 | 100 | run this command 101 | 102 | ``` 103 | cargo run -- --lang en parse "tomorrow morning" 104 | ``` 105 | 106 | It will display how the sentence has been parsed by rustling without any ML model. (Faster to compile because the training is not done) 107 | 108 | In debug mode, the reference date used is 2013/02/12 109 | 110 | # License 111 | 112 | ## Apache 2.0/MIT 113 | 114 | All original work licensed under either of 115 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 116 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 117 | at your option. 118 | 119 | ## Contribution 120 | 121 | Unless you explicitly state otherwise, any contribution intentionally submitted 122 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 123 | be dual licensed as above, without any additional terms or conditions. 124 | -------------------------------------------------------------------------------- /benches/example.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bencher; 3 | extern crate rustling_ontology; 4 | extern crate serde; 5 | extern crate serde_json; 6 | #[macro_use] 7 | extern crate serde_derive; 8 | 9 | use std::env; 10 | use std::fs; 11 | use std::path; 12 | use std::str::FromStr; 13 | 14 | use rustling_ontology::*; 15 | use rustling_ontology::dimension::Dimension; 16 | use bencher::Bencher; 17 | 18 | #[derive(Debug, Deserialize)] 19 | struct BenchInput { 20 | language: String, 21 | small_numbers: Option>, 22 | big_numbers: Option>, 23 | intent_sentences: Vec, 24 | complex_sentence: String, 25 | } 26 | 27 | impl BenchInput { 28 | fn rustling_lang(&self) -> Lang { 29 | Lang::from_str(&self.language).unwrap() 30 | } 31 | } 32 | 33 | fn parse_bench_input() -> BenchInput { 34 | let path = env::var("SNIPS_RUSTLING_BENCH_INPUT") 35 | .map_err(|_| "SNIPS_RUSTLING_BENCH_INPUT env var not defined") 36 | .unwrap(); 37 | let file = fs::File::open(file_path(&path)).unwrap(); 38 | 39 | serde_json::from_reader(file).unwrap() 40 | } 41 | 42 | fn file_path(file_name: &str) -> path::PathBuf { 43 | if env::var("DINGHY").is_ok() { 44 | env::current_exe() 45 | .unwrap() 46 | .parent() 47 | .unwrap() 48 | .join("test_data/data") 49 | .join(file_name) 50 | } else { 51 | path::PathBuf::from("data").join(file_name) 52 | } 53 | } 54 | 55 | fn parsing_tagger<'a>(kinds: &'a [OutputKind], context: &'a IdentityContext) -> CandidateTagger<'a, IdentityContext> { 56 | CandidateTagger { 57 | output_kind_filter: kinds, 58 | context: context, 59 | resolve_all_candidates: false, 60 | } 61 | } 62 | 63 | fn parser_training(bench: &mut Bencher) { 64 | let input = parse_bench_input(); 65 | 66 | bench.iter(|| train_parser(input.rustling_lang()).unwrap()); 67 | } 68 | 69 | fn parser_loading(bench: &mut Bencher) { 70 | let input = parse_bench_input(); 71 | 72 | bench.iter(|| build_parser(input.rustling_lang()).unwrap()); 73 | } 74 | 75 | fn parse_small_numbers(bench: &mut Bencher) { 76 | let input = parse_bench_input(); 77 | 78 | let parser = build_parser(input.rustling_lang()).unwrap(); 79 | let context = ResolverContext::default(); 80 | let sentences = input.small_numbers.unwrap_or(vec![]); 81 | bench.iter(|| { 82 | for i in sentences.iter() { 83 | let _ = parser.parse(&*i, &context); 84 | } 85 | }); 86 | } 87 | 88 | fn parse_big_numbers(bench: &mut Bencher) { 89 | let input = parse_bench_input(); 90 | let parser = build_parser(input.rustling_lang()).unwrap(); 91 | let context = ResolverContext::default(); 92 | let sentences = input.big_numbers.unwrap_or(vec![]); 93 | bench.iter(|| { 94 | for i in sentences.iter() { 95 | let _ = parser.parse(&*i, &context); 96 | } 97 | }); 98 | } 99 | 100 | fn parse_intent_sentences(bench: &mut Bencher) { 101 | let input = parse_bench_input(); 102 | let parser = build_parser(input.rustling_lang()).unwrap(); 103 | let sentences = input.intent_sentences; 104 | let context = ResolverContext::default(); 105 | bench.iter(|| { 106 | for i in sentences.iter() { 107 | let _ = parser.parse(&*i, &context); 108 | } 109 | }); 110 | } 111 | 112 | fn parse_complex_time_sentence(bench: &mut Bencher) { 113 | let input = parse_bench_input(); 114 | let parser = build_parser(input.rustling_lang()).unwrap(); 115 | let context = ResolverContext::default(); 116 | let sentence = input.complex_sentence; 117 | bench.iter(|| parser.parse(&sentence, &context)); 118 | } 119 | 120 | benchmark_group!(benches, 121 | parse_small_numbers, 122 | parse_big_numbers, 123 | parse_intent_sentences, 124 | parse_complex_time_sentence 125 | ); 126 | 127 | benchmark_main!(benches); 128 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate rmp_serde; 2 | extern crate rustling; 3 | extern crate rustling_ontology_grammar as grammar; 4 | extern crate rustling_ontology_values; 5 | extern crate serde; 6 | 7 | #[path = "src/parser.rs"] 8 | mod parser; 9 | 10 | use grammar::Lang; 11 | use std::thread::{self, JoinHandle}; 12 | use std::{env, fs, path}; 13 | 14 | pub fn train_async(lang: Lang) -> JoinHandle<()> { 15 | println!( 16 | "cargo:rerun-if-changed=grammar/{}/src/", 17 | lang.to_string().to_lowercase() 18 | ); 19 | thread::spawn(move || { 20 | let out_dir = path::PathBuf::from(env::var("OUT_DIR").unwrap()); 21 | let mut file = fs::File::create(out_dir.join(format!( 22 | "{}{}", 23 | lang.to_string().to_lowercase(), 24 | ".rmp" 25 | ))) 26 | .unwrap(); 27 | let rules = grammar::rules(lang).unwrap(); 28 | let examples = grammar::examples(lang); 29 | let model = rustling::train::train(&rules, examples, parser::FeatureExtractor()).unwrap(); 30 | rmp_serde::encode::write(&mut file, &model).unwrap(); 31 | }) 32 | } 33 | 34 | pub fn train_sync(lang: Lang) { 35 | println!( 36 | "cargo:rerun-if-changed=grammar/{}/src/", 37 | lang.to_string().to_lowercase() 38 | ); 39 | let out_dir = path::PathBuf::from(env::var("OUT_DIR").unwrap()); 40 | let mut file = 41 | fs::File::create(out_dir.join(format!("{}{}", lang.to_string().to_lowercase(), ".rmp"))) 42 | .unwrap(); 43 | let rules = grammar::rules(lang).unwrap(); 44 | let examples = grammar::examples(lang); 45 | let model = rustling::train::train(&rules, examples, parser::FeatureExtractor()).unwrap(); 46 | rmp_serde::encode::write(&mut file, &model).unwrap(); 47 | } 48 | 49 | pub fn train_all_async() { 50 | let join_handlers: Vec<_> = Lang::all() 51 | .into_iter() 52 | .map(|lang| train_async(lang)) 53 | .collect(); 54 | 55 | for join in join_handlers { 56 | join.join().unwrap(); 57 | } 58 | } 59 | 60 | pub fn train_all_sync() { 61 | for lang in Lang::all() { 62 | train_sync(lang); 63 | } 64 | } 65 | 66 | fn main() { 67 | train_all_sync(); 68 | } 69 | -------------------------------------------------------------------------------- /cli-debug/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-cli-debug" 3 | version = "0.19.3" 4 | authors = ["hdlj ", "Mathieu Poumeyrol "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-grammar = { path = "../grammar" } 10 | rustling-ontology-moment = { path = "../moment" } 11 | rustling-ontology-values = { path = "../values" } 12 | prettytable-rs = "0.6" 13 | clap = "2" 14 | -------------------------------------------------------------------------------- /cli-debug/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | extern crate rustling; 4 | extern crate rustling_ontology_grammar as grammar; 5 | extern crate rustling_ontology_values as values; 6 | extern crate rustling_ontology_moment; 7 | #[macro_use] 8 | extern crate prettytable; 9 | 10 | use rustling_ontology_moment::*; 11 | use prettytable::Table; 12 | use values::{ParsingContext, ResolverContext}; 13 | 14 | fn main() { 15 | let matches = clap_app!(rustling_cli => 16 | (@arg lang: -l --lang default_value[en] "2-letter language code (default to \"en\")") 17 | (@subcommand parse => 18 | (@arg kinds: -k --kinds +takes_value +use_delimiter "kinds, last one wins, coma separated") 19 | (@arg sentence: +required "Sentence to test") 20 | ) 21 | ).get_matches(); 22 | let lang = value_t!(matches.value_of("lang"), grammar::Lang).unwrap_or_else(|e| e.exit()); 23 | match matches.subcommand() { 24 | ("parse", Some(matches)) => { 25 | let sentence = matches.value_of("sentence").unwrap().to_lowercase(); 26 | let decoder = ResolverContext::for_reference(Interval::starting_at(Moment(Local.ymd(2013, 2, 12).and_hms(4, 30, 0)), Grain::Second)); 27 | let rules = grammar::rules(lang).unwrap(); 28 | let matches = rules.apply_all(&*sentence).unwrap(); 29 | let mut table = Table::new(); 30 | table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); 31 | table.set_titles(row!["ix", "text", "Dimension", "Output(OutputValue)", "rule", "children"]); 32 | for (ix, m) in matches.iter().enumerate().rev() { 33 | let mut hilite = String::new(); 34 | let byte_range = m.root_node.byte_range; 35 | for _ in 0..byte_range.0 { 36 | hilite.push('_'); 37 | } 38 | hilite.push_str(&sentence[byte_range.0..byte_range.1]); 39 | for _ in byte_range.1..sentence.len() { 40 | hilite.push('_'); 41 | } 42 | table.add_row(row![ix, 43 | hilite, 44 | &m.value, 45 | decoder.resolve(&m.value).map(|v| format!("{:?}", v)).unwrap_or("".into()), 46 | rules.resolve_sym(&m.root_node.rule_sym).unwrap_or(""), 47 | m.root_node 48 | .children 49 | .iter() 50 | .map(|n| { 51 | let name = rules.resolve_sym(&n.rule_sym).unwrap_or(""); 52 | name.chars().take(20).collect::() 53 | }) 54 | .collect::>() 55 | .join(" + ") 56 | ]); 57 | } 58 | table.printstd(); 59 | } 60 | (cmd, _) => panic!("Unknown command {}", cmd), 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-cli" 3 | version = "0.19.3" 4 | authors = ["hdlj ", "Mathieu Poumeyrol "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2" 9 | prettytable-rs = "0.6" 10 | rustling-ontology = { path = ".." } 11 | rustling-ontology-json-utils = { path = "../json-utils" } 12 | rustling-ontology-moment = { path = "../moment" } 13 | serde_json = "1" 14 | -------------------------------------------------------------------------------- /grammar/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-grammar" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-de = { path = "de" } 10 | rustling-ontology-en = { path = "en" } 11 | rustling-ontology-es = { path = "es" } 12 | rustling-ontology-fr = { path = "fr" } 13 | rustling-ontology-pt = { path = "pt" } 14 | rustling-ontology-ko = { path = "ko" } 15 | rustling-ontology-zh = { path = "zh" } 16 | rustling-ontology-ja = { path = "ja" } 17 | rustling-ontology-it = { path = "it" } 18 | rustling-ontology-values = { path = "../values" } 19 | -------------------------------------------------------------------------------- /grammar/de/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-de" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/de/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules; 7 | mod training; 8 | 9 | use rustling_ontology_values::DimensionKind::*; 10 | 11 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 12 | let mut b = ::rustling::RuleSetBuilder::new( 13 | ::rustling::BoundariesChecker::composed_word_or_detailed(), 14 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 15 | rules::rules_numbers(&mut b)?; 16 | rules::rules_datetime(&mut b)?; 17 | rules::rules_cycle(&mut b)?; 18 | rules::rules_duration(&mut b)?; 19 | rules::rules_temperature(&mut b)?; 20 | rules::rules_finance(&mut b)?; 21 | rules::rules_percentage(&mut b)?; 22 | Ok(b.build()) 23 | } 24 | 25 | pub fn dims() -> Vec { 26 | return vec![Number, Ordinal, Datetime, Duration, Temperature, AmountOfMoney, Percentage]; 27 | } 28 | 29 | pub fn examples() -> Vec<::rustling::train::Example> { 30 | let mut v = vec![]; 31 | training::examples_numbers(&mut v); 32 | training::examples_datetime(&mut v); 33 | training::examples_temperature(&mut v); 34 | training::examples_finance(&mut v); 35 | training::examples_percentage(&mut v); 36 | v 37 | } 38 | 39 | #[cfg(test)] 40 | mod test { 41 | use rustling::*; 42 | use rustling_ontology_values::dimension::Dimension; 43 | use super::*; 44 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 45 | for ex in examples.iter() { 46 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 47 | let correct_results = stash 48 | .into_iter() 49 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 50 | .collect::>(); 51 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 52 | } 53 | } 54 | #[test] 55 | fn test_examples() { 56 | let rules = rule_set().unwrap(); 57 | let examples = examples(); 58 | assert_examples(&rules, examples); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /grammar/en/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-en" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/en/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules_datetime; 7 | mod rules_celebrations; 8 | mod rules_duration; 9 | mod rules_number; 10 | mod rules_amount; 11 | mod training; 12 | 13 | use rustling_ontology_values::DimensionKind::*; 14 | 15 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 16 | let mut b = ::rustling::RuleSetBuilder::new( 17 | ::rustling::BoundariesChecker::detailed(), 18 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 19 | rules_number::rules_numbers(&mut b)?; 20 | rules_amount::rules_temperature(&mut b)?; 21 | rules_amount::rules_finance(&mut b)?; 22 | rules_amount::rules_percentage(&mut b)?; 23 | rules_datetime::rules_cycle(&mut b)?; 24 | rules_datetime::rules_datetime(&mut b)?; 25 | rules_datetime::rules_datetime_with_duration(&mut b)?; 26 | rules_datetime::rules_datetime_with_cycle(&mut b)?; 27 | rules_celebrations::rules_celebration(&mut b)?; 28 | rules_duration::rules_duration(&mut b)?; 29 | Ok(b.build()) 30 | } 31 | 32 | pub fn dims() -> Vec { 33 | return vec![Number, Ordinal, Datetime, Duration, Temperature, AmountOfMoney, Percentage]; 34 | } 35 | 36 | pub fn examples() -> Vec<::rustling::train::Example> { 37 | let mut v = vec![]; 38 | training::examples_numbers(&mut v); 39 | training::examples_datetime(&mut v); 40 | training::examples_durations(&mut v); 41 | training::examples_temperature(&mut v); 42 | training::examples_finance(&mut v); 43 | training::examples_percentage(&mut v); 44 | v 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use rustling::*; 50 | use rustling_ontology_values::dimension::Dimension; 51 | 52 | use super::*; 53 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 54 | for ex in examples.iter() { 55 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 56 | let correct_results = stash 57 | .into_iter() 58 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 59 | .collect::>(); 60 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 61 | } 62 | } 63 | #[test] 64 | fn test_examples() { 65 | let rules = rule_set().unwrap(); 66 | let examples = examples(); 67 | assert_examples(&rules, examples); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /grammar/en/src/rules_amount.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::dimension::Precision::*; 4 | use rustling_ontology_values::helpers; 5 | 6 | pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_2(" per cent", 8 | number_check!(), 9 | b.reg(r"(?:%|p\.c\.|per ?cents?)")?, 10 | |number, _| Ok(PercentageValue(number.value().value())) 11 | ); 12 | Ok(()) 13 | } 14 | 15 | pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { 16 | b.rule_2("intersect (X cents)", 17 | amount_of_money_check!(), 18 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 19 | |a, b| helpers::compose_money(a.value(), b.value())); 20 | b.rule_3("intersect (and X cents)", 21 | amount_of_money_check!(), 22 | b.reg(r#"and"#)?, 23 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 24 | |a, _, b| helpers::compose_money(&a.value(), &b.value())); 25 | b.rule_2("intersect", 26 | amount_of_money_check!(), 27 | number_check!(), 28 | |a, b| helpers::compose_money_number(&a.value(), &b.value())); 29 | b.rule_3("intersect (and number)", 30 | amount_of_money_check!(), 31 | b.reg(r#"and"#)?, 32 | number_check!(), 33 | |a, _, b| helpers::compose_money_number(&a.value(), &b.value())); 34 | b.rule_1_terminal("$", 35 | b.reg(r#"\$|dollars?"#)?, 36 | |_| Ok(MoneyUnitValue { unit: Some("$") }) 37 | ); 38 | b.rule_1_terminal("USD", 39 | b.reg(r#"us[d\$]|(?:us|american) dollars?|bucks?"#)?, 40 | |_| Ok(MoneyUnitValue { unit: Some("USD") }) 41 | ); 42 | b.rule_1_terminal("AUD", 43 | b.reg(r#"au[d\$]|australian dollars?"#)?, 44 | |_| Ok(MoneyUnitValue { unit: Some("AUD") }) 45 | ); 46 | b.rule_1_terminal("CAD", 47 | b.reg(r#"cad|canadian dollars?"#)?, 48 | |_| Ok(MoneyUnitValue { unit: Some("CAD") }) 49 | ); 50 | b.rule_1_terminal("HKD", 51 | b.reg(r#"hkd|hk dollars?|hong[- ]?kong dollars?"#)?, 52 | |_| Ok(MoneyUnitValue { unit: Some("HKD") }) 53 | ); 54 | b.rule_1_terminal("EUR", 55 | b.reg(r#"€|(?:[e€]uro?s?)"#)?, 56 | |_| Ok(MoneyUnitValue { unit: Some("EUR") }) 57 | ); 58 | b.rule_1_terminal("£", 59 | b.reg(r#"£|pounds?"#)?, 60 | |_| Ok(MoneyUnitValue { unit: Some("£") }) 61 | ); 62 | b.rule_1_terminal("GBP", 63 | b.reg(r#"gbp|(?:sterling|british) pounds?|sterlings?|quids?"#)?, 64 | |_| Ok(MoneyUnitValue { unit: Some("GBP") }) 65 | ); 66 | b.rule_1_terminal("CHF", 67 | b.reg(r#"chf|swiss francs?"#)?, 68 | |_| Ok(MoneyUnitValue { unit: Some("CHF") }) 69 | ); 70 | b.rule_1_terminal("KR", 71 | b.reg(r#"kroner?|crowns?|kr"#)?, 72 | |_| Ok(MoneyUnitValue { unit: Some("KR") }) 73 | ); 74 | b.rule_1_terminal("DKK", 75 | b.reg(r#"dkk|danish (?:kroner?|crowns?)"#)?, 76 | |_| Ok(MoneyUnitValue { unit: Some("DKK") }) 77 | ); 78 | b.rule_1_terminal("NOK", 79 | b.reg(r#"nok|norwegian (?:kroner?|crowns?)"#)?, 80 | |_| Ok(MoneyUnitValue { unit: Some("NOK") }) 81 | ); 82 | b.rule_1_terminal("SEK", 83 | b.reg(r#"sek|swedish (?:krona|kronor|crowns?)"#)?, 84 | |_| Ok(MoneyUnitValue { unit: Some("SEK") }) 85 | ); 86 | b.rule_1_terminal("RUB", 87 | b.reg(r#"(?:russian )?ro?ubles?|rub"#)?, 88 | |_| Ok(MoneyUnitValue { unit: Some("RUB") }) 89 | ); 90 | b.rule_1_terminal("INR", 91 | b.reg(r#"inr|rs\.?|(?:indian )?rupees?"#)?, 92 | |_| Ok(MoneyUnitValue { unit: Some("INR") }) 93 | ); 94 | b.rule_1_terminal("JPY", 95 | b.reg(r#"jpy|yens?"#)?, 96 | |_| Ok(MoneyUnitValue { unit: Some("JPY") }) 97 | ); 98 | b.rule_1_terminal("CNY", 99 | b.reg(r#"cny|cnh|rmb|yuans?|renminbis?"#)?, 100 | |_| Ok(MoneyUnitValue { unit: Some("CNY") }) 101 | ); 102 | b.rule_1_terminal("¥", 103 | b.reg(r#"¥"#)?, 104 | |_| Ok(MoneyUnitValue { unit: Some("¥") }) 105 | ); 106 | b.rule_1_terminal("KRW", 107 | b.reg(r#"₩|krw|(?:south[- ]?)?korean wons?|wons?"#)?, 108 | |_| Ok(MoneyUnitValue { unit: Some("KRW") }) 109 | ); 110 | b.rule_1_terminal("฿", 111 | b.reg(r#"฿|bitcoins?"#)?, 112 | |_| Ok(MoneyUnitValue { unit: Some("฿") }) 113 | ); 114 | b.rule_1_terminal("cent", 115 | b.reg(r#"centimes?|cents?|penn(?:y|ies)|c|¢"#)?, 116 | |_| Ok(MoneyUnitValue { unit: Some("cent") }) 117 | ); 118 | b.rule_2(" ", 119 | money_unit!(), 120 | number_check!(), 121 | |a, b| { 122 | Ok(AmountOfMoneyValue { 123 | value: b.value().value(), 124 | unit: a.value().unit, 125 | ..AmountOfMoneyValue::default() 126 | }) 127 | }); 128 | b.rule_2(" ", 129 | number_check!(), 130 | money_unit!(), 131 | |a, b| { 132 | Ok(AmountOfMoneyValue { 133 | value: a.value().value(), 134 | unit: b.value().unit, 135 | ..AmountOfMoneyValue::default() 136 | }) 137 | }); 138 | b.rule_2("about ", 139 | b.reg(r#"(?:about|approx(?:\.|imately)?|close to|near(?: to)?|around|almost)"#)?, 140 | amount_of_money_check!(), 141 | |_, a| { 142 | Ok(AmountOfMoneyValue { 143 | precision: Approximate, 144 | ..a.value().clone() 145 | }) 146 | }); 147 | b.rule_2("exactly ", 148 | b.reg(r#"exactly|precisely"#)?, 149 | amount_of_money_check!(), 150 | |_, a| { 151 | Ok(AmountOfMoneyValue { 152 | precision: Exact, 153 | ..a.value().clone() 154 | }) 155 | }); 156 | Ok(()) 157 | } 158 | 159 | pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { 160 | b.rule_1("number as temp", 161 | number_check!(), 162 | |a| { 163 | Ok(TemperatureValue { 164 | value: a.value().value(), 165 | unit: None, 166 | latent: true, 167 | }) 168 | }); 169 | b.rule_2(" degrees", 170 | temperature_check!(|temp: &TemperatureValue| temp.latent), 171 | b.reg(r#"(?:deg(?:ree?)?s?\.?)|°"#)?, 172 | |a, _| { 173 | Ok(TemperatureValue { 174 | value: a.value().value, 175 | unit: Some("degree"), 176 | latent: true, 177 | }) 178 | }); 179 | b.rule_2(" Celcius", 180 | temperature_check!(|temp: &TemperatureValue| temp.latent), 181 | b.reg(r#"centigrade|c(?:el[cs]?(?:ius)?)?\.?"#)?, 182 | |a, _| { 183 | Ok(TemperatureValue { 184 | value: a.value().value, 185 | unit: Some("celsius"), 186 | latent: false, 187 | }) 188 | }); 189 | b.rule_2(" Fahrenheit", 190 | temperature_check!(|temp: &TemperatureValue| temp.latent), 191 | b.reg(r#"f(?:ah?rh?eh?n(?:h?eit)?)?\.?"#)?, 192 | |a, _| { 193 | Ok(TemperatureValue { 194 | value: a.value().value, 195 | unit: Some("fahrenheit"), 196 | latent: false, 197 | }) 198 | }); 199 | b.rule_2(" Kelvin", 200 | temperature_check!(), 201 | b.reg(r#"k(?:elvin)?\.?"#)?, 202 | |a, _| { 203 | Ok(TemperatureValue { 204 | value: a.value().value, 205 | unit: Some("kelvin"), 206 | latent: false, 207 | }) 208 | }); 209 | Ok(()) 210 | } 211 | 212 | -------------------------------------------------------------------------------- /grammar/en/src/rules_duration.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | use rustling_ontology_moment::{Grain, PeriodComp, Period}; 5 | 6 | pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_1_terminal("second (unit-of-duration)", 8 | b.reg(r#"sec(?:ond)?s?"#)?, 9 | |_| Ok(UnitOfDurationValue::new(Grain::Second)) 10 | ); 11 | b.rule_1_terminal("minute (unit-of-duration)", 12 | b.reg(r#"min(?:ute)?s?"#)?, 13 | |_| Ok(UnitOfDurationValue::new(Grain::Minute)) 14 | ); 15 | b.rule_1_terminal("hour (unit-of-duration)", 16 | b.reg(r#"h(?:(?:(?:ou)?rs?)|r)?"#)?, 17 | |_| Ok(UnitOfDurationValue::new(Grain::Hour)) 18 | ); 19 | b.rule_1_terminal("day (unit-of-duration)", 20 | b.reg(r#"days?"#)?, 21 | |_| Ok(UnitOfDurationValue::new(Grain::Day)) 22 | ); 23 | b.rule_1_terminal("week (unit-of-duration)", 24 | b.reg(r#"weeks?"#)?, 25 | |_| Ok(UnitOfDurationValue::new(Grain::Week)) 26 | ); 27 | b.rule_1_terminal("month (unit-of-duration)", 28 | b.reg(r#"months?"#)?, 29 | |_| Ok(UnitOfDurationValue::new(Grain::Month)) 30 | ); 31 | b.rule_1_terminal("quarter (unit-of-duration)", 32 | b.reg(r#"quarters?"#)?, 33 | |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) 34 | ); 35 | b.rule_1_terminal("year (unit-of-duration)", 36 | b.reg(r#"years?"#)?, 37 | |_| Ok(UnitOfDurationValue::new(Grain::Year)) 38 | ); 39 | b.rule_1_terminal("quarter of an hour", 40 | b.reg(r#"1/4\s?h(?:our)?|(?:a\s)?quarter(?: of an |-)hour"#)?, 41 | |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) 42 | ); 43 | b.rule_1_terminal("half an hour", 44 | b.reg(r#"1/2\s?h(?:our)?|half an? hour|an? half hour"#)?, 45 | |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) 46 | ); 47 | b.rule_1_terminal("three-quarters of an hour", 48 | b.reg(r#"3/4\s?h(?:our)?|three(?:\s|-)quarters of an hour"#)?, 49 | |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) 50 | ); 51 | b.rule_1_terminal("fortnight", 52 | b.reg(r#"(?:a|one)? fortnight"#)?, 53 | |_| Ok(DurationValue::new(PeriodComp::days(14).into())) 54 | ); 55 | b.rule_2(" ", 56 | integer_check_by_range!(0), 57 | unit_of_duration_check!(), 58 | |integer, uod| Ok(DurationValue::new(PeriodComp::new(uod.value().grain, integer.value().value).into())) 59 | ); 60 | b.rule_3(" more ", 61 | integer_check_by_range!(0), 62 | b.reg(r#"more"#)?, 63 | unit_of_duration_check!(), 64 | |integer, _, uod| Ok(DurationValue::new(PeriodComp::new(uod.value().grain, integer.value().value).into())) 65 | ); 66 | b.rule_2_terminal("number.number hours", 67 | b.reg(r#"(\d+)\.(\d+)"#)?, 68 | b.reg(r#"hours?"#)?, 69 | |text_match, _| { 70 | Ok(DurationValue::new( 71 | PeriodComp::minutes( 72 | helpers::decimal_hour_in_minute(text_match.group(1), text_match.group(2))? 73 | ).into() 74 | ) 75 | ) 76 | } 77 | ); 78 | b.rule_2(" and a half hours", 79 | integer_check_by_range!(0), 80 | b.reg(r#"and (?:an? )?half hours?"#)?, 81 | |integer, _| Ok(DurationValue::new(PeriodComp::minutes(integer.value().value * 60 + 30).into())) 82 | ); 83 | b.rule_3(" and a half", 84 | integer_check_by_range!(0), 85 | unit_of_duration_check!(), 86 | b.reg(r#"and (?:an? )?half"#)?, 87 | |integer, uod, _| { 88 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).unwrap_or_else(|| Period::default()); 89 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 90 | } 91 | ); 92 | b.rule_3(" and a quarter", 93 | integer_check_by_range!(0), 94 | unit_of_duration_check!(), 95 | b.reg(r#"and (?:a? )?quarter"#)?, 96 | |integer, uod, _| { 97 | let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).unwrap_or_else(|| Period::default()); 98 | Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) 99 | } 100 | ); 101 | b.rule_3(" and a half ", 102 | integer_check_by_range!(0), 103 | b.reg(r#"and (?:an? )?half"#)?, 104 | unit_of_duration_check!(), 105 | |integer, _, uod| { 106 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).unwrap_or_else(|| Period::default()); 107 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 108 | } 109 | ); 110 | b.rule_3(" and a quarter ", 111 | integer_check_by_range!(0), 112 | b.reg(r#"and (?:a? )?quarter"#)?, 113 | unit_of_duration_check!(), 114 | |integer, _, uod| { 115 | let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).unwrap_or_else(|| Period::default()); 116 | Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) 117 | } 118 | ); 119 | b.rule_3(" h ", 120 | integer_check_by_range!(0), 121 | b.reg(r#"h(?:ours?)?"#)?, 122 | integer_check_by_range!(0,59), 123 | |hour, _, minute| { 124 | let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); 125 | let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); 126 | Ok(DurationValue::new(hour_period + minute_period)) 127 | } 128 | ); 129 | b.rule_2("a ", 130 | b.reg(r#"an?"#)?, 131 | unit_of_duration_check!(), 132 | |_, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, 1).into())) 133 | ); 134 | b.rule_2("for ", 135 | b.reg(r#"for"#)?, 136 | duration_check!(), 137 | |_, duration| Ok(duration.value().clone().prefixed()) 138 | ); 139 | b.rule_2("during ", 140 | b.reg(r#"during"#)?, 141 | duration_check!(), 142 | |_, duration| Ok(duration.value().clone().prefixed()) 143 | ); 144 | b.rule_2("after ", 145 | b.reg(r#"after"#)?, 146 | duration_check!(), 147 | |_, duration| Ok(duration 148 | .value() 149 | .in_present()? 150 | .mark_after_start()) 151 | ); 152 | b.rule_3(" and ", 153 | duration_check!(|duration: &DurationValue| !duration.suffixed), 154 | b.reg(r#"and"#)?, 155 | duration_check!(|duration: &DurationValue| !duration.prefixed), 156 | |a, _, b| Ok(a.value() + b.value()) 157 | ); 158 | 159 | b.rule_2(" ", 160 | duration_check!(|duration: &DurationValue| !duration.suffixed), 161 | duration_check!(|duration: &DurationValue| !duration.prefixed), 162 | |a, b| Ok(a.value() + b.value()) 163 | ); 164 | 165 | b.rule_2("about ", 166 | b.reg(r#"(?:about|around|approximately|roughly)"#)?, 167 | duration_check!(), 168 | |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) 169 | ); 170 | 171 | b.rule_2(" approximately", 172 | duration_check!(), 173 | b.reg(r#"(?:about|around|approximately|roughly)"#)?, 174 | |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) 175 | ); 176 | 177 | b.rule_2("exactly ", 178 | b.reg(r#"exactly|precisely"#)?, 179 | duration_check!(), 180 | |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) 181 | ); 182 | 183 | b.rule_2(" exactly", 184 | duration_check!(), 185 | b.reg(r#"exactly|precisely"#)?, 186 | |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) 187 | ); 188 | Ok(()) 189 | } -------------------------------------------------------------------------------- /grammar/es/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-es" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/es/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules_datetime; 7 | mod rules_celebrations; 8 | mod rules_duration; 9 | mod rules_number; 10 | mod rules_amount; 11 | mod training; 12 | 13 | use rustling_ontology_values::DimensionKind::*; 14 | 15 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 16 | let mut b = ::rustling::RuleSetBuilder::new( 17 | ::rustling::BoundariesChecker::detailed(), 18 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 19 | rules_number::rules_numbers(&mut b)?; 20 | rules_amount::rules_temperature(&mut b)?; 21 | rules_amount::rules_finance(&mut b)?; 22 | rules_amount::rules_percentage(&mut b)?; 23 | rules_datetime::rules_cycle(&mut b)?; 24 | rules_datetime::rules_datetime(&mut b)?; 25 | rules_datetime::rules_datetime_with_duration(&mut b)?; 26 | rules_datetime::rules_datetime_with_cycle(&mut b)?; 27 | rules_celebrations::rules_celebration(&mut b)?; 28 | rules_duration::rules_duration(&mut b)?; 29 | Ok(b.build()) 30 | } 31 | 32 | pub fn dims() -> Vec { 33 | return vec![Number, Ordinal, Datetime, Duration, Temperature, AmountOfMoney, Percentage]; 34 | } 35 | 36 | pub fn examples() -> Vec<::rustling::train::Example> { 37 | let mut v = vec![]; 38 | training::examples_numbers(&mut v); 39 | training::examples_percentage(&mut v); 40 | training::examples_temperature(&mut v); 41 | training::examples_finance(&mut v); 42 | training::examples_durations(&mut v); 43 | training::examples_datetime(&mut v); 44 | v 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use rustling::*; 50 | use rustling_ontology_values::dimension::Dimension; 51 | 52 | use super::*; 53 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 54 | for ex in examples.iter() { 55 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 56 | let correct_results = stash 57 | .into_iter() 58 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 59 | .collect::>(); 60 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 61 | } 62 | } 63 | #[test] 64 | fn test_examples() { 65 | let rules = rule_set().unwrap(); 66 | let examples = examples(); 67 | assert_examples(&rules, examples); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /grammar/es/src/rules_amount.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::dimension::Precision::*; 4 | use rustling_ontology_values::helpers; 5 | 6 | 7 | pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { 8 | b.rule_2(" per cent", 9 | number_check!(), 10 | // FIXME 11 | b.reg(r#"(?:%|p\.c\.|por ?cien(?:tos?)?)"#)?, 12 | |number, _| Ok(PercentageValue(number.value().value())) 13 | ); 14 | Ok(()) 15 | } 16 | 17 | 18 | pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { 19 | b.rule_2("intersect (X cents)", 20 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 21 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 22 | |a, b| helpers::compose_money(a.value(), b.value())); 23 | b.rule_3("intersect (and X cents)", 24 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 25 | b.reg(r#"y"#)?, 26 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 27 | |a, _, b| helpers::compose_money(&a.value(), &b.value())); 28 | b.rule_2("intersect", 29 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 30 | number_check!(), 31 | |a, b| helpers::compose_money_number(&a.value(), &b.value())); 32 | b.rule_1_terminal("$", 33 | b.reg(r#"\$|d[oóò]lar(?:es)?"#)?, 34 | |_| Ok(MoneyUnitValue { unit: Some("$") }) 35 | ); 36 | b.rule_1_terminal("EUR", 37 | b.reg(r#"€|(?:[e€]uro?s?)"#)?, 38 | |_| Ok(MoneyUnitValue { unit: Some("EUR") }) 39 | ); 40 | b.rule_1_terminal("£", 41 | b.reg(r#"(?:pound|libra)s?|£"#)?, 42 | |_| Ok(MoneyUnitValue { unit: Some("£") }) 43 | ); 44 | b.rule_1_terminal("USD", 45 | b.reg(r#"us[d\$]|d[oóò]lar(?:es)? (?:estadounidense|americano)s?"#)?, 46 | |_| Ok(MoneyUnitValue { unit: Some("USD") }) 47 | ); 48 | b.rule_1_terminal("CAD", 49 | b.reg(r#"cad|d[oóò]lar(?:es)? canadienses?"#)?, 50 | |_| Ok(MoneyUnitValue { unit: Some("CAD") }) 51 | ); 52 | b.rule_1_terminal("AUD", 53 | b.reg(r#"d[oóò]lar(?:es)? australianos?"#)?, 54 | |_| Ok(MoneyUnitValue { unit: Some("AUD") }) 55 | ); 56 | b.rule_1_terminal("Bitcoin", 57 | b.reg(r#"฿|bitc[oóò]in(?:e?s)?"#)?, 58 | |_| Ok(MoneyUnitValue { unit: Some("฿") }) 59 | ); 60 | b.rule_1_terminal("GBP", 61 | b.reg(r#"gbp|libras? esterlinas?"#)?, 62 | |_| Ok(MoneyUnitValue { unit: Some("GBP") }) 63 | ); 64 | b.rule_1_terminal("JPY", 65 | b.reg(r#"jpy|yen(?:es)?(?: japoneses?)?"#)?, 66 | |_| Ok(MoneyUnitValue { unit: Some("JPY") }) 67 | ); 68 | b.rule_1_terminal("¥", 69 | b.reg(r#"¥"#)?, 70 | |_| Ok(MoneyUnitValue { unit: Some("¥") }) 71 | ); 72 | b.rule_1_terminal("KRW", 73 | b.reg(r#"₩|krw|won(?:es)?(?: surcoreanos?)?"#)?, 74 | |_| Ok(MoneyUnitValue { unit: Some("KRW") }) 75 | ); 76 | b.rule_1_terminal("RMB|CNH|CNY", 77 | b.reg(r#"cny|cnh|rmb|yuan(?:es)?(?: chinos?)?|renmimbis?"#)?, 78 | |_| Ok(MoneyUnitValue { unit: Some("CNY") }) 79 | ); 80 | b.rule_1_terminal("INR", 81 | b.reg(r#"rupias?"#)?, 82 | |_| Ok(MoneyUnitValue { unit: Some("INR") }) 83 | ); 84 | b.rule_1_terminal("HKD", 85 | b.reg(r#"hkd|d[oóò]lar(?:es)? de hong[- ]kong"#)?, 86 | |_| Ok(MoneyUnitValue { unit: Some("HKD") }) 87 | ); 88 | b.rule_1_terminal("CHF", 89 | b.reg(r#"chf|francos suizos?"#)?, 90 | |_| Ok(MoneyUnitValue { unit: Some("CHF") }) 91 | ); 92 | b.rule_1_terminal("KR", 93 | b.reg(r#"kr|coronas?"#)?, 94 | |_| Ok(MoneyUnitValue { unit: Some("KR") }) 95 | ); 96 | b.rule_1_terminal("DKK", 97 | b.reg(r#"dkk|coronas? danesas?"#)?, 98 | |_| Ok(MoneyUnitValue { unit: Some("DKK") }) 99 | ); 100 | b.rule_1_terminal("NOK", 101 | b.reg(r#"nok|coronas? noruegas?"#)?, 102 | |_| Ok(MoneyUnitValue { unit: Some("NOK") }) 103 | ); 104 | b.rule_1_terminal("SEK", 105 | b.reg(r#"sek|coronas? suecas?"#)?, 106 | |_| Ok(MoneyUnitValue { unit: Some("SEK") }) 107 | ); 108 | b.rule_1_terminal("cent", 109 | b.reg(r#"c[eéè]nt(?:avo|imo)s?"#)?, 110 | |_| Ok(MoneyUnitValue { unit: Some("cent") }) 111 | ); 112 | b.rule_2(" ", 113 | number_check!(), 114 | money_unit!(), 115 | |a, b| { 116 | Ok(AmountOfMoneyValue { 117 | value: a.value().value(), 118 | unit: b.value().unit, 119 | ..AmountOfMoneyValue::default() 120 | }) 121 | }); 122 | b.rule_3(" of ", 123 | number_check!(), 124 | b.reg(r#"de"#)?, 125 | money_unit!(), 126 | |a, _, b| { 127 | Ok(AmountOfMoneyValue { 128 | value: a.value().value(), 129 | unit: b.value().unit, 130 | ..AmountOfMoneyValue::default() 131 | }) 132 | }); 133 | b.rule_2(" ", 134 | money_unit!(), 135 | number_check!(), 136 | |a, b| { 137 | Ok(AmountOfMoneyValue { 138 | value: b.value().value(), 139 | unit: a.value().unit, 140 | ..AmountOfMoneyValue::default() 141 | }) 142 | }); 143 | b.rule_2("about ", 144 | b.reg(r#"aproximadamente|sobre|cerca de|casi|un[oa]s"#)?, 145 | amount_of_money_check!(), 146 | |_, a| { 147 | Ok(AmountOfMoneyValue { 148 | precision: Approximate, 149 | ..a.value().clone() 150 | }) 151 | }); 152 | b.rule_2(" about", 153 | amount_of_money_check!(), 154 | b.reg(r#"m[aáà]s o menos|aproximadamente"#)?, 155 | |a, _| { 156 | Ok(AmountOfMoneyValue { 157 | precision: Approximate, 158 | ..a.value().clone() 159 | }) 160 | }); 161 | b.rule_2("exactly ", 162 | b.reg(r#"exactamente"#)?, 163 | amount_of_money_check!(), 164 | |_, a| { 165 | Ok(AmountOfMoneyValue { 166 | precision: Exact, 167 | ..a.value().clone() 168 | }) 169 | }); 170 | b.rule_2(" exactly", 171 | amount_of_money_check!(), 172 | b.reg(r#"exactos"#)?, 173 | |a, _| { 174 | Ok(AmountOfMoneyValue { 175 | precision: Exact, 176 | ..a.value().clone() 177 | }) 178 | } 179 | ); 180 | Ok(()) 181 | } 182 | 183 | 184 | pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { 185 | b.rule_1("number as temp", number_check!(), |a| { 186 | Ok(TemperatureValue { 187 | value: a.value().value(), 188 | unit: None, 189 | latent: true, 190 | }) 191 | }); 192 | b.rule_2(" temp", 193 | temperature_check!(), 194 | b.reg(r#"(?:grados?)|°"#)?, 195 | |a, _| { 196 | Ok(TemperatureValue { 197 | value: a.value().value, 198 | unit: Some("degree"), 199 | latent: false, 200 | }) 201 | }); 202 | b.rule_2(" Celcius", 203 | temperature_check!(), 204 | b.reg(r#"(?:cent(?:i|í)grados?|c(?:el[cs]?(?:ius)?)?\.?)"#)?, 205 | |a, _| { 206 | Ok(TemperatureValue { 207 | value: a.value().value, 208 | unit: Some("celsius"), 209 | latent: false, 210 | }) 211 | }); 212 | // FIXME: Check double Kelvin removal 213 | b.rule_2(" Kelvin", 214 | temperature_check!(), 215 | b.reg(r#"k(?:elvin)?"#)?, 216 | |a, _| { 217 | Ok(TemperatureValue { 218 | value: a.value().value, 219 | unit: Some("kelvin"), 220 | latent: false, 221 | }) 222 | }); 223 | b.rule_2(" Fahrenheit", 224 | temperature_check!(), 225 | b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, 226 | |a, _| { 227 | Ok(TemperatureValue { 228 | value: a.value().value, 229 | unit: Some("fahrenheit"), 230 | latent: false, 231 | }) 232 | }); 233 | b.rule_2(" temp bajo cero", 234 | temperature_check!(), 235 | b.reg(r#"bajo cero"#)?, 236 | |a, _| { 237 | Ok(TemperatureValue { 238 | value: -1.0 * a.value().value, 239 | latent: false, 240 | ..*a.value() 241 | }) 242 | }); 243 | Ok(()) 244 | } 245 | -------------------------------------------------------------------------------- /grammar/es/src/rules_celebrations.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | 5 | 6 | pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_1_terminal("Navidad", 8 | b.reg(r#"navidad"#)?, 9 | |_| Ok(helpers::month_day(12, 25)? 10 | .form(Form::Celebration)) 11 | ); 12 | b.rule_1_terminal("Nochevieja", 13 | b.reg(r#"nochevieja"#)?, 14 | |_| Ok(helpers::month_day(12, 31)? 15 | .form(Form::Celebration)) 16 | ); 17 | b.rule_1_terminal("ano nuevo", 18 | b.reg(r#"a[nñ]o nuevo"#)?, 19 | |_| Ok(helpers::month_day(1, 1)? 20 | .form(Form::Celebration)) 21 | ); 22 | b.rule_1_terminal("Father's day", 23 | b.reg(r#"(?:el )?d[íi]a del padre"#)?, 24 | |_| Ok(helpers::month_day(6, 18)? 25 | .form(Form::Celebration)) 26 | ); 27 | b.rule_1_terminal("National day", 28 | b.reg(r#"(?:el )?d[íi]a de la hispanidad"#)?, 29 | |_| Ok(helpers::month_day(7, 4)? 30 | .form(Form::Celebration)) 31 | ); 32 | b.rule_1_terminal("All saints days", 33 | b.reg(r#"(?:el )?d[íi]a de todos los santos"#)?, 34 | |_| Ok(helpers::month_day(9, 11)? 35 | .form(Form::Celebration)) 36 | ); 37 | b.rule_1_terminal("día de la constitucíon", 38 | b.reg(r#"(?:el )?d[íi]a de la constitucíon"#)?, 39 | |_| Ok(helpers::month_day(6, 14)? 40 | .form(Form::Celebration)) 41 | ); 42 | b.rule_1_terminal("Women's day", 43 | b.reg(r#"(?:el )?d[íi]a de la mujer"#)?, 44 | |_| Ok(helpers::month_day(8, 26)? 45 | .form(Form::Celebration)) 46 | ); 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /grammar/es/src/rules_duration.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | use rustling_ontology_moment::{Grain, PeriodComp, Period}; 5 | 6 | 7 | pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 8 | b.rule_1_terminal("seconde (unit-of-duration)", 9 | b.reg(r#"seg(?:undo)?s?"#)?, 10 | |_| Ok(UnitOfDurationValue::new(Grain::Second)) 11 | ); 12 | b.rule_1_terminal("minute (unit-of-duration)", 13 | b.reg(r#"min(?:uto)?s?"#)?, 14 | |_| Ok(UnitOfDurationValue::new(Grain::Minute)) 15 | ); 16 | b.rule_1_terminal("hour (unit-of-duration)", 17 | b.reg(r#"h(?:ora)?s?"#)?, 18 | |_| Ok(UnitOfDurationValue::new(Grain::Hour)) 19 | ); 20 | b.rule_1_terminal("day (unit-of-duration)", 21 | b.reg(r#"d[iíì]as?"#)?, 22 | |_| Ok(UnitOfDurationValue::new(Grain::Day)) 23 | ); 24 | b.rule_1_terminal("week (unit-of-duration)", 25 | b.reg(r#"semanas?"#)?, 26 | |_| Ok(UnitOfDurationValue::new(Grain::Week)) 27 | ); 28 | b.rule_1_terminal("month (unit-of-duration)", 29 | b.reg(r#"mes(?:es)?"#)?, 30 | |_| Ok(UnitOfDurationValue::new(Grain::Month)) 31 | ); 32 | b.rule_1_terminal("year (unit-of-duration)", 33 | b.reg(r#"a[nñ]os?"#)?, 34 | |_| Ok(UnitOfDurationValue::new(Grain::Year)) 35 | ); 36 | b.rule_1_terminal("quarter of an hour", 37 | b.reg(r#"(?:un )?(?:cuarto|1/4)(?: de hora)?"#)?, 38 | |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) 39 | ); 40 | b.rule_1_terminal("half an hour", 41 | b.reg(r#"(?:media|1/2) hora"#)?, 42 | |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) 43 | ); 44 | b.rule_1_terminal("three-quarters of an hour", 45 | b.reg(r#"(?:(?:3|tres) cuartos?|3/4)(?: de hora)?"#)?, 46 | |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) 47 | ); 48 | b.rule_2("during ", 49 | b.reg(r#"(?:durante|por|todo) (?:el|la|una?)"#)?, 50 | duration_check!(), 51 | |_, duration| Ok(duration.value().clone().prefixed()) 52 | ); 53 | b.rule_2("during ", 54 | b.reg(r#"(?:durante|por|todo)"#)?, 55 | duration_check!(), 56 | |_, duration| Ok(duration.value().clone().prefixed()) 57 | ); 58 | b.rule_2("exactly ", 59 | b.reg(r#"(?:precis|exact)amente|(?:exact|just)o"#)?, 60 | duration_check!(), 61 | |_, duration| Ok(duration.value().clone().prefixed().precision(Precision::Exact)) 62 | ); 63 | b.rule_2(" exactly", 64 | duration_check!(), 65 | b.reg(r#"(?:precis|exact)amente|(?:exact|just)o"#)?, 66 | |duration, _| Ok(duration.value().clone().prefixed().precision(Precision::Exact)) 67 | ); 68 | b.rule_2("approx ", 69 | b.reg(r#"sobre|cerca de"#)?, 70 | duration_check!(), 71 | |_, duration| Ok(duration.value().clone().prefixed().precision(Precision::Approximate)) 72 | ); 73 | b.rule_2(" approx", 74 | duration_check!(), 75 | b.reg(r#"m[aáà]s o menos|aproximadamente"#)?, 76 | |duration, _| Ok(duration.value().clone().prefixed().precision(Precision::Approximate)) 77 | ); 78 | b.rule_2(" ", 79 | integer_check_by_range!(0), 80 | unit_of_duration_check!(), 81 | |integer, uod| Ok(DurationValue::new(PeriodComp::new(uod.value().grain, integer.value().value).into())) 82 | ); 83 | b.rule_3(" and a half", 84 | integer_check_by_range!(0), 85 | unit_of_duration_check!(), 86 | b.reg(r#"y media"#)?, 87 | |integer, uod, _| { 88 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 89 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 90 | } 91 | ); 92 | b.rule_3(" and a quarter", 93 | integer_check_by_range!(0), 94 | unit_of_duration_check!(), 95 | b.reg(r#"y cuarto"#)?, 96 | |integer, uod, _| { 97 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 98 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 99 | } 100 | ); 101 | b.rule_3(" y ", 102 | duration_check!(|duration: &DurationValue| !duration.suffixed), 103 | b.reg(r#"y"#)?, 104 | duration_check!(|duration: &DurationValue| !duration.prefixed), 105 | |a, _, b| Ok(a.value() + b.value()) 106 | ); 107 | b.rule_2(" ", 108 | duration_check!(|duration: &DurationValue| !duration.suffixed), 109 | duration_check!(|duration: &DurationValue| !duration.prefixed), 110 | |a, b| Ok(a.value() + b.value()) 111 | ); 112 | b.rule_2(" ", 113 | duration_check!(|duration: &DurationValue| !duration.prefixed), 114 | integer_check_by_range!(0), 115 | |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) 116 | ); 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /grammar/fr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-fr" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/fr/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules_datetime; 7 | mod rules_celebrations; 8 | mod rules_duration; 9 | mod rules_number; 10 | mod rules_amount; 11 | pub mod training; 12 | 13 | use rustling_ontology_values::DimensionKind::*; 14 | 15 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 16 | let mut b = ::rustling::RuleSetBuilder::new( 17 | ::rustling::BoundariesChecker::detailed(), 18 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 19 | rules_number::rules_numbers(&mut b)?; 20 | rules_amount::rules_temperature(&mut b)?; 21 | rules_amount::rules_finance(&mut b)?; 22 | rules_amount::rules_percentage(&mut b)?; 23 | rules_datetime::rules_cycle(&mut b)?; 24 | rules_datetime::rules_datetime(&mut b)?; 25 | rules_datetime::rules_datetime_with_duration(&mut b)?; 26 | rules_datetime::rules_datetime_with_cycle(&mut b)?; 27 | rules_celebrations::rules_celebration(&mut b)?; 28 | rules_duration::rules_duration(&mut b)?; 29 | Ok(b.build()) 30 | } 31 | 32 | pub fn dims() -> Vec { 33 | return vec![Number, Ordinal, Duration, Datetime, Temperature, AmountOfMoney, Percentage]; 34 | } 35 | 36 | pub fn examples() -> Vec<::rustling::train::Example> { 37 | let mut v = vec![]; 38 | training::examples_numbers(&mut v); 39 | training::examples_datetime(&mut v); 40 | training::examples_temperature(&mut v); 41 | training::examples_finance(&mut v); 42 | training::examples_percentage(&mut v); 43 | v 44 | } 45 | 46 | #[cfg(test)] 47 | mod test { 48 | use rustling::*; 49 | use rustling_ontology_values::dimension::Dimension; 50 | 51 | use super::*; 52 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 53 | for ex in examples.iter() { 54 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 55 | let correct_results = stash 56 | .into_iter() 57 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 58 | .collect::>(); 59 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 60 | } 61 | } 62 | #[test] 63 | fn test_examples() { 64 | let rules = rule_set().unwrap(); 65 | let examples = examples(); 66 | assert_examples(&rules, examples); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /grammar/fr/src/rules_amount.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::dimension::Precision::*; 4 | use rustling_ontology_values::helpers; 5 | 6 | pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_2(" per cent", 8 | number_check!(), 9 | b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, 10 | |number, _| Ok(PercentageValue(number.value().value())) 11 | ); 12 | Ok(()) 13 | } 14 | 15 | pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { 16 | b.rule_2("intersect (X cents)", 17 | amount_of_money_check!(), 18 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 19 | |a, b| helpers::compose_money(a.value(), b.value())); 20 | b.rule_3("intersect (and X cents)", 21 | amount_of_money_check!(), 22 | b.reg(r#"et"#)?, 23 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 24 | |a, _, b| helpers::compose_money(&a.value(), &b.value())); 25 | b.rule_2("intersect", 26 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 27 | number_check!(), 28 | |a, b| helpers::compose_money_number(&a.value(), &b.value())); 29 | b.rule_1_terminal("$", 30 | b.reg(r#"\$|dollars?"#)?, 31 | |_| Ok(MoneyUnitValue { unit: Some("$") }) 32 | ); 33 | b.rule_1_terminal("EUR", 34 | b.reg(r#"€|(?:[e€]uro?s?)"#)?, 35 | |_| Ok(MoneyUnitValue { unit: Some("EUR") }) 36 | ); 37 | b.rule_1_terminal("£", 38 | b.reg(r#"£|livres?"#)?, 39 | |_| Ok(MoneyUnitValue { unit: Some("£") }) 40 | ); 41 | b.rule_1_terminal("USD", 42 | b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, 43 | |_| Ok(MoneyUnitValue { unit: Some("USD") }) 44 | ); 45 | b.rule_1_terminal("AUD", 46 | b.reg(r#"au[d\$]|dollars? australiens?"#)?, 47 | |_| Ok(MoneyUnitValue { unit: Some("AUD") }) 48 | ); 49 | b.rule_1_terminal("CAD", 50 | b.reg(r#"cad|dollars? canadiens?"#)?, 51 | |_| Ok(MoneyUnitValue { unit: Some("CAD") }) 52 | ); 53 | b.rule_1_terminal("HKD", 54 | b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, 55 | |_| Ok(MoneyUnitValue { unit: Some("HKD") }) 56 | ); 57 | b.rule_1_terminal("KR", 58 | b.reg(r#"kr|couronnes?"#)?, 59 | |_| Ok(MoneyUnitValue { unit: Some("KR") }) 60 | ); 61 | b.rule_1_terminal("DKK", 62 | b.reg(r#"dkk|couronnes? danoises?"#)?, 63 | |_| Ok(MoneyUnitValue { unit: Some("DKK") }) 64 | ); 65 | b.rule_1_terminal("NOK", 66 | b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, 67 | |_| Ok(MoneyUnitValue { unit: Some("NOK") }) 68 | ); 69 | b.rule_1_terminal("SEK", 70 | b.reg(r#"sek|couronnes? su[ée]doises?"#)?, 71 | |_| Ok(MoneyUnitValue { unit: Some("SEK") }) 72 | ); 73 | b.rule_1_terminal("CHF", 74 | b.reg(r#"chf|francs? suisses?"#)?, 75 | |_| Ok(MoneyUnitValue { unit: Some("CHF") }) 76 | ); 77 | b.rule_1_terminal("RUB", 78 | b.reg(r#"rub|roubles?"#)?, 79 | |_| Ok(MoneyUnitValue { unit: Some("RUB") }) 80 | ); 81 | b.rule_1_terminal("INR", 82 | b.reg(r#"inr|roupies?"#)?, 83 | |_| Ok(MoneyUnitValue { unit: Some("INR") }) 84 | ); 85 | b.rule_1_terminal("JPY", 86 | b.reg(r#"jpy|yens?"#)?, 87 | |_| Ok(MoneyUnitValue { unit: Some("JPY") }) 88 | ); 89 | b.rule_1_terminal("RMB|CNH|CNY", 90 | b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, 91 | |_| Ok(MoneyUnitValue { unit: Some("CNY") }) 92 | ); 93 | b.rule_1_terminal("¥", 94 | b.reg(r#"¥"#)?, 95 | |_| Ok(MoneyUnitValue { unit: Some("¥") }) 96 | ); 97 | b.rule_1_terminal("KRW", 98 | b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, 99 | |_| Ok(MoneyUnitValue { unit: Some("KRW") }) 100 | ); 101 | b.rule_1_terminal("Bitcoin", 102 | b.reg(r#"฿|bitcoins?"#)?, 103 | |_| Ok(MoneyUnitValue { unit: Some("฿") }) 104 | ); 105 | b.rule_1_terminal("GBP", 106 | b.reg(r#"gbp|livres? sterlings?"#)?, 107 | |_| Ok(MoneyUnitValue { unit: Some("GBP") }) 108 | ); 109 | b.rule_1_terminal("cent", 110 | b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, 111 | |_| Ok(MoneyUnitValue { unit: Some("cent") }) 112 | ); 113 | b.rule_1_terminal("unnamed currency", 114 | b.reg(r#"(?:balle)s?"#)?, 115 | |_| Ok(MoneyUnitValue { unit: None }) 116 | ); 117 | b.rule_2(" ", 118 | number_check!(), 119 | money_unit!(), 120 | |a, b| { 121 | Ok(AmountOfMoneyValue { 122 | value: a.value().value(), 123 | unit: b.value().unit, 124 | ..AmountOfMoneyValue::default() 125 | }) 126 | }); 127 | b.rule_3(" de ", // "un million de dollars" 128 | integer_check!(|integer: &IntegerValue| !integer.group), 129 | b.reg(r#"d[e']"#)?, 130 | money_unit!(), 131 | |a, _, b| { 132 | Ok(AmountOfMoneyValue { 133 | value: a.value().value as f64, 134 | precision: Exact, 135 | unit: b.value().unit, 136 | ..AmountOfMoneyValue::default() 137 | }) 138 | }); 139 | b.rule_3(" de ", // "une douzaine de dollars" 140 | integer_check!(|integer: &IntegerValue| integer.group), 141 | b.reg(r#"d[e']"#)?, 142 | money_unit!(), 143 | |a, _, b| { 144 | Ok(AmountOfMoneyValue { 145 | value: a.value().value as f64, 146 | precision: Approximate, 147 | unit: b.value().unit, 148 | ..AmountOfMoneyValue::default() 149 | }) 150 | }); 151 | b.rule_2("about ", 152 | b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, 153 | amount_of_money_check!(), 154 | |_, a| { 155 | Ok(AmountOfMoneyValue { 156 | precision: Approximate, 157 | ..a.value().clone() 158 | }) 159 | }); 160 | b.rule_2("exactly ", 161 | b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, 162 | amount_of_money_check!(), 163 | |_, a| { 164 | Ok(AmountOfMoneyValue { 165 | precision: Exact, 166 | ..a.value().clone() 167 | }) 168 | }); 169 | b.rule_2("exactly ", 170 | amount_of_money_check!(), 171 | b.reg(r#"pile(?: poil)?|tout rond"#)?, 172 | |a, _| { 173 | Ok(AmountOfMoneyValue { 174 | precision: Exact, 175 | ..a.value().clone() 176 | }) 177 | } 178 | ); 179 | Ok(()) 180 | } 181 | 182 | pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { 183 | b.rule_1("number as temp", 184 | number_check!(), 185 | |a| { 186 | Ok(TemperatureValue { 187 | value: a.value().value(), 188 | unit: None, 189 | latent: true, 190 | }) 191 | }); 192 | b.rule_2(" degrees", 193 | temperature_check!(), 194 | b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, 195 | |a, _| { 196 | Ok(TemperatureValue { 197 | value: a.value().value, 198 | unit: Some("degree"), 199 | latent: false, 200 | }) 201 | }); 202 | b.rule_2(" Celcius", 203 | temperature_check!(), 204 | b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, 205 | |a, _| { 206 | Ok(TemperatureValue { 207 | value: a.value().value, 208 | unit: Some("celsius"), 209 | latent: false, 210 | }) 211 | }); 212 | b.rule_2(" Fahrenheit", 213 | temperature_check!(), 214 | b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, 215 | |a, _| { 216 | Ok(TemperatureValue { 217 | value: a.value().value, 218 | unit: Some("fahrenheit"), 219 | latent: false, 220 | }) 221 | }); 222 | b.rule_2(" Kelvin", 223 | temperature_check!(), 224 | b.reg(r#"k(?:elvin)?\.?"#)?, 225 | |a, _| { 226 | Ok(TemperatureValue { 227 | value: a.value().value, 228 | unit: Some("kelvin"), 229 | latent: false, 230 | }) 231 | }); 232 | b.rule_2(" en dessous de zero", 233 | temperature_check!(|temp: &TemperatureValue| !temp.latent), 234 | b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, 235 | |a, _| { 236 | Ok(TemperatureValue { 237 | value: -1.0 * a.value().value, 238 | latent: false, 239 | ..*a.value() 240 | }) 241 | }); 242 | Ok(()) 243 | } 244 | -------------------------------------------------------------------------------- /grammar/fr/src/rules_celebrations.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | use rustling_ontology_moment::{Weekday, Grain}; 5 | 6 | pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_1_terminal("noel", 8 | b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, 9 | |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) 10 | ); 11 | b.rule_1_terminal("soir de noël", 12 | b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, 13 | |_| { 14 | let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; 15 | let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; 16 | Ok(start.span_to(&end, false)? 17 | .form(Form::Celebration)) 18 | } 19 | ); 20 | b.rule_1_terminal("saint sylvestre", 21 | b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, 22 | |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) 23 | ); 24 | b.rule_1_terminal("jour de l'an", 25 | b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, 26 | |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) 27 | ); 28 | b.rule_1_terminal("toussaint", 29 | b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, 30 | |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) 31 | ); 32 | b.rule_1_terminal("Armistice", 33 | b.reg(r#"(?:pour )?l'armistice"#)?, 34 | |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) 35 | ); 36 | b.rule_1_terminal("Saint Etienne (Alsace)", 37 | b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, 38 | |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) 39 | ); 40 | b.rule_1_terminal("jeudi saint", 41 | b.reg(r#"(?:le )?jeudi saint"#)?, 42 | |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? 43 | .form(Form::Celebration)) 44 | ); 45 | b.rule_1_terminal("vendredi saint", 46 | b.reg(r#"(?:le )?vendredi saint"#)?, 47 | |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? 48 | .form(Form::Celebration)) 49 | ); 50 | b.rule_1_terminal("samedi saint", 51 | b.reg(r#"(?:le )?samedi saint"#)?, 52 | |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? 53 | .form(Form::Celebration)) 54 | ); 55 | b.rule_1_terminal("pâques", 56 | b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, 57 | |_| Ok(helpers::easter()?.form(Form::Celebration)) 58 | ); 59 | b.rule_1_terminal("le lundi de pâques", 60 | b.reg(r#"le lundi de p[âa]ques"#)?, 61 | |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? 62 | .form(Form::Celebration)) 63 | ); 64 | b.rule_1_terminal("ascension", 65 | b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, 66 | |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? 67 | .form(Form::Celebration)) 68 | ); 69 | b.rule_1_terminal("pentecôte", 70 | b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, 71 | |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? 72 | .form(Form::Celebration)) 73 | ); 74 | b.rule_1_terminal("1er mai", 75 | b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, 76 | |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) 77 | ); 78 | b.rule_1_terminal("fêtes des pères", 79 | b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, 80 | |_| { 81 | let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; 82 | let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; 83 | Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June 84 | .form(Form::Celebration)) 85 | } 86 | ); 87 | b.rule_1_terminal("fêtes des mères", 88 | b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, 89 | |_| { 90 | // It is the last last sunday of may 91 | // If it is the same day as the Pentecost, it is the first sunday of june 92 | // This case is not supported for now 93 | Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? 94 | .form(Form::Celebration)) 95 | } 96 | ); 97 | b.rule_1_terminal("fête nationale", 98 | b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, 99 | |_| Ok(helpers::month_day(7, 14)? 100 | .form(Form::Celebration)) 101 | ); 102 | b.rule_1_terminal("assomption", 103 | b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, 104 | |_| Ok(helpers::month_day(8, 15)? 105 | .form(Form::Celebration)) 106 | ); 107 | b.rule_2("à ", 108 | b.reg(r#"au|[aà](?:l['a])?"#)?, 109 | datetime_check!(form!(Form::Celebration)), 110 | |_, a| Ok(a.value().clone()) 111 | ); 112 | 113 | Ok(()) 114 | } -------------------------------------------------------------------------------- /grammar/fr/src/rules_duration.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | use rustling_ontology_moment::{Grain, PeriodComp, Period}; 5 | 6 | pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_1_terminal("seconde (unit-of-duration)", 8 | b.reg(r#"sec(?:onde)?s?"#)?, 9 | |_| Ok(UnitOfDurationValue::new(Grain::Second)) 10 | ); 11 | b.rule_1_terminal("minute (unit-of-duration)", 12 | b.reg(r#"min(?:ute)?s?"#)?, 13 | |_| Ok(UnitOfDurationValue::new(Grain::Minute)) 14 | ); 15 | b.rule_1_terminal("heure (unit-of-duration)", 16 | b.reg(r#"h(?:eure)?s?"#)?, 17 | |_| Ok(UnitOfDurationValue::new(Grain::Hour)) 18 | ); 19 | b.rule_1_terminal("jour (unit-of-duration)", 20 | b.reg(r#"jour(?:n[ée]e?)?s?"#)?, 21 | |_| Ok(UnitOfDurationValue::new(Grain::Day)) 22 | ); 23 | b.rule_1_terminal("semaine (unit-of-duration)", 24 | b.reg(r#"semaines?"#)?, 25 | |_| Ok(UnitOfDurationValue::new(Grain::Week)) 26 | ); 27 | b.rule_1_terminal("mois (unit-of-duration)", 28 | b.reg(r#"mois?"#)?, 29 | |_| Ok(UnitOfDurationValue::new(Grain::Month)) 30 | ); 31 | b.rule_1_terminal("année (unit-of-duration)", 32 | b.reg(r#"an(?:n[ée]e?)?s?"#)?, 33 | |_| Ok(UnitOfDurationValue::new(Grain::Year)) 34 | ); 35 | b.rule_1_terminal("trimestre (unit-of-duration)", 36 | b.reg(r#"trimestres?"#)?, 37 | |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) 38 | ); 39 | b.rule_1_terminal("un quart heure", 40 | b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, 41 | |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) 42 | ); 43 | b.rule_1_terminal("une demi heure", 44 | b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, 45 | |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) 46 | ); 47 | b.rule_1_terminal("trois quarts d'heure", 48 | b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, 49 | |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) 50 | ); 51 | b.rule_2(" ", 52 | integer_check_by_range!(0), 53 | unit_of_duration_check!(), 54 | |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) 55 | ); 56 | b.rule_3(" de ", 57 | integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), 58 | b.reg(r#"d[e']"#)?, 59 | unit_of_duration_check!(), 60 | |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) 61 | ); 62 | b.rule_4(" h ", 63 | integer_check_by_range!(0), 64 | b.reg(r#"h(?:eures?)?"#)?, 65 | integer_check_by_range!(0,59), 66 | b.reg(r#"m(?:inutes?)?"#)?, 67 | |hour, _, minute, _| { 68 | let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); 69 | let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); 70 | Ok(DurationValue::new(hour_period + minute_period)) 71 | } 72 | ); 73 | b.rule_3(" et quart", 74 | integer_check_by_range!(0), 75 | unit_of_duration_check!(), 76 | b.reg(r#"et quart"#)?, 77 | |integer, uod, _| { 78 | let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 79 | Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) 80 | } 81 | ); 82 | b.rule_3(" et demie", 83 | integer_check_by_range!(0), 84 | unit_of_duration_check!(), 85 | b.reg(r#"et demie?"#)?, 86 | |integer, uod, _| { 87 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 88 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 89 | } 90 | ); 91 | b.rule_3(" et ", 92 | duration_check!(|duration: &DurationValue| !duration.suffixed), 93 | b.reg(r#"et"#)?, 94 | duration_check!(|duration: &DurationValue| !duration.prefixed), 95 | |a, _, b| Ok(a.value() + b.value()) 96 | ); 97 | b.rule_2(" ", 98 | duration_check!(|duration: &DurationValue| !duration.suffixed), 99 | duration_check!(|duration: &DurationValue| !duration.prefixed), 100 | |a, b| Ok(a.value() + b.value()) 101 | ); 102 | b.rule_2(" ", 103 | duration_check!(|duration: &DurationValue| !duration.prefixed), 104 | integer_check_by_range!(0), 105 | |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) 106 | ); 107 | b.rule_2("environ ", 108 | b.reg(r#"environ|approximativement|à peu près|presque"#)?, 109 | duration_check!(), 110 | |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) 111 | ); 112 | b.rule_2(" environ", 113 | duration_check!(), 114 | b.reg(r#"environ|approximativement|à peu près"#)?, 115 | |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) 116 | ); 117 | b.rule_2("exactement ", 118 | b.reg(r#"exactement|précisément"#)?, 119 | duration_check!(), 120 | |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) 121 | ); 122 | b.rule_2(" exactement", 123 | duration_check!(), 124 | b.reg(r#"exactement|précisément|pile"#)?, 125 | |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) 126 | ); 127 | // Ambiguous w/ time-of-day w/ "pour" 128 | // Duration has less priority than Datetime types, therefore duration will be output only 129 | // if the output kind filter is set for Duration 130 | b.rule_2("pendant ", 131 | b.reg(r#"pendant|durant|pour"#)?, 132 | duration_check!(), 133 | |_, duration| Ok(duration.value().clone().prefixed()) 134 | ); 135 | b.rule_2("une durée de ", 136 | b.reg(r#"une dur[ée]e d['e]"#)?, 137 | duration_check!(), 138 | |_, duration| Ok(duration.value().clone().prefixed()) 139 | ); 140 | Ok(()) 141 | } 142 | 143 | -------------------------------------------------------------------------------- /grammar/it/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-it" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/it/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules_datetime; 7 | mod rules_celebrations; 8 | mod rules_duration; 9 | mod rules_number; 10 | mod rules_amount; 11 | mod training; 12 | 13 | use rustling_ontology_values::DimensionKind::*; 14 | 15 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 16 | let mut b = ::rustling::RuleSetBuilder::new( 17 | ::rustling::BoundariesChecker::detailed(), 18 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 19 | rules_number::rules_numbers(&mut b)?; 20 | rules_amount::rules_temperature(&mut b)?; 21 | rules_amount::rules_finance(&mut b)?; 22 | rules_amount::rules_percentage(&mut b)?; 23 | rules_datetime::rules_cycle(&mut b)?; 24 | rules_datetime::rules_datetime(&mut b)?; 25 | rules_datetime::rules_datetime_with_duration(&mut b)?; 26 | rules_datetime::rules_datetime_with_nth_cycle(&mut b)?; 27 | rules_celebrations::rules_celebration(&mut b)?; 28 | rules_duration::rules_duration(&mut b)?; 29 | Ok(b.build()) 30 | } 31 | 32 | pub fn dims() -> Vec { 33 | return vec![Number, Ordinal, Duration, Datetime, Temperature, AmountOfMoney, Percentage]; 34 | } 35 | 36 | pub fn examples() -> Vec<::rustling::train::Example> { 37 | let mut v = vec![]; 38 | training::examples_numbers(&mut v); 39 | training::examples_finance(&mut v); 40 | training::examples_temperature(&mut v); 41 | training::examples_percentage(&mut v); 42 | training::examples_durations(&mut v); 43 | training::examples_datetime(&mut v); 44 | v 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use rustling::*; 50 | use rustling_ontology_values::dimension::Dimension; 51 | 52 | use super::*; 53 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 54 | for ex in examples.iter() { 55 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 56 | let correct_results = stash 57 | .into_iter() 58 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 59 | .collect::>(); 60 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 61 | } 62 | } 63 | #[test] 64 | fn test_examples() { 65 | let rules = rule_set().unwrap(); 66 | let examples = examples(); 67 | assert_examples(&rules, examples); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /grammar/it/src/rules_celebrations.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | //use rustling_ontology_moment::{Weekday, Grain}; 5 | 6 | 7 | pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 8 | b.rule_1_terminal("christmas", 9 | b.reg(r#"(?:giorno di )?natale"#)?, 10 | |_| Ok(helpers::month_day(12, 25)? 11 | .form(Form::Celebration)) 12 | ); 13 | b.rule_1_terminal("New year's eve", 14 | b.reg(r#"capodanno"#)?, 15 | |_| Ok(helpers::month_day(1, 1)? 16 | .form(Form::Celebration)) 17 | ); 18 | b.rule_1_terminal("Labor's day", 19 | b.reg(r#"festa de[li] lavor(?:o|atori)?"#)?, 20 | |_| Ok(helpers::month_day(5, 1)? 21 | .form(Form::Celebration)) 22 | ); 23 | b.rule_1_terminal("Festa della liberazione", 24 | b.reg(r#"festa della liberazione"#)?, 25 | |_| Ok(helpers::month_day(4, 25)? 26 | .form(Form::Celebration)) 27 | ); 28 | b.rule_1_terminal("Assumption", 29 | b.reg(r#"ferragosto"#)?, 30 | |_| Ok(helpers::month_day(8, 15)? 31 | .form(Form::Celebration)) 32 | ); 33 | b.rule_1_terminal("all soul's day", 34 | b.reg(r#"giorno dei morti"#)?, 35 | |_| Ok(helpers::month_day(11, 2)? 36 | .form(Form::Celebration)) 37 | ); 38 | b.rule_1_terminal("Father day", 39 | b.reg(r#"festa del papà"#)?, 40 | |_| Ok(helpers::month_day(3, 19)? 41 | .form(Form::Celebration)) 42 | ); 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /grammar/it/src/rules_duration.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | use rustling_ontology_moment::{Grain, PeriodComp, Period}; 5 | 6 | pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | // Basic duration units 8 | b.rule_1_terminal("second (unit-of-duration)", 9 | b.reg(r#"sec(?:ond[oi])?"#)?, 10 | |_| Ok(UnitOfDurationValue::new(Grain::Second)) 11 | ); 12 | b.rule_1_terminal("minute (unit-of-duration)", 13 | b.reg(r#"min(?:ut[oi])?"#)?, 14 | |_| Ok(UnitOfDurationValue::new(Grain::Minute)) 15 | ); 16 | b.rule_1_terminal("hour (unit-of-duration)", 17 | b.reg(r#"or[ae]"#)?, 18 | |_| Ok(UnitOfDurationValue::new(Grain::Hour)) 19 | ); 20 | b.rule_1_terminal("day (unit-of-duration)", 21 | b.reg(r#"giorn(?:[oi]|ata)"#)?, 22 | |_| Ok(UnitOfDurationValue::new(Grain::Day)) 23 | ); 24 | b.rule_1_terminal("week (unit-of-duration)", 25 | b.reg(r#"settiman[ae]"#)?, 26 | |_| Ok(UnitOfDurationValue::new(Grain::Week)) 27 | ); 28 | b.rule_1_terminal("month (unit-of-duration)", 29 | b.reg(r#"mes(?:e|i)"#)?, 30 | |_| Ok(UnitOfDurationValue::new(Grain::Month)) 31 | ); 32 | b.rule_1_terminal("year (unit-of-duration)", 33 | b.reg(r#"ann[oi]"#)?, 34 | |_| Ok(UnitOfDurationValue::new(Grain::Year)) 35 | ); 36 | b.rule_1_terminal("1 quarter of an hour", 37 | b.reg(r#"(1/4|(?:un|1) quarto) d'ora"#)?, 38 | |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) 39 | ); 40 | b.rule_1_terminal("1 half an hour", 41 | b.reg(r#"1/2 ora|(?:una |1 )?mezz'?ora"#)?, 42 | |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) 43 | ); 44 | b.rule_1_terminal("3 quarters of an hour", 45 | b.reg(r#"(?:3/4|tre quarti) d'ora"#)?, 46 | |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) 47 | ); 48 | b.rule_1_terminal("two weeks | fortnight", 49 | b.reg(r#"(?:un )?paio di settimane"#)?, 50 | |_| Ok(DurationValue::new(PeriodComp::days(14).into())) 51 | ); 52 | // N duration units 53 | b.rule_2(" ", 54 | integer_check_by_range!(0), 55 | unit_of_duration_check!(), 56 | |integer, uod| Ok(DurationValue::new(PeriodComp::new(uod.value().grain, integer.value().value).into())) 57 | ); 58 | b.rule_3(" hours ", 59 | integer_check_by_range!(0), 60 | b.reg(r#"or[ae] e|h"#)?, 61 | integer_check_by_range!(0,59), 62 | |hour, _, minute| { 63 | let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); 64 | let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); 65 | Ok(DurationValue::new(hour_period + minute_period)) 66 | } 67 | ); 68 | b.rule_3(" and a quarter", 69 | integer_check_by_range!(0), 70 | unit_of_duration_check!(), 71 | b.reg(r#"e un quarto"#)?, 72 | |integer, uod, _| { 73 | let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 74 | Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) 75 | } 76 | ); 77 | b.rule_3(" and a half", 78 | integer_check_by_range!(0), 79 | unit_of_duration_check!(), 80 | b.reg(r#"e mezz[oa]"#)?, 81 | |integer, uod, _| { 82 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 83 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 84 | } 85 | ); 86 | b.rule_3(" and 3 quarters of an hour", 87 | integer_check_by_range!(0), 88 | unit_of_duration_check!(), 89 | b.reg(r#"e tre quarti"#)?, 90 | |integer, uod, _| { 91 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 92 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 93 | } 94 | ); 95 | // Duration combinations 96 | b.rule_3(" and ", 97 | duration_check!(|duration: &DurationValue| !duration.suffixed), 98 | b.reg(r#"e"#)?, 99 | duration_check!(|duration: &DurationValue| !duration.prefixed), 100 | |a, _, b| Ok(a.value() + b.value()) 101 | ); 102 | b.rule_2(" ", 103 | duration_check!(|duration: &DurationValue| !duration.suffixed), 104 | duration_check!(|duration: &DurationValue| !duration.prefixed), 105 | |a, b| Ok(a.value() + b.value()) 106 | ); 107 | b.rule_2(" ", 108 | duration_check!(|duration: &DurationValue| !duration.prefixed), 109 | integer_check_by_range!(0), 110 | |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) 111 | ); 112 | b.rule_2("approx ", 113 | b.reg(r#"verso|interno a|(?:approssim|indic|orient)ativamente|(?:all'in)?circa|più o meno|pressappoco|suppergiù|grosso modo"#)?, 114 | duration_check!(), 115 | |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) 116 | ); 117 | b.rule_2(" approx", 118 | duration_check!(), 119 | b.reg(r#"(?:all'in)?circa|più o meno"#)?, 120 | |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) 121 | ); 122 | b.rule_2("exactly ", 123 | b.reg(r#"esattamente"#)?, 124 | duration_check!(), 125 | |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) 126 | ); 127 | b.rule_2(" exactly", 128 | duration_check!(), 129 | b.reg(r#"(?:esatt|precis)(?:[aoie]|amente)"#)?, 130 | |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) 131 | ); 132 | b.rule_2("during ", 133 | b.reg(r#"(?:durante|per)"#)?, 134 | duration_check!(), 135 | |_, duration| Ok(duration.value().clone().prefixed()) 136 | ); 137 | Ok(()) 138 | } -------------------------------------------------------------------------------- /grammar/ja/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-ja" 3 | version = "0.19.3" 4 | authors = ["Anaïs "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | regex = "1" 9 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 10 | rustling-ontology-moment = { path = "../../moment" } 11 | rustling-ontology-values = { path = "../../values" } 12 | -------------------------------------------------------------------------------- /grammar/ja/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | pub mod rules; 7 | pub mod training; 8 | 9 | use rustling_ontology_values::DimensionKind::*; 10 | 11 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 12 | let mut b = ::rustling::RuleSetBuilder::new( 13 | ::rustling::BoundariesChecker::no_check(), 14 | ::rustling::BoundariesChecker::no_check()); 15 | rules::rules_numbers(&mut b)?; 16 | rules::rules_datetime(&mut b)?; 17 | rules::rules_cycle(&mut b)?; 18 | rules::rules_duration(&mut b)?; 19 | rules::rules_temperature(&mut b)?; 20 | rules::rules_finance(&mut b)?; 21 | rules::rules_percentage(&mut b)?; 22 | Ok(b.build()) 23 | } 24 | 25 | pub fn dims() -> Vec { 26 | return vec![Number, Ordinal, Duration, Datetime, Temperature, AmountOfMoney, Percentage]; 27 | } 28 | 29 | pub fn examples() -> Vec<::rustling::train::Example> { 30 | let mut v = vec![]; 31 | training::examples_numbers(&mut v); 32 | training::examples_datetime(&mut v); 33 | training::examples_durations(&mut v); 34 | training::examples_temperature(&mut v); 35 | training::examples_finance(&mut v); 36 | training::examples_percentage(&mut v); 37 | v 38 | } 39 | 40 | #[cfg(test)] 41 | mod test { 42 | use rustling::*; 43 | use rustling_ontology_values::dimension::Dimension; 44 | 45 | use super::*; 46 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 47 | for ex in examples.iter() { 48 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 49 | let correct_results = stash 50 | .into_iter() 51 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 52 | .collect::>(); 53 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 54 | } 55 | } 56 | #[test] 57 | fn test_examples() { 58 | let rules = rule_set().unwrap(); 59 | let examples = examples(); 60 | assert_examples(&rules, examples); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /grammar/ko/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-ko" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | failure = "0.1" 9 | regex = "1" 10 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 11 | rustling-ontology-moment = { path = "../../moment" } 12 | rustling-ontology-values = { path = "../../values" } 13 | -------------------------------------------------------------------------------- /grammar/ko/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate failure; 3 | extern crate regex; 4 | extern crate rustling; 5 | #[macro_use] 6 | extern crate rustling_ontology_values; 7 | extern crate rustling_ontology_moment; 8 | 9 | pub mod rules; 10 | pub mod training; 11 | 12 | use rustling_ontology_values::DimensionKind::*; 13 | 14 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 15 | let mut b = ::rustling::RuleSetBuilder::new( 16 | ::rustling::BoundariesChecker::detailed(), 17 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 18 | rules::rules_numbers(&mut b)?; 19 | rules::rules_datetime(&mut b)?; 20 | rules::rules_cycle(&mut b)?; 21 | rules::rules_duration(&mut b)?; 22 | rules::rules_temperature(&mut b)?; 23 | rules::rules_finance(&mut b)?; 24 | Ok(b.build()) 25 | } 26 | 27 | pub fn dims() -> Vec { 28 | return vec![Number, Ordinal, Datetime, Duration, Temperature, AmountOfMoney]; 29 | } 30 | 31 | 32 | pub fn examples() -> Vec<::rustling::train::Example> { 33 | let mut v = vec![]; 34 | training::examples_numbers(&mut v); 35 | training::examples_datetime(&mut v); 36 | training::examples_finance(&mut v); 37 | training::examples_temperature(&mut v); 38 | v 39 | } 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use rustling::*; 44 | use rustling_ontology_values::dimension::Dimension; 45 | 46 | use super::*; 47 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 48 | for ex in examples.iter() { 49 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 50 | let correct_results = stash 51 | .into_iter() 52 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 53 | .collect::>(); 54 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 55 | } 56 | } 57 | #[test] 58 | fn test_examples() { 59 | let rules = rule_set().unwrap(); 60 | let examples = examples(); 61 | assert_examples(&rules, examples); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /grammar/pt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-pt" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/pt/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | mod rules_datetime; 7 | mod rules_celebrations; 8 | mod rules_duration; 9 | mod rules_number; 10 | mod rules_amount; 11 | mod training; 12 | 13 | use rustling_ontology_values::DimensionKind::*; 14 | 15 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 16 | let mut b = ::rustling::RuleSetBuilder::new( 17 | ::rustling::BoundariesChecker::detailed(), 18 | ::rustling::BoundariesChecker::separated_alphanumeric_word()); 19 | rules_number::rules_numbers(&mut b)?; 20 | rules_amount::rules_temperature(&mut b)?; 21 | rules_amount::rules_finance(&mut b)?; 22 | rules_amount::rules_percentage(&mut b)?; 23 | rules_datetime::rules_cycle(&mut b)?; 24 | rules_datetime::rules_datetime(&mut b)?; 25 | rules_datetime::rules_datetime_with_duration(&mut b)?; 26 | rules_datetime::rules_datetime_with_cycle(&mut b)?; 27 | rules_celebrations::rules_celebration(&mut b)?; 28 | rules_duration::rules_duration(&mut b)?; 29 | Ok(b.build()) 30 | } 31 | 32 | pub fn dims() -> Vec { 33 | return vec![Number, Ordinal, Datetime, Duration, Temperature, AmountOfMoney, Percentage]; 34 | } 35 | 36 | pub fn examples() -> Vec<::rustling::train::Example> { 37 | let mut v = vec![]; 38 | training::examples_numbers(&mut v); 39 | training::examples_percentage(&mut v); 40 | training::examples_temperature(&mut v); 41 | training::examples_finance(&mut v); 42 | training::examples_durations(&mut v); 43 | training::examples_datetime(&mut v); 44 | v 45 | } 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use rustling::*; 50 | use rustling_ontology_values::dimension::Dimension; 51 | 52 | use super::*; 53 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 54 | for ex in examples.iter() { 55 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 56 | let correct_results = stash 57 | .into_iter() 58 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 59 | .collect::>(); 60 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 61 | } 62 | } 63 | #[test] 64 | fn test_examples() { 65 | let rules = rule_set().unwrap(); 66 | let examples = examples(); 67 | assert_examples(&rules, examples); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /grammar/pt/src/rules_amount.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::dimension::Precision::*; 4 | use rustling_ontology_values::helpers; 5 | 6 | pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { 7 | b.rule_2(" per cent", 8 | number_check!(), 9 | b.reg(r#"(?:%|por ?cento)"#)?, 10 | |number, _| Ok(PercentageValue(number.value().value())) 11 | ); 12 | Ok(()) 13 | } 14 | 15 | pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { 16 | b.rule_2("intersect (X cents)", 17 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 18 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 19 | |a, b| helpers::compose_money(a.value(), b.value())); 20 | b.rule_3("intersect (and X cents)", 21 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 22 | b.reg(r#"e"#)?, 23 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), 24 | |a, _, b| helpers::compose_money(&a.value(), &b.value())); 25 | b.rule_2("intersect", 26 | amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), 27 | number_check!(), 28 | |a, b| helpers::compose_money_number(&a.value(), &b.value())); 29 | 30 | b.rule_1_terminal("$", 31 | b.reg(r#"\$|d[oó]lar(?:es)?"#)?, 32 | |_| Ok(MoneyUnitValue { unit: Some("$") }) 33 | ); 34 | b.rule_1_terminal("EUR", 35 | b.reg(r#"€|euros?"#)?, 36 | |_| Ok(MoneyUnitValue { unit: Some("EUR") }) 37 | ); 38 | b.rule_1_terminal("£", 39 | b.reg(r#"£|libras?"#)?, 40 | |_| Ok(MoneyUnitValue { unit: Some("£") }) 41 | ); 42 | b.rule_1_terminal("GBP", 43 | b.reg(r#"gbp|libras?(?: esterlinas?| inglesas?| brit[âa]nicas)"#)?, 44 | |_| Ok(MoneyUnitValue { unit: Some("GBP") }) 45 | ); 46 | b.rule_1_terminal("USD", 47 | b.reg(r#"d[oó]lar(?:es)?(?: americanos?| estadunidenses?)|us[d\$]"#)?, 48 | |_| Ok(MoneyUnitValue { unit: Some("USD") }) 49 | ); 50 | b.rule_1_terminal("CAD", 51 | b.reg(r#"d[oó]lar(?:es)? canadenses?|cad"#)?, 52 | |_| Ok(MoneyUnitValue { unit: Some("CAD") }) 53 | ); 54 | b.rule_1_terminal("AUD", 55 | b.reg(r#"d[oó]lar(?:es)? australianos?"#)?, 56 | |_| Ok(MoneyUnitValue { unit: Some("AUD") }) 57 | ); 58 | b.rule_1_terminal("Bitcoin", 59 | b.reg(r#"฿|bitcoins?|btc|xbt"#)?, 60 | |_| Ok(MoneyUnitValue { unit: Some("฿") }) 61 | ); 62 | b.rule_1_terminal("JPY", 63 | b.reg(r#"jpy|[yi]en(?:es?)?(?: japoneses?)?"#)?, 64 | |_| Ok(MoneyUnitValue { unit: Some("JPY") }) 65 | ); 66 | b.rule_1_terminal("¥", 67 | b.reg(r#"¥"#)?, 68 | |_| Ok(MoneyUnitValue { unit: Some("¥") }) 69 | ); 70 | b.rule_1_terminal("₽", 71 | b.reg(r#"₽"#)?, 72 | |_| Ok(MoneyUnitValue { unit: Some("₽") }) 73 | ); 74 | b.rule_1_terminal("KRW", 75 | b.reg(r#"krw|₩|won(?:e?s)?(?: (?:sul[- ])?coreanos?)?"#)?, 76 | |_| Ok(MoneyUnitValue { unit: Some("KRW") }) 77 | ); 78 | b.rule_1_terminal("RMB|CNH|CNY", 79 | b.reg(r#"yuan(?:e?s)?(?: chineses?)?|renminbis?"#)?, 80 | |_| Ok(MoneyUnitValue { unit: Some("CNY") }) 81 | ); 82 | b.rule_1_terminal("INR", 83 | b.reg(r#"r[uú]pias?"#)?, 84 | |_| Ok(MoneyUnitValue { unit: Some("INR") }) 85 | ); 86 | b.rule_1_terminal("INR", 87 | b.reg(r#"r[uú]pias? indianas?"#)?, 88 | |_| Ok(MoneyUnitValue { unit: Some("INR") }) 89 | ); 90 | b.rule_1_terminal("HKD", 91 | b.reg(r#"d[oó]lar(?:es)? de hong kong"#)?, 92 | |_| Ok(MoneyUnitValue { unit: Some("HKD") }) 93 | ); 94 | b.rule_1_terminal("CHF", 95 | b.reg(r#"francos? su[íi]ços?"#)?, 96 | |_| Ok(MoneyUnitValue { unit: Some("CHF") }) 97 | ); 98 | b.rule_1_terminal("KR", 99 | b.reg(r#"kr|coroas?"#)?, 100 | |_| Ok(MoneyUnitValue { unit: Some("KR") }) 101 | ); 102 | b.rule_1_terminal("DKK", 103 | b.reg(r#"dkk|coroas? dinamarquesas?"#)?, 104 | |_| Ok(MoneyUnitValue { unit: Some("DKK") }) 105 | ); 106 | b.rule_1_terminal("NOK", 107 | b.reg(r#"nok|coroas? norueguesas?"#)?, 108 | |_| Ok(MoneyUnitValue { unit: Some("NOK") }) 109 | ); 110 | b.rule_1_terminal("SEK", 111 | b.reg(r#"sek|coroas? suecas?"#)?, 112 | |_| Ok(MoneyUnitValue { unit: Some("SEK") }) 113 | ); 114 | b.rule_1_terminal("RUB", 115 | b.reg(r#"rublos?(?: russos?)?|rub"#)?, 116 | |_| Ok(MoneyUnitValue { unit: Some("RUB") }) 117 | ); 118 | b.rule_1_terminal("cent", 119 | b.reg(r#"centavos?"#)?, 120 | |_| Ok(MoneyUnitValue { unit: Some("cent") }) 121 | ); 122 | b.rule_2(" ", 123 | number_check!(), 124 | money_unit!(), 125 | |a, b| { 126 | Ok(AmountOfMoneyValue { 127 | value: a.value().value(), 128 | unit: b.value().unit, 129 | ..AmountOfMoneyValue::default() 130 | }) 131 | }); 132 | b.rule_3(" of ", 133 | number_check!(), 134 | b.reg(r#"de"#)?, 135 | money_unit!(), 136 | |a, _, b| { 137 | Ok(AmountOfMoneyValue { 138 | value: a.value().value(), 139 | unit: b.value().unit, 140 | ..AmountOfMoneyValue::default() 141 | }) 142 | }); 143 | b.rule_2(" ", 144 | money_unit!(), 145 | number_check!(), 146 | |a, b| { 147 | Ok(AmountOfMoneyValue { 148 | value: b.value().value(), 149 | unit: a.value().unit, 150 | ..AmountOfMoneyValue::default() 151 | }) 152 | }); 153 | b.rule_2("about ", 154 | b.reg(r#"quase|aproximadamente|cerca de|por (?:cerca|volta) de|em torno de|uns|umas"#)?, 155 | amount_of_money_check!(), 156 | |_, a| { 157 | Ok(AmountOfMoneyValue { 158 | precision: Approximate, 159 | ..a.value().clone() 160 | }) 161 | }); 162 | b.rule_2(" about", 163 | amount_of_money_check!(), 164 | b.reg(r#"aproximadamente|mais ou menos"#)?, 165 | |a, _| { 166 | Ok(AmountOfMoneyValue { 167 | precision: Approximate, 168 | ..a.value().clone() 169 | }) 170 | }); 171 | b.rule_2("exactly ", 172 | b.reg(r#"exatamente|precisamente"#)?, 173 | amount_of_money_check!(), 174 | |_, a| { 175 | Ok(AmountOfMoneyValue { 176 | precision: Exact, 177 | ..a.value().clone() 178 | }) 179 | }); 180 | b.rule_2(" exactly", 181 | amount_of_money_check!(), 182 | b.reg(r#"exatamente|precisamente|exatos?"#)?, 183 | |a, _| { 184 | Ok(AmountOfMoneyValue { 185 | precision: Exact, 186 | ..a.value().clone() 187 | }) 188 | } 189 | ); 190 | Ok(()) 191 | } 192 | 193 | pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { 194 | b.rule_1("number as temp", number_check!(), |a| { 195 | Ok(TemperatureValue { 196 | value: a.value().value(), 197 | unit: None, 198 | latent: true, 199 | }) 200 | }); 201 | b.rule_2(" temp", 202 | temperature_check!(), 203 | b.reg(r#"°|graus?"#)?, 204 | |a, _| { 205 | Ok(TemperatureValue { 206 | value: a.value().value, 207 | unit: Some("degree"), 208 | latent: false, 209 | }) 210 | }); 211 | b.rule_2(" Celsius", 212 | temperature_check!(), 213 | b.reg(r#"c\.?(?:elsius|ent[ií]grados?)?"#)?, 214 | |a, _| { 215 | Ok(TemperatureValue { 216 | value: a.value().value, 217 | unit: Some("celsius"), 218 | latent: false, 219 | }) 220 | }); 221 | b.rule_2(" Kelvin", 222 | temperature_check!(), 223 | b.reg(r#"k\.?(?:elvin)?"#)?, 224 | |a, _| { 225 | Ok(TemperatureValue { 226 | value: a.value().value, 227 | unit: Some("kelvin"), 228 | latent: false, 229 | }) 230 | }); 231 | b.rule_2(" Fahrenheit", 232 | temperature_check!(), 233 | b.reg(r#"f\.?(?:ahrenheit)?"#)?, 234 | |a, _| { 235 | Ok(TemperatureValue { 236 | value: a.value().value, 237 | unit: Some("fahrenheit"), 238 | latent: false, 239 | }) 240 | }); 241 | b.rule_2(" below zero", 242 | temperature_check!(), 243 | b.reg(r#"abaixo de zero"#)?, 244 | |a, _| { 245 | Ok(TemperatureValue { 246 | value: -1.0 * a.value().value, 247 | latent: false, 248 | ..*a.value() 249 | }) 250 | }); 251 | b.rule_2(" below zero", 252 | b.reg(r#"menos"#)?, 253 | temperature_check!(), 254 | |_, a| { 255 | Ok(TemperatureValue { 256 | value: -1.0 * a.value().value, 257 | latent: false, 258 | ..*a.value() 259 | }) 260 | }); 261 | Ok(()) 262 | } 263 | -------------------------------------------------------------------------------- /grammar/pt/src/rules_celebrations.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_values::helpers; 4 | 5 | pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 6 | // Date HOLIDAY 7 | b.rule_1_terminal("Christmas day", 8 | b.reg(r#"(?:dia de )?natal"#)?, 9 | |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) 10 | ); 11 | // Date HOLIDAY 12 | b.rule_1_terminal("New year's eve", 13 | b.reg(r#"ano novo|primeiro dia do ano"#)?, 14 | |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) 15 | ); 16 | // Date HOLIDAY 17 | b.rule_1_terminal("Christmas'eve", 18 | b.reg(r#"(?:véspera |(?:a )?noite )de natal"#)?, 19 | |_| { 20 | let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; 21 | let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; 22 | Ok(start.span_to(&end, false)? 23 | .form(Form::Celebration)) 24 | } 25 | ); 26 | // Date HOLIDAY 27 | b.rule_1_terminal("All saint's day", 28 | b.reg(r#"(?:(?:no )?dia de )?(?:todos os santos|finados)"#)?, 29 | |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) 30 | ); 31 | // Date HOLIDAY 32 | b.rule_1_terminal("1st of May", 33 | b.reg(r#"dia do (?:trabalho|trabalhador)"#)?, 34 | |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) 35 | ); 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /grammar/pt/src/rules_duration.rs: -------------------------------------------------------------------------------- 1 | use rustling::*; 2 | use rustling_ontology_values::dimension::*; 3 | use rustling_ontology_moment::{Grain, PeriodComp, Period}; 4 | 5 | pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { 6 | b.rule_1_terminal("seconde (unit-of-duration)", 7 | b.reg(r#"segundos?"#)?, 8 | |_| Ok(UnitOfDurationValue::new(Grain::Second)) 9 | ); 10 | b.rule_1_terminal("minute (unit-of-duration)", 11 | b.reg(r#"minutos?|min"#)?, 12 | |_| Ok(UnitOfDurationValue::new(Grain::Minute)) 13 | ); 14 | b.rule_1_terminal("hour (unit-of-duration)", 15 | b.reg(r#"horas?"#)?, 16 | |_| Ok(UnitOfDurationValue::new(Grain::Hour)) 17 | ); 18 | b.rule_1_terminal("day (unit-of-duration)", 19 | b.reg(r#"dias?"#)?, 20 | |_| Ok(UnitOfDurationValue::new(Grain::Day)) 21 | ); 22 | b.rule_1_terminal("week (unit-of-duration)", 23 | b.reg(r#"semanas?"#)?, 24 | |_| Ok(UnitOfDurationValue::new(Grain::Week)) 25 | ); 26 | b.rule_1_terminal("month (unit-of-duration)", 27 | b.reg(r#"m[eê]s(?:es)?"#)?, 28 | |_| Ok(UnitOfDurationValue::new(Grain::Month)) 29 | ); 30 | b.rule_1_terminal("year (unit-of-duration)", 31 | b.reg(r#"anos?"#)?, 32 | |_| Ok(UnitOfDurationValue::new(Grain::Year)) 33 | ); 34 | b.rule_1_terminal("trimester (unit-of-duration)", 35 | b.reg(r#"trimestres?"#)?, 36 | |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) 37 | ); 38 | b.rule_1_terminal("quarter of an hour", 39 | b.reg(r#"(?:um )?quarto de hora"#)?, 40 | |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) 41 | ); 42 | b.rule_1_terminal("half an hour", 43 | b.reg(r#"(?:uma )?meia hora"#)?, 44 | |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) 45 | ); 46 | b.rule_1_terminal("three-quarters of an hour", 47 | b.reg(r#"tr[eê]s quartos de hora"#)?, 48 | |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) 49 | ); 50 | b.rule_3(" h ", 51 | integer_check_by_range!(0), 52 | b.reg(r#"h(?:oras?)?"#)?, 53 | integer_check_by_range!(0,59), 54 | |hour, _, minute| { 55 | let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); 56 | let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); 57 | Ok(DurationValue::new(hour_period + minute_period)) 58 | } 59 | ); 60 | b.rule_2(" ", 61 | integer_check_by_range!(0), 62 | unit_of_duration_check!(), 63 | |integer, uod| Ok(DurationValue::new(PeriodComp::new(uod.value().grain, integer.value().value).into())) 64 | ); 65 | b.rule_3(" and a half", 66 | integer_check_by_range!(0), 67 | unit_of_duration_check!(), 68 | b.reg(r#"e mei[ao]"#)?, 69 | |integer, uod, _| { 70 | let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 71 | Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 72 | } 73 | ); 74 | // b.rule_3(" and a quarter", 75 | // integer_check_by_range!(0), 76 | // unit_of_duration_check!(), 77 | // b.reg(r#""#)?, 78 | // |integer, uod, _| { 79 | // let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; 80 | // Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) 81 | // } 82 | // ); 83 | b.rule_3(" and ", 84 | duration_check!(|duration: &DurationValue| !duration.suffixed), 85 | b.reg(r#"e"#)?, 86 | duration_check!(|duration: &DurationValue| !duration.prefixed), 87 | |a, _, b| Ok(a.value() + b.value()) 88 | ); 89 | b.rule_2(" ", 90 | duration_check!(|duration: &DurationValue| !duration.suffixed), 91 | duration_check!(|duration: &DurationValue| !duration.prefixed), 92 | |a, b| Ok(a.value() + b.value()) 93 | ); 94 | b.rule_2("duration of ", 95 | b.reg(r#"uma duração de"#)?, 96 | duration_check!(), 97 | |_, duration| Ok(duration.value().clone().prefixed()) 98 | ); 99 | b.rule_2("during ", 100 | b.reg(r#"por|durante"#)?, 101 | duration_check!(), 102 | |_, duration| Ok(duration.value().clone().prefixed()) 103 | ); 104 | // FUTUR 105 | b.rule_2("in (future moment)", 106 | b.reg(r#"em|daqui a|dentro de"#)?, 107 | duration_check!(), 108 | |_, duration| duration.value().in_present() 109 | ); 110 | // FUTUR 111 | b.rule_2(" later", 112 | duration_check!(), 113 | b.reg(r#"depois"#)?, 114 | |duration, _| duration.value().in_present() 115 | ); 116 | // PAST 117 | b.rule_2(" ago", 118 | duration_check!(), 119 | b.reg(r#"atr[aá]s"#)?, 120 | |duration, _| duration.value().ago() 121 | ); 122 | // PAST 123 | b.rule_2("ago ", 124 | b.reg(r#"há|faz"#)?, 125 | duration_check!(), 126 | |_, duration| duration.value().ago() 127 | ); 128 | b.rule_2("approx ", 129 | b.reg(r#"aproximadamente|cerca de|por cerca de|por volta de|em torno de"#)?, 130 | duration_check!(), 131 | |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) 132 | ); 133 | b.rule_2(" approx", 134 | duration_check!(), 135 | b.reg(r#"aproximadamente|mais ou? menos"#)?, 136 | |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) 137 | ); 138 | b.rule_2("precisely ", 139 | b.reg(r#"exactamente|precisamente"#)?, 140 | duration_check!(), 141 | |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) 142 | ); 143 | b.rule_2(" precisely", 144 | duration_check!(), 145 | b.reg(r#"exactamente|precisamente"#)?, 146 | |duration , _| Ok(duration.value().clone().precision(Precision::Exact)) 147 | ); 148 | Ok(()) 149 | } 150 | -------------------------------------------------------------------------------- /grammar/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | extern crate rustling_ontology_values; 3 | extern crate rustling_ontology_de as de; 4 | extern crate rustling_ontology_en as en; 5 | extern crate rustling_ontology_es as es; 6 | extern crate rustling_ontology_fr as fr; 7 | extern crate rustling_ontology_pt as pt; 8 | extern crate rustling_ontology_ja as ja; 9 | extern crate rustling_ontology_ko as ko; 10 | extern crate rustling_ontology_zh as zh; 11 | extern crate rustling_ontology_it as it; 12 | 13 | use std::result; 14 | 15 | macro_rules! lang_enum { 16 | ([$($lang:ident),*]) => { 17 | /// Enumerates all language supported for the general purpose ontology. 18 | #[derive(Copy,Clone,Debug,PartialEq, Eq)] 19 | pub enum Lang { 20 | $( $lang, )* 21 | } 22 | 23 | impl Lang { 24 | pub fn all() -> Vec { 25 | vec![ 26 | $( Lang::$lang, )* 27 | ] 28 | } 29 | } 30 | 31 | impl std::str::FromStr for Lang { 32 | type Err = String; 33 | fn from_str(it: &str) -> result::Result { 34 | match &*it.to_uppercase() { 35 | $( stringify!($lang) => Ok(Lang::$lang), )* 36 | _ => Err(format!("Unknown language {}", it)), 37 | } 38 | } 39 | } 40 | 41 | impl ::std::string::ToString for Lang { 42 | fn to_string(&self) -> String { 43 | match self { 44 | $( &Lang::$lang => stringify!($lang).to_string(),)* 45 | } 46 | } 47 | } 48 | 49 | } 50 | } 51 | 52 | lang_enum!([DE, EN, ES, FR, PT, JA, KO, ZH, IT]); 53 | 54 | /// Obtain rules for a given language. 55 | pub fn rules(lang: Lang) -> ::rustling::RustlingResult<::rustling::RuleSet> { 56 | match lang { 57 | Lang::DE => de::rule_set(), 58 | Lang::EN => en::rule_set(), 59 | Lang::ES => es::rule_set(), 60 | Lang::FR => fr::rule_set(), 61 | Lang::PT => pt::rule_set(), 62 | Lang::JA => ja::rule_set(), 63 | Lang::KO => ko::rule_set(), 64 | Lang::ZH => zh::rule_set(), 65 | Lang::IT => it::rule_set(), 66 | } 67 | } 68 | 69 | /// Obtain dimensions for a given language. 70 | pub fn dims(lang: Lang) -> Vec { 71 | match lang { 72 | Lang::DE => de::dims(), 73 | Lang::EN => en::dims(), 74 | Lang::ES => es::dims(), 75 | Lang::FR => fr::dims(), 76 | Lang::PT => pt::dims(), 77 | Lang::JA => ja::dims(), 78 | Lang::KO => ko::dims(), 79 | Lang::ZH => zh::dims(), 80 | Lang::IT => it::dims(), 81 | } 82 | } 83 | 84 | /// Obtain examples for a given language. 85 | pub fn examples(lang: Lang) -> Vec<::rustling::train::Example> { 86 | match lang { 87 | Lang::DE => de::examples(), 88 | Lang::EN => en::examples(), 89 | Lang::ES => es::examples(), 90 | Lang::FR => fr::examples(), 91 | Lang::PT => pt::examples(), 92 | Lang::JA => ja::examples(), 93 | Lang::KO => ko::examples(), 94 | Lang::ZH => zh::examples(), 95 | Lang::IT => it::examples(), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /grammar/zh/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-zh" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 9 | rustling-ontology-moment = { path = "../../moment" } 10 | rustling-ontology-values = { path = "../../values" } 11 | -------------------------------------------------------------------------------- /grammar/zh/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling; 2 | #[macro_use] 3 | extern crate rustling_ontology_values; 4 | extern crate rustling_ontology_moment; 5 | 6 | pub mod rules; 7 | pub mod training; 8 | 9 | use rustling_ontology_values::DimensionKind::*; 10 | 11 | pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet> { 12 | let mut b = ::rustling::RuleSetBuilder::new( 13 | ::rustling::BoundariesChecker::no_check(), 14 | ::rustling::BoundariesChecker::no_check()); 15 | rules::rules_numbers(&mut b)?; 16 | rules::rules_datetime(&mut b)?; 17 | rules::rules_cycle(&mut b)?; 18 | rules::rules_duration(&mut b)?; 19 | rules::rules_temperature(&mut b)?; 20 | Ok(b.build()) 21 | } 22 | 23 | pub fn dims() -> Vec { 24 | return vec![Number, Ordinal, Datetime, Duration, Temperature]; 25 | } 26 | 27 | pub fn examples() -> Vec<::rustling::train::Example> { 28 | let mut v = vec![]; 29 | training::examples_numbers(&mut v); 30 | training::examples_datetime(&mut v); 31 | training::examples_durations(&mut v); 32 | training::examples_temperature(&mut v); 33 | v 34 | } 35 | 36 | #[cfg(test)] 37 | mod test { 38 | use rustling::*; 39 | use rustling_ontology_values::dimension::Dimension; 40 | 41 | use super::*; 42 | fn assert_examples(rules: &RuleSet, examples: Vec>) { 43 | for ex in examples.iter() { 44 | let stash = rules.apply_all(&ex.text.to_lowercase()).unwrap(); 45 | let correct_results = stash 46 | .into_iter() 47 | .filter(|candidate| candidate.root_node.byte_range == Range(0, ex.text.len()) && ex.predicate.check(&candidate)) 48 | .collect::>(); 49 | assert!(!correct_results.is_empty(), format!("No full match found for: {:?}", ex.text)); 50 | } 51 | } 52 | #[test] 53 | fn test_examples() { 54 | let rules = rule_set().unwrap(); 55 | let examples = examples(); 56 | assert_examples(&rules, examples); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /json-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-json-utils" 3 | version = "0.19.3" 4 | authors = ["Hubert De La Jonquiere "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustling-ontology-moment = { path = "../moment" } 9 | rustling-ontology = { path =".." } 10 | serde = { version = "1", features = ["derive"] } 11 | serde_json = "1" 12 | -------------------------------------------------------------------------------- /moment/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-moment" 3 | version = "0.19.3" 4 | authors = ["hdlj "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | chrono = "=0.4.8" 9 | derive-new = "0.5" 10 | enum_primitive = "0.1" 11 | failure = "0.1" 12 | vec_map = "0.8" 13 | 14 | [dev-dependencies] 15 | bencher = "0.1" 16 | 17 | [[bench]] 18 | name = "example" 19 | harness = false 20 | -------------------------------------------------------------------------------- /moment/benches/example.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bencher; 3 | extern crate rustling_ontology_moment; 4 | extern crate chrono; 5 | 6 | use rustling_ontology_moment::*; 7 | use bencher::Bencher; 8 | use chrono::TimeZone; 9 | use chrono::offset::local::Local; 10 | 11 | fn build_context(moment: Moment) -> Context { 12 | let now = Interval::starting_at(moment, Grain::Second); 13 | Context::for_reference(now) 14 | } 15 | 16 | fn bench_hour_minute_with_intersection(bench: &mut Bencher) { 17 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 18 | let constraint = Hour::clock_24(10).intersect(&Minute::new(5)); 19 | let walker = constraint.to_walker(&context.reference, &context); 20 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 21 | } 22 | 23 | fn bench_hour_minute(bench: &mut Bencher) { 24 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 25 | let constraint = HourMinute::clock_24(10, 5); 26 | let walker = constraint.to_walker(&context.reference, &context); 27 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 28 | } 29 | 30 | fn bench_month_day_with_intersection(bench: &mut Bencher) { 31 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 32 | let constraint = Month::new(10).intersect(&DayOfMonth::new(5)); 33 | let walker = constraint.to_walker(&context.reference, &context); 34 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 35 | } 36 | 37 | fn bench_month_day(bench: &mut Bencher) { 38 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 39 | let constraint = MonthDay::new(10, 5); 40 | let walker = constraint.to_walker(&context.reference, &context); 41 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 42 | } 43 | 44 | fn bench_year_month_day_with_intersection(bench: &mut Bencher) { 45 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 46 | let constraint = Year::new(2017).intersect(&Month::new(10)).intersect(&DayOfMonth::new(5)); 47 | let walker = constraint.to_walker(&context.reference, &context); 48 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 49 | } 50 | 51 | fn bench_day_month_year_with_intersection(bench: &mut Bencher) { 52 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 53 | let constraint = DayOfMonth::new(5).intersect(&Month::new(10)).intersect(&Year::new(2017)); 54 | let walker = constraint.to_walker(&context.reference, &context); 55 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 56 | } 57 | 58 | fn bench_weekday_month_day_with_intersection(bench: &mut Bencher) { 59 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 60 | let constraint = DayOfWeek::new(Weekday::Mon).intersect(&DayOfMonth::new(5)).intersect(&Month::new(10)); 61 | let walker = constraint.to_walker(&context.reference, &context); 62 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 63 | } 64 | 65 | fn bench_weeday_day_to_weekday_day(bench: &mut Bencher) { 66 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 67 | let start = DayOfWeek::new(Weekday::Mon).intersect(&DayOfMonth::new(5)); 68 | let end = DayOfWeek::new(Weekday::Sun).intersect(&DayOfMonth::new(12)); 69 | let constraint = start.span_to(&end); 70 | let walker = constraint.to_walker(&context.reference, &context); 71 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 72 | } 73 | 74 | fn bench_year_month_day(bench: &mut Bencher) { 75 | let context = build_context(Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11))); 76 | let constraint = YearMonthDay::new(2017, 10, 5); 77 | let walker = constraint.to_walker(&context.reference, &context); 78 | bench.iter(|| walker.forward.clone().into_iter().take(5).collect::>()); 79 | } 80 | 81 | benchmark_group!(benches, 82 | bench_hour_minute, 83 | bench_hour_minute_with_intersection, 84 | bench_month_day_with_intersection, 85 | bench_year_month_day_with_intersection, 86 | bench_year_month_day, 87 | bench_month_day, 88 | bench_day_month_year_with_intersection, 89 | bench_weekday_month_day_with_intersection, 90 | bench_weeday_day_to_weekday_day 91 | ); 92 | benchmark_main!(benches); -------------------------------------------------------------------------------- /moment/src/bidirectional_walker.rs: -------------------------------------------------------------------------------- 1 | use crate::walker::*; 2 | 3 | #[derive(Clone)] 4 | pub struct BidirectionalWalker { 5 | pub forward: Walker, 6 | pub backward: Walker, 7 | } 8 | 9 | impl BidirectionalWalker { 10 | pub fn new() -> BidirectionalWalker { 11 | BidirectionalWalker { 12 | forward: Walker::Vec(vec![]), 13 | backward: Walker::Vec(vec![]), 14 | } 15 | } 16 | 17 | pub fn forward(self, combiner: Walker) -> BidirectionalWalker { 18 | BidirectionalWalker { 19 | forward: combiner, 20 | backward: self.backward, 21 | } 22 | } 23 | 24 | pub fn forward_values(self, values: Vec) -> BidirectionalWalker { 25 | BidirectionalWalker { 26 | forward: Walker::vec(values), 27 | backward: self.backward, 28 | } 29 | } 30 | 31 | pub fn forward_with(self, anchor: V, transform: FP) -> BidirectionalWalker 32 | where 33 | FP: Fn(V) -> V + 'static, 34 | { 35 | BidirectionalWalker { 36 | forward: Walker::generator(anchor, transform), 37 | backward: self.backward, 38 | } 39 | } 40 | 41 | pub fn backward(self, combiner: Walker) -> BidirectionalWalker { 42 | BidirectionalWalker { 43 | forward: self.forward, 44 | backward: combiner, 45 | } 46 | } 47 | 48 | pub fn backward_values(self, values: Vec) -> BidirectionalWalker { 49 | BidirectionalWalker { 50 | forward: self.forward, 51 | backward: Walker::vec(values), 52 | } 53 | } 54 | 55 | pub fn backward_with(self, anchor: V, transform: BP) -> BidirectionalWalker 56 | where 57 | BP: Fn(V) -> V + 'static, 58 | { 59 | BidirectionalWalker { 60 | forward: self.forward, 61 | backward: Walker::generator(anchor, transform), 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use crate::period::*; 70 | use crate::{Interval, Moment}; 71 | use chrono::{Local, TimeZone}; 72 | 73 | #[test] 74 | fn test_interval_iterator() { 75 | let now = Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)); 76 | let interval = Interval::starting_at(now, Grain::Second); 77 | let mut iterator = Walker::generator(interval, |prev| prev + PeriodComp::days(1)); 78 | 79 | assert_eq!( 80 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 81 | iterator.next().unwrap().start 82 | ); 83 | assert_eq!( 84 | Moment(Local.ymd(2017, 04, 26).and_hms(9, 10, 11)), 85 | iterator.next().unwrap().start 86 | ); 87 | assert_eq!( 88 | Moment(Local.ymd(2017, 04, 27).and_hms(9, 10, 11)), 89 | iterator.next().unwrap().start 90 | ); 91 | assert_eq!( 92 | Moment(Local.ymd(2017, 04, 28).and_hms(9, 10, 11)), 93 | iterator.next().unwrap().start 94 | ); 95 | } 96 | 97 | #[test] 98 | fn test_interval_bidirectional() { 99 | let anchor = Interval::starting_at( 100 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 101 | Grain::Second, 102 | ); 103 | 104 | let mut bidirectional = BidirectionalWalker::new() 105 | .forward_with(anchor, |prev| prev + PeriodComp::days(1)) 106 | .backward_with(anchor - PeriodComp::days(1), |prev| { 107 | prev - PeriodComp::days(1) 108 | }); 109 | 110 | assert_eq!( 111 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 112 | bidirectional.forward.next().unwrap().start 113 | ); 114 | assert_eq!( 115 | Moment(Local.ymd(2017, 04, 26).and_hms(9, 10, 11)), 116 | bidirectional.forward.next().unwrap().start 117 | ); 118 | assert_eq!( 119 | Moment(Local.ymd(2017, 04, 24).and_hms(9, 10, 11)), 120 | bidirectional.backward.next().unwrap().start 121 | ); 122 | assert_eq!( 123 | Moment(Local.ymd(2017, 04, 23).and_hms(9, 10, 11)), 124 | bidirectional.backward.next().unwrap().start 125 | ); 126 | } 127 | 128 | #[test] 129 | fn test_interval_bidirectional_forward_values() { 130 | let values = vec![ 131 | Interval::starting_at( 132 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 133 | Grain::Second, 134 | ), 135 | Interval::starting_at( 136 | Moment(Local.ymd(2017, 04, 26).and_hms(9, 10, 11)), 137 | Grain::Second, 138 | ), 139 | ]; 140 | 141 | let mut only_forward = BidirectionalWalker::new().forward_values(values); 142 | 143 | assert_eq!(None, only_forward.backward.next()); 144 | assert_eq!( 145 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 146 | only_forward.forward.next().unwrap().start 147 | ); 148 | assert_eq!( 149 | Moment(Local.ymd(2017, 04, 26).and_hms(9, 10, 11)), 150 | only_forward.forward.next().unwrap().start 151 | ); 152 | assert_eq!(None, only_forward.forward.next()); 153 | } 154 | 155 | #[test] 156 | fn test_interval_bidirectional_forward_closure() { 157 | let anchor = Interval::starting_at( 158 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 159 | Grain::Second, 160 | ); 161 | 162 | let mut only_forward = 163 | BidirectionalWalker::new().forward_with(anchor, |prev| prev + PeriodComp::days(1)); 164 | 165 | assert_eq!(None, only_forward.backward.next()); 166 | assert_eq!( 167 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 168 | only_forward.forward.next().unwrap().start 169 | ); 170 | assert_eq!( 171 | Moment(Local.ymd(2017, 04, 26).and_hms(9, 10, 11)), 172 | only_forward.forward.next().unwrap().start 173 | ); 174 | } 175 | 176 | #[test] 177 | fn test_interval_bidirectional_backward_values() { 178 | let values = vec![ 179 | Interval::starting_at( 180 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 181 | Grain::Second, 182 | ), 183 | Interval::starting_at( 184 | Moment(Local.ymd(2017, 04, 24).and_hms(9, 10, 11)), 185 | Grain::Second, 186 | ), 187 | ]; 188 | 189 | let mut only_backward = BidirectionalWalker::new().backward_values(values); 190 | 191 | assert_eq!(None, only_backward.forward.next()); 192 | assert_eq!( 193 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 194 | only_backward.backward.next().unwrap().start 195 | ); 196 | assert_eq!( 197 | Moment(Local.ymd(2017, 04, 24).and_hms(9, 10, 11)), 198 | only_backward.backward.next().unwrap().start 199 | ); 200 | assert_eq!(None, only_backward.backward.next()); 201 | } 202 | 203 | #[test] 204 | fn test_interval_bidirectional_backward_closure() { 205 | let anchor = Interval::starting_at( 206 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 207 | Grain::Second, 208 | ); 209 | 210 | let mut only_backward = 211 | BidirectionalWalker::new().backward_with(anchor, |prev| prev - PeriodComp::days(1)); 212 | 213 | assert_eq!(None, only_backward.forward.next()); 214 | assert_eq!( 215 | Moment(Local.ymd(2017, 04, 25).and_hms(9, 10, 11)), 216 | only_backward.backward.next().unwrap().start 217 | ); 218 | assert_eq!( 219 | Moment(Local.ymd(2017, 04, 24).and_hms(9, 10, 11)), 220 | only_backward.backward.next().unwrap().start 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! General purpose ontology based on rustling. 2 | //! 3 | //! Contains detectors for various entities, like numbers, temperatures, dates 4 | //! in french, english, ... 5 | //! 6 | //! ``` 7 | //! extern crate rustling; 8 | //! extern crate rustling_ontology; 9 | //! 10 | //! fn main() { 11 | //! use rustling_ontology::*; 12 | //! 13 | //! let ctx = ResolverContext::default(); 14 | //! let parser = build_parser(rustling_ontology::Lang::EN).unwrap(); 15 | //! let result = parser.parse("twenty-one", &ctx).unwrap(); 16 | //! 17 | //! let int: output::IntegerOutput= result[0].value.clone().attempt_into().unwrap(); 18 | //! assert_eq!(21, int.0); 19 | //! } 20 | //! ``` 21 | extern crate rmp_serde; 22 | extern crate rustling; 23 | extern crate rustling_ontology_grammar as grammar; 24 | extern crate rustling_ontology_moment; 25 | extern crate rustling_ontology_values; 26 | extern crate serde; 27 | 28 | pub use grammar::{dims, Lang}; 29 | pub use rustling::RustlingResult; 30 | pub use rustling::{AttemptInto, ParsedNode, ParserMatch, ParsingAnalysis, Range, Sym, Value}; 31 | pub use rustling_ontology_moment::Grain; 32 | pub use rustling_ontology_moment::{Interval, Local, Moment, TimeZone}; 33 | pub use rustling_ontology_values::dimension; 34 | pub use rustling_ontology_values::output; 35 | pub use rustling_ontology_values::output::{Output, OutputKind}; 36 | pub use rustling_ontology_values::{IdentityContext, ParsingContext, ResolverContext}; 37 | 38 | mod mapper; 39 | mod parser; 40 | mod tagger; 41 | 42 | pub use tagger::CandidateTagger; 43 | 44 | // Rustling raw parser. Don't use directly 45 | #[doc(hidden)] 46 | pub type RawParser = rustling::Parser; 47 | 48 | /// Main class to be use at runtime. 49 | pub struct Parser(RawParser); 50 | 51 | impl Parser { 52 | pub fn parse_with_kind_order( 53 | &self, 54 | input: &str, 55 | context: &ResolverContext, 56 | order: &[OutputKind], 57 | ) -> RustlingResult>> { 58 | let tagger = CandidateTagger { 59 | output_kind_filter: order, 60 | context, 61 | resolve_all_candidates: false, 62 | }; 63 | Ok(self 64 | .0 65 | .parse(input, &tagger)? 66 | .into_iter() 67 | .filter_map(|m| { 68 | if let Some(v) = m.value { 69 | Some(ParserMatch { 70 | byte_range: m.byte_range, 71 | char_range: m.char_range, 72 | parsing_tree_height: m.parsing_tree_height, 73 | parsing_tree_num_nodes: m.parsing_tree_num_nodes, 74 | value: v, 75 | probalog: m.probalog, 76 | latent: m.latent, 77 | }) 78 | } else { 79 | None 80 | } 81 | }) 82 | .collect()) 83 | } 84 | 85 | pub fn parse( 86 | &self, 87 | input: &str, 88 | context: &ResolverContext, 89 | ) -> RustlingResult>> { 90 | let all_output = OutputKind::all(); 91 | self.parse_with_kind_order(input, context, &all_output) 92 | } 93 | 94 | pub fn analyse_with_kind_order( 95 | &self, 96 | examples: Vec<&str>, 97 | context: &ResolverContext, 98 | order: &[OutputKind], 99 | ) -> RustlingResult { 100 | let tagger = CandidateTagger { 101 | output_kind_filter: order, 102 | context, 103 | resolve_all_candidates: false, 104 | }; 105 | self.0.analyse(examples, &tagger) 106 | } 107 | 108 | pub fn analyse( 109 | &self, 110 | examples: Vec<&str>, 111 | context: &ResolverContext, 112 | ) -> RustlingResult { 113 | let all_kind = OutputKind::all(); 114 | self.analyse_with_kind_order(examples, &context, &all_kind) 115 | } 116 | 117 | pub fn num_rules(&self) -> usize { 118 | self.0.num_rules() 119 | } 120 | 121 | pub fn num_text_patterns(&self) -> usize { 122 | self.0.num_text_patterns() 123 | } 124 | } 125 | 126 | /// Obtain a parser for a given language. 127 | pub fn build_parser(lang: Lang) -> RustlingResult { 128 | build_raw_parser(lang).map(crate::Parser) 129 | } 130 | 131 | /// Obtain a parser for a given language. 132 | pub fn build_raw_parser(lang: Lang) -> RustlingResult { 133 | let rules = grammar::rules(lang)?; 134 | let model = match lang { 135 | Lang::DE => { 136 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/de.rmp"))[..]) 137 | } 138 | Lang::EN => { 139 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/en.rmp"))[..]) 140 | } 141 | Lang::ES => { 142 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/es.rmp"))[..]) 143 | } 144 | Lang::IT => { 145 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/it.rmp"))[..]) 146 | } 147 | Lang::FR => { 148 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/fr.rmp"))[..]) 149 | } 150 | Lang::PT => { 151 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/pt.rmp"))[..]) 152 | } 153 | Lang::JA => { 154 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/ja.rmp"))[..]) 155 | } 156 | Lang::KO => { 157 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/ko.rmp"))[..]) 158 | } 159 | Lang::ZH => { 160 | ::rmp_serde::decode::from_read(&include_bytes!(concat!(env!("OUT_DIR"), "/zh.rmp"))[..]) 161 | } 162 | }?; 163 | Ok(crate::RawParser::new( 164 | rules, 165 | model, 166 | crate::parser::FeatureExtractor(), 167 | )) 168 | } 169 | 170 | pub fn train_parser(lang: Lang) -> RustlingResult { 171 | let rules = grammar::rules(lang)?; 172 | let examples = grammar::examples(lang); 173 | let model = ::rustling::train::train(&rules, examples, crate::parser::FeatureExtractor())?; 174 | Ok(Parser(::rustling::Parser::new( 175 | rules, 176 | model, 177 | crate::parser::FeatureExtractor(), 178 | ))) 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use super::*; 184 | 185 | #[test] 186 | fn test_long_number_en() { 187 | let ctx = ResolverContext::default(); 188 | let parser = build_parser(Lang::EN).unwrap(); 189 | let number = "one million five hundred twenty-one thousand eighty-two"; 190 | let result = parser 191 | .parse_with_kind_order(number, &ctx, &[OutputKind::Number]) 192 | .unwrap(); 193 | let int: output::IntegerOutput = result[0].value.clone().attempt_into().unwrap(); 194 | assert_eq!(1521082, int.0); 195 | } 196 | 197 | #[test] 198 | #[ignore] 199 | fn time_resolve_complex_train_sentence() { 200 | let parser = build_raw_parser(Lang::EN).unwrap(); 201 | // let sent = "I want a return train ticket from Bordeaux to Strasbourg, friday the 12th of May, 10:32 am to wednesday the 7th of june, 6:22 pm"; 202 | let sent = "I want a return train ticket from Bordeaux to Strasbourg, friday the 12th of May, 10:32 am to wednesday the 7th of june, 6:22 pm".to_lowercase(); 203 | let tagger = CandidateTagger { 204 | output_kind_filter: &OutputKind::all(), 205 | context: &ResolverContext::default(), 206 | resolve_all_candidates: false, 207 | }; 208 | let result = parser.candidates(&*sent, &tagger).unwrap(); 209 | println!("{}", result.len()); 210 | for r in &result { 211 | println!( 212 | "{:?}", 213 | &sent[r.node.root_node.byte_range.0..r.node.root_node.byte_range.1] 214 | ); 215 | } 216 | panic!(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/mapper.rs: -------------------------------------------------------------------------------- 1 | use rustling_ontology_values::dimension::{Dimension,DatetimeKind}; 2 | use rustling_ontology_values::output::OutputKind; 3 | pub use rustling_ontology_moment::{Interval, Moment, Local, TimeZone}; 4 | 5 | pub fn map_dimension(dimension: &mut Dimension, output_kind_filter: &[OutputKind]) { 6 | if let Dimension::Datetime(datetime_value) = dimension { 7 | 8 | // If the filter contains Datetime but no datetime subkind, then no subtyping is required 9 | if output_kind_filter.contains(&OutputKind::Datetime) && 10 | !output_kind_filter.contains(&OutputKind::Date) && 11 | !output_kind_filter.contains(&OutputKind::DatePeriod) && 12 | !output_kind_filter.contains(&OutputKind::Time) && 13 | !output_kind_filter.contains(&OutputKind::TimePeriod) { return } 14 | 15 | // If Datetime IS NOT in the OutputKind filter, then SOME specific subtyping is 16 | // required, through the field datetime_kind of the datetime_value. 17 | 18 | // Technically if the client filter is None, then the filter will contain all possible 19 | // output kinds, in which case we want to return more specific subkinds rather than 20 | // Datetime. 21 | 22 | // So Datetime will be returned only if Datetime has been 23 | // explicitly defined as the filter 24 | 25 | // Helper values to later determine the datetime subkind, if some datetime subkind 26 | // hasn't already been set by the grammar. If so then the match will work if the filter 27 | // allows for it. 28 | if !is_valid_datetime_kind(&datetime_value.datetime_kind) { 29 | 30 | // Find the subkind: Figure out the Datetime subtype from the Form, Grain and other 31 | // stuff contained in the Dimension::Datetime(datetime_value) 32 | 33 | let date_time_grain = (datetime_value.constraint.grain_left().is_date_grain() && 34 | datetime_value.constraint.grain_right().is_time_grain()) || 35 | (datetime_value.constraint.grain_right().is_date_grain() && 36 | datetime_value.constraint.grain_left().is_time_grain()); 37 | 38 | let date_grain = !date_time_grain && datetime_value.constraint.grain_min().is_date_grain(); 39 | 40 | let time_grain = datetime_value.is_today_date_and_time() || 41 | !date_time_grain && datetime_value.constraint.grain_min().is_time_grain(); 42 | 43 | // Assign the relevant Datetime subtype depending on above helper values and 44 | // subkinds in the filter 45 | if check_filter(output_kind_filter, &OutputKind::Date) && 46 | !datetime_value.is_period() && date_grain { 47 | datetime_value.datetime_kind = DatetimeKind::Date; 48 | } else if check_filter(output_kind_filter, &OutputKind::Time) && 49 | !datetime_value.is_period() && time_grain { 50 | datetime_value.datetime_kind = DatetimeKind::Time 51 | } else if check_filter(output_kind_filter, &OutputKind::DatePeriod) && 52 | datetime_value.is_period() && date_grain { 53 | datetime_value.datetime_kind = DatetimeKind::DatePeriod; 54 | } else if check_filter(output_kind_filter, &OutputKind::TimePeriod) && 55 | datetime_value.is_period() && time_grain { 56 | datetime_value.datetime_kind = DatetimeKind::TimePeriod; 57 | } else { 58 | // If the dimension is datetime and none of the 4 subtypes, then it's the 59 | // complement subtype, hence Datetime 60 | datetime_value.datetime_kind = DatetimeKind::Datetime; 61 | } 62 | } 63 | } 64 | } 65 | 66 | fn check_filter(output_kind_filter: &[OutputKind], output_kind: &OutputKind) -> bool { 67 | output_kind_filter.is_empty() || output_kind_filter.contains(&output_kind) 68 | } 69 | 70 | // This is here and not e.g. on the DatetimeKind type because it's up to the mapper to decide 71 | // which kinds are valid 72 | fn is_valid_datetime_kind(datetime_kind: &DatetimeKind) -> bool { 73 | match datetime_kind { 74 | &DatetimeKind::Empty => false, 75 | &DatetimeKind::DatetimeComplement { .. } => false, 76 | _ => true, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use rustling; 2 | use rustling_ontology_values::dimension::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Hash, Clone, Eq, PartialEq, Serialize, Deserialize)] 6 | pub enum Feat { 7 | Rules(Vec), 8 | Grains(Vec), 9 | } 10 | 11 | impl rustling::Feature for Feat {} 12 | 13 | pub struct FeatureExtractor(); 14 | 15 | impl rustling::FeatureExtractor for FeatureExtractor { 16 | fn for_parsed_node( 17 | &self, 18 | node: &rustling::ParsedNode, 19 | ) -> rustling::Input { 20 | self.for_node(&node.root_node) 21 | } 22 | fn for_node(&self, node: &rustling::Node) -> rustling::Input { 23 | extract_node_features(&node) 24 | } 25 | } 26 | 27 | pub fn extract_node_features( 28 | node: &rustling::Node, 29 | ) -> rustling::Input { 30 | let grains_feat = node 31 | .children 32 | .iter() 33 | .filter_map(|c| c.payload.map(|p| p.0 as usize)) 34 | .collect::>(); 35 | let rules_feat = node 36 | .children 37 | .iter() 38 | .map( |child| child.rule_sym ) 39 | .collect::>(); 40 | 41 | let mut features = vec![Feat::Rules(rules_feat)]; 42 | if !grains_feat.is_empty() { 43 | features.push(Feat::Grains(grains_feat)); 44 | } 45 | 46 | let children_features = node 47 | .children 48 | .iter() 49 | .map(|child| extract_node_features(child)) 50 | .collect(); 51 | 52 | rustling::Input { 53 | classifier_id: rustling::RuleId(node.rule_sym), 54 | features, 55 | children: children_features, 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use crate::{build_raw_parser, CandidateTagger, Lang, OutputKind}; 62 | use rustling::ParserMatch; 63 | use rustling::*; 64 | use rustling_ontology_values::dimension::*; 65 | use rustling_ontology_values::IdentityContext; 66 | 67 | #[test] 68 | fn test_twenty() { 69 | let parser = build_raw_parser(Lang::EN).unwrap(); 70 | let tagger = CandidateTagger { 71 | output_kind_filter: &[OutputKind::Number], 72 | context: &IdentityContext::new(), 73 | resolve_all_candidates: false, 74 | }; 75 | let result = parser.parse("twenty", &tagger).unwrap(); 76 | // TODO: check why `parsing_tree_height` and `parsing_tree_num_nodes` equal 2 instead of 1 77 | assert_eq!( 78 | ParserMatch { 79 | byte_range: Range(0, 6), 80 | char_range: Range(0, 6), 81 | parsing_tree_height: 2, 82 | parsing_tree_num_nodes: 2, 83 | value: Some(IntegerValue::new_with_grain(20, 1).unwrap().into()), 84 | probalog: 0.0, 85 | latent: false, 86 | }, 87 | result[0] 88 | ); 89 | } 90 | 91 | #[test] 92 | fn test_21() { 93 | let parser = build_raw_parser(Lang::EN).unwrap(); 94 | let tagger = CandidateTagger { 95 | output_kind_filter: &[OutputKind::Number], 96 | context: &IdentityContext::new(), 97 | resolve_all_candidates: false, 98 | }; 99 | let result = parser.parse("twenty-one", &tagger).unwrap(); 100 | assert_eq!( 101 | 21, 102 | IntegerValue::attempt_from(result[0].value.clone().unwrap()) 103 | .unwrap() 104 | .value 105 | ); 106 | } 107 | 108 | #[test] 109 | fn test_2_1000() { 110 | let tagger = CandidateTagger { 111 | output_kind_filter: &[OutputKind::Number], 112 | context: &IdentityContext::new(), 113 | resolve_all_candidates: false, 114 | }; 115 | let parser = build_raw_parser(Lang::EN).unwrap(); 116 | let result = parser.parse("twenty-one thousands", &tagger).unwrap(); 117 | assert_eq!( 118 | 21000, 119 | IntegerValue::attempt_from(result[0].value.clone().unwrap()) 120 | .unwrap() 121 | .value 122 | ); 123 | } 124 | 125 | #[test] 126 | fn test_foobar() { 127 | let parser = build_raw_parser(Lang::EN).unwrap(); 128 | let tagger = CandidateTagger { 129 | output_kind_filter: &[OutputKind::Number], 130 | context: &IdentityContext::new(), 131 | resolve_all_candidates: false, 132 | }; 133 | let result = parser.parse("foobar twenty thousands", &tagger).unwrap(); 134 | assert_eq!( 135 | 20000, 136 | IntegerValue::attempt_from(result[0].value.clone().unwrap()) 137 | .unwrap() 138 | .value 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/tagger.rs: -------------------------------------------------------------------------------- 1 | use crate::mapper; 2 | use rustling::{Candidate, MaxElementTagger, ParsedNode, ParserMatch, Range, Value}; 3 | use rustling_ontology_values::{Dimension, OutputKind, ParsingContext}; 4 | use std::cmp::{Ordering, PartialOrd}; 5 | 6 | pub struct CandidateTagger<'a, C: ParsingContext + 'a> { 7 | pub output_kind_filter: &'a [OutputKind], 8 | pub context: &'a C, 9 | pub resolve_all_candidates: bool, 10 | } 11 | 12 | impl<'a, C: ParsingContext> MaxElementTagger for CandidateTagger<'a, C> { 13 | type O = Option; 14 | 15 | fn tag( 16 | &self, 17 | mut candidates: Vec<(ParsedNode, ParserMatch)>, 18 | ) -> Vec>> { 19 | // The filter is an OutputKind vector 20 | 21 | // Update the candidate Dimension values, specifically for Datetime 22 | // values, which will be tagged with a specific subtype or with Datetime. 23 | // This is necessary to filter candidates acc. to the OutputKind filter, and to 24 | // later propagate the info of Datetime subtype to the Output value. 25 | // => parsed_node.value and parser_match.value are a Dimension(dimension_value) 26 | 27 | for (ref mut parsed_node, ref mut parser_match) in &mut candidates { 28 | mapper::map_dimension(&mut parsed_node.value, self.output_kind_filter); 29 | mapper::map_dimension(&mut parser_match.value, self.output_kind_filter); 30 | } 31 | 32 | // 1. Filtering and priorisation of candidates among OutputKinds, based on the filter: 33 | // - presence: candidate is valid if its dimension matches an OutputKind present in the 34 | // filter 35 | // - order: candidate associated with position of OutputKind in the filter 36 | let mut candidates = candidates 37 | .into_iter() 38 | .filter_map(|(parsed_node, parser_match)| { 39 | if parsed_node.value.is_too_ambiguous() { 40 | None 41 | } else { 42 | self.output_kind_filter 43 | .iter() 44 | .rev() 45 | // Keep candidates whose Dimension(dimension_value) matches an OutputKind 46 | // from the filter. 47 | .position(|output_kind| output_kind.match_dim(&parsed_node.value)) 48 | .map(|position| (parsed_node, parser_match, position)) 49 | } 50 | }) 51 | .collect::>(); 52 | 53 | // 2. Priorisation intra OutputKind - Use probas from training, and many other things 54 | // like match length etc. 55 | candidates.sort_by(|a, b| { 56 | a.1.byte_range 57 | .len() 58 | .cmp(&b.1.byte_range.len()) 59 | .then_with(|| a.1.byte_range.0.cmp(&b.1.byte_range.0)) 60 | .then_with(|| a.2.cmp(&b.2)) 61 | .then_with(|| { 62 | if a.1.value.kind() == b.1.value.kind() { 63 | a.1.probalog 64 | .partial_cmp(&b.1.probalog) 65 | .unwrap_or(Ordering::Equal) 66 | } else { 67 | Ordering::Equal 68 | } 69 | }) 70 | .then_with(|| b.1.parsing_tree_height.cmp(&a.1.parsing_tree_height)) 71 | .then_with(|| b.1.parsing_tree_num_nodes.cmp(&a.1.parsing_tree_num_nodes)) 72 | }); 73 | 74 | let mut selected_ranges: Vec = vec![]; 75 | 76 | candidates 77 | .into_iter() 78 | .rev() 79 | .map(|c| { 80 | if selected_ranges 81 | .iter() 82 | .all(|a| a.is_disjoint(&c.1.byte_range)) 83 | { 84 | let resolved_value = self.context.resolve(&c.1.value); 85 | if resolved_value.is_some() { 86 | selected_ranges.push(c.1.byte_range); 87 | return Candidate { 88 | node: c.0, 89 | match_: ParserMatch { 90 | byte_range: c.1.byte_range, 91 | char_range: c.1.char_range, 92 | parsing_tree_height: c.1.parsing_tree_height, 93 | parsing_tree_num_nodes: c.1.parsing_tree_num_nodes, 94 | value: resolved_value, 95 | probalog: c.1.probalog, 96 | latent: c.1.latent, 97 | }, 98 | tagged: true, 99 | }; 100 | } 101 | } 102 | let resolved_value = if self.resolve_all_candidates { 103 | self.context.resolve(&c.1.value) 104 | } else { 105 | None 106 | }; 107 | Candidate { 108 | node: c.0, 109 | match_: ParserMatch { 110 | byte_range: c.1.byte_range, 111 | char_range: c.1.char_range, 112 | parsing_tree_height: c.1.parsing_tree_height, 113 | parsing_tree_num_nodes: c.1.parsing_tree_num_nodes, 114 | value: resolved_value, 115 | probalog: c.1.probalog, 116 | latent: c.1.latent, 117 | }, 118 | tagged: false, 119 | } 120 | }) 121 | .collect() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/de.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_de_numbers() { 13 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_de_ordinal() { 19 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_de_percentage() { 25 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_de_duration() { 31 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_de_temperature() { 37 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_de_amount_of_money() { 43 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_de_datetime() { 49 | utils::run_json_test(Lang::DE, utils::build_resources_path("de", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/en.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_en_numbers() { 13 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_en_ordinal() { 19 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_en_percentage() { 25 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_en_duration() { 31 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_en_temperature() { 37 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_en_amount_of_money() { 43 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_en_datetime() { 49 | utils::run_json_test(Lang::EN, utils::build_resources_path("en", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/es.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_es_numbers() { 13 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_es_ordinal() { 19 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_es_percentage() { 25 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_es_duration() { 31 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_es_temperature() { 37 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_es_amount_of_money() { 43 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_es_datetime() { 49 | utils::run_json_test(Lang::ES, utils::build_resources_path("es", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/fr.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_fr_numbers() { 13 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_fr_ordinal() { 19 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_fr_percentage() { 25 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_fr_duration() { 31 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_fr_temperature() { 37 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_fr_amount_of_money() { 43 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_fr_datetime() { 49 | utils::run_json_test(Lang::FR, utils::build_resources_path("fr", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/it.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_it_numbers() { 13 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_it_ordinal() { 19 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_it_percentage() { 25 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_it_duration() { 31 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_it_temperature() { 37 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_it_amount_of_money() { 43 | utils::run_json_test(Lang::IT, utils::build_resources_path("it", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_it_datetime() { 49 | utils::run_json_test(Lang::FR, utils::build_resources_path("it", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/ja.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_ja_numbers() { 13 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_ja_ordinal() { 19 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_ja_percentage() { 25 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_ja_duration() { 31 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_ja_temperature() { 37 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_ja_amount_of_money() { 43 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_ja_datetime() { 49 | utils::run_json_test(Lang::JA, utils::build_resources_path("ja", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/pt.rs: -------------------------------------------------------------------------------- 1 | extern crate rustling_ontology; 2 | extern crate rustling_ontology_moment as moment; 3 | extern crate rustling_ontology_json_utils as json_utils; 4 | extern crate serde_json; 5 | 6 | mod utils; 7 | 8 | use rustling_ontology::Lang; 9 | 10 | #[test] 11 | #[ignore] 12 | fn test_pt_numbers() { 13 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "number.json")); 14 | } 15 | 16 | #[test] 17 | #[ignore] 18 | fn test_pt_ordinal() { 19 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "ordinal.json")); 20 | } 21 | 22 | #[test] 23 | #[ignore] 24 | fn test_pt_percentage() { 25 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "percentage.json")); 26 | } 27 | 28 | #[test] 29 | #[ignore] 30 | fn test_pt_duration() { 31 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "duration.json")); 32 | } 33 | 34 | #[test] 35 | #[ignore] 36 | fn test_pt_temperature() { 37 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "temperature.json")); 38 | } 39 | 40 | #[test] 41 | #[ignore] 42 | fn test_pt_amount_of_money() { 43 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "amount_of_money.json")); 44 | } 45 | 46 | #[test] 47 | #[ignore] 48 | fn test_pt_datetime() { 49 | utils::run_json_test(Lang::PT, utils::build_resources_path("pt", "datetime.json")); 50 | } -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use json_utils::*; 2 | use moment; 3 | use rustling_ontology::{ResolverContext, Interval, build_parser, Lang}; 4 | use serde_json; 5 | 6 | use std::env; 7 | use std::path; 8 | 9 | pub fn run_json_test>(lang: Lang, path: P) { 10 | let file = ::std::fs::File::open(path).unwrap(); 11 | let utterances: Vec = serde_json::from_reader(&file).unwrap(); 12 | let utterances: Vec = utterances.into_iter().filter(|it| it.keep()).collect(); 13 | let parser = build_parser(lang).unwrap(); 14 | for utterance in utterances { 15 | let context = ResolverContext::for_reference(Interval::starting_at(utterance.context, moment::Grain::Second)); 16 | let entities = parser.parse(utterance.phrase.to_lowercase().as_str(), &context).unwrap(); 17 | assert_eq!(entities.len(), 1, "Only one match was exepcted for this sentence: {:?}", utterance.phrase.as_str()); 18 | 19 | let entity = entities.first(); 20 | match (entity, utterance.value) { 21 | (Some(entity), Some(expected_value)) => { 22 | assert_eq!(entity.byte_range.len(), utterance.phrase.len(), "Expected full match for this sentence: {:?} found: {:?}", utterance.phrase.as_str(), entity); 23 | let value: SlotValue = entity.value.clone().into(); 24 | assert_eq!(value, expected_value, "Sentence: {:?}, Found: {:?} expected: {:?}", utterance.phrase.as_str(), entities, expected_value); 25 | } 26 | (None, None) => {}, 27 | (entity, utterance_value) => { 28 | assert!(false, "Found: {:?} expected: {:?}", entity, utterance_value); 29 | } 30 | } 31 | 32 | } 33 | } 34 | 35 | pub fn build_resources_path(lang: &str, file_name: &str) -> path::PathBuf { 36 | let path = env::var("SNIPS_RUSTLING_COVERAGE_RESOURCES") 37 | .map_err(|_| "SNIPS_RUSTLING_COVERAGE_RESOURCES env var not defined") 38 | .unwrap(); 39 | path::PathBuf::from(path) 40 | .join(lang) 41 | .join(file_name) 42 | } -------------------------------------------------------------------------------- /update_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | NEW_VERSION=$1 6 | 7 | if [ -z $NEW_VERSION ] 8 | then 9 | echo "Usage: $0 NEW_VERSION" 10 | exit 1 11 | fi 12 | 13 | perl -p -i -e "s/^version = \".*\"\$/version = \"$NEW_VERSION\"/g" Cargo.toml 14 | perl -p -i -e "s/^version = \".*\"\$/version = \"$NEW_VERSION\"/g" */Cargo.toml 15 | perl -p -i -e "s/^version = \".*\"\$/version = \"$NEW_VERSION\"/g" grammar/*/Cargo.toml 16 | -------------------------------------------------------------------------------- /values/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustling-ontology-values" 3 | version = "0.19.3" 4 | authors = ["Mathieu Poumeyrol "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4" 9 | failure = "0.1" 10 | regex = "1" 11 | rustling = { git = "https://github.com/snipsco/rustling", tag = "0.9.1" } 12 | rustling-ontology-moment = { path = "../moment" } 13 | -------------------------------------------------------------------------------- /values/src/check.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{ParsingContext, ResolverContext}; 2 | use crate::dimension::*; 3 | use crate::output::*; 4 | use moment::{Grain, Interval, Local, Moment, Period}; 5 | use rustling::{AttemptFrom, Check, ParsedNode}; 6 | 7 | #[derive(Debug)] 8 | pub struct CheckInteger { 9 | pub value: i64, 10 | } 11 | 12 | impl Check for CheckInteger { 13 | fn check(&self, pn: &ParsedNode) -> bool { 14 | IntegerValue::attempt_from(pn.value.clone()) 15 | .map(|v| v.value == self.value) 16 | .unwrap_or(false) 17 | } 18 | } 19 | 20 | pub fn check_integer(v: i64) -> CheckInteger { 21 | CheckInteger { value: v } 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct CheckOrdinal { 26 | pub value: i64, 27 | } 28 | 29 | impl Check for CheckOrdinal { 30 | fn check(&self, pn: &ParsedNode) -> bool { 31 | OrdinalValue::attempt_from(pn.value.clone()) 32 | .map(|v| v.value == self.value) 33 | .unwrap_or(false) 34 | } 35 | } 36 | 37 | pub fn check_ordinal(v: i64) -> CheckOrdinal { 38 | CheckOrdinal { value: v } 39 | } 40 | 41 | #[derive(Debug)] 42 | pub struct CheckFloat { 43 | pub value: f64, 44 | } 45 | 46 | impl Check for CheckFloat { 47 | fn check(&self, pn: &ParsedNode) -> bool { 48 | FloatValue::attempt_from(pn.value.clone()) 49 | .map(|v| v.value == self.value) 50 | .unwrap_or(false) 51 | } 52 | } 53 | 54 | pub fn check_float(v: f64) -> CheckFloat { 55 | CheckFloat { value: v } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct CheckDuration { 60 | pub period: Period, 61 | pub precision: Precision, 62 | } 63 | 64 | impl Check for CheckDuration { 65 | fn check(&self, pn: &ParsedNode) -> bool { 66 | DurationValue::attempt_from(pn.value.clone()) 67 | .map(|v| v.precision == self.precision && v.period == self.period) 68 | .unwrap_or(false) 69 | } 70 | } 71 | 72 | pub fn check_duration(period: Period, precision: Precision) -> CheckDuration { 73 | CheckDuration { period, precision } 74 | } 75 | 76 | #[derive(Debug)] 77 | pub struct CheckMoment { 78 | pub direction: Option, 79 | pub precision: Precision, 80 | pub interval: Interval, 81 | pub context: ResolverContext, 82 | } 83 | 84 | impl Check for CheckMoment { 85 | fn check(&self, pn: &ParsedNode) -> bool { 86 | match self.direction { 87 | None => self 88 | .context 89 | .resolve(&pn.value) 90 | .and_then(|v| DatetimeOutput::attempt_from(v)) 91 | .map(|v| { 92 | let check_value = 93 | v.moment == self.interval.start && v.grain == self.interval.grain; 94 | let check_precision = v.precision == self.precision; 95 | check_value && check_precision 96 | }) 97 | .unwrap_or(false), 98 | Some(Direction::After) => self 99 | .context 100 | .resolve(&pn.value) 101 | .and_then(|v| DatetimeIntervalOutput::attempt_from(v)) 102 | .map(|v| { 103 | if let DatetimeIntervalKind::After(m) = v.interval_kind { 104 | let check_value = 105 | m.moment == self.interval.start && m.grain == self.interval.grain; 106 | let check_precision = m.precision == self.precision; 107 | check_value && check_precision 108 | } else { 109 | false 110 | } 111 | }) 112 | .unwrap_or(false), 113 | Some(Direction::Before) => self 114 | .context 115 | .resolve(&pn.value) 116 | .and_then(|v| DatetimeIntervalOutput::attempt_from(v)) 117 | .map(|v| { 118 | if let DatetimeIntervalKind::Before(m) = v.interval_kind { 119 | let check_value = 120 | m.moment == self.interval.start && m.grain == self.interval.grain; 121 | let check_precision = m.precision == self.precision; 122 | check_value && check_precision 123 | } else { 124 | false 125 | } 126 | }) 127 | .unwrap_or(false), 128 | } 129 | } 130 | } 131 | 132 | pub fn check_moment( 133 | context: ResolverContext, 134 | moment: Moment, 135 | grain: Grain, 136 | precision: Precision, 137 | direction: Option, 138 | ) -> CheckMoment { 139 | CheckMoment { 140 | direction, 141 | precision, 142 | interval: Interval::starting_at(moment, grain), 143 | context, 144 | } 145 | } 146 | 147 | #[derive(Debug)] 148 | pub struct CheckMomentSpan { 149 | pub interval: Interval, 150 | pub precision: Precision, 151 | pub context: ResolverContext, 152 | } 153 | 154 | impl Check for CheckMomentSpan { 155 | fn check(&self, pn: &ParsedNode) -> bool { 156 | self.context 157 | .resolve(&pn.value) 158 | .and_then(|v| DatetimeIntervalOutput::attempt_from(v)) 159 | .map(|v| { 160 | if let DatetimeIntervalKind::Between { 161 | start, 162 | end, 163 | precision, 164 | .. 165 | } = v.interval_kind 166 | { 167 | start == self.interval.start 168 | && Some(end) == self.interval.end 169 | && precision == self.precision 170 | } else { 171 | false 172 | } 173 | }) 174 | .unwrap_or(false) 175 | } 176 | } 177 | 178 | pub fn check_moment_span( 179 | context: ResolverContext, 180 | precision: Precision, 181 | start: Moment, 182 | end: Moment, 183 | grain: Grain, 184 | ) -> CheckMomentSpan { 185 | CheckMomentSpan { 186 | interval: Interval::new(start, Some(end), grain), 187 | precision, 188 | context, 189 | } 190 | } 191 | 192 | #[derive(Debug)] 193 | pub struct CheckFinance { 194 | pub value: f64, 195 | pub unit: Option<&'static str>, 196 | pub precision: Precision, 197 | } 198 | 199 | impl Check for CheckFinance { 200 | fn check(&self, pn: &ParsedNode) -> bool { 201 | AmountOfMoneyValue::attempt_from(pn.value.clone()) 202 | .map(|v| v.value == self.value && v.precision == self.precision && v.unit == self.unit) 203 | .unwrap_or(false) 204 | } 205 | } 206 | 207 | pub fn check_finance(value: f64, unit: Option<&'static str>, precision: Precision) -> CheckFinance { 208 | CheckFinance { 209 | value, 210 | precision, 211 | unit, 212 | } 213 | } 214 | 215 | #[derive(Debug)] 216 | pub struct CheckPercentage { 217 | pub value: f64, 218 | } 219 | 220 | impl Check for CheckPercentage { 221 | fn check(&self, pn: &ParsedNode) -> bool { 222 | PercentageValue::attempt_from(pn.value.clone()) 223 | .map(|v| v.0 == self.value) 224 | .unwrap_or(false) 225 | } 226 | } 227 | 228 | pub fn check_percentage(value: f64) -> CheckPercentage { 229 | CheckPercentage { value } 230 | } 231 | 232 | #[derive(Debug)] 233 | pub struct CheckTemperature { 234 | pub value: f64, 235 | pub unit: Option<&'static str>, 236 | } 237 | 238 | impl Check for CheckTemperature { 239 | fn check(&self, pn: &ParsedNode) -> bool { 240 | TemperatureValue::attempt_from(pn.value.clone()) 241 | .map(|v| v.value == self.value && v.unit == self.unit) 242 | .unwrap_or(false) 243 | } 244 | } 245 | 246 | pub fn check_temperature(value: f64, unit: Option<&'static str>) -> CheckTemperature { 247 | CheckTemperature { value, unit } 248 | } 249 | -------------------------------------------------------------------------------- /values/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::dimension::*; 2 | use crate::output::*; 3 | use log::warn; 4 | use moment::*; 5 | use rustling::Value; 6 | 7 | pub trait ParsingContext { 8 | type O; 9 | fn resolve(&self, value: &V) -> Option; 10 | } 11 | 12 | pub struct IdentityContext { 13 | _phantom: ::std::marker::PhantomData, 14 | } 15 | 16 | impl IdentityContext { 17 | pub fn new() -> IdentityContext { 18 | IdentityContext { 19 | _phantom: ::std::marker::PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl ParsingContext for IdentityContext { 25 | type O = V; 26 | fn resolve(&self, value: &V) -> Option { 27 | Some(value.clone()) 28 | } 29 | } 30 | 31 | #[derive(Default, Debug, Copy, Clone)] 32 | pub struct ResolverContext { 33 | ctx: Context, 34 | } 35 | 36 | impl ResolverContext { 37 | pub fn from_secs(secs: i64) -> ResolverContext { 38 | let anchor = Interval::starting_at(Moment(Local.timestamp(secs, 0)), Grain::Second); 39 | ResolverContext::for_reference(anchor) 40 | } 41 | 42 | /// Returns a ResolverContext for the given interval. This API is working for 32bits and 64bits 43 | /// operating system by supporting dates only between 1970 and 2038 44 | pub fn for_reference(now: Interval) -> ResolverContext { 45 | ResolverContext { 46 | ctx: Context::for_reference(now), 47 | } 48 | } 49 | 50 | /// Returns a ResolverContext with the given intervals. No restrictions is applied. 51 | pub fn new(now: Interval, min: Interval, max: Interval) -> ResolverContext { 52 | ResolverContext { 53 | ctx: Context::new(now, min, max) 54 | } 55 | } 56 | } 57 | 58 | impl ParsingContext for ResolverContext { 59 | type O = Output; 60 | 61 | fn resolve(&self, dim: &Dimension) -> Option { 62 | match dim { 63 | &Dimension::Datetime(ref datetime_value) => { 64 | let mut walker = datetime_value 65 | .constraint 66 | .to_walker(&self.ctx.reference, &self.ctx); 67 | walker 68 | .forward 69 | .next() 70 | .and_then(|h| { 71 | if datetime_value.form.not_immediate().unwrap_or(false) 72 | && h.intersect(self.ctx.reference).is_some() 73 | { 74 | walker.forward.next() 75 | } else { 76 | Some(h) 77 | } 78 | }) 79 | .or_else(|| walker.backward.next()) 80 | .map(|interval| { 81 | if let Some(bounded_direction) = datetime_value.direction { 82 | let anchor = match bounded_direction.bound { 83 | Bound::Start => interval.start, 84 | Bound::End { only_interval } if only_interval => { 85 | interval.end.unwrap_or(interval.start) 86 | } 87 | Bound::End { .. } => interval.end_moment(), 88 | }; 89 | let datetime_output_value = DatetimeOutput { 90 | moment: anchor, 91 | grain: interval.grain, 92 | precision: datetime_value.precision, 93 | latent: datetime_value.latent, 94 | datetime_kind: datetime_value.datetime_kind, 95 | }; 96 | match bounded_direction.direction { 97 | Direction::After => { 98 | let datetime_interval_output_value = DatetimeIntervalOutput { 99 | interval_kind: DatetimeIntervalKind::After( 100 | datetime_output_value, 101 | ), 102 | datetime_kind: datetime_output_value.datetime_kind, 103 | }; 104 | Output::DatetimeInterval(datetime_interval_output_value) 105 | } 106 | Direction::Before => { 107 | let datetime_interval_output_value = DatetimeIntervalOutput { 108 | interval_kind: DatetimeIntervalKind::Before( 109 | datetime_output_value, 110 | ), 111 | datetime_kind: datetime_output_value.datetime_kind, 112 | }; 113 | Output::DatetimeInterval(datetime_interval_output_value) 114 | } 115 | } 116 | } else if let Some(end) = interval.end { 117 | if datetime_value.datetime_kind == DatetimeKind::Date 118 | || datetime_value.datetime_kind == DatetimeKind::Time 119 | { 120 | warn!( 121 | "{:?} kind with an interval - {:?}", 122 | datetime_value.datetime_kind, interval 123 | ); 124 | } 125 | let datetime_interval_output_value = DatetimeIntervalOutput { 126 | interval_kind: DatetimeIntervalKind::Between { 127 | start: interval.start, 128 | end: end, 129 | precision: datetime_value.precision, 130 | latent: datetime_value.latent, 131 | }, 132 | datetime_kind: datetime_value.datetime_kind, 133 | }; 134 | Output::DatetimeInterval(datetime_interval_output_value) 135 | } else { 136 | let datetime_output_value = DatetimeOutput { 137 | moment: interval.start, 138 | grain: interval.grain, 139 | precision: datetime_value.precision, 140 | latent: datetime_value.latent, 141 | datetime_kind: datetime_value.datetime_kind, 142 | }; 143 | Output::Datetime(datetime_output_value) 144 | } 145 | }) 146 | } 147 | &Dimension::Number(ref number) => match number { 148 | &NumberValue::Integer(ref v) => Some(Output::Integer(IntegerOutput(v.value))), 149 | &NumberValue::Float(ref v) => Some(Output::Float(FloatOutput(v.value))), 150 | }, 151 | &Dimension::Ordinal(ref ordinal) => Some(Output::Ordinal(OrdinalOutput(ordinal.value))), 152 | &Dimension::AmountOfMoney(ref aom) => { 153 | Some(Output::AmountOfMoney(AmountOfMoneyOutput { 154 | value: aom.value, 155 | precision: aom.precision, 156 | unit: aom.unit, 157 | })) 158 | } 159 | &Dimension::Temperature(ref temp) => Some(Output::Temperature(TemperatureOutput { 160 | value: temp.value, 161 | unit: temp.unit, 162 | latent: temp.latent, 163 | })), 164 | &Dimension::Duration(ref duration) => Some(Output::Duration(DurationOutput { 165 | period: duration.period.clone(), 166 | precision: duration.precision, 167 | })), 168 | &Dimension::Percentage(ref percentage) => { 169 | Some(Output::Percentage(PercentageOutput(percentage.0))) 170 | } 171 | _ => None, 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /values/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate failure; 3 | extern crate log; 4 | #[macro_use] 5 | extern crate rustling; 6 | extern crate rustling_ontology_moment as moment; 7 | 8 | extern crate regex; 9 | 10 | pub mod check; 11 | pub mod dimension; 12 | pub mod helpers; 13 | #[macro_use] 14 | pub mod macros_training; 15 | #[macro_use] 16 | pub mod macros_rules; 17 | pub mod output; 18 | pub mod context; 19 | 20 | pub use dimension::Dimension; 21 | pub use dimension::DimensionKind; 22 | pub use output::Output; 23 | pub use output::OutputKind; 24 | pub use context::{ResolverContext, ParsingContext, IdentityContext}; 25 | -------------------------------------------------------------------------------- /values/src/macros_rules.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_export] 3 | macro_rules! b { 4 | ($a:expr) => (Box::new($a)) 5 | } 6 | 7 | 8 | #[macro_export] 9 | macro_rules! integer_check_by_range { 10 | ($min:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|integer: &IntegerValue| integer.value >= $min)]) ); 11 | ($min:expr, $max:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|integer: &IntegerValue| integer.value >= $min && integer.value <= $max)]) ); 12 | ($min:expr, $max:expr, $predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|integer: &IntegerValue| integer.value >= $min && integer.value <= $max), b!($predicate)]) ); 13 | } 14 | 15 | 16 | #[macro_export] 17 | macro_rules! integer_check { 18 | () => ( ::rustling::core::AnyNodePattern::::new()); 19 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 20 | } 21 | 22 | 23 | #[macro_export] 24 | macro_rules! number_check { 25 | () => ( ::rustling::core::AnyNodePattern::::new() ); 26 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 27 | } 28 | 29 | 30 | #[macro_export] 31 | macro_rules! number_check_by_range { 32 | ($min:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|number: &NumberValue| number.value() >= $min)]) ); 33 | ($min:expr, $max:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|number: &NumberValue| number.value() >= $min && number.value() <= $max)]) ); 34 | } 35 | 36 | 37 | #[macro_export] 38 | macro_rules! ordinal_check { 39 | () => ( ::rustling::core::AnyNodePattern::::new() ); 40 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 41 | } 42 | 43 | 44 | #[macro_export] 45 | macro_rules! ordinal_check_by_range { 46 | ($min:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|ordinal: &OrdinalValue| ordinal.value >= $min)]) ); 47 | ($min:expr, $max:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|ordinal: &OrdinalValue| ordinal.value >= $min && ordinal.value <= $max)]) ); 48 | ($min:expr, $max:expr, $predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|ordinal: &OrdinalValue| ordinal.value >= $min && integer.value <= $max), b!($predicate)]) ); 49 | } 50 | 51 | 52 | #[macro_export] 53 | macro_rules! amount_of_money_check { 54 | () => ( ::rustling::core::AnyNodePattern::::new() ); 55 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 56 | } 57 | 58 | 59 | #[macro_export] 60 | macro_rules! money_unit { 61 | () => ( ::rustling::core::AnyNodePattern::::new() ); 62 | } 63 | 64 | 65 | #[macro_export] 66 | macro_rules! cycle_check { 67 | () => ( ::rustling::core::AnyNodePattern::::new() ); 68 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 69 | } 70 | 71 | 72 | #[macro_export] 73 | macro_rules! unit_of_duration_check { 74 | () => ( ::rustling::core::AnyNodePattern::::new() ); 75 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 76 | } 77 | 78 | 79 | #[macro_export] 80 | macro_rules! temperature_check { 81 | () => ( ::rustling::core::AnyNodePattern::::new() ); 82 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 83 | } 84 | 85 | 86 | #[macro_export] 87 | macro_rules! datetime_check { 88 | () => ( ::rustling::core::AnyNodePattern::::new() ); 89 | ($($predicate:expr),*) => ( ::rustling::core::FilterNodePattern::::filter(vec![ $( b!($predicate) ),*]) ); 90 | } 91 | 92 | #[macro_export] 93 | macro_rules! datetime_check_exclude_too_ambiguous { 94 | () => ( ::rustling::core::FilterNodePattern::::filter(vec![b!(|datetime: &DatetimeValue| !datetime.is_too_ambiguous())]) ); 95 | } 96 | 97 | #[macro_export] 98 | macro_rules! time_of_day_check_hour { 99 | ($min:expr, $max:expr) => ( 100 | #[allow(unused_comparisons)] 101 | { 102 | ::rustling::core::FilterNodePattern::::filter(vec![b!(|datetime: &DatetimeValue| { 103 | if let ::rustling_ontology_values::dimension::Form::TimeOfDay(ref tod) = datetime.form { 104 | $min <= tod.full_hour() && tod.full_hour() <= $max 105 | } else { 106 | false 107 | } 108 | })]) 109 | }); 110 | ($min_1:expr, $max_1:expr, $min_2:expr, $max_2:expr) => ( 111 | #[allow(unused_comparisons)] 112 | { 113 | ::rustling::core::FilterNodePattern::::filter(vec![b!(|datetime: &DatetimeValue| { 114 | if let ::rustling_ontology_values::dimension::Form::TimeOfDay(ref tod) = datetime.form { 115 | ($min_1 <= tod.full_hour() && tod.full_hour() <= $max_1) || ($min_2 <= tod.full_hour() && tod.full_hour() <= $max_2) 116 | } else { 117 | false 118 | } 119 | })]) 120 | }); 121 | } 122 | 123 | 124 | #[macro_export] 125 | macro_rules! duration_check { 126 | () => ( ::rustling::core::AnyNodePattern::::new() ); 127 | ($predicate:expr) => ( ::rustling::core::FilterNodePattern::::filter(vec![b!($predicate)]) ); 128 | } 129 | 130 | 131 | #[macro_export] 132 | macro_rules! relative_minute_check { 133 | () => ( ::rustling::core::AnyNodePattern::::new() ); 134 | } 135 | 136 | 137 | #[macro_export] 138 | macro_rules! form { 139 | ($form:pat) => (|datetime: &DatetimeValue| if let $form = datetime.form { true } else { false }) 140 | } 141 | 142 | 143 | #[macro_export] 144 | macro_rules! excluding_form { 145 | ($form:pat) => (|datetime: &DatetimeValue| if let $form = datetime.form { false } else { true }) 146 | } 147 | 148 | #[macro_export] 149 | macro_rules! excluding_too_ambiguous { 150 | () => (|datetime: &DatetimeValue| !datetime.is_too_ambiguous()) 151 | } 152 | 153 | #[macro_export] 154 | macro_rules! excluding_latent { 155 | () => (|datetime: &DatetimeValue| !datetime.latent) 156 | } 157 | -------------------------------------------------------------------------------- /values/src/macros_training.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! example { 3 | ($v:expr, $check:expr, $($ex:expr),*) => { 4 | $( $v.push(::rustling::Example::new($ex, Box::new($check))); )* 5 | }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! check_finance { 10 | ($value:expr) => (check_finance($value, None, Precision::Exact)); 11 | ($value:expr, $unit:expr) => (check_finance($value, $unit, Precision::Exact)); 12 | ($value:expr, $unit:expr, $precision:expr) => (check_finance($value, $unit, $precision)); 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! check_duration { 17 | ([$($item:expr),*]) => ( ::rustling_ontology_values::check::check_duration(period!($( $item ),*), Precision::Exact) ); 18 | ([$($item:expr),*], $precision:expr) => ( ::rustling_ontology_values::check::check_duration(period!($( $item ),*), $precision) ); 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! check_moment { 23 | ($context:expr, [$($item:expr),*]) => ( ::rustling_ontology_values::check::check_moment($context, moment!($( $item ),*), grain!($( $item ),*), Precision::Exact, None) ); 24 | ($context:expr, [$($item:expr),*], $grain:expr) => ( ::rustling_ontology_values::check::check_moment($context, moment!($( $item ),*), $grain, Precision::Exact, None) ); 25 | } 26 | 27 | #[macro_export] 28 | macro_rules! check_moment_with_precision { 29 | ($context:expr, [$($item:expr),*], $precision:expr) => ( ::rustling_ontology_values::check::check_moment($context, moment!($( $item ),*), grain!($( $item ),*), $precision, None) ); 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! check_moment_with_direction { 34 | ($context:expr, [$($item:expr),*], $direction:expr) => ( ::rustling_ontology_values::check::check_moment($context, moment!($( $item ),*), grain!($( $item ),*), Precision::Exact, Some($direction)) ); 35 | ($context:expr, [$($item:expr),*], $direction:expr, $grain:expr) => ( ::rustling_ontology_values::check::check_moment($context, moment!($( $item ),*), $grain, Precision::Exact, Some($direction)) ); 36 | 37 | } 38 | 39 | #[macro_export] 40 | macro_rules! check_moment_span { 41 | ($context:expr, [$($item1:expr),*], [$($item2:expr),*]) => ( ::rustling_ontology_values::check::check_moment_span($context, Precision::Exact, moment!($( $item1 ),*), moment!($( $item2 ),*), grain!($( $item1 ),*)) ); 42 | ($context:expr, [$($item1:expr),*], [$($item2:expr),*], $grain:expr) => ( ::rustling_ontology_values::check::check_moment_span($context, Precision::Exact, moment!($( $item1 ),*), moment!($( $item2 ),*), $grain) ); 43 | } 44 | 45 | #[macro_export] 46 | macro_rules! check_moment_span_with_precision { 47 | ($context:expr, [$($item1:expr),*], [$($item2:expr),*], $precision:expr) => ( ::rustling_ontology_values::check::check_moment_span($context, $precision, moment!($( $item1 ),*), moment!($( $item2 ),*), grain!($( $item1 ),*)) ); 48 | ($context:expr, [$($item1:expr),*], [$($item2:expr),*], $precision:expr, $grain:expr) => ( ::rustling_ontology_values::check::check_moment_span($context, $precision, moment!($( $item1 ),*), moment!($( $item2 ),*), $grain) ); 49 | } 50 | 51 | #[macro_export] 52 | macro_rules! moment { 53 | ($y:expr) => ( Moment(Local.ymd($y, 1, 1).and_hms(0, 0, 0))); 54 | ($y:expr, $m:expr) => ( Moment(Local.ymd($y, $m, 1).and_hms(0, 0, 0)) ); 55 | ($y:expr, $m:expr, $d:expr) => ( Moment(Local.ymd($y, $m, $d).and_hms(0, 0, 0)) ); 56 | ($y:expr, $m:expr, $d:expr, $h:expr) => ( Moment(Local.ymd($y, $m, $d).and_hms($h, 0, 0)) ); 57 | ($y:expr, $m:expr, $d:expr, $h:expr, $min:expr) => ( Moment(Local.ymd($y, $m, $d).and_hms($h, $min, 0)) ); 58 | ($y:expr, $m:expr, $d:expr, $h:expr, $min:expr, $sec:expr) => ( Moment(Local.ymd($y, $m, $d).and_hms($h, $min, $sec)) ); 59 | } 60 | 61 | #[macro_export] 62 | macro_rules! period { 63 | ($y:expr) => ( year_period!($y) ); 64 | ($y:expr, $m:expr) => ( year_period!($y) + month_period!($m) ); 65 | ($y:expr, $m:expr, $w:expr) => ( year_period!($y) + month_period!($m) + week_period!($w) ); 66 | ($y:expr, $m:expr, $w:expr, $d:expr) => ( year_period!($y) + month_period!($m) + week_period!($w) + day_period!($d) ); 67 | ($y:expr, $m:expr, $w:expr, $d:expr, $h:expr) => ( year_period!($y) + month_period!($m) + week_period!($w) + day_period!($d) + hour_period!($h) ); 68 | ($y:expr, $m:expr, $w:expr, $d:expr, $h:expr, $min:expr) => ( year_period!($y) + month_period!($m) + week_period!($w) + day_period!($d) + hour_period!($h) + minute_period!($min) ); 69 | ($y:expr, $m:expr, $w:expr, $d:expr, $h:expr, $min:expr, $sec:expr) => ( year_period!($y) + month_period!($m) + week_period!($w) + day_period!($d) + hour_period!($h) + minute_period!($min) + second_period!($sec) ); 70 | } 71 | 72 | #[macro_export] 73 | macro_rules! year_period { 74 | ($y:expr) => ( Period::from(PeriodComp::new(Grain::Year, $y)) ); 75 | } 76 | 77 | #[macro_export] 78 | macro_rules! month_period { 79 | ($m:expr) => ( Period::from(PeriodComp::new(Grain::Month, $m)) ); 80 | } 81 | 82 | #[macro_export] 83 | macro_rules! week_period { 84 | ($w:expr) => ( Period::from(PeriodComp::new(Grain::Week, $w)) ); 85 | } 86 | 87 | #[macro_export] 88 | macro_rules! day_period { 89 | ($d:expr) => ( Period::from(PeriodComp::new(Grain::Day, $d)) ); 90 | } 91 | 92 | #[macro_export] 93 | macro_rules! hour_period { 94 | ($h:expr) => ( Period::from(PeriodComp::new(Grain::Hour, $h)) ); 95 | } 96 | 97 | #[macro_export] 98 | macro_rules! minute_period { 99 | ($min:expr) => ( Period::from(PeriodComp::new(Grain::Minute, $min)) ); 100 | } 101 | 102 | #[macro_export] 103 | macro_rules! second_period { 104 | ($sec:expr) => ( Period::from(PeriodComp::new(Grain::Second, $sec)) ); 105 | } 106 | 107 | #[macro_export] 108 | macro_rules! grain { 109 | ($y:expr) => (Grain::Year); 110 | ($y:expr, $m:expr) => (Grain::Month); 111 | ($y:expr, $m:expr, $d:expr) => (Grain::Day); 112 | ($y:expr, $m:expr, $d:expr, $h:expr) => (Grain::Hour); 113 | ($y:expr, $m:expr, $d:expr, $h:expr, $min:expr) => (Grain::Minute); 114 | ($y:expr, $m:expr, $d:expr, $h:expr, $min:expr, $sec:expr) => (Grain::Second); 115 | } 116 | 117 | #[macro_export] 118 | macro_rules! enum_kind { 119 | ($kindname:ident, [$($varname:ident),*]) => { 120 | #[derive(Debug,Copy,Clone,PartialEq, Hash, Eq)] 121 | pub enum $kindname { 122 | $( $varname ),* 123 | } 124 | 125 | impl $kindname { 126 | pub fn all() -> Vec<$kindname> { 127 | vec![ 128 | $( $kindname::$varname ),* 129 | ] 130 | } 131 | } 132 | 133 | impl ::std::str::FromStr for $kindname { 134 | type Err=String; 135 | fn from_str(s: &str) -> ::std::result::Result<$kindname, Self::Err> { 136 | match s { 137 | $( 138 | stringify!($varname) => Ok($kindname::$varname), 139 | )* 140 | _ => Err(format!("{} is not a known {}", s, stringify!($kindname))) 141 | } 142 | } 143 | } 144 | 145 | impl ::std::string::ToString for $kindname { 146 | fn to_string(&self) -> String { 147 | match self { 148 | $( 149 | &$kindname::$varname => stringify!($varname).to_string(), 150 | )* 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /values/src/output.rs: -------------------------------------------------------------------------------- 1 | use crate::dimension::*; 2 | use moment::*; 3 | use rustling::Value; 4 | 5 | #[derive(Clone, PartialEq, Debug)] 6 | pub enum Output { 7 | Integer(IntegerOutput), 8 | Float(FloatOutput), 9 | Percentage(PercentageOutput), 10 | Ordinal(OrdinalOutput), 11 | Datetime(DatetimeOutput), 12 | DatetimeInterval(DatetimeIntervalOutput), 13 | AmountOfMoney(AmountOfMoneyOutput), 14 | Temperature(TemperatureOutput), 15 | Duration(DurationOutput), 16 | } 17 | 18 | impl Output { 19 | pub fn kind(&self) -> OutputKind { 20 | match self { 21 | &Output::Integer(_) => OutputKind::Number, 22 | &Output::Float(_) => OutputKind::Number, 23 | &Output::Ordinal(_) => OutputKind::Ordinal, 24 | Output::Datetime(datetime_output_value) => { 25 | match datetime_output_value.datetime_kind { 26 | // Only Date and Time should occur here 27 | DatetimeKind::Date => OutputKind::Date, 28 | DatetimeKind::Time => OutputKind::Time, 29 | DatetimeKind::DatePeriod => OutputKind::DatePeriod, 30 | DatetimeKind::TimePeriod => OutputKind::TimePeriod, 31 | _ => OutputKind::Datetime, 32 | } 33 | } 34 | Output::DatetimeInterval(datetime_interval_output_value) => { 35 | match datetime_interval_output_value.datetime_kind { 36 | // Only DatePeriod and TimePeriod should occur here 37 | DatetimeKind::Date => OutputKind::Date, 38 | DatetimeKind::Time => OutputKind::Time, 39 | DatetimeKind::DatePeriod => OutputKind::DatePeriod, 40 | DatetimeKind::TimePeriod => OutputKind::TimePeriod, 41 | _ => OutputKind::Datetime, 42 | } 43 | } 44 | &Output::AmountOfMoney(_) => OutputKind::AmountOfMoney, 45 | &Output::Temperature(_) => OutputKind::Temperature, 46 | &Output::Duration(_) => OutputKind::Duration, 47 | &Output::Percentage(_) => OutputKind::Percentage, 48 | } 49 | } 50 | } 51 | 52 | enum_kind!( 53 | OutputKind, 54 | [ 55 | Number, 56 | Ordinal, 57 | Date, 58 | Time, 59 | DatePeriod, 60 | TimePeriod, 61 | Datetime, 62 | Duration, 63 | AmountOfMoney, 64 | Temperature, 65 | Percentage 66 | ] 67 | ); 68 | 69 | impl OutputKind { 70 | pub fn to_dim(&self) -> DimensionKind { 71 | match self { 72 | &OutputKind::Number => DimensionKind::Number, 73 | &OutputKind::Ordinal => DimensionKind::Ordinal, 74 | &OutputKind::Datetime => DimensionKind::Datetime, 75 | &OutputKind::Date => DimensionKind::Datetime, 76 | &OutputKind::Time => DimensionKind::Datetime, 77 | &OutputKind::DatePeriod => DimensionKind::Datetime, 78 | &OutputKind::TimePeriod => DimensionKind::Datetime, 79 | &OutputKind::AmountOfMoney => DimensionKind::AmountOfMoney, 80 | &OutputKind::Temperature => DimensionKind::Temperature, 81 | &OutputKind::Duration => DimensionKind::Duration, 82 | &OutputKind::Percentage => DimensionKind::Percentage, 83 | } 84 | } 85 | 86 | pub fn match_dim(&self, dimension_value: &Dimension) -> bool { 87 | match dimension_value { 88 | Dimension::Datetime(datetime_value) => { 89 | match self { 90 | OutputKind::Date => DatetimeKind::Date == datetime_value.datetime_kind, 91 | OutputKind::Time => DatetimeKind::Time == datetime_value.datetime_kind, 92 | OutputKind::DatePeriod => { 93 | DatetimeKind::DatePeriod == datetime_value.datetime_kind 94 | } 95 | OutputKind::TimePeriod => { 96 | DatetimeKind::TimePeriod == datetime_value.datetime_kind 97 | } 98 | // If the dimension is datetime and none of the 4 subtypes, then it's the 99 | // complement subtype, hence Datetime 100 | // This works if the arm matching hasn't matched something first 101 | OutputKind::Datetime => true, 102 | _ => false, 103 | } 104 | } 105 | _ => self.to_dim() == dimension_value.kind(), 106 | } 107 | } 108 | } 109 | 110 | #[derive(Clone, Copy, PartialEq, Debug)] 111 | pub struct IntegerOutput(pub i64); 112 | 113 | #[derive(Clone, Copy, PartialEq, Debug)] 114 | pub struct FloatOutput(pub f64); 115 | 116 | #[derive(Clone, Copy, PartialEq, Debug)] 117 | pub struct PercentageOutput(pub f64); 118 | 119 | #[derive(Clone, Copy, PartialEq, Debug)] 120 | pub struct OrdinalOutput(pub i64); 121 | 122 | #[derive(Clone, Copy, PartialEq, Debug)] 123 | pub struct DatetimeOutput { 124 | pub moment: Moment, 125 | pub grain: Grain, 126 | pub precision: Precision, 127 | pub latent: bool, 128 | pub datetime_kind: DatetimeKind, 129 | } 130 | 131 | impl DatetimeOutput { 132 | pub fn datetime_kind(self, datetime_kind: DatetimeKind) -> DatetimeOutput { 133 | DatetimeOutput { 134 | datetime_kind, 135 | ..self 136 | } 137 | } 138 | } 139 | 140 | #[derive(Clone, Copy, PartialEq, Debug)] 141 | pub struct DatetimeIntervalOutput { 142 | pub interval_kind: DatetimeIntervalKind, 143 | pub datetime_kind: DatetimeKind, 144 | } 145 | 146 | #[derive(Clone, Copy, PartialEq, Debug)] 147 | pub enum DatetimeIntervalKind { 148 | After(DatetimeOutput), 149 | Before(DatetimeOutput), 150 | Between { 151 | start: Moment, 152 | end: Moment, 153 | precision: Precision, 154 | latent: bool, 155 | }, 156 | } 157 | 158 | #[derive(Clone, Copy, PartialEq, Debug)] 159 | pub struct AmountOfMoneyOutput { 160 | pub value: f64, 161 | pub precision: Precision, 162 | pub unit: Option<&'static str>, 163 | } 164 | 165 | #[derive(Clone, Copy, PartialEq, Debug)] 166 | pub struct TemperatureOutput { 167 | pub value: f64, 168 | pub unit: Option<&'static str>, 169 | pub latent: bool, 170 | } 171 | 172 | #[derive(Clone, PartialEq, Debug)] 173 | pub struct DurationOutput { 174 | pub period: Period, 175 | pub precision: Precision, 176 | } 177 | 178 | variant_converters!(Output, Integer, IntegerOutput); 179 | variant_converters!(Output, Float, FloatOutput); 180 | variant_converters!(Output, Percentage, PercentageOutput); 181 | variant_converters!(Output, Ordinal, OrdinalOutput); 182 | variant_converters!(Output, Datetime, DatetimeOutput); 183 | variant_converters!(Output, DatetimeInterval, DatetimeIntervalOutput); 184 | variant_converters!(Output, AmountOfMoney, AmountOfMoneyOutput); 185 | variant_converters!(Output, Temperature, TemperatureOutput); 186 | variant_converters!(Output, Duration, DurationOutput); 187 | --------------------------------------------------------------------------------