use clap::{Parser as ClapParser, Subcommand};
use serde_json;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use ternlang_core::parser::Parser;
use ternlang_core::codegen::betbc::BytecodeEmitter;
use ternlang_core::codegen::tern_asm::emit_tern_asm;
use ternlang_core::vm::{BetVm, Value};
use ternlang_core::ModuleResolver;
use ternlang_hdl::{BetSimEmitter, BetRtlProcessor};
use ternlang_runtime::TernNode;
use walkdir::WalkDir;
use colored::*;
#[derive(ClapParser)]
#[command(name = "ternlang")]
#[command(about = "Ternlang — Balanced Ternary Intelligence Stack\n\nUsage:\n ternlang → interactive REPL\n ternlang <file.tern> → run a .tern file directly\n ternlang run <file.tern> → same as above\n ternlang repl → interactive REPL\n ternlang build <file.tern> → compile to bytecode\n ternlang fmt <file.tern> → format source\n ternlang test [path] → run test suite", long_about = None)]
struct Cli {
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Run {
file: PathBuf,
#[arg(long, value_name = "ADDR")]
node_addr: Option<String>,
#[arg(long, value_name = "ADDR")]
peer: Vec<String>,
#[arg(long)]
emit_symbols: bool,
#[arg(long)]
debug: bool,
},
Check {
file: PathBuf,
},
Exec {
file: PathBuf,
#[arg(long)]
debug: bool,
},
Build {
file: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
emit_tern: bool,
},
Repl,
Fmt {
file: PathBuf,
#[arg(short, long)]
write: bool,
},
Sim {
file: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long)]
run: bool,
#[arg(long)]
rtl: bool,
#[arg(long, default_value = "10000")]
max_cycles: u64,
},
HdlSynth {
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long)]
synth: bool,
},
Test {
#[arg(default_value = ".")]
path: PathBuf,
},
Audit {
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
#[arg(short, long, value_name = "OUT")]
output: Option<PathBuf>,
#[arg(long)]
html: bool,
},
Translate {
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
#[arg(short, long, default_value = "python")]
language: String,
#[arg(short, long, value_name = "OUT")]
output: Option<PathBuf>,
},
}
fn main() {
let cli = Cli::parse();
let command = match (cli.file, cli.command) {
(Some(f), _) => Commands::Run { file: f, node_addr: None, peer: vec![], emit_symbols: false, debug: false },
(None, Some(c)) => c,
(None, None) => Commands::Repl,
};
match &command {
Commands::Run { file, node_addr, peer, emit_symbols, debug } => {
let input = fs::read_to_string(file).expect("Failed to read file");
let mut parser = Parser::new(&input);
let mut emitter = BytecodeEmitter::new();
let header_patch = emitter.emit_header_jump();
match parser.parse_program() {
Ok(mut prog) => {
ModuleResolver::from_source_file(file).resolve(&mut prog);
emitter.emit_program(&prog);
emitter.patch_header_jump(header_patch);
emitter.emit_entry_call("main");
}
Err(e) => {
let error_str = format!("{:?}", e);
if error_str.contains("ExpectedToken(\"Fn\"") || error_str.contains("UnexpectedToken(\"Let\"") {
let mut parser = Parser::new(&input);
emitter.patch_header_jump(header_patch);
let mut found_functions = false;
loop {
match parser.parse_stmt() {
Ok(stmt) => emitter.emit_stmt(&stmt),
Err(e) => {
let e_str = format!("{:?}", e);
if e_str.contains("EOF") {
break;
}
if e_str.contains("UnexpectedToken(\"Fn\")") {
match parser.parse_function() {
Ok(func) => {
emitter.emit_function(&func);
found_functions = true;
}
Err(e2) => {
eprintln!("Parse fn error: {:?}", e2);
break;
}
}
} else {
eprintln!("Parse stmt error: {:?}", e);
break;
}
}
}
}
if found_functions {
emitter.emit_entry_call("main");
}
} else {
eprintln!("Parse program error: {:?}", e);
std::process::exit(1);
}
}
}
if *emit_symbols {
let sym_map = emitter.get_function_symbols("main")
.or_else(|| Some(emitter.get_symbols()));
if let Some(map) = sym_map {
let mut parts: Vec<String> = map
.iter()
.filter(|(k, _)| !k.contains('.') && !k.contains('['))
.map(|(k, v)| format!("{}={}", k, v))
.collect();
parts.sort();
eprintln!("TERN_SYMBOLS:{}", parts.join(","));
}
}
let code = emitter.finalize();
let byte_count = code.len();
let sym_rev: std::collections::HashMap<u8, String> = {
let map = emitter.get_symbols();
map.iter()
.filter(|(k, _)| !k.contains('.') && !k.contains('['))
.map(|(k, v)| (*v, k.clone()))
.collect()
};
let mut vm = BetVm::new(code);
emitter.register_agents(&mut vm);
if let Some(addr) = node_addr {
let node = Arc::new(TernNode::new(addr));
node.listen();
eprintln!("[runtime] TernNode listening on {}", addr);
vm.set_node_id(addr.clone());
for peer_addr in peer {
match node.connect(peer_addr) {
Ok(()) => eprintln!("[runtime] connected to peer {}", peer_addr),
Err(e) => eprintln!("[runtime] peer {} unreachable: {}", peer_addr, e),
}
}
vm.set_remote(node);
}
let sep = "─".repeat(52);
println!("{}", sep.dimmed());
if *debug {
println!(" {} {} {}", "ternlang".bold(), file.display().to_string().cyan(), "[debug]".yellow());
} else {
println!(" {} {}", "ternlang".bold(), file.display().to_string().cyan());
}
println!(" compiled {} bytes · BET bytecode", byte_count.to_string().dimmed());
println!("{}", sep.dimmed());
match vm.run() {
Ok(_) => {
if *debug {
let mut printed_any = false;
for i in 0u8..32 {
let val = vm.get_register(i);
let is_default = matches!(val, Value::Trit(ternlang_core::trit::Trit::Tend));
if !is_default {
if !printed_any {
println!(" {}", "registers (non-zero):".bold());
printed_any = true;
}
let name_hint = sym_rev.get(&i)
.map(|n| format!(" [{}]", n).dimmed().to_string())
.unwrap_or_default();
let val_str = format_value(&val);
println!(" r{:<3} {}{}", i, val_str, name_hint);
}
}
if printed_any { println!("{}", sep.dimmed()); }
}
print_run_result(&vm, &sep);
if !debug {
println!();
println!(" {} ternlang build {} → emits .tbc bytecode to disk",
"tip:".dimmed(), file.display().to_string().dimmed());
println!(" {} ternlang exec <file.tbc> → run pre-compiled bytecode",
" ".dimmed());
println!(" {} ternlang run {} --debug → show register state",
" ".dimmed(), file.display().to_string().dimmed());
}
}
Err(e) => {
println!("{}", sep.dimmed());
eprintln!(" {} VM error: {}", "✗".red().bold(), e);
std::process::exit(1);
}
}
}
Commands::Check { file } => {
run_check(file);
}
Commands::Exec { file, debug } => {
let code = fs::read(file).unwrap_or_else(|e| {
eprintln!(" {} cannot read {:?}: {}", "✗".red().bold(), file, e);
std::process::exit(1);
});
let sep = "─".repeat(52);
println!("{}", sep.dimmed());
if *debug {
println!(" {} {} {}", "ternlang exec".bold(), file.display().to_string().cyan(), "[debug]".yellow());
} else {
println!(" {} {}", "ternlang exec".bold(), file.display().to_string().cyan());
}
println!(" bytecode {} bytes · pre-compiled .tbc", code.len().to_string().dimmed());
println!("{}", sep.dimmed());
let mut vm = BetVm::new(code);
if *debug {
let sym_rev: std::collections::HashMap<u8, String> = std::collections::HashMap::new();
match vm.run() {
Ok(_) => {
let mut printed_any = false;
for i in 0u8..32 {
let val = vm.get_register(i);
let is_default = matches!(val, Value::Trit(ternlang_core::trit::Trit::Tend));
if !is_default {
if !printed_any {
println!(" {}", "registers (non-zero):".bold());
printed_any = true;
}
let name_hint = sym_rev.get(&i)
.map(|n| format!(" [{}]", n).dimmed().to_string())
.unwrap_or_default();
println!(" r{:<3} {}{}", i, format_value(&val), name_hint);
}
}
if printed_any { println!("{}", sep.dimmed()); }
print_run_result(&vm, &sep);
}
Err(e) => {
println!("{}", sep.dimmed());
eprintln!(" {} VM error: {}", "✗".red().bold(), e);
std::process::exit(1);
}
}
} else {
match vm.run() {
Ok(_) => print_run_result(&vm, &sep),
Err(e) => {
println!("{}", sep.dimmed());
eprintln!(" {} VM error: {}", "✗".red().bold(), e);
std::process::exit(1);
}
}
}
}
Commands::Repl => {
run_repl();
}
Commands::Fmt { file, write } => {
run_fmt(file, *write);
}
Commands::Sim { file, output, run, rtl, max_cycles } => {
if *rtl {
run_rtl_sim(file, *max_cycles);
} else {
run_sim(file, output.as_deref(), *run);
}
}
Commands::HdlSynth { output, synth } => {
run_hdl_synth(output.as_deref(), *synth);
}
Commands::Test { path } => {
run_tests(path);
}
Commands::Translate { file, language, output } => {
run_translate(file.as_deref(), language, output.as_deref());
}
Commands::Audit { file, output, html } => {
run_audit(file.as_deref(), output.as_deref(), *html);
}
Commands::Build { file, output, emit_tern } => {
let input = fs::read_to_string(file).expect("Failed to read file");
let mut parser = Parser::new(&input);
let resolver = ModuleResolver::from_source_file(file);
let prog_result = parser.parse_program().map(|mut p| { resolver.resolve(&mut p); p });
if *emit_tern {
let prog = match prog_result {
Ok(p) => p,
Err(e) => { eprintln!("Parse error: {:?}", e); std::process::exit(1); }
};
let asm = emit_tern_asm(&prog);
let out_path = output.clone().unwrap_or_else(|| {
let mut path = file.clone();
path.set_extension("tern.asm");
path
});
fs::write(&out_path, &asm).expect("Failed to write TERN assembly");
println!("TERN assembly written to {}", out_path.display());
} else {
let mut emitter = BytecodeEmitter::new();
let header_patch = emitter.emit_header_jump();
match prog_result {
Ok(prog) => {
emitter.emit_program(&prog);
emitter.patch_header_jump(header_patch);
emitter.emit_entry_call("main");
}
Err(_) => {
let mut parser = Parser::new(&input);
emitter.patch_header_jump(header_patch);
while let Ok(stmt) = parser.parse_stmt() {
emitter.emit_stmt(&stmt);
}
}
}
let code = emitter.finalize();
let out_path = output.clone().unwrap_or_else(|| {
let mut path = file.clone();
path.set_extension("tbc");
path
});
let byte_count = code.len();
fs::write(&out_path, code).expect("Failed to write bytecode");
let sep = "─".repeat(52);
println!("{}", sep.dimmed());
println!(" {} {}", "compiled".green().bold(), out_path.display().to_string().cyan());
println!(" {} {} bytes · run with: ternlang exec {}",
"size".dimmed(), byte_count, out_path.display());
println!("{}", sep.dimmed());
}
}
}
}
fn run_rtl_sim(file: &std::path::PathBuf, max_cycles: u64) {
let input = fs::read_to_string(file).expect("Failed to read file");
let mut parser = Parser::new(&input);
let mut emitter = BytecodeEmitter::new();
match parser.parse_program() {
Ok(mut prog) => { ModuleResolver::from_source_file(file).resolve(&mut prog); emitter.emit_program(&prog); }
Err(_) => {
let mut parser = Parser::new(&input);
while let Ok(stmt) = parser.parse_stmt() { emitter.emit_stmt(&stmt); }
}
}
let code = emitter.finalize();
println!("BET RTL Simulator — Phase 6.1");
println!("Bytecode: {} bytes | Max cycles: {}", code.len(), max_cycles);
println!("{}", "─".repeat(52));
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(max_cycles);
println!(" Cycles elapsed : {}", trace.cycles);
println!(" Halted cleanly : {}", trace.halted);
println!("{}", "─".repeat(52));
println!(" Final registers (0–9):");
for (i, &v) in trace.final_regs.iter().take(10).enumerate() {
let label = match v { 1 => "+1 (truth)", -1 => "-1 (conflict)", _ => " 0 (hold)" };
println!(" r{:02}: {}", i, label);
}
if !trace.final_stack.is_empty() {
println!(" Final stack (top→bottom): {:?}", trace.final_stack.iter().rev().collect::<Vec<_>>());
} else {
println!(" Final stack: empty");
}
if trace.cycles_state.len() > 1 {
println!("{}", "─".repeat(52));
println!(" Last {} cycles:", trace.cycles_state.len().min(5));
for snap in trace.cycles_state.iter().rev().take(5).rev() {
println!(" [cy {:>4}] pc={:04x} op=0x{:02x} stack={:?}",
snap.cycle, snap.pc, snap.opcode, snap.stack);
}
}
if !trace.halted {
eprintln!(" WARNING: max_cycles ({}) reached without THALT", max_cycles);
}
}
fn run_sim(file: &std::path::PathBuf, output: Option<&std::path::Path>, run: bool) {
let input = fs::read_to_string(file).expect("Failed to read file");
let mut parser = Parser::new(&input);
let mut emitter = BytecodeEmitter::new();
match parser.parse_program() {
Ok(mut prog) => {
ModuleResolver::from_source_file(file).resolve(&mut prog);
emitter.emit_program(&prog);
}
Err(e) => {
eprintln!("Parse error: {:?}", e);
return;
}
}
let code = emitter.finalize();
println!("Compiled: {} bytes of BET bytecode", code.len());
let sim = BetSimEmitter::new();
let tb = sim.emit_testbench(&code);
let tb_path = output
.map(|p| p.to_path_buf())
.unwrap_or_else(|| {
let mut p = file.clone();
p.set_extension("sim.v");
p
});
fs::write(&tb_path, &tb).expect("Failed to write testbench");
println!("Testbench: {}", tb_path.display());
if run {
if BetSimEmitter::iverilog_available() {
let path_str = tb_path.to_string_lossy();
match BetSimEmitter::run_iverilog(&path_str) {
Ok(output) => {
println!("\n--- Simulation output ---");
print!("{}", output);
}
Err(e) => eprintln!("Simulation failed: {}", e),
}
} else {
println!("iverilog not found on PATH. To run the simulation:");
println!(" sudo apt install iverilog");
println!(" iverilog -o bet_sim.vvp {} && vvp bet_sim.vvp", tb_path.display());
}
} else {
println!("\nTo run with Icarus Verilog:");
println!(" iverilog -o bet_sim.vvp -g2001 {} && vvp bet_sim.vvp", tb_path.display());
println!(" # Open bet_sim.vcd in GTKWave for waveform inspection");
}
}
fn print_run_result(vm: &BetVm, sep: &str) {
let result = vm.peek_stack();
match &result {
Some(Value::Trit(ternlang_core::trit::Trit::Reject)) => {
println!(" {} {}", "result".bold(), "-1 reject".red().bold());
println!("{}", sep.dimmed());
eprintln!(" {} program returned reject (−1).", "✗".red().bold());
std::process::exit(1);
}
Some(Value::Trit(ternlang_core::trit::Trit::Tend)) => {
println!(" {} {}", "result".bold(), " 0 hold".yellow());
println!("{}", sep.dimmed());
println!(" {} program held — no final decision.", "●".yellow());
}
Some(Value::Trit(ternlang_core::trit::Trit::Affirm)) => {
println!(" {} {}", "result".bold(), "+1 affirm".green().bold());
println!("{}", sep.dimmed());
println!(" {} program exited cleanly.", "✓".green().bold());
}
Some(v) => {
println!(" {} {}", "result".bold(), format_value(v).green());
println!("{}", sep.dimmed());
println!(" {} program exited cleanly.", "✓".green().bold());
}
None => {
println!(" {} {}", "result".bold(), "(stack empty)".dimmed());
println!("{}", sep.dimmed());
println!(" {} program exited cleanly.", "✓".green().bold());
}
}
}
fn format_value(val: &Value) -> String {
match val {
Value::Trit(ternlang_core::trit::Trit::Affirm) => "+1 affirm".to_string(),
Value::Trit(ternlang_core::trit::Trit::Tend) => " 0 hold".to_string(),
Value::Trit(ternlang_core::trit::Trit::Reject) => "-1 reject".to_string(),
Value::Int(v) => format!("{} int", v),
Value::Float(f) => format!("{:.6} float", f),
Value::TensorRef(r) => format!("tensor@{}", r),
Value::AgentRef(a, _) => format!("agent:{}", a),
Value::String(s) => format!("{:?}", s),
}
}
fn run_check(path: &std::path::PathBuf) {
use std::io::Write;
let files: Vec<std::path::PathBuf> = if path.is_dir() {
WalkDir::new(path).into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |x| x == "tern"))
.map(|e| e.path().to_path_buf())
.collect()
} else {
vec![path.clone()]
};
if files.is_empty() {
eprintln!(" no .tern files found in {:?}", path);
std::process::exit(1);
}
let sep = "─".repeat(52);
println!("{}", sep.dimmed());
println!(" {} checking {} file{}", "ternlang check".bold(), files.len(),
if files.len() == 1 { "" } else { "s" });
println!("{}", sep.dimmed());
let mut errors = 0usize;
let mut ok = 0usize;
for f in &files {
let name = f.display().to_string();
print!(" {:<48}", name);
std::io::stdout().flush().unwrap();
let src = match fs::read_to_string(f) {
Ok(s) => s,
Err(e) => {
println!(" {} (read error: {})", "✗".red().bold(), e);
errors += 1;
continue;
}
};
let mut parser = Parser::new(&src);
let result = parser.parse_program();
match result {
Ok(prog) => {
let fn_count = prog.functions.len();
println!(" {} {} fn", "ok".green().bold(), fn_count);
ok += 1;
}
Err(e) => {
let mut p2 = Parser::new(&src);
let mut stmt_ok = true;
loop {
match p2.parse_stmt() {
Ok(_) => {}
Err(ref e2) if format!("{:?}", e2).contains("EOF") => break,
Err(_) => { stmt_ok = false; break; }
}
}
if stmt_ok {
println!(" {} (script)", "ok".green().bold());
ok += 1;
} else {
println!(" {} {:?}", "error".red().bold(), e);
errors += 1;
}
}
}
}
println!("{}", sep.dimmed());
if errors == 0 {
println!(" {} {} file{} checked, no errors.",
"✓".green().bold(), ok, if ok == 1 { "" } else { "s" });
} else {
println!(" {} {} error{} found.",
"✗".red().bold(), errors, if errors == 1 { "" } else { "s" });
std::process::exit(1);
}
}
fn run_repl() {
use std::io::{self, Write};
println!("ternlang REPL v0.1 — type a trit expression and press Enter. :q to quit.");
println!("Examples: consensus(1, 0) invert(-1) 1 + -1");
println!();
loop {
print!("tern> ");
io::stdout().flush().unwrap();
let mut line = String::new();
if io::stdin().read_line(&mut line).is_err() { break; }
let line = line.trim();
if line == ":q" || line == "quit" || line.is_empty() && line == "" {
if line == ":q" { break; }
if line.is_empty() { continue; }
}
let wrapped = format!("fn __repl__() -> trit {{ return {}; }}", line);
let mut parser = Parser::new(&wrapped);
match parser.parse_program() {
Err(e) => { eprintln!(" parse error: {:?}", e); continue; }
Ok(prog) => {
let mut emitter = BytecodeEmitter::new();
emitter.emit_program(&prog);
let code = emitter.finalize();
let mut vm = BetVm::new(code);
emitter.register_agents(&mut vm);
match vm.run() {
Ok(_) => {
let result = vm.get_register(0);
match result {
Value::Trit(t) => println!(" → {}", t),
Value::Int(v) => println!(" → {}", v),
Value::Float(f) => println!(" → {}", f),
Value::TensorRef(r) => println!(" → tensor_ref({})", r),
Value::AgentRef(a, _) => println!(" → agent_ref({})", a),
Value::String(s) => println!(" → {:?}", s),
}
}
Err(e) => eprintln!(" vm error: {}", e),
}
}
}
}
}
fn run_fmt(file: &std::path::PathBuf, write: bool) {
let input = std::fs::read_to_string(file).expect("Failed to read file");
let formatted = fmt_source(&input);
if write {
std::fs::write(file, &formatted).expect("Failed to write formatted file");
println!("Formatted {:?}", file);
} else {
print!("{}", formatted);
}
}
fn fmt_source(source: &str) -> String {
let mut out = String::new();
let mut in_match = false;
for line in source.lines() {
let trimmed = line.trim();
if in_match && (trimmed.starts_with("1 =>") || trimmed.starts_with("0 =>") || trimmed.starts_with("-1 =>")) {
let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect();
let (trit_str, rest) = if trimmed.starts_with("-1") {
("-1", &trimmed[2..])
} else if trimmed.starts_with('1') {
(" 1", &trimmed[1..])
} else {
(" 0", &trimmed[1..])
};
out.push_str(&format!("{}{}{}\n", indent, trit_str, rest));
continue;
}
if trimmed.starts_with("match ") { in_match = true; }
if trimmed == "}" && in_match { in_match = false; }
out.push_str(line);
out.push('\n');
}
out
}
fn run_hdl_synth(output: Option<&std::path::Path>, run_yosys: bool) {
use ternlang_hdl::{VerilogEmitter, BetIsaEmitter};
use std::process::Command;
let out_dir = output
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::path::PathBuf::from("bet_hdl"));
fs::create_dir_all(&out_dir).expect("Failed to create output directory");
println!("[hdl-synth] Emitting BET processor Verilog → {}/", out_dir.display());
let primitives: &[(&str, String)] = &[
("trit_neg.v", VerilogEmitter::trit_neg().render()),
("trit_cons.v", VerilogEmitter::trit_cons().render()),
("trit_mul.v", VerilogEmitter::trit_mul().render()),
("trit_add.v", VerilogEmitter::trit_add().render()),
("trit_reg.v", VerilogEmitter::trit_reg().render()),
("bet_alu.v", VerilogEmitter::bet_alu().render()),
("sparse_matmul_4x4.v", VerilogEmitter::sparse_matmul(4).render()),
];
for (name, src) in primitives {
let path = out_dir.join(name);
fs::write(&path, src).expect("Failed to write Verilog");
println!(" wrote {}", path.display());
}
let isa = BetIsaEmitter::new();
let isa_modules: &[(&str, String)] = &[
("bet_regfile.v", isa.emit_register_file()),
("bet_pc.v", isa.emit_program_counter()),
("bet_control.v", isa.emit_control_unit()),
("bet_processor.v",isa.emit_top()),
];
for (name, src) in isa_modules {
let path = out_dir.join(name);
fs::write(&path, src).expect("Failed to write Verilog");
println!(" wrote {}", path.display());
}
let ys_script = format!(
"# Auto-generated Yosys synthesis script\n\
# Run: yosys {}/synth_bet.ys\n\
read_verilog {0}/trit_neg.v\n\
read_verilog {0}/trit_cons.v\n\
read_verilog {0}/trit_mul.v\n\
read_verilog {0}/trit_add.v\n\
read_verilog {0}/trit_reg.v\n\
read_verilog {0}/bet_alu.v\n\
read_verilog {0}/bet_regfile.v\n\
read_verilog {0}/bet_pc.v\n\
read_verilog {0}/bet_control.v\n\
read_verilog {0}/bet_processor.v\n\
hierarchy -check -top bet_processor\n\
proc\nopt\ntechmap\nopt\n\
stat\n\
write_verilog -noattr {0}/synth_out.v\n",
out_dir.display()
);
let ys_path = out_dir.join("synth_bet.ys");
fs::write(&ys_path, &ys_script).expect("Failed to write Yosys script");
println!(" wrote {}", ys_path.display());
println!();
println!("[hdl-synth] {} Verilog modules emitted.", primitives.len() + isa_modules.len());
println!("[hdl-synth] To synthesise:");
println!(" sudo apt install yosys # if not installed");
println!(" yosys {}", ys_path.display());
if run_yosys {
match Command::new("yosys").arg(ys_path.to_str().unwrap()).status() {
Ok(s) if s.success() => println!("[hdl-synth] Yosys synthesis complete."),
Ok(s) => eprintln!("[hdl-synth] Yosys exited with status {}", s),
Err(_) => {
eprintln!("[hdl-synth] yosys not found on PATH.");
eprintln!(" Install: sudo apt install yosys");
eprintln!(" Then re-run: ternlang hdl-synth --synth");
}
}
}
}
fn run_translate(input_file: Option<&std::path::Path>, language: &str, output_path: Option<&std::path::Path>) {
let code = if let Some(path) = input_file {
fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("[translate] Cannot read {:?}: {}", path, e);
std::process::exit(1);
})
} else {
use std::io::Read;
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf).expect("failed to read stdin");
buf
};
let mut hold_zones = 0usize;
let mut tern_lines: Vec<String> = Vec::new();
let mut explanation_notes: Vec<String> = Vec::new();
tern_lines.push(format!("// Translated from {} by ternlang translate — RFI-IRFOS v0.3.1", language));
tern_lines.push(format!("// Original: {} lines", code.lines().count()));
tern_lines.push(String::new());
match language {
"python" => {
let mut in_chain = false;
let mut has_else = false;
for line in code.lines() {
let trimmed = line.trim();
if trimmed.starts_with("if ") && trimmed.ends_with(':') {
in_chain = true; has_else = false;
let cond = &trimmed[3..trimmed.len()-1];
tern_lines.push(format!("let condition: trit = cast({});", cond));
tern_lines.push("match condition {".to_string());
tern_lines.push(" 1 => {".to_string());
} else if trimmed.starts_with("elif ") && trimmed.ends_with(':') {
let cond = &trimmed[5..trimmed.len()-1];
tern_lines.push(" }".to_string());
tern_lines.push(format!(" // elif {}", cond));
tern_lines.push(" -1 => {".to_string());
} else if trimmed == "else:" {
has_else = true;
tern_lines.push(" }".to_string());
tern_lines.push(" 0 => {".to_string());
} else if trimmed.starts_with("return ") {
let val = &trimmed[7..];
let trit_val = if val.contains("True") || val.contains("1") { "truth()" }
else if val.contains("False") || val.contains("0") { "conflict()" }
else { "hold()" };
tern_lines.push(format!(" return {};", trit_val));
} else if !trimmed.is_empty() {
tern_lines.push(format!(" // {}", trimmed));
}
}
if in_chain {
if !has_else {
tern_lines.push(" }".to_string());
tern_lines.push(" 0 => {".to_string());
tern_lines.push(" // HOLD ZONE — original had no else branch".to_string());
tern_lines.push(" return hold();".to_string());
hold_zones += 1;
explanation_notes.push("Injected tend arm: original if/elif had no else branch.".into());
}
tern_lines.push(" }".to_string());
tern_lines.push("}".to_string());
}
}
"sql" => {
tern_lines.push("fn evaluate(signal: trit) -> trit {".to_string());
let mut in_case = false;
for line in code.lines() {
let upper = line.trim().to_uppercase();
if upper.starts_with("CASE") {
in_case = true; tern_lines.push(" match signal {".to_string());
} else if upper.starts_with("WHEN") {
tern_lines.push(format!(" 1 => {{ // WHEN {}", line.trim()[4..].trim()));
} else if upper.starts_with("THEN") {
tern_lines.push(format!(" return truth(); // THEN {}", line.trim()[4..].trim()));
tern_lines.push(" }".to_string());
} else if upper.starts_with("ELSE") {
tern_lines.push(format!(" -1 => {{ return conflict(); }} // ELSE {}", line.trim()[4..].trim()));
} else if upper.starts_with("END") && in_case {
tern_lines.push(" 0 => { return hold(); } // NULL/unknown → hold".to_string());
tern_lines.push(" }".to_string());
hold_zones += 1; in_case = false;
explanation_notes.push("Injected tend arm: SQL CASE had no NULL/unknown branch.".into());
}
}
tern_lines.push("}".to_string());
}
"json_rules" => {
match serde_json::from_str::<serde_json::Value>(&code) {
Ok(rules) if rules.is_array() => {
tern_lines.push("fn apply_rules(signal: trit) -> trit {".to_string());
tern_lines.push(" match signal {".to_string());
for rule in rules.as_array().unwrap() {
let cond = rule["if"].as_str().unwrap_or("condition");
let then = rule["then"].as_str().unwrap_or("pass");
let tval = if then.contains("pass") || then.contains("allow") { "truth()" }
else if then.contains("deny") || then.contains("block") { "conflict()" }
else { "hold()" };
tern_lines.push(format!(" 1 => {{ return {}; }} // if: {}", tval, cond));
}
tern_lines.push(" 0 => { return hold(); } // HOLD ZONE".to_string());
tern_lines.push(" -1 => { return conflict(); }".to_string());
tern_lines.push(" }".to_string());
tern_lines.push("}".to_string());
hold_zones += 1;
explanation_notes.push("Injected tend arm for unmatched inputs.".into());
}
_ => {
eprintln!("[translate] json_rules: code must be a valid JSON array.");
std::process::exit(1);
}
}
}
other => {
eprintln!("[translate] unsupported language '{}'. Use: python, sql, json_rules", other);
std::process::exit(1);
}
}
tern_lines.push(String::new());
tern_lines.push("fn main() -> trit { return hold(); }".to_string());
let tern_code = tern_lines.join("\n");
println!("{}", "══ TernTranslator ═══════════════════════════════════".bold());
println!(" Language: {}", language.cyan());
println!(" Hold zones: {}", hold_zones.to_string().green());
if !explanation_notes.is_empty() {
println!(" Notes:");
for note in &explanation_notes {
println!(" • {}", note);
}
}
println!();
if let Some(out) = output_path {
fs::write(out, &tern_code).unwrap_or_else(|e| {
eprintln!("[translate] Cannot write {:?}: {}", out, e);
std::process::exit(1);
});
println!("{} Written to {}", "✓".green().bold(), out.display());
} else {
println!("{}", tern_code);
}
}
fn run_audit(input_file: Option<&std::path::Path>, output_path: Option<&std::path::Path>, write_html: bool) {
use serde_json::Value;
let raw = if let Some(path) = input_file {
match fs::read_to_string(path) {
Ok(s) => s,
Err(e) => { eprintln!("{} {}", "Error reading input file:".red(), e); std::process::exit(1); }
}
} else {
let mut buf = String::new();
use std::io::Read;
std::io::stdin().read_to_string(&mut buf).expect("failed to read stdin");
buf
};
let decisions: Vec<Value> = match serde_json::from_str(&raw) {
Ok(v) => v,
Err(e) => { eprintln!("{} {}", "Error parsing JSON:".red(), e); std::process::exit(1); }
};
if decisions.is_empty() {
eprintln!("{}", "Error: decisions array is empty.".red());
std::process::exit(1);
}
let binary_words = ["yes","no","true","false","accept","reject","allow","deny",
"approve","block","pass","fail","correct","incorrect","valid","invalid"];
let hold_words = ["maybe","uncertain","unclear","depends","possibly","insufficient",
"further review","hold","tend","need more","context-dependent"];
let high_conf_pat = ["definitely","certainly","always","never","absolutely","guaranteed","100%"];
let mut affirm_n = 0usize;
let mut tend_n = 0usize;
let mut reject_n = 0usize;
let mut forced = 0usize;
let mut flagged: Vec<Value> = Vec::new();
for d in &decisions {
let output = d["output"].as_str().unwrap_or("").to_lowercase();
let confidence = d["confidence"].as_f64().unwrap_or(-1.0);
let input_text = d["input"].as_str().unwrap_or("");
let is_binary = binary_words.iter().any(|w| output.split_whitespace()
.any(|tok| tok.trim_matches(|c: char| !c.is_alphabetic()) == *w));
let has_hedge = hold_words.iter().any(|w| output.contains(*w));
let is_forced = high_conf_pat.iter().any(|w| output.contains(*w))
|| (confidence >= 0.0 && confidence > 0.9);
if has_hedge { tend_n += 1; }
else if output.contains("reject") || output.contains("deny")
|| output.contains(" no ") || output.contains("false") { reject_n += 1; }
else { affirm_n += 1; }
if is_binary && !has_hedge && is_forced {
forced += 1;
flagged.push(serde_json::json!({
"input": input_text,
"output": d["output"].as_str().unwrap_or(""),
"confidence": if confidence >= 0.0 { serde_json::json!(confidence) } else { Value::Null },
"trit": 0,
"reason": "Forced high-confidence binary decision — tend zone may have been appropriate."
}));
}
}
let n = decisions.len() as f32;
let binary_ratio = forced as f32 / n;
let tend_ratio = tend_n as f32 / n;
let art13 = if tend_ratio > 0.1 { "pass" } else { "warn" };
let art14 = if binary_ratio < 0.3 { "pass" } else { "fail" };
let cal_score = if binary_ratio < 0.2 { "good" } else if binary_ratio < 0.5 { "warn" } else { "poor" };
let report = serde_json::json!({
"total_decisions": decisions.len(),
"affirm_count": affirm_n,
"tend_count": tend_n,
"reject_count": reject_n,
"forced_binary_ratio": (binary_ratio * 100.0).round() / 100.0,
"tend_ratio": (tend_ratio * 100.0).round() / 100.0,
"eu_ai_act": {
"article_13_transparency": art13,
"article_14_human_oversight": art14,
"note": "Heuristic assessment only — not a substitute for legal compliance review."
},
"calibration": {
"score": cal_score,
"advice": if binary_ratio < 0.2 {
"Calibration looks healthy — the agent is using the full ternary range."
} else if binary_ratio < 0.5 {
"Moderate binary habituation. Add confidence thresholds to route uncertain outputs to tend."
} else {
"High binary habituation. The agent is collapsing ambiguity instead of surfacing it."
}
},
"flagged": flagged,
"generated_by": "ternlang audit — RFI-IRFOS TernAudit Phase 13"
});
println!("\n{}", "══ TernAudit Report ══════════════════════════════════".bold());
println!(" Total decisions : {}", decisions.len());
println!(" Affirm : {} Tend : {} Reject : {}", affirm_n, tend_n, reject_n);
println!(" Binary ratio : {:.0}% Calibration : {}", binary_ratio * 100.0,
match cal_score { "good" => cal_score.green().to_string(), "warn" => cal_score.yellow().to_string(), _ => cal_score.red().to_string() });
println!(" EU AI Act Art.13 transparency : {} Art.14 oversight : {}",
if art13 == "pass" { "pass".green().to_string() } else { "warn".yellow().to_string() },
if art14 == "pass" { "pass".green().to_string() } else { "fail".red().to_string() });
if !flagged.is_empty() {
println!(" {} flagged decisions (see report for details)", flagged.len().to_string().yellow());
}
let json_path = output_path.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::path::PathBuf::from("audit_report.json"));
match fs::write(&json_path, serde_json::to_string_pretty(&report).unwrap_or_default()) {
Ok(_) => println!("\n {} {}", "JSON report →".green(), json_path.display()),
Err(e) => eprintln!(" {} {}", "Failed to write JSON report:".red(), e),
}
if write_html {
let html_path = json_path.with_extension("html");
let flagged_rows: String = report["flagged"].as_array().map(|f| f.iter().map(|d| {
format!("<tr><td>{}</td><td>{}</td><td>{}</td></tr>",
d["input"].as_str().unwrap_or(""),
d["output"].as_str().unwrap_or(""),
d["reason"].as_str().unwrap_or(""))
}).collect::<Vec<_>>().join("\n")).unwrap_or_default();
let html = format!(r#"<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
<title>TernAudit Report — RFI-IRFOS</title>
<style>body{{font-family:sans-serif;max-width:900px;margin:40px auto;padding:0 20px}}
h1{{color:#2d2d2d}}table{{width:100%;border-collapse:collapse}}
th,td{{border:1px solid #ccc;padding:8px;text-align:left;vertical-align:top}}
th{{background:#f5f5f5}}.good{{color:green}}.warn{{color:orange}}.fail{{color:red}}</style></head>
<body><h1>TernAudit Report</h1>
<h2>Summary</h2><table>
<tr><th>Metric</th><th>Value</th></tr>
<tr><td>Total decisions</td><td>{total}</td></tr>
<tr><td>Affirm / Tend / Reject</td><td>{affirm} / {tend} / {reject}</td></tr>
<tr><td>Forced binary ratio</td><td>{ratio:.0}%</td></tr>
<tr><td>Calibration</td><td class="{cal}">{cal}</td></tr>
<tr><td>EU AI Act — Art. 13 transparency</td><td class="{art13}">{art13}</td></tr>
<tr><td>EU AI Act — Art. 14 human oversight</td><td class="{art14}">{art14}</td></tr>
</table>
<h2>Flagged Decisions ({flagged_n})</h2>
<table><tr><th>Input</th><th>Output</th><th>Reason</th></tr>{rows}</table>
<p style="color:#888;font-size:0.85em">Generated by ternlang audit · RFI-IRFOS TernAudit Phase 13 · ternlang.com</p>
</body></html>"#,
total=decisions.len(), affirm=affirm_n, tend=tend_n, reject=reject_n,
ratio=binary_ratio*100.0, cal=cal_score, art13=art13, art14=art14,
flagged_n=report["flagged"].as_array().map(|f|f.len()).unwrap_or(0),
rows=flagged_rows);
match fs::write(&html_path, html) {
Ok(_) => println!(" {} {}", "HTML report →".green(), html_path.display()),
Err(e) => eprintln!(" {} {}", "Failed to write HTML report:".red(), e),
}
}
}
fn run_tests(path: &std::path::PathBuf) {
println!("{}", "Ternary Test Runner".bold().bright_blue());
println!("Searching for *_test.tern in {:?}", path);
println!("{}", "─".repeat(52));
let mut passed = 0;
let mut failed = 0;
let mut skipped = 0;
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
let fpath = entry.path();
if fpath.is_file() && fpath.extension().map_or(false, |ext| ext == "tern") {
let fname = fpath.file_name().unwrap().to_str().unwrap();
if fname.ends_with("_test.tern") {
print!("test {} ... ", fname);
use std::io::Write;
std::io::stdout().flush().unwrap();
let input = match fs::read_to_string(fpath) {
Ok(s) => s,
Err(_) => {
println!("{}", "ERROR (read)".red());
failed += 1;
continue;
}
};
let mut parser = Parser::new(&input);
let mut emitter = BytecodeEmitter::new();
match parser.parse_program() {
Ok(mut prog) => {
ternlang_core::ModuleResolver::stdlib_only().resolve(&mut prog);
emitter.emit_program(&prog);
if prog.functions.iter().any(|f| f.name == "main") {
emitter.emit_entry_call("main");
}
}
Err(_) => {
let mut parser = Parser::new(&input);
while let Ok(stmt) = parser.parse_stmt() {
emitter.emit_stmt(&stmt);
}
}
}
let code = emitter.finalize();
let mut vm = BetVm::new(code);
emitter.register_agents(&mut vm);
match vm.run() {
Ok(_) => {
let result = vm.peek_stack(); match result {
Some(Value::Trit(t)) => {
match t {
ternlang_core::trit::Trit::Affirm => {
println!("{}", "ok".green());
passed += 1;
}
ternlang_core::trit::Trit::Tend => {
println!("{}", "skipped".yellow());
skipped += 1;
}
ternlang_core::trit::Trit::Reject => {
println!("{}", "FAILED".red());
failed += 1;
}
}
}
_ => {
println!("{}", "FAILED (no trit return)".red());
failed += 1;
}
}
}
Err(e) => {
println!("{} ({})", "FAILED (vm)".red(), e);
failed += 1;
}
}
}
}
}
println!("{}", "─".repeat(52));
let status_str = if failed == 0 { "ok".green() } else { "FAILED".red() };
println!("test result: {}. {} passed; {} failed; {} skipped", status_str, passed, failed, skipped);
if failed > 0 {
std::process::exit(1);
}
}