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 (_) -> false
} }
fn ordered? { fn indexed? {
"Returns true if a value is an indexed collection: list or tuple." "Returns true if a value is indexed (can use `at`): list, tuple, or string."
(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? { & for now we don't need this: we don't have pkgs
"Returns true if a value is an associative collection: a dict or a pkg." & fn assoc? {
(d as :dict) -> true & "Returns true if a value is an associative collection: a dict or a pkg."
(_) -> false & (d as :dict) -> true
} & (_) -> false
& }
& &&& nil: working with nothing & &&& nil: working with nothing
@ -121,13 +122,13 @@ fn fn? {
& 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: tuple, list, or string. 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 :first (str)
} }
fn rest { fn rest {
@ -136,7 +137,13 @@ fn rest {
(()) -> () (()) -> ()
(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)
}
fn last {
"Returns the last element of a list or tuple."
(xs) if indexed? (xs) -> base :last (xs)
(_) -> nil
} }
fn inc { fn inc {
@ -178,6 +185,21 @@ fn any? {
(_) -> false (_) -> 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? { 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
@ -185,7 +207,7 @@ fn list? {
} }
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) (x) -> base :list (x)
} }
@ -257,7 +279,9 @@ fn keep {
} }
fn concat { 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}" (x as :string, y as :string) -> "{x}{y}"
(xs as :list, ys as :list) -> base :concat (xs, ys) (xs as :list, ys as :list) -> base :concat (xs, ys)
& (xs as :set, ys as :set) -> base :concat (xs, ys) & (xs as :set, ys as :set) -> base :concat (xs, ys)
@ -265,7 +289,7 @@ fn concat {
} }
fn contains? { 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, s as :set) -> bool (base :get (s, value))
(value, l as :list) -> loop (l) with { (value, l as :list) -> loop (l) with {
([]) -> false ([]) -> false
@ -363,18 +387,13 @@ fn downcase {
} }
fn chars { 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 { (str as :string) -> match base :chars (str) with {
(:ok, chrs) -> chrs (:ok, chrs) -> chrs
(:err, msg) -> panic! msg (: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? { fn ws? {
"Tells if a string is a whitespace character." "Tells if a string is a whitespace character."
(" ") -> true (" ") -> true
@ -395,16 +414,24 @@ fn strip {
(x) -> x (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 { fn words {
"Takes a string and returns a list of the words in the string. Strips all whitespace." "Takes a string and returns a list of the words in the string. Strips all whitespace."
(str as :string) -> { (str as :string) -> do str > condense > strip > split (_, " ")
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, [])
}
} }
fn sentence { fn sentence {
@ -804,35 +831,6 @@ fn range {
} }
& additional list operations now that we have comparitors & 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 { fn slice {
"Returns a slice of a list or a string, representing a sub-list or sub-string." "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) neg? (start) -> slice (xs, 0, end)
true -> base :slice (xs, start, end) true -> base :slice (xs, start, end)
} }
(str as :string, end as :number) -> base :str_slice (str, 0, end) (str as :string, end as :number) -> base :slice (str, 0, end)
(str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) (str as :string, start as :number, end as :number) -> base :slice (str, start, end)
} }
fn slice_n { fn slice_n {
"Returns a slice of a list or a string, representing a sub-list or sub-string." "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)) (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)) (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))) (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 &&& keywords: funny names
fn keyword? { fn keyword? {
"Returns true if a value is a keyword, otherwise returns false." "Returns true if a value is a keyword, otherwise returns false."
@ -865,6 +913,13 @@ fn keyword? {
(_) -> false (_) -> 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. & 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 { fn assoc {
@ -872,7 +927,9 @@ fn assoc {
() -> #{} () -> #{}
(d as :dict) -> d (d as :dict) -> d
(d as :dict, k as :keyword, 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)
(d as :dict, (k as :keyword, 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 { fn dissoc {
@ -881,18 +938,17 @@ fn dissoc {
(d as :dict, k as :keyword) -> base :dissoc (d, k) (d as :dict, k as :keyword) -> base :dissoc (d, k)
} }
& TODO: consider merging `get` and `at`
fn get { 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." "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) -> get (k, _)
(k as :keyword, d as :dict) -> base :get (d, k) (d as :dict, k as :keyword) -> base :get (d, k)
(k as :keyword, d as :dict, default) -> match base :get (d, k) with { (d as :dict, k as :keyword, default) -> match base :get (d, k) with {
nil -> default nil -> default
val -> val val -> val
} }
(k as :string) -> get (k, _) (k as :string) -> get (k, _)
(k as :string, d as :dict) -> base :get (d, k) (d as :dict, k as :string) -> base :get (d, k)
(k as :string, d as :dict, default) -> match base :get (d, k) with { (d as :dict, k as :string, default) -> match base :get (d, k) with {
nil -> default nil -> default
val -> val val -> val
} }
@ -914,15 +970,14 @@ fn values {
(d as :dict) -> do d > list > map (second, _) (d as :dict) -> do d > list > map (second, _)
} }
& TODO: add sets to this?
fn has? { fn has? {
"Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." "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) -> has? (_, k)
(k as :keyword, d as :dict) -> do d > get (k) > some? (d as :dict, k as :keyword) -> do d > get (k) > some?
} }
fn dict { 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 (d as :dict) -> d
(l as :list) -> fold (assoc, l) (l as :list) -> fold (assoc, l)
(t as :tuple) -> do t > list > dict (t as :tuple) -> do t > list > dict
@ -1118,7 +1173,7 @@ fn fetch {
let pid = self () let pid = self ()
spawn! (fn () -> request_fetch! (pid, url)) spawn! (fn () -> request_fetch! (pid, url))
receive { receive {
(:reply, response) -> response (:reply, (_, response)) -> response
} }
} }
} }
@ -1495,7 +1550,7 @@ fn key_pressed? {
append append
assert! assert!
assoc assoc
assoc? & assoc?
at at
atan/2 atan/2
back! back!
@ -1511,12 +1566,12 @@ fn key_pressed? {
cdr cdr
ceil ceil
chars chars
chars/safe
clear! clear!
coll? coll?
colors colors
colours colours
concat concat
condense
cons cons
console console
contains? contains?
@ -1561,6 +1616,9 @@ fn key_pressed? {
hideturtle! hideturtle!
home! home!
inc inc
indexed?
index_of
indices_of
inv inv
inv/0 inv/0
inv/safe inv/safe
@ -1589,7 +1647,6 @@ fn key_pressed? {
odd? odd?
ok ok
ok? ok?
ordered?
pc! pc!
pd! pd!
pencolor pencolor

View File

@ -403,7 +403,7 @@ function __wbg_get_imports() {
_assertBoolean(ret); _assertBoolean(ret);
return 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); const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20);
return ret; return ret;
}, arguments) }; }, 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 { pub fn chars(x: &Value) -> Value {
match x { match x {
Value::Interned(s) => { Value::Interned(s) => {
let chars = s.chars();
let mut charlist = vector![]; let mut charlist = vector![];
for char in chars { for char in s.chars() {
if char.is_ascii() { charlist.push_back(Value::string(char.to_string()))
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"))),
]));
}
} }
Value::Tuple(Rc::new(vec![ Value::Tuple(Rc::new(vec![
Value::Keyword("ok"), Value::Keyword("ok"),
@ -102,18 +93,9 @@ pub fn chars(x: &Value) -> Value {
])) ]))
} }
Value::String(s) => { Value::String(s) => {
let chars = s.chars();
let mut charlist = vector![]; let mut charlist = vector![];
for char in chars { for char in s.chars() {
if char.is_ascii() { charlist.push_back(Value::string(char.to_string()))
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"))),
]));
}
} }
Value::Tuple(Rc::new(vec![ Value::Tuple(Rc::new(vec![
Value::Keyword("ok"), Value::Keyword("ok"),
@ -210,6 +192,14 @@ pub fn first(ordered: &Value) -> Value {
Some(n) => n.clone(), Some(n) => n.clone(),
None => Value::Nil, 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"), _ => unreachable!("internal Ludus error"),
} }
} }
@ -232,6 +222,20 @@ pub fn at(ordered: &Value, i: &Value) -> Value {
None => Value::Nil, 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"), _ => unreachable!("internal Ludus error"),
} }
} }
@ -264,6 +268,14 @@ pub fn last(ordered: &Value) -> Value {
Some(x) => x.clone(), Some(x) => x.clone(),
None => Value::Nil, 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"), _ => unreachable!("internal Ludus error"),
} }
} }

View File

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

View File

@ -98,9 +98,16 @@ pub fn panic(panic: Panic) -> String {
// panic.call_stack.last().unwrap().chunk().dissasemble(); // panic.call_stack.last().unwrap().chunk().dissasemble();
// console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
let mut msgs = vec![]; let mut msgs = vec![];
let msg = match panic.msg { let Panic { msg, scrutinee, .. } = &panic;
let msg = match msg {
PanicMsg::Generic(ref s) => s, 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(msg.clone());
msgs.push(traceback(&panic)); msgs.push(traceback(&panic));