feat: engram-lang — new programming language, quantum-sealed prod target, spreading activation types
This commit is contained in:
@@ -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 }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user