Claude Code transcript - page 1/2

Implement the following plan:

Fix #85: Support receive into indexed target variables

Context

Channel receive (ch ? var) only accepts a simple identifier as the target. Indexed targets like flags[0] or state.of.neighbour[d] cause parse failures because the parser uses expectPeek(IDENT) and leaves the [...] unprocessed. The same limitation affects ALT case parsing. This blocks historical-examples/life.occ (issue #72).

The fix adds VariableIndices []Expression to Receive and AltCase, mirroring the existing ChannelIndices and Assignment.Indices patterns.

Changes

1. AST — ast/ast.go

Add VariableIndices []Expression field to both structs:

  • Receive (line 378): Add after Variable string
  • AltCase (line 402): Add after Variable string

2. Parser — parser/parser.go

After each expectPeek(IDENT) + Variable = curToken.Literal site, add index collection loop:

for p.peekTokenIs(lexer.LBRACKET) {
    p.nextToken() // move to [
    p.nextToken() // move past [
    <target>.VariableIndices = append(<target>.VariableIndices, p.parseExpression(LOWEST))
    if !p.expectPeek(lexer.RBRACKET) {
        return nil
    }
}

5 locations: 1. parseReceive() (~line 1294) — stmt.VariableIndices 2. parseIndexedOperation() receive branch (~line 792) — stmt.VariableIndices 3. parseAltCase() simple channel (~line 1765) — altCase.VariableIndices 4. parseAltCase() indexed channel (~line 1785) — altCase.VariableIndices 5. parseAltCase() guarded channel (~line 1825) — altCase.VariableIndices

3. Codegen — codegen/codegen.go

Build variable reference with indices using existing generateIndicesStr():

6 locations: 1. generateReceive() simple path (~line 1362): Append indices to varRef 2. generateReceive() sequential path (~line 1349): Append indices to primary varRef 3. generateAltBlock() guarded case (line 1906): Build varRef with indices 4. generateAltBlock() indexed channel case (line 1908): Build varRef with indices 5. generateAltBlock() simple channel case (line 1912): Build varRef with indices 6. generateReplicatedAlt() (line 2043): Build varRef with indices

Pattern at each site:

varRef := goIdent(name)
if len(indices) > 0 {
    varRef += g.generateIndicesStr(indices)
}

4. Tests

Parser tests (parser/parser_test.go): - TestReceiveIndexedVariable: ch ? flags[0] — verify VariableIndices has 1 entry - TestReceiveMultiIndexedVariable: ch ? grid[i][j] — verify 2 indices - TestIndexedChannelReceiveIndexedVariable: cs[0] ? flags[1] — both channel and variable indexed

E2E tests (codegen/e2e_concurrency_test.go): - TestE2E_ReceiveIntoIndexedVariable: Simple channel receive into array element - TestE2E_IndexedChannelReceiveIntoIndexedVariable: cs[i] ? arr[j] pattern

Verification

go test ./...

If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/cccd5ad5-122e-445d-bd16-94c2d59c7a1a.jsonl

Thinking

Let me implement this plan step by step. I'll start by reading the relevant files to understand the current code, then make the changes.

I'll implement this plan. Let me start by reading the relevant source files in parallel.

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/ast/ast.go",
  "offset": 370,
  "limit": 50
}
   370→func (s *Send) statementNode()       {}
   371→func (s *Send) TokenLiteral() string { return s.Token.Literal }
   372→
   373→// Receive represents a channel receive: c ? x or c ? x ; y
   374→type Receive struct {
   375→	Token          lexer.Token  // the ? token
   376→	Channel        string       // channel name
   377→	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x
   378→	Variable       string       // variable to receive into (simple receive)
   379→	Variables      []string     // additional variables for sequential receives (c ? x ; y)
   380→}
   381→
   382→func (r *Receive) statementNode()       {}
   383→func (r *Receive) TokenLiteral() string { return r.Token.Literal }
   384→
   385→// AltBlock represents an ALT block (alternation/select)
   386→// If Replicator is non-nil, this is a replicated ALT (ALT i = 0 FOR n)
   387→type AltBlock struct {
   388→	Token      lexer.Token // the ALT token
   389→	Cases      []AltCase
   390→	Replicator *Replicator // optional replicator
   391→	Priority   bool        // true for PRI ALT
   392→}
   393→
   394→func (a *AltBlock) statementNode()       {}
   395→func (a *AltBlock) TokenLiteral() string { return a.Token.Literal }
   396→
   397→// AltCase represents a single case in an ALT block
   398→type AltCase struct {
   399→	Guard          Expression   // optional guard condition (nil if no guard)
   400→	Channel        string       // channel name
   401→	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
   402→	Variable       string       // variable to receive into
   403→	Body           []Statement  // the body to execute
   404→	IsTimer        bool         // true if this is a timer AFTER case
   405→	IsSkip         bool         // true if this is a guarded SKIP case (guard & SKIP)
   406→	Timer          string       // timer name (when IsTimer)
   407→	Deadline       Expression   // AFTER deadline expression (when IsTimer)
   408→	Declarations   []Statement  // scoped declarations before channel input (e.g., BYTE ch:)
   409→}
   410→
   411→// TimerDecl represents a timer declaration: TIMER tim:
   412→type TimerDecl struct {
   413→	Token lexer.Token // the TIMER token
   414→	Names []string    // timer variable names
   415→}
   416→
   417→func (td *TimerDecl) statementNode()       {}
   418→func (td *TimerDecl) TokenLiteral() string { return td.Token.Literal }
   419→
