diff --git a/implementations/rust/preserves-schema/src/compiler/mod.rs b/implementations/rust/preserves-schema/src/compiler/mod.rs index af06535..c593739 100644 --- a/implementations/rust/preserves-schema/src/compiler/mod.rs +++ b/implementations/rust/preserves-schema/src/compiler/mod.rs @@ -1,4 +1,38 @@ -//! Implementation of the Schema-to-Rust compiler. +//! Implementation of the Schema-to-Rust compiler; this is the core of the +//! [preserves-schema-rs][] program. +//! +//! See the [documentation for preserves-schema-rs][preserves-schema-rs] for examples of how to +//! use the compiler programmatically from a `build.rs` script, but very briefly, use +//! [preserves-schemac](https://preserves.dev/doc/preserves-schemac.html) to generate a +//! metaschema instance `*.prb` file, and then put something like this in `build.rs`: +//! +//! ```rust,ignore +//! use preserves_schema::compiler::*; +//! +//! const PATH_TO_PRB_FILE: &'static str = "your-metaschema-instance-file.prb"; +//! +//! fn main() -> Result<(), std::io::Error> { +//! let buildroot = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); +//! +//! let mut gen_dir = buildroot.clone(); +//! gen_dir.push("src/schemas"); +//! let mut c = CompilerConfig::new(gen_dir, "crate::schemas".to_owned()); +//! +//! let inputs = expand_inputs(&vec![PATH_TO_PRB_FILE.to_owned()])?; +//! c.load_schemas_and_bundles(&inputs)?; +//! compile(&c) +//! } +//! ``` +//! +//! plus something like this in your `lib.rs` or main program: +//! +//! ```rust,ignore +//! pub mod schemas { +//! include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs")); +//! } +//! ``` +//! +//! [preserves-schema-rs]: https://preserves.dev/doc/preserves-schema-rs.html pub mod context; pub mod cycles; @@ -31,11 +65,18 @@ use std::io::Read; use std::io::Write; use std::path::PathBuf; +/// Names a Schema module within a (collection of) Schema bundle(s). pub type ModulePath = Vec; +/// Implement this trait to extend the compiler with custom code generation support. The main +/// code generators are also implemented as plugins. +/// +/// For an example of its use outside the core compiler, see [`build.rs` for the `syndicate-rs` project](https://git.syndicate-lang.org/syndicate-lang/syndicate-rs/src/commit/60e6c6badfcbcbccc902994f4f32db6048f60d1f/syndicate/build.rs). pub trait Plugin: std::fmt::Debug { + /// Use `_module_ctxt` to emit code at a per-module level. fn generate_module(&self, _module_ctxt: &mut ModuleContext) {} + /// Use `module_ctxt` to emit code at a per-Schema-[Definition] level. fn generate_definition( &self, module_ctxt: &mut ModuleContext, @@ -112,17 +153,30 @@ impl ExternalModule { } } +/// Main entry point to the compiler. #[derive(Debug)] pub struct CompilerConfig { + /// All known Schema modules, indexed by [ModulePath] and annotated with a [Purpose]. pub bundle: Map, + /// Where output Rust code files will be placed. pub output_dir: PathBuf, + /// Fully-qualified Rust module prefix to use for each generated module. pub fully_qualified_module_prefix: String, + /// Rust module path to the [preserves_schema::support][crate::support] module. pub support_crate: String, + /// External modules for cross-referencing. pub external_modules: Map, + /// Plugins active in this compiler instance. pub plugins: Vec>, + /// If true, a directive is emitted in each module instructing + /// [rustfmt](https://github.com/rust-lang/rustfmt) to ignore it. pub rustfmt_skip: bool, } +/// Loads a [Schema] or [Bundle] from path `i` into `bundle` for the given `purpose`. +/// +/// If `i` holds a [Schema], then the file stem of `i` is used as the module name when placing +/// the schema in `bundle`. pub fn load_schema_or_bundle_with_purpose( bundle: &mut Map, i: &PathBuf, @@ -136,6 +190,11 @@ pub fn load_schema_or_bundle_with_purpose( Ok(()) } +/// Loads a [Schema] or [Bundle] from raw binary encoded value `input` into `bundle` for the +/// given `purpose`. +/// +/// If `input` corresponds to a [Schema], then `prefix` is used as its module name; otherwise, +/// it's a [Bundle], and `prefix` is ignored. pub fn load_schema_or_bundle_bin_with_purpose( bundle: &mut Map, prefix: &str, @@ -167,6 +226,10 @@ fn bundle_prefix(i: &PathBuf) -> io::Result<&str> { }) } +/// Loads a [Schema] or [Bundle] from path `i` into `bundle`. +/// +/// If `i` holds a [Schema], then the file stem of `i` is used as the module name when placing +/// the schema in `bundle`. pub fn load_schema_or_bundle(bundle: &mut Map, i: &PathBuf) -> io::Result<()> { let mut f = File::open(&i)?; let mut bs = vec![]; @@ -174,6 +237,10 @@ pub fn load_schema_or_bundle(bundle: &mut Map, i: &PathBuf) load_schema_or_bundle_bin(bundle, bundle_prefix(i)?, &bs[..]) } +/// Loads a [Schema] or [Bundle] from raw binary encoded value `input` into `bundle`. +/// +/// If `input` corresponds to a [Schema], then `prefix` is used as its module name; otherwise, +/// it's a [Bundle], and `prefix` is ignored. pub fn load_schema_or_bundle_bin( bundle: &mut Map, prefix: &str, @@ -201,6 +268,8 @@ pub fn load_schema_or_bundle_bin( } impl CompilerConfig { + /// Construct a [CompilerConfig] configured to send output files to `output_dir`, and to + /// use `fully_qualified_module_prefix` as the Rust module prefix for generated code. pub fn new(output_dir: PathBuf, fully_qualified_module_prefix: String) -> Self { CompilerConfig { bundle: Map::new(), @@ -279,6 +348,7 @@ impl CompilerConfig { } } +/// Expands a vector of [mod@glob]s to a vector of actual paths. pub fn expand_inputs(globs: &Vec) -> io::Result> { let mut result = Vec::new(); for g in globs.iter() { @@ -324,6 +394,7 @@ impl Schema { } } +/// Main entry point: runs the compilation process. pub fn compile(config: &CompilerConfig) -> io::Result<()> { let mut b = BundleContext::new(config);