Claude Code transcript - page 1/1

Please take a look at issue #45.

$ Bash
View GitHub issue #45
gh issue view 45
title:	Support ALT replicators
state:	OPEN
author:	associate-1
labels:	
comments:	0
assignees:	
projects:	
milestone:	
number:	45
--
## Description

The parser does not support replicated `ALT` blocks (`ALT i = 0 FOR n`), analogous to the already-supported replicated `SEQ`, `PAR`, and `IF` constructs.

### Syntax

```occam
ALT i = 0 FOR n.inputs
  BYTE ch:
  in[i] ? ch
    SEQ
      out ! ch
```

This creates `n.inputs` alternatives, each guarded by `in[i] ? ch`, and selects whichever channel is ready first.

### Affected files

- `kroc/modules/course/examples/sort_pump.occ` (line 184)
- `kroc/modules/course/examples/sort_pump_2.occ` (line 184)
- `kroc/modules/course/examples/sort_inside.occ` (line 213)
- `kroc/modules/course/examples/sort_inside_2.occ` (line 224, 294)
- `kroc/modules/course/examples/bar.occ` (lines 327, 338, 352)

This is the single most impactful missing feature — it blocks 5 out of 16 example files.

### Current error

```
sort_pump.occ:184: expected indented block after ALT
sort_pump.occ:184: unexpected token: =
```

The parser sees `ALT` and expects an indented block of alternatives, but instead finds the replicator syntax `i = 0 FOR n`.

### Proposed Go mapping

```go
// ALT i = 0 FOR n  →  select over dynamically built cases
// Requires reflect.Select for runtime-variable number of cases
```

Since Go's `select` requires statically known cases, the replicated ALT will need `reflect.Select` to build the case list dynamically at runtime:

```go
cases := make([]reflect.SelectCase, n)
for i := 0; i < n; i++ {
    cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(in[i])}
}
chosen, value, _ := reflect.Select(cases)
ch := byte(value.Int())
// ... body using chosen as i
```

### Related

- #43 - Course examples tracking issue
Thinking

This is a well-documented issue about adding support for ALT replicators. Let me understand the scope:

  1. The parser needs to handle ALT i = 0 FOR n syntax (replicated ALT)
  2. The codegen needs to generate Go code using reflect.Select since Go's select can't handle dynamic case counts
  3. This is similar to how SEQ/PAR/IF replicators are already handled

Let me explore the codebase to understand the existing replicator implementations and ALT handling before proposing a plan.

This is a well-scoped feature request for replicated ALT (ALT i = 0 FOR n). It's analogous to existing replicated SEQ/PAR/IF but requires reflect.Select for codegen since Go's select needs statically known cases.

Let me plan the implementation.

EnterPlanMode
{}
Entered plan mode. You should now focus on exploring the codebase and designing an implementation approach.

In plan mode, you should:
1. Thoroughly explore the codebase to understand existing patterns
2. Identify similar features and architectural approaches
3. Consider multiple approaches and their trade-offs
4. Use AskUserQuestion if you need to clarify the approach
5. Design a concrete implementation strategy
6. When ready, use ExitPlanMode to present your plan for approval

