diff --git a/Cargo.toml b/Cargo.toml index 2d3722c..6f00ccd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + [dependencies] ariadne = { git = "https://github.com/zesterer/ariadne" } chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] } @@ -18,3 +21,4 @@ index_vec = "0.1.4" num-derive = "0.4.2" num-traits = "0.2.19" regex = "1.11.1" +wasm-bindgen = "0.2" diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index e98d011..dfcb9f3 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -267,35 +267,33 @@ fn contains? { } } -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - :ok +&&& boxes: mutable state and state changes +fn box? { + "Returns true if a value is a box." + (b as :box) -> true + (_) -> false +} + +fn unbox { + "Returns the value that is stored in a box." + (b as :box) -> base :unbox (b) +} + +fn store! { + "Stores a value in a box, replacing the value that was previously there. Returns the value." + (b as :box, value) -> { + base :store! (b, value) + value } } -fn show { - "Returns a text representation of a Ludus value as a string." - (x) -> base :show (x) -} - -fn report! { - "Prints a value, then returns it." - (x) -> { - print! (x) - x +fn update! { + "Updates a box by applying a function to its value. Returns the new value." + (b as :box, f as :fn) -> { + let current = unbox (b) + let new = f (current) + store! (b, new) } - (msg as :string, x) -> { - print! (concat ("{msg} ", show (x))) - x - } -} - -fn doc! { - "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc! > print! - (_) -> :none } &&& strings: harder than they look! @@ -305,6 +303,11 @@ fn string? { (_) -> false } +fn show { + "Returns a text representation of a Ludus value as a string." + (x) -> base :show (x) +} + fn string { "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." (x as :string) -> x @@ -405,34 +408,33 @@ fn to_number { (num as :string) -> base :number (num) } -&&& boxes: mutable state and state changes +box console = [] -fn box? { - "Returns true if a value is a box." - (b as :box) -> true - (_) -> false -} - -fn unbox { - "Returns the value that is stored in a box." - (b as :box) -> base :unbox (b) -} - -fn store! { - "Stores a value in a box, replacing the value that was previously there. Returns the value." - (b as :box, value) -> { - base :store! (b, value) - value +fn print! { + "Sends a text representation of Ludus values to the console." + (...args) -> { + let line = do args > map (string, _) > join (_, " ") + update! (console, append (_, line)) + :ok } } -fn update! { - "Updates a box by applying a function to its value. Returns the new value." - (b as :box, f as :fn) -> { - let current = unbox (b) - let new = f (current) - store! (b, new) +fn report! { + "Prints a value, then returns it." + (x) -> { + print! (x) + x } + (msg as :string, x) -> { + print! (concat ("{msg} ", show (x))) + x + } +} + +fn doc! { + "Prints the documentation of a function to the console." + (f as :fn) -> do f > base :doc! > print! + (_) -> :none } &&& numbers, basically: arithmetic and not much else, yet @@ -1210,9 +1212,6 @@ fn penwidth { box state = nil #{ - apply_command - add_command! - abs abs add @@ -1240,6 +1239,7 @@ box state = nil coll? colors concat + console contains? cos count diff --git a/sandbox.ld b/sandbox.ld index ec9a4dc..ff914f7 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,19 +1,4 @@ -fn circle! () -> repeat 20 { - fd! (2) - rt! (inv (20)) -} - -fn flower! () -> repeat 10 { - circle! () - rt! (inv (10)) -} - -fn garland! () -> repeat 10 { - flower! () - fd! (30) -} - -garland! () - -do turtle_commands > unbox > print! -do turtle_state > unbox > print! +print! (:foo, :bar, :baz) +print! ("I wrote something") +let false = true +print! (1, 2, 3) diff --git a/src/main.rs b/src/main.rs index 3b26244..1103a54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,190 +1,12 @@ -use chumsky::{input::Stream, prelude::*}; -use imbl::HashMap; +use crate::lib::run; use std::env; use std::fs; -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 = 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!(), - } -} - -pub fn run(src: &'static str) { - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - println!("{:?}", lex_errs); - return; - } - - 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() { - println!("{:?}", parse_errors); - return; - } - - // ::sigh:: The AST should be 'static - // This simplifies lifetimes, and - // in any event, the AST should live forever - let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - - let prelude = prelude(); - // let prelude = imbl::HashMap::new(); - - let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone()); - validator.validate(); - - if !validator.errors.is_empty() { - println!("Ludus found some validation errors:"); - report_invalidation(validator.errors); - return; - } - - 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 output = match result { - Ok(val) => val.to_string(), - Err(panic) => format!("Ludus panicked! {panic}"), - }; - if DEBUG_SCRIPT_RUN { - vm.print_stack(); - } - println!("{output}"); -} - -pub fn ld_fmt(src: &'static str) -> Result { - 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 = Box::leak(Box::new(parse_result.unwrap())); - - Ok(parsed.0.show()) -} +mod lib; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak(); - match ld_fmt(src) { - Ok(src) => println!("{}", src), - Err(msg) => println!("Could not format source with errors:\n{}", msg), - } - run(src); + let src = fs::read_to_string("sandbox.ld").unwrap(); + let json = run(src); + println!("{json}"); } diff --git a/src/value.rs b/src/value.rs index 2f387f9..5d314c2 100644 --- a/src/value.rs +++ b/src/value.rs @@ -219,10 +219,7 @@ impl Value { False => "false".to_string(), Number(n) => format!("{n}"), Interned(str) => format!("\"{str}\""), - String(str) => { - let str_str = str.to_string(); - format!("\"{str_str}\"") - } + String(str) => format!("\"{str}\""), Keyword(str) => format!(":{str}"), Tuple(t) => { let members = t.iter().map(|e| e.show()).collect::>().join(", "); @@ -258,6 +255,45 @@ impl Value { } } + pub fn to_json(&self) -> Option { + use Value::*; + match self { + True | False | String(..) | Interned(..) => Some(self.show()), + Keyword(str) => Some(format!("\"{str}\"")), + List(members) => { + let mut joined = "".to_string(); + for member in members.iter() { + match member.to_json() { + Some(json) => joined = format!("{joined}, {json}"), + None => return None, + } + } + Some(format!("[{joined}]")) + } + Tuple(members) => { + let mut joined = "".to_string(); + for member in members.iter() { + match member.to_json() { + Some(json) => joined = format!("{joined}, {json}"), + None => return None, + } + } + Some(format!("[{joined}]")) + } + Dict(members) => { + let mut joined = "".to_string(); + for (key, value) in members.iter() { + match value.to_json() { + Some(json) => joined = format!("{joined}, \"{key}\": {json}"), + None => return None, + } + } + Some(format!("{{{joined}}}")) + } + _ => None, + } + } + pub fn stringify(&self) -> String { use Value::*; match &self { @@ -266,14 +302,14 @@ impl Value { False => "false".to_string(), Number(n) => format!("{n}"), Interned(str) => str.to_string(), - Keyword(str) => str.to_string(), + Keyword(str) => format!(":{str}"), Tuple(t) => { let members = t .iter() .map(|e| e.stringify()) .collect::>() .join(", "); - members.to_string() + format!("({members})") } List(l) => { let members = l @@ -281,7 +317,7 @@ impl Value { .map(|e| e.stringify()) .collect::>() .join(", "); - members.to_string() + format!("[{members}]") } Dict(d) => { let members = d @@ -293,12 +329,14 @@ impl Value { }) .collect::>() .join(", "); - members.to_string() + format!("#{{{members}}}") } String(s) => s.as_ref().clone(), Box(x) => x.as_ref().borrow().stringify(), Fn(lfn) => format!("fn {}", lfn.name()), - _ => todo!(), + Partial(partial) => format!("fn {}/partial", partial.name), + BaseFn(_) => format!("{self}"), + Nothing => unreachable!(), } }