Claude Code transcript - page 1/2

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.

Thinking

Let me look at issue #72 and the game of life example to understand what's needed.

$ Bash
View issue #72 details
gh issue view 72
Glob
{
  "pattern": "**/game*of*life*"
}
Glob
{
  "pattern": "**/life*"
}
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
No files found
Read
{
  "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→
Thinking

This is a Game of Life program from the occam2 book. Let me analyze what it needs:

  1. It references write.string, write.formatted, data.int, data.int, terminal.keyboard, terminal.screen - these are from the course module/library
  2. It uses CHAN OF BYTE, CHAN OF DATA.ITEM - DATA.ITEM is from the course module
  3. It lacks #INCLUDE directives for the course module

Let 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.

$ Bash
Build the transpiler
go build -o occam2go . 2>&1
$ Bash
Try transpiling life.occ as-is
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
$ Bash
Check if transpiled Go code compiles
go vet /tmp/life.go 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.

Glob
{
  "pattern": "kroc/modules/course/libsrc/*.inc"
}
/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
Glob
{
  "pattern": "kroc/modules/course/libsrc/*.occ"
}
/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
Read
{
  "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→
Read
{
  "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→
Thinking

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.

Let me look at the generated Go code to understand what's undefined.

Read
{
  "file_path": "/tmp/life.go"
}
     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→
Thinking

I can see several issues in the generated Go code:

  1. write_string - undefined (line 124, 182-190)
  2. write_formatted - undefined (line 133)
  3. DATA.ITEM - undefined type (line 128: make(chan DATA.ITEM))
  4. 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)
  5. terminal_keyboard, terminal_screen - undefined (line 378)

  6. 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

  7. The PRI ALT with SKIP guard at line 363 translates to default: in a select, which makes it non-blocking instead of preserving the original semantics

I 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:

  1. Modify life.occ to be self-contained (no external library dependencies)
  2. Add the needed PROC definitions inline
  3. For terminal I/O, we'll need some kind of adapter - maybe I can add Go-level code after transpiling

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.

Task
Find abbreviation codegen for typed arrays
{
  "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>"
  }
]
Thinking

Great, now I understand the codegen bug. Let me fix the bugs and modify the life.occ source. Let me plan:

  1. Fix the VAL [n]TYPE abbreviation codegen bug (AST + parser + codegen)
  2. Modify life.occ to remove book-library dependencies

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.

Read
{
  "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
Read
{
  "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
Read
{
  "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("")
Read
{
  "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)

Now I have all the context. Let me fix the codegen bug and modify life.occ in parallel.

✏️ Edit ast.go
/home/david/projects/code-associates/occam2go/ast/ast.go
// 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.
✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
	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.

Now fix both codegen locations to handle IsFixedArray:

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
		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>
Read
{
  "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("")
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	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>
$ Bash
Check exact whitespace
sed -n '281,282p' /home/david/projects/code-associates/occam2go/codegen/codegen.go | cat -A
^I^I^IgoType := g.occamTypeToGo(abbr.Type)$
^I^I^Iif abbr.IsOpenArray {$
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
			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>
Read
{
  "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
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
			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 variables
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.

Now fix the function-scoped version:

Read
{
  "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)))
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	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:

$ Bash
Build transpiler with fixes
go build -o occam2go . 2>&1
$ Bash
Run all tests
go test ./... 2>&1
?   	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:

  1. write.string(chan, "str") - not in course module, define inline
  2. write.formatted(chan, fmt, data_chan) + DATA.ITEM protocol + data.int - complex formatted output, rewrite move.cursor directly
  3. terminal.keyboard / terminal.screen - runtime-provided channels, need declarations
Read
{
  "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:

✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
--  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.

Now replace clear.screen and move.cursor to not use write.formatted/DATA.ITEM:

✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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:

Read
{
  "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 ] :
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
--
--  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.

Now let's try to transpile and compile:

$ Bash
Transpile modified life.occ
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
$ Bash
Check if transpiled Go code compiles
go vet /tmp/life.go 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
Read
{
  "file_path": "/tmp/life.go",
  "offset": 318,
  "limit": 15
}
   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)
Thinking

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.

Grep
{
  "pattern": "chan int = nil",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content"
}
No matches found
Grep
{
  "pattern": "_alt\\d.*= nil",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content"
}
No matches found
Grep
{
  "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))
Read
{
  "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)
Thinking

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.

Grep
{
  "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()       {}
Thinking

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:

  1. Look at the variable type being received into - if it's declared as BYTE char, then the channel carries byte
  2. Track channel types in the codegen state
  3. Just use the variable declarations in the current scope

Actually, 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.

Grep
{
  "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
Read
{
  "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.

Grep
{
  "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
Thinking

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.

Read
{
  "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 {
Thinking

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.

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	// 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.

Now initialize it:

Grep
{
  "pattern": "g.chanProtocols = make",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
111:	g.chanProtocols = make(map[string]string)
Read
{
  "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)
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	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:

Read
{
  "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→		}

I'll add channel element type tracking to generateChanDecl:

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
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:

Read
{
  "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
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
		// 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.

Now use chanElemTypes in the ALT generation instead of hardcoded int:

Read
{
  "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))
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
			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>
$ Bash
Check exact whitespace on target lines
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))$

The indentation uses 4 tabs, not 3. Let me fix:

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
				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>
Read
{
  "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)
✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
				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.

Now build and test:

$ Bash
Build transpiler with ALT fix
go build -o occam2go . 2>&1
$ Bash
Run all tests
go test ./... 2>&1
?   	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)

All tests pass. Now let's try transpiling life.occ again:

$ Bash
Transpile modified life.occ
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
$ Bash
Check if transpiled Go code compiles
go vet /tmp/life.go 2>&1

It compiles cleanly now. Let me verify the generated Go code looks correct:

Read
{
  "file_path": "/tmp/life.go"
}
     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:

  • Line 323: var _alt0 chan byte = nil - fixed (was chan int)
  • Lines 419-421: var nx []int = []int{...} - fixed (was var nx int)
  • Lines 142-156: clear_screen and move_cursor use direct byte output instead of the missing library functions
  • Lines 44-61: write_string and write_small_int are self-contained
  • Lines 371-372: terminal_keyboard and terminal_screen are properly declared as channels

Let me also verify the course module still transpiles cleanly:

$ Bash
Verify course module still transpiles and vets
./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:

Results

The Game of Life example (historical-examples/life.occ) now transpiles and compiles cleanly (go vet passes).

Source code changes to life.occ

The 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:

  1. Added inline write.string PROC — loops over bytes, replacing the book library's version
  2. Added write.small.int PROC — outputs small integers as decimal digits for ANSI escape sequences
  3. Rewrote clear.screen — outputs ESC [2J directly as individual bytes instead of using write.string with an embedded escape character
  4. Rewrote move.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 tag
  5. Added terminal.keyboard and terminal.screen channel declarations — these were runtime-provided channels in the original occam 2 environment

Transpiler bug fixes (3 bugs)

  1. VAL [n]TYPE abbreviations generated wrong Go typeVAL [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.

  2. 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.

  3. Both fixes apply to the package-level and function-scoped codegen paths.

Note

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.

Thinking

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.

$ Bash
Show working tree status
git -C /home/david/projects/code-associates/occam2go status
$ Bash
Show all unstaged changes
git -C /home/david/projects/code-associates/occam2go diff
$ Bash
Show recent commits
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
$ Bash
Create feature branch
git -C /home/david/projects/code-associates/occam2go checkout -b fix/life-occ-compilation-72
Switched to a new branch 'fix/life-occ-compilation-72'
$ Bash
Stage changed files
git -C /home/david/projects/code-associates/occam2go add ast/ast.go codegen/codegen.go parser/parser.go historical-examples/life.occ
$ Bash
Create commit
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(-)
$ Bash
Push branch to remote
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'.
$ Bash
Create pull request
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
)"
https://github.com/codeassociates/occam2go/pull/89

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.

Thinking

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.

Task
Find how examples handle terminal I/O
{
  "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>"
  }
]

[Request interrupted by user]

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.

Read
{
  "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:

$ Bash
Transpile double_echo example
./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
$ Bash
Run double_echo with piped input
echo "abcZ" | go run /tmp/double_echo.go 2>&1
aabbccZZ

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.

Read
{
  "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→
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
--
--  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.

Now transpile and check:

$ Bash
Transpile updated life.occ
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
$ Bash
Check if it compiles
go vet /tmp/life.go 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:

Grep
{
  "pattern": "PROC controller\"",
  "path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
  "output_mode": "content",
  "-n": true
}
No matches found
Grep
{
  "pattern": "PROC controller\"",
  "path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
  "output_mode": "content",
  "-n": true,
  "-i": true
}
No matches found
Grep
{
  "pattern": "PROC controller",
  "path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
  "output_mode": "content",
  "-n": true
}
384:PROC controller(CHAN OF BYTE keyboard, screen,
Read
{
  "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):

Grep
{
  "pattern": "PROC .*(CHAN OF BYTE|CHAN BYTE)\"",
  "path": "/home/david/projects/code-associates/occam2go/historical-examples/life.occ",
  "output_mode": "content",
  "-n": true
}
No matches found
Grep
{
  "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:

✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.
✏️ Edit life.occ
/home/david/projects/code-associates/occam2go/historical-examples/life.occ
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.

Now transpile and check:

$ Bash
Transpile life.occ with direction annotations
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1
$ Bash
Check compilation
go vet /tmp/life.go 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
Read
{
  "file_path": "/tmp/life.go",
  "offset": 314,
  "limit": 20
}
   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:
Thinking

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:

Read
{
  "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.

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
				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.
$ Bash
Build and run all tests
go build -o occam2go . 2>&1 && go test ./... 2>&1
?   	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)

All tests pass. Now try life.occ again:

$ Bash
Transpile and vet life.occ
./occam2go -o /tmp/life.go historical-examples/life.occ 2>&1 && go vet /tmp/life.go 2>&1

Compiles cleanly. Let me also verify the course module still works:

$ Bash
Verify course module still works
./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

Good. Now let me amend the commit on the PR branch with these additional changes:

$ Bash
Show changes since last commit
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])
+:
$ Bash
Commit entry point and direction fixes
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(-)
$ Bash
Push to remote
git -C /home/david/projects/code-associates/occam2go push 2>&1
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:

  1. The transpiler fixesVAL [n]TYPE abbreviation types, channel element type tracking for ALT guards
  2. The entry point pattern — wraps life.occ's main body in PROC 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 them

Thanks 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.