Remember: DO NOT write or edit any files yet. This is a read-only exploration and planning phase.
Task
Explore ALT and replicator patterns
{
  "subagent_type": "Explore",
  "prompt": "I need to understand how ALT blocks and replicators (SEQ/PAR/IF) are currently implemented in this occam-to-Go transpiler. Please explore thoroughly:\n\n1. **AST nodes**: In `ast/ast.go`, find:\n   - The `AltStatement` and related structs (AltCase, etc.)\n   - Replicator-related fields on `SeqBlock`, `ParBlock`, `IfStatement` — how they store the replicator variable, start, count, step\n   - Any existing `AltReplicator` or similar struct\n\n2. **Parser**: In `parser/parser.go`, find:\n   - `parseAltStatement()` — how ALT blocks are currently parsed\n   - How SEQ/PAR/IF replicators are parsed (e.g., `parseSeqBlock()`, `parseParBlock()`, `parseIfStatement()`) — the pattern for detecting `IDENT = expr FOR expr` after the keyword\n   - The `parseReplicator()` function if it exists\n\n3. **Codegen**: In `codegen/codegen.go`, find:\n   - `generateAlt()` or similar — how ALT is currently generated\n   - How SEQ/PAR/IF replicators are generated (the loop generation pattern)\n   - Any existing use of `reflect` package\n\n4. **Tests**: Check `codegen/e2e_test.go` and `codegen/codegen_test.go` for existing ALT tests and replicator tests to understand the testing patterns.\n\n5. **One of the affected example files** like `kroc/modules/course/examples/sort_pump.occ` around line 184 to see the actual occam syntax.\n\nPlease provide code snippets and line numbers for all findings."
}
[
  {
    "type": "text",
    "text": "Perfect. Now I have all the information I need. Let me compile a comprehensive report.\n\n---\n\n## Comprehensive Exploration Report: ALT Blocks and Replicators in occam2go\n\nI have conducted a thorough exploration of the ALT blocks and replicators in the occam2go transpiler. Here are my complete findings:\n\n### 1. AST Node Definitions (`ast/ast.go`)\n\n**Replicator Structure (Lines 108-114):**\n```go\n// Replicator represents a replication spec: i = start FOR count [STEP step]\ntype Replicator struct {\n\tVariable string     // loop variable name\n\tStart    Expression // start value\n\tCount    Expression // number of iterations\n\tStep     Expression // optional step value (nil means step of 1)\n}\n```\n\n**SeqBlock with Replicator Support (Lines 88-95):**\n```go\ntype SeqBlock struct {\n\tToken      lexer.Token // the SEQ token\n\tStatements []Statement\n\tReplicator *Replicator // optional replicator\n}\n```\n\n**ParBlock with Replicator Support (Lines 99-106):**\n```go\ntype ParBlock struct {\n\tToken      lexer.Token // the PAR token\n\tStatements []Statement\n\tReplicator *Replicator // optional replicator\n}\n```\n\n**IfStatement with Replicator Support (Lines 200-213):**\n```go\ntype IfStatement struct {\n\tToken      lexer.Token // the IF token\n\tChoices    []IfChoice\n\tReplicator *Replicator // optional replicator for IF i = start FOR count\n}\n```\n\n**AltBlock WITHOUT Replicator Support (Lines 384-391):**\n```go\ntype AltBlock struct {\n\tToken lexer.Token // the ALT token\n\tCases []AltCase\n}\n// NOTE: No Replicator field!\n```\n\n**AltCase Structure (Lines 394-403):**\n```go\ntype AltCase struct {\n\tGuard        Expression  // optional guard condition (nil if no guard)\n\tChannel      string      // channel name\n\tChannelIndex Expression  // non-nil for cs[i] ? x in ALT\n\tVariable     string      // variable to receive into\n\tBody         []Statement // the body to execute\n\tIsTimer      bool        // true if this is a timer AFTER case\n\tTimer        string      // timer name (when IsTimer)\n\tDeadline     Expression  // AFTER deadline expression (when IsTimer)\n}\n```\n\n---\n\n### 2. Parser Implementation (`parser/parser.go`)\n\n**parseReplicator() (Lines 1535-1566):**\n```go\n// parseReplicator parses: variable = start FOR count [STEP step]\n// Assumes the variable identifier has already been consumed and is in curToken\nfunc (p *Parser) parseReplicator() *ast.Replicator {\n\trep := &ast.Replicator{\n\t\tVariable: p.curToken.Literal,\n\t}\n\n\t// Expect =\n\tif !p.expectPeek(lexer.EQ) {\n\t\treturn nil\n\t}\n\n\t// Parse start expression\n\tp.nextToken()\n\trep.Start = p.parseExpression(LOWEST)\n\n\t// Expect FOR\n\tif !p.expectPeek(lexer.FOR) {\n\t\treturn nil\n\t}\n\n\t// Parse count expression\n\tp.nextToken()\n\trep.Count = p.parseExpression(LOWEST)\n\n\t// Optional STEP\n\tif p.peekTokenIs(lexer.STEP) {\n\t\tp.nextToken() // consume STEP\n\t\tp.nextToken() // move to step expression\n\t\trep.Step = p.parseExpression(LOWEST)\n\t}\n\n\treturn rep\n}\n```\n\n**parseSeqBlock() (Lines 1463-1496):**\nChecks for replicator pattern: `if p.peekTokenIs(lexer.IDENT)` then `if p.peekTokenIs(lexer.EQ)` triggers `p.parseReplicator()`.\n\n**parseParBlock() (Lines 1498-1531):**\nIdentical replicator detection pattern as SEQ.\n\n**parseIfStatement() (Lines 2445-2535):**\nReplicator detection at lines 2448-2457:\n```go\n// Check for replicator: IF i = start FOR count\nif p.peekTokenIs(lexer.IDENT) {\n\tp.nextToken() // move to identifier\n\tif p.peekTokenIs(lexer.EQ) {\n\t\tstmt.Replicator = p.parseReplicator()\n\t} else {\n\t\tp.addError(\"unexpected identifier after IF\")\n\t\treturn stmt\n\t}\n}\n```\n\n**parseAltBlock() (Lines 1568-1586):**\n```go\nfunc (p *Parser) parseAltBlock() *ast.AltBlock {\n\tblock := &ast.AltBlock{Token: p.curToken}\n\n\t// Skip to next line\n\tfor p.peekTokenIs(lexer.NEWLINE) {\n\t\tp.nextToken()\n\t}\n\n\t// Expect INDENT\n\tif !p.peekTokenIs(lexer.INDENT) {\n\t\tp.addError(\"expected indented block after ALT\")\n\t\treturn block\n\t}\n\tp.nextToken() // consume INDENT\n\n\tblock.Cases = p.parseAltCases()\n\n\treturn block\n}\n```\n**Note:** `parseAltBlock()` has NO replicator detection logic.\n\n**parseAltCases() and parseAltCase() (Lines 1588-1751):**\n- `parseAltCases()` (1588-1638): Parses multiple cases, calls `parseAltCase()` for each\n- `parseAltCase()` (1640-1751): Handles guards, channels, indexed channels, timers. No replicator support.\n\n---\n\n### 3. Code Generation (`codegen/codegen.go`)\n\n**generateSeqBlock() (Lines 1494-1535):**\nReplicator handling at 1495-1523:\n- Without STEP: `for v := start; v < start + count; v++ { ... }`\n- With STEP: `for counter := 0; counter < count; counter++ { v := start + counter * step; ... }`\n\n**generateParBlock() (Lines 1537-1602):**\nReplicator handling at 1538-1584:\n- Creates `sync.WaitGroup` and spawns goroutines in loop\n- With STEP: uses counter-based calculation `v := start + counter * step`\n- Without STEP: uses direct variable with closure capture pattern to avoid goroutine closure issues (line 1572)\n\n**generateIfStatement() (Lines 1982-2063):**\nTwo paths:\n- With `Replicator != nil`: calls `generateReplicatedIfLoop()` (lines 2010-2063)\n- Without: flattens nested non-replicated IFs via `flattenIfChoices()` then `generateIfChoiceChain()`\n\nReplicated IF generates `for` loop with `if/else if` chain inside, breaks on first match (line 2055).\n\n**generateAltBlock() (Lines 1604-1658):**\n```go\nfunc (g *Generator) generateAltBlock(alt *ast.AltBlock) {\n\t// Check if any cases have guards\n\thasGuards := false\n\tfor _, c := range alt.Cases {\n\t\tif c.Guard != nil {\n\t\t\thasGuards = true\n\t\t\tbreak\n\t\t}\n\t}\n\t\n\tif hasGuards {\n\t\t// Generate channel variables for guarded cases\n\t\t// ... creates nil channels for unguarded cases\n\t}\n\t\n\tg.writeLine(\"select {\")\n\tfor i, c := range alt.Cases {\n\t\t// ... case generation\n\t}\n\tg.writeLine(\"}\")\n}\n```\n**Note:** NO replicator handling. The function does NOT check for `alt.Replicator`.\n\n---\n\n### 4. Tests\n\n**Parser Tests (`parser/parser_test.go`, Lines 274-334):**\n- `TestAltBlock()`: Basic ALT without replicator\n- `TestAltBlockWithGuard()`: ALT with boolean guards\n- No tests for ALT replicators\n\n**Codegen Tests:**\n\n**e2e_replicator_test.go (152 lines):**\nTests for SEQ, PAR, and IF replicators with various patterns:\n- `TestE2E_ReplicatedSeq()`: Basic SEQ replicator\n- `TestE2E_ReplicatedSeqWithExpression()`: Expression for count\n- `TestE2E_ReplicatedSeqWithStartOffset()`: Non-zero start\n- `TestE2E_ReplicatedSeqSum()`: Accumulation test\n- `TestE2E_ReplicatedPar()`: PAR replicator with channel communication\n- `TestE2E_ReplicatedIf()`: IF replicator finding first match\n- `TestE2E_ReplicatedSeqStep()`: STEP clause (0, 2, 4, 6, 8)\n- `TestE2E_ReplicatedSeqNegativeStep()`: Negative step (9, 8, 7, 6, 5)\n- `TestE2E_ReplicatedParStep()`: PAR with STEP clause\n- **No ALT replicator tests**\n\n**e2e_concurrency_test.go:**\n- `TestE2E_AltFirstChannel()`: Basic ALT\n- `TestE2E_AltSecondChannel()`: ALT case selection\n- `TestE2E_AltWithBody()`: ALT with multi-statement body\n- `TestE2E_TimerRead()`: Timer with AFTER clause in ALT\n- **No ALT replicator tests**\n\n---\n\n### 5. Real-World Example\n\n**sort_pump.occ (Line 184):**\n```occam\nWHILE TRUE\n  ALT i = 0 FOR n.inputs\n    BYTE ch:\n    in[i] ? ch\n      --{{{  lock on to this channel for the whole message\n      SEQ\n        WHILE ch <> end.message\n          SEQ\n            ...\n```\n\nThis is an **ALT replicator** — a loop that tries to receive from channels `in[0]`, `in[1]`, ..., `in[n.inputs-1]` and handles the first one that's ready.\n\n---\n\n### 6. Current Implementation Status\n\n**SUMMARY TABLE:**\n\n| Construct | Replicator Support | Parser | Codegen | Tests |\n|-----------|-------------------|--------|---------|-------|\n| SEQ | ✓ FULL | Lines 1463-1496 | Lines 1494-1535 | 8 tests |\n| PAR | ✓ FULL | Lines 1498-1531 | Lines 1537-1602 | 3 tests |\n| IF | ✓ FULL | Lines 2445-2535 | Lines 1982-2063 | 2 tests |\n| ALT | ✗ NOT IMPLEMENTED | None (1568-1586) | None (1604-1658) | 0 tests |\n\n---\n\n### 7. What's Missing for ALT Replicators\n\n1. **AST**: Add `Replicator *Replicator` field to `AltBlock` struct\n2. **Parser**: Add replicator detection in `parseAltBlock()` (like SEQ/PAR/IF do)\n3. **Codegen**: Add replicator handling in `generateAltBlock()` to wrap `select` in a `for` loop with appropriate variable binding\n4. **Tests**: Add parser and e2e tests for ALT replicators with various patterns (basic, with guards, with timers, with step, etc.)\n\n---\n\n### 8. Key Code Patterns to Follow\n\n**Replicator Detection Pattern (all block types use this):**\n```go\nif p.peekTokenIs(lexer.IDENT) {\n\tp.nextToken() // move to identifier\n\tif p.peekTokenIs(lexer.EQ) {\n\t\t// This is a replicator\n\t\tblock.Replicator = p.parseReplicator()\n\t} else {\n\t\t// Not a replicator, might be a statement\n\t}\n}\n```\n\n**Counter-Based STEP Codegen (PAR pattern, lines 1547-1560):**\n```go\nif par.Replicator.Step != nil {\n\tcounter := \"_repl_\" + v\n\tg.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n\tg.write(fmt.Sprintf(\"for %s := 0; %s < \", counter, counter))\n\tg.generateExpression(par.Replicator.Count)\n\tg.write(fmt.Sprintf(\"; %s++ {\\n\", counter))\n\tg.indent++\n\t// Compute loop variable from counter — also serves as closure capture\n\tg.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n\tg.write(fmt.Sprintf(\"%s := \", v))\n\tg.generateExpression(par.Replicator.Start)\n\tg.write(fmt.Sprintf(\" + %s * \", counter))\n\tg.generateExpression(par.Replicator.Step)\n\tg.write(\"\\n\")\n}\n```\n\n**Closure Capture for Replicators (PAR pattern, line 1572):**\n```go\n// Capture loop variable to avoid closure issues\ng.writeLine(fmt.Sprintf(\"%s := %s\", v, v))\n```\n\n**IF Replicated Loop Pattern (lines 2008-2063):**\n```go\nfor i, choice := range stmt.Choices {\n\tg.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n\tif i == 0 {\n\t\tg.write(\"if \")\n\t} else {\n\t\tg.write(\"} else if \")\n\t}\n\tg.generateExpression(choice.Condition)\n\tg.write(\" {\\n\")\n\t// ... body ...\n\tg.writeLine(\"break\")\n}\ng.writeLine(\"}\")\n```\n\n**Guard Handling in ALT (lines 1617-1633):**\n```go\nif hasGuards {\n\t// Generate channel variables for guarded cases\n\tfor i, c := range alt.Cases {\n\t\tif c.Guard != nil {\n\t\t\tg.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n\t\t\tg.write(fmt.Sprintf(\"var _alt%d chan int = nil\\n\", i))\n\t\t\tg.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n\t\t\tg.write(fmt.Sprintf(\"if \"))\n\t\t\tg.generateExpression(c.Guard)\n\t\t\tg.write(fmt.Sprintf(\" { _alt%d = %s }\\n\", i, goIdent(c.Channel)))\n\t\t}\n\t}\n}\n```\n\n---\n\n### Absolute File Paths for All Key Locations:\n\n1. **AST Definitions**: `/home/david/projects/code-associates/occam2go/ast/ast.go` (lines 108-114, 88-95, 99-106, 200-213, 384-391, 394-403)\n2. **Parser**: `/home/david/projects/code-associates/occam2go/parser/parser.go` (lines 1463-1566, 2445-2535, 1568-1751)\n3. **Codegen**: `/home/david/projects/code-associates/occam2go/codegen/codegen.go` (lines 1494-2063)\n4. **Tests**: \n   - `/home/david/projects/code-associates/occam2go/codegen/e2e_replicator_test.go` (152 lines)\n   - `/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go` (test functions around lines 81-175)\n   - `/home/david/projects/code-associates/occam2go/parser/parser_test.go` (lines 274-334)\n5. **Real-World Example**: `/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/sort_pump.occ` (line 184)\n6. **Documentation**: `/home/david/projects/code-associates/occam2go/TODO.md`, `/home/david/projects/code-associates/occam2go/CLAUDE.md`"
  },
  {
    "type": "text",
    "text": "agentId: a00174a2c2906a2a7 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 60470\ntool_uses: 41\nduration_ms: 76238</usage>"
  }
]
Thinking

