Begin forking with new features
This commit is contained in:
parent
bcc6a07879
commit
44170697cb
316
.github/workflows/ci.yml
vendored
316
.github/workflows/ci.yml
vendored
@ -1,316 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
precheck_default:
|
||||
name: Check default
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust: [stable]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all --bins --examples --lib
|
||||
|
||||
precheck_all_features:
|
||||
name: Check all features
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust: [stable]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --all --bins --examples --tests --lib
|
||||
|
||||
check_msrv:
|
||||
needs: [precheck_default, precheck_all_features]
|
||||
name: Check MSRV with all features
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.65.0
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --all --bins --examples --tests --lib
|
||||
|
||||
check_benches:
|
||||
needs: [precheck_default, precheck_all_features]
|
||||
name: Check benches with all features
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
rust: [ nightly ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --all --bins --benches --examples --tests --lib
|
||||
|
||||
check_sync_readme:
|
||||
needs: [precheck_default, precheck_all_features]
|
||||
name: Check sync readme
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
rust: [ stable ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Install cargo-sync-readme
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-sync-readme
|
||||
|
||||
- name: Sync readme check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: sync-readme
|
||||
args: --check
|
||||
|
||||
check_platform_compatibility:
|
||||
needs: [precheck_default, precheck_all_features]
|
||||
name: Check platform compatibility
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
rust: [stable]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --all --bins --examples --tests --lib
|
||||
|
||||
check_cli:
|
||||
needs: [ precheck_default, precheck_all_features ]
|
||||
name: Check CLI
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
rust: [ stable ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: CLI
|
||||
uses: GuillaumeFalourd/assert-command-line-output@v2
|
||||
with:
|
||||
command_line: cargo run -- p = 2\; p + 3
|
||||
contains: 5
|
||||
expected_result: PASSED
|
||||
|
||||
detailed_tests:
|
||||
needs: [precheck_default, precheck_all_features]
|
||||
name: Check, test, doc, format and lint with all features
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust: [stable, beta, nightly]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
components: rustfmt, clippy
|
||||
override: true
|
||||
|
||||
- name: Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.os }}
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --all --bins --examples --tests --lib
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features --all
|
||||
|
||||
- name: Docs
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --all-features
|
||||
|
||||
- name: Format
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
|
||||
- name: Lint
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-features --bins --examples --tests --lib
|
||||
|
||||
coveralls_io:
|
||||
needs: [detailed_tests]
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: ⚡ Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
default: true
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
default: false
|
||||
|
||||
- name: Install cargo-tarpaulin
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-tarpaulin
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
|
||||
- name: Coverage Report with tarpaulin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: tarpaulin
|
||||
args: --all-features --out Lcov --run-types Tests Doctests -- --test-threads 1
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./lcov.info
|
535
CHANGELOG.md
535
CHANGELOG.md
@ -1,535 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Notes
|
||||
|
||||
### Added
|
||||
|
||||
* Methods to mutably iterate over operators (`Node::iter_operators_mut`) and all types of identifiers (`Node::iter_identifiers_mut`, `Node::iter_*_identifiers_mut`) (#136)
|
||||
|
||||
### Removed
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Ben Weinstein-Raun](https://github.com/benwr)
|
||||
|
||||
## [11.0.0](https://github.com/ISibboI/evalexpr/compare/10.0.0...11.0.0) - 2023-06-01
|
||||
|
||||
### Notes
|
||||
|
||||
* Due to the introduction of [GATs](https://blog.rust-lang.org/2022/10/28/gats-stabilization.html), the MSRV changes to `1.65.0` in this update.
|
||||
|
||||
### Added
|
||||
|
||||
* Builtin function `math::abs` (#130)
|
||||
* A CLI for evaluating expressions. Install via `cargo install evalexpr`. (#133)
|
||||
* The ability to parse integers from hex literals (#131)
|
||||
|
||||
### Changed
|
||||
|
||||
* The `IterVariablesContext` from using a lifetime parameter to GATs (#135)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Heki](https://github.com/LinuxHeki)
|
||||
* [Kaspar Schleiser](https://github.com/kaspar030)
|
||||
|
||||
## [10.0.0](https://github.com/ISibboI/evalexpr/compare/9.1.0...10.0.0) - 2023-05-21
|
||||
|
||||
### Added
|
||||
|
||||
* Builtin functions can now be disabled (#129)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [hexofyore](https://github.com/hexofyore)
|
||||
|
||||
## [9.1.0](https://github.com/ISibboI/evalexpr/compare/9.0.0...9.1.0) - 2023-05-16
|
||||
|
||||
### Added
|
||||
|
||||
* Builtin functions `contains` and `contains_any` (#127)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [nickisyourfan](https://github.com/nickisyourfan)
|
||||
|
||||
## [9.0.0](https://github.com/ISibboI/evalexpr/compare/8.2.0...9.0.0) - 2023-04-13
|
||||
|
||||
### Fixed
|
||||
|
||||
* Taking numbers to negative powers gave unexpected results (#120)
|
||||
* **Update MSRV to 1.56.1 (2021 edition release)** to allow builds with the latest versions of all dependencies.
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Pham Nhat Huy](https://github.com/012e)
|
||||
|
||||
## [8.2.0](https://github.com/ISibboI/evalexpr/compare/8.1.0...8.2.0) - 2023-04-13
|
||||
|
||||
### Added
|
||||
|
||||
* `EvalExprError` now derives `Clone` (#116)
|
||||
|
||||
### Changed
|
||||
|
||||
* Occurrences of `f64` and `i64` have been replaced with the type aliases `FloatType` and `IntType` where applicable (#113)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Natan Freeman](https://github.com/NatanFreeman)
|
||||
* [Claus Matzinger](https://github.com/celaus)
|
||||
|
||||
## [8.1.0](https://github.com/ISibboI/evalexpr/compare/8.0.0...8.1.0) - 2022-07-22
|
||||
|
||||
### Added
|
||||
|
||||
* Get all variables or variable/value pairs in a context via a new trait `IterateVariablesContext` (#108)
|
||||
* Functions `iter_(read/write)_variable_identifiers`, which iterate over all `ReadVariableIdentifier`s or all `WriteVariableIdentifier`s in an operator tree (#110)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [egel557](https://github.com/egel557)
|
||||
* [Tobias Schmitt](https://github.com/tsmt09)
|
||||
|
||||
## [8.0.0](https://github.com/ISibboI/evalexpr/compare/7.2.0...8.0.0) - 2022-07-06
|
||||
|
||||
### Added
|
||||
|
||||
* Builtin functions to check for nan, infinity and subnormality in floats (#101)
|
||||
* Builtin random function (#102)
|
||||
* Implement `TryFrom<Value>` for all types a value can hold (#105)
|
||||
* Split VariableIdentifier node into read and write variants (#106)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Ophir LOJKINE](https://github.com/lovasoa)
|
||||
* [Joe Grund](https://github.com/jgrund)
|
||||
* [Luka Maljic](https://github.com/malj)
|
||||
|
||||
## [7.2.0](https://github.com/ISibboI/evalexpr/compare/7.1.1...7.2.0) - 2022-03-16
|
||||
|
||||
### Added
|
||||
|
||||
* The builtin function `if`, which mimics the if-else construct existing in many programming languages.
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to:
|
||||
|
||||
* [Ophir LOJKINE](https://github.com/lovasoa)
|
||||
|
||||
## [7.1.1](https://github.com/ISibboI/evalexpr/compare/7.1.0...7.1.1) - 2022-03-14
|
||||
|
||||
### Fixed
|
||||
|
||||
* Set regex minimum version to `1.5.5`, as the previous versions contains a security vulnerability.
|
||||
See https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw?pli=1.
|
||||
This vulnerability does not affect this crate as of now, but if we ever allow passing parameters to the regex engine, it might.
|
||||
|
||||
## [7.1.0](https://github.com/ISibboI/evalexpr/compare/7.0.1...7.1.0) - 2022-03-13
|
||||
|
||||
### Added
|
||||
|
||||
* Bit shift functions `shl` and `shr`, same as Rust's shift functions on `i64`.
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Diane Sparks](https://github.com/FractalDiane)
|
||||
|
||||
## [7.0.1](https://github.com/ISibboI/evalexpr/compare/7.0.0...7.0.1) - 2022-02-20
|
||||
|
||||
### Changed
|
||||
|
||||
* Updated the optional dependencies, and fixed them to a minimum tested version.
|
||||
For simplicity, I fixed them to the newest version, but since I export none of them, this is luckily not breaking.
|
||||
* Updated the dev-dependencies.
|
||||
|
||||
## [7.0.0](https://github.com/ISibboI/evalexpr/compare/6.6.0...7.0.0) - 2022-01-13
|
||||
|
||||
### Changed
|
||||
|
||||
* Made the `EvalexprError` enum `non_exhaustive`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Expressions that have dangling parenthese expressions such as `4(5)` now produce an error.
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [dbr/Ben](https://github.com/dbr)
|
||||
|
||||
## [6.6.0](https://github.com/ISibboI/evalexpr/compare/6.5.0...6.6.0) - 2021-10-13
|
||||
|
||||
### Added
|
||||
|
||||
* Bitwise operators as builtin functions `bitand`, `bitor`, `bitxor`, `bitnot` (#88)
|
||||
* Public immutable and mutable accessor functions to the operator and children of a Node.
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Michał Hanusek](https://github.com/hanusek)
|
||||
* [Kai Giebeler](https://github.com/kawogi)
|
||||
|
||||
## [6.5.0](https://github.com/ISibboI/evalexpr/compare/6.4.0...6.5.0) - 2021-08-16
|
||||
|
||||
### Added
|
||||
|
||||
* Make `Function::new` able to accept closures (thanks to Jakub Dąbek)
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Jakub Dąbek](https://github.com/jakubdabek)
|
||||
* [LonnonjamesD](https://github.com/LonnonjamesD)
|
||||
|
||||
## [6.4.0](https://github.com/ISibboI/evalexpr/compare/6.3.0...6.4.0) - 2021-07-21
|
||||
|
||||
### Notes
|
||||
|
||||
* Minimum supported Rust version (MSRV) increased to `1.46.0`
|
||||
* Increased test coverage by adding more test and ignoring untestable files
|
||||
|
||||
### Added
|
||||
|
||||
* Allow scientific notation in float literals
|
||||
|
||||
### Changed
|
||||
|
||||
* Made some functions `const`. This increased the MSRV
|
||||
|
||||
### Fixed
|
||||
|
||||
* `eval_number` methods returned `EvalexprError::ExpectedFloat` before, now they correctly return `EvalexprError::ExpectedNumber`
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Dennis Marttinen](https://github.com/twelho)
|
||||
|
||||
## [6.3.0](https://github.com/ISibboI/evalexpr/compare/6.2.1...6.3.0) - 2021-07-06
|
||||
|
||||
### Added
|
||||
|
||||
* Implement more builtin math methods
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Magnus Ulimoen](https://github.com/mulimoen)
|
||||
|
||||
## [6.2.0](https://github.com/ISibboI/evalexpr/compare/6.1.1...6.2.0) - 2021-06-24
|
||||
|
||||
### Notes
|
||||
|
||||
* Increased test coverage
|
||||
|
||||
### Added
|
||||
|
||||
* Implemented `Clone` for `HashMapContext`
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Magnus Ulimoen](https://github.com/mulimoen)
|
||||
|
||||
## [6.1.1](https://github.com/ISibboI/evalexpr/compare/6.1.0...6.1.1) - 2021-06-22
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improved syntax of documentation
|
||||
|
||||
## [6.1.0](https://github.com/ISibboI/evalexpr/compare/6.0.0...6.1.0) - 2021-06-02
|
||||
|
||||
### Added
|
||||
|
||||
* Macro `math_consts_context` adding all of Rust's `f64` constants
|
||||
* All common math functions implemented by Rust's `f64` are now builtin
|
||||
* Continuous integration and test coverage report
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [Edwin](https://github.com/olback)
|
||||
|
||||
## [6.0.0](https://github.com/ISibboI/evalexpr/compare/5.1.0...6.0.0) - 2021-05-28
|
||||
|
||||
### Added
|
||||
|
||||
* `#![forbid(unsafe_code)]`
|
||||
* Made `Function` derive `Clone`
|
||||
* Ensure that `Function` implements `Send` and `Sync`
|
||||
|
||||
### Removed
|
||||
|
||||
* `Cargo.lock`
|
||||
|
||||
### Changed
|
||||
|
||||
* Decomposed `Context` into `Context`, `ContextWithMutableVariables` and `ContextWithMutableFunctions`
|
||||
* Replaced the `get_function` method of `Context` with a `call_function` method
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [dvtomas](https://github.com/dvtomas)
|
||||
|
||||
## [5.1.0](https://github.com/ISibboI/evalexpr/compare/5.0.5...5.1.0) - 2021-05-28
|
||||
|
||||
### Added
|
||||
|
||||
* Make `Node` cloneable
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [dvtomas](https://github.com/dvtomas)
|
||||
|
||||
## [5.0.5](https://github.com/ISibboI/evalexpr/compare/5.0.4...5.0.5) - 2019-09-13
|
||||
|
||||
### Fixed
|
||||
|
||||
* is-it-maintained badges had wrong repository definitions
|
||||
* maintenance status was given wrongly
|
||||
* move maintenance status to top
|
||||
|
||||
## [5.0.4](https://github.com/ISibboI/evalexpr/compare/5.0.3...5.0.4) - 2019-09-13
|
||||
|
||||
### Added
|
||||
|
||||
* maintenance badge
|
||||
* is-it-maintained badges
|
||||
|
||||
## [5.0.3](https://github.com/ISibboI/evalexpr/compare/5.0.2...5.0.3) - 2019-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
* The `!=` operator was wrongfully parsed as Token::Eq
|
||||
|
||||
### Contributors
|
||||
|
||||
* [slientgoat](https://github.com/slientgoat)
|
||||
|
||||
## [5.0.2](https://github.com/ISibboI/evalexpr/compare/5.0.1...5.0.2) - 2019-08-30
|
||||
|
||||
### Changed
|
||||
|
||||
* Removed target.bench.dev-dependencies completely, as they can be just listed under the normal dev-dependencies
|
||||
|
||||
## [5.0.1](https://github.com/ISibboI/evalexpr/compare/5.0.0...5.0.1) - 2019-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
* Bench dependencies are now dev-dependencies so they are not listed on crates.io as normal dependencies anymore
|
||||
|
||||
## [5.0.0](https://github.com/ISibboI/evalexpr/compare/4.1.0...5.0.0) - *'Sanity'* - 2019-08-30
|
||||
|
||||
### Notes
|
||||
|
||||
Finally, 'Sanity' has been released, including a huge bunch of new features.
|
||||
Notably, and providing a reason for the name of this release, function call and tuple semantics have improved a lot.
|
||||
Functions now always take exactly one argument, but this can then be a tuple.
|
||||
It is now possible to construct tuples of tuples, such that mode complex values can be constructed.
|
||||
As of now there is no way to deconstruct them though.
|
||||
|
||||
A lot has been done on string processing, special thanks for that goes to [bittrance](https://github.com/bittrance).
|
||||
Specifically, under the feature flag `regex_support` two regex functions for strings are hiding now.
|
||||
Also, the operators `+` and comparison operators have been fitted to support strings.
|
||||
|
||||
Thanks to [lovasoa](https://github.com/lovasoa), we now have a nice macro for context creation.
|
||||
|
||||
Thanks to [Atul9](https://github.com/Atul9), the crate is now Rust 2018 compliant.
|
||||
|
||||
Thanks to [mestachs'](https://github.com/mestachs) request, we now have functions to iterate over identifiers within an expression.
|
||||
|
||||
Internally, the structure of the operator tree changed from being `&dyn`-based to being `enum`-based.
|
||||
Also, we have benchmarks now to observe performance changes in future releases.
|
||||
|
||||
### Added
|
||||
|
||||
* Iterator over all identifiers within an expression, including duplicates
|
||||
* Iterators over only variable or only function identifiers within an expression, including duplicates
|
||||
* Overload the `+` operator to concatenate strings
|
||||
* Overload `<`, `<=`, `>` and `>=` for strings using lexical ordering (Note: `==` and `!=` compare strings as expected)
|
||||
* Add `len`, `str::regex_matches`, `str::regex_replace`, `str::to_lowercase`, `str::to_uppercase`, `str::trim` functions for strings
|
||||
* Add a macro for more convenient definition of contexts including the direct definition of static contexts
|
||||
* Add API for value decomposition
|
||||
* Allow using context operations in `eval` calls without context
|
||||
* Operator assignment operators for each binary operation (`+=`, `-=`, ...)
|
||||
* The `Operator` enum is now public for better error types
|
||||
* Benchmarks for observing performance of future releases
|
||||
|
||||
### Removed
|
||||
|
||||
* Function arguments are not decomposed anymore.
|
||||
The function implementation will receive exactly one argument now.
|
||||
This allows the function to be called on a tuple properly.
|
||||
|
||||
### Changed
|
||||
|
||||
* Operators are an enum now instead of trait objects
|
||||
* Update to Rust 2018
|
||||
* Updated dependencies
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow variable assignments in eval calls without context.
|
||||
A `HashMapContext` is created automatically now.
|
||||
* The error string for `ExpectedNumber` was wrong
|
||||
* Operators panicked when adding a number to a string
|
||||
* Some documentation was not updated for the 4.x releases
|
||||
|
||||
### Contributors
|
||||
|
||||
My warmhearted thanks goes to
|
||||
|
||||
* [bittrance](https://github.com/bittrance)
|
||||
* [lovasoa](https://github.com/lovasoa)
|
||||
* [Atul9](https://github.com/Atul9)
|
||||
* [mestachs](https://github.com/mestachs)
|
||||
|
||||
## [4.1.0](https://github.com/ISibboI/evalexpr/compare/4.0.0...4.1.0) - 2019-03-31
|
||||
|
||||
### Added
|
||||
|
||||
* Export `expect_function_argument_amount`
|
||||
|
||||
## [4.0.0](https://github.com/ISibboI/evalexpr/compare/3.1.0...4.0.0) - 2019-03-30
|
||||
|
||||
### Added
|
||||
|
||||
* String constants
|
||||
|
||||
## [3.1.0](https://github.com/ISibboI/evalexpr/compare/3.0.0...3.1.0) - 2019-03-28
|
||||
|
||||
### Added
|
||||
|
||||
* Add serde support to `HashMapContext`
|
||||
* Make `HashMapContext` derive `Default` and `Debug`
|
||||
|
||||
### Changed
|
||||
|
||||
* Changed name of serde feature flag to `serde_support`
|
||||
|
||||
## [3.0.0](https://github.com/ISibboI/evalexpr/compare/2.0.0...3.0.0) - 2019-03-28
|
||||
|
||||
### Notes
|
||||
|
||||
The 3.0.0 update transforms the expression evaluator `evalexpr` to a tiny scripting language.
|
||||
It allows assignments and chaining of expressions.
|
||||
Some changes in this update are breaking, hence the major release.
|
||||
|
||||
### Added
|
||||
|
||||
* Methods `Node::eval_<type>_with_context_mut` and crate level `eval_<type>_with_context_mut`
|
||||
* Empty type and corresponding shortcut methods. The empty type is emitted by empty expressions or empty subexpressions `()`.
|
||||
* The assignment operator `=`
|
||||
* The expression chaining operator `;`
|
||||
|
||||
### Removed
|
||||
|
||||
* Generic arguments from `Context` traits are now static to allow using trait objects of `Context`
|
||||
* `EvalexprError::EmptyExpression` is not required anymore since empty expressions now evaluate to the empty type
|
||||
|
||||
### Changed
|
||||
|
||||
* Merge `ContextMut` trait into `Context` trait
|
||||
|
||||
## [2.0.0](https://github.com/ISibboI/evalexpr/compare/1.2.0...2.0.0) - 2019-03-28
|
||||
|
||||
### Notes
|
||||
|
||||
The 2.0.0 update is the first step to transform evalexpr to a tiny scripting language with support of at least variable assignment.
|
||||
The main change for now is that `Configuration` is called `Context`, which seems to be a more proper naming for a set of variables that can not only be read, but also manipulated via expressions.
|
||||
This update includes further renamings and some inconsistencies in the API were fixed.
|
||||
For more details, see the following subsections.
|
||||
|
||||
### Added
|
||||
|
||||
* Add the `ContextMut` trait, that is a manipulable configuration/context
|
||||
* Add `ContextNotManipulable` error variant for the `EmptyContext`
|
||||
* Make the `TupleType` alias public
|
||||
* Add the `ValueType` enum that represents the type of a value for easier comparisons and matchings
|
||||
* Add `EvalexprResult<T>` type that uses the `EvalexprError` type (renamed from `Error`)
|
||||
* Add `Node::eval_number` and `Node::eval_number_with_context` to evaluate to int or float and silently converting to float
|
||||
* Add `eval_number` and `eval_number_with_context` crate methods to evaluate to int or float and silently converting to float
|
||||
|
||||
### Changed
|
||||
|
||||
* Get rid of some unwraps to improve safety
|
||||
* Rename `Error` to `EvalexprError`
|
||||
* Rename `Configuration` to `Context`
|
||||
* Rename `HashMapConfiguration` to `HashMapContext` and `EmptyConfiguration` to `EmptyContext`
|
||||
* Rename `Value::as_float` to `Value::as_number` and add new `Value::as_float` that fails if value is an integer
|
||||
|
||||
## [1.2.0](https://github.com/ISibboI/evalexpr/compare/1.1.0...1.2.0) - 2019-03-23
|
||||
|
||||
### Added
|
||||
|
||||
* Add `serde` feature
|
||||
* Implement `serde::de::Deserialize` for `Node`
|
||||
* Document `serde` usage
|
||||
* Add custom error type with a `String` message
|
||||
|
||||
### Changed
|
||||
|
||||
* Highlighting in documentation
|
||||
|
||||
## [1.1.0](https://github.com/ISibboI/evalexpr/compare/1.0.0...1.1.0) - 2019-03-20
|
||||
|
||||
### Added
|
||||
|
||||
* Internal aliases `IntType` and `FloatType` used by the `Value` enum are now public
|
||||
* Type alias `TupleType` used to represent tuples was added
|
||||
* Error types like `Error::ExpectedInt` for expecting each value type were added
|
||||
* Shortcut functions like `eval_int` or `eval_int_with_configuration` to evaluate directly into a value type were added
|
||||
* Documentation for the shortcut functions was added
|
||||
* Functions to decompose `Value`s were added and documented
|
||||
|
||||
### Removed
|
||||
|
||||
* Integration tests were removed from shipped crate
|
||||
|
||||
### Fixed
|
||||
|
||||
* Wording of some documentation items was changed to improve readability
|
||||
|
||||
## [1.0.0](https://github.com/ISibboI/evalexpr/tree/1.0.0) - 2019-03-20
|
||||
|
||||
* First stable release
|
21
Cargo.toml
21
Cargo.toml
@ -1,22 +1,13 @@
|
||||
[package]
|
||||
name = "evalexpr"
|
||||
version = "11.0.0"
|
||||
description = "A powerful arithmetic and boolean expression evaluator"
|
||||
keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"]
|
||||
categories = ["parsing", "game-engines"]
|
||||
authors = ["isibboi <isibboi@gmail.com>"]
|
||||
repository = "https://github.com/ISibboI/evalexpr.git"
|
||||
homepage = "https://github.com/ISibboI/evalexpr"
|
||||
documentation = "https://docs.rs/evalexpr"
|
||||
name = "expressive"
|
||||
version = "0.1.0"
|
||||
description = "Fork of evalexpr"
|
||||
authors = ["jeff <dev@jeffa.io.com>"]
|
||||
repository = "https://git.jeffa.io/jeff/expressive.git"
|
||||
homepage = "https://git.jeffa.io/jeff/expressive"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
is-it-maintained-issue-resolution = { repository = "ISibboI/evalexpr" }
|
||||
is-it-maintained-open-issues = { repository = "ISibboI/evalexpr" }
|
||||
|
||||
[lib]
|
||||
name = "evalexpr"
|
||||
|
597
README.md
597
README.md
@ -1,596 +1,3 @@
|
||||
# evalexpr
|
||||
# expressive
|
||||
|
||||
[![Version](https://img.shields.io/crates/v/evalexpr.svg)](https://crates.io/crates/evalexpr)
|
||||
[![Downloads](https://img.shields.io/crates/d/evalexpr.svg)](https://crates.io/crates/evalexpr)
|
||||
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/ISibboI/evalexpr/badge.svg?branch=main)](https://coveralls.io/github/ISibboI/evalexpr?branch=main)
|
||||
[![](http://meritbadge.herokuapp.com/evalexpr)](https://crates.io/crates/evalexpr)
|
||||
[![](https://docs.rs/evalexpr/badge.svg)](https://docs.rs/evalexpr)
|
||||
|
||||
Evalexpr is an expression evaluator and tiny scripting language in Rust.
|
||||
It has a small and easy to use interface and can be easily integrated into any application.
|
||||
It is very lightweight and comes with no further dependencies.
|
||||
Evalexpr is [available on crates.io](https://crates.io/crates/evalexpr), and its [API Documentation is available on docs.rs](https://docs.rs/evalexpr).
|
||||
|
||||
|
||||
**Minimum Supported Rust Version:** 1.65.0
|
||||
|
||||
<!-- cargo-sync-readme start -->
|
||||
|
||||
|
||||
## Quickstart
|
||||
|
||||
Add `evalexpr` as dependency to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
evalexpr = "<desired version>"
|
||||
```
|
||||
|
||||
Then you can use `evalexpr` to **evaluate expressions** like this:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(6)));
|
||||
// `eval` returns a variant of the `Value` enum,
|
||||
// while `eval_[type]` returns the respective type directly.
|
||||
// Both can be used interchangeably.
|
||||
assert_eq!(eval_int("1 + 2 + 3"), Ok(6));
|
||||
assert_eq!(eval("1 - 2 * 3"), Ok(Value::from(-5)));
|
||||
assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from(7.0)));
|
||||
assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true)));
|
||||
```
|
||||
|
||||
You can **chain** expressions and **assign** to variables like this:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
// Assign 5 to a like this
|
||||
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
|
||||
// The HashMapContext is type safe, so this will fail now
|
||||
assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context),
|
||||
Err(EvalexprError::expected_int(Value::from(5.0))));
|
||||
// We can check which value the context stores for a like this
|
||||
assert_eq!(context.get_value("a"), Some(&Value::from(5)));
|
||||
// And use the value in another expression like this
|
||||
assert_eq!(eval_int_with_context_mut("a = a + 2; a", &mut context), Ok(7));
|
||||
// It is also possible to save a bit of typing by using an operator-assignment operator
|
||||
assert_eq!(eval_int_with_context_mut("a += 2; a", &mut context), Ok(9));
|
||||
```
|
||||
|
||||
And you can use **variables** and **functions** in expressions like this:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let context = context_map! {
|
||||
"five" => 5,
|
||||
"twelve" => 12,
|
||||
"f" => Function::new(|argument| {
|
||||
if let Ok(int) = argument.as_int() {
|
||||
Ok(Value::Int(int / 2))
|
||||
} else if let Ok(float) = argument.as_float() {
|
||||
Ok(Value::Float(float / 2.0))
|
||||
} else {
|
||||
Err(EvalexprError::expected_number(argument.clone()))
|
||||
}
|
||||
}),
|
||||
"avg" => Function::new(|argument| {
|
||||
let arguments = argument.as_tuple()?;
|
||||
|
||||
if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) {
|
||||
Ok(Value::Int((a + b) / 2))
|
||||
} else {
|
||||
Ok(Value::Float((arguments[0].as_number()? + arguments[1].as_number()?) / 2.0))
|
||||
}
|
||||
})
|
||||
}.unwrap(); // Do proper error handling here
|
||||
|
||||
assert_eq!(eval_with_context("five + 8 > f(twelve)", &context), Ok(Value::from(true)));
|
||||
// `eval_with_context` returns a variant of the `Value` enum,
|
||||
// while `eval_[type]_with_context` returns the respective type directly.
|
||||
// Both can be used interchangeably.
|
||||
assert_eq!(eval_boolean_with_context("five + 8 > f(twelve)", &context), Ok(true));
|
||||
assert_eq!(eval_with_context("avg(2, 4) == 3", &context), Ok(Value::from(true)));
|
||||
```
|
||||
|
||||
You can also **precompile** expressions like this:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let precompiled = build_operator_tree("a * b - c > 5").unwrap(); // Do proper error handling here
|
||||
|
||||
let mut context = context_map! {
|
||||
"a" => 6,
|
||||
"b" => 2,
|
||||
"c" => 3
|
||||
}.unwrap(); // Do proper error handling here
|
||||
assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true)));
|
||||
|
||||
context.set_value("c".into(), 8.into()).unwrap(); // Do proper error handling here
|
||||
assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(false)));
|
||||
// `Node::eval_with_context` returns a variant of the `Value` enum,
|
||||
// while `Node::eval_[type]_with_context` returns the respective type directly.
|
||||
// Both can be used interchangeably.
|
||||
assert_eq!(precompiled.eval_boolean_with_context(&context), Ok(false));
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
While primarily meant to be used as a library, `evalexpr` is also available as a command line tool.
|
||||
It can be installed and used as follows:
|
||||
|
||||
```bash
|
||||
cargo install evalexpr
|
||||
evalexpr 2 + 3 # outputs `5` to stdout.
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Operators
|
||||
|
||||
This crate offers a set of binary and unary operators for building expressions.
|
||||
Operators have a precedence to determine their order of evaluation, where operators of higher precedence are evaluated first.
|
||||
The precedence should resemble that of most common programming languages, especially Rust.
|
||||
Variables and values have a precedence of 200, and function literals have 190.
|
||||
|
||||
Supported binary operators:
|
||||
|
||||
| Operator | Precedence | Description |
|
||||
|----------|------------|-------------|
|
||||
| ^ | 120 | Exponentiation |
|
||||
| * | 100 | Product |
|
||||
| / | 100 | Division (integer if both arguments are integers, otherwise float) |
|
||||
| % | 100 | Modulo (integer if both arguments are integers, otherwise float) |
|
||||
| + | 95 | Sum or String Concatenation |
|
||||
| - | 95 | Difference |
|
||||
| < | 80 | Lower than |
|
||||
| \> | 80 | Greater than |
|
||||
| <= | 80 | Lower than or equal |
|
||||
| \>= | 80 | Greater than or equal |
|
||||
| == | 80 | Equal |
|
||||
| != | 80 | Not equal |
|
||||
| && | 75 | Logical and |
|
||||
| || | 70 | Logical or |
|
||||
| = | 50 | Assignment |
|
||||
| += | 50 | Sum-Assignment or String-Concatenation-Assignment |
|
||||
| -= | 50 | Difference-Assignment |
|
||||
| *= | 50 | Product-Assignment |
|
||||
| /= | 50 | Division-Assignment |
|
||||
| %= | 50 | Modulo-Assignment |
|
||||
| ^= | 50 | Exponentiation-Assignment |
|
||||
| &&= | 50 | Logical-And-Assignment |
|
||||
| ||= | 50 | Logical-Or-Assignment |
|
||||
| , | 40 | Aggregation |
|
||||
| ; | 0 | Expression Chaining |
|
||||
|
||||
Supported unary operators:
|
||||
|
||||
| Operator | Precedence | Description |
|
||||
|----------|------------|-------------|
|
||||
| - | 110 | Negation |
|
||||
| ! | 110 | Logical not |
|
||||
|
||||
Operators that take numbers as arguments can either take integers or floating point numbers.
|
||||
If one of the arguments is a floating point number, all others are converted to floating point numbers as well, and the resulting value is a floating point number as well.
|
||||
Otherwise, the result is an integer.
|
||||
An exception to this is the exponentiation operator that always returns a floating point number.
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval("1 / 2"), Ok(Value::from(0)));
|
||||
assert_eq!(eval("1.0 / 2"), Ok(Value::from(0.5)));
|
||||
assert_eq!(eval("2^2"), Ok(Value::from(4.0)));
|
||||
```
|
||||
|
||||
#### The Aggregation Operator
|
||||
|
||||
The aggregation operator aggregates a set of values into a tuple.
|
||||
A tuple can contain arbitrary values, it is not restricted to a single type.
|
||||
The operator is n-ary, so it supports creating tuples longer than length two.
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval("1, \"b\", 3"),
|
||||
Ok(Value::from(vec![Value::from(1), Value::from("b"), Value::from(3)])));
|
||||
```
|
||||
|
||||
To create nested tuples, use parentheses:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval("1, 2, (true, \"b\")"), Ok(Value::from(vec![
|
||||
Value::from(1),
|
||||
Value::from(2),
|
||||
Value::from(vec![
|
||||
Value::from(true),
|
||||
Value::from("b")
|
||||
])
|
||||
])));
|
||||
```
|
||||
|
||||
#### The Assignment Operator
|
||||
|
||||
This crate features the assignment operator, that allows expressions to store their result in a variable in the expression context.
|
||||
If an expression uses the assignment operator, it must be evaluated with a mutable context.
|
||||
|
||||
Note that assignments are type safe when using the `HashMapContext`.
|
||||
That means that if an identifier is assigned a value of a type once, it cannot be assigned a value of another type.
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
assert_eq!(eval_with_context("a = 5", &context), Err(EvalexprError::ContextNotMutable));
|
||||
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
|
||||
assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context),
|
||||
Err(EvalexprError::expected_int(5.0.into())));
|
||||
assert_eq!(eval_int_with_context("a", &context), Ok(5));
|
||||
assert_eq!(context.get_value("a"), Some(5.into()).as_ref());
|
||||
```
|
||||
|
||||
For each binary operator, there exists an equivalent operator-assignment operator.
|
||||
Here are some examples:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval_int("a = 2; a *= 2; a += 2; a"), Ok(6));
|
||||
assert_eq!(eval_float("a = 2.2; a /= 2.0 / 4 + 1; a"), Ok(2.2 / (2.0 / 4.0 + 1.0)));
|
||||
assert_eq!(eval_string("a = \"abc\"; a += \"def\"; a"), Ok("abcdef".to_string()));
|
||||
assert_eq!(eval_boolean("a = true; a &&= false; a"), Ok(false));
|
||||
```
|
||||
|
||||
#### The Expression Chaining Operator
|
||||
|
||||
The expression chaining operator works as one would expect from programming languages that use the semicolon to end statements, like `Rust`, `C` or `Java`.
|
||||
It has the special feature that it returns the value of the last expression in the expression chain.
|
||||
If the last expression is terminated by a semicolon as well, then `Value::Empty` is returned.
|
||||
Expression chaining is useful together with assignment to create small scripts.
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
assert_eq!(eval("1;2;3;4;"), Ok(Value::Empty));
|
||||
assert_eq!(eval("1;2;3;4"), Ok(4.into()));
|
||||
|
||||
// Initialization of variables via script
|
||||
assert_eq!(eval_empty_with_context_mut("hp = 1; max_hp = 5; heal_amount = 3;", &mut context),
|
||||
Ok(EMPTY_VALUE));
|
||||
// Precompile healing script
|
||||
let healing_script = build_operator_tree("hp = min(hp + heal_amount, max_hp); hp").unwrap(); // Do proper error handling here
|
||||
// Execute precompiled healing script
|
||||
assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(4));
|
||||
assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(5));
|
||||
```
|
||||
|
||||
### Contexts
|
||||
|
||||
An expression evaluator that just evaluates expressions would be useful already, but this crate can do more.
|
||||
It allows using [variables](#variables), [assignments](#the-assignment-operator), [statement chaining](#the-expression-chaining-operator) and [user-defined functions](#user-defined-functions) within an expression.
|
||||
When assigning to variables, the assignment is stored in a context.
|
||||
When the variable is read later on, it is read from the context.
|
||||
Contexts can be preserved between multiple calls to eval by creating them yourself.
|
||||
Here is a simple example to show the difference between preserving and not preserving context between evaluations:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
assert_eq!(eval("a = 5;"), Ok(Value::from(())));
|
||||
// The context is not preserved between eval calls
|
||||
assert_eq!(eval("a"), Err(EvalexprError::VariableIdentifierNotFound("a".to_string())));
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
assert_eq!(eval_with_context_mut("a = 5;", &mut context), Ok(Value::from(())));
|
||||
// Assignments require mutable contexts
|
||||
assert_eq!(eval_with_context("a = 6", &context), Err(EvalexprError::ContextNotMutable));
|
||||
// The HashMapContext is type safe
|
||||
assert_eq!(eval_with_context_mut("a = 5.5", &mut context),
|
||||
Err(EvalexprError::ExpectedInt { actual: Value::from(5.5) }));
|
||||
// Reading a variable does not require a mutable context
|
||||
assert_eq!(eval_with_context("a", &context), Ok(Value::from(5)));
|
||||
|
||||
```
|
||||
|
||||
Note that the assignment is forgotten between the two calls to eval in the first example.
|
||||
In the second part, the assignment is correctly preserved.
|
||||
Note as well that to assign to a variable, the context needs to be passed as a mutable reference.
|
||||
When passed as an immutable reference, an error is returned.
|
||||
|
||||
Also, the `HashMapContext` is type safe.
|
||||
This means that assigning to `a` again with a different type yields an error.
|
||||
Type unsafe contexts may be implemented if requested.
|
||||
For reading `a`, it is enough to pass an immutable reference.
|
||||
|
||||
Contexts can also be manipulated in code.
|
||||
Take a look at the following example:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
// We can set variables in code like this...
|
||||
context.set_value("a".into(), 5.into());
|
||||
// ...and read from them in expressions
|
||||
assert_eq!(eval_int_with_context("a", &context), Ok(5));
|
||||
// We can write or overwrite variables in expressions...
|
||||
assert_eq!(eval_with_context_mut("a = 10; b = 1.0;", &mut context), Ok(().into()));
|
||||
// ...and read the value in code like this
|
||||
assert_eq!(context.get_value("a"), Some(&Value::from(10)));
|
||||
assert_eq!(context.get_value("b"), Some(&Value::from(1.0)));
|
||||
```
|
||||
|
||||
Contexts are also required for user-defined functions.
|
||||
Those can be passed one by one with the `set_function` method, but it might be more convenient to use the `context_map!` macro instead:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
|
||||
let context = context_map!{
|
||||
"f" => Function::new(|args| Ok(Value::from(args.as_int()? + 5))),
|
||||
}.unwrap_or_else(|error| panic!("Error creating context: {}", error));
|
||||
assert_eq!(eval_int_with_context("f 5", &context), Ok(10));
|
||||
```
|
||||
|
||||
For more information about user-defined functions, refer to the respective [section](#user-defined-functions).
|
||||
|
||||
### Builtin Functions
|
||||
|
||||
This crate offers a set of builtin functions (see below for a full list).
|
||||
They can be disabled if needed as follows:
|
||||
|
||||
```rust
|
||||
use evalexpr::*;
|
||||
let mut context = HashMapContext::new();
|
||||
assert_eq!(eval_with_context("max(1,3)",&context),Ok(Value::from(3)));
|
||||
context.set_builtin_functions_disabled(true).unwrap(); // Do proper error handling here
|
||||
assert_eq!(eval_with_context("max(1,3)",&context),Err(EvalexprError::FunctionIdentifierNotFound(String::from("max"))));
|
||||
```
|
||||
|
||||
Not all contexts support enabling or disabling builtin functions.
|
||||
Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled.
|
||||
Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled.
|
||||
|
||||
| Identifier | Argument Amount | Argument Types | Description |
|
||||
|----------------------|-----------------|-------------------------------|-------------|
|
||||
| `min` | >= 1 | Numeric | Returns the minimum of the arguments |
|
||||
| `max` | >= 1 | Numeric | Returns the maximum of the arguments |
|
||||
| `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) |
|
||||
| `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number |
|
||||
| `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 |
|
||||
| `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number |
|
||||
| `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third |
|
||||
| `contains` | 2 | Tuple, any non-tuple | Returns true if second argument exists in first tuple argument. |
|
||||
| `contains_any` | 2 | Tuple, Tuple of any non-tuple | Returns true if one of the values in the second tuple argument exists in first tuple argument. |
|
||||
| `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument |
|
||||
| `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number |
|
||||
| `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise |
|
||||
| `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise |
|
||||
| `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise |
|
||||
| `math::ln` | 1 | Numeric | Returns the natural logarithm of the number |
|
||||
| `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base |
|
||||
| `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number |
|
||||
| `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number |
|
||||
| `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) |
|
||||
| `math::exp2` | 1 | Numeric | Returns `2^(number)` |
|
||||
| `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number |
|
||||
| `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) |
|
||||
| `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] |
|
||||
| `math::cosh` | 1 | Numeric | Hyperbolic cosine function |
|
||||
| `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function |
|
||||
| `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) |
|
||||
| `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] |
|
||||
| `math::sinh` | 1 | Numeric | Hyperbolic sine function |
|
||||
| `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function |
|
||||
| `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) |
|
||||
| `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] |
|
||||
| `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians |
|
||||
| `math::tanh` | 1 | Numeric | Hyperbolic tangent function |
|
||||
| `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. |
|
||||
| `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number |
|
||||
| `math::cbrt` | 1 | Numeric | Returns the cube root of a number |
|
||||
| `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments |
|
||||
| `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise |
|
||||
| `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) |
|
||||
| `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) |
|
||||
| `str::to_lowercase` | 1 | String | Returns the lower-case version of the string |
|
||||
| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string |
|
||||
| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string |
|
||||
| `str::from` | >= 0 | Any | Returns passed value as string |
|
||||
| `bitand` | 2 | Int | Computes the bitwise and of the given integers |
|
||||
| `bitor` | 2 | Int | Computes the bitwise or of the given integers |
|
||||
| `bitxor` | 2 | Int | Computes the bitwise xor of the given integers |
|
||||
| `bitnot` | 1 | Int | Computes the bitwise not of the given integer |
|
||||
| `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer |
|
||||
| `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer |
|
||||
| `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. |
|
||||
|
||||
The `min` and `max` functions can deal with a mixture of integer and floating point arguments.
|
||||
If the maximum or minimum is an integer, then an integer is returned.
|
||||
Otherwise, a float is returned.
|
||||
|
||||
The regex functions require the feature flag `regex_support`.
|
||||
|
||||
### Values
|
||||
|
||||
Operators take values as arguments and produce values as results.
|
||||
Values can be booleans, integer or floating point numbers, strings, tuples or the empty type.
|
||||
Values are denoted as displayed in the following table.
|
||||
|
||||
| Value type | Example |
|
||||
|------------|---------|
|
||||
| `Value::String` | `"abc"`, `""`, `"a\"b\\c"` |
|
||||
| `Value::Boolean` | `true`, `false` |
|
||||
| `Value::Int` | `3`, `-9`, `0`, `135412`, `0xfe02`, `-0x1e` |
|
||||
| `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554`, `23e4`, `-2e-3`, `3.54e+2` |
|
||||
| `Value::Tuple` | `(3, 55.0, false, ())`, `(1, 2)` |
|
||||
| `Value::Empty` | `()` |
|
||||
|
||||
Integers are internally represented as `i64`, and floating point numbers are represented as `f64`.
|
||||
Tuples are represented as `Vec<Value>` and empty values are not stored, but represented by Rust's unit type `()` where necessary.
|
||||
|
||||
There exist type aliases for some of the types.
|
||||
They include `IntType`, `FloatType`, `TupleType` and `EmptyType`.
|
||||
|
||||
Values can be constructed either directly or using the `From` trait.
|
||||
They can be decomposed using the `Value::as_[type]` methods.
|
||||
The type of a value can be checked using the `Value::is_[type]` methods.
|
||||
|
||||
**Examples for constructing a value:**
|
||||
|
||||
| Code | Result |
|
||||
|------|--------|
|
||||
| `Value::from(4)` | `Value::Int(4)` |
|
||||
| `Value::from(4.4)` | `Value::Float(4.4)` |
|
||||
| `Value::from(true)` | `Value::Boolean(true)` |
|
||||
| `Value::from(vec![Value::from(3)])` | `Value::Tuple(vec![Value::Int(3)])` |
|
||||
|
||||
**Examples for deconstructing a value:**
|
||||
|
||||
| Code | Result |
|
||||
|------|--------|
|
||||
| `Value::from(4).as_int()` | `Ok(4)` |
|
||||
| `Value::from(4.4).as_float()` | `Ok(4.4)` |
|
||||
| `Value::from(true).as_int()` | `Err(Error::ExpectedInt {actual: Value::Boolean(true)})` |
|
||||
|
||||
Values have a precedence of 200.
|
||||
|
||||
### Variables
|
||||
|
||||
This crate allows to compile parameterizable formulas by using variables.
|
||||
A variable is a literal in the formula, that does not contain whitespace or can be parsed as value.
|
||||
For working with variables, a [context](#contexts) is required.
|
||||
It stores the mappings from variables to their values.
|
||||
|
||||
Variables do not have fixed types in the expression itself, but are typed by the context.
|
||||
Once a variable is assigned a value of a specific type, it cannot be assigned a value of another type.
|
||||
This might change in the future and can be changed by using a type-unsafe context (not provided by this crate as of now).
|
||||
|
||||
Here are some examples and counter-examples on expressions that are interpreted as variables:
|
||||
|
||||
| Expression | Variable? | Explanation |
|
||||
|------------|--------|-------------|
|
||||
| `a` | yes | |
|
||||
| `abc` | yes | |
|
||||
| `a<b` | no | Expression is interpreted as variable `a`, operator `<` and variable `b` |
|
||||
| `a b` | no | Expression is interpreted as function `a` applied to argument `b` |
|
||||
| `123` | no | Expression is interpreted as `Value::Int` |
|
||||
| `true` | no | Expression is interpreted as `Value::Bool` |
|
||||
| `.34` | no | Expression is interpreted as `Value::Float` |
|
||||
|
||||
Variables have a precedence of 200.
|
||||
|
||||
### User-Defined Functions
|
||||
|
||||
This crate allows to define arbitrary functions to be used in parsed expressions.
|
||||
A function is defined as a `Function` instance, wrapping an `fn(&Value) -> EvalexprResult<Value>`.
|
||||
The definition needs to be included in the [`Context`](#contexts) that is used for evaluation.
|
||||
As of now, functions cannot be defined within the expression, but that might change in the future.
|
||||
|
||||
The function gets passed what ever value is directly behind it, be it a tuple or a single values.
|
||||
If there is no value behind a function, it is interpreted as a variable instead.
|
||||
More specifically, a function needs to be followed by either an opening brace `(`, another literal, or a value.
|
||||
While not including special support for multi-valued functions, they can be realized by requiring a single tuple argument.
|
||||
|
||||
Be aware that functions need to verify the types of values that are passed to them.
|
||||
The `error` module contains some shortcuts for verification, and error types for passing a wrong value type.
|
||||
Also, most numeric functions need to distinguish between being called with integers or floating point numbers, and act accordingly.
|
||||
|
||||
Here are some examples and counter-examples on expressions that are interpreted as function calls:
|
||||
|
||||
| Expression | Function? | Explanation |
|
||||
|------------|--------|-------------|
|
||||
| `a v` | yes | |
|
||||
| `x 5.5` | yes | |
|
||||
| `a (3, true)` | yes | |
|
||||
| `a b 4` | yes | Call `a` with the result of calling `b` with `4` |
|
||||
| `5 b` | no | Error, value cannot be followed by a literal |
|
||||
| `12 3` | no | Error, value cannot be followed by a value |
|
||||
| `a 5 6` | no | Error, function call cannot be followed by a value |
|
||||
|
||||
Functions have a precedence of 190.
|
||||
|
||||
### [Serde](https://serde.rs)
|
||||
|
||||
To use this crate with serde, the `serde_support` feature flag has to be set.
|
||||
This can be done like this in the `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
evalexpr = {version = "7", features = ["serde_support"]}
|
||||
```
|
||||
|
||||
This crate implements `serde::de::Deserialize` for its type `Node` that represents a parsed expression tree.
|
||||
The implementation expects a [serde `string`](https://serde.rs/data-model.html) as input.
|
||||
Example parsing with [ron format](docs.rs/ron):
|
||||
|
||||
```rust
|
||||
extern crate ron;
|
||||
use evalexpr::*;
|
||||
|
||||
let mut context = context_map!{
|
||||
"five" => 5
|
||||
}.unwrap(); // Do proper error handling here
|
||||
|
||||
// In ron format, strings are surrounded by "
|
||||
let serialized_free = "\"five * five\"";
|
||||
match ron::de::from_str::<Node>(serialized_free) {
|
||||
Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))),
|
||||
Err(error) => {
|
||||
() // Handle error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With `serde`, expressions can be integrated into arbitrarily complex data.
|
||||
|
||||
The crate also implements `Serialize` and `Deserialize` for the `HashMapContext`,
|
||||
but note that only the variables get (de)serialized, not the functions.
|
||||
|
||||
## License
|
||||
|
||||
This crate is primarily distributed under the terms of the MIT license.
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
|
||||
<!-- cargo-sync-readme end -->
|
||||
|
||||
## No Panicking
|
||||
|
||||
This crate makes extensive use of the `Result` pattern and is intended to never panic.
|
||||
The *exception* are panics caused by *failed allocations*.
|
||||
But unfortunately, Rust does not provide any features to prove this behavior.
|
||||
The developer of this crate has not found a good solution to ensure no-panic behavior in any way.
|
||||
Please report a panic in this crate immediately as issue on [github](https://github.com/ISibboI/evalexpr/issues).
|
||||
|
||||
Even if the crate itself is panic free, it allows the user to define custom functions that are executed by the crate.
|
||||
The user needs to ensure that the functions they provide to the crate never panic.
|
||||
|
||||
## Untrusted input
|
||||
|
||||
This crate was not built with untrusted input in mind, but due to its simplicity and freedom of panics it is likely secure, keeping the following in mind:
|
||||
* Limit the length of the untrusted input.
|
||||
* If a mutable context is maintained between evaluations of untrusted input, the untrusted input might fill it gradually until the application runs out of memory.
|
||||
* If no context is provided, a temporary mutable context is implicitly provided. This is freed after evaluation of every single string, so gradual filling cannot happen.
|
||||
* If no context or a mutable context is provided, and the `regex_support` feature is activated, the `regex_replace` builtin function can be used to build an exponentially sized string.
|
||||
|
||||
## Contribution
|
||||
|
||||
If you have any ideas for features or see any problems in the code, architecture, interface, algorithmics or documentation, please open an issue on [github](https://github.com/ISibboI/evalexpr/issues).
|
||||
If there is already an issue describing what you want to say, please add a thumbs up or whatever emoji you think fits to the issue, so I know which ones I should prioritize.
|
||||
|
||||
**Notes for contributors:**
|
||||
|
||||
* This crate uses the [`sync-readme`](https://github.com/phaazon/cargo-sync-readme) cargo subcommand to keep the documentation in `src/lib.rs` and `README.md` in sync.
|
||||
The subcommand only syncs from the documentation in `src/lib.rs` to `README.md`.
|
||||
So please alter the documentation in the `src/lib.rs` rather than altering anything in between `<!-- cargo-sync-readme start -->` and `<!-- cargo-sync-readme end -->` in the `README.md`.
|
||||
Fork of [evalexpr](https://github.com/ISibboI/evalexpr).
|
||||
|
@ -22,13 +22,6 @@ pub trait Context {
|
||||
/// Calls the function that is linked to the given identifier with the given argument.
|
||||
/// If no function with the given identifier is found, this method returns `EvalexprError::FunctionIdentifierNotFound`.
|
||||
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value>;
|
||||
|
||||
/// Checks if builtin functions are disabled.
|
||||
fn are_builtin_functions_disabled(&self) -> bool;
|
||||
|
||||
/// Disables builtin functions if `disabled` is `true`, and enables them otherwise.
|
||||
/// If the context does not support enabling or disabling builtin functions, an error is returned.
|
||||
fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()>;
|
||||
}
|
||||
|
||||
/// A context that allows to assign to variables.
|
||||
@ -67,152 +60,46 @@ pub trait IterateVariablesContext {
|
||||
fn iter_variable_names(&self) -> Self::VariableNameIterator<'_>;
|
||||
}
|
||||
|
||||
/*/// A context that allows to retrieve functions programmatically.
|
||||
pub trait GetFunctionContext: Context {
|
||||
/// Returns the function that is linked to the given identifier.
|
||||
///
|
||||
/// This might not be possible for all functions, as some might be hard-coded.
|
||||
/// In this case, a special error variant should be returned (Not yet implemented).
|
||||
fn get_function(&self, identifier: &str) -> Option<&Function>;
|
||||
}*/
|
||||
|
||||
/// A context that returns `None` for each identifier.
|
||||
/// Builtin functions are disabled and cannot be enabled.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EmptyContext;
|
||||
|
||||
impl Context for EmptyContext {
|
||||
fn get_value(&self, _identifier: &str) -> Option<&Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn call_function(&self, identifier: &str, _argument: &Value) -> EvalexprResult<Value> {
|
||||
Err(EvalexprError::FunctionIdentifierNotFound(
|
||||
identifier.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Builtin functions are always disabled for `EmptyContext`.
|
||||
fn are_builtin_functions_disabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Builtin functions can't be enabled for `EmptyContext`.
|
||||
fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> {
|
||||
if disabled {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(EvalexprError::BuiltinFunctionsCannotBeEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IterateVariablesContext for EmptyContext {
|
||||
type VariableIterator<'a> = iter::Empty<(String, Value)>;
|
||||
type VariableNameIterator<'a> = iter::Empty<String>;
|
||||
|
||||
fn iter_variables(&self) -> Self::VariableIterator<'_> {
|
||||
iter::empty()
|
||||
}
|
||||
|
||||
fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
|
||||
iter::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A context that returns `None` for each identifier.
|
||||
/// Builtin functions are enabled and cannot be disabled.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EmptyContextWithBuiltinFunctions;
|
||||
|
||||
impl Context for EmptyContextWithBuiltinFunctions {
|
||||
fn get_value(&self, _identifier: &str) -> Option<&Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn call_function(&self, identifier: &str, _argument: &Value) -> EvalexprResult<Value> {
|
||||
Err(EvalexprError::FunctionIdentifierNotFound(
|
||||
identifier.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Builtin functions are always enabled for EmptyContextWithBuiltinFunctions.
|
||||
fn are_builtin_functions_disabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Builtin functions can't be disabled for EmptyContextWithBuiltinFunctions.
|
||||
fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> {
|
||||
if disabled {
|
||||
Err(EvalexprError::BuiltinFunctionsCannotBeDisabled)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IterateVariablesContext for EmptyContextWithBuiltinFunctions {
|
||||
type VariableIterator<'a> = iter::Empty<(String, Value)>;
|
||||
type VariableNameIterator<'a> = iter::Empty<String>;
|
||||
|
||||
fn iter_variables(&self) -> Self::VariableIterator<'_> {
|
||||
iter::empty()
|
||||
}
|
||||
|
||||
fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> {
|
||||
iter::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A context that stores its mappings in hash maps.
|
||||
///
|
||||
/// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.*
|
||||
///
|
||||
/// This context is type-safe, meaning that an identifier that is assigned a value of some type once cannot be assigned a value of another type.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct HashMapContext {
|
||||
variables: HashMap<String, Value>,
|
||||
#[cfg_attr(feature = "serde_support", serde(skip))]
|
||||
functions: HashMap<String, Function>,
|
||||
pub struct ContextData {
|
||||
map_name: String,
|
||||
|
||||
/// True if builtin functions are disabled.
|
||||
without_builtin_functions: bool,
|
||||
variables: HashMap<String, Value>,
|
||||
|
||||
functions: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
impl HashMapContext {
|
||||
impl ContextData {
|
||||
/// Constructs a `HashMapContext` with no mappings.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for HashMapContext {
|
||||
impl Context for ContextData {
|
||||
fn get_value(&self, identifier: &str) -> Option<&Value> {
|
||||
self.variables.get(identifier)
|
||||
}
|
||||
|
||||
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
|
||||
if let Some(function) = self.functions.get(identifier) {
|
||||
function.call(argument)
|
||||
} else {
|
||||
Err(EvalexprError::FunctionIdentifierNotFound(
|
||||
identifier.to_string(),
|
||||
))
|
||||
match identifier {
|
||||
"map" => {
|
||||
let map = Value::Map(self.variables.clone());
|
||||
self.variables.insert(identifier.to_string(), map);
|
||||
Ok(Value::Empty)
|
||||
},
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn are_builtin_functions_disabled(&self) -> bool {
|
||||
self.without_builtin_functions
|
||||
}
|
||||
|
||||
fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> {
|
||||
self.without_builtin_functions = disabled;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextWithMutableVariables for HashMapContext {
|
||||
impl ContextWithMutableVariables for ContextData {
|
||||
fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> {
|
||||
if let Some(existing_value) = self.variables.get_mut(&identifier) {
|
||||
if ValueType::from(&existing_value) == ValueType::from(&value) {
|
||||
@ -229,14 +116,14 @@ impl ContextWithMutableVariables for HashMapContext {
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextWithMutableFunctions for HashMapContext {
|
||||
impl ContextWithMutableFunctions for ContextData {
|
||||
fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
|
||||
self.functions.insert(identifier, function);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IterateVariablesContext for HashMapContext {
|
||||
impl IterateVariablesContext for ContextData {
|
||||
type VariableIterator<'a> = std::iter::Map<
|
||||
std::collections::hash_map::Iter<'a, String, Value>,
|
||||
fn((&String, &Value)) -> (String, Value),
|
||||
|
@ -44,6 +44,7 @@ impl fmt::Display for EvalexprError {
|
||||
expected_len, actual
|
||||
),
|
||||
ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
|
||||
ExpectedMap { actual } => write!(f, "Expected a Value::Map, but got {:?}.", actual),
|
||||
AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."),
|
||||
PrecedenceViolation => write!(
|
||||
f,
|
||||
|
@ -91,6 +91,12 @@ pub enum EvalexprError {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// A map value was expected.
|
||||
ExpectedMap {
|
||||
/// The actual value.
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// Tried to append a child to a leaf node.
|
||||
/// Leaf nodes cannot have children.
|
||||
AppendedToLeafNode,
|
||||
@ -280,6 +286,11 @@ impl EvalexprError {
|
||||
EvalexprError::ExpectedEmpty { actual }
|
||||
}
|
||||
|
||||
/// Constructs `EvalexprError::ExpectedEmpty{actual}`.
|
||||
pub fn expected_map(actual: Value) -> Self {
|
||||
EvalexprError::ExpectedMap { actual }
|
||||
}
|
||||
|
||||
/// Constructs an error that expresses that the type of `expected` was expected, but `actual` was found.
|
||||
pub(crate) fn expected_type(expected: &Value, actual: Value) -> Self {
|
||||
match ValueType::from(expected) {
|
||||
@ -289,6 +300,7 @@ impl EvalexprError {
|
||||
ValueType::Boolean => Self::expected_boolean(actual),
|
||||
ValueType::Tuple => Self::expected_tuple(actual),
|
||||
ValueType::Empty => Self::expected_empty(actual),
|
||||
ValueType::Map => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
|
||||
Value::Boolean(_) => "boolean",
|
||||
Value::Tuple(_) => "tuple",
|
||||
Value::Empty => "empty",
|
||||
Value::Map(_) => "map",
|
||||
}
|
||||
.into())
|
||||
})),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
token, tree, value::TupleType, Context, ContextWithMutableVariables, EmptyType, EvalexprError,
|
||||
EvalexprResult, FloatType, HashMapContext, IntType, Node, Value, EMPTY_VALUE,
|
||||
EvalexprResult, FloatType, ContextData, IntType, Node, Value, EMPTY_VALUE,
|
||||
};
|
||||
|
||||
/// Evaluate the given expression string.
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval(string: &str) -> EvalexprResult<Value> {
|
||||
eval_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string with the given context.
|
||||
@ -91,21 +91,21 @@ pub fn build_operator_tree(string: &str) -> EvalexprResult<Node> {
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_string(string: &str) -> EvalexprResult<String> {
|
||||
eval_string_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_string_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into an integer.
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_int(string: &str) -> EvalexprResult<IntType> {
|
||||
eval_int_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_int_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into a float.
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_float(string: &str) -> EvalexprResult<FloatType> {
|
||||
eval_float_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_float_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into a float.
|
||||
@ -113,28 +113,28 @@ pub fn eval_float(string: &str) -> EvalexprResult<FloatType> {
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_number(string: &str) -> EvalexprResult<FloatType> {
|
||||
eval_number_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_number_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into a boolean.
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_boolean(string: &str) -> EvalexprResult<bool> {
|
||||
eval_boolean_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_boolean_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into a tuple.
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_tuple(string: &str) -> EvalexprResult<TupleType> {
|
||||
eval_tuple_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_tuple_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into an empty value.
|
||||
///
|
||||
/// *See the [crate doc](index.html) for more examples and explanations of the expression format.*
|
||||
pub fn eval_empty(string: &str) -> EvalexprResult<EmptyType> {
|
||||
eval_empty_with_context_mut(string, &mut HashMapContext::new())
|
||||
eval_empty_with_context_mut(string, &mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluate the given expression string into a string with the given context.
|
||||
|
@ -561,8 +561,7 @@ extern crate serde_derive;
|
||||
|
||||
pub use crate::{
|
||||
context::{
|
||||
Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext,
|
||||
EmptyContextWithBuiltinFunctions, HashMapContext, IterateVariablesContext,
|
||||
Context, ContextWithMutableFunctions, ContextWithMutableVariables, ContextData, IterateVariablesContext,
|
||||
},
|
||||
error::{EvalexprError, EvalexprResult},
|
||||
function::Function,
|
||||
|
@ -459,8 +459,7 @@ impl Operator {
|
||||
let arguments = &arguments[0];
|
||||
|
||||
match context.call_function(identifier, arguments) {
|
||||
Err(EvalexprError::FunctionIdentifierNotFound(_))
|
||||
if !context.are_builtin_functions_disabled() =>
|
||||
Err(EvalexprError::FunctionIdentifierNotFound(_)) =>
|
||||
{
|
||||
if let Some(builtin_function) = builtin_function(identifier) {
|
||||
builtin_function.call(arguments)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
token::Token,
|
||||
value::{TupleType, EMPTY_VALUE},
|
||||
Context, ContextWithMutableVariables, EmptyType, FloatType, HashMapContext, IntType,
|
||||
Context, ContextWithMutableVariables, EmptyType, FloatType, ContextData, IntType,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -346,7 +346,7 @@ impl Node {
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval(&self) -> EvalexprResult<Value> {
|
||||
self.eval_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into a string with an the given context.
|
||||
@ -532,21 +532,21 @@ impl Node {
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_string(&self) -> EvalexprResult<String> {
|
||||
self.eval_string_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_string_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into a float.
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_float(&self) -> EvalexprResult<FloatType> {
|
||||
self.eval_float_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_float_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into an integer.
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_int(&self) -> EvalexprResult<IntType> {
|
||||
self.eval_int_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_int_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into a float.
|
||||
@ -554,28 +554,28 @@ impl Node {
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_number(&self) -> EvalexprResult<FloatType> {
|
||||
self.eval_number_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_number_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into a boolean.
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_boolean(&self) -> EvalexprResult<bool> {
|
||||
self.eval_boolean_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_boolean_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into a tuple.
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_tuple(&self) -> EvalexprResult<TupleType> {
|
||||
self.eval_tuple_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_tuple_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Evaluates the operator tree rooted at this node into an empty value.
|
||||
///
|
||||
/// Fails, if one of the operators in the expression tree fails.
|
||||
pub fn eval_empty(&self) -> EvalexprResult<EmptyType> {
|
||||
self.eval_empty_with_context_mut(&mut HashMapContext::new())
|
||||
self.eval_empty_with_context_mut(&mut ContextData::new())
|
||||
}
|
||||
|
||||
/// Returns the children of this node as a slice.
|
||||
|
@ -23,6 +23,7 @@ impl Display for Value {
|
||||
write!(f, ")")
|
||||
},
|
||||
Value::Empty => write!(f, "()"),
|
||||
Value::Map(_) => write!(f, "{:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::error::{EvalexprError, EvalexprResult};
|
||||
use std::convert::TryFrom;
|
||||
use std::{convert::TryFrom, collections::HashMap};
|
||||
|
||||
mod display;
|
||||
pub mod value_type;
|
||||
@ -36,6 +36,8 @@ pub enum Value {
|
||||
Tuple(TupleType),
|
||||
/// An empty value.
|
||||
Empty,
|
||||
/// Collection of key-value pairs.
|
||||
Map(HashMap<String, Value>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
|
@ -15,6 +15,8 @@ pub enum ValueType {
|
||||
Tuple,
|
||||
/// The `Value::Empty` type.
|
||||
Empty,
|
||||
/// The `Value::Map` type.
|
||||
Map,
|
||||
}
|
||||
|
||||
impl From<&Value> for ValueType {
|
||||
@ -26,6 +28,7 @@ impl From<&Value> for ValueType {
|
||||
Value::Boolean(_) => ValueType::Boolean,
|
||||
Value::Tuple(_) => ValueType::Tuple,
|
||||
Value::Empty => ValueType::Empty,
|
||||
Value::Map(_) => ValueType::Map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ fn test_boolean_examples() {
|
||||
|
||||
#[test]
|
||||
fn test_with_context() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
context
|
||||
.set_value("tr".into(), Value::Boolean(true))
|
||||
.unwrap();
|
||||
@ -144,7 +144,7 @@ fn test_with_context() {
|
||||
|
||||
#[test]
|
||||
fn test_functions() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
context
|
||||
.set_function(
|
||||
"sub2".to_string(),
|
||||
@ -175,7 +175,7 @@ fn test_functions() {
|
||||
|
||||
#[test]
|
||||
fn test_n_ary_functions() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
context
|
||||
.set_function(
|
||||
"sub2".into(),
|
||||
@ -282,7 +282,7 @@ fn test_n_ary_functions() {
|
||||
|
||||
#[test]
|
||||
fn test_capturing_functions() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
// this variable is captured by the function
|
||||
let three = 3;
|
||||
context
|
||||
@ -601,7 +601,7 @@ fn test_no_panic() {
|
||||
|
||||
#[test]
|
||||
fn test_shortcut_functions() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
context
|
||||
.set_value("string".into(), Value::from("a string"))
|
||||
.unwrap();
|
||||
@ -1284,7 +1284,7 @@ fn test_whitespace() {
|
||||
|
||||
#[test]
|
||||
fn test_assignment() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
assert_eq!(
|
||||
eval_empty_with_context_mut("int = 3", &mut context),
|
||||
Ok(EMPTY_VALUE)
|
||||
@ -1324,7 +1324,7 @@ fn test_assignment() {
|
||||
|
||||
#[test]
|
||||
fn test_expression_chaining() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
assert_eq!(
|
||||
eval_int_with_context_mut("a = 5; a = a + 2; a", &mut context),
|
||||
Ok(7)
|
||||
@ -1333,7 +1333,7 @@ fn test_expression_chaining() {
|
||||
|
||||
#[test]
|
||||
fn test_strings() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
assert_eq!(eval("\"string\""), Ok(Value::from("string")));
|
||||
assert_eq!(
|
||||
eval_with_context_mut("a = \"a string\"", &mut context),
|
||||
@ -1441,7 +1441,7 @@ fn test_implicit_context() {
|
||||
|
||||
#[test]
|
||||
fn test_operator_assignments() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(()));
|
||||
assert_eq!(eval_empty_with_context_mut("a += 5", &mut context), Ok(()));
|
||||
assert_eq!(eval_empty_with_context_mut("a -= 5", &mut context), Ok(()));
|
||||
@ -1463,7 +1463,7 @@ fn test_operator_assignments() {
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
assert_eq!(eval_int_with_context_mut("a = 5; a", &mut context), Ok(5));
|
||||
assert_eq!(eval_int_with_context_mut("a += 3; a", &mut context), Ok(8));
|
||||
assert_eq!(eval_int_with_context_mut("a -= 5; a", &mut context), Ok(3));
|
||||
@ -1643,7 +1643,7 @@ fn test_hashmap_context_type_safety() {
|
||||
|
||||
#[test]
|
||||
fn test_hashmap_context_clone_debug() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
// this variable is captured by the function
|
||||
let three = 3;
|
||||
context
|
||||
@ -2228,7 +2228,7 @@ fn assignment_lhs_is_identifier() {
|
||||
let tree = build_operator_tree("a = 1").unwrap();
|
||||
let operators: Vec<_> = tree.iter().map(|node| node.operator().clone()).collect();
|
||||
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
tree.eval_with_context_mut(&mut context).unwrap();
|
||||
assert_eq!(context.get_value("a"), Some(&Value::Int(1)));
|
||||
|
||||
@ -2250,7 +2250,7 @@ fn assignment_lhs_is_identifier() {
|
||||
|
||||
#[test]
|
||||
fn test_variable_assignment_and_iteration() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
eval_with_context_mut("a = 5; b = 5.0", &mut context).unwrap();
|
||||
|
||||
let mut variables: Vec<_> = context.iter_variables().collect();
|
||||
@ -2278,7 +2278,7 @@ fn test_negative_power() {
|
||||
|
||||
#[test]
|
||||
fn test_builtin_functions_context() {
|
||||
let mut context = HashMapContext::new();
|
||||
let mut context = ContextData::new();
|
||||
// Builtin functions are enabled by default for HashMapContext.
|
||||
assert_eq!(eval_with_context("max(1,3)", &context), Ok(Value::from(3)));
|
||||
// Disabling builtin function in Context.
|
||||
|
Loading…
Reference in New Issue
Block a user