Compare commits

...

5 Commits

Author SHA1 Message Date
Scott Richmond
9d798e5e58 fix interpolated string stack discipline 2025-06-22 21:15:17 -04:00
Scott Richmond
f0cf43b486 update block stack work 2025-06-22 20:44:07 -04:00
Scott Richmond
e2c9619fa6 update block stack work 2025-06-22 20:43:51 -04:00
Scott Richmond
f35cdd0e8d fix and & or 2025-06-22 20:26:38 -04:00
Scott Richmond
c00e1275fd fix and & or 2025-06-22 20:26:08 -04:00
4 changed files with 292 additions and 261 deletions

View File

@ -1,133 +1,133 @@
& the very base: know something's type & the very base: know something's type
& fn type { fn type {
& "Returns a keyword representing the type of the value passed in." "Returns a keyword representing the type of the value passed in."
& (x) -> base :type (x) (x) -> base :type (x)
& } }
& & some helper type functions & & some helper type functions
& fn coll? { fn coll? {
& "Returns true if a value is a collection: dict, list, tuple, or set." "Returns true if a value is a collection: dict, list, tuple, or set."
& (coll as :dict) -> true (coll as :dict) -> true
& (coll as :list) -> true (coll as :list) -> true
& (coll as :tuple) -> true (coll as :tuple) -> true
& & (coll as :set) -> true & (coll as :set) -> true
& (_) -> false (_) -> false
& } }
& fn ordered? { fn ordered? {
& "Returns true if a value is an indexed collection: list or tuple." "Returns true if a value is an indexed collection: list or tuple."
& (coll as :list) -> true (coll as :list) -> true
& (coll as :tuple) -> true (coll as :tuple) -> true
& (coll as :string) -> true (coll as :string) -> true
& (_) -> false (_) -> false
& } }
& fn assoc? { fn assoc? {
& "Returns true if a value is an associative collection: a dict or a pkg." "Returns true if a value is an associative collection: a dict or a pkg."
& (d as :dict) -> true (d as :dict) -> true
& (_) -> false (_) -> false
& } }
& &&& nil: working with nothing & &&& nil: working with nothing
& fn nil? { fn nil? {
& "Returns true if a value is nil." "Returns true if a value is nil."
& (nil) -> true (nil) -> true
& (_) -> false (_) -> false
& } }
& fn some? { fn some? {
& "Returns true if a value is not nil." "Returns true if a value is not nil."
& (nil) -> false (nil) -> false
& (_) -> true (_) -> true
& } }
& fn some { fn some {
& "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil." "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil."
& (nil, default) -> default (nil, default) -> default
& (value, _) -> value (value, _) -> value
& } }
& ...and if two things are the same & ...and if two things are the same
fn eq? { fn eq? {
"Returns true if all arguments have the same value." "Returns true if all arguments have the same value."
& (x) -> true (x) -> true
(x, y) -> base :eq? (x, y) (x, y) -> base :eq? (x, y)
& (x, y, ...zs) -> if eq? (x, y) (x, y, ...zs) -> if eq? (x, y)
& then loop (y, zs) with { then loop (y, zs) with {
& (a, []) -> eq? (a, x) (a, [b]) -> and (eq? (a, x), eq? (b, x))
& (a, [b, ...cs]) -> if eq? (a, x) (a, [b, ...cs]) -> if eq? (a, x)
& then recur (b, cs) then recur (b, cs)
& else false else false
& } }
& else false else false
} }
& &&& true & false: boolean logic (part the first) & &&& true & false: boolean logic (part the first)
& fn bool? { fn bool? {
& "Returns true if a value is of type :boolean." "Returns true if a value is of type :boolean."
& (false) -> true (false) -> true
& (true) -> true (true) -> true
& (_) -> false (_) -> false
& } }
& fn true? { fn true? {
& "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else."
& (true) -> true (true) -> true
& (_) -> false (_) -> false
& } }
& fn false? { fn false? {
& "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`."
& (false) -> true (false) -> true
& (_) -> false (_) -> false
& } }
& fn bool { fn bool {
& "Returns false if a value is nil or false, otherwise returns true." "Returns false if a value is nil or false, otherwise returns true."
& (nil) -> false (nil) -> false
& (false) -> false (false) -> false
& (_) -> true (_) -> true
& } }
& fn not { fn not {
& "Returns false if a value is truthy, true if a value is falsy." "Returns false if a value is truthy, true if a value is falsy."
& (nil) -> true (nil) -> true
& (false) -> true (false) -> true
& (_) -> false (_) -> false
& } }
& & tuples: not a lot you can do with them functionally & & tuples: not a lot you can do with them functionally
& fn tuple? { fn tuple? {
& "Returns true if a value is a tuple." "Returns true if a value is a tuple."
& (tuple as :tuple) -> true (tuple as :tuple) -> true
& (_) -> false (_) -> false
& } }
& &&& functions: getting things done & &&& functions: getting things done
& fn fn? { fn fn? {
& "Returns true if an argument is a function." "Returns true if an argument is a function."
& (f as :fn) -> true (f as :fn) -> true
& (_) -> false (_) -> false
& } }
& what we need for some very basic list manipulation & what we need for some very basic list manipulation
fn first { fn first {
"Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." "Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil."
([]) -> nil ([]) -> nil
& (()) -> nil (()) -> nil
& ("") -> nil & ("") -> nil
(xs as :list) -> base :first (xs) (xs as :list) -> base :first (xs)
& (xs as :tuple) -> base :first (xs) (xs as :tuple) -> base :first (xs)
& (str as :string) -> base :slice (str, 0, 1) & (str as :string) -> base :slice (str, 0, 1)
} }
fn rest { fn rest {
"Returns all but the first element of a list or tuple, as a list." "Returns all but the first element of a list or tuple, as a list."
([]) -> [] ([]) -> []
& (()) -> () (()) -> ()
(xs as :list) -> base :rest (xs) (xs as :list) -> base :rest (xs)
& (xs as :tuple) -> base :rest (xs) (xs as :tuple) -> base :rest (xs)
& (str as :string) -> base :rest (str) & (str as :string) -> base :rest (str)
} }
@ -136,50 +136,50 @@ fn inc {
(x as :number) -> base :inc (x) (x as :number) -> base :inc (x)
} }
& fn dec { fn dec {
& "Decrements a number." "Decrements a number."
& (x as :number) -> base :dec (x) (x as :number) -> base :dec (x)
& } }
& fn count { fn count {
& "Returns the number of elements in a collection (including string)." "Returns the number of elements in a collection (including string)."
& (xs as :list) -> base :count (xs) (xs as :list) -> base :count (xs)
& (xs as :tuple) -> base :count (xs) (xs as :tuple) -> base :count (xs)
& (xs as :dict) -> base :count (xs) (xs as :dict) -> base :count (xs)
& (xs as :string) -> base :count (xs) (xs as :string) -> base :count (xs)
& & (xs as :set) -> base :count (xs) & (xs as :set) -> base :count (xs)
& } }
& fn empty? { fn empty? {
& "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)."
& ([]) -> true ([]) -> true
& (#{}) -> true (#{}) -> true
& & (s as :set) -> eq? (s, ${}) & (s as :set) -> eq? (s, ${})
& (()) -> true (()) -> true
& ("") -> true ("") -> true
& (_) -> false (_) -> false
& } }
& fn any? { fn any? {
& "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)."
& ([...]) -> true ([...]) -> true
& (#{...}) -> true (#{...}) -> true
& & (s as :set) -> not (empty? (s)) & (s as :set) -> not (empty? (s))
& ((...)) -> true ((...)) -> true
& (s as :string) -> not (empty? (s)) (s as :string) -> not (empty? (s))
& (_) -> false (_) -> false
& } }
& fn list? { fn list? {
& "Returns true if the value is a list." "Returns true if the value is a list."
& (l as :list) -> true (l as :list) -> true
& (_) -> false (_) -> false
& } }
& fn list { fn list {
& "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples."
& (x) -> base :list (x) (x) -> base :list (x)
& } }
fn append { fn append {
"Adds an element to a list." "Adds an element to a list."
@ -203,43 +203,58 @@ fn fold {
} }
} }
& fn map { fn foldr {
& "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." "Folds a list, right-associatively."
& & (f as :fn) -> map (f, _) (f as :fn, []) -> []
& & (kw as :keyword) -> map (kw, _) (f as :fn, xs as :list) -> foldr(f, xs, f ())
& (f as :fn, xs) -> { (f as :fn, [], root) -> []
& fn mapper (prev, curr) -> append (prev, f (curr)) (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with {
& fold (mapper, xs, []) (prev, curr, []) -> f (curr, prev)
& } (prev, curr, remaining) -> recur (
& & (kw as :keyword, xs) -> { f (curr, prev)
& & fn mapper (prev, curr) -> append (prev, kw (curr)) first (remaining)
& & fold (mapper, xs, []) rest (remaining)
& & } )
& } }
}
& fn filter { fn map {
& "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`." "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away."
& (p? as :fn) -> filter (p?, _) (f as :fn) -> map (f, _)
& (p? as :fn, xs) -> { (kw as :keyword) -> map (kw, _)
& fn filterer (filtered, x) -> if p? (x) (f as :fn, xs) -> {
& then append (filtered, x) fn mapper (prev, curr) -> append (prev, f (curr))
& else filtered fold (mapper, xs, [])
& fold (filterer, xs, []) }
& } (kw as :keyword, xs) -> {
& } fn mapper (prev, curr) -> append (prev, kw (curr))
fold (mapper, xs, [])
}
}
& fn keep { fn filter {
& "Takes a list and returns a new list with any `nil` values omitted." "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`."
& (xs) -> filter (some?, xs) (p? as :fn) -> filter (p?, _)
& } (p? as :fn, xs) -> {
fn filterer (filtered, x) -> if p? (x)
then append (filtered, x)
else filtered
fold (filterer, xs, [])
}
}
& fn concat { fn keep {
& "Combines two lists, strings, or sets." "Takes a list and returns a new list with any `nil` values omitted."
& (x as :string, y as :string) -> "{x}{y}" (xs) -> filter (some?, xs)
& (xs as :list, ys as :list) -> base :concat (xs, ys) }
& & (xs as :set, ys as :set) -> base :concat (xs, ys)
& (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) fn concat {
& } "Combines two lists, strings, or sets."
(x as :string, y as :string) -> "{x}{y}"
(xs as :list, ys as :list) -> base :concat (xs, ys)
& (xs as :set, ys as :set) -> base :concat (xs, ys)
(xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys))
}
fn contains? { fn contains? {
"Returns true if a set or list contains a value." "Returns true if a set or list contains a value."
@ -252,44 +267,92 @@ fn contains? {
} }
} }
fn add { fn print! {
() -> 0 "Sends a text representation of Ludus values to the console."
(x) -> x (...args) -> {
(x, y) -> base :add (x, y) base :print! (args)
:ok
}
}
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
}
(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!
fn string? {
"Returns true if a value is a string."
(x as :string) -> true
(_) -> false
}
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
(x) -> show (x)
(x, ...xs) -> loop (string (x), xs) with {
(out, [y]) -> concat (out, show (y))
(out, [y, ...ys]) -> recur (concat (out, show (y)), ys)
}
} }
#{ #{
& type type
& coll? coll?
& ordered? ordered?
& assoc? assoc?
& nil? nil?
& some? some?
& some some
eq? eq?
& bool? bool?
& true? true?
& false? false?
& bool bool
& not not
& tuple? tuple?
& fn? fn?
first first
rest rest
& inc inc
& dec dec
& count count
& empty? empty?
& any? any?
& list? list?
& list list
& first first
& fold fold
& append foldr
& map append
& filter map
& keep filter
& concat keep
& contains? concat
add contains?
print!
show
report!
doc!
string
string?
} }

