title: Replace historical example source file with download and modify state: OPEN author: dboreham labels: comments: 0 assignees: projects: milestone: number: 94 -- Currently we have a source file in the repository that was copied from the web site for a book. It is an example program from the book. The file is at: `./historical-examples/life.occ`. The original book web site url for the file is here: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt. In order to make the program build and run within the transpiler environment and kroc course module it was necessary to modify it slightly. In addition there are constants for the board size (`array.width` and `array.height`). These have been set to specific values so the program parses. The file is subject to copyright and although the book web site allows use for some purposes, it would be better not to have it copied as-is into this repository. Therefore it would be better to instead do the following with a new script: 1. Download the book version of the life program from its URL, naming it life.occ. 2. Apply changes to that file such that it matches the current in-repo version of life.occ. Once the script is working the repo copy of life.occ can be removed. The end result should be that: 1. There's no copy of the book example program in this repository. 2. The user can get the life.occ example as it currently exists in the repository, by running the script. Take note that a simple context diff patch may not give the desired outcome -- it will make the correct file contents but the patch file may end up including a significant amount of the original copyrighted file, defeating the objective to have none of that material in this repo. Note also that the original file hasn't changed since 1988 and therefore any patching/editing done can safely assume that the file will never change. This may help to remove the need for context in the patch.
Let me understand the issue:
./historical-examples/life.occ from a bookLet me look at both files to understand the differences.
{
"file_path": "/home/david/projects/codeassociates/occam2go/historical-examples/life.occ"
} 1→-- Code copied from Programming in occam®2
2→-- © Geraint Jones, Michael Goldsmith 1988, 2001.
3→-- Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk
4→--
5→-- The program in this chapter plays Life on a terminal screen.
6→--
7→-- Adapted for occam2go: replaced book-library functions
8→-- (write.string, write.formatted, DATA.ITEM) with inline
9→-- definitions; added terminal.keyboard/terminal.screen declarations.
10→--
11→
12→--
13→-- helper procedures (replaces book standard library)
14→--
15→
16→PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)
17→ SEQ i = 0 FOR SIZE s
18→ out ! s[i]
19→:
20→
21→PROC write.small.int(CHAN OF BYTE out!, VAL INT n)
22→ -- outputs a small non-negative integer (0..999) as decimal digits
23→ IF
24→ n >= 100
25→ SEQ
26→ out ! BYTE ((n / 100) + (INT '0'))
27→ out ! BYTE (((n / 10) \ 10) + (INT '0'))
28→ out ! BYTE ((n \ 10) + (INT '0'))
29→ n >= 10
30→ SEQ
31→ out ! BYTE ((n / 10) + (INT '0'))
32→ out ! BYTE ((n \ 10) + (INT '0'))
33→ TRUE
34→ out ! BYTE (n + (INT '0'))
35→:
36→
37→--
38→-- configuration constants
39→--
40→
41→VAL INT array.width IS 20 : -- number of cells across the board
42→VAL INT array.height IS 20 : -- number of cells down the board
43→
44→VAL INT radius IS 1 : -- of the `sphere of influence'
45→VAL INT diameter IS (2 * radius) + 1 :
46→VAL INT neighbours IS (diameter * diameter) - 1 :
47→
48→VAL INT number.of.cells IS array.height * array.width :
49→VAL INT number.of.links IS neighbours * number.of.cells :
50→
51→--
52→-- protocols
53→--
54→
55→PROTOCOL STATE IS BOOL :
56→
57→VAL BOOL alive IS TRUE :
58→VAL BOOL dead IS NOT alive :
59→
60→PROTOCOL COMMAND
61→ CASE
62→ set.state; BOOL
63→ evolve
64→ terminate
65→:
66→
67→PROTOCOL RESPONSE IS BOOL; BOOL :
68→--
69→-- cell processes
70→--
71→
72→PROC broadcast.present.state([][][]CHAN OF STATE link,
73→ VAL INT x, y, VAL BOOL state )
74→ PAR d = 0 FOR neighbours
75→ link[x][y][d] ! state
76→:
77→
78→PROC calculate.next.state([][][]CHAN OF STATE link,
79→ VAL []INT nx, ny,
80→ VAL BOOL state, BOOL next.state )
81→ INT count : -- number of living neighbours
82→ SEQ
83→ [neighbours]BOOL state.of.neighbour :
84→ SEQ
85→ PAR d = 0 FOR neighbours
86→ link[nx[d]][ny[d]][d] ? state.of.neighbour[d]
87→ count := 0
88→ SEQ d = 0 FOR neighbours
89→ IF
90→ state.of.neighbour[d] = alive
91→ count := count + 1
92→ state.of.neighbour[d] = dead
93→ SKIP
94→ IF
95→ count < 2 -- death from isolation
96→ next.state := dead
97→ count = 2 -- this cell is stable
98→ next.state := state
99→ count = 3 -- stable if alive, a birth if dead
100→ next.state := alive
101→ count > 3 -- death from overcrowding
102→ next.state := dead
103→:
104→
105→
106→
107→
108→
109→
110→
111→
112→
113→
114→
115→
116→
117→
118→
119→PROC cell([][][]CHAN OF STATE link,
120→ VAL INT x, y, VAL []INT nx, ny,
121→ CHAN OF COMMAND control,
122→ CHAN OF RESPONSE sense )
123→ BOOL state, not.finished :
124→ SEQ
125→ state := dead -- the whole board starts off dead
126→ not.finished := TRUE
127→ WHILE not.finished
128→ control ? CASE
129→
130→ set.state; state
131→ SKIP -- state has been set to the new value
132→
133→ evolve
134→ BOOL next.state :
135→ SEQ
136→ PAR
137→ broadcast.present.state(link, x, y, state)
138→ SEQ
139→ calculate.next.state(link, nx, ny,
140→ state, next.state )
141→ sense ! (state <> next.state); next.state
142→ state := next.state
143→
144→ terminate
145→ not.finished := FALSE
146→:
147→
148→--
149→-- terminal-dependent output routines
150→--
151→
152→PROC clear.screen(CHAN OF BYTE terminal!)
153→ -- clear screen sequence for an ANSI terminal: ESC [ 2 J
154→ SEQ
155→ terminal ! BYTE #1B
156→ terminal ! '['
157→ terminal ! '2'
158→ terminal ! 'J'
159→:
160→
161→PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)
162→ -- left-handed co-ordinates, origin 0,0 at top left
163→ -- outputs ANSI escape sequence: ESC [ row ; col H
164→ SEQ
165→ terminal ! BYTE #1B
166→ terminal ! '['
167→ write.small.int(terminal, y + 1)
168→ terminal ! ';'
169→ write.small.int(terminal, x + 1)
170→ terminal ! 'H'
171→:
172→
173→
174→
175→
176→--
177→-- display routines
178→--
179→
180→PROC initialize.display(CHAN OF BYTE screen!)
181→ -- display an entirely dead board
182→ clear.screen(screen)
183→:
184→
185→PROC clean.up.display(CHAN OF BYTE screen!)
186→ move.cursor(screen, 0, array.height)
187→:
188→
189→PROC display.state(CHAN OF BYTE screen!, VAL INT x, y, VAL BOOL state)
190→ SEQ
191→ move.cursor(screen, x, y)
192→ IF
193→ state = alive
194→ screen ! '**'
195→ state = dead
196→ screen ! '*s'
197→:
198→
199→--
200→-- controller states
201→--
202→
203→VAL INT idle IS 0 : -- controller activity values
204→VAL INT editing IS 1 :
205→VAL INT single.step IS 2 :
206→VAL INT free.running IS 3 :
207→VAL INT terminated IS 4 :
208→
209→
210→INT FUNCTION new.activity(VAL BYTE char)
211→ INT activity :
212→ VALOF
213→ CASE char -- typed on the keyboard ...
214→ 'q', 'Q' -- ... Q to finish program
215→ activity := terminated
216→ 's', 'S' -- ... S to halt evolution
217→ activity := idle
218→ 'e', 'E' -- ... E to start editing
219→ activity := editing
220→ 'r', 'R' -- ... R to start evolution
221→ activity := free.running
222→ ELSE -- ... or anything else for one generation
223→ activity := single.step
224→ RESULT activity
225→:
226→
227→PROC display.activity(CHAN OF BYTE screen!, VAL INT activity)
228→ SEQ
229→ move.cursor(screen, array.width+1, array.height/2)
230→ CASE activity
231→ idle
232→ write.string(screen, "Idle")
233→ editing
234→ write.string(screen, "Edit")
235→ single.step
236→ write.string(screen, "Step")
237→ free.running
238→ write.string(screen, "Busy")
239→ terminated
240→ write.string(screen, "Done")
241→:
242→
243→--
244→-- generation
245→--
246→
247→PROC generation(CHAN OF BYTE screen!,
248→ [][]CHAN OF COMMAND control,
249→ [][]CHAN OF RESPONSE sense,
250→ BOOL active )
251→ SEQ
252→ PAR x = 0 FOR array.width
253→ PAR y = 0 FOR array.height
254→ control[x][y] ! evolve
255→ active := FALSE
256→ SEQ x = 0 FOR array.width
257→ SEQ y = 0 FOR array.height
258→ BOOL changed, next.state :
259→ SEQ
260→ sense[x][y] ? changed; next.state
261→ IF
262→ changed
263→ SEQ
264→ display.state(screen, x, y, next.state)
265→ active := TRUE
266→ NOT changed
267→ SKIP
268→:
269→
270→
271→
272→
273→
274→
275→
276→
277→
278→--
279→-- editor
280→--
281→
282→INT FUNCTION min(VAL INT a, b)
283→ INT min :
284→ VALOF
285→ IF
286→ a <= b
287→ min := a
288→ b <= a
289→ min := b
290→ RESULT min
291→:
292→
293→INT FUNCTION max(VAL INT a, b)
294→ INT max :
295→ VALOF
296→ IF
297→ a >= b
298→ max := a
299→ b >= a
300→ max := b
301→ RESULT max
302→:
303→
304→
305→
306→
307→
308→
309→
310→
311→
312→
313→
314→
315→
316→
317→
318→
319→
320→
321→
322→
323→
324→
325→
326→
327→
328→
329→PROC editor(CHAN OF BYTE keyboard?, screen!,
330→ [][]CHAN OF COMMAND control )
331→ INT x, y :
332→ BOOL editing :
333→ SEQ
334→ -- initialize co-ordinates to centre of board
335→ x, y := array.width / 2, array.height / 2
336→ editing := TRUE
337→ WHILE editing
338→ BYTE char :
339→ SEQ
340→ move.cursor(screen, x, y)
341→ keyboard ? char
342→ CASE char
343→ 'A' -- move up, if possible
344→ y := max(y - 1, 0)
345→ 'B' -- move down, if possible
346→ y := min(y + 1, array.height - 1)
347→ 'C' -- move right, if possible
348→ x := min(x + 1, array.width - 1)
349→ 'D' -- move left, if possible
350→ x := max(x - 1, 0)
351→ '*s', '**'
352→ VAL BOOL state IS (char = '**') = alive :
353→ PAR
354→ control[x][y] ! set.state; state
355→ display.state(screen, x, y, state)
356→ 'q', 'Q'
357→ editing := FALSE
358→ ELSE
359→ SKIP -- ignore anything else
360→:
361→
362→
363→
364→
365→
366→
367→
368→
369→
370→
371→
372→
373→
374→
375→
376→
377→
378→
379→
380→--
381→-- controller
382→--
383→
384→PROC controller(CHAN OF BYTE keyboard?, screen!,
385→ [][]CHAN OF COMMAND control,
386→ [][]CHAN OF RESPONSE sense )
387→ INT activity :
388→ SEQ
389→ activity := idle
390→ initialize.display(screen)
391→ WHILE activity <> terminated
392→ SEQ
393→ display.activity(screen, activity)
394→ BYTE char :
395→ PRI ALT
396→ (activity <> editing) & keyboard ? char
397→ activity := new.activity(char)
398→ (activity <> idle) & SKIP
399→ CASE activity
400→ editing
401→ SEQ
402→ editor(keyboard, screen, control)
403→ activity := idle
404→ free.running, single.step
405→ BOOL changing :
406→ SEQ
407→ generation(screen, control, sense, changing)
408→ IF
409→ (activity = single.step) OR (NOT changing)
410→ activity := idle
411→ (activity = free.running) AND changing
412→ SKIP
413→ display.activity(screen, activity)
414→ PAR x = 0 FOR array.width
415→ PAR y = 0 FOR array.height
416→ control[x][y] ! terminate
417→ clean.up.display(screen)
418→:
419→
420→
421→
422→
423→
424→
425→
426→
427→
428→
429→
430→
431→--
432→-- structure of the program
433→--
434→
435→PROC life (CHAN BYTE keyboard?, screen!, error!)
436→ [array.width][array.height][neighbours]CHAN OF STATE link :
437→ [array.width][array.height]CHAN OF COMMAND control :
438→ [array.width][array.height]CHAN OF RESPONSE sense :
439→ PAR
440→ controller(keyboard, screen, control, sense)
441→ PAR x = 0 FOR array.width
442→ PAR y = 0 FOR array.height
443→ VAL INT left IS ((x - 1) + array.width) \ array.width :
444→ VAL INT right IS (x + 1) \ array.width :
445→ VAL INT up IS (y + 1) \ array.height :
446→ VAL INT down IS ((y - 1) + array.height) \ array.height :
447→ VAL [neighbours]INT nx IS
448→ [ right, x, left, left, left, x, right, right ] :
449→ VAL [neighbours]INT ny IS
450→ [ down, down, down, y, up, up, up, y ] :
451→ cell(link, x, y, nx, ny, control[x][y], sense[x][y])
452→:
453→
454→{
"url": "https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt",
"prompt": "Return the COMPLETE raw text content of this file exactly as-is, preserving all whitespace, indentation, and line breaks. Do not summarize or modify anything."
}ls /home/david/projects/codeassociates/occam2go/historical-examples/
# Complete Raw Text Content
```
-- The program in this chapter plays Life on a terminal screen.
--
--
-- configuration constants
--
VAL INT array.width IS ... : -- number of cells across the board
VAL INT array.height IS ... : -- number of cells down the board
VAL INT radius IS 1 : -- of the `sphere of influence'
VAL INT diameter IS (2 * radius) + 1 :
VAL INT neighbours IS (diameter * diameter) - 1 :
VAL INT number.of.cells IS array.height * array.width :
VAL INT number.of.links IS neighbours * number.of.cells :
--
-- protocols
--
PROTOCOL STATE IS BOOL :
VAL BOOL alive IS TRUE :
VAL BOOL dead IS NOT alive :
PROTOCOL COMMAND
CASE
set.state; BOOL
evolve
terminate
:
PROTOCOL RESPONSE IS BOOL; BOOL :
--
-- cell processes
--
PROC broadcast.present.state([][][]CHAN OF STATE link,
VAL INT x, y, VAL BOOL state )
PAR d = 0 FOR neighbours
link[x][y][d] ! state
:
PROC calculate.next.state([][][]CHAN OF STATE link,
VAL []INT nx, ny,
VAL BOOL state, BOOL next.state )
INT count : -- number of living neighbours
SEQ
[neighbours]BOOL state.of.neighbour :
SEQ
PAR d = 0 FOR neighbours
link[nx[d]][ny[d]][d] ? state.of.neighbour[d]
count := 0
SEQ d = 0 FOR neighbours
IF
state.of.neighbour[d] = alive
count := count + 1
state.of.neighbour[d] = dead
SKIP
IF
count < 2 -- death from isolation
next.state := dead
count = 2 -- this cell is stable
next.state := state
count = 3 -- stable if alive, a birth if dead
next.state := alive
count > 3 -- death from overcrowding
next.state := dead
:
PROC cell([][][]CHAN OF STATE link,
VAL INT x, y, VAL []INT nx, ny,
CHAN OF COMMAND control,
CHAN OF RESPONSE sense )
BOOL state, not.finished :
SEQ
state := dead -- the whole board starts off dead
not.finished := TRUE
WHILE not.finished
control ? CASE
set.state; state
SKIP -- state has been set to the new value
evolve
BOOL next.state :
SEQ
PAR
broadcast.present.state(link, x, y, state)
SEQ
calculate.next.state(link, nx, ny,
state, next.state )
sense ! (state <> next.state); next.state
state := next.state
terminate
not.finished := FALSE
:
--
-- terminal-dependent output routines
--
PROC clear.screen(CHAN OF BYTE terminal)
-- clear screen sequence for an ANSI terminal
write.string(terminal, "*#1B[2J")
:
PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
-- left-handed co-ordinates, origin 0,0 at top left
CHAN OF DATA.ITEM c :
PAR
write.formatted(terminal, "*#1B[%d;%dH", c)
SEQ
c ! data.int; y + 1
c ! data.int; x + 1
:
--
-- display routines
--
PROC initialize.display(CHAN OF BYTE screen)
-- display an entirely dead board
clear.screen(screen)
:
PROC clean.up.display(CHAN OF BYTE screen)
move.cursor(screen, 0, array.height)
:
PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state)
SEQ
move.cursor(screen, x, y)
IF
state = alive
screen ! '**'
state = dead
screen ! '*s'
:
--
-- controller states
--
VAL INT idle IS 0 : -- controller activity values
VAL INT editing IS 1 :
VAL INT single.step IS 2 :
VAL INT free.running IS 3 :
VAL INT terminated IS 4 :
INT FUNCTION new.activity(VAL BYTE char)
INT activity :
VALOF
CASE char -- typed on the keyboard ...
'q', 'Q' -- ... Q to finish program
activity := terminated
's', 'S' -- ... S to halt evolution
activity := idle
'e', 'E' -- ... E to start editing
activity := editing
'r', 'R' -- ... R to start evolution
activity := free.running
ELSE -- ... or anything else for one generation
activity := single.step
RESULT activity
:
PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
SEQ
move.cursor(screen, array.width+1, array.height/2)
CASE activity
idle
write.string(screen, "Idle")
editing
write.string(screen, "Edit")
single.step
write.string(screen, "Step")
free.running
write.string(screen, "Busy")
terminated
write.string(screen, "Done")
:
--
-- generation
--
PROC generation(CHAN OF BYTE screen,
[][]CHAN OF COMMAND control,
[][]CHAN OF RESPONSE sense,
BOOL active )
SEQ
PAR x = 0 FOR array.width
PAR y = 0 FOR array.height
control[x][y] ! evolve
active := FALSE
SEQ x = 0 FOR array.width
SEQ y = 0 FOR array.height
BOOL changed, next.state :
SEQ
sense[x][y] ? changed; next.state
IF
changed
SEQ
display.state(screen, x, y, next.state)
active := TRUE
NOT changed
SKIP
:
--
-- editor
--
INT FUNCTION min(VAL INT a, b)
INT min :
VALOF
IF
a <= b
min := a
b <= a
min := b
RESULT min
:
INT FUNCTION max(VAL INT a, b)
INT max :
VALOF
IF
a >= b
max := a
b >= a
max := b
RESULT max
:
PROC editor(CHAN OF BYTE keyboard, screen,
[][]CHAN OF COMMAND control )
INT x, y :
BOOL editing :
SEQ
-- initialize co-ordinates to centre of board
x, y := array.width / 2, array.height / 2
editing := TRUE
WHILE editing
BYTE char :
SEQ
move.cursor(screen, x, y)
keyboard ? char
CASE char
'A' -- move up, if possible
y := max(y - 1, 0)
'B' -- move down, if possible
y := min(y + 1, array.height - 1)
'C' -- move right, if possible
x := min(x + 1, array.width - 1)
'D' -- move left, if possible
x := max(x - 1, 0)
'*s', '**'
VAL BOOL state IS (char = '**') = alive :
PAR
control[x][y] ! set.state; state
display.state(screen, x, y, state)
'q', 'Q'
editing := FALSE
ELSE
SKIP -- ignore anything else
:
--
-- controller
--
PROC controller(CHAN OF BYTE keyboard, screen,
[][]CHAN OF COMMAND control,
[][]CHAN OF RESPONSE sense )
INT activity :
SEQ
activity := idle
initialize.display(screen)
WHILE activity <> terminated
SEQ
display.activity(screen, activity)
BYTE char :
PRI ALT
(activity <> editing) & keyboard ? char
activity := new.activity(char)
(activity <> idle) & SKIP
CASE activity
editing
SEQ
editor(keyboard, screen, control)
activity := idle
free.running, single.step
BOOL changing :
SEQ
generation(screen, control, sense, changing)
IF
(activity = single.step) OR (NOT changing)
activity := idle
(activity = free.running) AND changing
SKIP
display.activity(screen, activity)
PAR x = 0 FOR array.width
PAR y = 0 FOR array.height
control[x][y] ! terminate
clean.up.display(screen)
:
--
-- structure of the program
--
[array.width][array.height][neighbours]CHAN OF STATE link :
[array.width][array.height]CHAN OF COMMAND control :
[array.width][array.height]CHAN OF RESPONSE sense :
PAR
controller(terminal.keyboard, terminal.screen, control, sense)
PAR x = 0 FOR array.width
PAR y = 0 FOR array.height
VAL INT left IS ((x - 1) + array.width) \ array.width :
VAL INT right IS (x + 1) \ array.width :
VAL INT up IS (y + 1) \ array.height :
VAL INT down IS ((y - 1) + array.height) \ array.height :
VAL [neighbours]INT nx IS
[ right, x, left, left, left, x, right, right ] :
VAL [neighbours]INT ny IS
[ down, down, down, y, up, up, up, y ] :
cell(link, x, y, nx, ny, control[x][y], sense[x][y])
```Now I need to understand the exact differences. Let me download the original file properly and do a diff. The WebFetch might not have preserved exact formatting. Let me download it with curl.
curl -s -o /tmp/original-life.txt https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt && wc -l /tmp/original-life.txt
diff /tmp/original-life.txt /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
0a1,4 > -- Code copied from Programming in occam®2 > -- © Geraint Jones, Michael Goldsmith 1988, 2001. > -- Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk > -- 2a7,35 > -- Adapted for occam2go: replaced book-library functions > -- (write.string, write.formatted, DATA.ITEM) with inline > -- definitions; added terminal.keyboard/terminal.screen declarations. > -- > > -- > -- helper procedures (replaces book standard library) > -- > > PROC write.string(CHAN OF BYTE out!, VAL []BYTE s) > SEQ i = 0 FOR SIZE s > out ! s[i] > : > > PROC write.small.int(CHAN OF BYTE out!, VAL INT n) > -- outputs a small non-negative integer (0..999) as decimal digits > IF > n >= 100 > SEQ > out ! BYTE ((n / 100) + (INT '0')) > out ! BYTE (((n / 10) \ 10) + (INT '0')) > out ! BYTE ((n \ 10) + (INT '0')) > n >= 10 > SEQ > out ! BYTE ((n / 10) + (INT '0')) > out ! BYTE ((n \ 10) + (INT '0')) > TRUE > out ! BYTE (n + (INT '0')) > : 8,9c41,42 < VAL INT array.width IS ... : -- number of cells across the board < VAL INT array.height IS ... : -- number of cells down the board --- > VAL INT array.width IS 20 : -- number of cells across the board > VAL INT array.height IS 20 : -- number of cells down the board 119,121c152,158 < PROC clear.screen(CHAN OF BYTE terminal) < -- clear screen sequence for an ANSI terminal < write.string(terminal, "*#1B[2J") --- > PROC clear.screen(CHAN OF BYTE terminal!) > -- clear screen sequence for an ANSI terminal: ESC [ 2 J > SEQ > terminal ! BYTE #1B > terminal ! '[' > terminal ! '2' > terminal ! 'J' 124c161 < PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y) --- > PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y) 126,131c163,170 < CHAN OF DATA.ITEM c : < PAR < write.formatted(terminal, "*#1B[%d;%dH", c) < SEQ < c ! data.int; y + 1 < c ! data.int; x + 1 --- > -- outputs ANSI escape sequence: ESC [ row ; col H > SEQ > terminal ! BYTE #1B > terminal ! '[' > write.small.int(terminal, y + 1) > terminal ! ';' > write.small.int(terminal, x + 1) > terminal ! 'H' 141c180 < PROC initialize.display(CHAN OF BYTE screen) --- > PROC initialize.display(CHAN OF BYTE screen!) 146c185 < PROC clean.up.display(CHAN OF BYTE screen) --- > PROC clean.up.display(CHAN OF BYTE screen!) 150c189 < PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state) --- > PROC display.state(CHAN OF BYTE screen!, VAL INT x, y, VAL BOOL state) 188c227 < PROC display.activity(CHAN OF BYTE screen, VAL INT activity) --- > PROC display.activity(CHAN OF BYTE screen!, VAL INT activity) 208c247 < PROC generation(CHAN OF BYTE screen, --- > PROC generation(CHAN OF BYTE screen!, 290c329 < PROC editor(CHAN OF BYTE keyboard, screen, --- > PROC editor(CHAN OF BYTE keyboard?, screen!, 345c384 < PROC controller(CHAN OF BYTE keyboard, screen, --- > PROC controller(CHAN OF BYTE keyboard?, screen!, 396,411c435,453 < [array.width][array.height][neighbours]CHAN OF STATE link : < [array.width][array.height]CHAN OF COMMAND control : < [array.width][array.height]CHAN OF RESPONSE sense : < PAR < controller(terminal.keyboard, terminal.screen, control, sense) < PAR x = 0 FOR array.width < PAR y = 0 FOR array.height < VAL INT left IS ((x - 1) + array.width) \ array.width : < VAL INT right IS (x + 1) \ array.width : < VAL INT up IS (y + 1) \ array.height : < VAL INT down IS ((y - 1) + array.height) \ array.height : < VAL [neighbours]INT nx IS < [ right, x, left, left, left, x, right, right ] : < VAL [neighbours]INT ny IS < [ down, down, down, y, up, up, up, y ] : < cell(link, x, y, nx, ny, control[x][y], sense[x][y]) --- > PROC life (CHAN BYTE keyboard?, screen!, error!) > [array.width][array.height][neighbours]CHAN OF STATE link : > [array.width][array.height]CHAN OF COMMAND control : > [array.width][array.height]CHAN OF RESPONSE sense : > PAR > controller(keyboard, screen, control, sense) > PAR x = 0 FOR array.width > PAR y = 0 FOR array.height > VAL INT left IS ((x - 1) + array.width) \ array.width : > VAL INT right IS (x + 1) \ array.width : > VAL INT up IS (y + 1) \ array.height : > VAL INT down IS ((y - 1) + array.height) \ array.height : > VAL [neighbours]INT nx IS > [ right, x, left, left, left, x, right, right ] : > VAL [neighbours]INT ny IS > [ down, down, down, y, up, up, up, y ] : > cell(link, x, y, nx, ny, control[x][y], sense[x][y]) > : >
Now I understand the differences. The changes are:
... with 20 for array.width and array.heightclear.screen implementation (was using write.string with escape, now uses individual byte sends)move.cursor implementation (was using write.formatted/DATA.ITEM, now uses write.small.int)Add ! direction annotation to various CHAN OF BYTE params (screen!)
Add ? and ! direction annotations to editor and controller params
PROC life(...) with indentationThe key insight is that a context diff would expose copyrighted material, so I need a script using line-number based operations like sed that can reference specific positions in the original file without including the actual content. Since the file is fixed, I can design sed commands that insert the copyright header and other modifications at precise line numbers.
Now I'm mapping out all the specific line edits needed: inserting the copyright and helper procs at the top, replacing the ellipsis with 20, updating the clear.screen and move.cursor implementations, adding direction annotations to various procedures, and replacing the final section with the wrapped PROC life body.
Since the file structure is stable, I can rely on line numbers for precise sed substitutions—mostly adding ! markers after screen or terminal references. I'll create a shell script to automate these changes.
Now I have a clear picture of all the differences. Let me create the script that downloads and transforms the file. The key is to use line-number based edits (since the original never changes) to avoid including copyrighted content.
#!/bin/bash
#
# Downloads the Life example from the "Programming in occam 2" book website
# and applies modifications needed to build with occam2go and the kroc course module.
#
# The original source is copyright Geraint Jones & Michael Goldsmith 1988, 2001.
# See: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt
#
# Changes applied:
# - Add copyright/attribution header and adaptation notes
# - Add helper procedures (write.string, write.small.int) replacing book library
# - Set board dimensions (array.width/array.height) to 20
# - Replace clear.screen/move.cursor to avoid book library dependencies
# - Add channel direction annotations (? and !) for occam2go compatibility
# - Wrap main body in PROC life(...) declaration
#
set -euo pipefail
URL="https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT="${SCRIPT_DIR}/life.occ"
echo "Downloading original life.occ from book website..."
if ! curl -sfS -o "${OUTPUT}" "${URL}"; then
echo "Error: failed to download ${URL}" >&2
exit 1
fi
echo "Applying modifications for occam2go..."
# All sed edits use line numbers since the original file is fixed (unchanged since 1988).
# This avoids including copyrighted content as context in patches.
sed -i \
-e '1i\
-- Code copied from Programming in occam®2\
-- © Geraint Jones, Michael Goldsmith 1988, 2001.\
-- Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk\
--' \
"${OUTPUT}"
# Lines shifted by 4 after the header insertion. Original line 2 is now line 6.
# Insert adaptation comment and helper procs after line 6 (the "--" blank comment line).
sed -i '6a\
-- Adapted for occam2go: replaced book-library functions\
-- (write.string, write.formatted, DATA.ITEM) with inline\
-- definitions; added terminal.keyboard/terminal.screen declarations.\
--\
\
--\
-- helper procedures (replaces book standard library)\
--\
\
PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)\
SEQ i = 0 FOR SIZE s\
out ! s[i]\
:\
\
PROC write.small.int(CHAN OF BYTE out!, VAL INT n)\
-- outputs a small non-negative integer (0..999) as decimal digits\
IF\
n >= 100\
SEQ\
out ! BYTE ((n / 100) + (INT '"'"'0'"'"'))\
out ! BYTE (((n / 10) \\ 10) + (INT '"'"'0'"'"'))\
out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
n >= 10\
SEQ\
out ! BYTE ((n / 10) + (INT '"'"'0'"'"'))\
out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
TRUE\
out ! BYTE (n + (INT '"'"'0'"'"'))\
:' "${OUTPUT}"
# Now the file has grown. Recalculate line positions.
# Original lines 8-9 (array.width/height with "...") are now at lines 45-46.
sed -i \
-e '45s/IS \.\.\. :/IS 20 :/' \
-e '46s/IS \.\.\. :/IS 20 :/' \
"${OUTPUT}"
# Original lines 119-121 (clear.screen) are now at lines 156-158.
# Replace the 3-line clear.screen with new 7-line version.
sed -i '156,158c\
PROC clear.screen(CHAN OF BYTE terminal!)\
-- clear screen sequence for an ANSI terminal: ESC [ 2 J\
SEQ\
terminal ! BYTE #1B\
terminal ! '"'"'['"'"'\
terminal ! '"'"'2'"'"'\
terminal ! '"'"'J'"'"'' "${OUTPUT}"
# After replacing 3 lines with 7, net +4 lines.
# Original line 124 (move.cursor signature) is now at line 165.
sed -i '165c\
PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)' "${OUTPUT}"
# Original lines 126-131 (move.cursor body) are now at lines 167-172.
# Replace with new implementation.
sed -i '167,172c\
-- outputs ANSI escape sequence: ESC [ row ; col H\
SEQ\
terminal ! BYTE #1B\
terminal ! '"'"'['"'"'\
write.small.int(terminal, y + 1)\
terminal ! '"'"';'"'"'\
write.small.int(terminal, x + 1)\
terminal ! '"'"'H'"'"'' "${OUTPUT}"
# After replacing 6 lines with 8, net +2. Running offset from original: +4+2+37 = +43.
# But let's just count from the current state. We need to add ! to various CHAN OF BYTE params.
# Use targeted substitutions on specific lines.
# initialize.display: original line 141 -> now line 184
sed -i '184s/CHAN OF BYTE screen)/CHAN OF BYTE screen!)/' "${OUTPUT}"
# clean.up.display: original line 146 -> now line 189
sed -i '189s/CHAN OF BYTE screen)/CHAN OF BYTE screen!)/' "${OUTPUT}"
# display.state: original line 150 -> now line 193
sed -i '193s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"
# display.activity: original line 188 -> now line 231
sed -i '231s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"
# generation: original line 208 -> now line 251
sed -i '251s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"
# editor: original line 290 -> now line 333
sed -i '333s/CHAN OF BYTE keyboard, screen,/CHAN OF BYTE keyboard?, screen!,/' "${OUTPUT}"
# controller: original line 345 -> now line 388
sed -i '388s/CHAN OF BYTE keyboard, screen,/CHAN OF BYTE keyboard?, screen!,/' "${OUTPUT}"
# Main body: original lines 396-411 -> now lines 439-454.
# Wrap in PROC life and indent the body.
sed -i '439,454c\
PROC life (CHAN BYTE keyboard?, screen!, error!)\
[array.width][array.height][neighbours]CHAN OF STATE link :\
[array.width][array.height]CHAN OF COMMAND control :\
[array.width][array.height]CHAN OF RESPONSE sense :\
PAR\
controller(keyboard, screen, control, sense)\
PAR x = 0 FOR array.width\
PAR y = 0 FOR array.height\
VAL INT left IS ((x - 1) + array.width) \\\\ array.width :\
VAL INT right IS (x + 1) \\\\ array.width :\
VAL INT up IS (y + 1) \\\\ array.height :\
VAL INT down IS ((y - 1) + array.height) \\\\ array.height :\
VAL [neighbours]INT nx IS\
[ right, x, left, left, left, x, right, right ] :\
VAL [neighbours]INT ny IS\
[ down, down, down, y, up, up, up, y ] :\
cell(link, x, y, nx, ny, control[x][y], sense[x][y])\
:\
' "${OUTPUT}"
echo "Done. Output written to: ${OUTPUT}"
The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.
While this looks promising, I'm wondering if there is a way to include much less of the copyright material in the script -- most of the changes look to be moving existing lines, adding one character, adding indentation and so on. Can that be done without including the entire new line of code (which includes the original line obviously) in the script/patch?
The user makes a great point. Many of the sed commands I wrote include full lines from the original copyrighted file (just with small modifications like adding !). Instead, I should use more surgical edits:
For adding ! or ? to channel params: use a substitution like `s/screen)/screen!)/' on the specific line number - this only references the small changed part, not the whole line. Actually, I was already doing this for some of them. Let me reconsider.
For the PROC life wrapper: instead of replacing all 16 lines with 18 new lines (which includes nearly all the original content), I could:
PROC life (...) line before the blockterminal.keyboard with keyboard and terminal.screen with screenAdd : and blank line at the end
For clear.screen: the original is 3 lines, replacement is 7. The original used write.string(terminal, "*#1B[2J"). We need to replace this with individual byte sends. This is a genuine rewrite that doesn't contain much original material.
For move.cursor: similar - the original used write.formatted and DATA.ITEM, we're replacing those with direct byte operations, so there's minimal overlap with the original code.
The ... -> 20 substitution is fine, very minimal.
The header insertion is all new text (attribution), fine.
The helper procs (write.string, write.small.int) are entirely new code, fine.
So the main issue is the PROC life wrapper at the end, and the channel direction additions. The channel direction annotations are just tiny syntax changes, which is fine. For the PROC life wrapper itself, I'm wrapping the original bare declarations and PAR block in a procedure signature and indenting everything by 2 spaces, which is a structural transformation rather than copying the original content.
I could minimize this further by inserting the PROC line, indenting the existing lines, and substituting the terminal references with the new channel names, rather than reproducing the original lines verbatim. The backslash continuations in the original would just get the indentation prepended naturally.
Now I'm thinking through how to handle the clear.screen replacement where 3 lines become 7 lines...
Writing the script...
Great point. Most of my sed commands were reproducing entire original lines just to add ! or indent them. A better approach:
!/? additions: small substitutions like s/screen)/screen!)/ — already minimal, fine.... → 20: already minimal, fine.terminal.keyboard → keyboard / terminal.screen → screen, and append the closing :\n.write.string/write.formatted/DATA.ITEM which are deleted and replaced with entirely new code — no original content in the replacements.Let me write a cleaner script using this approach.
#!/bin/bash
#
# Downloads the Life example from the "Programming in occam 2" book website
# and applies modifications needed to build with occam2go and the kroc course module.
#
# The original source is copyright Geraint Jones & Michael Goldsmith 1988, 2001.
# See: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt
#
# Changes applied:
# - Add copyright/attribution header and adaptation notes
# - Add helper procedures (write.string, write.small.int) replacing book library
# - Set board dimensions (array.width/array.height) to 20
# - Replace clear.screen/move.cursor to avoid book library dependencies
# - Add channel direction annotations (? and !) for occam2go compatibility
# - Wrap main body in PROC life(...) declaration
#
# All line-number references are to the original downloaded file, which has not
# changed since 1988. Edits are applied bottom-to-top so that line numbers of
# earlier edits remain valid.
set -euo pipefail
URL="https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT="${SCRIPT_DIR}/life.occ"
echo "Downloading original life.occ from book website..."
if ! curl -sfS -o "${OUTPUT}" "${URL}"; then
echo "Error: failed to download ${URL}" >&2
exit 1
fi
echo "Applying modifications for occam2go..."
# We apply edits bottom-to-top so earlier line numbers stay correct.
# --- Main body (lines 396-411): wrap in PROC life, indent, rename params ---
# Insert PROC header before line 396
sed -i '396i\PROC life (CHAN BYTE keyboard?, screen!, error!)' "${OUTPUT}"
# Indent lines 397-412 (the original 396-411, now shifted by 1) by 2 spaces
sed -i '397,412s/^/ /' "${OUTPUT}"
# Rename terminal.keyboard -> keyboard, terminal.screen -> screen in that range
sed -i '397,412s/terminal\.keyboard/keyboard/' "${OUTPUT}"
sed -i '397,412s/terminal\.screen/screen/' "${OUTPUT}"
# Append closing colon after line 412
sed -i '412a\:' "${OUTPUT}"
# Append trailing blank line
sed -i '413a\\' "${OUTPUT}"
# --- controller (line 345): add channel direction annotations ---
sed -i '345s/keyboard, screen,/keyboard?, screen!,/' "${OUTPUT}"
# --- editor (line 290): add channel direction annotations ---
sed -i '290s/keyboard, screen,/keyboard?, screen!,/' "${OUTPUT}"
# --- generation (line 208): add ! to screen param ---
sed -i '208s/screen,/screen!,/' "${OUTPUT}"
# --- display.activity (line 188): add ! to screen param ---
sed -i '188s/screen,/screen!,/' "${OUTPUT}"
# --- display.state (line 150): add ! to screen param ---
sed -i '150s/screen,/screen!,/' "${OUTPUT}"
# --- clean.up.display (line 146): add ! to screen param ---
sed -i '146s/screen)/screen!)/' "${OUTPUT}"
# --- initialize.display (line 141): add ! to screen param ---
sed -i '141s/screen)/screen!)/' "${OUTPUT}"
# --- move.cursor (lines 124-131): replace signature and body ---
# Line 124: add ! to terminal param
sed -i '124s/terminal,/terminal!,/' "${OUTPUT}"
# Lines 126-131: delete old body (DATA.ITEM/write.formatted), insert new
sed -i '126,131d' "${OUTPUT}"
# Insert new body at line 126 (after the comment on line 125)
sed -i '125a\
-- outputs ANSI escape sequence: ESC [ row ; col H\
SEQ\
terminal ! BYTE #1B\
terminal ! '"'"'['"'"'\
write.small.int(terminal, y + 1)\
terminal ! '"'"';'"'"'\
write.small.int(terminal, x + 1)\
terminal ! '"'"'H'"'"'' "${OUTPUT}"
# --- clear.screen (lines 119-121): replace implementation ---
# Line 119: add ! to terminal param
sed -i '119s/terminal)/terminal!)/' "${OUTPUT}"
# Line 120: replace comment (add explicit chars description)
sed -i '120s/-- clear screen sequence for an ANSI terminal/-- clear screen sequence for an ANSI terminal: ESC [ 2 J/' "${OUTPUT}"
# Line 121: delete old one-liner body
sed -i '121d' "${OUTPUT}"
# Insert new multi-line body after line 120
sed -i '120a\
SEQ\
terminal ! BYTE #1B\
terminal ! '"'"'['"'"'\
terminal ! '"'"'2'"'"'\
terminal ! '"'"'J'"'"'' "${OUTPUT}"
# --- array dimensions (lines 8-9): replace ... with 20 ---
sed -i '8s/IS \.\.\. :/IS 20 :/' "${OUTPUT}"
sed -i '9s/IS \.\.\. :/IS 20 :/' "${OUTPUT}"
# --- Insert adaptation comment and helper procs after line 2 ---
sed -i '2a\
-- Adapted for occam2go: replaced book-library functions\
-- (write.string, write.formatted, DATA.ITEM) with inline\
-- definitions; added terminal.keyboard/terminal.screen declarations.\
--\
\
--\
-- helper procedures (replaces book standard library)\
--\
\
PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)\
SEQ i = 0 FOR SIZE s\
out ! s[i]\
:\
\
PROC write.small.int(CHAN OF BYTE out!, VAL INT n)\
-- outputs a small non-negative integer (0..999) as decimal digits\
IF\
n >= 100\
SEQ\
out ! BYTE ((n / 100) + (INT '"'"'0'"'"'))\
out ! BYTE (((n / 10) \\ 10) + (INT '"'"'0'"'"'))\
out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
n >= 10\
SEQ\
out ! BYTE ((n / 10) + (INT '"'"'0'"'"'))\
out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
TRUE\
out ! BYTE (n + (INT '"'"'0'"'"'))\
:' "${OUTPUT}"
# --- Insert copyright/attribution header at the very top ---
sed -i '1i\
-- Code copied from Programming in occam®2\
-- © Geraint Jones, Michael Goldsmith 1988, 2001.\
-- Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk\
--' "${OUTPUT}"
echo "Done. Output written to: ${OUTPUT}"
File created successfully at: /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh
chmod +x /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh
Now let me test it by running the script and comparing the output against the current file.
cp /home/david/projects/codeassociates/occam2go/historical-examples/life.occ /tmp/life-expected.occ && /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh 2>&1
Downloading original life.occ from book website... Applying modifications for occam2go... Done. Output written to: /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
diff /tmp/life-expected.occ /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
The output matches exactly. Now let me restore the original file and verify the script works from a clean state by removing the generated file and running again.
rm /home/david/projects/codeassociates/occam2go/historical-examples/life.occ && /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh 2>&1 && diff /tmp/life-expected.occ /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
Downloading original life.occ from book website... Applying modifications for occam2go... Done. Output written to: /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
Clean run produces identical output. Now let me also verify the transpiler still works with the generated file.
go build -o occam2go . && ./occam2go -o /tmp/life_test.go historical-examples/life.occ && go vet /tmp/life_test.go
Everything works. Now let me check what needs to go into the PR — the script should be added and life.occ should be removed from git tracking. Let me also check if life.occ is referenced anywhere else.
historical-examples/fetch-life.sh:25:OUTPUT="${SCRIPT_DIR}/life.occ"
historical-examples/fetch-life.sh:27:echo "Downloading original life.occ from book website..."Good — no other references. Let me also add life.occ to .gitignore so the generated file isn't accidentally committed.
test -f /home/david/projects/codeassociates/occam2go/historical-examples/.gitignore && cat /home/david/projects/codeassociates/occam2go/historical-examples/.gitignore; echo "---"; test -f /home/david/projects/codeassociates/occam2go/.gitignore && cat /home/david/projects/codeassociates/occam2go/.gitignore; echo "exit: $?"
--- # Binaries occam2go *.exe *.exe~ *.dll *.so *.dylib # Test binaries *.test # Output of go coverage *.out # Go workspace file go.work # Dependency directories vendor/ # IDE .idea/ .vscode/ *.swp *.swo # OS .DS_Store Thumbs.db # Generated Go files from examples examples/*.go # External repositories kroc/ exit: 0
1→# Binaries
2→occam2go
3→*.exe
4→*.exe~
5→*.dll
6→*.so
7→*.dylib
8→
9→# Test binaries
10→*.test
11→
12→# Output of go coverage
13→*.out
14→
15→# Go workspace file
16→go.work
17→
18→# Dependency directories
19→vendor/
20→
21→# IDE
22→.idea/
23→.vscode/
24→*.swp
25→*.swo
26→
27→# OS
28→.DS_Store
29→Thumbs.db
30→
31→# Generated Go files from examples
32→examples/*.go
33→
34→# External repositories
35→kroc/
36→# External repositories kroc/
# Generated occam files from fetch scripts historical-examples/life.occ # External repositories kroc/
The file /home/david/projects/codeassociates/occam2go/.gitignore has been updated successfully.
.gitignore | 3 + historical-examples/fetch-life.sh | 145 ++++++++++++ historical-examples/life.occ | 453 -------------------------------------- 3 files changed, 148 insertions(+), 453 deletions(-)
677b6ee Merge pull request #93 from codeassociates/docs/update-line-counts a2144f1 Update codebase line count statistics in README 0c15516 Merge pull request #92 from codeassociates/fix/life-display-and-alt-busywait-72 eb7faab Fix display buffering and PRI ALT busy-wait in life.occ (#72) 6272c72 Merge pull request #91 from codeassociates/feat/raw-terminal-keyboard-90
git commit -m "$(cat <<'EOF' Replace copyrighted life.occ with download-and-patch script (#94) Remove the in-repo copy of the book example program and replace it with a fetch-life.sh script that downloads the original from the book website and applies the necessary modifications via line-number-targeted sed edits. The script avoids reproducing copyrighted content by using small surgical substitutions (e.g. adding channel direction annotations) and indentation rather than replacing entire lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
3 files changed, 148 insertions(+), 453 deletions(-) create mode 100755 historical-examples/fetch-life.sh delete mode 100644 historical-examples/life.occ
remote: remote: Create a pull request for 'fix/replace-life-with-fetch-script-94' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/fix/replace-life-with-fetch-script-94 remote: To associate-3.github.com:codeassociates/occam2go.git * [new branch] fix/replace-life-with-fetch-script-94 -> fix/replace-life-with-fetch-script-94 branch 'fix/replace-life-with-fetch-script-94' set up to track 'origin/fix/replace-life-with-fetch-script-94'.
gh pr create --title "Replace copyrighted life.occ with download-and-patch script" --body "$(cat <<'EOF' ## Summary - Removes the in-repo copy of the copyrighted book example program (`historical-examples/life.occ`) - Adds `fetch-life.sh` script that downloads the original from the book website and applies modifications via surgical sed edits - Adds `historical-examples/life.occ` to `.gitignore` so the generated file isn't accidentally committed ## Approach The script uses line-number-targeted sed edits applied bottom-to-top so line numbers remain stable. Modifications that touch existing lines use minimal substitutions (e.g. `s/screen)/screen!)/`) rather than replacing entire lines, avoiding reproduction of copyrighted content. Only genuinely new code (helper procedures, rewritten `clear.screen`/`move.cursor` implementations) appears in full. ## Test plan - [x] Script downloads and patches successfully, producing output identical to the previous in-repo file - [x] Transpiled output passes `go vet` - [ ] Run `./historical-examples/fetch-life.sh` from a clean checkout and verify `life.occ` is created Closes #94 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"
PR created: https://github.com/codeassociates/occam2go/pull/95
Here's a summary of what was done:
historical-examples/life.occ from the repositoryhistorical-examples/fetch-life.sh — a script that downloads the original from the book website and applies modifications using surgical sed edits.gitignore to exclude the generated life.occThe key design choice per your feedback: the script avoids reproducing copyrighted lines by using minimal substitutions on specific line numbers. For example:
- Adding !/? channel direction annotations: s/screen)/screen!)/ — only the tiny changed fragment
- Wrapping in PROC life: inserts the header line, indents the existing range by 2 spaces, and substitutes terminal.keyboard → keyboard — never reproduces any original line
- Only genuinely new code (helper procs, rewritten clear.screen/move.cursor) appears in full
Great work thanks. I think it would be useful to add a readme file to the historical-examples directory explaining things for the user. You can reference the top level book web site which is at this url: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/
# Historical Examples This directory contains example programs from historical occam publications, adapted to build and run with occam2go. ## Life (Conway's Game of Life) From *Programming in occam 2* by Geraint Jones and Michael Goldsmith (1988). Book website: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/ The original source is copyrighted and not included in this repository. To obtain it, run the fetch script which downloads it from the book website and applies the modifications needed for occam2go: ```bash ./historical-examples/fetch-life.sh ``` This produces `historical-examples/life.occ`, which can then be transpiled and run: ```bash ./occam2go -o life.go historical-examples/life.occ go run life.go ``` ### Controls - **E** — enter editor mode (use arrow keys to move, space/asterisk to toggle cells, Q to exit editor) - **R** — run (free-running evolution) - **S** — stop - **Any other key** — single step - **Q** — quit
File created successfully at: /home/david/projects/codeassociates/occam2go/historical-examples/README.md
git add historical-examples/README.md && git commit -m "$(cat <<'EOF' Add README for historical-examples directory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )" && git push
1 file changed, 29 insertions(+) create mode 100644 historical-examples/README.md To associate-3.github.com:codeassociates/occam2go.git cf76024..7bb1257 fix/replace-life-with-fetch-script-94 -> fix/replace-life-with-fetch-script-94
Done — pushed to the existing PR. The README explains the provenance of the example, links to the book website, shows how to fetch and run it, and documents the keyboard controls.