Lines
0 %
Functions
Branches
100 %
use std::fs;
use std::io::{self, BufRead, IsTerminal, Read, Write};
use std::path::Path;
use clap::Parser;
use scripting::nomiscript::{Reader, Value};
use nms::interpreter;
mod repl;
#[derive(Parser)]
#[command(name = "nms")]
#[command(about = "Nomiscript interpreter", long_about = None)]
struct Cli {
/// File to evaluate (use - for stdin)
#[arg(value_name = "FILE")]
file: Option<String>,
/// Evaluate a string expression
#[arg(short, long, value_name = "EXPR")]
eval: Option<String>,
/// Compile a source file to WASM bytecode
#[arg(long, value_name = "FILE")]
compile: Option<String>,
/// Load and run a pre-compiled WASM file
load: Option<String>,
/// Use TUI mode for the REPL
#[arg(short, long)]
tui: bool,
/// Enable debug-level tracing output
#[arg(long)]
debug: bool,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if let Some(source) = cli.compile {
compile_file(&source, cli.debug)?;
} else if let Some(wasm_path) = cli.load {
load_wasm(&wasm_path, cli.debug)?;
} else if let Some(expr_str) = cli.eval {
eval_string(&expr_str, cli.debug)?;
} else if let Some(file) = cli.file {
if file == "-" {
eval_stdin(cli.debug)?;
} else {
eval_file(&file, cli.debug)?;
} else if cli.tui {
repl::run()?;
plain_repl(cli.debug)?;
Ok(())
fn plain_repl(debug: bool) -> anyhow::Result<()> {
let stdin = io::stdin();
let mut stdout = io::stdout();
let use_color = stdout.is_terminal();
let mut interp = interpreter::Interpreter::new(debug)?;
let mut buffer = String::new();
loop {
if buffer.is_empty() {
print!("\n> ");
print!(" ");
stdout.flush()?;
let mut line = String::new();
if stdin.lock().read_line(&mut line)? == 0 {
break;
if buffer.is_empty() && line.trim().is_empty() {
continue;
buffer.push_str(&line);
if Reader::is_incomplete(&buffer) {
let input = buffer.trim();
if input.is_empty() {
buffer.clear();
match interp.eval(input) {
Ok(values) => {
for value in values {
println!("{}", format_value(&value, use_color));
Err(e) => eprint!("{}", e.render(use_color)),
fn compile_file(path: &str, debug: bool) -> anyhow::Result<()> {
let content = fs::read_to_string(path)?;
let use_color = io::stderr().is_terminal();
match interp.compile_to_wasm(&content) {
Ok(wasm) => {
let out_path = Path::new(path).with_extension("wasm");
fs::write(&out_path, &wasm)?;
eprintln!("wrote {}", out_path.display());
Err(e) => {
eprint!("{}", e.render(use_color));
std::process::exit(1);
fn load_wasm(path: &str, debug: bool) -> anyhow::Result<()> {
let wasm = fs::read(path)?;
let interp = interpreter::Interpreter::new(debug)?;
match interp.run_wasm(&wasm) {
Ok(value) => {
let use_color = io::stdout().is_terminal();
fn eval_string(input: &str, debug: bool) -> anyhow::Result<()> {
print_results(&mut interp, input)
fn eval_file(path: &str, debug: bool) -> anyhow::Result<()> {
print_results(&mut interp, &content)
fn eval_stdin(debug: bool) -> anyhow::Result<()> {
let mut content = String::new();
io::stdin().read_to_string(&mut content)?;
fn print_results(interp: &mut interpreter::Interpreter, input: &str) -> anyhow::Result<()> {
fn format_value(value: &Value, use_color: bool) -> String {
match value {
Value::Nil | Value::Bool(false) => {
if use_color {
"\x1b[90mNIL\x1b[0m".to_string()
"NIL".to_string()
Value::Bool(true) => {
"\x1b[32m#T\x1b[0m".to_string()
"#T".to_string()
Value::Number(n) => {
let text = if *n.denom() == 1 {
n.numer().to_string()
format!("{}/{}", n.numer(), n.denom())
};
format!("\x1b[36m{text}\x1b[0m")
text
Value::String(s) => {
format!("\x1b[33m{s}\x1b[0m")
s.clone()
Value::Symbol(s) => {
format!("\x1b[35m{s}\x1b[0m")
Value::Pair(_) => "<pair>".to_string(),
Value::Vector(_) => "<vector>".to_string(),
Value::Closure(_) => "<closure>".to_string(),
Value::Struct { name, fields } => {
format!("\x1b[94m#{name}({} fields)\x1b[0m", fields.len())
format!("#{name}({} fields)", fields.len())