From d98febc31605cf1683713b4695f84327771067f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 4 Apr 2024 17:02:12 +0200 Subject: [PATCH] Add a prelude with common layout definitions. Fixes #177 --- layouts/{preset => prelude}/boolean.json | 0 layouts/prelude/i16.json | 5 ++ layouts/prelude/i32.json | 5 ++ layouts/prelude/i64.json | 5 ++ layouts/prelude/i8.json | 5 ++ layouts/{preset => prelude}/id.json | 0 layouts/{preset => prelude}/string.json | 0 layouts/prelude/u16.json | 5 ++ layouts/prelude/u32.json | 5 ++ layouts/prelude/u64.json | 5 ++ layouts/prelude/u8.json | 5 ++ layouts/{preset => prelude}/unit.json | 0 layouts/preset/i16.json | 4 - layouts/preset/i32.json | 4 - layouts/preset/i64.json | 4 - layouts/preset/i8.json | 4 - layouts/preset/u16.json | 4 - layouts/preset/u32.json | 4 - layouts/preset/u64.json | 4 - layouts/preset/u8.json | 4 - layouts/src/distill/de/mod.rs | 42 +++++++-- layouts/src/distill/hy/mod.rs | 45 ++++++++-- layouts/src/layout/mod.rs | 2 +- layouts/src/lib.rs | 110 +++++++++++++++++++++++ layouts/src/prelude.rs | 67 ++++++++++++++ src/main.rs | 25 +++++- 26 files changed, 313 insertions(+), 50 deletions(-) rename layouts/{preset => prelude}/boolean.json (100%) create mode 100644 layouts/prelude/i16.json create mode 100644 layouts/prelude/i32.json create mode 100644 layouts/prelude/i64.json create mode 100644 layouts/prelude/i8.json rename layouts/{preset => prelude}/id.json (100%) rename layouts/{preset => prelude}/string.json (100%) create mode 100644 layouts/prelude/u16.json create mode 100644 layouts/prelude/u32.json create mode 100644 layouts/prelude/u64.json create mode 100644 layouts/prelude/u8.json rename layouts/{preset => prelude}/unit.json (100%) delete mode 100644 layouts/preset/i16.json delete mode 100644 layouts/preset/i32.json delete mode 100644 layouts/preset/i64.json delete mode 100644 layouts/preset/i8.json delete mode 100644 layouts/preset/u16.json delete mode 100644 layouts/preset/u32.json delete mode 100644 layouts/preset/u64.json delete mode 100644 layouts/preset/u8.json create mode 100644 layouts/src/prelude.rs diff --git a/layouts/preset/boolean.json b/layouts/prelude/boolean.json similarity index 100% rename from layouts/preset/boolean.json rename to layouts/prelude/boolean.json diff --git a/layouts/prelude/i16.json b/layouts/prelude/i16.json new file mode 100644 index 0000000..02ae661 --- /dev/null +++ b/layouts/prelude/i16.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#i16", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#short" +} \ No newline at end of file diff --git a/layouts/prelude/i32.json b/layouts/prelude/i32.json new file mode 100644 index 0000000..bdf3c53 --- /dev/null +++ b/layouts/prelude/i32.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#i32", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#int" +} \ No newline at end of file diff --git a/layouts/prelude/i64.json b/layouts/prelude/i64.json new file mode 100644 index 0000000..1bd13c8 --- /dev/null +++ b/layouts/prelude/i64.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#i64", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#long" +} \ No newline at end of file diff --git a/layouts/prelude/i8.json b/layouts/prelude/i8.json new file mode 100644 index 0000000..68274ea --- /dev/null +++ b/layouts/prelude/i8.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#i8", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#byte" +} \ No newline at end of file diff --git a/layouts/preset/id.json b/layouts/prelude/id.json similarity index 100% rename from layouts/preset/id.json rename to layouts/prelude/id.json diff --git a/layouts/preset/string.json b/layouts/prelude/string.json similarity index 100% rename from layouts/preset/string.json rename to layouts/prelude/string.json diff --git a/layouts/prelude/u16.json b/layouts/prelude/u16.json new file mode 100644 index 0000000..1fe44b8 --- /dev/null +++ b/layouts/prelude/u16.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#u16", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#unsignedShort" +} \ No newline at end of file diff --git a/layouts/prelude/u32.json b/layouts/prelude/u32.json new file mode 100644 index 0000000..8abafd0 --- /dev/null +++ b/layouts/prelude/u32.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#u32", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#unsignedInt" +} \ No newline at end of file diff --git a/layouts/prelude/u64.json b/layouts/prelude/u64.json new file mode 100644 index 0000000..eb28238 --- /dev/null +++ b/layouts/prelude/u64.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#u64", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#unsignedLong" +} \ No newline at end of file diff --git a/layouts/prelude/u8.json b/layouts/prelude/u8.json new file mode 100644 index 0000000..d15e227 --- /dev/null +++ b/layouts/prelude/u8.json @@ -0,0 +1,5 @@ +{ + "id": "https://treeldr.org/layouts#u8", + "type": "number", + "datatype": "http://www.w3.org/2001/XMLSchema#unsignedByte" +} \ No newline at end of file diff --git a/layouts/preset/unit.json b/layouts/prelude/unit.json similarity index 100% rename from layouts/preset/unit.json rename to layouts/prelude/unit.json diff --git a/layouts/preset/i16.json b/layouts/preset/i16.json deleted file mode 100644 index 6bd3122..0000000 --- a/layouts/preset/i16.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#i16", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/i32.json b/layouts/preset/i32.json deleted file mode 100644 index 10bc84f..0000000 --- a/layouts/preset/i32.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#i32", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/i64.json b/layouts/preset/i64.json deleted file mode 100644 index feb4346..0000000 --- a/layouts/preset/i64.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#i64", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/i8.json b/layouts/preset/i8.json deleted file mode 100644 index 45e516a..0000000 --- a/layouts/preset/i8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#i8", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/u16.json b/layouts/preset/u16.json deleted file mode 100644 index 7ebf3bd..0000000 --- a/layouts/preset/u16.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#u16", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/u32.json b/layouts/preset/u32.json deleted file mode 100644 index ee0285f..0000000 --- a/layouts/preset/u32.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#u32", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/u64.json b/layouts/preset/u64.json deleted file mode 100644 index 5750d6c..0000000 --- a/layouts/preset/u64.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#u64", - "type": "number" -} \ No newline at end of file diff --git a/layouts/preset/u8.json b/layouts/preset/u8.json deleted file mode 100644 index e515767..0000000 --- a/layouts/preset/u8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": "https://treeldr.org/layouts#u8", - "type": "number" -} \ No newline at end of file diff --git a/layouts/src/distill/de/mod.rs b/layouts/src/distill/de/mod.rs index 5b6055f..1de3841 100644 --- a/layouts/src/distill/de/mod.rs +++ b/layouts/src/distill/de/mod.rs @@ -5,7 +5,7 @@ use std::{ use crate::{ layout::{DataLayout, LayoutType, ListLayout, LiteralLayout, ProductLayoutType}, - Layout, Layouts, Literal, Pattern, Ref, Value, ValueFormat, + Layout, LayoutRegistry, Literal, Pattern, Ref, Value, ValueFormat, }; use iref::IriBuf; use rdf_types::{ @@ -43,7 +43,7 @@ pub enum Error { #[error(transparent)] TermAmbiguity(Box), - #[error("layout {0} not found")] + #[error("layout `{0}` is undefined")] LayoutNotFound(Ref), #[error("missing required field `{field_name}`")] @@ -267,7 +267,7 @@ impl Options { /// resources to be collected during deserialization. The collected terms /// are then returned along with the RDF dataset. pub fn dehydrate( - layouts: &Layouts, + layouts: impl LayoutRegistry, value: &Value, layout_ref: &Ref, mut options: Options, @@ -510,7 +510,37 @@ pub fn dehydrate( /// the deserialized RDF dataset. pub fn dehydrate_with( rdf: &mut RdfContextMut, - layouts: &Layouts, + layouts: impl LayoutRegistry, + value: &Value, + current_graph: Option<&I::Resource>, + layout_ref: &Ref, + inputs: &[I::Resource], + output: &mut D, +) -> Result<(), Error> +where + V: VocabularyMut, + V::Iri: Clone, + I: InterpretationMut + + ReverseIriInterpretationMut + + ReverseLiteralInterpretationMut, + I::Resource: Clone + Ord, + Q: Clone + Ord + Into, + D: TraversableDataset + DatasetMut, +{ + dehydrate_with_ref( + rdf, + &layouts, + value, + current_graph, + layout_ref, + inputs, + output, + ) +} + +fn dehydrate_with_ref( + rdf: &mut RdfContextMut, + layouts: &impl LayoutRegistry, value: &Value, current_graph: Option<&I::Resource>, layout_ref: &Ref, @@ -804,7 +834,7 @@ where fn dehydrate_sub_value( rdf: &mut RdfContextMut, - layouts: &Layouts, + layouts: &impl LayoutRegistry, value: &Value, current_graph: Option<&I::Resource>, format: &ValueFormat, @@ -828,7 +858,7 @@ where None => current_graph.cloned(), }; - dehydrate_with( + dehydrate_with_ref( rdf, layouts, value, diff --git a/layouts/src/distill/hy/mod.rs b/layouts/src/distill/hy/mod.rs index 14f27ee..0344981 100644 --- a/layouts/src/distill/hy/mod.rs +++ b/layouts/src/distill/hy/mod.rs @@ -6,7 +6,7 @@ use crate::{ matching, pattern::Substitution, utils::QuadsExt, - Layout, Layouts, Matching, Pattern, Ref, TypedLiteral, TypedValue, Value, + Layout, LayoutRegistry, Matching, Pattern, Ref, TypedLiteral, TypedValue, Value, }; use iref::IriBuf; use rdf_types::{ @@ -40,7 +40,7 @@ pub enum Error { #[error("no matching literal representation found")] NoMatchingLiteral, - #[error("layout `{0}` not found")] + #[error("layout `{0}` is undefined")] LayoutNotFound(Ref), } @@ -114,7 +114,7 @@ impl MatchingForFragment for Result { /// This function a tree value annotated (typed) with references to the /// different layouts used to serialize each part of the tree. pub fn hydrate( - context: &Layouts, + context: impl LayoutRegistry, dataset: &D, layout_ref: &Ref, inputs: &[Term], @@ -130,7 +130,34 @@ where pub fn hydrate_with( vocabulary: &V, interpretation: &I, - context: &Layouts, + context: impl LayoutRegistry, + dataset: &D, + current_graph: Option<&I::Resource>, + layout_ref: &Ref, + inputs: &[I::Resource], +) -> Result, Error> +where + V: Vocabulary, + V::Iri: PartialEq, + I: ReverseIriInterpretation + ReverseLiteralInterpretation, + I::Resource: Clone + Ord, + D: PatternMatchingDataset, +{ + hydrate_with_ref( + vocabulary, + interpretation, + &context, + dataset, + current_graph, + layout_ref, + inputs, + ) +} + +fn hydrate_with_ref( + vocabulary: &V, + interpretation: &I, + context: &impl LayoutRegistry, dataset: &D, current_graph: Option<&I::Resource>, layout_ref: &Ref, @@ -252,7 +279,7 @@ where &variant_substitution, ); - let value = hydrate_with( + let value = hydrate_with_ref( vocabulary, interpretation, context, @@ -325,7 +352,7 @@ where let item_graph = select_graph(current_graph, &field.value.graph, &field_substitution); - let value = hydrate_with( + let value = hydrate_with_ref( vocabulary, interpretation, context, @@ -387,7 +414,7 @@ where &item_substitution, ); - let item = hydrate_with( + let item = hydrate_with_ref( vocabulary, interpretation, context, @@ -451,7 +478,7 @@ where &item_substitution, ); - let item = hydrate_with( + let item = hydrate_with_ref( vocabulary, interpretation, context, @@ -501,7 +528,7 @@ where let item_graph = select_graph(current_graph, &item.value.graph, &item_substitution); - let item = hydrate_with( + let item = hydrate_with_ref( vocabulary, interpretation, context, diff --git a/layouts/src/layout/mod.rs b/layouts/src/layout/mod.rs index 1743e0f..7286da3 100644 --- a/layouts/src/layout/mod.rs +++ b/layouts/src/layout/mod.rs @@ -67,7 +67,7 @@ impl DerefResource for Layouts { Hash(bound = "R: Ord + Hash") )] #[serde(bound(deserialize = "R: Clone + Ord + serde::Deserialize<'de>"))] -pub enum Layout { +pub enum Layout { /// Bottom layout. /// /// This layout does not match any tree value or RDF dataset. diff --git a/layouts/src/lib.rs b/layouts/src/lib.rs index b56d498..c3992fb 100644 --- a/layouts/src/lib.rs +++ b/layouts/src/lib.rs @@ -94,6 +94,7 @@ pub mod graph; pub mod layout; pub mod matching; pub mod pattern; +mod prelude; pub mod preset; pub mod r#ref; pub mod utils; @@ -108,10 +109,99 @@ pub use layout::Layout; use layout::LayoutType; pub use matching::Matching; pub use pattern::Pattern; +pub use prelude::Prelude; pub use preset::PresetLayout; pub use r#ref::{DerefResource, Ref}; +use rdf_types::Term; pub use value::{Literal, TypedLiteral, TypedValue, Value}; +/// Layout registry. +/// +/// Any collection of layout, used by the serialization and deserialization +/// functions. +/// +/// This trait is notably implemented by [`Layouts`], returned after compiling +/// layouts from the abstract syntax, and by [`Prelude`] which provides all the +/// layouts defined by TreeLDR's prelude (`tldr:bool`, `tldr:string`, `tldr:u8`, +/// etc.). +/// +/// Registries can be combined together using the [`with`] method. +/// +/// [`with`]: LayoutRegistry::with +/// +/// # Example +/// +/// ``` +/// use serde_json::json; +/// use treeldr_layouts::{abs, LayoutRegistry, distill::dehydrate, Prelude}; +/// let mut builder = abs::Builder::new(); +/// let layout: abs::syntax::Layout = serde_json::from_value(json!({ +/// "type": "record", +/// "prefixes": { "tldr": "https://treeldr.org/layouts#" }, +/// "fields": { +/// "name": { +/// "value": "tldr:string", // This layout is defined in the prelude. +/// "property": "https://example.org/#name" +/// } +/// } +/// })).unwrap(); +/// let layout_ref = layout.build(&mut builder).unwrap(); +/// let layouts = builder.build(); +/// let input: treeldr_layouts::Value = serde_json::from_value(json!({ +/// "name": "John Smith" +/// })).unwrap(); +/// dehydrate( +/// layouts.with(Prelude), // Add the prelude layouts. +/// &input, +/// &layout_ref, +/// Default::default() +/// ); +/// ``` +pub trait LayoutRegistry { + /// Returns the layout definition associated to the given layout identifier. + fn get(&self, id: &Ref) -> Option<&Layout>; + + /// Checks if this registry contains the definition for the given layout. + fn contains(&self, id: &Ref) -> bool { + self.get(id).is_some() + } + + /// Returns the union of this registry with `other`. + fn with>(&self, other: T) -> LayoutRegistryUnion { + LayoutRegistryUnion(other, self) + } + + /// Returns the union of this registry with `other`. + /// + /// Same as `with`, but consumes `self`. + fn into_with>(self, other: T) -> LayoutRegistryUnion + where + Self: Sized, + { + LayoutRegistryUnion(other, self) + } +} + +impl<'a, R, T: LayoutRegistry> LayoutRegistry for &'a T { + fn get(&self, id: &Ref) -> Option<&Layout> { + T::get(*self, id) + } + + fn contains(&self, id: &Ref) -> bool { + T::contains(*self, id) + } +} + +impl> LayoutRegistry for Option { + fn get(&self, id: &Ref) -> Option<&Layout> { + self.as_ref()?.get(id) + } + + fn contains(&self, id: &Ref) -> bool { + self.as_ref().is_some_and(|r| r.contains(id)) + } +} + /// Layout collection. /// /// Stores compiled layouts definitions, which can then be fetched using the @@ -207,6 +297,12 @@ impl Layouts { } } +impl LayoutRegistry for Layouts { + fn get(&self, id: &Ref) -> Option<&Layout> { + self.get(id) + } +} + /// Layout definitions iterator. /// /// Returned by the [`Layouts::iter`] method. @@ -250,3 +346,17 @@ impl IntoIterator for Layouts { LayoutsIntoIter(self.layouts.into_iter()) } } + +/// Union of two layout registries. +/// +/// Registry `B` gets precedence over registry `A`. If a given layout is defined +/// in both registries, the definition provided by `B` will be returned. +pub struct LayoutRegistryUnion(pub A, pub B); + +impl, B: LayoutRegistry> LayoutRegistry + for LayoutRegistryUnion +{ + fn get(&self, id: &Ref) -> Option<&Layout> { + self.1.get(id).or_else(|| self.0.get(id)) + } +} diff --git a/layouts/src/prelude.rs b/layouts/src/prelude.rs new file mode 100644 index 0000000..ed7327b --- /dev/null +++ b/layouts/src/prelude.rs @@ -0,0 +1,67 @@ +use std::sync::OnceLock; + +use rdf_types::{ + interpretation::{IriInterpretationMut, LiteralInterpretationMut}, + InterpretationMut, VocabularyMut, +}; + +use crate::{ + abs::{self, Builder}, + layout::LayoutType, + Layout, LayoutRegistry, Layouts, Ref, +}; + +const LAYOUTS: [&str; 12] = [ + include_str!("../prelude/unit.json"), + include_str!("../prelude/boolean.json"), + include_str!("../prelude/u8.json"), + include_str!("../prelude/u16.json"), + include_str!("../prelude/u32.json"), + include_str!("../prelude/u64.json"), + include_str!("../prelude/i8.json"), + include_str!("../prelude/i16.json"), + include_str!("../prelude/i32.json"), + include_str!("../prelude/i64.json"), + include_str!("../prelude/string.json"), + include_str!("../prelude/id.json"), +]; + +pub struct Prelude; + +impl Prelude { + pub fn build() -> Layouts { + let mut builder = Builder::new(); + for json in LAYOUTS { + let layout: abs::syntax::Layout = serde_json::from_str(json).unwrap(); + layout.build(&mut builder).unwrap(); + } + + builder.build() + } + + pub fn build_with(vocabulary: &mut V, interpretation: &mut I) -> Layouts + where + V: VocabularyMut, + I: IriInterpretationMut + + LiteralInterpretationMut + + InterpretationMut, + I::Resource: Clone + Eq + Ord + std::fmt::Debug, + { + let mut builder = Builder::::new(); + for json in LAYOUTS { + let layout: abs::syntax::Layout = serde_json::from_str(json).unwrap(); + layout + .build_with_interpretation(vocabulary, interpretation, &mut builder) + .unwrap(); + } + + builder.build() + } +} + +impl LayoutRegistry for Prelude { + fn get(&self, id: &Ref) -> Option<&Layout> { + static LAYOUTS: OnceLock = OnceLock::new(); + LAYOUTS.get_or_init(Self::build).get(id) + } +} diff --git a/src/main.rs b/src/main.rs index 2851fcb..4ead360 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use std::{ path::PathBuf, process::ExitCode, }; -use treeldr_layouts::{layout::LayoutType, Layouts, Ref}; +use treeldr_layouts::{layout::LayoutType, LayoutRegistry, Layouts, Prelude, Ref}; mod format; use format::{RDFFormat, TreeFormat}; @@ -35,10 +35,24 @@ struct Args { #[arg(short, long = "verbose", action = clap::ArgAction::Count, global = true)] verbosity: u8, + /// Do not load TreeLDR's prelude. + #[arg(short, long, global = true)] + no_prelude: bool, + #[command(subcommand)] command: Option, } +impl Args { + pub fn prelude(&self) -> Option { + if self.no_prelude { + None + } else { + Some(Prelude) + } + } +} + #[derive(clap::Subcommand)] enum Command { /// Serializes an RDF dataset into a tree value. @@ -141,6 +155,7 @@ impl DefaultLayoutRef { fn run(files: &mut SimpleFiles, args: Args) -> Result<(), Error> { let mut layouts = Layouts::new(); + let prelude = args.prelude(); let mut default_layout = DefaultLayoutRef::None; for filename in args.layouts { let content = fs::read_to_string(&filename).map_err(Error::IO)?; @@ -155,6 +170,8 @@ fn run(files: &mut SimpleFiles, args: Args) -> Result<(), Error> load_layout(files, file_id, &mut layouts)?; } + let layouts = layouts.with(prelude); + match args.command { None => Ok(()), Some(command) => command.run(layouts, default_layout), @@ -162,7 +179,11 @@ fn run(files: &mut SimpleFiles, args: Args) -> Result<(), Error> } impl Command { - fn run(self, layouts: Layouts, default_layout: DefaultLayoutRef) -> Result<(), Error> { + fn run( + self, + layouts: impl LayoutRegistry, + default_layout: DefaultLayoutRef, + ) -> Result<(), Error> { match self { Self::Hydrate { input,