Grep
{
  "pattern": "func \\(p \\*Parser\\) parseReceive",
  "path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "output_mode": "content",
  "-n": true
}
1274:func (p *Parser) parseReceive() ast.Statement {
Grep
{
  "pattern": "func \\(p \\*Parser\\) parseAltCase",
  "path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "output_mode": "content",
  "-n": true
}
1648:func (p *Parser) parseAltCases() []ast.AltCase {
1713:func (p *Parser) parseAltCase() *ast.AltCase {
Grep
{
  "pattern": "func \\(g \\*Generator\\) generateReceive",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
1337:func (g *Generator) generateReceive(recv *ast.Receive) {
Grep
{
  "pattern": "func \\(g \\*Generator\\) generateAltBlock",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
1860:func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
Grep
{
  "pattern": "func \\(g \\*Generator\\) generateReplicatedAlt",
  "path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "output_mode": "content",
  "-n": true
}
1935:func (g *Generator) generateReplicatedAlt(alt *ast.AltBlock) {

Now let me read the specific parser and codegen sections I need to modify.

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 1274,
  "limit": 50
}
  1274→func (p *Parser) parseReceive() ast.Statement {
  1275→	channel := p.curToken.Literal
  1276→
  1277→	p.nextToken() // move to ?
  1278→	recvToken := p.curToken
  1279→
  1280→	// Check for variant receive: c ? CASE
  1281→	if p.peekTokenIs(lexer.CASE) {
  1282→		p.nextToken() // move to CASE
  1283→		return p.parseVariantReceive(channel, recvToken)
  1284→	}
  1285→
  1286→	stmt := &ast.Receive{
  1287→		Channel: channel,
  1288→		Token:   recvToken,
  1289→	}
  1290→
  1291→	if !p.expectPeek(lexer.IDENT) {
  1292→		return nil
  1293→	}
  1294→	stmt.Variable = p.curToken.Literal
  1295→
  1296→	// Check for sequential receive: c ? x ; y ; z
  1297→	for p.peekTokenIs(lexer.SEMICOLON) {
  1298→		p.nextToken() // move to ;
  1299→		if !p.expectPeek(lexer.IDENT) {
  1300→			return nil
  1301→		}
  1302→		stmt.Variables = append(stmt.Variables, p.curToken.Literal)
  1303→	}
  1304→
  1305→	return stmt
  1306→}
  1307→
  1308→func (p *Parser) parseVariantReceive(channel string, token lexer.Token) *ast.VariantReceive {
  1309→	stmt := &ast.VariantReceive{
  1310→		Token:   token,
  1311→		Channel: channel,
  1312→	}
  1313→
  1314→	// Skip to next line
  1315→	for p.peekTokenIs(lexer.NEWLINE) {
  1316→		p.nextToken()
  1317→	}
  1318→
  1319→	// Expect INDENT
  1320→	if !p.peekTokenIs(lexer.INDENT) {
  1321→		p.addError("expected indented block after ? CASE")
  1322→		return stmt
  1323→	}
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 770,
  "limit": 50
}
   770→	}
   771→
   772→	if p.peekTokenIs(lexer.RECEIVE) {
   773→		// Indexed channel receive: cs[i] ? x or cs[i][j] ? x or cs[i] ? CASE ...
   774→		p.nextToken() // move to ?
   775→		recvToken := p.curToken
   776→
   777→		// Check for variant receive: cs[i] ? CASE
   778→		if p.peekTokenIs(lexer.CASE) {
   779→			p.nextToken() // move to CASE
   780→			return p.parseVariantReceiveWithIndex(name, indices, recvToken)
   781→		}
   782→
   783→		stmt := &ast.Receive{
   784→			Token:          recvToken,
   785→			Channel:        name,
   786→			ChannelIndices: indices,
   787→		}
   788→
   789→		if !p.expectPeek(lexer.IDENT) {
   790→			return nil
   791→		}
   792→		stmt.Variable = p.curToken.Literal
   793→
   794→		// Check for sequential receive
   795→		for p.peekTokenIs(lexer.SEMICOLON) {
   796→			p.nextToken() // move to ;
   797→			if !p.expectPeek(lexer.IDENT) {
   798→				return nil
   799→			}
   800→			stmt.Variables = append(stmt.Variables, p.curToken.Literal)
   801→		}
   802→
   803→		return stmt
   804→	}
   805→
   806→	// Default: treat as indexed assignment (shouldn't reach here normally)
   807→	p.addError(fmt.Sprintf("expected :=, !, or ? after %s[...], got %s", name, p.peekToken.Type))
   808→	return nil
   809→}
   810→
   811→func (p *Parser) parseIndexExpression(left ast.Expression) *ast.IndexExpr {
   812→	expr := &ast.IndexExpr{
   813→		Token: p.curToken,
   814→		Left:  left,
   815→	}
   816→
   817→	p.nextToken() // move past [
   818→	expr.Index = p.parseExpression(LOWEST)
   819→
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 1713,
  "limit": 150
}
  1713→func (p *Parser) parseAltCase() *ast.AltCase {
  1714→	altCase := &ast.AltCase{}
  1715→
  1716→	// Parse scoped declarations before the channel input (e.g., BYTE ch:, VAL INT X IS expr:)
  1717→	for p.isAltDeclStart() {
  1718→		stmt := p.parseStatement()
  1719→		if stmt != nil {
  1720→			altCase.Declarations = append(altCase.Declarations, stmt)
  1721→		}
  1722→		// Advance past the end of the declaration
  1723→		if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
  1724→			p.nextToken()
  1725→		}
  1726→		// Skip newlines to reach next token
  1727→		for p.curTokenIs(lexer.NEWLINE) {
  1728→			p.nextToken()
  1729→		}
  1730→	}
  1731→
  1732→	// Check for guard: expression & channel ? var
  1733→	// or: channel ? var (no guard)
  1734→	// or: guard & SKIP
  1735→
  1736→	// First token should be identifier, TRUE/FALSE, or ( for guard expression
  1737→	if !p.curTokenIs(lexer.IDENT) && !p.curTokenIs(lexer.TRUE) && !p.curTokenIs(lexer.FALSE) && !p.curTokenIs(lexer.LPAREN) {
  1738→		p.addError(fmt.Sprintf("expected channel name or guard in ALT case, got %s", p.curToken.Type))
  1739→		return nil
  1740→	}
  1741→
  1742→	// Look ahead to determine if this is a guard or channel
  1743→	// If current is ( then it must be a guard expression
  1744→	// If next token is & then we have a guard
  1745→	// If next token is ? then it's a channel/timer receive
  1746→	if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.RECEIVE) {
  1747→		name := p.curToken.Literal
  1748→		if p.timerNames[name] {
  1749→			// Timer case: tim ? AFTER deadline
  1750→			altCase.IsTimer = true
  1751→			altCase.Timer = name
  1752→			p.nextToken() // move to ?
  1753→			if !p.expectPeek(lexer.AFTER) {
  1754→				return nil
  1755→			}
  1756→			p.nextToken() // move past AFTER
  1757→			altCase.Deadline = p.parseExpression(LOWEST)
  1758→		} else {
  1759→			// Simple case: channel ? var
  1760→			altCase.Channel = name
  1761→			p.nextToken() // move to ?
  1762→			if !p.expectPeek(lexer.IDENT) {
  1763→				return nil
  1764→			}
  1765→			altCase.Variable = p.curToken.Literal
  1766→		}
  1767→	} else if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.LBRACKET) {
  1768→		// Indexed channel case: cs[i] ? var or cs[i][j] ? var
  1769→		name := p.curToken.Literal
  1770→		altCase.Channel = name
  1771→		for p.peekTokenIs(lexer.LBRACKET) {
  1772→			p.nextToken() // move to [
  1773→			p.nextToken() // move past [
  1774→			altCase.ChannelIndices = append(altCase.ChannelIndices, p.parseExpression(LOWEST))
  1775→			if !p.expectPeek(lexer.RBRACKET) {
  1776→				return nil
  1777→			}
  1778→		}
  1779→		if !p.expectPeek(lexer.RECEIVE) {
  1780→			return nil
  1781→		}
  1782→		if !p.expectPeek(lexer.IDENT) {
  1783→			return nil
  1784→		}
  1785→		altCase.Variable = p.curToken.Literal
  1786→	} else {
  1787→		// Guard followed by & channel ? var, or guard & SKIP
  1788→		guard := p.parseExpression(LOWEST)
  1789→		altCase.Guard = guard
  1790→
  1791→		// Expect &
  1792→		if !p.peekTokenIs(lexer.AMPERSAND) {
  1793→			p.addError("expected & after guard in ALT case")
  1794→			return nil
  1795→		}
  1796→		p.nextToken() // move to &
  1797→		p.nextToken() // move past &
  1798→
  1799→		if p.curTokenIs(lexer.SKIP) {
  1800→			// Guarded SKIP: guard & SKIP
  1801→			altCase.IsSkip = true
  1802→		} else if !p.curTokenIs(lexer.IDENT) {
  1803→			p.addError(fmt.Sprintf("expected channel name or SKIP after guard, got %s", p.curToken.Type))
  1804→			return nil
  1805→		} else {
  1806→			// Channel operation after guard
  1807→			altCase.Channel = p.curToken.Literal
  1808→
  1809→			for p.peekTokenIs(lexer.LBRACKET) {
  1810→				// Indexed channel with guard: guard & cs[i] ? var or cs[i][j] ? var
  1811→				p.nextToken() // move to [
  1812→				p.nextToken() // move past [
  1813→				altCase.ChannelIndices = append(altCase.ChannelIndices, p.parseExpression(LOWEST))
  1814→				if !p.expectPeek(lexer.RBRACKET) {
  1815→					return nil
  1816→				}
  1817→			}
  1818→
  1819→			if !p.expectPeek(lexer.RECEIVE) {
  1820→				return nil
  1821→			}
  1822→			if !p.expectPeek(lexer.IDENT) {
  1823→				return nil
  1824→			}
  1825→			altCase.Variable = p.curToken.Literal
  1826→		}
  1827→	}
  1828→
  1829→	// Skip to next line for the body
  1830→	for p.peekTokenIs(lexer.NEWLINE) {
  1831→		p.nextToken()
  1832→	}
  1833→
  1834→	// Expect INDENT for body
  1835→	if !p.peekTokenIs(lexer.INDENT) {
  1836→		p.addError("expected indented body after ALT case")
  1837→		return altCase
  1838→	}
  1839→	p.nextToken() // consume INDENT
  1840→	p.nextToken() // move into body
  1841→
  1842→	altCase.Body = p.parseBodyStatements()
  1843→
  1844→	return altCase
  1845→}
  1846→
  1847→func (p *Parser) parseBlockStatements() []ast.Statement {
  1848→	var statements []ast.Statement
  1849→	startLevel := p.indentLevel
  1850→
  1851→	p.nextToken() // move past INDENT
  1852→
  1853→	for !p.curTokenIs(lexer.EOF) {
  1854→		// Skip newlines
  1855→		for p.curTokenIs(lexer.NEWLINE) {
  1856→			p.nextToken()
  1857→		}
  1858→
  1859→		// Handle DEDENT tokens
  1860→		// If we're at a DEDENT and indentLevel has dropped below startLevel,
  1861→		// this DEDENT ends our block - stop parsing
  1862→		// If indentLevel >= startLevel, this DEDENT is from a nested block - skip it
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 1337,
  "limit": 60
}
  1337→func (g *Generator) generateReceive(recv *ast.Receive) {
  1338→	chanRef := goIdent(recv.Channel)
  1339→	if len(recv.ChannelIndices) > 0 {
  1340→		chanRef += g.generateIndicesStr(recv.ChannelIndices)
  1341→	}
  1342→
  1343→	if len(recv.Variables) > 0 {
  1344→		// Sequential receive: _tmpN := <-c; x = _tmpN._0; y = _tmpN._1
  1345→		tmpName := fmt.Sprintf("_tmp%d", g.tmpCounter)
  1346→		g.tmpCounter++
  1347→		g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef))
  1348→		varRef := goIdent(recv.Variable)
  1349→		if g.refParams[recv.Variable] {
  1350→			varRef = "*" + varRef
  1351→		}
  1352→		g.writeLine(fmt.Sprintf("%s = %s._0", varRef, tmpName))
  1353→		for i, v := range recv.Variables {
  1354→			vRef := goIdent(v)
  1355→			if g.refParams[v] {
  1356→				vRef = "*" + vRef
  1357→			}
  1358→			g.writeLine(fmt.Sprintf("%s = %s._%d", vRef, tmpName, i+1))
  1359→		}
  1360→	} else {
  1361→		varRef := goIdent(recv.Variable)
  1362→		if g.refParams[recv.Variable] {
  1363→			varRef = "*" + varRef
  1364→		}
  1365→		g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef))
  1366→	}
  1367→}
  1368→
  1369→func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) {
  1370→	gName := goIdent(proto.Name)
  1371→	switch proto.Kind {
  1372→	case "simple":
  1373→		goType := g.occamTypeToGoBase(proto.Types[0])
  1374→		g.writeLine(fmt.Sprintf("type _proto_%s = %s", gName, goType))
  1375→		g.writeLine("")
  1376→	case "sequential":
  1377→		g.writeLine(fmt.Sprintf("type _proto_%s struct {", gName))
  1378→		g.indent++
  1379→		for i, t := range proto.Types {
  1380→			goType := g.occamTypeToGoBase(t)
  1381→			g.writeLine(fmt.Sprintf("_%d %s", i, goType))
  1382→		}
  1383→		g.indent--
  1384→		g.writeLine("}")
  1385→		g.writeLine("")
  1386→	case "variant":
  1387→		// Interface type
  1388→		g.writeLine(fmt.Sprintf("type _proto_%s interface {", gName))
  1389→		g.indent++
  1390→		g.writeLine(fmt.Sprintf("_is_%s()", gName))
  1391→		g.indent--
  1392→		g.writeLine("}")
  1393→		g.writeLine("")
  1394→		// Concrete types for each variant
  1395→		for _, v := range proto.Variants {
  1396→			gTag := goIdent(v.Tag)
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 1860,
  "limit": 100
}
  1860→func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
  1861→	if alt.Replicator != nil {
  1862→		g.generateReplicatedAlt(alt)
  1863→		return
  1864→	}
  1865→
  1866→	// ALT becomes Go select statement
  1867→	// For guards, we use a pattern with nil channels
  1868→
  1869→	// Check if any cases have guards
  1870→	hasGuards := false
  1871→	for _, c := range alt.Cases {
  1872→		if c.Guard != nil {
  1873→			hasGuards = true
  1874→			break
  1875→		}
  1876→	}
  1877→
  1878→	if hasGuards {
  1879→		// Generate channel variables for guarded cases
  1880→		for i, c := range alt.Cases {
  1881→			if c.Guard != nil && !c.IsSkip {
  1882→				g.builder.WriteString(strings.Repeat("\t", g.indent))
  1883→				g.write(fmt.Sprintf("var _alt%d chan ", i))
  1884→				// We don't know the channel type here, so use interface{}
  1885→				// Actually, we should use the same type as the original channel
  1886→				// For now, let's just reference the original channel conditionally
  1887→				g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
  1888→				g.builder.WriteString(strings.Repeat("\t", g.indent))
  1889→				g.write(fmt.Sprintf("if "))
  1890→				g.generateExpression(c.Guard)
  1891→				g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, goIdent(c.Channel)))
  1892→			}
  1893→		}
  1894→	}
  1895→
  1896→	g.writeLine("select {")
  1897→	for i, c := range alt.Cases {
  1898→		g.builder.WriteString(strings.Repeat("\t", g.indent))
  1899→		if c.IsSkip {
  1900→			g.write("default:\n")
  1901→		} else if c.IsTimer {
  1902→			g.write("case <-time.After(time.Duration(")
  1903→			g.generateExpression(c.Deadline)
  1904→			g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
  1905→		} else if c.Guard != nil {
  1906→			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
  1907→		} else if len(c.ChannelIndices) > 0 {
  1908→			g.write(fmt.Sprintf("case %s = <-%s", goIdent(c.Variable), goIdent(c.Channel)))
  1909→			g.generateIndices(c.ChannelIndices)
  1910→			g.write(":\n")
  1911→		} else {
  1912→			g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
  1913→		}
  1914→		g.indent++
  1915→		guardedSkip := c.IsSkip && c.Guard != nil
  1916→		if guardedSkip {
  1917→			g.builder.WriteString(strings.Repeat("\t", g.indent))
  1918→			g.write("if ")
  1919→			g.generateExpression(c.Guard)
  1920→			g.write(" {\n")
  1921→			g.indent++
  1922→		}
  1923→		for _, s := range c.Body {
  1924→			g.generateStatement(s)
  1925→		}
  1926→		if guardedSkip {
  1927→			g.indent--
  1928→			g.writeLine("}")
  1929→		}
  1930→		g.indent--
  1931→	}
  1932→	g.writeLine("}")
  1933→}
  1934→
  1935→func (g *Generator) generateReplicatedAlt(alt *ast.AltBlock) {
  1936→	// Replicated ALT: ALT i = start FOR count
  1937→	// Uses reflect.Select for runtime-variable case count
  1938→	if len(alt.Cases) == 0 {
  1939→		return
  1940→	}
  1941→	c := alt.Cases[0]
  1942→	rep := alt.Replicator
  1943→	v := goIdent(rep.Variable)
  1944→
  1945→	// Determine receive type from scoped declarations
  1946→	recvType := "int" // default
  1947→	for _, decl := range c.Declarations {
  1948→		if vd, ok := decl.(*ast.VarDecl); ok {
  1949→			for _, name := range vd.Names {
  1950→				if name == c.Variable {
  1951→					recvType = g.occamTypeToGo(vd.Type)
  1952→					break
  1953→				}
  1954→			}
  1955→		}
  1956→	}
  1957→
  1958→	// Open a block for scoping
  1959→	g.writeLine("{")
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 1935,
  "limit": 120
}
  1935→func (g *Generator) generateReplicatedAlt(alt *ast.AltBlock) {
  1936→	// Replicated ALT: ALT i = start FOR count
  1937→	// Uses reflect.Select for runtime-variable case count
  1938→	if len(alt.Cases) == 0 {
  1939→		return
  1940→	}
  1941→	c := alt.Cases[0]
  1942→	rep := alt.Replicator
  1943→	v := goIdent(rep.Variable)
  1944→
  1945→	// Determine receive type from scoped declarations
  1946→	recvType := "int" // default
  1947→	for _, decl := range c.Declarations {
  1948→		if vd, ok := decl.(*ast.VarDecl); ok {
  1949→			for _, name := range vd.Names {
  1950→				if name == c.Variable {
  1951→					recvType = g.occamTypeToGo(vd.Type)
  1952→					break
  1953→				}
  1954→			}
  1955→		}
  1956→	}
  1957→
  1958→	// Open a block for scoping
  1959→	g.writeLine("{")
  1960→	g.indent++
  1961→
  1962→	// _altCount := int(<count>)
  1963→	g.builder.WriteString(strings.Repeat("\t", g.indent))
  1964→	g.write("_altCount := int(")
  1965→	g.generateExpression(rep.Count)
  1966→	g.write(")\n")
  1967→
  1968→	// _altCases := make([]reflect.SelectCase, _altCount)
  1969→	g.writeLine("_altCases := make([]reflect.SelectCase, _altCount)")
  1970→
  1971→	// Setup loop: build select cases
  1972→	g.writeLine("for _altI := 0; _altI < _altCount; _altI++ {")
  1973→	g.indent++
  1974→
  1975→	// Compute replicator variable
  1976→	g.builder.WriteString(strings.Repeat("\t", g.indent))
  1977→	if rep.Step != nil {
  1978→		g.write(fmt.Sprintf("%s := ", v))
  1979→		g.generateExpression(rep.Start)
  1980→		g.write(" + _altI * (")
  1981→		g.generateExpression(rep.Step)
  1982→		g.write(")\n")
  1983→	} else {
  1984→		g.write(fmt.Sprintf("%s := ", v))
  1985→		g.generateExpression(rep.Start)
  1986→		g.write(" + _altI\n")
  1987→	}
  1988→
  1989→	// Generate scoped abbreviations (needed for channel index computation)
  1990→	for _, decl := range c.Declarations {
  1991→		if abbr, ok := decl.(*ast.Abbreviation); ok {
  1992→			g.generateAbbreviation(abbr)
  1993→		}
  1994→	}
  1995→
  1996→	// Build select case entry
  1997→	g.builder.WriteString(strings.Repeat("\t", g.indent))
  1998→	g.write("_altCases[_altI] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(")
  1999→	if len(c.ChannelIndices) > 0 {
  2000→		g.write(goIdent(c.Channel))
  2001→		g.generateIndices(c.ChannelIndices)
  2002→	} else {
  2003→		g.write(goIdent(c.Channel))
  2004→	}
  2005→	g.write(")}\n")
  2006→
  2007→	g.indent--
  2008→	g.writeLine("}")
  2009→
  2010→	// Call reflect.Select
  2011→	g.writeLine("_altChosen, _altValue, _ := reflect.Select(_altCases)")
  2012→
  2013→	// Recompute replicator variable from chosen index
  2014→	g.builder.WriteString(strings.Repeat("\t", g.indent))
  2015→	if rep.Step != nil {
  2016→		g.write(fmt.Sprintf("%s := ", v))
  2017→		g.generateExpression(rep.Start)
  2018→		g.write(" + _altChosen * (")
  2019→		g.generateExpression(rep.Step)
  2020→		g.write(")\n")
  2021→	} else {
  2022→		g.write(fmt.Sprintf("%s := ", v))
  2023→		g.generateExpression(rep.Start)
  2024→		g.write(" + _altChosen\n")
  2025→	}
  2026→	g.writeLine(fmt.Sprintf("_ = %s", v))
  2027→
  2028→	// Generate scoped var declarations
  2029→	for _, decl := range c.Declarations {
  2030→		if vd, ok := decl.(*ast.VarDecl); ok {
  2031→			g.generateVarDecl(vd)
  2032→		}
  2033→	}
  2034→
  2035→	// Generate scoped abbreviations
  2036→	for _, decl := range c.Declarations {
  2037→		if abbr, ok := decl.(*ast.Abbreviation); ok {
  2038→			g.generateAbbreviation(abbr)
  2039→		}
  2040→	}
  2041→
  2042→	// Assign received value from reflect.Value
  2043→	g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", goIdent(c.Variable), recvType))
  2044→
  2045→	// Generate body
  2046→	for _, s := range c.Body {
  2047→		g.generateStatement(s)
  2048→	}
  2049→
  2050→	g.indent--
  2051→	g.writeLine("}")
  2052→}
  2053→
  2054→func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
