rudus/src/lib.rs

215 lines
5.8 KiB
Rust
Raw Normal View History

2025-06-25 19:41:44 +00:00
use chumsky::{input::Stream, prelude::*};
use imbl::HashMap;
use wasm_bindgen::prelude::*;
const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;
mod base;
2025-06-26 20:11:35 +00:00
2025-06-26 05:28:33 +00:00
mod world;
2025-06-26 20:11:35 +00:00
use crate::world::World;
2025-06-25 19:41:44 +00:00
mod spans;
use crate::spans::Spanned;
mod lexer;
use crate::lexer::lexer;
mod parser;
use crate::parser::{parser, Ast};
mod validator;
use crate::validator::Validator;
mod errors;
use crate::errors::report_invalidation;
mod chunk;
mod op;
mod compiler;
use crate::compiler::Compiler;
mod value;
use value::Value;
mod vm;
2025-06-26 05:28:33 +00:00
use vm::Creature;
2025-06-25 19:41:44 +00:00
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
2025-06-25 21:43:30 +00:00
fn prelude() -> HashMap<&'static str, Value> {
2025-06-25 19:41:44 +00:00
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
let (parsed, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
println!("ERROR PARSING PRELUDE:");
println!("{:?}", parse_errors);
panic!();
}
let parsed = parsed.unwrap();
let (ast, span) = &parsed;
let base = base::make_base();
let mut base_env = imbl::HashMap::new();
base_env.insert("base", base.clone());
let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env);
validator.validate();
if !validator.errors.is_empty() {
println!("VALIDATION ERRORS IN PRLUDE:");
report_invalidation(validator.errors);
panic!();
}
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
let mut compiler = Compiler::new(
parsed,
"prelude",
PRELUDE,
0,
HashMap::new(),
DEBUG_PRELUDE_COMPILE,
);
compiler.emit_constant(base);
compiler.bind("base");
compiler.compile();
let chunk = compiler.chunk;
2025-06-26 05:28:33 +00:00
let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN);
2025-06-25 19:41:44 +00:00
let prelude = vm.run().clone().unwrap();
match prelude {
Value::Dict(hashmap) => *hashmap,
_ => unreachable!(),
}
}
#[wasm_bindgen]
2025-06-26 02:56:39 +00:00
pub fn ludus(src: String) -> String {
2025-06-25 21:43:30 +00:00
let src = src.to_string().leak();
2025-06-25 19:41:44 +00:00
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
return format!("{:?}", lex_errs);
}
let tokens = tokens.unwrap();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
return format!("{:?}", parse_errors);
}
// ::sigh:: The AST should be 'static
// This simplifies lifetimes, and
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
let prelude = prelude();
let postlude = prelude.clone();
// let prelude = imbl::HashMap::new();
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
validator.validate();
// TODO: validator should generate a string, not print to the console
if !validator.errors.is_empty() {
report_invalidation(validator.errors);
return "Ludus found some validation errors.".to_string();
}
let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE);
// let base = base::make_base();
// compiler.emit_constant(base);
// compiler.bind("base");
compiler.compile();
if DEBUG_SCRIPT_COMPILE {
println!("=== source code ===");
println!("{src}");
compiler.disassemble();
println!("\n\n")
}
if DEBUG_SCRIPT_RUN {
println!("=== vm run ===");
}
let vm_chunk = compiler.chunk;
2025-06-26 20:11:35 +00:00
let vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN);
let grip = World::new(vm);
grip.borrow_mut().run();
let world = grip.borrow();
let result = world.result.clone().unwrap();
2025-06-25 19:41:44 +00:00
let console = postlude.get("console").unwrap();
let Value::Box(console) = console else {
unreachable!()
};
let Value::List(ref lines) = *console.borrow() else {
unreachable!()
};
let mut console = lines
.iter()
.map(|line| line.stringify())
.collect::<Vec<_>>()
.join("\n");
let turtle_commands = postlude.get("turtle_commands").unwrap();
let Value::Box(commands) = turtle_commands else {
unreachable!()
};
2025-06-25 21:43:30 +00:00
let commands = commands.borrow();
let commands = commands.to_json().unwrap();
2025-06-25 19:41:44 +00:00
let output = match result {
Ok(val) => val.show(),
Err(panic) => {
console = format!("{console}\nLudus panicked! {panic}");
"".to_string()
}
};
if DEBUG_SCRIPT_RUN {
2025-06-26 20:11:35 +00:00
// vm.print_stack();
2025-06-25 19:41:44 +00:00
}
// TODO: use serde_json to make this more robust?
format!(
2025-06-25 21:43:30 +00:00
"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}"
2025-06-25 19:41:44 +00:00
)
}
2025-06-25 21:43:30 +00:00
pub fn fmt(src: &'static str) -> Result<String, String> {
2025-06-25 19:41:44 +00:00
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
println!("{:?}", lex_errs);
return Err(format!("{:?}", lex_errs));
}
let tokens = tokens.unwrap();
let (parse_result, parse_errors) = parser()
.parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
.into_output_errors();
if !parse_errors.is_empty() {
return Err(format!("{:?}", parse_errors));
}
// ::sigh:: The AST should be 'static
// This simplifies lifetimes, and
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
Ok(parsed.0.show())
}