From 126b182e891cd4f9233c5aa2e22daef6fb11e732 Mon Sep 17 00:00:00 2001 From: Associate 1 Date: Mon, 23 Feb 2026 13:58:24 -0700 Subject: [PATCH 1/2] 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 --- ast/ast.go | 15 +++++----- codegen/codegen.go | 25 +++++++++++------ historical-examples/life.occ | 53 ++++++++++++++++++++++++++++++------ parser/parser.go | 13 +++++---- 4 files changed, 77 insertions(+), 29 deletions(-) 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, } } From 5ab69cb7aeef5cac441ad03cfb8ed702f5353999 Mon Sep 17 00:00:00 2001 From: Associate 1 Date: Mon, 23 Feb 2026 14:50:04 -0700 Subject: [PATCH 2/2] 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 --- codegen/codegen.go | 2 +- historical-examples/life.occ | 58 ++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 30 deletions(-) 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]) +: