do some things and stuff?

This commit is contained in:
Scott Richmond 2025-07-06 16:17:21 -04:00
parent 7a028fbaa2
commit 1337da395c
6 changed files with 176 additions and 100 deletions

View File

@ -22,19 +22,20 @@ fn coll? {
(_) -> false
}
fn ordered? {
"Returns true if a value is an indexed collection: list or tuple."
fn indexed? {
"Returns true if a value is indexed (can use `at`): list, tuple, or string."
(coll as :list) -> true
(coll as :tuple) -> true
(coll as :string) -> true
(_) -> false
}
fn assoc? {
"Returns true if a value is an associative collection: a dict or a pkg."
(d as :dict) -> true
(_) -> false
}
& for now we don't need this: we don't have pkgs
& fn assoc? {
& "Returns true if a value is an associative collection: a dict or a pkg."
& (d as :dict) -> true
& (_) -> false
& }
& &&& nil: working with nothing
@ -121,13 +122,13 @@ fn fn? {
& what we need for some very basic list manipulation
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: tuple, list, or string. If the collection is empty, returns nil."
([]) -> nil
(()) -> nil
& ("") -> nil
("") -> nil
(xs as :list) -> base :first (xs)
(xs as :tuple) -> base :first (xs)
& (str as :string) -> base :slice (str, 0, 1)
(str as :string) -> base :first (str)
}
fn rest {
@ -136,7 +137,13 @@ fn rest {
(()) -> ()
(xs as :list) -> base :rest (xs)
(xs as :tuple) -> base :rest (xs)
& (str as :string) -> base :rest (str)
(str as :string) -> base :rest (str)
}
fn last {
"Returns the last element of a list or tuple."
(xs) if indexed? (xs) -> base :last (xs)
(_) -> nil
}
fn inc {
@ -178,6 +185,21 @@ fn any? {
(_) -> false
}
fn at {
"Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string."
(i as :number) -> at (_, i)
(xs as :list, i as :number) -> base :at (xs, i)
(xs as :tuple, i as :number) -> base :at (xs, i)
(str as :string, i as :number) -> base :at (str, i)
(_) -> nil
}
fn second {
"Returns the second element of a list or tuple."
(xs) if indexed? (xs) -> at (xs, 1)
(_) -> nil
}
fn list? {
"Returns true if the value is a list."
(l as :list) -> true
@ -185,7 +207,7 @@ 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 atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`."
(x) -> base :list (x)
}
@ -257,7 +279,9 @@ fn keep {
}
fn concat {
"Combines two lists, strings, or sets."
"Combines lists, strings, or sets."
(x as :string) -> x
(xs as :list) -> xs
(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)
@ -265,7 +289,7 @@ fn concat {
}
fn contains? {
"Returns true if a set or list contains a value."
"Returns true if a list contains a value."
& (value, s as :set) -> bool (base :get (s, value))
(value, l as :list) -> loop (l) with {
([]) -> false
@ -363,18 +387,13 @@ fn downcase {
}
fn chars {
"Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters."
"Takes a string and returns its characters as a list. Each member of the list corresponds to a utf-8 character."
(str as :string) -> match base :chars (str) with {
(:ok, chrs) -> chrs
(:err, msg) -> panic! msg
}
}
fn chars/safe {
"Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters."
(str as :string) -> base :chars (str)
}
fn ws? {
"Tells if a string is a whitespace character."
(" ") -> true
@ -395,16 +414,24 @@ fn strip {
(x) -> x
}
fn condenser (charlist, curr_char) -> if and (
ws? (curr_char)
do charlist > last > ws?)
then charlist
else append (charlist, curr_char)
fn condense {
"Condenses the whitespace in a string. All whitespace will be replaced by spaces, and any repeated whitespace will be reduced to a single space."
(str as :string) -> {
let chrs = chars (str)
let condensed = fold (condenser, chrs, [])
join (condensed)
}
}
fn words {
"Takes a string and returns a list of the words in the string. Strips all whitespace."
(str as :string) -> {
let no_punct = strip (str)
let strs = split (no_punct, " ")
fn worder (l, s) -> if empty? (s)
then l
else append (l, s)
fold (worder, strs, [])
}
(str as :string) -> do str > condense > strip > split (_, " ")
}
fn sentence {
@ -804,35 +831,6 @@ fn range {
}
& additional list operations now that we have comparitors
fn at {
"Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string."
(xs as :list, n as :number) -> base :at (xs, n)
(xs as :tuple, n as :number) -> base :at (xs, n)
& (str as :string, n as :number) -> {
& let raw = base :at (str, n)
& when {
& nil? (raw) -> nil
& gte? (raw, 128) -> panic! "not an ASCII char"
& true -> base :str_slice (str, n, inc (n))
& }
& }
(_) -> nil
}
& fn first {
& "Returns the first element of a list or tuple."
& (xs) if ordered? -> at (xs, 0)
& }
fn second {
"Returns the second element of a list or tuple."
(xs) if ordered? (xs) -> at (xs, 1)
}
fn last {
"Returns the last element of a list or tuple."
(xs) if ordered? (xs) -> at (xs, dec (count (xs)))
}
fn slice {
"Returns a slice of a list or a string, representing a sub-list or sub-string."
@ -843,12 +841,14 @@ fn slice {
neg? (start) -> slice (xs, 0, end)
true -> base :slice (xs, start, end)
}
(str as :string, end as :number) -> base :str_slice (str, 0, end)
(str as :string, start as :number, end as :number) -> base :str_slice (str, start, end)
(str as :string, end as :number) -> base :slice (str, 0, end)
(str as :string, start as :number, end as :number) -> base :slice (str, start, end)
}
fn slice_n {
"Returns a slice of a list or a string, representing a sub-list or sub-string."
(xs as :list, n as :number) -> slice (xs, 0, n)
(str as :string, n as :number) -> slice (str, 0, n)
(xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n))
(str as :string, start as :number, n as :number) -> slice (str, start, add (start, n))
}
@ -858,6 +858,54 @@ fn butlast {
(xs as :list) -> slice (xs, 0, dec (count (xs)))
}
fn indices_of {
"Takes a list or string and returns a list of all the indices where the scrutinee appears. Returns an empty list if the scrutinee does not appear in the search target."
(target as :list, scrutinee) -> {
fn searcher ((i, indices), curr) -> if eq? (scrutinee, curr)
then (inc (i), append (indices, i))
else (inc (i), indices)
let (_, idxes) = fold (searcher, target, (0, []))
idxes
}
& (target as :string, scrutinee as :string) -> {
& let scrut_len = count (scrutinee)
& fn searcher ((i, indices), curr) -> {
& let srch_substr = slice_n (remaining, scrut_len)
& if eq? (scrutinee, srch_substr)
& then (inc (i), append (indices, i))
& else (inc (i), indices)
& }
& let (_, idxes) = fold (searcher, target, (0, []))
& idxes
& }
}
fn index_of {
"Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target."
(target as :list, scrutinee) -> first (indices_of (target, scrutinee))
(target as :string, scrutinee as :string) -> first (indices_of (target, scrutinee))
}
& (target as :list, scrutinee) -> loop (0) with {
& (i) if gte? (i, count (target)) -> nil
& (i) -> if eq? (scrutinee, at (target, i))
& then i
& else recur (inc (i))
& }
& (target as :string, scrutinee as :string) -> {
& let scrut_len = count (scrutinee)
& loop (0, target) with {
& (i, "") -> nil
& (i, remaining) -> {
& let srch_substr = slice_n (remaining, scrut_len)
& if eq? (scrutinee, srch_substr)
& then i
& else recur (inc (i), rest (remaining))
& }
& }
& }
& }
&&& keywords: funny names
fn keyword? {
"Returns true if a value is a keyword, otherwise returns false."
@ -865,6 +913,13 @@ fn keyword? {
(_) -> false
}
fn key? {
"Returns true if a value can be used as a key in a dict: if it's a string or a keyword."
(kw as :keyword) -> true
(str as :string) -> true
(_) -> false
}
& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc.
fn assoc {
@ -872,7 +927,9 @@ fn assoc {
() -> #{}
(d as :dict) -> d
(d as :dict, k as :keyword, val) -> base :assoc (d, k, val)
(d as :dict, k as :string, val) -> base :assoc (d, k, val)
(d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val)
(d as :dict, (k as :string, val)) -> base :assoc (d, k, val)
}
fn dissoc {
@ -881,18 +938,17 @@ fn dissoc {
(d as :dict, k as :keyword) -> base :dissoc (d, k)
}
& TODO: consider merging `get` and `at`
fn get {
"Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value."
(k as :keyword) -> get (k, _)
(k as :keyword, d as :dict) -> base :get (d, k)
(k as :keyword, d as :dict, default) -> match base :get (d, k) with {
(d as :dict, k as :keyword) -> base :get (d, k)
(d as :dict, k as :keyword, default) -> match base :get (d, k) with {
nil -> default
val -> val
}
(k as :string) -> get (k, _)
(k as :string, d as :dict) -> base :get (d, k)
(k as :string, d as :dict, default) -> match base :get (d, k) with {
(d as :dict, k as :string) -> base :get (d, k)
(d as :dict, k as :string, default) -> match base :get (d, k) with {
nil -> default
val -> val
}
@ -914,15 +970,14 @@ fn values {
(d as :dict) -> do d > list > map (second, _)
}
& TODO: add sets to this?
fn has? {
"Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key."
(k as :keyword) -> has? (k, _)
(k as :keyword, d as :dict) -> do d > get (k) > some?
(k as :keyword) -> has? (_, k)
(d as :dict, k as :keyword) -> do d > get (k) > some?
}
fn dict {
"Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed."
"Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed."
(d as :dict) -> d
(l as :list) -> fold (assoc, l)
(t as :tuple) -> do t > list > dict
@ -1118,7 +1173,7 @@ fn fetch {
let pid = self ()
spawn! (fn () -> request_fetch! (pid, url))
receive {
(:reply, response) -> response
(:reply, (_, response)) -> response
}
}
}
@ -1495,7 +1550,7 @@ fn key_pressed? {
append
assert!
assoc
assoc?
& assoc?
at
atan/2
back!
@ -1511,12 +1566,12 @@ fn key_pressed? {
cdr
ceil
chars
chars/safe
clear!
coll?
colors
colours
concat
condense
cons
console
contains?
@ -1561,6 +1616,9 @@ fn key_pressed? {
hideturtle!
home!
inc
indexed?
index_of
indices_of
inv
inv/0
inv/safe
@ -1589,7 +1647,6 @@ fn key_pressed? {
odd?
ok
ok?
ordered?
pc!
pd!
pencolor

View File

@ -403,7 +403,7 @@ function __wbg_get_imports() {
_assertBoolean(ret);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper8157 = function() { return logError(function (arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper8160 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20);
return ret;
}, arguments) };

BIN
pkg/rudus_bg.wasm (Stored with Git LFS)

Binary file not shown.

View File

@ -83,18 +83,9 @@ pub fn r#bool(x: &Value) -> Value {
pub fn chars(x: &Value) -> Value {
match x {
Value::Interned(s) => {
let chars = s.chars();
let mut charlist = vector![];
for char in chars {
if char.is_ascii() {
charlist.push_back(Value::String(Rc::new(char.to_string())))
} else {
return Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::String(Rc::new(format!("{char} is not an ascii character"))),
]));
}
for char in s.chars() {
charlist.push_back(Value::string(char.to_string()))
}
Value::Tuple(Rc::new(vec![
Value::Keyword("ok"),
@ -102,18 +93,9 @@ pub fn chars(x: &Value) -> Value {
]))
}
Value::String(s) => {
let chars = s.chars();
let mut charlist = vector![];
for char in chars {
if char.is_ascii() {
charlist.push_back(Value::String(Rc::new(char.to_string())))
} else {
return Value::Tuple(Rc::new(vec![
Value::Keyword("err"),
Value::String(Rc::new(format!("{char} is not an ascii character"))),
]));
}
for char in s.chars() {
charlist.push_back(Value::string(char.to_string()))
}
Value::Tuple(Rc::new(vec![
Value::Keyword("ok"),
@ -210,6 +192,14 @@ pub fn first(ordered: &Value) -> Value {
Some(n) => n.clone(),
None => Value::Nil,
},
Value::String(s) => match s.chars().next() {
Some(char) => Value::string(char.to_string()),
None => Value::Nil,
},
Value::Interned(s) => match s.chars().next() {
Some(char) => Value::string(char.to_string()),
None => Value::Nil,
},
_ => unreachable!("internal Ludus error"),
}
}
@ -232,6 +222,20 @@ pub fn at(ordered: &Value, i: &Value) -> Value {
None => Value::Nil,
}
}
(Value::String(s), Value::Number(n)) => {
let i = f64::from(*n) as usize;
match s.chars().nth(i) {
Some(n) => Value::string(n.to_string()),
None => Value::Nil,
}
}
(Value::Interned(s), Value::Number(n)) => {
let i = f64::from(*n) as usize;
match s.chars().nth(i) {
Some(n) => Value::string(n.to_string()),
None => Value::Nil,
}
}
_ => unreachable!("internal Ludus error"),
}
}
@ -264,6 +268,14 @@ pub fn last(ordered: &Value) -> Value {
Some(x) => x.clone(),
None => Value::Nil,
},
Value::String(s) => match s.chars().last() {
Some(char) => Value::string(char.to_string()),
None => Value::Nil,
},
Value::Interned(s) => match s.chars().last() {
Some(char) => Value::string(char.to_string()),
None => Value::Nil,
},
_ => unreachable!("internal Ludus error"),
}
}

View File

@ -1286,7 +1286,7 @@ impl Compiler {
let mut chunks = vec![];
for (arity, mut compiler) in compilers {
compiler.emit_op(Op::PanicNoMatch);
compiler.emit_op(Op::PanicNoFnMatch);
let chunk = compiler.chunk;
if self.debug {
println!("=== function chuncktion: {name}/{arity} ===");

View File

@ -98,9 +98,16 @@ pub fn panic(panic: Panic) -> String {
// panic.call_stack.last().unwrap().chunk().dissasemble();
// console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
let mut msgs = vec![];
let msg = match panic.msg {
let Panic { msg, scrutinee, .. } = &panic;
let msg = match msg {
PanicMsg::Generic(ref s) => s,
_ => &"no match".to_string(),
PanicMsg::NoFnMatch => &format!(
"no match against {}",
scrutinee
.as_ref()
.expect("expect a match panic to have a scrutinee")
),
_ => &String::from("no match"),
};
msgs.push(msg.clone());
msgs.push(traceback(&panic));