├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── pytest.ini ├── python ├── Cargo.toml ├── build.rs ├── examples │ └── eval.py ├── rscel.pyi └── src │ ├── cel_py_object.rs │ ├── celpycallable.rs │ ├── frompyobject.rs │ ├── lib.rs │ ├── py_bind_context.rs │ ├── py_cel_context.rs │ ├── py_cel_error.rs │ ├── py_cel_program.rs │ └── py_cel_value.rs ├── rscel-macro ├── Cargo.toml └── src │ ├── lib.rs │ ├── types.rs │ └── types │ ├── dispatch_arg.rs │ ├── dispatch_arg_type.rs │ ├── dispatch_func.rs │ └── dispatch_mod.rs ├── rscel ├── Cargo.toml ├── build.rs ├── examples │ ├── bench.rs │ ├── dumps_ast.rs │ ├── dumps_bc.rs │ ├── eval.rs │ ├── explain.rs │ └── prog2json.rs ├── src │ ├── compiler │ │ ├── ast_node.rs │ │ ├── compiled_prog.rs │ │ ├── compiled_prog │ │ │ └── preresolved.rs │ │ ├── compiler.rs │ │ ├── compiler │ │ │ └── pattern_utils.rs │ │ ├── grammar.rs │ │ ├── mod.rs │ │ ├── source_location.rs │ │ ├── source_range.rs │ │ ├── string_scanner.rs │ │ ├── string_tokenizer.rs │ │ ├── syntax_error.rs │ │ ├── tokenizer.rs │ │ └── tokens.rs │ ├── context │ │ ├── bind_context.rs │ │ ├── default_funcs.rs │ │ ├── default_funcs │ │ │ ├── math.rs │ │ │ ├── math │ │ │ │ ├── abs.rs │ │ │ │ ├── ceil.rs │ │ │ │ ├── floor.rs │ │ │ │ ├── log.rs │ │ │ │ ├── pow.rs │ │ │ │ ├── round.rs │ │ │ │ └── sqrt.rs │ │ │ ├── size.rs │ │ │ ├── string.rs │ │ │ ├── string │ │ │ │ ├── contains.rs │ │ │ │ ├── ends_with.rs │ │ │ │ ├── matches.rs │ │ │ │ ├── split_whitespace.rs │ │ │ │ └── starts_with.rs │ │ │ ├── time_funcs.rs │ │ │ └── time_funcs │ │ │ │ ├── get_date.rs │ │ │ │ ├── get_day_of_month.rs │ │ │ │ ├── get_day_of_week.rs │ │ │ │ ├── get_day_of_year.rs │ │ │ │ ├── get_full_year.rs │ │ │ │ ├── get_hours.rs │ │ │ │ ├── get_milliseconds.rs │ │ │ │ ├── get_minutes.rs │ │ │ │ ├── get_month.rs │ │ │ │ ├── get_seconds.rs │ │ │ │ └── helpers.rs │ │ ├── default_macros.rs │ │ ├── mod.rs │ │ └── type_funcs.rs │ ├── interp │ │ ├── interp.rs │ │ ├── mod.rs │ │ ├── types.rs │ │ └── types │ │ │ ├── bytecode.rs │ │ │ ├── celstackvalue.rs │ │ │ └── rscallable.rs │ ├── lib.rs │ ├── program │ │ ├── mod.rs │ │ └── program_details.rs │ ├── tests │ │ ├── general_tests.rs │ │ ├── mod.rs │ │ ├── neg_index_tests.rs │ │ ├── proto_tests.rs │ │ └── type_prop_tests.rs │ ├── types │ │ ├── cel_byte_code.rs │ │ ├── cel_bytes.rs │ │ ├── cel_error.rs │ │ ├── cel_value.rs │ │ ├── cel_value_dyn.rs │ │ └── mod.rs │ └── utils │ │ ├── eval_utils.rs │ │ ├── ident_filter.rs │ │ ├── mod.rs │ │ └── scoped_counter.rs └── test │ └── protos │ └── test.proto ├── rust-toolchain.toml ├── test ├── cel_spec_tests │ ├── __init__.py │ ├── proto │ │ ├── __init__.py │ │ ├── google │ │ │ ├── api │ │ │ │ └── expr │ │ │ │ │ └── v1alpha1 │ │ │ │ │ ├── checked_pb2.py │ │ │ │ │ ├── eval_pb2.py │ │ │ │ │ ├── explain_pb2.py │ │ │ │ │ ├── syntax_pb2.py │ │ │ │ │ └── value_pb2.py │ │ │ └── rpc │ │ │ │ └── status_pb2.py │ │ ├── simple_pb2.py │ │ ├── test │ │ │ └── v1 │ │ │ │ ├── proto2 │ │ │ │ └── test_all_types_pb2.py │ │ │ │ └── proto3 │ │ │ │ └── test_all_types_pb2.py │ │ ├── test_all_types_proto2_pb2.py │ │ └── test_all_types_proto3_pb2.py │ └── simple-test-data │ │ ├── README.md │ │ ├── basic.textproto │ │ ├── bindings_ext.textproto │ │ ├── comparisons.textproto │ │ ├── conversions.textproto │ │ ├── dynamic.textproto │ │ ├── encoders_ext.textproto │ │ ├── enums.textproto │ │ ├── fields.textproto │ │ ├── fp_math.textproto │ │ ├── integer_math.textproto │ │ ├── lists.textproto │ │ ├── logic.textproto │ │ ├── macros.textproto │ │ ├── math_ext.textproto │ │ ├── namespace.textproto │ │ ├── optionals.textproto │ │ ├── parse.textproto │ │ ├── plumbing.textproto │ │ ├── proto2.textproto │ │ ├── proto2_ext.textproto │ │ ├── proto3.textproto │ │ ├── string.textproto │ │ ├── string_ext.textproto │ │ ├── timestamps.textproto │ │ ├── unknowns.textproto │ │ └── wrappers.textproto ├── google │ ├── api │ │ └── expr │ │ │ └── v1alpha1 │ │ │ ├── checked_pb2.py │ │ │ ├── eval_pb2.py │ │ │ ├── explain_pb2.py │ │ │ ├── syntax_pb2.py │ │ │ └── value_pb2.py │ └── rpc │ │ └── status_pb2.py ├── test_cel_spec.py └── test_rscel.py └── wasm ├── Cargo.toml ├── Makefile ├── example ├── .babelrc ├── package-lock.json ├── package.json ├── src │ ├── app.tsx │ ├── components │ │ ├── CelComponent.css │ │ ├── CelComponent.tsx │ │ ├── UnitTest.tsx │ │ ├── UnitTests.tsx │ │ └── testCases.ts │ ├── index.html │ └── index.js ├── tsconfig.json └── webpack.config.js ├── src ├── from_jsvalue.rs ├── into_jsvalue.rs ├── lib.rs ├── object_iter.rs ├── types.rs ├── types │ ├── api.rs │ ├── details_result.rs │ ├── eval_result.rs │ ├── wasm_cel_error.rs │ └── wasm_program_details.rs └── utils.rs └── tests ├── package-lock.json ├── package.json └── rscel.test.js /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | pyrscel = "run maturin develop" 3 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main", "develop"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - uses: Swatinem/rust-cache@v2 24 | 25 | - name: Build 26 | run: cargo build --workspace 27 | 28 | test-all: 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - name: Install Protoc 33 | uses: arduino/setup-protoc@v3 34 | 35 | - uses: actions/checkout@v3 36 | 37 | - uses: Swatinem/rust-cache@v2 38 | 39 | - name: Install wasm-pack 40 | run: cargo install wasm-pack 41 | 42 | - name: Run tests 43 | run: make run-all-tests 44 | 45 | deploy-rscel: 46 | runs-on: ubuntu-latest 47 | needs: test-all 48 | if: success() && github.ref == 'refs/heads/main' 49 | steps: 50 | - uses: actions/checkout@v3 51 | 52 | - uses: Swatinem/rust-cache@v2 53 | 54 | - uses: actions-rs/toolchain@v1 55 | with: 56 | toolchain: stable 57 | override: true 58 | 59 | - name: publish rscel 60 | run: cargo publish -p rscel --token ${{ secrets.CRATES_IO_API_KEY }} 61 | 62 | deploy-rscel-macro: 63 | runs-on: ubuntu-latest 64 | needs: test-all 65 | if: success() && github.ref == 'refs/heads/main' 66 | steps: 67 | - uses: actions/checkout@v3 68 | 69 | - uses: Swatinem/rust-cache@v2 70 | 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | toolchain: stable 74 | override: true 75 | 76 | - name: publish rscel-macro 77 | run: cargo publish -p rscel-macro --token ${{ secrets.CRATES_IO_API_KEY }} 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .env/ 17 | 18 | #mac shit 19 | .DS_Store 20 | 21 | __pycache__ 22 | node_modules 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rscel", "python", "wasm", "rscel-macro"] 3 | default-members = ["rscel"] 4 | resolver = "2" 5 | 6 | [workspace.package] 7 | version = "1.0.5" 8 | edition = "2021" 9 | description = "Cel interpreter in rust" 10 | license = "MIT" 11 | 12 | [profile.release-with-debug] 13 | inherits = "release" 14 | debug = true 15 | lto = false 16 | 17 | [profile.release] 18 | lto = true 19 | 20 | [workspace.dependencies] 21 | chrono = { version = "0.4.39", features = ["serde"] } 22 | serde_json = { version = "1.0.138", features = ["raw_value"] } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matt B 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CARGO_ARGS?= 2 | PYTEST_ARGS?= 3 | 4 | .PHONY: default 5 | default: build 6 | 7 | build: 8 | cargo build $(CARGO_ARGS) 9 | 10 | 11 | .env: 12 | python3 -m venv .env 13 | . .env/bin/activate && pip install maturin pytest protobuf 14 | 15 | wasm-binding: 16 | $(MAKE_COMMAND) -C wasm wasm-binding 17 | 18 | wasm-binding-release: 19 | $(MAKE_COMMAND) -C wasm wasm-binding-release 20 | 21 | python-binding: .env 22 | . .env/bin/activate && cd python && maturin build $(CARGO_ARGS) 23 | 24 | python-binding-release: .env 25 | . .env/bin/activate && cd python && maturin build --release $(CARGO_ARGS) 26 | 27 | wasm-example: 28 | $(MAKE_COMMAND) -C wasm wasm-example 29 | 30 | wasm-example-release: 31 | $(MAKE_COMMAND) -C wasm wasm-example-release 32 | 33 | .PHONY: all 34 | all: wasm-binding python-binding build 35 | 36 | run-tests: 37 | RSCEL_TEST_PROTO=1 cargo test -q $(CARGO_ARGS) 38 | 39 | run-no-feature-tests: 40 | cargo test -q --no-default-features $(CARGO_ARGS) 41 | 42 | run-python-tests: .env python-binding 43 | . .env/bin/activate && \ 44 | pip install --force-reinstall target/wheels/$(shell ls target/wheels) && \ 45 | cd test && \ 46 | python -m pytest test_rscel.py $(PYTEST_ARGS) 47 | 48 | run-all-python-tests: .env python-binding 49 | . .env/bin/activate && \ 50 | pip install --force-reinstall target/wheels/$(shell ls target/wheels) && \ 51 | cd test && \ 52 | python -m pytest test*.py $(PYTEST_ARGS) 53 | 54 | run-wasm-tests: 55 | $(MAKE_COMMAND) -C wasm wasm-tests 56 | 57 | .PHONY: run-all-tests 58 | run-all-tests: run-tests run-no-feature-tests run-python-tests run-wasm-tests 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rscel 2 | 3 | RsCel is a CEL evaluator written in Rust. CEL is a google project that 4 | describes a turing-incomplete language that can be used to evaluate 5 | a user provdided expression. The language specification can be found 6 | [here](https://github.com/google/cel-spec/blob/master/doc/langdef.md). 7 | 8 | The design goals of this project were are as follows: 9 | * Flexible enough to allow for a user to bend the spec if needed 10 | * Sandbox'ed in such a way that only specific values can be bound 11 | * Can be used as a wasm depenedency (or other ffi) 12 | 13 | The basic example of how to use: 14 | ```rust 15 | use rscel::{CelContext, BindContext}; 16 | 17 | let mut ctx = CelContext::new(); 18 | let mut exec_ctx = BindContext::new(); 19 | 20 | ctx.add_program_str("main", "foo + 3").unwrap(); 21 | exec_ctx.bind_param("foo", 3.into()); // convert to CelValue 22 | 23 | let res = ctx.exec("main", &exec_ctx).unwrap(); // CelValue::Int(6) 24 | assert_eq!(res, 6.into()); 25 | ``` 26 | 27 | As of 0.10.0 binding protobuf messages from the protobuf crate is now available! Given 28 | the following protobuf message: 29 | ```protobuf 30 | 31 | message Point { 32 | int32 x = 1; 33 | int32 y = 2; 34 | } 35 | 36 | ``` 37 | The following code can be used to evaluate a CEL expression on a Point message: 38 | 39 | ```rust 40 | use rscel::{CelContext, BindContext}; 41 | 42 | // currently rscel required protobuf messages to be in a box 43 | let p = Box::new(protos::Point::new()); 44 | p.x = 4; 45 | p.y = 5; 46 | 47 | let mut ctx = CelContext::new(); 48 | let mut exec_ctx = BindContext::new(); 49 | 50 | ctx.add_program_str("main", "p.x + 3").unwrap(); 51 | exec_ctx.bind_protobuf_msg("p", p); 52 | 53 | assert_eq!(ctx.exec("main", &exec_ctx), 7.into()); 54 | 55 | ``` 56 | 57 | Build status: [![Rust](https://github.com/1BADragon/rscel/actions/workflows/rust.yml/badge.svg)](https://github.com/1BADragon/rscel/actions/workflows/rust.yml) 58 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_classes=Check 3 | -------------------------------------------------------------------------------- /python/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rscel_python" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | description = "Python bindings for the rscel package" 6 | license = { workspace = true } 7 | 8 | [lib] 9 | name = "rscel" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | rscel = { path = "../rscel" } 14 | pyo3 = { version = "0.23.4", features = [ 15 | "py-clone", 16 | "extension-module", 17 | "chrono", 18 | ] } 19 | chrono = { workspace = true } 20 | serde_json = { workspace = true } 21 | bincode = "1.3.3" 22 | 23 | [build-dependencies] 24 | pyo3-build-config = "0.23.4" 25 | -------------------------------------------------------------------------------- /python/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | pyo3_build_config::add_extension_module_link_args(); 3 | } 4 | -------------------------------------------------------------------------------- /python/examples/eval.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import rscel 3 | import time 4 | 5 | if len(sys.argv) == 2: 6 | print(rscel.eval(sys.argv[1], {})) 7 | elif len(sys.argv) == 3: 8 | print(rscel.eval(*sys.argv[1:])) 9 | else: 10 | start_time = time.time() 11 | for i in range(10000): 12 | prog = "foo + 3" 13 | assert rscel.eval(prog, {"foo": i}) == (i + 3) 14 | print(f'{time.time() - start_time}') 15 | 16 | start_time = time.time() 17 | for i in range(10000): 18 | c = rscel.CelContext() 19 | c.add_program_str('entry', "foo + 3") 20 | b = rscel.BindContext() 21 | b.bind('foo', i) 22 | assert c.exec('entry', b) == (i + 3) 23 | print(f'{time.time() - start_time}') 24 | 25 | p = rscel.CelProgram() 26 | p.add_source("foo + 3") 27 | s = p.serialize_to_bincode() 28 | start_time = time.time() 29 | for i in range(10000): 30 | c = rscel.CelContext() 31 | p = rscel.CelProgram() 32 | p.add_serialized_bincode(s) 33 | 34 | c.add_program('entry', p) 35 | 36 | b = rscel.BindContext() 37 | b.bind('foo', i) 38 | assert c.exec('entry', b) == (i + 3) 39 | print(f'{time.time() - start_time}') 40 | -------------------------------------------------------------------------------- /python/rscel.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Tuple 2 | 3 | 4 | CelBasicType = int | float | str | bool | None 5 | CelArrayType = list[CelBasicType | 'CelArrayType' | 'CelDict'] 6 | CelDict = dict[str, 'CelValue'] 7 | CelValue = CelDict | CelArrayType | CelBasicType 8 | 9 | CelCallable = Callable[[*Tuple[CelValue, ...]], CelValue] 10 | 11 | CelBinding = dict[str, CelValue | CelCallable | Any] 12 | 13 | def eval(prog: str, binding: CelBinding) -> CelValue: 14 | ... 15 | 16 | class CelProgram: 17 | def __init__(self): 18 | ... 19 | 20 | def add_source(self, source: str): 21 | ... 22 | 23 | def add_serialized_json(self, source: str): 24 | ... 25 | 26 | def add_serialized_bincode(self, bincode: bytes): 27 | ... 28 | 29 | def serialize_to_json(self) -> str: 30 | ... 31 | 32 | def serialize_to_bincode(self) -> bytes: 33 | ... 34 | 35 | def details_json(self) -> str: 36 | ... 37 | 38 | class BindContext: 39 | def __init__(self): 40 | ... 41 | 42 | def bind_param(self, name: str, val: CelValue): 43 | ... 44 | 45 | def bind_func(self, name: str, val: Callable[[Any, Any]]): 46 | ... 47 | 48 | def bind(self, name: str, val: Any): 49 | ... 50 | 51 | class CelContext: 52 | def __init__(self): 53 | ... 54 | 55 | def add_program_string(self, name: str, source: str): 56 | ... 57 | 58 | def add_program(self, name: str, prog: "CelProgram"): 59 | ... 60 | 61 | def exec(self, name: str, bindings: BindContext) -> CelValue: 62 | ... 63 | -------------------------------------------------------------------------------- /python/src/cel_py_object.rs: -------------------------------------------------------------------------------- 1 | use pyo3::{types::PyAnyMethods, PyObject, Python}; 2 | use rscel::{CelError, CelValue, CelValueDyn}; 3 | use std::fmt; 4 | 5 | use crate::py_cel_value::PyCelValue; 6 | 7 | pub struct CelPyObject { 8 | inner: PyObject, 9 | } 10 | 11 | impl CelPyObject { 12 | pub fn new(inner: PyObject) -> Self { 13 | Self { inner } 14 | } 15 | 16 | pub fn as_inner(&self) -> &PyObject { 17 | &self.inner 18 | } 19 | } 20 | 21 | impl fmt::Display for CelPyObject { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "CelPyObject {}", self.inner) 24 | } 25 | } 26 | 27 | impl fmt::Debug for CelPyObject { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | write!(f, "CelPyObject {}", self.inner) 30 | } 31 | } 32 | 33 | impl CelValueDyn for CelPyObject { 34 | fn as_type(&self) -> CelValue { 35 | Python::with_gil(|py| { 36 | let inner = self.inner.bind(py); 37 | let name = inner.repr().unwrap(); 38 | 39 | CelValue::Type(format!("pyobj-{}", name)) 40 | }) 41 | } 42 | 43 | fn access(&self, key: &str) -> CelValue { 44 | Python::with_gil(|py| { 45 | let obj = self.inner.bind(py); 46 | 47 | match obj.getattr(key) { 48 | Ok(res) => match res.extract::() { 49 | Ok(val) => val.into_inner(), 50 | Err(err) => CelValue::from_err(CelError::Misc(err.to_string())), 51 | }, 52 | Err(err) => CelValue::from_err(CelError::Misc(err.to_string())), 53 | } 54 | }) 55 | } 56 | 57 | fn eq(&self, rhs: &CelValue) -> CelValue { 58 | let lhs_type = self.as_type(); 59 | let rhs_type = self.as_type(); 60 | 61 | if let CelValue::Dyn(rhs) = rhs { 62 | if let Some(rhs_obj) = rhs.any_ref().downcast_ref::() { 63 | return Python::with_gil(|py| { 64 | let lhs_obj = self.inner.bind(py); 65 | let rhs_obj = rhs_obj.inner.bind(py); 66 | 67 | match lhs_obj.eq(rhs_obj) { 68 | Ok(res) => CelValue::from_bool(res), 69 | Err(err) => CelValue::from_err(CelError::Misc(err.to_string())), 70 | } 71 | }); 72 | } 73 | } 74 | 75 | CelValue::from_err(CelError::invalid_op(&format!( 76 | "Invalid op == between {} and {}", 77 | lhs_type, rhs_type 78 | ))) 79 | } 80 | 81 | fn is_truthy(&self) -> bool { 82 | Python::with_gil(|py| { 83 | let inner = self.inner.bind(py); 84 | 85 | match inner.is_truthy() { 86 | Ok(res) => res, 87 | Err(_) => false, // this is just going to have to work. Basically is the equiv of calling bool(obj) and it throwing 88 | } 89 | }) 90 | } 91 | 92 | fn any_ref<'a>(&'a self) -> &'a dyn std::any::Any { 93 | self 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /python/src/celpycallable.rs: -------------------------------------------------------------------------------- 1 | use crate::py_cel_value::{PyCelValue, PyCelValueRef}; 2 | use pyo3::{types::PyTuple, Bound, IntoPyObject, Py, PyAny, PyErr, Python}; 3 | use rscel::{CelError, CelValue}; 4 | 5 | pub struct CelPyCallable { 6 | func: Py, 7 | } 8 | 9 | impl CelPyCallable { 10 | pub fn new(func: Py) -> CelPyCallable { 11 | CelPyCallable { func } 12 | } 13 | } 14 | 15 | impl FnOnce<(CelValue, Vec)> for CelPyCallable { 16 | type Output = CelValue; 17 | 18 | extern "rust-call" fn call_once(self, args: (CelValue, Vec)) -> Self::Output { 19 | Python::with_gil(|py| { 20 | match self.func.call( 21 | py, 22 | PyTuple::new( 23 | py, 24 | &[args.0] 25 | .iter() 26 | .filter(|x| !x.is_null()) 27 | .map(|x| PyCelValueRef::new(x).into_pyobject(py)) 28 | .chain( 29 | args.1 30 | .into_iter() 31 | .map(|x| PyCelValueRef::new(&x).into_pyobject(py)), 32 | ) 33 | .collect::>, PyErr>>() 34 | .expect("argument collection failed"), 35 | ) 36 | .expect("pytuple construction failed"), 37 | None, 38 | ) { 39 | Ok(val) => val.extract::(py).unwrap().into_inner(), 40 | Err(val) => CelValue::from_err(CelError::runtime(&val.to_string())), 41 | } 42 | }) 43 | } 44 | } 45 | 46 | impl FnMut<(CelValue, Vec)> for CelPyCallable { 47 | extern "rust-call" fn call_mut(&mut self, args: (CelValue, Vec)) -> Self::Output { 48 | Python::with_gil(|py| { 49 | match self.func.call( 50 | py, 51 | PyTuple::new( 52 | py, 53 | &[args.0] 54 | .iter() 55 | .filter(|x| !x.is_null()) 56 | .map(|x| { 57 | PyCelValueRef::new(x) 58 | .into_pyobject(py) 59 | .map(|o| o.into_any()) 60 | }) 61 | .chain( 62 | args.1 63 | .into_iter() 64 | .map(|x| PyCelValueRef::new(&x).into_pyobject(py)), 65 | ) 66 | .collect::>, PyErr>>() 67 | .expect("argument collection failed"), 68 | ) 69 | .expect("pytuple construction failed"), 70 | None, 71 | ) { 72 | Ok(val) => val.extract::(py).unwrap().into_inner(), 73 | Err(val) => CelValue::from_err(CelError::runtime(&val.to_string())), 74 | } 75 | }) 76 | } 77 | } 78 | 79 | impl Fn<(CelValue, Vec)> for CelPyCallable { 80 | extern "rust-call" fn call(&self, args: (CelValue, Vec)) -> Self::Output { 81 | Python::with_gil(|py| { 82 | match self.func.call( 83 | py, 84 | PyTuple::new( 85 | py, 86 | &[args.0] 87 | .iter() 88 | .filter(|x| !x.is_null()) 89 | .map(|x| { 90 | PyCelValueRef::new(x) 91 | .into_pyobject(py) 92 | .map(|o| o.into_any()) 93 | }) 94 | .chain( 95 | args.1 96 | .into_iter() 97 | .map(|x| PyCelValueRef::new(&x).into_pyobject(py)), 98 | ) 99 | .collect::>, PyErr>>() 100 | .expect("argument collection failed"), 101 | ) 102 | .expect("pytuple construction failed"), 103 | None, 104 | ) { 105 | Ok(val) => val.extract::(py).unwrap().into_inner(), 106 | Err(val) => CelValue::from_err(CelError::runtime(&val.to_string())), 107 | } 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /python/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(fn_traits)] 2 | #![feature(unboxed_closures)] 3 | use py_cel_error::PyCelError; 4 | use py_cel_value::PyCelValue; 5 | use rscel::{BindContext, CelContext, CelValue}; 6 | 7 | use pyo3::{ 8 | exceptions::PyException, 9 | prelude::*, 10 | types::{PyDict, PyString}, 11 | IntoPyObjectExt, 12 | }; 13 | 14 | mod cel_py_object; 15 | mod celpycallable; 16 | mod frompyobject; 17 | mod py_bind_context; 18 | mod py_cel_context; 19 | mod py_cel_error; 20 | mod py_cel_program; 21 | mod py_cel_value; 22 | 23 | use celpycallable::CelPyCallable; 24 | use py_bind_context::PyBindContext; 25 | use py_cel_context::PyCelContext; 26 | use py_cel_program::PyCelProgram; 27 | 28 | /* Eval entry point */ 29 | #[pyfunction] 30 | fn eval(py: Python<'_>, prog_str: String, bindings: &Bound) -> PyResult { 31 | let callables = { 32 | let mut callables = Vec::new(); 33 | for keyobj in bindings.keys().iter() { 34 | let key = keyobj.downcast::()?; 35 | let val = bindings.get_item(key).unwrap().unwrap().clone(); 36 | 37 | if val.is_callable() { 38 | callables.push((key.to_str()?.to_string(), CelPyCallable::new(val.into()))); 39 | } 40 | } 41 | callables 42 | }; 43 | let mut ctx = CelContext::new(); 44 | let mut exec_ctx = BindContext::new(); 45 | 46 | if let Err(e) = ctx.add_program_str("entry", &prog_str) { 47 | return Err(PyCelError::new(e).into()); 48 | } 49 | 50 | for keyobj in bindings.keys().iter() { 51 | let key = keyobj.downcast::()?; 52 | 53 | let val = bindings.get_item(key).unwrap().unwrap(); 54 | 55 | if !val.is_callable() { 56 | exec_ctx.bind_param(key.to_str()?, val.extract::()?.into_inner()) 57 | } 58 | } 59 | 60 | for callable in callables.iter() { 61 | exec_ctx.bind_func(&callable.0, &callable.1); 62 | } 63 | 64 | let res = ctx.exec("entry", &exec_ctx); 65 | 66 | match res { 67 | Ok(res) => Ok(PyCelValue::new(res).into_pyobject_or_pyerr(py)?.unbind()), 68 | Err(err) => Err(PyException::new_err(err.to_string())), 69 | } 70 | } 71 | 72 | /* Module decl */ 73 | #[pymodule] 74 | #[pyo3(name = "rscel")] 75 | fn rscel_module(_py: Python<'_>, m: &Bound) -> PyResult<()> { 76 | m.add_function(wrap_pyfunction!(eval, m)?)?; 77 | m.add_class::()?; 78 | m.add_class::()?; 79 | m.add_class::()?; 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /python/src/py_bind_context.rs: -------------------------------------------------------------------------------- 1 | use super::py_cel_value::PyCelValue; 2 | use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny, PyResult}; 3 | use rscel::CelValue; 4 | use std::collections::HashMap; 5 | 6 | use crate::celpycallable::CelPyCallable; 7 | 8 | #[pyclass(name = "BindContext")] 9 | pub struct PyBindContext { 10 | bindings: HashMap, 11 | funcs: HashMap, 12 | } 13 | 14 | #[pymethods] 15 | impl PyBindContext { 16 | #[new] 17 | pub fn new() -> PyBindContext { 18 | PyBindContext { 19 | bindings: HashMap::new(), 20 | funcs: HashMap::new(), 21 | } 22 | } 23 | 24 | pub fn bind_param(&mut self, name: &str, val: PyCelValue) { 25 | self.bindings.insert(name.to_owned(), val.into_inner()); 26 | } 27 | 28 | pub fn bind_func(&mut self, name: &str, val: &Bound) { 29 | self.funcs 30 | .insert(name.to_owned(), CelPyCallable::new(val.clone().unbind())); 31 | } 32 | 33 | pub fn bind(&mut self, name: &str, val: &Bound) -> PyResult<()> { 34 | if val.is_callable() { 35 | self.bind_func(name, val); 36 | } else { 37 | self.bind_param(name, val.extract()?); 38 | } 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl PyBindContext { 45 | pub fn bindings(&self) -> &HashMap { 46 | &self.bindings 47 | } 48 | 49 | pub fn funcs(&self) -> &HashMap { 50 | &self.funcs 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /python/src/py_cel_context.rs: -------------------------------------------------------------------------------- 1 | use crate::{py_cel_program::PyCelProgram, py_cel_value::PyCelValue}; 2 | 3 | use super::py_bind_context::PyBindContext; 4 | use pyo3::{ 5 | exceptions::PyValueError, pyclass, pymethods, IntoPyObjectExt, PyObject, PyRefMut, PyResult, 6 | }; 7 | use rscel::{BindContext, CelContext}; 8 | 9 | #[pyclass(name = "CelContext")] 10 | pub struct PyCelContext { 11 | ctx: CelContext, 12 | } 13 | 14 | #[pymethods] 15 | impl PyCelContext { 16 | #[new] 17 | pub fn new() -> PyCelContext { 18 | PyCelContext { 19 | ctx: CelContext::new(), 20 | } 21 | } 22 | 23 | pub fn add_program_str(mut slf: PyRefMut<'_, Self>, name: &str, prog: &str) -> PyResult<()> { 24 | if let Err(err) = slf.ctx.add_program_str(name, prog) { 25 | Err(PyValueError::new_err(err.to_string())) 26 | } else { 27 | Ok(()) 28 | } 29 | } 30 | 31 | pub fn add_program( 32 | mut slf: PyRefMut<'_, Self>, 33 | name: &str, 34 | prog: PyRefMut, 35 | ) -> PyResult<()> { 36 | match prog.as_inner() { 37 | Some(p) => Ok(slf.ctx.add_program(name, p.clone())), 38 | None => Err(PyValueError::new_err("Program not populated")), 39 | } 40 | } 41 | 42 | pub fn exec( 43 | mut slf: PyRefMut<'_, Self>, 44 | name: &str, 45 | bindings: &PyBindContext, 46 | ) -> PyResult { 47 | let mut bindctx = BindContext::new(); 48 | 49 | for (key, val) in bindings.bindings().iter() { 50 | bindctx.bind_param(&key, val.clone()); 51 | } 52 | 53 | for (key, val) in bindings.funcs().iter() { 54 | bindctx.bind_func(&key, val); 55 | } 56 | 57 | match slf.ctx.exec(name, &bindctx) { 58 | Ok(val) => Ok(PyCelValue::new(val) 59 | .into_pyobject_or_pyerr(slf.py())? 60 | .unbind()), 61 | Err(err) => Err(PyValueError::new_err(err.to_string())), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /python/src/py_cel_error.rs: -------------------------------------------------------------------------------- 1 | use pyo3::{exceptions::PyException, PyErr}; 2 | use rscel::CelError; 3 | 4 | pub struct PyCelError { 5 | inner: CelError, 6 | } 7 | 8 | impl PyCelError { 9 | pub fn new(inner: CelError) -> Self { 10 | Self { inner } 11 | } 12 | } 13 | 14 | impl From for PyErr { 15 | fn from(err: PyCelError) -> PyErr { 16 | PyErr::new::(format!("{}", err.inner)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /python/src/py_cel_program.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use pyo3::{exceptions::PyValueError, pyclass, pymethods, PyRefMut, PyResult}; 4 | use rscel::Program; 5 | 6 | #[pyclass(name = "CelProgram")] 7 | pub struct PyCelProgram { 8 | program: Option, 9 | } 10 | 11 | #[pymethods] 12 | impl PyCelProgram { 13 | #[new] 14 | fn new() -> PyCelProgram { 15 | PyCelProgram { program: None } 16 | } 17 | 18 | fn add_source(mut slf: PyRefMut<'_, PyCelProgram>, source: &str) -> PyResult<()> { 19 | slf.program = Some(match Program::from_source(source) { 20 | Ok(p) => p, 21 | Err(e) => return Err(PyValueError::new_err(format!("{e}"))), 22 | }); 23 | 24 | Ok(()) 25 | } 26 | 27 | fn add_serialized_json( 28 | mut slf: PyRefMut<'_, PyCelProgram>, 29 | serialized_json: &str, 30 | ) -> PyResult<()> { 31 | match serde_json::from_str(serialized_json) { 32 | Ok(p) => { 33 | slf.program = Some(p); 34 | Ok(()) 35 | } 36 | Err(e) => Err(PyValueError::new_err(format!("{e}"))), 37 | } 38 | } 39 | 40 | fn add_serialized_bincode( 41 | mut slf: PyRefMut<'_, PyCelProgram>, 42 | serialized_bincode: &[u8], 43 | ) -> PyResult<()> { 44 | match bincode::deserialize(serialized_bincode) { 45 | Ok(p) => { 46 | slf.program = Some(p); 47 | Ok(()) 48 | } 49 | Err(e) => Err(PyValueError::new_err(format!("{e}"))), 50 | } 51 | } 52 | 53 | fn serialize_to_json(slf: PyRefMut<'_, PyCelProgram>) -> PyResult { 54 | if let Some(program) = &slf.program { 55 | match serde_json::to_string(&program) { 56 | Ok(s) => Ok(s), 57 | Err(e) => Err(PyValueError::new_err(format!("{e}"))), 58 | } 59 | } else { 60 | Err(PyValueError::new_err("Program source not set")) 61 | } 62 | } 63 | 64 | fn serialize_to_bincode(slf: PyRefMut<'_, PyCelProgram>) -> PyResult> { 65 | if let Some(program) = &slf.program { 66 | match bincode::serialize(program) { 67 | Ok(b) => Ok(Cow::Owned(b)), 68 | Err(e) => Err(PyValueError::new_err(format!("{e}"))), 69 | } 70 | } else { 71 | Err(PyValueError::new_err("Program source not set")) 72 | } 73 | } 74 | 75 | fn details_json(slf: PyRefMut<'_, PyCelProgram>, pretty: bool) -> PyResult { 76 | if let Some(program) = &slf.program { 77 | match if pretty { 78 | serde_json::to_string_pretty(program.details().ast().unwrap()) 79 | } else { 80 | serde_json::to_string(program.details().ast().unwrap()) 81 | } { 82 | Ok(s) => Ok(s), 83 | Err(e) => Err(PyValueError::new_err(format!("{e}"))), 84 | } 85 | } else { 86 | Err(PyValueError::new_err("Program source not set")) 87 | } 88 | } 89 | } 90 | 91 | impl PyCelProgram { 92 | pub fn as_inner(&self) -> Option<&Program> { 93 | self.program.as_ref() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /python/src/py_cel_value.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | 4 | use pyo3::{types::PyBytes, IntoPyObject, Python}; 5 | use pyo3::{Bound, IntoPyObjectExt, PyAny, PyErr}; 6 | use rscel::CelValue; 7 | 8 | use crate::cel_py_object::CelPyObject; 9 | 10 | pub struct PyCelValue(CelValue); 11 | 12 | pub struct PyCelValueRef<'a>(&'a CelValue); 13 | 14 | impl<'a> PyCelValueRef<'a> { 15 | pub fn new(inner: &'a CelValue) -> Self { 16 | Self(inner) 17 | } 18 | } 19 | 20 | impl rscel::CelValueDyn for PyCelValue { 21 | fn as_type(&self) -> CelValue { 22 | ::as_type(&self.0) 23 | } 24 | 25 | fn access(&self, key: &str) -> CelValue { 26 | ::access(&self.0, key) 27 | } 28 | 29 | fn eq(&self, rhs_val: &CelValue) -> CelValue { 30 | ::eq(&self.0, rhs_val) 31 | } 32 | 33 | fn is_truthy(&self) -> bool { 34 | ::is_truthy(&self.0) 35 | } 36 | 37 | fn any_ref<'a>(&'a self) -> &'a dyn std::any::Any { 38 | ::any_ref(&self.0) 39 | } 40 | } 41 | 42 | impl PyCelValue { 43 | pub fn new(inner: CelValue) -> Self { 44 | Self(inner) 45 | } 46 | 47 | pub fn into_inner(self) -> CelValue { 48 | self.0 49 | } 50 | } 51 | 52 | impl<'py> IntoPyObject<'py> for PyCelValue { 53 | type Error = PyErr; 54 | type Target = PyAny; 55 | type Output = Bound<'py, PyAny>; 56 | 57 | fn into_pyobject(self, py: Python<'py>) -> Result { 58 | PyCelValueRef::new(&self.0).into_pyobject(py) 59 | } 60 | } 61 | 62 | impl fmt::Display for PyCelValue { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | write!(f, "{}", self.0) 65 | } 66 | } 67 | 68 | impl fmt::Debug for PyCelValue { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | write!(f, "{}", self.0) 71 | } 72 | } 73 | 74 | impl<'py, 'a> IntoPyObject<'py> for PyCelValueRef<'a> { 75 | type Target = PyAny; 76 | type Output = Bound<'py, Self::Target>; 77 | type Error = PyErr; 78 | 79 | fn into_pyobject(self, py: Python<'py>) -> Result { 80 | use crate::CelValue::*; 81 | 82 | match self.0 { 83 | Int(i) => i.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 84 | UInt(i) => i.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 85 | Float(f) => f.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 86 | Bool(b) => b 87 | .into_pyobject_or_pyerr(py) 88 | .map(|o| o.to_owned().into_any()), 89 | String(s) => s.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 90 | Bytes(s) => Ok(PyBytes::new(py, s.as_slice()).into_any()), 91 | List(l) => l 92 | .into_iter() 93 | .map(|x| { 94 | PyCelValueRef(x) 95 | .into_pyobject_or_pyerr(py) 96 | .map(|o| o.into_any()) 97 | }) 98 | .collect::, PyErr>>()? 99 | .into_pyobject_or_pyerr(py) 100 | .map(|o| o.into_any()), 101 | Map(m) => m 102 | .into_iter() 103 | .map(|(k, v)| PyCelValueRef(v).into_pyobject_or_pyerr(py).map(|o| (k, o))) 104 | .collect::, PyErr>>()? 105 | .into_pyobject_or_pyerr(py) 106 | .map(|o| o.into_any()), 107 | TimeStamp(ts) => ts.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 108 | Duration(d) => d.into_pyobject_or_pyerr(py).map(|o| o.into_any()), 109 | Null => Ok(py.None().bind(py).to_owned()), 110 | Dyn(d) => { 111 | match d.any_ref().downcast_ref::() { 112 | Some(obj) => Ok(obj.as_inner().clone().bind(py).to_owned()), 113 | // This *should* never happen. If this downcase were to fail that would 114 | // mean that the data in this dyn isn't a CelPyObject which should be impossible 115 | // for these bidnings 116 | None => Ok(py.None().bind(py).to_owned()), 117 | } 118 | } 119 | _ => Ok(py.None().bind(py).to_owned()), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /rscel-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rscel-macro" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | description = { workspace = true } 6 | license = { workspace = true } 7 | readme = "../README.md" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.93" 14 | quote = "1.0.38" 15 | syn = { version = "2.0.98", features = ["full"] } 16 | -------------------------------------------------------------------------------- /rscel-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, ItemMod}; 3 | use types::DispatchMod; 4 | 5 | mod types; 6 | 7 | #[proc_macro_attribute] 8 | pub fn dispatch(_attr: TokenStream, item: TokenStream) -> TokenStream { 9 | // Parse the module with functions 10 | let input = parse_macro_input!(item as ItemMod); 11 | 12 | DispatchMod::from_mod(input).into_token_stream() 13 | } 14 | -------------------------------------------------------------------------------- /rscel-macro/src/types.rs: -------------------------------------------------------------------------------- 1 | mod dispatch_arg; 2 | mod dispatch_arg_type; 3 | 4 | mod dispatch_func; 5 | mod dispatch_mod; 6 | 7 | pub use dispatch_mod::DispatchMod; 8 | -------------------------------------------------------------------------------- /rscel-macro/src/types/dispatch_arg.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro2::Span; 4 | use syn::{token, FnArg, Ident, Pat, PatIdent, PatTupleStruct, PathSegment}; 5 | 6 | use super::dispatch_arg_type::DispatchArgType; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct DispatchArg { 10 | this: bool, 11 | arg_type: DispatchArgType, 12 | } 13 | 14 | impl DispatchArg { 15 | pub fn from_fnarg(arg: FnArg) -> Self { 16 | match &arg { 17 | FnArg::Receiver(_) => panic!("Receiver args not allowed"), 18 | FnArg::Typed(t) => { 19 | let ident = match t.pat.as_ref() { 20 | Pat::Ident(i) => i.ident.to_string(), 21 | _ => panic!("Only basic args allowed"), 22 | }; 23 | 24 | DispatchArg { 25 | this: ident == "this", 26 | arg_type: DispatchArgType::from_type(&t.ty), 27 | } 28 | } 29 | } 30 | } 31 | 32 | pub fn mangle_sym(&self) -> String { 33 | let mut s = if self.this { 34 | String::from_str("z").unwrap() 35 | } else { 36 | String::new() 37 | }; 38 | 39 | s.push(self.arg_type.mangle_sym()); 40 | 41 | s 42 | } 43 | 44 | pub fn is_this(&self) -> bool { 45 | self.this 46 | } 47 | 48 | pub fn as_tuple_struct(&self, ident: &str) -> PatTupleStruct { 49 | PatTupleStruct { 50 | attrs: Vec::new(), 51 | qself: None, 52 | path: syn::Path { 53 | leading_colon: None, 54 | segments: [ 55 | PathSegment { 56 | ident: Ident::new("CelValue", Span::call_site()), 57 | arguments: syn::PathArguments::None, 58 | }, 59 | PathSegment { 60 | ident: Ident::new(self.arg_type.celvalue_enum(), Span::call_site()), 61 | arguments: syn::PathArguments::None, 62 | }, 63 | ] 64 | .into_iter() 65 | .collect(), 66 | }, 67 | paren_token: token::Paren::default(), 68 | elems: [Pat::Ident(PatIdent { 69 | attrs: Vec::new(), 70 | by_ref: None, 71 | mutability: None, 72 | ident: Ident::new(ident, Span::call_site()), 73 | subpat: None, 74 | })] 75 | .into_iter() 76 | .collect(), 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rscel-macro/src/types/dispatch_arg_type.rs: -------------------------------------------------------------------------------- 1 | use syn::{GenericArgument, PathArguments, Type}; 2 | 3 | #[derive(Clone, Debug)] 4 | #[allow(dead_code)] 5 | pub enum DispatchArgType { 6 | Int, 7 | Uint, 8 | Double, 9 | Boolean, 10 | String, 11 | Bytes, 12 | Vec, 13 | Map, 14 | Timestamp, 15 | Duration, 16 | CelResult, 17 | } 18 | 19 | impl DispatchArgType { 20 | pub fn from_type(pat: &Type) -> Self { 21 | match pat { 22 | Type::Path(path) => { 23 | if path.qself.is_some() { 24 | panic!("Path qualifiers not allowed"); 25 | } 26 | 27 | match path.path.segments.last() { 28 | Some(t) => match t.ident.to_string().as_str() { 29 | "i64" => DispatchArgType::Int, 30 | "u64" => DispatchArgType::Uint, 31 | "f64" => DispatchArgType::Double, 32 | "bool" => DispatchArgType::Boolean, 33 | "String" => DispatchArgType::String, 34 | "Vec" => match &t.arguments { 35 | PathArguments::AngleBracketed(angle) => match angle.args.last() { 36 | Some(GenericArgument::Type(t)) => match t { 37 | Type::Path(path) => match path.path.segments.last() { 38 | Some(t) => match t.ident.to_string().as_str() { 39 | "CelValue" => DispatchArgType::Vec, 40 | _ => panic!("Vec arg must be either CelValue"), 41 | }, 42 | _ => panic!("Empty Vec args"), 43 | }, 44 | _ => panic!("Vec arg must be path"), 45 | }, 46 | _ => panic!("Vec arg must be path"), 47 | }, 48 | _ => panic!("Vec arg must be either CelValue or u8"), 49 | }, 50 | "Map" => DispatchArgType::Map, 51 | "DateTime" => DispatchArgType::Timestamp, 52 | "Duration" => DispatchArgType::Duration, 53 | "CelResult" => DispatchArgType::CelResult, 54 | "CelBytes" => DispatchArgType::Bytes, 55 | other => panic!("Unknown type: {}", other), 56 | }, 57 | None => panic!("No type info"), 58 | } 59 | } 60 | _ => panic!("Only type paths are allowed for argument types"), 61 | } 62 | } 63 | 64 | pub fn mangle_sym(&self) -> char { 65 | match self { 66 | DispatchArgType::Int => 'i', 67 | DispatchArgType::Uint => 'u', 68 | DispatchArgType::Double => 'd', 69 | DispatchArgType::Boolean => 'b', 70 | DispatchArgType::String => 's', 71 | DispatchArgType::Bytes => 'p', 72 | DispatchArgType::Vec => 'v', 73 | DispatchArgType::Map => 'm', 74 | DispatchArgType::Timestamp => 't', 75 | DispatchArgType::Duration => 'y', 76 | DispatchArgType::CelResult => 'r', 77 | } 78 | } 79 | 80 | pub fn celvalue_enum(&self) -> &'static str { 81 | match self { 82 | DispatchArgType::Int => "Int", 83 | DispatchArgType::Uint => "UInt", 84 | DispatchArgType::Double => "Float", 85 | DispatchArgType::Boolean => "Bool", 86 | DispatchArgType::String => "String", 87 | DispatchArgType::Bytes => "Bytes", 88 | DispatchArgType::Vec => "List", 89 | DispatchArgType::Map => "Map", 90 | DispatchArgType::Timestamp => "TimeStamp", 91 | DispatchArgType::Duration => "Duration", 92 | _ => unreachable!(), 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rscel-macro/src/types/dispatch_func.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{punctuated::Punctuated, token, Arm, Ident, ItemFn, Pat, PatPath, PatTuple, PathSegment}; 3 | 4 | use super::{dispatch_arg::DispatchArg, dispatch_arg_type::DispatchArgType}; 5 | 6 | pub struct DispatchFunc { 7 | pub func: ItemFn, 8 | pub args: Vec, 9 | pub return_type: DispatchArgType, 10 | } 11 | 12 | impl DispatchFunc { 13 | pub fn from_item(f: ItemFn) -> Self { 14 | DispatchFunc { 15 | func: f.clone(), 16 | args: f 17 | .sig 18 | .inputs 19 | .iter() 20 | .map(|input| DispatchArg::from_fnarg(input.clone())) 21 | .collect(), 22 | return_type: DispatchArgType::from_type(match f.sig.output { 23 | syn::ReturnType::Default => panic!("Dispatch functions must have return type"), 24 | syn::ReturnType::Type(_rarrow, ref t) => &t, 25 | }), 26 | } 27 | } 28 | 29 | pub fn into_dispatch_fn(self) -> ItemFn { 30 | let mangled = self.mangled_name(); 31 | let mut func = self.func; 32 | 33 | let s = func.sig.ident.span(); 34 | func.sig.ident = Ident::new(&mangled, s); 35 | 36 | func 37 | } 38 | 39 | pub fn mangled_name(&self) -> String { 40 | let mut dispatch_name = self.func.sig.ident.to_string(); 41 | dispatch_name.push('_'); 42 | dispatch_name.extend(self.args.iter().map(|a| a.mangle_sym())); 43 | dispatch_name.push(self.return_type.mangle_sym()); 44 | 45 | dispatch_name 46 | } 47 | 48 | pub fn as_arm(&self, max_args: usize) -> Arm { 49 | let mut elems = Vec::new(); 50 | let mut args: Vec = Vec::new(); 51 | let mut arg_index = 0usize; 52 | 53 | if self.args.len() > arg_index && self.args[arg_index].is_this() { 54 | elems.push(Pat::TupleStruct(self.args[0].as_tuple_struct("this"))); 55 | args.push(syn::Expr::Path(syn::ExprPath { 56 | attrs: Vec::new(), 57 | qself: None, 58 | path: Ident::new("this", Span::call_site()).into(), 59 | })); 60 | arg_index += 1; 61 | } else { 62 | elems.push(Pat::Path(PatPath { 63 | attrs: Vec::new(), 64 | qself: None, 65 | path: syn::Path { 66 | leading_colon: None, 67 | segments: [ 68 | PathSegment { 69 | ident: Ident::new("CelValue", Span::call_site()), 70 | arguments: syn::PathArguments::None, 71 | }, 72 | PathSegment { 73 | ident: Ident::new("Null", Span::call_site()), 74 | arguments: syn::PathArguments::None, 75 | }, 76 | ] 77 | .into_iter() 78 | .collect(), 79 | }, 80 | })); 81 | } 82 | 83 | for i in 0..(max_args) { 84 | if arg_index >= self.args.len() { 85 | elems.push(Pat::Path(PatPath { 86 | attrs: Vec::new(), 87 | qself: None, 88 | path: syn::Path { 89 | leading_colon: None, 90 | segments: [ 91 | PathSegment { 92 | ident: Ident::new("CelValue", Span::call_site()), 93 | arguments: syn::PathArguments::None, 94 | }, 95 | PathSegment { 96 | ident: Ident::new("Null", Span::call_site()), 97 | arguments: syn::PathArguments::None, 98 | }, 99 | ] 100 | .into_iter() 101 | .collect(), 102 | }, 103 | })); 104 | } else { 105 | let a = format!("a{}", i); 106 | elems.push(Pat::TupleStruct(self.args[arg_index].as_tuple_struct(&a))); 107 | args.push(syn::Expr::Path(syn::ExprPath { 108 | attrs: Vec::new(), 109 | qself: None, 110 | path: Ident::new(&a, Span::call_site()).into(), 111 | })); 112 | arg_index += 1; 113 | } 114 | } 115 | 116 | Arm { 117 | attrs: Vec::new(), 118 | pat: Pat::Tuple(PatTuple { 119 | attrs: Vec::new(), 120 | paren_token: token::Paren::default(), 121 | elems: elems.into_iter().collect(), 122 | }), 123 | guard: None, 124 | fat_arrow_token: token::FatArrow::default(), 125 | body: Box::new(syn::Expr::MethodCall(syn::ExprMethodCall { 126 | attrs: Vec::new(), 127 | receiver: Box::new(syn::Expr::Call(syn::ExprCall { 128 | attrs: Vec::new(), 129 | func: Box::new(syn::Expr::Path(syn::ExprPath { 130 | attrs: Vec::new(), 131 | qself: None, 132 | path: Ident::new(&self.mangled_name(), Span::call_site()).into(), 133 | })), 134 | paren_token: token::Paren::default(), 135 | args: args.into_iter().collect(), 136 | })), 137 | dot_token: token::Dot::default(), 138 | method: Ident::new("into", Span::call_site()), 139 | turbofish: None, 140 | paren_token: token::Paren::default(), 141 | args: Punctuated::new(), 142 | })), 143 | comma: Some(token::Comma::default()), 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /rscel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rscel" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | description = { workspace = true } 6 | license = { workspace = true } 7 | readme = "../README.md" 8 | 9 | [features] 10 | default = ["type_prop", "protobuf", "neg_index"] 11 | ast_ser = [] 12 | debug_output = [] 13 | type_prop = [] 14 | neg_index = [] 15 | protobuf = ["dep:protobuf"] 16 | 17 | [build-dependencies] 18 | protobuf-codegen = { version = "3.7.1" } 19 | protoc-bin-vendored = { version = "3.1.0" } 20 | 21 | [dependencies] 22 | rscel-macro = { path = "../rscel-macro" } 23 | test-case = "3.3.1" 24 | regex = "1.11.1" 25 | serde = { version = "1.0.217", features = ["derive", "rc"] } 26 | serde_with = { version = "3.12.0", features = ["chrono"] } 27 | serde_json = { workspace = true } 28 | chrono = { workspace = true } 29 | duration-str = "0.13.0" 30 | protobuf = { version = "3.7.1", optional = true } 31 | chrono-tz = "0.10.1" 32 | num-traits = "0.2.19" 33 | -------------------------------------------------------------------------------- /rscel/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | println!("cargo::rustc-check-cfg=cfg(test_protos)"); 5 | println!("cargo:rerun-if-env-changed=RSCEL_TEST_PROTO"); 6 | 7 | if let Ok(_) = env::var("RSCEL_TEST_PROTO") { 8 | println!("cargo:rustc-cfg=test_protos"); 9 | println!("cargo:rustc-cfg=protobuf"); 10 | protobuf_codegen::Codegen::new() 11 | .protoc() 12 | .include("test/protos") 13 | .inputs(["test/protos/test.proto"]) 14 | .cargo_out_dir("test_protos") 15 | .run_from_script(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rscel/examples/bench.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use rscel::{BindContext, CelContext}; 3 | 4 | type BenchmarkFn = fn(); 5 | 6 | const BENCHMARKS: &[(&str, BenchmarkFn)] = &[ 7 | ("Run One No Binding", bench_run_one_nobindings), 8 | ("Run Many No Binding", bench_run_many_no_bindings), 9 | ("Run One With Binding", bench_run_one_with_binding), 10 | ("Run Many With Bindings", bench_run_one_with_many_bindings), 11 | ("Build Many", bench_build_many), 12 | ( 13 | "Build Many With Bindings", 14 | bench_construct_many_with_bindings, 15 | ), 16 | ]; 17 | 18 | fn main() { 19 | for benchmark in BENCHMARKS.iter() { 20 | let start_time = Local::now(); 21 | benchmark.1(); 22 | let end_time = Local::now(); 23 | 24 | println!("{}: {}", benchmark.0, end_time - start_time); 25 | } 26 | } 27 | 28 | fn bench_run_one_nobindings() { 29 | let mut cel = CelContext::new(); 30 | let exec = BindContext::new(); 31 | 32 | cel.add_program_str("entry", "((4 * 3) - 4) + 3").unwrap(); 33 | 34 | cel.exec("entry", &exec).unwrap(); 35 | } 36 | 37 | fn bench_run_many_no_bindings() { 38 | let mut cel = CelContext::new(); 39 | let exec = BindContext::new(); 40 | 41 | cel.add_program_str("entry", "((4 * 3) - 4) + 3").unwrap(); 42 | 43 | for _ in 0..10_000 { 44 | cel.exec("entry", &exec).unwrap(); 45 | } 46 | } 47 | 48 | fn bench_run_one_with_binding() { 49 | let mut cel = CelContext::new(); 50 | let mut exec = BindContext::new(); 51 | 52 | cel.add_program_str("entry", "((4 * 3) - foo) + 3").unwrap(); 53 | exec.bind_param("foo", 6.into()); 54 | 55 | cel.exec("entry", &exec).unwrap(); 56 | } 57 | 58 | fn bench_run_one_with_many_bindings() { 59 | let mut cel = CelContext::new(); 60 | let mut exec = BindContext::new(); 61 | 62 | cel.add_program_str("entry", "((4 * 3) - foo) + 3").unwrap(); 63 | 64 | for o in 0..10_000 { 65 | exec.bind_param("foo", o.into()); 66 | 67 | cel.exec("entry", &exec).unwrap(); 68 | } 69 | } 70 | 71 | fn bench_build_many() { 72 | let mut cel = CelContext::new(); 73 | 74 | for o in 0..1_000 { 75 | cel.add_program_str(&format!("prog{}", o), &format!("((4 * 3) - {}) + 3", o)) 76 | .unwrap(); 77 | } 78 | } 79 | 80 | fn bench_construct_many_with_bindings() { 81 | for o in 0..10_000 { 82 | let mut cel = CelContext::new(); 83 | let mut exec = BindContext::new(); 84 | 85 | cel.add_program_str("entry", "((4 * 3) - foo) + 3").unwrap(); 86 | exec.bind_param("foo", o.into()); 87 | 88 | cel.exec("entry", &exec).unwrap(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /rscel/examples/dumps_bc.rs: -------------------------------------------------------------------------------- 1 | use rscel::Program; 2 | use std::env; 3 | 4 | fn main() { 5 | let args = env::args().collect::>(); 6 | 7 | if args.len() < 2 { 8 | panic!("No program passed") 9 | } 10 | 11 | let p = Program::from_source(&args[1]).expect("Failed to compile program"); 12 | for bc in p.bytecode().iter() { 13 | println!("{:?}", bc); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rscel/examples/eval.rs: -------------------------------------------------------------------------------- 1 | use rscel::{BindContext, CelContext}; 2 | use std::env; 3 | 4 | fn main() { 5 | let args: Vec = env::args().collect(); 6 | let mut context = CelContext::new(); 7 | let mut exec = BindContext::new(); 8 | 9 | context.add_program_str("prog", &args[1]).unwrap(); 10 | 11 | if args.len() > 2 { 12 | exec.bind_params_from_json_obj(args[2].parse().unwrap()) 13 | .unwrap(); 14 | } 15 | 16 | let res = context.exec("prog", &exec).unwrap(); 17 | println!("{}", res); 18 | } 19 | -------------------------------------------------------------------------------- /rscel/examples/prog2json.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use rscel::{CelCompiler, StringTokenizer}; 4 | 5 | fn main() { 6 | let args: Vec = env::args().collect(); 7 | 8 | if args.len() != 2 { 9 | eprintln!("Usage: prog2json "); 10 | return; 11 | } 12 | 13 | let mut tokenizer = StringTokenizer::with_input(&args[1]); 14 | let prog = CelCompiler::with_tokenizer(&mut tokenizer) 15 | .compile() 16 | .unwrap(); 17 | 18 | println!("{}", serde_json::to_string_pretty(&prog).unwrap()); 19 | } 20 | -------------------------------------------------------------------------------- /rscel/src/compiler/ast_node.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{source_location::SourceLocation, source_range::SourceRange}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 6 | pub struct AstNode { 7 | loc: SourceRange, 8 | 9 | node: T, 10 | } 11 | 12 | impl AstNode { 13 | pub fn new(node: T, loc: SourceRange) -> AstNode { 14 | AstNode:: { loc, node } 15 | } 16 | 17 | pub fn into_parts(self) -> (T, SourceRange) { 18 | (self.node, self.loc) 19 | } 20 | 21 | pub fn start(&self) -> SourceLocation { 22 | self.loc.start() 23 | } 24 | 25 | pub fn end(&self) -> SourceLocation { 26 | self.loc.end() 27 | } 28 | 29 | pub fn range(&self) -> SourceRange { 30 | self.loc 31 | } 32 | 33 | pub fn node(&self) -> &T { 34 | &self.node 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rscel/src/compiler/compiled_prog/preresolved.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{interp::JmpWhen, types::CelByteCode, ByteCode}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum PreResolvedCodePoint { 7 | Bytecode(ByteCode), 8 | Jmp { label: u32 }, 9 | JmpCond { when: JmpWhen, label: u32 }, 10 | Label(u32), 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct PreResolvedByteCode { 15 | inner: Vec, 16 | len: usize, 17 | } 18 | 19 | impl From for PreResolvedCodePoint { 20 | fn from(value: ByteCode) -> Self { 21 | PreResolvedCodePoint::Bytecode(value) 22 | } 23 | } 24 | 25 | impl From for Vec { 26 | fn from(value: CelByteCode) -> Self { 27 | value.into_iter().map(|b| b.into()).collect() 28 | } 29 | } 30 | 31 | impl PreResolvedByteCode { 32 | pub fn new() -> Self { 33 | PreResolvedByteCode { 34 | inner: Vec::new(), 35 | len: 0, 36 | } 37 | } 38 | 39 | pub fn push(&mut self, val: impl Into) { 40 | let v = val.into(); 41 | self.inner.push(v); 42 | } 43 | 44 | pub fn extend(&mut self, byte_codes: impl IntoIterator) { 45 | for b in byte_codes.into_iter() { 46 | match &b { 47 | PreResolvedCodePoint::Label(_) => {} 48 | _ => self.len += 1, 49 | } 50 | 51 | self.inner.push(b) 52 | } 53 | } 54 | 55 | pub fn into_iter(self) -> impl Iterator { 56 | self.inner.into_iter() 57 | } 58 | 59 | pub fn len(&self) -> usize { 60 | self.len 61 | } 62 | 63 | pub fn resolve(self) -> CelByteCode { 64 | let mut curr_loc: usize = 0; 65 | let mut locations = HashMap::::new(); 66 | let mut ret = CelByteCode::new(); 67 | 68 | // determine label locations 69 | for c in self.inner.iter() { 70 | match c { 71 | PreResolvedCodePoint::Label(i) => { 72 | if locations.contains_key(i) { 73 | panic!("Duplicate label found!"); 74 | } 75 | locations.insert(*i, curr_loc); 76 | } 77 | _ => { 78 | curr_loc += 1; 79 | } 80 | } 81 | } 82 | 83 | curr_loc = 0; 84 | 85 | // resolve the label locations 86 | for c in self.inner.into_iter() { 87 | match c { 88 | PreResolvedCodePoint::Bytecode(byte_code) => { 89 | curr_loc += 1; 90 | ret.push(byte_code); 91 | } 92 | PreResolvedCodePoint::Jmp { label } => { 93 | curr_loc += 1; 94 | let jmp_loc = locations[&label]; 95 | let offset = (jmp_loc as isize) - (curr_loc as isize); 96 | ret.push(ByteCode::Jmp( 97 | i32::try_from(offset).expect("Attempt to jump farther than possible"), 98 | )); 99 | } 100 | PreResolvedCodePoint::JmpCond { when, label } => { 101 | curr_loc += 1; 102 | let jmp_loc = locations[&label]; 103 | let offset = (jmp_loc as isize) - (curr_loc as isize); 104 | ret.push(ByteCode::JmpCond { 105 | when, 106 | dist: offset as i32, 107 | }); 108 | } 109 | PreResolvedCodePoint::Label(_) => {} 110 | } 111 | } 112 | 113 | ret 114 | } 115 | } 116 | 117 | impl From for PreResolvedByteCode { 118 | fn from(value: CelByteCode) -> Self { 119 | value.into_iter().collect() 120 | } 121 | } 122 | 123 | impl FromIterator for PreResolvedByteCode { 124 | fn from_iter>(iter: T) -> Self { 125 | let v: Vec<_> = iter.into_iter().map(|b| b.into()).collect(); 126 | let l = v.len(); 127 | 128 | PreResolvedByteCode { inner: v, len: l } 129 | } 130 | } 131 | 132 | impl FromIterator for PreResolvedByteCode { 133 | fn from_iter>(iter: T) -> Self { 134 | let mut code_points = Vec::new(); 135 | let mut size = 0; 136 | 137 | for code_point in iter.into_iter() { 138 | match &code_point { 139 | PreResolvedCodePoint::Label(_) => {} 140 | _ => { 141 | size += 1; 142 | } 143 | } 144 | 145 | code_points.push(code_point); 146 | } 147 | 148 | PreResolvedByteCode { 149 | inner: code_points, 150 | len: size, 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /rscel/src/compiler/compiler/pattern_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{compiler::tokens::Token, ByteCode}; 2 | 3 | use super::MatchCmpOp; 4 | 5 | pub enum PrefixPattern { 6 | Eq, 7 | Neq, 8 | Gt, 9 | Ge, 10 | Lt, 11 | Le, 12 | } 13 | 14 | impl PrefixPattern { 15 | pub fn from_token(token: &Token) -> Option { 16 | match token { 17 | Token::EqualEqual => Some(PrefixPattern::Eq), 18 | Token::NotEqual => Some(PrefixPattern::Neq), 19 | Token::GreaterThan => Some(PrefixPattern::Gt), 20 | Token::GreaterEqual => Some(PrefixPattern::Ge), 21 | Token::LessThan => Some(PrefixPattern::Lt), 22 | Token::LessEqual => Some(PrefixPattern::Le), 23 | _ => None, 24 | } 25 | } 26 | 27 | pub fn as_bytecode(&self) -> ByteCode { 28 | match self { 29 | PrefixPattern::Eq => ByteCode::Eq, 30 | PrefixPattern::Neq => ByteCode::Ne, 31 | PrefixPattern::Gt => ByteCode::Gt, 32 | PrefixPattern::Ge => ByteCode::Ge, 33 | PrefixPattern::Lt => ByteCode::Lt, 34 | PrefixPattern::Le => ByteCode::Le, 35 | } 36 | } 37 | 38 | pub fn as_ast(&self) -> MatchCmpOp { 39 | match self { 40 | PrefixPattern::Eq => MatchCmpOp::Eq, 41 | PrefixPattern::Neq => MatchCmpOp::Neq, 42 | PrefixPattern::Gt => MatchCmpOp::Gt, 43 | PrefixPattern::Ge => MatchCmpOp::Ge, 44 | PrefixPattern::Lt => MatchCmpOp::Lt, 45 | PrefixPattern::Le => MatchCmpOp::Le, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rscel/src/compiler/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ast_node; 2 | pub mod compiled_prog; 3 | pub mod compiler; 4 | pub mod grammar; 5 | pub mod source_location; 6 | pub mod source_range; 7 | pub mod string_scanner; 8 | pub mod string_tokenizer; 9 | pub mod syntax_error; 10 | pub mod tokenizer; 11 | pub mod tokens; 12 | -------------------------------------------------------------------------------- /rscel/src/compiler/source_location.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 4 | pub struct SourceLocation(usize, usize); 5 | 6 | impl SourceLocation { 7 | pub fn new(line: usize, col: usize) -> SourceLocation { 8 | SourceLocation(line, col) 9 | } 10 | 11 | pub fn line(&self) -> usize { 12 | self.0 13 | } 14 | 15 | pub fn col(&self) -> usize { 16 | self.1 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod test { 22 | use super::SourceLocation; 23 | 24 | #[test] 25 | fn test_source_location() { 26 | let loc1 = SourceLocation(0, 1); 27 | let loc2 = SourceLocation(1, 1); 28 | let loc3 = SourceLocation(0, 1); 29 | 30 | assert!(loc1 < loc2); 31 | assert_eq!(loc1.line(), 0); 32 | assert_eq!(loc1.col(), 1); 33 | assert_eq!(loc1, loc3); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rscel/src/compiler/source_range.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::source_location::SourceLocation; 4 | 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] 6 | pub struct SourceRange { 7 | start: SourceLocation, 8 | end: SourceLocation, 9 | } 10 | 11 | impl SourceRange { 12 | pub fn new(start: SourceLocation, end: SourceLocation) -> SourceRange { 13 | SourceRange { start, end } 14 | } 15 | 16 | pub fn start(&self) -> SourceLocation { 17 | self.start 18 | } 19 | 20 | pub fn end(&self) -> SourceLocation { 21 | self.end 22 | } 23 | 24 | pub fn surrounding(self, other: SourceRange) -> SourceRange { 25 | SourceRange::new(self.start.min(other.start), self.end.max(other.end)) 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod test { 31 | use crate::compiler::source_location::SourceLocation; 32 | 33 | use super::SourceRange; 34 | 35 | #[test] 36 | fn test_surrounding() { 37 | let p = SourceRange::new(SourceLocation::new(0, 3), SourceLocation::new(0, 5)).surrounding( 38 | SourceRange::new(SourceLocation::new(0, 4), SourceLocation::new(0, 7)), 39 | ); 40 | 41 | assert_eq!( 42 | p, 43 | SourceRange::new(SourceLocation::new(0, 3), SourceLocation::new(0, 7)) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rscel/src/compiler/string_scanner.rs: -------------------------------------------------------------------------------- 1 | use std::str::Chars; 2 | 3 | use super::source_location::SourceLocation; 4 | 5 | pub struct StringScanner<'l> { 6 | input: &'l str, 7 | iterator: Chars<'l>, 8 | current: Option, 9 | line: usize, 10 | column: usize, 11 | eof: bool, 12 | } 13 | 14 | impl<'l> StringScanner<'l> { 15 | pub fn from_input(input: &'l str) -> StringScanner<'l> { 16 | StringScanner { 17 | input, 18 | iterator: input.chars(), 19 | current: None, 20 | line: 0, 21 | column: 0, 22 | eof: false, 23 | } 24 | } 25 | 26 | pub fn peek(&mut self) -> Option { 27 | if self.current.is_none() { 28 | self.current = self.collect_next(); 29 | } 30 | 31 | self.current 32 | } 33 | 34 | pub fn next(&mut self) -> Option { 35 | if self.current.is_none() { 36 | self.current = self.collect_next(); 37 | } 38 | 39 | match self.current { 40 | Some(c) => { 41 | if c == '\n' { 42 | self.line += 1; 43 | self.column = 0; 44 | } else { 45 | self.column += 1; 46 | } 47 | 48 | self.current = None; 49 | Some(c) 50 | } 51 | None => None, 52 | } 53 | } 54 | 55 | pub fn location(&self) -> SourceLocation { 56 | SourceLocation::new(self.line, self.column) 57 | } 58 | 59 | fn collect_next(&mut self) -> Option { 60 | if self.eof { 61 | return None; 62 | } 63 | 64 | match self.iterator.next() { 65 | Some(val) => Some(val), 66 | None => { 67 | self.eof = true; 68 | None 69 | } 70 | } 71 | } 72 | 73 | pub fn input(&self) -> &'l str { 74 | self.input 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod test { 80 | use crate::compiler::source_location::SourceLocation; 81 | 82 | use super::StringScanner; 83 | 84 | #[test] 85 | fn string_scanner_location() { 86 | let mut scanner = StringScanner::from_input("foo + bar"); 87 | 88 | assert_eq!(scanner.location(), SourceLocation::new(0, 0)); 89 | 90 | let c = scanner.peek().unwrap(); 91 | assert_eq!(c, 'f'); 92 | assert_eq!(scanner.location(), SourceLocation::new(0, 0)); 93 | 94 | let _ = scanner.next().unwrap(); 95 | assert_eq!(scanner.location(), SourceLocation::new(0, 1)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rscel/src/compiler/syntax_error.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt; 3 | 4 | use super::source_location::SourceLocation; 5 | 6 | #[derive(Serialize, Deserialize, Clone, Debug)] 7 | pub struct SyntaxError { 8 | loc: SourceLocation, 9 | 10 | message: Option, 11 | } 12 | 13 | impl SyntaxError { 14 | pub fn from_location(loc: SourceLocation) -> SyntaxError { 15 | SyntaxError { loc, message: None } 16 | } 17 | 18 | pub fn with_message(mut self, msg: String) -> SyntaxError { 19 | self.message = Some(msg); 20 | self 21 | } 22 | 23 | pub fn message<'a>(&'a self) -> Option<&'a str> { 24 | self.message.as_deref() 25 | } 26 | 27 | pub fn loc(&self) -> SourceLocation { 28 | self.loc 29 | } 30 | } 31 | 32 | impl fmt::Display for SyntaxError { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "line {}, column {}", self.loc.line(), self.loc.col()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rscel/src/compiler/tokenizer.rs: -------------------------------------------------------------------------------- 1 | pub use super::syntax_error::SyntaxError; 2 | use super::{ 3 | source_location::SourceLocation, 4 | source_range::SourceRange, 5 | tokens::{AsToken, IntoToken, Token}, 6 | }; 7 | 8 | #[derive(Debug, PartialEq, Clone)] 9 | pub struct TokenWithLoc { 10 | pub token: Token, 11 | pub loc: SourceRange, 12 | } 13 | 14 | pub trait Tokenizer { 15 | fn peek(&mut self) -> Result, SyntaxError>; 16 | fn next(&mut self) -> Result, SyntaxError>; 17 | 18 | fn source<'a>(&'a self) -> &'a str; 19 | fn location(&self) -> SourceLocation; 20 | } 21 | 22 | impl TokenWithLoc { 23 | pub fn new(token: Token, loc: SourceRange) -> Self { 24 | TokenWithLoc { token, loc } 25 | } 26 | 27 | pub fn token(&self) -> &Token { 28 | &self.token 29 | } 30 | 31 | pub fn start(&self) -> SourceLocation { 32 | self.loc.start() 33 | } 34 | 35 | pub fn end(&self) -> SourceLocation { 36 | self.loc.end() 37 | } 38 | 39 | pub fn into_token(self) -> Token { 40 | self.token 41 | } 42 | } 43 | 44 | impl AsToken for TokenWithLoc { 45 | fn as_token(&self) -> Option<&Token> { 46 | Some(&self.token) 47 | } 48 | } 49 | 50 | impl IntoToken for TokenWithLoc { 51 | fn into_token(self) -> Option { 52 | Some(self.token) 53 | } 54 | } 55 | 56 | impl AsToken for &TokenWithLoc { 57 | fn as_token(&self) -> Option<&Token> { 58 | (*self).as_token() 59 | } 60 | } 61 | 62 | impl AsToken for Option { 63 | fn as_token(&self) -> Option<&Token> { 64 | match self { 65 | Some(s) => s.as_token(), 66 | None => None, 67 | } 68 | } 69 | } 70 | 71 | impl IntoToken for Option { 72 | fn into_token(self) -> Option { 73 | match self { 74 | Some(t) => Some(t.into_token()), 75 | None => None, 76 | } 77 | } 78 | } 79 | 80 | impl AsToken for Option<&TokenWithLoc> { 81 | fn as_token(&self) -> Option<&Token> { 82 | match self { 83 | Some(s) => s.as_token(), 84 | None => None, 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rscel/src/compiler/tokens.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::types::CelBytes; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub enum Token { 8 | Question, // ? 9 | Colon, // : 10 | Add, // + 11 | Minus, // - 12 | Multiply, // * 13 | Divide, // / 14 | Mod, // % 15 | Not, // ! 16 | Dot, // . 17 | Comma, // , 18 | LBracket, // [ 19 | RBracket, // ] 20 | LBrace, // { 21 | RBrace, // } 22 | LParen, // ( 23 | RParen, // ) 24 | LessThan, // < 25 | GreaterThan, // > 26 | OrOr, // || 27 | AndAnd, // && 28 | LessEqual, // <= 29 | GreaterEqual, // >= 30 | EqualEqual, // == 31 | NotEqual, // != 32 | In, // 'in' 33 | Null, // 'null' 34 | Match, // 'match' 35 | Case, // 'case' 36 | BoolLit(bool), // true | false 37 | IntLit(u64), // [-+]?[0-9]+ 38 | UIntLit(u64), // [0-9]+u 39 | FloatLit(f64), // [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? 40 | StringLit(String), // r?('|")[^\n]*('|") 41 | FStringLit(Vec), // f'.*' 42 | ByteStringLit(CelBytes), // b('|")[^\n]('|") 43 | Ident(String), // [_A-Za-z][_A-Za-z0-9]* 44 | } 45 | 46 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 47 | pub enum FStringSegment { 48 | Lit(String), 49 | Expr(String), 50 | } 51 | 52 | pub trait AsToken { 53 | fn as_token(&self) -> Option<&Token>; 54 | } 55 | 56 | pub trait IntoToken { 57 | fn into_token(self) -> Option; 58 | } 59 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs.rs: -------------------------------------------------------------------------------- 1 | use super::bind_context::RsCelFunction; 2 | use crate::{BindContext, CelError, CelValue}; 3 | 4 | mod math; 5 | mod size; 6 | mod string; 7 | mod time_funcs; 8 | 9 | const DEFAULT_FUNCS: &[(&str, &'static RsCelFunction)] = &[ 10 | ("contains", &string::contains::contains), 11 | ("containsI", &string::contains::contains_i), 12 | ("size", &size::size), 13 | ("startsWith", &string::starts_with::starts_with), 14 | ("endsWith", &string::ends_with::ends_with), 15 | ("startsWithI", &string::starts_with::starts_with_i), 16 | ("endsWithI", &string::ends_with::ends_with_i), 17 | ("matches", &string::matches::matches), 18 | ("toLower", &string::to_lower_impl), 19 | ("toUpper", &string::to_upper_impl), 20 | ("trim", &string::trim_impl), 21 | ("trimStart", &string::trim_start_impl), 22 | ("trimEnd", &string::trim_end_impl), 23 | ( 24 | "splitWhiteSpace", 25 | &string::split_whitespace::split_whitespace, 26 | ), 27 | ("abs", &math::abs::abs), 28 | ("sqrt", &math::sqrt::sqrt), 29 | ("pow", &math::pow::pow), 30 | ("log", &math::log::log), 31 | ("ceil", &math::ceil::ceil), 32 | ("floor", &math::floor::floor), 33 | ("round", &math::round::round), 34 | ("min", &min_impl), 35 | ("max", &max_impl), 36 | ("getDate", &time_funcs::get_date::get_date), 37 | ( 38 | "getDayOfMonth", 39 | &time_funcs::get_day_of_month::get_day_of_month, 40 | ), 41 | ( 42 | "getDayOfWeek", 43 | &time_funcs::get_day_of_week::get_day_of_week, 44 | ), 45 | ( 46 | "getDayOfYear", 47 | &time_funcs::get_day_of_year::get_day_of_year, 48 | ), 49 | ("getFullYear", &time_funcs::get_full_year::get_full_year), 50 | ("getHours", &time_funcs::get_hours::get_hours), 51 | ( 52 | "getMilliseconds", 53 | &time_funcs::get_milliseconds::get_milliseconds, 54 | ), 55 | ("getMinutes", &time_funcs::get_minutes::get_minutes), 56 | ("getMonth", &time_funcs::get_month::get_month), 57 | ("getSeconds", &time_funcs::get_seconds::get_seconds), 58 | ]; 59 | 60 | pub fn load_default_funcs(exec_ctx: &mut BindContext) { 61 | for (name, func) in DEFAULT_FUNCS.iter() { 62 | exec_ctx.bind_func(name, *func); 63 | } 64 | } 65 | 66 | fn min_impl(_this: CelValue, args: Vec) -> CelValue { 67 | if args.len() == 0 { 68 | return CelValue::from_err(CelError::argument("min() requires at lease one argument")); 69 | } 70 | 71 | let mut curr_min: Option<&CelValue> = None; 72 | 73 | for val in args.iter() { 74 | match curr_min { 75 | Some(curr) => { 76 | if val.clone().lt(curr.clone()).is_true() { 77 | curr_min = Some(&val); 78 | } 79 | } 80 | None => curr_min = Some(&val), 81 | } 82 | } 83 | 84 | match curr_min { 85 | Some(v) => v.clone(), 86 | None => CelValue::from_null(), 87 | } 88 | } 89 | 90 | fn max_impl(_this: CelValue, args: Vec) -> CelValue { 91 | if args.len() == 0 { 92 | return CelValue::from_err(CelError::argument("max() requires at lease one argument")); 93 | } 94 | 95 | let mut curr_max: Option<&CelValue> = None; 96 | 97 | for val in args.iter() { 98 | match curr_max { 99 | Some(curr) => { 100 | if val.clone().gt(curr.clone()).is_true() { 101 | curr_max = Some(val); 102 | } 103 | } 104 | None => curr_max = Some(val), 105 | } 106 | } 107 | 108 | match curr_max { 109 | Some(v) => v.clone(), 110 | None => CelValue::from_null(), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math.rs: -------------------------------------------------------------------------------- 1 | pub mod abs; 2 | pub mod ceil; 3 | pub mod floor; 4 | pub mod log; 5 | pub mod pow; 6 | pub mod round; 7 | pub mod sqrt; 8 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/abs.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as abs; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn abs(n: i64) -> i64 { 10 | n.abs() 11 | } 12 | 13 | fn abs(n: u64) -> u64 { 14 | n 15 | } 16 | 17 | fn abs(n: f64) -> f64 { 18 | n.abs() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/ceil.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as ceil; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn ceil(n: i64) -> i64 { 10 | n 11 | } 12 | 13 | fn ceil(n: u64) -> u64 { 14 | n 15 | } 16 | 17 | fn ceil(n: f64) -> i64 { 18 | n.ceil() as i64 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/floor.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as floor; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn floor(n: i64) -> i64 { 10 | n 11 | } 12 | 13 | fn floor(n: u64) -> u64 { 14 | n 15 | } 16 | 17 | fn floor(n: f64) -> i64 { 18 | n.floor() as i64 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/log.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as log; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn log(n: i64) -> i64 { 10 | n.ilog10() as i64 11 | } 12 | 13 | fn log(n: u64) -> u64 { 14 | n.ilog10() as u64 15 | } 16 | 17 | fn log(n: f64) -> f64 { 18 | n.log10() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/pow.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as pow; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn pow(n1: i64, n2: i64) -> i64 { 10 | n1.pow(n2 as u32) 11 | } 12 | 13 | fn pow(n1: i64, n2: u64) -> i64 { 14 | n1.pow(n2 as u32) 15 | } 16 | 17 | fn pow(n1: i64, n2: f64) -> i64 { 18 | n1.pow(n2 as u32) 19 | } 20 | 21 | fn pow(n1: u64, n2: i64) -> u64 { 22 | n1.pow(n2 as u32) 23 | } 24 | 25 | fn pow(n1: u64, n2: u64) -> u64 { 26 | n1.pow(n2 as u32) 27 | } 28 | 29 | fn pow(n1: u64, n2: f64) -> u64 { 30 | n1.pow(n2 as u32) 31 | } 32 | 33 | fn pow(n1: f64, n2: i64) -> f64 { 34 | n1.powi(n2 as i32) 35 | } 36 | 37 | fn pow(n1: f64, n2: u64) -> f64 { 38 | n1.powi(n2 as i32) 39 | } 40 | 41 | fn pow(n1: f64, n2: f64) -> f64 { 42 | n1.powf(n2) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/round.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as round; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn round(n: i64) -> i64 { 10 | n 11 | } 12 | 13 | fn round(n: u64) -> u64 { 14 | n 15 | } 16 | 17 | fn round(n: f64) -> i64 { 18 | n.round() as i64 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/math/sqrt.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as sqrt; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn sqrt(n: i64) -> f64 { 10 | (n as f64).sqrt() 11 | } 12 | 13 | fn sqrt(n: u64) -> f64 { 14 | (n as f64).sqrt() 15 | } 16 | 17 | fn sqrt(n: f64) -> f64 { 18 | n.sqrt() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/size.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as size; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::{types::CelBytes, CelValue}; 8 | 9 | fn size(this: String) -> u64 { 10 | this.len() as u64 11 | } 12 | 13 | fn size(this: CelBytes) -> u64 { 14 | this.len() as u64 15 | } 16 | 17 | fn size(this: Vec) -> u64 { 18 | this.len() as u64 19 | } 20 | 21 | fn size(arg: String) -> u64 { 22 | arg.len() as u64 23 | } 24 | 25 | fn size(arg: CelBytes) -> u64 { 26 | arg.len() as u64 27 | } 28 | 29 | fn size(arg: Vec) -> u64 { 30 | arg.len() as u64 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string.rs: -------------------------------------------------------------------------------- 1 | use crate::{CelError, CelValue}; 2 | 3 | pub mod contains; 4 | pub mod ends_with; 5 | pub mod matches; 6 | pub mod split_whitespace; 7 | pub mod starts_with; 8 | 9 | macro_rules! string_func { 10 | ($cel_func_name: ident, $func_name:ident, $str_func:ident) => { 11 | pub fn $func_name(this: CelValue, args: Vec) -> CelValue { 12 | if args.len() > 0 { 13 | return CelValue::from_err(CelError::argument( 14 | "$cel_func_name does not take any argments", 15 | )); 16 | } 17 | 18 | if let CelValue::String(s) = this { 19 | CelValue::String(s.$str_func().chars().collect()) 20 | } else { 21 | return CelValue::from_err(CelError::value( 22 | "$cel_func_name only available on string", 23 | )); 24 | } 25 | } 26 | }; 27 | } 28 | 29 | string_func!(toLower, to_lower_impl, to_lowercase); 30 | string_func!(toUpper, to_upper_impl, to_uppercase); 31 | string_func!(trim, trim_impl, trim); 32 | string_func!(trimStart, trim_start_impl, trim_start); 33 | string_func!(trimEnd, trim_end_impl, trim_end); 34 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string/contains.rs: -------------------------------------------------------------------------------- 1 | use rscel_macro::dispatch; 2 | 3 | pub use contains_i_methods::dispatch as contains_i; 4 | pub use contains_methods::dispatch as contains; 5 | 6 | #[dispatch] 7 | mod contains_methods { 8 | use crate::CelValue; 9 | 10 | fn contains(this: String, needle: String) -> bool { 11 | this.contains(&needle) 12 | } 13 | } 14 | 15 | #[dispatch] 16 | mod contains_i_methods { 17 | use crate::CelValue; 18 | 19 | fn contains_i(this: String, needle: String) -> bool { 20 | this.to_lowercase().contains(&needle.to_lowercase()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string/ends_with.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use ends_with_i_methods::dispatch as ends_with_i; 4 | pub use ends_with_methods::dispatch as ends_with; 5 | 6 | #[dispatch] 7 | mod ends_with_methods { 8 | use crate::CelValue; 9 | 10 | fn ends_with(this: String, needle: String) -> bool { 11 | this.ends_with(&needle) 12 | } 13 | } 14 | 15 | #[dispatch] 16 | mod ends_with_i_methods { 17 | use crate::CelValue; 18 | 19 | fn ends_with_i(this: String, needle: String) -> bool { 20 | this.to_lowercase().ends_with(&needle.to_lowercase()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string/matches.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as matches; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::{CelResult, CelValue}; 8 | 9 | fn matches(this: String, needle: String) -> CelResult { 10 | internal::matches(&this, &needle) 11 | } 12 | 13 | fn matches(haystack: String, needle: String) -> CelResult { 14 | internal::matches(&haystack, &needle) 15 | } 16 | 17 | mod internal { 18 | use regex::Regex; 19 | 20 | use crate::{CelError, CelResult}; 21 | 22 | pub fn matches(haystack: &str, needle: &str) -> CelResult { 23 | match Regex::new(needle) { 24 | Ok(re) => return Ok(re.is_match(haystack)), 25 | Err(err) => { 26 | return Err(CelError::value(&format!( 27 | "Invalid regular expression: {}", 28 | err 29 | ))) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string/split_whitespace.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as split_whitespace; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::CelValue; 8 | 9 | fn split_whitespace(this: String) -> Vec { 10 | this.split_whitespace().map(|t| t.into()).collect() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/string/starts_with.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use starts_with_i_methods::dispatch as starts_with_i; 4 | pub use starts_with_methods::dispatch as starts_with; 5 | 6 | #[dispatch] 7 | mod starts_with_methods { 8 | use crate::CelValue; 9 | 10 | fn starts_with(this: String, needle: String) -> bool { 11 | this.starts_with(&needle) 12 | } 13 | } 14 | 15 | #[dispatch] 16 | mod starts_with_i_methods { 17 | use crate::CelValue; 18 | 19 | fn starts_with_i(this: String, needle: String) -> bool { 20 | this.to_lowercase().starts_with(&needle.to_lowercase()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs.rs: -------------------------------------------------------------------------------- 1 | mod helpers; 2 | 3 | pub mod get_date; 4 | pub mod get_day_of_month; 5 | pub mod get_day_of_week; 6 | pub mod get_day_of_year; 7 | pub mod get_full_year; 8 | pub mod get_hours; 9 | pub mod get_milliseconds; 10 | pub mod get_minutes; 11 | pub mod get_month; 12 | pub mod get_seconds; 13 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_date.rs: -------------------------------------------------------------------------------- 1 | use rscel_macro::dispatch; 2 | 3 | pub use methods::dispatch as get_date; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use super::super::helpers::get_adjusted_datetime; 8 | use crate::{CelResult, CelValue}; 9 | use chrono::{DateTime, Datelike, Utc}; 10 | 11 | fn get_date(this: DateTime) -> i64 { 12 | this.day() as i64 13 | } 14 | 15 | fn get_date(this: DateTime, timezone: String) -> CelResult { 16 | Ok(get_adjusted_datetime(this, timezone)?.day() as i64) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_day_of_month.rs: -------------------------------------------------------------------------------- 1 | use rscel_macro::dispatch; 2 | 3 | pub use methods::dispatch as get_day_of_month; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::{ 8 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 9 | }; 10 | use chrono::{DateTime, Datelike, Utc}; 11 | 12 | fn get_day_of_month(this: DateTime) -> i64 { 13 | this.day() as i64 - 1 14 | } 15 | 16 | fn get_day_of_month(this: DateTime, timezone: String) -> CelResult { 17 | Ok(get_adjusted_datetime(this, timezone)?.day() as i64 - 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_day_of_week.rs: -------------------------------------------------------------------------------- 1 | use rscel_macro::dispatch; 2 | 3 | pub use methods::dispatch as get_day_of_week; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use crate::{CelResult, CelValue}; 8 | use chrono::{DateTime, Datelike, Utc}; 9 | 10 | use crate::context::default_funcs::time_funcs::helpers::get_adjusted_datetime; 11 | 12 | fn get_day_of_week(this: DateTime) -> i64 { 13 | this.weekday().num_days_from_sunday() as i64 14 | } 15 | 16 | fn get_day_of_week(this: DateTime, timezone: String) -> CelResult { 17 | Ok(get_adjusted_datetime(this, timezone)? 18 | .weekday() 19 | .number_from_sunday() as i64) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_day_of_year.rs: -------------------------------------------------------------------------------- 1 | use rscel_macro::dispatch; 2 | 3 | pub use methods::dispatch as get_day_of_year; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Datelike, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_day_of_year(this: DateTime) -> i64 { 14 | this.ordinal0() as i64 15 | } 16 | 17 | fn get_day_of_year(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.ordinal0() as i64) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_full_year.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_full_year; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Datelike, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_full_year(this: DateTime) -> i64 { 14 | this.year() as i64 15 | } 16 | 17 | fn get_full_year(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.year() as i64) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_hours.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_hours; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Duration, Timelike, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_hours(this: DateTime) -> i64 { 14 | this.time().hour() as i64 15 | } 16 | 17 | fn get_hours(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.time().hour() as i64) 19 | } 20 | 21 | fn get_hours(this: Duration) -> i64 { 22 | this.num_hours() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_milliseconds.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_milliseconds; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Duration, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_milliseconds(this: DateTime) -> i64 { 14 | this.timestamp_subsec_millis() as i64 15 | } 16 | 17 | fn get_milliseconds(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.timestamp_subsec_millis() as i64) 19 | } 20 | 21 | fn get_milliseconds(this: Duration) -> i64 { 22 | this.subsec_nanos() as i64 / 1000000i64 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_minutes.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_minutes; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Duration, Timelike, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_minutes(this: DateTime) -> i64 { 14 | this.time().minute() as i64 15 | } 16 | 17 | fn get_minutes(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.time().minute() as i64) 19 | } 20 | 21 | fn get_minutes(this: Duration) -> i64 { 22 | this.num_minutes() as i64 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_month.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_month; 4 | 5 | #[dispatch] 6 | mod methods { 7 | use chrono::{DateTime, Datelike, Utc}; 8 | 9 | use crate::{ 10 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 11 | }; 12 | 13 | fn get_month(this: DateTime) -> i64 { 14 | this.month0() as i64 15 | } 16 | 17 | fn get_month(this: DateTime, timezone: String) -> CelResult { 18 | Ok(get_adjusted_datetime(this, timezone)?.month0() as i64) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/get_seconds.rs: -------------------------------------------------------------------------------- 1 | use crate::macros::dispatch; 2 | 3 | pub use methods::dispatch as get_seconds; 4 | 5 | #[dispatch] 6 | mod methods { 7 | 8 | use chrono::{DateTime, Duration, Timelike, Utc}; 9 | 10 | use crate::{ 11 | context::default_funcs::time_funcs::helpers::get_adjusted_datetime, CelResult, CelValue, 12 | }; 13 | 14 | fn get_seconds(this: DateTime) -> i64 { 15 | this.time().second() as i64 16 | } 17 | 18 | fn get_seconds(this: DateTime, timezone: String) -> CelResult { 19 | Ok(get_adjusted_datetime(this, timezone)?.time().second() as i64) 20 | } 21 | 22 | fn get_seconds(this: Duration) -> i64 { 23 | this.num_seconds() as i64 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rscel/src/context/default_funcs/time_funcs/helpers.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use chrono::{DateTime, Utc}; 4 | use chrono_tz::Tz; 5 | 6 | use crate::{CelError, CelResult}; 7 | 8 | pub fn get_adjusted_datetime(this: DateTime, timezone: String) -> CelResult> { 9 | if let Ok(tz) = Tz::from_str(&timezone) { 10 | Ok(this.with_timezone(&tz)) 11 | } else { 12 | Err(CelError::argument("Failed to parse timezone")) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rscel/src/context/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | mod bind_context; 4 | mod default_funcs; 5 | mod default_macros; 6 | mod type_funcs; 7 | use crate::{ 8 | compiler::{compiler::CelCompiler, string_tokenizer::StringTokenizer}, 9 | interp::Interpreter, 10 | program::{Program, ProgramDetails}, 11 | CelResult, CelValue, 12 | }; 13 | pub use bind_context::{BindContext, RsCelFunction, RsCelMacro}; 14 | pub use type_funcs::construct_type; 15 | 16 | /// The CelContext is the core context in RsCel. This context contains 17 | /// Program information as well as the primary entry point for evaluating 18 | /// an expression. 19 | pub struct CelContext { 20 | progs: HashMap, 21 | } 22 | 23 | impl CelContext { 24 | /// Constructs a new empty CelContext 25 | pub fn new() -> CelContext { 26 | CelContext { 27 | progs: HashMap::new(), 28 | } 29 | } 30 | 31 | /// Add an already constructed Program to the context with a given name. Using 32 | /// This method can allow a Program to be constructed once and shared between 33 | /// contexts, if desired. Will override an existing program with same name. 34 | pub fn add_program(&mut self, name: &str, prog: Program) { 35 | self.progs.insert(name.to_owned(), prog); 36 | } 37 | 38 | /// Add a Program to the context with the given name and source string. Return of this 39 | /// function indicates parseing result of the constructed Program. This method will not 40 | /// allow for a Program to be shared. Will override an existing program with same name. 41 | pub fn add_program_str(&mut self, name: &str, prog_str: &str) -> CelResult<()> { 42 | let mut tokenizer = StringTokenizer::with_input(prog_str); 43 | let prog = CelCompiler::with_tokenizer(&mut tokenizer).compile()?; 44 | 45 | self.add_program(name, prog); 46 | Ok(()) 47 | } 48 | 49 | /// Returns ProgramDetails for a program by name if it exists. 50 | pub fn program_details<'a>(&'a self, name: &str) -> Option<&'a ProgramDetails> { 51 | let prog = self.progs.get(name)?; 52 | 53 | Some(prog.details()) 54 | } 55 | 56 | pub fn get_program<'a>(&'a self, name: &str) -> Option<&'a Program> { 57 | self.progs.get(name) 58 | } 59 | 60 | /// Evaluate a Program with the given name with a provided ExecContext. A single CelContext 61 | /// can be run multiple times with different ExecContext's. The return of this function is 62 | /// a Result with either a ValueCell representing the final solution of the Program or an Error 63 | /// that is discovered during execution, such as mismatch of types 64 | pub fn exec<'l>(&'l mut self, name: &str, bindings: &'l BindContext) -> CelResult { 65 | let interp = Interpreter::new(&self, bindings); 66 | 67 | interp.run_program(name) 68 | } 69 | 70 | // pub(crate) fn eval_expr( 71 | // &mut self, 72 | // expr: &Expr, 73 | // ctx: &BindContext, 74 | // ) -> ValueCellResult { 75 | // self.current_ctx = Some(ctx.clone()); 76 | 77 | // let res = eval_expr(expr, self); 78 | 79 | // self.current_ctx = None; 80 | // return res; 81 | // } 82 | } 83 | 84 | impl Clone for CelContext { 85 | fn clone(&self) -> Self { 86 | CelContext { 87 | progs: self.progs.clone(), 88 | } 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod test { 94 | 95 | use super::{BindContext, CelContext}; 96 | 97 | #[test] 98 | fn test_eval_basic() { 99 | let mut ctx = CelContext::new(); 100 | let exec_ctx = BindContext::new(); 101 | 102 | ctx.add_program_str("test_main", "3 + 4").unwrap(); 103 | 104 | let res = ctx.exec("test_main", &exec_ctx).unwrap(); 105 | 106 | assert!(res == 7.into()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /rscel/src/interp/mod.rs: -------------------------------------------------------------------------------- 1 | mod interp; 2 | mod types; 3 | 4 | pub use interp::Interpreter; 5 | pub use types::*; 6 | 7 | #[cfg(test)] 8 | mod test { 9 | use crate::{types::CelByteCode, CelValue}; 10 | 11 | use super::{types::ByteCode, Interpreter}; 12 | use test_case::test_case; 13 | 14 | #[test_case(ByteCode::Add, 7.into())] 15 | #[test_case(ByteCode::Sub, 1.into())] 16 | #[test_case(ByteCode::Mul, 12.into())] 17 | #[test_case(ByteCode::Div, 1.into())] 18 | #[test_case(ByteCode::Mod, 1.into())] 19 | #[test_case(ByteCode::Lt, false.into())] 20 | #[test_case(ByteCode::Le, false.into())] 21 | #[test_case(ByteCode::Eq, false.into())] 22 | #[test_case(ByteCode::Ne, true.into())] 23 | #[test_case(ByteCode::Ge, true.into())] 24 | #[test_case(ByteCode::Gt, true.into())] 25 | fn test_interp_ops(op: ByteCode, expected: CelValue) { 26 | let mut prog = 27 | CelByteCode::from_vec(vec![ByteCode::Push(4.into()), ByteCode::Push(3.into())]); 28 | prog.push(op); 29 | let interp = Interpreter::empty(); 30 | 31 | assert!(interp.run_raw(&prog, true).unwrap() == expected); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rscel/src/interp/types.rs: -------------------------------------------------------------------------------- 1 | mod bytecode; 2 | mod celstackvalue; 3 | mod rscallable; 4 | 5 | pub use bytecode::{ByteCode, JmpWhen}; 6 | pub use celstackvalue::CelStackValue; 7 | pub use rscallable::RsCallable; 8 | -------------------------------------------------------------------------------- /rscel/src/interp/types/bytecode.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::CelValue; 6 | 7 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 8 | pub enum JmpWhen { 9 | True, 10 | False, 11 | } 12 | 13 | impl JmpWhen { 14 | pub fn as_bool(&self) -> bool { 15 | match self { 16 | JmpWhen::True => true, 17 | JmpWhen::False => false, 18 | } 19 | } 20 | } 21 | 22 | #[derive(Clone, PartialEq, Serialize, Deserialize)] 23 | pub enum ByteCode { 24 | Push(CelValue), 25 | Pop, 26 | Test, 27 | Dup, 28 | Or, 29 | And, 30 | Not, 31 | Neg, 32 | Add, 33 | Sub, 34 | Mul, 35 | Div, 36 | Mod, 37 | Lt, 38 | Le, 39 | Eq, 40 | Ne, 41 | Ge, 42 | Gt, 43 | In, 44 | Jmp(i32), 45 | JmpCond { when: JmpWhen, dist: i32 }, 46 | MkList(u32), 47 | MkDict(u32), 48 | Index, 49 | Access, 50 | Call(u32), 51 | FmtString(u32), 52 | } 53 | 54 | impl fmt::Debug for ByteCode { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | use ByteCode::*; 57 | 58 | match self { 59 | Push(val) => write!(f, "PUSH {:?}", val), 60 | Pop => write!(f, "POP"), 61 | Test => write!(f, "TEST"), 62 | Dup => write!(f, "DUP"), 63 | Or => write!(f, "OR"), 64 | And => write!(f, "AND"), 65 | Not => write!(f, "NOT"), 66 | Neg => write!(f, "NEG"), 67 | Add => write!(f, "ADD"), 68 | Sub => write!(f, "SUB"), 69 | Mul => write!(f, "MUL"), 70 | Div => write!(f, "DIV"), 71 | Mod => write!(f, "MOD"), 72 | Lt => write!(f, "LT"), 73 | Le => write!(f, "LE"), 74 | Eq => write!(f, "EQ"), 75 | Ne => write!(f, "NE"), 76 | Ge => write!(f, "GE"), 77 | Gt => write!(f, "GT"), 78 | In => write!(f, "IN"), 79 | Jmp(dist) => write!(f, "JMP {}", dist), 80 | JmpCond { when, dist } => write!(f, "JMP {:?} {}", when, dist), 81 | MkList(size) => write!(f, "MKLIST {}", size), 82 | MkDict(size) => write!(f, "MKDICT {}", size), 83 | Index => write!(f, "INDEX"), 84 | Access => write!(f, "ACCESS"), 85 | Call(size) => write!(f, "CALL {}", size), 86 | FmtString(size) => write!(f, "FMT {}", size), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rscel/src/interp/types/celstackvalue.rs: -------------------------------------------------------------------------------- 1 | use crate::{CelError, CelResult, CelValue}; 2 | 3 | use super::RsCallable; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum CelStackValue<'a> { 7 | Value(CelValue), 8 | BoundCall { 9 | callable: RsCallable<'a>, 10 | value: CelValue, 11 | }, 12 | } 13 | 14 | impl<'a> CelStackValue<'a> { 15 | pub fn into_value(self) -> CelResult { 16 | match self { 17 | CelStackValue::Value(val) => Ok(val), 18 | _ => Err(CelError::internal("Expected value")), 19 | } 20 | } 21 | 22 | pub fn as_value(&'a self) -> CelResult<&'a CelValue> { 23 | match self { 24 | CelStackValue::Value(val) => Ok(val), 25 | _ => Err(CelError::internal("Expected value")), 26 | } 27 | } 28 | 29 | pub fn as_bound_call(&'a self) -> Option<(&'a RsCallable<'a>, &'a CelValue)> { 30 | match self { 31 | CelStackValue::BoundCall { callable, value } => Some((callable, value)), 32 | _ => None, 33 | } 34 | } 35 | } 36 | 37 | impl<'a> Into> for CelValue { 38 | fn into(self) -> CelStackValue<'a> { 39 | CelStackValue::Value(self) 40 | } 41 | } 42 | 43 | impl<'a> TryInto for CelStackValue<'a> { 44 | type Error = CelError; 45 | fn try_into(self) -> Result { 46 | if let CelStackValue::Value(val) = self { 47 | Ok(val) 48 | } else { 49 | Err(CelError::internal("Expected value 2")) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rscel/src/interp/types/rscallable.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{RsCelFunction, RsCelMacro}; 4 | 5 | /// Wrapper enum that contains either an RsCelCallable or an RsCelFunction. Used 6 | /// as a ValueCell value. 7 | #[derive(Clone)] 8 | pub enum RsCallable<'a> { 9 | Function(&'a RsCelFunction), 10 | Macro(&'a RsCelMacro), 11 | } 12 | 13 | impl<'a> fmt::Debug for RsCallable<'a> { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Self::Function(_) => write!(f, "Function"), 17 | Self::Macro(_) => write!(f, "Macro"), 18 | } 19 | } 20 | } 21 | 22 | impl<'a> PartialEq for RsCallable<'a> { 23 | fn eq(&self, _other: &Self) -> bool { 24 | false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rscel/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! RsCel is a CEL evaluator written in Rust. CEL is a google project that 2 | //! describes a turing-incomplete language that can be used to evaluate 3 | //! a user provdided expression. The language specification can be found 4 | //! [here](https://github.com/google/cel-spec/blob/master/doc/langdef.md). 5 | //! 6 | //! The design goals of this project were are as follows: 7 | //! * Flexible enough to allow for a user to bend the spec if needed 8 | //! * Sandbox'ed in such a way that only specific values can be bound 9 | //! * Can be used as a wasm depenedency (or other ffi) 10 | //! 11 | //! The basic example of how to use: 12 | //! ``` 13 | //! use rscel::{CelContext, BindContext, serde_json}; 14 | //! 15 | //! let mut ctx = CelContext::new(); 16 | //! let mut exec_ctx = BindContext::new(); 17 | //! 18 | //! ctx.add_program_str("main", "foo + 3").unwrap(); 19 | //! exec_ctx.bind_param("foo", 3.into()); // 3 converted to CelValue 20 | //! 21 | //! let res = ctx.exec("main", &exec_ctx).unwrap(); // CelValue::Int(6) 22 | //! assert_eq!(res, 6.into()); 23 | //! ``` 24 | //! As of 0.10.0 binding protobuf messages from the protobuf crate is now available! Given 25 | //! the following protobuf message: 26 | //! ```protobuf 27 | //! 28 | //! message Point { 29 | //! int32 x = 1; 30 | //! int32 y = 2; 31 | //! } 32 | //! 33 | //! ``` 34 | //! The following code can be used to evaluate a CEL expression on a Point message: 35 | //! 36 | //! ```ignore 37 | //! use rscel::{CelContext, BindContext}; 38 | //! 39 | //! // currently rscel required protobuf messages to be in a box 40 | //! let p = Box::new(protos::Point::new()); 41 | //! p.x = 4; 42 | //! p.y = 5; 43 | //! 44 | //! let mut ctx = CelContext::new(); 45 | //! let mut exec_ctx = BindContext::new(); 46 | //! 47 | //! ctx.add_program_str("main", "p.x + 3").unwrap(); 48 | //! exec_ctx.bind_protobuf_msg("p", p); 49 | //! 50 | //! assert_eq!(ctx.exec("main", &exec_ctx).unwrap(), 7.into()); 51 | //! 52 | //! ``` 53 | mod compiler; 54 | mod context; 55 | mod interp; 56 | mod program; 57 | mod types; 58 | 59 | // Export some public interface 60 | pub mod utils; 61 | pub use compiler::{ 62 | ast_node::AstNode, compiler::CelCompiler, grammar::*, source_location::SourceLocation, 63 | source_range::SourceRange, string_tokenizer::StringTokenizer, tokenizer::Tokenizer, 64 | }; 65 | pub use context::{BindContext, CelContext, RsCelFunction, RsCelMacro}; 66 | pub use interp::ByteCode; 67 | pub use program::{Program, ProgramDetails}; 68 | pub use types::{CelError, CelResult, CelValue, CelValueDyn}; 69 | 70 | // Some re-exports to allow a consistent use of serde 71 | pub use serde; 72 | pub use serde_json; 73 | 74 | pub use rscel_macro as macros; 75 | 76 | #[cfg(test)] 77 | mod tests; 78 | -------------------------------------------------------------------------------- /rscel/src/program/mod.rs: -------------------------------------------------------------------------------- 1 | mod program_details; 2 | 3 | use crate::{ 4 | compiler::{ast_node::AstNode, grammar::Expr}, 5 | types::CelByteCode, 6 | CelCompiler, CelResult, StringTokenizer, 7 | }; 8 | pub use program_details::ProgramDetails; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | #[derive(Debug, Deserialize, Serialize)] 12 | pub struct Program { 13 | details: ProgramDetails, 14 | bytecode: CelByteCode, 15 | } 16 | 17 | impl Program { 18 | pub fn new(details: ProgramDetails, bytecode: CelByteCode) -> Program { 19 | Program { details, bytecode } 20 | } 21 | 22 | pub fn from_source(source: &str) -> CelResult { 23 | CelCompiler::with_tokenizer(&mut StringTokenizer::with_input(source)).compile() 24 | } 25 | 26 | pub fn params<'a>(&'a self) -> Vec<&'a str> { 27 | self.details.params() 28 | } 29 | 30 | pub fn source<'a>(&'a self) -> Option<&'a str> { 31 | self.details.source() 32 | } 33 | 34 | pub fn into_details(self) -> ProgramDetails { 35 | self.details 36 | } 37 | 38 | pub fn details<'a>(&'a self) -> &'a ProgramDetails { 39 | &self.details 40 | } 41 | 42 | pub fn details_mut<'a>(&'a mut self) -> &'a mut ProgramDetails { 43 | &mut self.details 44 | } 45 | 46 | pub fn bytecode<'a>(&'a self) -> &'a CelByteCode { 47 | &self.bytecode 48 | } 49 | 50 | pub fn dumps_bc(&self) -> String { 51 | let mut lines = Vec::new(); 52 | 53 | for code in self.bytecode.iter() { 54 | lines.push(format!("{:?}", code)) 55 | } 56 | 57 | lines.join("\n") 58 | } 59 | 60 | pub fn ast<'a>(&'a self) -> Option<&'a AstNode> { 61 | self.details.ast() 62 | } 63 | } 64 | 65 | impl Clone for Program { 66 | fn clone(&self) -> Self { 67 | Program { 68 | details: self.details.clone(), 69 | bytecode: self.bytecode.clone(), 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /rscel/src/program/program_details.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | compiler::{ast_node::AstNode, grammar::Expr}, 3 | utils::IdentFilterIter, 4 | BindContext, 5 | }; 6 | use serde::{Deserialize, Serialize}; 7 | use serde_with::serde_as; 8 | use std::collections::HashSet; 9 | 10 | #[derive(Debug, Clone, Serialize, Deserialize)] 11 | #[serde_as] 12 | pub struct ProgramDetails { 13 | source: Option, 14 | params: HashSet, 15 | #[serde(skip_serializing, skip_deserializing)] 16 | ast: Option>, 17 | } 18 | 19 | impl ProgramDetails { 20 | pub fn new() -> ProgramDetails { 21 | ProgramDetails { 22 | source: None, 23 | params: HashSet::new(), 24 | ast: None, 25 | } 26 | } 27 | 28 | pub fn joined2(mut pd1: ProgramDetails, pd2: ProgramDetails) -> ProgramDetails { 29 | pd1.union_from(pd2); 30 | pd1 31 | } 32 | 33 | pub fn add_ast(&mut self, ast: AstNode) { 34 | self.ast = Some(ast); 35 | } 36 | 37 | pub fn add_source(&mut self, source: String) { 38 | self.source = Some(source); 39 | } 40 | 41 | pub fn union_from(&mut self, other: ProgramDetails) { 42 | for param in other.params.iter() { 43 | self.params.insert(param.to_string()); 44 | } 45 | } 46 | 47 | pub fn ast<'a>(&'a self) -> Option<&'a AstNode> { 48 | self.ast.as_ref() 49 | } 50 | 51 | pub fn source<'a>(&'a self) -> Option<&'a str> { 52 | self.source.as_deref() 53 | } 54 | 55 | pub fn add_param(&mut self, name: &str) { 56 | self.params.insert(name.to_owned()); 57 | } 58 | 59 | pub fn params<'a>(&'a self) -> Vec<&'a str> { 60 | self.params.iter().map(|x| x.as_str()).collect() 61 | } 62 | 63 | pub fn filter_from_bindings(&mut self, bindings: &BindContext) { 64 | self.params = 65 | IdentFilterIter::new(bindings, &mut self.params.iter().map(|x| x.as_str())).collect(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rscel/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod general_tests; 2 | mod neg_index_tests; 3 | mod type_prop_tests; 4 | 5 | #[cfg(test_protos)] 6 | #[cfg(feature = "protobuf")] 7 | mod proto_tests; 8 | -------------------------------------------------------------------------------- /rscel/src/tests/neg_index_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{BindContext, CelContext}; 2 | 3 | #[test] 4 | fn test_neg_index() { 5 | let mut ctx = CelContext::new(); 6 | let bindings = BindContext::new(); 7 | 8 | ctx.add_program_str("test1", "[1,2,3][-1]") 9 | .expect("Failed to compile program"); 10 | ctx.add_program_str("test2", "[1,2,3][-2]") 11 | .expect("Failed to compile program"); 12 | 13 | if cfg!(feature = "neg_index") { 14 | assert_eq!(ctx.exec("test1", &bindings).unwrap(), 3.into()); 15 | assert_eq!(ctx.exec("test2", &bindings).unwrap(), 2.into()); 16 | } else { 17 | assert!(ctx.exec("test1", &bindings).is_err()); 18 | assert!(ctx.exec("test2", &bindings).is_err()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rscel/src/tests/proto_tests.rs: -------------------------------------------------------------------------------- 1 | use protobuf::MessageField; 2 | use test_case::test_case; 3 | 4 | use crate::{BindContext, CelContext, CelValue}; 5 | 6 | mod protos { 7 | include!(concat!(env!("OUT_DIR"), "/test_protos/mod.rs")); 8 | } 9 | 10 | #[test_case("p.x", 3.into(); "message access")] 11 | #[test_case("c.unitialized_field == 0", true.into(); "unitialized field access")] 12 | #[test_case("c.enum_field == 1", true.into(); "enum int eq")] 13 | #[test_case("c.enum_field == 'FIELD2'", true.into(); "enum str eq")] 14 | #[test_case("c.nested_field.unitialized_field == 0", true.into(); "nexted unitialized field access")] 15 | #[test_case("c.oneof_field1 == 45", true.into(); "oneof field access")] 16 | fn proto_test(prog: &str, res: CelValue) { 17 | let mut ctx = CelContext::new(); 18 | let mut exec_ctx = BindContext::new(); 19 | 20 | let mut p = Box::new(protos::test::Point::new()); 21 | p.x = 3; 22 | p.y = 4; 23 | exec_ctx.bind_param_proto_msg("p", p); 24 | 25 | let mut c = Box::new(protos::test::TestMessage1::new()); 26 | c.initialized_field = 7; 27 | c.enum_field = protos::test::MyEnum::FIELD2.into(); 28 | let mut nested_c = protos::test::TestMessage1::new(); 29 | nested_c.initialized_field = 8; 30 | c.nested_field = MessageField::some(nested_c); 31 | c.set_oneof_field1(45); 32 | exec_ctx.bind_param_proto_msg("c", c); 33 | 34 | ctx.add_program_str("entry", prog) 35 | .expect("Failed to compile prog"); 36 | 37 | assert_eq!( 38 | ctx.exec("entry", &exec_ctx).expect("failed to run prog"), 39 | res 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /rscel/src/tests/type_prop_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{BindContext, CelContext, CelValue}; 2 | use std::assert; 3 | use test_case::test_case; 4 | 5 | #[test_case("3 + 2.1", 5.1; "type prop: int plus float")] 6 | #[test_case("2.1 + 3", 5.1; "type prop: float plus int")] 7 | #[test_case("3u + 2.1", 5.1; "type prop; unsigned plus float")] 8 | #[test_case("2.1 + 3u", 5.1; "type prop; float plus unsigned")] 9 | #[test_case("3 * 2.1", 6.3; "type prop: int times float")] 10 | #[test_case("2.1 * 3", 6.3; "type prop: float times int")] 11 | #[test_case("3u * 2.1", 6.3; "type prop; unsigned times float")] 12 | #[test_case("2.1 * 3u", 6.3; "type prop; float times unsigned")] 13 | #[test_case("3 - 2.1", 0.9; "type prop: int minus float")] 14 | #[test_case("2.1 - 3", -0.9; "type prop: float minus int")] 15 | #[test_case("3u - 2.1", 0.9; "type prop; unsigned minus float")] 16 | #[test_case("2.1 - 3u", -0.9; "type prop; float minus unsigned")] 17 | #[test_case("4 / 2.0", 2.0; "type prop: int div float")] 18 | #[test_case("4.0 / 2", 2.0; "type prop: float div int")] 19 | #[test_case("4u / 2.0", 2.0; "type prop; unsigned div float")] 20 | #[test_case("4.0 - 2u", 2.0; "type prop; float div unsigned")] 21 | fn test_equation_float(prog: &str, res: f64) { 22 | let mut ctx = CelContext::new(); 23 | let exec_ctx = BindContext::new(); 24 | 25 | ctx.add_program_str("main", prog).unwrap(); 26 | 27 | let eval_res = ctx.exec("main", &exec_ctx); 28 | if cfg!(feature = "type_prop") { 29 | if let CelValue::Float(f) = eval_res.unwrap() { 30 | assert!((f - res) < 0.00001); 31 | } else { 32 | panic!("Invalid type, expected float") 33 | } 34 | } else { 35 | assert!(eval_res.is_err()); 36 | } 37 | } 38 | 39 | #[test_case("2 + 1u", 3.into(); "type prop: int plus unsigned")] 40 | #[test_case("1u + 2", 3.into(); "type prop: unsigned plus ing")] 41 | #[test_case("1 && 1", true.into(); "type prop: int and int")] 42 | #[test_case("0 && 1u", false.into(); "type prop: int and unsigned")] 43 | #[test_case("0 || 1u", true.into(); "type prop: int or unsigned")] 44 | #[test_case("0 || 0", false.into(); "type prop: int or int")] 45 | #[test_case("0 ? 1 : 2", 2.into(); "type prop: int as bool")] 46 | #[test_case("\"\" ? 1 : 2", 2.into(); "type prop: empty string as bool")] 47 | #[test_case("[] ? 1 : 2", 2.into(); "type prop: empty list as bool")] 48 | #[test_case("{} ? 1 : 2", 2.into(); "type prop: empty obj as bool")] 49 | #[test_case("\"1\" ? 1 : 2", 1.into(); "type prop: full string as bool")] 50 | #[test_case("[1] ? 1 : 2", 1.into(); "type prop: full list as bool")] 51 | #[test_case("{\"foo\": 1} ? 1 : 2", 1.into(); "type prop: full obj as bool")] 52 | #[test_case("5 + (5 == 10 || 10 - 5 == 5) && false", false.into(); "Complex mixed precedence")] 53 | #[test_case("bool('3')", true.into(); "bool nonempty string")] 54 | #[test_case("bool('')", false.into(); "bool empty string")] 55 | #[test_case("bool([])", false.into(); "bool empty list")] 56 | #[test_case("bool([1])", true.into(); "bool nonempty list")] 57 | #[test_case("bool(null)", false.into(); "bool null")] 58 | #[test_case("bool({})", false.into(); "bool empty map")] 59 | #[test_case("bool({'foo': 42})", true.into(); "bool nonempty map")] 60 | #[test_case("bool(0)", false.into(); "bool zero")] 61 | #[test_case("bool(1)", true.into(); "bool nonzero")] 62 | fn test_equation(prog: &str, res: CelValue) { 63 | let mut ctx = CelContext::new(); 64 | let exec_ctx = BindContext::new(); 65 | 66 | ctx.add_program_str("main", prog).unwrap(); 67 | 68 | let eval_res = ctx.exec("main", &exec_ctx); 69 | println!("{:?}", eval_res); 70 | if cfg!(feature = "type_prop") { 71 | assert_eq!(eval_res.unwrap(), res); 72 | } else { 73 | assert!(eval_res.is_err()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rscel/src/types/cel_byte_code.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Index; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::interp::ByteCode; 6 | 7 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 8 | pub struct CelByteCode { 9 | inner: Vec, 10 | } 11 | 12 | impl CelByteCode { 13 | pub fn new() -> Self { 14 | CelByteCode { inner: Vec::new() } 15 | } 16 | 17 | pub fn from_code_point(code_point: ByteCode) -> Self { 18 | CelByteCode { 19 | inner: vec![code_point], 20 | } 21 | } 22 | 23 | pub fn from_vec(code_points: Vec) -> Self { 24 | CelByteCode { inner: code_points } 25 | } 26 | 27 | pub fn extend(&mut self, items: T) 28 | where 29 | T: IntoIterator, 30 | { 31 | self.inner.extend(items); 32 | } 33 | 34 | pub fn push(&mut self, code_point: ByteCode) { 35 | self.inner.push(code_point); 36 | } 37 | 38 | pub fn len(&self) -> usize { 39 | self.inner.len() 40 | } 41 | 42 | pub fn iter(&self) -> impl Iterator { 43 | self.inner.iter() 44 | } 45 | 46 | pub fn into_iter(self) -> impl Iterator { 47 | self.inner.into_iter() 48 | } 49 | 50 | pub fn as_slice(&self) -> &[ByteCode] { 51 | self.inner.as_slice() 52 | } 53 | } 54 | 55 | impl From> for CelByteCode { 56 | fn from(value: Vec) -> Self { 57 | CelByteCode { inner: value } 58 | } 59 | } 60 | 61 | impl Into> for CelByteCode { 62 | fn into(self) -> Vec { 63 | self.inner 64 | } 65 | } 66 | 67 | impl Index for CelByteCode { 68 | type Output = ByteCode; 69 | 70 | fn index(&self, index: usize) -> &Self::Output { 71 | &self.inner[index] 72 | } 73 | } 74 | 75 | impl FromIterator for CelByteCode { 76 | fn from_iter>(iter: T) -> Self { 77 | CelByteCode { 78 | inner: iter.into_iter().collect(), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rscel/src/types/cel_bytes.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] 4 | pub struct CelBytes { 5 | inner: Vec, 6 | } 7 | 8 | impl CelBytes { 9 | pub fn new() -> Self { 10 | CelBytes { inner: Vec::new() } 11 | } 12 | 13 | pub fn from_vec(bytes: Vec) -> Self { 14 | CelBytes { inner: bytes } 15 | } 16 | 17 | pub fn len(&self) -> usize { 18 | self.inner.len() 19 | } 20 | 21 | pub fn into_vec(self) -> Vec { 22 | self.inner 23 | } 24 | 25 | pub fn extend(&mut self, bytes: T) 26 | where 27 | T: IntoIterator, 28 | { 29 | self.inner.extend(bytes.into_iter()); 30 | } 31 | 32 | pub fn as_slice(&self) -> &[u8] { 33 | self.inner.as_ref() 34 | } 35 | } 36 | 37 | impl From> for CelBytes { 38 | fn from(value: Vec) -> Self { 39 | CelBytes::from_vec(value) 40 | } 41 | } 42 | 43 | impl Into> for CelBytes { 44 | fn into(self) -> Vec { 45 | self.inner 46 | } 47 | } 48 | 49 | impl IntoIterator for CelBytes { 50 | type Item = u8; 51 | type IntoIter = std::vec::IntoIter; 52 | 53 | fn into_iter(self) -> Self::IntoIter { 54 | self.inner.into_iter() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rscel/src/types/cel_error.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::compiler::syntax_error::SyntaxError; 6 | 7 | #[derive(Serialize, Deserialize, Clone, Debug)] 8 | pub enum CelError { 9 | Misc(String), 10 | Syntax(SyntaxError), 11 | Value(String), 12 | Argument(String), 13 | InvalidOp(String), 14 | Runtime(String), 15 | Binding { symbol: String }, 16 | Attribute { parent: String, field: String }, 17 | DivideByZero, 18 | 19 | Internal(String), 20 | } 21 | pub type CelResult = Result; 22 | 23 | impl CelError { 24 | pub fn misc(msg: &str) -> CelError { 25 | CelError::Misc(msg.to_owned()) 26 | } 27 | 28 | pub fn syntax(err: SyntaxError) -> CelError { 29 | CelError::Syntax(err) 30 | } 31 | 32 | pub fn value(msg: &str) -> CelError { 33 | CelError::Value(msg.to_owned()) 34 | } 35 | 36 | pub fn argument(msg: &str) -> CelError { 37 | CelError::Argument(msg.to_owned()) 38 | } 39 | 40 | pub fn internal(msg: &str) -> CelError { 41 | CelError::Internal(msg.to_owned()) 42 | } 43 | 44 | pub fn invalid_op(msg: &str) -> CelError { 45 | CelError::InvalidOp(msg.to_owned()) 46 | } 47 | 48 | pub fn runtime(msg: &str) -> CelError { 49 | CelError::Runtime(msg.to_owned()) 50 | } 51 | 52 | pub fn binding(sym_name: &str) -> CelError { 53 | CelError::Binding { 54 | symbol: sym_name.to_owned(), 55 | } 56 | } 57 | 58 | pub fn attribute(parent_name: &str, field_name: &str) -> CelError { 59 | CelError::Attribute { 60 | parent: parent_name.to_string(), 61 | field: field_name.to_string(), 62 | } 63 | } 64 | 65 | pub fn type_string(&self) -> &'static str { 66 | use CelError::*; 67 | 68 | match self { 69 | Misc(..) => "MISC", 70 | Syntax { .. } => "SYNTAX", 71 | Value(..) => "VALUE", 72 | Argument(..) => "ARGUMENT", 73 | InvalidOp(..) => "INVALID OP", 74 | Runtime(_) => "RUNTIME", 75 | Binding { .. } => "BINDING", 76 | Attribute { .. } => "ATTRIBUTE", 77 | DivideByZero => "DIVIDE BY ZERO", 78 | 79 | Internal(..) => "INTERNAL", 80 | } 81 | } 82 | } 83 | 84 | impl From for CelError { 85 | fn from(value: SyntaxError) -> Self { 86 | CelError::Syntax(value) 87 | } 88 | } 89 | 90 | impl fmt::Display for CelError { 91 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | use CelError::*; 93 | 94 | match self { 95 | Misc(msg) => write!(f, "{}", msg), 96 | Syntax(err) => write!(f, "Syntax Error: {}", err), 97 | 98 | Value(msg) => write!(f, "{}", msg), 99 | Argument(msg) => write!(f, "{}", msg), 100 | InvalidOp(msg) => write!(f, "{}", msg), 101 | 102 | Internal(msg) => write!(f, "{}", msg), 103 | Runtime(msg) => write!(f, "{}", msg), 104 | Binding { symbol } => write!(f, "Symbol not bound: {}", symbol), 105 | Attribute { parent, field } => { 106 | write!(f, "Field {} does not exist on {}", field, parent) 107 | } 108 | DivideByZero => write!(f, "Divide by zero error"), 109 | } 110 | } 111 | } 112 | 113 | impl error::Error for CelError {} 114 | -------------------------------------------------------------------------------- /rscel/src/types/cel_value_dyn.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, fmt}; 2 | 3 | use crate::CelValue; 4 | 5 | // unsure how much i want to support with this... 6 | // im thinking I allow object like things only for the first iteration and 7 | // slowly move towards the whole CelValue's set of operations 8 | pub trait CelValueDyn: fmt::Debug + fmt::Display + Send + Sync { 9 | fn as_type(&self) -> CelValue; 10 | fn access(&self, key: &str) -> CelValue; 11 | fn eq(&self, rhs: &CelValue) -> CelValue; 12 | fn is_truthy(&self) -> bool; 13 | fn any_ref<'a>(&'a self) -> &'a dyn Any; 14 | } 15 | -------------------------------------------------------------------------------- /rscel/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cel_byte_code; 2 | pub mod cel_bytes; 3 | pub mod cel_error; 4 | pub mod cel_value; 5 | pub mod cel_value_dyn; 6 | 7 | pub use cel_byte_code::CelByteCode; 8 | pub use cel_bytes::CelBytes; 9 | pub use cel_error::{CelError, CelResult}; 10 | pub use cel_value::CelValue; 11 | pub use cel_value_dyn::CelValueDyn; 12 | -------------------------------------------------------------------------------- /rscel/src/utils/eval_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{interp::Interpreter, types::CelByteCode, CelError, CelResult, CelValue}; 2 | 3 | pub fn eval_ident(prog: &CelByteCode) -> CelResult { 4 | let interp = Interpreter::empty(); 5 | 6 | if let CelValue::Ident(ident) = interp.run_raw(prog, false)? { 7 | Ok(ident) 8 | } else { 9 | Err(CelError::misc("ident required")) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rscel/src/utils/ident_filter.rs: -------------------------------------------------------------------------------- 1 | use crate::BindContext; 2 | 3 | pub struct IdentFilterIter<'a> { 4 | bindings: &'a BindContext<'a>, 5 | iter: &'a mut dyn Iterator, 6 | } 7 | 8 | impl<'a> IdentFilterIter<'a> { 9 | pub fn new( 10 | bindings: &'a BindContext, 11 | iterable: &'a mut dyn Iterator, 12 | ) -> IdentFilterIter<'a> { 13 | IdentFilterIter { 14 | bindings, 15 | iter: iterable, 16 | } 17 | } 18 | } 19 | 20 | impl<'a> Iterator for IdentFilterIter<'a> { 21 | type Item = String; 22 | 23 | fn next(&mut self) -> Option { 24 | loop { 25 | match self.iter.next() { 26 | Some(val) => { 27 | if self.bindings.is_bound(val) { 28 | continue; 29 | } 30 | 31 | return Some(val.to_owned()); 32 | } 33 | None => return None, 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rscel/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod eval_utils; 2 | mod ident_filter; 3 | mod scoped_counter; 4 | 5 | pub use eval_utils::eval_ident; 6 | pub use ident_filter::IdentFilterIter; 7 | pub use scoped_counter::ScopedCounter; 8 | -------------------------------------------------------------------------------- /rscel/src/utils/scoped_counter.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | pub struct ScopedCounter { 4 | count: RefCell, 5 | } 6 | 7 | pub struct ScopedCounterRef<'a> { 8 | count: &'a RefCell, 9 | } 10 | 11 | impl ScopedCounter { 12 | pub fn new() -> ScopedCounter { 13 | ScopedCounter { 14 | count: RefCell::new(0), 15 | } 16 | } 17 | 18 | pub fn count(&self) -> usize { 19 | *self.count.borrow() 20 | } 21 | 22 | pub fn inc<'a>(&'a self) -> ScopedCounterRef<'a> { 23 | { 24 | let mut count = self.count.borrow_mut(); 25 | 26 | *count += 1; 27 | } 28 | ScopedCounterRef { count: &self.count } 29 | } 30 | } 31 | 32 | impl<'a> ScopedCounterRef<'a> { 33 | pub fn count(&self) -> usize { 34 | *self.count.borrow() 35 | } 36 | } 37 | 38 | impl<'a> Drop for ScopedCounterRef<'a> { 39 | fn drop(&mut self) { 40 | let mut count = self.count.borrow_mut(); 41 | 42 | *count -= 1; 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod test { 48 | use super::ScopedCounter; 49 | 50 | #[test] 51 | fn test_scoped_counter() { 52 | let counter = ScopedCounter::new(); 53 | 54 | assert_eq!(counter.count(), 0); 55 | 56 | { 57 | let c = counter.inc(); 58 | 59 | assert_eq!(c.count(), 1); 60 | assert_eq!(counter.count(), 1); 61 | } 62 | 63 | assert_eq!(counter.count(), 0); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rscel/test/protos/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/descriptor.proto"; 4 | 5 | message Point { 6 | int32 x = 1; 7 | int32 y = 2; 8 | } 9 | 10 | enum MyEnum { 11 | FIELD1 = 0; 12 | FIELD2 = 1; 13 | } 14 | 15 | message TestMessage1 { 16 | int32 initialized_field = 1; 17 | int32 unitialized_field = 2; 18 | 19 | MyEnum enum_field = 3; 20 | 21 | TestMessage1 nested_field = 4; 22 | 23 | oneof oneof_field { 24 | int32 oneof_field1 = 5; 25 | string oneof_field2 = 6; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | 4 | -------------------------------------------------------------------------------- /test/cel_spec_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1BADragon/rscel/66a1e9e99f72bc9b9b44142a238219567ab3c2e9/test/cel_spec_tests/__init__.py -------------------------------------------------------------------------------- /test/cel_spec_tests/proto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1BADragon/rscel/66a1e9e99f72bc9b9b44142a238219567ab3c2e9/test/cel_spec_tests/proto/__init__.py -------------------------------------------------------------------------------- /test/cel_spec_tests/proto/google/api/expr/v1alpha1/eval_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/api/expr/v1alpha1/eval.proto 4 | # Protobuf Python Version: 4.25.3 5 | """Generated protocol buffer code.""" 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | from google.protobuf.internal import builder as _builder 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | from google.api.expr.v1alpha1 import value_pb2 as google_dot_api_dot_expr_dot_v1alpha1_dot_value__pb2 16 | from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2 17 | 18 | 19 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#google/api/expr/v1alpha1/eval.proto\x12\x18google.api.expr.v1alpha1\x1a$google/api/expr/v1alpha1/value.proto\x1a\x17google/rpc/status.proto\"\xa4\x01\n\tEvalState\x12\x33\n\x06values\x18\x01 \x03(\x0b\x32#.google.api.expr.v1alpha1.ExprValue\x12;\n\x07results\x18\x03 \x03(\x0b\x32*.google.api.expr.v1alpha1.EvalState.Result\x1a%\n\x06Result\x12\x0c\n\x04\x65xpr\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x03\"\xb3\x01\n\tExprValue\x12\x30\n\x05value\x18\x01 \x01(\x0b\x32\x1f.google.api.expr.v1alpha1.ValueH\x00\x12\x33\n\x05\x65rror\x18\x02 \x01(\x0b\x32\".google.api.expr.v1alpha1.ErrorSetH\x00\x12\x37\n\x07unknown\x18\x03 \x01(\x0b\x32$.google.api.expr.v1alpha1.UnknownSetH\x00\x42\x06\n\x04kind\".\n\x08\x45rrorSet\x12\"\n\x06\x65rrors\x18\x01 \x03(\x0b\x32\x12.google.rpc.Status\"\x1b\n\nUnknownSet\x12\r\n\x05\x65xprs\x18\x01 \x03(\x03\x42l\n\x1c\x63om.google.api.expr.v1alpha1B\tEvalProtoP\x01Z\n\nexpr_steps\x18\x02 \x03(\x0b\x32*.google.api.expr.v1alpha1.Explain.ExprStep\x1a+\n\x08\x45xprStep\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x13\n\x0bvalue_index\x18\x02 \x01(\x05:\x02\x18\x01\x42o\n\x1c\x63om.google.api.expr.v1alpha1B\x0c\x45xplainProtoP\x01Z 4 ? 'baz' : 'quux'" 19 | eval_error: { 20 | errors: { message: "division by zero" } 21 | } 22 | } 23 | test { 24 | name: "mixed_type" 25 | expr: "true ? 'cows' : 17" 26 | disable_check: true 27 | value: { string_value: "cows" } 28 | } 29 | test { 30 | name: "bad_type" 31 | expr: "'cows' ? false : 17" 32 | disable_check: true 33 | eval_error: { 34 | errors: { message: "no matching overload" } 35 | } 36 | } 37 | } 38 | section { 39 | name: "AND" 40 | description: "Tests for logical AND." 41 | test { 42 | name: "all_true" 43 | expr: "true && true" 44 | value: { bool_value: true } 45 | } 46 | test { 47 | name: "all_false" 48 | expr: "false && false" 49 | value: { bool_value: false } 50 | } 51 | test { 52 | name: "false_left" 53 | expr: "false && true" 54 | value: { bool_value: false } 55 | } 56 | test { 57 | name: "false_right" 58 | expr: "true && false" 59 | value: { bool_value: false } 60 | } 61 | test { 62 | name: "short_circuit_type_left" 63 | expr: "false && 32" 64 | disable_check: true 65 | value: { bool_value: false } 66 | } 67 | test { 68 | name: "short_circuit_type_right" 69 | expr: "'horses' && false" 70 | disable_check: true 71 | value: { bool_value: false } 72 | } 73 | test { 74 | name: "short_circuit_error_left" 75 | expr: "false && (2 / 0 > 3 ? false : true)" 76 | value: { bool_value: false } 77 | } 78 | test { 79 | name: "short_circuit_error_right" 80 | expr: "(2 / 0 > 3 ? false : true) && false" 81 | value: { bool_value: false } 82 | } 83 | test { 84 | name: "error_right" 85 | expr: "true && 1/0 != 0" 86 | eval_error: { 87 | errors: { message: "no matching overload" } 88 | } 89 | } 90 | test { 91 | name: "error_left" 92 | expr: "1/0 != 0 && true" 93 | eval_error: { 94 | errors: { message: "no matching overload" } 95 | } 96 | } 97 | test { 98 | name: "no_overload" 99 | expr: "'less filling' && 'tastes great'" 100 | disable_check: true 101 | eval_error: { 102 | errors: { message: "no matching overload" } 103 | } 104 | } 105 | } 106 | section { 107 | name: "OR" 108 | description: "Tests for logical OR" 109 | test { 110 | name: "all_true" 111 | expr: "true || true" 112 | value: { bool_value: true } 113 | } 114 | test { 115 | name: "all_false" 116 | expr: "false || false" 117 | value: { bool_value: false } 118 | } 119 | test { 120 | name: "false_left" 121 | expr: "false || true" 122 | value: { bool_value: true } 123 | } 124 | test { 125 | name: "false_right" 126 | expr: "true || false" 127 | value: { bool_value: true } 128 | } 129 | test { 130 | name: "short_circuit_type_left" 131 | expr: "true || 32" 132 | disable_check: true 133 | value: { bool_value: true } 134 | } 135 | test { 136 | name: "short_circuit_type_right" 137 | expr: "'horses' || true" 138 | disable_check: true 139 | value: { bool_value: true } 140 | } 141 | test { 142 | name: "short_circuit_error_left" 143 | expr: "true || (2 / 0 > 3 ? false : true)" 144 | value: { bool_value: true } 145 | } 146 | test { 147 | name: "short_circuit_error_right" 148 | expr: "(2 / 0 > 3 ? false : true) || true" 149 | value: { bool_value: true } 150 | } 151 | test { 152 | name: "error_right" 153 | expr: "false || 1/0 != 0" 154 | eval_error: { 155 | errors: { message: "no matching overload" } 156 | } 157 | } 158 | test { 159 | name: "error_left" 160 | expr: "1/0 != 0 || false" 161 | eval_error: { 162 | errors: { message: "no matching overload" } 163 | } 164 | } 165 | test { 166 | name: "no_overload" 167 | expr: "'less filling' || 'tastes great'" 168 | disable_check: true 169 | eval_error: { 170 | errors: { message: "no matching overload" } 171 | } 172 | } 173 | } 174 | section { 175 | name: "NOT" 176 | description: "Tests for logical NOT." 177 | test { 178 | name: "not_true" 179 | expr: "!true" 180 | value: { bool_value: false } 181 | } 182 | test { 183 | name: "not_false" 184 | expr: "!false" 185 | value: { bool_value: true } 186 | } 187 | test { 188 | name: "no_overload" 189 | expr: "!0" 190 | disable_check: true 191 | eval_error: { 192 | errors: { message: "no matching overload" } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /test/cel_spec_tests/simple-test-data/namespace.textproto: -------------------------------------------------------------------------------- 1 | name: "namespace" 2 | description: "Uses of qualified identifiers and namespaces." 3 | section { 4 | name: "qualified" 5 | description: "Qualified variable lookups." 6 | test { 7 | name: "self_eval_qualified_lookup" 8 | expr: "x.y" 9 | value: { bool_value: true } 10 | type_env: { 11 | name: "x.y", 12 | ident: { type: { primitive: BOOL } } 13 | } 14 | bindings: { 15 | key: "x.y" 16 | value: { value: { bool_value: true } } 17 | } 18 | } 19 | } 20 | section { 21 | name: "namespace" 22 | description: "Namespaced identifiers." 23 | test { 24 | name: "self_eval_container_lookup" 25 | expr: "y" 26 | container: "x" 27 | type_env: { 28 | name: "x.y", 29 | ident: { type: { primitive: BOOL } } 30 | } 31 | type_env: { 32 | name: "y", 33 | ident: { type: { primitive: STRING } } 34 | } 35 | bindings: { 36 | key: "x.y" 37 | value: { value: { bool_value: true } } 38 | } 39 | bindings: { 40 | key: "y" 41 | value: { value: { string_value: "false" } } 42 | } 43 | value: { bool_value: true } 44 | } 45 | test { 46 | name: "self_eval_container_lookup_unchecked" 47 | expr: "y" 48 | container: "x" 49 | type_env: { 50 | name: "x.y", 51 | ident: { type: { primitive: BOOL } } 52 | } 53 | type_env: { 54 | name: "y", 55 | ident: { type: { primitive: BOOL } } 56 | } 57 | bindings: { 58 | key: "x.y" 59 | value: { value: { bool_value: true } } 60 | } 61 | bindings: { 62 | key: "y" 63 | value: { value: { bool_value: false } } 64 | } 65 | disable_check: true ## ensure unchecked ASTs resolve the same as checked ASTs 66 | value: { bool_value: true } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/cel_spec_tests/simple-test-data/plumbing.textproto: -------------------------------------------------------------------------------- 1 | name: "plumbing" 2 | description: "Check that the ConformanceService server can accept all arguments and return all responses." 3 | section { 4 | name: "min" 5 | description: "Minimal programs." 6 | test { 7 | name: "min_program" 8 | description: "Smallest functionality: expr in, result out." 9 | expr: "17" 10 | value: { int64_value: 17 } 11 | } 12 | } 13 | section { 14 | name: "eval_results" 15 | description: "All evaluation result kinds." 16 | test { 17 | name: "error_result" 18 | description: "Check that error results go through." 19 | expr: "1 / 0" 20 | eval_error: { 21 | errors: { message: "foo" } 22 | } 23 | } 24 | test { 25 | name: "eval_map_results" 26 | description: "Check that map literals results are order independent." 27 | expr: '{"k1":"v1","k":"v"}' 28 | value: { 29 | map_value { 30 | entries { 31 | key: { string_value: "k" } 32 | value: { string_value: "v" } 33 | } 34 | entries { 35 | key: { string_value: "k1" } 36 | value: { string_value: "v1" } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | section { 43 | name: "check_inputs" 44 | description: "All inputs to Check phase." 45 | test { 46 | name: "skip_check" 47 | description: "Make sure we can skip type checking." 48 | expr: "[17, 'pancakes']" 49 | disable_check: true 50 | value: { 51 | list_value { 52 | values: { int64_value: 17 } 53 | values: { string_value: "pancakes" } 54 | } 55 | } 56 | } 57 | } 58 | section { 59 | name: "eval_inputs" 60 | description: "All inputs to Eval phase." 61 | test { 62 | name: "one_ignored_value_arg" 63 | description: "Check that value bindings can be given, even if ignored." 64 | expr: "'foo'" 65 | bindings: { 66 | key: "x" 67 | value: { value: { int64_value: 17 } } 68 | } 69 | value: { string_value: "foo" } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/cel_spec_tests/simple-test-data/unknowns.textproto: -------------------------------------------------------------------------------- 1 | name: "unknowns" 2 | description: "Tests for evaluation with unknown inputs." 3 | -------------------------------------------------------------------------------- /test/google/api/expr/v1alpha1/eval_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/api/expr/v1alpha1/eval.proto 4 | # Protobuf Python Version: 4.25.3 5 | """Generated protocol buffer code.""" 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | from google.protobuf.internal import builder as _builder 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | from google.api.expr.v1alpha1 import value_pb2 as google_dot_api_dot_expr_dot_v1alpha1_dot_value__pb2 16 | from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2 17 | 18 | 19 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#google/api/expr/v1alpha1/eval.proto\x12\x18google.api.expr.v1alpha1\x1a$google/api/expr/v1alpha1/value.proto\x1a\x17google/rpc/status.proto\"\xa4\x01\n\tEvalState\x12\x33\n\x06values\x18\x01 \x03(\x0b\x32#.google.api.expr.v1alpha1.ExprValue\x12;\n\x07results\x18\x03 \x03(\x0b\x32*.google.api.expr.v1alpha1.EvalState.Result\x1a%\n\x06Result\x12\x0c\n\x04\x65xpr\x18\x01 \x01(\x03\x12\r\n\x05value\x18\x02 \x01(\x03\"\xb3\x01\n\tExprValue\x12\x30\n\x05value\x18\x01 \x01(\x0b\x32\x1f.google.api.expr.v1alpha1.ValueH\x00\x12\x33\n\x05\x65rror\x18\x02 \x01(\x0b\x32\".google.api.expr.v1alpha1.ErrorSetH\x00\x12\x37\n\x07unknown\x18\x03 \x01(\x0b\x32$.google.api.expr.v1alpha1.UnknownSetH\x00\x42\x06\n\x04kind\".\n\x08\x45rrorSet\x12\"\n\x06\x65rrors\x18\x01 \x03(\x0b\x32\x12.google.rpc.Status\"\x1b\n\nUnknownSet\x12\r\n\x05\x65xprs\x18\x01 \x03(\x03\x42l\n\x1c\x63om.google.api.expr.v1alpha1B\tEvalProtoP\x01Z\n\nexpr_steps\x18\x02 \x03(\x0b\x32*.google.api.expr.v1alpha1.Explain.ExprStep\x1a+\n\x08\x45xprStep\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x13\n\x0bvalue_index\x18\x02 \x01(\x05:\x02\x18\x01\x42o\n\x1c\x63om.google.api.expr.v1alpha1B\x0c\x45xplainProtoP\x01Z(false); 9 | 10 | useEffect(() => { 11 | init().then((_res: any) => { 12 | setIsInit(true); 13 | }); 14 | }, []); 15 | 16 | if (!isInit) { 17 | return
Loading...
; 18 | } 19 | return ( 20 |
21 | 22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /wasm/example/src/components/CelComponent.css: -------------------------------------------------------------------------------- 1 | input, 2 | label { 3 | display: block; 4 | } 5 | -------------------------------------------------------------------------------- /wasm/example/src/components/CelComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "./CelComponent.css"; 3 | 4 | import { useState } from "react"; 5 | import { celDetails, celEval } from "rscel"; 6 | 7 | export default function CelComponent() { 8 | const [errorMessage, setErrorMessage] = useState(""); 9 | const [prog, setProg] = useState(""); 10 | const [params, setParams] = useState([]); 11 | const [paramVals, setParamVals] = useState({}); 12 | const [lastResult, setLastResult] = useState(undefined); 13 | 14 | const generateParams = (): JSX.Element[] => { 15 | return params.map((val) => { 16 | return ( 17 |
18 | 19 | { 22 | setParamVals((old: any) => { 23 | try { 24 | let newObj = { ...old }; 25 | newObj[val] = JSON.parse(event.target.value, (_, val) => { 26 | return typeof val === "string" && val.endsWith("n") 27 | ? BigInt(val.slice(0, -1)) 28 | : val; 29 | }); 30 | setErrorMessage(""); 31 | return newObj; 32 | } catch (e) { 33 | setErrorMessage(e.toString()); 34 | return old; 35 | } 36 | }); 37 | }} 38 | /> 39 |
40 | ); 41 | }); 42 | }; 43 | 44 | const renderResult = (currResult: any): JSX.Element => { 45 | switch (typeof currResult) { 46 | case "boolean": 47 | return ; 48 | case "number": 49 | return ; 50 | case "bigint": 51 | return ; 52 | case "string": 53 | return ; 54 | case "object": 55 | if (Array.isArray(currResult)) { 56 | return ( 57 | <> 58 | 59 |
60 | {currResult.map((value, index) => ( 61 | {renderResult(value)} 62 | ))} 63 |
64 | 65 | 66 | ); 67 | } else { 68 | return ( 69 | <> 70 | 71 |
72 | {Object.entries(currResult).map(([key, value], index) => { 73 | return ( 74 | 75 | 76 | {renderResult(value)} 77 | 78 | ); 79 | })} 80 |
81 | 82 | 83 | ); 84 | } 85 | } 86 | }; 87 | 88 | return ( 89 |
90 |

RsCel Evaluater

91 | 92 |