diff --git a/assets/prelude.ld b/assets/prelude.ld index d1c5b4c..6bd6fae 100644 --- a/assets/prelude.ld +++ b/assets/prelude.ld @@ -1,36 +1,18 @@ -& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports - -& let base = base - -& some forward declarations -& TODO: fix this so that we don't need (as many of) them -& fn and -fn append -fn apply_command -fn assoc -fn atan/2 -fn deg/rad -fn dict -fn first -fn floor -fn get -fn join -fn mod -fn neg? -fn print! -fn some? -fn store! -fn string -fn turn/rad -fn unbox -fn update! +&&& buffers: shared memory with Rust +& use types that are all either empty or any +box console = [] +box input = nil +box fetch_outbox = "" +box fetch_inbox = () +box keys_down = [] +& the very base: know something's type fn type { "Returns a keyword representing the type of the value passed in." (x) -> base :type (x) } -& some helper type functions +& & some helper type functions fn coll? { "Returns true if a value is a collection: dict, list, tuple, or set." (coll as :dict) -> true @@ -40,21 +22,22 @@ 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 +& &&& nil: working with nothing fn nil? { "Returns true if a value is nil." @@ -81,7 +64,7 @@ fn eq? { (x, y) -> base :eq? (x, y) (x, y, ...zs) -> if eq? (x, y) 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) then recur (b, cs) else false @@ -89,7 +72,7 @@ fn eq? { else false } -&&& true & false: boolean logic (part the first) +& &&& true & false: boolean logic (part the first) fn bool? { "Returns true if a value is of type :boolean." (false) -> true @@ -123,28 +106,14 @@ fn not { (_) -> false } -& fn neq? { -& "Returns true if none of the arguments have the same value." -& (x) -> false -& (x, y) -> not (eq? (x, y)) -& (x, y, ...zs) -> if eq? (x, y) -& then false -& else loop (y, zs) with { -& (a, []) -> neq? (a, x) -& (a, [b, ...cs]) -> if neq? (a, x) -& then recur (b, cs) -& else false -& } -& } - -& tuples: not a lot you can do with them functionally +& & tuples: not a lot you can do with them functionally fn tuple? { "Returns true if a value is a tuple." (tuple as :tuple) -> true (_) -> false } -&&& functions: getting things done +& &&& functions: getting things done fn fn? { "Returns true if an argument is a function." (f as :fn) -> true @@ -152,13 +121,30 @@ fn fn? { } & what we need for some very basic list manipulation +fn first { + "Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil." + ([]) -> nil + (()) -> nil + ("") -> nil + (xs as :list) -> base :first (xs) + (xs as :tuple) -> base :first (xs) + (str as :string) -> base :first (str) +} + fn rest { "Returns all but the first element of a list or tuple, as a list." ([]) -> [] (()) -> () (xs as :list) -> base :rest (xs) (xs as :tuple) -> base :rest (xs) - (xs as :string) -> base :str_slice (xs, 1) + (str as :string) -> base :rest (str) +} + +fn last { + "Returns the last element of an indexed value: list, tuple, or string." + (xs as :list) -> base :last (xs) + (xs as :tuple) -> base :last (xs) + (str as :string) -> base :last (str) } fn inc { @@ -200,6 +186,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 @@ -207,8 +208,15 @@ 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." - (x) -> base :to_list (x) + "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) +} + +fn append { + "Adds an element to a list." + () -> [] + (xs as :list) -> xs + (xs as :list, x) -> base :append (xs, x) } fn fold { @@ -226,6 +234,21 @@ fn fold { } } +fn foldr { + "Folds a list, right-associatively." + (f as :fn, []) -> [] + (f as :fn, xs as :list) -> foldr(f, xs, f ()) + (f as :fn, [], root) -> [] + (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { + (prev, curr, []) -> f (curr, prev) + (prev, curr, remaining) -> recur ( + f (curr, prev) + first (remaining) + rest (remaining) + ) + } +} + fn map { "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." (f as :fn) -> map (f, _) @@ -256,200 +279,28 @@ fn keep { (xs) -> filter (some?, xs) } -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj (xs, x) - & (xs as :set) -> xs - & (xs as :set, x) -> base :conj (xs, x) -} - -fn append! { - "Adds an element to a list, modifying it." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj! (xs, x) -} - 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) (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) } -& fn set { -& "Takes an ordered collection--list or tuple--and turns it into a set. Returns sets unharmed." -& (xs as :list) -> fold (append, xs, ${}) -& (xs as :tuple) -> do xs > list > set -& (xs as :set) -> xs -& } - -& fn set? { -& "Returns true if a value is a set." -& (xs as :set) -> true -& (_) -> false -& } - 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 - ([x]) -> eq? (x, value) - ([x, ...xs]) -> if eq? (x, value) + ([...xs]) -> if eq? (first(xs), value) then true - else recur (xs) + else recur (rest (xs)) } } -& fn omit { -& "Returns a new set with the value omitted." -& (value, s as :set) -> base :disj (s, value) -& } - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - 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 (x, xs) with { - (out, [y]) -> concat (out, show (y)) - (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, break as :string) -> base :split (break, str) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -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." - (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 - ("\n") -> true - ("\t") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (x) -> x -} - -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, []) - } -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :to_number (num) -} - &&& boxes: mutable state and state changes - fn box? { "Returns true if a value is a box." (b as :box) -> true @@ -478,6 +329,150 @@ fn update! { } } +&&& strings: harder than they look! +fn string? { + "Returns true if a value is a string." + (x as :string) -> true + (_) -> 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 + (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) + } +} + +fn join { + "Takes a list of strings, and joins them into a single string, interposing an optional separator." + ([]) -> "" + ([str as :string]) -> str + (strs as :list) -> join (strs, "") + ([], separator as :string) -> "" + ([str as :string], separator as :string) -> str + ([str, ...strs], separator as :string) -> fold ( + fn (joined, to_join) -> concat (joined, separator, to_join) + strs + str + ) +} + +fn split { + "Takes a string, and turns it into a list of strings, breaking on the separator." + (str as :string, splitter as :string) -> base :split (str, splitter) +} + +fn trim { + "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." + (str as :string) -> base :trim (str) + (str as :string, :left) -> base :triml (str) + (str as :string, :right) -> base :trimr (str) +} + +fn upcase { + "Takes a string and returns it in all uppercase. Works only for ascii characters." + (str as :string) -> base :upcase (str) +} + +fn downcase { + "Takes a string and returns it in all lowercase. Works only for ascii characters." + (str as :string) -> base :downcase (str) +} + +fn chars { + "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 ws? { + "Tells if a string is a whitespace character." + (" ") -> true + ("\t") -> true + ("\n") -> true + ("\r") -> true + (_) -> false +} + +fn strip { + "Removes punctuation from a string, removing all instances of ,.;:?!" + ("{x},{y}") -> strip ("{x}{y}") + ("{x}.{y}") -> strip ("{x}{y}") + ("{x};{y}") -> strip ("{x}{y}") + ("{x}:{y}") -> strip ("{x}{y}") + ("{x}?{y}") -> strip ("{x}{y}") + ("{x}!{y}") -> strip ("{x}{y}") + (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) -> base :words (str) +} + +fn sentence { + "Takes a list of words and turns it into a sentence." + (strs as :list) -> join (strs, " ") +} + +fn to_number { + "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." + (num as :string) -> base :number (num) +} + +fn print! { + "Sends a text representation of Ludus values to the console." + (...args) -> { + base :print! (args) + let line = do args > map (string, _) > join (_, " ") + update! (console, append (_, line)) + :ok + } +} + +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! + (_) -> nil +} + &&& numbers, basically: arithmetic and not much else, yet & TODO: add nan?, fn number? { @@ -515,6 +510,11 @@ fn mult { ((x, y), scalar as :number) -> mult (scalar, (x, y)) } +fn pow { + "Raises a number to the power of another number." + (x as :number, y as :number) -> base :pow (x, y) +} + fn div { "Divides numbers. Panics on division by zero." (x as :number) -> x @@ -563,22 +563,11 @@ fn inv/safe { (x as :number) -> div/safe (1, x) } -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - fn neg { "Multiplies a number by -1, negating it." (n as :number) -> mult (n, -1) } -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - fn zero? { "Returns true if a number is 0." (0) -> true @@ -653,197 +642,10 @@ fn pos? { (_) -> false } -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn min { - "Returns the number in its arguments that is closest to negative infinity." - (x as :number) -> x - (x as :number, y as :number) -> if base :lt? (x, y) then x else y - (x, y, ...zs) -> fold (min, zs, min (x, y)) -} - -fn max { - "Returns the number in its arguments that is closest to positive infinity." - (x as :number) -> x - (x as :number, y as :number) -> if gt? (x, y) then x else y - (x, y, ...zs) -> fold (max, zs, max (x, y)) -} - -& 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 :nth (n, xs) - (xs as :tuple, n as :number) -> base :nth (n, xs) - (str as :string, n as :number) -> { - let raw = base :nth (n, str) - 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 butlast { - "Returns a list, omitting the last element." - (xs as :list) -> base :slice (xs, 0, dec (count (xs))) -} - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - 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) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> 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: make `and` and `or` special forms which lazily evaluate arguments -& fn and { -& "Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :and (x, y) -& (x, y, ...zs) -> fold (and, zs, base :and (x, y)) -& } - -& fn or { -& "Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :or (x, y) -& (x, y, ...zs) -> fold (or, zs, base :or (x, y)) -& } - -fn assoc { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (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) -} - -fn dissoc { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> base :assoc (d, k, updater (get (k, d))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -& fn diff { -& "Takes two dicts and returns a dict describing their differences. Does this shallowly, offering diffs only for keys in the original dict." -& (d1 as :dict, d2 as :dict) -> { -& let key1 = keys (d1) -& let key2 = keys (d2) -& let all = do concat (d1, d2) > set > list -& let diffs = loop (all, []) with { -& & TODO: reduce this redundancy? -& ([k, ...ks], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then recur (ks, diffs) -& else recur (ks, append (diffs, (k, (v1, v2)))) -& } -& ([k], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then diffs -& else append (diffs, (k, (v1, v2))) -& } -& } -& dict (diffs) -& } -& } - -& 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) -> get (k, d, nil) - (k as :keyword, d as :dict, default) -> base :get (k, d, default) -} - -& 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> k > nil? -} - -fn dict { - "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 -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } +fn abs { + "Returns the absolute value of a number." + (0) -> 0 + (n as :number) -> if neg? (n) then mult (-1, n) else n } &&& Trigonometry functions @@ -856,6 +658,36 @@ let pi = base :pi let tau = mult (2, pi) +fn turn/deg { + "Converts an angle in turns to an angle in degrees." + (a as :number) -> mult (a, 360) +} + +fn deg/turn { + "Converts an angle in degrees to an angle in turns." + (a as :number) -> div (a, 360) +} + +fn turn/rad { + "Converts an angle in turns to an angle in radians." + (a as :number) -> mult (a, tau) +} + +fn rad/turn { + "Converts an angle in radians to an angle in turns." + (a as :number) -> div (a, tau) +} + +fn deg/rad { + "Converts an angle in degrees to an angle in radians." + (a as :number) -> mult (tau, div (a, 360)) +} + +fn rad/deg { + "Converts an angle in radians to an angle in degrees." + (a as :number) -> mult (360, div (a, tau)) +} + fn sin { "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." (a as :number) -> do a > turn/rad > base :sin @@ -889,35 +721,6 @@ fn rotate { ) } -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} fn atan/2 { "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." @@ -929,6 +732,11 @@ fn atan/2 { ((x, y), units as :keyword) -> atan/2 (x, y, units) } +fn angle { + "Calculates the angle between two vectors." + (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) +} + fn mod { "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." (x as :number, 0) -> panic! "Division by zero." @@ -947,6 +755,18 @@ fn mod/safe { (x as :number, y as :number) -> (:ok, base :mod (x, y)) } +fn even? { + "Returns true if a value is an even number, otherwise returns false." + (x as :number) if eq? (0, mod (x, 2)) -> true + (_) -> false +} + +fn odd? { + "Returns true if a value is an odd number, otherwise returns false." + (x as :number) if eq? (1, mod (x, 2)) -> true + (_) -> false +} + fn square { "Squares a number." (x as :number) -> mult (x, x) @@ -990,33 +810,6 @@ fn heading/vector { } } -&&& more number functions -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (key, d) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - fn floor { "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." (n as :number) -> base :floor (n) @@ -1038,6 +831,161 @@ fn range { (start as :number, end as :number) -> base :range (start, end) } +& additional list operations now that we have comparitors + +fn slice { + "Returns a slice of a list or a string, representing a sub-list or sub-string." + (xs as :list, end as :number) -> slice (xs, 0, end) + (xs as :list, start as :number, end as :number) -> when { + gte? (start, end) -> [] + gt? (end, count (xs)) -> slice (xs, start, count (xs)) + neg? (start) -> slice (xs, 0, end) + true -> base :slice (xs, 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)) +} + +fn butlast { + "Returns a list, omitting the last element." + (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 target appears. Returns an empty list if the target does not appear in the scrutinee." + (scrutinee as :list, target) -> base :indices_of (scrutinee, target) +} + +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." + (scrutinee as :list, target) -> base :index_of (scrutinee, target) +} + +&&& keywords: funny names +fn keyword? { + "Returns true if a value is a keyword, otherwise returns false." + (kw as :keyword) -> true + (_) -> 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 { + "Takes a dict, key, and value, and returns a new dict with the key set to value." + () -> #{} + (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 { + "Takes a dict and a key, and returns a new dict with the key and associated value omitted." + (d as :dict) -> d + (d as :dict, k as :keyword) -> base :dissoc (d, k) +} + +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, _) + (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, _) + (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 + } +} + +fn update { + "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." + (d as :dict) -> d + (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k))) +} + +fn keys { + "Takes a dict and returns a list of keys in that dict." + (d as :dict) -> do d > list > map (first, _) +} + +fn values { + "Takes a dict and returns a list of values in that dict." + (d as :dict) -> do d > list > map (second, _) +} + +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) + (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." + (d as :dict) -> d + (l as :list) -> fold (assoc, l) + (t as :tuple) -> do t > list > dict +} + +fn dict? { + "Returns true if a value is a dict." + (d as :dict) -> true + (_) -> false +} + +fn each! { + "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." + (f! as :fn, []) -> nil + (f! as :fn, [x]) -> { f! (x); nil } + (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } +} + +fn random { + "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." + () -> base :random () + (n as :number) -> mult (n, random ()) + (m as :number, n as :number) -> add (m, random (sub (n, m))) + (l as :list) -> { + let i = do l > count > random > floor + at (l, i) + } + (t as :tuple) -> { + let i = do t > count > random > floor + at (t, i) + } + (d as :dict) -> { + let key = do d > keys > random + get (d, key) + } + & (s as :set) -> do s > list > random +} + +fn random_int { + "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." + (n as :number) -> do n > random > floor + (m as :number, n as :number) -> floor (random (m, n)) +} + + &&& Results, errors and other unhappy values fn ok { @@ -1065,8 +1013,7 @@ fn err? { fn unwrap! { "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." ((:ok, value)) -> value - ((:err, msg)) -> panic! string ("Unwrapped :err! ", msg) - (_) -> panic! "Cannot unwrap something that's not an error tuple." + ((:err, msg)) -> panic! "Unwrapped :err! {msg}" } fn unwrap_or { @@ -1085,6 +1032,173 @@ fn assert! { else panic! "Assert failed: {msg} with {value}" } + +&&& processes: doing more than one thing +fn self { + "Returns the current process's pid, as a keyword." + () -> base :process (:self) +} + +fn send { + "Sends a message to the specified process and returns the message." + (pid as :keyword, msg) -> { + base :process (:send, pid, msg) + msg + } +} + +fn link! { + "Links this process to another process. When either one dies--panics or returns--both are shut down." + (pid as :keyword) -> base :process (:link, pid) +} + +fn spawn! { + "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> { + let new_pid = base :process (:spawn, f) + link! (new_pid) + new_pid + } +} + +fn fledge! { + "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." + (f as :fn) -> base :process (:spawn, f) +} + +fn yield! { + "Forces a process to yield, allowing other processes to execute." + () -> base :process (:yield) +} + +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn unlink! { + "Unlinks this process from the other process." + (pid as :keyword) -> base :process (:unlink, pid) +} + +fn monitor! { + "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." + (pid as :keyword) -> if alive? (pid) + then base :process (:monitor, pid) + else nil +} + +fn flush! { + "Clears the current process's mailbox and returns all the messages." + () -> base :process (:flush) +} + +fn sleep! { + "Puts the current process to sleep for at least the specified number of milliseconds." + (ms as :number) -> base :process (:sleep, ms) +} + +fn await! { + "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics. If the other process is not alive, returns `nil`." + (pid as :keyword) -> if monitor! (pid) + then receive { + (:exit, sender, (:ok, result)) if eq? (pid, sender) -> result + (:exit, sender, (:err, msg)) if eq? (pid, sender) -> { + let me = self () + panic! "Process {me} paniced because {pid} panicked: {msg}" + } + } + else nil + & (pids as :list) -> { + & each! (monitor!, pids) + & fold ( + & fn (results, pid) -> { + & let result = await! (pid) + & append (results, result) + & } + & pids + & [] + & ) + & } +} + +fn hibernate! { + "Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up." + () -> receive { _ -> hibernate! () } +} + +fn heed! { + "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." + () -> receive { + (:reply, result) -> result + } +} + +fn send_sync { + "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." + (pid, msg) -> { + send (pid, msg) + receive { + (:reply, res) -> res + } + } +} + +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> request_fetch! (pid, url)) + receive { + (:reply, (_, response)) -> response + } + } +} + +fn input_reader! { + (pid as :keyword) -> { + if do input > unbox > not + then { + yield! () + input_reader! (pid) + } + else { + send (pid, (:reply, unbox (input))) + store! (input, nil) + } + } +} + +fn read_input { + "Waits until there is input in the input buffer, and returns it once there is." + () -> { + let pid = self () + spawn! (fn () -> input_reader! (pid)) + receive { + (:reply, response) -> response + } + } +} + &&& Turtle & other graphics & some basic colors @@ -1094,6 +1208,7 @@ let colors = #{ :black (0, 0, 0, 255) :silver (192, 192, 192, 255) :gray (128, 128, 128, 255) + :grey (128, 128, 128, 255) :white (255, 255, 255, 255) :maroon (128, 0, 0, 255) :red (255, 0, 0, 255) @@ -1109,6 +1224,8 @@ let colors = #{ :aqua (0, 255, 25, 255) } +let colours = colors + & the initial turtle state let turtle_init = #{ :position (0, 0) & let's call this the origin for now @@ -1123,352 +1240,476 @@ let turtle_init = #{ & turtle_commands is a list of commands, expressed as tuples box turtle_commands = [] box turtle_state = turtle_init +box turtle_states = #{:turtle_0 turtle_init} +box command_id = 0 -fn add_command! (command) -> { - update! (turtle_commands, append! (_, command)) - let prev = unbox (turtle_state) +fn apply_command + +fn add_command! (turtle_id, command) -> { + let idx = unbox (command_id) + update! (command_id, inc) + update! (turtle_commands, append (_, (turtle_id, idx, command))) + let prev = do turtle_states > unbox > turtle_id + print!("previous state: {turtle_id}", prev) + print!("applying command", command) let curr = apply_command (prev, command) - store! (turtle_state, curr) + update! (turtle_states, assoc (_, turtle_id, curr)) :ok } fn forward! { - "Moves the turtle forward by a number of steps. Alias: fd!" - (steps as :number) -> add_command! ((:forward, steps)) + "Moves the turtle forward by a number of steps. Alias: `fd!`" + (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) } let fd! = forward! fn back! { - "Moves the turtle backward by a number of steps. Alias: bk!" - (steps as :number) -> add_command! ((:back, steps)) + "Moves the turtle backward by a number of steps. Alias: `bk!`" + (steps as :number) -> add_command! (:turtle_0, (:back, steps)) } let bk! = back! fn left! { - "Rotates the turtle left, measured in turns. Alias: lt!" - (turns as :number) -> add_command! ((:left, turns)) + "Rotates the turtle left, measured in turns. Alias: `lt!`" + (turns as :number) -> add_command! (:turtle_0, (:left, turns)) } let lt! = left! fn right! { - "Rotates the turtle right, measured in turns. Alias: rt!" - (turns as :number) -> add_command! ((:right, turns)) + "Rotates the turtle right, measured in turns. Alias: `rt!`" + (turns as :number) -> add_command! (:turtle_0, (:right, turns)) } let rt! = right! fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" - () -> add_command! ((:penup)) + "Lifts the turtle's pen, stopping it from drawing. Alias: `pu!`" + () -> add_command! (:turtle_0, (:penup)) } let pu! = penup! fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: pd!" - () -> add_command! ((:pendown)) + "Lowers the turtle's pen, causing it to draw. Alias: `pd!`" + () -> add_command! (:turtle_0, (:pendown)) } let pd! = pendown! fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" - (color as :keyword) -> add_command! ((:pencolor, color)) - (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) + "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!`" + (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) + (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) } +let pencolour! = pencolor! + let pc! = pencolor! fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" - (width as :number) -> add_command! ((:penwidth, width)) + "Sets the width of the turtle's pen, measured in pixels. Alias: `pw!`" + (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) } let pw! = penwidth! fn background! { - "Sets the background color behind the turtle and path. Alias: bg!" - (color as :keyword) -> add_command! ((:background, color)) - (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) + "Sets the background color behind the turtle and path. Alias: `bg!`" + (color as :keyword) -> add_command! (:turtle_0, (:background, color)) + (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, a))) } let bg! = background! fn home! { "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! ((:home)) + () -> add_command! (:turtle_0, (:home)) } fn clear! { "Clears the canvas and sends the turtle home." - () -> add_command! ((:clear)) + () -> add_command! (:turtle_0, (:clear)) } fn goto! { "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! ((:goto, (x, y))) + (x as :number, y as :number) -> add_command! (:turtle_0, (:goto, (x, y))) ((x, y)) -> goto! (x, y) } fn setheading! { "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! ((:setheading, heading)) + (heading as :number) -> add_command! (:turtle_0, (:setheading, heading)) } fn showturtle! { "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! ((:show)) + () -> add_command! (:turtle_0, (:show)) } fn hideturtle! { "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! ((:hide)) + () -> add_command! (:turtle_0, (:hide)) } fn loadstate! { "Sets the turtle state to a previously saved state." (state) -> { let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + add_command! (:turtle_0, (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + } +} + +fn turtle_listener () -> { + receive { + (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) + (:back!, steps as :number) -> add_command! (self (), (:back, steps)) + (:left!, turns as :number) -> add_command! (self (), (:left, turns)) + (:right!, turns as :number) -> add_command! (self (), (:right, turns)) + (:penup!) -> add_command! (self (), (:penup)) + (:pendown!) -> add_command! (self (), (:pendown)) + (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) + (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) + (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) + (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) + (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) + (:home!) -> add_command! (self (), (:home)) + (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) + (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) + (:show!) -> add_command! (self (), (:show)) + (:hide!) -> add_command! (self (), (:hide)) + (:loadstate!, state) -> { + let #{position, heading, pendown?, pencolor, penwidth, visible?} = state + add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + + } + (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) + (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) + (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) + (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) + does_not_understand -> { + let pid = self () + panic! "{pid} does not understand message: {does_not_understand}" + } + } + turtle_listener () +} + +fn spawn_turtle! { + "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." + () -> { + let pid = spawn! (fn () -> turtle_listener ()) + update! (turtle_states, assoc (_, pid, turtle_init)) + pid } } fn apply_command { "Takes a turtle state and a command and calculates a new state." - (state, command) -> match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - (:clear) -> do state > - assoc (state, :position, (0, 0)) > - assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - } + (state, command) -> { + match command with { + (:goto, (x, y)) -> assoc (state, :position, (x, y)) + (:home) -> do state > + assoc (_, :position, (0, 0)) > + assoc (_, :heading, 0) + & (:clear) -> do state > + & assoc (state, :position, (0, 0)) > + & assoc (_, :heading, 0) + (:right, turns) -> update (state, :heading, add (_, turns)) + (:left, turns) -> update (state, :heading, sub (_, turns)) + (:forward, steps) -> { + let #{heading, position, ...} = state + let unit = heading/vector (heading) + let vect = mult (steps, unit) + update (state, :position, add (vect, _)) + } + (:back, steps) -> { + let #{heading, position, ...} = state + let unit = heading/vector (heading) + let vect = mult (steps, unit) + update (state, :position, sub (_, vect)) + } + (:penup) -> assoc (state, :pendown?, false) + (:pendown) -> assoc (state, :pendown?, true) + (:penwidth, pixels) -> assoc (state, :penwidth, pixels) + (:pencolor, color) -> assoc (state, :pencolor, color) + (:setheading, heading) -> assoc (state, :heading, heading) + (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} + (:show) -> assoc (state, :visible?, true) + (:hide) -> assoc (state, :visible?, false) + (:background, _) -> state + }} } -& position () -> (x, y) fn position { - "Returns the turtle's current position." - () -> do turtle_state > unbox > :position + "Returns the turtle's current position as an `(x, y)` vector tuple." + () -> do turtle_states > unbox > :turtle_0 > :position } fn heading { "Returns the turtle's current heading." - () -> do turtle_state > unbox > :heading + () -> do turtle_states > unbox > :turtle_0 > :heading } fn pendown? { "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_state > unbox > :pendown? + () -> do turtle_states > unbox > :turtle_0 > :pendown? } fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." - () -> do turtle_state > unbox > :pencolor + "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`." + () -> do turtle_states > unbox > :turtle_0 > :pencolor } +let pencolour = pencolor + fn penwidth { "Returns the turtle's pen width in pixels." - () -> do turtle_state > unbox > :penwidth + () -> do turtle_states > unbox > :turtle_0 > :penwidth } -box state = nil + +&&& fake some lispisms with tuples +fn cons { + "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." + (x, y) -> (x, y) +} + +fn car { + "Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)." + ((x, _)) -> x +} + +fn cdr { + "Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list." + ((_, x)) -> x +} + +fn llist { + "Makes an old-timey linked list of its arguments, of LISt Processor fame." + (...xs) -> foldr (cons, xs, nil) +} + +&&& keyboard input +fn key_pressed? { + "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." + (key as :string) -> do keys_down > unbox > contains? (key, _) +} #{ - abs & math - add & math - & and & bool - angle & math - any? & dicts lists strings sets tuples - append & lists sets - assert! & errors - assoc & dicts - assoc? & dicts - at & lists strings - atan/2 & math - back! & turtles - background! & turtles - between? & math - bg! & turtles - bk! & turtles - bool & bool - bool? & bool - box? & boxes - butlast & lists strings tuples - ceil & math - chars & strings - clear! & turtles - coll? & dicts lists sets tuples - colors & turtles - concat & string list set - contains? & list set - cos & math - count & string list set tuple dict - dec & math - deg/rad & math - deg/turn & math - dict & dict - dict? & dict - & diff & dict - dissoc & dict - dist & math - div & math - div/0 & math - div/safe & math - doc! & env - downcase & string - each! & list - empty? & list dict set string tuple - eq? & values - err & result - err? & result - even? & math - false? & bool - fd! & turtles - filter & list - first & list tuple - floor & math - fn? & functions - fold & lists - forward! & turtles - get & dicts - goto! & turtles - gt? & math - gte? & math - heading & turtles - heading/vector & math - hideturtle! & turtles - home! & turtles - inc & math - inv & math - inv/0 & math - inv/safe & math - join & lists strings - keep & lists - keys & dicts - keyword? & keywords - last & lists tuples - left! & turtles - list & lists - list? & lists - loadstate! & turtles - lt! & turtles - lt? & math - lte? & math - map & lists - max & math - min & math - mod & math + & completed actor functions + self + send + spawn! & <- is no longer a special form + yield! + sleep! + alive? + flush! + + & wip actor functions + link! + monitor! + await! + heed! + unlink! + hibernate! + + spawn_turtle! + + key_pressed? + + & shared memory w/ rust + & `box`es are actually way cool + console + input + fetch_outbox + fetch_inbox + keys_down + + & a fetch fn + fetch + & await user input + read_input + + abs + abs + add + angle + any? + append + assert! + assoc + & assoc? + at + atan/2 + back! + background! + between? + bg! + bk! + bool + bool? + box? + butlast + car + cdr + ceil + chars + clear! + coll? + colors + colours + concat + condense + cons + console + contains? + cos + count + dec + deg/rad + deg/turn + dict + dict? + dissoc + dist + div + div/0 + div/safe + doc! + downcase + each! + empty? + eq? + err + err? + even? + false? + fd! + filter + first + first + first + floor + fn? + fold + foldr + forward! + get + goto! + gt? + gte? + has? + heading + heading/vector + hideturtle! + home! + inc + indexed? + index_of + indices_of + inv + inv/0 + inv/safe + join + keep + keys + keyword? + last + left! + list + list? + llist + loadstate! + lt! + lt? + lte? + map + mod mod/0 mod/safe - mult & math - neg & math - neg? & math - neq? & values - nil? & nil - not & bool - odd? & math - ok & results - ok? & results - & omit & set - & or & bool - ordered? & lists tuples strings - pc! & turtles - pd! & turtles - pencolor & turtles - pencolor! & turtles - pendown! & turtles - pendown? & turtles - penup! & turtles - penwidth & turtles - penwidth! & turtles - pi & math - pos? & math - position & turtles - print! & environment - pu! & turtles - pw! & turtles - rad/deg & math - rad/turn & math - random & math dicts lists tuples sets - random_int & math - range & math lists - report! & environment - rest & lists tuples - right! & turtles - round & math - rt! & turtles - second & lists tuples - sentence & lists strings - & set & sets - & set? & sets - setheading! & turtles - show & strings - showturtle! & turtles - sin & math - slice & lists tuples strings - some & values - some? & values - split & strings - sqrt & math - sqrt/safe & math - square & math - state & environment - store! & boxes - string & strings - string? & strings - strip & strings - sub & math - sum_of_squares & math - tan & math - tau & math - to_number & strings numbers - trim & strings - tuple? & tuples - turn/deg & math - turn/rad & math - turtle_commands & turtles - turtle_init & turtles - turtle_state & turtles - type & values - unbox & boxes - unwrap! & results - unwrap_or & results - upcase & strings - update & dicts - update! & boxes - values & dicts - words & strings lists - ws? & strings - zero? & math + mult + neg + neg? + nil? + not + odd? + ok + ok? + pc! + pd! + pencolor + pencolor! + pencolour! + pendown! + pendown? + penup! + penwidth + penwidth! + pi + pos? + position + pow + print! + pu! + pw! + rad/deg + rad/turn + random + random_int + range + report! + rest + right! + rotate + round + rt! + second + sentence + setheading! + show + showturtle! + sin + slice + slice_n + some + some? + split + sqrt + sqrt/safe + square + store! + string + string? + strip + sub + tan + tau + to_number + trim + true? + tuple? + turn/deg + turn/rad + turtle_commands + turtle_init + turtle_state + type + unbox + unwrap! + unwrap_or + upcase + update + update! + values + words + ws? + zero? } diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld deleted file mode 100644 index 25d6194..0000000 --- a/assets/test_prelude.ld +++ /dev/null @@ -1,1711 +0,0 @@ -&&& buffers: shared memory with Rust -& use types that are all either empty or any -box console = [] -box input = nil -box fetch_outbox = "" -box fetch_inbox = () -box keys_down = [] - -& the very base: know something's type -fn type { - "Returns a keyword representing the type of the value passed in." - (x) -> base :type (x) -} - -& & some helper type functions -fn coll? { - "Returns true if a value is a collection: dict, list, tuple, or set." - (coll as :dict) -> true - (coll as :list) -> true - (coll as :tuple) -> true - & (coll as :set) -> true - (_) -> false -} - -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 -} - -& 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 - -fn nil? { - "Returns true if a value is nil." - (nil) -> true - (_) -> false -} - -fn some? { - "Returns true if a value is not nil." - (nil) -> false - (_) -> true -} - -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." - (nil, default) -> default - (value, _) -> value -} - -& ...and if two things are the same -fn eq? { - "Returns true if all arguments have the same value." - (x) -> true - (x, y) -> base :eq? (x, y) - (x, y, ...zs) -> if eq? (x, y) - then loop (y, zs) with { - (a, [b]) -> and (eq? (a, x), eq? (b, x)) - (a, [b, ...cs]) -> if eq? (a, x) - then recur (b, cs) - else false - } - else false -} - -& &&& true & false: boolean logic (part the first) -fn bool? { - "Returns true if a value is of type :boolean." - (false) -> true - (true) -> true - (_) -> false -} - -fn true? { - "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." - (true) -> true - (_) -> false -} - -fn false? { - "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." - (false) -> true - (_) -> false -} - -fn bool { - "Returns false if a value is nil or false, otherwise returns true." - (nil) -> false - (false) -> false - (_) -> true -} - -fn not { - "Returns false if a value is truthy, true if a value is falsy." - (nil) -> true - (false) -> true - (_) -> false -} - -& & tuples: not a lot you can do with them functionally -fn tuple? { - "Returns true if a value is a tuple." - (tuple as :tuple) -> true - (_) -> false -} - -& &&& functions: getting things done -fn fn? { - "Returns true if an argument is a function." - (f as :fn) -> true - (_) -> false -} - -& what we need for some very basic list manipulation -fn first { - "Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil." - ([]) -> nil - (()) -> nil - ("") -> nil - (xs as :list) -> base :first (xs) - (xs as :tuple) -> base :first (xs) - (str as :string) -> base :first (str) -} - -fn rest { - "Returns all but the first element of a list or tuple, as a list." - ([]) -> [] - (()) -> () - (xs as :list) -> base :rest (xs) - (xs as :tuple) -> base :rest (xs) - (str as :string) -> base :rest (str) -} - -fn last { - "Returns the last element of an indexed value: list, tuple, or string." - (xs as :list) -> base :last (xs) - (xs as :tuple) -> base :last (xs) - (str as :string) -> base :last (str) -} - -fn inc { - "Increments a number." - (x as :number) -> base :inc (x) -} - -fn dec { - "Decrements a number." - (x as :number) -> base :dec (x) -} - -fn count { - "Returns the number of elements in a collection (including string)." - (xs as :list) -> base :count (xs) - (xs as :tuple) -> base :count (xs) - (xs as :dict) -> base :count (xs) - (xs as :string) -> base :count (xs) - & (xs as :set) -> base :count (xs) -} - -fn empty? { - "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." - ([]) -> true - (#{}) -> true - & (s as :set) -> eq? (s, ${}) - (()) -> true - ("") -> true - (_) -> false -} - -fn any? { - "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." - ([...]) -> true - (#{...}) -> true - & (s as :set) -> not (empty? (s)) - ((...)) -> true - (s as :string) -> not (empty? (s)) - (_) -> 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 - (_) -> false -} - -fn list { - "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) -} - -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :append (xs, x) -} - -fn fold { - "Folds a list." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> fold (f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (prev, curr) - (prev, curr, remaining) -> recur ( - f (prev, curr) - first (remaining) - rest (remaining) - ) - } -} - -fn foldr { - "Folds a list, right-associatively." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> foldr(f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (curr, prev) - (prev, curr, remaining) -> recur ( - f (curr, prev) - first (remaining) - rest (remaining) - ) - } -} - -fn map { - "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." - (f as :fn) -> map (f, _) - (kw as :keyword) -> map (kw, _) - (f as :fn, xs) -> { - fn mapper (prev, curr) -> append (prev, f (curr)) - fold (mapper, xs, []) - } - (kw as :keyword, xs) -> { - fn mapper (prev, curr) -> append (prev, kw (curr)) - fold (mapper, xs, []) - } -} - -fn filter { - "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]`." - (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 keep { - "Takes a list and returns a new list with any `nil` values omitted." - (xs) -> filter (some?, xs) -} - -fn concat { - "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) - (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) -} - -fn contains? { - "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 - ([...xs]) -> if eq? (first(xs), value) - then true - else recur (rest (xs)) - } -} - -&&& 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 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) - } -} - -&&& strings: harder than they look! -fn string? { - "Returns true if a value is a string." - (x as :string) -> true - (_) -> 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 - (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) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, splitter as :string) -> base :split (str, splitter) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -fn chars { - "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 ws? { - "Tells if a string is a whitespace character." - (" ") -> true - ("\t") -> true - ("\n") -> true - ("\r") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (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) -> base :words (str) -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :number (num) -} - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - let line = do args > map (string, _) > join (_, " ") - update! (console, append (_, line)) - :ok - } -} - -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! - (_) -> nil -} - -&&& numbers, basically: arithmetic and not much else, yet -& TODO: add nan?, -fn number? { - "Returns true if a value is a number." - (x as :number) -> true - (_) -> false -} - -fn add { - "Adds numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :add (x, y) - (x, y, ...zs) -> fold (add, zs, base :add (x, y)) - & add vectors - ((x1, y1), (x2, y2)) -> (add (x1, x2), add (y1, y2)) -} - -fn sub { - "Subtracts numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :sub (x, y) - (x, y, ...zs) -> fold (sub, zs, base :sub (x, y)) - ((x1, y1), (x2, y2)) -> (base :sub (x1, x2), base :sub (y1, y2)) -} - -fn mult { - "Multiplies numbers or vectors." - () -> 1 - (x as :number) -> x - (x as :number, y as :number) -> base :mult (x, y) - (x, y, ...zs) -> fold (mult, zs, mult (x, y)) - (scalar as :number, (x, y)) -> (mult (x, scalar), mult (y, scalar)) - ((x, y), scalar as :number) -> mult (scalar, (x, y)) -} - -fn pow { - "Raises a number to the power of another number." - (x as :number, y as :number) -> base :pow (x, y) -} - -fn div { - "Divides numbers. Panics on division by zero." - (x as :number) -> x - (_, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div (x, divisor) - } -} - -fn div/0 { - "Divides numbers. Returns 0 on division by zero." - (x as :number) -> x - (_, 0) -> 0 - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/0 (x, divisor) - } -} - -fn div/safe { - "Divides a number. Returns a result tuple." - (x as :number) -> (:ok, x) - (_, 0) -> (:err, "Division by zero") - (x, y) -> (:ok, div (x, y)) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/safe (x, divisor) - } -} - -fn inv { - "Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero." - (x as :number) -> div (1, x) -} - -fn inv/0 { - "Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero." - (x as :number) -> div/0 (1, x) -} - -fn inv/safe { - "Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple." - (x as :number) -> div/safe (1, x) -} - -fn neg { - "Multiplies a number by -1, negating it." - (n as :number) -> mult (n, -1) -} - -fn zero? { - "Returns true if a number is 0." - (0) -> true - (_) -> false -} - -fn gt? { - "Returns true if numbers are in decreasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :gt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gt? (a, b) - (a, [b, ...cs]) -> if base :gt? (a, b) - then recur (b, cs) - else false - } -} - -fn gte? { - "Returns true if numbers are in decreasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :gte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gte? (a, b) - (a, [b, ...cs]) -> if base :gte? (a, b) - then recur (b, cs) - else false - } -} - -fn lt? { - "Returns true if numbers are in increasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :lt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lt? (a, b) - (a, [b, ...cs]) -> if base :lt? (a, b) - then recur (b, cs) - else false - } -} - -fn lte? { - "Returns true if numbers are in increasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :lte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lte? (a, b) - (a, [b, ...cs]) -> if base :lte? (a, b) - then recur (b, cs) - else false - } -} - -fn between? { - "Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher." - (lower as :number, higher as :number, x as :number) -> and ( - gte? (x, lower) - lt? (x, higher) - ) -} - -fn neg? { - "Returns true if a value is a negative number, otherwise returns false." - (x as :number) if lt? (x, 0) -> true - (_) -> false -} - -fn pos? { - "Returns true if a value is a positive number, otherwise returns false." - (x as :number) if gt? (x, 0) -> true - (_) -> false -} - -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - -&&& Trigonometry functions - -& Ludus uses turns as its default unit to measure angles -& However, anything that takes an angle can also take a -& units argument, that's a keyword of :turns, :degrees, or :radians - -let pi = base :pi - -let tau = mult (2, pi) - -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} - -fn sin { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :sin - (a as :number, :turns) -> do a > turn/rad > base :sin - (a as :number, :degrees) -> do a > deg/rad > base :sin - (a as :number, :radians) -> base :sin (a) -} - -fn cos { - "Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :cos - (a as :number, :turns) -> do a > turn/rad > base :cos - (a as :number, :degrees) -> do a > deg/rad > base :cos - (a as :number, :radians) -> base :cos (a) -} - -fn tan { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :tan - (a as :number, :turns) -> do a > turn/rad > base :tan - (a as :number, :degrees) -> do a > deg/rad > base :tan - (a as :number, :radians) -> base :tan (a) -} - -fn rotate { - "Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - ((x, y), a) -> rotate ((x, y), a, :turns) - ((x, y), a, units as :keyword) -> ( - sub (mult (x, cos (a, units)), mult (y, sin (a, units))) - add (mult (x, sin (a, units)), mult (y, cos (a, units))) - ) -} - - -fn atan/2 { - "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." - (x as :number, y as :number) -> do base :atan_2 (x, y) > rad/turn - (x, y, :turns) -> atan/2 (x, y) - (x, y, :radians) -> base :atan_2 (x, y) - (x, y, :degrees) -> do base :atan_2 (x, y) > rad/deg - ((x, y)) -> atan/2 (x, y) - ((x, y), units as :keyword) -> atan/2 (x, y, units) -} - -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - -fn mod { - "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." - (x as :number, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/0 { - "Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0." - (x as :number, 0) -> 0 - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/safe { - "Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity." - (x as :number, 0) -> (:err, "Division by zero.") - (x as :number, y as :number) -> (:ok, base :mod (x, y)) -} - -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn square { - "Squares a number." - (x as :number) -> mult (x, x) -} - -fn sqrt { - "Returns the square root of a number. Panics if the number is negative." - (x as :number) if not (neg? (x)) -> base :sqrt (x) -} - -fn sqrt/safe { - "Returns a result containing the square root of a number, or an error if the number is negative." - (x as :number) -> if not (neg? (x)) - then (:ok, base :sqrt (x)) - else (:err, "sqrt of negative number") -} - -fn sum_of_squares { - "Returns the sum of squares of numbers." - () -> 0 - (x as :number) -> square (x) - (x as :number, y as :number) -> add (square (x), square (y)) - (x, y, ...zs) -> fold ( - fn (sum, z) -> add (sum, square (z)) - zs - sum_of_squares (x, y)) -} - -fn dist { - "Returns the distance from the origin to a point described by x and y, or by the vector (x, y)." - (x as :number, y as :number) -> sqrt (sum_of_squares (x, y)) - ((x, y)) -> dist (x, y) -} - -fn heading/vector { - "Takes a turtle heading, and returns a unit vector of that heading." - (heading) -> { - & 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T - let a = add (neg (heading), 0.25) - (cos (a), sin (a)) - } -} - -fn floor { - "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." - (n as :number) -> base :floor (n) -} - -fn ceil { - "Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer." - (n as :number) -> base :ceil (n) -} - -fn round { - "Rounds a number to the nearest integer." - (n as :number) -> base :round (n) -} - -fn range { - "Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list." - (end as :number) -> base :range (0, end) - (start as :number, end as :number) -> base :range (start, end) -} - -& additional list operations now that we have comparitors - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - true -> base :slice (xs, 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)) -} - -fn butlast { - "Returns a list, omitting the last element." - (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 target appears. Returns an empty list if the target does not appear in the scrutinee." - (scrutinee as :list, target) -> base :indices_of (scrutinee, target) -} - -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." - (scrutinee as :list, target) -> base :index_of (scrutinee, target) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> true - (_) -> 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 { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (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 { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -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, _) - (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, _) - (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 - } -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -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) - (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." - (d as :dict) -> d - (l as :list) -> fold (assoc, l) - (t as :tuple) -> do t > list > dict -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } -} - -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (d, key) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - - -&&& Results, errors and other unhappy values - -fn ok { - "Takes a value and wraps it in an :ok result tuple." - (value) -> (:ok, value) -} - -fn ok? { - "Takes a value and returns true if it is an :ok result tuple." - ((:ok, _)) -> true - (_) -> false -} - -fn err { - "Takes a value and wraps it in an :err result tuple, presumably as an error message." - (msg) -> (:err, msg) -} - -fn err? { - "Takes a value and returns true if it is an :err result tuple." - ((:err, _)) -> true - (_) -> false -} - -fn unwrap! { - "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." - ((:ok, value)) -> value - ((:err, msg)) -> panic! "Unwrapped :err! {msg}" -} - -fn unwrap_or { - "Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value." - ((:ok, value), _) -> value - ((:err, _), default) -> default -} - -fn assert! { - "Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." - (value) -> if value - then value - else panic! "Assert failed: {value}" - (msg, value) -> if value - then value - else panic! "Assert failed: {msg} with {value}" -} - - -&&& processes: doing more than one thing -fn self { - "Returns the current process's pid, as a keyword." - () -> base :process (:self) -} - -fn send { - "Sends a message to the specified process and returns the message." - (pid as :keyword, msg) -> { - base :process (:send, pid, msg) - msg - } -} - -fn spawn! { - "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." - (f as :fn) -> { - let new_pid = base :process (:spawn, f) - link! (new_pid) - new_pid - } -} - -fn fledge! { - "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." - (f as :fn) -> base :process (:spawn, f) -} - -fn yield! { - "Forces a process to yield, allowing other processes to execute." - () -> base :process (:yield) -} - -fn alive? { - "Tells if the passed keyword is the id for a live process." - (pid as :keyword) -> base :process (:alive, pid) -} - -fn link! { - "Links this process to another process. When either one dies--panics or returns--both are shut down." - (pid as :keyword) -> base :process (:link, pid) -} - -fn unlink! { - "Unlinks this process from the other process." - (pid as :keyword) -> base :process (:unlink, pid) -} - -fn monitor! { - "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." - (pid as :keyword) -> base :process (:monitor, pid) -} - -fn flush! { - "Clears the current process's mailbox and returns all the messages." - () -> base :process (:flush) -} - -fn sleep! { - "Puts the current process to sleep for at least the specified number of milliseconds." - (ms as :number) -> base :process (:sleep, ms) -} - -fn await! { - "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics." - (pid as :keyword) -> { - monitor! (pid) - receive { - (:exit, _, (:ok, result)) -> result - (:exit, _, (:err, msg)) -> { - panic! "Monitored process {pid} panicked with {msg}" } - } - } - (pids as :list) -> { - each! (pids, monitor!) - fold ( - (fn (results, pid) -> append (results, await! (pid))) - pids - [] - ) - } -} - -fn hibernate! { - "Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up." - () -> receive { _ -> hibernate! () } -} - -fn heed! { - "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." - () -> receive { - (:reply, result) -> result - } -} - -fn send_sync { - "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." - (pid, msg) -> { - send (pid, msg) - receive { - (:reply, res) -> res - } - } -} - -& TODO: make this more robust, to handle multiple pending requests w/o data races -fn request_fetch! { - (pid as :keyword, url as :string) -> { - store! (fetch_outbox, url) - request_fetch! (pid) - } - (pid as :keyword) -> { - if empty? (unbox (fetch_inbox)) - then { - yield! () - request_fetch! (pid) - } - else { - send (pid, (:reply, unbox (fetch_inbox))) - store! (fetch_inbox, ()) - } - } -} - -fn fetch { - "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." - (url) -> { - let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) - receive { - (:reply, (_, response)) -> response - } - } -} - -fn input_reader! { - (pid as :keyword) -> { - if do input > unbox > not - then { - yield! () - input_reader! (pid) - } - else { - send (pid, (:reply, unbox (input))) - store! (input, nil) - } - } -} - -fn read_input { - "Waits until there is input in the input buffer, and returns it once there is." - () -> { - let pid = self () - spawn! (fn () -> input_reader! (pid)) - receive { - (:reply, response) -> response - } - } -} - -&&& Turtle & other graphics - -& some basic colors -& these are the "basic" css colors -& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color -let colors = #{ - :black (0, 0, 0, 255) - :silver (192, 192, 192, 255) - :gray (128, 128, 128, 255) - :grey (128, 128, 128, 255) - :white (255, 255, 255, 255) - :maroon (128, 0, 0, 255) - :red (255, 0, 0, 255) - :purple (128, 0, 128, 255) - :fuchsia (255, 0, 255, 255) - :green (0, 128, 0, 255) - :lime (0, 255, 0, 255) - :olive (128, 128, 0, 255) - :yellow (255, 255, 0, 255) - :navy (0, 0, 128, 255) - :blue (0, 0, 255, 255) - :teal (0, 128, 128, 255) - :aqua (0, 255, 25, 255) -} - -let colours = colors - -& the initial turtle state -let turtle_init = #{ - :position (0, 0) & let's call this the origin for now - :heading 0 & this is straight up - :pendown? true - :pencolor :white - :penwidth 1 - :visible? true -} - -& turtle states: refs that get modified by calls -& turtle_commands is a list of commands, expressed as tuples -box turtle_commands = [] -box turtle_state = turtle_init -box turtle_states = #{:turtle_0 turtle_init} -box command_id = 0 - -fn apply_command - -fn add_command! (turtle_id, command) -> { - let idx = unbox (command_id) - update! (command_id, inc) - update! (turtle_commands, append (_, (turtle_id, idx, command))) - let prev = do turtle_states > unbox > turtle_id - print!("previous state: {turtle_id}", prev) - print!("applying command", command) - let curr = apply_command (prev, command) - update! (turtle_states, assoc (_, turtle_id, curr)) - :ok -} - -fn forward! { - "Moves the turtle forward by a number of steps. Alias: `fd!`" - (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) -} - -let fd! = forward! - -fn back! { - "Moves the turtle backward by a number of steps. Alias: `bk!`" - (steps as :number) -> add_command! (:turtle_0, (:back, steps)) -} - -let bk! = back! - -fn left! { - "Rotates the turtle left, measured in turns. Alias: `lt!`" - (turns as :number) -> add_command! (:turtle_0, (:left, turns)) -} - -let lt! = left! - -fn right! { - "Rotates the turtle right, measured in turns. Alias: `rt!`" - (turns as :number) -> add_command! (:turtle_0, (:right, turns)) -} - -let rt! = right! - -fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: `pu!`" - () -> add_command! (:turtle_0, (:penup)) -} - -let pu! = penup! - -fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: `pd!`" - () -> add_command! (:turtle_0, (:pendown)) -} - -let pd! = pendown! - -fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!`" - (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) - (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) -} - -let pencolour! = pencolor! - -let pc! = pencolor! - -fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: `pw!`" - (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) -} - -let pw! = penwidth! - -fn background! { - "Sets the background color behind the turtle and path. Alias: `bg!`" - (color as :keyword) -> add_command! (:turtle_0, (:background, color)) - (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, a))) -} - -let bg! = background! - -fn home! { - "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! (:turtle_0, (:home)) -} - -fn clear! { - "Clears the canvas and sends the turtle home." - () -> add_command! (:turtle_0, (:clear)) -} - -fn goto! { - "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! (:turtle_0, (:goto, (x, y))) - ((x, y)) -> goto! (x, y) -} - -fn setheading! { - "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! (:turtle_0, (:setheading, heading)) -} - -fn showturtle! { - "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! (:turtle_0, (:show)) -} - -fn hideturtle! { - "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! (:turtle_0, (:hide)) -} - -fn loadstate! { - "Sets the turtle state to a previously saved state." - (state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! (:turtle_0, (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - } -} - -fn turtle_listener () -> { - print!("listening in", self()) - receive { - (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) - (:back!, steps as :number) -> add_command! (self (), (:back, steps)) - (:left!, turns as :number) -> add_command! (self (), (:left, turns)) - (:right!, turns as :number) -> add_command! (self (), (:right, turns)) - (:penup!) -> add_command! (self (), (:penup)) - (:pendown!) -> add_command! (self (), (:pendown)) - (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) - (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) - (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) - (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) - (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) - (:home!) -> add_command! (self (), (:home)) - (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) - (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) - (:show!) -> add_command! (self (), (:show)) - (:hide!) -> add_command! (self (), (:hide)) - (:loadstate!, state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - - } - (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) - (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) - (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) - (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) - does_not_understand -> { - let pid = self () - panic! "{pid} does not understand message: {does_not_understand}" - } - } - print! ("lisening from:", self ()) - turtle_listener () -} - -fn spawn_turtle! { - "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." - () -> { - let pid = spawn! (fn () -> turtle_listener ()) - update! (turtle_states, assoc (_, pid, turtle_init)) - pid - } -} - -fn apply_command { - "Takes a turtle state and a command and calculates a new state." - (state, command) -> { - match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - & (:clear) -> do state > - & assoc (state, :position, (0, 0)) > - & assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - }} -} - -fn position { - "Returns the turtle's current position as an `(x, y)` vector tuple." - () -> do turtle_states > unbox > :turtle_0 > :position -} - -fn heading { - "Returns the turtle's current heading." - () -> do turtle_states > unbox > :turtle_0 > :heading -} - -fn pendown? { - "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_states > unbox > :turtle_0 > :pendown? -} - -fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`." - () -> do turtle_states > unbox > :turtle_0 > :pencolor -} - -let pencolour = pencolor - -fn penwidth { - "Returns the turtle's pen width in pixels." - () -> do turtle_states > unbox > :turtle_0 > :penwidth -} - - -&&& fake some lispisms with tuples -fn cons { - "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." - (x, y) -> (x, y) -} - -fn car { - "Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)." - ((x, _)) -> x -} - -fn cdr { - "Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list." - ((_, x)) -> x -} - -fn llist { - "Makes an old-timey linked list of its arguments, of LISt Processor fame." - (...xs) -> foldr (cons, xs, nil) -} - -&&& keyboard input -fn key_pressed? { - "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." - (key as :string) -> do keys_down > unbox > contains? (key, _) -} - -#{ - & completed actor functions - self - send - spawn! & <- is no longer a special form - yield! - sleep! - alive? - flush! - - & wip actor functions - link! - monitor! - await! - heed! - unlink! - hibernate! - - spawn_turtle! - - key_pressed? - - & shared memory w/ rust - & `box`es are actually way cool - console - input - fetch_outbox - fetch_inbox - keys_down - - & a fetch fn - fetch - & await user input - read_input - - abs - abs - add - angle - any? - append - assert! - assoc - & assoc? - at - atan/2 - back! - background! - between? - bg! - bk! - bool - bool? - box? - butlast - car - cdr - ceil - chars - clear! - coll? - colors - colours - concat - condense - cons - console - contains? - cos - count - dec - deg/rad - deg/turn - dict - dict? - dissoc - dist - div - div/0 - div/safe - doc! - downcase - each! - empty? - eq? - err - err? - even? - false? - fd! - filter - first - first - first - floor - fn? - fold - foldr - forward! - get - goto! - gt? - gte? - has? - heading - heading/vector - hideturtle! - home! - inc - indexed? - index_of - indices_of - inv - inv/0 - inv/safe - join - keep - keys - keyword? - last - left! - list - list? - llist - loadstate! - lt! - lt? - lte? - map - mod - mod/0 - mod/safe - mult - neg - neg? - nil? - not - odd? - ok - ok? - pc! - pd! - pencolor - pencolor! - pencolour! - pendown! - pendown? - penup! - penwidth - penwidth! - pi - pos? - position - pow - print! - pu! - pw! - rad/deg - rad/turn - random - random_int - range - report! - rest - right! - rotate - round - rt! - second - sentence - setheading! - show - showturtle! - sin - slice - slice_n - some - some? - split - sqrt - sqrt/safe - square - store! - string - string? - strip - sub - tan - tau - to_number - trim - true? - tuple? - turn/deg - turn/rad - turtle_commands - turtle_init - turtle_state - type - unbox - unwrap! - unwrap_or - upcase - update - update! - values - words - ws? - zero? -} diff --git a/pkg/rudus.js b/pkg/rudus.js index 37f3f9e..47d24b2 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_wrapper8194 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index bd47f85..fc62f72 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ diff --git a/src/compiler.rs b/src/compiler.rs index 2a301e9..c18ba4c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,6 +1,5 @@ use crate::ast::{Ast, StringPart}; use crate::chunk::{Chunk, StrPattern}; -use crate::errors::line_number; use crate::op::Op; use crate::spans::Spanned; use crate::value::*; @@ -1115,6 +1114,7 @@ impl Compiler { self.emit_op(Op::Spawn); } Receive(clauses) => { + self.msg("********starting receive".to_string()); let tail_pos = self.tail_pos; self.emit_op(Op::ClearMessage); let receive_begin = self.len(); @@ -1158,7 +1158,8 @@ impl Compiler { } self.pop_n(self.stack_depth - stack_depth); self.emit_op(Op::Load); - self.stack_depth += 1; + // self.stack_depth += 1; + self.msg("********receive completed".to_string()); } MatchClause(..) => unreachable!(), Fn(name, body, doc) => { diff --git a/src/main.rs b/src/main.rs index cbcdcdf..ac3284d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,6 @@ -use phf::phf_map; use rudus::value::Value; use std::env; -const KEYWORDS: phf::Map<&'static str, Value> = phf_map! { - "ok" => Value::keyword("ok"), - "err" => Value::keyword("err"), -} - pub fn main() { env::set_var("RUST_BACKTRACE", "1"); println!("Hello, world.") diff --git a/src/validator.rs b/src/validator.rs index 3c6527f..dc8bfdf 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -103,10 +103,16 @@ impl<'a> Validator<'a> { } fn declare_fn(&mut self, name: String) { + if name.is_empty() { + return; + } self.locals.push((name, self.span, FnInfo::Declared)); } fn define_fn(&mut self, name: String, info: FnInfo) { + if name.is_empty() { + return; + } let i = self.locals.iter().position(|(n, ..)| *n == name).unwrap(); let new_binding = (name, self.locals[i].1, info); self.locals[i] = new_binding; diff --git a/src/vm.rs b/src/vm.rs index ed1156e..8890342 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -305,7 +305,7 @@ impl Creature { unreachable!("expected keyword pid in monitor"); }; if other != self.pid { - self.unlink(Value::Keyword(other)); + self.do_unlink(Value::Keyword(other)); let mut other = self.zoo.borrow_mut().catch(other); other.parents.push(self.pid); self.zoo.borrow_mut().release(other); @@ -322,6 +322,11 @@ impl Creature { } fn unlink(&mut self, other: Value) { + self.do_unlink(other); + self.push(Value::Keyword("ok")) + } + + fn do_unlink(&mut self, other: Value) { let Value::Keyword(other) = other else { unreachable!("expected keyword pid in unlink") }; @@ -331,7 +336,6 @@ impl Creature { other.delete_from_siblings(self.pid); self.zoo.borrow_mut().release(other); } - self.push(Value::Keyword("ok")) } fn link(&mut self, other: Value) { @@ -1324,7 +1328,12 @@ impl Creature { } Return => { if self.debug { - console_log!("== returning from {} ==", self.frame.function.show()) + console_log!("== returning from {} ==", self.frame.function.show()); + let destination = match self.call_stack.last() { + Some(frame) => frame.function.as_fn().name(), + None => "toplevel", + }; + console_log!("== returning to {destination} =="); } let mut value = Value::Nothing; swap(&mut self.register[0], &mut value);