mod banner;
mod render;
mod repl;
mod repl_line;
use std::io::IsTerminal;
fn main() {
let args: Vec<String> = std::env::args().collect();
let cmd = args.get(1).map(String::as_str);
let rest = if args.len() > 2 { &args[2..] } else { &[] };
let color = std::io::stdout().is_terminal();
match cmd {
None | Some("repl") => repl::run(color),
Some("run") => cmd_run(rest),
Some("eval") => cmd_eval(rest, color),
Some("fmt") => cmd_fmt(rest),
Some("check") => cmd_check(rest),
Some("help") | Some("-h") | Some("--help") => print!("{}", banner::help(color)),
Some("--version") | Some("-V") => println!("rune {}", env!("CARGO_PKG_VERSION")),
Some(c) => { eprintln!("unknown command: {c}"); std::process::exit(1); }
}
}
fn cmd_run(args: &[String]) {
let path = args.first().unwrap_or_else(|| { eprintln!("usage: rune run <file>"); std::process::exit(1); });
let src = read_file(path);
run_source_event(&src);
}
fn cmd_eval(args: &[String], color: bool) {
let (reg, rest) = match args.first().map(String::as_str) {
Some("--pure") => (Some(rune_parse::Register::Pure), &args[1..]),
Some("--classic") => (Some(rune_parse::Register::Classic), &args[1..]),
_ => (None, args),
};
let expr = rest.first().unwrap_or_else(|| { eprintln!("usage: rune eval [--pure|--classic] <expr>"); std::process::exit(1); });
let register = reg.unwrap_or_else(|| rune_parse::detect_register(expr));
let formula = lower_ast(parse_in(expr, register));
let subject = subject();
let mut host = CollectHost { emitted: Vec::new() };
let result = match rune_interp::eval_with_host(&subject, &formula, &mut host) {
Ok(n) => n,
Err(e) => { eprintln!("eval error: {}", e.message); std::process::exit(1); }
};
let mut chunks = Vec::new();
for n in &host.emitted { chunks.extend(rune_prysm::noun_to_chunks(n)); }
chunks.extend(rune_prysm::noun_to_chunks(&result));
if chunks.is_empty() {
println!("{}", display_noun(&result));
} else {
println!("{}", render::ansi_chunks(&chunks, color));
}
}
fn cmd_fmt(args: &[String]) {
let (target, file) = match args.first().map(String::as_str) {
Some("--to=rune") => ("rune", args.get(1)),
Some("--to=rust") => ("rust", args.get(1)),
_ => ("rust", args.first()),
};
let path = file.unwrap_or_else(|| {
eprintln!("usage: rune fmt [--to=rust|--to=rune] <file>");
std::process::exit(1);
});
let src = read_file(path);
let ast = parse_ast(&src);
if target == "rune" {
println!("{}", rune_parse_pure::format_expr(&ast));
} else {
println!("{}", rune_parse::format_expr(&ast));
}
}
fn cmd_check(args: &[String]) {
let path = args.first().unwrap_or_else(|| {
eprintln!("usage: rune check <file>");
std::process::exit(1);
});
let src = read_file(path);
let ast = parse_ast(&src);
let errors = rune_mold::MoldChecker::check(&ast);
if errors.is_empty() {
println!("ok");
} else {
for e in &errors {
eprintln!("{}", e.message);
}
std::process::exit(1);
}
}
pub(crate) fn subject() -> rune_ast::Noun {
let mut s = rune_subject::Subject::standalone();
if let Ok(d) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
s.now = rune_ast::Noun::Atom(d.as_secs());
}
s.to_noun()
}
fn read_file(path: &str) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| { eprintln!("{e}"); std::process::exit(1); })
}
fn parse_ast(src: &str) -> rune_ast::Expr {
parse_in(src, rune_parse::detect_register(src))
}
fn parse_in(src: &str, reg: rune_parse::Register) -> rune_ast::Expr {
let body = strip_pragma(src);
match reg {
rune_parse::Register::Classic => match rune_parse::parse(body) {
Ok(a) => a,
Err(e) => { eprintln!("parse error: {}", e.message); std::process::exit(1); }
},
rune_parse::Register::Pure => match rune_parse_pure::parse(body) {
Ok(a) => a,
Err(e) => { eprintln!("parse error: {}", e.message); std::process::exit(1); }
},
}
}
fn strip_pragma(src: &str) -> &str {
if src.trim_start().starts_with(":: register:") {
match src.find('\n') {
Some(i) => &src[i + 1..],
None => "",
}
} else {
src
}
}
fn lower_ast(ast: rune_ast::Expr) -> rune_ast::Noun {
match rune_lower::lower(ast) {
Ok(n) => n,
Err(e) => { eprintln!("lower error: {}", e.message); std::process::exit(1); }
}
}
fn run_source_event(src: &str) {
use rune_interp::event::{eval_step, resume, StepResult};
let formula = lower_ast(parse_ast(src));
let subject = subject();
let mut step = eval_step(&subject, &formula);
loop {
match step {
StepResult::Done(n) => { println!("{}", display_noun(&n)); return; }
StepResult::Err(e) => { eprintln!("eval error: {}", e.message); std::process::exit(1); }
StepResult::Yield { tag, selector, continuation } => {
eprintln!("yield tag={tag} selector={}", display_noun(&selector));
let event = read_event_noun();
step = resume(continuation, event);
}
}
}
}
pub(crate) struct CollectHost {
pub(crate) emitted: Vec<rune_ast::Noun>,
}
impl rune_interp::Host for CollectHost {
fn perform(
&mut self,
act: u64,
args: &rune_ast::Noun,
_caps: &rune_ast::Noun,
) -> Result<rune_ast::Noun, rune_interp::InterpError> {
if act == rune_ast::act::EMIT {
self.emitted.push(args.clone());
}
Ok(rune_ast::Noun::Atom(0))
}
}
fn read_event_noun() -> rune_ast::Noun {
use std::io::BufRead;
let line = std::io::stdin().lock().lines().next()
.and_then(|r| r.ok())
.unwrap_or_default();
parse_noun_literal(line.trim()).unwrap_or(rune_ast::Noun::Atom(0))
}
fn parse_noun_literal(s: &str) -> Option<rune_ast::Noun> {
let s = s.trim();
if let Ok(n) = s.parse::<u64>() {
return Some(rune_ast::Noun::Atom(n));
}
if s.starts_with('[') && s.ends_with(']') {
let inner = s[1..s.len() - 1].trim();
let (head_s, tail_s) = split_noun_pair(inner)?;
let head = parse_noun_literal(head_s)?;
let tail = parse_noun_literal(tail_s)?;
return Some(rune_ast::Noun::cell(head, tail));
}
None
}
fn split_noun_pair(s: &str) -> Option<(&str, &str)> {
let mut depth = 0usize;
for (i, c) in s.char_indices() {
match c {
'[' => depth += 1,
']' => depth -= 1,
' ' if depth == 0 => {
let tail = s[i + 1..].trim_start();
return Some((&s[..i], tail));
}
_ => {}
}
}
None
}
pub(crate) fn display_noun(noun: &rune_ast::Noun) -> String {
match noun {
rune_ast::Noun::Atom(n) => n.to_string(),
rune_ast::Noun::Cell(h, t) => format!("[{} {}]", display_noun(h), display_noun(t)),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn a(n: u64) -> rune_ast::Noun { rune_ast::Noun::Atom(n) }
fn c(h: rune_ast::Noun, t: rune_ast::Noun) -> rune_ast::Noun { rune_ast::Noun::cell(h, t) }
#[test]
fn parse_atom() {
assert_eq!(parse_noun_literal("42"), Some(a(42)));
assert_eq!(parse_noun_literal("0"), Some(a(0)));
}
#[test]
fn parse_flat_cell() {
assert_eq!(parse_noun_literal("[1 2]"), Some(c(a(1), a(2))));
}
#[test]
fn parse_nested_cell() {
assert_eq!(parse_noun_literal("[1 [2 3]]"), Some(c(a(1), c(a(2), a(3)))));
assert_eq!(parse_noun_literal("[[1 2] 3]"), Some(c(c(a(1), a(2)), a(3))));
}
#[test]
fn parse_invalid_returns_none() {
assert_eq!(parse_noun_literal("abc"), None);
assert_eq!(parse_noun_literal("[1]"), None);
}
#[test]
fn display_roundtrip() {
let noun = c(a(42), c(a(1), a(2)));
let s = display_noun(&noun);
assert_eq!(parse_noun_literal(&s), Some(noun));
}
}