Difference between revision 19 and current revision
No diff available.Part of Oot Details.
Read Oot first.
Here we describe, in detail, the syntax of Oot.
(todo: some of this isn't just syntax)
Parsing occurs as a separate stage prior to the rest of Oot compilation/execution. There are scoped metaprogramming constructs that allow custom parsing of individual, clearly-marked strings, lines, or blocks within code, and there is a per-file 'source filter' preprocessing facility, but there are no metaprogramming constructs that can alter the behavior of Oot parsing in a non-local way. This guarantees that if you are reading Oot code outside the scope of the above-mentioned metaprogramming constructs, you can be assured that it parses in a standard way.
The precedence of operators is determined by which symbols they are composed of; although users can define custom binary operators, you never have to look up a function definition just to see how to source code will parse.
Echoing [1], one might say that Oot's parser has syntax defined in terms of characters, and the Oot language has syntax defined in terms of symbols, graphs, and literals. The parser is available to the user via the function ootparse, which reads the next form from a stream, and returns the objects represented by that form.
Any Oot program can be expressed as a single line, by using ';'s in place of newlines, by closing all parentheses explicitly instead of using paragraphs, and by using "\n"s in place of multiline strings.
Each line is either:
todo i think this is out of date: (a) no longer for reversing, (b) .. is not pipeline, infix
| is |
Periods are syntactic sugar for various things.
One period is a shortcut for applying a function on the left to a keyword on the right, without having to put the keyword in uppercase. When the thing on the left is a data structure, the function is the 'getter' for the data structure, and this acts as 'attribute lookup', for example:
P employeeRecord.address == P (employeeRecord ADDRESS)
todo does this affect the way setters work when on the LHS? Eg does 'a.b.c = y' mean something different than 'a b c = y', perhaps relating to whether the setter first called is the one for 'a' or the one for 'c'; eg do you actually run "a b c" and then call the setter on the result, or does the entire expression "a b c" have a special meaning b/c its on the lhs?
todo does this still work when '.' is unattached?
todo somewhere explain that 'attribute access' eg getters is just ordinary function application, and 'setters' is unified with function application on the LHS
Two periods are a shortcut to 'pipeline' the application of a function on the right to the data on the left, for example:
doSomething (sort (myFilter (x))) == x..myFilter..sort..doSomething
In the above example, '..' is binding tightly, because it is being used infix attached. If it is unattached, it binds loosely:
[3 2 5] .. sort .. map timesTwo == ([3 2 5])..(sort)..(map timesTwo) == map timesTwo (sort [3 2 5]) == map timesTwo ([2 3 5]) == [4 6 10] map timesTwo [3 2 5] .. sum == (map timesTwo [3 2 5])..sum == sum (map timesTwo [3 2 5]) == sum ([6 4 10]) == 20 3 + [5 2]..max == 3 + ([5 2]..max) == 3 + (max [5 2]) == 3 + 5 == 8
A suffix colon indicates an implicit parenthetical grouping starting to the right of the identifier to which the colon is attached, and going until either the end of the containing grouping, or the next suffix colon (on the same level, ie not in an (explicit or implicit) parenthetical or block sub-grouping), even over multiple lines. In addition, the suffix colon indicates a parenthetical grouping starting right after the identifier to which the colon is attached, and going until either the next prefix colon (on the same level) or the end of the containing grouping or line.
A prefix colon indicates an implicit block starting to the right of the identifier to which the colon is attached, and going until either the next prefix colon or the end of the containing grouping, even over multiple lines, and additionally indicates a keyword argument, functioning similar to '/'; the identifer to which the colon attached is the keyword (the left hand side of the '/'), and the block to the right of it is the argument.
For example:
if: condition
:then
first
:else
second
is equivalent to:
if (condition) then/{first} else/{second};
Note that these constructs implicitly nest, provided that the prefix and suffix colons are alternating. For example:
if: condition
:then
if: condition2
:then
handle-true-and-true
:else
handle-true-and-false
:else
handle-false
is equivalent to:
(if (condition) then/{(if (condition2) then/{handle-true-and-true} else/{handle-true-and-false})} else/{handle-false});
Infix colons create implicit parentheses on both the lefthand and the righthand of the colon, until the next infix colon or the end of the containing group or the next implicit semicolon. Infix colons have higher precedence than ('bind tighter than') prefix or suffix colons or implicit semicolons. Multiple infix colons do the same thing, but have lower precedence the more of them there are.
For example,
a b : c d
is equivalent to
(a b) (c b);
Another example:
a b :: c d : e f g h : i j
is equivalent to:
(a b) ((c d) (e f)); (g h) (i j);
todo: what do commas do? i think: on the rhs: implicit data constructor; on the lhs: bind multiple return values
todo: is there something that does Haskell's a b $ c d $ e f == a b (c d (e f))? (ps is that even how $ works in haskell?)
places an implicit parentheses around everything to its left, to the beginning of the line or to the end ofthe containing grouping, and also places an implicit block around everything to its right, until the next line (on this grouping level) containing a , or the end of the block (much like a prefix colon). Then it functions like a '/' between the value to its left, and the block to its right, except that instead of treating the thing to its left as a keyword, it evaluates it (at runtime).
todo: why evaluate the thing on the left? just leave it as a block
todo: is this really a good replacement both as a separator in 'cond' lists, and also for Prolog's ':-'?
Note: 'cond' is like 'switch' in C
For example:
i = 1 j = 2 cond: i 0 // "zero" 1 // "one" j // "two"
is equivalent to:
i = 1; (cond (i) (0)$/"zero" (1)$/"one" (j)$/"two");
and
i = 1 j = 2 cond: i i == j // "equal" i -= j // "not equal"
is equivalent to:
i = 1; j = 2; (cond (i) (i == j)$/"equal" (i -= j)$/"not equal");
todo: i no longer understand the dollar signs i put in there
Integer: 3
Floating point number: 3.0, INF, NAN
String: "Hello!"
String, with interpolation and newline substitution: "Hello {name}!\n"
String, raw: r"Hello, i can include the '{' and \n characters!"
Multiline string ('HERE documents'): """ This is a string named {name} that is 3 lines long. """
Multiline string with custom delimiter: """xyz This is a string named {name} containing """triple quotes""" that is 3 lines long. """xyz
Multiline raw string with custom delimiter: r"""xyz This is a string containing """triple quotes""" and a { and a \n and which is 3 lines long. """xyz
In addition to 'r', string delimiters may be prefixed by '#' and then a string, which indicates that the string is a literal that is to be passed to a macro.
symbol: THIS-IS-A-SYMBOL
A symbol literal is just like a string literal, except (a) you don't have to enclose it in double-quotes, (b) the implementation is encouraged to represent it internally as an integer to aid performance, (c) translation tables can't contain mappings for keywords. Although you can try to coerce symbol values to strings and print them out, this is really only for debugging and most implementations will just print out the internal integer representation unless this is a debug build.
nil: NIL
booleans: FALSE, TRUE (synonyms: F, T)
If your Oot implementation supports Unicode source files, non-ASCII characters are ONLY allowed within strings.
Oot has a facility to attach a separate file providing translations of strings. So, for instance, in the source code you could write (x = "Hello!"), and then in the translation file for French you could map "Hello!" to "Bonjour!", and then at runtime, if Oot is in a French locale, x will be set to "Bonjour!" when the line (x = "Hello!") is encountered in the source code.
The meaning of a string consisting of alphanumerics-with-dashs depends on its case:
Lowercase: an ordinary identifier
Capitalized: an annotation
Uppercase: a symbol literal (see above)
mixedCase: the upper-case parts are actually macro operators (we call these 'attached macros') operating on the surrounding lowercase words. eg "mixedCase" would be read as a macro 'C' applied to 'mixed' and 'ase'. (todo: do longer-than-one-letter such 'macros' exist? if not, then just the first letter, reading from the left, is the operator, right?)
Single-letter identifiers (or identifiers with only a single letter followed by a number) are considered to be uppercase, not capitalized; that is, annotations must have at least two letters at the beginning.
Any alphanumerics-with-dashs that BEGINS with dashes has a special meaning, depending on how many dashes there are:
To prevent confusion, although the case of a string is significant, there is only one namespace; you are not allowed to, for example, have an ordinary identifier, and an annotation, with the same name except that one is capitalized and one is not.
When you see alphanumerics-with-dashs smashed together with other punctuation character(s) without intervening spaces, then the punctuation characters act as operators or modifiers acting upon the alphanumerics-with-dashs they are attached to. A given (string of) punctuation character(s) may have three DIFFERENT MEANING depending on whether:
Generally the prefix and the postfix meanings are related; they are usually rough inverses of some sort, where prefix can be imagined as going 'up' (constructing) and postfix goes back 'down' (deconstructing) in some very abstract space. Sometimes the composition of a postfix with the corresponding prefix is an identity. For example, y = &x takes a pointer or reference to x, and y& defererences the pointer in y; (&x)& === x.
Strings of commas and strings of puntuation containing =s operate the same whether they are attached or unattached to their neighbors.
Attached punctuation binds tighter than unattached operators.
Any from of whitespace separates non-whitespace. Note that the meaning of punctuation may change dependent on whether it is attached to something, or separated from it by a space.
Oot usually inserts semicolons at every newline. There are two exceptions:
In debug mode, it is possible to mark a certain dynamic scope of the program so that expressions in that scope which were delimited by newlines, as opposed to explicit semicolons, automatically print out the value of the expression on that line.
A region of text which does not contain blank lines and which is surrounded by either one or more blank lines and/or the boundaries of a block is called a 'paragraph'. A blank line is two newlines with no non-whitespace characters in between them (excluding comments). The only function of paragraphs is that, upon reaching the end of a paragraph, any levels of parenthesis within the current block scope which have not yet been closed are implicitly closed (just as if the blank line contained a string of closing parenthesis of length sufficent to balance the parenthesis in this block); and a semicolon is inserted at the end.
For example, each of the following is equivalent to "print (1+1);":
print (1+1) print (1 + 1) print (1 + 1
Two or more adjacent semicolons, followed by a space, has the effect of a newline and begins a comment that continutes until the end of the line. Two or more adjacent semicolons, followed by a non-whitespace string, begins a comment that continutes until the same delimiter is encountered, even over multiple lines; note that no newline is inserted. For example:
print 1+1 ;; nice day today
print 1+1 ;;;;; yay!
print 1+ ;;xyz one ;;xyz 1
print 1+ ;;xyz man this is
quite a long
comment
;;xyz 1
If a block has some lines with comments started with three or more semicolons, and some lines without that (with no comments, or with comments started with only two semicolons), then this is a hint to the reader that the lines with three or more semicolons are the more important ones, containing the 'happy path' or the 'main idea' of the block, in comparison to the other lines, which are mere 'details' such as error handling. IDEs are encouraged to have modes that collapse the less important lines, highlight the important lines, etc.
At the beginning of the file, if one or more lines begin with the character '#', then these lines are also treated as comments (to facilitate the Unix shebang convention on the first line, as well as build systems etc that can use the following lines to annotate).
Comments are removed at an early stage of parsing and have no further effects besides those mentioned above. Comments within strings, or within raw text being fed to metaprogramming facilities, are not removed.
Aside from whitespace, there are four grouping constructs:
The following region is associated with the keyword value HELLO:
v = 3
HELLO{{print "Hi"}}HELLO
If no value is given, the associated value defaults to NIL:
v = 3
{{print "Hi"}}
Every region must be terminated in the sense that every region opening double braces must correspond to exactly one point in the same lexical scope as the opening at which the region has closed; however, regions do not have to nest in lexical scopes.
So one might see the following:
REGION1{{
if x > y: {
do_something
do_something_else
}
else: {
do_a_third_thing
}
}}REGION1
But the following is illegal (assuming this is not an excerpt but the entire file), because REGION1 is never closed:
REGION1{{
if x > y: {
do_something
do_something_else
}
else: {
do_a_third_thing
}
Because regions don't have to nest lexically, the same region-opening might be closed by two or more places, providing that these places are not lexical ancestors of one another. For example, one might see:
REGION1{{
if x > y: {
do_something
}}REGION1
do_something_else
}
else: {
do_a_third_thing
}}REGION1
}
And similarly for region openings. For example, one might see:
if x > y: {
do_something
REGION1{{
do_something_else
}
else: {
REGION1{{
do_a_third_thing
}
}}REGION1
But the following would be illegal, because REGION1 is never terminated in the ELSE branch:
REGION1{{
if x > y:
do_something
}}REGION1
do_something_else
else:
do_a_third_thing
Similarly, the following is illegal, because REGION1 is terminated both in the first branch of the if, and also at a later point in the lexical parent of this branch:
REGION1{{
if x > y:
do_something
}}REGION1
do_something_else
else:
do_a_third_thing
}}REGION1
Regions associated to different values do not (syntactically) affect one another, and can overlap:
REGION1{{
REGION2{{
if x > y:
do_something
}}REGION1
do_something_else
}}REGION2
else:
}}REGION2
do_a_third_thing
}}REGION1
A region can be closed and then re-opened later, or can be opened multiple times, but these closings and openings must 'nest' in the sense described above.
The following is legal:
REGION1{{
x = 3
}}REGION1
y = 4
REGION1{{
if x > y: {
do_something
REGION1{{
do_something_else
}}REGION1
}
else: {
do_a_third_thing
}
}}REGION1
These restrictions ensure that:
todo regions in graphs
Every line and block is an expression in Oot. The value of a block is the value of the last expression in it. The value of a conditional control construct such as 'if' is the value of the last line on whichever part of it executes.
Assignment statements return the value being assigned (ie the 'rhs', the right hand side).
Operators are unary if and only if they end with the character '-', except that the operator '-' is unary negation if it is an attached prefix, and otherwise it is the 2-ary subtraction operator. Unary operators are attached prefixes.
The following characters can be used in names for binary operators: todo
todo: what is the operator to negate a number or boolean or to invert a function?
todo: in many (but maybe not all) ways, the earlier G-constructor syntax (see below, in OLD section (later: did i mean ootOldMain.txt?) is better than this, merge these 2 proposals
Oot has one primitive structure/composite data constructor, []. It is used to construct Oot Graphs. Oot Graphs can be used as lists, as associative arrays/dicts/hashes, as trees (acyclic graphs), or as (potentially cyclic) graphs. Examples:
a list:
l = [1 2 3]; l 1 == 2;
an associative array with string keys:
d = ["apple"="red" "pumpkin"="orange"]; d "apple" == "red";
an associative array with keyword keys:
[APPLE="red" PUMPKIN="orange"]; d APPLE == "red";
an associative array with variable keys:
key1 = APPLE; val2 = 'orange' [key1="red" PUMPKIN=val2]; d APPLE == "red";
a tree (edges by node syntax):
tree1 = [SALLY=[$BOB, $ALICE]; BOB=[] ALICE=[$SALLYS_GRANDDAUGHTER] SALLYS_GRANDDAUGHTER;]; tree1 SALLY ALICE 0 == tree SALLYS_GRANDDAUGHTER
In the previous example, we used prefix dollarsign (eg '$BOB') to indicate that, eg instead of SALLY's first child being the keyword BOB, rather SALLY's first child is the node whose LABEL is the keyword BOB. Prefix dollarsigns are resolved within the context of the currently-in-scope data context.
alternate syntax for declaring edges (edgelist syntax):
t2 = [SALLY BOB ALICE SALLYS_GRANDDAUGHTER SALLY/BOB BOB/ALICE ALICE/SALLYS_GRANDDAUGHTER]; tree2 == tree1;
using newlines:
tree3 = [ SALLY BOB ALICE SALLYS_GRANDDAUGHTER SALLY/BOB BOB/ALICE ALICE/SALLYS_GRANDDAUGHTER ]; tree3 == tree2;
a cyclic graph:
cg = [A B C A/B B/C C/A] cg A B C == cg A
a graph with a self-loop:
sl = [NODE1 = [--SELF]]; sl NODE1 0 == sl NODE1;
a graph with an edge to the graph itself:
gwaettgi = [NODE1= [--ROOT]]; gwaettgi NODE1 == gwaettgi;
a graph with an edge whose target is another edge, rather than a node:
gwet = [NODE1 NODE2 NODE1/NODE2 NODE2/(NODE1 NODE2). ); gwet NODE2 0 --SRC == gwet NODE1
the same graph, with the addition of a NODE3 which is a node representing a reified edge:
gwet = [NODE1 NODE2 NODE1/NODE2 NODE3=(NODE1 NODE2). NODE2/NODE3 ); gwet NODE2 0 --SRC == gwet NODE1; gwet NODE2 0 == NODE3;
Here the a postfix '.' means 'the last edge along the path just given'. For example, 'x y Z.' would refer to the edge whose source is the result of evaluating x y, and whose label is Z.
Graphs can contain 'metadata', which are invisible to ordinary graph operations, but accessible using the 'metadata' view of the graph. Metadata is given using the '^' prefix operator. For example, here is a graph with two nodes, labeled APPLE and PUMPKIN, with metadata indicating sharding:
key1 = APPLE; val2 = 'orange' [key1="red" PUMPKIN=val2 key1^[shard=1253] PUMPKIN^[shard=543] ]; d APPLE == "red";
Functions associate to the left. 'Multiargument' functions are curried. Function application is by juxtaposition. Eg:
sqrt 4 == 2 add 2 3 == (add 2) 3 == 5
Graphs can be accessed as functions:
d = ["apple"="red" "pumpkin"="orange"]; d "apple" == "red";
function_name parameter1 parameter2 = function_body f x = x + x
The stuff to the left of the equals sign ('function_name parameter1 parameter2' above) is called an 'argument specification' or argspec.
Curly braces ('{}'s) must be used to enclose the function body if it has multiple statements (and even if the function body is a single statement, if that statement includes an equal sign ('='), that equals sign and the function-defining equals sign must be separated by some sort of grouping; {} is recommended but () will also work; this is because two '='s which are syntactic siblings are the 'named return argument' construct, see below). Eg:
add_3 x = {k = 3; x + 1}
Function definitions are also expressions which return the function that was defined. For example, the following defines a function named 'f' with one parameter, and then assigns this function to the variable 'a':
a = (f x = c)
Note that surrounding the function definition with some sort of grouping is mandatory, because otherwise this would be recognized as the 'named return argument' construct, see below.
Anonymous functions are written with a '=>' to separate the parameters from the function body. {}s can be used for functions with multiple lines, eg:
x => x + x
x => {y = 3; x + y}
0-ary functions can be defined using this syntax by having no parameters, eg:
=> 3
Named functions are just syntactic sugar for anonymous functions. 'f x = body' is syntactic sugar for:
f = (x => body) (f@meta).name = 'f'
todo change the previous line to use the real View syntax (instead of @) and the real 'get string name of' meta syntax (instead of 'f')
todo can we omit this if we use identifiers like _1, eg is "_1 + _1" equal to "x $ x + x"?
Functions with named or multiple return values (multiple return values require named return values) are defined using the syntax:
named_return_value1, named_return_value2 = function_name arg1 arg2 = function_body
The motivation for using an '=' on the left here is so that you can easily copy-and-paste from the function definition to the places where you are writing code to use the function. For example, in the above you can copy 'named_return_value1, named_return_value2 = function_name arg1 arg2', and paste that into the call site, then edit it to be what you need. The motivation for using an '=' sign on the right is so that function definitions without multiple return arguments are still very easy to type, and so that they look like ordinary variable assignment/binding.
To call a function and bind multiple return values, use:
return_value1, return_value2 = function_name arg1 arg2
Note that this function call looks somewhat like a function definition. The way to tell them apart is that the LHS (LHS means left-hand-side of an equals-sign) of a function callsite binding named return values has a comma in it, eg 'r1, r1 = f x'; by contrast, a function definition would look like 'f x = body', where 'f' and 'x' are separated by spaces. Note that commas on the LHS are significant; when seen on the LHS, they are NOT a shortcut for [return_value1 return_value2]; '[return_value1 return_value2] = function_name arg1 arg2' is NOT equivalent (that would be a destructuring bind on the primary return value).
If you need multiple statements in the function_body, you must make it a block (ie surround it with curly braces ('{}')).
The value of the last line of the block is called the 'primary return value' and the others are called 'secondary return values'. The function body gives the primary return value on the last line of the function, or by using 'return'.
Note that having two equal-signs ('=') like this does NOT mean a chained assignment (chained assignment is a construct that some other languages have; Oot does not have chained assignment); it is a special form for a function definition including named return values. On the other hand, if the two '='s are not syntactic 'siblings', but rather are are explicitly grouped separately via parentheses (or similar, todo decide exactly what is meant here), then they are treated as two separate =s. For example, 'a = (f x = c)', which we saw above.
Examples:
r1, r2 = f x = {r2 = 3; x + x}
primary_result, z = f 1 ;; accessing the r2 result by position
primary_result, r2/z = f 1 ;; accessing the r2 result by name
primary_result == 2
z == 3
An anonymous function with named return values can be defined using the same syntax, but with '_' in the place of the function name:
anonymous_fn = (named_return_value1, named_return_value2 = _ arg1 arg2 = function_body)
'return' works similar to languages like C and Python. When encountered within a function body, it causes the function to exit, returning the value given to <=. For example:
division_except_divide_by_zero_is_zero x y = {if y == 0 (return 0); div x y}
(note to self: 'return' is alphabetic because it is not needed in Oot Core because it is semantically equivalent to throwing an exception which is caught at the function level, and replacing the last line of the function with a construct that returns what was caught by such an exception, if there was one, or by the result of the expression on the actual last line otherwise; of course for performance reasons the Oot implementation will have to treat return specially, unless the compiler is smart enough to handle it as an exception but then to recognize that this exception is always caught within the function and optimize it to a goto, or even an actual return if we can)
Expression evaluation is non-strict but not necessarily lazy. I'm not quite sure exactly how this will work, but right i'm thinking that the requirement will be that the program should not diverge or give an error unless a lazy evaluation strategy would also diverge or give an error; EXCEPT if the entire expression being evaluated was (a) created at the behest of the current module AND (b) not marked as lazy.
In terms of implementation, each thunk will be marked as to the modules in the call chain at the time of its origin, and marked if it is 'lazy' or not. If it originated in the current module and it is not marked lazy, it is evaluated eagerly. Otherwise, it is evaluated lazily yet with some heuristically bounded speculative eager evaluation, perhaps involving the maximal lexical nesting depth of the current module (static, i suppose, lest some macro make a really deep depth?); but if an error is generated, this error is not delivered until the value would have been demanded lazily.
Footnotes are a special form of compile-time macro.
'#x', where x is a 'footnote identifier' is a 'footnote' (or more formally, a 'footnote invocation'). A footnote identifier has the syntax of either an identifier or an integer constant (the purpose of allowing integers as identifiers is to allow/encourage authors to not waste mental time/space in coming up with a meaningful identifier). A footnote captures and replaces everything to its left within the current explicit group (ie until the previous EOL or open parens or open braces or open bracket). Within the footnote definition, the ## is replaced by the captured text.
The captured block is not 'run' unless/until requested by the footnote.
Footnote x is 'defined' using the special syntax '#x= ...', where ... is a placeholder for an expression within which '##' refers to the captured text. Note that the '=' is postfix attached; '#x = ...' is not the same.
The 'definition' of the footnote identifiers comes AFTER their invocation. The same footnote identifier may be defined multiple times; the rule is that the definition that comes lexically soonest after the invocation is the one that is used.
Within the scope of a data constructor (that is, after an unquoted [ but before the closing ]), we are said to in 'data context'. Otherwise, we are said to be in 'code context'. Some syntax, and the operation of macros, differs depending on whether we are in data context or code context.
Parenthesis within data context enters code context. A nested data context can be entered from this code context, etc. Eg:
[(1 + 1), ([1 2] + [3 4])] == [2 [1 2 3 4]]
Base context is the program that is being executed. Metacontext is annotations and other data attached to parts of the program being executed. For example, static type annotations are in metacontext. Annotations (metacontext) may be attached to parts of the program within code context and also to parts within data context.
From base context, metacontext is entered via the prefix ^^. From metacontext, base context is entered via postfix ^^. As a shorthand, any capitalized word is automatically placed in metacontext. Eg:
Int i = 3 is shorthand for ^Int i = 3
To place multiple space-separated words into metacontext, use ^^ with parenthesis, eg:
^(List int) l = [1,2,3]
To place multiple lines into metacontext, use ^^ with blocks, eg:
^{
Wrapper1
List int
}
Both base and metacontext can have both data context and code context within them.
An AssertionException? is raised if any expression in the block, aside from the last, evaluates to FALSE. Eg in the following block, the line 'y = 5' will never be reached, because execution will terminate with an AssertionException? when the line 'x == 4' evaluates to FALSE:
x = 3 x == 4 y = 5 y
' involves passage between a mode of error-handling in which exceptions are handled by immediately raising the exception, but variables are guaranteed not to contain nils, and a mode in which exceptions are captured by Fail values using Option types, but exceptions are always caught.
'x evaluates x, and, if this evaluation raises an error, it catches that error and puts it into the errorneous case of an Option type. If x does not raise an error, it puts the result into the successful case of an Option type.
x' takes an Option type and tests if it is an erroneous case or a successful case. If it is erroneous, it raises the contained error. If it is successful, it returns the contained result value.
x'e is like x' except that if x is an erroneous case of an option type, it returns not the contained error, but rather the result of applying the function e to the contained error.
For example:
(todo is the syntax right to terminate the optional arguments of the DivideByZeroError?