Please try again to see if the game of life example mentioned in issue #72 will compile now. You may want to update the program's sourcce code to include the course module since it seems to reference that library but lacks the include directive for it.
Let me look at issue #72 and the game of life example to understand what's needed.
title: Investigate transpiler compatibility with example program from the Occam2 book state: OPEN author: dboreham labels: comments: 1 assignees: projects: milestone: number: 72 -- A code example from the book "Programming in Occam2" has been added to this repo here: ./historical-examples/life.occ Please analyze this program to determine why it doesn't parse and run with the current version of the transpiler.
/home/david/projects/code-associates/occam2go/kroc/modules/occade/examples/images/lifeno.png /home/david/projects/code-associates/occam2go/kroc/modules/occade/examples/images/lifeyes.png /home/david/projects/code-associates/occam2go/historical-examples/life.occ /home/david/projects/code-associates/occam2go/.git/refs/heads/fix/life-occ-issues-73-75-78-79 /home/david/projects/code-associates/occam2go/.git/logs/refs/heads/fix/life-occ-issues-73-75-78-79 /home/david/projects/code-associates/occam2go/.git/refs/remotes/origin/fix/life-occ-issues-73-75-78-79 /home/david/projects/code-associates/occam2go/.git/logs/refs/remotes/origin/fix/life-occ-issues-73-75-78-79
{
"file_path": "/home/david/projects/code-associates/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→
8→--
9→-- configuration constants
10→--
11→
12→VAL INT array.width IS 20 : -- number of cells across the board
13→VAL INT array.height IS 20 : -- number of cells down the board
14→
15→VAL INT radius IS 1 : -- of the `sphere of influence'
16→VAL INT diameter IS (2 * radius) + 1 :
17→VAL INT neighbours IS (diameter * diameter) - 1 :
18→
19→VAL INT number.of.cells IS array.height * array.width :
20→VAL INT number.of.links IS neighbours * number.of.cells :
21→
22→--
23→-- protocols
24→--
25→
26→PROTOCOL STATE IS BOOL :
27→
28→VAL BOOL alive IS TRUE :
29→VAL BOOL dead IS NOT alive :
30→
31→PROTOCOL COMMAND
32→ CASE
33→ set.state; BOOL
34→ evolve
35→ terminate
36→:
37→
38→PROTOCOL RESPONSE IS BOOL; BOOL :
39→--
40→-- cell processes
41→--
42→
43→PROC broadcast.present.state([][][]CHAN OF STATE link,
44→ VAL INT x, y, VAL BOOL state )
45→ PAR d = 0 FOR neighbours
46→ link[x][y][d] ! state
47→:
48→
49→PROC calculate.next.state([][][]CHAN OF STATE link,
50→ VAL []INT nx, ny,
51→ VAL BOOL state, BOOL next.state )
52→ INT count : -- number of living neighbours
53→ SEQ
54→ [neighbours]BOOL state.of.neighbour :
55→ SEQ
56→ PAR d = 0 FOR neighbours
57→ link[nx[d]][ny[d]][d] ? state.of.neighbour[d]
58→ count := 0
59→ SEQ d = 0 FOR neighbours
60→ IF
61→ state.of.neighbour[d] = alive
62→ count := count + 1
63→ state.of.neighbour[d] = dead
64→ SKIP
65→ IF
66→ count < 2 -- death from isolation
67→ next.state := dead
68→ count = 2 -- this cell is stable
69→ next.state := state
70→ count = 3 -- stable if alive, a birth if dead
71→ next.state := alive
72→ count > 3 -- death from overcrowding
73→ next.state := dead
74→:
75→
76→
77→
78→
79→
80→
81→
82→
83→
84→
85→
86→
87→
88→
89→
90→PROC cell([][][]CHAN OF STATE link,
91→ VAL INT x, y, VAL []INT nx, ny,
92→ CHAN OF COMMAND control,
93→ CHAN OF RESPONSE sense )
94→ BOOL state, not.finished :
95→ SEQ
96→ state := dead -- the whole board starts off dead
97→ not.finished := TRUE
98→ WHILE not.finished
99→ control ? CASE
100→
101→ set.state; state
102→ SKIP -- state has been set to the new value
103→
104→ evolve
105→ BOOL next.state :
106→ SEQ
107→ PAR
108→ broadcast.present.state(link, x, y, state)
109→ SEQ
110→ calculate.next.state(link, nx, ny,
111→ state, next.state )
112→ sense ! (state <> next.state); next.state
113→ state := next.state
114→
115→ terminate
116→ not.finished := FALSE
117→:
118→
119→--
120→-- terminal-dependent output routines
121→--
122→
123→PROC clear.screen(CHAN OF BYTE terminal)
124→ -- clear screen sequence for an ANSI terminal
125→ write.string(terminal, "*#1B[2J")
126→:
127→
128→PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
129→ -- left-handed co-ordinates, origin 0,0 at top left
130→ CHAN OF DATA.ITEM c :
131→ PAR
132→ write.formatted(terminal, "*#1B[%d;%dH", c)
133→ SEQ
134→ c ! data.int; y + 1
135→ c ! data.int; x + 1
136→:
137→
138→
139→
140→
141→--
142→-- display routines
143→--
144→
145→PROC initialize.display(CHAN OF BYTE screen)
146→ -- display an entirely dead board
147→ clear.screen(screen)
148→:
149→
150→PROC clean.up.display(CHAN OF BYTE screen)
151→ move.cursor(screen, 0, array.height)
152→:
153→
154→PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state)
155→ SEQ
156→ move.cursor(screen, x, y)
157→ IF
158→ state = alive
159→ screen ! '**'
160→ state = dead
161→ screen ! '*s'
162→:
163→
164→--
165→-- controller states
166→--
167→
168→VAL INT idle IS 0 : -- controller activity values
169→VAL INT editing IS 1 :
170→VAL INT single.step IS 2 :
171→VAL INT free.running IS 3 :
172→VAL INT terminated IS 4 :
173→
174→
175→INT FUNCTION new.activity(VAL BYTE char)
176→ INT activity :
177→ VALOF
178→ CASE char -- typed on the keyboard ...
179→ 'q', 'Q' -- ... Q to finish program
180→ activity := terminated
181→ 's', 'S' -- ... S to halt evolution
182→ activity := idle
183→ 'e', 'E' -- ... E to start editing
184→ activity := editing
185→ 'r', 'R' -- ... R to start evolution
186→ activity := free.running
187→ ELSE -- ... or anything else for one generation
188→ activity := single.step
189→ RESULT activity
190→:
191→
192→PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
193→ SEQ
194→ move.cursor(screen, array.width+1, array.height/2)
195→ CASE activity
196→ idle
197→ write.string(screen, "Idle")
198→ editing
199→ write.string(screen, "Edit")
200→ single.step
201→ write.string(screen, "Step")
202→ free.running
203→ write.string(screen, "Busy")
204→ terminated
205→ write.string(screen, "Done")
206→:
207→
208→--
209→-- generation
210→--
211→
212→PROC generation(CHAN OF BYTE screen,
213→ [][]CHAN OF COMMAND control,
214→ [][]CHAN OF RESPONSE sense,
215→ BOOL active )
216→ SEQ
217→ PAR x = 0 FOR array.width
218→ PAR y = 0 FOR array.height
219→ control[x][y] ! evolve
220→ active := FALSE
221→ SEQ x = 0 FOR array.width
222→ SEQ y = 0 FOR array.height
223→ BOOL changed, next.state :
224→ SEQ
225→ sense[x][y] ? changed; next.state
226→ IF
227→ changed
228→ SEQ
229→ display.state(screen, x, y, next.state)
230→ active := TRUE
231→ NOT changed
232→ SKIP
233→:
234→
235→
236→
237→
238→
239→
240→
241→
242→
243→--
244→-- editor
245→--
246→
247→INT FUNCTION min(VAL INT a, b)
248→ INT min :
249→ VALOF
250→ IF
251→ a <= b
252→ min := a
253→ b <= a
254→ min := b
255→ RESULT min
256→:
257→
258→INT FUNCTION max(VAL INT a, b)
259→ INT max :
260→ VALOF
261→ IF
262→ a >= b
263→ max := a
264→ b >= a
265→ max := b
266→ RESULT max
267→:
268→
269→
270→
271→
272→
273→
274→
275→
276→
277→
278→
279→
280→
281→
282→
283→
284→
285→
286→
287→
288→
289→
290→
291→
292→
293→
294→PROC editor(CHAN OF BYTE keyboard, screen,
295→ [][]CHAN OF COMMAND control )
296→ INT x, y :
297→ BOOL editing :
298→ SEQ
299→ -- initialize co-ordinates to centre of board
300→ x, y := array.width / 2, array.height / 2
301→ editing := TRUE
302→ WHILE editing
303→ BYTE char :
304→ SEQ
305→ move.cursor(screen, x, y)
306→ keyboard ? char
307→ CASE char
308→ 'A' -- move up, if possible
309→ y := max(y - 1, 0)
310→ 'B' -- move down, if possible
311→ y := min(y + 1, array.height - 1)
312→ 'C' -- move right, if possible
313→ x := min(x + 1, array.width - 1)
314→ 'D' -- move left, if possible
315→ x := max(x - 1, 0)
316→ '*s', '**'
317→ VAL BOOL state IS (char = '**') = alive :
318→ PAR
319→ control[x][y] ! set.state; state
320→ display.state(screen, x, y, state)
321→ 'q', 'Q'
322→ editing := FALSE
323→ ELSE
324→ SKIP -- ignore anything else
325→:
326→
327→
328→
329→
330→
331→
332→
333→
334→
335→
336→
337→
338→
339→
340→
341→
342→
343→
344→
345→--
346→-- controller
347→--
348→
349→PROC controller(CHAN OF BYTE keyboard, screen,
350→ [][]CHAN OF COMMAND control,
351→ [][]CHAN OF RESPONSE sense )
352→ INT activity :
353→ SEQ
354→ activity := idle
355→ initialize.display(screen)
356→ WHILE activity <> terminated
357→ SEQ
358→ display.activity(screen, activity)
359→ BYTE char :
360→ PRI ALT
361→ (activity <> editing) & keyboard ? char
362→ activity := new.activity(char)
363→ (activity <> idle) & SKIP
364→ CASE activity
365→ editing
366→ SEQ
367→ editor(keyboard, screen, control)
368→ activity := idle
369→ free.running, single.step
370→ BOOL changing :
371→ SEQ
372→ generation(screen, control, sense, changing)
373→ IF
374→ (activity = single.step) OR (NOT changing)
375→ activity := idle
376→ (activity = free.running) AND changing
377→ SKIP
378→ display.activity(screen, activity)
379→ PAR x = 0 FOR array.width
380→ PAR y = 0 FOR array.height
381→ control[x][y] ! terminate
382→ clean.up.display(screen)
383→:
384→
385→
386→
387→
388→
389→
390→
391→
392→
393→
394→
395→
396→--
397→-- structure of the program
398→--
399→
400→[array.width][array.height][neighbours]CHAN OF STATE link :
401→[array.width][array.height]CHAN OF COMMAND control :
402→[array.width][array.height]CHAN OF RESPONSE sense :
403→PAR
404→ controller(terminal.keyboard, terminal.screen, control, sense)
405→ PAR x = 0 FOR array.width
406→ PAR y = 0 FOR array.height
407→ VAL INT left IS ((x - 1) + array.width) \ array.width :
408→ VAL INT right IS (x + 1) \ array.width :
409→ VAL INT up IS (y + 1) \ array.height :
410→ VAL INT down IS ((y - 1) + array.height) \ array.height :
411→ VAL [neighbours]INT nx IS
412→ [ right, x, left, left, left, x, right, right ] :
413→ VAL [neighbours]INT ny IS
414→ [ down, down, down, y, up, up, up, y ] :
415→ cell(link, x, y, nx, ny, control[x][y], sense[x][y])
416→
417→This is a Game of Life program from the occam2 book. Let me analyze what it needs:
write.string, write.formatted, data.int, data.int, terminal.keyboard, terminal.screen - these are from the course module/libraryCHAN OF BYTE, CHAN OF DATA.ITEM - DATA.ITEM is from the course module#INCLUDE directives for the course moduleLet me first try to transpile it as-is to see what errors we get, then figure out what include directives it needs.
Now I'm checking what course module procedures and protocols are actually being used here - write.string, write.formatted, the data.int protocol variant, and the terminal.keyboard and terminal.screen channels all come from the course module, so I need to find the right include file to reference them.
Let me first try transpiling it as-is, then investigate what's needed from the course module.
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
Exit code 1 # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:124:2: undefined: write_string # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:124:2: undefined: write_string
As expected, it transpiles but the Go output references write_string, write_formatted, data_int, terminal_keyboard, and terminal_screen which come from the course library. Let me check what course module include files are available.
/home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/shared_screen.inc /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/consts.inc
/home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/float_io.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/file_in.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/stringbuf.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/random.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/utils.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/demo_cycles.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/shared_screen.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/string.occ /home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/demo_nets.occ
{
"file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/consts.inc"
} 1→--
2→-- Constants
3→-- Copyright (C) 1984 P.H. Welch
4→--
5→-- This library is free software; you can redistribute it and/or
6→-- modify it under the terms of the GNU Lesser General Public
7→-- License as published by the Free Software Foundation; either
8→-- version 2 of the License, or (at your option) any later version.
9→--
10→-- This library is distributed in the hope that it will be useful,
11→-- but WITHOUT ANY WARRANTY; without even the implied warranty of
12→-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13→-- Lesser General Public License for more details.
14→--
15→-- You should have received a copy of the GNU Lesser General Public
16→-- License along with this library; if not, write to the Free Software
17→-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18→--
19→
20→--** Course library.
21→--
22→-- The course library provides various simple utilities for occam programmers.
23→--
24→-- Strings (as handled by this library) are [@code BYTE] arrays left-justified
25→-- with trailing [@ref NULL]s. It is usually preferably to use mobile byte
26→-- arrays ([@code MOBILE []BYTE]) to represent strings in modern occam-pi
27→-- programs; the string features in this library exist for backwards
28→-- compatibility.
29→--
30→-- To use this library:
31→--
32→-- [@code
33→-- #INCLUDE "course.module"]
34→--
35→-- @module course
36→
37→--*{{{ ASCII Character values.
38→VAL BYTE NULL IS 0: --* ASCII NUL
39→VAL BYTE BELL IS 7: --* ASCII BEL - terminal bell
40→VAL BYTE BACK IS 8: --* ASCII BS - backspace key
41→VAL BYTE ESCAPE IS 27: --* ASCII ESC - escape key
42→VAL BYTE DELETE IS 127: --* ASCII DEL - delete key
43→--*}}}
44→
45→--{{{ special constants
46→VAL BYTE FLUSH IS 255: --* Flush output buffer
47→VAL BYTE END.OF.FILE IS 255: --* End of file
48→--}}}
49→
50→--*{{{ COMPARE.STRING Return values from [@ref compare.string].
51→VAL INT string.less IS -1: --* Left less than right
52→VAL INT string.same IS 0: --* Left and right the same
53→VAL INT string.more IS 1: --* Left more than right
54→--*}}}
55→
56→{
"file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/libsrc/utils.occ"
} 1→--
2→-- Utilities
3→-- Copyright (C) 1997 P.H. Welch
4→--
5→-- This library is free software; you can redistribute it and/or
6→-- modify it under the terms of the GNU Lesser General Public
7→-- License as published by the Free Software Foundation; either
8→-- version 2 of the License, or (at your option) any later version.
9→--
10→-- This library is distributed in the hope that it will be useful,
11→-- but WITHOUT ANY WARRANTY; without even the implied warranty of
12→-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13→-- Lesser General Public License for more details.
14→--
15→-- You should have received a copy of the GNU Lesser General Public
16→-- License along with this library; if not, write to the Free Software
17→-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18→--
19→
20→--** @module course
21→
22→#COMMENT "Copyright: P.H.Welch, 1997"
23→
24→#INCLUDE "consts.inc"
25→
26→--{{{ out stuff
27→--{{{ PROC out.repeat (VAL BYTE ch, VAL INT n, CHAN BYTE out!)
28→--* Write a character repeatedly to a channel.
29→-- This outputs [@code ch] down the channel [@code out] [@code n] times. If
30→-- [@code n] is negative, nothing happens.
31→-- @param ch Character
32→-- @param n Number of times to output (negative values result in no output)
33→-- @param out Channel to write to
34→PROC out.repeat (VAL BYTE ch, VAL INT n, CHAN BYTE out!)
35→ --{{{
36→ IF
37→ n > 0
38→ SEQ i = 0 FOR n
39→ out ! ch
40→ TRUE
41→ SKIP
42→ --}}}
43→:
44→--}}}
45→--{{{ PROC out.ch (VAL BYTE ch, VAL INT field, CHAN BYTE out!)
46→--* Write a character to a channel.
47→-- This outputs [@code ch] in a fieldwidth [@code field] down [@code out]. If
48→-- the fieldwidth is too wide for [@code ch], it right-justifies [@code ch]
49→-- with spaces on the left. If the field is not wide enough, it prints the
50→-- [@code ch] anyway. These rules for fieldwidth are the same as those used by
51→-- the Pascal [@text write] procedure.
52→-- @param ch Character
53→-- @param field Field width to right-justify in
54→-- @param out Channel to write to
55→PROC out.ch (VAL BYTE ch, VAL INT field, CHAN BYTE out!)
56→ --{{{
57→ SEQ
58→ out.repeat (' ', field - 1, out!)
59→ out ! ch
60→ --}}}
61→:
62→--}}}
63→--{{{ PROC out.string (VAL []BYTE s, VAL INT field, CHAN BYTE out!)
64→--* Write a string to a channel.
65→-- This outputs [@code s] in a fieldwidth [@code field] down [@code out].
66→-- @param s String
67→-- @param field Field width to right-justify in
68→-- @param out Channel to write to
69→PROC out.string (VAL []BYTE s, VAL INT field, CHAN BYTE out!)
70→ --{{{
71→ VAL INT length IS SIZE s:
72→ SEQ
73→ out.repeat (' ', field - length, out!)
74→ SEQ i = 0 FOR length
75→ out ! s[i]
76→ --}}}
77→:
78→--}}}
79→--{{{ PROC out.byte (VAL BYTE b, VAL INT field, CHAN BYTE out!)
80→--* Write a byte in decimal to a channel.
81→-- This outputs [@code b] in a fieldwidth [@code field] down [@code out]. If
82→-- the fieldwidth is too wide for [@code b], it right-justifies [@code b] with
83→-- spaces on the left. If the field is not wide enough, it prints the [@code
84→-- b] anyway. These rules for fieldwidth are the same as those used by the
85→-- Pascal [@text write] procedure.
86→-- @param b Byte
87→-- @param field Field width to right-justify in
88→-- @param out Channel to write to
89→PROC out.byte (VAL BYTE b, VAL INT field, CHAN BYTE out!)
90→ --{{{
91→ VAL BYTE hundreds IS b/100:
92→ VAL BYTE rest IS b\100:
93→ VAL BYTE tens IS rest/10:
94→ VAL BYTE ones IS rest\10:
95→ IF
96→ hundreds > 0
97→ SEQ
98→ out.repeat (' ', field - 3, out!)
99→ out ! hundreds + '0'
100→ out ! tens + '0'
101→ out ! ones + '0'
102→ tens > 0
103→ SEQ
104→ out.repeat (' ', field - 2, out!)
105→ out ! tens + '0'
106→ out ! ones + '0'
107→ TRUE
108→ SEQ
109→ out.repeat (' ', field - 1, out!)
110→ out ! ones + '0'
111→ --}}}
112→:
113→--}}}
114→--{{{ PROC out.int (VAL INT n, VAL INT field, CHAN BYTE out!)
115→--* Write an integer in decimal to a channel.
116→-- This outputs [@code n] in a fieldwidth [@code field] down [@code out]. The
117→-- rules for fieldwidth are as [@ref out.byte].
118→-- @param n Integer
119→-- @param field Field width to right-justify in
120→-- @param out Channel to write to
121→PROC out.int (VAL INT n, VAL INT field, CHAN BYTE out!)
122→ --{{{
123→ IF
124→ n = (MOSTNEG INT)
125→ --{{{ minint
126→ out.string ("-2147483648", field, out!)
127→ --}}}
128→ n = 0
129→ --{{{ zero
130→ SEQ
131→ IF
132→ 1 < field
133→ out.repeat (' ', field - 1, out!)
134→ TRUE
135→ SKIP
136→ out ! '0'
137→ --}}}
138→ TRUE
139→ --{{{ anything else
140→ VAL INT max.digits IS 20:
141→ [max.digits]INT D:
142→ INT x, i:
143→ SEQ
144→ --{{{ check negative
145→ IF
146→ n < 0
147→ x := -n
148→ TRUE -- (n > 0)
149→ x := n
150→ --}}}
151→ --{{{ decompose
152→ SEQ
153→ i := 0
154→ WHILE x > 0
155→ SEQ
156→ D[i] := x\10
157→ x := x/10
158→ i := i + 1
159→ --}}}
160→ --{{{ pad
161→ IF
162→ n > 0
163→ out.repeat (' ', field - i, out!)
164→ TRUE
165→ SEQ
166→ out.repeat (' ', (field - 1) - i, out!)
167→ out ! '-'
168→ --}}}
169→ --{{{ output
170→ #PRAGMA DEFINED D
171→ WHILE i > 0
172→ SEQ
173→ i := i - 1
174→ out ! BYTE (D[i] + (INT '0'))
175→ --}}}
176→ --}}}
177→ --}}}
178→:
179→--}}}
180→--{{{ PROC out.hex (VAL INT n, VAL INT field, CHAN BYTE out!)
181→--* Write an integer in hexadecimal to a channel.
182→-- Aside from the output base, this behaves like [@ref out.int].
183→-- @param n Integer
184→-- @param field Field width to right-justify in
185→-- @param out Channel to write to
186→PROC out.hex (VAL INT n, VAL INT field, CHAN BYTE out!)
187→ IF
188→ n = (MOSTNEG INT)
189→ out.string ("#80000000", field, out!)
190→ n = 0
191→ out.string ("#00000000", field, out!)
192→ TRUE
193→ --{{{ anything else
194→ VAL []BYTE cmap IS "0123456789ABCDEF":
195→ INT x, i:
196→ SEQ
197→ x := n
198→ IF
199→ field = 0
200→ i := 8
201→ TRUE
202→ i := (field - 1)
203→ IF
204→ i > 0
205→ SEQ
206→ out ! '#'
207→ WHILE i > 0
208→ SEQ
209→ out ! cmap[((x >> ((i - 1) << 2)) /\ #0000000F)]
210→ i := i - 1
211→ TRUE
212→ SKIP
213→ --}}}
214→:
215→--}}}
216→--{{{ PROC out.bool (VAL BOOL b, VAL INT field, CHAN BYTE out!)
217→--* Write a Boolean value to a channel (as [@text TRUE]/[@text FALSE]).
218→-- This outputs [@code b] as the string [@text TRUE] or [@text FALSE] in a
219→-- fieldwidth [@code field] down [@code out].
220→-- @param b Boolean
221→-- @param field Field width to right-justify in
222→-- @param out Channel to write to
223→PROC out.bool (VAL BOOL b, VAL INT field, CHAN BYTE out!)
224→ --{{{
225→ IF
226→ b
227→ out.string ("TRUE", field, out!)
228→ TRUE
229→ out.string ("FALSE", field, out!)
230→ --}}}
231→:
232→--}}}
233→--{{{ PROC out.yes.no (VAL BOOL b, VAL INT field, CHAN BYTE out!)
234→--* Write a Boolean value to a channel (as [@text yes]/[@text no]).
235→-- This outputs [@code b] as the string [@text yes] or [@text no] in a
236→-- fieldwidth [@code field] down [@code out].
237→-- @param b Boolean
238→-- @param field Field width to right-justify in
239→-- @param out Channel to write to
240→PROC out.yes.no (VAL BOOL b, VAL INT field, CHAN BYTE out!)
241→ --{{{
242→ IF
243→ b
244→ out.string ("yes", field, out!)
245→ TRUE
246→ out.string ("no", field, out!)
247→ --}}}
248→:
249→--}}}
250→--{{{ PROC flush (CHAN BYTE out!)
251→--* Flush an output channel.
252→-- If [@code out] is (ultimately) connected to the external [@text stdout] or
253→-- [@text stderr] channel, this forces UNIX to flush all outstanding output to
254→-- that file descriptor. UNIX normally buffers this character output on a
255→-- line-by-line basis.
256→-- @param out Channel to write to
257→PROC flush (CHAN BYTE out!)
258→ --{{{
259→ out ! FLUSH
260→ --}}}
261→:
262→--}}}
263→--}}}
264→--{{{ in stuff
265→--{{{ PROC in.skip (BYTE ch, CHAN BYTE in?)
266→--* Discard whitespace.
267→-- This inputs from [@code in] until a non-space character is found. The
268→-- non-space character is returned in [@code ch].
269→-- @param ch The non-whitespace character
270→-- @param in Channel to read from
271→PROC in.skip (BYTE ch, CHAN BYTE in?)
272→ --{{{
273→ SEQ
274→ ch := ' '
275→ WHILE ch = ' '
276→ in ? ch
277→ --}}}
278→:
279→--}}}
280→--{{{ PROC in.digit (BYTE d, CHAN BYTE in?, out!)
281→--* Read a digit.
282→-- This inputs a digit from [@code in] into [@code d]. Non-digit characters
283→-- are ignored and the [@code out] channel is beeped. The accepted digit is
284→-- not echoed down [@code out].
285→-- @param d The digit read
286→-- @param in Channel to read from
287→-- @param out Channel to write to
288→PROC in.digit (BYTE d, CHAN BYTE in?, out!)
289→ --{{{
290→ SEQ
291→ in ? d
292→ WHILE (d < '0') OR ('9' < d)
293→ SEQ
294→ out ! BELL
295→ out ! FLUSH
296→ in ? d
297→ --}}}
298→:
299→--}}}
300→--{{{ PROC in.string ([]BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!)
301→--* Read a string.
302→-- This inputs a string of up to [@code max] characters from [@code in] into
303→-- [@code s]. Input is terminated by a carriage-return or new-line (which is
304→-- not included in [@code s]. The number of characters actually input is
305→-- recorded in [@code length]. The string [@code s] is padded on the right
306→-- with [@ref NULL]s if necessary. All characters are echoed on [@code out].
307→-- The backspace ([@ref BACK]) and delete ([@ref DELETE]) characters are
308→-- processed sensibly.
309→-- @param s Buffer to read into
310→-- @param length Number of bytes read
311→-- @param max Maximum number of bytes to read
312→-- @param in Channel to read from
313→-- @param out Channel to write to
314→PROC in.string ([]BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!)
315→ --{{{ interactive input of s (maximum of max characters)
316→ INT cut.max:
317→ BYTE ch:
318→ SEQ
319→ --{{{ initialise
320→ SEQ
321→ IF
322→ max > (SIZE s)
323→ cut.max := SIZE s
324→ TRUE
325→ cut.max := max
326→ length := 0
327→ in ? ch
328→ --}}}
329→ --{{{ main cycle
330→ WHILE (ch <> '*c') AND (ch <> '*n')
331→ SEQ
332→ --{{{ process ch
333→ IF
334→ (ch = BACK) OR (ch = DELETE)
335→ --{{{
336→ IF
337→ length > 0
338→ SEQ
339→ length := length - 1
340→ out ! BACK
341→ out ! ' '
342→ out ! BACK
343→ out ! FLUSH
344→ length = 0
345→ SEQ
346→ out ! BELL
347→ out ! FLUSH
348→ --}}}
349→ length = cut.max
350→ --{{{
351→ SEQ
352→ out ! BELL
353→ out ! FLUSH
354→ --}}}
355→ (' ' <= ch) AND (ch <= '~')
356→ --{{{
357→ SEQ
358→ s[length] := ch
359→ length := length + 1
360→ out ! ch
361→ out ! FLUSH
362→ --}}}
363→ TRUE
364→ --{{{
365→ SEQ
366→ out ! BELL
367→ out ! FLUSH
368→ --}}}
369→ --}}}
370→ in ? ch
371→ --}}}
372→ --{{{ finalisation
373→ SEQ i = length FOR (SIZE s) - length
374→ s[i] := NULL
375→ --}}}
376→ --}}}
377→:
378→--}}}
379→--{{{ PROC in.bool (BOOL b, CHAN BYTE in?, out!)
380→--* Read a boolean.
381→-- This reads a Boolean value as the characters [@text y] or [@text n]
382→-- (or [@text Y] or [@text N]).
383→-- Any other characters result in a [@ref BELL] being sent to [@code out].
384→-- The value read is echoed as [@text yes] or [@text no].
385→-- @param b The Boolean read
386→-- @param in Channel to read from
387→-- @param out Channel to write to
388→PROC in.bool (BOOL b, CHAN BYTE in?, out!)
389→ --{{{ interactive input of b (type 'y' or 'n')
390→ BYTE ch:
391→ SEQ
392→ in ? ch
393→ WHILE NOT ((ch = 'y') OR (ch = 'Y') OR (ch = 'n') OR (ch = 'N'))
394→ PAR
395→ SEQ
396→ out ! BELL
397→ out ! FLUSH
398→ in ? ch
399→ b := (ch = 'y') OR (ch = 'Y')
400→ IF
401→ b
402→ out.string ("yes", 0, out!)
403→ TRUE
404→ out.string ("no", 0, out!)
405→ out ! FLUSH
406→ --}}}
407→:
408→--}}}
409→--{{{ PROC in.byte.chars ([]BYTE s, INT length, CHAN BYTE in?, out!)
410→--* Read a byte value as a string.
411→-- This routine is used for inputting a BYTE value expressed as decimal digits.
412→-- It inputs a string of up to 3 digits from [@code in] into [@code s]. It
413→-- works just like [@ref in.string] except that only digits are accepted --
414→-- anything else is bleeped. Input is terminated by a carriage-return,
415→-- new-line or space (none of which characters are recorded). This is a
416→-- low-level routine used by [@ref in.byte].
417→-- @param s Buffer to read into
418→-- @param length Number of bytes read
419→-- @param in Channel to read from
420→-- @param out Channel to write to
421→-- @private
422→PROC in.byte.chars ([]BYTE s, INT length, CHAN BYTE in?, out!)
423→ --{{{
424→ VAL INT max IS 3:
425→ INT cut.max:
426→ BYTE ch:
427→ SEQ
428→ --{{{ cut.max := min (max, SIZE s)
429→ IF
430→ max > (SIZE s)
431→ cut.max := SIZE s
432→ TRUE
433→ cut.max := max
434→ length := 0
435→ --}}}
436→ in ? ch
437→ WHILE (ch <> '*c') AND (ch <> '*n') AND (ch <> ' ')
438→ SEQ
439→ --{{{ process ch
440→ IF
441→ (ch = BACK) OR (ch = DELETE)
442→ --{{{
443→ IF
444→ length > 0
445→ SEQ
446→ length := length - 1
447→ out ! BACK
448→ out ! ' '
449→ out ! BACK
450→ out ! FLUSH
451→ length = 0
452→ SEQ
453→ out ! BELL
454→ out ! FLUSH
455→ --}}}
456→ length = cut.max
457→ --{{{
458→ SEQ
459→ out ! BELL
460→ out ! FLUSH
461→ --}}}
462→ ('0' <= ch) AND (ch <= '9')
463→ --{{{
464→ SEQ
465→ s[length] := ch
466→ length := length + 1
467→ out ! ch
468→ out ! FLUSH
469→ --}}}
470→ TRUE
471→ --{{{
472→ SEQ
473→ out ! BELL
474→ out ! FLUSH
475→ --}}}
476→ --}}}
477→ in ? ch
478→ --}}}
479→:
480→--}}}
481→--{{{ INT FUNCTION convert.byte.chars (VAL []BYTE s, VAL INT length)
482→--* Parse a byte value stored in decimal.
483→-- This assumes that [@code s] has only digits. It converts that string into
484→-- an integer and returns it. It does not guard against overflow.
485→-- This is a low-level routine used by [@ref in.byte].
486→-- @param s Buffer to read from
487→-- @param length Number of bytes in buffer
488→-- @return Value in buffer
489→-- @private
490→INT FUNCTION convert.byte.chars (VAL []BYTE s, VAL INT length)
491→ --{{{
492→ INT number:
493→ VALOF
494→ --{{{ compute number
495→ SEQ
496→ number := 0
497→ SEQ i = 0 FOR length
498→ number := (10*number) + ((INT s[i]) - (INT '0'))
499→ --}}}
500→ RESULT number
501→ --}}}
502→:
503→--}}}
504→--{{{ PROC in.byte (BYTE b, VAL INT max, CHAN BYTE in?, out!)
505→--* Read a byte value in decimal.
506→-- This routine is used for inputting a BYTE value expressed as decimal digits.
507→-- It inputs a string of up to [@code max] digits from [@code in] into
508→-- [@code s]. If [@code max] is more than 3, it is truncated to 3. Input is
509→-- terminated by a carriage-return, new-line or space. Input strings
510→-- converting to values greater than 255 are rejected.
511→-- @param b Value read
512→-- @param max Maximum number of bytes to read
513→-- @param in Channel to read from
514→-- @param out Channel to write to
515→PROC in.byte (BYTE b, VAL INT max, CHAN BYTE in?, out!)
516→ --{{{ interactive input of b (up to max digits)
517→ INT number:
518→ VAL INT max.byte.digits IS 3:
519→ [max.byte.digits]BYTE s:
520→ INT length, cut.max:
521→ SEQ
522→ --{{{ cut.max := min (max, max.byte.digits)
523→ IF
524→ max > max.byte.digits
525→ cut.max := max.byte.digits
526→ TRUE
527→ cut.max := max
528→ --}}}
529→ in.byte.chars ([s FOR cut.max], length, in?, out!)
530→ #PRAGMA DEFINED s
531→ number := convert.byte.chars ([s FOR cut.max], length)
532→ WHILE number > 255
533→ SEQ
534→ --{{{ erase bad byte
535→ SEQ
536→ SEQ i = 0 FOR length
537→ SEQ
538→ out ! BACK
539→ out ! ' '
540→ out ! BACK
541→ out ! BELL
542→ out ! FLUSH
543→ --}}}
544→ in.byte.chars ([s FOR cut.max], length, in?, out!)
545→ number := convert.byte.chars (s, length)
546→ b := BYTE number
547→ --}}}
548→:
549→--}}}
550→--{{{ PROC in.num.chars ([]BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!)
551→--* Read an integer value as a string.
552→-- This inputs a string of up to [@code max] characters from [@code in] into
553→-- [@code s]. The number input is recorded in [@code length]. It works just
554→-- like [@ref in.string] except that only digits and [@text +] or [@text -] are
555→-- accepted -- any non-numeric characters are bleeped. Input is terminated by
556→-- a carriage-return, new-line or space. This is a low-level routine used by
557→-- [@ref in.int].
558→-- @param s Buffer to read into
559→-- @param length Number of bytes read
560→-- @param max Maximum number of bytes to read
561→-- @param in Channel to read from
562→-- @param out Channel to write to
563→-- @private
564→PROC in.num.chars ([]BYTE s, INT length, VAL INT max, CHAN BYTE in?, out!)
565→ --{{{
566→ INT cut.max:
567→ BYTE ch:
568→ SEQ
569→ --{{{ initialise
570→ SEQ
571→ IF
572→ max > (SIZE s)
573→ cut.max := SIZE s
574→ TRUE
575→ cut.max := max
576→ length := 0
577→ in ? ch
578→ --}}}
579→ --{{{ main cycle
580→ WHILE (ch <> '*c') AND (ch <> '*n') AND (ch <> ' ')
581→ SEQ
582→ --{{{ process ch
583→ IF
584→ (ch = BACK) OR (ch = DELETE)
585→ --{{{
586→ IF
587→ length > 0
588→ SEQ
589→ length := length - 1
590→ out ! BACK
591→ out ! ' '
592→ out ! BACK
593→ out ! FLUSH
594→ length = 0
595→ SEQ
596→ out ! BELL
597→ out ! FLUSH
598→ --}}}
599→ length = cut.max
600→ --{{{
601→ SEQ
602→ out ! BELL
603→ out ! FLUSH
604→ --}}}
605→ (('0' <= ch) AND (ch <= '9')) OR (ch = '+') OR (ch = '-')
606→ --{{{
607→ SEQ
608→ s[length] := ch
609→ length := length + 1
610→ out ! ch
611→ out ! FLUSH
612→ --}}}
613→ TRUE
614→ --{{{
615→ SEQ
616→ out ! BELL
617→ out ! FLUSH
618→ --}}}
619→ --}}}
620→ in ? ch
621→ --}}}
622→ --}}}
623→:
624→--}}}
625→--{{{ BOOL FUNCTION check.num.chars (VAL []BYTE s, VAL INT length)
626→--* Check the format of an integer.
627→-- This returns whether the string [@code s] has the format of a legal integer.
628→-- It assumes [@code s] consists only of decimal digits or [@text +]/[@text -]
629→-- symbols (e.g. having been input using [@ref in.num.chars] above). This is a
630→-- low-level routine used by [@ref in.int].
631→-- @param s Buffer to read from
632→-- @param length Number of bytes in buffer
633→-- @return Whether the buffer represents a legal integer
634→-- @private
635→BOOL FUNCTION check.num.chars (VAL []BYTE s, VAL INT length)
636→ --{{{
637→ BOOL ok:
638→ VALOF
639→ --{{{ is it ok?
640→ IF
641→ length = 0
642→ ok := FALSE
643→ IF i = 1 FOR length - 1
644→ (s[i] = '+') OR (s[i] = '-')
645→ ok := FALSE
646→ length > 1
647→ ok := TRUE
648→ length = 1
649→ ok := ('0' <= s[0]) AND (s[0] <= '9')
650→ TRUE
651→ ok := FALSE
652→ --}}}
653→ RESULT ok
654→ --}}}
655→:
656→--}}}
657→--{{{ BOOL, INT FUNCTION convert.num.chars (VAL []BYTE s)
658→--* Parse an integer value stored in decimal.
659→-- This assumes that [@code s] has the format of a legal integer. It computes
660→-- that integer and returns it. It guards against overflow, returning
661→-- success in the BOOL result. This is a low-level routine used
662→-- by [@ref in.int].
663→-- @param s Buffer to read from
664→-- @return Whether the conversion was successful
665→-- @return Value in the buffer
666→-- @private
667→BOOL, INT FUNCTION convert.num.chars (VAL []BYTE s)
668→ --{{{
669→ BOOL ok:
670→ INT x:
671→ VALOF
672→ --{{{ convert to x
673→ VAL INT length IS SIZE s:
674→ INT start, n.digits:
675→ BOOL positive:
676→ SEQ
677→ --{{{ check for sign
678→ IF
679→ s[0] = '+'
680→ --{{{
681→ SEQ
682→ positive := TRUE
683→ start := 1
684→ n.digits := length - 1
685→ --}}}
686→ s[0] = '-'
687→ --{{{
688→ SEQ
689→ positive := FALSE
690→ start := 1
691→ n.digits := length - 1
692→ --}}}
693→ TRUE
694→ --{{{
695→ SEQ
696→ positive := TRUE
697→ start := 0
698→ n.digits := length
699→ --}}}
700→ --}}}
701→ --{{{ compute number
702→ SEQ
703→ x := 0
704→ ok := TRUE
705→ SEQ i = start FOR n.digits
706→ SEQ
707→ x := (10 TIMES x) PLUS ((INT s[i]) - (INT '0'))
708→ ok := ok AND (x >= 0)
709→ --}}}
710→ --{{{ set the sign
711→ IF
712→ positive
713→ SKIP
714→ x = (MOSTNEG INT)
715→ ok := TRUE
716→ TRUE
717→ x := MINUS x
718→ --}}}
719→ --}}}
720→ RESULT ok, x
721→ --}}}
722→:
723→--}}}
724→--{{{ PROC in.int (INT n, VAL INT max, CHAN BYTE in?, out!)
725→--* Read an integer value in decimal.
726→-- This routine is used for inputting an INT value expressed as decimal digits.
727→-- It inputs a string of up to [@code max] characters from [@code in] into
728→-- [@code s]. If [@code max] is more than 20, it is truncated to 20. The
729→-- first character may be [@text +] or [@text -]. Input is terminated by a
730→-- carriage-return, new-line or space. It does guard against overflow.
731→-- @param n Value read
732→-- @param max Maximum number of bytes to read
733→-- @param in Channel to read from
734→-- @param out Channel to write to
735→PROC in.int (INT n, VAL INT max, CHAN BYTE in?, out!)
736→ --{{{ interactive input of n (up to max digits)
737→ VAL INT max.num.digits IS 20:
738→ [max.num.digits]BYTE s:
739→ INT length, cut.max:
740→ BOOL ok:
741→ SEQ
742→ --{{{ cut.max := min (max, max.num.digits)
743→ IF
744→ max > max.num.digits
745→ cut.max := max.num.digits
746→ TRUE
747→ cut.max := max
748→ --}}}
749→ in.num.chars (s, length, cut.max, in?, out!)
750→ ok := FALSE
751→ #PRAGMA DEFINED s
752→ WHILE NOT ok
753→ SEQ
754→ WHILE NOT check.num.chars (s, length)
755→ SEQ
756→ --{{{ erase bad number
757→ SEQ
758→ SEQ i = 0 FOR length
759→ SEQ
760→ out ! BACK
761→ out ! ' '
762→ out ! BACK
763→ out ! BELL
764→ out ! FLUSH
765→ --}}}
766→ in.num.chars (s, length, cut.max, in?, out!)
767→ ok, n := convert.num.chars ([s FOR length])
768→ --{{{ fix if bad
769→ IF
770→ ok
771→ SKIP
772→ TRUE
773→ s[1] := '+'
774→ --}}}
775→ --}}}
776→:
777→--}}}
778→--{{{ PROC black.hole (CHAN BYTE in?)
779→--* Discard characters forever.
780→-- This inputs characters from [@code in] forever. It is used to mask off
781→-- unwanted [@code BYTE] streams.
782→-- @param in Channel to read from
783→PROC black.hole (CHAN BYTE in?)
784→ --{{{
785→ WHILE TRUE
786→ BYTE any:
787→ in ? any
788→ --}}}
789→:
790→--}}}
791→--}}}
792→--{{{ ask stuff
793→--{{{ PROC ask.string (VAL []BYTE prompt, []BYTE s, INT length,
794→--* Read a string with a prompt.
795→-- This prints a prompt, calls [@ref in.string], then prints a newline.
796→-- @param prompt Prompt to print
797→-- @param s Buffer to read into
798→-- @param length Number of bytes read
799→-- @param max Maximum number of bytes to read
800→-- @param in Channel to read from
801→-- @param out Channel to write to
802→PROC ask.string (VAL []BYTE prompt, []BYTE s, INT length,
803→ VAL INT max, CHAN BYTE in?, out!)
804→ --{{{ interactive input of s using in & out (maximum of max characters)
805→ SEQ
806→ out.string (prompt, 0, out!)
807→ out ! FLUSH
808→ in.string (s, length, max, in?, out!)
809→ out ! '*c'
810→ out ! '*n'
811→ out ! FLUSH
812→ --}}}
813→:
814→--}}}
815→--{{{ PROC ask.bool (VAL []BYTE prompt, BOOL b, CHAN BYTE in?, out!)
816→--* Read a boolean with a prompt.
817→-- This prints a prompt, calls [@ref in.bool], then prints a newline.
818→-- @param prompt Prompt to print
819→-- @param b The Boolean read
820→-- @param in Channel to read from
821→-- @param out Channel to write to
822→PROC ask.bool (VAL []BYTE prompt, BOOL b, CHAN BYTE in?, out!)
823→ --{{{ interactive input of b using in & out (input 'y'/'Y' or 'n'/'N')
824→ SEQ
825→ out.string (prompt, 0, out!)
826→ out ! FLUSH
827→ in.bool (b, in?, out!)
828→ out ! '*c'
829→ out ! '*n'
830→ out ! FLUSH
831→ --}}}
832→:
833→--}}}
834→--{{{ PROC ask.byte (VAL []BYTE prompt, BYTE b, VAL INT max, CHAN BYTE in?, out!)
835→--* Read a byte value in decimal with a prompt.
836→-- This prints a prompt, calls [@ref in.byte], then prints a newline.
837→-- @param prompt Prompt to print
838→-- @param b Value read
839→-- @param max Maximum number of bytes to read
840→-- @param in Channel to read from
841→-- @param out Channel to write to
842→PROC ask.byte (VAL []BYTE prompt, BYTE b, VAL INT max, CHAN BYTE in?, out!)
843→ --{{{ interactive input of b using in & out (up to max digits)
844→ SEQ
845→ out.string (prompt, 0, out!)
846→ out ! FLUSH
847→ in.byte (b, max, in?, out!)
848→ out ! '*c'
849→ out ! '*n'
850→ --}}}
851→:
852→--}}}
853→--{{{ PROC ask.int (VAL []BYTE prompt, INT n, VAL INT max, CHAN BYTE in?, out!)
854→--* Read an integer value in decimal with a prompt.
855→-- This prints a prompt, calls [@ref in.int], then prints a newline.
856→-- @param prompt Prompt to print
857→-- @param n Value read
858→-- @param max Maximum number of bytes to read
859→-- @param in Channel to read from
860→-- @param out Channel to write to
861→PROC ask.int (VAL []BYTE prompt, INT n, VAL INT max, CHAN BYTE in?, out!)
862→ --{{{ interactive input of n using in & out (up to max digits)
863→ SEQ
864→ out.string (prompt, 0, out!)
865→ out ! FLUSH
866→ in.int (n, max, in?, out!)
867→ out ! '*c'
868→ out ! '*n'
869→ --}}}
870→:
871→--}}}
872→--}}}
873→--{{{ screen stuff (VT220 protocol)
874→--{{{ PROC cursor.x.y (VAL BYTE x, y, CHAN BYTE out!)
875→--* Place the cursor.
876→-- This outputs a VT220 control sequence down channel [@code out] to place the
877→-- cursor at screen coordinates ([@code x], [@code y]).
878→-- @param x X coordinate
879→-- @param y Y coordinate
880→-- @param out Channel to write to
881→PROC cursor.x.y (VAL BYTE x, y, CHAN BYTE out!)
882→ --{{{
883→ SEQ
884→ out ! ESCAPE
885→ out ! '['
886→ out.byte (y, 0, out!)
887→ out ! ';'
888→ out.byte (x, 0, out!)
889→ out ! 'H'
890→ --}}}
891→:
892→--}}}
893→--{{{ PROC cursor.up (VAL BYTE n, CHAN BYTE out!)
894→--* Move the cursor up.
895→-- This outputs a VT220 control sequence down channel [@code out] to move the
896→-- cursor up [@code n] positions.
897→-- @param n Number of positions to move
898→-- @param out Channel to write to
899→PROC cursor.up (VAL BYTE n, CHAN BYTE out!)
900→ --{{{
901→ SEQ
902→ out ! ESCAPE
903→ out ! '['
904→ out.byte (n, 0, out!)
905→ out ! 'A'
906→ --}}}
907→:
908→--}}}
909→--{{{ PROC cursor.down (VAL BYTE n, CHAN BYTE out!)
910→--* Move the cursor down.
911→-- This outputs a VT220 control sequence down channel [@code out] to move the
912→-- cursor down [@code n] positions.
913→-- @param n Number of positions to move
914→-- @param out Channel to write to
915→PROC cursor.down (VAL BYTE n, CHAN BYTE out!)
916→ --{{{
917→ SEQ
918→ out ! ESCAPE
919→ out ! '['
920→ out.byte (n, 0, out!)
921→ out ! 'B'
922→ --}}}
923→:
924→--}}}
925→--{{{ PROC cursor.right (VAL BYTE n, CHAN BYTE out!)
926→--* Move the cursor right.
927→-- This outputs a VT220 control sequence down channel [@code out] to move the
928→-- cursor right [@code n] positions.
929→-- @param n Number of positions to move
930→-- @param out Channel to write to
931→PROC cursor.right (VAL BYTE n, CHAN BYTE out!)
932→ --{{{
933→ SEQ
934→ out ! ESCAPE
935→ out ! '['
936→ out.byte (n, 0, out!)
937→ out ! 'C'
938→ --}}}
939→:
940→--}}}
941→--{{{ PROC cursor.left (VAL BYTE n, CHAN BYTE out!)
942→--* Move the cursor left.
943→-- This outputs a VT220 control sequence down channel [@code out] to move the
944→-- cursor left [@code n] positions.
945→-- @param n Number of positions to move
946→-- @param out Channel to write to
947→PROC cursor.left (VAL BYTE n, CHAN BYTE out!)
948→ --{{{
949→ SEQ
950→ out ! ESCAPE
951→ out ! '['
952→ out.byte (n, 0, out!)
953→ out ! 'D'
954→ --}}}
955→:
956→--}}}
957→--{{{ PROC erase.eol (CHAN BYTE out!)
958→--* Erase to end of line.
959→-- This outputs a VT220 control sequence to erase characters from the
960→-- current cursor position to the end of the screen line.
961→-- @param out Channel to write to
962→PROC erase.eol (CHAN BYTE out!)
963→ --{{{
964→ SEQ
965→ out ! ESCAPE
966→ out ! '['
967→ out ! '0'
968→ out ! 'K'
969→ --}}}
970→:
971→--}}}
972→--{{{ PROC erase.bol (CHAN BYTE out!)
973→--* Erase to beginning of line.
974→-- This outputs a VT220 control sequence to erase characters from the
975→-- current cursor position to the beginning of the screen line.
976→-- @param out Channel to write to
977→PROC erase.bol (CHAN BYTE out!)
978→ --{{{
979→ SEQ
980→ out ! ESCAPE
981→ out ! '['
982→ out ! '1'
983→ out ! 'K'
984→ --}}}
985→:
986→--}}}
987→--{{{ PROC erase.line (CHAN BYTE out!)
988→--* Erase whole line.
989→-- This outputs a VT220 control sequence to erase all characters on the
990→-- current screen line.
991→-- @param out Channel to write to
992→PROC erase.line (CHAN BYTE out!)
993→ --{{{
994→ SEQ
995→ out ! ESCAPE
996→ out ! '['
997→ out ! '2'
998→ out ! 'K'
999→ --}}}
1000→:
1001→--}}}
1002→--{{{ PROC erase.eos (CHAN BYTE out!)
1003→--* Erase to end of screen.
1004→-- This outputs a VT220 control sequence to erase characters from the
1005→-- current cursor position to the end of the screen.
1006→-- @param out Channel to write to
1007→PROC erase.eos (CHAN BYTE out!)
1008→ --{{{
1009→ SEQ
1010→ out ! ESCAPE
1011→ out ! '['
1012→ out ! '0'
1013→ out ! 'J'
1014→ --}}}
1015→:
1016→--}}}
1017→--{{{ PROC erase.bos (CHAN BYTE out!)
1018→--* Erase to beginning of screen.
1019→-- This outputs a VT220 control sequence to erase characters from the
1020→-- current cursor position backwards to the start of the screen.
1021→-- @param out Channel to write to
1022→PROC erase.bos (CHAN BYTE out!)
1023→ --{{{
1024→ SEQ
1025→ out ! ESCAPE
1026→ out ! '['
1027→ out ! '1'
1028→ out ! 'J'
1029→ --}}}
1030→:
1031→--}}}
1032→--{{{ PROC erase.screen (CHAN BYTE out!)
1033→--* Erase whole screen.
1034→-- This outputs a VT220 control sequence to erase all characters from the
1035→-- screen.
1036→-- @param out Channel to write to
1037→PROC erase.screen (CHAN BYTE out!)
1038→ --{{{
1039→ SEQ
1040→ out ! ESCAPE
1041→ out ! '['
1042→ out ! '2'
1043→ out ! 'J'
1044→ --}}}
1045→:
1046→--}}}
1047→--{{{ PROC cursor.visible (CHAN BYTE out!)
1048→--* Make cursor visible.
1049→-- This outputs a VT220 control sequence to make the cursor visible.
1050→-- @param out Channel to write to
1051→PROC cursor.visible (CHAN BYTE out!)
1052→ --{{{
1053→ SEQ
1054→ out ! ESCAPE
1055→ out ! '['
1056→ out ! '?'
1057→ out ! '2'
1058→ out ! '5'
1059→ out ! 'h'
1060→ --}}}
1061→:
1062→--}}}
1063→--{{{ PROC cursor.invisible (CHAN BYTE out!)
1064→--* Make cursor invisible.
1065→-- This outputs a VT220 control sequence to make the cursor invisible.
1066→-- @param out Channel to write to
1067→PROC cursor.invisible (CHAN BYTE out!)
1068→ --{{{
1069→ SEQ
1070→ out ! ESCAPE
1071→ out ! '['
1072→ out ! '?'
1073→ out ! '2'
1074→ out ! '5'
1075→ out ! 'l'
1076→ --}}}
1077→:
1078→--}}}
1079→--{{{ PROC pos.int (VAL INT col, row, n, field, CHAN BYTE out!)
1080→--* Move cursor and write an integer.
1081→-- This outputs a VT220 control sequence to place the cursor at screen
1082→-- coordinates ([@code col], [@code row]) and output [@code n] in a fieldwidth
1083→-- of [@code field].
1084→-- @param col X coordinate
1085→-- @param row Y coordinate
1086→-- @param n Integer
1087→-- @param field Field width to right-justify in
1088→-- @param out Channel to write to
1089→PROC pos.int (VAL INT col, row, n, field, CHAN BYTE out!)
1090→ --{{{
1091→ SEQ
1092→ cursor.x.y (BYTE col, BYTE row, out!)
1093→ out.int (n, field, out!)
1094→ --}}}
1095→:
1096→--}}}
1097→--{{{ PROC pos.yes.no (VAL INT col, row, VAL BOOL b,
1098→--* Move cursor and write a Boolean value (as yes/no).
1099→-- This outputs a VT220 control sequence to place the cursor at screen
1100→-- coordinates ([@code col], [@code row]) and output [@code b] in a fieldwidth
1101→-- of [@code field].
1102→-- @param col X coordinate
1103→-- @param row Y coordinate
1104→-- @param b Boolean
1105→-- @param field Field width to right-justify in
1106→-- @param out Channel to write to
1107→PROC pos.yes.no (VAL INT col, row, VAL BOOL b,
1108→ VAL INT field, CHAN BYTE out!)
1109→ --{{{
1110→ SEQ
1111→ cursor.x.y (BYTE col, BYTE row, out!)
1112→ out.yes.no (b, field, out!)
1113→ --}}}
1114→:
1115→--}}}
1116→--}}}
1117→
1118→
1119→--{{{ deprecated stuff
1120→--* Synonym for [@ref out.int].
1121→-- @deprecated
1122→PROC out.number (VAL INT n, w, CHAN BYTE o!)
1123→ out.int (n, w, o!)
1124→:
1125→--* Synonym for [@ref cursor.x.y].
1126→-- @deprecated
1127→PROC goto.x.y (VAL INT x, y, CHAN BYTE o!)
1128→ cursor.x.y (BYTE x, BYTE y, o!)
1129→:
1130→--}}}
1131→
1132→The life.occ program uses:
1. write.string(terminal, "...") - This doesn't match out.string from utils.occ. This is a different library function.
2. write.formatted(terminal, "...", c) - This is a formatted output function
3. CHAN OF DATA.ITEM c : - DATA.ITEM is a protocol used for formatted output
4. data.int - A variant tag of the DATA.ITEM protocol
5. terminal.keyboard / terminal.screen - These are channel names that would be provided by the runtime environment
These are from a different edition of the occam book/library - this is the original "Occam 2" book library, not the KRoC course module. The KRoC course module uses out.string, out.int, etc.
So the book's standard library provides write.string and write.formatted for output operations, along with the DATA.ITEM protocol for handling different data types, whereas the KRoC course module takes a different approach with its own output functions.
The core issue is that the program relies on functions and channels that don't exist in the available course module—write.string, write.formatted, terminal.keyboard, and terminal.screen are all specific to the "Programming in occam 2" book's environment. To get this compiling, I need to either create stub implementations for these missing pieces, rewrite the program to use the course module's actual functions like out.string, or add an include file that declares what's missing.
Looking at what the program actually uses, it's calling write.string and write.formatted with a terminal channel, but the course module doesn't provide these functions at all—it has different output primitives entirely. I'm realizing the program doesn't actually use the course module at all—it references functions from the "Programming in occam 2" book's standard library, which has different APIs than KRoC's course module. The best approach would be to add the course module include and then adapt the function calls to match what's actually available, like replacing write.string with out.string and handling the formatted output differently using out.int instead of the DATA.ITEM protocol.
Looking at the actual errors I'd get from the transpiler, the main issues are that write.string, write.formatted, and DATA.ITEM are all undefined, and the program expects terminal.keyboard and terminal.screen to be pre-declared channels from the runtime—but in KRoC these would need to come from wrapping the program in a PROC main that receives them as parameters.
The most practical fix would be to define these missing pieces directly in the life.occ file: implement a simple write.string procedure that outputs bytes, define the DATA.ITEM protocol and write.formatted for formatted output, and either add the terminal channels or restructure the program as a main process that receives them.
Looking at what the program actually needs from write.formatted, it's being used for cursor positioning with escape sequences, so I need to understand exactly what that procedure does before deciding whether to implement it fully or find a simpler workaround.
The key insight is that I could either implement the full write.formatted with DATA.ITEM protocol, or take a more pragmatic approach: rewrite move.cursor to construct and output the escape sequence directly using simpler primitives like out.int, and replace the predefined channel references with the standard course module channels. I'm realizing that using the course module's out.string and out.int would be the simplest path forward, since they handle the channel type conversions automatically and I can avoid defining everything from scratch.
1→package main
2→
3→import (
4→ "sync"
5→)
6→
7→type _proto_STATE = bool
8→
9→type _proto_COMMAND interface {
10→ _is_COMMAND()
11→}
12→
13→type _proto_COMMAND_set_state struct {
14→ _0 bool
15→}
16→func (_proto_COMMAND_set_state) _is_COMMAND() {}
17→
18→type _proto_COMMAND_evolve struct{}
19→func (_proto_COMMAND_evolve) _is_COMMAND() {}
20→
21→type _proto_COMMAND_terminate struct{}
22→func (_proto_COMMAND_terminate) _is_COMMAND() {}
23→
24→type _proto_RESPONSE struct {
25→ _0 bool
26→ _1 bool
27→}
28→
29→var array_width int = 20
30→var array_height int = 20
31→var radius int = 1
32→var diameter int = ((2 * radius) + 1)
33→var neighbours int = ((diameter * diameter) - 1)
34→var number_of_cells int = (array_height * array_width)
35→var number_of_links int = (neighbours * number_of_cells)
36→var alive bool = true
37→var dead bool = !alive
38→var idle int = 0
39→var editing int = 1
40→var single_step int = 2
41→var free_running int = 3
42→var terminated int = 4
43→
44→func broadcast_present_state(link [][][]chan _proto_STATE, x int, y int, state bool) {
45→ var wg sync.WaitGroup
46→ wg.Add(int(neighbours))
47→ for d := 0; d < 0 + neighbours; d++ {
48→ d := d
49→ go func() {
50→ defer wg.Done()
51→ link[x][y][d] <- state
52→ }()
53→ }
54→ wg.Wait()
55→}
56→
57→func calculate_next_state(link [][][]chan _proto_STATE, nx []int, ny []int, state bool, next_state *bool) {
58→ var count int
59→ _ = count
60→ state_of_neighbour := make([]bool, neighbours)
61→ var wg sync.WaitGroup
62→ wg.Add(int(neighbours))
63→ for d := 0; d < 0 + neighbours; d++ {
64→ d := d
65→ go func() {
66→ defer wg.Done()
67→ state_of_neighbour[d] = <-link[nx[d]][ny[d]][d]
68→ }()
69→ }
70→ wg.Wait()
71→ count = 0
72→ for d := 0; d < 0 + neighbours; d++ {
73→ if (state_of_neighbour[d] == alive) {
74→ count = (count + 1)
75→ } else if (state_of_neighbour[d] == dead) {
76→ // SKIP
77→ }
78→ }
79→ if (count < 2) {
80→ *next_state = dead
81→ } else if (count == 2) {
82→ *next_state = state
83→ } else if (count == 3) {
84→ *next_state = alive
85→ } else if (count > 3) {
86→ *next_state = dead
87→ }
88→}
89→
90→func cell(link [][][]chan _proto_STATE, x int, y int, nx []int, ny []int, control chan _proto_COMMAND, sense chan _proto_RESPONSE) {
91→ var state, not_finished bool
92→ _ = state
93→ _ = not_finished
94→ state = dead
95→ not_finished = true
96→ for not_finished {
97→ switch _v := (<-control).(type) {
98→ case _proto_COMMAND_set_state:
99→ state = _v._0
100→ // SKIP
101→ case _proto_COMMAND_evolve:
102→ var next_state bool
103→ _ = next_state
104→ var wg sync.WaitGroup
105→ wg.Add(2)
106→ go func() {
107→ defer wg.Done()
108→ broadcast_present_state(link, x, y, state)
109→ }()
110→ go func() {
111→ defer wg.Done()
112→ calculate_next_state(link, nx, ny, state, &next_state)
113→ sense <- _proto_RESPONSE{(state != next_state), next_state}
114→ }()
115→ wg.Wait()
116→ state = next_state
117→ case _proto_COMMAND_terminate:
118→ not_finished = false
119→ }
120→ }
121→}
122→
123→func clear_screen(terminal chan byte) {
124→ write_string(terminal, "*#1B[2J")
125→}
126→
127→func move_cursor(terminal chan byte, x int, y int) {
128→ c := make(chan DATA.ITEM)
129→ var wg sync.WaitGroup
130→ wg.Add(2)
131→ go func() {
132→ defer wg.Done()
133→ write_formatted(terminal, "*#1B[%d;%dH", c)
134→ }()
135→ go func() {
136→ defer wg.Done()
137→ c <- data_int
138→ c <- data_int
139→ }()
140→ wg.Wait()
141→}
142→
143→func initialize_display(screen chan byte) {
144→ clear_screen(screen)
145→}
146→
147→func clean_up_display(screen chan byte) {
148→ move_cursor(screen, 0, array_height)
149→}
150→
151→func display_state(screen chan byte, x int, y int, state bool) {
152→ move_cursor(screen, x, y)
153→ if (state == alive) {
154→ screen <- byte(42)
155→ } else if (state == dead) {
156→ screen <- byte(32)
157→ }
158→}
159→
160→func new_activity(char byte) int {
161→ var activity int
162→ _ = activity
163→ switch char {
164→ case byte(113), byte(81):
165→ activity = terminated
166→ case byte(115), byte(83):
167→ activity = idle
168→ case byte(101), byte(69):
169→ activity = editing
170→ case byte(114), byte(82):
171→ activity = free_running
172→ default:
173→ activity = single_step
174→ }
175→ return activity
176→}
177→
178→func display_activity(screen chan byte, activity int) {
179→ move_cursor(screen, (array_width + 1), (array_height / 2))
180→ switch activity {
181→ case idle:
182→ write_string(screen, "Idle")
183→ case editing:
184→ write_string(screen, "Edit")
185→ case single_step:
186→ write_string(screen, "Step")
187→ case free_running:
188→ write_string(screen, "Busy")
189→ case terminated:
190→ write_string(screen, "Done")
191→ }
192→}
193→
194→func generation(screen chan byte, control [][]chan _proto_COMMAND, sense [][]chan _proto_RESPONSE, active *bool) {
195→ var wg sync.WaitGroup
196→ wg.Add(int(array_width))
197→ for x := 0; x < 0 + array_width; x++ {
198→ x := x
199→ go func() {
200→ defer wg.Done()
201→ var wg sync.WaitGroup
202→ wg.Add(int(array_height))
203→ for y := 0; y < 0 + array_height; y++ {
204→ y := y
205→ go func() {
206→ defer wg.Done()
207→ control[x][y] <- _proto_COMMAND_evolve{}
208→ }()
209→ }
210→ wg.Wait()
211→ }()
212→ }
213→ wg.Wait()
214→ *active = false
215→ for x := 0; x < 0 + array_width; x++ {
216→ for y := 0; y < 0 + array_height; y++ {
217→ var changed, next_state bool
218→ _ = changed
219→ _ = next_state
220→ _tmp0 := <-sense[x][y]
221→ changed = _tmp0._0
222→ next_state = _tmp0._1
223→ if changed {
224→ display_state(screen, x, y, next_state)
225→ *active = true
226→ } else if !changed {
227→ // SKIP
228→ }
229→ }
230→ }
231→}
232→
233→func min(a int, b int) int {
234→ var min int
235→ _ = min
236→ if (a <= b) {
237→ min = a
238→ } else if (b <= a) {
239→ min = b
240→ }
241→ return min
242→}
243→
244→func max(a int, b int) int {
245→ var max int
246→ _ = max
247→ if (a >= b) {
248→ max = a
249→ } else if (b >= a) {
250→ max = b
251→ }
252→ return max
253→}
254→
255→func editor(keyboard chan byte, screen chan byte, control [][]chan _proto_COMMAND) {
256→ var x, y int
257→ _ = x
258→ _ = y
259→ var editing bool
260→ _ = editing
261→ x, y = (array_width / 2), (array_height / 2)
262→ editing = true
263→ for editing {
264→ var char byte
265→ _ = char
266→ move_cursor(screen, x, y)
267→ char = <-keyboard
268→ switch char {
269→ case byte(65):
270→ y = max((y - 1), 0)
271→ case byte(66):
272→ y = min((y + 1), (array_height - 1))
273→ case byte(67):
274→ x = min((x + 1), (array_width - 1))
275→ case byte(68):
276→ x = max((x - 1), 0)
277→ case byte(32), byte(42):
278→ var state bool = ((char == byte(42)) == alive)
279→ _ = state
280→ var wg sync.WaitGroup
281→ wg.Add(2)
282→ go func() {
283→ defer wg.Done()
284→ control[x][y] <- _proto_COMMAND_set_state{state}
285→ }()
286→ go func() {
287→ defer wg.Done()
288→ display_state(screen, x, y, state)
289→ }()
290→ wg.Wait()
291→ case byte(113), byte(81):
292→ editing = false
293→ default:
294→ // SKIP
295→ }
296→ }
297→}
298→
299→func controller(keyboard chan byte, screen chan byte, control [][]chan _proto_COMMAND, sense [][]chan _proto_RESPONSE) {
300→ var activity int
301→ _ = activity
302→ activity = idle
303→ initialize_display(screen)
304→ for (activity != terminated) {
305→ display_activity(screen, activity)
306→ var char byte
307→ _ = char
308→ var _alt0 chan int = nil
309→ if (activity != editing) { _alt0 = keyboard }
310→ select {
311→ case char = <-_alt0:
312→ activity = new_activity(char)
313→ default:
314→ if (activity != idle) {
315→ switch activity {
316→ case editing:
317→ editor(keyboard, screen, control)
318→ activity = idle
319→ case free_running, single_step:
320→ var changing bool
321→ _ = changing
322→ generation(screen, control, sense, &changing)
323→ if ((activity == single_step) || !changing) {
324→ activity = idle
325→ } else if ((activity == free_running) && changing) {
326→ // SKIP
327→ }
328→ }
329→ }
330→ }
331→ }
332→ display_activity(screen, activity)
333→ var wg sync.WaitGroup
334→ wg.Add(int(array_width))
335→ for x := 0; x < 0 + array_width; x++ {
336→ x := x
337→ go func() {
338→ defer wg.Done()
339→ var wg sync.WaitGroup
340→ wg.Add(int(array_height))
341→ for y := 0; y < 0 + array_height; y++ {
342→ y := y
343→ go func() {
344→ defer wg.Done()
345→ control[x][y] <- _proto_COMMAND_terminate{}
346→ }()
347→ }
348→ wg.Wait()
349→ }()
350→ }
351→ wg.Wait()
352→ clean_up_display(screen)
353→}
354→
355→func main() {
356→ link := make([][][]chan _proto_STATE, array_width)
357→ for _i0 := range link {
358→ link[_i0] = make([][]chan _proto_STATE, array_height)
359→ for _i1 := range link[_i0] {
360→ link[_i0][_i1] = make([]chan _proto_STATE, neighbours)
361→ for _i2 := range link[_i0][_i1] { link[_i0][_i1][_i2] = make(chan _proto_STATE) }
362→ }
363→ }
364→ control := make([][]chan _proto_COMMAND, array_width)
365→ for _i0 := range control {
366→ control[_i0] = make([]chan _proto_COMMAND, array_height)
367→ for _i1 := range control[_i0] { control[_i0][_i1] = make(chan _proto_COMMAND) }
368→ }
369→ sense := make([][]chan _proto_RESPONSE, array_width)
370→ for _i0 := range sense {
371→ sense[_i0] = make([]chan _proto_RESPONSE, array_height)
372→ for _i1 := range sense[_i0] { sense[_i0][_i1] = make(chan _proto_RESPONSE) }
373→ }
374→ var wg sync.WaitGroup
375→ wg.Add(2)
376→ go func() {
377→ defer wg.Done()
378→ controller(terminal_keyboard, terminal_screen, control, sense)
379→ }()
380→ go func() {
381→ defer wg.Done()
382→ var wg sync.WaitGroup
383→ wg.Add(int(array_width))
384→ for x := 0; x < 0 + array_width; x++ {
385→ x := x
386→ go func() {
387→ defer wg.Done()
388→ var wg sync.WaitGroup
389→ wg.Add(int(array_height))
390→ for y := 0; y < 0 + array_height; y++ {
391→ y := y
392→ go func() {
393→ defer wg.Done()
394→ var left int = (((x - 1) + array_width) % array_width)
395→ _ = left
396→ var right int = ((x + 1) % array_width)
397→ _ = right
398→ var up int = ((y + 1) % array_height)
399→ _ = up
400→ var down int = (((y - 1) + array_height) % array_height)
401→ _ = down
402→ var nx int = []int{right, x, left, left, left, x, right, right}
403→ _ = nx
404→ var ny int = []int{down, down, down, y, up, up, up, y}
405→ _ = ny
406→ cell(link, x, y, nx, ny, control[x][y], sense[x][y])
407→ }()
408→ }
409→ wg.Wait()
410→ }()
411→ }
412→ wg.Wait()
413→ }()
414→ wg.Wait()
415→}
416→I can see several issues in the generated Go code:
write_string - undefined (line 124, 182-190)write_formatted - undefined (line 133)DATA.ITEM - undefined type (line 128: make(chan DATA.ITEM))data_int - undefined (lines 137-138) - but also the code generation is wrong, it's sending data_int without the sequential protocol data (the ; y + 1 part)terminal_keyboard, terminal_screen - undefined (line 378)
Variable declaration bug where nx is declared as int but initialized with a slice literal []int{...} (lines 402-404) - this is a codegen issue with the VAL [neighbours]INT nx IS [...] syntax
PRI ALT with SKIP guard at line 363 translates to default: in a select, which makes it non-blocking instead of preserving the original semanticsI should focus on what the user actually needs - getting this to compile. The plan is to modify the occam file to avoid these problematic constructs and fix the code generation issues.
For the source changes, I'll replace the problematic library calls with simpler alternatives - swap out write.string and write.formatted with direct byte output, rewrite move.cursor to avoid the DATA.ITEM dependency, and handle the terminal.keyboard and terminal.screen channels by either declaring them or letting the transpiler generate them as terminal_keyboard and terminal_screen.
The real question is how to handle those terminal channels since they were originally folded channels mapped to stdin/stdout in occam2 - I could wire them as Go channels connected to standard input/output, or wrap everything in a PROC that takes them as parameters.
For now, let me focus on just getting it to compile as the user requested. I need to define write.string as a simple PROC, rewrite move.cursor to output ANSI escape sequences directly without relying on write.formatted or DATA.ITEM, and declare terminal.keyboard and terminal.screen as regular channels even if they won't be fully functional yet. The user is suggesting adding #INCLUDE "course.module", but the functions life.occ uses like write.string and write.formatted aren't from that module—they're from the book's standard library. The course module has different names like out.string and out.int. I think the better approach is to define these functions inline in life.occ to keep it self-contained and true to the original program's intent.
Defining inline functions... Writing integer conversion logic... I need to check how the transpiler handles I/O in its existing tests, since terminal.keyboard and terminal.screen were hardware-mapped channels in the original occam2 compiler and we don't have a direct equivalent. The simplest path forward is to modify the life.occ file by defining the missing procedures inline, then transpile it to see what compilation errors surface. I'll add implementations for write.string and write.int, rewrite move.cursor to avoid write.formatted, and wire up the I/O channels so we can identify any remaining codegen issues. I need to focus on getting this to compile first. My plan is to add the missing write.string and write.int procedure definitions, rewrite move.cursor and clear.screen to output directly instead of using write.formatted, declare the terminal channels as byte channels at the program level, and then check for any codegen issues. I'm also noticing a potential bug with how the VAL array declarations are being handled in the life.occ file...
Looking at the generated Go code, I can see the type annotation is wrong—it's declaring var nx int when it should be var nx []int for an array. And there's another issue with the variant protocol send in move.cursor: it's sending the tag data_int twice without actually sending the integer value that should follow it.
Now I'm reconsidering how to restructure the life.occ file. The helper procedures like clear.screen and write.string need to be defined before they're used, and I need to verify that the transpiler correctly handles escape sequences like *#1B within string literals when generating Go code. I need to work around the channel declarations—if I just declare terminal.keyboard and terminal.screen as bare channels without anything driving them, the program will deadlock. For a compilation test, I could stub them out or find another approach to make the syntax valid without needing external dependencies. add them. But for actually running, we'd need stdin/stdout wrappers.
Let me look at how the transpiler handles stdin/stdout currently. The built-in print procedures like write.full.string go directly to fmt.Print. But for channel-based I/O, there's no built-in mechanism.
Actually, for compilation purposes, let me just add them as CHAN OF BYTE declarations and see if the rest compiles. The program won't run, but the user asked about compilation.
Hmm, but actually the user might want it to run too. Let me take a pragmatic approach:
Let me first just modify the occam source and get it through the transpiler and Go compiler, then worry about runtime behavior. I'll start by checking what exactly fails to parse and compile, then fix the occam source minimally before tackling any remaining codegen issues in the transpiler itself.
My plan is to remove the write.formatted and DATA.ITEM dependency by rewriting move.cursor to output ANSI escape sequences directly, and I'll define a helper procedure that outputs small positive integers as decimal digits...
Defining write.string inline to handle channel-based output...
Now I need to fix clear.screen by sending escape sequences as individual bytes rather than trying to use a string literal. For the terminal channels, I should declare them at the program level and set up helper goroutines to connect them to stdin and stdout, since the transpiler doesn't have the hardware-mapped channels that original occam2 provided...
Actually, the real issue is that terminal.keyboard should be a source of input bytes from stdin, while terminal.screen should accept output bytes to stdout. The transpiler's built-in functions like write.full.string bypass channels entirely and use fmt.Print directly, so I need to add channel support to make this work properly.
I could either declare the channels at the program level and let the transpiler convert them to Go channels, then manually wire up stdin/stdout in the output, or restructure the program to have a main PROC that accepts these channels and have a Go-level main function handle the wiring. Since the user is asking about compilation rather than execution, I'll just declare the channels and get it compiling first. That said, I'm realizing the transpiler might not handle multidimensional channel arrays like [array.width][array.height][neighbours] properly, so I need to check how that's being handled.
Looking at the codegen output, it seems the 3D channel arrays are actually working correctly. But I'm noticing an issue with how typed abbreviations with array sizes are being generated—the code is producing var nx int = []int{...} when it should be var nx []int. Let me trace through the abbreviation codegen to understand what's going wrong.
Now I can see the issues clearly. The program uses several functions from the "Programming in occam 2" book's standard library (write.string, write.formatted, DATA.ITEM protocol, terminal.keyboard, terminal.screen) which are different from the KRoC course module. I also see a codegen bug where VAL [n]INT abbreviations generate the wrong Go type. Let me investigate both.
{
"subagent_type": "Explore",
"prompt": "In the occam2go transpiler codebase, find the code in codegen/codegen.go that generates Go code for abbreviations (VAL [...] TYPE or [...] TYPE IS expressions). Specifically, I need to understand how `VAL [n]INT nx IS [array literal]:` is handled - where it generates the Go type for the abbreviation. The issue is that `VAL [neighbours]INT nx IS [right, x, left, ...]:` generates `var nx int = []int{...}` instead of `var nx []int = []int{...}`. Find the relevant generateAbbreviation code and show me the section that determines the Go type for the variable declaration. Search thoroughly - check for \"Abbreviation\", \"generateAbbreviation\", \"goType\", and any array type handling."
}[
{
"type": "text",
"text": "Perfect! Now I have all the information I need. Let me provide a comprehensive analysis:\n\n## Summary\n\nI've found the relevant code that handles abbreviations in the occam2go transpiler. The issue you described is in how the transpiler determines the Go type for array-based abbreviations.\n\n### The Problem\n\nWhen you write `VAL [neighbours]INT nx IS [right, x, left, ...]:`, the transpiler generates:\n```go\nvar nx int = []int{...}\n```\n\nBut it should generate:\n```go\nvar nx []int = []int{...}\n```\n\n### Root Cause\n\nThe issue has **two parts**:\n\n#### 1. Parser: Fixed-size array size information is lost\n\n**File:** `/home/david/projects/code-associates/occam2go/parser/parser.go` (lines 338-350)\n\nThe parser DOES recognize `VAL [n]INT` syntax:\n\n```go\n// Check for [n]TYPE (fixed-size array, used in RETYPES)\nisArray := false\nvar arraySize ast.Expression\nif !isOpenArray && p.curTokenIs(lexer.LBRACKET) {\n // Could be [n]TYPE name RETYPES ...\n isArray = true\n p.nextToken() // move past [\n arraySize = p.parseExpression(LOWEST)\n if !p.expectPeek(lexer.RBRACKET) {\n return nil\n }\n p.nextToken() // move to type\n}\n```\n\nHowever, the `isArray` variable and `arraySize` are **only used for RETYPES declarations** (line 398). When creating a regular `Abbreviation` struct (lines 419-426), this information is discarded:\n\n```go\nreturn &ast.Abbreviation{\n Token: token,\n IsVal: true,\n IsOpenArray: isOpenArray, // <-- only this is set for arrays\n Type: typeName,\n Name: name,\n Value: value,\n // isArray and arraySize are NOT included\n}\n```\n\n#### 2. Codegen: Cannot detect array type from value alone\n\n**File:** `/home/david/projects/code-associates/occam2go/codegen/codegen.go` (lines 1107-1131)\n\nThe `generateAbbreviation()` function only checks `abbr.IsOpenArray` to determine if the variable should be a slice:\n\n```go\nfunc (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {\n g.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n if abbr.Type != \"\" {\n goType := g.occamTypeToGo(abbr.Type) // e.g., \"int\"\n if abbr.IsOpenArray {\n goType = \"[]\" + goType // prepend \"[]\" for []TYPE\n }\n g.write(fmt.Sprintf(\"var %s %s = \", goIdent(abbr.Name), goType))\n } else {\n g.write(fmt.Sprintf(\"%s := \", goIdent(abbr.Name)))\n }\n // ... rest of function\n}\n```\n\nThis only adds `[]` when `IsOpenArray` is true (which requires `VAL []INT` syntax with empty brackets). When you write `VAL [n]INT`, `IsOpenArray` remains false, so it generates `var nx int = []int{...}`.\n\nThere's also a package-level version at lines 270-296 with the same logic.\n\n### The Fix Required\n\nYou need to:\n\n1. **In the AST** (`ast/ast.go`): Add fields to `Abbreviation` struct to track fixed-size arrays:\n ```go\n type Abbreviation struct {\n // ... existing fields ...\n IsFixedArray bool // true for [n]TYPE abbreviations\n ArraySize ast.Expression // the size expression\n }\n ```\n\n2. **In the Parser** (`parser/parser.go`): Pass the `isArray` and `arraySize` to the Abbreviation:\n ```go\n return &ast.Abbreviation{\n Token: token,\n IsVal: true,\n IsOpenArray: isOpenArray,\n IsFixedArray: isArray, // ADD THIS\n ArraySize: arraySize, // ADD THIS\n Type: typeName,\n Name: name,\n Value: value,\n }\n ```\n\n3. **In Codegen** (`codegen/codegen.go`): Update type generation to check for fixed arrays:\n ```go\n if abbr.Type != \"\" {\n goType := g.occamTypeToGo(abbr.Type)\n if abbr.IsOpenArray || abbr.IsFixedArray { // CHECK BOTH\n goType = \"[]\" + goType\n }\n g.write(fmt.Sprintf(\"var %s %s = \", goIdent(abbr.Name), goType))\n }\n ```\n\n### Key Files with Complete Context\n\n- **Parser (`/home/david/projects/code-associates/occam2go/parser/parser.go:325-427`)**: The `parseAbbreviation()` function that loses the array size information\n- **AST (`/home/david/projects/code-associates/occam2go/ast/ast.go:493-501`)**: The `Abbreviation` struct definition\n- **Codegen package-level (`/home/david/projects/code-associates/occam2go/codegen/codegen.go:270-296`)**: Package-level abbreviation generation\n- **Codegen function-level (`/home/david/projects/code-associates/occam2go/codegen/codegen.go:1107-1131`)**: Function-scoped abbreviation generation"
},
{
"type": "text",
"text": "agentId: a4d643f4d85b01967 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 32943\ntool_uses: 24\nduration_ms: 63098</usage>"
}
]Great, now I understand the codegen bug. Let me fix the bugs and modify the life.occ source. Let me plan:
VAL [n]TYPE abbreviation codegen bug (AST + parser + codegen)Let me start with both in parallel.
Good, I found the codegen bug. Let me fix the VAL [n]TYPE abbreviation bug and modify life.occ to remove the book-library dependencies.
{
"file_path": "/home/david/projects/code-associates/occam2go/ast/ast.go",
"offset": 490,
"limit": 20
} 490→func (se *SliceExpr) TokenLiteral() string { return se.Token.Literal }
491→
492→// Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42:
493→type Abbreviation struct {
494→ Token lexer.Token // VAL, INITIAL, or type token
495→ IsVal bool // true for VAL abbreviations
496→ IsInitial bool // true for INITIAL declarations
497→ IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
498→ Type string // "INT", "BYTE", "BOOL", etc.
499→ Name string // variable name
500→ Value Expression // the expression
501→}
502→
503→func (a *Abbreviation) statementNode() {}
504→func (a *Abbreviation) TokenLiteral() string { return a.Token.Literal }
505→
506→// ArrayLiteral represents an array literal expression: [expr1, expr2, ...]
507→type ArrayLiteral struct {
508→ Token lexer.Token // the [ token
509→ Elements []Expression // the elements{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 325,
"limit": 110
} 325→func (p *Parser) parseAbbreviation() ast.Statement {
326→ token := p.curToken // VAL token
327→
328→ p.nextToken()
329→
330→ // Check for []TYPE (open array abbreviation)
331→ isOpenArray := false
332→ if p.curTokenIs(lexer.LBRACKET) && p.peekTokenIs(lexer.RBRACKET) {
333→ isOpenArray = true
334→ p.nextToken() // consume ]
335→ p.nextToken() // move to type
336→ }
337→
338→ // Check for [n]TYPE (fixed-size array, used in RETYPES)
339→ isArray := false
340→ var arraySize ast.Expression
341→ if !isOpenArray && p.curTokenIs(lexer.LBRACKET) {
342→ // Could be [n]TYPE name RETYPES ...
343→ isArray = true
344→ p.nextToken() // move past [
345→ arraySize = p.parseExpression(LOWEST)
346→ if !p.expectPeek(lexer.RBRACKET) {
347→ return nil
348→ }
349→ p.nextToken() // move to type
350→ }
351→
352→ // Check for untyped VAL abbreviation: VAL name IS expr :
353→ // Detect: curToken is IDENT and peekToken is IS (no type keyword)
354→ if !isOpenArray && !isArray && p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.IS) {
355→ name := p.curToken.Literal
356→ p.nextToken() // consume IS
357→ p.nextToken() // move to expression
358→ value := p.parseExpression(LOWEST)
359→ if !p.expectPeek(lexer.COLON) {
360→ return nil
361→ }
362→ return &ast.Abbreviation{
363→ Token: token,
364→ IsVal: true,
365→ Type: "",
366→ Name: name,
367→ Value: value,
368→ }
369→ }
370→
371→ // Expect a type keyword
372→ if !isTypeToken(p.curToken.Type) {
373→ p.addError(fmt.Sprintf("expected type after VAL, got %s", p.curToken.Type))
374→ return nil
375→ }
376→ typeName := p.curToken.Literal
377→
378→ // Expect name
379→ if !p.expectPeek(lexer.IDENT) {
380→ return nil
381→ }
382→ name := p.curToken.Literal
383→
384→ // Check for RETYPES (instead of IS)
385→ if p.peekTokenIs(lexer.RETYPES) {
386→ p.nextToken() // consume RETYPES
387→ if !p.expectPeek(lexer.IDENT) {
388→ return nil
389→ }
390→ source := p.curToken.Literal
391→ if !p.expectPeek(lexer.COLON) {
392→ return nil
393→ }
394→ return &ast.RetypesDecl{
395→ Token: token,
396→ IsVal: true,
397→ TargetType: typeName,
398→ IsArray: isArray,
399→ ArraySize: arraySize,
400→ Name: name,
401→ Source: source,
402→ }
403→ }
404→
405→ // Expect IS
406→ if !p.expectPeek(lexer.IS) {
407→ return nil
408→ }
409→
410→ // Parse expression
411→ p.nextToken()
412→ value := p.parseExpression(LOWEST)
413→
414→ // Expect colon
415→ if !p.expectPeek(lexer.COLON) {
416→ return nil
417→ }
418→
419→ return &ast.Abbreviation{
420→ Token: token,
421→ IsVal: true,
422→ IsOpenArray: isOpenArray,
423→ Type: typeName,
424→ Name: name,
425→ Value: value,
426→ }
427→}
428→
429→// parseInitialDecl parses an INITIAL declaration: INITIAL INT x IS expr:
430→// Current token is INITIAL.
431→func (p *Parser) parseInitialDecl() *ast.Abbreviation {
432→ token := p.curToken // INITIAL token
433→
434→ // Expect a type keyword{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 265,
"limit": 35
} 265→
266→ // Generate type definitions first (at package level)
267→ for _, stmt := range typeDecls {
268→ g.generateStatement(stmt)
269→ }
270→
271→ // Generate package-level abbreviations (constants)
272→ for _, stmt := range abbrDecls {
273→ abbr := stmt.(*ast.Abbreviation)
274→ if abbr.Type == "" {
275→ // Untyped VAL: let Go infer the type
276→ g.builder.WriteString("var ")
277→ g.write(fmt.Sprintf("%s = ", goIdent(abbr.Name)))
278→ g.generateExpression(abbr.Value)
279→ g.write("\n")
280→ } else {
281→ goType := g.occamTypeToGo(abbr.Type)
282→ if abbr.IsOpenArray {
283→ goType = "[]" + goType
284→ }
285→ g.builder.WriteString("var ")
286→ g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
287→ // Wrap string literals with []byte() when assigned to []byte variables
288→ if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
289→ g.write("[]byte(")
290→ g.generateExpression(abbr.Value)
291→ g.write(")")
292→ } else {
293→ g.generateExpression(abbr.Value)
294→ }
295→ g.write("\n")
296→ }
297→ }
298→ if len(abbrDecls) > 0 {
299→ g.writeLine(""){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1105,
"limit": 30
} 1105→}
1106→
1107→func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
1108→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1109→ if abbr.Type != "" {
1110→ goType := g.occamTypeToGo(abbr.Type)
1111→ if abbr.IsOpenArray {
1112→ goType = "[]" + goType
1113→ }
1114→ g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
1115→ } else {
1116→ g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name)))
1117→ }
1118→ // Wrap string literals with []byte() when assigned to []byte variables
1119→ if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
1120→ g.write("[]byte(")
1121→ g.generateExpression(abbr.Value)
1122→ g.write(")")
1123→ } else {
1124→ g.generateExpression(abbr.Value)
1125→ }
1126→ g.write("\n")
1127→ // Suppress "declared and not used" for abbreviations inside function bodies
1128→ if g.nestingLevel > 0 {
1129→ g.writeLine(fmt.Sprintf("_ = %s", goIdent(abbr.Name)))
1130→ }
1131→}
1132→
1133→func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
1134→ goType := g.occamTypeToGo(decl.ElemType)// Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42:
type Abbreviation struct {
Token lexer.Token // VAL, INITIAL, or type token
IsVal bool // true for VAL abbreviations
IsInitial bool // true for INITIAL declarations
IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
Type string // "INT", "BYTE", "BOOL", etc.
Name string // variable name
Value Expression // the expression
}// Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42:
type Abbreviation struct {
Token lexer.Token // VAL, INITIAL, or type token
IsVal bool // true for VAL abbreviations
IsInitial bool // true for INITIAL declarations
IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
IsFixedArray bool // true for [n]TYPE abbreviations (e.g. VAL [8]INT)
Type string // "INT", "BYTE", "BOOL", etc.
Name string // variable name
Value Expression // the expression
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
return &ast.Abbreviation{
Token: token,
IsVal: true,
IsOpenArray: isOpenArray,
Type: typeName,
Name: name,
Value: value,
}
} return &ast.Abbreviation{
Token: token,
IsVal: true,
IsOpenArray: isOpenArray,
IsFixedArray: isArray,
Type: typeName,
Name: name,
Value: value,
}
}The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
g.write("[]byte(")
g.generateExpression(abbr.Value)
g.write(")")
} else {
g.generateExpression(abbr.Value)
}
g.write("\n")
}
}
if len(abbrDecls) > 0 {
g.writeLine("") goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
g.write("[]byte(")
g.generateExpression(abbr.Value)
g.write(")")
} else {
g.generateExpression(abbr.Value)
}
g.write("\n")
}
}
if len(abbrDecls) > 0 {
g.writeLine("")<tool_use_error>String to replace not found in file.
String: goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
g.write("[]byte(")
g.generateExpression(abbr.Value)
g.write(")")
} else {
g.generateExpression(abbr.Value)
}
g.write("\n")
}
}
if len(abbrDecls) > 0 {
g.writeLine("")</tool_use_error>{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 270,
"limit": 30
} 270→
271→ // Generate package-level abbreviations (constants)
272→ for _, stmt := range abbrDecls {
273→ abbr := stmt.(*ast.Abbreviation)
274→ if abbr.Type == "" {
275→ // Untyped VAL: let Go infer the type
276→ g.builder.WriteString("var ")
277→ g.write(fmt.Sprintf("%s = ", goIdent(abbr.Name)))
278→ g.generateExpression(abbr.Value)
279→ g.write("\n")
280→ } else {
281→ goType := g.occamTypeToGo(abbr.Type)
282→ if abbr.IsOpenArray {
283→ goType = "[]" + goType
284→ }
285→ g.builder.WriteString("var ")
286→ g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
287→ // Wrap string literals with []byte() when assigned to []byte variables
288→ if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
289→ g.write("[]byte(")
290→ g.generateExpression(abbr.Value)
291→ g.write(")")
292→ } else {
293→ g.generateExpression(abbr.Value)
294→ }
295→ g.write("\n")
296→ }
297→ }
298→ if len(abbrDecls) > 0 {
299→ g.writeLine("") goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" { goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {<tool_use_error>String to replace not found in file.
String: goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables
if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {</tool_use_error>sed -n '281,282p' /home/david/projects/code-associates/occam2go/codegen/codegen.go | cat -A
goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType)) goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 278,
"limit": 10
} 278→ g.generateExpression(abbr.Value)
279→ g.write("\n")
280→ } else {
281→ goType := g.occamTypeToGo(abbr.Type)
282→ if abbr.IsOpenArray {
283→ goType = "[]" + goType
284→ }
285→ g.builder.WriteString("var ")
286→ g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
287→ // Wrap string literals with []byte() when assigned to []byte variables goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variables goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
// Wrap string literals with []byte() when assigned to []byte variablesThe file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1107,
"limit": 10
} 1107→func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
1108→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1109→ if abbr.Type != "" {
1110→ goType := g.occamTypeToGo(abbr.Type)
1111→ if abbr.IsOpenArray {
1112→ goType = "[]" + goType
1113→ }
1114→ g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
1115→ } else {
1116→ g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name))) if abbr.Type != "" {
goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray {
goType = "[]" + goType
}
g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
} else {
g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name)))
} if abbr.Type != "" {
goType := g.occamTypeToGo(abbr.Type)
if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
} else {
g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name)))
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
Good. Now let me build and verify existing tests still pass, while also modifying life.occ:
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 18.468s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser 0.002s ok github.com/codeassociates/occam2go/preproc (cached)
All tests pass. Now let me modify life.occ to remove the book-library dependencies. The program uses:
write.string(chan, "str") - not in course module, define inlinewrite.formatted(chan, fmt, data_chan) + DATA.ITEM protocol + data.int - complex formatted output, rewrite move.cursor directlyterminal.keyboard / terminal.screen - runtime-provided channels, need declarations{
"file_path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"offset": 1,
"limit": 50
} 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→
8→--
9→-- configuration constants
10→--
11→
12→VAL INT array.width IS 20 : -- number of cells across the board
13→VAL INT array.height IS 20 : -- number of cells down the board
14→
15→VAL INT radius IS 1 : -- of the `sphere of influence'
16→VAL INT diameter IS (2 * radius) + 1 :
17→VAL INT neighbours IS (diameter * diameter) - 1 :
18→
19→VAL INT number.of.cells IS array.height * array.width :
20→VAL INT number.of.links IS neighbours * number.of.cells :
21→
22→--
23→-- protocols
24→--
25→
26→PROTOCOL STATE IS BOOL :
27→
28→VAL BOOL alive IS TRUE :
29→VAL BOOL dead IS NOT alive :
30→
31→PROTOCOL COMMAND
32→ CASE
33→ set.state; BOOL
34→ evolve
35→ terminate
36→:
37→
38→PROTOCOL RESPONSE IS BOOL; BOOL :
39→--
40→-- cell processes
41→--
42→
43→PROC broadcast.present.state([][][]CHAN OF STATE link,
44→ VAL INT x, y, VAL BOOL state )
45→ PAR d = 0 FOR neighbours
46→ link[x][y][d] ! state
47→:
48→
49→PROC calculate.next.state([][][]CHAN OF STATE link,
50→ VAL []INT nx, ny,Now let me write the modified version. I'll add the helper PROCs, rewrite move.cursor and clear.screen, and declare the terminal channels:
-- 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 -- -- The program in this chapter plays Life on a terminal screen. -- -- -- configuration constants --
-- 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
--
-- The program in this chapter plays Life on a terminal screen.
--
-- 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'))
:
--
-- configuration constants
--The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
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
: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'
:
PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
-- left-handed co-ordinates, origin 0,0 at top left
-- 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'
:The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
Now I need to add terminal.keyboard and terminal.screen channel declarations. Let me look at the main body:
{
"file_path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"offset": 413,
"limit": 35
}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→[array.width][array.height][neighbours]CHAN OF STATE link : 436→[array.width][array.height]CHAN OF COMMAND control : 437→[array.width][array.height]CHAN OF RESPONSE sense : 438→PAR 439→ controller(terminal.keyboard, terminal.screen, control, sense) 440→ PAR x = 0 FOR array.width 441→ PAR y = 0 FOR array.height 442→ VAL INT left IS ((x - 1) + array.width) \ array.width : 443→ VAL INT right IS (x + 1) \ array.width : 444→ VAL INT up IS (y + 1) \ array.height : 445→ VAL INT down IS ((y - 1) + array.height) \ array.height : 446→ VAL [neighbours]INT nx IS 447→ [ right, x, left, left, left, x, right, right ] :
-- -- 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)
-- -- structure of the program -- CHAN OF BYTE terminal.keyboard : CHAN OF BYTE terminal.screen : [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)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
Exit code 1 # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:324:38: cannot use keyboard (variable of type chan byte) as chan int value in assignment # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:324:38: cannot use keyboard (variable of type chan byte) as chan int value in assignment
318→ initialize_display(screen)
319→ for (activity != terminated) {
320→ display_activity(screen, activity)
321→ var char byte
322→ _ = char
323→ var _alt0 chan int = nil
324→ if (activity != editing) { _alt0 = keyboard }
325→ select {
326→ case char = <-_alt0:
327→ activity = new_activity(char)
328→ default:
329→ if (activity != idle) {
330→ switch activity {
331→ case editing:
332→ editor(keyboard, screen, control)The issue is in the PRI ALT code generation. The ALT guard (activity <> editing) & keyboard ? char creates a guarded receive. The codegen creates var _alt0 chan int = nil but keyboard is chan byte. It should be var _alt0 chan byte = nil.
Let me find where this is generated in the codegen.
The ALT code generation uses chan int as the default guard type, but the channel is chan byte. Let me find where this is generated.
{
"pattern": "chan int = nil",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content"
}{
"pattern": "_alt\\d.*= nil",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content"
}{
"pattern": "_alt",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}1887: g.write(fmt.Sprintf("var _alt%d chan ", i))
1895: g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, goIdent(c.Channel)))
1914: g.write(fmt.Sprintf("case %s = <-_alt%d:\n", varRef, i))
1978: // _altCount := int(<count>)
1980: g.write("_altCount := int(")
1984: // _altCases := make([]reflect.SelectCase, _altCount)
1985: g.writeLine("_altCases := make([]reflect.SelectCase, _altCount)")
1988: g.writeLine("for _altI := 0; _altI < _altCount; _altI++ {")
1996: g.write(" + _altI * (")
2002: g.write(" + _altI\n")
2014: g.write("_altCases[_altI] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(")
2027: g.writeLine("_altChosen, _altValue, _ := reflect.Select(_altCases)")
2034: g.write(" + _altChosen * (")
2040: g.write(" + _altChosen\n")
2063: g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", varRef, recvType)){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1880,
"limit": 45
} 1880→ }
1881→
1882→ if hasGuards {
1883→ // Generate channel variables for guarded cases
1884→ for i, c := range alt.Cases {
1885→ if c.Guard != nil && !c.IsSkip {
1886→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1887→ g.write(fmt.Sprintf("var _alt%d chan ", i))
1888→ // We don't know the channel type here, so use interface{}
1889→ // Actually, we should use the same type as the original channel
1890→ // For now, let's just reference the original channel conditionally
1891→ g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
1892→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1893→ g.write(fmt.Sprintf("if "))
1894→ g.generateExpression(c.Guard)
1895→ g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, goIdent(c.Channel)))
1896→ }
1897→ }
1898→ }
1899→
1900→ g.writeLine("select {")
1901→ for i, c := range alt.Cases {
1902→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1903→ if c.IsSkip {
1904→ g.write("default:\n")
1905→ } else if c.IsTimer {
1906→ g.write("case <-time.After(time.Duration(")
1907→ g.generateExpression(c.Deadline)
1908→ g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
1909→ } else if c.Guard != nil {
1910→ varRef := goIdent(c.Variable)
1911→ if len(c.VariableIndices) > 0 {
1912→ varRef += g.generateIndicesStr(c.VariableIndices)
1913→ }
1914→ g.write(fmt.Sprintf("case %s = <-_alt%d:\n", varRef, i))
1915→ } else if len(c.ChannelIndices) > 0 {
1916→ varRef := goIdent(c.Variable)
1917→ if len(c.VariableIndices) > 0 {
1918→ varRef += g.generateIndicesStr(c.VariableIndices)
1919→ }
1920→ g.write(fmt.Sprintf("case %s = <-%s", varRef, goIdent(c.Channel)))
1921→ g.generateIndices(c.ChannelIndices)
1922→ g.write(":\n")
1923→ } else {
1924→ varRef := goIdent(c.Variable)I see the issue - at line 1891: g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
This hardcodes int as the channel type. It needs to determine the actual channel type from the channel declaration. This is a pre-existing bug but it only shows up when the channel is not CHAN OF INT.
To fix this properly, I need to look up the channel type from the channel name. The codegen has procSigs for proc signatures, but for channel types, it might need to look at the declarations.
Let me think about how to resolve the channel type. The c.Channel field has the channel name.
I need to figure out what type the channel carries. Since the codegen has access to the AST, I could track channel types during generation, look them up from the c.Protocol field if it's available in the AltCase node, or check the variable type from the receive statement. Let me examine the AltCase AST structure to see what information's available.
Found the bug - line 1891 hardcodes int as the channel type. Let me check the AltCase AST to find the channel's element type.
{
"pattern": "type AltCase struct",
"path": "/home/david/projects/code-associates/occam2go/ast/ast.go",
"output_mode": "content",
"-A": 20
}399:type AltCase struct {
400- Guard Expression // optional guard condition (nil if no guard)
401- Channel string // channel name
402- ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
403- Variable string // variable to receive into
404- VariableIndices []Expression // non-empty for c ? flags[0] or c ? grid[i][j]
405- Body []Statement // the body to execute
406- IsTimer bool // true if this is a timer AFTER case
407- IsSkip bool // true if this is a guarded SKIP case (guard & SKIP)
408- Timer string // timer name (when IsTimer)
409- Deadline Expression // AFTER deadline expression (when IsTimer)
410- Declarations []Statement / scoped declarations before channel input (e.g., BYTE ch:)
411-}
412-
413-/ TimerDecl represents a timer declaration: TIMER tim:
414-type TimerDecl struct {
415- Token lexer.Token // the TIMER token
416- Names []string // timer variable names
417-}
418-
419-func (td *TimerDecl) statementNode() {}The AltCase doesn't store the channel's element type. We need to determine it from context. Let me think about the simplest way to fix this.
One approach: instead of declaring var _alt0 chan int = nil, we can use the channel's actual type. We need to figure out the channel type from somewhere. Options:
BYTE char, then the channel carries byteActually, the simplest fix would be to look at the AltCase declarations.
The Declarations field contains scoped declarations like BYTE char:. The variable being received into would tell us the type. But that's also complex. Let me think of an even simpler approach...
Actually, the simplest approach might be to not pre-declare the channel variable with a type at all. Instead, use a bool flag plus the original channel name in the select. Wait, Go's select doesn't support conditional cases directly. That's why the codegen creates a nil-able channel variable. The pattern is to declare a channel variable that can be set to nil or the actual channel, then use it in the select statement to conditionally enable that case.
When the channel is nil, receiving from it blocks forever, which effectively disables the case in the select. So I do need to determine the type somehow. The most straightforward approach is to check the scoped declarations on the AltCase itself—the variable being received into should be declared there with its type.
Looking at how the parser structures this, the BYTE char : declaration appears before the ALT block as a scoped declaration in the parent, not inside the case itself. This means I'd need to track variable types through the entire block scope. A better approach might be to look at the protocol type of the channel directly—the keyboard channel should have a defined type that tells me what it communicates.
If I could track channel types through the codegen, I could look up keyboard and find it's CHAN OF BYTE. The challenge is that keyboard is a function parameter, so I'd need to check the procSigs to match the parameter name to its type. That's possible but adds complexity.
Let me explore a simpler solution by changing how the codegen handles this...
Actually, I could use an interface{} channel instead of trying to infer the specific type. but that won't work because Go's type system requires exact channel types.
Let me try yet another approach: What about inferring the type from the channel itself? If the channel is a simple identifier, I could look it up in the current proc's parameters. The codegen tracks parameter information including whether something is a channel and its type name, so if the channel is a function parameter, I could potentially look it up there. But this is getting complex and fragile.
The pragmatic solution is to add a ChannelType field to AltCase during parsing or codegen. Since the parser already knows the channel name, it could also determine the type if it tracks variable declarations. Actually, I could determine the type from the receive variable itself—if char is declared as BYTE, I can look up the declarations in scope to find the type. The challenge is that the codegen doesn't currently track local variable types either.
For now, the most practical approach is to look at the actual Go type in the generated code and work backwards from there. I'm going to add a chanTypes map to the Generator to track channel element types as they're declared, then look it up when generating ALT statements—this is the cleanest approach.
The ALT codegen hardcodes chan int for guarded channel variables. I need to fix this by tracking channel types. Let me look at what's available.
{
"pattern": "type Generator struct",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-A": 25,
"-n": true
}11:type Generator struct {
12- indent int
13- builder strings.Builder
14- needSync bool // track if we need sync package import
15- needFmt bool // track if we need fmt package import
16- needTime bool // track if we need time package import
17- needOs bool // track if we need os package import
18- needMath bool // track if we need math package import
19- needMathBits bool // track if we need math/bits package import
20- needBufio bool // track if we need bufio package import
21- needReflect bool // track if we need reflect package import
22- needBoolHelper bool // track if we need _boolToInt helper
23-
24- // Track procedure signatures for proper pointer handling
25- procSigs map[string][]ast.ProcParam
26- // Track current procedure's reference parameters
27- refParams map[string]bool
28-
29- // Protocol support
30- protocolDefs map[string]*ast.ProtocolDecl
31- chanProtocols map[string]string // channel name → protocol name
32- tmpCounter int // for unique temp variable names
33-
34- // Record support
35- recordDefs map[string]*ast.RecordDecl
36- recordVars map[string]string // variable name → record type name{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 36,
"limit": 25
} 36→ recordVars map[string]string // variable name → record type name
37→
38→ // Bool variable tracking (for type conversion codegen)
39→ boolVars map[string]bool
40→
41→ // Nesting level: 0 = package level, >0 = inside a function
42→ nestingLevel int
43→
44→ // RETYPES parameter renames: when a RETYPES declaration shadows a
45→ // parameter (e.g. VAL INT X RETYPES X :), the parameter is renamed
46→ // in the signature so := can create a new variable with the original name.
47→ retypesRenames map[string]string
48→}
49→
50→// Transputer intrinsic function names
51→var transpIntrinsics = map[string]bool{
52→ "LONGPROD": true,
53→ "LONGDIV": true,
54→ "LONGSUM": true,
55→ "LONGDIFF": true,
56→ "NORMALISE": true,
57→ "SHIFTRIGHT": true,
58→ "SHIFTLEFT": true,
59→}
60→I see there's already chanProtocols mapping channel names to protocol names. I need a similar mapping for simple (non-protocol) channel element types. Actually, chanProtocols is only for protocol channels. Let me look at how it's populated and used.
{
"pattern": "chanProtocols",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}31: chanProtocols map[string]string // channel name → protocol name 108: g.chanProtocols = make(map[string]string) 1300: protoName := g.chanProtocols[send.Channel] 1421: protoName := g.chanProtocols[vr.Channel] 1460: g.chanProtocols[name] = s.ElemType 1476: g.chanProtocols[p.Name] = p.ChanElemType 2106: g.chanProtocols[p.Name] = p.ChanElemType
chanProtocols maps channel names to their element type (protocol name or simple type). It's populated when ChanDecl statements are encountered (line 1460) and when proc params are processed (lines 1476, 2106).
So chanProtocols already tracks channel element types for ALL channels, not just protocol channels (despite the name). Let me verify by looking at line 1460.
chanProtocols already maps channel names to their element types. Let me verify this works for simple types like BYTE.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1455,
"limit": 30
} 1455→func (g *Generator) collectChanProtocols(stmt ast.Statement) {
1456→ switch s := stmt.(type) {
1457→ case *ast.ChanDecl:
1458→ if _, ok := g.protocolDefs[s.ElemType]; ok {
1459→ for _, name := range s.Names {
1460→ g.chanProtocols[name] = s.ElemType
1461→ }
1462→ }
1463→ case *ast.SeqBlock:
1464→ for _, inner := range s.Statements {
1465→ g.collectChanProtocols(inner)
1466→ }
1467→ case *ast.ParBlock:
1468→ for _, inner := range s.Statements {
1469→ g.collectChanProtocols(inner)
1470→ }
1471→ case *ast.ProcDecl:
1472→ // Register PROC param channels (including channel array params)
1473→ for _, p := range s.Params {
1474→ if p.IsChan || p.ChanArrayDims > 0 {
1475→ if _, ok := g.protocolDefs[p.ChanElemType]; ok {
1476→ g.chanProtocols[p.Name] = p.ChanElemType
1477→ }
1478→ }
1479→ }
1480→ for _, inner := range s.Body {
1481→ g.collectChanProtocols(inner)
1482→ }
1483→ case *ast.FuncDecl:
1484→ for _, inner := range s.Body {I see - chanProtocols ONLY maps channels whose element type is a protocol. It checks if _, ok := g.protocolDefs[s.ElemType]; ok before adding. So for CHAN OF BYTE keyboard, since BYTE is not a protocol, it wouldn't be in chanProtocols.
I need a different approach. I should either:
1. Extend chanProtocols to also track non-protocol channel types
2. Add a separate map for all channel element types
The cleanest approach is to add a general chanTypes map that tracks element types for ALL channels (not just protocol channels), and use it in the ALT generation.
But modifying chanProtocols to also include non-protocol types might break existing logic that assumes it only contains protocol types. Instead, I'll create a new chanElemTypes map that tracks all channel element types alongside chanProtocols. Actually, wait—I already have the channel name in the ALT case, so I could look up the channel's type directly from the proc parameters. Since controller receives keyboard chan byte, I should be able to extract that type information when processing the ALT statement.
The simplest approach is to extend the channel tracking to capture all element types, not just protocols. I'll add a chanElemTypes map to the Generator to store this information.
I see - chanProtocols only tracks channels with protocol element types (it checks protocolDefs). I need a separate map for all channel element types. Let me add one.
// Bool variable tracking (for type conversion codegen) boolVars map[string]bool
// Channel element type tracking (for ALT guard codegen) chanElemTypes map[string]string // channel name → Go element type // Bool variable tracking (for type conversion codegen) boolVars map[string]bool
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"pattern": "g.chanProtocols = make",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 108,
"limit": 8
}108→ g.procSigs = make(map[string][]ast.ProcParam) 109→ g.refParams = make(map[string]bool) 110→ g.protocolDefs = make(map[string]*ast.ProtocolDecl) 111→ g.chanProtocols = make(map[string]string) 112→ g.tmpCounter = 0 113→ g.recordDefs = make(map[string]*ast.RecordDecl) 114→ g.recordVars = make(map[string]string) 115→ g.boolVars = make(map[string]bool)
g.chanProtocols = make(map[string]string) g.tmpCounter = 0 g.recordDefs = make(map[string]*ast.RecordDecl) g.recordVars = make(map[string]string) g.boolVars = make(map[string]bool)
g.chanProtocols = make(map[string]string) g.chanElemTypes = make(map[string]string) g.tmpCounter = 0 g.recordDefs = make(map[string]*ast.RecordDecl) g.recordVars = make(map[string]string) g.boolVars = make(map[string]bool)
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
Now populate chanElemTypes when channel declarations are encountered. Let me find where ChanDecl is generated:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1135,
"limit": 45
} 1135→}
1136→
1137→func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
1138→ goType := g.occamTypeToGo(decl.ElemType)
1139→ if len(decl.Sizes) > 0 {
1140→ for _, name := range decl.Names {
1141→ n := goIdent(name)
1142→ g.generateMultiDimChanInit(n, goType, decl.Sizes, 0)
1143→ }
1144→ } else {
1145→ for _, name := range decl.Names {
1146→ g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
1147→ }
1148→ }
1149→}
1150→
1151→// generateMultiDimChanInit generates nested make+init loops for multi-dimensional channel arrays.
1152→// For [w][h]CHAN OF INT link: generates:
1153→//
1154→// link := make([][]chan int, w)
1155→// for _i0 := range link { link[_i0] = make([]chan int, h)
1156→// for _i1 := range link[_i0] { link[_i0][_i1] = make(chan int) }
1157→// }
1158→func (g *Generator) generateMultiDimChanInit(name, goType string, sizes []ast.Expression, depth int) {
1159→ if depth == 0 {
1160→ // Top-level: name := make([]...[]chan goType, sizes[0])
1161→ sliceType := strings.Repeat("[]", len(sizes)) + "chan " + goType
1162→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1163→ g.write(fmt.Sprintf("%s := make(%s, ", name, sliceType))
1164→ g.generateExpression(sizes[0])
1165→ g.write(")\n")
1166→ if len(sizes) == 1 {
1167→ // Single dim: init each channel
1168→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1169→ g.write(fmt.Sprintf("for _i0 := range %s { %s[_i0] = make(chan %s) }\n", name, name, goType))
1170→ } else {
1171→ // Multi dim: recurse
1172→ ivar := "_i0"
1173→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1174→ g.write(fmt.Sprintf("for %s := range %s {\n", ivar, name))
1175→ g.indent++
1176→ g.generateMultiDimChanInit(name+"["+ivar+"]", goType, sizes, 1)
1177→ g.indent--
1178→ g.writeLine("}")
1179→ }func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
if len(decl.Sizes) > 0 {
for _, name := range decl.Names {
n := goIdent(name)
g.generateMultiDimChanInit(n, goType, decl.Sizes, 0)
}
} else {
for _, name := range decl.Names {
g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
}
}
}func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
for _, name := range decl.Names {
g.chanElemTypes[name] = goType
}
if len(decl.Sizes) > 0 {
for _, name := range decl.Names {
n := goIdent(name)
g.generateMultiDimChanInit(n, goType, decl.Sizes, 0)
}
} else {
for _, name := range decl.Names {
g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
}
}
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
Now I need to also populate chanElemTypes from proc parameters. Let me find where proc params register channel types:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2095,
"limit": 25
} 2095→ }
2096→ }
2097→ for _, p := range proc.Params {
2098→ if !p.IsVal && !p.IsChan && p.ChanArrayDims == 0 && p.OpenArrayDims == 0 && p.ArraySize == "" {
2099→ newRefParams[p.Name] = true
2100→ } else {
2101→ // Own param shadows any inherited ref param with same name
2102→ delete(newRefParams, p.Name)
2103→ }
2104→ // Track BOOL params; delete non-BOOL params that shadow inherited names
2105→ if p.Type == "BOOL" && !p.IsChan && p.ChanArrayDims == 0 {
2106→ newBoolVars[p.Name] = true
2107→ } else {
2108→ delete(newBoolVars, p.Name)
2109→ }
2110→ // Register chan params with protocol mappings
2111→ if p.IsChan || p.ChanArrayDims > 0 {
2112→ if _, ok := g.protocolDefs[p.ChanElemType]; ok {
2113→ g.chanProtocols[p.Name] = p.ChanElemType
2114→ }
2115→ }
2116→ // Register record-typed params
2117→ if !p.IsChan {
2118→ if _, ok := g.recordDefs[p.Type]; ok {
2119→ g.recordVars[p.Name] = p.Type // Register chan params with protocol mappings
if p.IsChan || p.ChanArrayDims > 0 {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
g.chanProtocols[p.Name] = p.ChanElemType
}
} // Register chan params with protocol mappings and element types
if p.IsChan || p.ChanArrayDims > 0 {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
g.chanProtocols[p.Name] = p.ChanElemType
}
g.chanElemTypes[p.Name] = g.occamTypeToGo(p.ChanElemType)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1884,
"limit": 16
} 1884→ hasGuards = true
1885→ break
1886→ }
1887→ }
1888→
1889→ if hasGuards {
1890→ // Generate channel variables for guarded cases
1891→ for i, c := range alt.Cases {
1892→ if c.Guard != nil && !c.IsSkip {
1893→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1894→ g.write(fmt.Sprintf("var _alt%d chan ", i))
1895→ // We don't know the channel type here, so use interface{}
1896→ // Actually, we should use the same type as the original channel
1897→ // For now, let's just reference the original channel conditionally
1898→ g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
1899→ g.builder.WriteString(strings.Repeat("\t", g.indent)) g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("var _alt%d chan ", i))
// We don't know the channel type here, so use interface{}
// Actually, we should use the same type as the original channel
// For now, let's just reference the original channel conditionally
g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now g.builder.WriteString(strings.Repeat("\t", g.indent))
// Look up the channel's element type
elemType := "int" // default fallback
if t, ok := g.chanElemTypes[c.Channel]; ok {
elemType = t
}
g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))<tool_use_error>String to replace not found in file.
String: g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("var _alt%d chan ", i))
// We don't know the channel type here, so use interface{}
// Actually, we should use the same type as the original channel
// For now, let's just reference the original channel conditionally
g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now</tool_use_error>sed -n '1893,1899p' /home/david/projects/code-associates/occam2go/codegen/codegen.go | cat -A
^I^I^I^Ig.builder.WriteString(strings.Repeat("\t", g.indent))$
^I^I^I^Ig.write(fmt.Sprintf("var _alt%d chan ", i))$
^I^I^I^I// We don't know the channel type here, so use interface{}$
^I^I^I^I// Actually, we should use the same type as the original channel$
^I^I^I^I// For now, let's just reference the original channel conditionally$
^I^I^I^Ig.write(fmt.Sprintf("int = nil\n")) // Assuming int for now$
^I^I^I^Ig.builder.WriteString(strings.Repeat("\t", g.indent))$ g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("var _alt%d chan ", i))
// We don't know the channel type here, so use interface{}
// Actually, we should use the same type as the original channel
// For now, let's just reference the original channel conditionally
g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now g.builder.WriteString(strings.Repeat("\t", g.indent))
// Look up the channel's element type
elemType := "int" // default fallback
if t, ok := g.chanElemTypes[c.Channel]; ok {
elemType = t
}
g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1890,
"limit": 12
} 1890→ // Generate channel variables for guarded cases
1891→ for i, c := range alt.Cases {
1892→ if c.Guard != nil && !c.IsSkip {
1893→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1894→ g.write(fmt.Sprintf("var _alt%d chan ", i))
1895→ // We don't know the channel type here, so use interface{}
1896→ // Actually, we should use the same type as the original channel
1897→ // For now, let's just reference the original channel conditionally
1898→ g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
1899→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1900→ g.write(fmt.Sprintf("if "))
1901→ g.generateExpression(c.Guard) g.write(fmt.Sprintf("var _alt%d chan ", i))
// We don't know the channel type here, so use interface{}
// Actually, we should use the same type as the original channel
// For now, let's just reference the original channel conditionally
g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now // Look up the channel's element type
elemType := "int" // default fallback
if t, ok := g.chanElemTypes[c.Channel]; ok {
elemType = t
}
g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 19.429s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser (cached) ok github.com/codeassociates/occam2go/preproc (cached)
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
1→package main
2→
3→import (
4→ "sync"
5→)
6→
7→type _proto_STATE = bool
8→
9→type _proto_COMMAND interface {
10→ _is_COMMAND()
11→}
12→
13→type _proto_COMMAND_set_state struct {
14→ _0 bool
15→}
16→func (_proto_COMMAND_set_state) _is_COMMAND() {}
17→
18→type _proto_COMMAND_evolve struct{}
19→func (_proto_COMMAND_evolve) _is_COMMAND() {}
20→
21→type _proto_COMMAND_terminate struct{}
22→func (_proto_COMMAND_terminate) _is_COMMAND() {}
23→
24→type _proto_RESPONSE struct {
25→ _0 bool
26→ _1 bool
27→}
28→
29→var array_width int = 20
30→var array_height int = 20
31→var radius int = 1
32→var diameter int = ((2 * radius) + 1)
33→var neighbours int = ((diameter * diameter) - 1)
34→var number_of_cells int = (array_height * array_width)
35→var number_of_links int = (neighbours * number_of_cells)
36→var alive bool = true
37→var dead bool = !alive
38→var idle int = 0
39→var editing int = 1
40→var single_step int = 2
41→var free_running int = 3
42→var terminated int = 4
43→
44→func write_string(out chan byte, s []byte) {
45→ for i := 0; i < 0 + len(s); i++ {
46→ out <- s[i]
47→ }
48→}
49→
50→func write_small_int(out chan byte, n int) {
51→ if (n >= 100) {
52→ out <- byte(((n / 100) + int(byte(48))))
53→ out <- byte((((n / 10) % 10) + int(byte(48))))
54→ out <- byte(((n % 10) + int(byte(48))))
55→ } else if (n >= 10) {
56→ out <- byte(((n / 10) + int(byte(48))))
57→ out <- byte(((n % 10) + int(byte(48))))
58→ } else if true {
59→ out <- byte((n + int(byte(48))))
60→ }
61→}
62→
63→func broadcast_present_state(link [][][]chan _proto_STATE, x int, y int, state bool) {
64→ var wg sync.WaitGroup
65→ wg.Add(int(neighbours))
66→ for d := 0; d < 0 + neighbours; d++ {
67→ d := d
68→ go func() {
69→ defer wg.Done()
70→ link[x][y][d] <- state
71→ }()
72→ }
73→ wg.Wait()
74→}
75→
76→func calculate_next_state(link [][][]chan _proto_STATE, nx []int, ny []int, state bool, next_state *bool) {
77→ var count int
78→ _ = count
79→ state_of_neighbour := make([]bool, neighbours)
80→ var wg sync.WaitGroup
81→ wg.Add(int(neighbours))
82→ for d := 0; d < 0 + neighbours; d++ {
83→ d := d
84→ go func() {
85→ defer wg.Done()
86→ state_of_neighbour[d] = <-link[nx[d]][ny[d]][d]
87→ }()
88→ }
89→ wg.Wait()
90→ count = 0
91→ for d := 0; d < 0 + neighbours; d++ {
92→ if (state_of_neighbour[d] == alive) {
93→ count = (count + 1)
94→ } else if (state_of_neighbour[d] == dead) {
95→ // SKIP
96→ }
97→ }
98→ if (count < 2) {
99→ *next_state = dead
100→ } else if (count == 2) {
101→ *next_state = state
102→ } else if (count == 3) {
103→ *next_state = alive
104→ } else if (count > 3) {
105→ *next_state = dead
106→ }
107→}
108→
109→func cell(link [][][]chan _proto_STATE, x int, y int, nx []int, ny []int, control chan _proto_COMMAND, sense chan _proto_RESPONSE) {
110→ var state, not_finished bool
111→ _ = state
112→ _ = not_finished
113→ state = dead
114→ not_finished = true
115→ for not_finished {
116→ switch _v := (<-control).(type) {
117→ case _proto_COMMAND_set_state:
118→ state = _v._0
119→ // SKIP
120→ case _proto_COMMAND_evolve:
121→ var next_state bool
122→ _ = next_state
123→ var wg sync.WaitGroup
124→ wg.Add(2)
125→ go func() {
126→ defer wg.Done()
127→ broadcast_present_state(link, x, y, state)
128→ }()
129→ go func() {
130→ defer wg.Done()
131→ calculate_next_state(link, nx, ny, state, &next_state)
132→ sense <- _proto_RESPONSE{(state != next_state), next_state}
133→ }()
134→ wg.Wait()
135→ state = next_state
136→ case _proto_COMMAND_terminate:
137→ not_finished = false
138→ }
139→ }
140→}
141→
142→func clear_screen(terminal chan byte) {
143→ terminal <- byte(27)
144→ terminal <- byte(91)
145→ terminal <- byte(50)
146→ terminal <- byte(74)
147→}
148→
149→func move_cursor(terminal chan byte, x int, y int) {
150→ terminal <- byte(27)
151→ terminal <- byte(91)
152→ write_small_int(terminal, (y + 1))
153→ terminal <- byte(59)
154→ write_small_int(terminal, (x + 1))
155→ terminal <- byte(72)
156→}
157→
158→func initialize_display(screen chan byte) {
159→ clear_screen(screen)
160→}
161→
162→func clean_up_display(screen chan byte) {
163→ move_cursor(screen, 0, array_height)
164→}
165→
166→func display_state(screen chan byte, x int, y int, state bool) {
167→ move_cursor(screen, x, y)
168→ if (state == alive) {
169→ screen <- byte(42)
170→ } else if (state == dead) {
171→ screen <- byte(32)
172→ }
173→}
174→
175→func new_activity(char byte) int {
176→ var activity int
177→ _ = activity
178→ switch char {
179→ case byte(113), byte(81):
180→ activity = terminated
181→ case byte(115), byte(83):
182→ activity = idle
183→ case byte(101), byte(69):
184→ activity = editing
185→ case byte(114), byte(82):
186→ activity = free_running
187→ default:
188→ activity = single_step
189→ }
190→ return activity
191→}
192→
193→func display_activity(screen chan byte, activity int) {
194→ move_cursor(screen, (array_width + 1), (array_height / 2))
195→ switch activity {
196→ case idle:
197→ write_string(screen, []byte("Idle"))
198→ case editing:
199→ write_string(screen, []byte("Edit"))
200→ case single_step:
201→ write_string(screen, []byte("Step"))
202→ case free_running:
203→ write_string(screen, []byte("Busy"))
204→ case terminated:
205→ write_string(screen, []byte("Done"))
206→ }
207→}
208→
209→func generation(screen chan byte, control [][]chan _proto_COMMAND, sense [][]chan _proto_RESPONSE, active *bool) {
210→ var wg sync.WaitGroup
211→ wg.Add(int(array_width))
212→ for x := 0; x < 0 + array_width; x++ {
213→ x := x
214→ go func() {
215→ defer wg.Done()
216→ var wg sync.WaitGroup
217→ wg.Add(int(array_height))
218→ for y := 0; y < 0 + array_height; y++ {
219→ y := y
220→ go func() {
221→ defer wg.Done()
222→ control[x][y] <- _proto_COMMAND_evolve{}
223→ }()
224→ }
225→ wg.Wait()
226→ }()
227→ }
228→ wg.Wait()
229→ *active = false
230→ for x := 0; x < 0 + array_width; x++ {
231→ for y := 0; y < 0 + array_height; y++ {
232→ var changed, next_state bool
233→ _ = changed
234→ _ = next_state
235→ _tmp0 := <-sense[x][y]
236→ changed = _tmp0._0
237→ next_state = _tmp0._1
238→ if changed {
239→ display_state(screen, x, y, next_state)
240→ *active = true
241→ } else if !changed {
242→ // SKIP
243→ }
244→ }
245→ }
246→}
247→
248→func min(a int, b int) int {
249→ var min int
250→ _ = min
251→ if (a <= b) {
252→ min = a
253→ } else if (b <= a) {
254→ min = b
255→ }
256→ return min
257→}
258→
259→func max(a int, b int) int {
260→ var max int
261→ _ = max
262→ if (a >= b) {
263→ max = a
264→ } else if (b >= a) {
265→ max = b
266→ }
267→ return max
268→}
269→
270→func editor(keyboard chan byte, screen chan byte, control [][]chan _proto_COMMAND) {
271→ var x, y int
272→ _ = x
273→ _ = y
274→ var editing bool
275→ _ = editing
276→ x, y = (array_width / 2), (array_height / 2)
277→ editing = true
278→ for editing {
279→ var char byte
280→ _ = char
281→ move_cursor(screen, x, y)
282→ char = <-keyboard
283→ switch char {
284→ case byte(65):
285→ y = max((y - 1), 0)
286→ case byte(66):
287→ y = min((y + 1), (array_height - 1))
288→ case byte(67):
289→ x = min((x + 1), (array_width - 1))
290→ case byte(68):
291→ x = max((x - 1), 0)
292→ case byte(32), byte(42):
293→ var state bool = ((char == byte(42)) == alive)
294→ _ = state
295→ var wg sync.WaitGroup
296→ wg.Add(2)
297→ go func() {
298→ defer wg.Done()
299→ control[x][y] <- _proto_COMMAND_set_state{state}
300→ }()
301→ go func() {
302→ defer wg.Done()
303→ display_state(screen, x, y, state)
304→ }()
305→ wg.Wait()
306→ case byte(113), byte(81):
307→ editing = false
308→ default:
309→ // SKIP
310→ }
311→ }
312→}
313→
314→func controller(keyboard chan byte, screen chan byte, control [][]chan _proto_COMMAND, sense [][]chan _proto_RESPONSE) {
315→ var activity int
316→ _ = activity
317→ activity = idle
318→ initialize_display(screen)
319→ for (activity != terminated) {
320→ display_activity(screen, activity)
321→ var char byte
322→ _ = char
323→ var _alt0 chan byte = nil
324→ if (activity != editing) { _alt0 = keyboard }
325→ select {
326→ case char = <-_alt0:
327→ activity = new_activity(char)
328→ default:
329→ if (activity != idle) {
330→ switch activity {
331→ case editing:
332→ editor(keyboard, screen, control)
333→ activity = idle
334→ case free_running, single_step:
335→ var changing bool
336→ _ = changing
337→ generation(screen, control, sense, &changing)
338→ if ((activity == single_step) || !changing) {
339→ activity = idle
340→ } else if ((activity == free_running) && changing) {
341→ // SKIP
342→ }
343→ }
344→ }
345→ }
346→ }
347→ display_activity(screen, activity)
348→ var wg sync.WaitGroup
349→ wg.Add(int(array_width))
350→ for x := 0; x < 0 + array_width; x++ {
351→ x := x
352→ go func() {
353→ defer wg.Done()
354→ var wg sync.WaitGroup
355→ wg.Add(int(array_height))
356→ for y := 0; y < 0 + array_height; y++ {
357→ y := y
358→ go func() {
359→ defer wg.Done()
360→ control[x][y] <- _proto_COMMAND_terminate{}
361→ }()
362→ }
363→ wg.Wait()
364→ }()
365→ }
366→ wg.Wait()
367→ clean_up_display(screen)
368→}
369→
370→func main() {
371→ terminal_keyboard := make(chan byte)
372→ terminal_screen := make(chan byte)
373→ link := make([][][]chan _proto_STATE, array_width)
374→ for _i0 := range link {
375→ link[_i0] = make([][]chan _proto_STATE, array_height)
376→ for _i1 := range link[_i0] {
377→ link[_i0][_i1] = make([]chan _proto_STATE, neighbours)
378→ for _i2 := range link[_i0][_i1] { link[_i0][_i1][_i2] = make(chan _proto_STATE) }
379→ }
380→ }
381→ control := make([][]chan _proto_COMMAND, array_width)
382→ for _i0 := range control {
383→ control[_i0] = make([]chan _proto_COMMAND, array_height)
384→ for _i1 := range control[_i0] { control[_i0][_i1] = make(chan _proto_COMMAND) }
385→ }
386→ sense := make([][]chan _proto_RESPONSE, array_width)
387→ for _i0 := range sense {
388→ sense[_i0] = make([]chan _proto_RESPONSE, array_height)
389→ for _i1 := range sense[_i0] { sense[_i0][_i1] = make(chan _proto_RESPONSE) }
390→ }
391→ var wg sync.WaitGroup
392→ wg.Add(2)
393→ go func() {
394→ defer wg.Done()
395→ controller(terminal_keyboard, terminal_screen, control, sense)
396→ }()
397→ go func() {
398→ defer wg.Done()
399→ var wg sync.WaitGroup
400→ wg.Add(int(array_width))
401→ for x := 0; x < 0 + array_width; x++ {
402→ x := x
403→ go func() {
404→ defer wg.Done()
405→ var wg sync.WaitGroup
406→ wg.Add(int(array_height))
407→ for y := 0; y < 0 + array_height; y++ {
408→ y := y
409→ go func() {
410→ defer wg.Done()
411→ var left int = (((x - 1) + array_width) % array_width)
412→ _ = left
413→ var right int = ((x + 1) % array_width)
414→ _ = right
415→ var up int = ((y + 1) % array_height)
416→ _ = up
417→ var down int = (((y - 1) + array_height) % array_height)
418→ _ = down
419→ var nx []int = []int{right, x, left, left, left, x, right, right}
420→ _ = nx
421→ var ny []int = []int{down, down, down, y, up, up, up, y}
422→ _ = ny
423→ cell(link, x, y, nx, ny, control[x][y], sense[x][y])
424→ }()
425→ }
426→ wg.Wait()
427→ }()
428→ }
429→ wg.Wait()
430→ }()
431→ wg.Wait()
432→}
433→The generated Go code looks correct. Key things to verify:
var _alt0 chan byte = nil - fixed (was chan int)var nx []int = []int{...} - fixed (was var nx int)clear_screen and move_cursor use direct byte output instead of the missing library functionswrite_string and write_small_int are self-containedterminal_keyboard and terminal_screen are properly declared as channelsLet me also verify the course module still transpiles cleanly:
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go kroc/modules/course/libsrc/course.module 2>&1 && go vet /tmp/course_out.go 2>&1
Everything works. Here's a summary of what I did:
The Game of Life example (historical-examples/life.occ) now transpiles and compiles cleanly (go vet passes).
life.occThe program used functions from the "Programming in occam 2" book's standard library (write.string, write.formatted, DATA.ITEM protocol, terminal.keyboard, terminal.screen) which are different from the KRoC course module. I made these modifications:
write.string PROC — loops over bytes, replacing the book library's versionwrite.small.int PROC — outputs small integers as decimal digits for ANSI escape sequencesclear.screen — outputs ESC [2J directly as individual bytes instead of using write.string with an embedded escape charactermove.cursor — outputs the ANSI cursor positioning sequence directly using write.small.int, eliminating the dependency on write.formatted, DATA.ITEM protocol, and data.int variant tagterminal.keyboard and terminal.screen channel declarations — these were runtime-provided channels in the original occam 2 environmentVAL [n]TYPE abbreviations generated wrong Go type — VAL [8]INT nx IS [...] produced var nx int = []int{...} instead of var nx []int = []int{...}. Fixed by adding IsFixedArray flag to the AST Abbreviation struct and propagating it through parser → codegen.
ALT guard channels hardcoded to chan int — Guarded ALT cases created var _alt0 chan int = nil regardless of the actual channel type. Fixed by adding chanElemTypes tracking to the Generator, populated from channel declarations and proc parameters.
Both fixes apply to the package-level and function-scoped codegen paths.
The program compiles but won't run interactively since terminal.keyboard and terminal.screen are declared as plain Go channels with nothing connected to stdin/stdout. Making it fully runnable would require adding I/O goroutines to bridge these channels to the terminal.
Good work thanks. Interesting about the stdio channels. I think somehow we have support for that already because I've seen example programs that both print to stdout and react to input (e.g. there are programs in the examples for kroc's course module that do things like echo characters typed, and they work). Anyway, please create a PR for the work so far so we can get that into the mainline.
The user wants me to create a PR for the work done. Let me first check the current state of things - what's changed, create a branch, commit, and make a PR.
git -C /home/david/projects/code-associates/occam2go status
git -C /home/david/projects/code-associates/occam2go log --oneline -10
On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: ast/ast.go modified: codegen/codegen.go modified: historical-examples/life.occ modified: parser/parser.go no changes added to commit (use "git add" and/or "git commit -a")
diff --git a/ast/ast.go b/ast/ast.go
index 634f647..722e8d7 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -491,13 +491,14 @@ func (se *SliceExpr) TokenLiteral() string { return se.Token.Literal }
// Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42:
type Abbreviation struct {
- Token lexer.Token // VAL, INITIAL, or type token
- IsVal bool // true for VAL abbreviations
- IsInitial bool // true for INITIAL declarations
- IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
- Type string // "INT", "BYTE", "BOOL", etc.
- Name string // variable name
- Value Expression // the expression
+ Token lexer.Token // VAL, INITIAL, or type token
+ IsVal bool // true for VAL abbreviations
+ IsInitial bool // true for INITIAL declarations
+ IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
+ IsFixedArray bool // true for [n]TYPE abbreviations (e.g. VAL [8]INT)
+ Type string // "INT", "BYTE", "BOOL", etc.
+ Name string // variable name
+ Value Expression // the expression
}
func (a *Abbreviation) statementNode() {}
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 8d6e4c8..7814dab 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -35,6 +35,9 @@ type Generator struct {
recordDefs map[string]*ast.RecordDecl
recordVars map[string]string // variable name → record type name
+ // Channel element type tracking (for ALT guard codegen)
+ chanElemTypes map[string]string // channel name → Go element type
+
// Bool variable tracking (for type conversion codegen)
boolVars map[string]bool
@@ -106,6 +109,7 @@ func (g *Generator) Generate(program *ast.Program) string {
g.refParams = make(map[string]bool)
g.protocolDefs = make(map[string]*ast.ProtocolDecl)
g.chanProtocols = make(map[string]string)
+ g.chanElemTypes = make(map[string]string)
g.tmpCounter = 0
g.recordDefs = make(map[string]*ast.RecordDecl)
g.recordVars = make(map[string]string)
@@ -279,7 +283,7 @@ func (g *Generator) Generate(program *ast.Program) string {
g.write("\n")
} else {
goType := g.occamTypeToGo(abbr.Type)
- if abbr.IsOpenArray {
+ if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.builder.WriteString("var ")
@@ -1108,7 +1112,7 @@ func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
g.builder.WriteString(strings.Repeat("\t", g.indent))
if abbr.Type != "" {
goType := g.occamTypeToGo(abbr.Type)
- if abbr.IsOpenArray {
+ if abbr.IsOpenArray || abbr.IsFixedArray {
goType = "[]" + goType
}
g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
@@ -1132,6 +1136,9 @@ func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
+ for _, name := range decl.Names {
+ g.chanElemTypes[name] = goType
+ }
if len(decl.Sizes) > 0 {
for _, name := range decl.Names {
n := goIdent(name)
@@ -1884,11 +1891,12 @@ func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
for i, c := range alt.Cases {
if c.Guard != nil && !c.IsSkip {
g.builder.WriteString(strings.Repeat("\t", g.indent))
- g.write(fmt.Sprintf("var _alt%d chan ", i))
- // We don't know the channel type here, so use interface{}
- // Actually, we should use the same type as the original channel
- // For now, let's just reference the original channel conditionally
- g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
+ // Look up the channel's element type
+ elemType := "int" // default fallback
+ if t, ok := g.chanElemTypes[c.Channel]; ok {
+ elemType = t
+ }
+ g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("if "))
g.generateExpression(c.Guard)
@@ -2100,11 +2108,12 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
} else {
delete(newBoolVars, p.Name)
}
- // Register chan params with protocol mappings
+ // Register chan params with protocol mappings and element types
if p.IsChan || p.ChanArrayDims > 0 {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
g.chanProtocols[p.Name] = p.ChanElemType
}
+ g.chanElemTypes[p.Name] = g.occamTypeToGo(p.ChanElemType)
}
// Register record-typed params
if !p.IsChan {
diff --git a/historical-examples/life.occ b/historical-examples/life.occ
index d3997bf..ec635c9 100644
--- a/historical-examples/life.occ
+++ b/historical-examples/life.occ
@@ -4,6 +4,35 @@
--
-- The program in this chapter plays Life on a terminal screen.
--
+-- 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'))
+:
--
-- configuration constants
@@ -121,18 +150,24 @@ PROC cell([][][]CHAN OF STATE link,
--
PROC clear.screen(CHAN OF BYTE terminal)
- -- clear screen sequence for an ANSI terminal
- write.string(terminal, "*#1B[2J")
+ -- clear screen sequence for an ANSI terminal: ESC [ 2 J
+ SEQ
+ terminal ! BYTE #1B
+ terminal ! '['
+ terminal ! '2'
+ terminal ! 'J'
:
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
+ -- 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'
:
@@ -397,6 +432,8 @@ PROC controller(CHAN OF BYTE keyboard, screen,
-- structure of the program
--
+CHAN OF BYTE terminal.keyboard :
+CHAN OF BYTE terminal.screen :
[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 :
diff --git a/parser/parser.go b/parser/parser.go
index 9e90386..2ec9d5f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -417,12 +417,13 @@ func (p *Parser) parseAbbreviation() ast.Statement {
}
return &ast.Abbreviation{
- Token: token,
- IsVal: true,
- IsOpenArray: isOpenArray,
- Type: typeName,
- Name: name,
- Value: value,
+ Token: token,
+ IsVal: true,
+ IsOpenArray: isOpenArray,
+ IsFixedArray: isArray,
+ Type: typeName,
+ Name: name,
+ Value: value,
}
}3540129 Merge pull request #88 from codeassociates/fix/receive-indexed-target-85 58a1542 Support receive into indexed target variables (#85) 32dedd5 Merge pull request #87 from codeassociates/fix/variant-receive-scoped-decl-86 694dcc3 Support scoped declarations in variant receive case bodies (#86) 2bd8573 Merge pull request #83 from codeassociates/feature/pri-alt-par-76 b49c3b8 Add PRI ALT and PRI PAR support (#76) 5ded5ab Merge pull request #82 from codeassociates/fix/guarded-skip-alt-77 24dcefe Fix guarded SKIP in ALT to respect guard expression (#77) 27f95e8 Merge pull request #81 from codeassociates/feature/multi-dim-arrays 103a119 Add multi-dimensional array and channel array support
git -C /home/david/projects/code-associates/occam2go checkout -b fix/life-occ-compilation-72
git -C /home/david/projects/code-associates/occam2go add ast/ast.go codegen/codegen.go parser/parser.go historical-examples/life.occ
git commit -m "$(cat <<'EOF' Fix Game of Life example compilation (#72) Three transpiler bugs fixed: - VAL [n]TYPE abbreviations generated wrong Go type (int instead of []int) - ALT guard channels hardcoded to chan int regardless of actual channel type - Added chanElemTypes tracking to resolve channel element types from declarations and proc parameters Adapted life.occ to remove dependencies on the "Programming in occam 2" book standard library (write.string, write.formatted, DATA.ITEM protocol) by defining inline helper PROCs and rewriting ANSI escape output directly. Added terminal.keyboard/terminal.screen channel declarations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
4 files changed, 77 insertions(+), 29 deletions(-)
git -C /home/david/projects/code-associates/occam2go push -u origin fix/life-occ-compilation-72 2>&1
remote: remote: Create a pull request for 'fix/life-occ-compilation-72' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/fix/life-occ-compilation-72 remote: To associate-1.github.com:codeassociates/occam2go.git * [new branch] fix/life-occ-compilation-72 -> fix/life-occ-compilation-72 branch 'fix/life-occ-compilation-72' set up to track 'origin/fix/life-occ-compilation-72'.
gh pr create --title "Fix Game of Life example compilation (#72)" --body "$(cat <<'EOF'
## Summary
- **Fixed `VAL [n]TYPE` abbreviation codegen**: `VAL [8]INT nx IS [...]` was generating `var nx int = []int{...}` — now correctly generates `var nx []int = []int{...}`. Added `IsFixedArray` flag to AST `Abbreviation` struct, propagated through parser and both codegen paths.
- **Fixed ALT guard channel type**: Guarded ALT cases were hardcoding `var _alt0 chan int = nil` regardless of actual channel type. Added `chanElemTypes` map to track channel element types from declarations and proc parameters, used during ALT codegen.
- **Adapted `life.occ`**: Replaced dependencies on the "Programming in occam 2" book standard library (`write.string`, `write.formatted`, `DATA.ITEM` protocol, `data.int`) with inline helper PROCs and direct ANSI escape byte output. Added `terminal.keyboard`/`terminal.screen` channel declarations.
The program now transpiles and passes `go vet` cleanly.
## Test plan
- [x] All existing tests pass (`go test ./...`)
- [x] `./occam2go -o /tmp/life.go historical-examples/life.occ` transpiles without errors
- [x] `go vet /tmp/life.go` passes
- [x] Course module still transpiles and vets cleanly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"PR created: https://github.com/codeassociates/occam2go/pull/89
Regarding the stdio channels — you're right, there's likely existing support for that. The transpiler's built-in print procedures (write.full.string, etc.) map directly to fmt.Print, and the e2e tests that read input probably use a similar mechanism. The terminal.keyboard/terminal.screen channels in life.occ are a different pattern though — they're occam-style channels that the original transputer runtime provided as hardware-mapped I/O. Making them work would need goroutines bridging stdin/stdout to the Go channels. That could be a follow-up task if you want the program to actually run interactively.
Ok I head what you say and it sounds plausible, but it still doesn't jive with what I think I've seen actually running already. Fwiw the transputer never had direct terminal I/O -- that was always provided by some kind of runtime proxy to a program running on the host PC, since there was no actual I/O other than links on a transputer chip.
The user is pushing back on my claim about hardware-mapped I/O on transputers. They're right - transputers didn't have direct terminal I/O, it was always proxied through links. And they're saying they've seen programs that do terminal I/O already working with the transpiler. Let me investigate how existing example programs handle stdin/stdout I/O through channels, since the user says this already works.
You're right, I stand corrected on the transputer I/O. Let me look at how the existing examples actually handle terminal I/O — there must be a mechanism I'm not seeing.
{
"subagent_type": "Explore",
"prompt": "In the occam2go transpiler codebase, I need to understand how existing example programs handle terminal/stdin/stdout I/O through channels. The user says they've seen programs that print to stdout and react to keyboard input already working. \n\nSearch thoroughly for:\n1. Example .occ files in the examples/ directory or anywhere else that use keyboard/screen/terminal channels or do I/O\n2. How the codegen handles stdin/stdout — look for \"keyboard\", \"screen\", \"terminal\", \"stdin\", \"stdout\", \"bufio\", \"os.Stdin\", \"os.Stdout\" in codegen.go\n3. Any special handling in the transpiler for channel-based I/O (e.g. does it generate goroutines that bridge channels to stdin/stdout?)\n4. The `needBufio` flag in the Generator struct — what triggers it and what does it generate?\n5. Any e2e tests that test interactive I/O or channel-based I/O to stdout\n\nShow me the relevant code sections."
}[
{
"type": "text",
"text": "Excellent! Now I have all the information I need. Let me compile a comprehensive report with all the code sections:\n\n## Summary: Terminal/Stdin/Stdout I/O in occam2go\n\nBased on my thorough search of the codebase, here's how the occam2go transpiler handles terminal and stdin/stdout I/O:\n\n### 1. **Entry Point Detection (codegen.go, lines 395-414)**\n\nThe transpiler recognizes a standard **entry point PROC** with exactly 3 `CHAN OF BYTE` parameters:\n```go\n// findEntryProc looks for the last top-level PROC with the standard occam\n// entry point signature: exactly 3 CHAN OF BYTE params (keyboard?, screen!, error!).\nfunc (g *Generator) findEntryProc(procDecls []ast.Statement) *ast.ProcDecl {\n\tvar entry *ast.ProcDecl\n\tfor _, stmt := range procDecls {\n\t\tproc, ok := stmt.(*ast.ProcDecl)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif len(proc.Params) != 3 {\n\t\t\tcontinue\n\t\t}\n\t\tp0, p1, p2 := proc.Params[0], proc.Params[1], proc.Params[2]\n\t\tif p0.IsChan && p0.ChanElemType == \"BYTE\" && p0.ChanDir == \"?\" &&\n\t\t\tp1.IsChan && p1.ChanElemType == \"BYTE\" && p1.ChanDir == \"!\" &&\n\t\t\tp2.IsChan && p2.ChanElemType == \"BYTE\" && p2.ChanDir == \"!\" {\n\t\t\tentry = proc\n\t\t}\n\t}\n\treturn entry\n}\n```\n\n**Signature pattern**: `PROC name (CHAN BYTE keyboard?, screen!, error!)`\n- First param: `keyboard?` (input-only channel for stdin)\n- Second param: `screen!` (output-only channel for stdout)\n- Third param: `error!` (output-only channel for stderr)\n\n### 2. **needBufio Flag**\n\nThe `Generator` struct has a `needBufio` field that is automatically set to `true` when an entry proc is detected:\n\n```go\ntype Generator struct {\n\t...\n\tneedBufio bool // track if we need bufio package import\n\t...\n}\n\n// In Generate():\nif len(mainStatements) == 0 {\n entryProc = g.findEntryProc(procDecls)\n if entryProc != nil {\n g.needOs = true\n g.needSync = true\n g.needBufio = true // <-- Always true for entry procs\n }\n}\n```\n\n`needBufio` triggers the import of the `bufio` package. It's only set when an entry proc is found; there's no other mechanism that triggers it.\n\n### 3. **Entry Harness Generation (codegen.go, lines 417-511)**\n\nWhen an entry proc is detected, instead of generating a regular `func main()`, the transpiler generates `generateEntryHarness(proc)` which:\n\n#### Creates 3 buffered channels (256-byte capacity):\n```go\nkeyboard := make(chan byte, 256)\nscreen := make(chan byte, 256)\n_error := make(chan byte, 256)\n```\n\n#### Launches 3 goroutines to bridge stdin/stdout/stderr to channels:\n\n**Screen writer goroutine** (stdout):\n```go\ngo func() {\n defer wg.Done()\n w := bufio.NewWriter(os.Stdout)\n for b := range screen {\n if b == 255 { // 255 is a flush sentinel\n w.Flush()\n } else {\n w.WriteByte(b)\n }\n }\n w.Flush()\n}()\n```\n\n**Error writer goroutine** (stderr):\n```go\ngo func() {\n defer wg.Done()\n w := bufio.NewWriter(os.Stderr)\n for b := range _error {\n if b == 255 { // 255 is a flush sentinel\n w.Flush()\n } else {\n w.WriteByte(b)\n }\n }\n w.Flush()\n}()\n```\n\n**Keyboard reader goroutine** (stdin):\n```go\ngo func() {\n r := bufio.NewReader(os.Stdin)\n for {\n b, err := r.ReadByte()\n if err != nil {\n close(keyboard)\n return\n }\n keyboard <- b\n }\n}()\n```\n\n#### Calls the entry proc:\n```go\nhello(keyboard, screen, _error)\n```\n\n#### Cleanup:\n```go\nclose(screen)\nclose(_error)\nwg.Wait() // Wait for writer goroutines to drain\n```\n\n### 4. **Channel Send/Receive Generation**\n\n**Basic send**: `screen ! 65` becomes:\n```go\nscreen <- 65\n```\n\n**Basic receive**: `keyboard ? x` becomes:\n```go\nx = <-keyboard\n```\n\nThese are in `generateSend()` (codegen.go, ~line 1293) and `generateReceive()` (codegen.go, lines 1344-1377).\n\nFor **sequential (multi-value) sends/receives** and **variant sends/receives**, the transpiler wraps values in protocol structs (see lines 1293-1343).\n\n### 5. **Example Programs**\n\n**`/home/david/projects/code-associates/occam2go/examples/course_hello.occ`** — Demonstrates the standard pattern:\n```occam\n#INCLUDE \"course.module\"\n\nPROC hello (CHAN BYTE keyboard?, screen!, error!)\n SEQ\n out.string (\"Hello from occam2go!*c*n\", 0, screen!)\n out.string (\"The answer is: \", 0, screen!)\n out.int (42, 0, screen!)\n ...\n:\n```\n\n**`/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/hello_world.occ`** — Simpler example:\n```occam\n#INCLUDE \"course.module\"\n\nPROC hello.world (CHAN BYTE keyboard?, screen!, error!)\n out.string (\"Hello World*c*n\", 0, screen!)\n:\n```\n\n**`/home/david/projects/code-associates/occam2go/historical-examples/life.occ`** — Complex example using channels for screen control:\n- Contains `PROC controller(CHAN OF BYTE keyboard, screen, ...)`\n- Uses channels to read from keyboard and write to screen\n- Uses ANSI escape sequences (e.g., `terminal ! BYTE #1B` for ESC)\n- Demonstrates interactive terminal I/O with select-based input handling (PRI ALT)\n\n### 6. **No Interactive I/O Tests Found**\n\nThe e2e test suite (`e2e_course_test.go`) has a test that transpiles and runs the hello_world example, but it doesn't test **interactive** input (no stdin piping). The test simply runs the executable and checks stdout:\n```go\nfunc TestE2E_HelloWorldCourseModule(t *testing.T) {\n ...\n output := transpileCompileRunWithDefines(t, mainFile, []string{includeDir}, defines)\n expected := \"Hello World\\r\\n\"\n if output != expected { ... }\n}\n```\n\n### Key Insights\n\n1. **No special library code**: The transpiler doesn't generate any custom keyboard/screen goroutines in the Occam code itself. The bridge is entirely in the generated Go harness.\n\n2. **Byte-level I/O**: All I/O is at the byte level. String I/O and formatted output (like `out.string`, `out.int`) are provided by the course module library.\n\n3. **Buffering**: `bufio.NewWriter` ensures efficient output; byte value 255 is used as a flush signal.\n\n4. **Channel-based abstraction**: The Occam program never directly touches `os.Stdin`, `os.Stdout`, or `os.Stderr` — it only sends/receives on CHAN BYTE parameters.\n\n5. **The keyboard reading goroutine** doesn't use `wg.Done()` because it runs indefinitely until EOF, at which point it closes the channel. The WaitGroup only waits for the two writer goroutines."
},
{
"type": "text",
"text": "agentId: a4ac70dd1c61e58c9 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 43778\ntool_uses: 33\nduration_ms: 52552</usage>"
}
]I would look at the kroc example at ./kroc/modules/course/examples/double_echo.occ -- it looks like the course module has a way to pass in already open channels. If you transpile and run that example I think you'll see that it does in fact echo stdin to stdout. To make the game of life example use this pattern presumably it would need to be slightly re-written to use that same inversion of control pattern.
{
"file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/double_echo.occ"
} 1→#INCLUDE "course.module"
2→
3→PROC double.echo (CHAN BYTE keyboard?, screen!, error!)
4→ --{{{
5→ BYTE ch:
6→ SEQ
7→ ch := ' '
8→ WHILE ch <> 'Z'
9→ SEQ
10→ keyboard ? ch
11→ screen ! ch
12→ screen ! ch
13→ screen ! FLUSH
14→ screen ! '*c'
15→ screen ! '*n'
16→ --}}}
17→:
18→Right — the pattern is clear. PROC name (CHAN BYTE keyboard?, screen!, error!) is the standard entry point signature, and the transpiler already generates a harness that bridges those channels to stdin/stdout/stderr. Let me verify it works:
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/double_echo.go kroc/modules/course/examples/double_echo.occ 2>&1
Works perfectly. So the fix for life.occ is to wrap its main body in a PROC life (CHAN BYTE keyboard?, screen!, error!) and wire terminal.keyboard/terminal.screen to those parameters instead of declaring them as standalone channels. Let me update the program.
{
"file_path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"offset": 428,
"limit": 30
}428→ 429→ 430→ 431→-- 432→-- structure of the program 433→-- 434→ 435→CHAN OF BYTE terminal.keyboard : 436→CHAN OF BYTE terminal.screen : 437→[array.width][array.height][neighbours]CHAN OF STATE link : 438→[array.width][array.height]CHAN OF COMMAND control : 439→[array.width][array.height]CHAN OF RESPONSE sense : 440→PAR 441→ controller(terminal.keyboard, terminal.screen, control, sense) 442→ PAR x = 0 FOR array.width 443→ PAR y = 0 FOR array.height 444→ VAL INT left IS ((x - 1) + array.width) \ array.width : 445→ VAL INT right IS (x + 1) \ array.width : 446→ VAL INT up IS (y + 1) \ array.height : 447→ VAL INT down IS ((y - 1) + array.height) \ array.height : 448→ VAL [neighbours]INT nx IS 449→ [ right, x, left, left, left, x, right, right ] : 450→ VAL [neighbours]INT ny IS 451→ [ down, down, down, y, up, up, up, y ] : 452→ cell(link, x, y, nx, ny, control[x][y], sense[x][y]) 453→ 454→
--
-- structure of the program
--
CHAN OF BYTE terminal.keyboard :
CHAN OF BYTE terminal.screen :
[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])--
-- structure of the program
--
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])
:The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
Exit code 1 # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:395:14: cannot use keyboard (variable of type <-chan byte) as chan byte value in argument to controller # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:395:14: cannot use keyboard (variable of type <-chan byte) as chan byte value in argument to controller
The entry harness generates directional channels (<-chan byte for keyboard), but controller expects plain chan byte. The issue is that controller's params aren't direction-annotated. Let me check:
{
"pattern": "PROC controller\"",
"path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"output_mode": "content",
"-n": true
}{
"pattern": "PROC controller\"",
"path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"output_mode": "content",
"-n": true,
"-i": true
}{
"pattern": "PROC controller",
"path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"output_mode": "content",
"-n": true
}{
"file_path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"offset": 384,
"limit": 4
}384→PROC controller(CHAN OF BYTE keyboard, screen, 385→ [][]CHAN OF COMMAND control, 386→ [][]CHAN OF RESPONSE sense ) 387→ INT activity :
The controller PROC takes CHAN OF BYTE keyboard, screen without direction annotations. Since the entry point passes keyboard as <-chan byte (receive-only), it can't be passed to a function expecting bidirectional chan byte. The original book code didn't use direction annotations because that's the occam 2 style. I need to add them to controller and editor (and any other proc that takes these channels):
{
"pattern": "PROC .*(CHAN OF BYTE|CHAN BYTE)\"",
"path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"output_mode": "content",
"-n": true
}{
"pattern": "PROC.*CHAN OF BYTE",
"path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
"output_mode": "content",
"-n": true
}16:PROC write.string(CHAN OF BYTE out, VAL []BYTE s) 21:PROC write.small.int(CHAN OF BYTE out, VAL INT n) 152:PROC clear.screen(CHAN OF BYTE terminal) 161:PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y) 180:PROC initialize.display(CHAN OF BYTE screen) 185:PROC clean.up.display(CHAN OF BYTE screen) 189:PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state) 227:PROC display.activity(CHAN OF BYTE screen, VAL INT activity) 247:PROC generation(CHAN OF BYTE screen, 329:PROC editor(CHAN OF BYTE keyboard, screen, 384:PROC controller(CHAN OF BYTE keyboard, screen,
All the PROCs that take CHAN OF BYTE need direction annotations. The keyboard param is always read from (?), and screen/terminal/out are always written to (!). Let me add them:
PROC write.string(CHAN OF BYTE out, VAL []BYTE s)
PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC write.small.int(CHAN OF BYTE out, VAL INT n)
PROC write.small.int(CHAN OF BYTE out!, VAL INT n)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC clear.screen(CHAN OF BYTE terminal)
PROC clear.screen(CHAN OF BYTE terminal!)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC initialize.display(CHAN OF BYTE screen)
PROC initialize.display(CHAN OF BYTE screen!)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC clean.up.display(CHAN OF BYTE screen)
PROC clean.up.display(CHAN OF BYTE screen!)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
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)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
PROC display.activity(CHAN OF BYTE screen!, VAL INT activity)
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC generation(CHAN OF BYTE screen,
PROC generation(CHAN OF BYTE screen!,
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC editor(CHAN OF BYTE keyboard, screen,
PROC editor(CHAN OF BYTE keyboard?, screen!,
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
PROC controller(CHAN OF BYTE keyboard, screen,
PROC controller(CHAN OF BYTE keyboard?, screen!,
The file /home/david/projects/code-associates/occam2go/historical-examples/life.occ has been updated successfully.
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
Exit code 1 # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:326:38: cannot use keyboard (variable of type <-chan byte) as chan byte value in assignment # command-line-arguments # [command-line-arguments] vet: /tmp/life.go:326:38: cannot use keyboard (variable of type <-chan byte) as chan byte value in assignment
314→}
315→
316→func controller(keyboard <-chan byte, screen chan<- byte, control [][]chan _proto_COMMAND, sense [][]chan _proto_RESPONSE) {
317→ var activity int
318→ _ = activity
319→ activity = idle
320→ initialize_display(screen)
321→ for (activity != terminated) {
322→ display_activity(screen, activity)
323→ var char byte
324→ _ = char
325→ var _alt0 chan byte = nil
326→ if (activity != editing) { _alt0 = keyboard }
327→ select {
328→ case char = <-_alt0:
329→ activity = new_activity(char)
330→ default:
331→ if (activity != idle) {
332→ switch activity {
333→ case editing:The issue is that in the ALT guard codegen, we create var _alt0 chan byte = nil (bidirectional channel), but keyboard is <-chan byte (receive-only). You can't assign a <-chan byte to a chan byte. The fix is to use the correct directional channel type for the guard variable. Since this is an ALT (receive), the guard variable should be <-chan byte when the original channel is receive-only.
I need to update the ALT guard codegen to use the correct channel direction. When the channel parameter has a direction annotation, the guard variable should match.
Looking at how direction is tracked, the proc params store ChanDir as "?" for receive-only, and the codegen translates CHAN OF BYTE keyboard? to keyboard <-chan byte. So the guard variable needs to match that directional type. Since nil works as a value for any channel type including directional ones, I should make the guard variable <-chan elemType instead of the bidirectional chan elemType.
But I need to handle both cases: if the original channel is bidirectional chan byte, the guard should be chan byte, and if it's receive-only <-chan byte, the guard should be <-chan byte. The guard variable needs to be assignment-compatible with whatever the original channel's type is.
Actually, since ALT always receives, I can declare the guard as <-chan elemType universally — a bidirectional channel can be implicitly converted to receive-only, so this works for both cases.
The ALT guard variable is chan byte (bidirectional) but the source channel is <-chan byte (receive-only). Since ALT always receives, the guard should use <-chan:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1890,
"limit": 12
} 1890→ // Generate channel variables for guarded cases
1891→ for i, c := range alt.Cases {
1892→ if c.Guard != nil && !c.IsSkip {
1893→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1894→ // Look up the channel's element type
1895→ elemType := "int" // default fallback
1896→ if t, ok := g.chanElemTypes[c.Channel]; ok {
1897→ elemType = t
1898→ }
1899→ g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))
1900→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1901→ g.write(fmt.Sprintf("if "))Since ALT cases always receive, using <-chan is universally correct — a bidirectional chan byte can be assigned to <-chan byte, and a receive-only <-chan byte can too.
g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType)) g.write(fmt.Sprintf("var _alt%d <-chan %s = nil\n", i, elemType))The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 19.121s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser (cached) ok github.com/codeassociates/occam2go/preproc (cached)
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1 && go vet /tmp/life.go 2>&1
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go kroc/modules/course/libsrc/course.module 2>&1 && go vet /tmp/course_out.go 2>&1
git -C /home/david/projects/code-associates/occam2go diff
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 7814dab..4211f74 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -1896,7 +1896,7 @@ func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
if t, ok := g.chanElemTypes[c.Channel]; ok {
elemType = t
}
- g.write(fmt.Sprintf("var _alt%d chan %s = nil\n", i, elemType))
+ g.write(fmt.Sprintf("var _alt%d <-chan %s = nil\n", i, elemType))
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("if "))
g.generateExpression(c.Guard)
diff --git a/historical-examples/life.occ b/historical-examples/life.occ
index ec635c9..69b738b 100644
--- a/historical-examples/life.occ
+++ b/historical-examples/life.occ
@@ -13,12 +13,12 @@
-- helper procedures (replaces book standard library)
--
-PROC write.string(CHAN OF BYTE out, VAL []BYTE s)
+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)
+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
@@ -149,7 +149,7 @@ PROC cell([][][]CHAN OF STATE link,
-- terminal-dependent output routines
--
-PROC clear.screen(CHAN OF BYTE terminal)
+PROC clear.screen(CHAN OF BYTE terminal!)
-- clear screen sequence for an ANSI terminal: ESC [ 2 J
SEQ
terminal ! BYTE #1B
@@ -158,7 +158,7 @@ PROC clear.screen(CHAN OF BYTE terminal)
terminal ! 'J'
:
-PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
+PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)
-- left-handed co-ordinates, origin 0,0 at top left
-- outputs ANSI escape sequence: ESC [ row ; col H
SEQ
@@ -177,16 +177,16 @@ PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
-- display routines
--
-PROC initialize.display(CHAN OF BYTE screen)
+PROC initialize.display(CHAN OF BYTE screen!)
-- display an entirely dead board
clear.screen(screen)
:
-PROC clean.up.display(CHAN OF BYTE 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)
+PROC display.state(CHAN OF BYTE screen!, VAL INT x, y, VAL BOOL state)
SEQ
move.cursor(screen, x, y)
IF
@@ -224,7 +224,7 @@ INT FUNCTION new.activity(VAL BYTE char)
RESULT activity
:
-PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
+PROC display.activity(CHAN OF BYTE screen!, VAL INT activity)
SEQ
move.cursor(screen, array.width+1, array.height/2)
CASE activity
@@ -244,7 +244,7 @@ PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
-- generation
--
-PROC generation(CHAN OF BYTE screen,
+PROC generation(CHAN OF BYTE screen!,
[][]CHAN OF COMMAND control,
[][]CHAN OF RESPONSE sense,
BOOL active )
@@ -326,7 +326,7 @@ INT FUNCTION max(VAL INT a, b)
-PROC editor(CHAN OF BYTE keyboard, screen,
+PROC editor(CHAN OF BYTE keyboard?, screen!,
[][]CHAN OF COMMAND control )
INT x, y :
BOOL editing :
@@ -381,7 +381,7 @@ PROC editor(CHAN OF BYTE keyboard, screen,
-- controller
--
-PROC controller(CHAN OF BYTE keyboard, screen,
+PROC controller(CHAN OF BYTE keyboard?, screen!,
[][]CHAN OF COMMAND control,
[][]CHAN OF RESPONSE sense )
INT activity :
@@ -432,22 +432,22 @@ PROC controller(CHAN OF BYTE keyboard, screen,
-- structure of the program
--
-CHAN OF BYTE terminal.keyboard :
-CHAN OF BYTE terminal.screen :
-[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])
+:git -C /home/david/projects/code-associates/occam2go add codegen/codegen.go historical-examples/life.occ && git -C /home/david/projects/code-associates/occam2go commit -m "$(cat <<'EOF' Use entry point PROC pattern for life.occ terminal I/O Wrap the main body in PROC life (CHAN BYTE keyboard?, screen!, error!) so the transpiler generates the stdin/stdout/stderr harness automatically. Add channel direction annotations to all PROCs that pass through the keyboard/screen channels. Fix ALT guard variables to use <-chan (receive-only) instead of chan (bidirectional), since ALT always receives. This allows directional channels from the entry harness to be assigned to guard variables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
2 files changed, 30 insertions(+), 30 deletions(-)
To associate-1.github.com:codeassociates/occam2go.git 126b182..5ab69cb fix/life-occ-compilation-72 -> fix/life-occ-compilation-72
Pushed. The PR at https://github.com/codeassociates/occam2go/pull/89 now has two commits:
VAL [n]TYPE abbreviation types, channel element type tracking for ALT guardsPROC life (CHAN BYTE keyboard?, screen!, error!) so the transpiler auto-generates the stdin/stdout harness, plus adds direction annotations (?/!) to all PROCs that pass through the keyboard/screen channels, and fixes ALT guard variables to use <-chan (receive-only) so directional channels can be assigned to themThanks for the correction on the transputer I/O and pointing me to double_echo.occ — the entry point detection pattern was already there, the program just needed to use it.