A little more documentation for preserves-schema

This commit is contained in:
Tony Garnock-Jones 2023-10-27 14:03:53 +02:00
parent f009920dd7
commit 8db860648b
1 changed files with 72 additions and 1 deletions

View File

@ -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<String>;
/// 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<ModulePath, (Schema, Purpose)>,
/// 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<ModulePath, ExternalModule>,
/// Plugins active in this compiler instance.
pub plugins: Vec<Box<dyn Plugin>>,
/// 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<ModulePath, (Schema, Purpose)>,
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<ModulePath, (Schema, Purpose)>,
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<ModulePath, Schema>, 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<ModulePath, Schema>, 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<ModulePath, Schema>,
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<String>) -> io::Result<Vec<PathBuf>> {
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);