feat: engram-lang — new programming language, quantum-sealed prod target, spreading activation types

This commit is contained in:
Will Anderson
2026-04-27 18:46:51 -05:00
commit 9ced941590
5569 changed files with 8153 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "el"
description = "Engram language CLI — el build / run / check / seal / unseal"
version.workspace = true
edition.workspace = true
license.workspace = true
[[bin]]
name = "el"
path = "src/main.rs"
[dependencies]
el-lexer = { workspace = true }
el-parser = { workspace = true }
el-types = { workspace = true }
el-compiler = { workspace = true }
el-seal = { workspace = true }
clap = { workspace = true }
thiserror = { workspace = true }
+332
View File
@@ -0,0 +1,332 @@
//! el — The Engram language CLI.
//!
//! Commands:
//! el build <file.el> [--target debug|release|prod] [--output <path>]
//! el run <file.el>
//! el check <file.el>
//! el seal <artifact>
//! el unseal <artifact>
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use el_compiler::{Compiler, CompilerOptions, Target};
use el_seal::{seal as seal_fn, unseal as unseal_fn, SealedArtifact, DeploymentBinding, SealAlgorithm, SealConfig};
#[derive(Parser, Debug)]
#[command(name = "el", about = "The Engram programming language compiler and toolchain", version)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
/// Compile an Engram source file.
Build {
/// Source file (*.el)
file: PathBuf,
/// Compilation target: debug | release | prod
#[arg(long, default_value = "debug")]
target: String,
/// Output path
#[arg(long, short = 'o')]
output: Option<PathBuf>,
},
/// Compile and run an Engram source file (debug target).
Run {
/// Source file (*.el)
file: PathBuf,
},
/// Type-check an Engram source file without producing output.
Check {
/// Source file (*.el)
file: PathBuf,
},
/// Seal an existing release artifact.
Seal {
/// Release artifact to seal
artifact: PathBuf,
/// Output path (default: <artifact>.sealed)
#[arg(long, short = 'o')]
output: Option<PathBuf>,
},
/// Unseal a sealed artifact (requires ENGRAM_SEAL_KEY env var).
Unseal {
/// Sealed artifact
artifact: PathBuf,
/// Output path for decrypted bytecode
#[arg(long, short = 'o')]
output: Option<PathBuf>,
},
}
fn main() {
let cli = Cli::parse();
if let Err(e) = run(cli) {
eprintln!("error: {e}");
std::process::exit(1);
}
}
fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
match cli.command {
Command::Build { file, target, output } => {
let source = std::fs::read_to_string(&file)
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
let compilation_target = parse_target(&target)?;
let out_path = output.unwrap_or_else(|| {
let stem = file.file_stem().unwrap_or_default().to_string_lossy();
match &compilation_target {
Target::Debug => PathBuf::from(format!("{stem}.elc")),
Target::Release => PathBuf::from(format!("{stem}.elc")),
Target::Prod => PathBuf::from(format!("{stem}.sealed")),
}
});
let seal_config = build_seal_config()?;
let opts = CompilerOptions {
target: compilation_target,
output_path: out_path.clone(),
source_path: file.clone(),
engram_db_path: None,
seal_config,
};
let output = Compiler::compile(&source, opts)?;
// Print diagnostics
for d in &output.diagnostics {
eprintln!("warning: {d}");
}
// Write artifact
std::fs::write(&out_path, &output.artifact)
.map_err(|e| format!("cannot write {}: {e}", out_path.display()))?;
// Write source map alongside (debug only)
if let Some(sm) = &output.source_map {
let sm_path = out_path.with_extension("map.json");
std::fs::write(&sm_path, sm)
.map_err(|e| format!("cannot write source map: {e}"))?;
println!("compiled {} -> {} (source map: {})",
file.display(), out_path.display(), sm_path.display());
} else {
println!("compiled {} -> {} [sealed={}]",
file.display(), out_path.display(), output.sealed);
}
}
Command::Run { file } => {
let source = std::fs::read_to_string(&file)
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
let opts = CompilerOptions {
target: Target::Debug,
..Default::default()
};
let output = Compiler::compile(&source, opts)?;
// Diagnostics
for d in &output.diagnostics {
eprintln!("warning: {d}");
}
// Run the bytecode through the interpreter
let instructions = el_compiler::Bytecode::deserialize_all(&output.artifact)
.unwrap_or_default();
run_interpreter(&instructions);
}
Command::Check { file } => {
let source = std::fs::read_to_string(&file)
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
let tokens = el_lexer::tokenize(&source)?;
let program = el_parser::parse(tokens, source.clone())?;
let mut checker = el_types::TypeChecker::with_builtins();
checker.check(&program);
if checker.ok() {
println!("{}: ok", file.display());
} else {
for d in checker.diagnostics.iter().filter(|d| d.is_error) {
eprintln!("error: {}", d.message);
}
std::process::exit(1);
}
}
Command::Seal { artifact, output } => {
let bytes = std::fs::read(&artifact)
.map_err(|e| format!("cannot read {}: {e}", artifact.display()))?;
let out_path = output.unwrap_or_else(|| {
let mut p = artifact.clone();
let ext = format!("{}.sealed",
p.extension().unwrap_or_default().to_string_lossy());
p.set_extension(ext);
p
});
let config = build_seal_config()?;
let sealed = seal_fn(&bytes, &config)?;
let artifact_bytes = sealed.to_bytes()?;
std::fs::write(&out_path, &artifact_bytes)
.map_err(|e| format!("cannot write {}: {e}", out_path.display()))?;
println!("sealed {} -> {} ({} bytes)", artifact.display(), out_path.display(), artifact_bytes.len());
}
Command::Unseal { artifact, output } => {
let bytes = std::fs::read(&artifact)
.map_err(|e| format!("cannot read {}: {e}", artifact.display()))?;
let out_path = output.unwrap_or_else(|| {
artifact.with_extension("elc")
});
let sealed = SealedArtifact::from_bytes(&bytes)?;
// Get the binding key from environment
let key_str = std::env::var("ENGRAM_SEAL_KEY")
.unwrap_or_default();
let key_bytes = key_str.as_bytes();
let plaintext = unseal_fn(&sealed, key_bytes)?;
std::fs::write(&out_path, &plaintext)
.map_err(|e| format!("cannot write {}: {e}", out_path.display()))?;
println!("unsealed {} -> {} ({} bytes)", artifact.display(), out_path.display(), plaintext.len());
}
}
Ok(())
}
// ── Helpers ───────────────────────────────────────────────────────────────────
fn parse_target(s: &str) -> Result<Target, String> {
match s {
"debug" => Ok(Target::Debug),
"release" => Ok(Target::Release),
"prod" => Ok(Target::Prod),
other => Err(format!("unknown target '{other}': use debug, release, or prod")),
}
}
fn build_seal_config() -> Result<SealConfig, String> {
Ok(SealConfig {
algorithm: SealAlgorithm::Aes256Gcm,
deployment_binding: if std::env::var("ENGRAM_SEAL_KEY").is_ok() {
DeploymentBinding::EnvironmentKey("ENGRAM_SEAL_KEY".into())
} else {
DeploymentBinding::None
},
})
}
/// Minimal interpreter for demonstration — prints values to stdout.
fn run_interpreter(instructions: &[el_compiler::Bytecode]) {
use el_compiler::{Bytecode, Value};
let mut stack: Vec<Value> = Vec::new();
let mut locals: std::collections::HashMap<String, Value> = std::collections::HashMap::new();
let mut ip = 0usize;
while ip < instructions.len() {
match &instructions[ip] {
Bytecode::Push(v) => stack.push(v.clone()),
Bytecode::Pop => { stack.pop(); }
Bytecode::Dup => {
if let Some(top) = stack.last().cloned() { stack.push(top); }
}
Bytecode::Add => {
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
stack.push(match (a, b) {
(Value::Int(x), Value::Int(y)) => Value::Int(x + y),
(Value::Float(x), Value::Float(y)) => Value::Float(x + y),
(Value::Str(x), Value::Str(y)) => Value::Str(x + &y),
_ => Value::Nil,
});
}
Bytecode::Sub => {
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
stack.push(match (a, b) {
(Value::Int(x), Value::Int(y)) => Value::Int(x - y),
(Value::Float(x), Value::Float(y)) => Value::Float(x - y),
_ => Value::Nil,
});
}
Bytecode::Mul => {
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
stack.push(match (a, b) {
(Value::Int(x), Value::Int(y)) => Value::Int(x * y),
(Value::Float(x), Value::Float(y)) => Value::Float(x * y),
_ => Value::Nil,
});
}
Bytecode::Div => {
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
stack.push(match (a, b) {
(Value::Int(x), Value::Int(y)) if y != 0 => Value::Int(x / y),
(Value::Float(x), Value::Float(y)) => Value::Float(x / y),
_ => Value::Nil,
});
}
Bytecode::Eq => {
let (b, a) = (stack.pop().unwrap_or(Value::Nil), stack.pop().unwrap_or(Value::Nil));
stack.push(Value::Bool(a == b));
}
Bytecode::Not => {
let v = stack.pop().unwrap_or(Value::Nil);
stack.push(Value::Bool(!matches!(v, Value::Bool(true))));
}
Bytecode::StoreLocal(name) => {
let v = stack.pop().unwrap_or(Value::Nil);
locals.insert(name.clone(), v);
}
Bytecode::LoadLocal(name) => {
let v = locals.get(name).cloned().unwrap_or(Value::Nil);
stack.push(v);
}
Bytecode::Call { name, .. } => {
if name == "print" || name == "println" {
let v = stack.pop().unwrap_or(Value::Nil);
println!("{v}");
stack.push(Value::Nil);
}
}
Bytecode::Activate { type_name, query } => {
println!("[activate] {type_name} where \"{query}\" (no DB connected)");
stack.push(Value::List(vec![]));
}
Bytecode::Jump(offset) => {
let new_ip = (ip as i32 + 1 + offset) as usize;
ip = new_ip;
continue;
}
Bytecode::JumpIf(offset) => {
let cond = stack.pop().unwrap_or(Value::Nil);
if matches!(cond, Value::Bool(true)) {
let new_ip = (ip as i32 + 1 + offset) as usize;
ip = new_ip;
continue;
}
}
Bytecode::JumpIfNot(offset) => {
let cond = stack.pop().unwrap_or(Value::Nil);
if !matches!(cond, Value::Bool(true)) {
let new_ip = (ip as i32 + 1 + offset) as usize;
ip = new_ip;
continue;
}
}
Bytecode::Return => break,
Bytecode::Halt => break,
Bytecode::SealedBegin => eprintln!("[sealed section begin]"),
Bytecode::SealedEnd => eprintln!("[sealed section end]"),
_ => {}
}
ip += 1;
}
}