Thinking

Now I have a clear picture of what needs to change. Let me implement all the changes:

  1. AST: Add VariableIndices []Expression to Receive and AltCase
  2. Parser: Add index collection after variable assignment in 5 locations
  3. Codegen: Use indices when building varRef in 6 locations
  4. Tests: Add parser and e2e tests

Let me also check the existing test files to understand the patterns.

Now I have all the context I need. Let me implement the changes. I'll start with the AST and parser changes, then codegen, then tests.

TaskCreate
Add VariableIndices []Expression field to Receive and AltCase in ast/ast.go
{
  "subject": "Add VariableIndices to AST structs",
  "activeForm": "Adding VariableIndices to AST"
}
Task #1 created successfully: Add VariableIndices to AST structs
TaskCreate
In parser.go, add index collection loops after Variable assignment in parseReceive(), parseIndexedOperation(), and parseAltCase() (5 locations)
{
  "subject": "Add index parsing for receive targets",
  "activeForm": "Adding index parsing for receive targets"
}
Task #2 created successfully: Add index parsing for receive targets
TaskCreate
In codegen.go, build varRef with indices in generateReceive(), generateAltBlock(), and generateReplicatedAlt() (6 locations)
{
  "subject": "Update codegen to emit indexed receive targets",
  "activeForm": "Updating codegen for indexed targets"
}
Task #3 created successfully: Update codegen to emit indexed receive targets
TaskCreate
Add parser tests for indexed receive variable and e2e tests for receive into indexed variable
{
  "subject": "Add parser and e2e tests",
  "activeForm": "Adding tests"
}
Task #4 created successfully: Add parser and e2e tests
TaskUpdate
{
  "taskId": "1",
  "status": "in_progress"
}
Updated task #1 status

