start splitting rudus into lib & main
This commit is contained in:
parent
3e84aa3f14
commit
333f5c9518
218
src/lib.rs
Normal file
218
src/lib.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
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;
|
||||
|
||||
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;
|
||||
use vm::Vm;
|
||||
|
||||
const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
|
||||
|
||||
pub fn prelude() -> HashMap<&'static str, Value> {
|
||||
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;
|
||||
let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN);
|
||||
let prelude = vm.run().clone().unwrap();
|
||||
match prelude {
|
||||
Value::Dict(hashmap) => *hashmap,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn run(src: String) -> String {
|
||||
let src = src.leak();
|
||||
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;
|
||||
|
||||
let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN);
|
||||
let result = vm.run();
|
||||
|
||||
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!()
|
||||
};
|
||||
let commands = commands.borrow().to_json().unwrap();
|
||||
|
||||
let output = match result {
|
||||
Ok(val) => val.show(),
|
||||
Err(panic) => {
|
||||
console = format!("{console}\nLudus panicked! {panic}");
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
if DEBUG_SCRIPT_RUN {
|
||||
vm.print_stack();
|
||||
}
|
||||
|
||||
// TODO: use serde_json to make this more robust?
|
||||
format!(
|
||||
"{{\"result\": \"{output}\",
|
||||
\"io\": {{
|
||||
\"stdout\": {{
|
||||
\"proto\": [\"text-stream\", \"0.1.0\"],
|
||||
\"data\": \"{console}\"
|
||||
}},
|
||||
\"turtle\": {{
|
||||
\"proto\": [\"turtle-graphics\", \"0.1.0\"],
|
||||
\"data\": {commands}
|
||||
}}
|
||||
}}
|
||||
}}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ld_fmt(src: &'static str) -> Result<String, String> {
|
||||
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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user