git @ Cat's Eye Technologies Robin / 81acc44
Initial import of Robin (not-quite-0.1-yet) sources. catseye 13 years ago
23 changed file(s) with 2407 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 syntax: regexp
1
2 ^bin/
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
(New empty file)
0 #!/bin/sh
1 ghc Main.lhs -o bin/robin
2 rm -f *.o *.hi Robin/*.o Robin/*.hi
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) (stdlib (0 . 1))
1 (literal hello-world))
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 )
0 #!/bin/sh
1
2 if [ ! -e bin/robin ]; then
3 ./build.sh
4 fi
5
6 falderal test doc/Robin.falderal \
7 doc/module/Core.falderal \
8 doc/module/Small.falderal \
9 doc/module/Exception.falderal \
10 doc/module/Concurrency.falderal