Claude Code transcript - page 1/2

Please take a look at issue #94 and if you are able to create a fix please make a PR for it.

Let me start by looking at the issue.

$ Bash
View GitHub issue #94
gh issue view 94
title:	Replace historical example source file with download and modify
state:	OPEN
author:	dboreham
labels:	
comments:	0
assignees:	
projects:	
milestone:	
number:	94
--
Currently we have a source file in the repository that was copied from the web site for a book. It is an example program from the book. The file is at: `./historical-examples/life.occ`. The original book web site url for the file is here: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt. In order to make the program build and run within the transpiler environment and kroc course module it was necessary to modify it slightly. In addition there are constants for the board size (`array.width` and `array.height`). These have been set to specific values so the program parses.

The file is subject to copyright and although the book web site allows use for some purposes, it would be better not to have it copied as-is into this repository. Therefore it would be better to instead do the following with a new script:

1. Download the book version of the life program from its URL, naming it life.occ.
2. Apply changes to that file such that it matches the current in-repo version of life.occ.

Once the script is working the repo copy of life.occ can be removed.

The end result should be that:

1. There's no copy of the book example program in this repository.
2. The user can get the life.occ example as it currently exists in the repository, by running the script.

Take note that a simple context diff patch may not give the desired outcome -- it will make the correct file contents but the patch file may end up including a significant amount of the original copyrighted file, defeating the objective to have none of that material in this repo. Note also that the original file hasn't changed since 1988 and therefore any patching/editing done can safely assume that the file will never change. This may help to remove the need for context in the patch.
Thinking

Let me understand the issue:

  1. There's a copyrighted file ./historical-examples/life.occ from a book
  2. The original is at https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt
  3. We need to create a script that downloads the original and applies modifications to match the current repo version
  4. Then remove the file from the repo
  5. The patch/script shouldn't contain significant portions of the copyrighted text

Let me look at both files to understand the differences.

Let me examine the current file and download the original to understand the differences.

