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
|
type
|
||||||
coll?
|
coll?
|
||||||
|
@ -355,4 +982,87 @@ fn string {
|
||||||
doc!
|
doc!
|
||||||
string
|
string
|
||||||
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.
|
Meanwhile, other `loop`/`recur` forms seem to work okay for me.
|
||||||
So: ugh.
|
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 {
|
pub fn split(source: &Value, splitter: &Value) -> Value {
|
||||||
match (source, splitter) {
|
match (source, splitter) {
|
||||||
(Value::String(source), Value::String(splitter)) => {
|
(Value::String(source), Value::String(splitter)) => {
|
||||||
|
println!("splitting {source} with {splitter}");
|
||||||
let parts = source.split_terminator(splitter.as_str());
|
let parts = source.split_terminator(splitter.as_str());
|
||||||
let mut list = vector![];
|
let mut list = vector![];
|
||||||
for part in parts {
|
for part in parts {
|
||||||
|
@ -396,29 +397,16 @@ pub fn split(source: &Value, splitter: &Value) -> Value {
|
||||||
}
|
}
|
||||||
Value::List(Box::new(list))
|
Value::List(Box::new(list))
|
||||||
}
|
}
|
||||||
(Value::String(source), Value::Interned(splitter)) => {
|
(Value::String(_), Value::Interned(splitter)) => {
|
||||||
let parts = source.split_terminator(splitter);
|
split(source, &Value::String(Rc::new(splitter.to_string())))
|
||||||
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(splitter)) => {
|
(Value::Interned(source), Value::String(_)) => {
|
||||||
let parts = source.split_terminator(splitter.as_str());
|
split(&Value::String(Rc::new(source.to_string())), 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::Interned(source), Value::Interned(splitter)) => {
|
(Value::Interned(source), Value::Interned(splitter)) => {
|
||||||
let parts = source.split_terminator(splitter);
|
let source = Value::String(Rc::new(source.to_string()));
|
||||||
let mut list = vector![];
|
let splitter = Value::String(Rc::new(splitter.to_string()));
|
||||||
for part in parts {
|
split(&source, &splitter)
|
||||||
list.push_back(Value::String(Rc::new(part.to_string())));
|
|
||||||
}
|
|
||||||
Value::List(Box::new(list))
|
|
||||||
}
|
}
|
||||||
_ => unreachable!("internal Ludus error"),
|
_ => unreachable!("internal Ludus error"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -975,7 +975,6 @@ impl<'a> Compiler<'a> {
|
||||||
self.tail_pos = false;
|
self.tail_pos = false;
|
||||||
self.visit(cond.as_ref());
|
self.visit(cond.as_ref());
|
||||||
let jif_jump_idx = self.stub_jump(Op::JumpIfFalse);
|
let jif_jump_idx = self.stub_jump(Op::JumpIfFalse);
|
||||||
self.stack_depth -= 1;
|
|
||||||
self.tail_pos = tail_pos;
|
self.tail_pos = tail_pos;
|
||||||
self.visit(body);
|
self.visit(body);
|
||||||
self.stack_depth -= 1;
|
self.stack_depth -= 1;
|
||||||
|
@ -1010,21 +1009,11 @@ impl<'a> Compiler<'a> {
|
||||||
Box::leak(Box::new(guard.clone().unwrap()));
|
Box::leak(Box::new(guard.clone().unwrap()));
|
||||||
self.visit(guard_expr);
|
self.visit(guard_expr);
|
||||||
no_match_jumps.push(self.stub_jump(Op::JumpIfFalse));
|
no_match_jumps.push(self.stub_jump(Op::JumpIfFalse));
|
||||||
self.stack_depth -= 1;
|
|
||||||
}
|
}
|
||||||
self.tail_pos = tail_pos;
|
self.tail_pos = tail_pos;
|
||||||
self.visit(body);
|
self.visit(body);
|
||||||
// self.emit_op(Op::Store);
|
|
||||||
self.store();
|
self.store();
|
||||||
self.leave_scope();
|
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);
|
self.pop_n(self.stack_depth - stack_depth);
|
||||||
jump_idxes.push(self.stub_jump(Op::Jump));
|
jump_idxes.push(self.stub_jump(Op::Jump));
|
||||||
for idx in no_match_jumps {
|
for idx in no_match_jumps {
|
||||||
|
|
|
@ -112,8 +112,8 @@ pub fn run(src: &'static str) {
|
||||||
// in any event, the AST should live forever
|
// in any event, the AST should live forever
|
||||||
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
|
||||||
|
|
||||||
let prelude = prelude();
|
// let prelude = prelude();
|
||||||
// let prelude = imbl::HashMap::new();
|
let prelude = imbl::HashMap::new();
|
||||||
|
|
||||||
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
|
let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
|
||||||
validator.validate();
|
validator.validate();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user