diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 186f1f5..6129593 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -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 diff --git a/pkg/rudus.js b/pkg/rudus.js index 65e5e4e..5357d14 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -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) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index ee3a380..f45f304 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de73e134772464686f83ecc0389fb8661f8946f705edeb896cc493578814356e -size 16770813 +oid sha256:2cd3cbbcbc8f12030dfd832a0adef051511837e7fd5c5c6fc6f8bad1a46f52ae +size 16774601 diff --git a/src/base.rs b/src/base.rs index 4292d0f..c2607ce 100644 --- a/src/base.rs +++ b/src/base.rs @@ -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"), } } diff --git a/src/compiler.rs b/src/compiler.rs index 1256b94..e225b0b 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -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} ==="); diff --git a/src/errors.rs b/src/errors.rs index cb0f72f..6f20cf6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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));