1. AST Changes

✏️ Edit ast.go
/home/david/projects/code-associates/occam2go/ast/ast.go
	Channel        string       // channel name
	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x
	Variable       string       // variable to receive into (simple receive)
	Variables      []string     // additional variables for sequential receives (c ? x ; y)
+
	Channel         string       // channel name
	ChannelIndices  []Expression // non-empty for cs[i] ? x or cs[i][j] ? x
	Variable        string       // variable to receive into (simple receive)
	VariableIndices []Expression // non-empty for c ? flags[0] or c ? grid[i][j]
	Variables       []string     // additional variables for sequential receives (c ? x ; y)
The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
✏️ Edit ast.go
/home/david/projects/code-associates/occam2go/ast/ast.go
	Guard          Expression   // optional guard condition (nil if no guard)
	Channel        string       // channel name
	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
	Variable       string       // variable to receive into
	Body           []Statement  // the body to execute
+
	Guard           Expression   // optional guard condition (nil if no guard)
	Channel         string       // channel name
	ChannelIndices  []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
	Variable        string       // variable to receive into
	VariableIndices []Expression // non-empty for c ? flags[0] or c ? grid[i][j]
	Body            []Statement  // the body to execute
The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
TaskUpdate
{
  "taskId": "1",
  "status": "completed"
}
Updated task #1 status
TaskUpdate
{
  "taskId": "2",
  "status": "in_progress"
}
Updated task #2 status

2. Parser Changes

Location 1: parseReceive() — simple channel receive

✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
	if !p.expectPeek(lexer.IDENT) {
		return nil
	}
	stmt.Variable = p.curToken.Literal

	// Check for sequential receive: c ? x ; y ; z
	for p.peekTokenIs(lexer.SEMICOLON) {
		p.nextToken() // move to ;
		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		stmt.Variables = append(stmt.Variables, p.curToken.Literal)
	}

	return stmt
}

func (p *Parser) parseVariantReceive(channel string, token lexer.Token) *ast.VariantReceive {
+
	if !p.expectPeek(lexer.IDENT) {
		return nil
	}
	stmt.Variable = p.curToken.Literal

	// Collect variable indices: c ? flags[0] or c ? grid[i][j]
	for p.peekTokenIs(lexer.LBRACKET) {
		p.nextToken() // move to [
		p.nextToken() // move past [
		stmt.VariableIndices = append(stmt.VariableIndices, p.parseExpression(LOWEST))
		if !p.expectPeek(lexer.RBRACKET) {
			return nil
		}
	}

	// Check for sequential receive: c ? x ; y ; z
	for p.peekTokenIs(lexer.SEMICOLON) {
		p.nextToken() // move to ;
		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		stmt.Variables = append(stmt.Variables, p.curToken.Literal)
	}

	return stmt
}

func (p *Parser) parseVariantReceive(channel string, token lexer.Token) *ast.VariantReceive {
The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.

Location 2: parseIndexedOperation() — indexed channel receive

✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
		stmt := &ast.Receive{
			Token:          recvToken,
			Channel:        name,
			ChannelIndices: indices,
		}

		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		stmt.Variable = p.curToken.Literal

		// Check for sequential receive
		for p.peekTokenIs(lexer.SEMICOLON) {
			p.nextToken() // move to ;
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			stmt.Variables = append(stmt.Variables, p.curToken.Literal)
		}

		return stmt
	}
+
		stmt := &ast.Receive{
			Token:          recvToken,
			Channel:        name,
			ChannelIndices: indices,
		}

		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		stmt.Variable = p.curToken.Literal

		// Collect variable indices: cs[i] ? flags[0] or cs[i] ? grid[j][k]
		for p.peekTokenIs(lexer.LBRACKET) {
			p.nextToken() // move to [
			p.nextToken() // move past [
			stmt.VariableIndices = append(stmt.VariableIndices, p.parseExpression(LOWEST))
			if !p.expectPeek(lexer.RBRACKET) {
				return nil
			}
		}

		// Check for sequential receive
		for p.peekTokenIs(lexer.SEMICOLON) {
			p.nextToken() // move to ;
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			stmt.Variables = append(stmt.Variables, p.curToken.Literal)
		}

		return stmt
	}
The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.

Locations 3-5: parseAltCase() — three sites

✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
		} else {
			// Simple case: channel ? var
			altCase.Channel = name
			p.nextToken() // move to ?
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			altCase.Variable = p.curToken.Literal
		}
+
		} else {
			// Simple case: channel ? var or channel ? var[i]
			altCase.Channel = name
			p.nextToken() // move to ?
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			altCase.Variable = p.curToken.Literal
			// Collect variable indices: ch ? flags[0]
			for p.peekTokenIs(lexer.LBRACKET) {
				p.nextToken() // move to [
				p.nextToken() // move past [
				altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
				if !p.expectPeek(lexer.RBRACKET) {
					return nil
				}
			}
		}