Read
{
  "file_path": "/home/david/projects/codeassociates/occam2go/historical-examples/life.occ"
}
     1→--  Code copied from Programming in occam®2
     2→--  © Geraint Jones, Michael Goldsmith 1988, 2001.
     3→--  Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk
     4→--
     5→--  The program in this chapter plays Life on a terminal screen.
     6→--
     7→--  Adapted for occam2go: replaced book-library functions
     8→--  (write.string, write.formatted, DATA.ITEM) with inline
     9→--  definitions; added terminal.keyboard/terminal.screen declarations.
    10→--
    11→
    12→--
    13→--  helper procedures (replaces book standard library)
    14→--
    15→
    16→PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)
    17→  SEQ i = 0 FOR SIZE s
    18→    out ! s[i]
    19→:
    20→
    21→PROC write.small.int(CHAN OF BYTE out!, VAL INT n)
    22→  -- outputs a small non-negative integer (0..999) as decimal digits
    23→  IF
    24→    n >= 100
    25→      SEQ
    26→        out ! BYTE ((n / 100) + (INT '0'))
    27→        out ! BYTE (((n / 10) \ 10) + (INT '0'))
    28→        out ! BYTE ((n \ 10) + (INT '0'))
    29→    n >= 10
    30→      SEQ
    31→        out ! BYTE ((n / 10) + (INT '0'))
    32→        out ! BYTE ((n \ 10) + (INT '0'))
    33→    TRUE
    34→      out ! BYTE (n + (INT '0'))
    35→:
    36→
    37→--
    38→--  configuration constants
    39→--
    40→
    41→VAL INT array.width  IS 20 :   -- number of cells across the board
    42→VAL INT array.height IS 20 :   -- number of cells down the board
    43→
    44→VAL INT radius     IS 1 :       -- of the `sphere of influence'
    45→VAL INT diameter   IS (2 * radius) + 1 :
    46→VAL INT neighbours IS (diameter * diameter) - 1 :
    47→
    48→VAL INT number.of.cells IS array.height * array.width :
    49→VAL INT number.of.links IS neighbours * number.of.cells :
    50→
    51→--
    52→--  protocols
    53→--
    54→
    55→PROTOCOL STATE IS BOOL :
    56→
    57→VAL BOOL alive IS TRUE :
    58→VAL BOOL dead  IS NOT alive :
    59→
    60→PROTOCOL COMMAND
    61→  CASE
    62→    set.state; BOOL
    63→    evolve
    64→    terminate
    65→:
    66→
    67→PROTOCOL RESPONSE IS BOOL; BOOL :
    68→--
    69→--  cell processes
    70→--
    71→
    72→PROC broadcast.present.state([][][]CHAN OF STATE link,
    73→                             VAL INT x, y, VAL BOOL state )
    74→  PAR d = 0 FOR neighbours
    75→    link[x][y][d] ! state
    76→:
    77→
    78→PROC calculate.next.state([][][]CHAN OF STATE link,
    79→                          VAL []INT nx, ny,
    80→                          VAL BOOL state, BOOL next.state )
    81→  INT count :       -- number of living neighbours
    82→  SEQ
    83→    [neighbours]BOOL state.of.neighbour :
    84→    SEQ
    85→      PAR d = 0 FOR neighbours
    86→        link[nx[d]][ny[d]][d] ? state.of.neighbour[d]
    87→      count := 0
    88→      SEQ d = 0 FOR neighbours
    89→        IF
    90→          state.of.neighbour[d] = alive
    91→            count := count + 1
    92→          state.of.neighbour[d] = dead
    93→            SKIP
    94→    IF
    95→      count < 2     -- death from isolation
    96→        next.state := dead
    97→      count = 2     -- this cell is stable
    98→        next.state := state
    99→      count = 3     -- stable if alive, a birth if dead
   100→        next.state := alive
   101→      count > 3     -- death from overcrowding
   102→        next.state := dead
   103→:
   104→
   105→
   106→
   107→
   108→
   109→
   110→
   111→
   112→
   113→
   114→
   115→
   116→
   117→
   118→
   119→PROC cell([][][]CHAN OF STATE link,
   120→          VAL INT x, y, VAL []INT nx, ny,
   121→          CHAN OF COMMAND control,
   122→          CHAN OF RESPONSE sense         )
   123→  BOOL state, not.finished :
   124→  SEQ
   125→    state := dead       -- the whole board starts off dead
   126→    not.finished := TRUE
   127→    WHILE not.finished
   128→      control ? CASE
   129→
   130→        set.state; state
   131→          SKIP          -- state has been set to the new value
   132→
   133→        evolve
   134→          BOOL next.state :
   135→          SEQ
   136→            PAR
   137→              broadcast.present.state(link, x, y, state)
   138→              SEQ
   139→                calculate.next.state(link, nx, ny,
   140→                                     state, next.state )
   141→                sense ! (state <> next.state); next.state
   142→            state := next.state
   143→
   144→        terminate
   145→          not.finished := FALSE
   146→:
   147→
   148→--
   149→--  terminal-dependent output routines
   150→--
   151→
   152→PROC clear.screen(CHAN OF BYTE terminal!)
   153→  -- clear screen sequence for an ANSI terminal: ESC [ 2 J
   154→  SEQ
   155→    terminal ! BYTE #1B
   156→    terminal ! '['
   157→    terminal ! '2'
   158→    terminal ! 'J'
   159→:
   160→
   161→PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)
   162→  -- left-handed co-ordinates, origin 0,0 at top left
   163→  -- outputs ANSI escape sequence: ESC [ row ; col H
   164→  SEQ
   165→    terminal ! BYTE #1B
   166→    terminal ! '['
   167→    write.small.int(terminal, y + 1)
   168→    terminal ! ';'
   169→    write.small.int(terminal, x + 1)
   170→    terminal ! 'H'
   171→:
   172→
   173→
   174→
   175→
   176→--
   177→--  display routines
   178→--
   179→
   180→PROC initialize.display(CHAN OF BYTE screen!)
   181→  -- display an entirely dead board
   182→  clear.screen(screen)
   183→:
   184→
   185→PROC clean.up.display(CHAN OF BYTE screen!)
   186→  move.cursor(screen, 0, array.height)
   187→:
   188→
   189→PROC display.state(CHAN OF BYTE screen!, VAL INT x, y, VAL BOOL state)
   190→  SEQ
   191→    move.cursor(screen, x, y)
   192→    IF
   193→      state = alive
   194→        screen ! '**'
   195→      state = dead
   196→        screen ! '*s'
   197→:
   198→
   199→--
   200→--  controller states
   201→--
   202→
   203→VAL INT idle         IS 0 :  -- controller activity values
   204→VAL INT editing      IS 1 :
   205→VAL INT single.step  IS 2 :
   206→VAL INT free.running IS 3 :
   207→VAL INT terminated   IS 4 :
   208→
   209→
   210→INT FUNCTION new.activity(VAL BYTE char)
   211→  INT activity :
   212→  VALOF
   213→    CASE char     -- typed on the keyboard ...
   214→      'q', 'Q'              -- ... Q to finish program
   215→        activity := terminated
   216→      's', 'S'              -- ... S to halt evolution
   217→        activity := idle
   218→      'e', 'E'              -- ... E to start editing
   219→        activity := editing
   220→      'r', 'R'              -- ... R to start evolution
   221→        activity := free.running
   222→      ELSE  -- ... or anything else for one generation
   223→        activity := single.step
   224→    RESULT activity
   225→:
   226→
   227→PROC display.activity(CHAN OF BYTE screen!, VAL INT activity)
   228→  SEQ
   229→    move.cursor(screen, array.width+1, array.height/2)
   230→    CASE activity
   231→      idle
   232→        write.string(screen, "Idle")
   233→      editing
   234→        write.string(screen, "Edit")
   235→      single.step
   236→        write.string(screen, "Step")
   237→      free.running
   238→        write.string(screen, "Busy")
   239→      terminated
   240→        write.string(screen, "Done")
   241→:
   242→
   243→--
   244→--  generation
   245→--
   246→
   247→PROC generation(CHAN OF BYTE screen!,
   248→                [][]CHAN OF COMMAND control,
   249→                [][]CHAN OF RESPONSE sense,
   250→                BOOL active                 )
   251→  SEQ
   252→    PAR x = 0 FOR array.width
   253→      PAR y = 0 FOR array.height
   254→        control[x][y] ! evolve
   255→    active := FALSE
   256→    SEQ x = 0 FOR array.width
   257→      SEQ y = 0 FOR array.height
   258→        BOOL changed, next.state :
   259→        SEQ
   260→          sense[x][y] ? changed; next.state
   261→          IF
   262→            changed
   263→              SEQ
   264→                display.state(screen, x, y, next.state)
   265→                active := TRUE
   266→            NOT changed
   267→              SKIP
   268→:
   269→
   270→
   271→
   272→
   273→
   274→
   275→
   276→
   277→
   278→--
   279→--  editor
   280→--
   281→
   282→INT FUNCTION min(VAL INT a, b)
   283→  INT min :
   284→  VALOF
   285→    IF
   286→      a <= b
   287→        min := a
   288→      b <= a
   289→        min := b
   290→    RESULT min
   291→:
   292→
   293→INT FUNCTION max(VAL INT a, b)
   294→  INT max :
   295→  VALOF
   296→    IF
   297→      a >= b
   298→        max := a
   299→      b >= a
   300→        max := b
   301→    RESULT max
   302→:
   303→
   304→
   305→
   306→
   307→
   308→
   309→
   310→
   311→
   312→
   313→
   314→
   315→
   316→
   317→
   318→
   319→
   320→
   321→
   322→
   323→
   324→
   325→
   326→
   327→
   328→
   329→PROC editor(CHAN OF BYTE keyboard?, screen!,
   330→            [][]CHAN OF COMMAND control   )
   331→  INT x, y :
   332→  BOOL editing :
   333→  SEQ
   334→    -- initialize co-ordinates to centre of board
   335→    x, y := array.width / 2, array.height / 2
   336→    editing := TRUE
   337→    WHILE editing
   338→      BYTE char :
   339→      SEQ
   340→        move.cursor(screen, x, y)
   341→        keyboard ? char
   342→        CASE char
   343→          'A'        -- move up, if possible
   344→            y := max(y - 1, 0)
   345→          'B'        -- move down, if possible
   346→            y := min(y + 1, array.height - 1)
   347→          'C'        -- move right, if possible
   348→            x := min(x + 1, array.width - 1)
   349→          'D'        -- move left, if possible
   350→            x := max(x - 1, 0)
   351→          '*s', '**'
   352→            VAL BOOL state IS (char = '**') = alive :
   353→            PAR
   354→              control[x][y] ! set.state; state
   355→              display.state(screen, x, y, state)
   356→          'q', 'Q'
   357→            editing := FALSE
   358→          ELSE
   359→            SKIP     -- ignore anything else
   360→:
   361→
   362→
   363→
   364→
   365→
   366→
   367→
   368→
   369→
   370→
   371→
   372→
   373→
   374→
   375→
   376→
   377→
   378→
   379→
   380→--
   381→--  controller
   382→--
   383→
   384→PROC controller(CHAN OF BYTE keyboard?, screen!,
   385→                [][]CHAN OF COMMAND control,
   386→                [][]CHAN OF RESPONSE sense    )
   387→  INT activity :
   388→  SEQ
   389→    activity := idle
   390→    initialize.display(screen)
   391→    WHILE activity <> terminated
   392→      SEQ
   393→        display.activity(screen, activity)
   394→        BYTE char :
   395→        PRI ALT
   396→          (activity <> editing) & keyboard ? char
   397→            activity := new.activity(char)
   398→          (activity <> idle) & SKIP
   399→            CASE activity
   400→              editing
   401→                SEQ
   402→                  editor(keyboard, screen, control)
   403→                  activity := idle
   404→              free.running, single.step
   405→                BOOL changing :
   406→                SEQ
   407→                  generation(screen, control, sense, changing)
   408→                  IF
   409→                    (activity = single.step) OR (NOT changing)
   410→                      activity := idle
   411→                    (activity = free.running) AND changing
   412→                      SKIP
   413→    display.activity(screen, activity)
   414→    PAR x = 0 FOR array.width
   415→      PAR y = 0 FOR array.height
   416→        control[x][y] ! terminate
   417→    clean.up.display(screen)
   418→:
   419→
   420→
   421→
   422→
   423→
   424→
   425→
   426→
   427→
   428→
   429→
   430→
   431→--
   432→--  structure of the program
   433→--
   434→
   435→PROC life (CHAN BYTE keyboard?, screen!, error!)
   436→  [array.width][array.height][neighbours]CHAN OF STATE link :
   437→  [array.width][array.height]CHAN OF COMMAND control :
   438→  [array.width][array.height]CHAN OF RESPONSE sense :
   439→  PAR
   440→    controller(keyboard, screen, control, sense)
   441→    PAR x = 0 FOR array.width
   442→      PAR y = 0 FOR array.height
   443→        VAL INT left  IS ((x - 1) + array.width)  \ array.width  :
   444→        VAL INT right IS  (x + 1)                 \ array.width  :
   445→        VAL INT up    IS  (y + 1)                 \ array.height :
   446→        VAL INT down  IS ((y - 1) + array.height) \ array.height :
   447→        VAL [neighbours]INT nx IS
   448→              [ right, x,    left, left, left, x,  right, right ] :
   449→        VAL [neighbours]INT ny IS
   450→              [ down,  down, down, y,    up,   up, up,    y     ] :
   451→        cell(link, x, y, nx, ny, control[x][y], sense[x][y])
   452→:
   453→
   454→
WebFetch
{
  "url": "https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt",
  "prompt": "Return the COMPLETE raw text content of this file exactly as-is, preserving all whitespace, indentation, and line breaks. Do not summarize or modify anything."
}
$ Bash
List historical-examples directory
ls /home/david/projects/codeassociates/occam2go/historical-examples/
life.occ
# Complete Raw Text Content

```
--  The program in this chapter plays Life on a terminal screen.
--

