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 issueThis is a well-documented issue about adding support for ALT replicators. Let me understand the scope:
ALT i = 0 FOR n syntax (replicated ALT)reflect.Select since Go's select can't handle dynamic case countsLet 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.
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.
{
"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>"
}
]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.
{
"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{
"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{
"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→--}}}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.
{
"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→:
{
"pattern": "func containsAlt|func containsTimer|func containsReflect|\"reflect\"",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}{
"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→ --}}}Interesting observations:
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.
The sort_inside_2.occ and sort_pump.occ use plain ALT i = 0 FOR n.inputs which is what we need.
The bar.occ replicators have interesting features:
PRI ALT j = favourite FOR s — non-zero start (runtime variable)VAL INT X IS (j \ s):)Multiple statements after the case guard
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.
{
"pattern": "func contains|import.*scanning|needsImport",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}{
"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]{
"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{
"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 ")Now let me check how the import flags are set. They must be set during the first pass (scan/collect phase) before code generation.
{
"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
{
"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) {{
"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 trueNow let me look at the AltCase parsing more carefully — specifically the scoped declarations pattern in the sort_pump example.
{
"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]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.
{
"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.
{
"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 {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.
{
"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,
{
"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]
{
"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.
# 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
```
{
"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.