The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
		if !p.expectPeek(lexer.RECEIVE) {
			return nil
		}
		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		altCase.Variable = p.curToken.Literal
	} else {
+
		if !p.expectPeek(lexer.RECEIVE) {
			return nil
		}
		if !p.expectPeek(lexer.IDENT) {
			return nil
		}
		altCase.Variable = p.curToken.Literal
		// Collect variable indices: cs[i] ? flags[0]
		for p.peekTokenIs(lexer.LBRACKET) {
			p.nextToken() // move to [
			p.nextToken() // move past [
			altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
			if !p.expectPeek(lexer.RBRACKET) {
				return nil
			}
		}
	} else {
The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.

Now the guarded channel case (location 5). Let me re-read that section to make sure I have the right context after the previous edits.

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 1820,
  "limit": 40
}
  1820→			if !p.expectPeek(lexer.RBRACKET) {
  1821→				return nil
  1822→			}
  1823→		}
  1824→	} else {
  1825→		// Guard followed by & channel ? var, or guard & SKIP
  1826→		guard := p.parseExpression(LOWEST)
  1827→		altCase.Guard = guard
  1828→
  1829→		// Expect &
  1830→		if !p.peekTokenIs(lexer.AMPERSAND) {
  1831→			p.addError("expected & after guard in ALT case")
  1832→			return nil
  1833→		}
  1834→		p.nextToken() // move to &
  1835→		p.nextToken() // move past &
  1836→
  1837→		if p.curTokenIs(lexer.SKIP) {
  1838→			// Guarded SKIP: guard & SKIP
  1839→			altCase.IsSkip = true
  1840→		} else if !p.curTokenIs(lexer.IDENT) {
  1841→			p.addError(fmt.Sprintf("expected channel name or SKIP after guard, got %s", p.curToken.Type))
  1842→			return nil
  1843→		} else {
  1844→			// Channel operation after guard
  1845→			altCase.Channel = p.curToken.Literal
  1846→
  1847→			for p.peekTokenIs(lexer.LBRACKET) {
  1848→				// Indexed channel with guard: guard & cs[i] ? var or cs[i][j] ? var
  1849→				p.nextToken() // move to [
  1850→				p.nextToken() // move past [
  1851→				altCase.ChannelIndices = append(altCase.ChannelIndices, p.parseExpression(LOWEST))
  1852→				if !p.expectPeek(lexer.RBRACKET) {
  1853→					return nil
  1854→				}
  1855→			}
  1856→
  1857→			if !p.expectPeek(lexer.RECEIVE) {
  1858→				return nil
  1859→			}
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
  "offset": 1857,
  "limit": 20
}
  1857→			if !p.expectPeek(lexer.RECEIVE) {
  1858→				return nil
  1859→			}
  1860→			if !p.expectPeek(lexer.IDENT) {
  1861→				return nil
  1862→			}
  1863→			altCase.Variable = p.curToken.Literal
  1864→		}
  1865→	}
  1866→
  1867→	// Skip to next line for the body
  1868→	for p.peekTokenIs(lexer.NEWLINE) {
  1869→		p.nextToken()
  1870→	}
  1871→
  1872→	// Expect INDENT for body
  1873→	if !p.peekTokenIs(lexer.INDENT) {
  1874→		p.addError("expected indented body after ALT case")
  1875→		return altCase
  1876→	}
✏️ Edit parser.go
/home/david/projects/code-associates/occam2go/parser/parser.go
			if !p.expectPeek(lexer.RECEIVE) {
				return nil
			}
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			altCase.Variable = p.curToken.Literal
		}
	}

	// Skip to next line for the body
+
			if !p.expectPeek(lexer.RECEIVE) {
				return nil
			}
			if !p.expectPeek(lexer.IDENT) {
				return nil
			}
			altCase.Variable = p.curToken.Literal
			// Collect variable indices: guard & ch ? flags[0]
			for p.peekTokenIs(lexer.LBRACKET) {
				p.nextToken() // move to [
				p.nextToken() // move past [
				altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
				if !p.expectPeek(lexer.RBRACKET) {
					return nil
				}
			}
		}
	}

	// Skip to next line for the body
The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
TaskUpdate
{
  "taskId": "2",
  "status": "completed"
}
Updated task #2 status
TaskUpdate
{
  "taskId": "3",
  "status": "in_progress"
}
Updated task #3 status

3. Codegen Changes

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	if len(recv.Variables) > 0 {
		// Sequential receive: _tmpN := <-c; x = _tmpN._0; y = _tmpN._1
		tmpName := fmt.Sprintf("_tmp%d", g.tmpCounter)
		g.tmpCounter++
		g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef))
		varRef := goIdent(recv.Variable)
		if g.refParams[recv.Variable] {
			varRef = "*" + varRef
		}
		g.writeLine(fmt.Sprintf("%s = %s._0", varRef, tmpName))
		for i, v := range recv.Variables {
			vRef := goIdent(v)
			if g.refParams[v] {
				vRef = "*" + vRef
			}
			g.writeLine(fmt.Sprintf("%s = %s._%d", vRef, tmpName, i+1))
		}
	} else {
		varRef := goIdent(recv.Variable)
		if g.refParams[recv.Variable] {
			varRef = "*" + varRef
		}
		g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef))
	}
+
	if len(recv.Variables) > 0 {
		// Sequential receive: _tmpN := <-c; x = _tmpN._0; y = _tmpN._1
		tmpName := fmt.Sprintf("_tmp%d", g.tmpCounter)
		g.tmpCounter++
		g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef))
		varRef := goIdent(recv.Variable)
		if len(recv.VariableIndices) > 0 {
			varRef += g.generateIndicesStr(recv.VariableIndices)
		} else if g.refParams[recv.Variable] {
			varRef = "*" + varRef
		}
		g.writeLine(fmt.Sprintf("%s = %s._0", varRef, tmpName))
		for i, v := range recv.Variables {
			vRef := goIdent(v)
			if g.refParams[v] {
				vRef = "*" + vRef
			}
			g.writeLine(fmt.Sprintf("%s = %s._%d", vRef, tmpName, i+1))
		}
	} else {
		varRef := goIdent(recv.Variable)
		if len(recv.VariableIndices) > 0 {
			varRef += g.generateIndicesStr(recv.VariableIndices)
		} else if g.refParams[recv.Variable] {
			varRef = "*" + varRef
		}
		g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef))
	}
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.

Now the generateAltBlock() — the 4 case branches for varRef:

Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
  "offset": 1896,
  "limit": 25
}
  1896→			}
  1897→		}
  1898→	}
  1899→
  1900→	g.writeLine("select {")
  1901→	for i, c := range alt.Cases {
  1902→		g.builder.WriteString(strings.Repeat("\t", g.indent))
  1903→		if c.IsSkip {
  1904→			g.write("default:\n")
  1905→		} else if c.IsTimer {
  1906→			g.write("case <-time.After(time.Duration(")
  1907→			g.generateExpression(c.Deadline)
  1908→			g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
  1909→		} else if c.Guard != nil {
  1910→			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
  1911→		} else if len(c.ChannelIndices) > 0 {
  1912→			g.write(fmt.Sprintf("case %s = <-%s", goIdent(c.Variable), goIdent(c.Channel)))
  1913→			g.generateIndices(c.ChannelIndices)
  1914→			g.write(":\n")
  1915→		} else {
  1916→			g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
  1917→		}
  1918→		g.indent++
  1919→		guardedSkip := c.IsSkip && c.Guard != nil
  1920→		if guardedSkip {

I need to build varRef with indices for the guarded, indexed-channel, and simple-channel branches:

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
		} else if c.Guard != nil {
			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
		} else if len(c.ChannelIndices) > 0 {
			g.write(fmt.Sprintf("case %s = <-%s", goIdent(c.Variable), goIdent(c.Channel)))
			g.generateIndices(c.ChannelIndices)
			g.write(":\n")
		} else {
			g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
		}
+
		} else if c.Guard != nil {
			varRef := goIdent(c.Variable)
			if len(c.VariableIndices) > 0 {
				varRef += g.generateIndicesStr(c.VariableIndices)
			}
			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", varRef, i))
		} else if len(c.ChannelIndices) > 0 {
			varRef := goIdent(c.Variable)
			if len(c.VariableIndices) > 0 {
				varRef += g.generateIndicesStr(c.VariableIndices)
			}
			g.write(fmt.Sprintf("case %s = <-%s", varRef, goIdent(c.Channel)))
			g.generateIndices(c.ChannelIndices)
			g.write(":\n")
		} else {
			varRef := goIdent(c.Variable)
			if len(c.VariableIndices) > 0 {
				varRef += g.generateIndicesStr(c.VariableIndices)
			}
			g.write(fmt.Sprintf("case %s = <-%s:\n", varRef, goIdent(c.Channel)))
		}
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.

Now the generateReplicatedAlt() — the _altValue.Interface() assignment:

✏️ Edit codegen.go
/home/david/projects/code-associates/occam2go/codegen/codegen.go
	// Assign received value from reflect.Value
	g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", goIdent(c.Variable), recvType))
+
	// Assign received value from reflect.Value
	varRef := goIdent(c.Variable)
	if len(c.VariableIndices) > 0 {
		varRef += g.generateIndicesStr(c.VariableIndices)
	}
	g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", varRef, recvType))
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
TaskUpdate
{
  "taskId": "3",
  "status": "completed"
}
Updated task #3 status
TaskUpdate
{
  "taskId": "4",
  "status": "in_progress"
}
Updated task #4 status

4. Tests

Let me check the existing test patterns first.