Excellent, very thorough exploration. Now let me look at the actual codegen for ALT more closely, particularly the guard handling and how reflect.Select would work. Let me also check one of the example files to understand the exact patterns needed.

Now let me read the key files to finalize my understanding before writing the plan.

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 1604,
  "limit": 80
}
  1604→func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
  1605→	// ALT becomes Go select statement
  1606→	// For guards, we use a pattern with nil channels
  1607→
  1608→	// Check if any cases have guards
  1609→	hasGuards := false
  1610→	for _, c := range alt.Cases {
  1611→		if c.Guard != nil {
  1612→			hasGuards = true
  1613→			break
  1614→		}
  1615→	}
  1616→
  1617→	if hasGuards {
  1618→		// Generate channel variables for guarded cases
  1619→		for i, c := range alt.Cases {
  1620→			if c.Guard != nil {
  1621→				g.builder.WriteString(strings.Repeat("\t", g.indent))
  1622→				g.write(fmt.Sprintf("var _alt%d chan ", i))
  1623→				// We don't know the channel type here, so use interface{}
  1624→				// Actually, we should use the same type as the original channel
  1625→				// For now, let's just reference the original channel conditionally
  1626→				g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
  1627→				g.builder.WriteString(strings.Repeat("\t", g.indent))
  1628→				g.write(fmt.Sprintf("if "))
  1629→				g.generateExpression(c.Guard)
  1630→				g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, goIdent(c.Channel)))
  1631→			}
  1632→		}
  1633→	}
  1634→
  1635→	g.writeLine("select {")
  1636→	for i, c := range alt.Cases {
  1637→		g.builder.WriteString(strings.Repeat("\t", g.indent))
  1638→		if c.IsTimer {
  1639→			g.write("case <-time.After(time.Duration(")
  1640→			g.generateExpression(c.Deadline)
  1641→			g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
  1642→		} else if c.Guard != nil {
  1643→			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
  1644→		} else if c.ChannelIndex != nil {
  1645→			g.write(fmt.Sprintf("case %s = <-%s[", goIdent(c.Variable), goIdent(c.Channel)))
  1646→			g.generateExpression(c.ChannelIndex)
  1647→			g.write("]:\n")
  1648→		} else {
  1649→			g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
  1650→		}
  1651→		g.indent++
  1652→		for _, s := range c.Body {
  1653→			g.generateStatement(s)
  1654→		}
  1655→		g.indent--
  1656→	}
  1657→	g.writeLine("}")
  1658→}
  1659→
  1660→func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
  1661→	// Track reference parameters for this procedure
  1662→	oldRefParams := g.refParams
  1663→	newRefParams := make(map[string]bool)
  1664→	// Inherit parent's ref params for closure captures when nested
  1665→	if g.nestingLevel > 0 {
  1666→		for k, v := range oldRefParams {
  1667→			newRefParams[k] = v
  1668→		}
  1669→	}
  1670→	for _, p := range proc.Params {
  1671→		if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
  1672→			newRefParams[p.Name] = true
  1673→		} else {
  1674→			// Own param shadows any inherited ref param with same name
  1675→			delete(newRefParams, p.Name)
  1676→		}
  1677→		// Register chan params with protocol mappings
  1678→		if p.IsChan || p.IsChanArray {
  1679→			if _, ok := g.protocolDefs[p.ChanElemType]; ok {
  1680→				g.chanProtocols[p.Name] = p.ChanElemType
  1681→			}
  1682→		}
  1683→		// Register record-typed params
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 1568,
  "limit": 200
}
  1568→func (p *Parser) parseAltBlock() *ast.AltBlock {
  1569→	block := &ast.AltBlock{Token: p.curToken}
  1570→
  1571→	// Skip to next line
  1572→	for p.peekTokenIs(lexer.NEWLINE) {
  1573→		p.nextToken()
  1574→	}
  1575→
  1576→	// Expect INDENT
  1577→	if !p.peekTokenIs(lexer.INDENT) {
  1578→		p.addError("expected indented block after ALT")
  1579→		return block
  1580→	}
  1581→	p.nextToken() // consume INDENT
  1582→
  1583→	block.Cases = p.parseAltCases()
  1584→
  1585→	return block
  1586→}
  1587→
  1588→func (p *Parser) parseAltCases() []ast.AltCase {
  1589→	var cases []ast.AltCase
  1590→	startLevel := p.indentLevel
  1591→
  1592→	p.nextToken() // move past INDENT
  1593→
  1594→	for !p.curTokenIs(lexer.EOF) {
  1595→		// Skip newlines
  1596→		for p.curTokenIs(lexer.NEWLINE) {
  1597→			p.nextToken()
  1598→		}
  1599→
  1600→		// Handle DEDENT tokens
  1601→		for p.curTokenIs(lexer.DEDENT) {
  1602→			if p.indentLevel < startLevel {
  1603→				return cases
  1604→			}
  1605→			p.nextToken()
  1606→		}
  1607→
  1608→		// Skip any more newlines after DEDENT
  1609→		for p.curTokenIs(lexer.NEWLINE) {
  1610→			p.nextToken()
  1611→		}
  1612→
  1613→		if p.curTokenIs(lexer.EOF) {
  1614→			break
  1615→		}
  1616→
  1617→		if p.indentLevel < startLevel {
  1618→			break
  1619→		}
  1620→
  1621→		// Safety guard: record position before parsing to detect no-progress
  1622→		prevToken := p.curToken
  1623→		prevPeek := p.peekToken
  1624→
  1625→		// Parse an ALT case: [guard &] channel ? var
  1626→		altCase := p.parseAltCase()
  1627→		if altCase != nil {
  1628→			cases = append(cases, *altCase)
  1629→		}
  1630→
  1631→		// No-progress guard: if we haven't moved, break to prevent infinite loop
  1632→		if p.curToken == prevToken && p.peekToken == prevPeek {
  1633→			break
  1634→		}
  1635→	}
  1636→
  1637→	return cases
  1638→}
  1639→
  1640→func (p *Parser) parseAltCase() *ast.AltCase {
  1641→	altCase := &ast.AltCase{}
  1642→
  1643→	// Check for guard: expression & channel ? var
  1644→	// For now, we expect: channel ? var (no guard support yet)
  1645→	// or: guard & channel ? var
  1646→
  1647→	// First token should be identifier (channel name or guard start)
  1648→	if !p.curTokenIs(lexer.IDENT) && !p.curTokenIs(lexer.TRUE) && !p.curTokenIs(lexer.FALSE) {
  1649→		p.addError(fmt.Sprintf("expected channel name or guard in ALT case, got %s", p.curToken.Type))
  1650→		return nil
  1651→	}
  1652→
  1653→	// Look ahead to determine if this is a guard or channel
  1654→	// If next token is & then we have a guard
  1655→	// If next token is ? then it's a channel/timer receive
  1656→	if p.peekTokenIs(lexer.RECEIVE) {
  1657→		name := p.curToken.Literal
  1658→		if p.timerNames[name] {
  1659→			// Timer case: tim ? AFTER deadline
  1660→			altCase.IsTimer = true
  1661→			altCase.Timer = name
  1662→			p.nextToken() // move to ?
  1663→			if !p.expectPeek(lexer.AFTER) {
  1664→				return nil
  1665→			}
  1666→			p.nextToken() // move past AFTER
  1667→			altCase.Deadline = p.parseExpression(LOWEST)
  1668→		} else {
  1669→			// Simple case: channel ? var
  1670→			altCase.Channel = name
  1671→			p.nextToken() // move to ?
  1672→			if !p.expectPeek(lexer.IDENT) {
  1673→				return nil
  1674→			}
  1675→			altCase.Variable = p.curToken.Literal
  1676→		}
  1677→	} else if p.peekTokenIs(lexer.LBRACKET) {
  1678→		// Indexed channel case: cs[i] ? var
  1679→		name := p.curToken.Literal
  1680→		altCase.Channel = name
  1681→		p.nextToken() // move to [
  1682→		p.nextToken() // move past [
  1683→		altCase.ChannelIndex = p.parseExpression(LOWEST)
  1684→		if !p.expectPeek(lexer.RBRACKET) {
  1685→			return nil
  1686→		}
  1687→		if !p.expectPeek(lexer.RECEIVE) {
  1688→			return nil
  1689→		}
  1690→		if !p.expectPeek(lexer.IDENT) {
  1691→			return nil
  1692→		}
  1693→		altCase.Variable = p.curToken.Literal
  1694→	} else {
  1695→		// Could be a guard followed by & channel ? var
  1696→		// For simplicity, parse expression until we hit &
  1697→		// For now, only support simple TRUE/FALSE or identifier guards
  1698→		guard := p.parseExpression(LOWEST)
  1699→		altCase.Guard = guard
  1700→
  1701→		// Expect &
  1702→		if !p.peekTokenIs(lexer.AMPERSAND) {
  1703→			p.addError("expected & after guard in ALT case")
  1704→			return nil
  1705→		}
  1706→		p.nextToken() // move to &
  1707→		p.nextToken() // move past &
  1708→
  1709→		// Now expect channel ? var or channel[index] ? var
  1710→		if !p.curTokenIs(lexer.IDENT) {
  1711→			p.addError(fmt.Sprintf("expected channel name after guard, got %s", p.curToken.Type))
  1712→			return nil
  1713→		}
  1714→		altCase.Channel = p.curToken.Literal
  1715→
  1716→		if p.peekTokenIs(lexer.LBRACKET) {
  1717→			// Indexed channel with guard: guard & cs[i] ? var
  1718→			p.nextToken() // move to [
  1719→			p.nextToken() // move past [
  1720→			altCase.ChannelIndex = p.parseExpression(LOWEST)
  1721→			if !p.expectPeek(lexer.RBRACKET) {
  1722→				return nil
  1723→			}
  1724→		}
  1725→
  1726→		if !p.expectPeek(lexer.RECEIVE) {
  1727→			return nil
  1728→		}
  1729→		if !p.expectPeek(lexer.IDENT) {
  1730→			return nil
  1731→		}
  1732→		altCase.Variable = p.curToken.Literal
  1733→	}
  1734→
  1735→	// Skip to next line for the body
  1736→	for p.peekTokenIs(lexer.NEWLINE) {
  1737→		p.nextToken()
  1738→	}
  1739→
  1740→	// Expect INDENT for body
  1741→	if !p.peekTokenIs(lexer.INDENT) {
  1742→		p.addError("expected indented body after ALT case")
  1743→		return altCase
  1744→	}
  1745→	p.nextToken() // consume INDENT
  1746→	p.nextToken() // move into body
  1747→
  1748→	altCase.Body = p.parseBodyStatements()
  1749→
  1750→	return altCase
  1751→}
  1752→
  1753→func (p *Parser) parseBlockStatements() []ast.Statement {
  1754→	var statements []ast.Statement
  1755→	startLevel := p.indentLevel
  1756→
  1757→	p.nextToken() // move past INDENT
  1758→
  1759→	for !p.curTokenIs(lexer.EOF) {
  1760→		// Skip newlines
  1761→		for p.curTokenIs(lexer.NEWLINE) {
  1762→			p.nextToken()
  1763→		}
  1764→
  1765→		// Handle DEDENT tokens
  1766→		// If we're at a DEDENT and indentLevel has dropped below startLevel,
  1767→		// this DEDENT ends our block - stop parsing
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/sort_pump.occ",
  "offset": 175,
  "limit": 30
}
   175→  SEQ
   176→    --{{{  initialise (clear screen)
   177→    SEQ
   178→      cursor.x.y (1, 1, out!)
   179→      erase.eos (out!)
   180→      flush (out!)
   181→    --}}}
   182→    --{{{  main cycle
   183→    WHILE TRUE
   184→      ALT i = 0 FOR n.inputs
   185→        BYTE ch:
   186→        in[i] ? ch
   187→          --{{{  lock on to this channel for the whole message
   188→          SEQ
   189→            WHILE ch <> end.message
   190→              SEQ
   191→                --{{{  end.marker --> end.char
   192→                IF
   193→                  ch = end.marker
   194→                    ch := end.char
   195→                  TRUE
   196→                    SKIP
   197→                --}}}
   198→                out ! ch
   199→                in[i] ? ch
   200→            flush (out!)
   201→          --}}}
   202→    --}}}
   203→:
   204→--}}}
