bugfixes and improvements

This commit is contained in:
Scott Richmond 2024-06-15 21:51:56 -04:00
parent d64467ef6d
commit 07c6d23587

View File

@ -9,26 +9,26 @@
& some forward declarations & some forward declarations
& TODO: fix this so that we don't need (as many of) them & TODO: fix this so that we don't need (as many of) them
fn first
fn append
fn some?
fn update!
fn string
fn join
fn neg?
fn atan/2
fn mod
fn assoc & ?
fn dict
fn get
fn unbox
fn store!
fn turn/rad
fn deg/rad
fn floor
fn and fn and
fn append
fn apply_command 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 some?
fn state/call fn state/call
fn store!
fn string
fn turn/rad
fn unbox
fn update!
& the very base: know something's type & the very base: know something's type
fn type { fn type {
@ -36,6 +36,30 @@ fn type {
(x) -> base :type (x) (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 & ...and if two things are the same
fn eq? { fn eq? {
"Returns true if all arguments have the same value." "Returns true if all arguments have the same value."
@ -166,25 +190,10 @@ fn list? {
} }
fn list { fn list {
"Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order. Associative collections return lists of (key, value) tuples." "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) (x) -> base :to_list (x)
} }
& TODO: make this work with Janet base
fn set {
"Takes an ordered collection--list or tuple--and turns it into a set."
(xs as :list) -> base :into (${}, xs)
(xs as :tuple) -> base :into (${}, xs)
}
fn set? {
"Returns true if a value is a set."
(xs as :set) -> true
(_) -> false
}
& add a contains? or has? function
fn fold { fn fold {
"Folds a list." "Folds a list."
(f as :fn, xs as :list) -> fold (f, xs, f ()) (f as :fn, xs as :list) -> fold (f, xs, f ())
@ -245,30 +254,29 @@ fn concat {
(xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys))
} }
& the console: sending messages to the outside world
& the console is *both* something we send to the host language's console
& ...and also a list of messages.
& box console = []
& fn flush! { fn set {
& "Clears the console, and returns the messages." "Takes an ordered collection--list or tuple--and turns it into a set."
& () -> { (xs as :list) -> fold (append, xs, ${})
& let msgs = unbox (console) (xs as :tuple) -> do xs > list > set
& store! (console, []) }
& msgs
& }
& }
& fn add_msg! { fn set? {
& "Adds a message to the console." "Returns true if a value is a set."
& (msg as :string) -> update! (console, append (_, msg)) (xs as :set) -> true
& (msgs as :list) -> { (_) -> false
& base :print! (("adding msg", msgs)) }
& let msg = do msgs > map (string, _) > join
& base :print! (("msg: ", msg)) fn contains? {
& update! (console, append (_, msg)) "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! { fn print! {
"Sends a text representation of Ludus values to the console." "Sends a text representation of Ludus values to the console."
@ -283,15 +291,6 @@ fn show {
(x) -> base :show (x) (x) -> base :show (x)
} }
fn prn! {
"Prints the underlying Clojure data structure of a Ludus value."
(x) -> {
base :prn (x)
& add_msg! (x)
:ok
}
}
fn report! { fn report! {
"Prints a value, then returns it." "Prints a value, then returns it."
(x) -> { (x) -> {
@ -299,7 +298,7 @@ fn report! {
x x
} }
(msg as :string, x) -> { (msg as :string, x) -> {
print! (concat (msg, show (x))) print! (concat ("{msg} ", show (x)))
x x
} }
} }
@ -332,7 +331,7 @@ fn join {
([]) -> "" ([]) -> ""
([str as :string]) -> str ([str as :string]) -> str
(strs as :list) -> join (strs, "") (strs as :list) -> join (strs, "")
([str], separator as :string) -> str ([str as :string], separator as :string) -> str
([str, ...strs], separator as :string) -> fold ( ([str, ...strs], separator as :string) -> fold (
fn (joined, to_join) -> concat (joined, separator, to_join) fn (joined, to_join) -> concat (joined, separator, to_join)
strs strs
@ -398,13 +397,7 @@ fn sentence {
(strs as :list) -> join (strs, " ") (strs as :list) -> join (strs, " ")
} }
&&& boxes: mutable state and state changes
& in another prelude, with a better actual base language than Java (thanks, Rich), counting strings would be reasonable but complex: count/bytes, count/points, count/glyphs. Java's UTF16 strings make this unweildy.
& TODO: add trim, trim/left, trim/right; pad/left, pad/right
& ...also a version of at,
&&& references: mutable state and state changes
fn box? { fn box? {
"Returns true if a value is a box." "Returns true if a value is a box."
@ -650,17 +643,17 @@ fn at {
fn first { fn first {
"Returns the first element of a list or tuple." "Returns the first element of a list or tuple."
(xs) -> at (xs, 0) (xs) if ordered? (xs) -> at (xs, 0)
} }
fn second { fn second {
"Returns the second element of a list or tuple." "Returns the second element of a list or tuple."
(xs) -> at (xs, 1) (xs) if ordered? (xs) -> at (xs, 1)
} }
fn last { fn last {
"Returns the last element of a list or tuple." "Returns the last element of a list or tuple."
(xs) -> at (xs, dec (count (xs))) (xs) if ordered? (xs) -> at (xs, dec (count (xs)))
} }
fn butlast { fn butlast {
@ -731,9 +724,6 @@ fn or {
(x, y, ...zs) -> fold (or, zs, base :or (x, y)) (x, y, ...zs) -> fold (or, zs, base :or (x, y))
} }
&&& associative collections: dicts, structs, namespaces
& TODO?: get_in, update_in, merge
& TODO?: consider renaming these: put & take, not assoc/dissoc
fn assoc { fn assoc {
"Takes a dict, key, and value, and returns a new dict with the key set to value." "Takes a dict, key, and value, and returns a new dict with the key set to value."
() -> #{} () -> #{}
@ -791,30 +781,6 @@ fn diff {
} }
} }
fn coll? {
"Returns true if a value is a collection: dict, list, pkg, tuple, or set."
(coll as :dict) -> true
(coll as :list) -> true
(coll as :tuple) -> true
(coll as :set) -> true
(coll as :pkg) -> 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
}
& TODO: consider merging `get` and `at` & TODO: consider merging `get` and `at`
fn get { fn get {
"Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value."
@ -843,15 +809,11 @@ fn dict? {
(_) -> false (_) -> false
} }
& TODO: make this less awkward once we have tail recursion
fn each! { fn each! {
"Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." "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, []) -> nil
(f! as :fn, [x]) -> { f! (x); nil } (f! as :fn, [x]) -> { f! (x); nil }
(f! as :fn, [...xs]) -> loop (xs) with { (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) }
([x]) -> { f! (x); nil }
([x, ...xs]) -> { f! (x); recur (xs) }
}
} }
&&& Trigonometry functions &&& Trigonometry functions
@ -938,18 +900,38 @@ fn atan/2 {
} }
fn mod { fn mod {
"Returns the modulus of num and div. Truncates towards negative infinity." "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) (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 { fn square {
"Squares a number." "Squares a number."
(x as :number) -> mult (x, x) (x as :number) -> mult (x, x)
} }
fn sqrt { fn sqrt {
"Returns the square root of a number." "Returns the square root of a number. Panics if the number is negative."
(x as :number) -> base :sqrt (x) (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 { fn sum_of_squares {
@ -957,18 +939,21 @@ fn sum_of_squares {
() -> 0 () -> 0
(x as :number) -> square (x) (x as :number) -> square (x)
(x as :number, y as :number) -> add (square (x), square (y)) (x as :number, y as :number) -> add (square (x), square (y))
(x, y, ...zs) -> fold (sum_of_squares, zs, sum_of_squares (x, y)) (x, y, ...zs) -> fold (
fn (sum, z) -> add (sum, square (z))
zs
sum_of_squares (x, y))
} }
fn dist { fn dist {
"Returns the distance from the origin to a point described by (x, y)." "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 as :number, y as :number) -> sqrt (sum_of_squares (x, y))
((x, y)) -> dist (x, y) ((x, y)) -> dist (x, y)
} }
&&& more number functions &&& more number functions
fn random { fn random {
"Returns a random number. 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 list, it returns a random member of that list." "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 () () -> base :random ()
(n as :number) -> mult (n, random ()) (n as :number) -> mult (n, random ())
(m as :number, n as :number) -> add (m, random (sub (n, m))) (m as :number, n as :number) -> add (m, random (sub (n, m)))
@ -980,6 +965,7 @@ fn random {
let key = do d > keys > random let key = do d > keys > random
get (key, d) get (key, d)
} }
(s as :set) -> do s > list > random
} }
fn random_int { fn random_int {
@ -1048,10 +1034,12 @@ fn unwrap_or {
fn assert! { fn assert! {
"Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." "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! string ("Assert failed:", value) (value) -> if value
then value
else panic! "Assert failed: {value}"
(msg, value) -> if value (msg, value) -> if value
then value then value
else panic! string ("Assert failed: ", msg, " with ", value) else panic! "Assert failed: {msg} with {value}"
} }
&&& Turtle & other graphics &&& Turtle & other graphics
@ -1198,8 +1186,6 @@ fn back! {
let bk! = back! let bk! = back!
& turtles, like eveyrthing else in Ludus, use turns by default,
& not degrees
fn left! { fn left! {
"Rotates the turtle left, measured in turns. Alias: lt!" "Rotates the turtle left, measured in turns. Alias: lt!"
(turns as :number) -> add_command! ((:left, turns)) (turns as :number) -> add_command! ((:left, turns))
@ -1374,6 +1360,7 @@ pkg Prelude {
coll? & dicts lists sets tuples coll? & dicts lists sets tuples
colors & turtles colors & turtles
concat & string list set concat & string list set
contains? & list set
cos & math cos & math
count & string list set tuple dict count & string list set tuple dict
dec & math dec & math
@ -1430,6 +1417,8 @@ pkg Prelude {
max & math max & math
min & math min & math
mod & math mod & math
mod/0
mod/safe
mult & math mult & math
neg & math neg & math
neg? & math neg? & math
@ -1439,6 +1428,7 @@ pkg Prelude {
odd? & math odd? & math
ok & results ok & results
ok? & results ok? & results
omit & set
or & bool or & bool
ordered? & lists tuples strings ordered? & lists tuples strings
p5_calls & turtles p5_calls & turtles
@ -1455,7 +1445,6 @@ pkg Prelude {
pos? & math pos? & math
position & turtles position & turtles
print! & environment print! & environment
& prn! & environment
pu! & turtles pu! & turtles
pw! & turtles pw! & turtles
rad/deg & math rad/deg & math
@ -1480,6 +1469,8 @@ pkg Prelude {
some & values some & values
some? & values some? & values
split & strings split & strings
sqrt & math
sqrt/safe & math
square & math square & math
state & environment state & environment
store! & boxes store! & boxes