View File

@ -1,31 +1,4 @@
let test = 3
let quux = loop ([1, 2]) with {
([]) -> false
([x]) -> eq? (x, test)
([x, ...xs]) -> if eq? (x, test)
then :yes
else recur (xs)
}
let foo = :bar
fn not {
(false) -> true
(nil) -> true
(_) -> false
}
let frob = loop ([1, 2, 3]) with {
([]) -> false
([y]) -> eq? (y, test)
([y, ...ys]) -> if not (eq? (y, test))
then recur (ys)
else true
}
[quux, frob]

View File

@ -310,6 +310,11 @@ impl<'a> Compiler<'a> {
self.stack_depth += 1; self.stack_depth += 1;
} }
fn duplicate(&mut self) {
self.emit_op(Op::Duplicate);
self.stack_depth += 1;
}
fn pop(&mut self) { fn pop(&mut self) {
self.emit_op(Op::Pop); self.emit_op(Op::Pop);
self.stack_depth -= 1; self.stack_depth -= 1;
@ -428,7 +433,6 @@ impl<'a> Compiler<'a> {
self.tail_pos = false; self.tail_pos = false;
// increase the scope // increase the scope
self.enter_scope(); self.enter_scope();
// self.scope_depth += 1;
// stash the stack depth // stash the stack depth
let stack_depth = self.stack_depth; let stack_depth = self.stack_depth;
// evaluate all the lines but the last // evaluate all the lines but the last
@ -449,7 +453,6 @@ impl<'a> Compiler<'a> {
// we do this by pretending it's a binding // we do this by pretending it's a binding
(Let(patt, expr), _) => { (Let(patt, expr), _) => {
// self.match_depth = 0; // self.match_depth = 0;
// self.emit_op(Op::ResetMatch);
self.visit(expr); self.visit(expr);
let expr_pos = self.stack_depth - 1; let expr_pos = self.stack_depth - 1;
self.report_ast("let binding: matching".to_string(), patt); self.report_ast("let binding: matching".to_string(), patt);
@ -467,28 +470,20 @@ impl<'a> Compiler<'a> {
} }
} }
// we've made a new value, so increase the stack level in the compiler
self.stack_depth += 1;
// store the value in the return register // store the value in the return register
// self.emit_op(Op::Store);
self.store(); self.store();
// reset the scope // reset the scope
self.leave_scope(); self.leave_scope();
// self.scope_depth -= 1;
// while let Some(binding) = self.bindings.last() {
// if binding.depth > self.scope_depth {
// self.bindings.pop();
// } else {
// break;
// }
// }
// reset the stack // reset the stack
self.pop_n(self.stack_depth - stack_depth - 1); self.report_depth("leaving block before pop");
self.msg(format!(
"popping back from {} to {}",
self.stack_depth, stack_depth,
));
self.pop_n(self.stack_depth - stack_depth);
// load the value from the return register // load the value from the return register
self.load(); self.load();
// self.emit_op(Op::Load);
} }
If(cond, then, r#else) => { If(cond, then, r#else) => {
let tail_pos = self.tail_pos; let tail_pos = self.tail_pos;
@ -863,42 +858,44 @@ impl<'a> Compiler<'a> {
self.stack_depth -= 1; self.stack_depth -= 1;
} }
(Or, Arguments(args)) => { (Or, Arguments(args)) => {
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![]; let mut jump_idxes = vec![];
if !args.is_empty() { if !args.is_empty() {
for arg in args { let mut args = args.iter().rev();
let last = args.next().unwrap();
for arg in args.rev() {
self.visit(arg); self.visit(arg);
self.emit_op(Op::Stash); self.duplicate();
jump_idxes.push(self.stub_jump(Op::JumpIfTrue)); jump_idxes.push(self.stub_jump(Op::JumpIfTrue));
self.pop();
} }
self.visit(last);
for idx in jump_idxes { for idx in jump_idxes {
self.patch_jump(idx, self.len() - idx); self.patch_jump(idx, self.len() - idx - 3);
} }
// self.emit_op(Op::Load);
self.load();
} else { } else {
self.emit_op(Op::False); self.emit_op(Op::False);
self.stack_depth += 1;
} }
self.stack_depth = stack_depth + 1;
} }
(And, Arguments(args)) => { (And, Arguments(args)) => {
let stack_depth = self.stack_depth;
let mut jump_idxes = vec![]; let mut jump_idxes = vec![];
if !args.is_empty() { if !args.is_empty() {
for arg in args { let mut args = args.iter().rev();
let last = args.next().unwrap();
for arg in args.rev() {
self.visit(arg); self.visit(arg);
self.emit_op(Op::Stash); self.duplicate();
jump_idxes.push(self.stub_jump(Op::JumpIfFalse)); jump_idxes.push(self.stub_jump(Op::JumpIfFalse));
self.pop();
} }
self.visit(last);
for idx in jump_idxes { for idx in jump_idxes {
self.patch_jump(idx, self.len() - idx); self.patch_jump(idx, self.len() - idx - 3);
} }
// self.emit_op(Op::Load);
self.load();
} else { } else {
self.emit_op(Op::True); self.emit_op(Op::True);
self.stack_depth += 1;
} }
self.stack_depth = stack_depth + 1;
} }
(Word(fn_name), Arguments(args)) => { (Word(fn_name), Arguments(args)) => {
self.report_depth_str(format!("calling function {fn_name}")); self.report_depth_str(format!("calling function {fn_name}"));
@ -951,13 +948,12 @@ impl<'a> Compiler<'a> {
self.stack_depth -= 1; self.stack_depth -= 1;
} }
Arguments(args) => { Arguments(args) => {
self.emit_op(Op::Stash); self.store();
self.pop();
let arity = args.len(); let arity = args.len();
for arg in args { for arg in args {
self.visit(arg); self.visit(arg);
} }
self.emit_op(Op::Load); self.load();
if tail_pos && i == num_rest_terms - 1 { if tail_pos && i == num_rest_terms - 1 {
self.emit_op(Op::TailCall) self.emit_op(Op::TailCall)
} else { } else {
@ -1149,7 +1145,6 @@ impl<'a> Compiler<'a> {
} }
compiler.tail_pos = true; compiler.tail_pos = true;
compiler.visit(clause_body); compiler.visit(clause_body);
// compiler.emit_op(Op::Store);
compiler.store(); compiler.store();
compiler.scope_depth -= 1; compiler.scope_depth -= 1;
while let Some(binding) = compiler.bindings.last() { while let Some(binding) = compiler.bindings.last() {
@ -1391,7 +1386,6 @@ impl<'a> Compiler<'a> {
let allocated = Value::String(Rc::new(str.clone())); let allocated = Value::String(Rc::new(str.clone()));
self.emit_constant(allocated); self.emit_constant(allocated);
self.emit_op(Op::ConcatStrings); self.emit_op(Op::ConcatStrings);
self.stack_depth -= 1;
} }
StringPart::Word(word) => { StringPart::Word(word) => {
self.resolve_binding(word); self.resolve_binding(word);
@ -1399,6 +1393,7 @@ impl<'a> Compiler<'a> {
self.emit_op(Op::ConcatStrings); self.emit_op(Op::ConcatStrings);
} }
} }
self.stack_depth -= 1;
} }
} }
Do(terms) => { Do(terms) => {

View File

@ -284,7 +284,7 @@ impl<'a> Validator<'a> {
// check arity against fn info if first term is word and second term is args // check arity against fn info if first term is word and second term is args
Synthetic(first, second, rest) => { Synthetic(first, second, rest) => {
match (&first.0, &second.0) { match (&first.0, &second.0) {
(Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Tuple(_)) => { (Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Arguments(_)) => {
self.visit(second.as_ref()) self.visit(second.as_ref())
} }
(Ast::Word(_), Ast::Keyword(_)) => self.visit(first.as_ref()), (Ast::Word(_), Ast::Keyword(_)) => self.visit(first.as_ref()),