From 5de97b8a9ce6e2548e275e19d4c527073d6ef310 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Dec 2024 20:08:55 -0500 Subject: [PATCH] create doc dir --- doc/language.md | 513 +++++++++++++ doc/prelude.md | 1574 ++++++++++++++++++++++++++++++++++++++++ doc/turtle-graphics.md | 92 +++ 3 files changed, 2179 insertions(+) create mode 100644 doc/language.md create mode 100644 doc/prelude.md create mode 100644 doc/turtle-graphics.md diff --git a/doc/language.md b/doc/language.md new file mode 100644 index 0000000..24e1665 --- /dev/null +++ b/doc/language.md @@ -0,0 +1,513 @@ +# Ludus language reference + +This is not intended for beginners, but to be a language overview for experienced programmers. That said, it may help beginners orient themselves in the language. + +## Comments +Ludus's comment character is `&`. Anything after an ampersand on a line is ignored. There are no multiline comments. + +## Atomic values +Ludus has four types of atomic values. + +### `nil` +`nil` is Ludus's representation of nothing. In the grand Lisp tradition, Ludus can, and occasionally does, use `nil`-punning. Its type is `:nil`. + +### Booleans +`true` and `false`. That said, in all conditional constructs, `nil` and `false` are "falsy," and everything else is "truthy." Their type is `:boolean`. + +### Numbers +Ludus has numbers, which are IEEE-754 64-bit floats. Numbers are more complicated than you think, probably. + +Number literals in Ludus are either integers or decimal floating point numbers, e.g. `32.34`, `42`, `-0.23`. Underscores in numbers are ignored, and can be used to separate long numbers, e.g. `1_234_567_890`. + +Numbers' type is `:number`. + +### Keywords +Ludus keywords begin with a colon and a letter, e.g. `:keyword`. Types are represented as keywords. Some functions take an optional units argument as a keyword, e.g. `:radians`. Keywords are also used as keys for associative collections. Keywords' type is `:keyword`. + +Keywords must begin with an ASCII upper- or lower-case letter, and can then include any letter character, as well as `_`, `/`, `!`, `?`, and `*`. + +## Strings +Ludus strings are UTF-8 strings, and only use double quotes. Strings may be multiline. For example, this is a string: `"foo"`. So is this: + +``` +"foo + + +bar baz" +``` +Strings use backslashes for escapes, including `\n` for newline, `\t` for tab, `\"` for a double quote, and `\{` for an open curly brace (see below on interpolation). + +Strings' type is `:string`. + +### String interpolation +Strings may also insert a string representation of any Ludus value that is bound to a name, by inserting that name in curly braces. To wit, +``` +let foo = :foo +let bar = 42 +let baz = [1, 2, 3] +"{foo} {bar} {baz}" &=> ":foo 42 1, 2, 3" +``` +Interpolations may _not_ be arbitrary expressions: only bound names may be used in interpolations. + +## Collections +Ludus has a few different types of collections, in increasing order of complexity: tuples, lists, sets, dicts, and packages. All collections are immutable. + +#### Separators +In all collection literals, members are written with a separator between them. On the same line, use a comma; or a newline will also separate elements. You may use as many separators as you wish at any point inside a collection or pattern. `(,,,,,,,3,,4,,,,,,)` and `(3, 4)` are the same value. + +#### Efficiency +At the current moment, Ludus collections are all copy-on-write; this means that Ludus is _not_ performant with large collections. Eventually, Ludus will have Clojure-style persistent, immutable collections. + +### Tuples +Tuples are fully-immutable, ordered collections of any kinds of values, delimited by parentheses, e.g. `(1, :a, "foo")`. At current, they have no length limit (although they eventually will). Unlike in some languages, tuples can be empty or contain a single element: `()` and `(:foo)` are both just fine. Tuples largely cannot be manipulated functionally; they must be written as literals and unpacked using pattern matching. They can, however, be converted to lists, either through pattern matching or the `list` function. Their type is `:tuple`. + +### Lists +Lists are persistent and immutable ordered collections of any kinds of values, delimited by square braces, e.g. `[1, :a, "foo"]`. Their type is `:list`. + +Lists may be combined using splats, written with ellipses, e.g., `[...foo, ...bar]`. + +### Sets +Sets are persistent and immutable unordered collections of any kinds of values, which can only contain one instance of any given value. They are written similarly to ordered collections: `${1, :a, "foo"}`. Their type is `:set`. + +### Dictionaries, or dicts +Dicts are persistent and immutable associative collections of any kinds of values. Dicts use keywords as keys (and cannot use any other kind of Ludus value as a key, not even strings), but can store any values. Dict literals are written as keyword-value pairs: `#{:a 1, :b false}`. Single words may be used as a shorthand for a key-value pair. Accessing a key that holds no value returns `nil`. Their type is `:dict`. + +### Packages +Packages are immutable collections of bindings. They may only be described at the top level of a script, and their names must begin with a capital letter. Accessing a key that has no value on a package results in a validation error. They may not be accessed using functions, but only direct keyword access. Their type is `:pkg`. + +They are written with the form `pkg`, then a package name, beginning with a capital letter, that will be bound as their name, and then an associative structure (pairs or word shorthands), delimited by `{}`, e.g.: + +``` +pkg Foo { + :bar "bar" + :baz 42 + quux +} +``` + +### Working with collections +Ludus names are bound permanently and immutably. Collections are immutable. How do you add something to a list or a dict? How do you get things out of them? + +Ludus provides functions that allow working with persistent collections. They're detailed in [the Prelude](prelude.md). That said, all functions that modify collections take a collection and produce the modified collection _as a return value_, without changing the original collection. E.g., `append ([1, 2, 3], 4)` will produce `[1, 2, 3, 4]`, but the original list is unchanged. (For dicts, the equivalent is `assoc`.) + +## Expressions +Ludus is an expression-based language: all forms in the language are expressions and return values, except `panic!`. That said, not all expressions may be used everywhere. + +### Terminating expressions +Expressions in scripts and blocks are terminated by a newline or a semicolon. In compound forms, like, `if`, the terminator comes after the `else` expression. + +In forms with multiple clauses surrounded by curly braces (i.e., function bodies, `match`, `when`, etc.), you may separate clauses with semicolons as well as newlines. + +### Toplevel expressions +Some expressions may only be used in the "top level" of a script. Because they are the toplevel, they are assured to be statically knowable. These include: `pkg`, `ns`, `use`, `import`, and `test`. (NB: not all of these are yet implemented.) + +### Non-binding expressions +Some forms may take any expression that does _not_ [bind a name](#words-and-bindings), for example, any entry in a collection, or the right-hand side of a `let` binding. This is because binding a name in some positions is ambiguous, or nonsensical, or leads to unwarranted complications. + +### Simple expressions +Many compound forms will only accept "simple" expressions. Formally, simple expressions are either literal (atomic, string, or collection literals) or synthetic expressions. They are expressions which do not take sub-expressions: no `if`, `when`, `match`, etc. (`do` expressions are currently not simple, but that may be revised.) + +## Words and bindings +Ludus uses _words_ to bind values to names. Words must start with a lower case ASCII letter, and can subsequently include any letter character (modulo backing character encoding), as well as , `_`, `/`, `?`, `!`, and `*`. + +Ludus binds values to names immutably and permanently: no name in the same scope may ever be re-bound to a different value. (Although see [boxes](#boxes-and-state), below. + +Attempting to use an unbound name (a word that has not had a value bound to it) will result in a validation error, and the script will not run. + +### `let` bindings: a very short introduction +Ludus's basic binding form is `let`: + +``` +let foo = :bar & `foo` is now bound to `bar` for the rest of the scope. + +let foo = :baz & Validation error: name foo was bound in line 1 +``` + +`let` bindings are more complex; we will return to these below. + +## Patterns +Ludus makes extensive use of pattern-matching. Patterns do two jobs at once: they match values (or don't); and they bind names. The left-hand side of the examples just above in the `let` binding is not just a word: it is a pattern. Patterns also arise in conditional forms and function declarations. + +### The placeholder: `_` +The simplest pattern is the placeholder: it matches against anything, and does not bind a name. It is written as a single underscore: `_`, e.g., `let _ = :foo`. + +#### Ignored names +If you wish to be a bit more explict than using a placeholder, you can use an ignored name, which is a name that starts with an underscore: `_foo`. This is not bound, is not a valid name, and can be used however much you wish, even multiple times in the same pattern. It is, in fact, a placeholder, plus a reader-facing description. + +### Literal patterns +Patterns can be literal atomic values or strings: `0`, `false`, `nil`, `:foo`, etc. That means you can write `let 0 = 0` or `let :foo = :foo`, and, while nothing will happen, everything will be just fine. + +Literals match against, well, literal values: if the pattern and the value are the same, they match! If not, they don't match. + +Literal values do not bind anything. + +### Word patterns +Word patterns match against any value, and bind that value to the word as a name. The scope of that binding depends on the form the pattern is in. `let foo = :bar` binds `:bar` to `foo` for the rest of the scope. + +#### Typed patterns +Word patterns can, optionally, take a type, using the `as` reserved word, and the keyword representing the desired type: `let foo as :number = 42`. + +### String patterns +Ludus has a simple but powerful form of string pattern matching that mirrors string interpolation. Any word inside curly braces in a string will match against a substring of a string passed into a pattern. + +``` +let i_am = "I am the walrus" + +let "I {verb} the {noun}" = i_am +(verb, noun) &=> ("am", "walrus") +``` + +Note that such names may well be bound to empty strings: a match does not guarantee that there will be anything in the string. This is particularly relevant at the beginning and end of string patterns: + +``` +let we_are = "We are the eggmen" +let "{first}We {what}" = we_are +(first, what) &=> ("", "are the eggmen") +``` + +### Collection patterns +Tuples, lists, and dicts can be destructured using patterns. They are written nearly identically to their literal counterparts. Collection patterns are composed of any number of simpler patterns or other collection patterns. They bind any names nested in them, match literals in them, etc. + +#### Tuple patterns +Tuple patterns are delimited using parens, using commas or newlines to separate any number of other patterns. Consider `let (x, y, z) = (1, 2, 3)`. `x`, `y`, and `z` are now bound to 1, 2, and 3, respectively. + +The last item in a tuple pattern can be a splat--`...`--which either discards any remaining unenumerated values in a tuple, or binds them to a list. Without a splat, tuples patterns only match against tuples of the same length. + +``` +let mytup = (1, 2, 3) +let (x, _, y) = mytup & x is now 1, y is now 3 +let (a, ...) = mytup & a is now 1; a bare splat (without a name) is just fine +let (_, ...cs) = mytup & cs is now [2, 3] +let (p, q) = mytup & panic! no match +let () = () & empty tuples are also patterns +``` + +#### List patterns +List patterns are identical to tuple patterns, but they are written using square braces. They also match against a specific number of members, and may take a splat in the last position, e.g. `let [first, ...rest] = [1, 2, 3]`. + +Note that list patterns, like tuple patterns, match on explicit length. That means that if you are matching only the first items of a list, you must explicitly include a splat pattern, e.g. `let [first, second, ...] = [1, 2, 3, 4]`. + +#### Dict patterns +Dict patterns are written either with shorthand words, or keyword-pattern pairs. Consider: `let #{:a foo, :b 12, c} = #{:a 1, :b 12, :c 4}`. `foo` is now 1; `b` is now 12, `c` is now 4. If a dict does not hold a value at a particular key, there is no match. + +Dict patterns may also use a splat as their last member: `let #{:a 1, ...b} = #{:a 1, :b 2, :c 3}` will bind `b` to `#{:b 2, :c 3}`. + +Like tuple and list patterns, dict patterns without a splat at the end match only on exact equivalence on all keys. + +## `let` bindings +`let` bindings are the basic form of matching and binding in Ludus. It is written `let {pattern} = {non-binding expression}`. The pattern can be arbitrarily complex. If the left-hand side of a `let` binding does not match, Ludus will raise a panic, halting evaluation of the script. + +## Scope and blocks +Ludus is lexically scoped. Bindings are valid for the remainder of the scope they act on. To introduce a new scope, Ludus uses a block, a collection of expressions delimited by curly braces and separated by semicolons or newlines. The value of a block, as well as the value of a script, is the last expression in it. In `let foo = {:this; :set; :of; :expressions; "is actually"; :ignored }`, `foo` will be bound to `:ignored`. + +That said, you can use bindings in blocks, which will not be available outside that block--but blocks can use bidnings from their parent scope: + +``` +let outer = 42 + +let first = { + let inner = 23 + add (outer, inner) +} & first is now bound to 65 + +inner & Validation error: unbound name inner + +``` + +### Shadowing +Even though names are bound permanently in Ludus, it is perfectly possible (and occasionally quite useful) to "shadow" names from an enclosing scope. + +``` +let x = 42 + +let y = { + let first = x + let x = 23 & this is fine + let second = x + add (first, second) +} & y is now 65 + +``` + +## Conditional forms +Ludus has a robust set of conditional forms, all of which are expressions and resolve to a single value. + +### `if` +`if` evaluates a condition; if the result of the condition is truthy, it evaluates is `then` branch; if the condition is falsy, it evaluates its `else` branch. Both branches must always be present. Newlines may come before `then` and `else`. + +`if {simple expression} then {non-binding expression} else {non-binding expression}` + +### `when` +`when` is like Lisp's `cond`: it takes a series of clauses, separated by semicolons or newlines, delimited by curly braces. Clauses are written `lhs -> rhs`. `when` expressions are extremely useful for avoiding nested `if`s. + +The left hand of a clause is a simple expression; the right hand of a clause is any expression. When the left hand is truthy, the right hand is evaluated, and the result of that evaluation is returned; no further clauses are evaluated. If no clause has a truthy left-hand value, then a panic is raised. In the example below, not the use of literal `true` as an always-matching clause. + +``` +when { + maybe () -> :something + mabye_not () -> :something_else + true -> :always +} +``` + +### `match` +A `match` form is the most powerful conditional form in Ludus. It consists of a test expression, and a series of clauses. The test expression must be a simple expression, followed by `with`, and then a series of clauses separated by a semicolon or newline, delimited by curly braces. + +``` +match may_fail () with { + (:ok, value) -> calculate_result (value) + (:err, msg) -> { log! (msg); recover_somehow () } +} +``` + +The left hand of a match clause is a pattern; the right hand is an expression: `pattern -> expression`. If the pattern matches the test expression of a clause, the expression is evaluated with any bindings from the pattern, and `match` form evaluates to the result of that expression. + +If a test expression does not match against any clause's pattern, a panic is raised. + +Ludus does not attempt to do any exhaustiveness checking on match forms; match errors are always runtime errors. + +#### Guards +`match` clauses may have a _guard expression_, which allows a clause only to match if the expression's result is truthy. In the previous example, consider that we might want different behaviour depending on the value of the number: +``` +match may_fail () with { + (:ok, value) if pos? (value) -> calculate_positive_result (value) + (:ok, value) if neg? (value) -> calculate_negative_result (value) + (:ok, 0) -> do_something_with_zero () + (:err, msg) -> { log! (msg); recover_somehow () } +} +``` + +## Functions +Ludus is an emphatically functional language. Almost everything in Ludus is accomplished by applying functions to values, or calling functions with arguments. (These are precise synonyms.) + +Functions have the type `:fn`. + +### Calling functions +Functions are called by placing a tuple with arguments immediately after a function name, e.g. `add (1, 2)` adds `1` and `2`. Because they are represented as tuples, arguments must be explicitly written; splats cannot be used to pass an arbitrary number of arguments to a function. + +### Defining functions +Functions have three increasingly complex forms to define them. All of them include the concept of a function clause, which is just a match clause whose left hand side must be a _tuple_ pattern. + +#### Anonymous lambda +An anonymous lambda is written `fn {tuple pattern} -> {expression}`, `fn (x, y) -> if gt? (x, y) then x else add (x, y)`. Lambdas may only have one clause. + +#### Named functions +A named function is identical to a lambda, with the one change that a word follows the `fn` reserved word: `fn {name} {tuple pattern} -> {expression}`. E.g., `fn add_1 (x) -> add (x, 1)`. The name of the function is bound for the remainder of the scope. + +#### Compound functions +Compound functions are functions that have multiple clauses. They must be named, and in place of a single clause after a name, they consist in one or more clauses, separated by semicolons or newlines, delimited by curly braces. Optionally, compound functions may have a docstring as their first element after the opening curly brace. The docstring explains the function's purpose and use, before any of the function clauses. + +An example from Ludus's Prelude: + +``` +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 +} +``` + +### Closures +Functions in Ludus are closures: function bodies have access not only to their specific scope, but any enclosing scope. That said, functions only have access to names bound _before_ they are defined; nothing is hoisted in Ludus. + +### Mutual recursion and forward declaration +If you try the following, you'll get a validation error: + +``` +fn stupid_odd? { + (0) -> false + (x) -> supid_even? (dec (x)) & Validation error: unbound name stupid_even? +} + +fn stupid_even? { + (0) -> true + (x) -> stupid_odd? (dec (x)) +} +``` + +To allow for mutual recursion, Ludus allows forward declarations, which are written `fn name` without any clauses. In the example above, we would simply put `fn stupid_even?` before we define `stupid_odd?`. + +If you declare a function without defining it, however, Ludus will raise a validation error. + +### The Prelude +The Prelude is a substantial set of functions that is available in any given Ludus script. (It is, itself, just a Ludus file that has special access to host functions.) Because of that, a large number of functions are always available. The prelude documentation is [here](prelude.md). + +### Partial application +Functions in Ludus can be partially applied by using a placeholder in the arguments. Partial application may only use a single placeholder (partially applied functions are always unary), but it can be anywhere in the arguments: `let add_1 = add(1, _)` or `let double = mult(_, 2)`. + +Unary functions and called keywords may _not_ be partially applied: it is redundant. + +Because of "partial application," Ludus has a concept of an "argument tuple" (which may include a single placeholder) in addition to a tuple literal (which may not include a placeholder). + +### Function pipelines, or `do` forms +In place of nesting function calls inside other function calls, Ludus allows for a more streamlined version of function application: the `do` form or function pipeline. `do` is followed by an initial expression. `do` expressions use `>` as an operator: whatever is on the left hand side of the `>` is passed in as a single argument to whatever is on its right hand side. For example: + +``` +let silly_result = do 23 > + mult (_, 2) > add (1, _) > + sub (_, 2) > div (_, 9) & silly_result is 5 +``` +Newlines may appear after any instance of `>` in a `do` expression. That does, however, mean that you must be careful not to accidentally include any trailing `>`s. + +### Called keywords +Keywords may be called as functions, in which case they extract the value stored at that key in the value passed in: + +``` +let foo = #{:a 1, :b 2} +let bar = :a (foo) & `bar` is now 1 +``` + +Called keywords can be used in pipelines. + +In addition, keywords may be called when they are bound to names: + +``` +let foo = #{:a 1, :b 2} +let bar = :a +bar (foo) & => 1 +``` + +## Synthetic expressions +Synthetic expressions are valid combinations of words, keywords, package names, and argument tuples which allow for calling functions and extracting values from associative collections. The root--first term--of a synthetic expression must be a word or a keyword; subsequent terms must be either argument tuples or keywords. + +``` +let foo = #{:a 1, :b #{:c "bar" :d "baz"}} + +let bar = foo :b :c & `bar` is now "bar" + +let baz = :b (foo) :d & `baz` is now "baz" + +``` + +## Looping forms +Ludus has optimized tail calls--the most straightforward way to accomplish repeating behaviour is function recursion. There are two additional looping forms, `repeat` and `loop`. + +### `repeat` +`repeat` is a help to learners, and is useful for executing side effects multiple times. It is written `repeat {number|word} { {exprs} }`. From turtle graphics: + +``` +repeat 4 { + forward! (100) + right! (0.25) +} +``` +Note that `repeat` does two interesting things: + +1. It never returns a value other than `nil`. If it's in the block, it disappears. This is a unique (and frankly, undesirable) property in Ludus. +2. Unlike everything else in Ludus, repeate _requires_ a block, and not simply an expression. You cannot write `repeat 4 forward! (100)`. + +### `loop`/`recur` +`loop` and `recur` are largely identical to recursive functions for repetition, but use a special form to allow an anonymous construction and a few guard rails. + +The syntax here is `loop with { }`. (Or, you can have a single function clause instead of a set of clauses.) The tuple is passed in as the first set of arguments. + +``` +let xs = [1, 2, 3, 4] +loop (xs, 0) with { + ([x], sum) -> add (x, sum) & matches against the last element of the list + ([x, ...xs], sum) -> recur (xs, add (x, sum)) & recurs with the tail +} &=> 10 +``` + +`recur` is the recursive call. It must be in tail position--`recur` must be the root of a synthetic expression, in return position. If `recur` is not in tail position, a validation error will be raised. + +In addition, `recur` calls must have the same number of arguments as the original tuple passed to `loop`. While Ludus will allow you to write clauses in `loop` forms with a different arity than the original tuple, those will never match. + +`recur` calls return to the nearest `loop`. Nested `loop`s are probably a bad idea and should be avoided when possible. + +## Environment and context: the toplevel +The "toplevel" of a script are the expressions that are not embedded in other expressions or forms: not inside a block, not a member of a collection, not on the right hand side of a binding, not inside a function body. The toplevel-only forms: + +### `import` +`import` allows for the evaluation of other Ludus scripts: `import "path/to/file" as name`. `import` just evaluates that file, and then binds a name to the result of evaluating that script. This, right now, is quite basic: circular imports are currently allowed but will lead to endless recursion; results are not cached, so each `import` in a chain re-evaluates the file; and so on. + +Status: not yet implemented. + +### `use` +`use` loads the contents of a namespace into a script's context. To ensure that this is statically checkable, this must be at the toplevel. + +Status: not yet implemented. + +### `pkg` +Packages, `pkg`es, may only be described at the toplevel of a script. This is to ensure they can be statically evaluatable. + +### `test` +A `test` expression is a way of ensuring things behave the way you want them to. Run the script in test mode, and these are evaluated. If the expression under `test` returns a truthy value, you're all good! If the expression under `test` returns a falsy value or raises a panic, then Ludus will report which test(s) failed. + +``` +test "something goes right" eq? (:foo, :foo) + +test "something goes wrong" { + let foo = :foo + let bar = :bar + eq? (foo, bar) +} &=> test failed: "something goes wrong" on line 3 +``` + +`test`s must be at the toplevel--or embedded within other tests in _their_ highest level. + +Formally: `test `. + +Status: not yet implemented. + +## Changing things: `box`es +Ludus does not let you re-bind names. It does, however, have a type that allows for changing values over time: `box`. A box is a place to put things, it has its own identity, it can store whatever you put in it, but to get what's in it, you have to `unbox` it. + +Syntactically and semantically, `box`es are straightforward, but do require a bit more overhead than `let` bindings. The idea is that Ludus makes it obvious where mutable state is in a program, as well as where that mutable state may change. It does so elegantly, but with some guardrails that may take a little getting used to. + +The type of a `box` is, predictably, `:box`. + +``` +box foo = 42 & foo is now bound to a _box that contains 42_ +add (1, foo) & panic! no match: foo is _not_ a number +store! (foo, 23) & foo is now a box containing 23 +update! (foo, inc) & foo is now a ref containing 24 +unbox (foo) &=> 23; use unbox to get the value contained in a box +``` + +### Ending with a bang! +Ludus has a strong naming convention that functions that change state or could cause an explicit panic end in an exclamation point (or, in computer nerd parlance, a "bang"). So anything function that mutates the value held in a reference ends with a bang: `store!` and `update!` take bangs; `unbox` does not. + +This convention also includes anything that prints to the console: `print!`, `report!`, `doc!`, `update!`, `store!`, etc. + +(Note that there are a few counter-examples to this: math functions that could cause a panic [in place of returning NaN] do not end with bangs: `div`, `inv`, and `mod`; each of these has variants that allow for more graceful error handling). + +### Ending with a whimper? +Relatedly, just about any function that returns a boolean value is a predicate function--and has a name that ends with a question mark: `eq?` tests for equality; `box?` tells you if something is a ref or not; `lte?` is less-than-or-equal. + +## Errors: panic! in the Ludus script +A special form, `panic!`, halts script execution with the expression that follows as an error message. + +``` +panic! :oops + +if true then :ok else panic! "It's false!" +``` + +Panics also happen in the following cases: +* a `let` binding pattern has no match against the value of its expression +* a `match` or `when` form has no matching clause +* a function is called with arguments that do not match any of its clauses +* something that is not a function or keyword is called as a function +* a called keyword is partially applied +* `div`, `inv`, or `mod` divides by zero +* `sqrt` takes the square root of a negative number +* certain error handling functions, like `unwrap!` or `assert!`, are invoked on values that cause them to panic + +In fact, the only functions in the Prelude which explicitly cause panics are, at current, `div`, `inv`, `mod`, `sqrt`, `unwrap!`, and `assert!`. + +### `nil`s, not errors +Ludus, however, tries to return `nil` instead of panicking where it seems prudent. So, for example, attempting to get access a value at a keyword off a number or `nil`, while nonsensical, will return `nil` rather than panicking: + +``` +let a = true +a :b :c :d :e &=> nil + +let b = [1, 2, 3] +at (b, 12) &=> nil +``` + +### Result tuples +Operations that could fail--especially when you want some information about why--don't always return `nil` on failures. Instead of exceptions or special error values, recoverable errors in Ludus are handled instead by result tuples: `(:ok, value)` and `(:err, msg)`. So, for example, `unwrap!` takes a result tuple and either returns the value in the `:ok` case, or panics in the `:err` case. + +Variants of some functions that may have undesirably inexplicit behaviour are written as `{name}/safe`. So, for example, you can get a variant of `div` that returns a result tuple in `div/safe`, which returns `(:ok, result)` when everything's good; and `(:err, "division by zero")` when the divisor is 0. diff --git a/doc/prelude.md b/doc/prelude.md new file mode 100644 index 0000000..3e01279 --- /dev/null +++ b/doc/prelude.md @@ -0,0 +1,1574 @@ +# Ludus prelude documentation +These functions are available in every Ludus script. +The documentation for any function can be found within Ludus by passing the function to `doc!`, +e.g., running `doc! (add)` will send the documentation for `add` to the console. + +For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md). + +The prelude itself is just a Ludus file, which you can see at [prelude.ld](./prelude.ld). + +## A few notes +**Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans. +Functions whose name ends with an exclamation point, e.g., `make!`, change state in some way. +In other words, they _do things_ rather than _calculating values_. +Functions whose name includes a slash either convert from one value to another, e.g. `deg/rad`, +or they are variations on a function, e.g. `div/0` as a variation on `div`. + +**How entries are formatted.** Each entry has a brief (sometimes too brief!) description of what it does. +It is followed by the patterns for each of its function clauses. +This should be enough to indicate order of arguments, types, and so on. + +**Patterns often, but do not always, indicate types.** Typed patterns are written as `foo as :bar`, +where the type is indicated by the keyword. +Possible ludus types are: `:nil`, `:boolean`, `:number`, `:keyword` (atomic values); +`:string` (strings are their own beast); `:tuple` and `:list` (ordered collections), `:set`s, and `:dict`ionaries (the other collection types); `:pkg` (packages, which are quasi-collections); `:fn` (functions); and `:box`es. + +**Conventional types.** Ludus has two types based on conventions. +* _Result tuples._ Results are a way of modeling the result of a calculation that might fail. +The two possible values are `(:ok, value)` and `(:err, msg)`. +`msg` is usually a string describing what went wrong. +To work with result tuples, see [`unwrap!`](#unwrap) and [`unwrap_or`](#unwrap_or). +That said, usually you work with these using pattern matching. + +* _Vectors._ Vectors are 2-element tuples of x and y coordinates. +The origin is `(0, 0)`. +Many math functions take vectors as well as numbers, e.g., `add` and `mult`. +You will see vectors indicated in patterns by an `(x, y)` tuple. +You can see what this looks like in the last clause of `add`: `((x1, y1), (x2, y2))`. + +## Functions by topic +### Boolean +[and](#and)    [bool](#bool)    [bool?](#bool)    [false?](#false)    [not](#not)    [or](#or)    [true?](#true) + +### Boxes and state +[box?](#box)    [store!](#store)    [unbox](#unbox)    [update!](#update) + +### Dicts +[any?](#any)    [assoc](#assoc)    [assoc?](#assoc)    [coll?](#coll)    [count](#count)    [dict](#dict)    [dict?](#dict)    [diff](#diff)    [dissoc](#dissoc)    [empty?](#empty)    [get](#get)    [keys](#keys)    [random](#random)    [update](#update)    [values](#values) + +### Environment and i/o +[doc!](#doc)    [print!](#print)    [report!](#report)    [state](#state) + +### Errors +[assert!](#assert) + +### Lists +[any?](#any)    [append](#append)    [at](#at)    [butlast](#butlast)    [coll?](#coll)    [concat](#concat)    [count](#count)    [each!](#each)    [empty?](#empty)    [filter](#filter)    [first](#first)    [fold](#fold)    [join](#join)    [keep](#keep)    [last](#last)    [list](#list)    [list?](#list)    [map](#map)    [ordered?](#ordered)    [random](#random)    [range](#range)    [rest](#rest)    [second](#second)    [sentence](#sentence)    [slice](#slice) + +### Math +[abs](#abs)    [add](#add)    [angle](#angle)    [atan/2](#atan2)    [between?](#between)    [ceil](#ceil)    [cos](#cos)    [dec](#dec)    [deg/rad](#degrad)    [deg/turn](#degturn)    [dist](#dist)    [div](#div)    [div/0](#div0)    [div/safe](#divsafe)    [even?](#even)    [floor](#floor)    [gt?](#gt)    [gte?](#gte)    [heading/vector](#headingvector)    [inc](#inc)    [inv](#inv)    [inv/0](#inv0)    [inv/safe](#invsafe)    [lt?](#lt)    [lte?](#lte)    [max](#max)    [min](#min)    [mod](#mod)    [mod/0](#mod0)    [mod/safe](#modsafe)    [mult](#mult)    [neg](#neg)    [neg?](#neg)    [odd?](#odd)    [pi](#pi)    [pos?](#pos)    [rad/deg](#raddeg)    [rad/turn](#radturn)    [random](#random)    [random\_int](#random\_int)    [range](#range)    [round](#round)    [sin](#sin)    [sqrt](#sqrt)    [sqrt/safe](#sqrtsafe)    [square](#square)    [sub](#sub)    [sum\_of_squares](#sum\_of_squares)    [tan](#tan)    [tau](#tau)    [to\_number](#to\_number)    [turn/deg](#turndeg)    [turn/rad](#turnrad)    [zero?](#zero) + +### Results +[err](#err)    [err?](#err)    [ok](#ok)    [ok?](#ok)    [unwrap!](#unwrap)    [unwrap\_or](#unwrap\_or) + +### Sets +[any?](#any)    [append](#append)    [coll?](#coll)    [concat](#concat)    [contains?](#contains)    [count](#count)    [empty?](#empty)    [omit](#omit)    [random](#random)    [set](#set)    [set?](#set) + +### Strings +[any?](#any)    [chars](#chars)    [chars/safe](#charssafe)    [concat](#concat)    [count](#count)    [downcase](#downcase)    [empty?](#empty)    [join](#join)    [sentence](#sentence)    [show](#show)    [slice](#slice)    [split](#split)    [string](#string)    [string?](#string)    [strip](#strip)    [to\_number](#to\_number)    [trim](#trim)    [upcase](#upcase)    [words](#words) + +### Tuples +[any?](#any)    [at](#at)    [coll?](#coll)    [count](#count)    [empty?](#empty)    [first](#first)    [last](#last)    [ordered?](#ordered)    [rest](#rest)    [second](#second)    [tuple?](#tuple) + +### Turtle graphics +[back!](#back)    [background!](#background)    [bk!](#bk)    [clear!](#clear)    [colors](#colors)    [fd!](#fd)    [forward!](#forward)    [goto!](#goto)    [heading](#heading)    [heading/vector](#headingvector)    [hideturtle!](#hideturtle)    [home!](#home)    [left!](#left)    [loadstate!](#loadstate)    [lt!](#lt)    [pc!](#pc)    [pd!](#pd)    [pencolor](#pencolor)    [pencolor!](#pencolor)    [pendown!](#pendown)    [pendown?](#pendown)    [penup!](#penup)    [penwidth](#penwidth)    [penwidth!](#penwidth)    [position](#position)    [pu!](#pu)    [pw!](#pw)    [render\_turtle!](#render\_turtle)    [reset\_turtle!](#reset\_turtle)    [right!](#right)    [rt!](#rt)    [setheading!](#setheading)    [showturtle!](#showturtle)    [turtle\_state](#turtle\_state) + +### Types and values +[assoc?](#assoc)    [bool?](#bool)    [box?](#box)    [coll?](#coll)    [dict?](#dict)    [eq?](#eq)    [fn?](#fn)    [keyword?](#keyword)    [list?](#list)    [neq?](#neq)    [nil?](#nil)    [number?](#number)    [ordered?](#ordered)    [set?](#set)    [show](#show)    [some](#some)    [some?](#some)    [string?](#string)    [tuple?](#tuple)    [type](#type) + +## All functions, alphabetically +[abs](#abs)    [add](#add)    [and](#and)    [angle](#angle)    [any?](#any)    [append](#append)    [assert!](#assert)    [assoc](#assoc)    [assoc?](#assoc)    [at](#at)    [atan/2](#atan2)    [back!](#back)    [background!](#background)    [between?](#between)    [bg!](#bg)    [bk!](#bk)    [bool](#bool)    [bool?](#bool)    [box?](#box)    [butlast](#butlast)    [ceil](#ceil)    [chars](#chars)    [clear!](#clear)    [coll?](#coll)    [colors](#colors)    [concat](#concat)    [contains?](#contains)    [cos](#cos)    [count](#count)    [dec](#dec)    [deg/rad](#degrad)    [deg/turn](#degturn)    [dict](#dict)    [dict?](#dict)    [diff](#diff)    [dissoc](#dissoc)    [dist](#dist)    [div](#div)    [div/0](#div0)    [div/safe](#divsafe)    [doc!](#doc)    [downcase](#downcase)    [each!](#each)    [empty?](#empty)    [eq?](#eq)    [err](#err)    [err?](#err)    [even?](#even)    [false?](#false)    [fd!](#fd)    [filter](#filter)    [first](#first)    [floor](#floor)    [fn?](#fn)    [fold](#fold)    [forward!](#forward)    [get](#get)    [goto!](#goto)    [gt?](#gt)    [gte?](#gte)    [heading](#heading)    [heading/vector](#headingvector)    [hideturtle!](#hideturtle)    [home!](#home)    [inc](#inc)    [inv](#inv)    [inv/0](#inv0)    [inv/safe](#invsafe)    [join](#join)    [keep](#keep)    [keys](#keys)    [keyword?](#keyword)    [last](#last)    [left!](#left)    [list](#list)    [list?](#list)    [loadstate!](#loadstate)    [lt!](#lt)    [lt?](#lt)    [lte?](#lte)    [map](#map)    [max](#max)    [min](#min)    [mod](#mod)    [mod/0](#mod0)    [mod/safe](#modsafe)    [mult](#mult)    [neg](#neg)    [neg?](#neg)    [neq?](#neq)    [nil?](#nil)    [not](#not)    [odd?](#odd)    [ok](#ok)    [ok?](#ok)    [omit](#omit)    [or](#or)    [ordered?](#ordered)    [pc!](#pc)    [pd!](#pd)    [pencolor](#pencolor)    [pencolor!](#pencolor)    [pendown!](#pendown)    [pendown?](#pendown)    [penup!](#penup)    [penwidth](#penwidth)    [penwidth!](#penwidth)    [pi](#pi)    [pos?](#pos)    [position](#position)    [print!](#print)    [pu!](#pu)    [pw!](#pw)    [rad/deg](#raddeg)    [rad/turn](#radturn)    [random](#random)    [random\_int](#random\_int)    [range](#range)    [report!](#report)    [rest](#rest)    [right!](#right)    [round](#round)    [rt!](#rt)    [second](#second)    [sentence](#sentence)    [set](#set)    [set?](#set)    [setheading!](#setheading)    [show](#show)    [showturtle!](#showturtle)    [sin](#sin)    [slice](#slice)    [some](#some)    [some?](#some)    [split](#split)    [sqrt](#sqrt)    [sqrt/safe](#sqrtsafe)    [square](#square)    [state](#state)    [store!](#store)    [string](#string)    [string?](#string)    [strip](#strip)    [sub](#sub)    [sum\_of_squares](#sum\_of_squares)    [tan](#tan)    [tau](#tau)    [to\_number](#to\_number)    [trim](#trim)    [tuple?](#tuple)    [turn/deg](#turndeg)    [turn/rad](#turnrad)    [turtle\_commands](#turtle\_commands)    [turtle\_init](#turtle\_init)    [turtle\_state](#turtle\_state)    [type](#type)    [unbox](#unbox)    [unwrap!](#unwrap)    [unwrap\_or](#unwrap\_or)    [upcase](#upcase)    [update](#update)    [update!](#update)    [values](#values)    [words](#words)    [ws?](#ws)    [zero?](#zero) +## Function documentation + +### abs +Returns the absolute value of a number. +``` +(0) +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### add +Adds numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +((x1, y1), (x2, y2)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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. +``` +() +(x) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### angle +Calculates the angle between two vectors. +``` +(v1, v2) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### any? +Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers). +``` +([...]) +(#{...}) +(s as :set) +((...)) +(s as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### append +Adds an element to a list or set. +``` +() +(xs as :list) +(xs as :list, x) +(xs as :set) +(xs as :set, x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### assert! +Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message. +``` +(value) +(msg, value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### assoc +Takes a dict, key, and value, and returns a new dict with the key set to value. +``` +() +(d as :dict) +(d as :dict, k as :keyword, val) +(d as :dict, (k as :keyword, val)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### assoc? +Returns true if a value is an associative collection: a dict or a pkg. +``` +(d as :dict) +(p as :pkg) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(xs as :tuple, n as :number) +(str as :string, n as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(x, y, :turns) +(x, y, :radians) +(x, y, :degrees) +((x, y)) +((x, y), units as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### back! +Moves the turtle backward by a number of steps. Alias: bk! +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### background! +Sets the background color behind the turtle and path. Alias: bg! +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bg! +Sets the background color behind the turtle and path. Alias: bg! +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bk! +Moves the turtle backward by a number of steps. Alias: bk! +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bool +Returns false if a value is nil or false, otherwise returns true. +``` +(nil) +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bool? +Returns true if a value is of type :boolean. +``` +(false) +(true) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### box? +Returns true if a value is a box. +``` +(b as :box) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### butlast +Returns a list, omitting the last element. +``` +(xs as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### clear! +Clears the canvas and sends the turtle home. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### coll? +Returns true if a value is a collection: dict, list, tuple, or set. +``` +(coll as :dict) +(coll as :list) +(coll as :tuple) +(coll as :set) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### colors +No documentation available. + +--- +### concat +Combines two lists, strings, or sets. +``` +(x as :string, y as :string) +(xs as :list, ys as :list) +(xs as :set, ys as :set) +(xs, ys, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### contains? +Returns true if a set or list contains a value. +``` +(value, s as :set) +(value, l as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### count +Returns the number of elements in a collection (including string). +``` +(xs as :list) +(xs as :tuple) +(xs as :dict) +(xs as :string) +(xs as :set) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dec +Decrements a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### deg/rad +Converts an angle in degrees to an angle in radians. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### deg/turn +Converts an angle in degrees to an angle in turns. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dict +Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed. +``` +(d as :dict) +(l as :list) +(t as :tuple) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dict? +Returns true if a value is a dict. +``` +(d as :dict) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dissoc +Takes a dict and a key, and returns a new dict with the key and associated value omitted. +``` +(d as :dict) +(d as :dict, k as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +((x, y)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div +Divides numbers. Panics on division by zero. +``` +(x as :number) +(_, 0) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div/0 +Divides numbers. Returns 0 on division by zero. +``` +(x as :number) +(_, 0) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div/safe +Divides a number. Returns a result tuple. +``` +(x as :number) +(_, 0) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### doc! +Prints the documentation of a function to the console. +``` +(f as :fn) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### downcase +Takes a string and returns it in all lowercase. Works only for ascii characters. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### each! +Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil. +``` +(f! as :fn, []) +(f! as :fn, [x]) +(f! as :fn, [x, ...xs]) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### empty? +Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers). +``` +([]) +(#{}) +(s as :set) +(()) +("") +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### eq? +Returns true if all arguments have the same value. +``` +(x) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### err +Takes a value and wraps it in an :err result tuple, presumably as an error message. +``` +(msg) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### err? +Takes a value and returns true if it is an :err result tuple. +``` +((:err, _)) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### even? +Returns true if a value is an even number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### false? +Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`. +``` +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fd! +Moves the turtle forward by a number of steps. Alias: fd! +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### first +Returns the first element of a list or tuple. +``` +(xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fn? +Returns true if an argument is a function. +``` +(f as :fn) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fold +Folds a list. +``` +(f as :fn, []) +(f as :fn, xs as :list) +(f as :fn, [], root) +(f as :fn, xs as :list, root) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### forward! +Moves the turtle forward by a number of steps. Alias: fd! +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(k as :keyword, d as :dict) +(k as :keyword, d as :dict, default) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +((x, y)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### gt? +Returns true if numbers are in decreasing order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### gte? +Returns true if numbers are in decreasing or flat order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### heading +Returns the turtle's current heading. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### heading/vector +Takes a turtle heading, and returns a unit vector of that heading. +``` +(heading) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### hideturtle! +If the turtle is visible, hides it. If the turtle is already hidden, does nothing. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inc +Increments a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inv +Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inv/0 +Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inv/safe +Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### join +Takes a list of strings, and joins them into a single string, interposing an optional separator. +``` +([]) +([str as :string]) +(strs as :list) +([], separator as :string) +([str as :string], separator as :string) +([str, ...strs], separator as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keep +Takes a list and returns a new list with any `nil` values omitted. +``` +(xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keys +Takes a dict and returns a list of keys in that dict. +``` +(d as :dict) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keyword? +Returns true if a value is a keyword, otherwise returns false. +``` +(kw as :keyword) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### last +Returns the last element of a list or tuple. +``` +(xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### left! +Rotates the turtle left, measured in turns. Alias: lt! +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### list? +Returns true if the value is a list. +``` +(l as :list) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### loadstate! +Sets the turtle state to a previously saved state. +``` +(state) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lt! +Rotates the turtle left, measured in turns. Alias: lt! +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lt? +Returns true if numbers are in increasing order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lte? +Returns true if numbers are in increasing or flat order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(kw as :keyword, xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### max +Returns the number in its arguments that is closest to positive infinity. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### min +Returns the number in its arguments that is closest to negative infinity. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mod +Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0. +``` +(x as :number, 0) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mod/0 +Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0. +``` +(x as :number, 0) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mult +Multiplies numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +(scalar as :number, (x, y)) +((x, y), scalar as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### neg +Multiplies a number by -1, negating it. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### neg? +Returns true if a value is a negative number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### neq? +Returns true if none of the arguments have the same value. +``` +(x) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### nil? +Returns true if a value is nil. +``` +(nil) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### not +Returns false if a value is truthy, true if a value is falsy. +``` +(nil) +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### odd? +Returns true if a value is an odd number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ok +Takes a value and wraps it in an :ok result tuple. +``` +(value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ok? +Takes a value and returns true if it is an :ok result tuple. +``` +((:ok, _)) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### omit +Returns a new set with the value omitted. +``` +(value, s as :set) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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. +``` +() +(x) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ordered? +Returns true if a value is an indexed collection: list or tuple. +``` +(coll as :list) +(coll as :tuple) +(coll as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pc! +Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc! +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pd! +Lowers the turtle's pen, causing it to draw. Alias: pd! +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pencolor +Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pencolor! +Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc! +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pendown! +Lowers the turtle's pen, causing it to draw. Alias: pd! +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pendown? +Returns the turtle's pen state: true if the pen is down. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penup! +Lifts the turtle's pen, stopping it from drawing. Alias: pu! +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penwidth +Returns the turtle's pen width in pixels. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penwidth! +Sets the width of the turtle's pen, measured in pixels. Alias: pw! +``` +(width as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pi +No documentation available. + +--- +### pos? +Returns true if a value is a positive number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### position +Returns the turtle's current position. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### print! +Sends a text representation of Ludus values to the console. +``` +(...args) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pu! +Lifts the turtle's pen, stopping it from drawing. Alias: pu! +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pw! +Sets the width of the turtle's pen, measured in pixels. Alias: pw! +``` +(width as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rad/deg +Converts an angle in radians to an angle in degrees. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rad/turn +Converts an angle in radians to an angle in turns. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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. +``` +() +(n as :number) +(m as :number, n as :number) +(l as :list) +(t as :tuple) +(d as :dict) +(s as :set) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(m as :number, n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(start as :number, end as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### report! +Prints a value, then returns it. +``` +(x) +(msg as :string, x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rest +Returns all but the first element of a list or tuple, as a list. +``` +([]) +(()) +(xs as :list) +(xs as :tuple) +(xs as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### right! +Rotates the turtle right, measured in turns. Alias: rt! +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### round +Rounds a number to the nearest integer. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rt! +Rotates the turtle right, measured in turns. Alias: rt! +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### second +Returns the second element of a list or tuple. +``` +(xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sentence +Takes a list of words and turns it into a sentence. +``` +(strs as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### set +Takes an ordered collection--list or tuple--and turns it into a set. Returns sets unharmed. +``` +(xs as :list) +(xs as :tuple) +(xs as :set) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### set? +Returns true if a value is a set. +``` +(xs as :set) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### show +Returns a text representation of a Ludus value as a string. +``` +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### showturtle! +If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### slice +Returns a slice of a list or a string, representing a sub-list or sub-string. +``` +(xs as :list, end as :number) +(xs as :list, start as :number, end as :number) +(str as :string, end as :number) +(str as :string, start as :number, end as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(value, _) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### some? +Returns true if a value is not nil. +``` +(nil) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### split +Takes a string, and turns it into a list of strings, breaking on the separator. +``` +(str as :string, break as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sqrt +Returns the square root of a number. Panics if the number is negative. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sqrt/safe +Returns a result containing the square root of a number, or an error if the number is negative. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### square +Squares a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### state +No documentation available. + +--- +### store! +Stores a value in a box, replacing the value that was previously there. Returns the value. +``` +(b as :box, value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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, ...xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### string? +Returns true if a value is a string. +``` +(x as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### strip +Removes punctuation from a string, removing all instances of ,.;:?! +``` +("{x},{y}") +("{x}.{y}") +("{x};{y}") +("{x}:{y}") +("{x}?{y}") +("{x}!{y}") +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sub +Subtracts numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +((x1, y1), (x2, y2)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sum_of_squares +Returns the sum of squares of numbers. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### tau +No documentation available. + +--- +### 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) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### trim +Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right. +``` +(str as :string) +(str as :string, :left) +(str as :string, :right) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### tuple? +Returns true if a value is a tuple. +``` +(tuple as :tuple) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turn/deg +Converts an angle in turns to an angle in degrees. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turn/rad +Converts an angle in turns to an angle in radians. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turtle_commands +No documentation available. + +--- +### turtle_init +No documentation available. + +--- +### turtle_state +No documentation available. + +--- +### type +Returns a keyword representing the type of the value passed in. +``` +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### unbox +Returns the value that is stored in a box. +``` +(b as :box) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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)) +((:err, msg)) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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), _) +((:err, _), default) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### upcase +Takes a string and returns it in all uppercase. Works only for ascii characters. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### 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 as :dict, k as :keyword, updater as :fn) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### update! +Updates a box by applying a function to its value. Returns the new value. +``` +(b as :box, f as :fn) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### values +Takes a dict and returns a list of values in that dict. +``` +(d as :dict) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### words +Takes a string and returns a list of the words in the string. Strips all whitespace. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ws? +Tells if a string is a whitespace character. +``` +(" ") +("\n") +("\t") +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### zero? +Returns true if a number is 0. +``` +(0) +(_) +``` +[Back to top.](#ludus-prelude-documentation) diff --git a/doc/turtle-graphics.md b/doc/turtle-graphics.md new file mode 100644 index 0000000..90e801b --- /dev/null +++ b/doc/turtle-graphics.md @@ -0,0 +1,92 @@ +# Turtle Graphics protocol + +name: "turtle-graphics" + +version: 0.1.0 + +### Description +Turtle graphics describe the movements and drawing behaviours of screen, robot, and print "turtles." +* `proto`: `["turtle-graphics", "{version number}"]` +* `data`: an array of arrays; each array represents a turtle command; the first element of a command array is the verb; any subsequent items are the arguments to the verbs. +* Valid arguments are numbers, strings, and booleans. +* Depending on what we end up doing, we may add arrays of these, representing tuples or lists, and/or objects with string keys whose text are well-formed keywords in Ludus. For now, however, arguments must be atomic values. +* E.g., `["forward", 100]` +* Each turtle has its own stream. +* At current, this protocol describes the behaviour of turtle-like objects, all of which "live" in the same "world"; there is not yet a provision for multiple canvases/worlds. That said, an additional field for "world" in at the top level may well be added in the future to allow for multiple worlds to unfold at the same time. + +### Verbs and arguments +* `forward`, steps: number + - Moves the turtle forward by the number of steps/pixels. +* `back`, steps: number + - Moves the turtle backwards by the number of steps/pixels. +* `right`, turns: number + - Turns the turtle right by the number of turns. (1 turn = 360 degrees.) +* `left`, turns: number + - Turns the turtle to the left by the number of turns. (1 turn = 360 degrees.) +* `penup`, no arguments + - "Lifts" the turtle's pen, keeping it from drawing. +* `pendown`, no arguments + - "Lowers" the turtle's pen, starting it drawing a path. +* `pencolor`, red: number, green: number, blue: number, alpha: number, OR: color: string + - Sets the turtle's pen's color to the specified RGBA color. +* `penwidth`, width: number + - Sets the width of the turtle's pen, in pixels (or some other metric). +* `home`, no arguments + - Sends the turtle back to its starting point, with a heading of 0. +* `goto`, x: number, y: number + - Sends the turtle to the specified Cartesian coordinates, where the origin is the turtle's starting position. +* `setheading`, heading: number + - Sets the turtle's heading. 0 is the turtle's starting heading, with increasing numbers turning to the right. +* `show`, no arguments + - Shows the turtle. +* `hide`, no arguments + - Hides the turtle. +* `loadstate`, x: number, y: number, heading: number, visible: boolean, pendown: boolean, width: number, color: string OR r: number, g: number, b: number, a: number + - Loads a turtle state. +* `clear`, no arguments + - Erases any paths drawn and sets the background color to the default. +* `background`, red: number, green: number, blue: number, alpha: number + - Sets the background color to the specified RGBA color, OR: color: string + +These last two feel a little weird to me, since the background color is more the property of the **world** the turtle is in, not the turtle itself. Worlds with multiple turtles will be set up so that _any_ turtle will be able to change the background, and erase all paths. + +That said, since we don't yet have a world abstraction/entity, then there's no other place to put them. This will likely be shifted around in later versions of the protocol. + +### Other considerations +**Not all turtles will know how to do all these things.** +The idea is that this single abstraction will talk to all the turtle-like things we eventually use. +That means that some turtles won't be able to do all the things; that's fine! +They just won't do things they can't do; but warnings should go to `stderr`. + +**Errors are not passed back to Ludus.** +These are fire-off commands. +Errors should be _reported_ to `stderr` or equivalent. +But Ludus sending things to its output streams should only cause Ludus panics when there's an issue in Ludus. + +**Colors aren't always RGBA.** +For pen-and-paper turtles, we don't have RGBA colors. +Colors should also be specifiable with strings corresponding to CSS basic colors: black, silver, gray, white, maroon, red, purple, fuchsia, green, lime, olive, yellow, navy, blue, teal, and aqua. + +**Turtles should communicate states.** +Ludus should have access to turtle states. +This is important for push/pop situations that we use for L-systems. +There are two ways to do this: Ludus does its own bookkeeping for turtle states, or it has a way to get the state from a turtle. + +The latter has the value of being instantaneous, and gives us an _expected_ state of the turtle after the commands are all processed. +In particular, this will be necessary for the recursive L-systems that require pushing and popping turtle state. +The latter has the drawback of potentially allowing the turtle state and expected turtle state to fall out of synch. + +The former has the value of always giving us the correct, actual state of the turtle. +It has the drawback of requiring such state reporting to be asynchronous, and perhaps wildly asynchronous, as things like moving robots and plotters will take quite some time to actually draw what Ludus tells it to. +(Being able to wait until `eq? (expected, actual)` to do anything else may well be extremely useful.) + +That suggests, then, that both forms of turtle state are desirable and necessary. +Thus: turtles should communicate states (and thus there ought to be a protocol for communicating state back to Ludus) and Ludus should always do the bookkeeping of calculating the expected state. + +**Turtles use Cartesian, rather than screen, coordinates.** +The starting position of the turtle is `(0, 0)`, which is the origin, and _centred_ in the field of view. +Increasing the x-coordinate moves the turtle to the right; increasing the y-coordinate moves the turtle _up_. + +**Turtles use compass headings, not mathematical angles.** +Turtles start pointing vertially, at heading `0`. +Turning right _increases_ the heading; pointing due "east" is `0.25`; south `0.5`, and west, `0.75`.