diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb46831..055c3d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,62 +1,143 @@ -name: Continuous Integration +name: CI -on: [push, pull_request] +on: + pull_request: + push: + +env: + RUSTFLAGS: -Dwarnings jobs: - build_test_format_lint: - name: ${{matrix.command.name}} (${{matrix.toolchain}}) - runs-on: ubuntu-latest - + precheck_default: + name: Check default + runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: - toolchain: [stable, beta, nightly, 1.46.0] - command: - - name: Check - command: check - args: --all-features - components: "" - - name: Test - command: test - args: --all-features - components: "" - include: - - toolchain: nightly - command: - name: Format - command: fmt - args: --all -- --check - components: rustfmt - - toolchain: nightly - command: - name: Lint - command: clippy - args: --all-features --tests --benches - components: clippy - - toolchain: stable - command: - name: Sync readme - command: sync-readme - args: --check + os: [ubuntu-latest] + rust: [stable] 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') }} + - uses: actions/checkout@master - - name: Install toolchain + - name: Install ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.toolchain }} + toolchain: ${{ matrix.rust }} override: true - default: true - components: ${{matrix.command.components}} + + - 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.56.1 + 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 @@ -64,49 +145,93 @@ jobs: command: install args: cargo-sync-readme - - name: ${{matrix.command.name}} + - name: Sync readme check uses: actions-rs/cargo@v1 with: - command: ${{matrix.command.command}} - args: ${{matrix.command.args}} + command: sync-readme + args: --check - coveralls_io: - name: Coverage - runs-on: ubuntu-latest + 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@v2 - - name: ⚡ Cache - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - uses: actions/checkout@master - - name: Install toolchain + - name: Install ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: ${{ matrix.rust }} override: true - default: true - - name: Install cargo-tarpaulin - uses: actions-rs/install@v0.1 + - name: Rust cache + uses: Swatinem/rust-cache@v2 with: - crate: cargo-tarpaulin - version: latest - use-tool-cache: true + key: ${{ matrix.os }} - - name: Coverage Report with tarpaulin + - name: Check uses: actions-rs/cargo@v1 with: - command: tarpaulin - args: --all --all-features --out Lcov -- --test-threads 1 + command: check + args: --all-features --all --bins --examples --tests --lib - - name: Upload Coverage - uses: coverallsapp/github-action@master + 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: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./lcov.info \ No newline at end of file + 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 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0c473..6164f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,19 @@ ### Contributors +## [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 @@ -30,8 +43,8 @@ My warmhearted thanks goes to: -* [Natan Freeman](https://github.com/NatanFreeman) -* [Claus Matzinger](https://github.com/celaus) + * [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 diff --git a/Cargo.toml b/Cargo.toml index 2e38a1e..3ff05fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/evalexpr" readme = "README.md" license = "MIT" edition = "2018" -rust-version = "1.46.0" +rust-version = "1.56.1" [badges] maintenance = { status = "actively-developed" } diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 5f2be51..13920e1 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -173,6 +173,11 @@ impl Operator { } } + /// Returns true if this operator is unary, i.e. it requires exactly one argument. + pub(crate) fn is_unary(&self) -> bool { + self.max_argument_amount() == Some(1) && *self != Operator::RootNode + } + /// Evaluates the operator with the given arguments and context. pub(crate) fn eval( &self, diff --git a/src/tree/mod.rs b/src/tree/mod.rs index c225994..347f787 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -459,8 +459,13 @@ impl Node { } fn insert_back_prioritized(&mut self, node: Node, is_root_node: bool) -> EvalexprResult<()> { - // println!("Inserting {:?} into {:?}", node.operator, self.operator()); - if self.operator().precedence() < node.operator().precedence() || is_root_node + // println!( + // "Inserting {:?} into {:?}, is_root_node = {is_root_node}", + // node.operator(), + // self.operator() + // ); + // println!("Self is {:?}", self); + if self.operator().precedence() < node.operator().precedence() || node.operator().is_unary() || is_root_node // Right-to-left chaining || (self.operator().precedence() == node.operator().precedence() && !self.operator().is_left_to_right() && !node.operator().is_left_to_right()) { @@ -471,12 +476,15 @@ impl Node { let last_child_operator = self.children.last().unwrap().operator(); if last_child_operator.precedence() - < node.operator().precedence() + < node.operator().precedence() || node.operator().is_unary() // Right-to-left chaining || (last_child_operator.precedence() == node.operator().precedence() && !last_child_operator.is_left_to_right() && !node.operator().is_left_to_right()) { - // println!("Recursing into {:?}", self.children.last().unwrap().operator()); + // println!( + // "Recursing into {:?}", + // self.children.last().unwrap().operator() + // ); // Unwrap cannot fail because is_leaf being false and has_enough_children being true implies that the operator wants and has at least one child self.children .last_mut() diff --git a/tests/integration.rs b/tests/integration.rs index 6748fe3..5786f2f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2145,3 +2145,14 @@ fn test_variable_assignment_and_iteration() { variables.sort_unstable(); assert_eq!(variables, vec!["a".to_string(), "b".to_string()],); } + +#[test] +fn test_negative_power() { + println!("{:?}", build_operator_tree("3^-2").unwrap()); + assert_eq!(eval("3^-2"), Ok(Value::Float(1.0 / 9.0))); + assert_eq!(eval("3^(-2)"), Ok(Value::Float(1.0 / 9.0))); + assert_eq!(eval("-3^2"), Ok(Value::Float(-9.0))); + assert_eq!(eval("-(3)^2"), Ok(Value::Float(-9.0))); + assert_eq!(eval("(-3)^-2"), Ok(Value::Float(1.0 / 9.0))); + assert_eq!(eval("-(3^-2)"), Ok(Value::Float(-1.0 / 9.0))); +}