rudus/assets/test_prelude.ld

1378 lines
34 KiB
Plaintext

& 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 ordered? {
"Returns true if a value is an indexed collection: list or tuple."
(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
}
& &&& 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--a tuple or a list. 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 :slice (str, 0, 1)
}
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 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 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 values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples."
(x) -> base :list (x)
}
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 two lists, strings, or sets."
(x as :string, y as :string) -> "{x}{y}"
(xs as :list, ys as :list) -> base :concat (xs, ys)
& (xs as :set, ys as :set) -> base :concat (xs, ys)
(xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys))
}
fn contains? {
"Returns true if a set or 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))
}
}
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 (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. 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 :number (num)
}
&&& 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)
}
}
&&& 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 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 at {
"Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string."
(xs as :list, n as :number) -> base :at (xs, n)
(xs as :tuple, n as :number) -> base :at (xs, n)
& (str as :string, n as :number) -> {
& let raw = base :at (str, n)
& when {
& nil? (raw) -> nil
& gte? (raw, 128) -> panic! "not an ASCII char"
& true -> base :str_slice (str, n, inc (n))
& }
& }
(_) -> nil
}
& fn first {
& "Returns the first element of a list or tuple."
& (xs) if ordered? -> at (xs, 0)
& }
fn second {
"Returns the second element of a list or tuple."
(xs) if ordered? (xs) -> at (xs, 1)
}
fn last {
"Returns the last element of a list or tuple."
(xs) if ordered? (xs) -> at (xs, dec (count (xs)))
}
fn slice {
"Returns a slice of a list or a string, representing a sub-list or sub-string."
(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)
}
fn butlast {
"Returns a list, omitting the last element."
(xs as :list) -> slice (xs, 0, dec (count (xs)))
}
&&& 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.
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)
}
& TODO: consider merging `get` and `at`
fn get {
"Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value."
(k as :keyword) -> get (k, _)
(k as :keyword, d as :dict) -> base :get (d, k)
(k as :keyword, d as :dict, default) -> match base :get (d, k) with {
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 (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, _)
}
& TODO: add sets to this?
fn has? {
"Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key."
(k as :keyword) -> has? (k, _)
(k as :keyword, d as :dict) -> do d > get (k) > some?
}
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 (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))
}
&&& 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! string ("Unwrapped :err! ", msg)
(_) -> panic! "Cannot unwrap something that's not an error tuple."
}
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}"
}
&&& 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)
: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)
}
& 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
fn apply_command
fn add_command! (command) -> {
update! (turtle_commands, append (_, command))
let prev = unbox (turtle_state)
let curr = apply_command (prev, command)
store! (turtle_state, curr)
:ok
}
fn forward! {
"Moves the turtle forward by a number of steps. Alias: fd!"
(steps as :number) -> add_command! ((: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))
}
let bk! = back!
fn left! {
"Rotates the turtle left, measured in turns. Alias: lt!"
(turns as :number) -> add_command! ((:left, turns))
}
let lt! = left!
fn right! {
"Rotates the turtle right, measured in turns. Alias: rt!"
(turns as :number) -> add_command! ((:right, turns))
}
let rt! = right!
fn penup! {
"Lifts the turtle's pen, stopping it from drawing. Alias: pu!"
() -> add_command! ((:penup))
}
let pu! = penup!
fn pendown! {
"Lowers the turtle's pen, causing it to draw. Alias: pd!"
() -> add_command! ((: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)))
}
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))
}
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)))
}
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))
}
fn clear! {
"Clears the canvas and sends the turtle home."
() -> add_command! ((: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, 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))
}
fn showturtle! {
"If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing."
() -> add_command! ((:show))
}
fn hideturtle! {
"If the turtle is visible, hides it. If the turtle is already hidden, does nothing."
() -> add_command! ((: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))
}
}
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
}
}
& position () -> (x, y)
fn position {
"Returns the turtle's current position."
() -> do turtle_state > unbox > :position
}
fn heading {
"Returns the turtle's current heading."
() -> do turtle_state > unbox > :heading
}
fn pendown? {
"Returns the turtle's pen state: true if the pen is down."
() -> do turtle_state > unbox > :pendown?
}
fn pencolor {
"Returns the turtle's pen color as an (r, g, b, a) tuple or keyword."
() -> do turtle_state > unbox > :pencolor
}
fn penwidth {
"Returns the turtle's pen width in pixels."
() -> do turtle_state > unbox > :penwidth
}
box state = nil
#{
type
coll?
ordered?
assoc?
nil?
some?
some
eq?
bool?
true?
false?
bool
not
tuple?
fn?
first
rest
inc
dec
count
empty?
any?
list?
list
first
fold
foldr
append
map
filter
keep
concat
contains?
print!
show
report!
doc!
string
string?
join
split
trim
upcase
downcase
chars
chars/safe
ws?
strip
words
sentence
to_number
box?
unbox
store!
update!
add
sub
mult
div
div/0
div/safe
inv
inv/0
inv/safe
abs
neg
zero?
gt?
gte?
lt?
lte?
between?
neg?
pos?
abs
pi
tau
turn/deg
deg/turn
turn/rad
rad/turn
deg/rad
rad/deg
sin
cos
tan
rotate
atan/2
angle
mod
mod/0
mod/safe
even?
odd?
square
sqrt
sqrt/safe
dist
heading/vector
floor
ceil
round
range
at
first
second
last
butlast
slice
keyword?
assoc
dissoc
update
keys
values
get
has?
dict
dict?
each!
random
random_int
ok
ok?
err
err?
unwrap!
unwrap_or
assert!
colors
turtle_init
turtle_commands
turtle_state
forward!
fd!
back!
bk!
left!
lt!
right!
rt!
penup!
pu!
pendown!
pd!
pencolor!
pc!
penwidth!
pw!
background!
bg!
home!
clear!
goto!
setheading!
showturtle!
hideturtle!
loadstate!
position
heading
pendown?
pencolor
penwidth
state
}