preserves/implementations/rust/preserves-schema/src/compiler/mod.rs

341 lines
11 KiB
Rust

pub mod context;
pub mod cycles;
pub mod names;
pub mod parsers;
pub mod readers;
pub mod types;
pub mod unparsers;
use crate::*;
use crate::compiler::context::*;
use crate::gen::Language;
use crate::gen::schema;
use crate::gen::schema::*;
use crate::syntax::block::{Formatter, Item};
use crate::syntax::block::constructors::*;
use glob::glob;
use preserves::value::BinarySource;
use preserves::value::IOBinarySource;
use preserves::value::Map;
use preserves::value::Set;
use preserves::value::Reader;
use std::fs::DirBuilder;
use std::fs::File;
use std::io;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
pub type ModulePath = Vec<String>;
pub trait Plugin: std::fmt::Debug {
fn generate_module(&self, _module_ctxt: &mut ModuleContext) {}
fn generate_definition(
&self,
module_ctxt: &mut ModuleContext,
definition_name: &str,
definition: &Definition,
);
}
pub struct LanguageTypes {
pub fallback: Option<Box<dyn Fn(&str) -> Set<String>>>,
pub definitions: Map<String, Box<dyn Fn(&str) -> Set<String>>>,
}
impl std::fmt::Debug for LanguageTypes {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.debug_struct("LanguageTypes")
.field("fallback", &self.fallback.as_ref().map(|f| f("_")))
.field("definitions", &self.definitions.iter().map(
|(k, f)| (k.clone(), f("_"))).collect::<Map<String, Set<String>>>())
.finish()
}
}
#[derive(Debug)]
pub struct ExternalModule {
pub path: ModulePath,
pub rust_namespace: String,
pub rust_language_types: LanguageTypes,
}
impl ExternalModule {
pub fn new(path: ModulePath, rust_namespace: &str) -> Self {
ExternalModule {
path,
rust_namespace: rust_namespace.to_owned(),
rust_language_types: LanguageTypes {
fallback: None,
definitions: Map::new(),
},
}
}
pub fn set_fallback_language_types<F: 'static + Fn(&str) -> Set<String>>(
mut self,
f: F,
) -> Self {
self.rust_language_types.fallback = Some(Box::new(f));
self
}
pub fn set_definition_language_types<F: 'static + Fn(&str) -> Set<String>>(
mut self,
d: &str,
f: F,
) -> Self {
if self.rust_language_types.definitions.insert(d.to_owned(), Box::new(f)).is_some() {
panic!("Duplicate language types definition installed: {:?} {:?}", &self.path, d);
}
self
}
}
#[derive(Debug)]
pub struct CompilerConfig {
pub bundle: Map<ModulePath, Schema>,
pub output_dir: PathBuf,
pub fully_qualified_module_prefix: String,
pub support_crate: String,
pub external_modules: Map<ModulePath, ExternalModule>,
pub plugins: Vec<Box<dyn Plugin>>,
}
pub fn load_schema_or_bundle(bundle: &mut Map<ModulePath, Schema>, 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,
fully_qualified_module_prefix: String,
) -> Self {
CompilerConfig {
bundle: Map::new(),
output_dir: output_dir,
fully_qualified_module_prefix: fully_qualified_module_prefix,
support_crate: "preserves_schema".to_owned(),
external_modules: Map::new(),
plugins: vec![
Box::new(types::TypePlugin),
Box::new(readers::ReaderPlugin),
Box::new(parsers::ParserPlugin),
Box::new(unparsers::UnparserPlugin),
],
}
}
pub fn add_external_module(&mut self, m: ExternalModule) {
let path = m.path.clone();
if self.external_modules.insert(path.clone(), m).is_some() {
panic!("Duplicate external module installed: {:?}", path)
}
}
pub fn load_schemas_and_bundles(&mut self, inputs: &Vec<PathBuf>) -> io::Result<()> {
for i in inputs {
load_schema_or_bundle(&mut self.bundle, i)?;
}
Ok(())
}
fn build_type_cache(&self) -> Map<Ref, types::TDefinition> {
self.bundle.iter().flat_map(|(modpath, s)| {
let modpath = ModulePath(modpath.clone());
s.definitions.0.iter().map(move |(name, def)| {
let ty = types::definition_type(&modpath, name, def);
(ty.self_ref.clone(), ty)
})
}).collect()
}
fn generate_definition(
&self,
b: &mut BundleContext,
k: &ModulePath,
v: &Schema,
n: &str,
d: &Definition,
mode: ModuleContextMode,
generated: &mut Map<ModuleContextMode, Vec<Item>>,
) {
b.generate_module(k, v, mode, generated, |m| {
for plugin in self.plugins.iter() {
plugin.generate_definition(m, n, d);
}
});
}
}
pub fn expand_inputs(globs: &Vec<String>) -> io::Result<Vec<PathBuf>> {
let mut result = Vec::new();
for g in globs.iter() {
for p in glob(g).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))? {
result.push(p.map_err(glob::GlobError::into_error)?)
}
}
Ok(result)
}
fn write_if_changed(output_path: &PathBuf, contents: &[u8]) -> io::Result<()> {
if output_path.exists() {
if let Ok(mut f) = File::open(output_path) {
let mut existing_contents = Vec::new();
f.read_to_end(&mut existing_contents)?;
if existing_contents == contents {
return Ok(());
}
}
}
let mut f = File::create(output_path)?;
f.write_all(contents)
}
impl Ref {
pub fn qualify(&self, default_module_path: &schema::ModulePath) -> Ref {
if self.module.0.is_empty() {
Ref { module: default_module_path.clone(), name: self.name.clone() }
} else {
self.clone()
}
}
}
impl Schema {
pub fn has_embedded_type(&self) -> bool {
self.embedded_type != EmbeddedTypeName::False
}
}
pub fn compile(config: &CompilerConfig) -> io::Result<()> {
let mut b = BundleContext::new(config);
for (k, v) in config.bundle.iter() {
let mut output_path = config.output_dir.clone();
output_path.extend(k);
let module_name = output_path.file_stem().unwrap().to_str().unwrap().to_owned();
let module_name = names::render_modname(&module_name);
output_path.set_file_name(format!("{}.rs", module_name));
DirBuilder::new().recursive(true).create(output_path.parent().unwrap())?;
//---------------------------------------------------------------------------
let mut generated = Map::new();
b.generate_module(k, v, ModuleContextMode::TargetModule, &mut generated, |m| {
for plugin in config.plugins.iter() {
plugin.generate_module(m);
}
});
for (n, d) in &v.definitions.0 {
use ModuleContextMode::*;
config.generate_definition(&mut b, k, v, n, d, TargetToplevel, &mut generated);
config.generate_definition(&mut b, k, v, n, d, TargetGeneric, &mut generated);
}
//---------------------------------------------------------------------------
let mut lines: Vec<String> = Vec::new();
lines.push(Formatter::to_string(vertical(false, seq![
"#![allow(unused_parens)]",
"#![allow(unused_imports)]",
"",
"use std::convert::TryFrom;",
format!("use {}::support as _support;", &config.support_crate),
"use _support::Deserialize;",
"use _support::Parse;",
"use _support::Unparse;",
"use _support::preserves;",
"use preserves::value::Domain;",
"use preserves::value::NestedValue;",
""])));
let mut emit_items = |items: Vec<Item>| {
if !items.is_empty() {
lines.push(Formatter::to_string(vertical(true, seq(items))));
lines.push("".to_owned());
}
};
emit_items(generated.remove(&ModuleContextMode::TargetModule).unwrap());
emit_items(generated.remove(&ModuleContextMode::TargetToplevel).unwrap());
emit_items(generated.remove(&ModuleContextMode::TargetGeneric).unwrap());
{
let contents = lines.join("\n");
write_if_changed(&output_path, contents.as_bytes())?;
}
}
{
let mut mod_rs = config.output_dir.clone();
mod_rs.extend(vec!["mod.rs"]);
let mut lines = Vec::new();
for modpath in config.bundle.keys() {
lines.push(format!("pub mod {};", names::render_modname(modpath.last().unwrap())));
}
lines.push("".to_owned());
lines.push(format!("use {}::support as _support;", &config.support_crate));
lines.push("use _support::preserves;".to_owned());
lines.push("".to_owned());
lines.push("#[allow(non_snake_case)]".to_owned());
lines.push(Formatter::to_string(item(seq![
"pub struct ", b.language_struct_name(), anglebrackets!["N: preserves::value::NestedValue"], " ",
vertical(false, braces(b.literals.iter().map(
|(value, name)| item(format!("pub {}: N /* {:?} */", name, value))).collect()))
])));
lines.push("".to_owned());
lines.push(Formatter::to_string(item(seq![
"impl", anglebrackets!["N: preserves::value::NestedValue"],
" Default for ", b.language_struct_name(), "<N> ", codeblock![
seq!["fn default() -> Self ", codeblock![
seq![b.language_struct_name(), " ", vertical(false, braces(b.literals.iter().map(|(value, name)| {
let bs = preserves::value::PackedWriter::encode_iovalue(&value).unwrap();
item(format!("{}: /* {:?} */ _support::decode_lit(&vec!{:?}).unwrap()",
name,
value,
bs))
}).collect()))]
]]
]
])));
lines.push("".to_owned());
let contents = lines.join("\n");
write_if_changed(&mod_rs, contents.as_bytes())?;
}
Ok(())
}