Initial import of Robin (not-quite-0.1-yet) sources.
catseye
13 years ago
0 | Robin is distributed under the following, BSD-compatible licenses. | |
1 | ||
2 | All documentation and tests are covered by this license, modelled | |
3 | after the "Report on the Programming Language Haskell 98" license: | |
4 | ||
5 | ----------------------------------------------------------------------------- | |
6 | ||
7 | Copyright (c)2012 Chris Pressey, Cat's Eye Technologies. | |
8 | ||
9 | The authors intend this Report to belong to the entire Robin | |
10 | community, and so we grant permission to copy and distribute it for | |
11 | any purpose, provided that it is reproduced in its entirety, | |
12 | including this Notice. Modified versions of this Report may also be | |
13 | copied and distributed for any purpose, provided that the modified | |
14 | version is clearly presented as such, and that it does not claim to | |
15 | be a definition of the Robin Programming Language. | |
16 | ||
17 | ----------------------------------------------------------------------------- | |
18 | ||
19 | All source code for the reference interpreter, except the Robin.Chan | |
20 | module, is covered by this license: | |
21 | ||
22 | ----------------------------------------------------------------------------- | |
23 | ||
24 | Copyright (c)2012 Cat's Eye Technologies. All rights reserved. | |
25 | ||
26 | Redistribution and use in source and binary forms, with or without | |
27 | modification, are permitted provided that the following conditions | |
28 | are met: | |
29 | ||
30 | 1. Redistributions of source code must retain the above copyright | |
31 | notices, this list of conditions and the following disclaimer. | |
32 | 2. Redistributions in binary form must reproduce the above copyright | |
33 | notices, this list of conditions, and the following disclaimer in | |
34 | the documentation and/or other materials provided with the | |
35 | distribution. | |
36 | 3. Neither the names of the copyright holders nor the names of their | |
37 | contributors may be used to endorse or promote products derived | |
38 | from this software without specific prior written permission. | |
39 | ||
40 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
41 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
42 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
43 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
44 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
45 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
46 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
47 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
48 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
49 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
50 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
51 | POSSIBILITY OF SUCH DAMAGE. | |
52 | ||
53 | ----------------------------------------------------------------------------- | |
54 | ||
55 | The Robin.Chan module is a derivative work based on the | |
56 | Control.Concurrent.Chan module from the GHC project, and is thus | |
57 | covered by this license: | |
58 | ||
59 | ----------------------------------------------------------------------------- | |
60 | ||
61 | The Glasgow Haskell Compiler License | |
62 | ||
63 | Copyright 2004, The University Court of the University of Glasgow. | |
64 | All rights reserved. | |
65 | ||
66 | Redistribution and use in source and binary forms, with or without | |
67 | modification, are permitted provided that the following conditions are met: | |
68 | ||
69 | - Redistributions of source code must retain the above copyright notice, | |
70 | this list of conditions and the following disclaimer. | |
71 | ||
72 | - Redistributions in binary form must reproduce the above copyright notice, | |
73 | this list of conditions and the following disclaimer in the documentation | |
74 | and/or other materials provided with the distribution. | |
75 | ||
76 | - Neither name of the University nor the names of its contributors may be | |
77 | used to endorse or promote products derived from this software without | |
78 | specific prior written permission. | |
79 | ||
80 | THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF | |
81 | GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
82 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
83 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
84 | UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE | |
85 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
86 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
87 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
88 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
89 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
90 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | |
91 | DAMAGE. |
0 | > module Main where | |
1 | ||
2 | > import Data.Ratio | |
3 | ||
4 | > import System | |
5 | ||
6 | > import Control.Concurrent (myThreadId) | |
7 | ||
8 | > import Robin.Expr | |
9 | > import Robin.Parser | |
10 | > import Robin.Chan | |
11 | > import Robin.IEnv | |
12 | > import qualified Robin.Env as Env | |
13 | > import Robin.Eval | |
14 | > import Robin.Core | |
15 | > import Robin.Concurrency | |
16 | > import Robin.Exception | |
17 | ||
18 | Module Loading | |
19 | -------------- | |
20 | ||
21 | > loadModule :: String -> Integer -> Integer -> IO Expr | |
22 | ||
23 | > loadModule "core" 0 1 = moduleCore | |
24 | > loadModule "concurrency" 0 1 = moduleConcurrency | |
25 | > loadModule "exception" 0 1 = moduleException | |
26 | > loadModule other major minor = | |
27 | > let | |
28 | > filename = "module/" ++ other ++ "_" ++ (show major) ++ "_" ++ (show minor) ++ ".robin" | |
29 | > in do | |
30 | > mod <- readFile filename | |
31 | > ast <- return $ insistParse mod | |
32 | > evalRobin ast | |
33 | ||
34 | > loadModules Null = do | |
35 | > return Env.empty | |
36 | > loadModules (Pair (Symbol name) (Pair version rest)) = do | |
37 | > (major, minor) <- parseVersion version | |
38 | > nextEnv <- loadModules rest | |
39 | > thisEnv <- loadModule name major minor | |
40 | > return $ Env.union nextEnv thisEnv | |
41 | ||
42 | > parseVersion (Pair (Number major) (Number minor)) = do | |
43 | > case (denominator major, denominator minor) of | |
44 | > (1, 1) -> return (numerator major, numerator minor) | |
45 | > _ -> error "version number components can't be fractions" | |
46 | ||
47 | > evalRobin (Pair (Symbol "robin") (Pair version (Pair modules (Pair expr Null)))) = do | |
48 | > (major, minor) <- parseVersion version | |
49 | > case (major, minor) of | |
50 | > (0, 1) -> do | |
51 | > initialEnv <- loadModules modules | |
52 | > threadId <- myThreadId | |
53 | > chan <- newChan | |
54 | > let ienv = newIEnv (stop) threadId chan | |
55 | > eval initialEnv ienv expr (\x -> do return x) | |
56 | > _ -> error ("unsupported language version " ++ show version) | |
57 | ||
58 | Command-line Entry Point | |
59 | ------------------------ | |
60 | ||
61 | > main = do | |
62 | > args <- getArgs | |
63 | > case args of | |
64 | > [filename] -> do | |
65 | > program <- readFile filename | |
66 | > case parseRobin program of | |
67 | > Right ast -> do | |
68 | > result <- evalRobin ast | |
69 | > putStrLn $ show result | |
70 | > exitWith ExitSuccess | |
71 | > Left problem -> do | |
72 | > print problem | |
73 | > exitWith $ ExitFailure 1 | |
74 | > _ -> do | |
75 | > putStrLn "Usage: robin source.robin" | |
76 | > exitWith $ ExitFailure 1 |
0 | Robin | |
1 | ===== | |
2 | ||
3 | Robin is a programming language which draws from [Scheme][] (via [Pixley][]), | |
4 | [PicoLisp][], and [Erlang][]. | |
5 | ||
6 | Robin's core language is quite ascetic; however, Robin supports a module | |
7 | system, by which functionality can be brought in from modules (written in | |
8 | Robin, or some other language,) which expose their functionality to programs | |
9 | and other modules. | |
10 | ||
11 | The standard modules include a small "standard library" to make programming | |
12 | slightly easier, a module for concurrent processes with message-passing, and | |
13 | a module for handling exceptions. | |
14 | ||
15 | Robin programs are homoiconic, and presented in a S-expression-based syntax. | |
16 | ||
17 | Instead of function values, Robin supplies _macros_ as the primitive | |
18 | abstraction. Robin's macros are somewhat like PicoLisp's one-argument | |
19 | lambas -- they do not automatically evaluate their arguments. Function | |
20 | values are built on top of macros, using the built-in macro `eval`. | |
21 | ||
22 | Like [Erlang][], Robin is purely functional except for message-passing. | |
23 | That is, functions have no side-effects, with the single exception of | |
24 | being able to send messages to, and receive messages from, other processes. | |
25 | All facilities of the operating system are modelled as such processes. | |
26 | ||
27 | Lastly, Robin supports a simple system of raising and handling exceptions. | |
28 | This helps define the semantics of otherwise undefined operations, such as | |
29 | trying to obtain the tail of a non-pair. | |
30 | ||
31 | [Erlang]: http://erlang.org/ | |
32 | [PicoLisp]: http://picolisp.com/ | |
33 | [Pixley]: http://catseye.tc/projects/pixley/ | |
34 | [Scheme]: http://schemers.org/ | |
35 | ||
36 | Distribution | |
37 | ------------ | |
38 | ||
39 | The current version of Robin under development is version 0.1. Even it | |
40 | is unreleased, so what you're looking at here is pure "technology | |
41 | preview" stuff. Expect everything to change, perhaps drastically. | |
42 | ||
43 | Documentation | |
44 | ------------- | |
45 | ||
46 | Robin's fundamental semantics are documented in | |
47 | [doc/module/Robin.falderal](doc/module/Robin.falderal). From there you | |
48 | will find links to documentation on each of the standard modules as well. | |
49 | ||
50 | Goals | |
51 | ----- | |
52 | ||
53 | * To not be unduly burdensome to implement or analyze. The core language | |
54 | is kept very small, the "standard library" can be written in Robin itself, | |
55 | and features such as concurrency and exceptions are optional. The core | |
56 | language is purely functional, to keep it mathematically simple, and the | |
57 | reference implementation is in Haskell, which is a lot closer to an | |
58 | "executable semantics" than one in, say, C would be. The functionality | |
59 | of the language is thoroughly tested. Both the implementation and the | |
60 | test suite are written in a literate style, to keep the prose of the | |
61 | specification in close proximity to the code so that they can be easily | |
62 | checked against each other for inconsistencies. | |
63 | ||
64 | * To err on the side of beauty and simplicity and orthogonality, rather | |
65 | than efficient implementation or expediency. | |
66 | ||
67 | * At the same time, to allow the programmer to do "real" work, like | |
68 | interfacing with an actual computer. | |
69 | ||
70 | * At the same time as that, to be decoupled from any particular computer | |
71 | or operating system, as far as possible. The language does not specify | |
72 | how Robin programs should be run, nor how to locate modules that are | |
73 | imported. Devices are abstracted to "virtual devices" and are modelled | |
74 | as processes; input and output are done with message-passing. | |
75 | ||
76 | * To minimize atavisms and jargon. The legacy of Robin's lexicon is | |
77 | Scheme, which itself comes from the legacy of Lisp; while there are some | |
78 | good patterns here (like predicates whose names end in `?`), there are | |
79 | also a lot of anachronisms (like `car` and `cdr`) which should be | |
80 | jettisoned. Proper English words should be used instead, although of | |
81 | course there is room for abbreviations when they are unambiguous | |
82 | (`env`, `eval`, `expr`, `arith`, and so forth.) | |
83 | ||
84 | * To serve as an outlet for my predilictions. Sometimes, when using a | |
85 | language, you come across a feature or aspect that just strikes you | |
86 | as wrong-headed, and it makes you want to build something that doesn't | |
87 | irritate you as badly. Robin is, to some extent, that, for me. | |
88 | ||
89 | * To not be taken *too* seriously -- it has many of the attributes of a | |
90 | production language, but it *is* something I am undertaking for fun. | |
91 | ||
92 | Plans | |
93 | ----- | |
94 | ||
95 | * Establish the relationship between exceptions and processes -- if a | |
96 | process raises an exception that it does not catch, it should send | |
97 | a message to its parent (the process that spawned it.) | |
98 | ||
99 | * More tests for concurrency, exceptions, and macros. | |
100 | ||
101 | * A simple I/O module, for reading and writing S-expressions to standard | |
102 | input and output. | |
103 | ||
104 | * Add an opaque type -- opaque values have internals that can only be | |
105 | accessed inside the module in which they were created. | |
106 | ||
107 | * Add comments to the language. | |
108 | ||
109 | * Add sugar for strings to the language (internally they would just be | |
110 | lists of numbers, which are Unicode code points.) | |
111 | ||
112 | * A simple I/O module, for reading and writing to standard input and | |
113 | output. We'll start simple and crude, possibly with just line- | |
114 | buffered string I/O, or S-expressions supported. | |
115 | ||
116 | * A `list` module, written in Robin, with the usual `map`, `fold`, | |
117 | `length`, and so forth. Possibly also containing alist macros. | |
118 | ||
119 | * An `env` module, with macros for manipulating the environment, like | |
120 | `unbind` and `sandbox`. | |
121 | ||
122 | * Document the "why" behind some of the design decisions. | |
123 | ||
124 | * Document the literate Haskell implementation better -- right now it's | |
125 | pretty scant. | |
126 | ||
127 | * Document the evaluation rules (they're very similar to Scheme's, but | |
128 | they should still be written down.) | |
129 | ||
130 | * Make the tests for `core` only ever import the `core` module -- rewrite | |
131 | those tests which currently import `small`, although they may be pretty | |
132 | ugly expressed purely in `core` terms. | |
133 | ||
134 | * Support qualifiers during module import. Have identifiers be imported | |
135 | from modules qualified by default, and have something to turn this off. | |
136 | Possibly support "only" and "hiding" qualifiers. | |
137 | ||
138 | * Do not export `make-env` and `make-env-wrap` from `small` -- encapsulate | |
139 | these inside the definition of `fun`. | |
140 | ||
141 | * Write _Hunt the Wumpus_ in Robin! |
0 | > module Robin.Chan | |
1 | ||
2 | This module implements channels for message-passing in Robin. It is | |
3 | derived from the `[Control.Concurrent.Chan][]` library module from GHC, | |
4 | and is thus (c)2001 The University of Glasgow, and covered under a | |
5 | [BSD-style license][]. | |
6 | ||
7 | [Control.Concurrent.Chan]: http://www.haskell.org/ghc/docs/latest/html/libraries/base/src/Control-Concurrent-Chan.html | |
8 | [BSD-style license]: http://www.haskell.org/ghc/docs/latest/html/libraries/base/LICENSE | |
9 | ||
10 | It has been modified to associate each channel with exactly one thread, and | |
11 | it allows only that thread to read values from the channel, unget items to | |
12 | the channel, or check the channel for emptiness. This both makes the | |
13 | message-passing semantics closer to those of Erlang (where each process | |
14 | has private access to its own message queue), and prevents a [potential | |
15 | deadlock][] between reading and ungetting (or reading and checking for | |
16 | emptiness.) | |
17 | ||
18 | [potential deadlock]: http://hackage.haskell.org/trac/ghc/ticket/4154 | |
19 | ||
20 | > ( | |
21 | > Chan, -- abstract | |
22 | > newChan, -- :: IO (Chan a) | |
23 | > writeChan, -- :: Chan a -> a -> IO () | |
24 | > readChan, -- :: Chan a -> IO a | |
25 | > unGetChan, -- :: Chan a -> a -> IO () | |
26 | > isEmptyChan -- :: Chan a -> IO Bool | |
27 | > ) where | |
28 | ||
29 | > import Control.Concurrent (ThreadId, myThreadId) | |
30 | > import Control.Concurrent.MVar | |
31 | ||
32 | `Chan` is an abstract type representing an unbounded FIFO channel. | |
33 | A channel is represented by two `MVar`s keeping track of the two ends | |
34 | of the channel contents,i.e., the read- and write ends. Empty `MVar`s | |
35 | are used to handle consumers trying to read from an empty channel. | |
36 | ||
37 | > data Chan a | |
38 | > = Chan ThreadId | |
39 | > (MVar (Stream a)) | |
40 | > (MVar (Stream a)) | |
41 | > deriving Eq | |
42 | ||
43 | > type Stream a = MVar (ChItem a) | |
44 | ||
45 | > data ChItem a = ChItem a (Stream a) | |
46 | ||
47 | See the Concurrent Haskell paper for a diagram explaining the | |
48 | how the different channel operations proceed. | |
49 | ||
50 | @newChan@ sets up the read and write end of a channel by initialising | |
51 | these two `MVar`s with an empty `MVar`. | |
52 | ||
53 | Build and return a new instance of `Chan`. | |
54 | ||
55 | > newChan :: IO (Chan a) | |
56 | > newChan = do | |
57 | > threadId <- myThreadId | |
58 | > hole <- newEmptyMVar | |
59 | > readVar <- newMVar hole | |
60 | > writeVar <- newMVar hole | |
61 | > return (Chan threadId readVar writeVar) | |
62 | ||
63 | To put an element on a channel, a new hole at the write end is created. | |
64 | What was previously the empty `MVar` at the back of the channel is then | |
65 | filled in with a new stream element holding the entered value and the | |
66 | new hole. | |
67 | ||
68 | Write a value to a `Chan`. | |
69 | ||
70 | > writeChan :: Chan a -> a -> IO () | |
71 | > writeChan (Chan _ _ writeVar) val = do | |
72 | > new_hole <- newEmptyMVar | |
73 | > modifyMVar_ writeVar $ \old_hole -> do | |
74 | > putMVar old_hole (ChItem val new_hole) | |
75 | > return new_hole | |
76 | ||
77 | Read the next value from the 'Chan'. | |
78 | ||
79 | > readChan :: Chan a -> IO a | |
80 | > readChan (Chan threadId readVar _) = do | |
81 | > me <- myThreadId | |
82 | > case threadId == me of | |
83 | > True -> | |
84 | > modifyMVar readVar $ \read_end -> do | |
85 | > (ChItem val new_read_end) <- readMVar read_end | |
86 | > return (new_read_end, val) | |
87 | > False -> | |
88 | > error ((show me) ++ " not allowed to read from this Chan") | |
89 | ||
90 | Put a data item back onto a channel, where it will be the next item read. | |
91 | ||
92 | > unGetChan :: Chan a -> a -> IO () | |
93 | > unGetChan (Chan threadId readVar _) val = do | |
94 | > me <- myThreadId | |
95 | > case threadId == me of | |
96 | > True -> do | |
97 | > new_read_end <- newEmptyMVar | |
98 | > modifyMVar_ readVar $ \read_end -> do | |
99 | > putMVar new_read_end (ChItem val read_end) | |
100 | > return new_read_end | |
101 | > False -> | |
102 | > error ((show me) ++ " not allowed to unget to this Chan") | |
103 | ||
104 | Return `True` if the supplied `Chan` is empty. | |
105 | ||
106 | > isEmptyChan :: Chan a -> IO Bool | |
107 | > isEmptyChan (Chan threadId readVar writeVar) = do | |
108 | > me <- myThreadId | |
109 | > case threadId == me of | |
110 | > True -> | |
111 | > withMVar readVar $ \r -> do | |
112 | > w <- readMVar writeVar | |
113 | > let eq = r == w | |
114 | > eq `seq` return eq | |
115 | > False -> | |
116 | > error ((show me) ++ " not allowed to check this Chan for emptiness") |
0 | > module Robin.Concurrency where | |
1 | ||
2 | > import Control.Concurrent (forkIO, myThreadId) | |
3 | > import Robin.Chan | |
4 | ||
5 | > import Robin.IEnv | |
6 | > import qualified Robin.Env as Env | |
7 | > import Robin.Expr | |
8 | > import Robin.Eval | |
9 | > import Robin.Core | |
10 | ||
11 | Concurrency | |
12 | =========== | |
13 | ||
14 | First, some helpers: | |
15 | ||
16 | > myself ienv = | |
17 | > Pid (getThreadId ienv) (getChannel ienv) | |
18 | ||
19 | TODO: the initial continuation should not be `stop`, but rather, | |
20 | something that sends a `oops I bombed` message to the parent pid. | |
21 | ||
22 | > launch :: Expr -> IEnv Expr -> Chan Expr -> Expr -> IO () | |
23 | ||
24 | > launch env ienv chan e = do | |
25 | > let expr = (Pair e (Pair (myself ienv) Null)) | |
26 | > tid <- myThreadId | |
27 | > let ienv = newIEnv (stop) tid chan | |
28 | > eval env ienv expr (\x -> do return Null) | |
29 | > return () | |
30 | ||
31 | > getChan (Pid _ c) = c | |
32 | ||
33 | > isPid (Pid _ _) = True | |
34 | > isPid _ = False | |
35 | ||
36 | Now the exported functions. | |
37 | ||
38 | > robinMyself env ienv Null cc = do | |
39 | > cc $ myself ienv | |
40 | > robinMyself env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
41 | ||
42 | > pidP = predP isPid | |
43 | ||
44 | > spawn env ienv (Pair e Null) cc = do | |
45 | > chan <- newChan | |
46 | > eval env ienv e (\macro -> | |
47 | > case isMacro macro of | |
48 | > True -> do | |
49 | > threadId <- forkIO (launch env ienv chan macro) | |
50 | > cc $ Pid threadId chan | |
51 | > other -> raise ienv (Pair (Symbol "expected-macro") macro)) | |
52 | > spawn env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
53 | ||
54 | > send env ienv (Pair pidExpr (Pair msgExpr (Pair body Null))) cc = do | |
55 | > eval env ienv pidExpr (\pid -> | |
56 | > case isPid pid of | |
57 | > True -> | |
58 | > eval env ienv msgExpr (\msg -> do | |
59 | > writeChan (getChan pid) msg | |
60 | > eval env ienv body cc) | |
61 | > other -> raise ienv (Pair (Symbol "expected-pid") pid)) | |
62 | > send env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
63 | ||
64 | > recv env ienv (Pair id@(Symbol _) (Pair body Null)) cc = do | |
65 | > message <- readChan $ getChan $ myself ienv | |
66 | > eval (Env.insert id message env) ienv body cc | |
67 | > recv env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
68 | ||
69 | > msgsP env ienv Null cc = do | |
70 | > isEmpty <- isEmptyChan $ getChan $ myself ienv | |
71 | > cc $ Boolean $ not isEmpty | |
72 | > msgsP env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
73 | ||
74 | Module Definition | |
75 | ----------------- | |
76 | ||
77 | > moduleConcurrency :: IO Expr | |
78 | ||
79 | > moduleConcurrency = do | |
80 | > return $ Env.fromList $ map (\(name,bif) -> (name, Builtin name bif)) | |
81 | > [ | |
82 | > ("myself", robinMyself), | |
83 | > ("pid?", pidP), | |
84 | > ("spawn", spawn), | |
85 | > ("send", send), | |
86 | > ("recv", recv), | |
87 | > ("msgs?", msgsP) | |
88 | > ] |
0 | > module Robin.Core where | |
1 | ||
2 | > import qualified Robin.Env as Env | |
3 | > import Robin.Expr | |
4 | > import Robin.Eval | |
5 | ||
6 | Core | |
7 | ==== | |
8 | ||
9 | > robinHead env ienv (Pair expr Null) cc = do | |
10 | > eval env ienv expr (\x -> | |
11 | > case x of | |
12 | > (Pair a _) -> cc $ a | |
13 | > other -> raise ienv (Pair (Symbol "expected-pair") other)) | |
14 | > robinHead env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
15 | ||
16 | > robinTail env ienv (Pair expr Null) cc = do | |
17 | > eval env ienv expr (\x -> | |
18 | > case x of | |
19 | > (Pair _ b) -> cc $ b | |
20 | > other -> raise ienv (Pair (Symbol "expected-pair") other)) | |
21 | > robinTail env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
22 | ||
23 | > robinPair env ienv (Pair e1 (Pair e2 Null)) cc = do | |
24 | > eval env ienv e1 (\x1 -> eval env ienv e2 (\x2 -> cc $ Pair x1 x2)) | |
25 | > robinPair env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
26 | ||
27 | > equalP env ienv (Pair e1 (Pair e2 Null)) cc = do | |
28 | > eval env ienv e1 (\x1 -> eval env ienv e2 (\x2 -> cc $ Boolean (x1 == x2))) | |
29 | > equalP env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
30 | ||
31 | > predP pred env ienv (Pair e Null) cc = do | |
32 | > eval env ienv e (\x -> cc $ Boolean (pred x)) | |
33 | > predP pred env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
34 | ||
35 | > symbolP = predP isSymbol | |
36 | > booleanP = predP isBoolean | |
37 | > pairP = predP isPair | |
38 | > macroP = predP isMacro | |
39 | ||
40 | > robinIf env ienv (Pair test (Pair texpr (Pair fexpr Null))) cc = do | |
41 | > eval env ienv test (\x -> | |
42 | > case x of | |
43 | > Boolean True -> eval env ienv texpr cc | |
44 | > Boolean False -> eval env ienv fexpr cc | |
45 | > other -> | |
46 | > raise ienv (Pair (Symbol "expected-boolean") other)) | |
47 | > robinIf env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
48 | ||
49 | > robinEval env ienv (Pair envlist (Pair form Null)) cc = do | |
50 | > eval env ienv envlist (\newEnv -> | |
51 | > eval env ienv form (\body -> do | |
52 | > eval newEnv ienv body cc)) | |
53 | > robinEval env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
54 | ||
55 | > macro env ienv (Pair args@(Pair (Symbol selfS) (Pair (Symbol argsS) (Pair (Symbol envS) Null))) (Pair body Null)) cc = do | |
56 | > cc $ Macro env args body | |
57 | > macro env ienv other cc = raise ienv (Pair (Symbol "illegal-arguments") other) | |
58 | ||
59 | > robinRaise env ienv (Pair expr Null) cc = | |
60 | > eval env ienv expr (\v -> raise ienv v) | |
61 | ||
62 | Module Definition | |
63 | ----------------- | |
64 | ||
65 | > moduleCore :: IO Expr | |
66 | ||
67 | > moduleCore = do | |
68 | > return $ Env.fromList $ map (\(name,bif) -> (name, Builtin name bif)) | |
69 | > [ | |
70 | > ("head", robinHead), | |
71 | > ("tail", robinTail), | |
72 | > ("pair", robinPair), | |
73 | > ("pair?", pairP), | |
74 | > ("symbol?", symbolP), | |
75 | > ("boolean?", booleanP), | |
76 | > ("macro?", macroP), | |
77 | > ("equal?", equalP), | |
78 | > ("macro", macro), | |
79 | > ("eval", robinEval), | |
80 | > ("if", robinIf), | |
81 | > ("raise", robinRaise) | |
82 | > ] | |
83 |
0 | > module Robin.Env where | |
1 | ||
2 | > import Robin.IEnv | |
3 | > import Robin.Expr | |
4 | ||
5 | Environments | |
6 | ============ | |
7 | ||
8 | An environment is an alist which associates symbols with | |
9 | values (arbitrary S-expressions). | |
10 | ||
11 | > empty = Null | |
12 | ||
13 | > insert s@(Symbol _) value env = | |
14 | > Pair (Pair s value) env | |
15 | ||
16 | Merge two environments to yield a new environment. The merge is | |
17 | left-biased; entries in the left env override those in the right. | |
18 | ||
19 | > union Null env = env | |
20 | > union (Pair binding rest) env = | |
21 | > Pair binding (union rest env) | |
22 | ||
23 | > fromList [] = | |
24 | > Null | |
25 | > fromList ((id, val):xs) = | |
26 | > Pair (Pair (Symbol id) val) (fromList xs) |
0 | > module Robin.Eval where | |
1 | ||
2 | > import Robin.IEnv | |
3 | > import qualified Robin.Env as Env | |
4 | > import Robin.Expr | |
5 | ||
6 | Evaluator | |
7 | ========= | |
8 | ||
9 | This is written in continuation-passing style. | |
10 | ||
11 | Every evaluation function is (and takes) a continuation, which is implemented | |
12 | as a function with signature: | |
13 | ||
14 | Expr -> IEnv -> Expr -> (Expr -> IO Expr) -> IO Expr | |
15 | ||
16 | (This is actually the `Bif` type from `Robin.Expr`.) | |
17 | ||
18 | The first argument is the Robin environment, which is directly visible | |
19 | (and modifiable, during `eval`) by Robin program. The second is the | |
20 | internal context, which contains things like the exception handler, etc. | |
21 | ||
22 | When evaluating a symbol, look it up in the environment to obtain a | |
23 | value. Then continue the current continuation with that value. | |
24 | ||
25 | > eval :: Bif | |
26 | ||
27 | > eval Null ienv s@(Symbol _) cc = | |
28 | > raise ienv (Pair (Symbol "unbound-identifier") s) | |
29 | > eval (Pair (Pair id@(Symbol _) value) env) ienv s@(Symbol _) cc | |
30 | > | id == s = cc value | |
31 | > | otherwise = eval env ienv s cc | |
32 | > eval (Pair (Pair other _) env) ienv s@(Symbol _) cc = | |
33 | > raise ienv (Pair (Symbol "expected-symbol") other) | |
34 | > eval (Pair head tail) ienv s@(Symbol _) cc = | |
35 | > raise ienv (Pair (Symbol "expected-pair") head) | |
36 | > eval env ienv s@(Symbol _) cc = | |
37 | > raise ienv (Pair (Symbol "expected-pair") env) | |
38 | ||
39 | Evaluating a pair means we must make several evaluations. We | |
40 | evaluate the head to obtain something to apply (which must be a | |
41 | macro, built-in or not.) We then apply the body of the macro, | |
42 | passing it the tail of the pair. | |
43 | ||
44 | > eval env ienv (Pair applierExpr actuals) cc = do | |
45 | > eval env ienv applierExpr (\applier -> | |
46 | > case applier of | |
47 | > m@(Macro _ _ body) -> do | |
48 | > eval (makeMacroEnv env actuals m) ienv body cc | |
49 | > Builtin _ fun -> do | |
50 | > fun env ienv actuals cc | |
51 | > other -> | |
52 | > raise ienv (Pair (Symbol "inapplicable-object") other)) | |
53 | ||
54 | Everything else just evaluates to itself. Continue the current | |
55 | continuation with that value. | |
56 | ||
57 | > eval env ienv e cc = do | |
58 | > cc e | |
59 | ||
60 | Helper function | |
61 | --------------- | |
62 | ||
63 | > makeMacroEnv env actuals m@(Macro closedEnv argList _) = | |
64 | > let | |
65 | > (Pair argSelf@(Symbol _) (Pair argFormal@(Symbol _) | |
66 | > (Pair envFormal@(Symbol _) Null))) = argList | |
67 | > newEnv = Env.insert argSelf m closedEnv | |
68 | > newEnv' = Env.insert argFormal actuals newEnv | |
69 | > newEnv'' = Env.insert envFormal env newEnv' | |
70 | > in | |
71 | > newEnv'' | |
72 | ||
73 | Exception Handler | |
74 | ----------------- | |
75 | ||
76 | > raise :: IEnv Expr -> Expr -> IO Expr | |
77 | > raise ienv expr = | |
78 | > (getExceptionHandler ienv) expr |
0 | > module Robin.Exception where | |
1 | ||
2 | > import Robin.IEnv | |
3 | > import qualified Robin.Env as Env | |
4 | > import Robin.Expr | |
5 | > import Robin.Eval | |
6 | > import Robin.Core | |
7 | ||
8 | Exception | |
9 | ========= | |
10 | ||
11 | > robinCatch env ienv (Pair id@(Symbol _) (Pair handler (Pair body Null))) cc = | |
12 | > let | |
13 | > handlerContinuation = (\errvalue -> | |
14 | > eval (Env.insert id errvalue env) ienv handler cc) | |
15 | > ienv' = setExceptionHandler handlerContinuation ienv | |
16 | > in | |
17 | > eval env ienv' body cc | |
18 | > robinCatch env ienv other cc = raise ienv ( | |
19 | > Pair (Symbol "illegal-arguments") other) | |
20 | ||
21 | Module Definition | |
22 | ----------------- | |
23 | ||
24 | > moduleException :: IO Expr | |
25 | ||
26 | > moduleException = | |
27 | > return $ Env.fromList $ map (\(name,bif) -> (name, Builtin name bif)) | |
28 | > [ | |
29 | > ("catch", robinCatch), | |
30 | > ("raise", robinRaise) | |
31 | > ] |
0 | > module Robin.Expr where | |
1 | ||
2 | > import Data.Ratio | |
3 | ||
4 | > import Control.Concurrent (ThreadId) | |
5 | ||
6 | > import Robin.IEnv | |
7 | > import Robin.Chan | |
8 | ||
9 | Definitions | |
10 | =========== | |
11 | ||
12 | A "bif" is a "built-in function" -- an acronym borrowed from Erlang, | |
13 | though somewhat regrettably, as it's quite lacking as a name. | |
14 | ||
15 | > type Bif = Expr -> IEnv Expr -> Expr -> (Expr -> IO Expr) -> IO Expr | |
16 | ||
17 | > data Expr = Symbol String | |
18 | > | Null | |
19 | > | Boolean Bool | |
20 | > | Number Rational | |
21 | > | Pid ThreadId (Chan Expr) | |
22 | > | Macro Expr Expr Expr | |
23 | > | Builtin String Bif | |
24 | > | Pair Expr Expr | |
25 | ||
26 | > instance Eq Expr where | |
27 | > (Symbol x) == (Symbol y) = x == y | |
28 | > Null == Null = True | |
29 | > (Boolean x) == (Boolean y) = x == y | |
30 | > (Number x) == (Number y) = x == y | |
31 | > (Pid x _) == (Pid y _) = x == y | |
32 | > (Macro _ _ _) == (Macro _ _ _) = False | |
33 | > (Builtin x _) == (Builtin y _) = x == y | |
34 | > (Pair x1 x2) == (Pair y1 y2) = (x1 == y1) && (x2 == y2) | |
35 | > _ == _ = False | |
36 | ||
37 | > instance Show Expr where | |
38 | > show (Symbol s) = s | |
39 | > show Null = "()" | |
40 | > show (Boolean True) = "#t" | |
41 | > show (Boolean False) = "#f" | |
42 | > show (Number n) = if | |
43 | > denominator n == 1 | |
44 | > then | |
45 | > show $ numerator n | |
46 | > else | |
47 | > ((show $ numerator n) ++ | |
48 | > "/" ++ (show $ denominator n)) | |
49 | > show (Pid t c) = "(pid " ++ (show t) ++ ")" | |
50 | > show (Macro env args body) = ("(macro " ++ (show args) ++ | |
51 | > " " ++ (show body) ++ ")") | |
52 | > show (Builtin name _) = "(builtin " ++ name ++ ")" | |
53 | > show e@(Pair _ _) = "(" ++ (showl e) | |
54 | ||
55 | > showl Null = ")" | |
56 | > showl (Pair a Null) = (show a) ++ ")" | |
57 | > showl (Pair a b) = (show a) ++ " " ++ (showl b) | |
58 | > showl other = ". " ++ (show other) ++ ")" | |
59 | ||
60 | Helper Functions | |
61 | ---------------- | |
62 | ||
63 | A helper function to make Pair lists from Haskell lists. | |
64 | ||
65 | > robinizeList [] last = | |
66 | > last | |
67 | > robinizeList (x:xs) last = | |
68 | > Pair x (robinizeList xs last) | |
69 | ||
70 | Predicates | |
71 | ---------- | |
72 | ||
73 | > isSymbol (Symbol _) = True | |
74 | > isSymbol _ = False | |
75 | ||
76 | > isPair (Pair _ _) = True | |
77 | > isPair _ = False | |
78 | ||
79 | > isBoolean (Boolean _) = True | |
80 | > isBoolean _ = False | |
81 | ||
82 | > isNumber (Number _) = True | |
83 | > isNumber _ = False | |
84 | ||
85 | > isList (Pair _ tail) = isList tail | |
86 | > isList Null = True | |
87 | > isList _ = False | |
88 | ||
89 | > isMacro (Macro _ _ _) = True | |
90 | > isMacro (Builtin _ _) = True | |
91 | > isMacro _ = False |
0 | > module Robin.IEnv where | |
1 | ||
2 | > import Control.Concurrent (ThreadId) | |
3 | > import Robin.Chan (Chan) | |
4 | ||
5 | Internal Environments | |
6 | ===================== | |
7 | ||
8 | This is the evaluation environment for Robin which is entirely | |
9 | internal; Robin programs cannot see or modify it directly. Here | |
10 | we keep things like: | |
11 | ||
12 | * the continuation which is the current exception handler | |
13 | * the ThreadId and Chan of the current Pid | |
14 | ||
15 | > data IEnv t = IEnv (t -> IO t) ThreadId (Chan t) | |
16 | ||
17 | > stop expr = | |
18 | > error ("uncaught exception: " ++ show expr) | |
19 | ||
20 | > newIEnv eh tid chan = | |
21 | > IEnv eh tid chan | |
22 | ||
23 | > getExceptionHandler (IEnv handler _ _) = handler | |
24 | ||
25 | > setExceptionHandler handler (IEnv _ tid chan) = (IEnv handler tid chan) | |
26 | ||
27 | > getThreadId (IEnv _ tid _) = tid | |
28 | > getChannel (IEnv _ _ chan) = chan |
0 | > module Robin.Parser (parseRobin, insistParse) where | |
1 | ||
2 | > import Data.Ratio | |
3 | ||
4 | > import Text.ParserCombinators.Parsec | |
5 | ||
6 | > import Robin.Expr | |
7 | ||
8 | Parser | |
9 | ====== | |
10 | ||
11 | The overall grammar of the language is: | |
12 | ||
13 | Expr ::= (symbol | number | boolean | "(" {Expr} ["." Expr] ")") | |
14 | ||
15 | A symbol is denoted by a string which may contain only alphanumeric | |
16 | characters, hyphens, underscores, and question marks. (TODO: this set | |
17 | of characters is provisional.) | |
18 | ||
19 | > symbol = do | |
20 | > c <- letter | |
21 | > cs <- many (alphaNum <|> char '-' <|> char '?' <|> char '_' <|> char '*') | |
22 | > return (Symbol (c:cs)) | |
23 | ||
24 | TODO: document these productions. | |
25 | ||
26 | > number = do | |
27 | > c <- digit | |
28 | > cs <- many digit | |
29 | > num <- return (read (c:cs) :: Integer) | |
30 | > fraction num <|> return (Number (num % 1)) | |
31 | ||
32 | > fraction num = do | |
33 | > string "/" | |
34 | > c <- digit | |
35 | > cs <- many digit | |
36 | > denom <- return (read (c:cs) :: Integer) | |
37 | > return (Number (num % denom)) | |
38 | ||
39 | > boolean = do | |
40 | > string "#" | |
41 | > c <- (char 't' <|> char 'f') | |
42 | > return (if c == 't' then (Boolean True) else (Boolean False)) | |
43 | ||
44 | > proper e = do | |
45 | > string ")" | |
46 | > return $ robinizeList e Null | |
47 | ||
48 | > improper e = do | |
49 | > string "." | |
50 | > spaces | |
51 | > e2 <- expr | |
52 | > string ")" | |
53 | > return $ robinizeList e e2 | |
54 | ||
55 | > list = do | |
56 | > string "(" | |
57 | > spaces | |
58 | > e <- many expr | |
59 | > proper e <|> improper e | |
60 | ||
61 | The top-level parsing function implements the overall grammar given above. | |
62 | Note that we need to give the type of this parser here -- otherwise the | |
63 | type inferencer freaks out for some reason. | |
64 | ||
65 | > expr :: Parser Expr | |
66 | > expr = do | |
67 | > r <- (symbol <|> number <|> boolean <|> list) | |
68 | > spaces | |
69 | > return r | |
70 | ||
71 | Convenience functions for parsing Robin programs. | |
72 | ||
73 | > parseRobin = parse expr "" | |
74 | ||
75 | > insistParse program = | |
76 | > let | |
77 | > Right ast = parseRobin program | |
78 | > in | |
79 | > ast |
0 | -> encoding: UTF-8 | |
1 | ||
2 | Robin | |
3 | ===== | |
4 | ||
5 | -> Functionality "Interpret Robin Program" is implemented by | |
6 | -> shell command "bin/robin %(test-file)" | |
7 | ||
8 | -> Tests for functionality "Interpret Robin Program" | |
9 | ||
10 | The document defines the fundamental semantics of Robin. | |
11 | ||
12 | Fundamentals | |
13 | ------------ | |
14 | ||
15 | In the following, words in ALL-CAPS indicate variables. | |
16 | ||
17 | Every Robin program is contained in a `robin` form. It has the syntax: | |
18 | ||
19 | (robin VERSION (MODULE-SPEC ...) EXPRESSION) | |
20 | ||
21 | The `VERSION` gives the version of the Robin semantics in use. It has | |
22 | the syntax: | |
23 | ||
24 | (NAT . NAT) | |
25 | ||
26 | ...where `NAT` is a natural number (an integer not less than zero.) | |
27 | More on versions later; the only version of Robin that currently | |
28 | exists is 0.1 (aka `(0 . 1)`), so we'll use that. | |
29 | ||
30 | The list of `MODULE-SPEC`s may be empty. So, without further ado, | |
31 | here is one of the simplest Robin programs one can write: | |
32 | ||
33 | | (robin (0 . 1) () #t) | |
34 | = #t | |
35 | ||
36 | Versioning | |
37 | ---------- | |
38 | ||
39 | The rules for version numbering follow [semantic versioning][]. | |
40 | i.e., If you ask for version 2.1, you may get version 2.2, 2.3, | |
41 | etc; the assumption is that each of these will implement everything | |
42 | 2.1 does, in a fashion which is backwards-compatible with version 2.1. | |
43 | You will not get 3.x, because it is assumed 3.x exports a different | |
44 | interface from 2.x; nor will you get 1.x, for the same reason. In the | |
45 | 0.x series, nothing is considered backwards-compatible with anything | |
46 | else; if you ask for 0.2, you will not get 0.1. | |
47 | ||
48 | Backwards-compatibility does not include functionality which is deemed | |
49 | to be a bug. Programs which rely on bugs are themselves buggy, and | |
50 | when the bug is fixed, they need to be fixed as well. There is probably | |
51 | a grey area where it's unclear if something is a bug or a feature, but | |
52 | my hope is that ample documentation, tests, and literate specifications | |
53 | where-ever possible will reduce the number of functionalities which | |
54 | fall into this grey area. | |
55 | ||
56 | [semantic versioning]: http://semver.org/ | |
57 | ||
58 | An implementation might, obviously, provide or not provide any | |
59 | particular version of the language. | |
60 | ||
61 | | (robin (0 . 781) () #t) | |
62 | ? robin: unsupported language version (0 . 781) | |
63 | ||
64 | The Robin semantics brought in by the version number in the `robin` | |
65 | form include things like datatypes and evaluation rules, and are not | |
66 | likely to change frequently. They do not include identifiers. These | |
67 | are supplied only by modules, and each module has its own version. | |
68 | ||
69 | Module Import | |
70 | ------------- | |
71 | ||
72 | Each `MODULE-SPEC` specifies a module, and a minimum required | |
73 | version, to import. It optionally contains a list of qualifiers which | |
74 | constrain how to import the module. The identifiers exported by the | |
75 | module will be available to the `EXPRESSION`. | |
76 | ||
77 | The module need not be implemented in Robin. | |
78 | ||
79 | If it is implemented in Robin, when it is imported, the definition of | |
80 | the module in Robin is evaluated to obtain a binding alist (see | |
81 | "Conventional Data Types", below, for more details on this data structure.) | |
82 | The bindings therein are added to the environment in which the | |
83 | `EXPRESSION` will be evaluated. They shadow any previous bindings with | |
84 | the same names. | |
85 | ||
86 | How the implementation locates any module for loading is *completely* | |
87 | implementation-dependent. In Robin, we consider this an implementation- | |
88 | level detail of which the language is blissfully unaware. This decouples | |
89 | the language definition from the details of the operating system, such | |
90 | as what a "file" is or where they may be loaded from the "file system". | |
91 | ||
92 | A `MODULE-SPEC` has the syntax: | |
93 | ||
94 | (MODULE-NAME VERSION [QUALIFIER...]) | |
95 | ||
96 | It is possible for the list of `MODULE-SPEC`s to be empty, but then | |
97 | no identifiers will be defined, and you will only be able to state | |
98 | the most basic of `EXPRESSION`s, such as ones consisting of a single | |
99 | literal of one of the built-in datatypes that can be expressed directly. | |
100 | ||
101 | Here is an example of importing a module (but not doing anything | |
102 | with it.) | |
103 | ||
104 | | (robin (0 . 1) (core (0 . 1)) #t) | |
105 | = #t | |
106 | ||
107 | A particular version of a module might, naturally, not be available. | |
108 | ||
109 | (TODO: The particular error message produced here may differ. Falderal | |
110 | needs a forigiving-error-message option.) | |
111 | ||
112 | | (robin (0 . 1) (small (0 . 781)) #t) | |
113 | ? robin: module/small_0_781.robin: openFile: does not exist (No such file or directory) | |
114 | ||
115 | And a particular module might, naturally, not even be available. | |
116 | ||
117 | | (robin (0 . 1) (gzgptzgztxxky (1 . 0)) #t) | |
118 | ? robin: module/gzgptzgztxxky_1_0.robin: openFile: does not exist (No such file or directory) | |
119 | ||
120 | And a particular version of a module may rely on a particular version | |
121 | of the fundamental semantics, so some combinations may not make sense. | |
122 | For example, if `small` 3.0 relies on Robin 2.0, this will fail | |
123 | (with a more appropriate error message like "Robin 2.0 required"): | |
124 | ||
125 | | (robin (1 . 0) (small (3 . 0)) #t) | |
126 | ? robin: unsupported language version (1 . 0) | |
127 | ||
128 | Intrinsic Data Types | |
129 | -------------------- | |
130 | ||
131 | ### S-expressions ### | |
132 | ||
133 | An S-expression is a sort of catch-all data type which includes | |
134 | all the other data types. It is inductively defined as follows: | |
135 | ||
136 | * A symbol is an S-expression. | |
137 | * A boolean is an S-expression. | |
138 | * A rational number is an S-expression. | |
139 | * A macro is an S-expression. | |
140 | * An opaque value is an S-expression. | |
141 | * A pair of two S-expressions is an S-expression. | |
142 | * Nothing else is an S-expression. | |
143 | ||
144 | S-expressions have a textual representation, but not all types have values | |
145 | that can be directly expressed in this textual representation. All | |
146 | S-expressions have some meaning when interpeted as Robin programs, as | |
147 | defined by Robin's evaluation rules, but that meaning might be to | |
148 | raise an exception to indicate an error. | |
149 | ||
150 | ### Symbol ### | |
151 | ||
152 | A symbol is an atomic value represented by a string of characters | |
153 | which may not include whitespace or parentheses or a few other | |
154 | characters (TODO: decide which ones) and which may not begin with | |
155 | a `#` (pound sign) or a few other characters (TODO: decide which | |
156 | ones.) | |
157 | ||
158 | When in a Robin program proper, a symbol can be bound to a value, and | |
159 | in this context is it referred to as an _identifier_. However, if an | |
160 | attempt is made to evaluate a symbol which is not bound to an identifier, | |
161 | an exception will be raised. For a symbol to appear unevaluated in a Robin | |
162 | program, it must be an argument to a macro. For that reason, we can't | |
163 | show an example of a literal symbol without first defining a macro. For | |
164 | illustrative purposes, we shall import the macro `literal` from the `small` | |
165 | module for this purpose, as it is the most straightforward way to create | |
166 | a literal symbol in a Robin program. | |
167 | ||
168 | | (robin (0 . 1) (small (0 . 1)) | |
169 | | (literal hello)) | |
170 | = hello | |
171 | ||
172 | A Robin program is not expected to be able to generate new symbols | |
173 | at runtime. | |
174 | ||
175 | ### Booleans ### | |
176 | ||
177 | There are two values of Boolean type, `#t`, representing truth, and `#f`, | |
178 | representing falsehood. By convention, an identifier which ends in `?` | |
179 | is a macro or function which evaluates to a Boolean. The `if` macro from | |
180 | the `core` module expects a Boolean expression as its first argument. | |
181 | ||
182 | Booleans always evaluate to themselves. | |
183 | ||
184 | ### Rational Numbers ### | |
185 | ||
186 | A rational number is a pair of integers, called the numerator and the | |
187 | denominator, considered as a ratio. No bounds are imposed upon rational | |
188 | numbers, save that the denominator cannot be zero; further, all rational | |
189 | numbers in Robin are exact. | |
190 | ||
191 | The rationale here is that Robin isn't meant for high-performance | |
192 | numerical computing, which is what floating-point values are meant | |
193 | for, so it doesn't have them; at the same time, you occasionally | |
194 | need to compute fractional values, and you don't want to worry | |
195 | overmuch about whether they will overflow or not. (Analysis | |
196 | techniques could be used to prove that, in a performance-critical | |
197 | section of code, a rational number is always an integer and/or | |
198 | always within a certain range, and this information could be used | |
199 | to optimize that section of code. But we'll worry about that | |
200 | later.) | |
201 | ||
202 | For example, 5 is a rational number: | |
203 | ||
204 | | (robin (0 . 1) () 5) | |
205 | = 5 | |
206 | ||
207 | The literal syntax for rational numbers allows one to use `/` to | |
208 | denote a fraction: | |
209 | ||
210 | | (robin (0 . 1) () 4/5) | |
211 | = 4/5 | |
212 | ||
213 | Rational numbers always evaluate to themselves. | |
214 | ||
215 | ### Macros ### | |
216 | ||
217 | A macro is an S-expression, in an environment, which describes how to | |
218 | translate one S-expression to another. | |
219 | ||
220 | One area where Robin diverges heavily from Lisp and Scheme is that, | |
221 | whereas Lisp and Scheme support macro capabilities, in Robin, the macro | |
222 | is a fundamental type. Other abstractions, such as function values, are | |
223 | built on top of macros. Macros are first-class objects that may exist | |
224 | at runtime and can evaluate to other macros. Therefore, the word "macro" | |
225 | has, perhaps, a slightly different meaning in Robin than in Lisp or Scheme. | |
226 | ||
227 | They can also be compared to the one-argument `lambda` form from PicoLisp; | |
228 | again, however, unlike PicoLisp's variety of `lambda` forms, Robin's | |
229 | macros are the only abstraction of this kind fundamentally available, and | |
230 | other abstractions must be built on top of macros. | |
231 | ||
232 | Whereas a function evaluates each of its arguments to values, and | |
233 | binds each of those values to a formal parameter of the function, then | |
234 | evaluates the body of the function in that new environment, a macro | |
235 | binds the literal tail of the list of the macro application to the second | |
236 | formal parameter of the macro, and evaluates the body of the macro. | |
237 | ||
238 | Each macro has two other formal parameters available to it; the first | |
239 | formal parameter is bound to the macro itself (to facilitate writing | |
240 | recursive macros), and the third formal parameter is bound to a binding | |
241 | alist representing the environment in effect at the point the macro was | |
242 | evaluated. | |
243 | ||
244 | There also exist macros which cannot effectively be expressed directly | |
245 | in Robin -- these are the "built-in" macros. One such "built-in" | |
246 | macro is `eval`. Many macros will make use of `eval`, to evaluate | |
247 | that literal tail they receive in a (perhaps modified) environment. | |
248 | ||
249 | Macros are defined with the `macro` macro in the `core` module. | |
250 | Macros are represented as the S-expression expansion of their | |
251 | implementation, except in the case of built-in macros. | |
252 | ||
253 | | (robin (0 . 1) (core (0 . 1)) | |
254 | | (macro (self args env) args)) | |
255 | = (macro (self args env) args) | |
256 | ||
257 | A built-in macro is represented thusly. (TODO: this representation | |
258 | has problems; see section on pairs below.) | |
259 | ||
260 | | (robin (0 . 1) (core (0 . 1)) | |
261 | | pair) | |
262 | = (builtin pair) | |
263 | ||
264 | One upshot of built-in macros is that *all* intrinsic Robin functionality, | |
265 | even things that in Scheme are special forms, can be passed around as | |
266 | values. | |
267 | ||
268 | | (robin (0 . 1) (core (0 . 1)) | |
269 | | (pair if head)) | |
270 | = ((builtin if) . (builtin head)) | |
271 | ||
272 | Macros always evaluate to themselves. | |
273 | ||
274 | ### Pairs ### | |
275 | ||
276 | A pair is a pair of values of any type (including another pair.) By | |
277 | convention, the first of the two values in the pair is referred to as the | |
278 | "head" of the pair, and the second as the "tail" of the pair. | |
279 | ||
280 | TODO: write more about this. | |
281 | ||
282 | | (robin (0 . 1) (small (0 . 1)) | |
283 | | (literal (7 . 8))) | |
284 | = (7 . 8) | |
285 | ||
286 | Representations of some types (like built-in macros) look funny because they | |
287 | don't follow the rules for depicting pairs with a dot and lists without -- | |
288 | effectively, the parens are "fake" on these things. | |
289 | ||
290 | | (robin (0 . 1) (core (0 . 1)) | |
291 | | (pair #f boolean?)) | |
292 | = (#f . (builtin boolean?)) | |
293 | ||
294 | Pairs do not evaluate to themselves; rather, they represent a macro | |
295 | application. TODO: document this. | |
296 | ||
297 | Conventional Data Types | |
298 | ----------------------- | |
299 | ||
300 | This section lists data types that are not intrinsic, but are rather | |
301 | arrangements of intrinsic types in a way that follows a convention. | |
302 | ||
303 | ### Lists ### | |
304 | ||
305 | By convention, a list is simply a lopsided tree of pairs. The first element | |
306 | (head) of each pair is the value in that position of the list, while the | |
307 | second element (tail) is either another pair representing the continuation | |
308 | of the list, or the special value `()`, which is pronounced "null". | |
309 | ||
310 | TODO: write about how S-expressions are lists unless they contain a dot, | |
311 | proper vs improper lists, and so forth. | |
312 | ||
313 | Unlike Scheme, you do not need to quote `()`; it evaluates to itself | |
314 | rather than indicating an illegal empty application. | |
315 | ||
316 | | (robin (0 . 1) () ()) | |
317 | = () | |
318 | ||
319 | ### Alists ### | |
320 | ||
321 | An alist, short for "association list", is simply a list of pairs. The | |
322 | idea is that each pair associates, somehow, the value in its head with | |
323 | the value in its tail. | |
324 | ||
325 | ### Binding Alists ### | |
326 | ||
327 | When the head of each pair in an alist is a symbol, we call it a binding | |
328 | alist. The idea is that it is a Robin representation of an evaluation | |
329 | environment, where the symbols in the heads of the pairs are bound to the | |
330 | values in the tails of the pairs. Binding alists can be created from an | |
331 | environment in effect (such as in the third argument of a macro) and can | |
332 | be used to change the evaluation environment in effect (such as in the | |
333 | first argument to `eval`.) | |
334 | ||
335 | Standard Modules | |
336 | ---------------- | |
337 | ||
338 | ### `core` ### | |
339 | ||
340 | Robin's `core` module exports the fundamental functionality that is used | |
341 | to evaluate programs, and that cannot be expressed as macros written | |
342 | in Robin. | |
343 | ||
344 | `core` is not optional -- every Robin implementation must provide a | |
345 | `core` module, or it's not Robin. | |
346 | ||
347 | The `core` module is documented in | |
348 | [module/Core.falderal](module/Core.falderal). | |
349 | ||
350 | ### `small` ### | |
351 | ||
352 | Robin's `small` module exports everything `core` does, and a few | |
353 | macros (which could be written in Robin, but could also be implemented | |
354 | natively for efficiency) to make writing programs somewhat easier -- | |
355 | basically to bring the language up to parity, in expressive power, with | |
356 | Pixley. | |
357 | ||
358 | `small` is technically an optional module, but it's really handy, | |
359 | and could be written in pure Robin, so it's likely to be available. | |
360 | ||
361 | The `small` module is documented in | |
362 | [module/Small.falderal](module/Small.falderal). | |
363 | ||
364 | ### `exception` ### | |
365 | ||
366 | Robin's `exception` module exports macros for catching exceptions. | |
367 | ||
368 | This module is optional. A Robin form that imports this module is asserting | |
369 | that it requires an implementation in which exceptions can be caught. | |
370 | ||
371 | Note that exceptions can still be raised in an implementation where they | |
372 | cannot be caught; they simply cause an immediate termination of the Robin | |
373 | program instead. | |
374 | ||
375 | The `exception` module is documented in | |
376 | [module/Exception.falderal](module/Exception.falderal). | |
377 | ||
378 | ### `concurrency` ### | |
379 | ||
380 | Robin's `concurrency` module exports macros for working with concurrently- | |
381 | executing processes which communicate with each other via message- | |
382 | passing. | |
383 | ||
384 | This module is optional. A Robin form that imports this module is asserting | |
385 | that it requires an implementation which supports concurrency. | |
386 | ||
387 | The `concurrency` module is documented in | |
388 | [module/Concurrency.falderal](module/Concurrency.falderal). |
0 | -> encoding: UTF-8 | |
1 | ||
2 | Robin - Concurrency Module | |
3 | ========================== | |
4 | ||
5 | -> Functionality "Interpret Robin Program" is implemented by | |
6 | -> shell command "bin/robin %(test-file)" | |
7 | ||
8 | -> Tests for functionality "Interpret Robin Program" | |
9 | ||
10 | The `concurrency` module exports macros for working with concurrently- | |
11 | executing processes which communicate with each other via message- | |
12 | passing. | |
13 | ||
14 | Functionality in this module is difficult to test in isolation, so | |
15 | many of the tests make use of more than one macro from this module. | |
16 | ||
17 | Data Types | |
18 | ---------- | |
19 | ||
20 | ### Process Identifiers ### | |
21 | ||
22 | A process identifier is an opaque value which identifies a process. | |
23 | ||
24 | Also known as "pids". | |
25 | ||
26 | Pids cannot be denoted directly in the textual S-expression format. | |
27 | Several macros in `concurrency` evaluate to a pid, however. | |
28 | ||
29 | Functions | |
30 | --------- | |
31 | ||
32 | Robin's `concurrency` module exports things for managing concurrently- | |
33 | executing processes. | |
34 | ||
35 | ### `myself` ### | |
36 | ||
37 | `myself` takes no arguments and evaluates to the pid of the currently | |
38 | running process. | |
39 | ||
40 | `myself` expects exactly zero arguments. | |
41 | ||
42 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
43 | | (myself 123)) | |
44 | ? robin: uncaught exception: (illegal-arguments 123) | |
45 | ||
46 | ### `pid?` ### | |
47 | ||
48 | `pid?` evaluates its argument, then evaluates to `#t` if that value | |
49 | is a process identifier, `#f` otherwise. | |
50 | ||
51 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
52 | | (pid? (literal b))) | |
53 | = #f | |
54 | ||
55 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
56 | | (pid? (myself))) | |
57 | = #t | |
58 | ||
59 | The argument to `pid?` may naturally be of any type, but there | |
60 | must be exactly one argument. | |
61 | ||
62 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
63 | | (pid?)) | |
64 | ? robin: uncaught exception: (illegal-arguments) | |
65 | ||
66 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
67 | | (pid? 200 500)) | |
68 | ? robin: uncaught exception: (illegal-arguments 200 500) | |
69 | ||
70 | ### `spawn` ### | |
71 | ||
72 | `spawn` evaluates its argument to a macro, starts a concurrent process | |
73 | running that macro, and evaluates to the process identifier (pid) for | |
74 | that new process. | |
75 | ||
76 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
77 | | (pid? (spawn (fun (parent) parent)))) | |
78 | = #t | |
79 | ||
80 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
81 | | (equal? (myself) (spawn (fun (parent) parent)))) | |
82 | = #f | |
83 | ||
84 | `spawn` takes exactly one argument, which must be a macro. | |
85 | ||
86 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
87 | | (spawn (literal cheesecake))) | |
88 | ? robin: uncaught exception: (expected-macro . cheesecake) | |
89 | ||
90 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
91 | | (spawn)) | |
92 | ? robin: uncaught exception: (illegal-arguments) | |
93 | ||
94 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
95 | | (spawn (fun (x) 0) (fun (x) 1))) | |
96 | ? robin: uncaught exception: (illegal-arguments (fun (x) 0) (fun (x) 1)) | |
97 | ||
98 | ### `send` ### | |
99 | ||
100 | `send` evaluates its first argument to obtain a pid, then its second | |
101 | argument to obtain a value. It then sends that value as a message to | |
102 | the process identified by the pid, then evaluates its third argument | |
103 | and itself evaluates to that. | |
104 | ||
105 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
106 | | (bind worker (spawn (fun (parent) parent)) | |
107 | | (send worker (literal spam) (literal ok)))) | |
108 | = ok | |
109 | ||
110 | `send` expects its first argument to be a pid. | |
111 | ||
112 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
113 | | (send (literal eggs) (literal spam) (literal ok))) | |
114 | ? robin: uncaught exception: (expected-pid . eggs) | |
115 | ||
116 | `send` expects exactly three arguments. | |
117 | ||
118 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
119 | | (bind worker (spawn (fun (parent) parent)) | |
120 | | (send worker worker))) | |
121 | ? robin: uncaught exception: (illegal-arguments worker worker) | |
122 | ||
123 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
124 | | (bind worker (spawn (fun (parent) parent)) | |
125 | | (send worker worker worker worker))) | |
126 | ? robin: uncaught exception: (illegal-arguments worker worker worker worker) | |
127 | ||
128 | ### `recv` ### | |
129 | ||
130 | `recv` waits for a message to arrive in the currently executing | |
131 | process's queue, and removes it. It binds the identifier given in | |
132 | its first argument to the value so received, and evaluates its second | |
133 | argument, and itself evaluates to that. | |
134 | ||
135 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
136 | | (bind worker (spawn (fun (parent) | |
137 | | (send parent (literal lettuce) (literal ok)))) | |
138 | | (recv message (pair message message)))) | |
139 | = (lettuce . lettuce) | |
140 | ||
141 | `recv` expects its first argument to be an identifier to be bound. (This | |
142 | is a case of illegal arguments, as the identifier is not an expression | |
143 | that must evaluate to a certain type.) | |
144 | ||
145 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
146 | | (bind worker (spawn (fun (parent) | |
147 | | (send parent (literal lettuce) (literal ok)))) | |
148 | | (recv (pair 7 8) 9))) | |
149 | ? robin: uncaught exception: (illegal-arguments (pair 7 8) 9) | |
150 | ||
151 | `recv` expects exactly two arguments. | |
152 | ||
153 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
154 | | (bind worker (spawn (fun (parent) | |
155 | | (send parent (literal lettuce) (literal ok)))) | |
156 | | (recv message))) | |
157 | ? robin: uncaught exception: (illegal-arguments message) | |
158 | ||
159 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
160 | | (bind worker (spawn (fun (parent) | |
161 | | (send parent (literal lettuce) (literal ok)))) | |
162 | | (recv message message message))) | |
163 | ? robin: uncaught exception: (illegal-arguments message message message) | |
164 | ||
165 | ### `msgs?` ### | |
166 | ||
167 | `msgs?` evaluates to `#t` if the current process has one or more messages | |
168 | waiting in its queue, `#f` otherwise. | |
169 | ||
170 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
171 | | (msgs?)) | |
172 | = #f | |
173 | ||
174 | Note: it's hard to write this test without a race condition... | |
175 | ||
176 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
177 | | (bind worker (spawn (fun (parent) | |
178 | | (send parent (literal lettuce) (literal ok)))) | |
179 | | (msgs?))) | |
180 | = #t | |
181 | ||
182 | `msgs?` expects exactly zero arguments. | |
183 | ||
184 | | (robin (0 . 1) (small (0 . 1) concurrency (0 . 1)) | |
185 | | (msgs? #t)) | |
186 | ? robin: uncaught exception: (illegal-arguments #t) |
0 | -> encoding: UTF-8 | |
1 | ||
2 | Module `core` | |
3 | ============= | |
4 | ||
5 | -> Functionality "Interpret Robin Program" is implemented by | |
6 | -> shell command "bin/robin %(test-file)" | |
7 | ||
8 | -> Tests for functionality "Interpret Robin Program" | |
9 | ||
10 | Robin's `core` module exports the set of intrinsic macros on top of which | |
11 | all other Robin macros and programs are built. | |
12 | ||
13 | ### `pair` ### | |
14 | ||
15 | `pair` evaluates both of its arguments, then evaluates to a pair which | |
16 | contains both of those values, in the same order. | |
17 | ||
18 | | (robin (0 . 1) (core (0 . 1)) | |
19 | | (pair #t #f)) | |
20 | = (#t . #f) | |
21 | ||
22 | | (robin (0 . 1) (core (0 . 1)) | |
23 | | (pair #t (pair #f ()))) | |
24 | = (#t #f) | |
25 | ||
26 | Arguments to `pair` can be any type, but fewer than or more than | |
27 | two arguments will raise an exception. | |
28 | ||
29 | | (robin (0 . 1) (core (0 . 1)) | |
30 | | (pair #t)) | |
31 | ? robin: uncaught exception: (illegal-arguments #t) | |
32 | ||
33 | | (robin (0 . 1) (core (0 . 1)) | |
34 | | (pair #f #t #f)) | |
35 | ? robin: uncaught exception: (illegal-arguments #f #t #f) | |
36 | ||
37 | ### `head` ### | |
38 | ||
39 | `head` evaluates its argument to a pair, and evaluates to the first element | |
40 | of that pair. | |
41 | ||
42 | | (robin (0 . 1) (core (0 . 1)) | |
43 | | (head (pair #t #f))) | |
44 | = #t | |
45 | ||
46 | `head` expects its argument to be a pair. | |
47 | ||
48 | | (robin (0 . 1) (core (0 . 1)) | |
49 | | (head #f)) | |
50 | ? robin: uncaught exception: (expected-pair . #f) | |
51 | ||
52 | `head` expects exactly one argument. | |
53 | ||
54 | | (robin (0 . 1) (core (0 . 1)) | |
55 | | (head (pair #t #f) (pair #f #t))) | |
56 | ? robin: uncaught exception: (illegal-arguments (pair #t #f) (pair #f #t)) | |
57 | ||
58 | | (robin (0 . 1) (core (0 . 1)) | |
59 | | (head)) | |
60 | ? robin: uncaught exception: (illegal-arguments) | |
61 | ||
62 | ### `tail` ### | |
63 | ||
64 | Likewise, the horribly-named `tail` also evaluates its argument to a | |
65 | pair, and evaluates to the second element of that pair. | |
66 | ||
67 | | (robin (0 . 1) (core (0 . 1)) | |
68 | | (tail (pair #t #f))) | |
69 | = #f | |
70 | ||
71 | `tail` expects its argument to be a pair. | |
72 | ||
73 | | (robin (0 . 1) (core (0 . 1)) | |
74 | | (tail #f)) | |
75 | ? robin: uncaught exception: (expected-pair . #f) | |
76 | ||
77 | `tail` expects exactly one argument. | |
78 | ||
79 | | (robin (0 . 1) (core (0 . 1)) | |
80 | | (tail (pair #t #f) (pair #f #t))) | |
81 | ? robin: uncaught exception: (illegal-arguments (pair #t #f) (pair #f #t)) | |
82 | ||
83 | | (robin (0 . 1) (core (0 . 1)) | |
84 | | (tail)) | |
85 | ? robin: uncaught exception: (illegal-arguments) | |
86 | ||
87 | ### `if` ### | |
88 | ||
89 | `if` evaluates its first argument to a boolean value. If that value is | |
90 | `#t`, it evaluates, and evaluates to, its second argument; or if that value | |
91 | is `#f` it evaluates, and evaluates to, its third argument. In all cases, | |
92 | at most two arguments are evaluated. | |
93 | ||
94 | | (robin (0 . 1) (core (0 . 1)) | |
95 | | (if #t 7 9)) | |
96 | = 7 | |
97 | ||
98 | | (robin (0 . 1) (core (0 . 1)) | |
99 | | (if #f 7 9)) | |
100 | = 9 | |
101 | ||
102 | The second and third arguments can be arbitrary expressions, but `if` | |
103 | expects its first argument to be a boolean. | |
104 | ||
105 | | (robin (0 . 1) (core (0 . 1)) | |
106 | | (if 5 7 9)) | |
107 | ? robin: uncaught exception: (expected-boolean . 5) | |
108 | ||
109 | `if` expects exactly three arguments. | |
110 | ||
111 | | (robin (0 . 1) (core (0 . 1)) | |
112 | | (if #t 7)) | |
113 | ? robin: uncaught exception: (illegal-arguments #t 7) | |
114 | ||
115 | | (robin (0 . 1) (core (0 . 1)) | |
116 | | (if #t 7 8 9)) | |
117 | ? robin: uncaught exception: (illegal-arguments #t 7 8 9) | |
118 | ||
119 | ### `equal?` ### | |
120 | ||
121 | `equal?` evaluates both of its arguments to arbitrary S-expressions | |
122 | and compares them for deep equality. | |
123 | ||
124 | `equal?` works on symbols. | |
125 | ||
126 | | (robin (0 . 1) (core (0 . 1)) | |
127 | | (equal? | |
128 | | ((macro (s a e) (head a)) this-symbol) | |
129 | | ((macro (s a e) (head a)) this-symbol))) | |
130 | = #t | |
131 | ||
132 | | (robin (0 . 1) (core (0 . 1)) | |
133 | | (equal? | |
134 | | ((macro (s a e) (head a)) this-symbol) | |
135 | | ((macro (s a e) (head a)) that-symbol))) | |
136 | = #f | |
137 | ||
138 | `equal?` works on lists. | |
139 | ||
140 | | (robin (0 . 1) (core (0 . 1)) | |
141 | | (equal? (pair 1 (pair 2 (pair 3 ()))) | |
142 | | (pair 1 (pair 2 (pair 3 ()))))) | |
143 | = #t | |
144 | ||
145 | Two values of different types are never equal. | |
146 | ||
147 | | (robin (0 . 1) (core (0 . 1)) | |
148 | | (equal? #t | |
149 | | (pair ((macro (self args env) (head args)) a) ()))) | |
150 | = #f | |
151 | ||
152 | | (robin (0 . 1) (core (0 . 1)) | |
153 | | (equal? #f | |
154 | | ())) | |
155 | = #f | |
156 | ||
157 | Arguments to `equal?` can be any type, but fewer than or more than | |
158 | two arguments will raise an exception. | |
159 | ||
160 | | (robin (0 . 1) (core (0 . 1)) | |
161 | | (equal? 7)) | |
162 | ? robin: uncaught exception: (illegal-arguments 7) | |
163 | ||
164 | | (robin (0 . 1) (core (0 . 1)) | |
165 | | (equal? 7 8 9)) | |
166 | ? robin: uncaught exception: (illegal-arguments 7 8 9) | |
167 | ||
168 | ### `pair?` ### | |
169 | ||
170 | `pair?` evaluates its argument, then evaluates to `#t` if it is a pair, | |
171 | `#f` otherwise. | |
172 | ||
173 | | (robin (0 . 1) (core (0 . 1)) | |
174 | | (pair? ((macro (self args env) (head args)) (a . b)))) | |
175 | = #t | |
176 | ||
177 | | (robin (0 . 1) (core (0 . 1)) | |
178 | | (pair? ((macro (self args env) (head args)) (a b c d e f)))) | |
179 | = #t | |
180 | ||
181 | | (robin (0 . 1) (core (0 . 1)) | |
182 | | (pair? (pair 4 5))) | |
183 | = #t | |
184 | ||
185 | Symbols are not pairs. | |
186 | ||
187 | | (robin (0 . 1) (core (0 . 1)) | |
188 | | (pair? ((macro (self args env) (head args)) b))) | |
189 | = #f | |
190 | ||
191 | The argument to `pair?` may (naturally) be any type, but there must be | |
192 | exactly one argument. | |
193 | ||
194 | | (robin (0 . 1) (core (0 . 1)) | |
195 | | (pair? (pair 4 5) (pair 6 7))) | |
196 | ? robin: uncaught exception: (illegal-arguments (pair 4 5) (pair 6 7)) | |
197 | ||
198 | ### `macro?` ### | |
199 | ||
200 | `macro?` evaluates its argument, then evaluates to `#t` if it is a macro | |
201 | (either built-in or user-defined), or `#f` if it is not. | |
202 | ||
203 | | (robin (0 . 1) (core (0 . 1)) | |
204 | | (macro? (macro (self args env) args))) | |
205 | = #t | |
206 | ||
207 | | (robin (0 . 1) (core (0 . 1)) | |
208 | | (macro? macro)) | |
209 | = #t | |
210 | ||
211 | | (robin (0 . 1) (core (0 . 1)) | |
212 | | (macro? ((macro (self args env) (head args)) macro))) | |
213 | = #f | |
214 | ||
215 | | (robin (0 . 1) (core (0 . 1)) | |
216 | | (macro? 4/5)) | |
217 | = #f | |
218 | ||
219 | The argument to `macro?` may (naturally) be any type, but there must be | |
220 | exactly one argument. | |
221 | ||
222 | | (robin (0 . 1) (core (0 . 1)) | |
223 | | (macro? macro macro)) | |
224 | ? robin: uncaught exception: (illegal-arguments macro macro) | |
225 | ||
226 | ### `symbol?` ### | |
227 | ||
228 | `symbol?` evaluates its argument, then evaluates to `#t` if it is a symbol, | |
229 | `#f` otherwise. | |
230 | ||
231 | | (robin (0 . 1) (core (0 . 1)) | |
232 | | (symbol? ((macro (s a e) (head a)) this-symbol))) | |
233 | = #t | |
234 | ||
235 | Pairs are not symbols. | |
236 | ||
237 | | (robin (0 . 1) (core (0 . 1)) | |
238 | | (symbol? (pair 1 2))) | |
239 | = #f | |
240 | ||
241 | The argument to `symbol?` may (naturally) be any type, but there must be | |
242 | exactly one argument. | |
243 | ||
244 | | (robin (0 . 1) (core (0 . 1)) | |
245 | | (symbol? 77 88)) | |
246 | ? robin: uncaught exception: (illegal-arguments 77 88) | |
247 | ||
248 | ### `boolean?` ### | |
249 | ||
250 | `boolean?` evaluates its argument, then evaluates to `#t` if it is a | |
251 | boolean value, `#f` otherwise. | |
252 | ||
253 | | (robin (0 . 1) (core (0 . 1)) | |
254 | | (boolean? #t)) | |
255 | = #t | |
256 | ||
257 | | (robin (0 . 1) (core (0 . 1)) | |
258 | | (boolean? #f)) | |
259 | = #t | |
260 | ||
261 | | (robin (0 . 1) (core (0 . 1)) | |
262 | | (boolean? ())) | |
263 | = #f | |
264 | ||
265 | The argument to `symbol?` may (naturally) be any type, but there must be | |
266 | exactly one argument. | |
267 | ||
268 | | (robin (0 . 1) (core (0 . 1)) | |
269 | | (boolean? #t #f)) | |
270 | ? robin: uncaught exception: (illegal-arguments #t #f) | |
271 | ||
272 | ### `eval` ### | |
273 | ||
274 | `eval` evaluates its first argument to obtain an environment, then | |
275 | evaluates its second argument to obtain an S-expression; it then | |
276 | evaluates that S-expression in the given environment. | |
277 | ||
278 | TODO: these tests use things from the `small` module; for the | |
279 | sake of purity, that dependency should be removed (but the tests | |
280 | will look awful.) | |
281 | ||
282 | | (robin (0 . 1) (small (0 . 1)) | |
283 | | (eval (env) (literal (pair (literal a) (literal b))))) | |
284 | = (a . b) | |
285 | ||
286 | | (robin (0 . 1) (small (0 . 1)) | |
287 | | (eval () (literal (pair (literal a) (literal b))))) | |
288 | ? robin: uncaught exception: (unbound-identifier . pair) | |
289 | ||
290 | | (robin (0 . 1) (small (0 . 1)) | |
291 | | (bind bindings (pair | |
292 | | (pair (literal same) equal?) | |
293 | | (pair (pair (literal x) #f) (literal ()))) | |
294 | | (eval bindings (literal (same x x))))) | |
295 | = #t | |
296 | ||
297 | If two bindings for the same identifier are supplied in the environment | |
298 | alist passed to `eval`, the one closer to the front of the alist takes | |
299 | precedence. | |
300 | ||
301 | | (robin (0 . 1) (small (0 . 1)) | |
302 | | (bind bindings (pair | |
303 | | (pair (literal foo) (literal yes)) | |
304 | | (pair (pair (literal foo) (literal no)) | |
305 | | (literal ()))) | |
306 | | (eval bindings (literal foo)))) | |
307 | = yes | |
308 | ||
309 | `eval` will happily use whatever type of value you like as the | |
310 | environment, however, subsequent evaluation will fail when it | |
311 | tries to look up things in that environment. | |
312 | ||
313 | | (robin (0 . 1) (small (0 . 1)) | |
314 | | (eval 103 (literal (pair (literal a) (literal b))))) | |
315 | ? robin: uncaught exception: (expected-pair . 103) | |
316 | ||
317 | Evaluation expects the contents of the list which makes up the | |
318 | environment to be pairs. | |
319 | ||
320 | | (robin (0 . 1) (small (0 . 1)) | |
321 | | (eval (pair #f ()) (literal (pair (literal a) (literal b))))) | |
322 | ? robin: uncaught exception: (expected-pair . #f) | |
323 | ||
324 | Evaluation expects the head of each pair in the list which makes up the | |
325 | environment to be a symbol. | |
326 | ||
327 | | (robin (0 . 1) (small (0 . 1)) | |
328 | | (eval (pair (pair 7 #f) ()) (literal (pair (literal a) (literal b))))) | |
329 | ? robin: uncaught exception: (expected-symbol . 7) | |
330 | ||
331 | `eval` expects exactly two arguments. | |
332 | ||
333 | | (robin (0 . 1) (core (0 . 1)) | |
334 | | (eval)) | |
335 | ? robin: uncaught exception: (illegal-arguments) | |
336 | ||
337 | | (robin (0 . 1) (core (0 . 1)) | |
338 | | (eval 4 5 6)) | |
339 | ? robin: uncaught exception: (illegal-arguments 4 5 6) | |
340 | ||
341 | ### `macro` ### | |
342 | ||
343 | `macro` takes its first argument to be a list of three formal | |
344 | parameters, and its second argument to be an arbitrary expression, | |
345 | and uses these two arguments to build, and evaluate to, a macro | |
346 | value. | |
347 | ||
348 | When this macro value is evaluated, the first formal argument will | |
349 | be bound to the macro itself, the second will be bound to the | |
350 | literal, unevaluated list of arguments passed to the macro, and the | |
351 | third will be bound to an alist representing the environment in | |
352 | effect at the point the macro value is evaluated. | |
353 | ||
354 | These formals are conventionally called `self`, `args`, and `env`, | |
355 | but different names can be chosen in the `macro` definition, for | |
356 | instance to avoid shadowing. | |
357 | ||
358 | `literal`, in fact, can be defined as a macro, and it is one of the | |
359 | simplest possible macros that can be written: | |
360 | ||
361 | | (robin (0 . 1) (core (0 . 1)) | |
362 | | ((macro (self args env) (head args)) (why hello there))) | |
363 | = (why hello there) | |
364 | ||
365 | `self` is there to let you write recursive macros. The following | |
366 | example demonstrates this; it evaluates `(pair b d)` in an environment | |
367 | where all the identifiers you list after `qqq` have been bound to 0. | |
368 | ||
369 | TODO: these tests use things from the `small` module; for the | |
370 | sake of purity, that dependency should be removed (but the tests | |
371 | will look awful.) | |
372 | ||
373 | | (robin (0 . 1) (small (0 . 1)) | |
374 | | (bind qqq | |
375 | | (macro (self args env) | |
376 | | (if (equal? args ()) | |
377 | | (eval env (literal (pair b d))) | |
378 | | (eval (pair (pair (head args) 0) env) | |
379 | | (pair self (tail args))))) | |
380 | | (bind b 1 (bind d 4 (qqq b c d))))) | |
381 | = (0 . 0) | |
382 | ||
383 | | (robin (0 . 1) (small (0 . 1)) | |
384 | | (bind qqq | |
385 | | (macro (self args env) | |
386 | | (if (equal? args ()) | |
387 | | (eval env (literal (pair b d))) | |
388 | | (eval (pair (pair (head args) 0) env) | |
389 | | (pair self (tail args))))) | |
390 | | (bind b 1 (bind d 4 (qqq x y z))))) | |
391 | = (1 . 4) | |
392 | ||
393 | Your recursive `macro` application doesn't have to be tail-recursive. | |
394 | ||
395 | | (robin (0 . 1) (small (0 . 1)) | |
396 | | (bind make-env | |
397 | | (macro (self args env) | |
398 | | (if (equal? args ()) | |
399 | | () | |
400 | | (pair (pair (head args) (eval env (head args))) | |
401 | | (eval env | |
402 | | (pair self (tail args)))))) | |
403 | | (bind b 1 (bind d 4 (make-env b d macro))))) | |
404 | = ((b . 1) (d . 4) (macro . (builtin macro))) | |
405 | ||
406 | `macro` expects exactly two arguments. | |
407 | ||
408 | | (robin (0 . 1) (core (0 . 1)) | |
409 | | ((macro (self args env)) (why hello there))) | |
410 | ? robin: uncaught exception: (illegal-arguments (self args env)) | |
411 | ||
412 | | (robin (0 . 1) (core (0 . 1)) | |
413 | | ((macro (self args env) pair pair) (why hello there))) | |
414 | ? robin: uncaught exception: (illegal-arguments (self args env) pair pair) | |
415 | ||
416 | `macro` expects its first argument to be a list of exactly three | |
417 | symbols. | |
418 | ||
419 | | (robin (0 . 1) (core (0 . 1)) | |
420 | | ((macro 100 pair) (why hello there))) | |
421 | ? robin: uncaught exception: (illegal-arguments 100 pair) | |
422 | ||
423 | | (robin (0 . 1) (core (0 . 1)) | |
424 | | ((macro (self args) pair) (why hello there))) | |
425 | ? robin: uncaught exception: (illegal-arguments (self args) pair) | |
426 | ||
427 | | (robin (0 . 1) (core (0 . 1)) | |
428 | | ((macro (self args env foo) pair) (why hello there))) | |
429 | ? robin: uncaught exception: (illegal-arguments (self args env foo) pair) | |
430 | ||
431 | | (robin (0 . 1) (core (0 . 1)) | |
432 | | ((macro (self args 99) pair) (why hello there))) | |
433 | ? robin: uncaught exception: (illegal-arguments (self args 99) pair) | |
434 | ||
435 | ### `raise` ### | |
436 | ||
437 | `raise` evaluates its argument to obtain a value, then raises an | |
438 | exception with that value. | |
439 | ||
440 | If the implementation of Robin does not support catching exceptions, or if | |
441 | it does but no exception handlers have been installed in the execution | |
442 | history, the Robin program will terminate with an error, ceasing execution | |
443 | of all Robin processes immediately, returning control to the operating | |
444 | system. For the sake of usability, the error should include a message which | |
445 | refers to the exception that triggered it, but this is not a strict | |
446 | requirement. | |
447 | ||
448 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
449 | | (raise (literal (nasty-value . 999999)))) | |
450 | ? robin: uncaught exception: (nasty-value . 999999) |
0 | -> encoding: UTF-8 | |
1 | ||
2 | Module `exception` | |
3 | ================== | |
4 | ||
5 | -> Functionality "Interpret Robin Program" is implemented by | |
6 | -> shell command "bin/robin %(test-file)" | |
7 | ||
8 | -> Tests for functionality "Interpret Robin Program" | |
9 | ||
10 | This module exports macros for catching exceptions. In particular, the | |
11 | ability to import this module means your implementation of Robin | |
12 | supports catching exceptions. | |
13 | ||
14 | `raise` itself is in the core, because it can be used to abort the program | |
15 | with an error, even in an implementation which does not support catching | |
16 | exceptions. However, because one might reasonably expect to find it in | |
17 | this module, this module re-exports it as well. | |
18 | ||
19 | ### `catch` ### | |
20 | ||
21 | `catch` installs an exception handler. | |
22 | ||
23 | If an exception is raised when evaluating the final argument of | |
24 | `catch`, the exception value is bound to the symbol given as the | |
25 | first argument of `catch`, and the second argument of `catch` is | |
26 | evaluated in that new environment. | |
27 | ||
28 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
29 | | (catch error (pair error #f) | |
30 | | (raise (literal (nasty-value . 999999))))) | |
31 | = ((nasty-value . 999999) . #f) | |
32 | ||
33 | `catch` can catch exceptions raised by core macros. | |
34 | ||
35 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
36 | | (catch error (pair error 5) | |
37 | | (head #f))) | |
38 | = ((expected-pair . #f) . 5) | |
39 | ||
40 | The innermost `catch` will catch the exception. | |
41 | ||
42 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
43 | | (catch error (pair error 5) | |
44 | | (catch error (pair error 9) | |
45 | | (head #f)))) | |
46 | = ((expected-pair . #f) . 9) | |
47 | ||
48 | An exception raised from within an exception handler is | |
49 | caught by the next innermost exception handler. | |
50 | ||
51 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
52 | | (catch error (pair error 5) | |
53 | | (catch error (pair error 9) | |
54 | | (catch error (raise (pair error error)) | |
55 | | (raise 7))))) | |
56 | = ((7 . 7) . 9) | |
57 | ||
58 | `catch` expects its first argument to be an identifier. | |
59 | ||
60 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
61 | | (catch #f 23 (head #f))) | |
62 | ? robin: uncaught exception: (illegal-arguments #f 23 (head #f)) | |
63 | ||
64 | `catch` expects exactly three arguments. | |
65 | ||
66 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
67 | | (catch error error)) | |
68 | ? robin: uncaught exception: (illegal-arguments error error) | |
69 | ||
70 | | (robin (0 . 1) (small (0 . 1) exception (0 . 1)) | |
71 | | (catch error error (head #f) 23)) | |
72 | ? robin: uncaught exception: (illegal-arguments error error (head #f) 23) |
0 | -> encoding: UTF-8 | |
1 | ||
2 | Module `small` | |
3 | ============== | |
4 | ||
5 | -> Functionality "Interpret Robin Program" is implemented by | |
6 | -> shell command "bin/robin %(test-file)" | |
7 | ||
8 | -> Tests for functionality "Interpret Robin Program" | |
9 | ||
10 | The `core` module only exports macros which are necessarily, or for reasons | |
11 | of practicality, not implemented in Robin itself. For example, in the Robin | |
12 | reference interpreter, they are implemented in Haskell. | |
13 | ||
14 | This small set of macros omits many abstractions to which programmers have | |
15 | become accustomed, and is thus rather brutal to program directly in. | |
16 | ||
17 | So to make Robin somewhat easier to program in, the `small` module exports a | |
18 | number of macros which help bring the language up to parity with Pixley. | |
19 | ||
20 | That is, the amount of functionality in `small` is rather modest -- only a | |
21 | fraction of what you would find in the Haskell standard prelude, or in R5RS | |
22 | Scheme, or in the Python core. | |
23 | ||
24 | All of these macros can be written in core Robin, but whether they are, or | |
25 | provided as builtins, is up to the implementation. | |
26 | ||
27 | In addition, the `small` module re-exports everything from `core`, so that | |
28 | it is not necessary to import both of these modules, only `small`. | |
29 | ||
30 | ### `literal` ### | |
31 | ||
32 | One of the most basic identifiers available in `small` is `literal`, | |
33 | which evaluates to the literal content of its sole argument, which can be | |
34 | any S-expression. | |
35 | ||
36 | | (robin (0 . 1) (small (0 . 1)) | |
37 | | (literal symbol)) | |
38 | = symbol | |
39 | ||
40 | | (robin (0 . 1) (small (0 . 1)) | |
41 | | (literal (a . pair))) | |
42 | = (a . pair) | |
43 | ||
44 | | (robin (0 . 1) (small (0 . 1)) | |
45 | | (literal (hello world))) | |
46 | = (hello world) | |
47 | ||
48 | `literal` could be implemented in Robin. | |
49 | ||
50 | | (robin (0 . 1) (small (0 . 1)) | |
51 | | ((macro (self args env) (head args)) hello)) | |
52 | = hello | |
53 | ||
54 | `literal` is basically equivalent to Scheme's `quote`. | |
55 | ||
56 | ### `fun` ### | |
57 | ||
58 | You can define functions with `fun`. They can be anonymous. | |
59 | ||
60 | | (robin (0 . 1) (small (0 . 1)) | |
61 | | ((fun (a) a) (literal whee))) | |
62 | = whee | |
63 | ||
64 | Bindings in force when a function is defined will still be in force | |
65 | when the function is applied, even if they are not lexically in scope. | |
66 | ||
67 | | (robin (0 . 1) (small (0 . 1)) | |
68 | | ((let | |
69 | | ((a (literal (hi))) | |
70 | | (f (fun (x) (pair x a)))) f) (literal oh))) | |
71 | = (oh hi) | |
72 | ||
73 | Functions can take functions. | |
74 | ||
75 | | (robin (0 . 1) (small (0 . 1)) | |
76 | | (let | |
77 | | ((apply (fun (x) (x (literal a))))) | |
78 | | (apply (fun (r) (pair r (literal ())))))) | |
79 | = (a) | |
80 | ||
81 | Functions can return functions. | |
82 | ||
83 | | (robin (0 . 1) (small (0 . 1)) | |
84 | | (let | |
85 | | ((mk (fun (x) (fun (y) (pair y x)))) | |
86 | | (mk2 (mk (literal (vindaloo))))) | |
87 | | (mk2 (literal chicken)))) | |
88 | = (chicken vindaloo) | |
89 | ||
90 | Functions can be implemented in Robin, using macros, but the implementation | |
91 | is rather onerous, so the details are omitted here. | |
92 | ||
93 | `fun` is basically equivalent to Scheme's `lambda`. | |
94 | ||
95 | ### `bind` ### | |
96 | ||
97 | `bind` binds a single identifier to the result of evaluating a single | |
98 | expression, and makes that binding available in another expression which | |
99 | it evaluates. | |
100 | ||
101 | | (robin (0 . 1) (small (0 . 1)) | |
102 | | (bind x (literal hello) | |
103 | | (pair x x))) | |
104 | = (hello . hello) | |
105 | ||
106 | | (robin (0 . 1) (small (0 . 1)) | |
107 | | (bind dup (fun (x) (pair x x)) | |
108 | | (dup (literal g)))) | |
109 | = (g . g) | |
110 | ||
111 | | (robin (0 . 1) (small (0 . 1)) | |
112 | | (bind dup (fun (x) (pair x x)) | |
113 | | (dup (dup (literal g))))) | |
114 | = ((g . g) g . g) | |
115 | ||
116 | | (robin (0 . 1) (small (0 . 1)) | |
117 | | (bind smoosh (fun (x y) (pair y x)) | |
118 | | (smoosh #t #f))) | |
119 | = (#f . #t) | |
120 | ||
121 | | (robin (0 . 1) (small (0 . 1)) | |
122 | | (bind find (fun (self alist key) | |
123 | | (if (equal? alist (literal ())) (literal ()) | |
124 | | (if (equal? key (head (head alist))) | |
125 | | (head alist) | |
126 | | (self self (tail alist) key)))) | |
127 | | (find find (literal ((c . d) (e . f) (a . b))) (literal a)))) | |
128 | = (a . b) | |
129 | ||
130 | `bind` could be implemented in Robin. | |
131 | ||
132 | | (robin (0 . 1) (small (0 . 1)) | |
133 | | ((macro (self args env) (eval | |
134 | | (pair (pair (head args) (eval env (head (tail args)))) env) | |
135 | | (head (tail (tail args))))) | |
136 | | x (literal hello) (pair x x))) | |
137 | = (hello . hello) | |
138 | ||
139 | `bind` is basically equivalent to Scheme's `let`, but only one | |
140 | binding may be given. | |
141 | ||
142 | ### `let` ### | |
143 | ||
144 | `let` lets you bind multiple identifiers to multiple values. | |
145 | ||
146 | An identifier can be bound to a symbol. | |
147 | ||
148 | | (robin (0 . 1) (small (0 . 1)) | |
149 | | (let ((a (literal hello))) a)) | |
150 | = hello | |
151 | ||
152 | `let` can appear in the binding expression in a `let`. | |
153 | ||
154 | | (robin (0 . 1) (small (0 . 1)) | |
155 | | (let ((a (let ((b (literal c))) b))) a)) | |
156 | = c | |
157 | ||
158 | `let` can bind a symbol to a function value. | |
159 | ||
160 | | (robin (0 . 1) (small (0 . 1)) | |
161 | | (let ((a (fun (x y) (pair x y)))) | |
162 | | (a (literal foo) (literal ())))) | |
163 | = (foo) | |
164 | ||
165 | Bindings established in a `let` remain in effect when evaluating | |
166 | the arguments things in the body of the `let`. | |
167 | ||
168 | | (robin (0 . 1) (small (0 . 1)) | |
169 | | (let ((dup (fun (x) (pair x x)))) | |
170 | | (dup (dup (literal g))))) | |
171 | = ((g . g) g . g) | |
172 | ||
173 | Bindings established in a binding in a `let` can be seen in | |
174 | subsequent bindings in the same `let`. | |
175 | ||
176 | | (robin (0 . 1) (small (0 . 1)) | |
177 | | (let ((a (literal hello)) (b (pair a (literal ())))) b)) | |
178 | = (hello) | |
179 | ||
180 | Shadowing happens. | |
181 | ||
182 | | (robin (0 . 1) (small (0 . 1)) | |
183 | | (let ((a (literal hello))) (let ((a (literal goodbye))) a))) | |
184 | = goodbye | |
185 | ||
186 | `let` can have an empty list of bindings. | |
187 | ||
188 | | (robin (0 . 1) (small (0 . 1)) | |
189 | | (let () (literal hi))) | |
190 | = hi | |
191 | ||
192 | `let` could be implemented as a recursive macro in Robin. | |
193 | ||
194 | | (robin (0 . 1) (small (0 . 1)) | |
195 | | ((macro (self args env) | |
196 | | (bind bindings (head args) | |
197 | | (if (equal? bindings ()) | |
198 | | (eval env (head (tail args))) | |
199 | | (bind binding (head bindings) | |
200 | | (bind name (head binding) | |
201 | | (bind value (eval env (head (tail binding))) | |
202 | | (bind newenv (pair (pair name value) env) | |
203 | | (bind newbindings (tail bindings) | |
204 | | (bind newargs (pair newbindings (tail args)) | |
205 | | (eval newenv (pair self newargs))))))))))) | |
206 | | ((a (literal b)) (c #f)) (pair a c))) | |
207 | = (b . #f) | |
208 | ||
209 | Bindings internal to the recursive macro don't leak. | |
210 | ||
211 | | (robin (0 . 1) (small (0 . 1)) | |
212 | | ((macro (self args env) | |
213 | | (bind bindings (head args) | |
214 | | (if (equal? bindings ()) | |
215 | | (eval env (head (tail args))) | |
216 | | (bind binding (head bindings) | |
217 | | (bind name (head binding) | |
218 | | (bind value (eval env (head (tail binding))) | |
219 | | (bind newenv (pair (pair name value) env) | |
220 | | (bind newbindings (tail bindings) | |
221 | | (bind newargs (pair newbindings (tail args)) | |
222 | | (eval newenv (pair self newargs))))))))))) | |
223 | | ((a (literal b)) (c #f)) newbindings)) | |
224 | ? robin: uncaught exception: (unbound-identifier . newbindings) | |
225 | ||
226 | `let` is basically equivalent to Scheme's `let*` or Haskell's `let`. | |
227 | ||
228 | ### `choose` ### | |
229 | ||
230 | `choose` expects to be given a list of tests. Each test is a two-element | |
231 | list, the first element of which is a condition which should evaluate to | |
232 | a boolean, and the second element of which is an expression, which | |
233 | will be evaluated only if the boolean is `#t`, and `choose` will immediately | |
234 | evaluate to that result without trying any of the subsequent tests. The | |
235 | condition in the final test must be the literal symbol `else`; the | |
236 | corresponding expression will be evaluated if all other tests failed. | |
237 | ||
238 | | (robin (0 . 1) (small (0 . 1)) | |
239 | | (choose (#t (literal hi)) (else (literal lo)))) | |
240 | = hi | |
241 | ||
242 | | (robin (0 . 1) (small (0 . 1)) | |
243 | | (choose (#f (literal hi)) (#t (literal med)) (else (literal lo)))) | |
244 | = med | |
245 | ||
246 | | (robin (0 . 1) (small (0 . 1)) | |
247 | | (choose (#f (literal hi)) (#f (literal med)) (else (literal lo)))) | |
248 | = lo | |
249 | ||
250 | `choose` can have zero tests before the `else`. | |
251 | ||
252 | | (robin (0 . 1) (small (0 . 1)) | |
253 | | (choose (else (literal woo)))) | |
254 | = woo | |
255 | ||
256 | `choose` is basically equivalent to Scheme's `cond`. | |
257 | ||
258 | ### `env` ### | |
259 | ||
260 | `env` evaluates to all the bindings in effect at the point of execution | |
261 | where this form is encountered, as an alist. | |
262 | ||
263 | | (robin (0 . 1) (small (0 . 1)) | |
264 | | (bind find (fun (self alist key) | |
265 | | (if (equal? alist (literal ())) (literal ()) | |
266 | | (if (equal? key (head (head alist))) | |
267 | | (head alist) | |
268 | | (self self (tail alist) key)))) | |
269 | | (pair | |
270 | | (find find (env) (literal boolean?)) (find find (env) (literal pair))))) | |
271 | = ((boolean? . (builtin boolean?)) pair . (builtin pair)) |
0 | (robin (0 . 1) (core (0 . 1)) | |
1 | (eval | |
2 | (pair | |
3 | (pair ((macro (self args env) (head args)) literal) (macro (self args env) (head args))) | |
4 | (pair | |
5 | (pair ((macro (self args env) (head args)) bind) (macro (self args env) (eval | |
6 | (pair (pair (head args) (eval env (head (tail args)))) env) | |
7 | (head (tail (tail args)))))) | |
8 | (pair | |
9 | (pair ((macro (self args env) (head args)) env) (macro (self args env) env)) | |
10 | ((macro (self args env) env)) | |
11 | ) | |
12 | ) | |
13 | ) | |
14 | ((macro (self args env) (head args)) | |
15 | (bind let (macro (self args env) | |
16 | (bind bindings (head args) | |
17 | (if (equal? bindings ()) | |
18 | (eval env (head (tail args))) | |
19 | (bind binding (head bindings) | |
20 | (bind name (head binding) | |
21 | (bind value (eval env (head (tail binding))) | |
22 | (bind newenv (pair (pair name value) env) | |
23 | (bind newbindings (tail bindings) | |
24 | (bind newargs (pair newbindings (tail args)) | |
25 | (eval newenv (pair self newargs))))))))))) | |
26 | (let | |
27 | ( | |
28 | (choose (macro (self args env) | |
29 | (let | |
30 | ((branch (head args)) | |
31 | (test (head branch)) | |
32 | (then (head (tail branch)))) | |
33 | (if (equal? test (literal else)) | |
34 | (eval env then) | |
35 | (if (eval env test) | |
36 | (eval env then) | |
37 | (eval env (pair self (tail args)))))))) | |
38 | (make-env (macro (self args env) | |
39 | (let ( | |
40 | (closed-env (head args)) | |
41 | (fun-env (head (tail args))) | |
42 | (formals (head (tail (tail args)))) | |
43 | (actuals (head (tail (tail (tail args))))) | |
44 | ) | |
45 | (if (equal? formals ()) | |
46 | closed-env | |
47 | (let ( | |
48 | (value (eval fun-env (head actuals))) | |
49 | (new-closed-env (pair (pair (head formals) value) closed-env)) | |
50 | (new-args (pair new-closed-env (pair fun-env (pair (tail formals) (pair (tail actuals) ()))))) | |
51 | ) | |
52 | (eval env | |
53 | (pair self new-args))))))) | |
54 | (make-env-wrap (macro (self args env) | |
55 | (let ( | |
56 | (closed-env (eval env (head args))) | |
57 | (fun-env (eval env (head (tail args)))) | |
58 | (formals (eval env (head (tail (tail args))))) | |
59 | (actuals (eval env (head (tail (tail (tail args)))))) | |
60 | (new-args (pair closed-env (pair fun-env (pair formals (pair actuals ()))))) | |
61 | ) | |
62 | (eval env | |
63 | (pair make-env new-args))))) | |
64 | (fun (macro (self args env) | |
65 | (let ((formals (head args)) | |
66 | (body (head (tail args)))) | |
67 | (macro (fun-self actuals fun-env) | |
68 | (eval (make-env-wrap env fun-env formals actuals) body))))) | |
69 | ) | |
70 | (env) | |
71 | ) | |
72 | ) | |
73 | ) | |
74 | ) | |
75 | ) |