diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index b09e865d..e38b44a8 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } +bumpalo = "3" either = "1.13" pyo3 = { version = "0.23", features = ["anyhow", "serde", "either"] } pyo3-async-runtimes = { version = "0.23", features = ["tokio-runtime", "attributes"] } diff --git a/bindings/python/src/expression.rs b/bindings/python/src/expression.rs index 1e2f982a..2d32d013 100644 --- a/bindings/python/src/expression.rs +++ b/bindings/python/src/expression.rs @@ -1,10 +1,11 @@ use crate::variable::PyVariable; use anyhow::{anyhow, Context}; use either::Either; -use pyo3::types::PyDict; +use pyo3::types::{PyAnyMethods, PyDict}; use pyo3::{pyclass, pyfunction, pymethods, Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python}; use pythonize::depythonize; use zen_expression::expression::{Standard, Unary}; +use zen_expression::validate::{validate_expression, validate_unary_expression, ValidationError}; use zen_expression::{Expression, Variable}; #[pyfunction] @@ -97,3 +98,31 @@ impl PyExpression { PyVariable(result).into_py_any(py) } } + +#[pyfunction] +#[pyo3(name = "validate_expression")] +pub fn py_validate_expression(py: Python, expression: String) -> PyResult>> { + let Some(err) = validate_expression(expression.as_str()) else { + return Ok(None); + }; + return Ok(Some(convert_error_to_dict(py, &err))); +} + +#[pyfunction] +#[pyo3(name = "validate_unary_expression")] +pub fn py_validate_unary_expression( + py: Python, + expression: String, +) -> PyResult>> { + let Some(err) = validate_unary_expression(expression.as_str()) else { + return Ok(None); + }; + return Ok(Some(convert_error_to_dict(py, &err))); +} + +fn convert_error_to_dict(py: Python, err: &ValidationError) -> Py { + let dict = PyDict::new(py); + dict.set_item("type", &err.error_type).unwrap(); + dict.set_item("source", &err.source).unwrap(); + return dict.unbind(); +} diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 4af01d59..b2b2cd16 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -2,7 +2,7 @@ use crate::decision::PyZenDecision; use crate::engine::PyZenEngine; use crate::expression::{ compile_expression, compile_unary_expression, evaluate_expression, evaluate_unary_expression, - render_template, PyExpression, + py_validate_expression, py_validate_unary_expression, render_template, PyExpression, }; use pyo3::prelude::PyModuleMethods; use pyo3::types::PyModule; @@ -28,6 +28,8 @@ fn zen(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(render_template, m)?)?; m.add_function(wrap_pyfunction!(compile_expression, m)?)?; m.add_function(wrap_pyfunction!(compile_unary_expression, m)?)?; + m.add_function(wrap_pyfunction!(py_validate_expression, m)?)?; + m.add_function(wrap_pyfunction!(py_validate_unary_expression, m)?)?; Ok(()) } diff --git a/bindings/python/zen.pyi b/bindings/python/zen.pyi index b1791e4d..b2a77ff7 100644 --- a/bindings/python/zen.pyi +++ b/bindings/python/zen.pyi @@ -1,5 +1,5 @@ from collections.abc import Awaitable -from typing import Any, Optional, TypedDict +from typing import Any, Optional, TypedDict, Literal class EvaluateResponse(TypedDict): @@ -42,5 +42,18 @@ def compile_expression(expression: str) -> Expression: ... def compile_unary_expression(expression: str) -> Expression: ... + class Expression: def evaluate(self, ctx: Optional[dict] = None) -> Any: ... + + +def validate_expression(expression: str) -> Optional[ValidationResponse]: ... + + +def validate_unary_expression(expression: str) -> Optional[ValidationResponse]: ... + + +class ValidationResponse(TypedDict): + type: Literal["lexerError","parserError", "compilerError"] + source: str + diff --git a/core/expression/src/lib.rs b/core/expression/src/lib.rs index e61ef9d0..e21a65c8 100644 --- a/core/expression/src/lib.rs +++ b/core/expression/src/lib.rs @@ -64,6 +64,7 @@ mod function; pub mod intellisense; pub mod lexer; pub mod parser; +pub mod validate; pub mod variable; pub mod vm; diff --git a/core/expression/src/validate.rs b/core/expression/src/validate.rs new file mode 100644 index 00000000..b2341f89 --- /dev/null +++ b/core/expression/src/validate.rs @@ -0,0 +1,98 @@ +use bumpalo::Bump; + +use crate::{compiler::Compiler, lexer::Lexer, parser::Parser}; + +pub struct ValidationError { + pub error_type: String, + pub source: String, +} + +pub fn validate_unary_expression(expression: &str) -> Option { + let mut lexer = Lexer::new(); + let tokens = match lexer.tokenize(expression) { + Err(e) => { + return Some(ValidationError { + error_type: "lexerError".to_string(), + source: e.to_string(), + }) + } + Ok(tokens) => tokens, + }; + + let bump = Bump::new(); + let parser = match Parser::try_new(tokens, &bump) { + Err(e) => { + return Some(ValidationError { + error_type: "parserError".to_string(), + source: e.to_string(), + }) + } + Ok(p) => p.unary(), + }; + + let parser_result = parser.parse(); + match parser_result.error() { + Err(e) => { + return Some(ValidationError { + error_type: "parserError".to_string(), + source: e.to_string(), + }) + } + Ok(n) => n, + }; + + let mut compiler = Compiler::new(); + if let Err(e) = compiler.compile(parser_result.root) { + return Some(ValidationError { + error_type: "compilerError".to_string(), + source: e.to_string(), + }); + } + + None +} + +pub fn validate_expression(expression: &str) -> Option { + let mut lexer = Lexer::new(); + let tokens = match lexer.tokenize(expression) { + Err(e) => { + return Some(ValidationError { + error_type: "lexerError".to_string(), + source: e.to_string(), + }) + } + Ok(tokens) => tokens, + }; + + let bump = Bump::new(); + let parser = match Parser::try_new(tokens, &bump) { + Err(e) => { + return Some(ValidationError { + error_type: "parserError".to_string(), + source: e.to_string(), + }) + } + Ok(p) => p.standard(), + }; + + let parser_result = parser.parse(); + match parser_result.error() { + Err(e) => { + return Some(ValidationError { + error_type: "parserError".to_string(), + source: e.to_string(), + }) + } + Ok(n) => n, + }; + + let mut compiler = Compiler::new(); + if let Err(e) = compiler.compile(parser_result.root) { + return Some(ValidationError { + error_type: "compilerError".to_string(), + source: e.to_string(), + }); + } + + None +}