use std::collections::{HashMap, HashSet};
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::Command;
const CRATES_DATA_PATH: &str = "crates/logicaffeine_data";
const CRATES_SYSTEM_PATH: &str = "crates/logicaffeine_system";
use std::fmt::Write as FmtWrite;
use crate::analysis::{DiscoveryPass, EscapeChecker, OwnershipChecker, PolicyRegistry};
use crate::arena::Arena;
use crate::arena_ctx::AstContext;
use crate::ast::{Expr, MatchArm, Stmt, TypeExpr};
use crate::ast::stmt::{BinaryOpKind, ClosureBody, Literal, Pattern, ReadSource, SelectBranch, StringPart};
use crate::codegen::{codegen_program, generate_c_header, generate_python_bindings, generate_typescript_bindings};
use crate::diagnostic::{parse_rustc_json, translate_diagnostics, LogosError};
use crate::drs::WorldState;
use crate::error::ParseError;
use crate::intern::Interner;
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::sourcemap::SourceMap;
#[derive(Debug, Clone)]
pub struct CrateDependency {
pub name: String,
pub version: String,
pub features: Vec<String>,
}
#[derive(Debug)]
pub struct CompileOutput {
pub rust_code: String,
pub dependencies: Vec<CrateDependency>,
pub c_header: Option<String>,
pub python_bindings: Option<String>,
pub typescript_types: Option<String>,
pub typescript_bindings: Option<String>,
}
pub fn interpret_program(source: &str) -> Result<String, ParseError> {
let result = crate::ui_bridge::interpret_for_ui_sync(source);
if let Some(err) = result.error {
Err(ParseError {
kind: crate::error::ParseErrorKind::Custom(err),
span: crate::token::Span::default(),
})
} else {
Ok(result.lines.join("\n"))
}
}
pub fn compile_to_rust(source: &str) -> Result<String, ParseError> {
compile_program_full(source).map(|o| o.rust_code)
}
pub fn compile_to_c(source: &str) -> Result<String, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, _policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
Ok(crate::codegen_c::codegen_program_c(&stmts, &codegen_registry, &interner))
}
pub fn compile_program_full(source: &str) -> Result<CompileOutput, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
let mut dependencies = extract_dependencies(&stmts, &interner)?;
let needs_wasm_bindgen = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
interner.resolve(*target).eq_ignore_ascii_case("wasm")
} else {
false
}
});
if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
dependencies.push(CrateDependency {
name: "wasm-bindgen".to_string(),
version: "0.2".to_string(),
features: vec![],
});
}
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
let has_c = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
match export_target {
None => true,
Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
}
} else {
false
}
});
let c_header = if has_c {
Some(generate_c_header(&stmts, "module", &interner, &codegen_registry))
} else {
None
};
if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
dependencies.push(CrateDependency {
name: "serde_json".to_string(),
version: "1".to_string(),
features: vec![],
});
}
let python_bindings = if has_c {
Some(generate_python_bindings(&stmts, "module", &interner, &codegen_registry))
} else {
None
};
let (typescript_bindings, typescript_types) = if has_c {
let (js, dts) = generate_typescript_bindings(&stmts, "module", &interner, &codegen_registry);
(Some(js), Some(dts))
} else {
(None, None)
};
Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
}
fn extract_dependencies(stmts: &[Stmt], interner: &Interner) -> Result<Vec<CrateDependency>, ParseError> {
use std::collections::HashMap;
let mut seen: HashMap<String, String> = HashMap::new(); let mut deps: Vec<CrateDependency> = Vec::new();
for stmt in stmts {
if let Stmt::Require { crate_name, version, features, span } = stmt {
let name = interner.resolve(*crate_name).to_string();
let ver = interner.resolve(*version).to_string();
if let Some(existing_ver) = seen.get(&name) {
if *existing_ver != ver {
return Err(ParseError {
kind: crate::error::ParseErrorKind::Custom(format!(
"Conflicting versions for crate \"{}\": \"{}\" and \"{}\".",
name, existing_ver, ver
)),
span: *span,
});
}
} else {
seen.insert(name.clone(), ver.clone());
deps.push(CrateDependency {
name,
version: ver,
features: features.iter().map(|f| interner.resolve(*f).to_string()).collect(),
});
}
}
}
Ok(deps)
}
pub fn compile_to_rust_checked(source: &str) -> Result<String, ParseError> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let stmts = crate::optimize::optimize_program(stmts, &imperative_expr_arena, &stmt_arena, &mut interner);
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let mut ownership_checker = OwnershipChecker::new(&interner);
ownership_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
Ok(rust_code)
}
#[cfg(feature = "verification")]
pub fn compile_to_rust_verified(source: &str) -> Result<String, ParseError> {
use crate::verification::VerificationPass;
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let (type_registry, policy_registry) = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
(result.types, result.policies)
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let mut escape_checker = EscapeChecker::new(&interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let mut verifier = VerificationPass::new(&interner);
verifier.verify_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(format!(
"Verification Failed:\n\n{}",
e
)),
span: crate::token::Span::default(),
}
})?;
let type_env = crate::analysis::check_program(&stmts, &interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(&interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, &interner, &type_env);
Ok(rust_code)
}
pub fn compile_to_dir(source: &str, output_dir: &Path) -> Result<(), CompileError> {
let output = compile_program_full(source).map_err(CompileError::Parse)?;
let src_dir = output_dir.join("src");
fs::create_dir_all(&src_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let main_path = src_dir.join("main.rs");
let mut file = fs::File::create(&main_path).map_err(|e| CompileError::Io(e.to_string()))?;
file.write_all(output.rust_code.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
let mut cargo_toml = String::from(r#"[package]
name = "logos_output"
version = "0.1.0"
edition = "2021"
[dependencies]
logicaffeine-data = { path = "./crates/logicaffeine_data" }
logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
[target.'cfg(target_os = "linux")'.dependencies]
logicaffeine-system = { path = "./crates/logicaffeine_system", features = ["full", "io-uring"] }
"#);
for dep in &output.dependencies {
if dep.features.is_empty() {
let _ = writeln!(cargo_toml, "{} = \"{}\"", dep.name, dep.version);
} else {
let feats = dep.features.iter()
.map(|f| format!("\"{}\"", f))
.collect::<Vec<_>>()
.join(", ");
let _ = writeln!(
cargo_toml,
"{} = {{ version = \"{}\", features = [{}] }}",
dep.name, dep.version, feats
);
}
}
cargo_toml.push_str("\n[profile.release]\nlto = true\nopt-level = 3\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\n");
let cargo_path = output_dir.join("Cargo.toml");
let mut file = fs::File::create(&cargo_path).map_err(|e| CompileError::Io(e.to_string()))?;
file.write_all(cargo_toml.as_bytes()).map_err(|e| CompileError::Io(e.to_string()))?;
let cargo_config_dir = output_dir.join(".cargo");
fs::create_dir_all(&cargo_config_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let config_content = "[build]\nrustflags = [\"-C\", \"target-cpu=native\"]\n";
let config_path = cargo_config_dir.join("config.toml");
fs::write(&config_path, config_content).map_err(|e| CompileError::Io(e.to_string()))?;
copy_runtime_crates(output_dir)?;
Ok(())
}
pub fn copy_runtime_crates(output_dir: &Path) -> Result<(), CompileError> {
let crates_dir = output_dir.join("crates");
fs::create_dir_all(&crates_dir).map_err(|e| CompileError::Io(e.to_string()))?;
let workspace_root = find_workspace_root()?;
let data_src = workspace_root.join(CRATES_DATA_PATH);
let data_dest = crates_dir.join("logicaffeine_data");
copy_dir_recursive(&data_src, &data_dest)?;
deworkspace_cargo_toml(&data_dest.join("Cargo.toml"))?;
let system_src = workspace_root.join(CRATES_SYSTEM_PATH);
let system_dest = crates_dir.join("logicaffeine_system");
copy_dir_recursive(&system_src, &system_dest)?;
deworkspace_cargo_toml(&system_dest.join("Cargo.toml"))?;
let base_src = workspace_root.join("crates/logicaffeine_base");
let base_dest = crates_dir.join("logicaffeine_base");
copy_dir_recursive(&base_src, &base_dest)?;
deworkspace_cargo_toml(&base_dest.join("Cargo.toml"))?;
Ok(())
}
fn deworkspace_cargo_toml(cargo_toml_path: &Path) -> Result<(), CompileError> {
let content = fs::read_to_string(cargo_toml_path)
.map_err(|e| CompileError::Io(e.to_string()))?;
let mut result = String::with_capacity(content.len());
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "edition.workspace = true" {
result.push_str("edition = \"2021\"");
} else if trimmed == "rust-version.workspace = true" {
result.push_str("rust-version = \"1.75\"");
} else if trimmed == "authors.workspace = true"
|| trimmed == "repository.workspace = true"
|| trimmed == "homepage.workspace = true"
|| trimmed == "documentation.workspace = true"
|| trimmed == "keywords.workspace = true"
|| trimmed == "categories.workspace = true"
|| trimmed == "license.workspace = true"
{
continue;
} else if trimmed.contains(".workspace = true") {
continue;
} else {
result.push_str(line);
}
result.push('\n');
}
fs::write(cargo_toml_path, result)
.map_err(|e| CompileError::Io(e.to_string()))?;
Ok(())
}
fn find_workspace_root() -> Result<std::path::PathBuf, CompileError> {
if let Ok(workspace) = std::env::var("LOGOS_WORKSPACE") {
let path = Path::new(&workspace);
if path.join("Cargo.toml").exists() && path.join("crates").exists() {
return Ok(path.to_path_buf());
}
}
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
let path = Path::new(&manifest_dir);
if let Some(parent) = path.parent().and_then(|p| p.parent()) {
if parent.join("Cargo.toml").exists() {
return Ok(parent.to_path_buf());
}
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let mut candidate = dir.to_path_buf();
for _ in 0..5 {
if candidate.join("Cargo.toml").exists() && candidate.join("crates").exists() {
return Ok(candidate);
}
if !candidate.pop() {
break;
}
}
}
}
let mut current = std::env::current_dir()
.map_err(|e| CompileError::Io(e.to_string()))?;
loop {
if current.join("Cargo.toml").exists() && current.join("crates").exists() {
return Ok(current);
}
if !current.pop() {
return Err(CompileError::Io(
"Could not find workspace root. Set LOGOS_WORKSPACE env var or run from within the workspace.".to_string()
));
}
}
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), CompileError> {
fs::create_dir_all(dst).map_err(|e| CompileError::Io(e.to_string()))?;
for entry in fs::read_dir(src).map_err(|e| CompileError::Io(e.to_string()))? {
let entry = entry.map_err(|e| CompileError::Io(e.to_string()))?;
let src_path = entry.path();
let file_name = entry.file_name();
let dst_path = dst.join(&file_name);
if file_name == "target"
|| file_name == ".git"
|| file_name == "Cargo.lock"
|| file_name == ".DS_Store"
{
continue;
}
if file_name.to_string_lossy().starts_with('.') {
continue;
}
if !src_path.exists() {
continue;
}
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else if file_name == "Cargo.toml" {
match fs::read_to_string(&src_path) {
Ok(content) => {
let filtered: String = content
.lines()
.filter(|line| !line.trim().starts_with("[workspace]"))
.collect::<Vec<_>>()
.join("\n");
fs::write(&dst_path, filtered)
.map_err(|e| CompileError::Io(e.to_string()))?;
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(CompileError::Io(e.to_string())),
}
} else {
match fs::copy(&src_path, &dst_path) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(CompileError::Io(e.to_string())),
}
}
}
Ok(())
}
pub fn compile_and_run(source: &str, output_dir: &Path) -> Result<String, CompileError> {
compile_to_rust_checked(source).map_err(CompileError::Parse)?;
compile_to_dir(source, output_dir)?;
let build_output = Command::new("cargo")
.arg("build")
.arg("--message-format=json")
.current_dir(output_dir)
.output()
.map_err(|e| CompileError::Io(e.to_string()))?;
if !build_output.status.success() {
let stderr = String::from_utf8_lossy(&build_output.stderr);
let stdout = String::from_utf8_lossy(&build_output.stdout);
let diagnostics = parse_rustc_json(&stdout);
if !diagnostics.is_empty() {
let source_map = SourceMap::new(source.to_string());
let interner = Interner::new();
if let Some(logos_error) = translate_diagnostics(&diagnostics, &source_map, &interner) {
return Err(CompileError::Ownership(logos_error));
}
}
return Err(CompileError::Build(stderr.to_string()));
}
let run_output = Command::new("cargo")
.arg("run")
.arg("--quiet")
.current_dir(output_dir)
.output()
.map_err(|e| CompileError::Io(e.to_string()))?;
if !run_output.status.success() {
let stderr = String::from_utf8_lossy(&run_output.stderr);
return Err(CompileError::Runtime(stderr.to_string()));
}
let stdout = String::from_utf8_lossy(&run_output.stdout);
Ok(stdout.to_string())
}
pub fn compile_file(path: &Path) -> Result<String, CompileError> {
let source = fs::read_to_string(path).map_err(|e| CompileError::Io(e.to_string()))?;
compile_to_rust(&source).map_err(CompileError::Parse)
}
pub fn compile_project(entry_file: &Path) -> Result<CompileOutput, CompileError> {
use crate::loader::Loader;
use crate::analysis::discover_with_imports;
let root_path = entry_file.parent().unwrap_or(Path::new(".")).to_path_buf();
let mut loader = Loader::new(root_path);
let mut interner = Interner::new();
let source = fs::read_to_string(entry_file)
.map_err(|e| CompileError::Io(format!("Failed to read entry file: {}", e)))?;
let type_registry = discover_with_imports(entry_file, &source, &mut loader, &mut interner)
.map_err(|e| CompileError::Io(e))?;
compile_to_rust_with_registry_full(&source, type_registry, &mut interner)
.map_err(CompileError::Parse)
}
fn compile_to_rust_with_registry_full(
source: &str,
type_registry: crate::analysis::TypeRegistry,
interner: &mut Interner,
) -> Result<CompileOutput, ParseError> {
let mut lexer = Lexer::new(source, interner);
let tokens = lexer.tokenize();
let policy_registry = {
let mut discovery = DiscoveryPass::new(&tokens, interner);
discovery.run_full().policies
};
let codegen_registry = type_registry.clone();
let codegen_policies = policy_registry.clone();
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena,
&term_arena,
&np_arena,
&sym_arena,
&role_arena,
&pp_arena,
&stmt_arena,
&imperative_expr_arena,
&type_expr_arena,
);
let mut parser = Parser::new(tokens, &mut world_state, interner, ast_ctx, type_registry);
let stmts = parser.parse_program()?;
let mut dependencies = extract_dependencies(&stmts, interner)?;
let needs_wasm_bindgen = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
interner.resolve(*target).eq_ignore_ascii_case("wasm")
} else {
false
}
});
if needs_wasm_bindgen && !dependencies.iter().any(|d| d.name == "wasm-bindgen") {
dependencies.push(CrateDependency {
name: "wasm-bindgen".to_string(),
version: "0.2".to_string(),
features: vec![],
});
}
let mut escape_checker = EscapeChecker::new(interner);
escape_checker.check_program(&stmts).map_err(|e| {
ParseError {
kind: crate::error::ParseErrorKind::Custom(e.to_string()),
span: e.span,
}
})?;
let type_env = crate::analysis::check_program(&stmts, interner, &codegen_registry)
.map_err(|e| ParseError {
kind: e.to_parse_error_kind(interner),
span: crate::token::Span::default(),
})?;
let rust_code = codegen_program(&stmts, &codegen_registry, &codegen_policies, interner, &type_env);
let has_c = stmts.iter().any(|stmt| {
if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
match export_target {
None => true,
Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
}
} else {
false
}
});
let c_header = if has_c {
Some(generate_c_header(&stmts, "module", interner, &codegen_registry))
} else {
None
};
if has_c && !dependencies.iter().any(|d| d.name == "serde_json") {
dependencies.push(CrateDependency {
name: "serde_json".to_string(),
version: "1".to_string(),
features: vec![],
});
}
let python_bindings = if has_c {
Some(generate_python_bindings(&stmts, "module", interner, &codegen_registry))
} else {
None
};
let (typescript_bindings, typescript_types) = if has_c {
let (js, dts) = generate_typescript_bindings(&stmts, "module", interner, &codegen_registry);
(Some(js), Some(dts))
} else {
(None, None)
};
Ok(CompileOutput { rust_code, dependencies, c_header, python_bindings, typescript_types, typescript_bindings })
}
#[derive(Debug)]
pub enum CompileError {
Parse(ParseError),
Io(String),
Build(String),
Runtime(String),
Ownership(LogosError),
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompileError::Parse(e) => write!(f, "Parse error: {:?}", e),
CompileError::Io(e) => write!(f, "IO error: {}", e),
CompileError::Build(e) => write!(f, "Build error: {}", e),
CompileError::Runtime(e) => write!(f, "Runtime error: {}", e),
CompileError::Ownership(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for CompileError {}
pub fn encode_program_source(source: &str) -> Result<String, ParseError> {
let full_source = if source.contains("## Main") || source.contains("## To ") {
source.to_string()
} else {
format!("## Main\n{}", source)
};
let mut interner = Interner::new();
let mut lexer = Lexer::new(&full_source, &mut interner);
let tokens = lexer.tokenize();
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
result.types
};
let mut variant_constructors: HashMap<String, Vec<String>> = HashMap::new();
for (_type_name, type_def) in type_registry.iter_types() {
if let crate::analysis::TypeDef::Enum { variants, .. } = type_def {
for variant in variants {
let vname = interner.resolve(variant.name).to_string();
let field_names: Vec<String> = variant.fields.iter()
.map(|f| interner.resolve(f.name).to_string())
.collect();
variant_constructors.insert(vname, field_names);
}
}
}
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = crate::parser::Parser::new(
tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
);
let stmts = parser.parse_program()?;
let mut functions: Vec<(String, Vec<String>, Vec<String>, String, Vec<&Stmt>)> = Vec::new();
let mut main_stmts: Vec<&Stmt> = Vec::new();
for stmt in &stmts {
if let Stmt::FunctionDef { name, params, body, return_type, is_native, .. } = stmt {
if *is_native {
continue; }
let fn_name = interner.resolve(*name).to_string();
let param_names: Vec<String> = params
.iter()
.map(|(name, _)| interner.resolve(*name).to_string())
.collect();
let param_types: Vec<String> = params
.iter()
.map(|(_, ty)| decompile_type_expr(ty, &interner))
.collect();
let ret_type = return_type
.map(|rt| decompile_type_expr(rt, &interner))
.unwrap_or_else(|| "Nothing".to_string());
let body_stmts: Vec<&Stmt> = body.iter().collect();
functions.push((fn_name, param_names, param_types, ret_type, body_stmts));
} else {
main_stmts.push(stmt);
}
}
let mut counter = 0usize;
let mut output = String::new();
output.push_str("Let encodedFuncMap be a new Map of Text to CFunc.\n");
for (fn_name, params, param_types, ret_type, body) in &functions {
let body_var = encode_stmt_list_src(body, &mut counter, &mut output, &interner, &variant_constructors);
let params_var = format!("params_{}", counter);
counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
for p in params {
output.push_str(&format!("Push \"{}\" to {}.\n", p, params_var));
}
let param_types_var = format!("paramTypes_{}", counter);
counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", param_types_var));
for pt in param_types {
output.push_str(&format!("Push \"{}\" to {}.\n", pt, param_types_var));
}
let func_var = format!("func_{}", counter);
counter += 1;
output.push_str(&format!(
"Let {} be a new CFuncDef with name \"{}\" and params {} and paramTypes {} and returnType \"{}\" and body {}.\n",
func_var, fn_name, params_var, param_types_var, ret_type, body_var
));
output.push_str(&format!(
"Set item \"{}\" of encodedFuncMap to {}.\n",
fn_name, func_var
));
}
let main_var = encode_stmt_list_src(&main_stmts, &mut counter, &mut output, &interner, &variant_constructors);
output.push_str(&format!("Let encodedMain be {}.\n", main_var));
Ok(output)
}
pub fn encode_program_source_compact(source: &str) -> Result<String, ParseError> {
let full_source = if source.contains("## Main") || source.contains("## To ") {
source.to_string()
} else {
format!("## Main\n{}", source)
};
let mut interner = Interner::new();
let mut lexer = Lexer::new(&full_source, &mut interner);
let tokens = lexer.tokenize();
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
result.types
};
let mut variant_constructors: HashMap<String, Vec<String>> = HashMap::new();
for (_type_name, type_def) in type_registry.iter_types() {
if let crate::analysis::TypeDef::Enum { variants, .. } = type_def {
for variant in variants {
let vname = interner.resolve(variant.name).to_string();
let field_names: Vec<String> = variant.fields.iter()
.map(|f| interner.resolve(f.name).to_string())
.collect();
variant_constructors.insert(vname, field_names);
}
}
}
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = crate::parser::Parser::new(
tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
);
let stmts = parser.parse_program()?;
let mut functions: Vec<(String, Vec<String>, Vec<String>, String, Vec<&Stmt>)> = Vec::new();
let mut main_stmts: Vec<&Stmt> = Vec::new();
for stmt in &stmts {
if let Stmt::FunctionDef { name, params, body, return_type, is_native, .. } = stmt {
if *is_native { continue; }
let fn_name = interner.resolve(*name).to_string();
let param_names: Vec<String> = params
.iter()
.map(|(name, _)| interner.resolve(*name).to_string())
.collect();
let param_types: Vec<String> = params
.iter()
.map(|(_, ty)| decompile_type_expr(ty, &interner))
.collect();
let ret_type = return_type
.map(|rt| decompile_type_expr(rt, &interner))
.unwrap_or_else(|| "Nothing".to_string());
let body_stmts: Vec<&Stmt> = body.iter().collect();
functions.push((fn_name, param_names, param_types, ret_type, body_stmts));
} else {
main_stmts.push(stmt);
}
}
let mut counter = 0usize;
let mut output = String::new();
output.push_str("Let encodedFuncMap be a new Map of Text to CFunc.\n");
for (fn_name, params, param_types, ret_type, body) in &functions {
let body_var = encode_stmt_list_compact(body, &mut counter, &mut output, &interner, &variant_constructors);
let params_var = format!("params_{}", counter);
counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
for p in params {
output.push_str(&format!("Push \"{}\" to {}.\n", p, params_var));
}
let param_types_var = format!("paramTypes_{}", counter);
counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", param_types_var));
for pt in param_types {
output.push_str(&format!("Push \"{}\" to {}.\n", pt, param_types_var));
}
let func_var = format!("func_{}", counter);
counter += 1;
output.push_str(&format!(
"Let {} be a new CFuncDef with name \"{}\" and params {} and paramTypes {} and returnType \"{}\" and body {}.\n",
func_var, fn_name, params_var, param_types_var, ret_type, body_var
));
output.push_str(&format!(
"Set item \"{}\" of encodedFuncMap to {}.\n",
fn_name, func_var
));
}
let main_var = encode_stmt_list_compact(&main_stmts, &mut counter, &mut output, &interner, &variant_constructors);
output.push_str(&format!("Let encodedMain be {}.\n", main_var));
Ok(output)
}
fn try_inline_expr(expr: &Expr, interner: &Interner) -> Option<String> {
match expr {
Expr::Literal(lit) => match lit {
Literal::Number(n) => Some(format!("(a new CInt with value {})", n)),
Literal::Boolean(b) => Some(format!("(a new CBool with value {})", b)),
Literal::Text(s) => {
let text = interner.resolve(*s);
Some(format!("(a new CText with value \"{}\")", text))
}
Literal::Float(f) => {
let fs = format!("{}", f);
let fs = if fs.contains('.') { fs } else { format!("{}.0", fs) };
Some(format!("(a new CFloat with value {})", fs))
}
Literal::Nothing => Some("(a new CText with value \"nothing\")".to_string()),
_ => None,
},
Expr::Identifier(sym) => {
let name = interner.resolve(*sym);
Some(format!("(a new CVar with name \"{}\")", name))
}
Expr::Not { operand } => {
if let Some(inner) = try_inline_expr(operand, interner) {
Some(format!("(a new CNot with inner {})", inner))
} else {
None
}
}
Expr::OptionNone => Some("(a new COptionNone)".to_string()),
_ => None,
}
}
fn encode_expr_compact(expr: &Expr, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
if let Some(inline) = try_inline_expr(expr, interner) {
return inline;
}
let var = format!("e_{}", *counter);
*counter += 1;
match expr {
Expr::BinaryOp { op, left, right } => {
let left_var = encode_expr_compact(left, counter, output, interner, variants);
let right_var = encode_expr_compact(right, counter, output, interner, variants);
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Subtract => "-",
BinaryOpKind::Multiply => "*",
BinaryOpKind::Divide => "/",
BinaryOpKind::Modulo => "%",
BinaryOpKind::Eq => "==",
BinaryOpKind::NotEq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::LtEq => "<=",
BinaryOpKind::GtEq => ">=",
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
BinaryOpKind::Concat => "+",
BinaryOpKind::BitXor => "^",
BinaryOpKind::Shl => "<<",
BinaryOpKind::Shr => ">>",
};
output.push_str(&format!(
"Let {} be a new CBinOp with op \"{}\" and left {} and right {}.\n",
var, op_str, left_var, right_var
));
}
Expr::Call { function, args } => {
let fn_name = interner.resolve(*function);
if let Some(field_names) = variants.get(fn_name) {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (i, arg) in args.iter().enumerate() {
let fname = field_names.get(i).map(|s| s.as_str()).unwrap_or("value");
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let arg_var = encode_expr_compact(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", arg_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, fn_name, names_var, vals_var
));
} else {
let args_var = format!("callArgs_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for arg in args {
let arg_var = encode_expr_compact(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
}
output.push_str(&format!(
"Let {} be a new CCall with name \"{}\" and args {}.\n",
var, fn_name, args_var
));
}
}
Expr::Index { collection, index } => {
let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
let idx_var = encode_expr_compact(index, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CIndex with coll {} and idx {}.\n",
var, coll_var, idx_var
));
}
Expr::Length { collection } => {
let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CLen with target {}.\n", var, coll_var));
}
Expr::FieldAccess { object, field } => {
let obj_var = encode_expr_compact(object, counter, output, interner, variants);
let field_name = interner.resolve(*field);
output.push_str(&format!(
"Let {} be a new CMapGet with target {} and key (a new CText with value \"{}\").\n",
var, obj_var, field_name
));
}
Expr::NewVariant { variant, fields, .. } => {
let variant_name = interner.resolve(*variant);
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (field_name, field_expr) in fields {
let fname = interner.resolve(*field_name);
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let field_var = encode_expr_compact(field_expr, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, variant_name, names_var, vals_var
));
}
Expr::New { type_name, init_fields, .. } => {
let tn = interner.resolve(*type_name);
if tn == "Seq" || tn == "List" {
output.push_str(&format!("Let {} be a new CNewSeq.\n", var));
} else if tn == "Set" {
output.push_str(&format!("Let {} be a new CNewSet.\n", var));
} else {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (field_name, field_expr) in init_fields {
let fname = interner.resolve(*field_name);
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let field_var = encode_expr_compact(field_expr, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, tn, names_var, vals_var
));
}
}
Expr::InterpolatedString(parts) => {
if parts.is_empty() {
output.push_str(&format!("Let {} be (a new CText with value \"\").\n", var));
} else {
let mut part_vars: Vec<String> = Vec::new();
for part in parts {
match part {
StringPart::Literal(sym) => {
let text = interner.resolve(*sym);
part_vars.push(format!("(a new CText with value \"{}\")", text));
}
StringPart::Expr { value, .. } => {
let pv = encode_expr_compact(value, counter, output, interner, variants);
part_vars.push(pv);
}
}
}
if part_vars.len() == 1 {
output.push_str(&format!("Let {} be {}.\n", var, part_vars[0]));
} else {
let mut acc = part_vars[0].clone();
for pv in &part_vars[1..] {
let concat_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CBinOp with op \"+\" and left {} and right {}.\n",
concat_var, acc, pv
));
acc = concat_var;
}
output.push_str(&format!("Let {} be {}.\n", var, acc));
}
}
}
Expr::Range { start, end } => {
let start_var = encode_expr_compact(start, counter, output, interner, variants);
let end_var = encode_expr_compact(end, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CRange with start {} and end {}.\n",
var, start_var, end_var
));
}
Expr::Copy { expr } => {
let inner_var = encode_expr_compact(expr, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CCopy with target {}.\n", var, inner_var));
}
Expr::Contains { collection, value } => {
let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
let val_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CContains with coll {} and elem {}.\n",
var, coll_var, val_var
));
}
Expr::OptionSome { value } => {
let inner_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new COptionSome with inner {}.\n", var, inner_var
));
}
Expr::Tuple(elems) => {
let items_var = format!("tupItems_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
for elem in elems {
let elem_var = encode_expr_compact(elem, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
}
output.push_str(&format!(
"Let {} be a new CTuple with items {}.\n", var, items_var
));
}
Expr::Closure { params, body, .. } => {
let params_var = format!("clp_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
let mut param_names = HashSet::new();
for (sym, _) in params {
let name = interner.resolve(*sym);
param_names.insert(name.to_string());
output.push_str(&format!("Push \"{}\" to {}.\n", name, params_var));
}
let body_var = format!("clb_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
match body {
ClosureBody::Expression(e) => {
let ret_expr = encode_expr_compact(e, counter, output, interner, variants);
let ret_var = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", ret_var, ret_expr));
output.push_str(&format!("Push {} to {}.\n", ret_var, body_var));
}
ClosureBody::Block(stmts) => {
for s in stmts.iter() {
let sv = encode_stmt_compact(s, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", sv, body_var));
}
}
}
let bound: HashSet<String> = param_names;
let free = collect_free_vars_expr(expr, interner, &bound);
let cap_var = format!("clc_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", cap_var));
for fv in &free {
output.push_str(&format!("Push \"{}\" to {}.\n", fv, cap_var));
}
output.push_str(&format!(
"Let {} be a new CClosure with params {} and body {} and captured {}.\n",
var, params_var, body_var, cap_var
));
}
Expr::CallExpr { callee, args } => {
let callee_var = encode_expr_compact(callee, counter, output, interner, variants);
let args_var = format!("cea_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for a in args {
let av = encode_expr_compact(a, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", av, args_var));
}
output.push_str(&format!(
"Let {} be a new CCallExpr with target {} and args {}.\n",
var, callee_var, args_var
));
}
Expr::Slice { collection, start, end } => {
let coll_var = encode_expr_compact(collection, counter, output, interner, variants);
let start_var = encode_expr_compact(start, counter, output, interner, variants);
let end_var = encode_expr_compact(end, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSlice with coll {} and startIdx {} and endIdx {}.\n",
var, coll_var, start_var, end_var
));
}
Expr::Union { left, right } => {
let left_var = encode_expr_compact(left, counter, output, interner, variants);
let right_var = encode_expr_compact(right, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CUnion with left {} and right {}.\n",
var, left_var, right_var
));
}
Expr::Intersection { left, right } => {
let left_var = encode_expr_compact(left, counter, output, interner, variants);
let right_var = encode_expr_compact(right, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CIntersection with left {} and right {}.\n",
var, left_var, right_var
));
}
Expr::Give { value } => {
let inner_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!("Let {} be {}.\n", var, inner_var));
}
Expr::Escape { code, .. } => {
let code_str = interner.resolve(*code);
output.push_str(&format!(
"Let {} be a new CEscExpr with code \"{}\".\n",
var, code_str.replace('\"', "\\\"")
));
}
_ => {
output.push_str(&format!("Let {} be (a new CText with value \"unsupported\").\n", var));
}
}
var
}
fn encode_stmt_compact(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
let var = format!("s_{}", *counter);
*counter += 1;
match stmt {
Stmt::Let { var: name, value, .. } => {
let name_str = interner.resolve(*name);
let expr_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CLet with name \"{}\" and expr {}.\n",
var, name_str, expr_var
));
}
Stmt::Set { target, value } => {
let name_str = interner.resolve(*target);
let expr_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSet with name \"{}\" and expr {}.\n",
var, name_str, expr_var
));
}
Stmt::If { cond, then_block, else_block } => {
let cond_var = encode_expr_compact(cond, counter, output, interner, variants);
let then_stmts: Vec<&Stmt> = then_block.iter().collect();
let then_var = encode_stmt_list_compact(&then_stmts, counter, output, interner, variants);
let else_var = if let Some(els) = else_block {
let else_stmts: Vec<&Stmt> = els.iter().collect();
encode_stmt_list_compact(&else_stmts, counter, output, interner, variants)
} else {
let empty_var = format!("emptyBlock_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_var));
empty_var
};
output.push_str(&format!(
"Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
var, cond_var, then_var, else_var
));
}
Stmt::While { cond, body, .. } => {
let cond_var = encode_expr_compact(cond, counter, output, interner, variants);
let body_stmts: Vec<&Stmt> = body.iter().collect();
let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CWhile with cond {} and body {}.\n",
var, cond_var, body_var
));
}
Stmt::Return { value } => {
if let Some(val) = value {
let expr_var = encode_expr_compact(val, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, expr_var));
} else {
output.push_str(&format!("Let {} be a new CReturn with expr (a new CText with value \"nothing\").\n", var));
}
}
Stmt::Show { object, .. } => {
let expr_var = encode_expr_compact(object, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CShow with expr {}.\n", var, expr_var));
}
Stmt::Repeat { pattern, iterable, body } => {
let var_str = match pattern {
Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
Pattern::Tuple(syms) => {
if let Some(s) = syms.first() {
interner.resolve(*s).to_string()
} else {
"item".to_string()
}
}
};
let coll_var = encode_expr_compact(iterable, counter, output, interner, variants);
let body_stmts: Vec<&Stmt> = body.iter().collect();
let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CRepeat with var \"{}\" and coll {} and body {}.\n",
var, var_str, coll_var, body_var
));
}
Stmt::Push { value, collection } => {
let coll_name = extract_ident_name(collection, interner);
let expr_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CPush with expr {} and target \"{}\".\n",
var, expr_var, coll_name
));
}
Stmt::SetIndex { collection, index, value } => {
let target_str = extract_ident_name(collection, interner);
let idx_var = encode_expr_compact(index, counter, output, interner, variants);
let val_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSetIdx with target \"{}\" and idx {} and val {}.\n",
var, target_str, idx_var, val_var
));
}
Stmt::SetField { object, field, value } => {
let target_str = extract_ident_name(object, interner);
let field_str = interner.resolve(*field);
let val_var = encode_expr_compact(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSetField with target \"{}\" and field \"{}\" and val {}.\n",
var, target_str, field_str, val_var
));
}
Stmt::Break => {
output.push_str(&format!("Let {} be a new CBreak.\n", var));
}
Stmt::Inspect { target, arms, .. } => {
let target_var = encode_expr_compact(target, counter, output, interner, variants);
let arms_var = format!("arms_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CMatchArm.\n", arms_var));
for arm in arms {
if let Some(variant_sym) = arm.variant {
let vname = interner.resolve(variant_sym);
let bindings_var = format!("bindings_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", bindings_var));
for (_, binding_name) in &arm.bindings {
let bn = interner.resolve(*binding_name);
output.push_str(&format!("Push \"{}\" to {}.\n", bn, bindings_var));
}
let body_stmts: Vec<&Stmt> = arm.body.iter().collect();
let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
let arm_var = format!("arm_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CWhen with variantName \"{}\" and bindings {} and body {}.\n",
arm_var, vname, bindings_var, body_var
));
output.push_str(&format!("Push {} to {}.\n", arm_var, arms_var));
} else {
let body_stmts: Vec<&Stmt> = arm.body.iter().collect();
let body_var = encode_stmt_list_compact(&body_stmts, counter, output, interner, variants);
let arm_var = format!("arm_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new COtherwise with body {}.\n",
arm_var, body_var
));
output.push_str(&format!("Push {} to {}.\n", arm_var, arms_var));
}
}
output.push_str(&format!(
"Let {} be a new CInspect with target {} and arms {}.\n",
var, target_var, arms_var
));
}
_ => {
return encode_stmt_src(stmt, counter, output, interner, variants);
}
}
var
}
fn encode_stmt_list_compact(stmts: &[&Stmt], counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
let list_var = format!("stmts_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", list_var));
for s in stmts {
let sv = encode_stmt_compact(s, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", sv, list_var));
}
list_var
}
fn collect_free_vars_expr<'a>(expr: &'a Expr, interner: &Interner, bound: &HashSet<String>) -> HashSet<String> {
let mut free = HashSet::new();
match expr {
Expr::Identifier(sym) => {
let name = interner.resolve(*sym).to_string();
if !bound.contains(&name) {
free.insert(name);
}
}
Expr::BinaryOp { left, right, .. } => {
free.extend(collect_free_vars_expr(left, interner, bound));
free.extend(collect_free_vars_expr(right, interner, bound));
}
Expr::Not { operand } => {
free.extend(collect_free_vars_expr(operand, interner, bound));
}
Expr::Copy { expr: inner } => {
free.extend(collect_free_vars_expr(inner, interner, bound));
}
Expr::CallExpr { callee, args } => {
free.extend(collect_free_vars_expr(callee, interner, bound));
for a in args {
free.extend(collect_free_vars_expr(a, interner, bound));
}
}
Expr::Index { collection, index } => {
free.extend(collect_free_vars_expr(collection, interner, bound));
free.extend(collect_free_vars_expr(index, interner, bound));
}
Expr::InterpolatedString(parts) => {
for part in parts {
if let StringPart::Expr { value, .. } = part {
free.extend(collect_free_vars_expr(value, interner, bound));
}
}
}
Expr::Closure { params, body, .. } => {
let mut inner_bound = bound.clone();
for (sym, _) in params {
inner_bound.insert(interner.resolve(*sym).to_string());
}
match body {
ClosureBody::Expression(e) => {
free.extend(collect_free_vars_expr(e, interner, &inner_bound));
}
ClosureBody::Block(stmts) => {
for s in stmts.iter() {
free.extend(collect_free_vars_stmt(s, interner, &inner_bound));
}
}
}
}
_ => {}
}
free
}
fn collect_free_vars_stmt<'a>(stmt: &'a Stmt, interner: &Interner, bound: &HashSet<String>) -> HashSet<String> {
let mut free = HashSet::new();
match stmt {
Stmt::Let { var, value, .. } => {
free.extend(collect_free_vars_expr(value, interner, bound));
}
Stmt::Set { target, value, .. } => {
let n = interner.resolve(*target).to_string();
if !bound.contains(&n) {
free.insert(n);
}
free.extend(collect_free_vars_expr(value, interner, bound));
}
Stmt::Show { object, .. } => {
free.extend(collect_free_vars_expr(object, interner, bound));
}
Stmt::Return { value } => {
if let Some(v) = value {
free.extend(collect_free_vars_expr(v, interner, bound));
}
}
_ => {}
}
free
}
fn encode_expr_src(expr: &Expr, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
let var = format!("e_{}", *counter);
*counter += 1;
match expr {
Expr::Literal(lit) => match lit {
Literal::Number(n) => {
output.push_str(&format!("Let {} be a new CInt with value {}.\n", var, n));
}
Literal::Boolean(b) => {
output.push_str(&format!("Let {} be a new CBool with value {}.\n", var, b));
}
Literal::Text(s) => {
let text = interner.resolve(*s);
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", var, text));
}
Literal::Float(f) => {
output.push_str(&format!("Let {} be a new CFloat with value {}.\n", var, f));
}
Literal::Duration(nanos) => {
let millis = nanos / 1_000_000;
let amount_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CInt with value {}.\n", amount_var, millis));
output.push_str(&format!("Let {} be a new CDuration with amount {} and unit \"milliseconds\".\n", var, amount_var));
}
Literal::Nothing => {
output.push_str(&format!("Let {} be a new CText with value \"nothing\".\n", var));
}
_ => {
output.push_str(&format!("Let {} be a new CText with value \"unsupported\".\n", var));
}
},
Expr::Identifier(sym) => {
let name = interner.resolve(*sym);
output.push_str(&format!("Let {} be a new CVar with name \"{}\".\n", var, name));
}
Expr::BinaryOp { op, left, right } => {
let left_var = encode_expr_src(left, counter, output, interner, variants);
let right_var = encode_expr_src(right, counter, output, interner, variants);
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Subtract => "-",
BinaryOpKind::Multiply => "*",
BinaryOpKind::Divide => "/",
BinaryOpKind::Modulo => "%",
BinaryOpKind::Eq => "==",
BinaryOpKind::NotEq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::LtEq => "<=",
BinaryOpKind::GtEq => ">=",
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
BinaryOpKind::Concat => "+",
BinaryOpKind::BitXor => "^",
BinaryOpKind::Shl => "<<",
BinaryOpKind::Shr => ">>",
};
output.push_str(&format!(
"Let {} be a new CBinOp with op \"{}\" and left {} and right {}.\n",
var, op_str, left_var, right_var
));
}
Expr::Not { operand } => {
let inner_var = encode_expr_src(operand, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CNot with inner {}.\n", var, inner_var));
}
Expr::Call { function, args } => {
let fn_name = interner.resolve(*function);
if let Some(field_names) = variants.get(fn_name) {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (i, arg) in args.iter().enumerate() {
let fname = field_names.get(i).map(|s| s.as_str()).unwrap_or("value");
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let arg_var = encode_expr_src(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", arg_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, fn_name, names_var, vals_var
));
} else {
let args_var = format!("callArgs_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for arg in args {
let arg_var = encode_expr_src(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
}
output.push_str(&format!(
"Let {} be a new CCall with name \"{}\" and args {}.\n",
var, fn_name, args_var
));
}
}
Expr::Index { collection, index } => {
let coll_var = encode_expr_src(collection, counter, output, interner, variants);
let idx_var = encode_expr_src(index, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CIndex with coll {} and idx {}.\n",
var, coll_var, idx_var
));
}
Expr::Length { collection } => {
let coll_var = encode_expr_src(collection, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CLen with target {}.\n", var, coll_var));
}
Expr::FieldAccess { object, field } => {
let obj_var = encode_expr_src(object, counter, output, interner, variants);
let field_name = interner.resolve(*field);
let key_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", key_var, field_name));
output.push_str(&format!(
"Let {} be a new CMapGet with target {} and key {}.\n",
var, obj_var, key_var
));
}
Expr::NewVariant { variant, fields, .. } => {
let variant_name = interner.resolve(*variant);
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (field_name, field_expr) in fields {
let fname = interner.resolve(*field_name);
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let field_var = encode_expr_src(field_expr, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, variant_name, names_var, vals_var
));
}
Expr::New { type_name, init_fields, .. } => {
let tn = interner.resolve(*type_name);
if tn == "Seq" || tn == "List" {
output.push_str(&format!("Let {} be a new CNewSeq.\n", var));
} else if tn == "Set" {
output.push_str(&format!("Let {} be a new CNewSet.\n", var));
} else if tn == "Map" || tn.starts_with("Map ") {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
output.push_str(&format!(
"Let {} be a new CNew with typeName \"Map\" and fieldNames {} and fields {}.\n",
var, names_var, vals_var
));
} else if init_fields.is_empty() {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, tn, names_var, vals_var
));
} else {
let names_var = format!("nvNames_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", names_var));
let vals_var = format!("nvVals_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", vals_var));
for (field_name, field_expr) in init_fields {
let fname = interner.resolve(*field_name);
output.push_str(&format!("Push \"{}\" to {}.\n", fname, names_var));
let field_var = encode_expr_src(field_expr, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", field_var, vals_var));
}
output.push_str(&format!(
"Let {} be a new CNewVariant with tag \"{}\" and fnames {} and fvals {}.\n",
var, tn, names_var, vals_var
));
}
}
Expr::InterpolatedString(parts) => {
if parts.is_empty() {
output.push_str(&format!("Let {} be a new CText with value \"\".\n", var));
} else {
let mut part_vars: Vec<String> = Vec::new();
for part in parts {
match part {
StringPart::Literal(sym) => {
let text = interner.resolve(*sym);
let pv = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", pv, text));
part_vars.push(pv);
}
StringPart::Expr { value, .. } => {
let pv = encode_expr_src(value, counter, output, interner, variants);
part_vars.push(pv);
}
}
}
if part_vars.len() == 1 {
output.push_str(&format!("Let {} be {}.\n", var, part_vars[0]));
} else {
let mut acc = part_vars[0].clone();
for pv in &part_vars[1..] {
let concat_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CBinOp with op \"+\" and left {} and right {}.\n",
concat_var, acc, pv
));
acc = concat_var;
}
output.push_str(&format!("Let {} be {}.\n", var, acc));
}
}
}
Expr::Range { start, end } => {
let start_var = encode_expr_src(start, counter, output, interner, variants);
let end_var = encode_expr_src(end, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CRange with start {} and end {}.\n",
var, start_var, end_var
));
}
Expr::Slice { collection, start, end } => {
let coll_var = encode_expr_src(collection, counter, output, interner, variants);
let start_var = encode_expr_src(start, counter, output, interner, variants);
let end_var = encode_expr_src(end, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSlice with coll {} and startIdx {} and endIdx {}.\n",
var, coll_var, start_var, end_var
));
}
Expr::Copy { expr } => {
let inner_var = encode_expr_src(expr, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CCopy with target {}.\n", var, inner_var));
}
Expr::Contains { collection, value } => {
let coll_var = encode_expr_src(collection, counter, output, interner, variants);
let val_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CContains with coll {} and elem {}.\n",
var, coll_var, val_var
));
}
Expr::Union { left, right } => {
let left_var = encode_expr_src(left, counter, output, interner, variants);
let right_var = encode_expr_src(right, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CUnion with left {} and right {}.\n",
var, left_var, right_var
));
}
Expr::Intersection { left, right } => {
let left_var = encode_expr_src(left, counter, output, interner, variants);
let right_var = encode_expr_src(right, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CIntersection with left {} and right {}.\n",
var, left_var, right_var
));
}
Expr::OptionSome { value } => {
let inner_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new COptionSome with inner {}.\n",
var, inner_var
));
}
Expr::OptionNone => {
output.push_str(&format!("Let {} be a new COptionNone.\n", var));
}
Expr::Tuple(elems) => {
let items_var = format!("tupItems_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
for elem in elems {
let elem_var = encode_expr_src(elem, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
}
output.push_str(&format!(
"Let {} be a new CTuple with items {}.\n",
var, items_var
));
}
Expr::Closure { params, body, .. } => {
let params_var = format!("clp_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", params_var));
let mut param_names = HashSet::new();
for (sym, _) in params {
let name = interner.resolve(*sym);
param_names.insert(name.to_string());
output.push_str(&format!("Push \"{}\" to {}.\n", name, params_var));
}
let body_var = format!("clb_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
match body {
ClosureBody::Expression(e) => {
let ret_expr = encode_expr_src(e, counter, output, interner, variants);
let ret_var = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", ret_var, ret_expr));
output.push_str(&format!("Push {} to {}.\n", ret_var, body_var));
}
ClosureBody::Block(stmts) => {
for s in stmts.iter() {
let sv = encode_stmt_src(s, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", sv, body_var));
}
}
}
let bound: HashSet<String> = param_names;
let free = collect_free_vars_expr(expr, interner, &bound);
let cap_var = format!("clc_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Text.\n", cap_var));
for fv in &free {
output.push_str(&format!("Push \"{}\" to {}.\n", fv, cap_var));
}
output.push_str(&format!(
"Let {} be a new CClosure with params {} and body {} and captured {}.\n",
var, params_var, body_var, cap_var
));
}
Expr::CallExpr { callee, args } => {
let callee_var = encode_expr_src(callee, counter, output, interner, variants);
let args_var = format!("cea_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for a in args {
let av = encode_expr_src(a, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", av, args_var));
}
output.push_str(&format!(
"Let {} be a new CCallExpr with target {} and args {}.\n",
var, callee_var, args_var
));
}
Expr::Give { value } => {
let inner_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!("Let {} be {}.\n", var, inner_var));
}
Expr::Escape { code, .. } => {
let code_str = interner.resolve(*code);
output.push_str(&format!(
"Let {} be a new CEscExpr with code \"{}\".\n",
var, code_str.replace('\"', "\\\"")
));
}
Expr::List(elems) => {
let items_var = format!("litems_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", items_var));
for elem in elems {
let elem_var = encode_expr_src(elem, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", elem_var, items_var));
}
output.push_str(&format!(
"Let {} be a new CList with items {}.\n",
var, items_var
));
}
_ => {
output.push_str(&format!("Let {} be a new CText with value \"unsupported\".\n", var));
}
}
var
}
fn encode_stmt_src(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
let var = format!("s_{}", *counter);
*counter += 1;
match stmt {
Stmt::Let { var: name, value, .. } => {
let name_str = interner.resolve(*name);
let expr_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CLet with name \"{}\" and expr {}.\n",
var, name_str, expr_var
));
}
Stmt::Set { target, value } => {
let name_str = interner.resolve(*target);
let expr_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSet with name \"{}\" and expr {}.\n",
var, name_str, expr_var
));
}
Stmt::If { cond, then_block, else_block } => {
let cond_var = encode_expr_src(cond, counter, output, interner, variants);
let then_stmts: Vec<&Stmt> = then_block.iter().collect();
let then_var = encode_stmt_list_src(&then_stmts, counter, output, interner, variants);
let else_var = if let Some(els) = else_block {
let else_stmts: Vec<&Stmt> = els.iter().collect();
encode_stmt_list_src(&else_stmts, counter, output, interner, variants)
} else {
let empty_var = format!("emptyBlock_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_var));
empty_var
};
output.push_str(&format!(
"Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
var, cond_var, then_var, else_var
));
}
Stmt::While { cond, body, .. } => {
let cond_var = encode_expr_src(cond, counter, output, interner, variants);
let body_stmts: Vec<&Stmt> = body.iter().collect();
let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CWhile with cond {} and body {}.\n",
var, cond_var, body_var
));
}
Stmt::Return { value } => {
if let Some(expr) = value {
let expr_var = encode_expr_src(expr, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, expr_var));
} else {
let nothing_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CInt with value 0.\n", nothing_var));
output.push_str(&format!("Let {} be a new CReturn with expr {}.\n", var, nothing_var));
}
}
Stmt::Show { object, .. } => {
let expr_var = encode_expr_src(object, counter, output, interner, variants);
output.push_str(&format!("Let {} be a new CShow with expr {}.\n", var, expr_var));
}
Stmt::Call { function, args } => {
let fn_name = interner.resolve(*function);
let args_var = format!("callSArgs_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for arg in args {
let arg_var = encode_expr_src(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", arg_var, args_var));
}
output.push_str(&format!(
"Let {} be a new CCallS with name \"{}\" and args {}.\n",
var, fn_name, args_var
));
}
Stmt::Push { value, collection } => {
let val_var = encode_expr_src(value, counter, output, interner, variants);
let coll_name = extract_ident_name(collection, interner);
output.push_str(&format!(
"Let {} be a new CPush with expr {} and target \"{}\".\n",
var, val_var, coll_name
));
}
Stmt::SetIndex { collection, index, value } => {
let coll_name = extract_ident_name(collection, interner);
let idx_var = encode_expr_src(index, counter, output, interner, variants);
let val_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSetIdx with target \"{}\" and idx {} and val {}.\n",
var, coll_name, idx_var, val_var
));
}
Stmt::SetField { object, field, value } => {
let map_name = extract_ident_name(object, interner);
let field_name = interner.resolve(*field);
let key_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", key_var, field_name));
let val_var = encode_expr_src(value, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CMapSet with target \"{}\" and key {} and val {}.\n",
var, map_name, key_var, val_var
));
}
Stmt::Pop { collection, .. } => {
let coll_name = extract_ident_name(collection, interner);
output.push_str(&format!(
"Let {} be a new CPop with target \"{}\".\n",
var, coll_name
));
}
Stmt::Add { value, collection } => {
let val_var = encode_expr_src(value, counter, output, interner, variants);
let coll_name = extract_ident_name(collection, interner);
output.push_str(&format!(
"Let {} be a new CAdd with elem {} and target \"{}\".\n",
var, val_var, coll_name
));
}
Stmt::Remove { value, collection } => {
let val_var = encode_expr_src(value, counter, output, interner, variants);
let coll_name = extract_ident_name(collection, interner);
output.push_str(&format!(
"Let {} be a new CRemove with elem {} and target \"{}\".\n",
var, val_var, coll_name
));
}
Stmt::Inspect { .. } => {
return String::new(); }
Stmt::Repeat { .. } => {
return String::new(); }
Stmt::Break => {
output.push_str(&format!("Let {} be a new CBreak.\n", var));
}
Stmt::RuntimeAssert { condition, .. } => {
let cond_var = encode_expr_src(condition, counter, output, interner, variants);
let msg_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"assertion failed\".\n", msg_var));
output.push_str(&format!(
"Let {} be a new CRuntimeAssert with cond {} and msg {}.\n",
var, cond_var, msg_var
));
}
Stmt::Give { object, recipient } => {
let expr_var = encode_expr_src(object, counter, output, interner, variants);
let target_name = extract_ident_name(recipient, interner);
output.push_str(&format!(
"Let {} be a new CGive with expr {} and target \"{}\".\n",
var, expr_var, target_name
));
}
Stmt::Escape { code, .. } => {
let code_str = interner.resolve(*code);
output.push_str(&format!(
"Let {} be a new CEscStmt with code \"{}\".\n",
var, code_str.replace('\"', "\\\"")
));
}
Stmt::Sleep { milliseconds } => {
let dur_var = encode_expr_src(milliseconds, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSleep with duration {}.\n",
var, dur_var
));
}
Stmt::ReadFrom { var: read_var, source } => {
let var_name = interner.resolve(*read_var);
match source {
ReadSource::Console => {
output.push_str(&format!(
"Let {} be a new CReadConsole with target \"{}\".\n",
var, var_name
));
}
ReadSource::File(path_expr) => {
let path_var = encode_expr_src(path_expr, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CReadFile with path {} and target \"{}\".\n",
var, path_var, var_name
));
}
}
}
Stmt::WriteFile { content, path } => {
let path_var = encode_expr_src(path, counter, output, interner, variants);
let content_var = encode_expr_src(content, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CWriteFile with path {} and content {}.\n",
var, path_var, content_var
));
}
Stmt::Check { source_text, .. } => {
let pred_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CBool with value true.\n", pred_var));
let msg_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", msg_var, source_text.replace('\"', "\\\"")));
output.push_str(&format!(
"Let {} be a new CCheck with predicate {} and msg {}.\n",
var, pred_var, msg_var
));
}
Stmt::Assert { .. } => {
let prop_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CBool with value true.\n", prop_var));
output.push_str(&format!(
"Let {} be a new CAssert with proposition {}.\n",
var, prop_var
));
}
Stmt::Trust { justification, .. } => {
let prop_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CBool with value true.\n", prop_var));
let just_str = interner.resolve(*justification);
output.push_str(&format!(
"Let {} be a new CTrust with proposition {} and justification \"{}\".\n",
var, prop_var, just_str
));
}
Stmt::Require { crate_name, .. } => {
let dep_name = interner.resolve(*crate_name);
output.push_str(&format!(
"Let {} be a new CRequire with dependency \"{}\".\n",
var, dep_name
));
}
Stmt::MergeCrdt { source, target } => {
let source_var = encode_expr_src(source, counter, output, interner, variants);
let target_name = match target {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
};
output.push_str(&format!(
"Let {} be a new CMerge with target \"{}\" and other {}.\n",
var, target_name, source_var
));
}
Stmt::IncreaseCrdt { object, field, amount } => {
let amount_var = encode_expr_src(amount, counter, output, interner, variants);
let target_name = match object {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
};
output.push_str(&format!(
"Let {} be a new CIncrease with target \"{}\" and amount {}.\n",
var, target_name, amount_var
));
}
Stmt::DecreaseCrdt { object, field, amount } => {
let amount_var = encode_expr_src(amount, counter, output, interner, variants);
let target_name = match object {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
};
output.push_str(&format!(
"Let {} be a new CDecrease with target \"{}\" and amount {}.\n",
var, target_name, amount_var
));
}
Stmt::AppendToSequence { sequence, value } => {
let value_var = encode_expr_src(value, counter, output, interner, variants);
let target_name = match sequence {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
};
output.push_str(&format!(
"Let {} be a new CAppendToSeq with target \"{}\" and value {}.\n",
var, target_name, value_var
));
}
Stmt::ResolveConflict { object, .. } => {
let target_name = match object {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
};
output.push_str(&format!(
"Let {} be a new CResolve with target \"{}\".\n",
var, target_name
));
}
Stmt::Sync { var: sync_var, topic } => {
let topic_var = encode_expr_src(topic, counter, output, interner, variants);
let var_name = interner.resolve(*sync_var);
output.push_str(&format!(
"Let {} be a new CSync with target \"{}\" and channel {}.\n",
var, var_name, topic_var
));
}
Stmt::Mount { var: mount_var, path } => {
let path_var = encode_expr_src(path, counter, output, interner, variants);
let var_name = interner.resolve(*mount_var);
output.push_str(&format!(
"Let {} be a new CMount with target \"{}\" and path {}.\n",
var, var_name, path_var
));
}
Stmt::Concurrent { tasks } => {
let branches_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Seq of CStmt.\n", branches_var));
let branch_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", branch_var));
for stmt in tasks.iter() {
let sv = encode_stmt_src(stmt, counter, output, interner, variants);
if !sv.is_empty() {
output.push_str(&format!("Push {} to {}.\n", sv, branch_var));
}
}
output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
output.push_str(&format!(
"Let {} be a new CConcurrent with branches {}.\n",
var, branches_var
));
}
Stmt::Parallel { tasks } => {
let branches_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of Seq of CStmt.\n", branches_var));
let branch_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", branch_var));
for stmt in tasks.iter() {
let sv = encode_stmt_src(stmt, counter, output, interner, variants);
if !sv.is_empty() {
output.push_str(&format!("Push {} to {}.\n", sv, branch_var));
}
}
output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
output.push_str(&format!(
"Let {} be a new CParallel with branches {}.\n",
var, branches_var
));
}
Stmt::LaunchTask { function, args } | Stmt::LaunchTaskWithHandle { function, args, .. } => {
let func_name = interner.resolve(*function);
let args_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CExpr.\n", args_var));
for arg in args {
let av = encode_expr_src(arg, counter, output, interner, variants);
output.push_str(&format!("Push {} to {}.\n", av, args_var));
}
let body_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
let call_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CCallS with name \"{}\" and args {}.\n",
call_var, func_name, args_var
));
output.push_str(&format!("Push {} to {}.\n", call_var, body_var));
let handle_name = if let Stmt::LaunchTaskWithHandle { handle, .. } = stmt {
interner.resolve(*handle).to_string()
} else {
"_task".to_string()
};
output.push_str(&format!(
"Let {} be a new CLaunchTask with body {} and handle \"{}\".\n",
var, body_var, handle_name
));
}
Stmt::StopTask { handle } => {
let handle_var = encode_expr_src(handle, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CStopTask with handle {}.\n",
var, handle_var
));
}
Stmt::CreatePipe { var: pipe_var, capacity, .. } => {
let cap = capacity.unwrap_or(32);
let cap_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CInt with value {}.\n", cap_var, cap));
let pipe_name = interner.resolve(*pipe_var);
output.push_str(&format!(
"Let {} be a new CCreatePipe with name \"{}\" and capacity {}.\n",
var, pipe_name, cap_var
));
}
Stmt::SendPipe { value, pipe } => {
let val_var = encode_expr_src(value, counter, output, interner, variants);
let pipe_name = match pipe {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "pipe".to_string(),
};
output.push_str(&format!(
"Let {} be a new CSendPipe with chan \"{}\" and value {}.\n",
var, pipe_name, val_var
));
}
Stmt::ReceivePipe { var: recv_var, pipe } => {
let recv_name = interner.resolve(*recv_var);
let pipe_name = match pipe {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "pipe".to_string(),
};
output.push_str(&format!(
"Let {} be a new CReceivePipe with chan \"{}\" and target \"{}\".\n",
var, pipe_name, recv_name
));
}
Stmt::TrySendPipe { value, pipe, .. } => {
let val_var = encode_expr_src(value, counter, output, interner, variants);
let pipe_name = match pipe {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "pipe".to_string(),
};
output.push_str(&format!(
"Let {} be a new CTrySendPipe with chan \"{}\" and value {}.\n",
var, pipe_name, val_var
));
}
Stmt::TryReceivePipe { var: recv_var, pipe } => {
let recv_name = interner.resolve(*recv_var);
let pipe_name = match pipe {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "pipe".to_string(),
};
output.push_str(&format!(
"Let {} be a new CTryReceivePipe with chan \"{}\" and target \"{}\".\n",
var, pipe_name, recv_name
));
}
Stmt::Select { branches } => {
let branches_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CSelectBranch.\n", branches_var));
for branch in branches {
match branch {
SelectBranch::Receive { var: recv_var, pipe, body } => {
let recv_name = interner.resolve(*recv_var);
let pipe_name = match pipe {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "pipe".to_string(),
};
let body_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
for stmt in body.iter() {
let sv = encode_stmt_src(stmt, counter, output, interner, variants);
if !sv.is_empty() {
output.push_str(&format!("Push {} to {}.\n", sv, body_var));
}
}
let branch_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CSelectRecv with chan \"{}\" and var \"{}\" and body {}.\n",
branch_var, pipe_name, recv_name, body_var
));
output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
}
SelectBranch::Timeout { milliseconds, body } => {
let dur_var = encode_expr_src(milliseconds, counter, output, interner, variants);
let body_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
for stmt in body.iter() {
let sv = encode_stmt_src(stmt, counter, output, interner, variants);
if !sv.is_empty() {
output.push_str(&format!("Push {} to {}.\n", sv, body_var));
}
}
let branch_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CSelectTimeout with duration {} and body {}.\n",
branch_var, dur_var, body_var
));
output.push_str(&format!("Push {} to {}.\n", branch_var, branches_var));
}
}
}
output.push_str(&format!(
"Let {} be a new CSelect with branches {}.\n",
var, branches_var
));
}
Stmt::Spawn { agent_type, name } => {
let agent_name = interner.resolve(*agent_type);
let target_name = interner.resolve(*name);
output.push_str(&format!(
"Let {} be a new CSpawn with agentType \"{}\" and target \"{}\".\n",
var, agent_name, target_name
));
}
Stmt::SendMessage { message, destination } => {
let target_var = encode_expr_src(destination, counter, output, interner, variants);
let msg_var = encode_expr_src(message, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CSendMessage with target {} and msg {}.\n",
var, target_var, msg_var
));
}
Stmt::AwaitMessage { into, .. } => {
let await_name = interner.resolve(*into);
output.push_str(&format!(
"Let {} be a new CAwaitMessage with target \"{}\".\n",
var, await_name
));
}
Stmt::Listen { address } => {
let addr_var = encode_expr_src(address, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CListen with addr {} and handler \"default\".\n",
var, addr_var
));
}
Stmt::ConnectTo { address } => {
let addr_var = encode_expr_src(address, counter, output, interner, variants);
output.push_str(&format!(
"Let {} be a new CConnectTo with addr {} and target \"conn\".\n",
var, addr_var
));
}
Stmt::Zone { name, body, .. } => {
let zone_name = interner.resolve(*name);
let body_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", body_var));
for stmt in body.iter() {
let sv = encode_stmt_src(stmt, counter, output, interner, variants);
if !sv.is_empty() {
output.push_str(&format!("Push {} to {}.\n", sv, body_var));
}
}
output.push_str(&format!(
"Let {} be a new CZone with name \"{}\" and kind \"heap\" and body {}.\n",
var, zone_name, body_var
));
}
Stmt::LetPeerAgent { var: pa_var, address } => {
let addr_var = encode_expr_src(address, counter, output, interner, variants);
let pa_name = interner.resolve(*pa_var);
output.push_str(&format!(
"Let {} be a new CConnectTo with addr {} and target \"{}\".\n",
var, addr_var, pa_name
));
}
_ => {
return String::new();
}
}
var
}
fn encode_stmts_src(stmt: &Stmt, counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> Vec<String> {
match stmt {
Stmt::Inspect { target, arms, .. } => {
let mut otherwise_stmts: Vec<&Stmt> = Vec::new();
let mut variant_arms: Vec<(&MatchArm, Vec<&Stmt>)> = Vec::new();
for arm in arms {
if arm.variant.is_none() {
otherwise_stmts = arm.body.iter().collect();
} else {
let body_refs: Vec<&Stmt> = arm.body.iter().collect();
variant_arms.push((arm, body_refs));
}
}
if variant_arms.is_empty() {
let mut result = Vec::new();
for s in &otherwise_stmts {
for v in encode_stmts_src(s, counter, output, interner, variants) {
result.push(v);
}
}
return result;
}
let has_otherwise = !otherwise_stmts.is_empty();
let mut result = Vec::new();
let matched_var_name = if has_otherwise {
let name = format!("__inspectMatched_{}", *counter);
*counter += 1;
let false_expr = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CBool with value false.\n", false_expr));
let let_stmt = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CLet with name \"{}\" and expr {}.\n",
let_stmt, name, false_expr
));
result.push(let_stmt);
Some(name)
} else {
None
};
for (arm, body_stmts) in &variant_arms {
let variant_name = interner.resolve(arm.variant.unwrap());
let tag_target = encode_expr_src(target, counter, output, interner, variants);
let tag_key = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"__tag\".\n", tag_key));
let tag_get = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CMapGet with target {} and key {}.\n",
tag_get, tag_target, tag_key
));
let variant_text = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", variant_text, variant_name));
let cond_var = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CBinOp with op \"==\" and left {} and right {}.\n",
cond_var, tag_get, variant_text
));
let then_list = format!("stmtList_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", then_list));
if let Some(ref mname) = matched_var_name {
let true_expr = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CBool with value true.\n", true_expr));
let set_stmt = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CSet with name \"{}\" and expr {}.\n",
set_stmt, mname, true_expr
));
output.push_str(&format!("Push {} to {}.\n", set_stmt, then_list));
}
for (field_name, binding_name) in &arm.bindings {
let field_str = interner.resolve(*field_name);
let bind_str = interner.resolve(*binding_name);
let bind_target = encode_expr_src(target, counter, output, interner, variants);
let fkey = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CText with value \"{}\".\n", fkey, field_str));
let fget = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CMapGet with target {} and key {}.\n",
fget, bind_target, fkey
));
let bind_let = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CLet with name \"{}\" and expr {}.\n",
bind_let, bind_str, fget
));
output.push_str(&format!("Push {} to {}.\n", bind_let, then_list));
}
for body_stmt in body_stmts {
match body_stmt {
Stmt::Inspect { .. } | Stmt::Repeat { .. } => {
let vars = encode_stmts_src(body_stmt, counter, output, interner, variants);
for v in vars {
output.push_str(&format!("Push {} to {}.\n", v, then_list));
}
}
_ => {
let bvar = encode_stmt_src(body_stmt, counter, output, interner, variants);
if !bvar.is_empty() {
output.push_str(&format!("Push {} to {}.\n", bvar, then_list));
}
}
}
}
let empty_else = format!("stmtList_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_else));
let if_var = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
if_var, cond_var, then_list, empty_else
));
result.push(if_var);
}
if let Some(ref mname) = matched_var_name {
let matched_ref = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CVar with name \"{}\".\n", matched_ref, mname));
let not_matched = format!("e_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new CNot with inner {}.\n", not_matched, matched_ref));
let otherwise_block = encode_stmt_list_src(&otherwise_stmts, counter, output, interner, variants);
let empty_else = format!("stmtList_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", empty_else));
let otherwise_if = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CIf with cond {} and thenBlock {} and elseBlock {}.\n",
otherwise_if, not_matched, otherwise_block, empty_else
));
result.push(otherwise_if);
}
result
}
Stmt::Repeat { pattern, iterable, body, .. } => {
let loop_var_name = match pattern {
Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
Pattern::Tuple(syms) => {
if let Some(s) = syms.first() {
interner.resolve(*s).to_string()
} else {
"item".to_string()
}
}
};
if let Expr::Range { start, end } = iterable {
let start_var = encode_expr_src(start, counter, output, interner, variants);
let end_var = encode_expr_src(end, counter, output, interner, variants);
let body_stmts: Vec<&Stmt> = body.iter().collect();
let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
let rr = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CRepeatRange with var \"{}\" and start {} and end {} and body {}.\n",
rr, loop_var_name, start_var, end_var, body_var
));
return vec![rr];
}
let coll_var = encode_expr_src(iterable, counter, output, interner, variants);
let body_stmts: Vec<&Stmt> = body.iter().collect();
let body_var = encode_stmt_list_src(&body_stmts, counter, output, interner, variants);
let rep = format!("s_{}", *counter);
*counter += 1;
output.push_str(&format!(
"Let {} be a new CRepeat with var \"{}\" and coll {} and body {}.\n",
rep, loop_var_name, coll_var, body_var
));
vec![rep]
}
_ => {
let v = encode_stmt_src(stmt, counter, output, interner, variants);
if v.is_empty() {
vec![]
} else {
vec![v]
}
}
}
}
fn encode_stmt_list_src(stmts: &[&Stmt], counter: &mut usize, output: &mut String, interner: &Interner, variants: &HashMap<String, Vec<String>>) -> String {
let list_var = format!("stmtList_{}", *counter);
*counter += 1;
output.push_str(&format!("Let {} be a new Seq of CStmt.\n", list_var));
for stmt in stmts {
for stmt_var in encode_stmts_src(stmt, counter, output, interner, variants) {
output.push_str(&format!("Push {} to {}.\n", stmt_var, list_var));
}
}
list_var
}
fn extract_ident_name(expr: &Expr, interner: &Interner) -> String {
match expr {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
}
}
pub fn projection1_source(_core_types: &str, _interpreter: &str, program: &str) -> Result<String, String> {
let full_source = if program.contains("## Main") || program.contains("## To ") {
program.to_string()
} else {
format!("## Main\n{}", program)
};
let mut interner = Interner::new();
let mut lexer = Lexer::new(&full_source, &mut interner);
let tokens = lexer.tokenize();
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
result.types
};
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = crate::parser::Parser::new(
tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
);
let stmts = parser.parse_program().map_err(|e| format!("Parse error: {:?}", e))?;
let optimized = crate::optimize::optimize_for_projection(
stmts, &imperative_expr_arena, &stmt_arena, &mut interner,
);
let mut output = String::new();
for stmt in &optimized {
if matches!(stmt, Stmt::FunctionDef { .. }) {
decompile_stmt(stmt, &interner, &mut output, 0);
output.push('\n');
}
}
output.push_str("## Main\n");
for stmt in &optimized {
if !matches!(stmt, Stmt::FunctionDef { .. }) {
decompile_stmt(stmt, &interner, &mut output, 0);
}
}
Ok(output)
}
fn decompile_stmt(stmt: &Stmt, interner: &Interner, out: &mut String, indent: usize) {
let pad = " ".repeat(indent);
match stmt {
Stmt::FunctionDef { name, params, body, return_type, .. } => {
let fn_name = interner.resolve(*name);
let param_strs: Vec<String> = params
.iter()
.map(|(name, ty)| {
let pname = interner.resolve(*name);
format!("{}: {}", pname, decompile_type_expr(ty, interner))
})
.collect();
let ret_str = if let Some(rt) = return_type {
format!(" -> {}", decompile_type_expr(rt, interner))
} else {
String::new()
};
out.push_str(&format!("{}## To {} ({}){}:\n", pad, fn_name, param_strs.join(", "), ret_str));
for s in body.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
}
Stmt::Let { var, value, mutable, .. } => {
let name = interner.resolve(*var);
let expr_str = decompile_expr(value, interner);
if *mutable {
out.push_str(&format!("{}Let mutable {} be {}.\n", pad, name, expr_str));
} else {
out.push_str(&format!("{}Let {} be {}.\n", pad, name, expr_str));
}
}
Stmt::Set { target, value } => {
let name = interner.resolve(*target);
let expr_str = decompile_expr(value, interner);
out.push_str(&format!("{}Set {} to {}.\n", pad, name, expr_str));
}
Stmt::Show { object, .. } => {
let expr_str = decompile_expr(object, interner);
out.push_str(&format!("{}Show {}.\n", pad, expr_str));
}
Stmt::Return { value } => {
if let Some(expr) = value {
let expr_str = decompile_expr(expr, interner);
out.push_str(&format!("{}Return {}.\n", pad, expr_str));
} else {
out.push_str(&format!("{}Return.\n", pad));
}
}
Stmt::If { cond, then_block, else_block } => {
let cond_str = decompile_expr(cond, interner);
out.push_str(&format!("{}If {}:\n", pad, cond_str));
for s in then_block.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
if let Some(els) = else_block {
out.push_str(&format!("{}Otherwise:\n", pad));
for s in els.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
}
}
Stmt::While { cond, body, .. } => {
let cond_str = decompile_expr(cond, interner);
out.push_str(&format!("{}While {}:\n", pad, cond_str));
for s in body.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
}
Stmt::Call { function, args } => {
let fn_name = interner.resolve(*function);
let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
if arg_strs.is_empty() {
out.push_str(&format!("{}{}().\n", pad, fn_name));
} else {
out.push_str(&format!("{}{}({}).\n", pad, fn_name, arg_strs.join(", ")));
}
}
Stmt::Push { value, collection } => {
let val_str = decompile_expr(value, interner);
let coll_str = decompile_expr(collection, interner);
out.push_str(&format!("{}Push {} to {}.\n", pad, val_str, coll_str));
}
Stmt::SetIndex { collection, index, value } => {
let coll_str = decompile_expr(collection, interner);
let idx_str = decompile_expr(index, interner);
let val_str = decompile_expr(value, interner);
out.push_str(&format!("{}Set item {} of {} to {}.\n", pad, idx_str, coll_str, val_str));
}
Stmt::SetField { object, field, value } => {
let obj_str = decompile_expr(object, interner);
let field_name = interner.resolve(*field);
let val_str = decompile_expr(value, interner);
out.push_str(&format!("{}Set {} of {} to {}.\n", pad, field_name, obj_str, val_str));
}
Stmt::Repeat { pattern, iterable, body, .. } => {
let var_name = match pattern {
Pattern::Identifier(sym) => interner.resolve(*sym).to_string(),
Pattern::Tuple(syms) => {
syms.iter().map(|s| interner.resolve(*s).to_string()).collect::<Vec<_>>().join(", ")
}
};
let iter_str = decompile_expr(iterable, interner);
out.push_str(&format!("{}Repeat for {} in {}:\n", pad, var_name, iter_str));
for s in body.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
}
Stmt::Inspect { target, arms, .. } => {
let target_str = decompile_expr(target, interner);
out.push_str(&format!("{}Inspect {}:\n", pad, target_str));
for arm in arms {
if let Some(variant) = arm.variant {
let variant_name = interner.resolve(variant);
let bindings: Vec<String> = arm.bindings.iter()
.map(|(_, b)| interner.resolve(*b).to_string())
.collect();
if bindings.is_empty() {
out.push_str(&format!("{} When {}:\n", pad, variant_name));
} else {
out.push_str(&format!("{} When {}({}):\n", pad, variant_name, bindings.join(", ")));
}
} else {
out.push_str(&format!("{} Otherwise:\n", pad));
}
for s in arm.body.iter() {
decompile_stmt(s, interner, out, indent + 2);
}
}
}
Stmt::Pop { collection, into } => {
let coll_str = decompile_expr(collection, interner);
if let Some(target) = into {
let target_name = interner.resolve(*target);
out.push_str(&format!("{}Pop from {} into {}.\n", pad, coll_str, target_name));
} else {
out.push_str(&format!("{}Pop from {}.\n", pad, coll_str));
}
}
Stmt::Break => {
out.push_str(&format!("{}Break.\n", pad));
}
Stmt::RuntimeAssert { condition } => {
let cond_str = decompile_expr(condition, interner);
out.push_str(&format!("{}Assert that {}.\n", pad, cond_str));
}
Stmt::Add { value, collection } => {
let val_str = decompile_expr(value, interner);
let coll_str = decompile_expr(collection, interner);
out.push_str(&format!("{}Add {} to {}.\n", pad, val_str, coll_str));
}
Stmt::Remove { value, collection } => {
let val_str = decompile_expr(value, interner);
let coll_str = decompile_expr(collection, interner);
out.push_str(&format!("{}Remove {} from {}.\n", pad, val_str, coll_str));
}
Stmt::Zone { name, body, .. } => {
let zone_name = interner.resolve(*name);
out.push_str(&format!("{}Inside a new zone called \"{}\":\n", pad, zone_name));
for s in body.iter() {
decompile_stmt(s, interner, out, indent + 1);
}
}
Stmt::ReadFrom { var, .. } => {
let var_name = interner.resolve(*var);
out.push_str(&format!("{}Read {} from the console.\n", pad, var_name));
}
Stmt::WriteFile { content, path } => {
let content_str = decompile_expr(content, interner);
let path_str = decompile_expr(path, interner);
out.push_str(&format!("{}Write {} to file {}.\n", pad, content_str, path_str));
}
Stmt::Sleep { milliseconds } => {
let ms = decompile_expr(milliseconds, interner);
out.push_str(&format!("{}Sleep {}.\n", pad, ms));
}
_ => {
}
}
}
fn decompile_expr(expr: &Expr, interner: &Interner) -> String {
match expr {
Expr::Literal(lit) => match lit {
Literal::Number(n) => n.to_string(),
Literal::Float(f) => format!("{}", f),
Literal::Boolean(b) => if *b { "true".to_string() } else { "false".to_string() },
Literal::Text(s) => format!("\"{}\"", interner.resolve(*s)),
Literal::Nothing => "nothing".to_string(),
Literal::Char(c) => format!("'{}'", c),
Literal::Duration(ns) => format!("{}", ns),
Literal::Date(days) => format!("{}", days),
Literal::Moment(ns) => format!("{}", ns),
Literal::Span { months, days } => format!("{} months {} days", months, days),
Literal::Time(ns) => format!("{}", ns),
},
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
Expr::BinaryOp { op, left, right } => {
let l = if matches!(left, Expr::BinaryOp { .. }) {
format!("({})", decompile_expr(left, interner))
} else {
decompile_expr(left, interner)
};
let r = if matches!(right, Expr::BinaryOp { .. }) {
format!("({})", decompile_expr(right, interner))
} else {
decompile_expr(right, interner)
};
if matches!(op, BinaryOpKind::Shl) {
if let Expr::Literal(Literal::Number(k)) = right {
let multiplier = 1i64 << k;
return format!("{} * {}", l, multiplier);
}
}
if matches!(op, BinaryOpKind::Shr) {
if let Expr::Literal(Literal::Number(k)) = right {
let divisor = 1i64 << k;
return format!("{} / {}", l, divisor);
}
}
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Subtract => "-",
BinaryOpKind::Multiply => "*",
BinaryOpKind::Divide => "/",
BinaryOpKind::Modulo => "%",
BinaryOpKind::Eq => "equals",
BinaryOpKind::NotEq => "is not",
BinaryOpKind::Lt => "is less than",
BinaryOpKind::Gt => "is greater than",
BinaryOpKind::LtEq => "is at most",
BinaryOpKind::GtEq => "is at least",
BinaryOpKind::And => "and",
BinaryOpKind::Or => "or",
BinaryOpKind::Concat => "+",
BinaryOpKind::BitXor => "+",
BinaryOpKind::Shl => "*",
BinaryOpKind::Shr => "/",
};
format!("{} {} {}", l, op_str, r)
}
Expr::Not { operand } => {
let inner = decompile_expr(operand, interner);
format!("not {}", inner)
}
Expr::Call { function, args } => {
let fn_name = interner.resolve(*function);
let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
if arg_strs.is_empty() {
format!("{}()", fn_name)
} else {
format!("{}({})", fn_name, arg_strs.join(", "))
}
}
Expr::Index { collection, index } => {
let coll = decompile_expr(collection, interner);
let idx = decompile_expr(index, interner);
format!("item {} of {}", idx, coll)
}
Expr::Length { collection } => {
let coll = decompile_expr(collection, interner);
format!("length of {}", coll)
}
Expr::FieldAccess { object, field } => {
let obj = decompile_expr(object, interner);
let field_name = interner.resolve(*field);
format!("{} of {}", field_name, obj)
}
Expr::New { type_name, .. } => {
let tn = interner.resolve(*type_name);
format!("a new {}", tn)
}
Expr::NewVariant { variant, fields, .. } => {
let vn = interner.resolve(*variant);
if fields.is_empty() {
format!("a new {}", vn)
} else {
let parts: Vec<String> = fields.iter().map(|(name, val)| {
let n = interner.resolve(*name);
let v = decompile_expr(val, interner);
format!("{} {}", n, v)
}).collect();
format!("a new {} with {}", vn, parts.join(" and "))
}
}
Expr::InterpolatedString(parts) => {
let mut result = String::new();
for part in parts {
match part {
StringPart::Literal(sym) => {
result.push_str(&interner.resolve(*sym));
}
StringPart::Expr { value, debug, .. } => {
let expr_str = decompile_expr(value, interner);
if *debug {
result.push_str(&format!("{{{}=}}", expr_str));
} else {
result.push_str(&format!("{{{}}}", expr_str));
}
}
}
}
format!("\"{}\"", result)
}
Expr::Slice { collection, start, end } => {
let coll = decompile_expr(collection, interner);
let s = decompile_expr(start, interner);
let e = decompile_expr(end, interner);
format!("{} {} through {}", coll, s, e)
}
Expr::Copy { expr } => {
let inner = decompile_expr(expr, interner);
format!("copy of {}", inner)
}
Expr::Give { value } => {
let inner = decompile_expr(value, interner);
format!("Give {}", inner)
}
Expr::Contains { collection, value } => {
let coll = decompile_expr(collection, interner);
let val = decompile_expr(value, interner);
format!("{} contains {}", coll, val)
}
Expr::Union { left, right } => {
let l = decompile_expr(left, interner);
let r = decompile_expr(right, interner);
format!("{} union {}", l, r)
}
Expr::Intersection { left, right } => {
let l = decompile_expr(left, interner);
let r = decompile_expr(right, interner);
format!("{} intersection {}", l, r)
}
Expr::List(elems) => {
let parts: Vec<String> = elems.iter().map(|e| decompile_expr(e, interner)).collect();
format!("[{}]", parts.join(", "))
}
Expr::Tuple(elems) => {
let parts: Vec<String> = elems.iter().map(|e| decompile_expr(e, interner)).collect();
format!("({})", parts.join(", "))
}
Expr::Range { start, end } => {
let s = decompile_expr(start, interner);
let e = decompile_expr(end, interner);
format!("{} to {}", s, e)
}
Expr::OptionSome { value } => {
let inner = decompile_expr(value, interner);
format!("some {}", inner)
}
Expr::OptionNone => "none".to_string(),
Expr::WithCapacity { value, capacity } => {
let val = decompile_expr(value, interner);
let cap = decompile_expr(capacity, interner);
format!("{} with capacity {}", val, cap)
}
Expr::Escape { language, code } => {
let lang = interner.resolve(*language);
let src = interner.resolve(*code);
format!("Escape to {}:\n{}", lang, src)
}
Expr::ManifestOf { zone } => {
let z = decompile_expr(zone, interner);
format!("the manifest of {}", z)
}
Expr::ChunkAt { index, zone } => {
let idx = decompile_expr(index, interner);
let z = decompile_expr(zone, interner);
format!("the chunk at {} in {}", idx, z)
}
Expr::Closure { params, body, return_type } => {
let param_strs: Vec<String> = params.iter().map(|(name, ty)| {
let n = interner.resolve(*name);
let t = decompile_type_expr(ty, interner);
format!("{}: {}", n, t)
}).collect();
let ret = if let Some(rt) = return_type {
format!(" -> {}", decompile_type_expr(rt, interner))
} else {
String::new()
};
match body {
ClosureBody::Expression(expr) => {
let e = decompile_expr(expr, interner);
format!("({}){} -> {}", param_strs.join(", "), ret, e)
}
ClosureBody::Block(_) => {
format!("({}){} -> [block]", param_strs.join(", "), ret)
}
}
}
Expr::CallExpr { callee, args } => {
let c = decompile_expr(callee, interner);
let arg_strs: Vec<String> = args.iter().map(|a| decompile_expr(a, interner)).collect();
format!("{}({})", c, arg_strs.join(", "))
}
}
}
fn decompile_type_expr(ty: &TypeExpr, interner: &Interner) -> String {
match ty {
TypeExpr::Primitive(sym) => interner.resolve(*sym).to_string(),
TypeExpr::Named(sym) => interner.resolve(*sym).to_string(),
TypeExpr::Generic { base, params } => {
let base_str = interner.resolve(*base);
let param_strs: Vec<String> = params.iter().map(|p| decompile_type_expr(p, interner)).collect();
format!("{} of {}", base_str, param_strs.join(" and "))
}
TypeExpr::Function { inputs, output } => {
let in_strs: Vec<String> = inputs.iter().map(|t| decompile_type_expr(t, interner)).collect();
let out_str = decompile_type_expr(output, interner);
format!("fn({}) -> {}", in_strs.join(", "), out_str)
}
TypeExpr::Refinement { base, .. } => {
decompile_type_expr(base, interner)
}
TypeExpr::Persistent { inner } => {
format!("Persistent {}", decompile_type_expr(inner, interner))
}
}
}
pub fn verify_no_overhead_source(source: &str) -> Result<(), String> {
let mut interner = Interner::new();
let mut lexer = Lexer::new(source, &mut interner);
let tokens = lexer.tokenize();
let type_registry = {
let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
let result = discovery.run_full();
result.types
};
let mut world_state = WorldState::new();
let expr_arena = Arena::new();
let term_arena = Arena::new();
let np_arena = Arena::new();
let sym_arena = Arena::new();
let role_arena = Arena::new();
let pp_arena = Arena::new();
let stmt_arena: Arena<Stmt> = Arena::new();
let imperative_expr_arena: Arena<Expr> = Arena::new();
let type_expr_arena: Arena<TypeExpr> = Arena::new();
let ast_ctx = AstContext::with_types(
&expr_arena, &term_arena, &np_arena, &sym_arena,
&role_arena, &pp_arena, &stmt_arena, &imperative_expr_arena,
&type_expr_arena,
);
let mut parser = crate::parser::Parser::new(
tokens, &mut world_state, &mut interner, ast_ctx, type_registry,
);
let stmts = parser.parse_program().map_err(|e| format!("Parse error: {:?}", e))?;
verify_no_overhead_stmts(&stmts, &interner)
}
const CORE_VARIANT_NAMES: &[&str] = &[
"CInt", "CBool", "CText", "CVar", "CBinOp", "CNot",
"CCall", "CIndex", "CLen", "CMapGet",
"CLet", "CSet", "CIf", "CWhile", "CReturn", "CShow",
"CCallS", "CPush", "CSetIdx", "CMapSet",
"CFuncDef", "CProg",
"VInt", "VBool", "VText", "VSeq", "VMap", "VError", "VNothing",
];
fn verify_no_overhead_stmts(stmts: &[Stmt], interner: &Interner) -> Result<(), String> {
for stmt in stmts {
check_stmt_overhead(stmt, interner)?;
}
Ok(())
}
fn check_stmt_overhead(stmt: &Stmt, interner: &Interner) -> Result<(), String> {
match stmt {
Stmt::Inspect { arms, .. } => {
for arm in arms {
if let Some(variant) = arm.variant {
let variant_name = interner.resolve(variant);
if CORE_VARIANT_NAMES.contains(&variant_name) {
return Err(format!(
"Interpretive overhead: Inspect dispatches on Core variant '{}'",
variant_name
));
}
}
for s in arm.body.iter() {
check_stmt_overhead(s, interner)?;
}
}
}
Stmt::If { cond, then_block, else_block } => {
check_expr_overhead(cond, interner)?;
for s in then_block.iter() {
check_stmt_overhead(s, interner)?;
}
if let Some(els) = else_block {
for s in els.iter() {
check_stmt_overhead(s, interner)?;
}
}
}
Stmt::While { cond, body, .. } => {
check_expr_overhead(cond, interner)?;
for s in body.iter() {
check_stmt_overhead(s, interner)?;
}
}
Stmt::FunctionDef { body, .. } => {
for s in body.iter() {
check_stmt_overhead(s, interner)?;
}
}
Stmt::Repeat { body, .. } => {
for s in body.iter() {
check_stmt_overhead(s, interner)?;
}
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } | Stmt::Show { object: value, .. } => {
check_expr_overhead(value, interner)?;
}
Stmt::Return { value } => {
if let Some(v) = value {
check_expr_overhead(v, interner)?;
}
}
_ => {}
}
Ok(())
}
fn check_expr_overhead(expr: &Expr, interner: &Interner) -> Result<(), String> {
match expr {
Expr::Index { collection, index } => {
if let Expr::Identifier(coll_sym) = collection {
let coll_name = interner.resolve(*coll_sym);
if coll_name == "env" {
if let Expr::Literal(Literal::Text(_)) = index {
return Err(
"Interpretive overhead: environment lookup 'item ... of env' on literal key".to_string()
);
}
}
}
check_expr_overhead(collection, interner)?;
check_expr_overhead(index, interner)?;
}
Expr::New { type_name, .. } => {
let tn = interner.resolve(*type_name);
if CORE_VARIANT_NAMES.contains(&tn) {
return Err(format!(
"Interpretive overhead: Core type constructor 'new {}'", tn
));
}
}
Expr::NewVariant { variant, .. } => {
let vn = interner.resolve(*variant);
if CORE_VARIANT_NAMES.contains(&vn) {
return Err(format!(
"Interpretive overhead: Core variant constructor '{}'", vn
));
}
}
Expr::Call { function, args } => {
let fn_name = interner.resolve(*function);
if CORE_VARIANT_NAMES.contains(&fn_name) {
return Err(format!(
"Interpretive overhead: Core variant call '{}'", fn_name
));
}
for a in args {
check_expr_overhead(a, interner)?;
}
}
Expr::BinaryOp { left, right, .. } => {
check_expr_overhead(left, interner)?;
check_expr_overhead(right, interner)?;
}
Expr::Not { operand } => {
check_expr_overhead(operand, interner)?;
}
Expr::Length { collection } => {
check_expr_overhead(collection, interner)?;
}
Expr::FieldAccess { object, .. } => {
check_expr_overhead(object, interner)?;
}
_ => {}
}
Ok(())
}
pub fn pe_source_text() -> &'static str {
include_str!("optimize/pe_source.logos")
}
pub fn decompile_source_text() -> &'static str {
include_str!("optimize/decompile_source.logos")
}
pub fn pe_bti_source_text() -> &'static str {
include_str!("optimize/pe_bti_source.logos")
}
pub fn pe_mini_source_text() -> &'static str {
include_str!("optimize/pe_mini_source.logos")
}
const CORE_TYPES_FOR_PE: &str = r#"
## A CExpr is one of:
A CInt with value Int.
A CFloat with value Real.
A CBool with value Bool.
A CText with value Text.
A CVar with name Text.
A CBinOp with op Text and left CExpr and right CExpr.
A CNot with inner CExpr.
A CCall with name Text and args Seq of CExpr.
A CIndex with coll CExpr and idx CExpr.
A CLen with target CExpr.
A CMapGet with target CExpr and key CExpr.
A CNewSeq.
A CNewVariant with tag Text and fnames Seq of Text and fvals Seq of CExpr.
A CList with items Seq of CExpr.
A CRange with start CExpr and end CExpr.
A CSlice with coll CExpr and startIdx CExpr and endIdx CExpr.
A CCopy with target CExpr.
A CNewSet.
A CContains with coll CExpr and elem CExpr.
A CUnion with left CExpr and right CExpr.
A CIntersection with left CExpr and right CExpr.
A COptionSome with inner CExpr.
A COptionNone.
A CTuple with items Seq of CExpr.
A CNew with typeName Text and fieldNames Seq of Text and fields Seq of CExpr.
A CFieldAccess with target CExpr and field Text.
A CClosure with params Seq of Text and body Seq of CStmt and captured Seq of Text.
A CCallExpr with target CExpr and args Seq of CExpr.
A CInterpolatedString with parts Seq of CStringPart.
A CDuration with amount CExpr and unit Text.
A CTimeNow.
A CDateToday.
A CEscExpr with code Text.
## A CStringPart is one of:
A CLiteralPart with value Text.
A CExprPart with expr CExpr.
## A CStmt is one of:
A CLet with name Text and expr CExpr.
A CSet with name Text and expr CExpr.
A CIf with cond CExpr and thenBlock Seq of CStmt and elseBlock Seq of CStmt.
A CWhile with cond CExpr and body Seq of CStmt.
A CReturn with expr CExpr.
A CShow with expr CExpr.
A CCallS with name Text and args Seq of CExpr.
A CPush with expr CExpr and target Text.
A CSetIdx with target Text and idx CExpr and val CExpr.
A CMapSet with target Text and key CExpr and val CExpr.
A CPop with target Text.
A CRepeat with var Text and coll CExpr and body Seq of CStmt.
A CRepeatRange with var Text and start CExpr and end CExpr and body Seq of CStmt.
A CBreak.
A CAdd with elem CExpr and target Text.
A CRemove with elem CExpr and target Text.
A CSetField with target Text and field Text and val CExpr.
A CStructDef with name Text and fieldNames Seq of Text.
A CInspect with target CExpr and arms Seq of CMatchArm.
A CEnumDef with name Text and variants Seq of Text.
A CRuntimeAssert with cond CExpr and msg CExpr.
A CGive with expr CExpr and target Text.
A CEscStmt with code Text.
A CSleep with duration CExpr.
A CReadConsole with target Text.
A CReadFile with path CExpr and target Text.
A CWriteFile with path CExpr and content CExpr.
A CCheck with predicate CExpr and msg CExpr.
A CAssert with proposition CExpr.
A CTrust with proposition CExpr and justification Text.
A CRequire with dependency Text.
A CMerge with target Text and other CExpr.
A CIncrease with target Text and amount CExpr.
A CDecrease with target Text and amount CExpr.
A CAppendToSeq with target Text and value CExpr.
A CResolve with target Text.
A CSync with target Text and channel CExpr.
A CMount with target Text and path CExpr.
A CConcurrent with branches Seq of Seq of CStmt.
A CParallel with branches Seq of Seq of CStmt.
A CLaunchTask with body Seq of CStmt and handle Text.
A CStopTask with handle CExpr.
A CSelect with branches Seq of CSelectBranch.
A CCreatePipe with name Text and capacity CExpr.
A CSendPipe with chan Text and value CExpr.
A CReceivePipe with chan Text and target Text.
A CTrySendPipe with chan Text and value CExpr.
A CTryReceivePipe with chan Text and target Text.
A CSpawn with agentType Text and target Text.
A CSendMessage with target CExpr and msg CExpr.
A CAwaitMessage with target Text.
A CListen with addr CExpr and handler Text.
A CConnectTo with addr CExpr and target Text.
A CZone with name Text and kind Text and body Seq of CStmt.
## A CSelectBranch is one of:
A CSelectRecv with chan Text and var Text and body Seq of CStmt.
A CSelectTimeout with duration CExpr and body Seq of CStmt.
## A CMatchArm is one of:
A CWhen with variantName Text and bindings Seq of Text and body Seq of CStmt.
A COtherwise with body Seq of CStmt.
## A CFunc is one of:
A CFuncDef with name Text and params Seq of Text and paramTypes Seq of Text and returnType Text and body Seq of CStmt.
## A CProgram is one of:
A CProg with funcs Seq of CFunc and main Seq of CStmt.
## A PEState is one of:
A PEStateR with env Map of Text to CVal and funcs Map of Text to CFunc and depth Int and staticEnv Map of Text to CExpr and specResults Map of Text to CExpr and onStack Seq of Text.
## A CVal is one of:
A VInt with value Int.
A VFloat with value Real.
A VBool with value Bool.
A VText with value Text.
A VSeq with items Seq of CVal.
A VMap with entries Map of Text to CVal.
A VError with msg Text.
A VNothing.
A VSet with items Seq of CVal.
A VOption with inner CVal and present Bool.
A VTuple with items Seq of CVal.
A VStruct with typeName Text and fields Map of Text to CVal.
A VVariant with typeName Text and variantName Text and fields Seq of CVal.
A VClosure with params Seq of Text and body Seq of CStmt and capturedEnv Map of Text to CVal.
A VDuration with millis Int.
A VDate with year Int and month Int and day Int.
A VMoment with millis Int.
A VSpan with startMillis Int and endMillis Int.
A VTime with hour Int and minute Int and second Int.
A VCrdt with kind Text and state Map of Text to CVal.
"#;
pub fn quote_pe_source() -> Result<String, String> {
let pe_source = pe_source_text();
let full_source = format!("{}\n{}", CORE_TYPES_FOR_PE, pe_source);
let encoded = encode_program_source(&full_source).map_err(|e| format!("Failed to encode PE: {:?}", e))?;
Ok(format!("{}\n{}", pe_source, encoded))
}
pub fn projection2_source() -> Result<String, String> {
let pe_source = pe_source_text();
let compiler_source = replace_word(&replace_word(&pe_source, "peExpr", "compileExpr"), "peBlock", "compileBlock");
Ok(format!("{}\n{}", CORE_TYPES_FOR_PE, compiler_source))
}
pub fn projection3_source() -> Result<String, String> {
let pe_source = pe_source_text();
let cogen_source = replace_word(&replace_word(&pe_source, "peExpr", "cogenExpr"), "peBlock", "cogenBlock");
Ok(format!("{}\n{}", CORE_TYPES_FOR_PE, cogen_source))
}
pub fn run_logos_source(source: &str) -> Result<String, String> {
let compile_output = compile_program_full(source)
.map_err(|e| format!("Compilation failed: {:?}", e))?;
let temp_base = std::env::temp_dir().join("logos_run_source");
std::fs::create_dir_all(&temp_base)
.map_err(|e| format!("mkdir failed: {}", e))?;
let pkg_name = format!(
"logos_run_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let project_dir = temp_base.join(&pkg_name);
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
let cargo_toml = format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"
[dependencies]
logicaffeine-data = {{ path = "{}/crates/logicaffeine_data" }}
logicaffeine-system = {{ path = "{}/crates/logicaffeine_system", features = ["full"] }}
tokio = {{ version = "1", features = ["rt-multi-thread", "macros"] }}
serde = {{ version = "1", features = ["derive"] }}
rayon = "1"
"#,
pkg_name,
workspace_root.display(),
workspace_root.display(),
);
std::fs::create_dir_all(project_dir.join("src"))
.map_err(|e| format!("mkdir failed: {}", e))?;
std::fs::write(project_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Write Cargo.toml failed: {}", e))?;
std::fs::write(project_dir.join("src/main.rs"), &compile_output.rust_code)
.map_err(|e| format!("Write main.rs failed: {}", e))?;
let target_dir = std::env::temp_dir().join("logos_e2e_cache");
std::fs::create_dir_all(&target_dir)
.map_err(|e| format!("mkdir target failed: {}", e))?;
let output = std::process::Command::new("cargo")
.args(["run", "--quiet"])
.current_dir(&project_dir)
.env("CARGO_TARGET_DIR", &target_dir)
.env("RUST_MIN_STACK", "268435456")
.output()
.map_err(|e| format!("cargo run failed: {}", e))?;
let _ = std::fs::remove_dir_all(&project_dir);
if !output.status.success() {
return Err(format!(
"Execution failed:\nstderr: {}\nstdout: {}",
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout),
));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
pub struct GenuineProjectionResult {
pub source: String,
pub block_entry: String,
pub expr_entry: Option<String>,
}
fn discover_entry_points(residual: &str, block_prefix: &str, expr_prefix: &str)
-> (String, Option<String>)
{
let mut block_entry = String::new();
let mut expr_entry = None;
for line in residual.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("## To ") {
let name = rest.split(" (").next().unwrap_or("").trim();
if name.starts_with(block_prefix) && block_entry.is_empty() {
block_entry = name.to_string();
} else if name.starts_with(expr_prefix) && expr_entry.is_none() {
expr_entry = Some(name.to_string());
}
}
}
(block_entry, expr_entry)
}
pub fn projection1_source_real(core_types: &str, _interpreter: &str, program: &str) -> Result<String, String> {
let full_source = if program.contains("## Main") || program.contains("## To ") {
program.to_string()
} else {
format!("## Main\n{}", program)
};
let encoded = encode_program_source(&full_source)
.map_err(|e| format!("Failed to encode program: {:?}", e))?;
let pe_source = pe_source_text();
let decompile_source = decompile_source_text();
let actual_core_types = if core_types.is_empty() { CORE_TYPES_FOR_PE } else { core_types };
let driver = r#"
Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let source be decompileBlock(residual, 0).
Show source.
"#;
let combined = format!(
"{}\n{}\n{}\n## Main\n{}\n{}",
actual_core_types,
pe_source,
decompile_source,
encoded,
driver,
);
let raw_residual = run_logos_source(&combined)?;
let trimmed = raw_residual.trim();
if trimmed.is_empty() {
return Ok("## Main\n".to_string());
}
if trimmed.contains("## To ") {
Ok(trimmed.to_string())
} else {
Ok(format!("## Main\n{}", trimmed))
}
}
pub fn run_genuine_p2_on_target(program: &str, core_types: &str, interpreter: &str) -> Result<String, String> {
let pe_mini = pe_mini_source_text();
let pe = pe_source_text();
let full_source = if program.contains("## Main") || program.contains("## To ") {
program.to_string()
} else {
format!("## Main\n{}", program)
};
let target_encoded = encode_program_source(&full_source)
.map_err(|e| format!("Failed to encode target: {:?}", e))?;
let pe_mini_prog = format!(
"{}\n{}\n{}\n## Main\n{}\n\
Let compileEnv be a new Map of Text to CVal.\n\
Let compileState be makePeState(compileEnv, encodedFuncMap, 200).\n\
Let compiled be peBlockM(encodedMain, compileState).\n\
Let runEnv be a new Map of Text to CVal.\n\
coreExecBlock(compiled, runEnv, encodedFuncMap).\n",
core_types, pe_mini, interpreter, target_encoded
);
let encoded = encode_program_source_compact(&pe_mini_prog)
.map_err(|e| format!("Failed to encode pe_mini+target for P2: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 500).
Let residual be peBlock(encodedMain, state).
Let allFuncs be peFuncs(state).
Let runEnv be a new Map of Text to CVal.
coreExecBlock(residual, runEnv, allFuncs).
"#;
let combined = format!(
"{}\n{}\n{}\n## Main\n{}\n{}",
CORE_TYPES_FOR_PE, pe, interpreter, encoded, driver
);
run_logos_source(&combined)
}
pub fn run_genuine_p3_on_target(program: &str, core_types: &str, interpreter: &str) -> Result<String, String> {
let pe_bti = pe_bti_source_text();
let pe = pe_source_text();
let full_source = if program.contains("## Main") || program.contains("## To ") {
program.to_string()
} else {
format!("## Main\n{}", program)
};
let bti_types = CORE_TYPES_FOR_PE
.replace("specResults", "memoCache")
.replace("onStack", "callGuard");
let target_encoded = encode_program_source(&full_source)
.map_err(|e| format!("Failed to encode target: {:?}", e))?;
let pe_bti_prog = format!(
"{}\n{}\n{}\n## Main\n{}\n\
Let compileEnv be a new Map of Text to CVal.\n\
Let compileState be makePeState(compileEnv, encodedFuncMap, 200).\n\
Let compiled be peBlockB(encodedMain, compileState).\n\
Let runEnv be a new Map of Text to CVal.\n\
coreExecBlock(compiled, runEnv, encodedFuncMap).\n",
bti_types, pe_bti, interpreter, target_encoded
);
let encoded = encode_program_source_compact(&pe_bti_prog)
.map_err(|e| format!("Failed to encode pe_bti+target for P3: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let runEnv be a new Map of Text to CVal.
coreExecBlock(residual, runEnv, encodedFuncMap).
"#;
let combined = format!(
"{}\n{}\n{}\n## Main\n{}\n{}",
CORE_TYPES_FOR_PE, pe, interpreter, encoded, driver
);
run_logos_source(&combined)
}
pub fn projection2_source_real(_core_types: &str, _interpreter: &str) -> Result<GenuineProjectionResult, String> {
let pe_mini = pe_mini_source_text();
let pe = pe_source_text();
let decompile = decompile_source_text();
let program = format!(
"{}\n{}\n## Main\n Let env be a new Map of Text to CVal.\n Let funcs be a new Map of Text to CFunc.\n Let state be makePeState(env, funcs, 200).\n Let result be peBlockM(targetStmts, state).\n Show \"done\".\n",
CORE_TYPES_FOR_PE, pe_mini
);
let encoded = encode_program_source_compact(&program)
.map_err(|e| format!("Failed to encode pe_mini for P2: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let nl be chr(10).
Let mutable output be "".
Let specFuncs be peFuncs(state).
Let mutable allNames be collectCallNames(residual).
Let mutable emitted be a new Map of Text to Bool.
Let mutable changed be true.
While changed:
Set changed to false.
Let mutable toAdd be a new Seq of Text.
Repeat for fnKey in allNames:
Let fkStr be "{fnKey}".
If emitted contains fkStr:
Let skipE be true.
Otherwise:
Set item fkStr of emitted to true.
Let fkStr2 be "{fnKey}".
If specFuncs contains fkStr2:
Let fdef be item fkStr2 of specFuncs.
Inspect fdef:
When CFuncDef (fn0, ps0, pt0, rt0, body0):
Let children be collectCallNames(body0).
Repeat for child in children:
Let childStr be "{child}".
If not emitted contains childStr:
Push child to toAdd.
Set changed to true.
Otherwise:
Let skipF be true.
Repeat for ta in toAdd:
Push ta to allNames.
Repeat for fnKey in allNames:
Let fkStr be "{fnKey}".
If specFuncs contains fkStr:
Let fdef be item fkStr of specFuncs.
Let funcSrc be decompileFunc(fdef).
If the length of funcSrc is greater than 0:
Set output to "{output}{funcSrc}{nl}".
Let mainSrc be decompileBlock(residual, 0).
Set output to "{output}## Main{nl}{mainSrc}".
Show output.
"#;
let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
let result = run_logos_source(&combined)?;
let result = fix_decompiled_types(&result, &[
("peExprM_", "(e: CExpr) -> CExpr:"),
("peBlockM_", "(stmts: Seq of CStmt) -> Seq of CStmt:"),
("checkLiteralM_", "(e: CExpr) -> Bool:"),
("exprToValM_", "(e: CExpr) -> CVal:"),
("valToExprM_", "(v: CVal) -> CExpr:"),
("evalBinOpM_", "(binOp: Text) and (lv: CVal) and (rv: CVal) -> CVal:"),
("isCopyPropSafeM_", "(e: CExpr) -> Bool:"),
("checkVNothingM_", "(v: CVal) -> Bool:"),
("hasReturnM_", "(stmts: Seq of CStmt) -> Bool:"),
("extractReturnM_", "(stmts: Seq of CStmt) -> CExpr:"),
("validateExtractReturnM_", "(result: CExpr) and (bodyStmts: Seq of CStmt) -> CExpr:"),
("makeKeyM_", "(fnName: Text) and (args: Seq of CExpr) -> Text:"),
("exprToKeyPartM_", "(e: CExpr) -> Text:"),
("collectSetVarsM_", "(stmts: Seq of CStmt) -> Seq of Text:"),
("peEnvM_", "(st: PEMiniState) -> Map of Text to CVal:"),
("peFuncsM_", "(st: PEMiniState) -> Map of Text to CFunc:"),
("peDepthM_", "(st: PEMiniState) -> Int:"),
("peStaticEnvM_", "(st: PEMiniState) -> Map of Text to CExpr:"),
("peMemoCacheM_", "(st: PEMiniState) -> Map of Text to CExpr:"),
("peStateWithEnvDepthM_", "(st: PEMiniState) and (newEnv: Map of Text to CVal) and (d: Int) -> PEMiniState:"),
("peStateWithEnvDepthStaticM_", "(st: PEMiniState) and (newEnv: Map of Text to CVal) and (d: Int) and (newSe: Map of Text to CExpr) -> PEMiniState:"),
]);
let (block_entry, expr_entry) = discover_entry_points(&result, "peBlockM_", "peExprM_");
if block_entry.is_empty() {
return Err("Genuine P2: no peBlockM_ entry found in residual".to_string());
}
let func_defs_only = strip_main_block(&result);
let pe_mini_helpers = pe_mini_source_text();
let alias = format!(
"\n## To compileBlock (stmts: Seq of CStmt) -> Seq of CStmt:\n Return {}(stmts).\n",
block_entry
);
let combined = format!("{}\n{}\n{}", pe_mini_helpers, func_defs_only, alias);
let full_source = deduplicate_functions(&combined);
Ok(GenuineProjectionResult {
source: full_source,
block_entry: "compileBlock".to_string(),
expr_entry,
})
}
pub fn genuine_projection2_residual() -> Result<String, String> {
let pe_mini = pe_mini_source_text();
let pe = pe_source_text();
let decompile = decompile_source_text();
let program = format!(
"{}\n{}\n## Main\n Let env be a new Map of Text to CVal.\n Let funcs be a new Map of Text to CFunc.\n Let state be makePeState(env, funcs, 200).\n Let result be peBlockM(targetStmts, state).\n Show \"done\".\n",
CORE_TYPES_FOR_PE, pe_mini
);
let encoded = encode_program_source_compact(&program)
.map_err(|e| format!("Failed to encode pe_mini: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let nl be chr(10).
Let mutable output be "".
Let specFuncs be peFuncs(state).
Let specNames be collectCallNames(residual).
Repeat for sn in specNames:
Let snKey be "{sn}".
If specFuncs contains snKey:
Let fdef be item snKey of specFuncs.
Let funcSrc be decompileFunc(fdef).
If the length of funcSrc is greater than 0:
Set output to "{output}{funcSrc}{nl}".
Let mainSrc be decompileBlock(residual, 0).
Set output to "{output}## Main{nl}{mainSrc}".
Show output.
"#;
let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
let result = run_logos_source(&combined)?;
Ok(result)
}
pub fn genuine_projection3_residual() -> Result<String, String> {
let pe_bti = pe_bti_source_text();
let pe = pe_source_text();
let decompile = decompile_source_text();
let bti_types = CORE_TYPES_FOR_PE
.replace("specResults", "memoCache")
.replace("onStack", "callGuard");
let program = format!(
"{}\n{}\n## Main\n Let env be a new Map of Text to CVal.\n Let funcs be a new Map of Text to CFunc.\n Let state be makePeState(env, funcs, 200).\n Let result be peBlockB(targetStmts, state).\n Show \"done\".\n",
bti_types, pe_bti
);
let encoded = encode_program_source_compact(&program)
.map_err(|e| format!("Failed to encode pe_bti: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let nl be chr(10).
Let mutable output be "".
Let specFuncs be peFuncs(state).
Let specNames be collectCallNames(residual).
Repeat for sn in specNames:
Let snKey be "{sn}".
If specFuncs contains snKey:
Let fdef be item snKey of specFuncs.
Let funcSrc be decompileFunc(fdef).
If the length of funcSrc is greater than 0:
Set output to "{output}{funcSrc}{nl}".
Let mainSrc be decompileBlock(residual, 0).
Set output to "{output}## Main{nl}{mainSrc}".
Show output.
"#;
let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
let result = run_logos_source(&combined)?;
Ok(result)
}
pub fn projection3_source_real(_core_types: &str) -> Result<GenuineProjectionResult, String> {
let pe_bti = pe_bti_source_text();
let pe = pe_source_text();
let decompile = decompile_source_text();
let bti_types = CORE_TYPES_FOR_PE
.replace("specResults", "memoCache")
.replace("onStack", "callGuard");
let program = format!(
"{}\n{}\n## Main\n Let env be a new Map of Text to CVal.\n Let funcs be a new Map of Text to CFunc.\n Let state be makePeState(env, funcs, 200).\n Let result be peBlockB(targetStmts, state).\n Show \"done\".\n",
bti_types, pe_bti
);
let encoded = encode_program_source_compact(&program)
.map_err(|e| format!("Failed to encode pe_bti for P3: {:?}", e))?;
let driver = r#" Let state be makePeState(a new Map of Text to CVal, encodedFuncMap, 200).
Let residual be peBlock(encodedMain, state).
Let nl be chr(10).
Let mutable output be "".
Let specFuncs be peFuncs(state).
Let mutable allNames be collectCallNames(residual).
Let mutable emitted be a new Map of Text to Bool.
Let mutable changed be true.
While changed:
Set changed to false.
Let mutable toAdd be a new Seq of Text.
Repeat for fnKey in allNames:
Let fkStr be "{fnKey}".
If emitted contains fkStr:
Let skipE be true.
Otherwise:
Set item fkStr of emitted to true.
Let fkStr2 be "{fnKey}".
If specFuncs contains fkStr2:
Let fdef be item fkStr2 of specFuncs.
Inspect fdef:
When CFuncDef (fn0, ps0, pt0, rt0, body0):
Let children be collectCallNames(body0).
Repeat for child in children:
Let childStr be "{child}".
If not emitted contains childStr:
Push child to toAdd.
Set changed to true.
Otherwise:
Let skipF be true.
Repeat for ta in toAdd:
Push ta to allNames.
Repeat for fnKey in allNames:
Let fkStr be "{fnKey}".
If specFuncs contains fkStr:
Let fdef be item fkStr of specFuncs.
Let funcSrc be decompileFunc(fdef).
If the length of funcSrc is greater than 0:
Set output to "{output}{funcSrc}{nl}".
Let mainSrc be decompileBlock(residual, 0).
Set output to "{output}## Main{nl}{mainSrc}".
Show output.
"#;
let combined = format!("{}\n{}\n{}\n## Main\n{}\n{}", CORE_TYPES_FOR_PE, pe, decompile, encoded, driver);
let result = run_logos_source(&combined)?;
let result = fix_decompiled_types(&result, &[
("peExprB_", "(e: CExpr) -> CExpr:"),
("peBlockB_", "(stmts: Seq of CStmt) -> Seq of CStmt:"),
("isStatic_", "(e: CExpr) -> Bool:"),
("isLiteral_", "(e: CExpr) -> Bool:"),
("allStatic_", "(args: Seq of CExpr) -> Bool:"),
("exprToVal_", "(e: CExpr) -> CVal:"),
("valToExpr_", "(v: CVal) -> CExpr:"),
("evalBinOp_", "(binOp: Text) and (lv: CVal) and (rv: CVal) -> CVal:"),
("isCopyPropSafe_", "(e: CExpr) -> Bool:"),
("isVNothing_", "(v: CVal) -> Bool:"),
("hasReturn_", "(stmts: Seq of CStmt) -> Bool:"),
("extractReturnB_", "(stmts: Seq of CStmt) -> CExpr:"),
("makeKey_", "(fnName: Text) and (args: Seq of CExpr) -> Text:"),
("exprToKeyPartB_", "(e: CExpr) -> Text:"),
("collectSetVars_", "(stmts: Seq of CStmt) -> Seq of Text:"),
]);
let (block_entry, expr_entry) = discover_entry_points(&result, "peBlockB_", "peExprB_");
if block_entry.is_empty() {
return Err("Genuine P3: no peBlockB_ entry found in residual".to_string());
}
let func_defs_only = strip_main_block(&result);
let pe_bti_helpers = pe_bti_source_text();
let alias = format!(
"\n## To cogenBlock (stmts: Seq of CStmt) -> Seq of CStmt:\n Return {}(stmts).\n",
block_entry
);
let combined = format!("{}\n{}\n{}", pe_bti_helpers, func_defs_only, alias);
let full_source = deduplicate_functions(&combined);
Ok(GenuineProjectionResult {
source: full_source,
block_entry: "cogenBlock".to_string(),
expr_entry,
})
}
fn deduplicate_functions(source: &str) -> String {
let mut seen = std::collections::HashSet::new();
let mut result = String::with_capacity(source.len());
let mut skip_until_next = false;
for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("## To ") {
let name = rest.split(' ').next().unwrap_or("");
if !seen.insert(name.to_string()) {
skip_until_next = true;
continue;
}
skip_until_next = false;
} else if trimmed.starts_with("## Main") {
skip_until_next = false;
} else if skip_until_next {
if !trimmed.starts_with("## ") {
continue;
}
skip_until_next = false;
}
result.push_str(line);
result.push('\n');
}
result
}
fn strip_main_block(source: &str) -> String {
let mut result = String::with_capacity(source.len());
let mut in_main = false;
for line in source.lines() {
let trimmed = line.trim();
if trimmed == "## Main" {
in_main = true;
continue;
}
if in_main {
if trimmed.starts_with("## To ") {
in_main = false;
} else {
continue;
}
}
result.push_str(line);
result.push('\n');
}
result
}
fn extract_main_block(source: &str) -> String {
let mut result = String::new();
let mut in_main = false;
for line in source.lines() {
let trimmed = line.trim();
if trimmed == "## Main" {
in_main = true;
continue;
}
if in_main {
if trimmed.starts_with("## To ") {
break;
}
result.push_str(line);
result.push('\n');
}
}
result
}
fn fix_decompiled_types(source: &str, type_map: &[(&str, &str)]) -> String {
let mut result = String::with_capacity(source.len());
for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("## To ") {
let name = rest.split(' ').next().unwrap_or("");
let mut fixed = false;
for (prefix, sig) in type_map {
if name.starts_with(prefix) {
result.push_str(&format!("## To {} {}\n", name, sig));
fixed = true;
break;
}
}
if !fixed {
result.push_str(line);
result.push('\n');
}
} else {
result.push_str(line);
result.push('\n');
}
}
let result = result
.replace("Seq of Any", "Seq of CExpr")
.replace("Set of Any", "Set of CExpr")
.replace(": Any)", ": CExpr)")
.replace("-> Any:", "-> CExpr:");
result
}
fn replace_word(source: &str, from: &str, to: &str) -> String {
let mut result = String::with_capacity(source.len());
let mut remaining = source;
while let Some(pos) = remaining.find(from) {
let before = if pos > 0 { remaining.as_bytes()[pos - 1] } else { b' ' };
let after_pos = pos + from.len();
let after = if after_pos < remaining.len() { remaining.as_bytes()[after_pos] } else { b' ' };
let is_word = !before.is_ascii_alphanumeric() && before != b'_'
&& !after.is_ascii_alphanumeric() && after != b'_';
result.push_str(&remaining[..pos]);
if is_word {
result.push_str(to);
} else {
result.push_str(from);
}
remaining = &remaining[after_pos..];
}
result.push_str(remaining);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_let_statement() {
let source = "## Main\nLet x be 5.";
let result = compile_to_rust(source);
assert!(result.is_ok(), "Should compile: {:?}", result);
let rust = result.unwrap();
assert!(rust.contains("fn main()"));
assert!(rust.contains("let x = 5;"));
}
#[test]
fn test_compile_return_statement() {
let source = "## Main\nReturn 42.";
let result = compile_to_rust(source);
assert!(result.is_ok(), "Should compile: {:?}", result);
let rust = result.unwrap();
assert!(rust.contains("return 42;"));
}
}