run is now String -> String, outputting a json result

This commit is contained in:
Scott Richmond 2025-06-25 15:41:30 -04:00
parent 87e58364e0
commit 3e84aa3f14
5 changed files with 111 additions and 262 deletions

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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<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!(),
}
}
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<Ast> = 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<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())
}
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}");
}

View File

@ -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::<Vec<_>>().join(", ");
@ -258,6 +255,45 @@ impl Value {
}
}
pub fn to_json(&self) -> Option<String> {
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::<Vec<_>>()
.join(", ");
members.to_string()
format!("({members})")
}
List(l) => {
let members = l
@ -281,7 +317,7 @@ impl Value {
.map(|e| e.stringify())
.collect::<Vec<_>>()
.join(", ");
members.to_string()
format!("[{members}]")
}
Dict(d) => {
let members = d
@ -293,12 +329,14 @@ impl Value {
})
.collect::<Vec<_>>()
.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!(),
}
}