Module:User:Pi zero/Wikilisp/doc
This module is used on over 9000 pages. Changes to this module may cause some server load, and mistakes will be visible on many pages. Please carefully test any edits before making them, and avoid making unnecessary edits. |
This module supports simple but flexible and fairly powerful operations on strings and numbers. It is meant to bring the supported operations within reach of ordinary wiki contributors, by using expressions embedded in wiki markup, and by minimizing the syntactic red tape involved.
This is a test version of the module; the release version is Module:Wikilisp.
Call the module like this:
- {{evalx|sequence|test-eval=User:Pi zero/Wikilisp|...}}
or
- {{#invoke:User:Pi zero/Wikilisp|rep|sequence|...}}
where sequence is a series of s-expressions, and there may be additional arguments thereafter (the "|..."). The s-expressions are evaluated, one at a time from left to right, and the result of evaluating the last of them is returned as the expansion of the module-call.
Although this module can be useful for tricky small-scale tasks, it can also do hefty transformations of the entire content of a wiki page, because the entire content of a wiki page is a string.
- Current version:
X0.19 (November 4, 2019)
S-expressions
[edit]The main kinds of values are numbers, strings (that is, unicode text), booleans (true and false), symbols (names for things), and lists.
Numbers, strings, booleans
[edit]Numbers and strings are kept separate form each other: 6
is different from "6"
. Evaluating a number or string results in that number or string, unchanged. The result of the module-call, if a simple value (rather than a list, as discussed below), is usually a number or string, unless the module call has an error in it, or is being debugged.
A numeric literal is a series of digits, with an optional decimal point (.), optional sign at the front (+ or -), and optional E notation.
A string literal may be delimited either by double-quotes (") or single-quotes ('), and may contain any characters except the delimiter. If a string needs to involve both single- and double-quotes, the easiest approach is to make the string an additional parameter to the module call; though there is also fully general advanced string syntax.
The boolean values, true and false, usually result from some logical test and are fed as operands into some other operation; so they are usually neither written explicitly into an input expression, nor written as part of an output expression. The boolean values are represented as true
and false
.
Symbols
[edit]Any input-text sequence that doesn't represent a string literal or numeric literal, and doesn't contain any whitespace or parentheses or backslash or semicolon, names a symbol. Also, any backslash (\
) that isn't inside a string literal names a symbol (the backslash symbol).
A symbol is evaluated by looking it up in the environment. For most (though not all) purposes, there's just one, global environment, defining the standard functions provided for use in expressions. There are advanced situations where you might alter the global environment, or even do some things in a local environment; but this module is really meant to provide powerful, flexible standard functions so you can almost always do the things you want to do without resorting to complicated techniques like that. When you do end up doing such things, it's probably time to think about what even better tools would make them even more rarely needed.
Lists
[edit]A list is a sequence of values. It is represented by a set of parentheses, with the values between them, separated by whitespace. A set of parentheses with nothing (but perhaps whitespace) between them represents the empty list.
When evaluating a non-empty list (the empty list evaluates to itself), the first element of the list is the operator and any additional elements are operands. The operator is evaluated first, and what happens thereafter depends on what the operator evaluated to. It must evaluate to a function (if not, that's an error). A few special functions act on their operands directly, without evaluating the operands first; for all other functions, the operands are evaluated, and the results of those evaluations are passed to the function.
Comments
[edit]A semicolon in an input expression, not inside a string literal, marks a comment: the interpreter ignores all characters from the semicolon to the end of the line it is on.
Functions
[edit]The interpreter is meant to be a simple device for filling in gaps in wiki-markup-based functionality; it is not meant to replace other wiki-markup facilities, and especially not to provide all functionality for template internals. It should not be capable of arbitrary (Turing-powerful) computation. It should provide a small, highly versatile set of functions affording succinct expression of valuable functionality not otherwise well-supported by wiki-markup. These constraints give its choice of supported functions a somewhat different character from those of a general-purpose programming language.
Background stuff
[edit]These functions do mundane tasks, filling in the gaps around the powerful tools that do the heavy lifting.
- Function
list
returns a list of its operands.(list (+ 1 1) (- 3 2))
would evaluate to (2 1),(list)
to ().
- The basic arithmetic functions are
+
-
*
/
^
. Subtraction and division require at least two operands; the first operand is acted on by all of the others, so(- 7 1 2)
would evaluate to 4, and(/ 12 2 3)
would evaluate to 2. Exponentiation requires exactly two operands;(^ 9 0.5)
would evaluate to 3.
- Function
+
also concatenates strings or lists, and combines booleans by logical conjunction.(+ "a" "bc" "d")
would evaluate to "abcd",(+ (list 1) () (list 2 3))
to (1 2 3),(+ true true false)
to false. This only works if the function is given at least one argument, so it knows what type to return; with no arguments, it just assumes it's doing numeric addition:(+)
evaluates to 0.
- Simple arithmetic functions
abs
ceil
floor
each take a single number operand, and return respectively its absolute value, the smallest integer not less than it (its ceiling), and the greatest integer not greater than it (its floor). Thus,(abs -2.3)
would evaluate to 2.3,(ceil -2.3)
to -2,(floor -2.3)
to -3; while(abs 4)
,(ceil 4)
, and(floor 4)
would each evaluate to 4.
- The numeric and string comparison functions are:
lt?
(less than),gt?
(greater than),le?
(less than or equal),ge?
(greater than or equal). Each function takes zero or more operands, and checks that every pair of consecutive operands have the named relation. Thus,(le? 2 2 3)
would evaluate to true,(gt? 3 2 2)
would be false because 2 is not greater than 2,(lt? "def" "abc")
would be false because "def" is not alphabetically before "abc".
- A general comparison function
equal?
determines whether all of its operands are (superficially) the same. Technically, it determines whether all of its operands would appear the same if they were output. There are some weird situations in which values that aren't really the same might "look" equal, but as long as you stick to numbers, strings, booleans, and lists, such situations won't happen.
- Each of functions
number?
string?
boolean?
list?
checks that all of its operands have the named type. So,(number? (+ 2 3))
would evaluate to true, as would(number?)
, while(string? ())
would evaluate to false. There are a few other types of values, and they have functions to check for them too, but ordinarily you shouldn't need to check for them (they're "advanced" features).
- Functions
to-number
andto-string
take one operand and convert it either way between a number and a string representation of a number. If the operand ofto-number
does not represent a number, it returns the empty list.
- Function
nth
takes two or more operands; first a list, then an integer or integers. With one integer, it returns the nth element of the list.(nth (list 5 7 11) 2)
would evaluate to 7. With multiple integers n, m, etc., it takes takes the nth element of the list, then expects that to be a list and takes the mth element of that, and so on.
- Function
not?
takes a single boolean operand, and returns true if the operand is false, false if the operand is true. The corresponding toolsand?
andor?
are special functions (below).
- Function
length
takes a single operand, which can be either a string or a list, and returns its length, as an integer: for a string, this is the number of unicode codepoints, for a list, the number of items on the list.(length ())
would evaluate to 0.
- Functions
trim
,lc
,lcfirst
,uc
,ucfirst
,to-entity
each take a single operand, either a string or a list of strings. Given a string,trim
returns the string with leading and trailing whitespace removed;lc
with all letters converted to lower-case,uc
to upper-case;lcfirst
with the first character converted to lower-case,ucfirst
to upper case;to-entity
converts the first character of the string to a numeric html entity reference, or if the string is empty returns the string. Given a list of strings, each function applies its operation to each string on the list, and returns a list of the results.(uc (list "abc" "def"))
would evaluate to ("ABC" "DEF").(to-entity "ABC")
would evaluate to "A" (which would appear as "A");(to-entity "")
would evaluate to "".
- Function
write
takes a single operand, and produces its output string representation. This is how the operand would appear if it were part of a larger result of computation, such as a list. If the operand isn't a string, it appears the same way if it is the entire result of computation as if it is embedded in some larger result; however, a string result of computation is output directly, rather than formatted with delimiters. If a value (barring oddball things like functions and patterns) is meant to be output from one evaluation and input to another, and may be a string,write
gives it the proper output format. For example,"foo""bar"
represents a string of length seven with one double-quote character in it, while(write "foo""bar")
would evaluate to a string of length ten with four double-quote characters in it; so{{evalx|"foo""bar"}}
would expand tofoo"bar
, while{{evalx|(write "foo""bar")}}
would expand to"foo""bar"
.
- Functions
urlencode
,anchorencode
,fullurl
, andcanonicalurl
provide substantially the magic words of the same names, per mw:Help:Magic words. Each takes one or, in some cases, two string operands. Some examples:(urlencode "fo'o bar")
would evaluate to "fo%27o+bar",(urlencode "fo'o bar" "path")
to "fo%27o%20bar",(urlencode "fo'o bar" "wiki")
to "fo%27o_bar";(anchorencode "fo'o bar")
to "fo'o_bar";(fullurl "foo bar")
to "//en.wikinews.org/wiki/Foo_bar";(canonicalurl "foo bar" "quux")
to "https://en.wikinews.org/w/index.php?title=Foo_bar&quux".
- Function
pattern
takes a string, taken to be a pattern (in the Scribuntu sense), and returns a pattern object, a separate data type usable in some string-search functions.
- Function
split
takes two strings, and splits the first string into a list of its substrings separated by the second string.(split "abba" "b")
would evaluate to ("a" "" "a"). Alternatively, the second string may be a pattern rather than a string;(split "foobar" (pattern "[ao]"))
would evaluate to ("f" "" "b" "r").split
also has more general forms described in the next section.
- Function
join
takes a list of strings and a string, and concatenates the strings from the list separated by the latter string.(join (list "a" "b") ",")
would evaluate to "a,b".join
also has more general forms described in the next section.
- Functions
get-substring
andget-sublist
take a string or list, and one or two integers, and return the substring/sublist starting at the element with the first index (counting from 1), and continuing through the element with the second index if any.(get-substring "abc" 2 2)
would evaluate to "b".(get-sublist (list 1 2 3) 2)
would evaluate to (2 3).get-substring
also has more general forms described in the next section.
- Functions
set-substring
andset-sublist
take four operands: a base string/list, two integers describing a segment of the base to be replaced (start/end indices, counting from 1), and a string/list to splice into that segment. A new string/list is returned with the indicated splice.(set-substring "foobar" 3 5 "z")
would evaluate to "fozr". The second index is included in the segment; to splice between two characters of the base, the second index should be one less than the first:(set-substring "ab" 2 1 "123")
would evaluate to "a123b".set-substring
also has more general forms described in the next section.
- Function
find
takes two operands, the first a target string or list in which to search, and returns a list of where matches occur in the target. With a list, the second operand is a function, which is applied to each element of the list and must return a boolean; each matching position is an index into the list (counting from 1). With a string, the second operand is either a string or pattern, and each matching position is a list of start/end indices (counting from 1).(find (list 2 "b" 2) number?)
would evaluate to (1 3).(find "foobar" "o"))
would evaluate to ((2 2) (3 3)).
- Function
member?
usually takes two operands, the second of which is a list, and returns true if any member of the list is equal to the first operand (per functionequal?
, above), otherwise returns false. If given just one operand,member?
returns a function that takes a list as operand and returns true or false depending on whether any element of the list is equal to the operand passed tomember?
.(member? 2 (list 1 2 3))
would evaluate to true, as would((member? 2) (list 1 2 3))
.
- Function
apply
takes a function and a list, and calls the function with the operands on the list.(apply + (list 1 2 3))
would evaluate to 6.
- Function
curry
takes a function and one or more additional operands, and returns a function that takes zero or more operands, and calls the earlier operand function with all the operands together, both the earlier ones and the later ones.((curry + 1 2 3) 4 5 6)
would evaluate to 21.((curry + 1 2 3))
would evaluate to 6.
Special functions, whose operands are not automatically evaluated:
- Special function
and?
can be used in two different ways. Given operands that evaluate to booleans, it returns true if all the operands evaluate to true, false if one of them evaluates to false; if one of them evaluates to false, it doesn't evaluate later operands. Given operands that evaluate to functions, it evaluates them all and returns a function that passes all its operands to each of those functions, expecting each of them to return a boolean; again, it returns true if they all return true, or stops and returns false if one of them returns false.((and? number? le?) 2 5 11)
would evaluate to true, because(number? 2 5 11)
and(le? 2 5 11)
evaluate to true.((and? number? le?) "foo")
would evaluate to false, because(number? "foo")
evaluates to false.
- Special function
or?
is likeand?
, with change of form: if all are false, returns false; if any is true, stops and returns true.((or? string? ge?) 2 5 11)
would evaluate to false, because(string? 2 5 11)
and(ge? 2 5 11)
evaluate to false.((or? string? le?) "foo")
would evaluate to true, because(string? "foo")
evaluates to true.
- Special function
if
takes three operands; as a special function, its operands are not automatically evaluated. It evaluates its first operand, the result of which must be boolean, and then evaluates the second or third operand depending on whether the result from the first was true or false, and returns the result of the latter evaluation. So(if (ge? 3 9) 3 9)
and(if (ge? 9 3) 9 3)
would both evaluate to 9.
- Special function
\
creates a function. It ordinarily takes two operands; the first is a symbol, which is not evaluated and is the name of the parameter to the new function; the second is the body of the new function. When the function is called, its operand is evaluated, and the parameter is locally bound to the result of this evaluation; then the body of the function is evaluated locally, with the parameter bound to the function argument, and the result of this evaluation of the body is the result of the function call. For example,((\x (* x x)) (+ 2 3))
would evaluate to 25.
- Special function
let
creates a temporary name for something. It takes at least one (usually two or more) operands; the first operand is a list of a symbol and an expression. The expression is evaluated, and the result becomes the temporary meaning of the symbol; the remaining operands are evaluated, from left to right, in the local environment so constructed, and the result of the last evaluation is returned (or if there was only the one operand, the empty list is returned). For example,(let (x 3) (* x x))
would evaluate to 9, while(let (x 2) (let (y 3) (* x y)))
would evaluate to 6.
- Special function
define
modifies the current environment (whereaslet
creates a new environment for temporary use). It takes two operands; the first is a symbol. It evaluates its second operand, and then binds its first operand to the result in the environment. For example, evaluating(define x (+ 3 4))
would modify the environment so thatx
would evaluate to 7; thus,{{evalx|(define x (+ 3 4)) (* x x)}}
would expand to 49.
- Special function
sequence
evaluates its operands, in order from left to right, and returns the result of the last evaluation. Given no operands, it returns the empty list. Handy for conditionally doing a series of things for effect, such as in(if (gt? x 10) (sequence (define y (+ y 1)) (define x (- x 10)) true) false)
which would return true or false and might also change the local values of x and y. The same thing could be accomplished using functionslist
andnth
, or justlist
if you're just going to throw out the result anyway; but besides saving a left of nesting when you do want the result, the name "sequence" makes it clearer what you're doing.
Powerful stuff
[edit]These functions do the heavy lifting.
- Function
get-arg
retrieves arguments to the module call. It takes one operand, identifying the argument to retrieve; this may be an integer or a string. Argument 1 is the sequence of s-expressions; thus,{{evalx|'foobar' (get-arg 1)}}
would expand to "'foobar' (get-arg 1)", while{{#invoke:User:Pi zero/Wikilisp|rep|"foobar" (get-arg "foobar")|foobar=quux}}
would expand to "quux". Functionget-arg-expr
also retrieves arguments, but instead of returning an argument as a string, it attempts to interpret the argument as an s-expression which it returns unevaluated. If the argument is not a valid s-expression, the function returns the empty list. This is handy for doing further computation on a data structure that was output from an earlier call to the interpreter, as perhaps in an earlier step of a dialog.{{evalx|(get-arg-expr 2)|(* 2 3)}}
would expand to (* 2 3). Functionget-args
retrieves a list of the names of all arguments to the module call.- If the module is invoked through alternative Lua function
trep
rather thanrep
, wikilisp functionsget-arg
andget-arg-expr
access arguments of the template that invokes the module, instead of arguments of the invocation itself. Template {{evalx}} does this.
- If the module is invoked through alternative Lua function
- Function
parse
takes one operand, which must be a text string and is interpreted as raw wiki markup (before template expansion). The function returns a data structure describing the positions, within the text string, of wikilinks, template calls, and template parameters; and, within each such item, the positions of the parts of the item (which are separated from each other by the pipe character, "|"). Other tools can then use this data structure to locate particular kinds of structures within the wiki markup, and transform them in various ways.- The data structure is a list of "item" data structures; accessor functions can recover the string form of each item, the number of parts, the string form of each part, and a list of items within each part.
- Function
get-parts
takes one operand, an item descriptor as provided byparse
, and returns a list of its parts. Functionget-items
takes one operand, a part descriptor as provided byparse
, and returns a list of items within it.
- Function
- The data structure is a list of "item" data structures; accessor functions can recover the string form of each item, the number of parts, the string form of each part, and a list of items within each part.
- Function
filter
at its simplest takes two operands: a data structure such as produced by functionparse
, and a predicate to be applied to the entries in the structure for links, calls, and parameters. It returns a pared-down data structure describing only those page elements that match the predicate. Additional operands are additional predicates that must also be satisfied, as with special functionand?
.- If the predicate(s) reject an item, but accept some items within one of the rejected item's parts, the accepted items are promoted to the level of the rejected item. For example, suppose a page contains a call to {{xambox}}, and within the text message passed to the xambox are some calls to {{w}}. If the page is parsed and filtered for calls to {{w}}, the calls within the xambox will end up at the top level of the filtered data structure.
- Functions
link?
call?
andparam?
are predicates determining whether their operands are item data structures describing, respectively, wikilinks, template calls, and template parameters.
- Functions
- Function
filter
isn't designed for selecting some members of an ordinary list, but that can be done by building a new list out of small lists, where each small list either contains a particular element of the original list or is empty. For example, given a list of numbersls
, one could select the ones strictly less than 10 with expression(apply (curry + ()) (map (\x (if (lt? x 10) (list x) ())) ls))
. (Note the trick ofcurry
ing+
with the empty list beforeapply
ing it; otherwise, ifls
happened to be empty,+
would be applied to the empty list, producing a number instead of a list.)
- If the predicate(s) reject an item, but accept some items within one of the rejected item's parts, the accepted items are promoted to the level of the rejected item. For example, suppose a page contains a call to {{xambox}}, and within the text message passed to the xambox are some calls to {{w}}. If the page is parsed and filtered for calls to {{w}}, the calls within the xambox will end up at the top level of the filtered data structure.
- Function
split
can take more general forms of its first operand, and can take either or both of two additional operands, beyond the string and string-or-pattern as in the previous section.- There may be a second string-or-pattern operand; instead of listing substrings separated by a single string-or-pattern, the function then lists substrings delimited by the two strings-or-patterns. For example,
(split "a(b)c(d)e" "(" ")")
would evaluate to ("b" "d"). The delimiters are assumed to be potentially nesting, and at each point in the string the leftmost left-delimiter is chosen that has a matching right-delimiter. For example,(split "(a(b(c)e)d(f(g(h)i)j" "(" ")")
would evaluate to ("b(c)e" "g(h)i"). - There may be a final list operand, of 1–3 elements that could be the second-and-later operands to
split
; if this is present, instead of returning a list of substrings from the aforementioned operation,split
recursively splits each of those substrings using this new set of second-and-later operands, and returns a list of the results of these splits. For example,(split "a(b,c;d,e)f(g,h;i,j)k" "(" ")" (list ";" (list ",")))
would evaluate to ((("b" "c") ("d" "e")) (("g" "h") ("i" "j"))). - The first operand, rather than simply a string, can in general be a tree of strings; that is, either a string or a list whose elements are themselves trees of strings. The string operation specified by all the later operands is then applied recursively to each element of the tree. For example,
(split (list (list "a(b,c)d") () "e(f,)g") "(" ")" (list ","))
would evaluate to (((("b" "c")))()(("f" ""))).
- There may be a second string-or-pattern operand; instead of listing substrings separated by a single string-or-pattern, the function then lists substrings delimited by the two strings-or-patterns. For example,
- Function
join
can take more general forms of its first operand, and can take either or both of two additional operands, beyond the list-of-strings and string as in the previous section.- There may be a second string operand; then each of the listed strings is delimited by the two strings. For example,
(join (list "1" "2") "{" "}")
would evaluate to "{1}{2}". - The first operand, rather than simply a list of strings, can in general be a nested list of strings; that is, either a list of strings or a list whose elements are themselves nested lists of strings. The operation specified by the one or two string operands is then applied recursively to each element of the nested list. For example,
(join (list (list "a" "b") (list "c" "d")) ",")
would evaluate to ("a,b" "c,d"). - There may be a final list operand, of 1–3 elements that could be the second-and-later operands to
join
; if this is present,join
first operates on its first operand using the one or two string operands, then recursively operates on the result using the finally-listed set of operands. For example,(join (list (list "a" "b") (list "c" "d")) "," (list "{" "}"))
would evaluate to "{a,b}{c,d}". Thusjoin
can restore nestings of separators and delimiters removed bysplit
; for example,(join (split "a{b}c, d{e}f" (pattern ",%s*") (list "{" "}")) "{" "}" (list ","))
would evaluate to "{b},{e}".
- There may be a second string operand; then each of the listed strings is delimited by the two strings. For example,
- Function
get-substring
can take a descriptor specifying a segment of the string, instead of integer indices as in the previous section. Three kinds of descriptors are accepted: an item descriptor, which is an element of a list returned byparse
orget-items
; a part descriptor, which is an element of a list returned byget-parts
; or a list of two integers, which are the 1-based indices of the starting and ending character of the substring within the string. The resulting substring is returned. For example,(get-substring "foobar" (list 3 5))
would evaluate to "oba". Alternatively, the second operand can be a list of segment descriptors, and a list of substrings is returned;(get-substring "foobar" (list (list 2 2) (list 4 5)))
would evaluate to ("o" "ba").
- Function
set-substring
can take a segment-descriptor (as just described for get-substring) instead of integer indices for where to splice as in the previous section. Alternatively, it can take a list of such segment-descriptors, and a list of strings; the segments must be in order from left to right.(set-substring "foobar" (list 3 5) "12345")
would evaluate to "fo12345r",(set-substring "abcd" (list (list 2 2) (list 4 3)) (list "123" "456"))
to "a123c456d".
- Function
get-coords
takes a segment-descriptor (as just described for get-substring) and returns a list of two integers, the 1-based indices fo the starting and ending character of the segment. This is useful for decoding item descriptors and part descriptors so that the coordinates can be manipulated directly for general purposes. For example,(map get-coords (parse "a [[b]] [[c]] d"))
would evaluate to ((3 7) (9 13)).
- Function
map
takes a function and one or more lists. It calls the function repeatedly, with one operand from each of the lists, and returns a list of the results. Usually it is used with just one list; for example,(map (\x (* x x)) (list 1 2 3))
would evaluate to (1 4 9). With multiple lists,(map * (list 2 3) (list 5 7))
would evaluate to (10 21). If some of the lists are longer than others,map
stops when any of the lists runs out; for example,(map list (list 1 2) (list 3) (list 4 5 6))
would evaluate to ((1 3 4)).
- Function
merge
takes a function and one or more lists. The function should be a binary predicate, for ordering elements of the lists. Each list is assumed already sorted by the predicate (i.e., the predicate would return true on any two elements of the same list in their order in the list). The function merges the lists into a single list sorted by the predicate. If there is only one list, it is simply returned. For example,(merge lt? (list 1 3 5) (list 2 4 6))
would evaluate to (1 2 3 4 5 6). This isn't meant to be used with a very large number of lists; it slows down as the square of the number of lists.
- Function
transformer
takes up to four optional operands, and generates amap
-like function for acting on a tree, that is, a nested list. The resulting function takes three operands: a function to apply to leaf nodes of the tree, a function to apply to parent nodes of the tree, and a tree. In the simplest case, with no optional operands, if the tree is not a list then the leaf-function is applied to it and the result returned; while if the tree is a list, each element of the list is recursively transformed and the parent-function is applied to a list of the results. For example,((transformer) (\x (* x x)) (\x x) (list 2 (list 3 4) 5))
would evaluate to (4 (9 16) 25),((transformer) (\x (* x x)) (\x (apply + x)) (list 2 (list 3 4) 5))
to 54.
- The last optional operand is a positive integer, n. The first n elements of each parent-node list are left alone rather than recursively operated on. For example,
((transformer 2) (\x (* x x)) (\x x) (list 2 3 4 5))
would evaluate to (2 3 16 25). - The first optional operand is a predicate. When the tree is a list, the predicate is applied to it, and if the result is false the tree is treated as a leaf instead of a parent, applying the leaf-function to it instead of recursing and passing a resultant list to the parent-function. For example,
((transformer (\x (gt? (length x) 1))) (\x "x") (\x x) (list (list 1 2) (list 3) (list 4 5)))
would evaluate to (("x" "x") "x" ("x" "x")). - Between these, the second and third optional operands, which must occur together, are a basis value and a successor function, used to generate an extra, depth operand for the leaf/parent functions: at the top-level node of the tree, this value is the basis, and at each level further down the tree, the value results from applying the successor function to the value used at the level above. The depth operand is passed to the leaf/parent function as its first operand, before the tree-node operand. For example,
((transformer 2 (\x (+ x 1))) (\(n t) n) (\(n t) t) (list "a" (list "b" "c") "d"))
would evaluate to (3 (4 4) 3). If the predicate operand is also provided, it receives only the tree-node, not the depth.
- The last optional operand is a positive integer, n. The first n elements of each parent-node list are left alone rather than recursively operated on. For example,
Advanced stuff
[edit]These things may help you better understand the inner workings of the interpreter, and occasionally help you do some unusual things that the more mundane features don't handle cleanly. When you start actively using these exotica to do unusual things, it may be time to look for a way to amplify the ordinary tools so it won't be necessary to resort to these; but that may be a very difficult design problem, and meanwhile these things are available to take up the slack.
- Special function
\
can create functions that take different numbers of arguments, and evaluate a sequence of expressions. For different numbers of arguments, instead of a symbol for the first operand, use a list of symbols; the list may be empty, so the function takes no arguments. To evaluate a sequence of expressions, just specify all of them after the parameter-list. When the function is called, the number of arguments to the call must be the same as the number of parameters; all the parameters are locally bound to the corresponding arguments, and the second and later operands to\
are evaluated in this local environment from left to right. The result of the last of these evaluations is the result of the function call, or if\
was given only one operand, the result of the function call is the empty list.
- An esoteric point: The local environment, in which the function's sequence of expressions are evaluated, is a child of the environment where
\
is called (technically, this is called lexical scope). So when a local environment needs to look up a symbol that isn't locally bound, this occurs where\
was called rather than where the created function is called. For example,(((\x (\y (+ (* x x) (* y y)))) 2) 3)
would evaluate to 13.
- Function
fn?
checks whether all the values passed to it are ordinary functions; functionop?
checks whether all the values passed to it are special functions.(fn? if)
would evaluate to false, sinceif
is not an ordinary function.(op? +)
would evaluate to false since + is not a special function.
- When an ordinary function is displayed as output, it is shown as
<[op:
name]>
, where name is the name of the function. For example,{{evalx|length}}
would expand to<[op: length]>
. The angle-brackets mean that the operands to the function call are automatically evaluated; underneath is a special function whose operands are the results of evaluating the operands to the ordinary function call. Evaluating(length (+ 1 2))
would not produce an error until after the operand has been evaluated to 3, at which point the special function underlyinglength
would discover it doesn't know what to do with an integer operand, producing error message<error: bad operand to [op: length]: expected list or string, got 3>
.
- When special function
\
creates a function, it doesn't give it a name.{{evalx|(\x (* x x))}}
would expand to<[op]>
. However, the first time an anonymous function is given a name in an environment, that name is attached to the function, and the function is known by that name thereafter. So,{{evalx|(define f (\x (* x x))) f}}
would expand to<[op: f]>
.
- There is a built-in limit on how deeply calls to
\
-defined functions can be nested. At the current writing, the limit is 4. That is,(let (g (\f (\x (f (f x))))) ((g (\x (+ 1 x))) 0))
would evaluate to 2,(let (g (\f (\x (f (f x))))) ((g (g (\x (+ 1 x)))) 0))
to 4, and(let (g (\f (\x (f (f x))))) ((g (g (g (\x (+ 1 x))))) 0))
to 8, but(let (g (\f (\x (f (f x))))) ((g (g (g (g (\x (+ 1 x)))))) 0))
would produce<error: exceeded maximum call-nesting depth (4)>
.
- If you really need to embed a double-quote in a string literal delimited by double-quotes, use two double-quotes inside the literal.
""
is the empty string;""""
is a string of length one, containing a single double-quote. There is no analogous way to embed a single-quote in a string literal delimited by single-quotes.
- Function
wikilisp-version
provides a string describing the current version of the module.(list (wikilisp-version))
currently evaluates to( "X0.19 (November 4, 2019)" )
.
Index of functions
[edit]Module tests
[edit]These aren't exhaustive. They aspire to exercise all of the code in the module at least once (both branches of an if, etc.), though there would be merit to deskchecking all the code to determine what parts of it have been overlooked.