git @ Cat's Eye Technologies Wagon / 79f717d
Initial import of files for the Wagon reference distribution. Chris Pressey 4 years ago
12 changed file(s) with 467 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 *.exe
1 *.hi
2 *.o
3 *.js
4 *.jsmod
0 Wagon
1 =====
2
3 Introduction
4 ------------
5
6 In a conventional concatenative language, every symbol represents
7 a function which takes program states to program states, and the program
8 is a concatenation (sequential composition) of such functions.
9 This is fine when it comes to manipulating state, but what about control?
10
11 One can allow individual functions to be defined and named seperately,
12 then applied, perhaps conditionally and perhaps recursively. While
13 this is conventional in the world of concatenative languages, such
14 languages are arguably no longer purely concatenative (a subject
15 explored by [Carriage][] and [Equipage][]).
16
17 If one wishes the language to remain purely concatenative, it seems one
18 must store control structures in the state (for example, [Equipage][]
19 stores functions on the stack) or, more drastically, allow the function
20 that is being constructed, to be examined somehow during the construction
21 process. Functions typically aren't considered examinable in this way,
22 but even so, examining it this way is not very different from putting
23 a copy of the program itself in the state.
24
25 **Wagon** is an experiment with a third option: *second-order functions*.
26 Instead of functions which take states to states, the symbols of the
27 language represent functions which take functions from states to states,
28 to functions that take states to states. The state is merely a stack of
29 integers, and the program is purely a concatenation of functions.
30
31 My hope was that these second-order functions could express control in
32 addition to expressing state manipulation. This does turn out to be the
33 case, but only to a degree. As far as I have been able to determine,
34 they can express some control structures, but not arbitrary ones.
35
36 Fundamentals of Wagon
37 ---------------------
38
39 Talking about functions that themselves work on functions is admittedly somewhat
40 awkward, so let's define some terms. Let's call a function that takes states
41 to states an _operation_. Let's call a function that takes operations to
42 operations a _macro_. A Wagon program, then, consists of a sequence of symbols,
43 each of which represents a macro. These individual macros are concatenated
44 (sequentially composed) to form a single macro.
45
46 Since a macro takes operations to operations, it's not possible to "run" a macro
47 directly. So, when asked to "run" or "evaluate" a Wagon program,
48 we take the following convention: apply the macro that the Wagon program represents
49 to the identity function, and apply the resulting function to an initial stack.
50 We may allow a Wagon program to accept input by supplying it on the initial stack,
51 but usually the initial stack is just empty. The output is usually a depiction
52 of the final state of the stack.
53
54 Many of the primitive macros in Wagon are based directly on operations. There are
55 two main ways we "lift" an operation _k_ to a macro. The first is a macro which takes
56 an operation _o_ and returns an operation which performs _o_ then performs _k_ —
57 we call this sort of macro an _after-lifting_ of _k_. The second is a macro which
58 takes an operation _o_ and returns an operation which performs _k_ then performs _o_ —
59 we call this sort of macro a _before-lifting_ of _k_.
60
61 Wagon's convention is that after-lifted operations are represented by lowercase letters,
62 and before-lifted operations are represented by uppercase letters. Macros which do not
63 clearly fall into either of these two categories are represented by punctuation symbols.
64
65 Primitive Macros of Wagon
66 -------------------------
67
68 -> Tests for functionality "Evaluate Wagon Program"
69
70 -> Functionality "Evaluate Wagon Program" is implemented by
71 -> shell command "bin/wagon run %(test-body-file)"
72
73 -> Functionality "Evaluate Wagon Program" is implemented by
74 -> shell command "bin/wagon eval %(test-body-file)"
75
76 Note that in the following examples, the `===>` line is not part of the program;
77 instead it shows the expected result of running each program. In the expected
78 result, the resulting stack is depicted top-to-bottom.
79
80 ### Rudimentary Arithmetic ###
81
82 `i` is a macro which takes an operation _o_ and returns an operation which
83 performs _o_ then pushes a 1 onto the stack.
84
85 `s` is a macro which takes an operation _o_ and returns an operation which
86 performs _o_ then pops _a_ from the stack then pops _b_ from the stack and
87 pushes _b_ - _a_.
88
89 With these we can construct some numbers.
90
91 i
92 ===> [1]
93
94 iis
95 ===> [0]
96
97 iis is
98 ===> [-1]
99
100 i iis is s
101 ===> [2]
102
103 As you can see, `i` and `s` are after-lifted operations. There are also
104 before-lifted counterparts of them:
105
106 `I` is a macro which takes an operation _o_ and returns an operation which
107 pushes a 1 onto the stack then performs _o_.
108
109 `S` is a macro which takes an operation _o_ and returns and operation which
110 pops _a_ from the stack then pops _b_ from the stack and pushes _b_ - _a_
111 then performs _o_.
112
113 SII
114 ===> [0]
115
116 Note also that whitespace maps to the identity function (a macro which takes
117 an operation and returns that same operation) so is effectively insignificant
118 in a Wagon program.
119
120 ### Rudimentary Stack Manipulation ###
121
122 `p` is an after-lifting of the operation: pop a value from the stack and discard it.
123
124 i iis iis iis ppp
125 ===> [1]
126
127 `P` is the before-lifted counterpart to `p`.
128
129 PI
130 ===> []
131
132 `d` is an after-lifting of the operation: duplicate the top value on the stack.
133
134 iis ddd
135 ===> [0,0,0,0]
136
137 `D` is the before-lifted counterpart to `d`.
138
139 DDDI
140 ===> [1,1,1,1]
141
142 ### Sophisticated Stack Manipulation ###
143
144 `r` is an after-lifted operation: it pops a value _n_ from the stack. Then it
145 pops _n_ values from the stack and temporarily remembers them. Then it
146 reverses the remainder of the stack. Then it pushes those _n_ remembered
147 values back onto the stack. _n_ must be zero or one.
148
149 iis i iiisiss
150 ===> [2,1,0]
151
152 iis i iiisiss iis r
153 ===> [0,1,2]
154
155 iis i iiisiss i r
156 ===> [2,0,1]
157
158 `R` is the before-lifted counterpart to `r`.
159
160 I SII
161 ===> [1,0]
162
163 R SII I SII
164 ===> [0,1]
165
166 ### Rudimentary Control Flow ###
167
168 `@` (pronounced "while") is a macro which takes an operation _o_ and returns
169 an operation that repeatedly performs _o_ as long as there are elements on
170 the stack and the top element of the stack is non-zero.
171
172 p@ I I I SII SII
173 ===> [0,0]
174
175 An "if" can be constructed by writing a "while" that, first thing it does is,
176 pop the non-zero it detected, and last thing it does is, push a 0 onto the
177 stack. Then immediately afterwards, pop the top element of the stack (which
178 we know must be zero because we know the loop just exited, whether it was
179 executed once, or not at all.)
180
181 Computational Class
182 -------------------
183
184 When Wagon was first formed, it was not clear if it would be Turing-complete
185 or not. The author observed it was possible to translate many programs
186 written in [Loose Circular Brainfuck][] into Wagon, using the following
187 correspondence:
188
189 + iisiss
190 - is
191 > iisrir
192 < iriisr
193 x[y]z y@Xz
194
195 Loose Circular Brainfuck is Turing-complete, so if this correspondence
196 was total, Wagon would be shown Turing-complete too. However, the correspondence
197 is not total.
198
199 It's the `@` that's the problem. We can construct a "while" loop
200 with contents, and with operations that happen before it,
201 and with operations that happen after it. And the contents can themselves
202 contain a nested "while" loop. But we cannot place a second "while" loop
203 *after* an already-given "while" loop. That is, we cannot have more than
204 one "while" loop on the same nesting level.
205
206 This might be best illustrated with a depiction of the nested structure that
207 a Wagon program represents.
208
209 -> Tests for functionality "Depict Wagon Program"
210
211 -> Functionality "Depict Wagon Program" is implemented by
212 -> shell command "bin/wagon depict %(test-body-file)"
213
214 p@ I I I SII SII
215 ===> Push1 Push1 Sub Push1 Push1 Sub Push1 Push1 Push1 (while Pop)
216
217 is@I is@I
218 ===> Push1 (while Push1 (while Push1 Sub) Push1 Sub)
219
220 isis@I @I
221 ===> Push1 (while Push1 (while Push1 Sub Push1 Sub))
222
223 i@Dp
224 ===> Dup (while Push1) Pop
225
226 i@Dp i@Dp
227 ===> Dup (while Dup (while Push1) Pop Push1) Pop
228
229 While it is possible to simulate a universal Turing machine with only a
230 single top-level "while" loop and a series of "if" statements inside the
231 body of the "while" (see, for example, [Burro][]), it is not known to me
232 if it is possible to simulate a universal Turing machine with only
233 strictly-singly-nested "while" loops as constructible in Wagon.
234
235 Any inner "while" loop can be turned into an "if" as described above,
236 but a conventional "if/else" is not possible, because it would normally
237 require two consecutive "while"s, one to check the condition, and one
238 to check the inverse of the condition. Similarly, it would not be
239 possible to check if the finite control of a Turing machine is in one
240 state, or another state. This would seem to be a fairly serious restriction.
241
242 However, shortly after being announced in the `#esoteric` IRC channel, it was
243 [shown by int-e](https://gist.github.com/int-e/e4ae1f40f8173d67860d8f8e45c433c0)
244 that it is possible to compile a Tag system into a Wagon program.
245 Since Tag systems are Turing-complete, Wagon is as well.
246
247 As of this writing, it remains unclear if Wagon is able to simulate a
248 Turing machine or Loose Circular Brainfuck program directly rather than
249 via a Tag system.
250
251 [Loose Circular Brainfuck]: https://esolangs.org/wiki/Loose_Circular_Brainfuck_(LCBF)
252 [Carriage]: https://catseye.tc/node/Carriage
253 [Equipage]: https://catseye.tc/node/Equipage
254 [Burro]: https://catseye.tc/node/Burro
0 #!/bin/sh
1
2 THIS=`realpath $0`
3 DIR=`dirname $THIS`
4 NAME=`basename $THIS`
5 SRC=$DIR/../src
6 if [ -x $DIR/$NAME.exe ] ; then
7 exec $DIR/$NAME.exe $*
8 elif command -v runhaskell 2>&1 >/dev/null ; then
9 exec runhaskell -i$SRC $SRC/Main.hs $*
10 elif command -v runhugs 2>&1 >/dev/null ; then
11 exec runhugs -i$SRC $SRC/Main.hs $*
12 else
13 echo "Cannot run $NAME; neither $NAME.exe, nor runhaskell, nor runhugs found."
14 exit 1
15 fi
0 #!/bin/sh
1
2 PROG=wagon
3
4 if command -v ghc >/dev/null 2>&1; then
5 echo "building $PROG.exe with ghc"
6 (cd src && ghc --make Main.hs -o ../bin/$PROG.exe)
7 else
8 echo "ghc not found, not building $PROG.exe"
9 fi
0 #!/bin/sh
1
2 rm -f src/*.hi src/*.o src/*.jsmod bin/*.exe
3 rm -f src/Language/Wagon/*.hi src/Language/Wagon/*.o src/Language/Wagon/*.jsmod
0 p@ I I I SII SII
0 module Language.Wagon.ConcatEval where
1
2 --
3 -- Concatenative version of the evaluator: a Haskell function
4 -- is constructed directly from the Wagon program.
5 --
6
7 appA op = m where m x = (op . x) -- After
8 appB op = m where m x = (x . op) -- Before
9
10 push1 s = (1:s)
11 sub (a:b:s) = (b-a:s)
12 pop s = tail s
13 dup (x:s) = (x:x:s)
14 rev (0:s) = reverse s
15 rev (1:x:s) = (x:reverse s)
16
17 mwhile op = op' where
18 op' s = cwhile op s
19 cwhile op s@[] = s
20 cwhile op s@(0:rest) = s
21 cwhile op s@(_:rest) = cwhile op (op s)
22
23 ic 'i' = appA push1
24 ic 'I' = appB push1
25
26 ic 's' = appA sub
27 ic 'S' = appB sub
28
29 ic 'p' = appA pop
30 ic 'P' = appB pop
31
32 ic 'd' = appA dup
33 ic 'D' = appB dup
34
35 ic 'r' = appA rev
36 ic 'R' = appB rev
37
38 ic '@' = mwhile
39
40 ic ' ' = id
41 ic '\t' = id
42 ic '\n' = id
43 ic '\r' = id
44
45 parse [] = id
46 parse (c:cs) = (parse cs) . (ic c)
47
48 eval t s = (t id) s
49
50 run t = eval (parse t) []
0 module Language.Wagon.Depict where
1
2 --
3 -- Convert an intermediate representation to a string which
4 -- reads like a program in a conventional nested programming language.
5 --
6
7 import Language.Wagon.IR
8
9
10 nestDepict (While op) = "(while " ++ (nestDepict op) ++ ")"
11 nestDepict (Cons Nil b) = nestDepict b
12 nestDepict (Cons a Nil) = nestDepict a
13 nestDepict (Cons a b) = (nestDepict b) ++ " " ++ (nestDepict a)
14 nestDepict Nil = ""
15 nestDepict other = (show other)
16
17 depict t = nestDepict (compile t)
0 module Language.Wagon.IR where
1
2 --
3 -- Intermediate representation for Wagon programs;
4 -- used in the symbolic interpreter and the depictor;
5 -- could be used in a compiler as well.
6 --
7
8 data Op = Push1
9 | Sub
10 | Pop
11 | Dup
12 | Rev
13 | If Op
14 | While Op
15 | Cons Op Op
16 | Nil
17 deriving (Show, Ord, Eq)
18
19 appA op = m where m x = Cons op x -- After
20 appB op = m where m x = Cons x op -- Before
21
22 ic 'i' = appA Push1
23 ic 'I' = appB Push1
24
25 ic 's' = appA Sub
26 ic 'S' = appB Sub
27
28 ic 'p' = appA Pop
29 ic 'P' = appB Pop
30
31 ic 'd' = appA Dup
32 ic 'D' = appB Dup
33
34 ic 'r' = appA Rev
35 ic 'R' = appB Rev
36
37 ic '@' = mwhile where mwhile op = While op
38
39 ic ' ' = id
40 ic '\t' = id
41 ic '\n' = id
42 ic '\r' = id
43
44 parse [] = id
45 parse (c:cs) = (parse cs) . (ic c)
46
47 compile t = (parse t) Nil
0 module Language.Wagon.SymInterp where
1
2 --
3 -- Symbolic version of the evaluator: the Wagon program is
4 -- compiled to its intermediate representation, which is interpreted.
5 --
6
7 import Language.Wagon.IR
8
9
10 eval Push1 s = (1:s)
11 eval Sub (a:b:s) = (b-a:s)
12 eval Pop s = tail s
13 eval Dup (x:s) = (x:x:s)
14 eval Rev (0:s) = reverse s
15 eval Rev (1:x:s) = (x:reverse s)
16 eval (While op) s = cwhile op s where
17 cwhile op s@[] = s
18 cwhile op s@(0:rest) = s
19 cwhile op s@(_:rest) = cwhile op (eval op s)
20 eval Nil s = s
21 eval (Cons a b) s = eval a (eval b s)
22
23 run t = eval (compile t) []
0 module Main where
1
2 import System.Environment
3 import System.Exit
4 import System.IO
5
6 import qualified Language.Wagon.ConcatEval as ConcatEval
7 import qualified Language.Wagon.SymInterp as SymInterp
8 import qualified Language.Wagon.Depict as Depict
9
10
11 main = do
12 args <- getArgs
13 case args of
14 ["run", fileName] -> do
15 text <- readFile fileName
16 putStrLn $ show $ ConcatEval.run text
17 return ()
18 ["eval", fileName] -> do
19 text <- readFile fileName
20 putStrLn $ show $ SymInterp.run text
21 return ()
22 ["depict", fileName] -> do
23 text <- readFile fileName
24 putStrLn $ Depict.depict text
25 return ()
26 _ -> do
27 abortWith "Usage: wagon (run|eval|depict) <wagon-program-text-filename>"
28
29 abortWith msg = do
30 hPutStrLn stderr msg
31 exitWith (ExitFailure 1)
0 #!/bin/sh
1
2 falderal README.md || exit 1