Grep
{
  "pattern": "TestReceive",
  "path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
  "output_mode": "content",
  "-n": true
}
248:func TestReceive(t *testing.T) {
Grep
{
  "pattern": "TestE2E_.*Chan",
  "path": "/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go",
  "output_mode": "content",
  "-n": true
}
22:func TestE2E_Channel(t *testing.T) {
39:func TestE2E_ChannelExpression(t *testing.T) {
57:func TestE2E_ChannelPingPong(t *testing.T) {
101:func TestE2E_AltSecondChannel(t *testing.T) {
184:func TestE2E_ChanParam(t *testing.T) {
204:func TestE2E_ChanParamTwoWay(t *testing.T) {
229:func TestE2E_ChanDirParam(t *testing.T) {
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
  "offset": 248,
  "limit": 40
}
   248→func TestReceive(t *testing.T) {
   249→	input := `c ? x
   250→`
   251→	l := lexer.New(input)
   252→	p := New(l)
   253→	program := p.ParseProgram()
   254→	checkParserErrors(t, p)
   255→
   256→	if len(program.Statements) != 1 {
   257→		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
   258→	}
   259→
   260→	recv, ok := program.Statements[0].(*ast.Receive)
   261→	if !ok {
   262→		t.Fatalf("expected Receive, got %T", program.Statements[0])
   263→	}
   264→
   265→	if recv.Channel != "c" {
   266→		t.Errorf("expected channel 'c', got %s", recv.Channel)
   267→	}
   268→
   269→	if recv.Variable != "x" {
   270→		t.Errorf("expected variable 'x', got %s", recv.Variable)
   271→	}
   272→}
   273→
   274→func TestAltBlock(t *testing.T) {
   275→	input := `ALT
   276→  c1 ? x
   277→    SKIP
   278→  c2 ? y
   279→    SKIP
   280→`
   281→	l := lexer.New(input)
   282→	p := New(l)
   283→	program := p.ParseProgram()
   284→	checkParserErrors(t, p)
   285→
   286→	if len(program.Statements) != 1 {
   287→		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go",
  "offset": 22,
  "limit": 40
}
    22→func TestE2E_Channel(t *testing.T) {
    23→	// Test basic channel communication between parallel processes
    24→	occam := `SEQ
    25→  CHAN OF INT c:
    26→  INT result:
    27→  PAR
    28→    c ! 42
    29→    c ? result
    30→  print.int(result)
    31→`
    32→	output := transpileCompileRun(t, occam)
    33→	expected := "42\n"
    34→	if output != expected {
    35→		t.Errorf("expected %q, got %q", expected, output)
    36→	}
    37→}
    38→
    39→func TestE2E_ChannelExpression(t *testing.T) {
    40→	// Test sending an expression over a channel
    41→	occam := `SEQ
    42→  CHAN OF INT c:
    43→  INT x, result:
    44→  x := 10
    45→  PAR
    46→    c ! x * 2
    47→    c ? result
    48→  print.int(result)
    49→`
    50→	output := transpileCompileRun(t, occam)
    51→	expected := "20\n"
    52→	if output != expected {
    53→		t.Errorf("expected %q, got %q", expected, output)
    54→	}
    55→}
    56→
    57→func TestE2E_ChannelPingPong(t *testing.T) {
    58→	// Test two-way communication: send a value, double it, send back
    59→	occam := `SEQ
    60→  CHAN OF INT request:
    61→  CHAN OF INT response:

Good. Now let me find where the parser test file ends and where the e2e concurrency test file ends, so I can append tests.

$ Bash
Count lines in test files
wc -l /home/david/projects/code-associates/occam2go/parser/parser_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go
  3890 /home/david/projects/code-associates/occam2go/parser/parser_test.go
   394 /home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go
  4284 total
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
  "offset": 3885,
  "limit": 10
}
  3885→		t.Errorf("expected VarDecl as first body statement, got %T", evolveCase.Body[0])
  3886→	}
  3887→	if _, ok := evolveCase.Body[1].(*ast.SeqBlock); !ok {
  3888→		t.Errorf("expected SeqBlock as second body statement, got %T", evolveCase.Body[1])
  3889→	}
  3890→}
  3891→
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go",
  "offset": 389,
  "limit": 10
}
   389→	output := transpileCompileRun(t, occam)
   390→	expected := "99\n"
   391→	if output != expected {
   392→		t.Errorf("expected %q, got %q", expected, output)
   393→	}
   394→}
   395→

Now let me add the parser tests and e2e tests.

✏️ Edit parser_test.go
/home/david/projects/code-associates/occam2go/parser/parser_test.go
	if _, ok := evolveCase.Body[1].(*ast.SeqBlock); !ok {
		t.Errorf("expected SeqBlock as second body statement, got %T", evolveCase.Body[1])
	}
}
+
	if _, ok := evolveCase.Body[1].(*ast.SeqBlock); !ok {
		t.Errorf("expected SeqBlock as second body statement, got %T", evolveCase.Body[1])
	}
}

func TestReceiveIndexedVariable(t *testing.T) {
	input := `ch ? flags[0]
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(t, p)

	if len(program.Statements) != 1 {
		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
	}

	recv, ok := program.Statements[0].(*ast.Receive)
	if !ok {
		t.Fatalf("expected Receive, got %T", program.Statements[0])
	}

	if recv.Channel != "ch" {
		t.Errorf("expected channel 'ch', got %s", recv.Channel)
	}

	if recv.Variable != "flags" {
		t.Errorf("expected variable 'flags', got %s", recv.Variable)
	}

	if len(recv.VariableIndices) != 1 {
		t.Fatalf("expected 1 variable index, got %d", len(recv.VariableIndices))
	}
}

func TestReceiveMultiIndexedVariable(t *testing.T) {
	input := `ch ? grid[i][j]
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(t, p)

	if len(program.Statements) != 1 {
		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
	}

	recv, ok := program.Statements[0].(*ast.Receive)
	if !ok {
		t.Fatalf("expected Receive, got %T", program.Statements[0])
	}

	if recv.Variable != "grid" {
		t.Errorf("expected variable 'grid', got %s", recv.Variable)
	}

	if len(recv.VariableIndices) != 2 {
		t.Fatalf("expected 2 variable indices, got %d", len(recv.VariableIndices))
	}
}

func TestIndexedChannelReceiveIndexedVariable(t *testing.T) {
	input := `cs[0] ? flags[1]
`
	l := lexer.New(input)
	p := New(l)
	program := p.ParseProgram()
	checkParserErrors(t, p)

	if len(program.Statements) != 1 {
		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
	}

	recv, ok := program.Statements[0].(*ast.Receive)
	if !ok {
		t.Fatalf("expected Receive, got %T", program.Statements[0])
	}

	if recv.Channel != "cs" {
		t.Errorf("expected channel 'cs', got %s", recv.Channel)
	}

	if len(recv.ChannelIndices) != 1 {
		t.Fatalf("expected 1 channel index, got %d", len(recv.ChannelIndices))
	}

	if recv.Variable != "flags" {
		t.Errorf("expected variable 'flags', got %s", recv.Variable)
	}

	if len(recv.VariableIndices) != 1 {
		t.Fatalf("expected 1 variable index, got %d", len(recv.VariableIndices))
	}
}
The file /home/david/projects/code-associates/occam2go/parser/parser_test.go has been updated successfully.

Now the e2e tests:

✏️ Edit e2e_concurrency_test.go
/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}
+
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}

func TestE2E_ReceiveIntoIndexedVariable(t *testing.T) {
	occam := `SEQ
  CHAN OF INT c:
  [3]INT arr:
  arr[0] := 0
  arr[1] := 0
  arr[2] := 0
  PAR
    c ! 42
    c ? arr[1]
  print.int(arr[1])
`
	output := transpileCompileRun(t, occam)
	expected := "42\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}

func TestE2E_IndexedChannelReceiveIntoIndexedVariable(t *testing.T) {
	occam := `SEQ
  [2]CHAN OF INT cs:
  [3]INT arr:
  arr[0] := 0
  arr[1] := 0
  arr[2] := 0
  PAR
    cs[0] ! 99
    cs[0] ? arr[2]
  print.int(arr[2])
`
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}
<tool_use_error>Found 2 matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.
String: 	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}</tool_use_error>
Read
{
  "file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go",
  "offset": 370,
  "limit": 25
}
   370→`
   371→	output := transpileCompileRun(t, occam)
   372→	expected := "42\n"
   373→	if output != expected {
   374→		t.Errorf("expected %q, got %q", expected, output)
   375→	}
   376→}
   377→
   378→func TestE2E_PriPar(t *testing.T) {
   379→	// Test PRI PAR: behaves the same as PAR in Go (no priority semantics)
   380→	occam := `SEQ
   381→  CHAN OF INT c:
   382→  INT result:
   383→  PRI PAR
   384→    c ! 99
   385→    SEQ
   386→      c ? result
   387→      print.int(result)
   388→`
   389→	output := transpileCompileRun(t, occam)
   390→	expected := "99\n"
   391→	if output != expected {
   392→		t.Errorf("expected %q, got %q", expected, output)
   393→	}
   394→}
✏️ Edit e2e_concurrency_test.go
/home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go
func TestE2E_PriPar(t *testing.T) {
	// Test PRI PAR: behaves the same as PAR in Go (no priority semantics)
	occam := `SEQ
  CHAN OF INT c:
  INT result:
  PRI PAR
    c ! 99
    SEQ
      c ? result
      print.int(result)
`
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}
+
func TestE2E_PriPar(t *testing.T) {
	// Test PRI PAR: behaves the same as PAR in Go (no priority semantics)
	occam := `SEQ
  CHAN OF INT c:
  INT result:
  PRI PAR
    c ! 99
    SEQ
      c ? result
      print.int(result)
`
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}

func TestE2E_ReceiveIntoIndexedVariable(t *testing.T) {
	occam := `SEQ
  CHAN OF INT c:
  [3]INT arr:
  arr[0] := 0
  arr[1] := 0
  arr[2] := 0
  PAR
    c ! 42
    c ? arr[1]
  print.int(arr[1])
`
	output := transpileCompileRun(t, occam)
	expected := "42\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}

func TestE2E_IndexedChannelReceiveIntoIndexedVariable(t *testing.T) {
	occam := `SEQ
  [2]CHAN OF INT cs:
  [3]INT arr:
  arr[0] := 0
  arr[1] := 0
  arr[2] := 0
  PAR
    cs[0] ! 99
    cs[0] ? arr[2]
  print.int(arr[2])
`
	output := transpileCompileRun(t, occam)
	expected := "99\n"
	if output != expected {
		t.Errorf("expected %q, got %q", expected, output)
	}
}
The file /home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go has been updated successfully.

Now let's run the tests:

$ Bash
Run all tests
go test ./... 2>&1 | tail -30
?   	github.com/codeassociates/occam2go	[no test files]
?   	github.com/codeassociates/occam2go/ast	[no test files]
ok  	github.com/codeassociates/occam2go/codegen	19.126s
ok  	github.com/codeassociates/occam2go/lexer	(cached)
ok  	github.com/codeassociates/occam2go/modgen	(cached)
ok  	github.com/codeassociates/occam2go/parser	0.004s
ok  	github.com/codeassociates/occam2go/preproc	(cached)

All tests pass. Let me also verify the course module still works:

$ Bash
Build and vet course module
go build -o /tmp/occam2go . && /tmp/occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go kroc/modules/course/libsrc/course.module && go vet /tmp/course_out.go 2>&1
TaskUpdate
{
  "taskId": "4",
  "status": "completed"
}
Updated task #4 status

All done. Here's a summary of the changes:

AST (ast/ast.go): Added VariableIndices []Expression to both Receive and AltCase structs.

Parser (parser/parser.go): Added index collection loops (for p.peekTokenIs(lexer.LBRACKET)) after every Variable = p.curToken.Literal site — 5 locations across parseReceive(), parseIndexedOperation(), and parseAltCase() (simple, indexed-channel, and guarded branches).

Codegen (codegen/codegen.go): At all 6 sites that build varRef from the variable name, added generateIndicesStr() when VariableIndices is non-empty — in generateReceive() (simple + sequential paths), generateAltBlock() (guarded, indexed-channel, and simple-channel branches), and generateReplicatedAlt().

Tests: 3 parser tests (TestReceiveIndexedVariable, TestReceiveMultiIndexedVariable, TestIndexedChannelReceiveIndexedVariable) and 2 e2e tests (TestE2E_ReceiveIntoIndexedVariable, TestE2E_IndexedChannelReceiveIntoIndexedVariable).

All tests pass including the course module vet check.

Thank you. Please create a PR for this fix.

$ Bash
Show working tree status
git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   ast/ast.go
	modified:   codegen/codegen.go
	modified:   codegen/e2e_concurrency_test.go
	modified:   parser/parser.go
	modified:   parser/parser_test.go

no changes added to commit (use "git add" and/or "git commit -a")
$ Bash
Show staged and unstaged changes
git diff
diff --git a/ast/ast.go b/ast/ast.go
index a504e54..634f647 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -373,10 +373,11 @@ func (s *Send) TokenLiteral() string { return s.Token.Literal }
 // Receive represents a channel receive: c ? x or c ? x ; y
 type Receive struct {
 	Token          lexer.Token  // the ? token
-	Channel        string       // channel name
-	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x
-	Variable       string       // variable to receive into (simple receive)
-	Variables      []string     // additional variables for sequential receives (c ? x ; y)
+	Channel         string       // channel name
+	ChannelIndices  []Expression // non-empty for cs[i] ? x or cs[i][j] ? x
+	Variable        string       // variable to receive into (simple receive)
+	VariableIndices []Expression // non-empty for c ? flags[0] or c ? grid[i][j]
+	Variables       []string     // additional variables for sequential receives (c ? x ; y)
 }
 
 func (r *Receive) statementNode()       {}
@@ -396,11 +397,12 @@ func (a *AltBlock) TokenLiteral() string { return a.Token.Literal }
 
 // AltCase represents a single case in an ALT block
 type AltCase struct {
-	Guard          Expression   // optional guard condition (nil if no guard)
-	Channel        string       // channel name
-	ChannelIndices []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
-	Variable       string       // variable to receive into
-	Body           []Statement  // the body to execute
+	Guard           Expression   // optional guard condition (nil if no guard)
+	Channel         string       // channel name
+	ChannelIndices  []Expression // non-empty for cs[i] ? x or cs[i][j] ? x in ALT
+	Variable        string       // variable to receive into
+	VariableIndices []Expression // non-empty for c ? flags[0] or c ? grid[i][j]
+	Body            []Statement  // the body to execute
 	IsTimer        bool         // true if this is a timer AFTER case
 	IsSkip         bool         // true if this is a guarded SKIP case (guard & SKIP)
 	Timer          string       // timer name (when IsTimer)
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 849ec88..8d6e4c8 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -1346,7 +1346,9 @@ func (g *Generator) generateReceive(recv *ast.Receive) {
 		g.tmpCounter++
 		g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef))
 		varRef := goIdent(recv.Variable)
-		if g.refParams[recv.Variable] {
+		if len(recv.VariableIndices) > 0 {
+			varRef += g.generateIndicesStr(recv.VariableIndices)
+		} else if g.refParams[recv.Variable] {
 			varRef = "*" + varRef
 		}
 		g.writeLine(fmt.Sprintf("%s = %s._0", varRef, tmpName))
@@ -1359,7 +1361,9 @@ func (g *Generator) generateReceive(recv *ast.Receive) {
 		}
 	} else {
 		varRef := goIdent(recv.Variable)
-		if g.refParams[recv.Variable] {
+		if len(recv.VariableIndices) > 0 {
+			varRef += g.generateIndicesStr(recv.VariableIndices)
+		} else if g.refParams[recv.Variable] {
 			varRef = "*" + varRef
 		}
 		g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef))
@@ -1903,13 +1907,25 @@ func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
 			g.generateExpression(c.Deadline)
 			g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
 		} else if c.Guard != nil {
-			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
+			varRef := goIdent(c.Variable)
+			if len(c.VariableIndices) > 0 {
+				varRef += g.generateIndicesStr(c.VariableIndices)
+			}
+			g.write(fmt.Sprintf("case %s = <-_alt%d:\n", varRef, i))
 		} else if len(c.ChannelIndices) > 0 {
-			g.write(fmt.Sprintf("case %s = <-%s", goIdent(c.Variable), goIdent(c.Channel)))
+			varRef := goIdent(c.Variable)
+			if len(c.VariableIndices) > 0 {
+				varRef += g.generateIndicesStr(c.VariableIndices)
+			}
+			g.write(fmt.Sprintf("case %s = <-%s", varRef, goIdent(c.Channel)))
 			g.generateIndices(c.ChannelIndices)
 			g.write(":\n")
 		} else {
-			g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
+			varRef := goIdent(c.Variable)
+			if len(c.VariableIndices) > 0 {
+				varRef += g.generateIndicesStr(c.VariableIndices)
+			}
+			g.write(fmt.Sprintf("case %s = <-%s:\n", varRef, goIdent(c.Channel)))
 		}
 		g.indent++
 		guardedSkip := c.IsSkip && c.Guard != nil
@@ -2040,7 +2056,11 @@ func (g *Generator) generateReplicatedAlt(alt *ast.AltBlock) {
 	}
 
 	// Assign received value from reflect.Value
-	g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", goIdent(c.Variable), recvType))
+	varRef := goIdent(c.Variable)
+	if len(c.VariableIndices) > 0 {
+		varRef += g.generateIndicesStr(c.VariableIndices)
+	}
+	g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", varRef, recvType))
 
 	// Generate body
 	for _, s := range c.Body {
diff --git a/codegen/e2e_concurrency_test.go b/codegen/e2e_concurrency_test.go
index 229120a..ccb8317 100644
--- a/codegen/e2e_concurrency_test.go
+++ b/codegen/e2e_concurrency_test.go
@@ -392,3 +392,41 @@ func TestE2E_PriPar(t *testing.T) {
 		t.Errorf("expected %q, got %q", expected, output)
 	}
 }
+
+func TestE2E_ReceiveIntoIndexedVariable(t *testing.T) {
+	occam := `SEQ
+  CHAN OF INT c:
+  [3]INT arr:
+  arr[0] := 0
+  arr[1] := 0
+  arr[2] := 0
+  PAR
+    c ! 42
+    c ? arr[1]
+  print.int(arr[1])
+`
+	output := transpileCompileRun(t, occam)
+	expected := "42\n"
+	if output != expected {
+		t.Errorf("expected %q, got %q", expected, output)
+	}
+}
+
+func TestE2E_IndexedChannelReceiveIntoIndexedVariable(t *testing.T) {
+	occam := `SEQ
+  [2]CHAN OF INT cs:
+  [3]INT arr:
+  arr[0] := 0
+  arr[1] := 0
+  arr[2] := 0
+  PAR
+    cs[0] ! 99
+    cs[0] ? arr[2]
+  print.int(arr[2])
+`
+	output := transpileCompileRun(t, occam)
+	expected := "99\n"
+	if output != expected {
+		t.Errorf("expected %q, got %q", expected, output)
+	}
+}
diff --git a/parser/parser.go b/parser/parser.go
index 704fcb8..9e90386 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -791,6 +791,16 @@ func (p *Parser) parseIndexedOperation() ast.Statement {
 		}
 		stmt.Variable = p.curToken.Literal
 
+		// Collect variable indices: cs[i] ? flags[0] or cs[i] ? grid[j][k]
+		for p.peekTokenIs(lexer.LBRACKET) {
+			p.nextToken() // move to [
+			p.nextToken() // move past [
+			stmt.VariableIndices = append(stmt.VariableIndices, p.parseExpression(LOWEST))
+			if !p.expectPeek(lexer.RBRACKET) {
+				return nil
+			}
+		}
+
 		// Check for sequential receive
 		for p.peekTokenIs(lexer.SEMICOLON) {
 			p.nextToken() // move to ;
@@ -1293,6 +1303,16 @@ func (p *Parser) parseReceive() ast.Statement {
 	}
 	stmt.Variable = p.curToken.Literal
 
+	// Collect variable indices: c ? flags[0] or c ? grid[i][j]
+	for p.peekTokenIs(lexer.LBRACKET) {
+		p.nextToken() // move to [
+		p.nextToken() // move past [
+		stmt.VariableIndices = append(stmt.VariableIndices, p.parseExpression(LOWEST))
+		if !p.expectPeek(lexer.RBRACKET) {
+			return nil
+		}
+	}
+
 	// Check for sequential receive: c ? x ; y ; z
 	for p.peekTokenIs(lexer.SEMICOLON) {
 		p.nextToken() // move to ;
@@ -1756,13 +1776,22 @@ func (p *Parser) parseAltCase() *ast.AltCase {
 			p.nextToken() // move past AFTER
 			altCase.Deadline = p.parseExpression(LOWEST)
 		} else {
-			// Simple case: channel ? var
+			// Simple case: channel ? var or channel ? var[i]
 			altCase.Channel = name
 			p.nextToken() // move to ?
 			if !p.expectPeek(lexer.IDENT) {
 				return nil
 			}
 			altCase.Variable = p.curToken.Literal
+			// Collect variable indices: ch ? flags[0]
+			for p.peekTokenIs(lexer.LBRACKET) {
+				p.nextToken() // move to [
+				p.nextToken() // move past [
+				altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
+				if !p.expectPeek(lexer.RBRACKET) {
+					return nil
+				}
+			}
 		}
 	} else if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.LBRACKET) {
 		// Indexed channel case: cs[i] ? var or cs[i][j] ? var
@@ -1783,6 +1812,15 @@ func (p *Parser) parseAltCase() *ast.AltCase {
 			return nil
 		}
 		altCase.Variable = p.curToken.Literal
+		// Collect variable indices: cs[i] ? flags[0]
+		for p.peekTokenIs(lexer.LBRACKET) {
+			p.nextToken() // move to [
+			p.nextToken() // move past [
+			altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
+			if !p.expectPeek(lexer.RBRACKET) {
+				return nil
+			}
+		}
 	} else {
 		// Guard followed by & channel ? var, or guard & SKIP
 		guard := p.parseExpression(LOWEST)
@@ -1823,6 +1861,15 @@ func (p *Parser) parseAltCase() *ast.AltCase {
 				return nil
 			}
 			altCase.Variable = p.curToken.Literal
+			// Collect variable indices: guard & ch ? flags[0]
+			for p.peekTokenIs(lexer.LBRACKET) {
+				p.nextToken() // move to [
+				p.nextToken() // move past [
+				altCase.VariableIndices = append(altCase.VariableIndices, p.parseExpression(LOWEST))
+				if !p.expectPeek(lexer.RBRACKET) {
+					return nil
+				}
+			}
 		}
 	}
 
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 1002b29..1fed10a 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -3888,3 +3888,93 @@ PROC test(CHAN OF CMD ch)
 		t.Errorf("expected SeqBlock as second body statement, got %T", evolveCase.Body[1])
 	}
 }
+
+func TestReceiveIndexedVariable(t *testing.T) {
+	input := `ch ? flags[0]
+`
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	if len(program.Statements) != 1 {
+		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
+	}
+
+	recv, ok := program.Statements[0].(*ast.Receive)
+	if !ok {
+		t.Fatalf("expected Receive, got %T", program.Statements[0])
+	}
+
+	if recv.Channel != "ch" {
+		t.Errorf("expected channel 'ch', got %s", recv.Channel)
+	}
+
+	if recv.Variable != "flags" {
+		t.Errorf("expected variable 'flags', got %s", recv.Variable)
+	}
+
+	if len(recv.VariableIndices) != 1 {
+		t.Fatalf("expected 1 variable index, got %d", len(recv.VariableIndices))
+	}
+}
+
+func TestReceiveMultiIndexedVariable(t *testing.T) {
+	input := `ch ? grid[i][j]
+`
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	if len(program.Statements) != 1 {
+		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
+	}
+
+	recv, ok := program.Statements[0].(*ast.Receive)
+	if !ok {
+		t.Fatalf("expected Receive, got %T", program.Statements[0])
+	}
+
+	if recv.Variable != "grid" {
+		t.Errorf("expected variable 'grid', got %s", recv.Variable)
+	}
+
+	if len(recv.VariableIndices) != 2 {
+		t.Fatalf("expected 2 variable indices, got %d", len(recv.VariableIndices))
+	}
+}
+
+func TestIndexedChannelReceiveIndexedVariable(t *testing.T) {
+	input := `cs[0] ? flags[1]
+`
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	if len(program.Statements) != 1 {
+		t.Fatalf("expected 1 statement, got %d", len(program.Statements))
+	}
+
+	recv, ok := program.Statements[0].(*ast.Receive)
+	if !ok {
+		t.Fatalf("expected Receive, got %T", program.Statements[0])
+	}
+
+	if recv.Channel != "cs" {
+		t.Errorf("expected channel 'cs', got %s", recv.Channel)
+	}
+
+	if len(recv.ChannelIndices) != 1 {
+		t.Fatalf("expected 1 channel index, got %d", len(recv.ChannelIndices))
+	}
+
+	if recv.Variable != "flags" {
+		t.Errorf("expected variable 'flags', got %s", recv.Variable)
+	}
+
+	if len(recv.VariableIndices) != 1 {
+		t.Fatalf("expected 1 variable index, got %d", len(recv.VariableIndices))
+	}
+}
$ Bash
Show recent commit messages
git log --oneline -5
32dedd5 Merge pull request #87 from codeassociates/fix/variant-receive-scoped-decl-86
694dcc3 Support scoped declarations in variant receive case bodies (#86)
2bd8573 Merge pull request #83 from codeassociates/feature/pri-alt-par-76
b49c3b8 Add PRI ALT and PRI PAR support (#76)
5ded5ab Merge pull request #82 from codeassociates/fix/guarded-skip-alt-77
$ Bash
Create feature branch
git checkout -b fix/receive-indexed-target-85
Switched to a new branch 'fix/receive-indexed-target-85'
$ Bash
Stage and commit changes
git add ast/ast.go parser/parser.go parser/parser_test.go codegen/codegen.go codegen/e2e_concurrency_test.go && git commit -m "$(cat <<'EOF'
Support receive into indexed target variables (#85)

Channel receive (`ch ? var`) now accepts indexed targets like `flags[0]`
or `grid[i][j]`, matching the existing pattern for indexed assignments.
Also supports indexed targets in ALT cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
5 files changed, 213 insertions(+), 16 deletions(-)
$ Bash
Push branch to remote
git push -u origin fix/receive-indexed-target-85
remote: 
remote: Create a pull request for 'fix/receive-indexed-target-85' on GitHub by visiting:        
remote:      https://github.com/codeassociates/occam2go/pull/new/fix/receive-indexed-target-85        
remote: 
To associate-1.github.com:codeassociates/occam2go.git
 * [new branch]      fix/receive-indexed-target-85 -> fix/receive-indexed-target-85
branch 'fix/receive-indexed-target-85' set up to track 'origin/fix/receive-indexed-target-85'.
$ Bash
Create pull request
gh pr create --title "Support receive into indexed target variables (#85)" --body "$(cat <<'EOF'
## Summary

- Add `VariableIndices []Expression` to `Receive` and `AltCase` AST nodes, mirroring the existing `ChannelIndices` pattern
- Parse index expressions after the target variable in channel receive (`ch ? flags[0]`, `cs[i] ? grid[j][k]`) across all 5 parser sites: `parseReceive()`, `parseIndexedOperation()`, and 3 branches of `parseAltCase()`
- Generate indexed variable references in codegen across all 6 sites: `generateReceive()` (simple + sequential), `generateAltBlock()` (guarded, indexed-channel, simple-channel), and `generateReplicatedAlt()`

Closes #85

## Test plan

- [x] 3 parser unit tests: single index, multi-index, both channel and variable indexed
- [x] 2 e2e tests: `c ? arr[1]` and `cs[0] ? arr[2]` patterns
- [x] All existing tests pass (`go test ./...`)
- [x] Course module still transpiles and passes `go vet`

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

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

Thank you. I've approved the PR and CI checks have passed. Please merge.

$ Bash
Merge PR #88
gh pr merge 88 --merge
$ Bash
Merge PR #88
gh pr merge 88 --merge
! Pull request codeassociates/occam2go#88 was already merged

PR #88 is already merged.

Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.

/exit exit