git @ Cat's Eye Technologies Robin / 6f28ac1
Merge pull request #7 from cpressey/develop-0.8 Develop 0.8 Chris Pressey authored 1 year, 5 months ago GitHub committed 1 year, 5 months ago
78 changed file(s) with 1700 addition(s) and 1065 deletion(s). Raw diff Collapse all Expand all
00 History of Robin
11 ================
2
3 Robin 0.8
4 ---------
5
6 * What was previously known as a `macro` is now known
7 as a `fexpr`.
8 * The `fexpr` form no longer provides the `self`
9 argument to its definition. If recursion is desired
10 in the definition of a fexpr, the fexpr should be
11 written recursively (in the same way functions have
12 traditionally been written recursively in Robin: pass
13 the fexpr itself as the first argument to the fexpr.)
14 The Robin definitions of fexprs in the standard library
15 such as `let` and `list` have been rewritten this way.
16 * The object that calling `fexpr` or `fun` produces is no
17 longer called a "macro". It is an "operator". There
18 there are other ways to obtain an operator than applying
19 a `fexpr` or `fun` (for instance there have always been
20 intrinsic operators; it's not fair to call them "macros".)
21 * When a reactor produces an abort value, it does not cause
22 a further event reporting the abort value to occur.
23
24 In the standard library,
25
26 * The Robin definition of `bind` now checks that the name
27 being bound is a symbol. The Robin definition of `let`
28 is now based on that of `bind` so it inherits this behaviour.
29 * The documentation for the alist functions in the standard
30 library was improved.
31 * Added the `bind-vals` operator, which is like `bind-args`
32 but does not evaluate the arguments, and which works on
33 possibly-deep lists.
34
35 For the reference implementation,
36
37 * When exceptions were replaced with abort values in 0.6, the
38 evaluator wasn't fully adapted to handling abort values in
39 all places. Evaluator support has been changed to make it
40 harder to forget to check for the abort value when needed.
41 * Fixed recent import changes which prevented it from
42 running under Hugs.
43 * The `Macro` type of expressions has been removed,
44 and `Builtin` renamed `Operator`.
45 * No builtins are exposed by default. The `--no-builtins`
46 flag was replaced by the `--enable-builtins` flag, which
47 has the complementary effect.
248
349 Robin 0.7
450 ---------
00 Robin
11 =====
22
3 _Version 0.7. Work-in-progress, subject to change._
3 _Version 0.8. Work-in-progress, subject to change._
44
55 Overview
66 --------
99 and [thoroughly specified](doc/Robin.md) functional programming language with
1010 [eager evaluation, latent typing, and a homoiconic syntax](#scheme),
1111 based on a [radically simple core semantics](#forth) in which
12 [the macro, rather than the function, is the fundamental abstraction](#picolisp).
12 [the so-called "fexpr" is the fundamental abstraction](#picolisp)
13 and both functions and macros are defined in terms of it.
1314
1415 Expressions in Robin are [referentially transparent](#haskell); programs
15 interact with the outside world [through a reactive framework](#elm).
16 interact with the outside world [through an event-driven framework](#elm).
1617
1718 For more information, see the [extended description](#extended-description)
1819 below.
3536
3637 You can then run it on one of the example Robin sources in `eg` like so:
3738
38 bin/robin eg/hello-world.robin
39 bin/robin pkg/stdlib.robin eg/hello-world.robin
3940
4041 You should see
4142
5253
5354 ./test.sh
5455
55 The tests that use only Robin's core semantics (`--no-builtins` flag) are quite
56 slow, so you may want to skip them. You can skip them by running
56 The tests that use only Robin's core semantics (with no help from implementation
57 "builtins") are quite slow, so you may want to skip them, by running
5758
5859 APPLIANCES="appliances/robin.md" ./test.sh
5960
6061 The test suite will also run some property tests (using QuickCheck). Notably,
61 for every macro that is defined multiple times (which includes much of stdlib,
62 for every operator that is defined multiple times (which includes much of stdlib,
6263 where the core definitions are written in Robin but also implemented in Haskell
6364 as "builtins" in the reference interpreter), QuickCheck will attempt to falsify
64 the assertion that the definitions define the same macro. These attempts are
65 the assertion that the definitions define the same operator. These attempts are
6566 currently rather crude; there is lots of room for improvement for them in some
6667 future release.
6768
8889
8990 ### PicoLisp ###
9091
91 [PicoLisp][] allows defining functions with unevaluated arguments.
92 Robin adopts this kind of function for the basis of what it calls a `macro`,
93 and builds everything else on top of `macro`s. (There *is* a `function` form
94 in Robin, but it's defined as a `macro`!) This is much like how the [Kernel][]
95 programming language builds everything from [fexpr][]s; however, Robin was
96 developed oblivious of Kernel — it adapted the idea directly from PicoLisp.
92 In most languages, the arguments to a function are evaluated before the
93 function is applied, but [PicoLisp][] allows defining functions with
94 unevaluated arguments. In historical Lisp, such operators were called
95 [fexpr][]s. Robin adopts fexprs as the fundamental abstraction — both
96 functions and macros are defined in terms of fexprs.
97
98 The [Kernel][] programming language also takes fexprs as its fundamental
99 abstraction; however, Robin was developed oblivious of Kernel — it adapted
100 the idea directly from PicoLisp.
97101
98102 ### Haskell ###
99103
103107
104108 ### Elm ###
105109
106 Reactive programs in Robin are built by composing transducers which are driven
110 Interactive programs in Robin are built by composing transducers which are driven
107111 by events and produce effects (which are modelled as further events), in a
108112 manner very similar to [The Elm Architecture][].
109113
117121
118122 Deserves at least a passing mention here, as one thing that Robin
119123 discards from Scheme is its jargony terminology: no `cdr`, no `cons`,
120 no `lambda`.
124 no `lambda`. (A notable exception is `fexpr` simply because there is no
125 satisfying short, non-jargony word that connotes how these operators work.)
121126
122127 For a full description of the Robin language, see
123128 [the Robin specification document](doc/Robin.md).
125130 Repository Layout
126131 -----------------
127132
128 * appliances/ — test appliances for the literate test suite.
129 * bin/ — driver script, destination for executable when built.
130 * demo/ — contains HTML5 document demonstrating build to JS by Haste.
131 * [doc/](doc/README.md) — Tutorial, specification, rationale, etc.
132 * eg/ — example programs written in Robin
133 * src/ — Haskell source for reference interpreter.
134 * stdlib/ — normative definitions of standard library symbols.
135 * [HISTORY.md](HISTORY.md) — history of this distribution.
136 * [TODO.md](TODO.md) — plans.
133 * `appliances/` — test appliances for the literate test suite.
134 * `bin/` — driver script, destination for executable when built.
135 * `demo/` — contains HTML5 document demonstrating build to JS by Haste.
136 * [`doc/`](doc/README.md) — Tutorial, specification, rationale, etc.
137 * `eg/` — example programs written in Robin
138 * `src/` — Haskell source for reference interpreter.
139 * `stdlib/` — normative definitions of standard library symbols.
140 * [`HISTORY.md`](HISTORY.md) — history of this distribution.
141 * [`TODO.md`](TODO.md) — plans.
137142
138143 [Scheme]: http://schemers.org/
139144 [Haskell]: https://www.haskell.org/
11 ====
22
33 (Note, some of these are possibly long-term plans.)
4
5 Macros
6 ------
7
8 Just as we have defined `fun` in terms of fexprs, we can
9 define `macro` in terms of fexprs. (It is expected to
10 return a literal chunk of program, which we then evaluate.)
11
12 It would just be a fexpr that evaluates (in the calling
13 environment) what the body evaluates to.
414
515 Disjointness of types
616 ---------------------
3141 Stdlib
3242 ------
3343
34 `(compose f1 f2)` composes two functions.
35
36 `(sgn x)`
44 `(compose f1 f2)` to compose two operators.
3745
3846 `(modulo x y)` which is always positive, change `remainder` to
3947 be sign of divisor (like R6RS.)
4048
49 `let` and `choose` follow the same pattern. Consider a general
50 `chain` combinator such that `(chain bind (...) ...)` is `let` and
51 `(chain if (...) ...)` is `choose`.
52
4153 Other libs
4254 ----------
4355
44 `schemeish` lib, that just gives you all the old jargon-y names
56 `lispish` lib, that just gives you all the old jargon-y names
4557 as aliases.
46
47 Static analysis lib. This is its own bucket of worms. It should
48 expose a macro that can be wrapped around an arbitrary standard
49 Robin program _p_ and, if static analysis of _p_ is successful,
50 simply evaluates to whatever _p_ evaluates to. And if not
51 successful, produces an abort. Should probably start small --
52 statically check the arity of every application, for instance.
53 Note that this relies on the assumption that all standard symbols
54 have their standard meanings.
5558
5659 TopLevel
5760 --------
6265 -------
6366
6467 Some way to make it easier to test reactive programs, i.e.
65 some way to provide mock facilities.
68 some way to provide mock facilities. (Actually, do we need this?
69 Or rather, how reactor-specific is this? You can just test the
70 transducer itself, feeding it a set of mock events.)
71
72 The QuickCheck tests for equivalency don't seem to be very strong. They might
73 even be outright wrong. Generally, lots and lots more could be done with
74 the QuickCheck tests.
75
76 Documentation
77 -------------
78
79 Finish the tutorial (recursive fexprs, advanced usage of reactors).
6680
6781 Reactors
6882 --------
7791 only make sense in the HTML5 DOM.
7892
7993 Subscription and unsubscription from facilities using standard commands.
94
95 More elegant way for handling abort responses (they should not
96 trigger events -- because this can lead to a cascade of abort
97 responses, if the reactor is erroneously handling those events too --
98 but they should be displayed if requested.)
99
100 Allow only one reactor. (Its transducer can be composed from
101 multiple operators, instead of having multiple reactors.)
80102
81103 Example programs
82104 ----------------
00 The following functionalities are used to test Robin Expressions.
11
22 -> Functionality "Evaluate core Robin Expression" is implemented by shell command
3 -> "bin/robin --no-builtins eval %(test-body-file)"
3 -> "bin/robin eval %(test-body-file)"
44
55 -> Functionality "Evaluate Robin Expression (with literal)" is implemented by shell command
6 -> "bin/robin --no-builtins stdlib/literal.robin eval %(test-body-file)"
6 -> "bin/robin stdlib/literal.robin eval %(test-body-file)"
77
8 -> Functionality "Evaluate Robin Expression (with literal and list)" is implemented by shell command
9 -> "bin/robin --no-builtins stdlib/literal.robin stdlib/list.robin eval %(test-body-file)"
8 -> Functionality "Evaluate Robin Expression (with literal and bind)" is implemented by shell command
9 -> "bin/robin stdlib/literal.robin stdlib/bind.robin eval %(test-body-file)"
10
11 -> Functionality "Evaluate Robin Expression (with literal and bind and list)" is implemented by shell command
12 -> "bin/robin stdlib/literal.robin stdlib/bind.robin stdlib/list.robin eval %(test-body-file)"
1013
1114 -> Functionality "Evaluate Robin Expression (with Small)" is implemented by shell command
12 -> "bin/robin --no-builtins pkg/small.robin eval %(test-body-file)"
15 -> "bin/robin pkg/small.robin eval %(test-body-file)"
1316
1417 -> Functionality "Evaluate Robin Expression (with List)" is implemented by shell command
15 -> "bin/robin --no-builtins pkg/small.robin pkg/list.robin eval %(test-body-file)"
18 -> "bin/robin pkg/small.robin pkg/list.robin eval %(test-body-file)"
1619
1720 -> Functionality "Evaluate Robin Expression (with Env)" is implemented by shell command
18 -> "bin/robin --no-builtins pkg/small.robin pkg/list.robin pkg/env.robin eval %(test-body-file)"
21 -> "bin/robin pkg/small.robin pkg/list.robin pkg/env.robin eval %(test-body-file)"
1922
2023 -> Functionality "Evaluate Robin Expression (with Boolean)" is implemented by shell command
21 -> "bin/robin --no-builtins pkg/small.robin pkg/boolean.robin eval %(test-body-file)"
24 -> "bin/robin pkg/small.robin pkg/boolean.robin eval %(test-body-file)"
2225
2326 -> Functionality "Evaluate Robin Expression (with Arith)" is implemented by shell command
24 -> "bin/robin --no-builtins pkg/small.robin pkg/arith.robin eval %(test-body-file)"
27 -> "bin/robin pkg/small.robin pkg/arith.robin eval %(test-body-file)"
2528
2629 -> Functionality "Evaluate Robin Expression (with Stdlib)" is implemented by shell command
27 -> "bin/robin --no-builtins pkg/stdlib.robin eval %(test-body-file)"
30 -> "bin/robin pkg/stdlib.robin eval %(test-body-file)"
2831
2932 The following functionalities are used to test Robin Toplevel programs.
3033
3134 -> Functionality "Execute core Robin Toplevel Program" is implemented by shell command
32 -> "bin/robin --no-builtins %(test-body-file)"
35 -> "bin/robin %(test-body-file)"
3336
3437 -> Functionality "Execute Robin Toplevel Program (with Small)" is implemented by shell command
35 -> "bin/robin --no-builtins pkg/small.robin %(test-body-file)"
38 -> "bin/robin pkg/small.robin %(test-body-file)"
39
40 -> Functionality "Execute Robin Toplevel Program (with Stdlib)" is implemented by shell command
41 -> "bin/robin pkg/stdlib.robin %(test-body-file)"
00 The following functionalities are used to test Robin Expressions.
11
22 -> Functionality "Evaluate core Robin Expression" is implemented by shell command
3 -> "bin/robin --no-builtins eval %(test-body-file)"
3 -> "bin/robin --enable-builtins eval %(test-body-file)"
44
55 -> Functionality "Evaluate Robin Expression (with literal)" is implemented by shell command
6 -> "bin/robin stdlib/literal.robin eval %(test-body-file)"
6 -> "bin/robin --enable-builtins stdlib/literal.robin eval %(test-body-file)"
77
8 -> Functionality "Evaluate Robin Expression (with literal and list)" is implemented by shell command
9 -> "bin/robin stdlib/literal.robin stdlib/list.robin eval %(test-body-file)"
8 -> Functionality "Evaluate Robin Expression (with literal and bind)" is implemented by shell command
9 -> "bin/robin --enable-builtins stdlib/literal.robin stdlib/bind.robin eval %(test-body-file)"
10
11 -> Functionality "Evaluate Robin Expression (with literal and bind and list)" is implemented by shell command
12 -> "bin/robin --enable-builtins stdlib/literal.robin stdlib/bind.robin stdlib/list.robin eval %(test-body-file)"
1013
1114 -> Functionality "Evaluate Robin Expression (with Small)" is implemented by shell command
12 -> "bin/robin eval %(test-body-file)"
15 -> "bin/robin --enable-builtins pkg/small.robin eval %(test-body-file)"
1316
1417 -> Functionality "Evaluate Robin Expression (with List)" is implemented by shell command
15 -> "bin/robin pkg/list.robin eval %(test-body-file)"
18 -> "bin/robin --enable-builtins pkg/list.robin eval %(test-body-file)"
1619
1720 -> Functionality "Evaluate Robin Expression (with Env)" is implemented by shell command
18 -> "bin/robin pkg/list.robin pkg/env.robin eval %(test-body-file)"
21 -> "bin/robin --enable-builtins pkg/list.robin pkg/env.robin eval %(test-body-file)"
1922
2023 -> Functionality "Evaluate Robin Expression (with Boolean)" is implemented by shell command
21 -> "bin/robin pkg/boolean.robin eval %(test-body-file)"
24 -> "bin/robin --enable-builtins pkg/boolean.robin eval %(test-body-file)"
2225
2326 -> Functionality "Evaluate Robin Expression (with Arith)" is implemented by shell command
24 -> "bin/robin pkg/arith.robin eval %(test-body-file)"
27 -> "bin/robin --enable-builtins pkg/arith.robin eval %(test-body-file)"
2528
2629 -> Functionality "Evaluate Robin Expression (with Stdlib)" is implemented by shell command
27 -> "bin/robin pkg/stdlib.robin eval %(test-body-file)"
30 -> "bin/robin --enable-builtins pkg/stdlib.robin eval %(test-body-file)"
2831
2932 The following functionalities are used to test Robin Toplevel programs.
3033
3134 -> Functionality "Execute core Robin Toplevel Program" is implemented by shell command
32 -> "bin/robin --no-builtins %(test-body-file)"
35 -> "bin/robin --enable-builtins %(test-body-file)"
3336
3437 -> Functionality "Execute Robin Toplevel Program (with Small)" is implemented by shell command
35 -> "bin/robin %(test-body-file)"
38 -> "bin/robin --enable-builtins %(test-body-file)"
39
40 -> Functionality "Execute Robin Toplevel Program (with Stdlib)" is implemented by shell command
41 -> "bin/robin --enable-builtins pkg/stdlib.robin %(test-body-file)"
44 mkdir -p pkg
55
66 cat stdlib/abort.robin stdlib/equal-p.robin stdlib/eval.robin stdlib/head.robin \
7 stdlib/if.robin stdlib/list-p.robin stdlib/macro-p.robin stdlib/macro.robin \
7 stdlib/if.robin stdlib/list-p.robin stdlib/operator-p.robin stdlib/fexpr.robin \
88 stdlib/number-p.robin stdlib/prepend.robin stdlib/recover.robin stdlib/sign.robin \
99 stdlib/subtract.robin stdlib/symbol-p.robin stdlib/tail.robin \
1010 > pkg/intrinsics.robin
1111
12 cat stdlib/literal.robin stdlib/env.robin stdlib/list.robin stdlib/bind.robin \
13 stdlib/let.robin stdlib/choose.robin \
12 cat stdlib/literal.robin stdlib/env.robin stdlib/bind.robin \
13 stdlib/list.robin stdlib/let.robin stdlib/choose.robin \
1414 stdlib/bind-args.robin \
1515 stdlib/fun.robin > pkg/small.robin
1616
3232 cat stdlib/env-p.robin stdlib/bound-p.robin stdlib/export.robin stdlib/sandbox.robin \
3333 stdlib/unbind.robin stdlib/unshadow.robin > pkg/env.robin
3434
35 cat stdlib/itoa.robin > pkg/misc.robin
35 cat stdlib/itoa.robin stdlib/bind-vals.robin > pkg/misc.robin
3636
3737 cat pkg/small.robin \
3838 pkg/boolean.robin \
0 Detecting Errors in Robin
1 =========================
2
3 This is a somewhat philosophical write-up that overlaps with the
4 [Design Goals and Rationale](Rationale.md) document to some degree.
5
6 A Sea of Parentheses
7 --------------------
8
9 The thing about lispoids is that all those parentheses _do_ make things hard to read and hard to edit.
10 Take these two Scheme expressions, for instance:
11
12 (list (+ 1 2) 3)
13
14 versus
15
16 (list (+ 1 2 3))
17
18 They're both syntactically correct, but they have rather different meanings, and the
19 difference is subtle. Deep in a sea of parentheses, the shifted parentheses might be
20 hard to spot. Your only clue might be a error message from somewhere higher up in
21 the code, that an attempt to apply `car` failed. Hunting it down to this shifted
22 parenthesis can be taxing.
23
24 This problem is a particularly bad problem in Robin, for the following reasons.
25
26 Robin has, as an ideal, that there should be as little done statically as possible. The static
27 phase is: parse an S-expression, get it into a data structure. That's all. The rest is up
28 to evaluating that data structure.
29
30 There aren't even any "special forms". The data structure you get might not be well-formed.
31 You might get `(bind a)`. You can have "syntax errors" at runtime.
32
33 Even worse, Robin has, as an ideal, not to do much extra work at runtime either. Meaning
34 that, some syntax oddities aren't checked for, they're just ignored.
35
36 In particular, maybe you call a fexpr with more arguments than it can understand. Say,
37
38 (bind a 1 (foo a) z)
39
40 In this case the `z` is ignored.
41
42 This is compounded by, maybe you said
43
44 (bar (bind a 1 (foo a) z))
45
46 but maybe you meant
47
48 (bar (bind a 1 (foo a)) z)
49
50 and maybe `bar` doesn't complain when you only give it one argument either. Maybe its
51 second argument is optional and giving it just changes the behaviour slightly.
52
53 Deep in a sea of parentheses these differences can be hard to find.
54
55 Addressing the problem
56 ----------------------
57
58 How do we approach this problem?
59
60 First, we recognize it. That's why I'm writing this.
61
62 Second, we accept it as the price we have to pay for having a set of rules that are
63 this simple.
64
65 Third, we consider whether we want to pay that price, or if there is some other way
66 to achieve those ends, and if we want to pay that price instead.
67
68 To that end we might want to examine why Robin has the ideals that it does.
69
70 It's been said that Lisp isn't a programming language, it's a building material.
71 Under that view, Robin isn't trying to be a better programming language; it's
72 trying to be a more building-material-like building material.
73
74 As little as possible is done statically because we want to give the programmer the
75 choice of what static checking is or is not done. By default, as little as possible
76 is done, and the programmer can add to that, by writing their own static analyzers,
77 if they like.
78
79 And we want to not do much extra work at runtime for essentially the same reason:
80 we want to give the programmer the choice of what checking is or is not done.
81 By default, as little as possible is done, and the programmer can add to it by,
82 for example, defining and using argument-checking wrappers, if they like.
83
84 Each of these goals by itself is fine. It's that putting them together doesn't
85 work very well.
86
87 In order to live with them together we need to start giving the Robin programmer
88 tools to do this checking that we're leaving up to them.
89
90 But that is *also* a particularly difficult problem, because Robin is based on
91 fexprs.
92
93 Static analysis of fexprs
94 -------------------------
95
96 It has been noted many times that fexprs are hard to statically
97 analyze. "Hard" isn't even the right word: they have a trivial
98 equational theory. In less formal terms, if I'm a static
99 analyzer and you give me a fexpr, then I'm like, what do I do
100 with this? I can't even tell which arguments are going to get
101 evaluated and which ones aren't, so how do I analyze what's in
102 the arguments?
103
104 Robin's approach to addressing this, which may or may not be a good one,
105 is to annotate operator values with some metadata which describes
106 what happens to the arguments, so that there is some chance of analyzing it.
107
108 Robin uses fexprs in a _definitional_, rather than _implementational_,
109 way. It is expected that most fexprs will be defined in the standard
110 library. So at the very least we can advertise behaviour like,
111 "this operator is the same as `bind` in the standard library, so
112 if you're an analyzer, you should treat it like that".
113
114 But not just specific operators in the standard library:
115 any operator defined with `fun` will come with some metadata
116 that says "This operator takes _n_ arguments and evaluates all
117 of them".
118
119 In a sense, we are supplanting the trivial equational theory of
120 fexprs, with our own non-trivial home-cooked equational theory that a
121 static analyzer can make use of.
122
123 A static analyzer is passed an environment, containing that value
124 and its metadata; when it sees the name that maps to that value
125 in the environment, it looks at the metadata and knows a bit more
126 how to analyze the arguments it sees.
127
128 This metadata would also be a good place for such an analyzer to
129 store information it deduces, such as types.
130
131 How exactly that information should be represented in the metadata
132 is still an open question. In some sense it is very similar to early
133 Lisps associating a property list with each symbol. But I think
134 ultimately it could be far more general, e.g. the metadata for an
135 operator could faithfully capture the complete axiomatic or
136 algebraic semantics of that operator, which could be used in a
137 proof. But that's a long way off.
138
139 The paper "Special Forms in Lisp" advocates removing fexprs from
140 the Lisp language and using macros instead.
141
142 In Robin, a macro can be defined as a kind of fexpr whose return value
143 does not get evaluated. This is easier for a static analyzer to
144 analyzer: it just expands the macro and analyzes that.
145
146 Because it is defined as a kind of fexpr in Robin, and that fexpr will
147 evaluate the value the macro returns (because to execute it, something
148 needs to), to support static analysis, the macro will need to produce
149 an annotation that only expands the macro, without evaluating the
150 result of the expansion. Such "expand-macro" annotation could also
151 be used by compilers or other tools which do not need to immediately
152 evaluate the macro, but rather want to know how it transforms the code.
153
154 Static analysis as a library
155 ----------------------------
156
157 Because Robin lets you write fexprs, it lets you write fexprs which
158 take a piece of code, check that piece of code, and if the code passes
159 the check, evaluates that piece of code, or otherwise evaluates to
160 an abort value.
161
162 In other words, you can write static analyzers as fexprs.
163
164 Such static analyzer fexprs should be available in the standard
165 library, i.e. there should be standard static analyses available.
166
167 The first such analyzer should simply check the arity of all the
168 applied operators in an expression.
169
170 That would begin to address the problem of shifted parentheses that was
171 brought up at the beginning of this document.
33 * [Robin Tutorial](Tutorial.md)
44 * [Robin Specification](Robin.md)
55 * [Design Goals and Rationale](Rationale.md)
6 * [Detecting Errors in Robin](Errors.md)
67
78 For normative definitions of the symbols in the standard library,
89 see the definition files in the [stdlib directory](../stdlib/).
2424
2525 It started off as an idea for how I would like to design an
2626 operating system based on [Pixley][]. It is no longer an
27 operating system design, but the reactive portion of it is
27 operating system design, but the event-driven portion of it is
2828 still reminiscent of that.
2929
3030 Note that, recently (2020), I wrote up a list of
9595 "the world's finest imperative language". I'm looking
9696 for the world's finest functional language though, right?)
9797
98 Macro as fundamental abstraction
98 Fexpr as fundamental abstraction
9999 --------------------------------
100100
101101 This is certainly the most unorthodox feature, the one that departs
105105
106106 It allows the language to have no "special forms" whatsoever.
107107 (Scheme would need at least `define-syntax` if it wanted to define
108 `if`, `set!`, and the other parts of its syntax, as macros.)
108 `if`, `set!`, and the other parts of its syntax, as fexprs.)
109109
110110 Whether having no special forms whatsoever is advantageous in any
111111 way, or not, remains to be seen.
112112
113113 One upshot is that any functionality expressible in the Robin
114 expression language, can be passed to a macro or a function, as
115 a parameter, or returned from a macro or function evaluation.
114 expression language, can be passed to a fexpr or macro as
115 a parameter, or returned from an operator application.
116116
117117 One also thinks it might make analysis of the code simpler — a
118118 parser or analyzer doesn't need to account for any special forms.
119119
120 But, in practice, since everything is a macro, `eval` is called a
120 But, in practice, since everything is a fexpr, `eval` is called a
121121 lot, and `eval` poses a significant problem for analysis.
122122
123123 But also in practice, an analysis tool will expect that the "small"
124124 library has been loaded, and that function calls will use `fun`
125125 as defined there, and thus can base their analysis on the semantics
126 of that macro without caring about its definition, or that its
126 of that fexpr without caring about its definition, or that its
127127 definition contains `eval`.
128128
129129 So the basic saving grace here is this: we can *define* the
130 forms of the language using macros without necessarily *implementing*
131 them using `macro`. As long as the implementation's behaviour
132 matches what the `macro` version specifies, it's compatible
130 forms of the language using fexprs without necessarily *implementing*
131 them using `fexpr`. As long as the implementation's behaviour
132 matches what the `fexpr` version specifies, it's compatible
133133 behaviour and thus an allowable implementation.
134134
135135 No variable numbers of parameters
221221 that (should) have a (relatively) fixed meaning in all Robin
222222 programs, whether they are used in any given program or not.
223223
224 * Note (that should be elsewhere?): most of the macros defined
224 * Note (that should be elsewhere?): most of the operators defined
225225 in `stdlib` are supposed to, intentionally, take a fixed number
226226 of arguments for some reason (nominally, to make some kind of
227227 future static analysis easier.)
348348 prepend
349349 list?
350350 symbol?
351 macro?
351 operator?
352352 number?
353353 equal?
354354 subtract
355355 sign
356 macro
356 fexpr
357357 eval
358358 if
359359 abort
00 Robin
11 =====
22
3 This document defines version 0.7 of the Robin programming language.
3 This document defines version 0.8 of the Robin programming language.
44 In this document, the name "Robin" by itself refers to the Robin
5 programming language version 0.7.
5 programming language version 0.8.
66
77 The Robin specification is modular in the sense that it consists
88 of several smaller specifications, some of which depend on others,
6161 Such a form is parsed as a conventional string data type (see
6262 the "String" section in the Robin Expression Language for details.)
6363
64 | (define literal (macro (s a e) (head a)))
64 | (define literal (fexpr (args env) (head args)))
6565 | (display
6666 | (literal ''Hello''))
6767 = (72 101 108 108 111)
6868
6969 A single single quote may appear in string literals of this kind.
7070
71 | (define literal (macro (s a e) (head a)))
71 | (define literal (fexpr (args env) (head args)))
7272 | (display
7373 | (literal ''He'llo''))
7474 = (72 101 39 108 108 111)
7878 match the sentinel given between the trailing single quote pair. The
7979 sentinel may consist of any text not containing a single quote.
8080
81 | (define literal (macro (s a e) (head a)))
81 | (define literal (fexpr (args env) (head args)))
8282 | (display
8383 | (literal 'X'Hello'X'))
8484 = (72 101 108 108 111)
8585
86 | (define literal (macro (s a e) (head a)))
86 | (define literal (fexpr (args env) (head args)))
8787 | (display
8888 | (literal '...('Hello'...('))
8989 = (72 101 108 108 111)
9090
91 | (define literal (macro (s a e) (head a)))
91 | (define literal (fexpr (args env) (head args)))
9292 | (display
9393 | (literal 'X'Hello'Y'))
9494 ? unexpected end of input
9595
9696 A sentinelized literal like this may embed a pair of single quotes.
9797
98 | (define literal (macro (s a e) (head a)))
98 | (define literal (fexpr (args env) (head args)))
9999 | (display
100100 | (literal 'X'Hel''lo'X'))
101101 = (72 101 108 39 39 108 111)
103103 By choosing different sentinels, string literals may contain any other
104104 string literal.
105105
106 | (define literal (macro (s a e) (head a)))
106 | (define literal (fexpr (args env) (head args)))
107107 | (display
108108 | (literal 'X'Hel'Y'bye'Y'lo'X'))
109109 = (72 101 108 39 89 39 98 121 101 39 89 39 108 111)
112112 (Functions to convert escape sequences commonly found in other languages
113113 may one day be available in a standard module.)
114114
115 | (define literal (macro (s a e) (head a)))
115 | (define literal (fexpr (args env) (head args)))
116116 | (display
117117 | (literal ''Hello\nworld''))
118118 = (72 101 108 108 111 92 110 119 111 114 108 100)
120120 All characters which appear in the source text between the delimiters
121121 of the string literal are literally included in the string.
122122
123 | (define literal (macro (s a e) (head a)))
123 | (define literal (fexpr (args env) (head args)))
124124 | (display
125125 | (literal ''Hello
126126 | world''))
128128
129129 Adjacent string literals are not automatically concatenated.
130130
131 | (define literal (macro (s a e) (head a)))
131 | (define literal (fexpr (args env) (head args)))
132132 | (display
133133 | (literal (''Hello'' ''world'')))
134134 = ((72 101 108 108 111) (119 111 114 108 100))
223223 * A symbol is a term.
224224 * A boolean is a term.
225225 * An integer is a term.
226 * A macro is a term.
226 * An operator is a term.
227227 * An empty list is a term.
228228 * A list cell containing a term, prepended to another list
229229 (which may be empty), is a term.
255255 For a symbol to appear unevaluated in a Robin program, it must be
256256 introduced as a literal. However, there is no intrinsic way to do this,
257257 so in order to demonstrate it, we must use something we haven't
258 covered yet: a macro. We'll just go ahead and show the example, and
259 will explain macros later.
260
261 | ((macro (s a e) (head a)) hello)
258 covered yet: an operator. We'll just go ahead and show the example, and
259 will explain operators later.
260
261 | ((fexpr (a e) (head a)) hello)
262262 = hello
263263
264264 A Robin implementation is not expected to be able to generate new symbols
271271
272272 There are two values of Boolean type, `#t`, representing truth, and `#f`,
273273 representing falsehood. By convention, an identifier which ends in `?`
274 is a macro or function which evaluates to a Boolean. The `if` intrinsic
274 denotes an operator which evaluates to a Boolean. The `if` intrinsic
275275 expects a Boolean expression as its first argument.
276276
277277 Booleans always evaluate to themselves.
311311 | (900 1 2 3)
312312 ? (abort (inapplicable-object 900))
313313
314 ### Macros ###
315
316 A macro is a term which, in an environment, describes how to
317 translate one S-expression to another.
314 ### Operators ###
315
316 An operator is a term which describes how to translate a given term to
317 another term, in a given environment.
318318
319319 One area where Robin diverges significantly from Lisp and Scheme is that,
320 whereas Lisp and Scheme support macro capabilities, in Robin, the macro
321 is a **fundamental type**. Other abstractions, such as function values, are
322 built on top of macros. Macros are "first-class" objects that may exist
323 at runtime and can evaluate to other macros. Therefore, the word "macro"
324 has, perhaps, a slightly different meaning in Robin than in Lisp or Scheme.
325
326 They can also be compared to the one-argument `lambda` form from PicoLisp;
327 again, however, unlike PicoLisp's variety of `lambda` forms, Robin's
328 macros are the *only* abstraction of this kind fundamentally available, and
329 other such abstractions *must* be built on top of macros.
330
331 Whereas a function evaluates each of its arguments to values, and
320 whereas only some (older and more fringe) Lisps support fexprs, in Robin,
321 `fexpr` is the fundamental way to define new operators. Other ways to
322 define operators, such as functions and macros, are built on top of `fexpr`.
323
324 A conventional function evaluates each of its arguments to values, and
332325 binds each of those values to a formal parameter of the function, then
333 evaluates the body of the function in that new environment, a macro:
334
335 * binds the macro value itself to the first formal parameter of the
336 macro (by convention called `self`) — this is to facilitate writing
337 recursive macros;
338 * binds the literal tail of the list of the macro application to
339 the second formal parameter of the macro (by convention called `args`);
326 evaluates the body of the function in that new environment. In contrast,
327 an operator defined by `fexpr`:
328
329 * binds the literal tail of the list of the operator application to
330 the first formal parameter of the `fexpr` (by convention called `args`);
340331 * binds a binding alist representing the environment in effect at the
341 point the macro was evaluated to the third formal parameter (by
332 point the operator was evaluated to the second formal parameter (by
342333 convention called `env`); and
343 * evaluates the body of the macro in that environment.
344
345 Macros are defined with the `macro` intrinsic.
346
347 Macros evaluate to themselves.
348
349 Macros are represented as the S-expression expansion of their
350 implementation.
351
352 | (macro (self args env) args)
353 = (macro (self args env) args)
354
355 Macros can be applied, and that is the typical use of them.
356
357 | ((macro (self args env) args) 1)
334 * evaluates the body of the `fexpr` in that environment.
335
336 Operators are either intrinsic, or are defined with the `fexpr` intrinsic.
337 (They may be built-in to an implementation as well, but they still need to
338 be given a definition in terms of `fexpr` in any case.)
339
340 Operators evaluate to themselves.
341
342 Operators are represented as an opaque descriptor (TODO this should be the
343 metadata of the operator.)
344
345 | (fexpr (args env) args)
346 = <operator>
347
348 Operators can be applied, and that is the typical use of them.
349
350 | ((fexpr (args env) args) 1)
358351 = (1)
359352
360353 ### Lists ###
378371 elements, preceded by a `(`, followed by a `)`, and delimited
379372 by whitespace.
380373
381 Non-empty lists do not evaluate to themselves; rather, they represent a macro
382 application. However, the `literal` macro (whose definition is
383 `(macro (s a e) (head a))`) may be used to obtain a literal list.
384
385 | ((macro (s a e) (head a)) (7 8)))
374 Non-empty lists do not evaluate to themselves; rather, they represent an
375 application of an operator to zero or more arguments. However, the
376 `literal` operator (whose definition is `(fexpr (args env) (head args))`)
377 may be used to obtain a literal list.
378
379 | ((fexpr (args env) (head args)) (7 8)))
386380 = (7 8)
387381
388382 Lists cannot be directly applied, but since a list itself represents an
443437 of an evaluation environment, where the symbols in the heads of the sublists
444438 are bound to the values in the tails of the pairs. Binding alists can be
445439 created from the environment currently in effect (such as in the case of the
446 third argument of a macro) and can be used to change the evaluation
440 second argument of a fexpr) and can be used to change the evaluation
447441 environment that is in effect (such as in the first argument to `eval`.)
448442
449443 (d) Standard Environments
462456
463457 ### Intrinsics ###
464458
465 Robin provides 15 intrinsics. These represent the fundamental functionality
466 that is used to evaluate programs, and that cannot be expressed as macros
467 written in Robin (not without resorting to meta-circularity, at any rate.)
468 All other macros are built up on top of the intrinsics.
459 Robin provides 15 intrinsic operators. These represent the fundamental
460 functionality that is used to evaluate programs, and that cannot be expressed
461 as fexprs written in Robin (not without resorting to meta-circularity, at any
462 rate.) All other operators are built up on top of the intrinsics.
469463
470464 This set of intrinsics is not optional — every Robin implementation must
471465 provide them, or it's not Robin.
472466
473 One important intrinsic is `eval`. Many macros will make use of `eval`,
467 One important intrinsic is `eval`. Many fexprs will make use of `eval`,
474468 to evaluate the literal args they receive. When they do this in the
475469 environment in which they were called, they behave a lot like functions.
476470 But they are not obligated to; they might evaluate them in a modified
477471 environment, or not evaluate them at all and treat them as a literal
478472 S-expression.
479473
480 Macros that are defined intrinsically does not support every operation
481 that defined macro support; for example, they do not support examining
482 their internals. The canonical representation of an intrinsic is the name
483 its bound to.
474 The canonical representation of an intrinsic operator is the canonical
475 name, to which it is bound in the standard environment. (TODO: this will
476 likely change when operators get metadata.)
484477
485478 | head
486479 = head
487480
488 All parts of the Robin Expression Language, including intrinsics,
481 All parts of the Robin Expression Language, including intrinsic operators,
489482 can be passed around as values.
490483
491484 | (prepend if (prepend head ()))
492485 = (if head)
493
494 All of the 15 intrinsics are macros, but there is nothing ontologically
495 requiring an intrinsic to be a value of macro type.
496486
497487 Each of the 15 intrinsics provided by Robin is specified in its own file
498488 in the standard library. Because these are intrinsics, no Robin
506496 * [head](../stdlib/head.robin)
507497 * [if](../stdlib/if.robin)
508498 * [list?](../stdlib/list-p.robin)
509 * [macro?](../stdlib/macro-p.robin)
510 * [macro](../stdlib/macro.robin)
499 * [fexpr](../stdlib/fexpr.robin)
511500 * [number?](../stdlib/number-p.robin)
501 * [operator?](../stdlib/operator-p.robin)
512502 * [prepend](../stdlib/prepend.robin)
513503 * [sign](../stdlib/sign.robin)
514504 * [subtract](../stdlib/subtract.robin)
521511 all but the most austere Robin programs would like to be built on.
522512
523513 * [literal](../stdlib/literal.robin)
514 * [env](../stdlib/env.robin)
515 * [bind](../stdlib/bind.robin)
524516 * [list](../stdlib/list.robin)
525 * [bind](../stdlib/bind.robin)
526 * [env](../stdlib/env.robin)
527517 * [let](../stdlib/let.robin)
528518 * [choose](../stdlib/choose.robin)
529519 * [bind-args](../stdlib/bind-args.robin)
520 * [fun](../stdlib/fun.robin)
530521
531522 ### Standard Library ###
532523
538529 take-while drop-while first rest last prefix? flatten
539530 * *alist*: lookup extend delete
540531 * *env*: env? bound? export sandbox unbind unshadow
541 * *arith*: abs add > >= < <= multiply divide remainder
532 * *arith*: abs add gt? gte? lt? lte? multiply divide remainder
542533 * *misc*: itoa
543534
544535 Part 2. Robin Toplevel Language
672663 is perhaps not the best example.)
673664
674665 | (define true #t)
675 | (define true ((macro (self args env) #t)))
666 | (define true ((fexpr (args env) #t)))
676667 | (display true)
677668 = #t
678669
692683 ### `reactor` ###
693684
694685 `(reactor LIST-OF-SYMBOLS STATE-EXPR BODY-EXPR)` installs a reactor. Reactors
695 permit the construction of reactive Robin programs. See the
696 [Reactors](#reactors) section for more information on reactors.
686 permit the construction of Robin programs that interact with their environment.
687 See the [Reactors](#reactors) section for more information on reactors.
697688
698689 Part 3. Robin Reactors
699690 ----------------------
703694 To separate the concerns of computation and interaction, Robin provides
704695 a construct called a _reactor_. While evaluation of a Robin expression
705696 accomplishes side-effect-free computation, reactors permit the construction
706 of so-called "reactive" programs, which are driven by events and may
707 interact with a user, a remote server on a network, or other source of
708 events. Reactors are similar to event handlers in Javascript, or to
709 processes in Erlang.
697 of event-driven programs which may interact with a user, a remote server on
698 a network, or other source of events, while they are executing. Reactors
699 are similar to event handlers in Javascript, or to processes in Erlang.
710700
711701 In Robin, a reactor is installed by giving a top-level form with the
712702 following syntax:
720710 The second argument is evaluated, and becomes the _initial state_ of the
721711 reactor.
722712
723 The third argument of the `reactor` form is evaluated to obtain a
724 macro. This is called the _transducer_ of the reactor.
713 The third argument of the `reactor` form is evaluated to obtain an
714 operator. This is called the _transducer_ of the reactor.
725715
726716 Whenever an event of interest to the reactor occurs, the transducer is
727717 evaluated, being passed two (pre-evaluated) arguments:
794784
795785 #### `line-terminal` ####
796786
797 -> Tests for functionality "Execute Robin Toplevel Program (with Small)"
787 -> Tests for functionality "Execute Robin Toplevel Program (with Stdlib)"
798788
799789 The `line-terminal` facility allows a Robin program to interact with
800790 something or someone over a line-oriented protocol, similar to what
826816 the integer 0, and the state is set to 0 after each event is reacted to.
827817
828818 | (reactor (line-terminal) 0
829 | (macro (self args env)
830 | (bind event (head args)
831 | (bind event-type (head event)
832 | (if (equal? event-type (literal init))
833 | (list 0
834 | (list (literal writeln) (literal ''Hello, world!''))
835 | (list (literal stop) 0))
836 | (list 0))))))
819 | (fexpr (args env)
820 | (bind-vals ((event-type event-payload) state) args
821 | (if (equal? event-type (literal init))
822 | (list state
823 | (list (literal writeln) (literal ''Hello, world!''))
824 | (list (literal stop) 0))
825 | (list state)))))
837826 = Hello, world!
838827
839828 Reactors which interact with `line-terminal` receive `readln` events.
846835 Thus we can construct a simple `cat` program:
847836
848837 | (reactor (line-terminal) 0
849 | (macro (self args env)
850 | (bind event (head args)
851 | (bind event-type (head event)
852 | (bind event-payload (head (tail event))
853 | (if (equal? event-type (literal readln))
854 | (list 0
855 | (list (literal writeln) event-payload))
856 | (list 0)))))))
838 | (fexpr (args env)
839 | (bind-vals ((event-type event-payload) state) args
840 | (if (equal? event-type (literal readln))
841 | (list state
842 | (list (literal writeln) event-payload))
843 | (list state)))))
857844 + Cat
858845 + Dog
859846 = Cat
881868 A reactor can issue multiple commands in its response to an event.
882869
883870 | (reactor (line-terminal) 0
884 | (macro (self args env)
885 | (bind event (head args)
886 | (bind event-type (head event)
887 | (bind event-payload (head (tail event))
888 | (if (equal? event-type (literal readln))
889 | (list 0
890 | (list (literal writeln) (literal ''Line:''))
891 | (list (literal writeln) event-payload))
892 | (list 0)))))))
871 | (fexpr (args env)
872 | (bind-vals ((event-type event-payload) state) args
873 | (if (equal? event-type (literal readln))
874 | (list state
875 | (list (literal writeln) (literal ''Line:''))
876 | (list (literal writeln) event-payload))
877 | (list state)))))
893878 + Cat
894879 + Dog
895880 = Line:
901886 message of some kind, but it should otherwise ignore it and keep going.
902887
903888 | (reactor (line-terminal) 0
904 | (macro (self args env)
905 | (bind event (head args)
906 | (bind event-type (head event)
907 | (bind event-payload (head (tail event))
908 | (if (equal? event-type (literal readln))
909 | (list 0
910 | (literal what-is-this)
911 | (literal i-dont-even)
912 | (list (literal writeln) event-payload))
913 | (list 0)))))))
889 | (fexpr (args env)
890 | (bind-vals ((event-type event-payload) state) args
891 | (if (equal? event-type (literal readln))
892 | (list state
893 | (literal what-is-this)
894 | (literal i-dont-even)
895 | (list (literal writeln) event-payload))
896 | (list state)))))
914897 + Cat
915898 + Dog
916899 = Cat
925908 but this is not a strict requirement.
926909
927910 | (reactor (line-terminal) 0
928 | (macro (self args env)
929 | (bind event (head args)
930 | (bind event-type (head event)
931 | (bind event-payload (head (tail event))
932 | (if (equal? event-type (literal readln))
933 | (if (equal? (head event-payload) 65)
934 | (abort 999999)
935 | (list 0 (list (literal writeln) event-payload)))
936 | (list 0)))))))
911 | (fexpr (args env)
912 | (bind-vals ((event-type event-payload) state) args
913 | (if (equal? event-type (literal readln))
914 | (if (equal? (head event-payload) 65)
915 | (abort 999999)
916 | (list state (list (literal writeln) event-payload)))
917 | (list state)))))
937918 + Cat
938919 + Dog
939920 + Alligator
944925
945926 Reactors can keep state.
946927
947 | (define inc (macro (self args env)
928 | (define inc (fexpr (args env)
948929 | (subtract (eval env (head args)) (subtract 0 1))))
949930 | (reactor (line-terminal) 65
950 | (macro (self args env)
951 | (bind state (head (tail args))
952 | (bind event (head args)
953 | (bind event-type (head event)
954 | (bind event-payload (head (tail event))
955 | (if (equal? event-type (literal readln))
956 | (list (inc state) (list (literal writeln) (list state)))
957 | (list state))))))))
931 | (fexpr (args env)
932 | (bind-vals ((event-type event-payload) state) args
933 | (if (equal? event-type (literal readln))
934 | (list (inc state) (list (literal writeln) (list state)))
935 | (list state)))))
958936 + Cat
959937 + Dog
960938 + Giraffe
965943 Multiple reactors can be instantiated, will react to the same events.
966944 Note that reactors react in the *opposite* order they were installed.
967945
968 | (define inc (macro (self args env)
946 | (define inc (fexpr (args env)
969947 | (subtract (eval env (head args)) (subtract 0 1))))
970948 | (reactor (line-terminal) 65
971 | (macro (self args env)
972 | (bind state (head (tail args))
973 | (bind event (head args)
974 | (bind event-type (head event)
975 | (bind event-payload (head (tail event))
976 | (if (equal? event-type (literal readln))
977 | (list (inc state) (list (literal writeln) (list state)))
978 | (list state))))))))
949 | (fexpr (args env)
950 | (bind-vals ((event-type event-payload) state) args
951 | (if (equal? event-type (literal readln))
952 | (list (inc state) (list (literal writeln) (list state)))
953 | (list state)))))
979954 | (reactor (line-terminal) 0
980 | (macro (self args env)
981 | (bind event (head args)
982 | (bind event-type (head event)
983 | (bind event-payload (head (tail event))
984 | (if (equal? event-type (literal readln))
985 | (list 0
986 | (list (literal writeln) event-payload))
987 | (list 0)))))))
955 | (fexpr (args env)
956 | (bind-vals ((event-type event-payload) state) args
957 | (if (equal? event-type (literal readln))
958 | (list state
959 | (list (literal writeln) event-payload))
960 | (list state)))))
988961 + Cat
989962 + Dog
990963 + Giraffe
997970
998971 A reactor can stop by issuing a `stop` command.
999972
1000 | (define inc (macro (self args env)
973 | (define inc (fexpr (args env)
1001974 | (subtract (eval env (head args)) (subtract 0 1))))
1002975 | (reactor (line-terminal) 65
1003 | (macro (self args env)
1004 | (bind state (head (tail args))
1005 | (bind event (head args)
1006 | (bind event-type (head event)
1007 | (bind event-payload (head (tail event))
1008 | (if (equal? event-type (literal readln))
1009 | (if (equal? state 68)
1010 | (list state (list (literal stop) 0))
1011 | (list (inc state) (list (literal writeln) event-payload)))
1012 | (list state))))))))
976 | (fexpr (args env)
977 | (bind-vals ((event-type event-payload) state) args
978 | (if (equal? event-type (literal readln))
979 | (if (equal? state 68)
980 | (list state (list (literal stop) 0))
981 | (list (inc state) (list (literal writeln) event-payload)))
982 | (list state)))))
1013983 + Cat
1014984 + Dog
1015985 + Giraffe
1021991
1022992 Stopping one reactor does not stop others.
1023993
1024 | (define inc (macro (self args env)
994 | (define inc (fexpr (args env)
1025995 | (subtract (eval env (head args)) (subtract 0 1))))
1026996 | (reactor (line-terminal) 65
1027 | (macro (self args env)
1028 | (bind state (head (tail args))
1029 | (bind event (head args)
1030 | (bind event-type (head event)
1031 | (bind event-payload (head (tail event))
1032 | (if (equal? event-type (literal readln))
1033 | (if (equal? state 68)
1034 | (list state (list (literal stop) 0))
1035 | (list (inc state) (list (literal writeln) event-payload)))
1036 | (list state))))))))
997 | (fexpr (args env)
998 | (bind-vals ((event-type event-payload) state) args
999 | (if (equal? event-type (literal readln))
1000 | (if (equal? state 68)
1001 | (list state (list (literal stop) 0))
1002 | (list (inc state) (list (literal writeln) event-payload)))
1003 | (list state)))))
10371004 | (reactor (line-terminal) 65
1038 | (macro (self args env)
1039 | (bind state (head (tail args))
1040 | (bind event (head args)
1041 | (bind event-type (head event)
1042 | (bind event-payload (head (tail event))
1043 | (if (equal? event-type (literal readln))
1044 | (list (inc state) (list (literal writeln) (list state)))
1045 | (list state))))))))
1005 | (fexpr (args env)
1006 | (bind-vals ((event-type event-payload) state) args
1007 | (if (equal? event-type (literal readln))
1008 | (list (inc state) (list (literal writeln) (list state)))
1009 | (list state)))))
10461010 + Cat
10471011 + Dog
10481012 + Giraffe
10701034
10711035 It is not really recommended to implement a system with multiple
10721036 reactors. It is better to compose a single large reactor out of
1073 multiple macros.
1037 multiple operators.
10741038
10751039 But currently we allow it, so we should say some words about it.
10761040
0 A Short Robin Tutorial
1 ======================
2
3 This document will lead you through writing a few simple Robin programs.
4
5 We will assume you've written some programs in a Lisp-like language
6 such as Scheme or Racket or [Irken][]. If you haven't, it will probably
7 help a lot if you work through one of the many excellent tutorials
8 available for these languages.
0 A Robin Tutorial
1 ================
2
3 This tutorial will lead you through writing a few simple Robin programs.
4
5 This document is aimed at programmers who have written some programs in a
6 Lisp-like language such as Scheme or Racket or [Irken][]. If you haven't,
7 it will probably help a lot if you work through one of the many excellent
8 tutorials available for these languages first. This tutorial will
9 concentrate on the more unusual features of Robin, the ones that are not
10 shared with many other languages.
911
1012 [Irken]: https://github.com/samrushing/irken-compiler/blob/master/docs/intro.md
1113
5052
5153 7
5254
53 As of this version of Robin, there are only 4 kinds of top-level
54 forms:
55 As of this version of Robin, there are five kinds of top-level forms:
5556
5657 * `display` evaluates an expression and outputs the result.
5758 * `define` evaluates an expression and assigns a name to it.
5859 * `assert` evaluates an expression and causes the interprter
5960 to abort with a message, if the expression does not evaluate
6061 to `#t` (true).
62 * `require` behaves like `assert`, but intends to signal that
63 a particular symbol needs to have been given a meaning.
6164 * `reactor` evaluates some expressions, creates a reactor
6265 based on them, and installs it.
6366
6568 -----------------------------------
6669
6770 Expressions in Robin are based on a very simple definition, in which there
68 are only 15 intrinsic operations. `subtract` is one such intrinsic
69 operation. `add`, by contrast, is not an intrinsic. If you create a program
71 are only 15 intrinsic operators. [`subtract`][] is one such intrinsic
72 operator. [`add`][], by contrast, is not an intrinsic operator. If you create
73 a program
7074
7175 (define one 1)
7276 (display (add 8 one))
7781
7882 you'll get an error like
7983
80 Main.hs: uncaught exception: (unbound-identifier add)
84 (abort (unbound-identifier add))
8185
8286 However, Robin does come with a standard library of operations, which are
8387 defined in terms of intrinsics. You need to tell Robin to load this standard
96100
97101 (define fact (fun (self n)
98102 (multiply n
99 (if (> n 1)
103 (if (gt? n 1)
100104 (self self (subtract n 1))
101105 1))))
102106 (display (fact fact 5))
103
104 `multiply` is not an intrinsic, so you'll need to run this with
105
106 bin/robin pkg/stdlib.robin fact.robin
107107
108108 Some of this definition is probably what you would expect from a
109109 recursive definition of factorial in any Lisp-like language.
124124 Builtins
125125 --------
126126
127 Even though they are defined in Robin, parts of the Standard Library
128 are often implemented in some other language. This is because their
129 definition in Robin is intended to be correct and normative, not
130 necessarily efficient. If the implementation of the operation in some
131 other language has the same semantics as the Robin definition of the
132 operation, it can be used interchangeably, and more efficiently.
133
134 In particular, the reference implementation exposes, by default,
127 If you run the above factorial program with the reference interpreter,
128
129 bin/robin fact.robin
130
131 You'll see a message such as the following:
132
133 (abort (inapplicable-object (abort (unbound-identifier fun))))
134
135 You might conclude from this that [`fun`][] is not built-in to the
136 language — and you'd be right! Unlike almost every other Lisp-like
137 language, in Robin, `fun` is defined in the standard library, which
138 must be imported before it can be used.
139
140 It's defined as a so-called "fexpr" (which is like a macro but
141 even more general — more on them in a minute.)
142
143 As you saw above, you can ask the Robin reference interpreter to
144 load in the standard library before it runs your program:
145
146 bin/robin pkg/stdlib.robin fact.robin
147
148 and you'll see the expected
149
150 120
151
152 but you may notice that it's not exactly quick to come back with
153 that answer.
154
155 Even though they are defined in Robin, parts of the standard library
156 are often implemented in some other language. This is because, while
157 their definition in Robin is intended to be correct and normative, it
158 is not necessarily efficient. If some other, more efficient
159 implementation of the operation has the same semantics as the Robin
160 definition of the operation, they can be used interchangeably.
161
162 In particular, the reference implementation can expose, if requested,
135163 a set of "builtins" which map to the "small" subset of the standard
136 library. You can turn them off with:
137
138 bin/robin --no-builtins pkg/stdlib.robin fact.robin
139
140 If you do this, you'll see
141
142 Main.hs: uncaught exception: (unbound-identifier fun)
143
144 You might conclude from this that `fun` is not a built-in — and you'd
145 be right! Unlike basically every other Lisp-like language, in Robin,
146 `fun` is a derived form. It's implemented as a macro.
164 library. You can turn them on with:
165
166 bin/robin --enable-builtins fact.robin
167
168 When you run this, you'll see it displays the answer much more promptly
169 this time.
170
171 Note that the reference implementation doesn't implement all of the
172 standard library as builtins; and note that there is no conflict if you
173 have it process the built-in definitions externally as well. So this
174 will work just as well:
175
176 bin/robin --enable-builtins pkg/stdlib.robin fact.robin
177
178 Fexprs
179 ------
180
181 As we mentioned above, functions aren't intrinsic to Robin — the
182 `fun` operator that creates a function is defined as a so-called "fexpr"
183 in the standard library.
184
185 You're quite free to simply import the standard library and use `fun`
186 without knowing or caring that it's defined as a fexpr, whatever that
187 is.
188
189 However, you can write your own fexprs as well, using [`fexpr`][].
190
191 The main difference between a fexpr and a function is that a fexpr
192 does *not* evaluate the arguments that are passed to it. It receives
193 them as an unevaluated S-expression. What it does with this unevaluated
194 S-expression is completely up to it.
195
196 One trivial thing it can do with it is simply return it unmodified.
197 This is what the [`literal`][] operator in the standard library does,
198 and this is how it's defined:
199
200 (define literal (fexpr (args env)
201 args))
202
203 With this definition in place you can run
204
205 (display (literal (hello world)))
206
207 and you'll see
208
209 (hello world)
210
211 So `literal` is essentially the same as `quote` in Lisp or Scheme.
212 Except, of course, it's not intrinsic to the language. We wrote a
213 fexpr to do it instead.
214
215 A fexpr defined this way also has access to the environment in which
216 it was called (the `env` parameter). There is also an intrinsic
217 operator called [`eval`][] which evaluates a given S-expression in a
218 given environment. With these tools, we can write fexprs that *do*
219 evaluate their arguments, just like functions.
220
221 (define id (fexprs (args env) (eval env (head args))))
222 (display (id (subtract 80 4)))
223
224 If you run this you should see 76.
225
226 When we don't care to distinguish between "functions" and "fexprs"
227 and "macros" we just call them all "operators".
228
229 ### Recursive fexprs
230
231 (To be written).
147232
148233 Macros
149234 ------
150235
151 (To be written).
236 Just like a function, in Robin, is a kind of fexpr, so too is a
237 macro a kind of fexpr. A macro is a fexpr whose return value is
238 expected to be a piece of syntax which will be further evaluated
239 as a Robin expression.
240
241 (To be continued).
242
243 Referential transparency
244 ------------------------
245
246 The code you've been writing inside those `define`s and `display`s
247 is an _expression_. Expressions are distinct from top-level forms;
248 you can't say `display` inside an expression, and you can't say
249 `subtract` at the top level. (This is different from a lot of
250 Lisps and Schemes, and it is quite intentional.)
251
252 In fact, in Robin, expressions cannot have any side-effects. This
253 is sometimes called being "purely functional".
254
255 An implication of this is that all data in Robin is immutable: once
256 created, a list or other data structure cannot be changed. Rather, a
257 new data structure, similar to the original data structure but altered
258 in some way, must be created.
259
260 We can be even more specific and say that in Robin, all expressions
261 are _referentially transparent_. There are a number of equivalent
262 ways to describe what this means. One that I find intuitive is:
263
264 > Evaluation of an operator is affected by nothing except its input
265 > values, and has an effect on nothing except its output value.
266
267 (I also find it reminiscent of the saying "Take only photographs,
268 leave only footprints", but as to whether that has any mnemonic value,
269 well, YMMV.)
270
271 This is a surprisingly strong guarantee. This is good, because it
272 helps immensely in reasoning about programs. But it can be surprising.
273
274 ### Abort values
275
276 For example, it rules out conventional exception handling, because
277 conventionally, exception handling involves setting up an exception
278 handler, and when an exception occurs in some operator, this handler
279 is invoked. But this handler is not an input value to the operator.
280 So the operator is relying on something that was not passed to it.
281 So it is not referentially transparent.
282
283 So instead of exceptions, Robin has _abort values_. You've seen
284 them already as results of running some of the example code above.
285
286 An abort value is produced whenever an operator encounters an error
287 and can't provide a sensible value.
288
289 You can also produce one explicitly with the [`abort`][] operator:
290
291 (display (abort (literal (something went wrong))))
292
293 Also, most operators have the following convenient behaviour:
294 _if any of their inputs are an abort value, they produce an abort value_.
295
296 In addition, they usually nest the abort value they received inside the abort
297 value they produce. This leads to a chain of abort values. This chain is
298 similar to the traceback that is provided when an uncaught exception
299 occurs in a procedural language such as Python or Java.
300
301 It is often quite reasonable to simply let a program evaluate to an
302 `abort` value, if there was an error in it — a philosophy sometimes
303 known as "[let it crash][]". However, if it does become important to
304 recover from such an error condition, the [`recover`][] operator can be
305 used to intercept an abort value and achieve some alternate computation
306 instead.
307
308 (display (recover
309 (abort (literal (something went wrong)))
310 value (list value #t)
311 error (list error #f)))
312
313 Running this (with stdlib) you should see:
314
315 ((something went wrong) #f)
316
317 which, you will note, is not an abort value.
318
319 [let it crash]: http://stratus3d.com/blog/2020/01/20/applying-the-let-it-crash-philosophy-outside-erlang/
152320
153321 Reactors
154322 --------
155323
156 (To be written).
324 The problem with expressions being referentially transparent is that,
325 in practice, software usually _does something_ over and above calculating
326 the result of an expression. Users click buttons, shapes get displayed
327 on the screen, files get written to filesystems.
328
329 Some languages solve this problem by having functions that interact
330 with the outside world take a representation of the outside world as one
331 of their inputs, and produce a (possibly modified) representation of the
332 outside world as part of their output.
333
334 Robin's approach is not this. Robin's approach is rather more like
335 JavaScript: it is _event-oriented_. Events happen, and the program
336 reacts to them by evaluating pure functions. These functions evaluate
337 to a description of actions for the system to take; and these actions
338 possibly cause more events to happen, and the cycle repeats.
339
340 Specifically, a Robin program may define a _reactor_ by giving an
341 initial state, and an operator called the _transducer_.
342
343 The transducer will be evaluated any time an event comes in. The event could
344 come in from the outside world (for example, the user clicks a button,)
345 or it could be generated by the Robin program itself.
346
347 When the transducer is evaluated, it is passed the event, and also the
348 current state. These values are passed in raw; they need no evaluation,
349 so the transducer is not written as a function. Instead, the [`bind-vals`][]
350 operator is useful for obtaining these arguments.
351
352 The return value of the transducer consists of two parts: a new value to
353 use as the new current state, and a list of "commands" to issue. Each
354 "command" is a two-element list, which is not really different from a new
355 event that will happen.
356
357 This setup is very similar to "The Elm Architecture" used in the language Elm.
358
359 Here is an example reactor.
360
361 (reactor (line-terminal) 0
362 (fexpr (args env)
363 (bind-vals ((event-type event-payload) state)
364 (if (equal? event-type (literal init))
365 (list state
366 (list (literal writeln) (literal ''Hello, world!''))
367 (list (literal stop) 0))
368 (list state)))))
369
370 The `(line-terminal)` part at the top is the configuration referred to above;
371 in this case it indicates that the reactor wishes to subscribe to a facility
372 called `line-terminal` (similar to "standard I/O" in most other languages.)
373
374 The `0` at the top is the initial state. In fact this example is so simple
375 that the state does not change and does not make any difference to the
376 program, but an initial state still must be given.
377
378 After that is the transducer, given as a `fexpr`. It receives, as its
379 arguments, an event, which consists of an event type and an event payload,
380 and the current state. It extracts these.
381
382 It compares the event type against `init` to see if it is the initialization
383 event.
384
385 If it is, it returns the current state unchanged and issues two commands: a
386 command to write `Hello, world!` as a line of output, and a command to `stop`
387 the program.
388
389 If it's not, it just returns the current state unchanged. The reactor will
390 wait for the next event and process it.
391
392 (To be continued).
393
394 [`abort]: ../stdlib/abort.robin
395 [`recover`]: ../stdlib/recover.robin
396 [`equal?`]: ../stdlib/equal-p.robin
397 [`eval`]: ../stdlib/eval.robin
398 [`head`]: ../stdlib/head.robin
399 [`if`]: ../stdlib/if.robin
400 [`list?`]: ../stdlib/list-p.robin
401 [`fexpr`]: ../stdlib/fexpr.robin
402 [`number?`]: ../stdlib/number-p.robin
403 [`operator?`]: ../stdlib/operator-p.robin
404 [`prepend`]: ../stdlib/prepend.robin
405 [`sign`]: ../stdlib/sign.robin
406 [`subtract`]: ../stdlib/subtract.robin
407 [`symbol?`]: ../stdlib/symbol-p.robin
408 [`tail`]: ../stdlib/tail.robin
409 [`add`]: ../stdlib/add.robin
410 [`fun`]: ../stdlib/fun.robin
411 [`literal`]: ../stdlib/literal.robin
412 [`abort`]: ../stdlib/abort.robin
00 (require fun)
1 (require empty?)
2 (require bind-vals)
13
2 (reactor (line-terminal) (list 0 0) (macro (self args env)
3 (let ((event (head args))
4 (event-type (head event))
5 (event-payload (head (tail event)))
6 (state (head (tail args)))
7 (x (head state))
8 (y (head (tail state)))
9 (move (fun (msg dx dy state)
10 (let ((newx (add x dx))
11 (newy (add y dy))
12 (newstate (list newx newy))
13 (room (append
14 (literal ''You are in room #'')
15 (list (add newx 65) (add newy 65)))))
16 (list newstate
17 (list (literal writeln) msg)
18 (list (literal writeln) room)))))
19 (dont-understand (fun (state)
20 (list state (list (literal writeln)
21 (literal ''Please enter n, s, e, or w, or q to quit.''))))))
22 (choose
23 ((equal? event-type (literal init))
24 (list state (list (literal writeln)
25 (literal ''Welcome to Not Quite an Adventure!''))))
26 ((equal? event-type (literal eof))
27 (list state (list (literal writeln)
28 (literal ''Bye!''))))
29 ((equal? event-type (literal readln))
30 (if (empty? event-payload)
31 (dont-understand state)
32 (bind letter (list (head event-payload))
33 (choose
34 ((equal? letter (literal ''n''))
35 (move (literal ''North!'') 0 1 state))
36 ((equal? letter (literal ''s''))
37 (move (literal ''South!'') 0 (subtract 0 1) state))
38 ((equal? letter (literal ''e''))
39 (move (literal ''East!'') 1 0 state))
40 ((equal? letter (literal ''w''))
41 (move (literal ''West!'') (subtract 0 1) 0 state))
42 ((equal? letter (literal ''q''))
43 (list state (list (literal stop) 0)))
44 (else
45 (dont-understand state))))))
46 (else
47 (list state))))))
4 (define command (fexpr (args env)
5 (list (head args) (eval env (head (tail args))))))
6
7 (reactor (line-terminal) (list 0 0)
8 (fexpr (args env)
9 (bind-vals ((event-type event-payload) (x y)) args
10 (let ((state (list x y))
11 (room-message (fun (x y)
12 (append
13 (literal ''You are in room #'')
14 (list (add x 55) (add y 55)))))
15 (move (fun (msg dx dy state)
16 (let ((newx (add x dx))
17 (newy (add y dy))
18 (newstate (list newx newy)))
19 (list newstate
20 (command writeln msg)
21 (command writeln (room-message newx newy))))))
22 (dont-understand (fun (state)
23 (list state (command writeln (literal ''Please enter n, s, e, or w, or q to quit.''))))))
24 (choose
25 ((equal? event-type (literal init))
26 (list state (command writeln (literal ''Welcome to Not Quite an Adventure!''))
27 (command writeln (room-message x y))))
28 ((equal? event-type (literal readln))
29 (if (empty? event-payload)
30 (dont-understand state)
31 (bind letter (list (head event-payload))
32 (choose
33 ((equal? letter (literal ''n''))
34 (move (literal ''North!'') 0 1 state))
35 ((equal? letter (literal ''s''))
36 (move (literal ''South!'') 0 (subtract 0 1) state))
37 ((equal? letter (literal ''e''))
38 (move (literal ''East!'') 1 0 state))
39 ((equal? letter (literal ''w''))
40 (move (literal ''West!'') (subtract 0 1) 0 state))
41 ((equal? letter (literal ''q''))
42 (list state (command writeln (literal ''Bye!'')) (command stop 0)))
43 (else
44 (dont-understand state))))))
45 (else
46 (list state)))))))
0 (require bind)
0 (require bind-vals)
11
22 (reactor (line-terminal) 0
3 (macro (self args env)
4 (bind event (head args)
5 (bind event-type (head event)
6 (bind event-payload (head (tail event))
7 (if (equal? event-type (literal readln))
8 (list 0
9 (list (literal writeln) event-payload))
10 (list 0)))))))
3 (fexpr (args env)
4 (bind-vals ((event-type event-payload) state) args
5 (if (equal? event-type (literal readln))
6 (list state
7 (list (literal writeln) event-payload))
8 (list state)))))
00 ;''
1 Deadfish in Robin 0.3
1 Deadfish ( http://esolangs.org/wiki/Deadfish ) implemented in Robin 0.8
22 ''
33
4 (require itoa)
4 (require itoa) (require bind-vals)
55
66 (reactor (line-terminal) 0
7 (macro (self args env)
8 (bind event (head args)
9 (bind event-type (head event)
10 (bind event-payload (head (tail event))
11 (bind prev-state (head (tail args))
12 (bind state
13 (if (equal? prev-state (subtract 0 1))
14 0
15 (if (equal? prev-state 256)
16 0
17 prev-state))
18 (bind prompt (macro (self args env)
19 (bind show (eval env (head args))
20 (bind state (eval env (head (tail args)))
21 (if show
22 (list state
23 (list (literal writeln) (itoa state))
24 (list (literal write) (literal ''>> '')))
25 (list state
26 (list (literal write) (literal ''>> '')))))))
7 (fexpr (args env)
8 (bind-vals ((event-type event-payload) prev-state) args
9 (bind state
10 (if (equal? prev-state (subtract 0 1))
11 0
12 (if (equal? prev-state 256)
13 0
14 prev-state))
15 (bind prompt (fexpr (args env)
16 (bind show (eval env (head args))
17 (bind state (eval env (head (tail args)))
18 (if show
19 (list state
20 (list (literal writeln) (itoa state))
21 (list (literal write) (literal ''>> '')))
22 (list state
23 (list (literal write) (literal ''>> '')))))))
24 (choose
25 ((equal? event-type (literal init))
26 (prompt #f state))
27 ((equal? event-type (literal readln))
28 (bind letter event-payload
2729 (choose
28 ((equal? event-type (literal init))
29 (prompt #f state))
30 ((equal? event-type (literal readln))
31 (bind letter event-payload
32 (choose
33 ((equal? letter (literal ''d''))
34 (prompt #f (subtract state 1)))
35 ((equal? letter (literal ''i''))
36 (prompt #f (subtract state (subtract 0 1))))
37 ((equal? letter (literal ''s''))
38 (prompt #f (multiply state state)))
39 ((equal? letter (literal ''o''))
40 (prompt #t state))
41 (else (prompt state)))))
42 (else
43 (list state)))))))))))
30 ((equal? letter (literal ''d''))
31 (prompt #f (subtract state 1)))
32 ((equal? letter (literal ''i''))
33 (prompt #f (subtract state (subtract 0 1))))
34 ((equal? letter (literal ''s''))
35 (prompt #f (multiply state state)))
36 ((equal? letter (literal ''o''))
37 (prompt #t state))
38 (else (prompt state)))))
39 (else
40 (list state))))))))
0 ;''Example of a recursive fexpr. It takes a list of bindings a la `let`
1 and returns a snippet of code `bind`ing those values. This is actually
2 how `let` is now defined in the stdlib.''
3
4 (define expand-let (fexpr (args env)
5 (bind body (head (tail args))
6 (bind expand-let-r (fexpr (iargs ienv)
7 (bind self (eval ienv (head iargs))
8 (bind items (eval ienv (head (tail iargs)))
9 (if (equal? items ())
10 body
11 (prepend (literal bind)
12 (prepend (head (head items))
13 (prepend (head (tail (head items)))
14 (prepend (self self (tail items))
15 ()))))))))
16 (expand-let-r expand-let-r (head args))))))
17
18 (display (expand-let ((a 1) (b 2) (c 3)) foo))
0 (require multiply)
0 ;''Example of a recursive function.''
11
22 (define fact (fun (self n)
33 (multiply n
11 (require literal)
22
33 (reactor (line-terminal) 0
4 (macro (self args env)
5 (bind event (head args)
6 (bind event-type (head event)
7 (if (equal? event-type (literal init))
8 (list 0
9 (list (literal writeln) (literal ''Hello, world!''))
10 (list (literal stop) 0))
11 (list 0))))))
4 (fexpr (args env)
5 (bind-vals ((event-type event-payload) state) args
6 (if (equal? event-type (literal init))
7 (list state
8 (list (literal writeln) (literal ''Hello, world!''))
9 (list (literal stop) 0))
10 (list state)))))
00 ;''Continually generate random numbers from 0 to 65535
1 and output the last digit of each number so generated.''
1 and output the last digit of each number so generated.
22
3 (require let) (require choose) (require itoa) (require abs) (require remainder)
3 Note that the Robin definition of remainder, currently,
4 is incredibly inefficient. You probably want to run
5 this with an optimized implementation of remainder.''
6
7 (require let) (require choose) (require list) (require abs) (require remainder)
48
59 (reactor (line-terminal random-u16-source) 0
6 (macro (self args env)
10 (fexpr (args env)
711 (let ((event (head args))
812 (event-type (head event))
913 (event-payload (head (tail event))))
0 ;''Demonstrates what happens when a reactor receives an abort value
1 from its transducer. To trigger this condition, enter a line
2 starting with a capital A. Running with --show-events will show
3 the abort message, but it will not stop the reactor, nor will it
4 be sent back to the reactor in the form of a new event.
5
6 If you don't like this behaviour, you should wrap your transducer
7 in a recover to intercept the abort value before the reactor
8 system swallows it, and do something else with it instead.''
9
10 (reactor (line-terminal) 0
11 (fexpr (args env)
12 (bind-vals ((event-type event-payload) state) args
13 (if (equal? event-type (literal readln))
14 (if (equal? (head event-payload) 65)
15 (abort 999999)
16 (list state (list (literal writeln) event-payload)))
17 (list state)))))
0 (require let) (require choose) (require fold)
1
2 ;''Demonstration of some code that traverses an expression, in preparation for
3 writing some actual Robin static analysis code, in Robin.
4
5 Note, this example code is woefully incomplete.''
6
7 (define traverse-expr (fun (expr)
8 (bind traverse-r (fun (self expr acc)
9 (if (list? expr)
10 (let ((fname (head expr))
11 (args (tail expr))
12 (acc1 (prepend fname acc))
13 (visit (fun (x a) (self self x a)))
14 (acc2 (fold visit acc1 args)))
15 acc2)
16 (prepend expr acc)))
17 (traverse-r traverse-r expr ()))))
18
19 (define little-program (literal
20 (add (mul 7 8) (mul 5 9))
21 ))
22
23 (display (traverse-expr little-program))
33 import System.Exit
44
55 import Language.Robin.CmdLine
6
7 import Language.Robin.Env (mergeEnvs)
8 import Language.Robin.Intrinsics (robinIntrinsics)
9 import Language.Robin.Builtins (robinBuiltins)
106
117 import Language.Robin.EventLoop (eventLoop)
128 import Language.Robin.Facilities (handler, waiter)
1814 args <- getArgs
1915 case args of
2016 [] -> do
21 abortWith "Usage: robin [--no-builtins] [--show-events] {[eval] source.robin}"
17 abortWithUsage
2218 _ -> do
23 let (args', env', showEvents) = processFlags args (mergeEnvs robinIntrinsics robinBuiltins) False
19 let (args', env', showEvents) = processFlags args
2420 (_, reactors, results) <- processArgs args' env'
2521 writeResults $ reverse results
2622 lineTerminal <- LineTerminal.init
00 module Language.Robin.Builtins where
1
2 import Prelude (
3 ($), (>), (>=), (<), (<=), (+), (*), div, mod, map, reverse, Bool(True, False)
4 )
5 import qualified Prelude as P
6
7 import Data.Int
81
92 import Language.Robin.Expr
103 import Language.Robin.Env (Env, fromList, mergeEnvs, empty, insert)
147 -- Robin Builtins
158 -- ==============
169 --
17 -- Note, these are functions which are built-in to the Robin reference
10 -- Note, these are operators which are built-in to the Robin reference
1811 -- intepreter, for performance, but they are *not* intrinsic to the
1912 -- Robin language. (See Intrinsics.hs for those.)
2013 --
2619 evalAll env [] acc cc =
2720 cc $ List $ reverse acc
2821 evalAll env (head:tail) acc cc =
29 eval env head (\value ->
22 evalB cc env head (\value ->
3023 evalAll env tail (value:acc) cc)
3124
32 -- formals actuals origActuals env continuation
33 evalArgs :: [Expr] -> [Expr] -> [Expr] -> Env -> (Env -> Expr) -> Expr
34 evalArgs [] [] _ _ cc =
25 -- errcont formals actuals origActuals env continuation
26 evalArgs :: (Env -> Expr) -> [Expr] -> [Expr] -> [Expr] -> Env -> (Env -> Expr) -> Expr
27 evalArgs ecc [] [] _ _ cc =
3528 cc empty
36 evalArgs ((Symbol formal):formals) (actual:actuals) origActuals env cc =
37 eval env actual (\value ->
38 evalArgs formals actuals origActuals env (\nenv ->
29 evalArgs ecc ((Symbol formal):formals) (actual:actuals) origActuals env cc =
30 evalB ecc env actual (\value ->
31 evalArgs ecc formals actuals origActuals env (\nenv ->
3932 cc $ insert formal value nenv))
40 evalArgs _ _ origActuals env cc =
41 errMsg "illegal-arguments" $ List origActuals
42
43
44 evalTwoNumbers :: (Int32 -> Int32 -> (Expr -> Expr) -> Expr) -> Evaluable
45 evalTwoNumbers fn env (List [xexpr, yexpr]) cc =
46 eval env xexpr (\x ->
47 assertNumber env x (\(Number xv) ->
48 eval env yexpr (\y ->
49 assertNumber env y (\(Number yv) ->
50 (fn xv yv cc)))))
51 evalTwoNumbers fn env other cc = errMsg "illegal-arguments" other
33 evalArgs ecc _ _ origActuals env _ =
34 errMsg ecc "illegal-arguments" $ List origActuals
5235
5336 --
5437 -- `Small`
6144 literal :: Evaluable
6245 literal env (List (expr:_)) cc =
6346 cc expr
64 literal env other cc = errMsg "illegal-arguments" other
47 literal env other cc = errMsg cc "illegal-arguments" other
6548
6649 list :: Evaluable
6750 list env (List exprs) cc =
7558 choose env (List [(List [(Symbol "else"), branch])]) cc =
7659 eval env branch cc
7760 choose env (List ((List [test, branch]):rest)) cc =
78 eval env test (\x ->
61 evalB cc env test (\x ->
7962 case x of
8063 Boolean True ->
8164 eval env branch cc
8265 Boolean False ->
8366 choose env (List rest) cc)
84 choose env other cc = errMsg "illegal-arguments" other
67 choose env other cc = errMsg cc "illegal-arguments" other
8568
8669 bind :: Evaluable
8770 bind env (List [(Symbol name), expr, body]) cc =
88 eval env expr (\value ->
71 evalB cc env expr (\value ->
8972 eval (insert name value env) body cc)
90 bind env other cc = errMsg "illegal-arguments" other
73 bind env other cc = errMsg cc "illegal-arguments" other
9174
9275 let_ :: Evaluable
9376 let_ env (List ((List bindings):body:_)) cc =
94 bindAll bindings env (\env' ->
77 bindAll cc bindings env (\env' ->
9578 eval env' body cc)
9679 where
97 bindAll [] env cc =
80 bindAll ecc [] env cc =
9881 cc env
99 bindAll (List ((Symbol name):sexpr:_):rest) env cc =
100 eval env sexpr (\value ->
101 bindAll rest (insert name value env) cc)
102 bindAll (other:rest) env cc =
103 errMsg "illegal-binding" other
104 let_ env other cc = errMsg "illegal-arguments" other
82 bindAll ecc (List ((Symbol name):sexpr:_):rest) env cc =
83 evalB ecc env sexpr (\value ->
84 bindAll ecc rest (insert name value env) cc)
85 bindAll ecc (other:rest) env cc =
86 errMsg ecc "illegal-binding" other
87 let_ env other cc = errMsg cc "illegal-arguments" other
10588
10689 bindArgs :: Evaluable
10790 bindArgs env (List [(List formals), givenArgs, givenEnvExpr, body]) cc =
108 eval env givenArgs (\(List actuals) ->
109 eval env givenEnvExpr (\outerEnvExpr ->
110 evalArgs formals actuals actuals outerEnvExpr (\argEnv ->
91 evalB cc env givenArgs (\(List actuals) ->
92 evalB cc env givenEnvExpr (\outerEnvExpr ->
93 evalArgs cc formals actuals actuals outerEnvExpr (\argEnv ->
11194 eval (mergeEnvs argEnv env) body cc)))
112 bindArgs env other cc = errMsg "illegal-arguments" other
95 bindArgs env other cc = errMsg cc "illegal-arguments" other
11396
11497 fun :: Evaluable
11598 fun closedEnv (List [(List formals), body]) cc =
116 cc $ Builtin "<lambda>" fun
99 cc $ Operator "<lambda>" fun
117100 where
118101 fun env (List actuals) cc =
119 evalArgs formals actuals actuals env (\argEnv ->
102 evalArgs cc formals actuals actuals env (\argEnv ->
120103 eval (mergeEnvs argEnv closedEnv) body cc)
121 fun env other cc = errMsg "illegal-arguments" other
104 fun env other cc = errMsg cc "illegal-arguments" other
122105
123106 --
124107 -- `Arith`
140123 lteP :: Evaluable
141124 lteP = evalTwoNumbers (\x y cc -> cc $ Boolean (x <= y))
142125
143 abs :: Evaluable
144 abs env (List [expr]) cc =
145 eval env expr (\x -> assertNumber env x (\(Number xv) -> cc (Number $ P.abs xv)))
146 abs env other cc = errMsg "illegal-arguments" other
126 abs_ :: Evaluable
127 abs_ env (List [expr]) cc =
128 evalToNumber cc env expr (\(Number xv) -> cc $ Number $ abs xv)
129 abs_ env other cc = errMsg cc "illegal-arguments" other
147130
148131 add :: Evaluable
149132 add = evalTwoNumbers (\x y cc -> cc $ Number (x + y))
153136
154137 divide :: Evaluable
155138 divide = evalTwoNumbers (\x y cc -> case y of
156 0 -> errMsg "division-by-zero" $ Number x
139 0 -> errMsg cc "division-by-zero" $ Number x
157140 _ -> cc $ Number (x `div` y))
158141
159142 remainder :: Evaluable
160143 remainder = evalTwoNumbers (\x y cc -> case y of
161 0 -> errMsg "division-by-zero" $ Number x
162 _ -> cc $ Number (P.abs (x `mod` y)))
144 0 -> errMsg cc "division-by-zero" $ Number x
145 _ -> cc $ Number (abs (x `mod` y)))
163146
164147 --
165148 -- Mapping of names to our functions, providing an evaluation environment.
166149 --
167150
168151 robinBuiltins :: Env
169 robinBuiltins = fromList $ map (\(name,bif) -> (name, Builtin name bif))
152 robinBuiltins = fromList $ map (\(name,bif) -> (name, Operator name bif))
170153 [
171154 ("literal", literal),
172155 ("list", list),
182165 ("lt?", ltP),
183166 ("lte?", lteP),
184167
185 ("abs", abs),
168 ("abs", abs_),
186169 ("add", add),
187170 ("multiply", multiply),
188171 ("divide", divide),
00 module Language.Robin.CmdLine where
1
2 import Prelude (id, return, show, (++), ($), String, Bool(True), Either(Left, Right))
31
42 import System.IO
53 import System.Exit
64
75 import Language.Robin.Expr (Expr(List, Symbol, Abort))
6 import Language.Robin.Env (mergeEnvs)
87 import Language.Robin.Parser (parseToplevel, parseExpr)
98 import Language.Robin.Intrinsics (robinIntrinsics)
9 import Language.Robin.Builtins (robinBuiltins)
1010 import Language.Robin.TopLevel (initialWorld, destructureWorld, collect, secondaryDefs)
1111
1212
1515 exitWith $ ExitFailure 1
1616
1717
18 processFlags ("--no-builtins":rest) env showEvents = processFlags rest robinIntrinsics showEvents
19 processFlags ("--show-events":rest) env showEvents = processFlags rest env True
20 processFlags args env showEvents = (args, env, showEvents)
18 abortWithUsage = do
19 abortWith "Usage: robin [--enable-builtins] [--show-events] {[eval] source.robin}"
20
21
22 processFlags flags = processFlags' flags (robinIntrinsics) False where
23 processFlags' ("--enable-builtins":rest) env showEvents = processFlags' rest (mergeEnvs robinIntrinsics robinBuiltins) showEvents
24 processFlags' ("--show-events":rest) env showEvents = processFlags' rest env True
25 processFlags' args env showEvents = (args, env, showEvents)
2126
2227
2328 processArgs args env = processArgs' args $ initialWorld env where
1717 entry = List [Symbol s, value]
1818 in
1919 List (entry:bindings)
20 insert s value term = abortVal "expected-env-list" term
2021
2122 find :: String -> Env -> Maybe Expr
2223 find _ (List []) = Nothing
3233
3334 mergeEnvs :: Env -> Env -> Env
3435 mergeEnvs (List a) (List b) = (List (a ++ b))
36 mergeEnvs (List a) term = abortVal "expected-env-list" term
37 mergeEnvs term (List b) = abortVal "expected-env-list" term
00 module Language.Robin.Eval where
11
2 import Prelude (Maybe(Just, Nothing), Bool(True, False))
2 import Data.Int
33
44 import Language.Robin.Expr
55 import Language.Robin.Env (Env, find, insert)
3535 Just value ->
3636 cc value
3737 Nothing ->
38 errMsg "unbound-identifier" sym
38 errMsg cc "unbound-identifier" sym
3939
4040 --
4141 -- Evaluating a list means we must make several evaluations. We
42 -- evaluate the head to obtain something to apply (which must be a
43 -- macro or intrinsic.) We then apply the body of the macro,
44 -- passing it the tail of the list.
42 -- evaluate the head to obtain something to apply, which must be an
43 -- operator. We then apply the operator, passing it the tail of the list.
4544 --
4645
4746 eval env (List (applierExpr:actuals)) cc =
4847 eval env applierExpr (\applier ->
4948 case applier of
50 m@(Macro _ _ body) ->
51 eval (makeMacroEnv env (List actuals) m) body cc
52 b@(Builtin _ fun) ->
49 Operator _ fun ->
5350 fun env (List actuals) cc
5451 other ->
55 errMsg "inapplicable-object" other)
52 errMsg cc "inapplicable-object" other)
5653
5754 --
5855 -- Everything else just evaluates to itself. Continue the current
6360 cc e
6461
6562 --
63 -- Evaluate, handling abort values
64 --
65
66 evalB ecc env e cc =
67 eval env e (\value ->
68 case value of
69 Abort _ -> ecc value
70 _ -> cc value)
71
72 --
6673 -- Helper functions
6774 --
6875
69 errMsg msg term =
70 Abort (List [(Symbol msg), term])
76 errMsg cc msg term = cc $ abortVal msg term
7177
72 makeMacroEnv :: Env -> Expr -> Expr -> Env
73 makeMacroEnv env actuals m@(Macro closedEnv argList _) =
78 makeFexpr :: Expr -> Expr -> Expr -> Evaluable
79 makeFexpr defineTimeEnv formals body =
80 \callTimeEnv actuals cc ->
81 let
82 env = makeFexprEnv callTimeEnv actuals defineTimeEnv formals
83 in
84 eval env body cc
85
86 makeFexprEnv callTimeEnv actuals defineTimeEnv argList =
7487 let
75 (List [(Symbol argSelf), (Symbol argFormal),
76 (Symbol envFormal)]) = argList
77 newEnv = insert argSelf m closedEnv
78 newEnv' = insert argFormal actuals newEnv
79 newEnv'' = insert envFormal env newEnv'
88 (List [(Symbol argFormal), (Symbol envFormal)]) = argList
89 newEnv' = insert argFormal actuals defineTimeEnv
90 newEnv'' = insert envFormal callTimeEnv newEnv'
8091 in
8192 newEnv''
93
8294
8395 --
8496 -- Assertions
8597 --
8698
87 assert env pred msg expr cc =
88 case pred expr of
89 True -> cc expr
90 False -> errMsg msg expr
99 evalExpect pred msg ecc env expr cc =
100 eval env expr (\value ->
101 case pred value of
102 True -> cc value
103 False -> errMsg ecc msg value)
91104
92 assertSymbol env = assert env (isSymbol) "expected-symbol"
93 assertBoolean env = assert env (isBoolean) "expected-boolean"
94 assertList env = assert env (isList) "expected-list"
95 assertNumber env = assert env (isNumber) "expected-number"
96 assertMacro env = assert env (isMacro) "expected-macro"
105 evalToBoolean = evalExpect (isBoolean) "expected-boolean"
106 evalToList = evalExpect (isList) "expected-list"
107 evalToNumber = evalExpect (isNumber) "expected-number"
108
109 evalTwoNumbers :: (Int32 -> Int32 -> (Expr -> Expr) -> Expr) -> Evaluable
110 evalTwoNumbers fn env (List [xexpr, yexpr]) cc =
111 evalToNumber cc env xexpr (\(Number xv) ->
112 evalToNumber cc env yexpr (\(Number yv) ->
113 (fn xv yv cc)))
114 evalTwoNumbers fn env other cc = errMsg cc "illegal-arguments" other
00 module Language.Robin.EventLoop where
1
2 import Prelude (return, show, filter, (/=), (++), ($), Bool(True, False), Either(Left, Right))
31
42 import qualified Data.Char as Char
53 import Data.Int
33 import Data.Int
44
55 --
6 -- An _evaluable_ is a Haskell object which behaves like a Robin macro.
7 -- It describes builtins (which includes intrinsics), and also happens
8 -- (perhaps unsurprisingly?) to be the type of the evaluator function.
6 -- An _evaluable_ is a Haskell object which implements a Robin operator.
7 -- It describes both builtins (which includes intrinsics) and user-defined
8 -- functions, macros, and fexprs, and also happens (perhaps unsurprisingly?)
9 -- to be the type of the evaluator function.
910 --
1011
1112 type Evaluable = Expr -> Expr -> (Expr -> Expr) -> Expr
1920 data Expr = Symbol String
2021 | Boolean Bool
2122 | Number Int32
22 | Macro Expr Expr Expr -- the 1st Expr is actually an Env
23 | Builtin String Evaluable
23 | Operator String Evaluable
2424 | List [Expr]
2525 | Abort Expr
2626
2828 (Symbol x) == (Symbol y) = x == y
2929 (Boolean x) == (Boolean y) = x == y
3030 (Number x) == (Number y) = x == y
31 (Macro e1 a1 b1) == (Macro e2 a2 b2) = e1 == e2 && a1 == a2 && b1 == b2
32 (Builtin x _) == (Builtin y _) = x == y
31 (Operator x _) == (Operator y _) = x == y
3332 (List x) == (List y) = x == y
3433 (Abort x) == (Abort y) = x == y
3534 _ == _ = False
3938 show (Boolean True) = "#t"
4039 show (Boolean False) = "#f"
4140 show (Number n) = show n
42 show (Macro env args body) = ("(macro " ++ (show args) ++
43 " " ++ (show body) ++ ")")
44 show (Builtin name _) = name
41 show (Operator name _) = name
4542 show (Abort e) = "(abort " ++ (show e) ++ ")"
4643 show (List exprs) = "(" ++ (showl exprs) ++ ")" where
4744 showl [] = ""
5451
5552 append (List x) (List y) =
5653 List (x ++ y)
54
55 abortVal msg term =
56 Abort (List [(Symbol msg), term])
5757
5858 --
5959 -- Predicates
7171 isList (List _) = True
7272 isList _ = False
7373
74 isMacro (Macro _ _ _) = True
75 isMacro (Builtin _ _) = True
76 isMacro _ = False
77
7874 isAbort (Abort _) = True
7975 isAbort _ = False
76
77 isOperator (Operator _ _) = True
78 isOperator _ = False
00 module Language.Robin.Intrinsics where
1
2 import Prelude (($), (==), (>=), (<), (-), map)
31
42 import Language.Robin.Expr
53 import Language.Robin.Env (Env, fromList, insert)
64 import Language.Robin.Eval
75
86
9 head :: Evaluable
10 head env (List [expr]) cc =
11 eval env expr (\x ->
12 assertList env x (\val ->
13 case val of
14 List (a:_) -> cc a
15 other -> errMsg "expected-list" other))
16 head env other cc = errMsg "illegal-arguments" other
7 head_ :: Evaluable
8 head_ env (List [expr]) cc =
9 evalToList cc env expr (\val ->
10 case val of
11 List (a:_) -> cc a
12 other -> errMsg cc "expected-list" other) -- FIXME: should really be "expected-nonempty-list"
13 head_ env other cc = errMsg cc "illegal-arguments" other
1714
18 tail :: Evaluable
19 tail env (List [expr]) cc =
20 eval env expr (\x ->
21 assertList env x (\val ->
22 case val of
23 List (_:b) -> cc (List b)
24 other -> errMsg "expected-list" other))
25 tail env other cc = errMsg "illegal-arguments" other
15 tail_ :: Evaluable
16 tail_ env (List [expr]) cc =
17 evalToList cc env expr (\val ->
18 case val of
19 List (_:b) -> cc (List b)
20 other -> errMsg cc "expected-list" other)
21 tail_ env other cc = errMsg cc "illegal-arguments" other
2622
2723 prepend :: Evaluable
2824 prepend env (List [e1, e2]) cc =
29 eval env e1 (\x1 -> eval env e2 (\val ->
30 case val of
31 List x2 -> cc $ List (x1:x2)
32 other -> errMsg "expected-list" other))
33 prepend env other cc = errMsg "illegal-arguments" other
25 evalB cc env e1 (\x1 ->
26 evalToList cc env e2 (\(List x2) -> cc $ List (x1:x2)))
27 prepend env other cc = errMsg cc "illegal-arguments" other
3428
3529 equalP :: Evaluable
3630 equalP env (List [e1, e2]) cc =
37 eval env e1 (\x1 -> eval env e2 (\x2 -> cc $ Boolean (x1 == x2)))
38 equalP env other cc = errMsg "illegal-arguments" other
31 evalB cc env e1 (\x1 -> evalB cc env e2 (\x2 -> cc $ Boolean (x1 == x2)))
32 equalP env other cc = errMsg cc "illegal-arguments" other
3933
4034 predP pred env (List [expr]) cc =
41 eval env expr (\x -> cc $ Boolean $ pred x)
42 predP pred env other cc = errMsg "illegal-arguments" other
35 evalB cc env expr (\x -> cc $ Boolean $ pred x)
36 predP pred env other cc = errMsg cc "illegal-arguments" other
4337
4438 symbolP = predP isSymbol
4539 listP = predP isList
46 macroP = predP isMacro
40 operatorP = predP isOperator
4741 numberP = predP isNumber
4842
49 subtract :: Evaluable
50 subtract env (List [xexpr, yexpr]) cc =
51 eval env xexpr (\x ->
52 assertNumber env x (\(Number xv) ->
53 eval env yexpr (\y ->
54 assertNumber env y (\(Number yv) ->
55 cc (Number (xv - yv))))))
56 subtract env other cc = errMsg "illegal-arguments" other
43 subtract_ :: Evaluable
44 subtract_ = evalTwoNumbers (\x y cc -> cc $ Number (x - y))
5745
5846 sign :: Evaluable
5947 sign env (List [expr]) cc =
6048 let
6149 sgn x = if x == 0 then 0 else if x < 0 then -1 else 1
6250 in
63 eval env expr (\x ->
64 assertNumber env x (\(Number xv) ->
65 cc $ Number $ sgn xv))
66 sign env other cc = errMsg "illegal-arguments" other
51 evalToNumber cc env expr (\(Number xv) ->
52 cc $ Number $ sgn xv)
53 sign env other cc = errMsg cc "illegal-arguments" other
6754
6855 if_ :: Evaluable
69 if_ env (List [test, texpr, fexpr]) cc =
70 eval env test (\x ->
71 assertBoolean env x (\(Boolean b) ->
72 if b then eval env texpr cc else eval env fexpr cc))
73 if_ env other cc = errMsg "illegal-arguments" other
56 if_ env (List [testExpr, trueExpr, falseExpr]) cc =
57 evalToBoolean cc env testExpr (\(Boolean b) ->
58 if b then eval env trueExpr cc else eval env falseExpr cc)
59 if_ env other cc = errMsg cc "illegal-arguments" other
7460
7561 eval_ :: Evaluable
7662 eval_ env (List [envlist, form]) cc =
77 eval env envlist (\newEnvVal ->
78 eval env form (\body ->
63 evalB cc env envlist (\newEnvVal ->
64 evalB cc env form (\body ->
7965 eval newEnvVal body cc))
80 eval_ env other cc = errMsg "illegal-arguments" other
66 eval_ env other cc = errMsg cc "illegal-arguments" other
8167
82 macro :: Evaluable
83 macro env (List [args@(List [(Symbol selfS), (Symbol argsS), (Symbol envS)]), body]) cc =
84 cc $ Macro env args body
85 macro env other cc = errMsg "illegal-arguments" other
68 fexpr :: Evaluable
69 fexpr env (List [args@(List [(Symbol argsS), (Symbol envS)]), body]) cc =
70 cc $ Operator "<operator>" $ makeFexpr env args body
71 fexpr env other cc = errMsg cc "illegal-arguments" other
8672
8773 abort :: Evaluable
8874 abort env (List [expr]) cc =
8975 eval env expr (\v -> cc $ Abort v)
90 abort env other cc = errMsg "illegal-arguments" other
76 abort env other cc = errMsg cc "illegal-arguments" other
9177
9278 recover :: Evaluable
9379 recover env (List [expr, (Symbol okName), okExpr, (Symbol abortName), abortExpr]) cc =
9783 eval (insert abortName contents env) abortExpr cc
9884 other ->
9985 eval (insert okName other env) okExpr cc)
100 recover env other cc = errMsg "illegal-arguments" other
86 recover env other cc = errMsg cc "illegal-arguments" other
10187
10288 robinIntrinsics :: Env
103 robinIntrinsics = fromList $ map (\(name,bif) -> (name, Builtin name bif))
89 robinIntrinsics = fromList $ map (\(name,bif) -> (name, Operator name bif))
10490 [
105 ("head", head),
106 ("tail", tail),
91 ("head", head_),
92 ("tail", tail_),
10793 ("prepend", prepend),
10894 ("list?", listP),
10995 ("symbol?", symbolP),
110 ("macro?", macroP),
96 ("operator?",operatorP),
11197 ("number?", numberP),
11298 ("equal?", equalP),
113 ("subtract", subtract),
99 ("subtract", subtract_),
114100 ("sign", sign),
115 ("macro", macro),
101 ("fexpr", fexpr),
116102 ("eval", eval_),
117103 ("if", if_),
118104 ("abort", abort),
00 {-# LANGUAGE FlexibleContexts #-}
11
22 module Language.Robin.Parser (parseToplevel, parseExpr) where
3
4 import Prelude (return, read, map, fromIntegral, (==), (/=), (++), ($), String, Bool(True, False), Either(Left, Right))
53
64 import Data.Char
75 import Data.Int
00 module Language.Robin.Reactor where
1
2 import Prelude (id, Show, Eq, (++))
31
42 import qualified Data.Char as Char
53 import Data.Int
3331 (List (state':commands)) ->
3432 (reactor{ state=state' }, applyStop commands)
3533 expr ->
36 (reactor, [List [(Symbol "malformed-response"), expr]])
34 -- make sure this event is ill-formed so that no reactors react to it
35 -- TODO handle this in a more elegant way
36 (reactor, [List [(Symbol "malformed-response"), (Symbol "malformed-response"), expr]])
3737
3838
3939 updateMany :: [Reactor] -> Expr -> ([Reactor], [Expr])
00 module Language.Robin.TopLevel where
1
2 import Prelude (id, fromIntegral, length, ($), (++), Bool(False), Maybe(Just, Nothing))
31
42 import Language.Robin.Expr
53 import Language.Robin.Env (Env, find, insert, empty)
33 import System.Exit
44
55 import Language.Robin.CmdLine
6
7 import Language.Robin.Env (mergeEnvs)
8 import Language.Robin.Intrinsics (robinIntrinsics)
9 import Language.Robin.Builtins (robinBuiltins)
106
117 import Language.Robin.EventLoop (eventLoop)
128 import Language.Robin.Facilities.Concurrent (orchestrate)
1814 args <- getArgs
1915 case args of
2016 [] -> do
21 abortWith "Usage: robin [--no-builtins] [--show-events] {[eval] source.robin}"
17 abortWithUsage
2218 _ -> do
23 let (args', env', showEvents) = processFlags args (mergeEnvs robinIntrinsics robinBuiltins) False
19 let (args', env', showEvents) = processFlags args
2420 (_, reactors, results) <- processArgs args' env'
2521 writeResults $ reverse results
2622 (handlers, waitForEvents) <- orchestrate [(LineTerminal.init), (RandomSource.init)]
3232 (Positive m) <- arbitrary
3333 let n' = n `div` (m + 1)
3434 oneof [
35 Abort <$> (arbExpr n'),
36 List <$> (arbExprList n'),
37 Macro <$> (arbExpr n') <*> (arbExpr n') <*> (arbExpr n')
35 -- Abort <$> (arbExpr n'), NOTE many laws do not hold in the presence of abort values
36 List <$> (arbExprList n')
37 -- TODO Operator ??
3838 ]
3939
4040 arbExprList :: Int -> Gen [Expr]
171171 testSecondaryDefEnv secondaryDefs env
172172
173173
174 testAll = do
174 main = do
175175 testBuiltins
176176 testNoBuiltins
177177 testSecondaryDefs
1616 `abs` expects exactly one numeric argument.
1717
1818 | (abs)
19 ? abort (illegal-arguments ())
19 ? abort (illegal-arguments
2020
2121 | (abs 14 23)
22 ? abort (illegal-arguments (14 23))
22 ? abort (illegal-arguments
2323
2424 | (abs #t)
2525 ? abort (expected-number #t)
2626
2727 '<<SPEC'
2828
29 (define abs (macro (self args env)
30 (bind-args (a) args env
31 (if (equal? (sign a) 1) a (subtract 0 a)))))
29 (define abs (fun (a)
30 (if (equal? (sign a) 1) a (subtract 0 a))))
1010 `add` expects exactly two arguments.
1111
1212 | (add 14)
13 ? abort (illegal-arguments (14))
13 ? abort (illegal-arguments
1414
1515 | (add 6 7 7)
16 ? abort (illegal-arguments (6 7 7))
16 ? abort (illegal-arguments
1717
1818 Both of the arguments to `add` must be numbers.
1919
2525
2626 '<<SPEC'
2727
28 (define add (macro (self args env)
29 (bind-args (a b) args env
30 (subtract a (subtract 0 b)))))
28 (define add (fun (a b)
29 (subtract a (subtract 0 b))))
2222 ? abort
2323
2424 | (and #t #f #f)
25 ? abort (illegal-arguments (#t #f #f))
25 ? abort (illegal-arguments
2626
2727 `and` expects both of its arguments to be booleans.
2828
4141
4242 '<<SPEC'
4343
44 (define and (macro (self args env)
45 (if (equal? (tail (tail args)) ())
46 (if (eval env (head args))
47 (if (eval env (head (tail args))) #t #f)
48 #f)
49 (abort (list (literal illegal-arguments) args)))))
44 (define and (fun (a b)
45 (if a (if b #t #f) #f)))
11
22 -> Tests for functionality "Evaluate Robin Expression (with Small)"
33
4 `bind-args` is a macro for evaluating a list of arguments and binding
4 `bind-args` is a fexpr for evaluating a list of arguments and binding
55 them to the identifiers in a list of identifiers, as well as asserting
66 that the correct number of arguments have been given, and that none of
77 the arguments evaluated to an abort value.
3939 | a)
4040 ? abort (illegal-arguments ((subtract 5 4) (subtract 1 0)))
4141
42 This is how it might be used in a macro definition. The reason for the
42 This is how it might be used in a fexpr definition. The reason for the
4343 seemingly strange requirements of the second and third arguments should
44 become clear here: typically you would just pass the macro's `args` and
44 become clear here: typically you would just pass the fexpr's `args` and
4545 `env` to those arguments.
4646
47 | (bind add (macro (self args env)
47 | (bind add (fexpr (args env)
4848 | (bind-args (a b) args env
4949 | (subtract a (subtract 0 b))))
5050 | (add 4 (add 5 6)))
5151 = 15
5252
53 | (bind add (macro (self args env)
53 | (bind add (fexpr (args env)
5454 | (bind-args (a b) args env
5555 | (subtract a (subtract 0 b))))
5656 | (bind r 7
5757 | (add r r)))
5858 = 14
5959
60 | (bind add (macro (self args env)
60 | (bind add (fexpr (args env)
6161 | (bind-args (a b) args env
6262 | (subtract a (subtract 0 b))))
6363 | (add (subtract 0 0)))
6464 ? abort (illegal-arguments ((subtract 0 0)))
6565
66 | (bind add (macro (self args env)
66 | (bind add (fexpr (args env)
6767 | (bind-args (a b) args env
6868 | (subtract a (subtract 0 b))))
6969 | (add 9 9 9))
7070 ? abort (illegal-arguments (9 9 9))
7171
72 | (bind add (macro (self args env)
72 | (bind add (fexpr (args env)
7373 | (bind-args (a b) args env
7474 | (subtract a (subtract 0 b))))
7575 | (add 1 n))
7878 '<<SPEC'
7979
8080 (define bind-args
81 (macro (self args env)
81 (fexpr (args env)
8282 (let (
8383 (id-list (head args))
8484 (orig-val-list (eval env (head (tail args))))
8585 (given-env (eval env (head (tail (tail args)))))
8686 (expr (head (tail (tail (tail args)))))
87 (bind-args-r (macro (self args env)
87 (bind-args-r (fexpr (args env)
8888 (let (
89 (id-list (eval env (head args)))
90 (val-list (eval env (head (tail args))))
91 (env-acc (eval env (head (tail (tail args)))))
89 (self (eval env (head args)))
90 (id-list (eval env (head (tail args))))
91 (val-list (eval env (head (tail (tail args)))))
92 (env-acc (eval env (head (tail (tail (tail args))))))
9293 )
9394 (if (equal? id-list ())
9495 (if (equal? val-list ())
9697 (abort (list (literal illegal-arguments) orig-val-list)))
9798 (if (equal? val-list ())
9899 (abort (list (literal illegal-arguments) orig-val-list))
99 (recover
100 (eval given-env (head val-list))
101 actual (self
100 (bind actual (eval given-env (head val-list))
101 (self self
102102 (tail id-list) (tail val-list)
103103 (prepend
104104 (list (head id-list) actual)
105 env-acc))
106 error (abort error))))))))
107 (recover
108 (bind-args-r id-list orig-val-list env)
109 new-env (eval new-env expr)
110 error (abort error)))))
105 env-acc)))))))))
106 (bind new-env (bind-args-r bind-args-r id-list orig-val-list env)
107 (eval new-env expr)))))
0 ;'<<SPEC'
1
2 -> Tests for functionality "Evaluate Robin Expression (with Stdlib)"
3
4 `bind-vals` is a fexpr for bindings the components of a possibly-deep list
5 to the identifiers given in another possibly-deep list (of symbols).
6
7 It is similar to `bind-args` but doesn't evaluate the values that are
8 being bound.
9
10 | (bind-vals (a b) (literal (1 2))
11 | (list a b))
12 = (1 2)
13
14 Expressions in the list of values are not evaluated.
15
16 | (bind-vals (a b) (literal ((subtract 5 4) (subtract 10 1)))
17 | (list a b))
18 = ((subtract 5 4) (subtract 10 1))
19
20 It handles deep lists.
21
22 | (bind-vals (a (b c)) (literal (1 (2 3)))
23 | (list a b c))
24 = (1 2 3)
25
26 This is how it might be used in a fexpr definition.
27
28 | (bind add (fexpr (args env)
29 | (bind-vals (a b) args
30 | (subtract a (subtract 0 b))))
31 | (add 4 5))
32 = 9
33
34 | (bind add (fexpr (args env)
35 | (bind-vals (a b) args
36 | (subtract a (subtract 0 b))))
37 | (add 4 (add 5 6)))
38 ? (abort (expected-number (add 5 6)))
39
40 | (bind transducer (fexpr (args env)
41 | (bind-vals ((event-type event-payload) state) args
42 | (list state (list event-type event-payload))))
43 | (transducer (click (button 12345)) 0))
44 = (0 (click (button 12345)))
45
46 '<<SPEC'
47
48 (define bind-vals
49 (fexpr (args env)
50 (let (
51 (id-list (head args))
52 (val-list (eval env (head (tail args))))
53 (expr (head (tail (tail args))))
54 (bind-vals-r (fexpr (args env)
55 (let (
56 (self (eval env (head args)))
57 (id-list (eval env (head (tail args))))
58 (val-list (eval env (head (tail (tail args)))))
59 (expr (eval env (head (tail (tail (tail args))))))
60 )
61 (if (equal? id-list ())
62 expr
63 (let ((id (head id-list)) (val (head val-list)))
64 (if (list? id)
65 (self self id val
66 (self self (tail id-list) (tail val-list) expr))
67 (list (literal bind) id (list (literal literal) val)
68 (self self (tail id-list) (tail val-list) expr)))))))))
69 (eval env (bind-vals-r bind-vals-r id-list val-list expr)))))
00 ;'<<SPEC'
11
2 -> Tests for functionality "Evaluate Robin Expression (with Small)"
2 -> Tests for functionality "Evaluate Robin Expression (with literal and bind)"
33
44 `bind` binds a single identifier to the result of evaluating a single
55 expression, and makes that binding available in another expression which
66 it evaluates.
77
8 | (bind x (literal hello)
9 | (list x x))
8 | (bind x (literal hello)
9 | (prepend x (prepend x ())))
1010 = (hello hello)
1111
12 | (bind dup (macro (self args env)
13 | (list (head args) (head args)))
14 | (dup g))
12 | (bind dup (fexpr (args env)
13 | (prepend (head args) (prepend (head args) ())))
14 | (dup g))
1515 = (g g)
1616
17 | (bind dup (macro (self args env)
18 | (bind x (eval env (head args))
19 | (list x x)))
20 | (dup (literal g)))
17 | (bind dup (fexpr (args env)
18 | (bind x (eval env (head args))
19 | (prepend x (prepend x ()))))
20 | (dup (literal g)))
2121 = (g g)
2222
23 | (bind dup (macro (self args env)
24 | (bind x (eval env (head args))
25 | (list x x)))
26 | (dup (dup (literal g))))
23 | (bind dup (fexpr (args env)
24 | (bind x (eval env (head args))
25 | (prepend x (prepend x ()))))
26 | (dup (dup (literal g))))
2727 = ((g g) (g g))
2828
29 | (bind find (macro (self args env)
30 | (bind-args (alist key) args env
31 | (if (equal? alist (literal ())) (literal ())
32 | (if (equal? key (head (head alist)))
33 | (head alist)
34 | (self (tail alist) key)))))
35 | (find (literal ((c d) (e f) (a b))) (literal a)))
36 = (a b)
29 `bind` can bind a recursive fexpr to a name.
30
31 | (bind elem?-r
32 | (fexpr (args env)
33 | (bind self (eval env (head args))
34 | (bind target (eval env (head (tail args)))
35 | (bind items (eval env (head (tail (tail args))))
36 | (if (equal? items ()) #f
37 | (if (equal? (head items) target) #t
38 | (self self target (tail items))))))))
39 | (elem?-r elem?-r (literal c) (literal (a b c d e f))))
40 = #t
3741
3842 `bind` expects exactly three arguments, or else an abort value will be produced.
3943
40 | (bind smoosh (fun (x y) (list y x)))
44 | (bind smoosh (fun (x y) (list y x)))
4145 ? abort
4246
43 | (bind smoosh)
47 | (bind smoosh)
4448 ? abort
4549
46 | (bind)
50 | (bind)
51 ? abort
52
53 The identifier in a binding must be a symbol.
54
55 | (bind 3 1 3)
4756 ? abort
4857
4958 `bind` is basically equivalent to Scheme's `let`, but only one
5160
5261 '<<SPEC'
5362
54 (define bind (macro (self args env)
55 (eval
56 (prepend
57 (prepend (head args) (prepend (eval env (head (tail args)))
58 ())) env)
59 (head (tail (tail args))))))
63 (define bind (fexpr (args env)
64 (if
65 (symbol? (head args))
66 (eval
67 (prepend
68 (prepend (head args) (prepend (eval env (head (tail args)))
69 ())) env)
70 (head (tail (tail args))))
71 (abort ((literal expected-symbol) (head args))))))
1717 exactly one argument.
1818
1919 | (boolean? #t #f)
20 ? abort (illegal-arguments (#t #f))
20 ? abort (illegal-arguments
2121
2222 | (boolean?)
23 ? abort (illegal-arguments ())
23 ? abort (illegal-arguments
2424
2525 '<<SPEC'
2626
27 (define boolean? (macro (self args env)
28 (bind-args (b) args env
29 (if (equal? b #t)
27 (define boolean? (fun (b)
28 (if (equal? b #t)
29 #t
30 (if (equal? b #f)
3031 #t
31 (if (equal? b #f)
32 #t
33 #f)))))
32 #f))))
2929
3030 '<<SPEC'
3131
32 (define bound? (macro (self args env)
32 (define bound? (fexpr (args env)
3333 (if (equal? args ())
3434 (abort (list (literal illegal-arguments) args))
3535 (if (equal? (tail args) ())
3636 | (choose (#f 66) (else))
3737 ? abort
3838
39 Bindings can be seen inside each of the branches, and inside the `else`.
40
41 | (bind n (literal hi) (choose (#t n) (else (literal lo))))
42 = hi
43
44 | (bind n (literal hi) (choose (#f (literal lo)) (else n)))
45 = hi
46
3947 `choose` is basically equivalent to Scheme's `cond`.
4048
4149 '<<SPEC'
4250
43 (define choose (macro (self args env)
44 (bind branch (head args)
45 (bind test (head branch)
46 (bind then (head (tail branch))
47 (if (equal? test (literal else))
48 (eval env then)
49 (if (eval env test)
50 (eval env then)
51 (eval env (prepend self (tail args))))))))))
51 (define choose (fexpr (args env)
52 (bind choose-r (fexpr (iargs ienv)
53 (bind self (eval ienv (head iargs))
54 (bind items (eval ienv (head (tail iargs)))
55 (bind branch (head items)
56 (bind test (head branch)
57 (bind then (head (tail branch))
58 (if (equal? test (literal else))
59 (eval env then)
60 (if (eval env test)
61 (eval env then)
62 (self self (tail items))))))))))
63 (choose-r choose-r args))))
11
22 -> Tests for functionality "Evaluate Robin Expression (with List)"
33
4 | (delete (literal b) (literal ((a 1) (b 2) (c 3))))
4 `delete` evaluates its first argument to a value of any type, and its
5 second argument to obtain an alist. A new alist is returned which
6 contains no pairs whose first element are equal to the given element.
7
8 | (delete (literal b) (literal ((a 1) (b 2) (c 3))))
59 = ((a 1) (c 3))
610
7 | (delete (literal b) (literal ((a 1) (b 2) (c 3) (b 4))))
11 All pairs with the given element as key are removed.
12
13 | (delete (literal b) (literal ((a 1) (b 2) (c 3) (b 4))))
814 = ((a 1) (c 3))
915
10 | (delete (literal r) (literal ((a 1) (b 2) (c 3))))
16 If there are no pairs with the given element as key, the same alist
17 is returned unchanged.
18
19 | (delete (literal r) (literal ((a 1) (b 2) (c 3))))
1120 = ((a 1) (b 2) (c 3))
1221
1322 The following should be true for any identifier i and alist x.
1423
15 | (let ((i (literal a))
16 | (x (literal ((a 5) (b 7)))))
17 | (lookup i (delete i x)))
24 | (let ((i (literal a))
25 | (x (literal ((a 5) (b 7)))))
26 | (lookup i (delete i x)))
1827 = ()
1928
20 | (delete (literal q) 55)
29 | (delete (literal q) 55)
2130 ? abort (expected-list 55)
2231
23 | (delete (literal q) (literal ((a 7) 99 (q 4))))
32 | (delete (literal q) (literal ((a 7) 99 (q 4))))
2433 ? abort (expected-list 99)
2534
2635 '<<SPEC'
66 division computes by what integer the second number can be multiplied
77 to make it as big as possible without exceeding the first number.
88
9 | (divide 100 3)
9 | (divide 100 3)
1010 = 33
1111
12 | (divide (subtract 0 100) 3)
12 | (divide (subtract 0 100) 3)
1313 = -34
1414
15 | (divide 100 (subtract 0 3))
15 | (divide 100 (subtract 0 3))
1616 = -34
1717
18 | (divide 33 33)
18 | (divide 33 33)
1919 = 1
2020
21 | (divide 33 34)
21 | (divide 33 34)
2222 = 0
2323
24 | (divide 10 0)
24 | (divide 10 0)
2525 ? abort (division-by-zero 10)
2626
2727 Division by zero is undefined, and an abort value will be produced.
2828
29 | (divide 10 0)
29 | (divide 10 0)
3030 ? abort (division-by-zero 10)
3131
32 `div` expects exactly two arguments, both numbers.
32 `divide` expects exactly two arguments, both numbers.
3333
34 | (divide 14)
35 ? abort (illegal-arguments (14))
34 | (divide 14)
35 ? abort (illegal-arguments
3636
37 | (divide 14 23 57)
38 ? abort (illegal-arguments (14 23 57))
37 | (divide 14 23 57)
38 ? abort (illegal-arguments
3939
40 | (divide 14 #t)
40 | (divide 14 #t)
4141 ? abort (expected-number #t)
4242
43 | (divide #t 51)
43 | (divide #t 51)
4444 ? abort (expected-number #t)
4545
4646 '<<SPEC'
4747
48 (define divide (macro (self args env)
48 (define divide (fun (n d)
4949 (bind divide-r-pos (fun (self n d acc) ;(d is positive)
5050 (if (gt? d n)
5151 acc
5454 (if (gt? (abs d) n)
5555 (subtract 0 (add 1 acc))
5656 (self self (add n d) d (add 1 acc))))
57 (bind-args (n d) args env
58 (if (equal? d 0)
59 (abort (list (literal division-by-zero) n))
60 (if (lt? n 0)
61 (self (subtract 0 n) (subtract 0 d))
62 (if (gt? d 0)
63 (divide-r-pos divide-r-pos n d 0)
64 (divide-r-neg divide-r-neg n d 0)))))))))
57 (if (equal? d 0)
58 (abort (list (literal division-by-zero) n))
59 (bind n-prime (if (lt? n 0) (subtract 0 n) n)
60 (bind d-prime (if (lt? n 0) (subtract 0 d) d)
61 (if (gt? d-prime 0)
62 (divide-r-pos divide-r-pos n-prime d-prime 0)
63 (divide-r-neg divide-r-neg n-prime d-prime 0)))))))))
44 `env` evaluates to all the bindings in effect at the point of execution
55 where this form is encountered, as an alist.
66
7 | (bind find (macro (self args env)
8 | (bind-args (alist key) args env
9 | (if (equal? alist (literal ())) (literal ())
10 | (if (equal? key (head (head alist)))
11 | (head alist)
12 | (self (tail alist) key)))))
13 | (prepend
14 | (find (env) (literal symbol?)) (find (env) (literal prepend))))
15 = ((symbol? symbol?) prepend prepend)
7 | (bind lookup-r
8 | (fexpr (args env)
9 | (bind self (eval env (head args))
10 | (bind target (eval env (head (tail args)))
11 | (bind items (eval env (head (tail (tail args))))
12 | (if (equal? items ()) ()
13 | (if (equal? (head (head items)) target) (tail (head items))
14 | (self self target (tail items))))))))
15 | (lookup-r lookup-r (literal symbol?) (env)))
16 = (symbol?)
1617
1718 `env` expects no arguments. Any arguments supplied will be simply ignored
1819 and discarded, without being evaluated.
1920
20 | (bind find (macro (self args env)
21 | (bind-args (alist key) args env
22 | (if (equal? alist (literal ())) (literal ())
23 | (if (equal? key (head (head alist)))
24 | (head alist)
25 | (self (tail alist) key)))))
26 | (prepend
27 | (find (env find) (literal symbol?))
28 | (find (env (goofah whatever)) (literal prepend))))
29 = ((symbol? symbol?) prepend prepend)
21 | (bind lookup-r
22 | (fexpr (args env)
23 | (bind self (eval env (head args))
24 | (bind target (eval env (head (tail args)))
25 | (bind items (eval env (head (tail (tail args))))
26 | (if (equal? items ()) ()
27 | (if (equal? (head (head items)) target) (tail (head items))
28 | (self self target (tail items))))))))
29 | (lookup-r lookup-r (literal symbol?) (env (goofah whatever))))
30 = (symbol?)
3031
3132 '<<SPEC'
3233
33 (define env (macro (self args env) env))
34 (define env (fexpr (args env) env))
3535 '<<SPEC'
3636
3737 (define export
38 (macro (self args env)
38 (fexpr (args env)
3939 (filter (fun (binding) (elem? (head binding) args)) env)))
00 ;'<<SPEC'
11
22 -> Tests for functionality "Evaluate Robin Expression (with List)"
3
4 `extend` evaluates its first two arguments to values of any type, and its
5 third argument to obtain an alist. It returns a new alist which
6 associates the first two values in a new pair. The new pair shadows
7 any existing pairs with the same key that may already be in the alist.
38
49 | (extend (literal b) 6 (literal ((a 1) (b 2) (c 3))))
510 = ((b 6) (a 1) (b 2) (c 3))
0 ;'<<SPEC'
1
2 ### `fexpr` ###
3
4 -> Tests for functionality "Evaluate Robin Expression (with literal and bind)"