git @ Cat's Eye Technologies Etcha / rel_1_0_2009_1004
Initial import of Etcha version 1.0 revision 2009.1004 distribution. Cat's Eye Technologies 12 years ago
30 changed file(s) with 698 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 JAVAC?="/cygdrive/c/Program Files/Java/jdk1.6.0_02/bin/javac"
1 JAVA?="/cygdrive/c/Program Files/Java/jre1.6.0_07/bin/java"
2
3 all: bin/tc/catseye/etcha/Executor.class
4
5 bin/tc/catseye/etcha/Executor.class: src/Etcha.java
6 $(JAVAC) -cp bin -d bin src/Etcha.java
7
8 clean:
9 rm bin/tc/catseye/etcha/*.class
10
11 test: bin/tc/catseye/etcha/Executor.class
12 $(JAVA) -cp bin tc.catseye.etcha.Main eg/test.etcha
0 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
1 <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
2 <head>
3 <title>The Etcha Programming Language</title>
4 <!-- begin html doc dynamic markup -->
5 <script type="text/javascript" src="/contrib/jquery-1.6.4.min.js"></script>
6 <script type="text/javascript" src="/scripts/documentation.js"></script>
7 <!-- end html doc dynamic markup -->
8 </head>
9 <body>
10
11 <h1>The Etcha Programming Language</h1>
12
13 <h2>Introduction</h2>
14
15 <p><dfn>Etcha</dfn> is an esoteric programming language based on Jeffry Johnston's <a class="external" href="http://www.esolangs.org/wiki/BitChanger">BitChanger</a>.
16 Like BitChanger, Etcha has four instructions, two of which are used to form Brainfuck-like while-loops.
17 Unlike BitChanger, Etcha has a 2-dimensional storage model based on <dfn>turtle graphics</dfn>, which permits it
18 to be immediately used for an alternative purpose: graphical composition. Unlike a classical turtle in a language such
19 as LOGO however, the turtle in Etcha is an integral part of the computation, playing a role similar to the
20 tape head of a Turing machine.</p>
21
22 <h2>Instructions</h2>
23
24 <ul>
25 <li><code>+</code> -- equivalent to FD 1</li>
26 <li><code>&gt;</code> -- equivalent to RT 90; toggles PU/PD every 4 executions</li>
27 <li><code>[</code> -- equivalent to While</li>
28 <li><code>]</code> -- equivalent to Wend</li>
29 </ul>
30
31 <p>In Etcha, instructions control a <dfn>turtle</dfn>. The turtle exists vis-a-vis
32 an unbounded Cartesian grid called the <dfn>playfield</dfn>.
33 The turtle has a <dfn>position</dfn> in that it occupies exactly one of the points on the playfield
34 (which are referred to as <dfn>pixels</dfn>). Each pixel has a
35 <dfn>state</dfn>, which is either black or white; all pixels are initially black.</p>
36
37 <p>The turtle also has an
38 <dfn>orientation</dfn> which describes the direction it would move in should it travel forward. Unlike a conventional
39 turtle, because of its Cartesian context, there are only four possible orientations which the Etcha turtle can possess: north, east, south,
40 and west, corresponding to headings of 0, 90, 180, and 270 degrees.
41 When an Etcha program starts, the turtle is initially oriented north. Because position is relative, it doesn't matter where the turtle is initially located,
42 but solely for psychological satisfaction we can say that it is initially situated in the very center of this unbounded Cartesian grid.</p>
43
44 <p>The turtle is also equipped with a <dfn>pen</dfn>, which has a <dfn>pen mode</dfn> and a <dfn>pen position</dfn>.
45 The pen mode is always XOR, meaning that when moving forward by execution of <code>+</code>, the
46 state of the pixel that was previously occupied by the turtle gets inverted (from black to white, or vice versa.) The pen position may be up or down.
47 It is initially down. Every fourth time an <code>&gt;</code> instruction is executed, the pen's position is toggled from up to down
48 or vice versa. These executions need not be consecutive; there may be any number of intervening instructions executed.</p>
49
50 <h2>Computational Class</h2>
51
52 <p>Etcha is Turing-complete if BitChanger is, because we can easily translate any BitChanger program into an equivalent Etcha
53 program. Let the Etcha program begin with <code>&gt;&gt;&gt;&gt;</code>, to initially lift the pen. The BitChanger
54 instruction <code>&lt;</code> is translated to the Etcha instructions <code>&gt;&gt;&gt;+&gt;&gt;&gt;&gt;&gt;</code>,
55 and the BitChanger instruction <code>}</code> is translated to <code>&gt;&gt;&gt;&gt;&gt;+&gt;&gt;&gt;</code>.
56 The instructions <code>[</code> and <code>]</code> remain the same. The relation between the BitChanger tape state
57 and the Etcha playfield is quite literal; the <var>y</var> dimension of the grid is simply ignored.</p>
58
59 <h2>Implementation</h2>
60
61 <p>In a particular implementation of Etcha on a microcomputer with a finite storage, the playfield cannot be truly unbounded,
62 and there will come a point in a long enough program execution, entirely dependent on the capabilities of the hardware and the
63 implementation, where a pixel change cannot be correctly stored. The behaviour after this point is undefined. <i>Such
64 <b>m</b>icro<b>c</b>omputer implementations of Etcha may be marketed under the name "MC Etcha".</i></p>
65
66 <p>Cat's Eye Technologies provides an implementation of Etcha written in the Java<sup>TM</sup> programming language.
67 This implementation attempts to demonstrate that the Model-View-Controller design pattern can be applied
68 not only to user interfaces, but also to programming language interpreters. The Model is the state of the
69 program (which is also the state of the Turtle graphics engine.) The View is the interpreter's interpretation of that
70 state, and the Controller is the interpreter's behaviour with respect to the View. For example, the Model exposes
71 the pen up/down semantics, but it is the Controller that implements the rule that <code>&gt;&gt;&gt;&gt;</code>
72 toggles the pen position.</p>
73
74 <p>Happy Etchin'!
75 <br/>Chris Pressey
76 <br/>October 4<sup>th</sup>, 2009
77 <br/>Mold City, USA</p>
78
79 </body>
80 </html>
0 >+++>+++>+++>+++>[+]>>>>+
0 package tc.catseye.etcha;
1
2 // Etcha.java
3 // Ridiculous over-implementation of the Etcha programming language.
4 // Chris Pressey, Cat's Eye Technologies.
5 // $Id: Etcha.java 257 2009-10-04 03:21:43Z cpressey $
6
7 import java.util.List;
8 import java.util.ArrayList;
9 import java.util.Map;
10 import java.util.HashMap;
11 import java.util.Set;
12 import java.util.HashSet;
13 import java.io.BufferedReader;
14 import java.io.FileReader;
15 import java.io.IOException;
16
17 interface Grid
18 {
19 void togglePixel(int x, int y);
20 boolean isPixelBlack(int x, int y);
21 }
22
23 class DefaultGrid implements Grid
24 {
25 private HashMap<Integer, HashMap<Integer, Boolean>> grid;
26 private int minX, minY, maxX, maxY;
27
28 DefaultGrid()
29 {
30 grid = new HashMap<Integer, HashMap<Integer, Boolean>>();
31 minX = minY = maxX = maxY = 0;
32 }
33
34 private static boolean getBoolean(Boolean b)
35 {
36 return b == null ? false : b;
37 }
38
39 public void togglePixel(int x, int y)
40 {
41 HashMap<Integer, Boolean> column = grid.get(x);
42 if (column == null) {
43 column = new HashMap<Integer, Boolean>();
44 column.put(y, true);
45 grid.put(x, column);
46 } else {
47 column.put(y, !getBoolean(column.get(y)));
48 }
49 if (x < minX) minX = x;
50 if (y < minY) minY = y;
51 if (x > maxX) maxX = x;
52 if (y > maxY) maxY = y;
53 }
54
55 public boolean isPixelBlack(int x, int y)
56 {
57 HashMap<Integer, Boolean> column = grid.get(x);
58 if (column == null) {
59 return false;
60 } else {
61 return getBoolean(column.get(y));
62 }
63 }
64
65 public void dump()
66 {
67 int width = maxY - minY;
68 for (int i = 0; i <= width; i++)
69 System.out.print("-");
70 System.out.println();
71 for (int y = minY; y <= maxY; y++) {
72 for (int x = minX; x <= maxX; x++) {
73 System.out.print(isPixelBlack(x, y) ? "#" : " ");
74 }
75 System.out.println();
76 }
77 for (int i = 0; i <= width; i++)
78 System.out.print("-");
79 System.out.println();
80 }
81 }
82
83 interface Turtle
84 {
85 void forward(int distance);
86 void rotate(int degrees);
87 void setPenDown(boolean penDown);
88 public boolean isPenDown();
89 boolean detectPixel();
90 }
91
92 class DefaultTurtle implements Turtle
93 {
94 private Grid grid;
95 private int x, y;
96 private int dx, dy;
97 private boolean penDown;
98 private int heading;
99
100 DefaultTurtle()
101 {
102 grid = new DefaultGrid();
103 x = 0;
104 y = 0;
105 penDown = true;
106 heading = 0;
107 }
108
109 DefaultTurtle(Grid g)
110 {
111 grid = g;
112 x = 0;
113 y = 0;
114 penDown = true;
115 heading = 0;
116 }
117
118 public void forward(int distance)
119 {
120 for (int i = 0; i < distance; i++) {
121 if (penDown)
122 grid.togglePixel(x, y);
123 x += dx();
124 y += dy();
125 }
126 }
127
128 private int dx()
129 {
130 if (heading == 90)
131 return 1;
132 if (heading == 270)
133 return -1;
134 return 0;
135 }
136
137 private int dy()
138 {
139 if (heading == 0)
140 return -1;
141 if (heading == 180)
142 return 1;
143 return 0;
144 }
145
146 public void rotate(int degrees)
147 {
148 heading += degrees;
149 heading %= 360;
150 }
151
152 public void setPenDown(boolean penDown)
153 {
154 this.penDown = penDown;
155 }
156
157 public boolean isPenDown()
158 {
159 return penDown;
160 }
161
162 public boolean detectPixel()
163 {
164 return grid.isPixelBlack(x, y);
165 }
166 }
167
168 interface Counter
169 {
170 public boolean count();
171 }
172
173 class DefaultCounter implements Counter
174 {
175 private int modulus;
176 private int counter;
177
178 DefaultCounter()
179 {
180 modulus = 4;
181 counter = 0;
182 }
183
184 DefaultCounter(int m)
185 {
186 modulus = m;
187 counter = 0;
188 }
189
190 DefaultCounter(int m, int c)
191 {
192 modulus = m;
193 counter = c;
194 }
195
196 public boolean count()
197 {
198 counter++;
199 counter %= modulus;
200 return (counter == 0);
201 }
202 }
203
204 // An execution context knows what command it is currently executing.
205 interface ExecutionContext
206 {
207 Turtle getTurtle();
208 Counter getCounter();
209 ExecutionContext step();
210 }
211
212 class BaseExecutionContext implements ExecutionContext
213 {
214 protected Turtle turtle;
215 protected Counter counter;
216 protected ExecutionContext parent;
217
218 BaseExecutionContext(Turtle t, Counter c)
219 {
220 turtle = t;
221 counter = c;
222 parent = null;
223 }
224
225 BaseExecutionContext(ExecutionContext parent)
226 {
227 turtle = parent.getTurtle();
228 counter = parent.getCounter();
229 this.parent = parent;
230 }
231
232 public Turtle getTurtle()
233 {
234 return turtle;
235 }
236
237 public Counter getCounter()
238 {
239 return counter;
240 }
241
242 public ExecutionContext step()
243 {
244 return parent;
245 }
246 }
247
248 class PrimitiveExecutionContext extends BaseExecutionContext
249 {
250 protected Command cmd;
251
252 PrimitiveExecutionContext(Command cmd, ExecutionContext parent)
253 {
254 super(parent);
255 this.cmd = cmd;
256 }
257
258 public ExecutionContext step()
259 {
260 cmd.execute(turtle, counter);
261 return parent;
262 }
263 }
264
265 class BlockExecutionContext extends PrimitiveExecutionContext
266 {
267 protected int pos;
268 protected Block cmd;
269
270 BlockExecutionContext(Block cmd, ExecutionContext parent)
271 {
272 super(cmd, parent);
273 this.cmd = cmd; // because our cmd is not our parent's!
274 pos = 0;
275 }
276
277 public ExecutionContext step()
278 {
279 Command sub = cmd.get(pos);
280 if (sub == null) {
281 return parent;
282 }
283 pos++;
284 ExecutionContext e = sub.createExecutionContext(this);
285 // Could just return e here. But then we'd have to do twice as many
286 // steps, stepping into each contained command
287 return e.step();
288 }
289 }
290
291 class LoopExecutionContext extends BlockExecutionContext
292 {
293 protected Loop cmd;
294
295 LoopExecutionContext(Loop cmd, ExecutionContext parent)
296 {
297 super(cmd, parent);
298 this.cmd = cmd; // because our cmd is not our parent's!
299 }
300
301 public ExecutionContext step()
302 {
303 if (pos >= cmd.size()) {
304 pos = 0;
305 }
306 if (pos == 0 && !turtle.detectPixel()) {
307 return parent;
308 }
309 return super.step();
310 }
311 }
312
313 interface Command
314 {
315 void execute(Turtle t, Counter c);
316 ExecutionContext createExecutionContext(ExecutionContext parent);
317 }
318
319 abstract class AbstractCommand implements Command
320 {
321 abstract public void execute(Turtle t, Counter c);
322 public ExecutionContext createExecutionContext(ExecutionContext parent)
323 {
324 return new PrimitiveExecutionContext(this, parent);
325 }
326 }
327
328 class Right extends AbstractCommand
329 {
330 public void execute(Turtle t, Counter c)
331 {
332 t.rotate(90);
333 if (c.count())
334 t.setPenDown(!t.isPenDown());
335 }
336 }
337
338 class Forward extends AbstractCommand
339 {
340 public void execute(Turtle t, Counter c)
341 {
342 t.forward(1);
343 }
344 }
345
346 class Block extends AbstractCommand
347 {
348 private List<Command> cmds;
349
350 Block(List<Command> cmds)
351 {
352 this.cmds = cmds;
353 }
354
355 public Command get(int index)
356 {
357 try {
358 return cmds.get(index);
359 } catch (IndexOutOfBoundsException e) {
360 return null;
361 }
362 }
363
364 public int size()
365 {
366 return cmds.size();
367 }
368
369 public void execute(Turtle t, Counter c)
370 {
371 for (Command cmd : cmds) {
372 cmd.execute(t, c);
373 }
374 }
375
376 public ExecutionContext createExecutionContext(ExecutionContext parent)
377 {
378 return new BlockExecutionContext(this, parent);
379 }
380 }
381
382 class Loop extends Block
383 {
384 Loop(List<Command> cmds)
385 {
386 super(cmds);
387 }
388
389 public void execute(Turtle t, Counter c)
390 {
391 while (t.detectPixel()) {
392 super.execute(t, c);
393 }
394 }
395
396 public ExecutionContext createExecutionContext(ExecutionContext parent)
397 {
398 return new LoopExecutionContext(this, parent);
399 }
400 }
401
402 class Executor
403 {
404 Turtle turtle;
405 Counter counter;
406
407 Executor()
408 {
409 turtle = new DefaultTurtle();
410 counter = new DefaultCounter();
411 }
412
413 Executor(Turtle t, Counter c)
414 {
415 turtle = t;
416 counter = c;
417 }
418
419 Executor(Grid g)
420 {
421 turtle = new DefaultTurtle(g);
422 counter = new DefaultCounter();
423 }
424
425 public void run(Command program)
426 {
427 program.execute(turtle, counter);
428 }
429
430 // same as run, but uses the step methods
431 public void stepAll(Command program)
432 {
433 ExecutionContext base = new BaseExecutionContext(turtle, counter);
434 ExecutionContext e = program.createExecutionContext(base);
435 while (e != base) {
436 e = e.step();
437 }
438 }
439 }
440
441 interface Warner
442 {
443 void warn(String warning);
444 }
445
446 interface ParseFactory<X>
447 {
448 char getRepresentation();
449 X generate(Parser p);
450 }
451
452 abstract class AbstractParseFactory<X> implements ParseFactory<X>
453 {
454 public abstract char getRepresentation();
455 public abstract X generate(Parser p);
456 }
457
458 class ForwardParseFactory extends AbstractParseFactory<Command>
459 {
460 public char getRepresentation()
461 {
462 return '+';
463 }
464
465 public Command generate(Parser p)
466 {
467 return new Forward();
468 }
469 }
470
471 class RightParseFactory extends AbstractParseFactory<Command>
472 {
473 public char getRepresentation()
474 {
475 return '>';
476 }
477
478 public Command generate(Parser p)
479 {
480 return new Right();
481 }
482 }
483
484 class LoopParseFactory extends AbstractParseFactory<Command>
485 {
486 public char getRepresentation()
487 {
488 return '[';
489 }
490
491 public Command generate(Parser p)
492 {
493 p.advance();
494 Set<Character> until = new HashSet<Character>();
495 until.add(new Character(']'));
496 List<Command> cmds = p.parseMany(until);
497 return new Loop(cmds);
498 }
499 }
500
501 class Parser
502 {
503 private String str;
504 private int pos;
505 private Warner warner;
506 private Map<Character,ParseFactory<Command>> factoryMap;
507
508 Parser(String s)
509 {
510 str = s;
511 pos = 0;
512 warner = null;
513 factoryMap = new HashMap<Character,ParseFactory<Command>>();
514 ArrayList<ParseFactory<Command>> factories = new ArrayList<ParseFactory<Command>>();
515 factories.add(new ForwardParseFactory());
516 factories.add(new RightParseFactory());
517 factories.add(new LoopParseFactory());
518 for (ParseFactory<Command> factory : factories) {
519 factoryMap.put(factory.getRepresentation(), factory);
520 }
521 }
522
523 public void setWarner(Warner w)
524 {
525 warner = w;
526 }
527
528 public void advance()
529 {
530 pos++;
531 }
532
533 public Command parseOne()
534 {
535 char c = str.charAt(pos);
536 Command cmd = null;
537
538 ParseFactory<Command> factory = factoryMap.get(c);
539 if (factory != null) {
540 cmd = factory.generate(this);
541 } else if (warner != null) {
542 warner.warn("unrecognized command '" + c +
543 "' at position " + pos);
544 }
545 advance();
546
547 return cmd;
548 }
549
550 public List<Command> parseMany(Set<Character> until)
551 {
552 List<Command> cmds = new ArrayList<Command>();
553
554 while (pos < str.length()) {
555 char c = str.charAt(pos);
556 if (until != null && until.contains(c))
557 break;
558 Command cmd = parseOne();
559 if (cmd != null)
560 cmds.add(cmd);
561 }
562
563 return cmds;
564 }
565 }
566
567 // A command-line interface to our Etcha interpreter.
568 class Main
569 {
570 static public void main(String argv[])
571 {
572 // parse command line arguments
573 for (String arg : argv)
574 loadAndInterpret(arg);
575 }
576
577 static void loadAndInterpret(String filename)
578 {
579 // open and read file
580 StringBuffer fileContents = new StringBuffer();
581 try {
582 BufferedReader input = new BufferedReader(new FileReader(filename));
583 String str;
584 str = input.readLine();
585 while (str != null) {
586 fileContents.append(str);
587 str = input.readLine();
588 }
589 input.close();
590 } catch (IOException e) {
591 System.err.println("Couldn't read '" + filename + "'");
592 return;
593 }
594 Parser parser = new Parser(fileContents.toString());
595 Set<Character> empty = new HashSet<Character>();
596 Block program = new Block(parser.parseMany(empty));
597 DefaultGrid grid = new DefaultGrid();
598 Executor executor = new Executor(grid);
599 executor.stepAll(program);
600 grid.dump();
601 }
602 }