use super::LoweringContext;
use crate::ast;
use crate::hir::HirModule;
pub fn lower_module(ctx: &mut LoweringContext, file: &ast::DolFile) -> HirModule {
let name = if let Some(ref module_decl) = file.module {
let path = module_decl.path.join(".");
ctx.intern(&path)
} else if let Some(first_decl) = file.declarations.first() {
ctx.intern(first_decl.name())
} else {
ctx.intern("anonymous")
};
let decls: Vec<crate::hir::HirDecl> = file
.declarations
.iter()
.map(|decl| ctx.lower_declaration(decl))
.collect();
crate::hir::HirModule {
id: ctx.fresh_id(),
name,
decls,
}
}
pub fn lower_file(source: &str) -> Result<(HirModule, LoweringContext), crate::error::ParseError> {
let mut parser = crate::parser::Parser::new(source);
let file = parser.parse_file()?;
let mut ctx = LoweringContext::new();
let hir = lower_module(&mut ctx, &file);
Ok((hir, ctx))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::{HirDecl, HirTypeDef};
#[test]
fn test_lower_simple_gene() {
let source = r#"
gene test.simple {
entity has identity
}
exegesis {
A simple test gene.
}
"#;
let result = lower_file(source);
assert!(result.is_ok());
let (hir, ctx) = result.unwrap();
assert!(!ctx.has_errors());
assert_eq!(ctx.symbols.resolve(hir.name), Some("test.simple"));
}
#[test]
fn test_lower_with_module_decl() {
let source = r#"
module my.test.module
gene test.gene {
entity has property
}
exegesis {
A test gene in a module.
}
"#;
let result = lower_file(source);
assert!(result.is_ok());
let (hir, ctx) = result.unwrap();
assert!(!ctx.has_errors());
assert_eq!(ctx.symbols.resolve(hir.name), Some("my.test.module"));
}
#[test]
fn test_lower_trait() {
let source = r#"
trait test.lifecycle {
uses test.exists
entity is created
}
exegesis {
A test trait.
}
"#;
let result = lower_file(source);
assert!(result.is_ok());
let (hir, ctx) = result.unwrap();
assert!(!ctx.has_errors());
assert_eq!(ctx.symbols.resolve(hir.name), Some("test.lifecycle"));
}
#[test]
fn test_lower_constraint() {
let source = r#"
constraint test.integrity {
entity has integrity
}
exegesis {
A test constraint.
}
"#;
let result = lower_file(source);
if let Ok((hir, ctx)) = result {
assert!(!ctx.has_errors());
assert_eq!(ctx.symbols.resolve(hir.name), Some("test.integrity"));
}
}
#[test]
fn test_lower_empty_declarations() {
let mut ctx = LoweringContext::new();
let empty_file = crate::ast::DolFile {
module: None,
uses: vec![],
declarations: vec![],
};
let hir = lower_module(&mut ctx, &empty_file);
assert_eq!(ctx.symbols.resolve(hir.name), Some("anonymous"));
}
#[test]
fn test_lower_gene_has_statements() {
let source = r#"
gene container.exists {
container has identity
container has state
container has boundaries
}
exegesis {
A container is the fundamental unit.
}
"#;
let result = lower_file(source);
assert!(result.is_ok());
let (hir, ctx) = result.unwrap();
assert!(!ctx.has_errors());
assert_eq!(hir.decls.len(), 1);
match &hir.decls[0] {
HirDecl::Type(type_decl) => {
assert_eq!(
ctx.symbols.resolve(type_decl.name),
Some("container.exists")
);
match &type_decl.body {
HirTypeDef::Gene(stmts) => {
assert_eq!(stmts.len(), 3);
}
_ => panic!("Expected Gene body"),
}
}
_ => panic!("Expected Type declaration"),
}
}
#[test]
fn test_lower_trait_has_declarations() {
let source = r#"
trait test.lifecycle {
uses test.exists
entity is created
entity is started
}
exegesis {
Lifecycle trait.
}
"#;
let result = lower_file(source);
assert!(result.is_ok());
let (hir, _ctx) = result.unwrap();
assert_eq!(hir.decls.len(), 1);
match &hir.decls[0] {
HirDecl::Trait(_) => {
}
_ => panic!("Expected Trait declaration"),
}
}
#[test]
fn test_lower_multiple_declarations() {
let mut ctx = LoweringContext::new();
let file = crate::ast::DolFile {
module: None,
uses: vec![],
declarations: vec![
crate::ast::Declaration::Gene(crate::ast::Gen {
visibility: crate::ast::Visibility::default(),
name: "gene.one".to_string(),
extends: None,
statements: vec![],
exegesis: "First gene".to_string(),
span: crate::ast::Span::default(),
}),
crate::ast::Declaration::Gene(crate::ast::Gen {
visibility: crate::ast::Visibility::default(),
name: "gene.two".to_string(),
extends: None,
statements: vec![],
exegesis: "Second gene".to_string(),
span: crate::ast::Span::default(),
}),
],
};
let hir = lower_module(&mut ctx, &file);
assert_eq!(hir.decls.len(), 2);
match &hir.decls[0] {
HirDecl::Type(type_decl) => {
assert_eq!(ctx.symbols.resolve(type_decl.name), Some("gene.one"));
}
_ => panic!("Expected Type declaration"),
}
match &hir.decls[1] {
HirDecl::Type(type_decl) => {
assert_eq!(ctx.symbols.resolve(type_decl.name), Some("gene.two"));
}
_ => panic!("Expected Type declaration"),
}
}
#[test]
fn test_lower_gene_with_various_statements() {
let mut ctx = LoweringContext::new();
let file = crate::ast::DolFile {
module: None,
uses: vec![],
declarations: vec![crate::ast::Declaration::Gene(crate::ast::Gen {
visibility: crate::ast::Visibility::default(),
name: "test.comprehensive".to_string(),
extends: None,
statements: vec![
crate::ast::Statement::Has {
subject: "entity".to_string(),
property: "identity".to_string(),
span: crate::ast::Span::default(),
},
crate::ast::Statement::Is {
subject: "entity".to_string(),
state: "active".to_string(),
span: crate::ast::Span::default(),
},
crate::ast::Statement::DerivesFrom {
subject: "identity".to_string(),
origin: "keypair".to_string(),
span: crate::ast::Span::default(),
},
crate::ast::Statement::Requires {
subject: "entity".to_string(),
requirement: "validation".to_string(),
span: crate::ast::Span::default(),
},
crate::ast::Statement::Uses {
reference: "other.gene".to_string(),
span: crate::ast::Span::default(),
},
],
exegesis: "Comprehensive test".to_string(),
span: crate::ast::Span::default(),
})],
};
let hir = lower_module(&mut ctx, &file);
assert_eq!(hir.decls.len(), 1);
match &hir.decls[0] {
HirDecl::Type(type_decl) => {
match &type_decl.body {
HirTypeDef::Gene(stmts) => {
assert_eq!(stmts.len(), 5);
}
_ => panic!("Expected Gene body"),
}
}
_ => panic!("Expected Type declaration"),
}
}
}