Thinking

Now let me also check the other example files to understand the patterns needed, particularly bar.occ which has multiple ALT replicators and might have guards.

Let me check the more complex examples and the import scanning pattern.

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/bar.occ",
  "offset": 320,
  "limit": 40
}
   320→      PRI.ALT
   321→        PRI ALT
   322→          BOOL any:
   323→          terminate ? any
   324→            running := FALSE
   325→          mode.change ? local.alt.mode
   326→            SKIP
   327→          PRI ALT i = 0 FOR s
   328→            INT any:
   329→            in[i] ? any
   330→              out ! any
   331→      FAIRISH.ALT
   332→        PRI ALT
   333→          BOOL any:
   334→          terminate ? any
   335→            running := FALSE
   336→          mode.change ? local.alt.mode
   337→            SKIP
   338→          PRI ALT j = favourite FOR s
   339→            VAL INT X IS (j \ s):
   340→            INT any:
   341→            in[X] ? any
   342→              SEQ
   343→                out ! any
   344→                favourite := ((favourite + 1) \ s)
   345→      FAIR.ALT
   346→        PRI ALT
   347→          BOOL any:
   348→          terminate ? any
   349→            running := FALSE
   350→          mode.change ? local.alt.mode
   351→            SKIP
   352→          PRI ALT i = favourite FOR s
   353→            VAL INT X IS (i \ s):
   354→            INT any:
   355→            in[X] ? any
   356→              SEQ
   357→                out ! any
   358→                favourite := (X + 1)
   359→:
