1523 lines
39 KiB
Plaintext
1523 lines
39 KiB
Plaintext
& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports
|
|
|
|
& 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 state/call
|
|
fn store!
|
|
fn string
|
|
fn turn/rad
|
|
fn unbox
|
|
fn update!
|
|
|
|
& 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
|
|
(_) -> false
|
|
}
|
|
|
|
fn assoc? {
|
|
"Returns true if a value is an associative collection: a dict or a pkg."
|
|
(assoc as :dict) -> true
|
|
(assoc as :pkg) -> true
|
|
(_) -> false
|
|
}
|
|
|
|
& ...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, []) -> eq? (a, 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
|
|
}
|
|
|
|
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
|
|
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 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)
|
|
}
|
|
|
|
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 :to_list (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)
|
|
)
|
|
}
|
|
}
|
|
|
|
& TODO: optimize these with base :conj!
|
|
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]`."
|
|
(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, 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 append {
|
|
"Adds an element to a list or set."
|
|
() -> []
|
|
(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 concat {
|
|
"Combines two lists, strings, or sets."
|
|
(x as :string, y as :string) -> base :concat (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."
|
|
(xs as :list) -> fold (append, xs, ${})
|
|
(xs as :tuple) -> do xs > list > set
|
|
}
|
|
|
|
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."
|
|
(value, set as :set) -> bool (base :get (set, value))
|
|
(value, list as :list) -> contains? (value, set (list))
|
|
}
|
|
|
|
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, [x]) -> concat (out, show (x))
|
|
(out, [x, ...xs]) -> recur (concat (out, show (x)), xs)
|
|
}
|
|
}
|
|
|
|
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 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 (list, str) -> if empty? (str)
|
|
then list
|
|
else append (list, str)
|
|
fold (worder, strs, [])
|
|
}
|
|
}
|
|
|
|
fn sentence {
|
|
"Takes a list of words and turns it into a sentence."
|
|
(strs as :list) -> join (strs, " ")
|
|
}
|
|
|
|
&&& 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 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
|
|
(_) -> 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 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 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) -> when {
|
|
neg? (n) -> ""
|
|
gte? (n, count (str)) -> ""
|
|
true -> base :slice (str, n, inc (n))
|
|
}
|
|
(_) -> nil
|
|
}
|
|
|
|
fn first {
|
|
"Returns the first element of a list or tuple."
|
|
(xs) if ordered? (xs) -> 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.
|
|
|
|
&&& 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
|
|
}
|
|
|
|
|
|
& 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."
|
|
() -> #{}
|
|
(dict as :dict) -> dict
|
|
(dict as :dict, key as :keyword, value) -> base :assoc (dict, key, value)
|
|
(dict as :dict, (key as :keyword, value)) -> base :assoc (dict, key, value)
|
|
}
|
|
|
|
fn dissoc {
|
|
"Takes a dict and a key, and returns a new dict with the key and associated value omitted."
|
|
(dict as :dict) -> dict
|
|
(dict as :dict, key as :keyword) -> base :dissoc (dict, key)
|
|
}
|
|
|
|
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."
|
|
(dict as :dict) -> dict
|
|
(dict as :dict, key as :keyword, updater as :fn) -> base :assoc (dict, key, updater (get (key, dict)))
|
|
}
|
|
|
|
fn keys {
|
|
"Takes a dict and returns a list of keys in that dict."
|
|
(dict as :dict) -> do dict > list > map (first, _)
|
|
}
|
|
|
|
fn values {
|
|
"Takes a dict and returns a list of values in that dict."
|
|
(dict) -> do dict > 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."
|
|
(key as :keyword) -> get (key, _)
|
|
(key as :keyword, dict as :dict) -> get (key, dict, nil)
|
|
(key as :keyword, dict as :dict, default) -> base :get (key, dict, 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."
|
|
(key as :keyword) -> has? (key, _)
|
|
(key as :keyword, dict as :dict) -> do dict > key > nil?
|
|
}
|
|
|
|
fn dict {
|
|
"Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed."
|
|
(dict as :dict) -> dict
|
|
(list as :list) -> fold (assoc, list)
|
|
(tup as :tuple) -> do tup > list > dict
|
|
}
|
|
|
|
fn dict? {
|
|
"Returns true if a value is a dict."
|
|
(dict 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) }
|
|
}
|
|
|
|
&&& 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 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), angle) -> rotate ((x, y), angle, :turns)
|
|
((x, y), angle, units as :keyword) -> (
|
|
sub (mult (x, cos (angle, units)), mult (y, sin (angle, units)))
|
|
add (mult (x, sin (angle, units)), mult (y, cos (angle, units)))
|
|
)
|
|
}
|
|
|
|
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."
|
|
(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 mod {
|
|
"Returns the modulus of num and div. Truncates towards negative infinity. Panics if div is 0."
|
|
(num as :number, 0) -> panic! "Division by zero."
|
|
(num as :number, div as :number) -> base :mod (num, div)
|
|
}
|
|
|
|
fn mod/0 {
|
|
"Returns the modulus of num and div. Truncates towards negative infinity. Returns 0 if div is 0."
|
|
(num as :number, 0) -> 0
|
|
(num as :number, div as :number) -> base :mod (num, div)
|
|
}
|
|
|
|
fn mod/safe {
|
|
"Returns the modulus of num and div in a result tuple, or an error if div is 0. Truncates towards negative infinity."
|
|
(num as :number, 0) -> (:err, "Division by zero.")
|
|
(num as :number, div as :number) -> (:ok, base :mod (num, div))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
&&& 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 (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)
|
|
}
|
|
(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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
&&& 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 colors :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 = []
|
|
|
|
& and a list of turtle states
|
|
box turtle_states = [turtle_init]
|
|
|
|
fn reset_turtle! {
|
|
"Resets the turtle to its original state."
|
|
() -> store! (turtle_states, [turtle_init])
|
|
}
|
|
|
|
& and a list of calls to p5--at least for now
|
|
box p5_calls = []
|
|
|
|
& ...and finally, a background color
|
|
& we need to store this separately because, while it can be updated later,
|
|
& it must be the first call to p5.
|
|
box bgcolor = colors :black
|
|
|
|
fn add_call! (call) -> update! (p5_calls, append (_, call))
|
|
|
|
fn add_command! (command) -> {
|
|
update! (turtle_commands, append (_, command))
|
|
let prev = do turtle_states > unbox > last
|
|
let curr = apply_command (prev, command)
|
|
update! (turtle_states, append (_, curr))
|
|
let call = state/call ()
|
|
if call then { add_call! (call); :ok } else :ok
|
|
}
|
|
|
|
fn make_line ((x1, y1), (x2, y2)) -> (:line, x1, y1, x2, y2)
|
|
|
|
let turtle_radius = 20
|
|
|
|
let turtle_angle = 0.385
|
|
|
|
let turtle_color = (255, 255, 255, 150)
|
|
|
|
fn render_turtle! () -> {
|
|
let state = do turtle_states > unbox > last
|
|
if state :visible?
|
|
then {
|
|
let (r, g, b, a) = turtle_color
|
|
add_call! ((:fill, r, g, b, a))
|
|
let #{heading
|
|
:pencolor (pen_r, pen_g, pen_b, pen_a)
|
|
:position (x, y)
|
|
pendown?
|
|
...} = state
|
|
let first = mult ((0, 1), turtle_radius)
|
|
let (x1, y1) = first
|
|
let (x2, y2) = rotate (first, turtle_angle)
|
|
let (x3, y3) = rotate (first, neg (turtle_angle))
|
|
add_call! ((:push))
|
|
add_call! ((:translate, x, y))
|
|
add_call! ((:rotate, turn/rad (heading)))
|
|
add_call! ((:noStroke))
|
|
add_call! ((:beginShape))
|
|
add_call! ((:vertex, x1, y1))
|
|
add_call! ((:vertex, x2, y2))
|
|
add_call! ((:vertex, x3, y3))
|
|
add_call! ((:endShape))
|
|
& there's a happy bug here: the stroke will be the same width as the pen width. Keep this for now. Consider also showing the pen colour here?
|
|
add_call! ((:stroke, pen_r, pen_g, pen_b, pen_a))
|
|
if pendown? then add_call! ((:line, 0, 0, x1, y1)) else nil
|
|
add_call! ((:pop))
|
|
:ok
|
|
}
|
|
else :ok
|
|
}
|
|
|
|
fn state/call () -> {
|
|
let cmd = do turtle_commands > unbox > last > first
|
|
let states = unbox (turtle_states)
|
|
let curr = last (states)
|
|
let prev = at (states, sub (count (states), 2))
|
|
match cmd with {
|
|
:forward -> if curr :pendown?
|
|
then make_line (prev :position, curr :position)
|
|
else nil
|
|
:back -> if curr :pendown?
|
|
then make_line (prev :position, curr :position)
|
|
else nil
|
|
:home -> if curr :pendown?
|
|
then make_line (prev :position, curr :position)
|
|
else nil
|
|
:goto -> if curr :pendown?
|
|
then make_line (prev :position, curr :position)
|
|
else nil
|
|
:penwidth -> (:strokeWeight, curr :penwidth)
|
|
:pencolor -> {
|
|
let (r, g, b, a) = curr :pencolor
|
|
(:stroke, r, g, b, a)
|
|
}
|
|
:clear -> (:background, 0, 0, 0, 255)
|
|
_ -> nil
|
|
}
|
|
}
|
|
|
|
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!"
|
|
(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!"
|
|
(gray as :number) -> store! (bgcolor, (gray, gray, gray, 255))
|
|
((r as :number, g as :number, b as :number)) -> store! (bgcolor, (r, g, b, 255))
|
|
((r as :number, g as :number, b as :number, a as :number)) -> store! (bgcolor, (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 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 angle = add (heading, 0.25)
|
|
(cos (angle), sin (angle))
|
|
}
|
|
}
|
|
|
|
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) -> assoc (state, :position, (0, 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)
|
|
(:show) -> assoc (state, :visible?, true)
|
|
(:hide) -> assoc (state, :visible?, false)
|
|
}
|
|
}
|
|
|
|
fn turtle_state {
|
|
"Returns the turtle's current state."
|
|
() -> do turtle_states > unbox > last
|
|
}
|
|
|
|
fn load_turtle_state! {
|
|
"Sets the turtle state to a previously saved state. Returns the state."
|
|
(state) -> {
|
|
update! (turtle_states, append (_, state))
|
|
let call = state/call ()
|
|
if call then { add_call! (call); :ok } else :ok
|
|
}
|
|
}
|
|
|
|
& position () -> (x, y)
|
|
fn position {
|
|
"Returns the turtle's current position."
|
|
() -> turtle_state () :position
|
|
}
|
|
|
|
fn heading {
|
|
"Returns the turtle's current heading."
|
|
() -> turtle_state () :heading
|
|
}
|
|
|
|
fn pendown? {
|
|
"Returns the turtle's pen state: true if the pen is down."
|
|
() -> turtle_state () :pendown?
|
|
}
|
|
|
|
fn pencolor {
|
|
"Returns the turtle's pen color as an (r, g, b, a) tuple."
|
|
() -> turtle_state () :pencolor
|
|
}
|
|
|
|
fn penwidth {
|
|
"Returns the turtle's pen width in pixels."
|
|
() -> turtle_state () :penwidth
|
|
}
|
|
|
|
box state = nil
|
|
|
|
pkg Prelude {
|
|
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
|
|
bgcolor & turtles
|
|
bk! & turtles
|
|
bool & bool
|
|
bool? & bool
|
|
box? & boxes
|
|
butlast & lists strings tuples
|
|
ceil & math
|
|
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
|
|
load_turtle_state! & turtles
|
|
lt! & turtles
|
|
lt? & math
|
|
lte? & math
|
|
map & lists
|
|
max & math
|
|
min & math
|
|
mod & math
|
|
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
|
|
p5_calls & turtles
|
|
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
|
|
render_turtle! & turtles
|
|
report! & environment
|
|
reset_turtle! & turtles
|
|
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
|
|
trim & strings
|
|
tuple? & tuples
|
|
turn/deg & math
|
|
turn/rad & math
|
|
turtle_commands & turtles
|
|
turtle_state & turtles
|
|
turtle_states & 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
|
|
}
|