--
--  configuration constants
--

VAL INT array.width  IS ... :   -- number of cells across the board
VAL INT array.height IS ... :   -- number of cells down the board

VAL INT radius     IS 1 :       -- of the `sphere of influence'
VAL INT diameter   IS (2 * radius) + 1 :
VAL INT neighbours IS (diameter * diameter) - 1 :

VAL INT number.of.cells IS array.height * array.width :
VAL INT number.of.links IS neighbours * number.of.cells :

--
--  protocols
--

PROTOCOL STATE IS BOOL :

VAL BOOL alive IS TRUE :
VAL BOOL dead  IS NOT alive :

PROTOCOL COMMAND
  CASE
    set.state; BOOL
    evolve
    terminate
:

PROTOCOL RESPONSE IS BOOL; BOOL :
--
--  cell processes
--

PROC broadcast.present.state([][][]CHAN OF STATE link,
                             VAL INT x, y, VAL BOOL state )
  PAR d = 0 FOR neighbours
    link[x][y][d] ! state
:

PROC calculate.next.state([][][]CHAN OF STATE link,
                          VAL []INT nx, ny,
                          VAL BOOL state, BOOL next.state )
  INT count :       -- number of living neighbours
  SEQ
    [neighbours]BOOL state.of.neighbour :
    SEQ
      PAR d = 0 FOR neighbours
        link[nx[d]][ny[d]][d] ? state.of.neighbour[d]
      count := 0
      SEQ d = 0 FOR neighbours
        IF
          state.of.neighbour[d] = alive
            count := count + 1
          state.of.neighbour[d] = dead
            SKIP
    IF
      count < 2     -- death from isolation
        next.state := dead
      count = 2     -- this cell is stable
        next.state := state
      count = 3     -- stable if alive, a birth if dead
        next.state := alive
      count > 3     -- death from overcrowding
        next.state := dead
:

PROC cell([][][]CHAN OF STATE link,
          VAL INT x, y, VAL []INT nx, ny,
          CHAN OF COMMAND control,
          CHAN OF RESPONSE sense         )
  BOOL state, not.finished :
  SEQ
    state := dead       -- the whole board starts off dead
    not.finished := TRUE
    WHILE not.finished
      control ? CASE

        set.state; state
          SKIP          -- state has been set to the new value

        evolve
          BOOL next.state :
          SEQ
            PAR
              broadcast.present.state(link, x, y, state)
              SEQ
                calculate.next.state(link, nx, ny,
                                     state, next.state )
                sense ! (state <> next.state); next.state
            state := next.state

        terminate
          not.finished := FALSE
:

--
--  terminal-dependent output routines
--

PROC clear.screen(CHAN OF BYTE terminal)
  -- clear screen sequence for an ANSI terminal
  write.string(terminal, "*#1B[2J")
:

PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
  -- left-handed co-ordinates, origin 0,0 at top left
  CHAN OF DATA.ITEM c :
  PAR
    write.formatted(terminal, "*#1B[%d;%dH", c)
    SEQ
      c ! data.int; y + 1
      c ! data.int; x + 1
:

--
--  display routines
--

PROC initialize.display(CHAN OF BYTE screen)
  -- display an entirely dead board
  clear.screen(screen)
:

PROC clean.up.display(CHAN OF BYTE screen)
  move.cursor(screen, 0, array.height)
:

PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state)
  SEQ
    move.cursor(screen, x, y)
    IF
      state = alive
        screen ! '**'
      state = dead
        screen ! '*s'
:

--
--  controller states
--

VAL INT idle         IS 0 :  -- controller activity values
VAL INT editing      IS 1 :
VAL INT single.step  IS 2 :
VAL INT free.running IS 3 :
VAL INT terminated   IS 4 :


INT FUNCTION new.activity(VAL BYTE char)
  INT activity :
  VALOF
    CASE char     -- typed on the keyboard ...
      'q', 'Q'              -- ... Q to finish program
        activity := terminated
      's', 'S'              -- ... S to halt evolution
        activity := idle
      'e', 'E'              -- ... E to start editing
        activity := editing
      'r', 'R'              -- ... R to start evolution
        activity := free.running
      ELSE  -- ... or anything else for one generation
        activity := single.step
    RESULT activity
:

PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
  SEQ
    move.cursor(screen, array.width+1, array.height/2)
    CASE activity
      idle
        write.string(screen, "Idle")
      editing
        write.string(screen, "Edit")
      single.step
        write.string(screen, "Step")
      free.running
        write.string(screen, "Busy")
      terminated
        write.string(screen, "Done")
:

--
--  generation
--

PROC generation(CHAN OF BYTE screen,
                [][]CHAN OF COMMAND control,
                [][]CHAN OF RESPONSE sense,
                BOOL active                 )
  SEQ
    PAR x = 0 FOR array.width
      PAR y = 0 FOR array.height
        control[x][y] ! evolve
    active := FALSE
    SEQ x = 0 FOR array.width
      SEQ y = 0 FOR array.height
        BOOL changed, next.state :
        SEQ
          sense[x][y] ? changed; next.state
          IF
            changed
              SEQ
                display.state(screen, x, y, next.state)
                active := TRUE
            NOT changed
              SKIP
:

--
--  editor
--

INT FUNCTION min(VAL INT a, b)
  INT min :
  VALOF
    IF
      a <= b
        min := a
      b <= a
        min := b
    RESULT min
:

INT FUNCTION max(VAL INT a, b)
  INT max :
  VALOF
    IF
      a >= b
        max := a
      b >= a
        max := b
    RESULT max
:

PROC editor(CHAN OF BYTE keyboard, screen,
            [][]CHAN OF COMMAND control   )
  INT x, y :
  BOOL editing :
  SEQ
    -- initialize co-ordinates to centre of board
    x, y := array.width / 2, array.height / 2
    editing := TRUE
    WHILE editing
      BYTE char :
      SEQ
        move.cursor(screen, x, y)
        keyboard ? char
        CASE char
          'A'        -- move up, if possible
            y := max(y - 1, 0)
          'B'        -- move down, if possible
            y := min(y + 1, array.height - 1)
          'C'        -- move right, if possible
            x := min(x + 1, array.width - 1)
          'D'        -- move left, if possible
            x := max(x - 1, 0)
          '*s', '**'
            VAL BOOL state IS (char = '**') = alive :
            PAR
              control[x][y] ! set.state; state
              display.state(screen, x, y, state)
          'q', 'Q'
            editing := FALSE
          ELSE
            SKIP     -- ignore anything else
:

--
--  controller
--

PROC controller(CHAN OF BYTE keyboard, screen,
                [][]CHAN OF COMMAND control,
                [][]CHAN OF RESPONSE sense    )
  INT activity :
  SEQ
    activity := idle
    initialize.display(screen)
    WHILE activity <> terminated
      SEQ
        display.activity(screen, activity)
        BYTE char :
        PRI ALT
          (activity <> editing) & keyboard ? char
            activity := new.activity(char)
          (activity <> idle) & SKIP
            CASE activity
              editing
                SEQ
                  editor(keyboard, screen, control)
                  activity := idle
              free.running, single.step
                BOOL changing :
                SEQ
                  generation(screen, control, sense, changing)
                  IF
                    (activity = single.step) OR (NOT changing)
                      activity := idle
                    (activity = free.running) AND changing
                      SKIP
    display.activity(screen, activity)
    PAR x = 0 FOR array.width
      PAR y = 0 FOR array.height
        control[x][y] ! terminate
    clean.up.display(screen)
:

--
--  structure of the program
--

[array.width][array.height][neighbours]CHAN OF STATE link :
[array.width][array.height]CHAN OF COMMAND control :
[array.width][array.height]CHAN OF RESPONSE sense :
PAR
  controller(terminal.keyboard, terminal.screen, control, sense)
  PAR x = 0 FOR array.width
    PAR y = 0 FOR array.height
      VAL INT left  IS ((x - 1) + array.width)  \ array.width  :
      VAL INT right IS  (x + 1)                 \ array.width  :
      VAL INT up    IS  (y + 1)                 \ array.height :
      VAL INT down  IS ((y - 1) + array.height) \ array.height :
      VAL [neighbours]INT nx IS
            [ right, x,    left, left, left, x,  right, right ] :
      VAL [neighbours]INT ny IS
            [ down,  down, down, y,    up,   up, up,    y     ] :
      cell(link, x, y, nx, ny, control[x][y], sense[x][y])
```
Thinking

