Repository navigation
Conversation
Evaluating several rules against one FromKV input from multiple goroutines raced on the unsynchronized memo map (fatal: concurrent map read and map write). Guard the map with a mutex and resolve each lazy path once via a per-path entry; plain values return before touching the memo.
Add json.Marshaler on *AST plus AST.JSON and AST.JSONSpan, with public JSONAST/JSONNode/JSONToken/JSONSpan types matching the shape the demo already emitted. ASTKind and Operator gain String methods. The demo now uses the library types and is its own module, so it is no longer part of the root module zip.
Add testdata/vectors (eval, literals, paths, fields, regex, functions, macros, parse_errors, print, trace, json_input, ast_json) with a schema README, and vectors_test.go, which strictly decodes and runs every vector file. Remove the Go tests the vectors now cover; keep Go-only coverage (AST/token/rewrite APIs, custom functions, Opts.Validate, input adapters, typed-slice and non-UTF-8 inputs, error wording, fuzzing, benchmarks).
…case, fixed example
This reverts commit 54e1c89.
Completes the shared URL rules (R1, R2); all vectors pass.
rewrite(ast, &[Edit { target: NodeId, replacement: &Ast }], mode) ports
Go Rewrite: unchanged source is kept, replacements are printed in the
mode, and overlapping edits are an error. EvalResult's accessors are
renamed to value() and error() to match the Go Result fields.
Every public item is documented (missing_docs is on), with doc examples for parse/eval, inputs, decode_json, functions and macros, tracing, printing, and rewrite. Also adds the hidden hooks the benchmarks use (__bench::cmp_number, the rulekit_size_probe cfg).
… probe benches/eval.rs (criterion) and benches/allocs.rs cover BenchmarkEval (incl. url_field), BenchmarkEvalLazyInput, BenchmarkEvalTrace, BenchmarkParse, and BenchmarkCmpNumber by name. bench/compare.sh prints Go and Rust ns/op and allocs/op side by side; bench/size.sh measures the traced-eval instantiation and rlib size. README.md ships in the package.
Trace<'a> borrows its expression, value, and missing fields (accessor methods; into_owned() detaches it). Traces are built from fragments, so an unnamed group is a plain Vec rather than a boxed node, and missing fields are Cow so functions can report owned names. BenchmarkEvalTrace: 22 allocations (Go 29), ~834 ns (Go ~858).
…s allocation-free)
Compile prints each node's canonical expression once, reusing its children's text, instead of re-printing every subtree; comparisons no longer clone their operand nodes while lowering.
eval takes (ctx, input, opts) like Go. Custom functions return Result<Val, FnError> and can report missing fields (the result is unknown), like Go's Function.Eval. Ip/Cidr/Mac/Url::parse return ValueParseError. Function and Macro both have with_doc/doc; Env::macro_ is macro_rule; Rule::ast() returns &Ast; TextForm is opaque; Val gets From<&str>. Docs, doc tests, README, and DESIGN updated.
Node results are generic over a trace slot that is () when not tracing, and keep errors and function-reported missing names in one boxed Problem, so a result is 96 bytes instead of 136. Recovers the untraced eval time lost when traces started borrowing (e.g. full_traversal 235 -> 216 ns, macro 117 -> 107 ns; Go 245 / 197 ns). EvalResult::missing_fields is now an iterator.
Replaces the dynamic function API (ArgSpec, the Args accessor, Function::new(specs, closure)). A function is Function::new(FuncSchema::<A, R>::new(name, doc), |ctx, args| ...) where A derives Args (one positional argument per field, rename attribute, optional last Rest<'a> for variadic) and R: Returns gives the declared return type statically; results may borrow from the context. The derive lives in the new rulekit-macros crate (workspace member), re-exported behind the default derive feature. EnvBuilder::function takes the name from the schema and rejects duplicates. starts_with uses the same API. ArgCount gains variadic. trybuild tests cover each derive error.
FuncSchema is a plain struct of &'static str name and doc; the argument and return types are named on Function::new, so the closure needs no annotations. Adds NoArgs for functions without arguments. Benches no longer build benches/cases.rs as a benchmark.
InputValue plus lazy list/object refs so a HashMap, Vec, or JSON value is indexed only where a rule touches it. `in` accepts a path (`"x" in tags`); a non-array, non-CIDR literal stays a parse error.
`in` stays an array or CIDR literal, matching Go; list membership is `contains`. Slice fields are `&dyn ListSource` via `&&[T]`, which is sized. No raw pointers or lifetime transmutes.
Matches the first path segment to a field and reads only that field. rename and skip attributes, generics, and a spanned error for an unsupported field type.
kv! builds a zero-alloc heterogeneous map; lazy boxes a closure at construction and runs it only when the field is read. derive(Input) takes an optional context type so a lazy field is checked at the derive site instead of for every C.
Default features url and http. Field access borrows. url::Url compares via as_str(); reading an http::Uri itself allocates its Display form (3 allocs), field access does not.
README and crate docs lead with derive(Input) and kv!. DESIGN records why serde Serialize was rejected. Eval and alloc benches compare a derived struct with the same rule over KvInput.
lazy() is the existing Lazy: one resolution per instance, single-flight across threads, errors not stored so the next read retries. The closure returns an owned value; a borrow of ctx is copied into the memo.
After those crates normalize, fields and comparisons follow rulekit's URL parsed from that text. http::Uri compares borrowed parts with scheme and host lowercased, so the comparison does not allocate.
#[rulekit(bytes)] borrows Vec<u8>, &[u8], [u8; N], Box<[u8]>, and Cow<[u8]> as a byte string. bytes::Bytes, BytesMut, and the serde_bytes wrappers are byte strings without the attribute. kv! uses rulekit::bytes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
#[rulekit(skip)]to the Rust#[derive(rulekit::Input)]. A skipped field is invisible to rules: a rule that reads it gets a missing field, as if the field did not exist.Without it, every field must have a type rulekit can read, and every field is readable by rules. Large fields do not need it: input is lazy, so fields a rule does not mention are never read.
Decision needed: this sets a precedent. Go would get the matching struct tag
rulekit:"-"if struct input lands (#37).This reverts
02f8180, which removed the feature fromv2so it can be decided separately.Checks (local, toolchain 1.97.1):
cargo fmt --all --check,cargo clippy --workspace --all-targets --locked -- -D warnings,cargo test --workspace --lockedpass.derive_inputcovers a skipped field reported as missing; theinput_skipcompile-fail case (skip as an unknown attribute) is removed.