Archived
rename crates/ to engrams/; add el-compiler el package with bootstrap artifact
- crates/ → engrams/ (Rust engrams live here)
- el-compiler/ added: el self-hosting compiler as an el package
- src/{compiler,lexer,parser,codegen}.el
- bootstrap/el-compiler.elc (114KB, Rust-compiled seed)
- el.toml Cargo.toml workspace paths updated
- neuron-rs cross-repo path deps fixed (were pointing to products/ instead of foundation/)
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
//! Top-level compiler struct — orchestrates the full pipeline.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use el_lexer::tokenize;
|
||||
use el_parser::parse;
|
||||
use el_seal::{seal, SealConfig, SealedArtifact};
|
||||
use el_types::TypeChecker;
|
||||
|
||||
use crate::bytecode::{deserialize_bytecode, serialize_bytecode};
|
||||
use crate::codegen::Codegen;
|
||||
use crate::error::{CompileError, CompileResult};
|
||||
|
||||
/// Which compilation target to produce.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Target {
|
||||
/// Full debug info: source maps, stack traces, no optimization.
|
||||
Debug,
|
||||
/// Optimized, stripped, no debug info.
|
||||
Release,
|
||||
/// Quantum-sealed: encrypted bytecode, cannot be decompiled.
|
||||
Prod,
|
||||
}
|
||||
|
||||
/// Compiler configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompilerOptions {
|
||||
pub target: Target,
|
||||
pub output_path: PathBuf,
|
||||
pub source_path: PathBuf,
|
||||
/// Path to an Engram database for `activate` type resolution.
|
||||
/// `None` disables semantic type compatibility (falls back to structural).
|
||||
pub engram_db_path: Option<PathBuf>,
|
||||
/// Seal configuration for the `prod` target.
|
||||
pub seal_config: SealConfig,
|
||||
}
|
||||
|
||||
impl Default for CompilerOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
target: Target::Debug,
|
||||
output_path: PathBuf::from("out.elc"),
|
||||
source_path: PathBuf::from("main.el"),
|
||||
engram_db_path: None,
|
||||
seal_config: SealConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of a compilation.
|
||||
#[derive(Debug)]
|
||||
pub struct CompileOutput {
|
||||
/// The compiled artifact bytes. Format depends on target:
|
||||
/// - Debug/Release: JSON-serialized `Vec<Bytecode>`
|
||||
/// - Prod: `SealedArtifact` wire format (`ENGRAM01` + JSON body)
|
||||
pub artifact: Vec<u8>,
|
||||
pub target: Target,
|
||||
/// Whether the artifact is quantum-sealed.
|
||||
pub sealed: bool,
|
||||
/// JSON source map (debug target only).
|
||||
pub source_map: Option<String>,
|
||||
/// Type-check and compilation diagnostics.
|
||||
pub diagnostics: Vec<String>,
|
||||
}
|
||||
|
||||
/// The Engram language compiler.
|
||||
pub struct Compiler;
|
||||
|
||||
impl Compiler {
|
||||
/// Compile `source` with the given options.
|
||||
pub fn compile(source: &str, opts: CompilerOptions) -> CompileResult<CompileOutput> {
|
||||
// ── Step 1: Lex ───────────────────────────────────────────────────────
|
||||
let tokens = tokenize(source)?;
|
||||
|
||||
// ── Step 2: Parse ─────────────────────────────────────────────────────
|
||||
let program = parse(tokens, source.to_string())?;
|
||||
|
||||
// ── Step 3: Type-check ────────────────────────────────────────────────
|
||||
let mut checker = TypeChecker::with_builtins();
|
||||
let diags = checker.check(&program);
|
||||
let diagnostics: Vec<String> = diags.iter().map(|d| d.message.clone()).collect();
|
||||
|
||||
// We continue compiling even with type errors in debug/release mode.
|
||||
// In prod mode, type errors are fatal.
|
||||
if opts.target == Target::Prod && !checker.ok() {
|
||||
return Err(CompileError::Type(
|
||||
diagnostics.join("; ")
|
||||
));
|
||||
}
|
||||
|
||||
// ── Step 4: Code generation ───────────────────────────────────────────
|
||||
let emit_sm = matches!(opts.target, Target::Debug);
|
||||
let cg = Codegen::new(emit_sm);
|
||||
let (bytecode, source_map) = cg.generate(&program)
|
||||
.map_err(|e| CompileError::Codegen(e.to_string()))?;
|
||||
|
||||
let bytecode_bytes = serialize_bytecode(&bytecode)
|
||||
.map_err(|e| CompileError::Codegen(e.to_string()))?;
|
||||
|
||||
// ── Step 5: Target-specific post-processing ───────────────────────────
|
||||
match opts.target {
|
||||
Target::Debug => {
|
||||
let sm_json = source_map.to_json()
|
||||
.map_err(|e| CompileError::Serialization(e.to_string()))?;
|
||||
Ok(CompileOutput {
|
||||
artifact: bytecode_bytes,
|
||||
target: Target::Debug,
|
||||
sealed: false,
|
||||
source_map: Some(sm_json),
|
||||
diagnostics,
|
||||
})
|
||||
}
|
||||
Target::Release => {
|
||||
Ok(CompileOutput {
|
||||
artifact: bytecode_bytes,
|
||||
target: Target::Release,
|
||||
sealed: false,
|
||||
source_map: None,
|
||||
diagnostics,
|
||||
})
|
||||
}
|
||||
Target::Prod => {
|
||||
let artifact = seal(&bytecode_bytes, &opts.seal_config)?;
|
||||
let artifact_bytes = artifact.to_bytes()
|
||||
.map_err(|e| CompileError::Serialization(e.to_string()))?;
|
||||
Ok(CompileOutput {
|
||||
artifact: artifact_bytes,
|
||||
target: Target::Prod,
|
||||
sealed: true,
|
||||
source_map: None,
|
||||
diagnostics,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: compile and unseal, returning the bytecode instructions.
|
||||
pub fn compile_and_unseal(
|
||||
source: &str,
|
||||
opts: CompilerOptions,
|
||||
binding_key: &[u8],
|
||||
) -> CompileResult<Vec<crate::bytecode::Bytecode>> {
|
||||
let output = Self::compile(source, opts)?;
|
||||
let sealed_artifact = SealedArtifact::from_bytes(&output.artifact)
|
||||
.map_err(CompileError::Seal)?;
|
||||
let bytecode_bytes = el_seal::unseal(&sealed_artifact, binding_key)
|
||||
.map_err(CompileError::Seal)?;
|
||||
let instructions = deserialize_bytecode(&bytecode_bytes)
|
||||
.map_err(|e| CompileError::Codegen(e.to_string()))?;
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use el_seal::{DeploymentBinding, SealAlgorithm};
|
||||
use super::*;
|
||||
|
||||
fn debug_opts() -> CompilerOptions {
|
||||
CompilerOptions {
|
||||
target: Target::Debug,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn release_opts() -> CompilerOptions {
|
||||
CompilerOptions {
|
||||
target: Target::Release,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn prod_opts() -> CompilerOptions {
|
||||
CompilerOptions {
|
||||
target: Target::Prod,
|
||||
seal_config: SealConfig {
|
||||
algorithm: SealAlgorithm::Aes256Gcm,
|
||||
deployment_binding: DeploymentBinding::None,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_hello_world_debug() {
|
||||
let src = r#"let msg: String = "Hello, World!""#;
|
||||
let out = Compiler::compile(src, debug_opts()).unwrap();
|
||||
assert!(!out.artifact.is_empty());
|
||||
assert!(!out.sealed);
|
||||
assert!(out.source_map.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_release_no_source_map() {
|
||||
let src = "let x: Int = 42";
|
||||
let out = Compiler::compile(src, release_opts()).unwrap();
|
||||
assert!(out.source_map.is_none());
|
||||
assert!(!out.sealed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_prod_is_sealed() {
|
||||
let src = "let x: Int = 1";
|
||||
let out = Compiler::compile(src, prod_opts()).unwrap();
|
||||
assert!(out.sealed);
|
||||
// Artifact must start with ENGRAM01 magic
|
||||
assert_eq!(&out.artifact[..8], b"ENGRAM01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prod_roundtrip() {
|
||||
let src = "let answer: Int = 42";
|
||||
let opts = prod_opts();
|
||||
let out = Compiler::compile(src, opts).unwrap();
|
||||
let sealed = SealedArtifact::from_bytes(&out.artifact).unwrap();
|
||||
let bytecode_bytes = el_seal::unseal(&sealed, &[]).unwrap();
|
||||
let instructions = deserialize_bytecode(&bytecode_bytes).unwrap();
|
||||
// Should have a PUSH 42, STORE answer, and HALT at minimum
|
||||
assert!(instructions.iter().any(|b| matches!(b, crate::bytecode::Bytecode::Push(crate::bytecode::Value::Int(42)))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_fn_def() {
|
||||
let src = r#"
|
||||
fn add(a: Int, b: Int) -> Int {
|
||||
return a + b
|
||||
}
|
||||
"#;
|
||||
let out = Compiler::compile(src, debug_opts()).unwrap();
|
||||
assert!(!out.artifact.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_type_mismatch_warning_debug() {
|
||||
// In debug mode, type errors are warnings (not fatal)
|
||||
let src = r#"let x: Int = "not an int""#;
|
||||
let out = Compiler::compile(src, debug_opts()).unwrap();
|
||||
assert!(!out.diagnostics.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_source_map_has_entries() {
|
||||
let src = "let x = 1\nlet y = 2";
|
||||
let out = Compiler::compile(src, debug_opts()).unwrap();
|
||||
let sm_json = out.source_map.unwrap();
|
||||
let sm: crate::source_map::SourceMap = serde_json::from_str(&sm_json).unwrap();
|
||||
assert!(!sm.entries.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user