Now I need to understand the exact differences. Let me download the original file properly and do a diff. The WebFetch might not have preserved exact formatting. Let me download it with curl.

Let me download the original file properly and diff them.

$ Bash
Download original life.occ source
curl -s -o /tmp/original-life.txt https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt && wc -l /tmp/original-life.txt
411 /tmp/original-life.txt
$ Bash
Diff original vs modified life.occ
diff /tmp/original-life.txt /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
0a1,4
> --  Code copied from Programming in occam®2
> --  © Geraint Jones, Michael Goldsmith 1988, 2001.
> --  Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk
> --
2a7,35
> --  Adapted for occam2go: replaced book-library functions
> --  (write.string, write.formatted, DATA.ITEM) with inline
> --  definitions; added terminal.keyboard/terminal.screen declarations.
> --
> 
> --
> --  helper procedures (replaces book standard library)
> --
> 
> PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)
>   SEQ i = 0 FOR SIZE s
>     out ! s[i]
> :
> 
> PROC write.small.int(CHAN OF BYTE out!, VAL INT n)
>   -- outputs a small non-negative integer (0..999) as decimal digits
>   IF
>     n >= 100
>       SEQ
>         out ! BYTE ((n / 100) + (INT '0'))
>         out ! BYTE (((n / 10) \ 10) + (INT '0'))
>         out ! BYTE ((n \ 10) + (INT '0'))
>     n >= 10
>       SEQ
>         out ! BYTE ((n / 10) + (INT '0'))
>         out ! BYTE ((n \ 10) + (INT '0'))
>     TRUE
>       out ! BYTE (n + (INT '0'))
> :
8,9c41,42
< VAL INT array.width  IS ... :   -- number of cells across the board
< VAL INT array.height IS ... :   -- number of cells down the board
---
> VAL INT array.width  IS 20 :   -- number of cells across the board
> VAL INT array.height IS 20 :   -- number of cells down the board
119,121c152,158
< PROC clear.screen(CHAN OF BYTE terminal)
<   -- clear screen sequence for an ANSI terminal
<   write.string(terminal, "*#1B[2J")
---
> PROC clear.screen(CHAN OF BYTE terminal!)
>   -- clear screen sequence for an ANSI terminal: ESC [ 2 J
>   SEQ
>     terminal ! BYTE #1B
>     terminal ! '['
>     terminal ! '2'
>     terminal ! 'J'
124c161
< PROC move.cursor(CHAN OF BYTE terminal, VAL INT x, y)
---
> PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)
126,131c163,170
<   CHAN OF DATA.ITEM c :
<   PAR
<     write.formatted(terminal, "*#1B[%d;%dH", c)
<     SEQ
<       c ! data.int; y + 1
<       c ! data.int; x + 1
---
>   -- outputs ANSI escape sequence: ESC [ row ; col H
>   SEQ
>     terminal ! BYTE #1B
>     terminal ! '['
>     write.small.int(terminal, y + 1)
>     terminal ! ';'
>     write.small.int(terminal, x + 1)
>     terminal ! 'H'
141c180
< PROC initialize.display(CHAN OF BYTE screen)
---
> PROC initialize.display(CHAN OF BYTE screen!)
146c185
< PROC clean.up.display(CHAN OF BYTE screen)
---
> PROC clean.up.display(CHAN OF BYTE screen!)
150c189
< PROC display.state(CHAN OF BYTE screen, VAL INT x, y, VAL BOOL state)
---
> PROC display.state(CHAN OF BYTE screen!, VAL INT x, y, VAL BOOL state)
188c227
< PROC display.activity(CHAN OF BYTE screen, VAL INT activity)
---
> PROC display.activity(CHAN OF BYTE screen!, VAL INT activity)
208c247
< PROC generation(CHAN OF BYTE screen,
---
> PROC generation(CHAN OF BYTE screen!,
290c329
< PROC editor(CHAN OF BYTE keyboard, screen,
---
> PROC editor(CHAN OF BYTE keyboard?, screen!,
345c384
< PROC controller(CHAN OF BYTE keyboard, screen,
---
> PROC controller(CHAN OF BYTE keyboard?, screen!,
396,411c435,453
< [array.width][array.height][neighbours]CHAN OF STATE link :
< [array.width][array.height]CHAN OF COMMAND control :
< [array.width][array.height]CHAN OF RESPONSE sense :
< PAR
<   controller(terminal.keyboard, terminal.screen, control, sense)
<   PAR x = 0 FOR array.width
<     PAR y = 0 FOR array.height
<       VAL INT left  IS ((x - 1) + array.width)  \ array.width  :
<       VAL INT right IS  (x + 1)                 \ array.width  :
<       VAL INT up    IS  (y + 1)                 \ array.height :
<       VAL INT down  IS ((y - 1) + array.height) \ array.height :
<       VAL [neighbours]INT nx IS
<             [ right, x,    left, left, left, x,  right, right ] :
<       VAL [neighbours]INT ny IS
<             [ down,  down, down, y,    up,   up, up,    y     ] :
<       cell(link, x, y, nx, ny, control[x][y], sense[x][y])
---
> PROC life (CHAN BYTE keyboard?, screen!, error!)
>   [array.width][array.height][neighbours]CHAN OF STATE link :
>   [array.width][array.height]CHAN OF COMMAND control :
>   [array.width][array.height]CHAN OF RESPONSE sense :
>   PAR
>     controller(keyboard, screen, control, sense)
>     PAR x = 0 FOR array.width
>       PAR y = 0 FOR array.height
>         VAL INT left  IS ((x - 1) + array.width)  \ array.width  :
>         VAL INT right IS  (x + 1)                 \ array.width  :
>         VAL INT up    IS  (y + 1)                 \ array.height :
>         VAL INT down  IS ((y - 1) + array.height) \ array.height :
>         VAL [neighbours]INT nx IS
>               [ right, x,    left, left, left, x,  right, right ] :
>         VAL [neighbours]INT ny IS
>               [ down,  down, down, y,    up,   up, up,    y     ] :
>         cell(link, x, y, nx, ny, control[x][y], sense[x][y])
> :
>
Thinking

Now I understand the differences. The changes are:

  1. Add copyright header comment (lines 1-4)
  2. Add adaptation comment and helper procedures (write.string, write.small.int) - lines 7-35
  3. Replace ... with 20 for array.width and array.height
  4. Replace clear.screen implementation (was using write.string with escape, now uses individual byte sends)
  5. Replace move.cursor implementation (was using write.formatted/DATA.ITEM, now uses write.small.int)
  6. Add ! direction annotation to various CHAN OF BYTE params (screen!)

  7. Add ? and ! direction annotations to editor and controller params

  8. Wrap the main body in PROC life(...) with indentation

The key insight is that a context diff would expose copyrighted material, so I need a script using line-number based operations like sed that can reference specific positions in the original file without including the actual content. Since the file is fixed, I can design sed commands that insert the copyright header and other modifications at precise line numbers.

Now I'm mapping out all the specific line edits needed: inserting the copyright and helper procs at the top, replacing the ellipsis with 20, updating the clear.screen and move.cursor implementations, adding direction annotations to various procedures, and replacing the final section with the wrapped PROC life body.

Since the file structure is stable, I can rely on line numbers for precise sed substitutions—mostly adding ! markers after screen or terminal references. I'll create a shell script to automate these changes.

Now I have a clear picture of all the differences. Let me create the script that downloads and transforms the file. The key is to use line-number based edits (since the original never changes) to avoid including copyrighted content.

📝 Write fetch-life.sh
/home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh
#!/bin/bash
#
# Downloads the Life example from the "Programming in occam 2" book website
# and applies modifications needed to build with occam2go and the kroc course module.
#
# The original source is copyright Geraint Jones & Michael Goldsmith 1988, 2001.
# See: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt
#
# Changes applied:
#   - Add copyright/attribution header and adaptation notes
#   - Add helper procedures (write.string, write.small.int) replacing book library
#   - Set board dimensions (array.width/array.height) to 20
#   - Replace clear.screen/move.cursor to avoid book library dependencies
#   - Add channel direction annotations (? and !) for occam2go compatibility
#   - Wrap main body in PROC life(...) declaration
#

set -euo pipefail

URL="https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT="${SCRIPT_DIR}/life.occ"

echo "Downloading original life.occ from book website..."
if ! curl -sfS -o "${OUTPUT}" "${URL}"; then
    echo "Error: failed to download ${URL}" >&2
    exit 1
fi

echo "Applying modifications for occam2go..."

# All sed edits use line numbers since the original file is fixed (unchanged since 1988).
# This avoids including copyrighted content as context in patches.

sed -i \
    -e '1i\
--  Code copied from Programming in occam®2\
--  © Geraint Jones, Michael Goldsmith 1988, 2001.\
--  Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk\
--' \
    "${OUTPUT}"

# Lines shifted by 4 after the header insertion. Original line 2 is now line 6.
# Insert adaptation comment and helper procs after line 6 (the "--" blank comment line).
sed -i '6a\
--  Adapted for occam2go: replaced book-library functions\
--  (write.string, write.formatted, DATA.ITEM) with inline\
--  definitions; added terminal.keyboard/terminal.screen declarations.\
--\
\
--\
--  helper procedures (replaces book standard library)\
--\
\
PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)\
  SEQ i = 0 FOR SIZE s\
    out ! s[i]\
:\
\
PROC write.small.int(CHAN OF BYTE out!, VAL INT n)\
  -- outputs a small non-negative integer (0..999) as decimal digits\
  IF\
    n >= 100\
      SEQ\
        out ! BYTE ((n / 100) + (INT '"'"'0'"'"'))\
        out ! BYTE (((n / 10) \\ 10) + (INT '"'"'0'"'"'))\
        out ! BYTE ((n \\  10) + (INT '"'"'0'"'"'))\
    n >= 10\
      SEQ\
        out ! BYTE ((n / 10) + (INT '"'"'0'"'"'))\
        out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
    TRUE\
      out ! BYTE (n + (INT '"'"'0'"'"'))\
:' "${OUTPUT}"

# Now the file has grown. Recalculate line positions.
# Original lines 8-9 (array.width/height with "...") are now at lines 45-46.
sed -i \
    -e '45s/IS \.\.\. :/IS 20 :/' \
    -e '46s/IS \.\.\. :/IS 20 :/' \
    "${OUTPUT}"

# Original lines 119-121 (clear.screen) are now at lines 156-158.
# Replace the 3-line clear.screen with new 7-line version.
sed -i '156,158c\
PROC clear.screen(CHAN OF BYTE terminal!)\
  -- clear screen sequence for an ANSI terminal: ESC [ 2 J\
  SEQ\
    terminal ! BYTE #1B\
    terminal ! '"'"'['"'"'\
    terminal ! '"'"'2'"'"'\
    terminal ! '"'"'J'"'"'' "${OUTPUT}"

# After replacing 3 lines with 7, net +4 lines.
# Original line 124 (move.cursor signature) is now at line 165.
sed -i '165c\
PROC move.cursor(CHAN OF BYTE terminal!, VAL INT x, y)' "${OUTPUT}"

# Original lines 126-131 (move.cursor body) are now at lines 167-172.
# Replace with new implementation.
sed -i '167,172c\
  -- outputs ANSI escape sequence: ESC [ row ; col H\
  SEQ\
    terminal ! BYTE #1B\
    terminal ! '"'"'['"'"'\
    write.small.int(terminal, y + 1)\
    terminal ! '"'"';'"'"'\
    write.small.int(terminal, x + 1)\
    terminal ! '"'"'H'"'"'' "${OUTPUT}"

# After replacing 6 lines with 8, net +2. Running offset from original: +4+2+37 = +43.
# But let's just count from the current state. We need to add ! to various CHAN OF BYTE params.
# Use targeted substitutions on specific lines.

# initialize.display: original line 141 -> now line 184
sed -i '184s/CHAN OF BYTE screen)/CHAN OF BYTE screen!)/' "${OUTPUT}"

# clean.up.display: original line 146 -> now line 189
sed -i '189s/CHAN OF BYTE screen)/CHAN OF BYTE screen!)/' "${OUTPUT}"

# display.state: original line 150 -> now line 193
sed -i '193s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"

# display.activity: original line 188 -> now line 231
sed -i '231s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"

# generation: original line 208 -> now line 251
sed -i '251s/CHAN OF BYTE screen,/CHAN OF BYTE screen!,/' "${OUTPUT}"

# editor: original line 290 -> now line 333
sed -i '333s/CHAN OF BYTE keyboard, screen,/CHAN OF BYTE keyboard?, screen!,/' "${OUTPUT}"

# controller: original line 345 -> now line 388
sed -i '388s/CHAN OF BYTE keyboard, screen,/CHAN OF BYTE keyboard?, screen!,/' "${OUTPUT}"

# Main body: original lines 396-411 -> now lines 439-454.
# Wrap in PROC life and indent the body.
sed -i '439,454c\
PROC life (CHAN BYTE keyboard?, screen!, error!)\
  [array.width][array.height][neighbours]CHAN OF STATE link :\
  [array.width][array.height]CHAN OF COMMAND control :\
  [array.width][array.height]CHAN OF RESPONSE sense :\
  PAR\
    controller(keyboard, screen, control, sense)\
    PAR x = 0 FOR array.width\
      PAR y = 0 FOR array.height\
        VAL INT left  IS ((x - 1) + array.width)  \\\\ array.width  :\
        VAL INT right IS  (x + 1)                 \\\\ array.width  :\
        VAL INT up    IS  (y + 1)                 \\\\ array.height :\
        VAL INT down  IS ((y - 1) + array.height) \\\\ array.height :\
        VAL [neighbours]INT nx IS\
              [ right, x,    left, left, left, x,  right, right ] :\
        VAL [neighbours]INT ny IS\
              [ down,  down, down, y,    up,   up, up,    y     ] :\
        cell(link, x, y, nx, ny, control[x][y], sense[x][y])\
:\
' "${OUTPUT}"

echo "Done. Output written to: ${OUTPUT}"
The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.

[Request interrupted by user for tool use]

While this looks promising, I'm wondering if there is a way to include much less of the copyright material in the script -- most of the changes look to be moving existing lines, adding one character, adding indentation and so on. Can that be done without including the entire new line of code (which includes the original line obviously) in the script/patch?

Thinking

The user makes a great point. Many of the sed commands I wrote include full lines from the original copyrighted file (just with small modifications like adding !). Instead, I should use more surgical edits:

  • For adding ! or ? to channel params: use a substitution like `s/screen)/screen!)/' on the specific line number - this only references the small changed part, not the whole line. Actually, I was already doing this for some of them. Let me reconsider.

  • For the PROC life wrapper: instead of replacing all 16 lines with 18 new lines (which includes nearly all the original content), I could:

  • Insert the PROC life (...) line before the block
  • Indent all lines in the block by 2 spaces
  • Replace terminal.keyboard with keyboard and terminal.screen with screen
  • Add : and blank line at the end

  • For clear.screen: the original is 3 lines, replacement is 7. The original used write.string(terminal, "*#1B[2J"). We need to replace this with individual byte sends. This is a genuine rewrite that doesn't contain much original material.

  • For move.cursor: similar - the original used write.formatted and DATA.ITEM, we're replacing those with direct byte operations, so there's minimal overlap with the original code.

  • The ... -> 20 substitution is fine, very minimal.

  • The header insertion is all new text (attribution), fine.

  • The helper procs (write.string, write.small.int) are entirely new code, fine.

So the main issue is the PROC life wrapper at the end, and the channel direction additions. The channel direction annotations are just tiny syntax changes, which is fine. For the PROC life wrapper itself, I'm wrapping the original bare declarations and PAR block in a procedure signature and indenting everything by 2 spaces, which is a structural transformation rather than copying the original content.

I could minimize this further by inserting the PROC line, indenting the existing lines, and substituting the terminal references with the new channel names, rather than reproducing the original lines verbatim. The backslash continuations in the original would just get the indentation prepended naturally.

Now I'm thinking through how to handle the clear.screen replacement where 3 lines become 7 lines...

Writing the script...

Great point. Most of my sed commands were reproducing entire original lines just to add ! or indent them. A better approach:

  • Channel direction !/? additions: small substitutions like s/screen)/screen!)/ — already minimal, fine.
  • ...20: already minimal, fine.
  • PROC life wrapper: instead of replacing all 16 lines with their indented copies, just insert the PROC header, indent the range by 2 spaces, substitute terminal.keyboardkeyboard / terminal.screenscreen, and append the closing :\n.
  • clear.screen / move.cursor: the originals use write.string/write.formatted/DATA.ITEM which are deleted and replaced with entirely new code — no original content in the replacements.
  • Header and helper procs: entirely new text.

Let me write a cleaner script using this approach.

📝 Write fetch-life.sh
/home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh
#!/bin/bash
#
# Downloads the Life example from the "Programming in occam 2" book website
# and applies modifications needed to build with occam2go and the kroc course module.
#
# The original source is copyright Geraint Jones & Michael Goldsmith 1988, 2001.
# See: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt
#
# Changes applied:
#   - Add copyright/attribution header and adaptation notes
#   - Add helper procedures (write.string, write.small.int) replacing book library
#   - Set board dimensions (array.width/array.height) to 20
#   - Replace clear.screen/move.cursor to avoid book library dependencies
#   - Add channel direction annotations (? and !) for occam2go compatibility
#   - Wrap main body in PROC life(...) declaration
#
# All line-number references are to the original downloaded file, which has not
# changed since 1988. Edits are applied bottom-to-top so that line numbers of
# earlier edits remain valid.

set -euo pipefail

URL="https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/code-life.txt"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT="${SCRIPT_DIR}/life.occ"

echo "Downloading original life.occ from book website..."
if ! curl -sfS -o "${OUTPUT}" "${URL}"; then
    echo "Error: failed to download ${URL}" >&2
    exit 1
fi

echo "Applying modifications for occam2go..."

# We apply edits bottom-to-top so earlier line numbers stay correct.

# --- Main body (lines 396-411): wrap in PROC life, indent, rename params ---
# Insert PROC header before line 396
sed -i '396i\PROC life (CHAN BYTE keyboard?, screen!, error!)' "${OUTPUT}"
# Indent lines 397-412 (the original 396-411, now shifted by 1) by 2 spaces
sed -i '397,412s/^/  /' "${OUTPUT}"
# Rename terminal.keyboard -> keyboard, terminal.screen -> screen in that range
sed -i '397,412s/terminal\.keyboard/keyboard/' "${OUTPUT}"
sed -i '397,412s/terminal\.screen/screen/' "${OUTPUT}"
# Append closing colon after line 412
sed -i '412a\:' "${OUTPUT}"
# Append trailing blank line
sed -i '413a\\' "${OUTPUT}"

# --- controller (line 345): add channel direction annotations ---
sed -i '345s/keyboard, screen,/keyboard?, screen!,/' "${OUTPUT}"

# --- editor (line 290): add channel direction annotations ---
sed -i '290s/keyboard, screen,/keyboard?, screen!,/' "${OUTPUT}"

# --- generation (line 208): add ! to screen param ---
sed -i '208s/screen,/screen!,/' "${OUTPUT}"

# --- display.activity (line 188): add ! to screen param ---
sed -i '188s/screen,/screen!,/' "${OUTPUT}"

# --- display.state (line 150): add ! to screen param ---
sed -i '150s/screen,/screen!,/' "${OUTPUT}"

# --- clean.up.display (line 146): add ! to screen param ---
sed -i '146s/screen)/screen!)/' "${OUTPUT}"

# --- initialize.display (line 141): add ! to screen param ---
sed -i '141s/screen)/screen!)/' "${OUTPUT}"

# --- move.cursor (lines 124-131): replace signature and body ---
# Line 124: add ! to terminal param
sed -i '124s/terminal,/terminal!,/' "${OUTPUT}"
# Lines 126-131: delete old body (DATA.ITEM/write.formatted), insert new
sed -i '126,131d' "${OUTPUT}"
# Insert new body at line 126 (after the comment on line 125)
sed -i '125a\
  -- outputs ANSI escape sequence: ESC [ row ; col H\
  SEQ\
    terminal ! BYTE #1B\
    terminal ! '"'"'['"'"'\
    write.small.int(terminal, y + 1)\
    terminal ! '"'"';'"'"'\
    write.small.int(terminal, x + 1)\
    terminal ! '"'"'H'"'"'' "${OUTPUT}"

# --- clear.screen (lines 119-121): replace implementation ---
# Line 119: add ! to terminal param
sed -i '119s/terminal)/terminal!)/' "${OUTPUT}"
# Line 120: replace comment (add explicit chars description)
sed -i '120s/-- clear screen sequence for an ANSI terminal/-- clear screen sequence for an ANSI terminal: ESC [ 2 J/' "${OUTPUT}"
# Line 121: delete old one-liner body
sed -i '121d' "${OUTPUT}"
# Insert new multi-line body after line 120
sed -i '120a\
  SEQ\
    terminal ! BYTE #1B\
    terminal ! '"'"'['"'"'\
    terminal ! '"'"'2'"'"'\
    terminal ! '"'"'J'"'"'' "${OUTPUT}"

# --- array dimensions (lines 8-9): replace ... with 20 ---
sed -i '8s/IS \.\.\. :/IS 20 :/' "${OUTPUT}"
sed -i '9s/IS \.\.\. :/IS 20 :/' "${OUTPUT}"

# --- Insert adaptation comment and helper procs after line 2 ---
sed -i '2a\
--  Adapted for occam2go: replaced book-library functions\
--  (write.string, write.formatted, DATA.ITEM) with inline\
--  definitions; added terminal.keyboard/terminal.screen declarations.\
--\
\
--\
--  helper procedures (replaces book standard library)\
--\
\
PROC write.string(CHAN OF BYTE out!, VAL []BYTE s)\
  SEQ i = 0 FOR SIZE s\
    out ! s[i]\
:\
\
PROC write.small.int(CHAN OF BYTE out!, VAL INT n)\
  -- outputs a small non-negative integer (0..999) as decimal digits\
  IF\
    n >= 100\
      SEQ\
        out ! BYTE ((n / 100) + (INT '"'"'0'"'"'))\
        out ! BYTE (((n / 10) \\ 10) + (INT '"'"'0'"'"'))\
        out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
    n >= 10\
      SEQ\
        out ! BYTE ((n / 10) + (INT '"'"'0'"'"'))\
        out ! BYTE ((n \\ 10) + (INT '"'"'0'"'"'))\
    TRUE\
      out ! BYTE (n + (INT '"'"'0'"'"'))\
:' "${OUTPUT}"

# --- Insert copyright/attribution header at the very top ---
sed -i '1i\
--  Code copied from Programming in occam®2\
--  © Geraint Jones, Michael Goldsmith 1988, 2001.\
--  Permission is granted to copy this material for private study; for other uses please contact occam-book@comlab.ox.ac.uk\
--' "${OUTPUT}"

echo "Done. Output written to: ${OUTPUT}"
File created successfully at: /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh
$ Bash
Make script executable
chmod +x /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh

Now let me test it by running the script and comparing the output against the current file.

$ Bash
Run fetch script and preserve expected output for comparison
cp /home/david/projects/codeassociates/occam2go/historical-examples/life.occ /tmp/life-expected.occ && /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh 2>&1
Downloading original life.occ from book website...
Applying modifications for occam2go...
Done. Output written to: /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
$ Bash
Compare expected vs generated output
diff /tmp/life-expected.occ /home/david/projects/codeassociates/occam2go/historical-examples/life.occ

The output matches exactly. Now let me restore the original file and verify the script works from a clean state by removing the generated file and running again.

$ Bash
Test clean run of fetch script
rm /home/david/projects/codeassociates/occam2go/historical-examples/life.occ && /home/david/projects/codeassociates/occam2go/historical-examples/fetch-life.sh 2>&1 && diff /tmp/life-expected.occ /home/david/projects/codeassociates/occam2go/historical-examples/life.occ
Downloading original life.occ from book website...
Applying modifications for occam2go...
Done. Output written to: /home/david/projects/codeassociates/occam2go/historical-examples/life.occ

Clean run produces identical output. Now let me also verify the transpiler still works with the generated file.

$ Bash
Verify transpiled output compiles
go build -o occam2go . && ./occam2go -o /tmp/life_test.go historical-examples/life.occ && go vet /tmp/life_test.go
go: downloading golang.org/x/term v0.40.0
go: downloading golang.org/x/sys v0.41.0

Everything works. Now let me check what needs to go into the PR — the script should be added and life.occ should be removed from git tracking. Let me also check if life.occ is referenced anywhere else.

Grep
{
  "pattern": "life\\.occ",
  "output_mode": "content"
}
historical-examples/fetch-life.sh:25:OUTPUT="${SCRIPT_DIR}/life.occ"
historical-examples/fetch-life.sh:27:echo "Downloading original life.occ from book website..."
Grep
{
  "pattern": "historical-examples",
  "output_mode": "content",
  "glob": "*.md"
}
No matches found

Good — no other references. Let me also add life.occ to .gitignore so the generated file isn't accidentally committed.

$ Bash
Check for existing gitignore files
test -f /home/david/projects/codeassociates/occam2go/historical-examples/.gitignore && cat /home/david/projects/codeassociates/occam2go/historical-examples/.gitignore; echo "---"; test -f /home/david/projects/codeassociates/occam2go/.gitignore && cat /home/david/projects/codeassociates/occam2go/.gitignore; echo "exit: $?"
---
# Binaries
occam2go
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binaries
*.test

# Output of go coverage
*.out

# Go workspace file
go.work

# Dependency directories
vendor/

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Generated Go files from examples
examples/*.go

# External repositories
kroc/
exit: 0
Read
{
  "file_path": "/home/david/projects/codeassociates/occam2go/.gitignore"
}
     1→# Binaries
     2→occam2go
     3→*.exe
     4→*.exe~
     5→*.dll
     6→*.so
     7→*.dylib
     8→
     9→# Test binaries
    10→*.test
    11→
    12→# Output of go coverage
    13→*.out
    14→
    15→# Go workspace file
    16→go.work
    17→
    18→# Dependency directories
    19→vendor/
    20→
    21→# IDE
    22→.idea/
    23→.vscode/
    24→*.swp
    25→*.swo
    26→
    27→# OS
    28→.DS_Store
    29→Thumbs.db
    30→
    31→# Generated Go files from examples
    32→examples/*.go
    33→
    34→# External repositories
    35→kroc/
    36→
✏️ Edit .gitignore
/home/david/projects/codeassociates/occam2go/.gitignore
# External repositories
kroc/
+
# Generated occam files from fetch scripts
historical-examples/life.occ

# External repositories
kroc/
The file /home/david/projects/codeassociates/occam2go/.gitignore has been updated successfully.

Now let me create the branch, commit, and PR.

$ Bash
Create feature branch
git checkout -b fix/replace-life-with-fetch-script-94
Switched to a new branch 'fix/replace-life-with-fetch-script-94'
$ Bash
Remove copyrighted file from git tracking
git rm historical-examples/life.occ
rm 'historical-examples/life.occ'
$ Bash
Stage new and modified files
git add historical-examples/fetch-life.sh .gitignore
$ Bash
Review staged changes
git diff --cached --stat
 .gitignore                        |   3 +
 historical-examples/fetch-life.sh | 145 ++++++++++++
 historical-examples/life.occ      | 453 --------------------------------------
 3 files changed, 148 insertions(+), 453 deletions(-)
$ Bash
Check recent commit style
git log --oneline -5
677b6ee Merge pull request #93 from codeassociates/docs/update-line-counts
a2144f1 Update codebase line count statistics in README
0c15516 Merge pull request #92 from codeassociates/fix/life-display-and-alt-busywait-72
eb7faab Fix display buffering and PRI ALT busy-wait in life.occ (#72)
6272c72 Merge pull request #91 from codeassociates/feat/raw-terminal-keyboard-90
$ Bash
Create commit
git commit -m "$(cat <<'EOF'
Replace copyrighted life.occ with download-and-patch script (#94)

Remove the in-repo copy of the book example program and replace it with
a fetch-life.sh script that downloads the original from the book website
and applies the necessary modifications via line-number-targeted sed edits.
The script avoids reproducing copyrighted content by using small surgical
substitutions (e.g. adding channel direction annotations) and indentation
rather than replacing entire lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
3 files changed, 148 insertions(+), 453 deletions(-)
 create mode 100755 historical-examples/fetch-life.sh
 delete mode 100644 historical-examples/life.occ
$ Bash
Push branch to remote
git push -u origin fix/replace-life-with-fetch-script-94
remote: 
remote: Create a pull request for 'fix/replace-life-with-fetch-script-94' on GitHub by visiting:        
remote:      https://github.com/codeassociates/occam2go/pull/new/fix/replace-life-with-fetch-script-94        
remote: 
To associate-3.github.com:codeassociates/occam2go.git
 * [new branch]      fix/replace-life-with-fetch-script-94 -> fix/replace-life-with-fetch-script-94
branch 'fix/replace-life-with-fetch-script-94' set up to track 'origin/fix/replace-life-with-fetch-script-94'.
$ Bash
Create pull request
gh pr create --title "Replace copyrighted life.occ with download-and-patch script" --body "$(cat <<'EOF'
## Summary
- Removes the in-repo copy of the copyrighted book example program (`historical-examples/life.occ`)
- Adds `fetch-life.sh` script that downloads the original from the book website and applies modifications via surgical sed edits
- Adds `historical-examples/life.occ` to `.gitignore` so the generated file isn't accidentally committed

## Approach
The script uses line-number-targeted sed edits applied bottom-to-top so line numbers remain stable. Modifications that touch existing lines use minimal substitutions (e.g. `s/screen)/screen!)/`) rather than replacing entire lines, avoiding reproduction of copyrighted content. Only genuinely new code (helper procedures, rewritten `clear.screen`/`move.cursor` implementations) appears in full.

## Test plan
- [x] Script downloads and patches successfully, producing output identical to the previous in-repo file
- [x] Transpiled output passes `go vet`
- [ ] Run `./historical-examples/fetch-life.sh` from a clean checkout and verify `life.occ` is created

Closes #94

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
https://github.com/codeassociates/occam2go/pull/95

PR created: https://github.com/codeassociates/occam2go/pull/95

Here's a summary of what was done:

  1. Removed historical-examples/life.occ from the repository
  2. Added historical-examples/fetch-life.sh — a script that downloads the original from the book website and applies modifications using surgical sed edits
  3. Updated .gitignore to exclude the generated life.occ

The key design choice per your feedback: the script avoids reproducing copyrighted lines by using minimal substitutions on specific line numbers. For example: - Adding !/? channel direction annotations: s/screen)/screen!)/ — only the tiny changed fragment - Wrapping in PROC life: inserts the header line, indents the existing range by 2 spaces, and substitutes terminal.keyboardkeyboard — never reproduces any original line - Only genuinely new code (helper procs, rewritten clear.screen/move.cursor) appears in full

Great work thanks. I think it would be useful to add a readme file to the historical-examples directory explaining things for the user. You can reference the top level book web site which is at this url: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/

📝 Write README.md
/home/david/projects/codeassociates/occam2go/historical-examples/README.md
# Historical Examples

This directory contains example programs from historical occam publications, adapted to build and run with occam2go.

## Life (Conway's Game of Life)

From *Programming in occam 2* by Geraint Jones and Michael Goldsmith (1988).
Book website: https://www.cs.ox.ac.uk/geraint.jones/publications/book/Pio2/

The original source is copyrighted and not included in this repository. To obtain it, run the fetch script which downloads it from the book website and applies the modifications needed for occam2go:

```bash
./historical-examples/fetch-life.sh
```

This produces `historical-examples/life.occ`, which can then be transpiled and run:

```bash
./occam2go -o life.go historical-examples/life.occ
go run life.go
```

### Controls

- **E** — enter editor mode (use arrow keys to move, space/asterisk to toggle cells, Q to exit editor)
- **R** — run (free-running evolution)
- **S** — stop
- **Any other key** — single step
- **Q** — quit
File created successfully at: /home/david/projects/codeassociates/occam2go/historical-examples/README.md
$ Bash
Commit and push README
git add historical-examples/README.md && git commit -m "$(cat <<'EOF'
Add README for historical-examples directory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)" && git push
1 file changed, 29 insertions(+)
 create mode 100644 historical-examples/README.md
To associate-3.github.com:codeassociates/occam2go.git
   cf76024..7bb1257  fix/replace-life-with-fetch-script-94 -> fix/replace-life-with-fetch-script-94

Done — pushed to the existing PR. The README explains the provenance of the example, links to the book website, shows how to fetch and run it, and documents the keyboard controls.

Thanks. Please merge the PR.

$ Bash
Merge PR #95
gh pr merge 95 --merge

PR #95 merged.