From ed63d46348c0de8a557c664f72655eeccca59723 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 16 Nov 2021 22:10:04 +0100 Subject: [PATCH] Experimental schema interpreter and preserves-path support --- .../rust/preserves-path/Cargo.toml | 6 +- implementations/rust/preserves-path/path.bin | 2 +- .../rust/preserves-path/src/context.rs | 48 ++++ .../rust/preserves-path/src/error.rs | 2 + .../rust/preserves-path/src/lib.rs | 5 +- .../rust/preserves-path/src/parse.rs | 62 +++-- .../rust/preserves-path/src/path.rs | 18 -- .../rust/preserves-path/src/predicate.rs | 20 +- .../rust/preserves-path/src/step.rs | 161 ++++++------ .../rust/preserves-schema/Cargo.toml | 2 +- .../rust/preserves-schema/src/compiler/mod.rs | 54 ++-- .../preserves-schema/src/support/interpret.rs | 233 ++++++++++++++++++ .../rust/preserves-schema/src/support/mod.rs | 2 + .../rust/preserves-tools/Cargo.toml | 5 +- .../preserves-tools/src/bin/preserves-tool.rs | 46 ++-- path/path.bin | 2 +- path/path.prs | 2 + preserves-path.md | 2 + 18 files changed, 493 insertions(+), 179 deletions(-) create mode 100644 implementations/rust/preserves-path/src/context.rs delete mode 100644 implementations/rust/preserves-path/src/path.rs create mode 100644 implementations/rust/preserves-schema/src/support/interpret.rs diff --git a/implementations/rust/preserves-path/Cargo.toml b/implementations/rust/preserves-path/Cargo.toml index ab2ff90..1154c48 100644 --- a/implementations/rust/preserves-path/Cargo.toml +++ b/implementations/rust/preserves-path/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "preserves-path" -version = "2.2.0" +version = "3.0.0" authors = ["Tony Garnock-Jones "] edition = "2018" description = "Implementation of preserves-path, a query language for Preserves documents." @@ -9,11 +9,11 @@ repository = "https://gitlab.com/preserves/preserves" license = "Apache-2.0" [build-dependencies] -preserves-schema = { path = "../preserves-schema", version = "^2.2.0"} +preserves-schema = { path = "../preserves-schema", version = "^2.3.0"} [dependencies] preserves = { path = "../preserves", version = "^2.2.0"} -preserves-schema = { path = "../preserves-schema", version = "^2.2.0"} +preserves-schema = { path = "../preserves-schema", version = "^2.3.0"} num = "0.4" regex = "1.5" diff --git a/implementations/rust/preserves-path/path.bin b/implementations/rust/preserves-path/path.bin index 5f9b5e5..ca12e84 100644 --- a/implementations/rust/preserves-path/path.bin +++ b/implementations/rust/preserves-path/path.bin @@ -1,4 +1,4 @@ -´łschema·łversion‘ł definitions·łAxis´łorµµ±values´łrec´łlitłvalues„´łtupleµ„„„„µ± descendants´łrec´łlitł descendants„´łtupleµ„„„„µ±at´łrec´łlitłat„´łtupleµ´łnamedłkeyłany„„„„„µ±label´łrec´łlitłlabel„´łtupleµ„„„„µ±keys´łrec´łlitłkeys„´łtupleµ„„„„µ±length´łrec´łlitłlength„´łtupleµ„„„„µ± annotations´łrec´łlitł annotations„´łtupleµ„„„„µ±embedded´łrec´łlitłembedded„´łtupleµ„„„„„„łStep´łorµµ±Axis´łrefµ„łAxis„„µ±Filter´łrefµ„łFilter„„„„łFilter´łorµµ±nop´łrec´łlitłnop„´łtupleµ„„„„µ±compare´łrec´łlitłcompare„´łtupleµ´łnamedłop´łrefµ„ł +´łschema·łversion‘ł definitions·łAxis´łorµµ±values´łrec´łlitłvalues„´łtupleµ„„„„µ± descendants´łrec´łlitł descendants„´łtupleµ„„„„µ±at´łrec´łlitłat„´łtupleµ´łnamedłkeyłany„„„„„µ±label´łrec´łlitłlabel„´łtupleµ„„„„µ±keys´łrec´łlitłkeys„´łtupleµ„„„„µ±length´łrec´łlitłlength„´łtupleµ„„„„µ± annotations´łrec´łlitł annotations„´łtupleµ„„„„µ±embedded´łrec´łlitłembedded„´łtupleµ„„„„µ±parse´łrec´łlitłparse„´łtupleµ´łnamedłmodule´łseqof´łatomłSymbol„„„´łnamedłname´łatomłSymbol„„„„„„µ±unparse´łrec´łlitłunparse„´łtupleµ´łnamedłmodule´łseqof´łatomłSymbol„„„´łnamedłname´łatomłSymbol„„„„„„„„łStep´łorµµ±Axis´łrefµ„łAxis„„µ±Filter´łrefµ„łFilter„„„„łFilter´łorµµ±nop´łrec´łlitłnop„´łtupleµ„„„„µ±compare´łrec´łlitłcompare„´łtupleµ´łnamedłop´łrefµ„ł Comparison„„´łnamedłliteralłany„„„„„µ±regex´łrec´łlitłregex„´łtupleµ´łnamedłregex´łatomłString„„„„„„µ±test´łrec´łlitłtest„´łtupleµ´łnamedłpred´łrefµ„ł Predicate„„„„„„µ±real´łrec´łlitłreal„´łtupleµ„„„„µ±int´łrec´łlitłint„´łtupleµ„„„„µ±kind´łrec´łlitłkind„´łtupleµ´łnamedłkind´łrefµ„ł ValueKind„„„„„„„„łSelector´łseqof´łrefµ„łStep„„ł Predicate´łorµµ±Selector´łrefµ„łSelector„„µ±not´łrec´łlitłnot„´łtupleµ´łnamedłpred´łrefµ„ł Predicate„„„„„„µ±or´łrec´łlitłor„´łtupleµ´łnamedłpreds´łseqof´łrefµ„ł Predicate„„„„„„„µ±and´łrec´łlitłand„´łtupleµ´łnamedłpreds´łseqof´łrefµ„ł Predicate„„„„„„„„„ł ValueKind´łorµµ±Boolean´łlitłBoolean„„µ±Float´łlitłFloat„„µ±Double´łlitłDouble„„µ± SignedInteger´łlitł SignedInteger„„µ±String´łlitłString„„µ± ByteString´łlitł ByteString„„µ±Symbol´łlitłSymbol„„µ±Record´łlitłRecord„„µ±Sequence´łlitłSequence„„µ±Set´łlitłSet„„µ± diff --git a/implementations/rust/preserves-path/src/context.rs b/implementations/rust/preserves-path/src/context.rs new file mode 100644 index 0000000..b0c12fc --- /dev/null +++ b/implementations/rust/preserves-path/src/context.rs @@ -0,0 +1,48 @@ +use preserves::value::IOValue; + +use preserves_schema::compiler::load_schema_or_bundle; +use preserves_schema::gen::schema::Definition; + +use std::io; + +#[derive(Default)] +pub struct Env(pub preserves_schema::support::interpret::Env); + +pub struct Context<'a> { + pub env: &'a Env, + path: Vec, +} + +impl<'a> Context<'a> { + pub fn new(env: &'a Env) -> Self { + Context { + env, + path: Vec::new(), + } + } + + pub fn with_path_step R>(&mut self, v: &IOValue, f: F) -> R { + self.path.push(v.clone()); + let result = f(self); + self.path.pop(); + result + } +} + +impl Env { + pub fn new() -> Self { + Default::default() + } + + pub fn load_bundle(&mut self, filename: &std::path::PathBuf) -> io::Result<()> { + load_schema_or_bundle(&mut self.0, filename) + } + + pub fn lookup_definition(&self, module: &Vec, name: &str) -> Option<&Definition> { + self.0.get(module).and_then(|s| s.definitions.0.get(name)) + } + + pub fn to_context(&self) -> Context { + Context::new(self) + } +} diff --git a/implementations/rust/preserves-path/src/error.rs b/implementations/rust/preserves-path/src/error.rs index 1780594..7e0afc5 100644 --- a/implementations/rust/preserves-path/src/error.rs +++ b/implementations/rust/preserves-path/src/error.rs @@ -10,6 +10,8 @@ pub enum CompilationError { MixedOperators, #[error("Invalid step")] InvalidStep, + #[error("Undefined schema definition name: {0}")] + UndefinedSchemaDefinitionName(String), #[error(transparent)] RegexError(#[from] regex::Error), } diff --git a/implementations/rust/preserves-path/src/lib.rs b/implementations/rust/preserves-path/src/lib.rs index 00a4a73..4e7b88e 100644 --- a/implementations/rust/preserves-path/src/lib.rs +++ b/implementations/rust/preserves-path/src/lib.rs @@ -1,6 +1,6 @@ +pub mod context; pub mod error; pub mod parse; -pub mod path; pub mod predicate; pub mod schemas { @@ -9,6 +9,9 @@ pub mod schemas { pub mod step; +pub use context::Context; +pub use context::Env; + pub use error::CompilationError; pub use parse::parse_selector; diff --git a/implementations/rust/preserves-path/src/parse.rs b/implementations/rust/preserves-path/src/parse.rs index 6466006..05d32f5 100644 --- a/implementations/rust/preserves-path/src/parse.rs +++ b/implementations/rust/preserves-path/src/parse.rs @@ -1,4 +1,5 @@ use crate::CompilationError; +use crate::context::Env; use crate::schemas::path; use crate::step::Node; @@ -33,22 +34,22 @@ fn split_binop(tokens: &[IOValue]) -> Result<(Vec<&[IOValue]>, Option), C } } -pub fn parse_selector(tokens: &[IOValue]) -> Result { +pub fn parse_selector(env: &Env, tokens: &[IOValue]) -> Result { let mut steps = Vec::new(); let mut tokens = tokens; - while let Some((s, remaining)) = parse_step(tokens)? { + while let Some((s, remaining)) = parse_step(env, tokens)? { steps.push(s); tokens = remaining; } Ok(path::Selector(steps)) } -pub fn parse_predicate(tokens: &[IOValue]) -> Result { +pub fn parse_predicate(env: &Env, tokens: &[IOValue]) -> Result { let (pieces, binop) = split_binop(tokens)?; match binop { - None => parse_non_binop(&pieces[0]), + None => parse_non_binop(env, &pieces[0]), Some(o) => { - let preds = pieces.into_iter().map(|ts| parse_non_binop(&ts)).collect::>()?; + let preds = pieces.into_iter().map(|ts| parse_non_binop(env, &ts)).collect::>()?; Ok(match o { Binop::Union => path::Predicate::Or { preds }, Binop::Intersection => path::Predicate::And { preds }, @@ -57,19 +58,29 @@ pub fn parse_predicate(tokens: &[IOValue]) -> Result Result { +fn parse_non_binop(env: &Env, tokens: &[IOValue]) -> Result { if !tokens.is_empty() { let t = tokens[0].value(); if let Some("!") = t.as_symbol().map(|s| s.as_str()) { - return Ok(path::Predicate::Not { pred: Box::new(parse_non_binop(&tokens[1..])?) }); + return Ok(path::Predicate::Not { pred: Box::new(parse_non_binop(env, &tokens[1..])?) }); } } - Ok(path::Predicate::Selector(Box::new(parse_selector(tokens)?))) + Ok(path::Predicate::Selector(Box::new(parse_selector(env, tokens)?))) } -fn parse_step(tokens: &[IOValue]) -> Result, CompilationError> { +fn parse_schema_definition_name(env: &Env, token: &IOValue) -> Result<(Vec, String), CompilationError> { + let defpath = token.value().to_symbol().map_err(|_| CompilationError::InvalidStep)?; + let mut module: Vec = defpath.split('.').map(|s| s.to_string()).collect(); + let name = module.pop().expect("at least one element in the Schema name"); + match env.lookup_definition(&module, &name) { + Some(_) => Ok((module, name)), + None => Err(CompilationError::UndefinedSchemaDefinitionName(format!("{:?}", token))), + } +} + +fn parse_step<'a>(env: &Env, tokens: &'a [IOValue]) -> Result, CompilationError> { if tokens.is_empty() { return Ok(None); } @@ -78,7 +89,7 @@ fn parse_step(tokens: &[IOValue]) -> Result, Co if tokens[0].value().is_sequence() { return Ok(Some((path::Step::Filter(Box::new(path::Filter::Test { - pred: Box::new(parse_predicate(tokens[0].value().as_sequence().unwrap())?), + pred: Box::new(parse_predicate(env, tokens[0].value().as_sequence().unwrap())?), })), remainder))); } @@ -96,7 +107,16 @@ fn parse_step(tokens: &[IOValue]) -> Result, Co ".length" => Ok(Some((path::Step::Axis(Box::new(path::Axis::Length)), remainder))), ".annotations" => Ok(Some((path::Step::Axis(Box::new(path::Axis::Annotations)), remainder))), ".embedded" => Ok(Some((path::Step::Axis(Box::new(path::Axis::Embedded)), remainder))), - + "%" => { + let (defpath, remainder) = pop_step_arg(remainder)?; + let (module, name) = parse_schema_definition_name(env, &defpath)?; + Ok(Some((path::Step::Axis(Box::new(path::Axis::Parse { module, name })), remainder))) + } + "%-" => { + let (defpath, remainder) = pop_step_arg(remainder)?; + let (module, name) = parse_schema_definition_name(env, &defpath)?; + Ok(Some((path::Step::Axis(Box::new(path::Axis::Unparse { module, name })), remainder))) + } "*" => Ok(Some((path::Step::Filter(Box::new(path::Filter::Nop)), remainder))), "eq" | "=" => parse_comparison(remainder, path::Comparison::Eq), "ne" | "!=" => parse_comparison(remainder, path::Comparison::Ne), @@ -170,20 +190,18 @@ fn parse_comparison( })), remainder))) } -impl std::str::FromStr for path::Selector { - type Err = CompilationError; - fn from_str(s: &str) -> Result { - parse_selector(&(BytesBinarySource::new(s.as_bytes()) - .text_iovalues() - .configured(false) - .collect::, _>>()?)) +impl path::Selector { + pub fn from_str(env: &Env, s: &str) -> Result { + parse_selector(env, &(BytesBinarySource::new(s.as_bytes()) + .text_iovalues() + .configured(false) + .collect::, _>>()?)) } } -impl std::str::FromStr for Node { - type Err = CompilationError; - fn from_str(s: &str) -> Result { - let expr = path::Selector::from_str(s)?; +impl Node { + pub fn from_str(env: &Env, s: &str) -> Result { + let expr = path::Selector::from_str(env, s)?; expr.compile() } } diff --git a/implementations/rust/preserves-path/src/path.rs b/implementations/rust/preserves-path/src/path.rs deleted file mode 100644 index adf6fed..0000000 --- a/implementations/rust/preserves-path/src/path.rs +++ /dev/null @@ -1,18 +0,0 @@ -use preserves::value::IOValue; - -use std::rc::Rc; - -pub enum Path { - Root, - Step(IOValue, Rc), -} - -impl Path { - pub fn root() -> Rc { - Rc::new(Path::Root) - } - - pub fn step(self: &Rc, v: &IOValue) -> Rc { - Rc::new(Path::Step(v.clone(), Rc::clone(self))) - } -} diff --git a/implementations/rust/preserves-path/src/predicate.rs b/implementations/rust/preserves-path/src/predicate.rs index d2fd026..1c7594f 100644 --- a/implementations/rust/preserves-path/src/predicate.rs +++ b/implementations/rust/preserves-path/src/predicate.rs @@ -1,5 +1,5 @@ use crate::CompilationError; -use crate::path::Path; +use crate::context::Context; use crate::schemas::path; use crate::step::BoolCollector; use crate::step::Node; @@ -7,10 +7,8 @@ use crate::step::StepMaker; use preserves::value::IOValue; -use std::rc::Rc; - pub trait Predicate: std::fmt::Debug { - fn test(&mut self, path: Rc, value: &IOValue) -> bool; + fn test(&mut self, ctxt: &mut Context, value: &IOValue) -> bool; } #[derive(Debug)] @@ -35,19 +33,19 @@ impl path::Predicate { } } - pub fn exec(&self, value: &IOValue) -> Result { - Ok(self.compile()?.test(Path::root(), value)) + pub fn exec(&self, ctxt: &mut Context, value: &IOValue) -> Result { + Ok(self.compile()?.test(ctxt, value)) } } impl Predicate for CompiledPredicate { - fn test(&mut self, path: Rc, value: &IOValue) -> bool { + fn test(&mut self, ctxt: &mut Context, value: &IOValue) -> bool { match self { - CompiledPredicate::Selector(n) => n.test(path, value), - CompiledPredicate::Not(p) => !p.test(path, value), + CompiledPredicate::Selector(n) => n.test(ctxt, value), + CompiledPredicate::Not(p) => !p.test(ctxt, value), CompiledPredicate::Or(ps) => { for p in ps.iter_mut() { - if p.test(Rc::clone(&path), value) { + if p.test(ctxt, value) { return true; } } @@ -55,7 +53,7 @@ impl Predicate for CompiledPredicate { }, CompiledPredicate::And(ps) => { for p in ps.iter_mut() { - if !p.test(Rc::clone(&path), value) { + if !p.test(ctxt, value) { return false; } } diff --git a/implementations/rust/preserves-path/src/step.rs b/implementations/rust/preserves-path/src/step.rs index ad483a9..f947148 100644 --- a/implementations/rust/preserves-path/src/step.rs +++ b/implementations/rust/preserves-path/src/step.rs @@ -2,7 +2,7 @@ // If we could make Schemas produce generics... use crate::CompilationError; -use crate::path::Path; +use crate::context::Context; use crate::predicate::CompiledPredicate; use crate::predicate::Predicate; use crate::schemas::path; @@ -18,8 +18,9 @@ use preserves::value::NestedValue; use preserves::value::Value; use preserves::value::ValueClass; +use preserves_schema::support::interpret; + use std::cell::RefCell; -use std::collections::VecDeque; use std::iter::Iterator; use std::rc::Rc; @@ -28,7 +29,7 @@ pub trait StepMaker { } pub trait Step: std::fmt::Debug { - fn accept(&mut self, path: Rc, value: &IOValue); + fn accept(&mut self, ctxt: &mut Context, value: &IOValue); fn finish(&mut self); fn reset(&mut self) -> Vec; } @@ -99,14 +100,14 @@ impl Node { Node(Rc::new(RefCell::new(s))) } - pub fn test(&self, path: Rc, value: &IOValue) -> bool { - self.accept(path, value); + pub fn test(&self, ctxt: &mut Context, value: &IOValue) -> bool { + self.accept(ctxt, value); self.finish(); !self.reset().is_empty() } - pub fn accept(&self, path: Rc, value: &IOValue) { - self.0.borrow_mut().accept(path, value) + pub fn accept(&self, ctxt: &mut Context, value: &IOValue) { + self.0.borrow_mut().accept(ctxt, value) } pub fn finish(&self) { @@ -117,8 +118,8 @@ impl Node { self.0.borrow_mut().reset() } - pub fn exec(&self, value: &IOValue) -> Vec { - self.accept(Path::root(), value); + pub fn exec(&self, ctxt: &mut Context, value: &IOValue) -> Vec { + self.accept(ctxt, value); self.finish(); self.reset() } @@ -154,83 +155,85 @@ impl StepMaker for path::Axis { } } +fn descendants(ctxt: &mut Context, step: &mut Node, v: &IOValue) { + step.accept(ctxt, v); + for c in v.value().children() { + ctxt.with_path_step(&c, |ctxt| descendants(ctxt, step, &c)); + } +} + impl Step for AxisStep { - fn accept(&mut self, path: Rc, value: &IOValue) { - match &self.axis { - path::Axis::Values => { - let path = path.step(value); + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { + ctxt.with_path_step(value, |ctxt| match &self.axis { + path::Axis::Values => for c in value.value().children() { - self.step.accept(Rc::clone(&path), &c) - } - } - path::Axis::Descendants => { - let mut q = VecDeque::new(); - q.push_back((path, value.clone())); - while let Some((p, c)) = q.pop_front() { - let p = p.step(&c); - for cc in c.value().children() { - q.push_back((Rc::clone(&p), cc.clone())); - } - self.step.accept(p, &c) - } - } + self.step.accept(ctxt, &c) + }, + path::Axis::Descendants => + descendants(ctxt, &mut self.step, value), path::Axis::At { key } => match value.value() { Value::String(s) | Value::Symbol(s) => - step_index(path.step(value), s.chars(), &key, |c| IOValue::new(String::from(c)), &mut self.step), + step_index(ctxt, s.chars(), &key, |c| IOValue::new(String::from(c)), &mut self.step), Value::Record(r) => - step_index(path.step(value), r.fields().iter(), &key, |v| v.clone(), &mut self.step), + step_index(ctxt, r.fields().iter(), &key, |v| v.clone(), &mut self.step), Value::Sequence(vs) => - step_index(path.step(value), vs.iter(), &key, |v| v.clone(), &mut self.step), + step_index(ctxt, vs.iter(), &key, |v| v.clone(), &mut self.step), Value::Dictionary(d) => if let Some(v) = d.get(&key) { - self.step.accept(path.step(value), v) + self.step.accept(ctxt, v) }, _ => (), }, path::Axis::Label => if let Some(r) = value.value().as_record(None) { - self.step.accept(path.step(value), r.label()) + self.step.accept(ctxt, r.label()) }, path::Axis::Keys => match value.value() { Value::String(s) | Value::Symbol(s) => - step_keys(path.step(value), s.len(), &mut self.step), - Value::ByteString(bs) => step_keys(path.step(value), bs.len(), &mut self.step), - Value::Record(r) => step_keys(path.step(value), r.arity(), &mut self.step), - Value::Sequence(vs) => step_keys(path.step(value), vs.len(), &mut self.step), - Value::Dictionary(d) => { - let path = path.step(value); + step_keys(ctxt, s.len(), &mut self.step), + Value::ByteString(bs) => step_keys(ctxt, bs.len(), &mut self.step), + Value::Record(r) => step_keys(ctxt, r.arity(), &mut self.step), + Value::Sequence(vs) => step_keys(ctxt, vs.len(), &mut self.step), + Value::Dictionary(d) => for k in d.keys() { - self.step.accept(Rc::clone(&path), k) - } - }, + self.step.accept(ctxt, k) + }, _ => (), }, path::Axis::Length => match value.value() { Value::String(s) | Value::Symbol(s) => - self.step.accept(path.step(value), &IOValue::new(s.len())), - Value::ByteString(bs) => self.step.accept(path.step(value), &IOValue::new(bs.len())), - Value::Record(r) => self.step.accept(path.step(value), &IOValue::new(r.arity())), - Value::Sequence(vs) => self.step.accept(path.step(value), &IOValue::new(vs.len())), - Value::Dictionary(d) => self.step.accept(path.step(value), &IOValue::new(d.len())), - _ => self.step.accept(path.step(value), &IOValue::new(0)), + self.step.accept(ctxt, &IOValue::new(s.len())), + Value::ByteString(bs) => self.step.accept(ctxt, &IOValue::new(bs.len())), + Value::Record(r) => self.step.accept(ctxt, &IOValue::new(r.arity())), + Value::Sequence(vs) => self.step.accept(ctxt, &IOValue::new(vs.len())), + Value::Dictionary(d) => self.step.accept(ctxt, &IOValue::new(d.len())), + _ => self.step.accept(ctxt, &IOValue::new(0)), }, - path::Axis::Annotations => { - let path = path.step(value); + path::Axis::Annotations => for c in value.annotations().slice() { - self.step.accept(Rc::clone(&path), &c) + self.step.accept(ctxt, &c) + }, + path::Axis::Embedded => if let Some(d) = value.value().as_embedded() { + self.step.accept(ctxt, d) + }, + path::Axis::Parse { module, name } => { + if let Some(p) = interpret::Context::new(&ctxt.env.0).dynamic_parse(module, name, value) { + self.step.accept(ctxt, &p) } } - path::Axis::Embedded => if let Some(d) = value.value().as_embedded() { - self.step.accept(path.step(value), d) - }, - } + path::Axis::Unparse { module, name } => { + if let Some(p) = interpret::Context::new(&ctxt.env.0).dynamic_unparse(module, name, value) { + self.step.accept(ctxt, &p) + } + } + }) } delegate_finish_and_reset!(self, self.step); } fn step_index, F: FnOnce(T) -> IOValue>( - p: Rc, + ctxt: &mut Context, mut vs: Ts, key: &IOValue, f: F, @@ -239,14 +242,14 @@ fn step_index, F: FnOnce(T) -> IOValue>( if let Some(i) = key.value().as_usize() { match vs.nth(i) { None => (), - Some(v) => step.accept(p, &f(v)), + Some(v) => step.accept(ctxt, &f(v)), } } } -fn step_keys(p: Rc, count: usize, step: &mut Node) { +fn step_keys(ctxt: &mut Context, count: usize, step: &mut Node) { for i in 0 .. count { - step.accept(Rc::clone(&p), &IOValue::new(i)) + step.accept(ctxt, &IOValue::new(i)) } } @@ -285,7 +288,7 @@ impl StepMaker for path::Filter { } impl Step for CompareStep { - fn accept(&mut self, path: Rc, value: &IOValue) { + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { if match self.op { path::Comparison::Eq => value == &self.literal, path::Comparison::Ne => value != &self.literal, @@ -294,7 +297,7 @@ impl Step for CompareStep { path::Comparison::Gt => value > &self.literal, path::Comparison::Le => value <= &self.literal, } { - self.step.accept(path, value) + self.step.accept(ctxt, value) } } @@ -302,10 +305,10 @@ impl Step for CompareStep { } impl Step for RegexStep { - fn accept(&mut self, path: Rc, value: &IOValue) { + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { match value.value() { Value::String(s) | Value::Symbol(s) => - if self.regex.is_match(s) { self.step.accept(path, value) }, + if self.regex.is_match(s) { self.step.accept(ctxt, value) }, _ => (), } @@ -315,9 +318,9 @@ impl Step for RegexStep { } impl Step for TestStep { - fn accept(&mut self, path: Rc, value: &IOValue) { - if self.pred.test(Rc::clone(&path), value) { - self.step.accept(path, value) + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { + if self.pred.test(ctxt, value) { + self.step.accept(ctxt, value) } } @@ -325,13 +328,13 @@ impl Step for TestStep { } impl Step for RealStep { - fn accept(&mut self, path: Rc, value: &IOValue) { + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { match value.value() { Value::SignedInteger(i) => if let Some(r) = BigInt::from(i).to_f64() { - self.step.accept(path, &IOValue::new(r)) + self.step.accept(ctxt, &IOValue::new(r)) }, - Value::Float(f) => self.step.accept(path, &IOValue::new(f32::from(*f) as f64)), - Value::Double(_) => self.step.accept(path, value), + Value::Float(f) => self.step.accept(ctxt, &IOValue::new(f32::from(*f) as f64)), + Value::Double(_) => self.step.accept(ctxt, value), _ => (), } } @@ -340,14 +343,14 @@ impl Step for RealStep { } impl Step for IntStep { - fn accept(&mut self, path: Rc, value: &IOValue) { + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { match value.value() { - Value::SignedInteger(_) => self.step.accept(path, value), + Value::SignedInteger(_) => self.step.accept(ctxt, value), Value::Float(f) => if let Some(i) = BigInt::from_f32(f32::from(*f)) { - self.step.accept(path, &IOValue::new(i)) + self.step.accept(ctxt, &IOValue::new(i)) }, Value::Double(d) => if let Some(i) = BigInt::from_f64(f64::from(*d)) { - self.step.accept(path, &IOValue::new(i)) + self.step.accept(ctxt, &IOValue::new(i)) }, _ => (), } @@ -363,7 +366,7 @@ impl VecCollector { } impl Step for VecCollector { - fn accept(&mut self, _path: Rc, value: &IOValue) { + fn accept(&mut self, _ctxt: &mut Context, value: &IOValue) { self.accumulator.push(value.clone()) } @@ -382,7 +385,7 @@ impl BoolCollector { } impl Step for BoolCollector { - fn accept(&mut self, _path: Rc, _value: &IOValue) { + fn accept(&mut self, _ctxt: &mut Context, _value: &IOValue) { self.seen_value = true } @@ -397,9 +400,9 @@ impl Step for BoolCollector { } impl Step for KindStep { - fn accept(&mut self, path: Rc, value: &IOValue) { + fn accept(&mut self, ctxt: &mut Context, value: &IOValue) { if value.value_class() == self.kind { - self.step.accept(path, value) + self.step.accept(ctxt, value) } } @@ -411,7 +414,7 @@ impl path::Selector { self.connect(VecCollector::new()) } - pub fn exec(&self, value: &IOValue) -> Result, CompilationError> { - Ok(self.compile()?.exec(value)) + pub fn exec(&self, ctxt: &mut Context, value: &IOValue) -> Result, CompilationError> { + Ok(self.compile()?.exec(ctxt, value)) } } diff --git a/implementations/rust/preserves-schema/Cargo.toml b/implementations/rust/preserves-schema/Cargo.toml index 923b68f..c9458f4 100644 --- a/implementations/rust/preserves-schema/Cargo.toml +++ b/implementations/rust/preserves-schema/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "preserves-schema" -version = "2.2.0" +version = "2.3.0" authors = ["Tony Garnock-Jones "] edition = "2018" description = "Implementation of Preserves Schema code generation and support for Rust." diff --git a/implementations/rust/preserves-schema/src/compiler/mod.rs b/implementations/rust/preserves-schema/src/compiler/mod.rs index ebb8c64..1fda19e 100644 --- a/implementations/rust/preserves-schema/src/compiler/mod.rs +++ b/implementations/rust/preserves-schema/src/compiler/mod.rs @@ -105,6 +105,33 @@ pub struct CompilerConfig { pub plugins: Vec>, } +pub fn load_schema_or_bundle(bundle: &mut Map, i: &PathBuf) -> io::Result<()> { + let mut f = File::open(&i)?; + let mut src = IOBinarySource::new(&mut f); + let mut reader = src.packed_iovalues(); + let blob = reader.demand_next(false)?; + let language = Language::default(); + + if let Ok(s) = language.parse(&blob) { + let prefix = i.file_stem().ok_or_else( + || io::Error::new(io::ErrorKind::InvalidData, + format!("Bad schema file stem: {:?}", i)))? + .to_str().ok_or_else( + || io::Error::new(io::ErrorKind::InvalidData, + format!("Invalid UTF-8 in schema file name: {:?}", i)))?; + bundle.insert(vec![prefix.to_owned()], s); + } else if let Ok(Bundle { modules }) = language.parse(&blob) { + for (ModulePath(k), v) in modules.0 { + bundle.insert(k, v); + } + } else { + return Err(io::Error::new(io::ErrorKind::InvalidData, + format!("Invalid schema binary blob {:?}", i))); + } + + Ok(()) +} + impl CompilerConfig { pub fn new( output_dir: PathBuf, @@ -134,32 +161,7 @@ impl CompilerConfig { pub fn load_schemas_and_bundles(&mut self, inputs: &Vec) -> io::Result<()> { for i in inputs { - let mut f = File::open(&i)?; - let mut src = IOBinarySource::new(&mut f); - let mut reader = src.packed_iovalues(); - let blob = reader.demand_next(false)?; - let language = Language::default(); - - if let Ok(s) = language.parse(&blob) { - let prefix = i.file_stem().ok_or_else( - || io::Error::new(io::ErrorKind::InvalidData, - format!("Bad schema file stem: {:?}", i)))? - .to_str().ok_or_else( - || io::Error::new(io::ErrorKind::InvalidData, - format!("Invalid UTF-8 in schema file name: {:?}", i)))?; - self.bundle.insert(vec![prefix.to_owned()], s); - continue; - } - - if let Ok(Bundle { modules }) = language.parse(&blob) { - for (ModulePath(k), v) in modules.0 { - self.bundle.insert(k, v); - } - continue; - } - - return Err(io::Error::new(io::ErrorKind::InvalidData, - format!("Invalid schema binary blob {:?}", i))); + load_schema_or_bundle(&mut self.bundle, i)?; } Ok(()) } diff --git a/implementations/rust/preserves-schema/src/support/interpret.rs b/implementations/rust/preserves-schema/src/support/interpret.rs new file mode 100644 index 0000000..23781a0 --- /dev/null +++ b/implementations/rust/preserves-schema/src/support/interpret.rs @@ -0,0 +1,233 @@ +use crate::gen::schema::*; + +use preserves::value::Map; +use preserves::value::NestedValue; +use preserves::value::Value; +use preserves::value::merge::merge2; + +pub type Env = Map, Schema>; + +#[derive(Debug)] +pub struct Context<'a, V: NestedValue> { + pub env: &'a Env, + module: Vec, +} + +#[derive(Debug)] +enum DynField { + Simple(V), + Compound(Map), +} + +impl<'a, V: NestedValue> Context<'a, V> { + pub fn new(env: &'a Env) -> Self { + Context { + env, + module: Vec::new(), + } + } + + pub fn dynamic_parse(&mut self, module: &Vec, name: &str, v: &V) -> Option { + let old_module = (module.len() > 0).then(|| std::mem::replace(&mut self.module, module.clone())); + let schema = self.env.get(&self.module); + let definition = schema.and_then(|s| s.definitions.0.get(name)); + let result = definition.and_then(|d| d.dynamic_parse(self, v)); + if let Some(m) = old_module { + self.module = m; + } + result + } + + pub fn dynamic_unparse(&mut self, module: &Vec, name: &str, w: &V) -> Option { + panic!("Not yet implemented"); + } +} + +impl Definition { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option { + match self { + Definition::Or { pattern_0, pattern_1, pattern_n } => + pattern_0.dynamic_parse(ctxt, v) + .or_else(|| pattern_1.dynamic_parse(ctxt, v)) + .or_else(|| pattern_n.iter().find_map(|p| p.dynamic_parse(ctxt, v))), + Definition::And { pattern_0, pattern_1, pattern_n } => + pattern_0.dynamic_parse(ctxt, v) + .and_then(|w0| pattern_1.dynamic_parse(ctxt, v) + .and_then(|w1| pattern_n.iter().fold(merge(w0, w1), |w, p| { + w.and_then(|w| p.dynamic_parse(ctxt, v).and_then( + |wn| merge(w, wn))) + }))) + .map(|w| DynField::Compound(w).finish()), + Definition::Pattern(p) => p.dynamic_parse(ctxt, v).map(|w| w.finish()), + } + } +} + +impl NamedAlternative { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option { + self.pattern.dynamic_parse(ctxt, v).map(|w| { + let mut r = Value::simple_record(&self.variant_label, 1); + match w { + DynField::Simple(field) => r.fields_vec_mut().push(field), + DynField::Compound(fields) => if fields.len() > 0 { + r.fields_vec_mut().push(V::new(fields)) + } + } + r.finish().wrap() + }) + } +} + +impl NamedPattern { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option> { + match self { + NamedPattern::Named(b) => { + let binding = &**b; + binding.pattern.dynamic_parse(ctxt, v).map(|w| w.to_map(Some(&binding.name))) + } + NamedPattern::Anonymous(b) => b.dynamic_parse(ctxt, v).map(|w| w.to_map(None)) + } + } +} + +impl NamedSimplePattern { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option> { + match self { + NamedSimplePattern::Named(b) => { + let binding = &**b; + binding.pattern.dynamic_parse(ctxt, v).map( + |w| DynField::Compound(w.to_map(Some(&binding.name)))) + } + NamedSimplePattern::Anonymous(b) => + b.dynamic_parse(ctxt, v), + } + } +} + +impl SimplePattern { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option> { + match self { + SimplePattern::Any => Some(DynField::Simple(v.clone())), + SimplePattern::Atom { atom_kind } => match &**atom_kind { + AtomKind::Boolean => v.value().is_boolean().then(|| DynField::Simple(v.clone())), + AtomKind::Float => v.value().is_float().then(|| DynField::Simple(v.clone())), + AtomKind::Double => v.value().is_double().then(|| DynField::Simple(v.clone())), + AtomKind::SignedInteger => v.value().is_signedinteger().then(|| DynField::Simple(v.clone())), + AtomKind::String => v.value().is_string().then(|| DynField::Simple(v.clone())), + AtomKind::ByteString => v.value().is_bytestring().then(|| DynField::Simple(v.clone())), + AtomKind::Symbol => v.value().is_symbol().then(|| DynField::Simple(v.clone())), + }, + SimplePattern::Embedded { .. } => v.value().is_embedded().then(|| DynField::Simple(v.clone())), + SimplePattern::Lit { value } => (v == value).then(|| DynField::Compound(Map::new())), + SimplePattern::Seqof { pattern } => + v.value().as_sequence() + .and_then(|vs| vs.iter().map(|v| (**pattern).dynamic_parse(ctxt, v).map(|w| w.finish())) + .collect::>>()) + .map(|ws| DynField::Simple(V::new(ws))), + SimplePattern::Setof { pattern } => + v.value().as_set() + .and_then(|vs| vs.iter().map(|v| (**pattern).dynamic_parse(ctxt, v).map(|w| w.finish())) + .collect::>>()) + .map(|ws| DynField::Simple(V::new(ws))), + SimplePattern::Dictof { key, value } => + v.value().as_dictionary() + .and_then(|d| { + d.iter().map(|(k, v)| { + (**key).dynamic_parse(ctxt, k) + .and_then(|kw| (**value).dynamic_parse(ctxt, v) + .map(|vw| (kw.finish(), vw.finish()))) + }).collect::>>() + }) + .map(|d| DynField::Simple(V::new(d))), + SimplePattern::Ref(r) => + ctxt.dynamic_parse(&r.module.0, &r.name, v).map(DynField::Simple), + } + } +} + +impl CompoundPattern { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option> { + match self { + CompoundPattern::Rec { label, fields } => + v.value().as_record(None).and_then( + |r| (**label).dynamic_parse(ctxt, r.label()).and_then( + |lw| (**fields).dynamic_parse(ctxt, &V::new(r.fields().to_vec())).and_then( + |fsw| merge(lw, fsw)))), + CompoundPattern::Tuple { patterns } => + v.value().as_sequence().and_then( + |vs| if vs.len() == patterns.len() { + patterns.iter().zip(vs) + .fold(Some(Map::new()), |acc, (p, v)| { + acc.and_then(|acc| p.dynamic_parse(ctxt, v).and_then( + |w| merge(acc, w))) + }) + } else { + None + }), + CompoundPattern::TuplePrefix { fixed, variable } => + v.value().as_sequence().and_then( + |vs| if vs.len() >= fixed.len() { + fixed.iter().zip(vs) + .fold(Some(Map::new()), |acc, (p, v)| { + acc.and_then(|acc| p.dynamic_parse(ctxt, v).and_then( + |w| merge(acc, w))) + }).and_then(|fixed_ws| { + let remainder = V::new(vs[fixed.len()..].to_vec()); + (**variable).dynamic_parse(ctxt, &remainder).and_then( + |variable_ws| merge(fixed_ws, variable_ws.unwrap_compound())) + }) + } else { + None + }), + CompoundPattern::Dict { entries } => + v.value().as_dictionary().and_then( + |d| (**entries).0.iter().fold(Some(Map::new()), |acc, (k, p)| { + acc.and_then(|acc| d.get(k).and_then(|v| p.dynamic_parse(ctxt, v).and_then( + |w| merge(acc, w.unwrap_compound())))) + })), + } + } +} + +impl Pattern { + fn dynamic_parse(&self, ctxt: &mut Context, v: &V) -> Option> { + match self { + Pattern::SimplePattern(b) => (**b).dynamic_parse(ctxt, v), + Pattern::CompoundPattern(b) => (**b).dynamic_parse(ctxt, v).map(DynField::Compound), + } + } +} + +impl DynField { + fn finish(self) -> V { + match self { + DynField::Simple(v) => v, + DynField::Compound(v) => V::new(v), + } + } + + fn to_map(self, key: Option<&str>) -> Map { + match self { + DynField::Simple(v) => { + let mut d = Map::new(); + if let Some(k) = key { + d.insert(V::symbol(k), v); + } + d + } + DynField::Compound(d) => d, + } + } + + fn unwrap_compound(self) -> Map { + match self { + DynField::Simple(_) => panic!("Cannot unwrap DynField::Simple to compound fields"), + DynField::Compound(d) => d, + } + } +} + +fn merge(a: Map, b: Map) -> Option> { + merge2(V::new(a), V::new(b)) + .map(|d| d.value_owned().into_dictionary().expect("merge to yield Dictionary")) +} diff --git a/implementations/rust/preserves-schema/src/support/mod.rs b/implementations/rust/preserves-schema/src/support/mod.rs index e656671..c1b15a7 100644 --- a/implementations/rust/preserves-schema/src/support/mod.rs +++ b/implementations/rust/preserves-schema/src/support/mod.rs @@ -4,6 +4,8 @@ pub use preserves; pub use preserves::value::Reader; pub use preserves::value::boundary as B; +pub mod interpret; + use preserves::value::ArcValue; use preserves::value::Domain; use preserves::value::IOValue; diff --git a/implementations/rust/preserves-tools/Cargo.toml b/implementations/rust/preserves-tools/Cargo.toml index 7b91eb8..239acca 100644 --- a/implementations/rust/preserves-tools/Cargo.toml +++ b/implementations/rust/preserves-tools/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "preserves-tools" -version = "2.2.1" +version = "2.3.0" authors = ["Tony Garnock-Jones "] edition = "2018" description = "Command-line utilities for working with Preserves documents." @@ -10,7 +10,8 @@ license = "Apache-2.0" [dependencies] preserves = { path = "../preserves", version = "^2.2.0"} -preserves-path = { path = "../preserves-path", version = "^2.2.0"} +preserves-path = { path = "../preserves-path", version = "^3.0.0"} +preserves-schema = { path = "../preserves-schema", version = "^2.3.0"} bytes = "1.0" clap = "=3.0.0-beta.2" diff --git a/implementations/rust/preserves-tools/src/bin/preserves-tool.rs b/implementations/rust/preserves-tools/src/bin/preserves-tool.rs index ed2fc8b..b11c32f 100644 --- a/implementations/rust/preserves-tools/src/bin/preserves-tool.rs +++ b/implementations/rust/preserves-tools/src/bin/preserves-tool.rs @@ -98,8 +98,8 @@ struct Convert { #[clap(long, arg_enum, value_name = "on/off", default_value = "on")] indent: Boolish, - #[clap(long, default_value="*")] - select: preserves_path::Node, + #[clap(long="select", default_value="*")] + select_expr: String, #[clap(long, arg_enum, default_value="sequence")] select_output: SelectOutput, @@ -112,6 +112,12 @@ struct Convert { #[clap(long, arg_enum, value_name = "on/off", default_value = "on")] write_annotations: Boolish, + + #[clap(long, value_name = "filename")] + bundle: Vec, + + #[clap(long)] + schema: Option, } #[derive(ArgEnum, Clone, Debug)] @@ -133,7 +139,6 @@ struct StringQuotation { escape_spaces: bool, } - #[derive(Clap, Clone, Debug)] enum QuotationOutput { String(StringQuotation), @@ -152,11 +157,11 @@ struct Quote { #[derive(Clap, Clone, Debug)] enum Subcommand { - Convert(Convert), Completions { #[clap(arg_enum, value_name = "dialect")] dialect: CompletionDialect, }, + Convert(Convert), Quote(Quote), } @@ -169,7 +174,7 @@ struct CommandLine { fn main() -> io::Result<()> { let args = CommandLine::parse(); - match args.command { + Ok(match args.command { Subcommand::Completions { dialect } => { let mut app = CommandLine::into_app(); match dialect { @@ -182,8 +187,7 @@ fn main() -> io::Result<()> { }, Subcommand::Convert(c) => convert(c)?, Subcommand::Quote(q) => quote(q)?, - } - Ok(()) + }) } struct RollingBuffer { @@ -365,6 +369,14 @@ fn print_unquoted(v: &IOValue) { } fn convert(c: Convert) -> io::Result<()> { + let mut env = preserves_path::Env::new(); + for f in c.bundle.iter() { + env.load_bundle(f)?; + } + let select = preserves_path::Node::from_str(&env, &c.select_expr) + .map_err(|e| io::Error::new( + io::ErrorKind::InvalidData, + format!("Invalid select expression: {}: {:?}", e, c.select_expr)))?; let mut vs = ValueStream::new(c.input_format, c.read_annotations.into(), io::stdin()); let write_ann: bool = c.write_annotations.into(); let mut w: Box io::Result<()>> = match c.output_format { @@ -385,18 +397,24 @@ fn convert(c: Convert) -> io::Result<()> { } OutputFormat::Binary => { let mut p = PackedWriter::new(io::stdout()); - Box::new(move |v| if write_ann { - p.write(&mut IOValueDomainCodec, v) - } else { - p.write(&mut IOValueDomainCodec, &v.strip_annotations::()) + Box::new(move |v| { + if write_ann { + p.write(&mut IOValueDomainCodec, v)?; + } else { + p.write(&mut IOValueDomainCodec, &v.strip_annotations::())?; + } + Ok(()) }) } OutputFormat::Unquoted => - Box::new(|v| Ok(print_unquoted(v))), + Box::new(|v| { + print_unquoted(v); + Ok(()) + }), }; while let Some(value) = vs.next() { let value = value?; - let matches = c.select.exec(&value); + let matches = select.exec(&mut env.to_context(), &value); if c.collect { match c.select_output { SelectOutput::Sequence => w(&IOValue::new(matches))?, @@ -410,7 +428,7 @@ fn convert(c: Convert) -> io::Result<()> { } if let Some(limit) = c.limit { if vs.count >= limit { - return Ok(()); + break; } } } diff --git a/path/path.bin b/path/path.bin index 5f9b5e5..ca12e84 100644 --- a/path/path.bin +++ b/path/path.bin @@ -1,4 +1,4 @@ -´łschema·łversion‘ł definitions·łAxis´łorµµ±values´łrec´łlitłvalues„´łtupleµ„„„„µ± descendants´łrec´łlitł descendants„´łtupleµ„„„„µ±at´łrec´łlitłat„´łtupleµ´łnamedłkeyłany„„„„„µ±label´łrec´łlitłlabel„´łtupleµ„„„„µ±keys´łrec´łlitłkeys„´łtupleµ„„„„µ±length´łrec´łlitłlength„´łtupleµ„„„„µ± annotations´łrec´łlitł annotations„´łtupleµ„„„„µ±embedded´łrec´łlitłembedded„´łtupleµ„„„„„„łStep´łorµµ±Axis´łrefµ„łAxis„„µ±Filter´łrefµ„łFilter„„„„łFilter´łorµµ±nop´łrec´łlitłnop„´łtupleµ„„„„µ±compare´łrec´łlitłcompare„´łtupleµ´łnamedłop´łrefµ„ł +´łschema·łversion‘ł definitions·łAxis´łorµµ±values´łrec´łlitłvalues„´łtupleµ„„„„µ± descendants´łrec´łlitł descendants„´łtupleµ„„„„µ±at´łrec´łlitłat„´łtupleµ´łnamedłkeyłany„„„„„µ±label´łrec´łlitłlabel„´łtupleµ„„„„µ±keys´łrec´łlitłkeys„´łtupleµ„„„„µ±length´łrec´łlitłlength„´łtupleµ„„„„µ± annotations´łrec´łlitł annotations„´łtupleµ„„„„µ±embedded´łrec´łlitłembedded„´łtupleµ„„„„µ±parse´łrec´łlitłparse„´łtupleµ´łnamedłmodule´łseqof´łatomłSymbol„„„´łnamedłname´łatomłSymbol„„„„„„µ±unparse´łrec´łlitłunparse„´łtupleµ´łnamedłmodule´łseqof´łatomłSymbol„„„´łnamedłname´łatomłSymbol„„„„„„„„łStep´łorµµ±Axis´łrefµ„łAxis„„µ±Filter´łrefµ„łFilter„„„„łFilter´łorµµ±nop´łrec´łlitłnop„´łtupleµ„„„„µ±compare´łrec´łlitłcompare„´łtupleµ´łnamedłop´łrefµ„ł Comparison„„´łnamedłliteralłany„„„„„µ±regex´łrec´łlitłregex„´łtupleµ´łnamedłregex´łatomłString„„„„„„µ±test´łrec´łlitłtest„´łtupleµ´łnamedłpred´łrefµ„ł Predicate„„„„„„µ±real´łrec´łlitłreal„´łtupleµ„„„„µ±int´łrec´łlitłint„´łtupleµ„„„„µ±kind´łrec´łlitłkind„´łtupleµ´łnamedłkind´łrefµ„ł ValueKind„„„„„„„„łSelector´łseqof´łrefµ„łStep„„ł Predicate´łorµµ±Selector´łrefµ„łSelector„„µ±not´łrec´łlitłnot„´łtupleµ´łnamedłpred´łrefµ„ł Predicate„„„„„„µ±or´łrec´łlitłor„´łtupleµ´łnamedłpreds´łseqof´łrefµ„ł Predicate„„„„„„„µ±and´łrec´łlitłand„´łtupleµ´łnamedłpreds´łseqof´łrefµ„ł Predicate„„„„„„„„„ł ValueKind´łorµµ±Boolean´łlitłBoolean„„µ±Float´łlitłFloat„„µ±Double´łlitłDouble„„µ± SignedInteger´łlitł SignedInteger„„µ±String´łlitłString„„µ± ByteString´łlitł ByteString„„µ±Symbol´łlitłSymbol„„µ±Record´łlitłRecord„„µ±Sequence´łlitłSequence„„µ±Set´łlitłSet„„µ± diff --git a/path/path.prs b/path/path.prs index 828ddf2..c0c8d88 100644 --- a/path/path.prs +++ b/path/path.prs @@ -20,6 +20,8 @@ Axis = / / / +/ +/ . Filter = diff --git a/preserves-path.md b/preserves-path.md index 5db1fd4..67ec76a 100644 --- a/preserves-path.md +++ b/preserves-path.md @@ -52,6 +52,8 @@ Axes: move around, applying filters after moving .length ;; Moves into the number of keys .annotations ;; Moves into any annotations that might be present .embedded ;; Moves into the representation of an embedded value + % name ;; Moves into successful Preserves Schema parse of definition `name` + %- name ;; Moves into successful Preserves Schema unparse of definition `name` Sets have children, but no keys/length; Strings, ByteStrings and Symbols have no children, but have keys/length.