keep adding prelude; fix when
stack discipline
This commit is contained in:
parent
9d798e5e58
commit
480e05f561
|
@ -315,6 +315,633 @@ fn string {
|
|||
}
|
||||
}
|
||||
|
||||
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 :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.
|
||||
|
||||
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 (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: 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 > 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))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#{
|
||||
type
|
||||
coll?
|
||||
|
@ -355,4 +982,87 @@ fn string {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -476,3 +476,16 @@ loop ([1, 2, 3]) with {
|
|||
|
||||
Meanwhile, other `loop`/`recur` forms seem to work okay for me.
|
||||
So: ugh.
|
||||
|
||||
### Grinding and grinding
|
||||
#### 2025-06-22
|
||||
Got 'er done.
|
||||
Fixed `loop`/`recur` and many other stack shananigans.
|
||||
I don't believe I've fixed everything yet.
|
||||
I may be surprised, though.
|
||||
|
||||
Currently fixing little bugs in prelude.
|
||||
Here's a list of things that need doing:
|
||||
* [ ] Escape characters in strings: \n, \t, and \{, \}.
|
||||
* [ ] `doc!` needs to print the patterns of a function.
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
|
||||
|
||||
when {
|
||||
false -> :no
|
||||
nil -> :nope
|
||||
:else -> :yup
|
||||
}
|
||||
|
||||
|
||||
|
|
28
src/base.rs
28
src/base.rs
|
@ -389,6 +389,7 @@ pub fn r#type(x: &Value) -> Value {
|
|||
pub fn split(source: &Value, splitter: &Value) -> Value {
|
||||
match (source, splitter) {
|
||||
(Value::String(source), Value::String(splitter)) => {
|
||||
println!("splitting {source} with {splitter}");
|
||||
let parts = source.split_terminator(splitter.as_str());
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
|
@ -396,29 +397,16 @@ pub fn split(source: &Value, splitter: &Value) -> Value {
|
|||
}
|
||||
Value::List(Box::new(list))
|
||||
}
|
||||
(Value::String(source), Value::Interned(splitter)) => {
|
||||
let parts = source.split_terminator(splitter);
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
(Value::String(_), Value::Interned(splitter)) => {
|
||||
split(source, &Value::String(Rc::new(splitter.to_string())))
|
||||
}
|
||||
(Value::Interned(source), Value::String(splitter)) => {
|
||||
let parts = source.split_terminator(splitter.as_str());
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
(Value::Interned(source), Value::String(_)) => {
|
||||
split(&Value::String(Rc::new(source.to_string())), splitter)
|
||||
}
|
||||
(Value::Interned(source), Value::Interned(splitter)) => {
|
||||
let parts = source.split_terminator(splitter);
|
||||
let mut list = vector![];
|
||||
for part in parts {
|
||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
||||
}
|
||||
Value::List(Box::new(list))
|
||||
let source = Value::String(Rc::new(source.to_string()));
|
||||
let splitter = Value::String(Rc::new(splitter.to_string()));
|
||||
split(&source, &splitter)
|
||||
}
|
||||
_ => unreachable!("internal Ludus error"),
|
||||
}
|
||||
|
|
|
@ -975,7 +975,6 @@ impl<'a> Compiler<'a> {
|
|||
self.tail_pos = false;
|
||||
self.visit(cond.as_ref());
|
||||
let jif_jump_idx = self.stub_jump(Op::JumpIfFalse);
|
||||
self.stack_depth -= 1;
|
||||
self.tail_pos = tail_pos;
|
||||
self.visit(body);
|
||||
self.stack_depth -= 1;
|
||||
|
@ -1010,21 +1009,11 @@ impl<'a> Compiler<'a> {
|
|||
Box::leak(Box::new(guard.clone().unwrap()));
|
||||
self.visit(guard_expr);
|
||||
no_match_jumps.push(self.stub_jump(Op::JumpIfFalse));
|
||||
self.stack_depth -= 1;
|
||||
}
|
||||
self.tail_pos = tail_pos;
|
||||
self.visit(body);
|
||||
// self.emit_op(Op::Store);
|
||||
self.store();
|
||||
self.leave_scope();
|
||||
// self.scope_depth -= 1;
|
||||
// while let Some(binding) = self.bindings.last() {
|
||||
// if binding.depth > self.scope_depth {
|
||||
// self.bindings.pop();
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
self.pop_n(self.stack_depth - stack_depth);
|
||||
jump_idxes.push(self.stub_jump(Op::Jump));
|
||||
for idx in no_match_jumps {
|
||||
|
|
|
@ -112,8 +112,8 @@ pub fn run(src: &'static str) {
|
|||
// in any event, the AST should live forever
|
||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
||||
|
||||
let prelude = prelude();
|
||||
// let prelude = imbl::HashMap::new();
|
||||
// let prelude = prelude();
|
||||
let prelude = imbl::HashMap::new();
|
||||
|
||||
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
|
||||
validator.validate();
|
||||
|
|
Loading…
Reference in New Issue
Block a user