mod crate_gen;
pub mod hir_rust;
mod jsonschema;
mod rust;
mod typescript;
pub use crate_gen::{CrateCodegen, CrateConfig, ModuleInfo};
pub use hir_rust::HirRustCodegen;
pub use jsonschema::JsonSchemaCodegen;
pub use rust::RustCodegen;
pub use typescript::TypeScriptCodegen;
use crate::ast::{Declaration, TypeExpr};
use crate::lower::{lower_file, LowerDiagnostic};
use crate::typechecker::Type;
pub trait Codegen {
fn generate(decl: &Declaration) -> String;
fn generate_all(decls: &[Declaration]) -> String {
decls
.iter()
.map(Self::generate)
.collect::<Vec<_>>()
.join("\n\n")
}
}
#[derive(Debug, Clone, Default)]
pub struct CodegenOptions {
pub include_docs: bool,
pub derive_macros: Vec<String>,
pub visibility: Visibility,
pub generate_builders: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Visibility {
Private,
#[default]
Public,
Crate,
}
pub trait TypeMapper {
fn map_type(ty: &Type) -> String;
fn map_type_expr(ty: &TypeExpr) -> String;
}
pub fn to_pascal_case(s: &str) -> String {
s.split('.')
.flat_map(|part| part.split('_'))
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
})
.collect()
}
pub fn to_snake_case(s: &str) -> String {
s.split('.')
.collect::<Vec<_>>()
.join("_")
.chars()
.enumerate()
.flat_map(|(i, c)| {
if c.is_uppercase() && i > 0 {
vec!['_', c.to_lowercase().next().unwrap()]
} else {
vec![c.to_lowercase().next().unwrap()]
}
})
.collect()
}
const RUST_KEYWORDS: &[&str] = &[
"as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
"false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
"ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type",
"unsafe", "use", "where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "try", "typeof",
"unsized", "virtual", "yield",
];
const RUST_RESERVED_NO_ESCAPE: &[&str] = &["self", "Self", "super", "crate"];
pub fn escape_rust_keyword(s: &str) -> String {
if RUST_RESERVED_NO_ESCAPE.contains(&s) {
format!("{}_", s)
} else if RUST_KEYWORDS.contains(&s) {
format!("r#{}", s)
} else {
s.to_string()
}
}
pub fn to_rust_ident(s: &str) -> String {
escape_rust_keyword(&to_snake_case(s))
}
pub fn compile_to_rust_via_hir(source: &str) -> Result<String, crate::error::ParseError> {
let (hir, ctx) = lower_file(source)?;
for diag in ctx.diagnostics() {
eprintln!("{}", diag);
}
let mut codegen = HirRustCodegen::with_symbols(ctx.symbols);
Ok(codegen.generate(&hir))
}
pub fn compile_with_diagnostics(
source: &str,
) -> Result<(String, Vec<LowerDiagnostic>), crate::error::ParseError> {
let (hir, mut ctx) = lower_file(source)?;
let mut codegen = HirRustCodegen::with_symbols(ctx.symbols.clone());
let code = codegen.generate(&hir);
Ok((code, ctx.take_diagnostics()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_pascal_case() {
assert_eq!(to_pascal_case("container.exists"), "ContainerExists");
assert_eq!(
to_pascal_case("identity.cryptographic"),
"IdentityCryptographic"
);
assert_eq!(to_pascal_case("simple"), "Simple");
assert_eq!(to_pascal_case("snake_case"), "SnakeCase");
}
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("container.exists"), "container_exists");
assert_eq!(to_snake_case("ContainerExists"), "container_exists");
assert_eq!(to_snake_case("simple"), "simple");
}
}
#[cfg(test)]
mod hir_pipeline_tests {
use super::*;
#[test]
fn test_compile_simple_gene() {
let source = r#"
gene test.point {
point has x
point has y
}
exegesis {
A 2D point.
}
"#;
let result = compile_to_rust_via_hir(source);
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
let code = result.unwrap();
assert!(code.contains("Generated from DOL HIR"));
}
#[test]
fn test_compile_with_diagnostics() {
let source = r#"
gene test.simple {
entity has identity
}
exegesis {
Simple test.
}
"#;
let result = compile_with_diagnostics(source);
assert!(result.is_ok());
let (code, diagnostics) = result.unwrap();
assert!(!code.is_empty());
assert!(
diagnostics.is_empty(),
"Unexpected diagnostics: {:?}",
diagnostics
);
}
#[test]
fn test_compile_trait() {
let source = r#"
trait test.lifecycle {
uses test.exists
entity is stateful
}
exegesis {
A lifecycle trait.
}
"#;
let result = compile_to_rust_via_hir(source);
assert!(
result.is_ok(),
"Failed to compile trait: {:?}",
result.err()
);
}
#[test]
fn test_compile_constraint() {
let source = r#"
constraint test.valid {
entity has validity
}
exegesis {
A validity constraint.
}
"#;
let result = compile_to_rust_via_hir(source);
if result.is_err() {
}
}
#[test]
fn test_hir_codegen_output_format() {
let source = r#"
gene container.exists {
container has id
container has image
container has name
}
exegesis {
A container is the fundamental unit.
}
"#;
let result = compile_to_rust_via_hir(source);
assert!(result.is_ok());
let code = result.unwrap();
assert!(code.contains("Generated from DOL HIR"), "Missing header");
}
}