Grep
{
  "pattern": "func containsAlt|func containsTimer|func containsReflect|\"reflect\"",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
No matches found
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/kroc/modules/course/examples/sort_inside_2.occ",
  "offset": 218,
  "limit": 20
}
   218→      cursor.x.y (1, 1, out!)
   219→      erase.eos (out!)
   220→      flush (out!)
   221→    --}}}
   222→    --{{{  main cycle
   223→    WHILE TRUE
   224→      ALT i = 0 FOR n.inputs
   225→        BYTE ch:
   226→        in[i] ? ch
   227→          --{{{  lock on to this channel for the whole message
   228→          SEQ
   229→            WHILE ch <> end.message
   230→              SEQ
   231→                --{{{  end.marker --> end.char
   232→                IF
   233→                  ch = end.marker
   234→                    ch := end.char
   235→                  TRUE
   236→                    SKIP
   237→                --}}}
Thinking

Interesting observations:

  1. The bar.occ examples use PRI ALT i = 0 FOR s which is PRI ALT replicator, not just plain ALT. PRI ALT is listed as "not yet implemented" in the issue description and CLAUDE.md. So for now we're just focused on plain ALT replicators.

  2. The sort_inside_2.occ and sort_pump.occ use plain ALT i = 0 FOR n.inputs which is what we need.

  3. The bar.occ replicators have interesting features:

  4. PRI ALT j = favourite FOR s — non-zero start (runtime variable)
  5. Scoped declarations inside the case body (VAL INT X IS (j \ s):)
  6. Multiple statements after the case guard

  7. There's no reflect import currently. We'll need to add it.

Now I'm thinking through the codegen approach. The issue suggests reflect.Select, and I need to figure out how to translate a replicated ALT into that. For a replicated ALT with a loop variable and channel array, I'd need to dynamically build the select cases at runtime using reflection rather than generating them statically.

Building the select cases...

The body comes after the channel receive, and in a replicated ALT, each case template gets instantiated n times with the replicator variable i bound to the chosen index. When parsing, I need to handle the replicator declaration, then parse the single case template that will be replicated across the range.

Looking at how replicated IF structures work, the IfStatement stores both the template choices and the replicator separately. I should apply the same pattern to AltBlock — store the case template alongside the replicator, so when a replicator is present, the cases represent the template rather than multiple distinct cases.

For code generation, I need to use reflect.Select to dynamically choose among the replicated channels at runtime. The approach is to build a slice of SelectCase values in a loop, evaluate the replicator count once, then use reflect.Select to pick which channel received a value — the chosen index becomes the replicator variable.

Now I'm realizing there's a subtlety when the replicator doesn't start at zero: if the loop is ALT i = 5 FOR 3, the cases array indexes 0, 1, 2 but they correspond to channels in[5], in[6], in[7], so I need to add the start offset to the chosen index to get the actual replicator value.

For the scoped variable declarations like BYTE ch:, these are local to each case body and receive the channel value. When using reflect.Select, the value comes back as a reflect.Value that needs type conversion based on the channel's element type—for byte channels it's byte(_value.Int()), for int channels it's int(_value.Int()), and so on. The challenge is that I might not always know the channel type at code generation time, so I need to figure out how to handle that.

Actually, using .Interface() with a type assertion is cleaner—something like ch := _value.Interface().(byte)—since it works for any type without needing to know the specifics upfront. Now I'm thinking through how the containsX() scanner pattern fits into this.

Grep
{
  "pattern": "func contains|import.*scanning|needsImport",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
No matches found
Grep
{
  "pattern": "import|\\\"sync\\\"|\\\"fmt\\\"|\\\"time\\\"",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true,
  "head_limit": 30
}
3:import (
4:	"fmt"
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
189:	// Detect entry point PROC so we can set import flags before writing imports
204:	// Write imports
206:		g.writeLine("import (")
212:			g.writeLine(`"fmt"`)
224:			g.writeLine(`"sync"`)
227:			g.writeLine(`"time"`)

[Showing results with pagination = limit: 30, offset: 0]
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 11,
  "limit": 30
}
    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→
    22→	// Track procedure signatures for proper pointer handling
    23→	procSigs map[string][]ast.ProcParam
    24→	// Track current procedure's reference parameters
    25→	refParams map[string]bool
    26→
    27→	// Protocol support
    28→	protocolDefs  map[string]*ast.ProtocolDecl
    29→	chanProtocols map[string]string // channel name → protocol name
    30→	tmpCounter    int               // for unique temp variable names
    31→
    32→	// Record support
    33→	recordDefs map[string]*ast.RecordDecl
    34→	recordVars map[string]string // variable name → record type name
    35→
    36→	// Nesting level: 0 = package level, >0 = inside a function
    37→	nestingLevel int
    38→
    39→	// RETYPES parameter renames: when a RETYPES declaration shadows a
    40→	// parameter (e.g. VAL INT X RETYPES X :), the parameter is renamed
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 185,
  "limit": 65
}
   185→			mainStatements = append(mainStatements, stmt)
   186→		}
   187→	}
   188→
   189→	// Detect entry point PROC so we can set import flags before writing imports
   190→	var entryProc *ast.ProcDecl
   191→	if len(mainStatements) == 0 {
   192→		entryProc = g.findEntryProc(procDecls)
   193→		if entryProc != nil {
   194→			g.needOs = true
   195→			g.needSync = true
   196→			g.needBufio = true
   197→		}
   198→	}
   199→
   200→	// Write package declaration
   201→	g.writeLine("package main")
   202→	g.writeLine("")
   203→
   204→	// Write imports
   205→	if g.needSync || g.needFmt || g.needTime || g.needOs || g.needMath || g.needMathBits || g.needBufio {
   206→		g.writeLine("import (")
   207→		g.indent++
   208→		if g.needBufio {
   209→			g.writeLine(`"bufio"`)
   210→		}
   211→		if g.needFmt {
   212→			g.writeLine(`"fmt"`)
   213→		}
   214→		if g.needMath {
   215→			g.writeLine(`"math"`)
   216→		}
   217→		if g.needMathBits {
   218→			g.writeLine(`"math/bits"`)
   219→		}
   220→		if g.needOs {
   221→			g.writeLine(`"os"`)
   222→		}
   223→		if g.needSync {
   224→			g.writeLine(`"sync"`)
   225→		}
   226→		if g.needTime {
   227→			g.writeLine(`"time"`)
   228→		}
   229→		g.indent--
   230→		g.writeLine(")")
   231→		g.writeLine("")
   232→	}
   233→
   234→	// Emit transputer intrinsic helper functions
   235→	if g.needMathBits {
   236→		g.emitIntrinsicHelpers()
   237→	}
   238→
   239→	// Generate type definitions first (at package level)
   240→	for _, stmt := range typeDecls {
   241→		g.generateStatement(stmt)
   242→	}
   243→
   244→	// Generate package-level abbreviations (constants)
   245→	for _, stmt := range abbrDecls {
   246→		abbr := stmt.(*ast.Abbreviation)
   247→		if abbr.Type == "" {
   248→			// Untyped VAL: let Go infer the type
   249→			g.builder.WriteString("var ")
Thinking

Now let me check how the import flags are set. They must be set during the first pass (scan/collect phase) before code generation.

Grep
{
  "pattern": "needSync = true|needFmt = true|needTime = true",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
109:			g.needSync = true
112:			g.needFmt = true
115:			g.needTime = true
119:			g.needFmt = true
195:			g.needSync = true
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 90,
  "limit": 35
}
    90→	g.builder.Reset()
    91→	g.needSync = false
    92→	g.needFmt = false
    93→	g.needTime = false
    94→	g.needOs = false
    95→	g.needMath = false
    96→	g.needMathBits = false
    97→	g.needBufio = false
    98→	g.procSigs = make(map[string][]ast.ProcParam)
    99→	g.refParams = make(map[string]bool)
   100→	g.protocolDefs = make(map[string]*ast.ProtocolDecl)
   101→	g.chanProtocols = make(map[string]string)
   102→	g.tmpCounter = 0
   103→	g.recordDefs = make(map[string]*ast.RecordDecl)
   104→	g.recordVars = make(map[string]string)
   105→
   106→	// First pass: collect procedure signatures, protocols, and check for PAR/print
   107→	for _, stmt := range program.Statements {
   108→		if g.containsPar(stmt) {
   109→			g.needSync = true
   110→		}
   111→		if g.containsPrint(stmt) {
   112→			g.needFmt = true
   113→		}
   114→		if g.containsTimer(stmt) {
   115→			g.needTime = true
   116→		}
   117→		if g.containsStop(stmt) {
   118→			g.needOs = true
   119→			g.needFmt = true
   120→		}
   121→		if g.containsMostExpr(stmt) {
   122→			g.needMath = true
   123→		}
   124→		if g.containsIntrinsics(stmt) {
Grep
{
  "pattern": "func \\(g \\*Generator\\) containsPar",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true,
  "-A": 3
}
476:func (g *Generator) containsPar(stmt ast.Statement) bool {
477-	switch s := stmt.(type) {
478-	case *ast.ParBlock:
479-		return true

Now let me look at the AltCase parsing more carefully — specifically the scoped declarations pattern in the sort_pump example.

Grep
{
  "pattern": "parseBodyStatements|ScopedDecl",
  "path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "output_mode": "content",
  "-n": true,
  "head_limit": 10
}
1748:	altCase.Body = p.parseBodyStatements()
1806:// parseBodyStatements parses multiple statements inside a branch body
1810:func (p *Parser) parseBodyStatements() []ast.Statement {
2440:	loop.Body = p.parseBodyStatements()
2522:				choice.Body = p.parseBodyStatements()
2607:			choice.Body = p.parseBodyStatements()

[Showing results with pagination = limit: 10, offset: 0]
Thinking

Now I have a good understanding. Let me also understand a key challenge: the ALT case structure in a replicated ALT.

In the sort_pump example:

ALT i = 0 FOR n.inputs
  BYTE ch:           <-- scoped declaration
  in[i] ? ch         <-- channel input (the ALT guard)
    SEQ              <-- body
      ...

The scoped declaration BYTE ch: is parsed as part of the ALT case by parseAltCase(). But wait, looking at the current parseAltCase(), it expects the first token to be an identifier for the channel name or guard.

I'm realizing that a BYTE token (type keyword) wouldn't be recognized by the current parser, which only checks for IDENT, TRUE, or FALSE tokens at the start of an ALT case. This means scoped declarations in ALT cases aren't currently supported, so I need to check whether the existing tests even cover this scenario.

Looking at how replicated ALTs work in occam, the structure allows declarations to scope to individual case templates, but the current parseAltCase implementation doesn't account for this—it expects the first token to be a channel name or guard, not a type declaration. the channel input in an ALT case. Looking at the current implementation, it doesn't handle declarations at all, even for non-replicated ALTs—but issue #45 is specifically about the replicator. For replicated ALTs, each instance of the template case needs scoped declarations like BYTE ch:, followed by the channel input and body. The key insight is that in the generated code, the scoped declaration has to come after the reflect.Select returns, since the variable gets its value from the received data.

Structuring the generated code... I need to figure out how to extract the actual value from the reflect.Value that comes back from the channel receive. The cleanest approach is probably to use .Interface() with a type assertion, but that requires knowing the channel's element type at codegen time—which the protocol tracking system should already have available. For the replicated ALT case, the template knows the channel expression and the variable it's receiving into, so I can use that type information to generate the correct type assertion. I need to track the variable type from the scoped declaration so I can generate the proper type assertion when using reflect.Select. The parser currently doesn't capture declarations within ALT cases, so I'll need to either extend parseAltCase() to handle them or manage them separately for the replicated ALT path. The receive variable needs to be scoped within the replicated ALT since it's declared as part of the template. I should add a Replicator field to AltBlock and modify the parser to detect the replicator pattern, then parse the case template which can include leading declarations—when the parser encounters a type keyword instead of an identifier, it knows to parse that as a scoped declaration first.

Looking at the actual bar.occ example with its replicated ALT structure, I can see how declarations like INT any: come before the channel input, and with abbreviations like VAL INT X IS, the pattern becomes clearer for how to handle this in the parser.

So my approach is to add a Replicator field to AltBlock and a Declarations field to AltCase to capture these scoped declarations, then update the parser to handle them before parsing the channel input.

For code generation, I need to detect when an AltBlock has a replicator and generate the reflect.Select pattern accordingly. I'll add a needReflect flag to the generator and scan for replicated ALT blocks to set it, then make sure the reflect package gets imported.

Generating the reflect.Select code...

Now I'm realizing the type conversion needs careful handling—when reflect.Select returns a value from a chan byte, it comes back as Uint8 in the reflect system, so I need to properly cast it back to the original channel element type.

The key insight is that I should look at the declaration of the receive variable itself to determine what type to assert to. If the variable is declared as BYTE ch:, I know to generate a byte type assertion; if it's INT any:, I generate an int assertion instead. I think approach (a) makes sense—I can extract the type information from the declarations when generating code. But I'm realizing there's a simpler path: for integer types, I can just use _value.Int() and cast it to the target type like int(_value.Int()) or byte(_value.Int()). This handles the common cases in Occam where channels carry integers or bytes, though I should verify what other channel types are actually supported in the language. I'm looking at the AltCase struct and realizing I need to capture the scoped declarations to properly handle type information during code generation. The challenge is that for replicated ALT statements, I need access to the channel array name, the index expression, and the receive variable's type—which comes from those declarations. Adding a Declarations field would work but feels like it's overcomplicating things. Let me step back and look at the actual pattern in the sort_pump example to see if there's a simpler approach.

Generating the reflect-based code... I'm realizing that in occam semantics, replicated ALT variables must be scoped to each case template since every replicated instance needs its own variable, so scoped declarations will always be present. Now I'm mapping out the implementation: I need to add a Replicator field to AltBlock and a Declarations field to AltCase, update the parser to detect replicators after ALT and handle scoped declarations in cases, and add reflection support to the codegen along with a scanner to detect replicated ALTs.

For the type mapping in the reflect codegen, I'm working through how to convert declaration types to Go types and their corresponding reflect.Value methods...

Actually, using .Interface() with type assertion is the cleanest approach that works universally across all types—I can just extract the value and cast it to the target type, letting Go's type system handle the conversion.

Now I need to figure out how to get the Go type from the scoped declaration. The VarDecl struct has a TypeName field that gives me the occam type name, which I can use to map to the corresponding Go type.

Grep
{
  "pattern": "func.*goType|func.*mapType|case \\\"INT\\\"|case \\\"BYTE\\\"",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true,
  "-C": 3
}
917-
918-func (g *Generator) generateMostExpr(e *ast.MostExpr) {
919-	switch e.ExprType {
920:	case "INT":
921-		if e.IsNeg {
922-			g.write("math.MinInt")
923-		} else {
924-			g.write("math.MaxInt")
925-		}
926:	case "BYTE":
927-		if e.IsNeg {
928-			g.write("0")
929-		} else {
--
1393-// (used inside protocol generation to avoid infinite recursion)
1394-func (g *Generator) occamTypeToGoBase(occamType string) string {
1395-	switch occamType {
1396:	case "INT":
1397-		return "int"
1398:	case "BYTE":
1399-		return "byte"
1400-	case "BOOL":
1401-		return "bool"
--
1412-
1413-func (g *Generator) occamTypeToGo(occamType string) string {
1414-	switch occamType {
1415:	case "INT":
1416-		return "int"
1417:	case "BYTE":
1418-		return "byte"
1419-	case "BOOL":
1420-		return "bool"

Good, occamTypeToGo() already exists. Now let me also check how variable declarations are handled in the AST to understand the Declarations field.

Grep
{
  "pattern": "type VarDecl struct",
  "path": "/home/david/projects/code-associates/occam2go/ast/ast.go",
  "output_mode": "content",
  "-n": true,
  "-A": 10
}
37:type VarDecl struct {
38-	Token lexer.Token // the type token (INT, BYTE, BOOL)
39-	Type  string      // "INT", "BYTE", "BOOL", etc.
40-	Names []string    / variable names (can declare multiple: INT x, y, z:)
41-}
42-
43-func (v *VarDecl) statementNode()       {}
44-func (v *VarDecl) TokenLiteral() string { return v.Token.Literal }
45-
46-/ ArrayDecl represents an array declaration: [5]INT arr:
47-type ArrayDecl struct {
Thinking

Now I have a comprehensive understanding. Let me also quickly look at one more thing - the parseAltCase to understand how it handles declarations. When parsing the ALT case, the current code expects IDENT as the first token. But with a scoped declaration like BYTE ch:, the first token would be BYTE (type token). So we need to handle this.

Looking at the parser, type declarations start with type tokens like INT_TYPE, BYTE_TYPE, etc. in the lexer. Let me check the lexer token types.

Grep
{
  "pattern": "INT_TYPE|BYTE_TYPE|BOOL_TYPE.*=",
  "path": "/home/david/projects/code-associates/occam2go/lexer/token.go",
  "output_mode": "content",
  "-n": true
}
77:	INT_TYPE
78:	BYTE_TYPE
167:	INT_TYPE:  "INT",
168:	BYTE_TYPE: "BYTE",
215:	"INT":   INT_TYPE,
216:	"BYTE":  BYTE_TYPE,
Grep
{
  "pattern": "case lexer\\.(INT_TYPE|BYTE_TYPE|BOOL_TYPE)",
  "path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "output_mode": "content",
  "-n": true,
  "head_limit": 10
}
168:	case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE, lexer.REAL32_TYPE, lexer.REAL64_TYPE:
915:	case lexer.INT_TYPE:
917:	case lexer.BYTE_TYPE:
919:	case lexer.BOOL_TYPE:
2768:	case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE, lexer.REAL32_TYPE, lexer.REAL64_TYPE:

[Showing results with pagination = limit: 10, offset: 0]
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 165,
  "limit": 15
}
   165→		return p.parseAbbreviation()
   166→	case lexer.INITIAL:
   167→		return p.parseInitialDecl()
   168→	case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE, lexer.REAL32_TYPE, lexer.REAL64_TYPE:
   169→		if p.peekTokenIs(lexer.FUNCTION) || p.peekTokenIs(lexer.FUNC) || p.peekTokenIs(lexer.COMMA) || p.peekTokenIs(lexer.INLINE) {
   170→			return p.parseFuncDecl()
   171→		}
   172→		return p.parseVarDeclOrAbbreviation()
   173→	case lexer.LBRACKET:
   174→		return p.parseArrayDecl()
   175→	case lexer.CHAN:
   176→		return p.parseChanDecl()
   177→	case lexer.PROTOCOL:
   178→		return p.parseProtocolDecl()
   179→	case lexer.RECORD:

I now have a thorough understanding of all the pieces. Let me write the implementation plan.

📝 Write sparkling-booping-grove.md
/home/david/.claude/plans/sparkling-booping-grove.md
# Plan: Support ALT Replicators (Issue #45)

## Context

The transpiler supports replicated SEQ, PAR, and IF but not replicated ALT (`ALT i = 0 FOR n`). This blocks 5 out of 16 course example files. The replicated ALT dynamically creates N channel alternatives and selects whichever is ready first. Since Go's `select` requires statically-known cases, the codegen must use `reflect.Select` for runtime-variable case counts.

## Changes

### 1. AST (`ast/ast.go`)

Add `Replicator` field to `AltBlock` and `Declarations` field to `AltCase`:

```go
type AltBlock struct {
    Token      lexer.Token
    Cases      []AltCase
    Replicator *Replicator   // NEW: optional ALT i = start FOR count
}

type AltCase struct {
    // ... existing fields ...
    Declarations []Statement  // NEW: scoped declarations before channel input (e.g., BYTE ch:)
}
```

The `Declarations` field is needed because replicated ALT cases commonly have scoped variable declarations before the channel input (e.g., `BYTE ch:` / `VAL INT X IS (j \ s):`).

### 2. Parser (`parser/parser.go`)

**`parseAltBlock()`** (~line 1568): Add replicator detection after consuming ALT token, using the same pattern as `parseSeqBlock()`/`parseParBlock()`/`parseIfStatement()`:

```go
// Check for replicator: ALT i = start FOR count
if p.peekTokenIs(lexer.IDENT) {
    // Save state, peek ahead for =
    p.nextToken()
    if p.peekTokenIs(lexer.EQ) {
        block.Replicator = p.parseReplicator()
    } else {
        // Not a replicator — error (ALT doesn't take bare IDENT after keyword)
        p.addError("unexpected identifier after ALT")
    }
}
```

**`parseAltCase()`** (~line 1640): Handle scoped declarations before the channel input. When the current token is a type keyword (`INT_TYPE`, `BYTE_TYPE`, etc.) or `VAL` or `LBRACKET` (for array decls), parse it as a declaration and store in `altCase.Declarations`. Loop until we hit an identifier that starts a channel input.

### 3. Codegen (`codegen/codegen.go`)

**Generator struct**: Add `needReflect bool` field.

**Import scanning** (~line 106): Add `containsAltReplicator()` scanner that walks the AST checking for `AltBlock` nodes with non-nil `Replicator`. Set `g.needReflect = true`.

**Import block** (~line 204): Add `"reflect"` to import list when `needReflect` is true.

**`generateAltBlock()`** (~line 1604): When `alt.Replicator != nil`, generate `reflect.Select` code:

```go
// Generated pattern for: ALT i = 0 FOR n  /  BYTE ch: / in[i] ? ch / body
{
    _count := <count>
    _cases := make([]reflect.SelectCase, _count)
    for _i := 0; _i < _count; _i++ {
        _cases[_i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(in[_i])}
    }
    _chosen, _value, _ := reflect.Select(_cases)
    i := <start> + _chosen
    var ch byte           // from scoped declaration
    ch = _value.Interface().(byte)  // type from declaration
    // ... body ...
}
```

Key details:
- Build `reflect.SelectCase` slice with `Dir: reflect.SelectRecv` and `Chan: reflect.ValueOf(channelArray[index])`
- The start expression is added to `_chosen` to compute the replicator variable
- Scoped declarations are generated as local `var` statements
- The receive variable is assigned from `_value.Interface().(goType)` where `goType` comes from `occamTypeToGo()` on the declaration type
- STEP support: use counter-based pattern like PAR replicator
- Handle scoped abbreviations (like `VAL INT X IS (j \ s):`) by generating them after the replicator variable is set

### 4. Tests

**Parser test** (`parser/parser_test.go`): Test that `ALT i = 0 FOR n` parses correctly with replicator, scoped declarations, indexed channel input, and body.

**E2e test** (`codegen/e2e_test.go` or `codegen/e2e_replicator_test.go`): Test replicated ALT with channel arrays — spawn N goroutines sending on channels, use replicated ALT to receive, verify correct value received.

### 5. Documentation (`TODO.md`, `CLAUDE.md`)

Update to reflect ALT replicator support.

## Verification

```bash
go test ./...                    # all tests pass
go test ./parser -run TestAlt    # parser tests for replicated ALT
go test ./codegen -run TestE2E   # e2e tests including replicated ALT

# Real-world validation:
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 \
    -o /tmp/sort_pump.go kroc/modules/course/examples/sort_pump.occ
```
File created successfully at: /home/david/.claude/plans/sparkling-booping-grove.md
ExitPlanMode
{
  "allowedPrompts": [
    {
      "tool": "Bash",
      "prompt": "run tests"
    },
    {
      "tool": "Bash",
      "prompt": "build the binary"
    },
    {
      "tool": "Bash",
      "prompt": "transpile occam examples"
    }
  ],
  "plan": "# Plan: Support ALT Replicators (Issue #45)\n\n## Context\n\nThe transpiler supports replicated SEQ, PAR, and IF but not replicated ALT (`ALT i = 0 FOR n`). This blocks 5 out of 16 course example files. The replicated ALT dynamically creates N channel alternatives and selects whichever is ready first. Since Go's `select` requires statically-known cases, the codegen must use `reflect.Select` for runtime-variable case counts.\n\n## Changes\n\n### 1. AST (`ast/ast.go`)\n\nAdd `Replicator` field to `AltBlock` and `Declarations` field to `AltCase`:\n\n```go\ntype AltBlock struct {\n    Token      lexer.Token\n    Cases      []AltCase\n    Replicator *Replicator   // NEW: optional ALT i = start FOR count\n}\n\ntype AltCase struct {\n    // ... existing fields ...\n    Declarations []Statement  // NEW: scoped declarations before channel input (e.g., BYTE ch:)\n}\n```\n\nThe `Declarations` field is needed because replicated ALT cases commonly have scoped variable declarations before the channel input (e.g., `BYTE ch:` / `VAL INT X IS (j \\ s):`).\n\n### 2. Parser (`parser/parser.go`)\n\n**`parseAltBlock()`** (~line 1568): Add replicator detection after consuming ALT token, using the same pattern as `parseSeqBlock()`/`parseParBlock()`/`parseIfStatement()`:\n\n```go\n// Check for replicator: ALT i = start FOR count\nif p.peekTokenIs(lexer.IDENT) {\n    // Save state, peek ahead for =\n    p.nextToken()\n    if p.peekTokenIs(lexer.EQ) {\n        block.Replicator = p.parseReplicator()\n    } else {\n        // Not a replicator — error (ALT doesn't take bare IDENT after keyword)\n        p.addError(\"unexpected identifier after ALT\")\n    }\n}\n```\n\n**`parseAltCase()`** (~line 1640): Handle scoped declarations before the channel input. When the current token is a type keyword (`INT_TYPE`, `BYTE_TYPE`, etc.) or `VAL` or `LBRACKET` (for array decls), parse it as a declaration and store in `altCase.Declarations`. Loop until we hit an identifier that starts a channel input.\n\n### 3. Codegen (`codegen/codegen.go`)\n\n**Generator struct**: Add `needReflect bool` field.\n\n**Import scanning** (~line 106): Add `containsAltReplicator()` scanner that walks the AST checking for `AltBlock` nodes with non-nil `Replicator`. Set `g.needReflect = true`.\n\n**Import block** (~line 204): Add `\"reflect\"` to import list when `needReflect` is true.\n\n**`generateAltBlock()`** (~line 1604): When `alt.Replicator != nil`, generate `reflect.Select` code:\n\n```go\n// Generated pattern for: ALT i = 0 FOR n  /  BYTE ch: / in[i] ? ch / body\n{\n    _count := <count>\n    _cases := make([]reflect.SelectCase, _count)\n    for _i := 0; _i < _count; _i++ {\n        _cases[_i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(in[_i])}\n    }\n    _chosen, _value, _ := reflect.Select(_cases)\n    i := <start> + _chosen\n    var ch byte           // from scoped declaration\n    ch = _value.Interface().(byte)  // type from declaration\n    // ... body ...\n}\n```\n\nKey details:\n- Build `reflect.SelectCase` slice with `Dir: reflect.SelectRecv` and `Chan: reflect.ValueOf(channelArray[index])`\n- The start expression is added to `_chosen` to compute the replicator variable\n- Scoped declarations are generated as local `var` statements\n- The receive variable is assigned from `_value.Interface().(goType)` where `goType` comes from `occamTypeToGo()` on the declaration type\n- STEP support: use counter-based pattern like PAR replicator\n- Handle scoped abbreviations (like `VAL INT X IS (j \\ s):`) by generating them after the replicator variable is set\n\n### 4. Tests\n\n**Parser test** (`parser/parser_test.go`): Test that `ALT i = 0 FOR n` parses correctly with replicator, scoped declarations, indexed channel input, and body.\n\n**E2e test** (`codegen/e2e_test.go` or `codegen/e2e_replicator_test.go`): Test replicated ALT with channel arrays — spawn N goroutines sending on channels, use replicated ALT to receive, verify correct value received.\n\n### 5. Documentation (`TODO.md`, `CLAUDE.md`)\n\nUpdate to reflect ALT replicator support.\n\n## Verification\n\n```bash\ngo test ./...                    # all tests pass\ngo test ./parser -run TestAlt    # parser tests for replicated ALT\ngo test ./codegen -run TestE2E   # e2e tests including replicated ALT\n\n# Real-world validation:\n./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 \\\n    -o /tmp/sort_pump.go kroc/modules/course/examples/sort_pump.occ\n```\n"
}
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]