Implement the following plan:
The transpiler only supports single-dimensional arrays. The Life example (historical-examples/life.occ) requires multi-dimensional arrays for its cell grid and channel network. This is the single most pervasive error source in that file, appearing at 15+ lines. The fix requires changes across AST, parser, codegen, and tests.
ast/ast.go — struct field changesparser/parser.go — multi-dim parsing loopscodegen/codegen.go — nested init loops, multi-index emissionparser/parser_test.go — update existing + add new testscodegen/codegen_test.go — update existing + add new testscodegen/e2e_array_test.go — add multi-dim e2e testsCLAUDE.md / TODO.md — documentation updatesast/ast.go)Rename single-value fields to plural slices. Existing single-dim code uses len(Xs) == 1.
| Struct | Old Field | New Field |
|---|---|---|
ArrayDecl |
Size Expression |
Sizes []Expression |
ChanDecl |
IsArray bool + Size Expression |
Sizes []Expression (remove IsArray; len(Sizes) > 0 = array) |
Send |
ChannelIndex Expression |
ChannelIndices []Expression |
Receive |
ChannelIndex Expression |
ChannelIndices []Expression |
AltCase |
ChannelIndex Expression |
ChannelIndices []Expression |
VariantReceive |
ChannelIndex Expression |
ChannelIndices []Expression |
Assignment |
Index Expression |
Indices []Expression |
MultiAssignTarget |
Index Expression |
Indices []Expression |
ProcParam |
IsChanArray bool + IsOpenArray bool |
ChanArrayDims int + OpenArrayDims int (0=not array, 1=[], 2=[][], etc.) |
Not changed: Abbreviation.IsOpenArray (only single-dim VAL []BYTE needed), IndexExpr.Index (expression-level chaining already works).
After AST changes, update every reference across parser, codegen, and tests:
| Old Pattern | New Pattern |
|---|---|
decl.Size (ArrayDecl) |
decl.Sizes[0] or loop decl.Sizes |
decl.IsArray / decl.Size (ChanDecl) |
len(decl.Sizes) > 0 / decl.Sizes |
send.ChannelIndex |
send.ChannelIndices |
recv.ChannelIndex |
recv.ChannelIndices |
vr.ChannelIndex |
vr.ChannelIndices |
c.ChannelIndex (AltCase) |
c.ChannelIndices |
assign.Index |
assign.Indices |
target.Index |
target.Indices |
p.IsChanArray |
p.ChanArrayDims > 0 |
p.IsOpenArray |
p.OpenArrayDims > 0 |
Key locations in codegen.go to update:
- containsMostExpr() (line 802): s.Index → loop s.Indices
- containsMostExpr() (line 805): t.Index → loop t.Indices
- walkStatements() (line 2871): s.Index → loop s.Indices
- generateChanDecl() (line 1119): decl.IsArray → len(decl.Sizes) > 0
- generateSend() (line 1160): single index → loop
- generateReceive() (line 1206): single index → loop
- generateVariantReceive() (line 1297): single index → loop
- generateAssignment() (line 1602): single index → loop
- generateMultiAssignment() (line 2216): single index → loop
- generateProcParams() (line 2040): IsChanArray → ChanArrayDims
- generateProcCall() (line 2103): IsChanArray/IsOpenArray → dims > 0
- generateProcDecl() (line 1946): IsChanArray/IsOpenArray → dims > 0
- collectChanProtocols() (line 1356): IsChanArray → ChanArrayDims > 0
- ALT codegen (lines 1793, 1873): single index → loop
- generateFuncDecl() (line 2143): IsChanArray → ChanArrayDims > 0
- generateFuncCallExpr() (line 2199): IsOpenArray → OpenArrayDims > 0
- generateRetypesDecl() (line 2671): r.IsArray → len(r.Sizes) > 0 — wait, this is RetypesDecl, keep as-is
Key locations in parser.go to update:
- parseProcParams() shared-type copy (line 2034-2035): copy ChanArrayDims/OpenArrayDims
- parseProcParams() direction check (lines 2041, 2160): IsChanArray → ChanArrayDims > 0
- parseAbbreviation() (line 420): IsOpenArray stays (on Abbreviation, not ProcParam)
- All ChannelIndex: assignments become ChannelIndices: []Expression{...} or append
parseArrayDecl() (line 530)After parsing first [size] and ], loop while peekTokenIs(LBRACKET) to collect additional sizes:
sizes := []Expression{firstSize}
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken(); p.nextToken() // move past [
size := p.parseExpression(LOWEST)
p.expectPeek(lexer.RBRACKET)
sizes = append(sizes, size)
}
Then create ChanDecl{Sizes: sizes} or ArrayDecl{Sizes: sizes}.
parseIndexedOperation() (line 676)After parsing first name[index], loop while peekTokenIs(LBRACKET):
indices := []Expression{index}
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken(); p.nextToken()
idx := p.parseExpression(LOWEST)
p.expectPeek(lexer.RBRACKET)
indices = append(indices, idx)
}
Then use Indices: indices / ChannelIndices: indices in all branches.
parseProcParams() (line 2070)Count consecutive [] pairs for open array params:
dims := 0
for p.curTokenIs(LBRACKET) && p.peekTokenIs(RBRACKET) {
dims++
p.nextToken(); p.nextToken() // consume []
}
Then set param.ChanArrayDims = dims or param.OpenArrayDims = dims.
parseAltCase() (lines 1733, 1773)After parsing channel name, loop while peekTokenIs(LBRACKET):
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken(); p.nextToken()
idx := p.parseExpression(LOWEST)
p.expectPeek(lexer.RBRACKET)
altCase.ChannelIndices = append(altCase.ChannelIndices, idx)
}
parseMultiAssignmentFrom() (line 503)When parsing subsequent targets with [, loop for multiple indices.
parseVariantReceiveWithIndex() (line 1384)Signature changes from (channel string, channelIndex Expression, ...) to (channel string, channelIndices []Expression, ...).
generateIndices() helperfunc (g *Generator) generateIndices(indices []ast.Expression) {
for _, idx := range indices {
g.write("["); g.generateExpression(idx); g.write("]")
}
}
Use this in generateSend, generateAssignment, generateMultiAssignment, ALT codegen.
generateArrayDecl() — nested make + init loopsFor [5][3]INT arr: (Sizes=[5,3]):
arr := make([][]int, 5)
for _i0 := range arr { arr[_i0] = make([]int, 3) }
Implement as recursive nested loop generator for arbitrary depth.
generateChanDecl() — nested make + init loops + innermost channel initFor [w][h][n]CHAN OF STATE link: (Sizes=[w,h,n]):
link := make([][][]chan bool, w)
for _i0 := range link {
link[_i0] = make([][]chan bool, h)
for _i1 := range link[_i0] {
link[_i0][_i1] = make([]chan bool, n)
for _i2 := range link[_i0][_i1] { link[_i0][_i1][_i2] = make(chan bool) }
}
}
generateProcParams() — multi-dim type stringsif p.ChanArrayDims > 0 {
goType = strings.Repeat("[]", p.ChanArrayDims) + "chan " + ...
} else if p.OpenArrayDims > 0 {
goType = strings.Repeat("[]", p.OpenArrayDims) + ...
}
Loop over ChannelIndices building chanRef string with nested [idx].
parser/parser_test.go)decl.Size → decl.Sizes[0], etc.TestMultiDimArrayDecl, TestMultiDimChanArrayDecl, TestMultiDimIndexedSend, TestMultiDimIndexedReceive, TestMultiDimIndexedAssignment, TestMultiDimOpenArrayParam, TestMultiDimChanArrayParamcodegen/codegen_test.go)TestMultiDimArrayDeclCodegen, TestMultiDimChanDeclCodegen, TestMultiDimSendCodegen, TestMultiDimReceiveCodegen, TestMultiDimProcParamCodegencodegen/e2e_array_test.go)TestE2E_MultiDimArray (2D array, fill + read), TestE2E_MultiDimChanArray (2D channel array with send/receive), TestE2E_MultiDimChanArrayWithProc (pass [][]CHAN to proc)CLAUDE.md mapping table to show multi-dim arraysTODO.md to mark multi-dim arrays as implementedgo build ./... # must compile
go test ./... # all tests pass (existing + new)
# Smoke test with life.occ patterns:
./occam2go -I examples -o /tmp/multidim_test.go examples/multidim_test.occ # if we create a test file
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/5d493ff0-ec84-4714-b1d7-c70b41242661.jsonl
This is a large multi-step implementation task. Let me break it down and work through it systematically. The plan is well-detailed, so I'll follow it closely.
Let me start by reading the key files to understand the current state, then make the changes step by step.
Let me create tasks to track progress, then start working through the steps.
{
"subject": "AST changes - rename fields to support multi-dim arrays",
"activeForm": "Updating AST struct fields"
}{
"subject": "Fix all compilation errors from AST renames",
"activeForm": "Fixing compilation errors from AST renames"
}{
"subject": "Parser multi-dim loops",
"activeForm": "Adding multi-dim parsing loops"
}{
"subject": "Codegen multi-dim generation",
"activeForm": "Implementing multi-dim codegen"
}{
"subject": "Add tests for multi-dim arrays",
"activeForm": "Adding multi-dim array tests"
}{
"subject": "Update documentation",
"activeForm": "Updating documentation"
} 1→package ast
2→
3→import (
4→ "github.com/codeassociates/occam2go/lexer"
5→)
6→
7→// Node is the base interface for all AST nodes
8→type Node interface {
9→ TokenLiteral() string
10→}
11→
12→// Statement represents a statement node
13→type Statement interface {
14→ Node
15→ statementNode()
16→}
17→
18→// Expression represents an expression node
19→type Expression interface {
20→ Node
21→ expressionNode()
22→}
23→
24→// Program is the root node of every AST
25→type Program struct {
26→ Statements []Statement
27→}
28→
29→func (p *Program) TokenLiteral() string {
30→ if len(p.Statements) > 0 {
31→ return p.Statements[0].TokenLiteral()
32→ }
33→ return ""
34→}
35→
36→// VarDecl represents a variable declaration: INT x:
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 {
48→ Token lexer.Token // the [ token
49→ Size Expression // array size
50→ Type string // element type ("INT", "BYTE", "BOOL", etc.)
51→ Names []string // variable names
52→}
53→
54→func (a *ArrayDecl) statementNode() {}
55→func (a *ArrayDecl) TokenLiteral() string { return a.Token.Literal }
56→
57→// Assignment represents an assignment: x := 5 or arr[i] := 5 or [arr FROM n FOR m] := value
58→type Assignment struct {
59→ Token lexer.Token // the := token
60→ Name string // variable name
61→ Index Expression // optional: index expression for arr[i] := x (nil for simple assignments)
62→ SliceTarget *SliceExpr // optional: slice target for [arr FROM n FOR m] := value
63→ Value Expression // the value being assigned
64→}
65→
66→func (a *Assignment) statementNode() {}
67→func (a *Assignment) TokenLiteral() string { return a.Token.Literal }
68→
69→// MultiAssignTarget represents one target in a multi-assignment.
70→// Name is always set. Index is non-nil for indexed targets like arr[i].
71→type MultiAssignTarget struct {
72→ Name string // variable name
73→ Index Expression // optional: index expression for arr[i] (nil for simple ident)
74→}
75→
76→// MultiAssignment represents a multi-target assignment: a, b := func(x)
77→type MultiAssignment struct {
78→ Token lexer.Token // the := token
79→ Targets []MultiAssignTarget // targets on the left side
80→ Values []Expression // expressions on the right side
81→}
82→
83→func (m *MultiAssignment) statementNode() {}
84→func (m *MultiAssignment) TokenLiteral() string { return m.Token.Literal }
85→
86→// SeqBlock represents a SEQ block (sequential execution)
87→// If Replicator is non-nil, this is a replicated SEQ (SEQ i = 0 FOR n)
88→type SeqBlock struct {
89→ Token lexer.Token // the SEQ token
90→ Statements []Statement
91→ Replicator *Replicator // optional replicator
92→}
93→
94→func (s *SeqBlock) statementNode() {}
95→func (s *SeqBlock) TokenLiteral() string { return s.Token.Literal }
96→
97→// ParBlock represents a PAR block (parallel execution)
98→// If Replicator is non-nil, this is a replicated PAR (PAR i = 0 FOR n)
99→type ParBlock struct {
100→ Token lexer.Token // the PAR token
101→ Statements []Statement
102→ Replicator *Replicator // optional replicator
103→}
104→
105→func (p *ParBlock) statementNode() {}
106→func (p *ParBlock) TokenLiteral() string { return p.Token.Literal }
107→
108→// Replicator represents a replication spec: i = start FOR count [STEP step]
109→type Replicator struct {
110→ Variable string // loop variable name
111→ Start Expression // start value
112→ Count Expression // number of iterations
113→ Step Expression // optional step value (nil means step of 1)
114→}
115→
116→// Skip represents the SKIP statement (no-op)
117→type Skip struct {
118→ Token lexer.Token
119→}
120→
121→func (s *Skip) statementNode() {}
122→func (s *Skip) TokenLiteral() string { return s.Token.Literal }
123→
124→// Stop represents the STOP statement (deadlock/halt)
125→type Stop struct {
126→ Token lexer.Token
127→}
128→
129→func (s *Stop) statementNode() {}
130→func (s *Stop) TokenLiteral() string { return s.Token.Literal }
131→
132→// ProcDecl represents a procedure declaration
133→type ProcDecl struct {
134→ Token lexer.Token // the PROC token
135→ Name string
136→ Params []ProcParam
137→ Body []Statement // local declarations + body process
138→}
139→
140→func (p *ProcDecl) statementNode() {}
141→func (p *ProcDecl) TokenLiteral() string { return p.Token.Literal }
142→
143→// ProcParam represents a procedure parameter
144→type ProcParam struct {
145→ IsVal bool // VAL parameter (pass by value)
146→ Type string // INT, BYTE, BOOL, etc.
147→ Name string
148→ IsChan bool // true if this is a CHAN OF <type> parameter
149→ IsChanArray bool // true for []CHAN OF TYPE params
150→ IsOpenArray bool // true for []TYPE params (open array)
151→ ChanElemType string // element type when IsChan (e.g., "INT")
152→ ChanDir string // "?" for input, "!" for output, "" for bidirectional
153→ ArraySize string // non-empty for fixed-size array params like [2]INT
154→}
155→
156→// ProcCall represents a procedure call
157→type ProcCall struct {
158→ Token lexer.Token // the procedure name token
159→ Name string
160→ Args []Expression
161→}
162→
163→func (p *ProcCall) statementNode() {}
164→func (p *ProcCall) TokenLiteral() string { return p.Token.Literal }
165→
166→// FuncDecl represents a function declaration (single or multi-result)
167→type FuncDecl struct {
168→ Token lexer.Token // the return type token
169→ ReturnTypes []string // return types: ["INT"], ["INT", "INT"], etc.
170→ Name string
171→ Params []ProcParam
172→ Body []Statement // local decls + body statements (VALOF form), empty for IS form
173→ ResultExprs []Expression // return expressions (from IS or RESULT)
174→}
175→
176→func (f *FuncDecl) statementNode() {}
177→func (f *FuncDecl) TokenLiteral() string { return f.Token.Literal }
178→
179→// FuncCall represents a function call expression
180→type FuncCall struct {
181→ Token lexer.Token // the function name token
182→ Name string
183→ Args []Expression
184→}
185→
186→func (f *FuncCall) expressionNode() {}
187→func (f *FuncCall) TokenLiteral() string { return f.Token.Literal }
188→
189→// WhileLoop represents a WHILE loop
190→type WhileLoop struct {
191→ Token lexer.Token // the WHILE token
192→ Condition Expression
193→ Body []Statement
194→}
195→
196→func (w *WhileLoop) statementNode() {}
197→func (w *WhileLoop) TokenLiteral() string { return w.Token.Literal }
198→
199→// IfStatement represents an IF statement
200→type IfStatement struct {
201→ Token lexer.Token // the IF token
202→ Choices []IfChoice
203→ Replicator *Replicator // optional replicator for IF i = start FOR count
204→}
205→
206→type IfChoice struct {
207→ Condition Expression
208→ Body []Statement
209→ NestedIf *IfStatement // non-nil when this choice is a nested/replicated IF
210→}
211→
212→func (i *IfStatement) statementNode() {}
213→func (i *IfStatement) TokenLiteral() string { return i.Token.Literal }
214→
215→// CaseStatement represents a CASE statement
216→type CaseStatement struct {
217→ Token lexer.Token // the CASE token
218→ Selector Expression // the selector expression
219→ Choices []CaseChoice
220→}
221→
222→type CaseChoice struct {
223→ Values []Expression // nil/empty for ELSE
224→ IsElse bool
225→ Body []Statement
226→}
227→
228→func (c *CaseStatement) statementNode() {}
229→func (c *CaseStatement) TokenLiteral() string { return c.Token.Literal }
230→
231→// Expressions
232→
233→// Identifier represents a variable reference
234→type Identifier struct {
235→ Token lexer.Token
236→ Value string
237→}
238→
239→func (i *Identifier) expressionNode() {}
240→func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
241→
242→// IntegerLiteral represents an integer literal
243→type IntegerLiteral struct {
244→ Token lexer.Token
245→ Value int64
246→}
247→
248→func (il *IntegerLiteral) expressionNode() {}
249→func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
250→
251→// BooleanLiteral represents TRUE or FALSE
252→type BooleanLiteral struct {
253→ Token lexer.Token
254→ Value bool
255→}
256→
257→func (bl *BooleanLiteral) expressionNode() {}
258→func (bl *BooleanLiteral) TokenLiteral() string { return bl.Token.Literal }
259→
260→// StringLiteral represents a string literal: "hello"
261→type StringLiteral struct {
262→ Token lexer.Token
263→ Value string
264→}
265→
266→func (sl *StringLiteral) expressionNode() {}
267→func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
268→
269→// ByteLiteral represents a byte literal: 'A', '*n', etc.
270→type ByteLiteral struct {
271→ Token lexer.Token
272→ Value byte
273→}
274→
275→func (bl *ByteLiteral) expressionNode() {}
276→func (bl *ByteLiteral) TokenLiteral() string { return bl.Token.Literal }
277→
278→// BinaryExpr represents a binary operation: x + y
279→type BinaryExpr struct {
280→ Token lexer.Token // the operator token
281→ Left Expression
282→ Operator string
283→ Right Expression
284→}
285→
286→func (be *BinaryExpr) expressionNode() {}
287→func (be *BinaryExpr) TokenLiteral() string { return be.Token.Literal }
288→
289→// UnaryExpr represents a unary operation: NOT x, -x
290→type UnaryExpr struct {
291→ Token lexer.Token // the operator token
292→ Operator string
293→ Right Expression
294→}
295→
296→func (ue *UnaryExpr) expressionNode() {}
297→func (ue *UnaryExpr) TokenLiteral() string { return ue.Token.Literal }
298→
299→// TypeConversion represents a type conversion expression: INT x, BYTE n, etc.
300→type TypeConversion struct {
301→ Token lexer.Token // the type token (INT, BYTE, etc.)
302→ TargetType string // "INT", "BYTE", "BOOL", "REAL"
303→ Qualifier string // "" (none), "ROUND", or "TRUNC"
304→ Expr Expression // the expression to convert
305→}
306→
307→func (tc *TypeConversion) expressionNode() {}
308→func (tc *TypeConversion) TokenLiteral() string { return tc.Token.Literal }
309→
310→// SizeExpr represents a SIZE expression: SIZE arr
311→type SizeExpr struct {
312→ Token lexer.Token // the SIZE token
313→ Expr Expression // the array/string expression
314→}
315→
316→func (se *SizeExpr) expressionNode() {}
317→func (se *SizeExpr) TokenLiteral() string { return se.Token.Literal }
318→
319→// MostExpr represents MOSTNEG/MOSTPOS type expressions: MOSTNEG INT, MOSTPOS BYTE, etc.
320→type MostExpr struct {
321→ Token lexer.Token // the MOSTNEG or MOSTPOS token
322→ ExprType string // "INT", "BYTE", "REAL32", "REAL64", etc.
323→ IsNeg bool // true for MOSTNEG, false for MOSTPOS
324→}
325→
326→func (me *MostExpr) expressionNode() {}
327→func (me *MostExpr) TokenLiteral() string { return me.Token.Literal }
328→
329→// ParenExpr represents a parenthesized expression
330→type ParenExpr struct {
331→ Token lexer.Token
332→ Expr Expression
333→}
334→
335→func (pe *ParenExpr) expressionNode() {}
336→func (pe *ParenExpr) TokenLiteral() string { return pe.Token.Literal }
337→
338→// IndexExpr represents an array index expression: arr[i]
339→type IndexExpr struct {
340→ Token lexer.Token // the [ token
341→ Left Expression // the array expression
342→ Index Expression // the index expression
343→}
344→
345→func (ie *IndexExpr) expressionNode() {}
346→func (ie *IndexExpr) TokenLiteral() string { return ie.Token.Literal }
347→
348→// ChanDecl represents a channel declaration: CHAN OF INT c: or [n]CHAN OF INT cs:
349→type ChanDecl struct {
350→ Token lexer.Token // the CHAN token
351→ ElemType string // the element type (INT, BYTE, etc.)
352→ Names []string // channel names
353→ IsArray bool // true for [n]CHAN OF TYPE
354→ Size Expression // array size when IsArray
355→}
356→
357→func (c *ChanDecl) statementNode() {}
358→func (c *ChanDecl) TokenLiteral() string { return c.Token.Literal }
359→
360→// Send represents a channel send: c ! x or c ! x ; y or c ! tag ; x
361→type Send struct {
362→ Token lexer.Token // the ! token
363→ Channel string // channel name
364→ ChannelIndex Expression // non-nil for cs[i] ! value
365→ Value Expression // value to send (simple send, backward compat)
366→ Values []Expression // additional values for sequential sends (c ! x ; y)
367→ VariantTag string // variant tag name for variant sends (c ! tag ; x)
368→}
369→
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→ ChannelIndex Expression // non-nil for cs[i] ? 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→}
392→
393→func (a *AltBlock) statementNode() {}
394→func (a *AltBlock) TokenLiteral() string { return a.Token.Literal }
395→
396→// AltCase represents a single case in an ALT block
397→type AltCase struct {
398→ Guard Expression // optional guard condition (nil if no guard)
399→ Channel string // channel name
400→ ChannelIndex Expression // non-nil for cs[i] ? x in ALT
401→ Variable string // variable to receive into
402→ Body []Statement // the body to execute
403→ IsTimer bool // true if this is a timer AFTER case
404→ IsSkip bool // true if this is a guarded SKIP case (guard & SKIP)
405→ Timer string // timer name (when IsTimer)
406→ Deadline Expression // AFTER deadline expression (when IsTimer)
407→ Declarations []Statement // scoped declarations before channel input (e.g., BYTE ch:)
408→}
409→
410→// TimerDecl represents a timer declaration: TIMER tim:
411→type TimerDecl struct {
412→ Token lexer.Token // the TIMER token
413→ Names []string // timer variable names
414→}
415→
416→func (td *TimerDecl) statementNode() {}
417→func (td *TimerDecl) TokenLiteral() string { return td.Token.Literal }
418→
419→// TimerRead represents a timer read: tim ? t
420→type TimerRead struct {
421→ Token lexer.Token // the ? token
422→ Timer string // timer name
423→ Variable string // variable to receive time into
424→}
425→
426→func (tr *TimerRead) statementNode() {}
427→func (tr *TimerRead) TokenLiteral() string { return tr.Token.Literal }
428→
429→// ProtocolDecl represents a protocol declaration
430→type ProtocolDecl struct {
431→ Token lexer.Token // the PROTOCOL token
432→ Name string // protocol name
433→ Kind string // "simple", "sequential", or "variant"
434→ Types []string // element types (simple: len=1, sequential: len>1)
435→ Variants []ProtocolVariant // only for Kind="variant"
436→}
437→
438→type ProtocolVariant struct {
439→ Tag string // tag name (e.g., "text", "quit")
440→ Types []string // associated types (empty for no-payload tags)
441→}
442→
443→func (pd *ProtocolDecl) statementNode() {}
444→func (pd *ProtocolDecl) TokenLiteral() string { return pd.Token.Literal }
445→
446→// VariantReceive represents a variant protocol receive: c ? CASE ...
447→type VariantReceive struct {
448→ Token lexer.Token // the ? token
449→ Channel string
450→ ChannelIndex Expression // non-nil for cs[i] ? CASE ...
451→ Cases []VariantCase
452→}
453→
454→type VariantCase struct {
455→ Tag string // variant tag name
456→ Variables []string // variables to bind payload fields
457→ Body Statement
458→}
459→
460→func (vr *VariantReceive) statementNode() {}
461→func (vr *VariantReceive) TokenLiteral() string { return vr.Token.Literal }
462→
463→// RecordDecl represents a record type declaration: RECORD POINT { INT x: INT y: }
464→type RecordDecl struct {
465→ Token lexer.Token // the RECORD token
466→ Name string // record type name
467→ Fields []RecordField // named fields
468→}
469→
470→type RecordField struct {
471→ Type string // "INT", "BYTE", "BOOL", "REAL"
472→ Name string
473→}
474→
475→func (rd *RecordDecl) statementNode() {}
476→func (rd *RecordDecl) TokenLiteral() string { return rd.Token.Literal }
477→
478→// SliceExpr represents an array slice: [arr FROM start FOR length]
479→type SliceExpr struct {
480→ Token lexer.Token // the [ token
481→ Array Expression // the array being sliced
482→ Start Expression // start index
483→ Length Expression // number of elements
484→}
485→
486→func (se *SliceExpr) expressionNode() {}
487→func (se *SliceExpr) TokenLiteral() string { return se.Token.Literal }
488→
489→// Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42:
490→type Abbreviation struct {
491→ Token lexer.Token // VAL, INITIAL, or type token
492→ IsVal bool // true for VAL abbreviations
493→ IsInitial bool // true for INITIAL declarations
494→ IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE)
495→ Type string // "INT", "BYTE", "BOOL", etc.
496→ Name string // variable name
497→ Value Expression // the expression
498→}
499→
500→func (a *Abbreviation) statementNode() {}
501→func (a *Abbreviation) TokenLiteral() string { return a.Token.Literal }
502→
503→// ArrayLiteral represents an array literal expression: [expr1, expr2, ...]
504→type ArrayLiteral struct {
505→ Token lexer.Token // the [ token
506→ Elements []Expression // the elements
507→}
508→
509→func (al *ArrayLiteral) expressionNode() {}
510→func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal }
511→
512→// RetypesDecl represents a RETYPES declaration:
513→// VAL INT X RETYPES X : or VAL [2]INT X RETYPES X :
514→type RetypesDecl struct {
515→ Token lexer.Token // the VAL token
516→ IsVal bool // always true for now (VAL ... RETYPES ...)
517→ TargetType string // "INT", "REAL32", etc.
518→ IsArray bool // true for [n]TYPE
519→ ArraySize Expression // array size when IsArray
520→ Name string // target variable name
521→ Source string // source variable name
522→}
523→
524→func (r *RetypesDecl) statementNode() {}
525→func (r *RetypesDecl) TokenLiteral() string { return r.Token.Literal }
526→File content (26966 tokens) exceeds maximum allowed tokens (25000). Please use offset and limit parameters to read specific portions of the file, or use the GrepTool to search for specific content.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"limit": 500
} 1→package parser
2→
3→import (
4→ "fmt"
5→ "strconv"
6→ "strings"
7→
8→ "github.com/codeassociates/occam2go/ast"
9→ "github.com/codeassociates/occam2go/lexer"
10→)
11→
12→// Operator precedence levels
13→const (
14→ _ int = iota
15→ LOWEST
16→ OR_PREC // OR
17→ AND_PREC // AND
18→ EQUALS // =, <>
19→ LESSGREATER // <, >, <=, >=
20→ SUM // +, -
21→ PRODUCT // *, /, \
22→ PREFIX // -x, NOT x
23→ INDEX // arr[i]
24→)
25→
26→var precedences = map[lexer.TokenType]int{
27→ lexer.OR: OR_PREC,
28→ lexer.AND: AND_PREC,
29→ lexer.EQ: EQUALS,
30→ lexer.NEQ: EQUALS,
31→ lexer.LT: LESSGREATER,
32→ lexer.GT: LESSGREATER,
33→ lexer.LE: LESSGREATER,
34→ lexer.GE: LESSGREATER,
35→ lexer.AFTER: LESSGREATER,
36→ lexer.PLUS: SUM,
37→ lexer.MINUS: SUM,
38→ lexer.PLUS_KW: SUM,
39→ lexer.MINUS_KW: SUM,
40→ lexer.MULTIPLY: PRODUCT,
41→ lexer.DIVIDE: PRODUCT,
42→ lexer.MODULO: PRODUCT,
43→ lexer.TIMES: PRODUCT,
44→ lexer.BITAND: PRODUCT,
45→ lexer.LSHIFT: PRODUCT,
46→ lexer.RSHIFT: PRODUCT,
47→ lexer.BITOR: SUM,
48→ lexer.BITXOR: SUM,
49→ lexer.LBRACKET: INDEX,
50→}
51→
52→type Parser struct {
53→ l *lexer.Lexer
54→ errors []string
55→
56→ curToken lexer.Token
57→ peekToken lexer.Token
58→
59→ // Track current indentation level
60→ indentLevel int
61→
62→ // Track timer names to distinguish timer reads from channel receives
63→ timerNames map[string]bool
64→
65→ // Track protocol names and definitions
66→ protocolNames map[string]bool
67→ protocolDefs map[string]*ast.ProtocolDecl
68→
69→ // Track record type names and definitions
70→ recordNames map[string]bool
71→ recordDefs map[string]*ast.RecordDecl
72→}
73→
74→func New(l *lexer.Lexer) *Parser {
75→ p := &Parser{
76→ l: l,
77→ errors: []string{},
78→ timerNames: make(map[string]bool),
79→ protocolNames: make(map[string]bool),
80→ protocolDefs: make(map[string]*ast.ProtocolDecl),
81→ recordNames: make(map[string]bool),
82→ recordDefs: make(map[string]*ast.RecordDecl),
83→ }
84→ // Read two tokens to initialize curToken and peekToken
85→ p.nextToken()
86→ p.nextToken()
87→ return p
88→}
89→
90→func (p *Parser) Errors() []string {
91→ return p.errors
92→}
93→
94→func (p *Parser) addError(msg string) {
95→ p.errors = append(p.errors, fmt.Sprintf("line %d: %s", p.curToken.Line, msg))
96→}
97→
98→func (p *Parser) nextToken() {
99→ p.curToken = p.peekToken
100→ p.peekToken = p.l.NextToken()
101→
102→ // Track indentation level
103→ if p.curToken.Type == lexer.INDENT {
104→ p.indentLevel++
105→ } else if p.curToken.Type == lexer.DEDENT {
106→ p.indentLevel--
107→ }
108→}
109→
110→func (p *Parser) curTokenIs(t lexer.TokenType) bool {
111→ return p.curToken.Type == t
112→}
113→
114→func (p *Parser) peekTokenIs(t lexer.TokenType) bool {
115→ return p.peekToken.Type == t
116→}
117→
118→func (p *Parser) expectPeek(t lexer.TokenType) bool {
119→ if p.peekTokenIs(t) {
120→ p.nextToken()
121→ return true
122→ }
123→ p.addError(fmt.Sprintf("expected %s, got %s", t, p.peekToken.Type))
124→ return false
125→}
126→
127→func (p *Parser) peekPrecedence() int {
128→ if prec, ok := precedences[p.peekToken.Type]; ok {
129→ return prec
130→ }
131→ return LOWEST
132→}
133→
134→func (p *Parser) curPrecedence() int {
135→ if prec, ok := precedences[p.curToken.Type]; ok {
136→ return prec
137→ }
138→ return LOWEST
139→}
140→
141→// ParseProgram parses the entire program
142→func (p *Parser) ParseProgram() *ast.Program {
143→ program := &ast.Program{}
144→ program.Statements = []ast.Statement{}
145→
146→ for !p.curTokenIs(lexer.EOF) {
147→ stmt := p.parseStatement()
148→ if stmt != nil {
149→ program.Statements = append(program.Statements, stmt)
150→ }
151→ p.nextToken()
152→ }
153→
154→ return program
155→}
156→
157→func (p *Parser) parseStatement() ast.Statement {
158→ // Skip newlines
159→ for p.curTokenIs(lexer.NEWLINE) {
160→ p.nextToken()
161→ }
162→
163→ switch p.curToken.Type {
164→ case lexer.VAL:
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→ lexer.INT16_TYPE, lexer.INT32_TYPE, lexer.INT64_TYPE:
170→ if p.peekTokenIs(lexer.FUNCTION) || p.peekTokenIs(lexer.FUNC) || p.peekTokenIs(lexer.COMMA) || p.peekTokenIs(lexer.INLINE) {
171→ return p.parseFuncDecl()
172→ }
173→ return p.parseVarDeclOrAbbreviation()
174→ case lexer.LBRACKET:
175→ return p.parseArrayDecl()
176→ case lexer.CHAN:
177→ return p.parseChanDecl()
178→ case lexer.PROTOCOL:
179→ return p.parseProtocolDecl()
180→ case lexer.RECORD:
181→ return p.parseRecordDecl()
182→ case lexer.TIMER:
183→ return p.parseTimerDecl()
184→ case lexer.SEQ:
185→ return p.parseSeqBlock()
186→ case lexer.PAR:
187→ return p.parseParBlock()
188→ case lexer.ALT:
189→ return p.parseAltBlock()
190→ case lexer.SKIP:
191→ return &ast.Skip{Token: p.curToken}
192→ case lexer.STOP:
193→ return &ast.Stop{Token: p.curToken}
194→ case lexer.PROC:
195→ return p.parseProcDecl()
196→ case lexer.WHILE:
197→ return p.parseWhileLoop()
198→ case lexer.IF:
199→ return p.parseIfStatement()
200→ case lexer.CASE:
201→ return p.parseCaseStatement()
202→ case lexer.IDENT:
203→ // Check for record variable declaration: TYPENAME var:
204→ if p.recordNames[p.curToken.Literal] && p.peekTokenIs(lexer.IDENT) {
205→ return p.parseRecordVarDecl()
206→ }
207→ // Could be assignment, indexed assignment, indexed send/receive, send, receive, or procedure call
208→ if p.peekTokenIs(lexer.LBRACKET) {
209→ return p.parseIndexedOperation()
210→ }
211→ if p.peekTokenIs(lexer.ASSIGN) {
212→ return p.parseAssignment()
213→ }
214→ if p.peekTokenIs(lexer.COMMA) {
215→ return p.parseMultiAssignment()
216→ }
217→ if p.peekTokenIs(lexer.SEND) {
218→ return p.parseSend()
219→ }
220→ if p.peekTokenIs(lexer.RECEIVE) {
221→ if p.timerNames[p.curToken.Literal] {
222→ return p.parseTimerRead()
223→ }
224→ return p.parseReceive()
225→ }
226→ return p.parseProcCall()
227→ case lexer.INDENT, lexer.DEDENT, lexer.EOF:
228→ return nil
229→ default:
230→ p.addError(fmt.Sprintf("unexpected token: %s", p.curToken.Type))
231→ return nil
232→ }
233→}
234→
235→func (p *Parser) parseVarDecl() *ast.VarDecl {
236→ decl := &ast.VarDecl{
237→ Token: p.curToken,
238→ Type: p.curToken.Literal,
239→ }
240→
241→ // Parse variable names
242→ for {
243→ if !p.expectPeek(lexer.IDENT) {
244→ return nil
245→ }
246→ decl.Names = append(decl.Names, p.curToken.Literal)
247→
248→ if p.peekTokenIs(lexer.COMMA) {
249→ p.nextToken() // consume comma
250→ } else {
251→ break
252→ }
253→ }
254→
255→ if !p.expectPeek(lexer.COLON) {
256→ return nil
257→ }
258→
259→ return decl
260→}
261→
262→// parseVarDeclOrAbbreviation parses either a variable declaration (INT x:)
263→// or a non-VAL abbreviation (INT x IS expr:). Called when current token is a type keyword.
264→func (p *Parser) parseVarDeclOrAbbreviation() ast.Statement {
265→ typeToken := p.curToken
266→ typeName := p.curToken.Literal
267→
268→ // Consume the name
269→ if !p.expectPeek(lexer.IDENT) {
270→ return nil
271→ }
272→ name := p.curToken.Literal
273→
274→ // Check if this is an abbreviation (next token is IS)
275→ if p.peekTokenIs(lexer.IS) {
276→ p.nextToken() // consume IS
277→ p.nextToken() // move to expression
278→ value := p.parseExpression(LOWEST)
279→
280→ if !p.expectPeek(lexer.COLON) {
281→ return nil
282→ }
283→
284→ return &ast.Abbreviation{
285→ Token: typeToken,
286→ IsVal: false,
287→ Type: typeName,
288→ Name: name,
289→ Value: value,
290→ }
291→ }
292→
293→ // Otherwise, it's a regular variable declaration — continue parsing names
294→ decl := &ast.VarDecl{
295→ Token: typeToken,
296→ Type: typeName,
297→ Names: []string{name},
298→ }
299→
300→ // Parse additional comma-separated names
301→ for p.peekTokenIs(lexer.COMMA) {
302→ p.nextToken() // consume comma
303→ if !p.expectPeek(lexer.IDENT) {
304→ return nil
305→ }
306→ decl.Names = append(decl.Names, p.curToken.Literal)
307→ }
308→
309→ if !p.expectPeek(lexer.COLON) {
310→ return nil
311→ }
312→
313→ return decl
314→}
315→
316→// parseAbbreviation parses VAL abbreviations:
317→// VAL INT x IS expr: (typed VAL abbreviation)
318→// VAL []BYTE x IS "string": (open array abbreviation)
319→// VAL x IS expr: (untyped VAL abbreviation)
320→// VAL INT X RETYPES X : (RETYPES declaration)
321→// VAL [n]INT X RETYPES X : (array RETYPES declaration)
322→// Current token is VAL.
323→func (p *Parser) parseAbbreviation() ast.Statement {
324→ token := p.curToken // VAL token
325→
326→ p.nextToken()
327→
328→ // Check for []TYPE (open array abbreviation)
329→ isOpenArray := false
330→ if p.curTokenIs(lexer.LBRACKET) && p.peekTokenIs(lexer.RBRACKET) {
331→ isOpenArray = true
332→ p.nextToken() // consume ]
333→ p.nextToken() // move to type
334→ }
335→
336→ // Check for [n]TYPE (fixed-size array, used in RETYPES)
337→ isArray := false
338→ var arraySize ast.Expression
339→ if !isOpenArray && p.curTokenIs(lexer.LBRACKET) {
340→ // Could be [n]TYPE name RETYPES ...
341→ isArray = true
342→ p.nextToken() // move past [
343→ arraySize = p.parseExpression(LOWEST)
344→ if !p.expectPeek(lexer.RBRACKET) {
345→ return nil
346→ }
347→ p.nextToken() // move to type
348→ }
349→
350→ // Check for untyped VAL abbreviation: VAL name IS expr :
351→ // Detect: curToken is IDENT and peekToken is IS (no type keyword)
352→ if !isOpenArray && !isArray && p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.IS) {
353→ name := p.curToken.Literal
354→ p.nextToken() // consume IS
355→ p.nextToken() // move to expression
356→ value := p.parseExpression(LOWEST)
357→ if !p.expectPeek(lexer.COLON) {
358→ return nil
359→ }
360→ return &ast.Abbreviation{
361→ Token: token,
362→ IsVal: true,
363→ Type: "",
364→ Name: name,
365→ Value: value,
366→ }
367→ }
368→
369→ // Expect a type keyword
370→ if !isTypeToken(p.curToken.Type) {
371→ p.addError(fmt.Sprintf("expected type after VAL, got %s", p.curToken.Type))
372→ return nil
373→ }
374→ typeName := p.curToken.Literal
375→
376→ // Expect name
377→ if !p.expectPeek(lexer.IDENT) {
378→ return nil
379→ }
380→ name := p.curToken.Literal
381→
382→ // Check for RETYPES (instead of IS)
383→ if p.peekTokenIs(lexer.RETYPES) {
384→ p.nextToken() // consume RETYPES
385→ if !p.expectPeek(lexer.IDENT) {
386→ return nil
387→ }
388→ source := p.curToken.Literal
389→ if !p.expectPeek(lexer.COLON) {
390→ return nil
391→ }
392→ return &ast.RetypesDecl{
393→ Token: token,
394→ IsVal: true,
395→ TargetType: typeName,
396→ IsArray: isArray,
397→ ArraySize: arraySize,
398→ Name: name,
399→ Source: source,
400→ }
401→ }
402→
403→ // Expect IS
404→ if !p.expectPeek(lexer.IS) {
405→ return nil
406→ }
407→
408→ // Parse expression
409→ p.nextToken()
410→ value := p.parseExpression(LOWEST)
411→
412→ // Expect colon
413→ if !p.expectPeek(lexer.COLON) {
414→ return nil
415→ }
416→
417→ return &ast.Abbreviation{
418→ Token: token,
419→ IsVal: true,
420→ IsOpenArray: isOpenArray,
421→ Type: typeName,
422→ Name: name,
423→ Value: value,
424→ }
425→}
426→
427→// parseInitialDecl parses an INITIAL declaration: INITIAL INT x IS expr:
428→// Current token is INITIAL.
429→func (p *Parser) parseInitialDecl() *ast.Abbreviation {
430→ token := p.curToken // INITIAL token
431→
432→ // Expect a type keyword
433→ p.nextToken()
434→ if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
435→ !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) &&
436→ !p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) {
437→ p.addError(fmt.Sprintf("expected type after INITIAL, got %s", p.curToken.Type))
438→ return nil
439→ }
440→ typeName := p.curToken.Literal
441→
442→ // Expect name
443→ if !p.expectPeek(lexer.IDENT) {
444→ return nil
445→ }
446→ name := p.curToken.Literal
447→
448→ // Expect IS
449→ if !p.expectPeek(lexer.IS) {
450→ return nil
451→ }
452→
453→ // Parse expression
454→ p.nextToken()
455→ value := p.parseExpression(LOWEST)
456→
457→ // Expect colon
458→ if !p.expectPeek(lexer.COLON) {
459→ return nil
460→ }
461→
462→ return &ast.Abbreviation{
463→ Token: token,
464→ IsInitial: true,
465→ Type: typeName,
466→ Name: name,
467→ Value: value,
468→ }
469→}
470→
471→func (p *Parser) parseAssignment() *ast.Assignment {
472→ stmt := &ast.Assignment{
473→ Name: p.curToken.Literal,
474→ }
475→
476→ p.nextToken() // move to :=
477→ stmt.Token = p.curToken
478→
479→ p.nextToken() // move past :=
480→ stmt.Value = p.parseExpression(LOWEST)
481→
482→ return stmt
483→}
484→
485→func (p *Parser) parseMultiAssignment() *ast.MultiAssignment {
486→ firstTarget := ast.MultiAssignTarget{Name: p.curToken.Literal}
487→ return p.parseMultiAssignmentFrom(firstTarget)
488→}
489→
490→// parseMultiAssignmentFrom parses a multi-assignment given the first target already parsed.
491→// The current token should be on the first target's last token (ident or ']').
492→// Peek token should be COMMA.
493→func (p *Parser) parseMultiAssignmentFrom(firstTarget ast.MultiAssignTarget) *ast.MultiAssignment {
494→ stmt := &ast.MultiAssignment{
495→ Targets: []ast.MultiAssignTarget{firstTarget},
496→ }
497→
498→ // Parse comma-separated targets: a, b[i], c
499→ for p.peekTokenIs(lexer.COMMA) {
500→ p.nextToken() // consume comma{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"limit": 500
} 1→package codegen
2→
3→import (
4→ "fmt"
5→ "strings"
6→
7→ "github.com/codeassociates/occam2go/ast"
8→)
9→
10→// Generator converts an AST to Go code
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→ needReflect bool // track if we need reflect package import
22→ needBoolHelper bool // track if we need _boolToInt helper
23→
24→ // Track procedure signatures for proper pointer handling
25→ procSigs map[string][]ast.ProcParam
26→ // Track current procedure's reference parameters
27→ refParams map[string]bool
28→
29→ // Protocol support
30→ protocolDefs map[string]*ast.ProtocolDecl
31→ chanProtocols map[string]string // channel name → protocol name
32→ tmpCounter int // for unique temp variable names
33→
34→ // Record support
35→ recordDefs map[string]*ast.RecordDecl
36→ recordVars map[string]string // variable name → record type name
37→
38→ // Bool variable tracking (for type conversion codegen)
39→ boolVars map[string]bool
40→
41→ // Nesting level: 0 = package level, >0 = inside a function
42→ nestingLevel int
43→
44→ // RETYPES parameter renames: when a RETYPES declaration shadows a
45→ // parameter (e.g. VAL INT X RETYPES X :), the parameter is renamed
46→ // in the signature so := can create a new variable with the original name.
47→ retypesRenames map[string]string
48→}
49→
50→// Transputer intrinsic function names
51→var transpIntrinsics = map[string]bool{
52→ "LONGPROD": true,
53→ "LONGDIV": true,
54→ "LONGSUM": true,
55→ "LONGDIFF": true,
56→ "NORMALISE": true,
57→ "SHIFTRIGHT": true,
58→ "SHIFTLEFT": true,
59→}
60→
61→// Built-in print procedures
62→var printBuiltins = map[string]bool{
63→ "print.int": true,
64→ "print.string": true,
65→ "print.bool": true,
66→ "print.newline": true,
67→}
68→
69→// New creates a new code generator
70→func New() *Generator {
71→ return &Generator{}
72→}
73→
74→// goIdent converts an occam identifier to a valid Go identifier.
75→// Occam allows dots in identifiers (e.g., out.repeat); Go does not.
76→// goReserved is a set of Go keywords and predeclared identifiers that cannot be
77→// used as variable names when they also appear as type conversions in the generated code.
78→var goReserved = map[string]bool{
79→ "byte": true, "int": true, "string": true, "len": true, "cap": true,
80→ "make": true, "new": true, "copy": true, "close": true, "delete": true,
81→ "panic": true, "recover": true, "print": true, "println": true,
82→ "error": true, "rune": true, "bool": true, "true": true, "false": true,
83→}
84→
85→func goIdent(name string) string {
86→ name = strings.ReplaceAll(name, ".", "_")
87→ if goReserved[name] {
88→ return "_" + name
89→ }
90→ return name
91→}
92→
93→// Generate produces Go code from the AST
94→func (g *Generator) Generate(program *ast.Program) string {
95→ g.builder.Reset()
96→ g.needSync = false
97→ g.needFmt = false
98→ g.needTime = false
99→ g.needOs = false
100→ g.needMath = false
101→ g.needMathBits = false
102→ g.needBufio = false
103→ g.needReflect = false
104→ g.needBoolHelper = false
105→ g.procSigs = make(map[string][]ast.ProcParam)
106→ g.refParams = make(map[string]bool)
107→ g.protocolDefs = make(map[string]*ast.ProtocolDecl)
108→ g.chanProtocols = make(map[string]string)
109→ g.tmpCounter = 0
110→ g.recordDefs = make(map[string]*ast.RecordDecl)
111→ g.recordVars = make(map[string]string)
112→ g.boolVars = make(map[string]bool)
113→
114→ // Pre-pass: collect BOOL variable names (needed before containsBoolConversion)
115→ for _, stmt := range program.Statements {
116→ g.collectBoolVars(stmt)
117→ }
118→
119→ // First pass: collect procedure signatures, protocols, and check for PAR/print
120→ for _, stmt := range program.Statements {
121→ if g.containsPar(stmt) {
122→ g.needSync = true
123→ }
124→ if g.containsPrint(stmt) {
125→ g.needFmt = true
126→ }
127→ if g.containsTimer(stmt) {
128→ g.needTime = true
129→ }
130→ if g.containsStop(stmt) {
131→ g.needOs = true
132→ g.needFmt = true
133→ }
134→ if g.containsMostExpr(stmt) {
135→ g.needMath = true
136→ }
137→ if g.containsIntrinsics(stmt) {
138→ g.needMathBits = true
139→ }
140→ if g.containsRetypes(stmt) {
141→ g.needMath = true
142→ }
143→ if g.containsAltReplicator(stmt) {
144→ g.needReflect = true
145→ }
146→ if g.containsBoolConversion(stmt) {
147→ g.needBoolHelper = true
148→ }
149→ if proc, ok := stmt.(*ast.ProcDecl); ok {
150→ g.procSigs[proc.Name] = proc.Params
151→ g.collectNestedProcSigs(proc.Body)
152→ }
153→ if fn, ok := stmt.(*ast.FuncDecl); ok {
154→ g.procSigs[fn.Name] = fn.Params
155→ }
156→ if proto, ok := stmt.(*ast.ProtocolDecl); ok {
157→ g.protocolDefs[proto.Name] = proto
158→ }
159→ if rec, ok := stmt.(*ast.RecordDecl); ok {
160→ g.recordDefs[rec.Name] = rec
161→ }
162→ g.collectChanProtocols(stmt)
163→ g.collectRecordVars(stmt)
164→ }
165→
166→ // Separate protocol, record, procedure declarations from other statements
167→ var typeDecls []ast.Statement
168→ var procDecls []ast.Statement
169→ var mainStatements []ast.Statement
170→
171→ // First pass: check if there are any proc/func declarations
172→ hasProcDecls := false
173→ for _, stmt := range program.Statements {
174→ if _, ok := stmt.(*ast.ProcDecl); ok {
175→ hasProcDecls = true
176→ break
177→ }
178→ if _, ok := stmt.(*ast.FuncDecl); ok {
179→ hasProcDecls = true
180→ break
181→ }
182→ }
183→
184→ var abbrDecls []ast.Statement
185→ for _, stmt := range program.Statements {
186→ switch s := stmt.(type) {
187→ case *ast.ProtocolDecl, *ast.RecordDecl:
188→ typeDecls = append(typeDecls, stmt)
189→ case *ast.ProcDecl, *ast.FuncDecl:
190→ procDecls = append(procDecls, stmt)
191→ case *ast.Abbreviation:
192→ if hasProcDecls {
193→ // Top-level abbreviations need to be at package level
194→ // so PROCs can reference them
195→ abbrDecls = append(abbrDecls, stmt)
196→ } else {
197→ mainStatements = append(mainStatements, stmt)
198→ }
199→ case *ast.RetypesDecl:
200→ _ = s
201→ // RETYPES declarations are local to functions, not package-level
202→ mainStatements = append(mainStatements, stmt)
203→ default:
204→ mainStatements = append(mainStatements, stmt)
205→ }
206→ }
207→
208→ // Detect entry point PROC so we can set import flags before writing imports
209→ var entryProc *ast.ProcDecl
210→ if len(mainStatements) == 0 {
211→ entryProc = g.findEntryProc(procDecls)
212→ if entryProc != nil {
213→ g.needOs = true
214→ g.needSync = true
215→ g.needBufio = true
216→ }
217→ }
218→
219→ // Write package declaration
220→ g.writeLine("package main")
221→ g.writeLine("")
222→
223→ // Write imports
224→ if g.needSync || g.needFmt || g.needTime || g.needOs || g.needMath || g.needMathBits || g.needBufio || g.needReflect {
225→ g.writeLine("import (")
226→ g.indent++
227→ if g.needBufio {
228→ g.writeLine(`"bufio"`)
229→ }
230→ if g.needFmt {
231→ g.writeLine(`"fmt"`)
232→ }
233→ if g.needMath {
234→ g.writeLine(`"math"`)
235→ }
236→ if g.needMathBits {
237→ g.writeLine(`"math/bits"`)
238→ }
239→ if g.needOs {
240→ g.writeLine(`"os"`)
241→ }
242→ if g.needReflect {
243→ g.writeLine(`"reflect"`)
244→ }
245→ if g.needSync {
246→ g.writeLine(`"sync"`)
247→ }
248→ if g.needTime {
249→ g.writeLine(`"time"`)
250→ }
251→ g.indent--
252→ g.writeLine(")")
253→ g.writeLine("")
254→ }
255→
256→ // Emit transputer intrinsic helper functions
257→ if g.needMathBits {
258→ g.emitIntrinsicHelpers()
259→ }
260→
261→ // Emit _boolToInt helper function
262→ if g.needBoolHelper {
263→ g.emitBoolHelper()
264→ }
265→
266→ // Generate type definitions first (at package level)
267→ for _, stmt := range typeDecls {
268→ g.generateStatement(stmt)
269→ }
270→
271→ // Generate package-level abbreviations (constants)
272→ for _, stmt := range abbrDecls {
273→ abbr := stmt.(*ast.Abbreviation)
274→ if abbr.Type == "" {
275→ // Untyped VAL: let Go infer the type
276→ g.builder.WriteString("var ")
277→ g.write(fmt.Sprintf("%s = ", goIdent(abbr.Name)))
278→ g.generateExpression(abbr.Value)
279→ g.write("\n")
280→ } else {
281→ goType := g.occamTypeToGo(abbr.Type)
282→ if abbr.IsOpenArray {
283→ goType = "[]" + goType
284→ }
285→ g.builder.WriteString("var ")
286→ g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType))
287→ // Wrap string literals with []byte() when assigned to []byte variables
288→ if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
289→ g.write("[]byte(")
290→ g.generateExpression(abbr.Value)
291→ g.write(")")
292→ } else {
293→ g.generateExpression(abbr.Value)
294→ }
295→ g.write("\n")
296→ }
297→ }
298→ if len(abbrDecls) > 0 {
299→ g.writeLine("")
300→ }
301→
302→ // Generate procedure declarations (at package level)
303→ for _, stmt := range procDecls {
304→ g.generateStatement(stmt)
305→ }
306→
307→ // Generate main function with other statements
308→ if len(mainStatements) > 0 {
309→ g.writeLine("func main() {")
310→ g.indent++
311→ g.nestingLevel++
312→ for _, stmt := range mainStatements {
313→ g.generateStatement(stmt)
314→ }
315→ g.nestingLevel--
316→ g.indent--
317→ g.writeLine("}")
318→ } else if entryProc != nil {
319→ g.generateEntryHarness(entryProc)
320→ }
321→
322→ return g.builder.String()
323→}
324→
325→// collectNestedProcSigs recursively collects procedure/function signatures
326→// from nested declarations inside PROC bodies.
327→func (g *Generator) collectNestedProcSigs(stmts []ast.Statement) {
328→ for _, stmt := range stmts {
329→ switch s := stmt.(type) {
330→ case *ast.ProcDecl:
331→ g.procSigs[s.Name] = s.Params
332→ g.collectNestedProcSigs(s.Body)
333→ case *ast.FuncDecl:
334→ g.procSigs[s.Name] = s.Params
335→ g.collectNestedProcSigs(s.Body)
336→ case *ast.SeqBlock:
337→ g.collectNestedProcSigs(s.Statements)
338→ case *ast.ParBlock:
339→ g.collectNestedProcSigs(s.Statements)
340→ case *ast.IfStatement:
341→ for _, c := range s.Choices {
342→ g.collectNestedProcSigs(c.Body)
343→ }
344→ case *ast.WhileLoop:
345→ g.collectNestedProcSigs(s.Body)
346→ case *ast.CaseStatement:
347→ for _, ch := range s.Choices {
348→ g.collectNestedProcSigs(ch.Body)
349→ }
350→ }
351→ }
352→}
353→
354→// collectNestedProcSigsScoped registers nested proc/func signatures into procSigs
355→// for the current scope. It saves old values into oldSigs so they can be restored
356→// after the scope ends (preventing name collisions between same-named nested procs
357→// in different parent procs).
358→func (g *Generator) collectNestedProcSigsScoped(stmts []ast.Statement, oldSigs map[string][]ast.ProcParam) {
359→ for _, stmt := range stmts {
360→ switch s := stmt.(type) {
361→ case *ast.ProcDecl:
362→ if _, saved := oldSigs[s.Name]; !saved {
363→ oldSigs[s.Name] = g.procSigs[s.Name] // nil if not previously set
364→ }
365→ g.procSigs[s.Name] = s.Params
366→ g.collectNestedProcSigsScoped(s.Body, oldSigs)
367→ case *ast.FuncDecl:
368→ if _, saved := oldSigs[s.Name]; !saved {
369→ oldSigs[s.Name] = g.procSigs[s.Name]
370→ }
371→ g.procSigs[s.Name] = s.Params
372→ g.collectNestedProcSigsScoped(s.Body, oldSigs)
373→ case *ast.SeqBlock:
374→ g.collectNestedProcSigsScoped(s.Statements, oldSigs)
375→ case *ast.ParBlock:
376→ g.collectNestedProcSigsScoped(s.Statements, oldSigs)
377→ case *ast.IfStatement:
378→ for _, c := range s.Choices {
379→ g.collectNestedProcSigsScoped(c.Body, oldSigs)
380→ }
381→ case *ast.WhileLoop:
382→ g.collectNestedProcSigsScoped(s.Body, oldSigs)
383→ case *ast.CaseStatement:
384→ for _, ch := range s.Choices {
385→ g.collectNestedProcSigsScoped(ch.Body, oldSigs)
386→ }
387→ }
388→ }
389→}
390→
391→// findEntryProc looks for the last top-level PROC with the standard occam
392→// entry point signature: exactly 3 CHAN OF BYTE params (keyboard?, screen!, error!).
393→func (g *Generator) findEntryProc(procDecls []ast.Statement) *ast.ProcDecl {
394→ var entry *ast.ProcDecl
395→ for _, stmt := range procDecls {
396→ proc, ok := stmt.(*ast.ProcDecl)
397→ if !ok {
398→ continue
399→ }
400→ if len(proc.Params) != 3 {
401→ continue
402→ }
403→ p0, p1, p2 := proc.Params[0], proc.Params[1], proc.Params[2]
404→ if p0.IsChan && p0.ChanElemType == "BYTE" && p0.ChanDir == "?" &&
405→ p1.IsChan && p1.ChanElemType == "BYTE" && p1.ChanDir == "!" &&
406→ p2.IsChan && p2.ChanElemType == "BYTE" && p2.ChanDir == "!" {
407→ entry = proc
408→ }
409→ }
410→ return entry
411→}
412→
413→// generateEntryHarness emits a func main() that wires stdin/stdout/stderr
414→// to channels and calls the entry PROC.
415→func (g *Generator) generateEntryHarness(proc *ast.ProcDecl) {
416→ name := goIdent(proc.Name)
417→ g.writeLine("func main() {")
418→ g.indent++
419→
420→ // Create channels
421→ g.writeLine("keyboard := make(chan byte, 256)")
422→ g.writeLine("screen := make(chan byte, 256)")
423→ g.writeLine("_error := make(chan byte, 256)")
424→ g.writeLine("")
425→
426→ // WaitGroup for writer goroutines to finish draining
427→ g.writeLine("var wg sync.WaitGroup")
428→ g.writeLine("wg.Add(2)")
429→ g.writeLine("")
430→
431→ // Screen writer goroutine
432→ g.writeLine("go func() {")
433→ g.indent++
434→ g.writeLine("defer wg.Done()")
435→ g.writeLine("w := bufio.NewWriter(os.Stdout)")
436→ g.writeLine("for b := range screen {")
437→ g.indent++
438→ g.writeLine("if b == 255 {")
439→ g.indent++
440→ g.writeLine("w.Flush()")
441→ g.indent--
442→ g.writeLine("} else {")
443→ g.indent++
444→ g.writeLine("w.WriteByte(b)")
445→ g.indent--
446→ g.writeLine("}")
447→ g.indent--
448→ g.writeLine("}")
449→ g.writeLine("w.Flush()")
450→ g.indent--
451→ g.writeLine("}()")
452→ g.writeLine("")
453→
454→ // Error writer goroutine
455→ g.writeLine("go func() {")
456→ g.indent++
457→ g.writeLine("defer wg.Done()")
458→ g.writeLine("w := bufio.NewWriter(os.Stderr)")
459→ g.writeLine("for b := range _error {")
460→ g.indent++
461→ g.writeLine("if b == 255 {")
462→ g.indent++
463→ g.writeLine("w.Flush()")
464→ g.indent--
465→ g.writeLine("} else {")
466→ g.indent++
467→ g.writeLine("w.WriteByte(b)")
468→ g.indent--
469→ g.writeLine("}")
470→ g.indent--
471→ g.writeLine("}")
472→ g.writeLine("w.Flush()")
473→ g.indent--
474→ g.writeLine("}()")
475→ g.writeLine("")
476→
477→ // Keyboard reader goroutine
478→ g.writeLine("go func() {")
479→ g.indent++
480→ g.writeLine("r := bufio.NewReader(os.Stdin)")
481→ g.writeLine("for {")
482→ g.indent++
483→ g.writeLine("b, err := r.ReadByte()")
484→ g.writeLine("if err != nil {")
485→ g.indent++
486→ g.writeLine("close(keyboard)")
487→ g.writeLine("return")
488→ g.indent--
489→ g.writeLine("}")
490→ g.writeLine("keyboard <- b")
491→ g.indent--
492→ g.writeLine("}")
493→ g.indent--
494→ g.writeLine("}()")
495→ g.writeLine("")
496→
497→ // Call the entry proc
498→ g.writeLine(fmt.Sprintf("%s(keyboard, screen, _error)", name))
499→ g.writeLine("")
500→{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 500,
"limit": 500
} 500→ p.nextToken() // consume comma
501→ p.nextToken() // move to next target
502→ target := ast.MultiAssignTarget{Name: p.curToken.Literal}
503→ if p.peekTokenIs(lexer.LBRACKET) {
504→ p.nextToken() // move to [
505→ p.nextToken() // move past [
506→ target.Index = p.parseExpression(LOWEST)
507→ if !p.expectPeek(lexer.RBRACKET) {
508→ return nil
509→ }
510→ }
511→ stmt.Targets = append(stmt.Targets, target)
512→ }
513→
514→ p.nextToken() // move to :=
515→ stmt.Token = p.curToken
516→
517→ p.nextToken() // move past :=
518→
519→ // Parse comma-separated values
520→ stmt.Values = []ast.Expression{p.parseExpression(LOWEST)}
521→ for p.peekTokenIs(lexer.COMMA) {
522→ p.nextToken() // consume comma
523→ p.nextToken() // move to next expression
524→ stmt.Values = append(stmt.Values, p.parseExpression(LOWEST))
525→ }
526→
527→ return stmt
528→}
529→
530→func (p *Parser) parseArrayDecl() ast.Statement {
531→ lbracketToken := p.curToken
532→
533→ // Parse size expression after [
534→ p.nextToken()
535→ size := p.parseExpression(LOWEST)
536→
537→ // Check if this is a slice assignment: [arr FROM start FOR length] := value
538→ // Also handles [arr FOR length] shorthand (FROM 0)
539→ if p.peekTokenIs(lexer.FROM) || p.peekTokenIs(lexer.FOR) {
540→ return p.parseSliceAssignment(lbracketToken, size)
541→ }
542→
543→ // Expect ]
544→ if !p.expectPeek(lexer.RBRACKET) {
545→ return nil
546→ }
547→
548→ // Check if this is a channel array: [n]CHAN OF TYPE
549→ if p.peekTokenIs(lexer.CHAN) {
550→ p.nextToken() // move to CHAN
551→ chanDecl := &ast.ChanDecl{
552→ Token: p.curToken,
553→ IsArray: true,
554→ Size: size,
555→ }
556→
557→ // Expect OF (optional — CHAN BYTE is shorthand for CHAN OF BYTE)
558→ if p.peekTokenIs(lexer.OF) {
559→ p.nextToken() // consume OF
560→ }
561→
562→ // Expect type (INT, BYTE, BOOL, etc.) or protocol name (IDENT)
563→ p.nextToken()
564→ if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) ||
565→ p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) ||
566→ p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) {
567→ chanDecl.ElemType = p.curToken.Literal
568→ } else if p.curTokenIs(lexer.IDENT) {
569→ chanDecl.ElemType = p.curToken.Literal
570→ } else {
571→ p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
572→ return nil
573→ }
574→
575→ // Parse channel names
576→ for {
577→ if !p.expectPeek(lexer.IDENT) {
578→ return nil
579→ }
580→ chanDecl.Names = append(chanDecl.Names, p.curToken.Literal)
581→
582→ if p.peekTokenIs(lexer.COMMA) {
583→ p.nextToken() // consume comma
584→ } else {
585→ break
586→ }
587→ }
588→
589→ if !p.expectPeek(lexer.COLON) {
590→ return nil
591→ }
592→
593→ return chanDecl
594→ }
595→
596→ // Regular array declaration
597→ decl := &ast.ArrayDecl{Token: lbracketToken, Size: size}
598→
599→ // Expect type (INT, BYTE, BOOL, REAL, REAL32, REAL64)
600→ p.nextToken()
601→ if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
602→ !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) &&
603→ !p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) {
604→ p.addError(fmt.Sprintf("expected type after array size, got %s", p.curToken.Type))
605→ return nil
606→ }
607→ decl.Type = p.curToken.Literal
608→
609→ // Parse variable names
610→ for {
611→ if !p.expectPeek(lexer.IDENT) {
612→ return nil
613→ }
614→ decl.Names = append(decl.Names, p.curToken.Literal)
615→
616→ if p.peekTokenIs(lexer.COMMA) {
617→ p.nextToken() // consume comma
618→ } else {
619→ break
620→ }
621→ }
622→
623→ if !p.expectPeek(lexer.COLON) {
624→ return nil
625→ }
626→
627→ return decl
628→}
629→
630→// parseSliceAssignment parses [arr FROM start FOR length] := value
631→// Also handles [arr FOR length] shorthand (start defaults to 0).
632→// Called from parseArrayDecl when FROM or FOR is detected after the array expression.
633→// lbracketToken is the [ token, arrayExpr is the already-parsed array expression.
634→func (p *Parser) parseSliceAssignment(lbracketToken lexer.Token, arrayExpr ast.Expression) ast.Statement {
635→ var startExpr ast.Expression
636→ if p.peekTokenIs(lexer.FOR) {
637→ // [arr FOR length] shorthand — start is 0
638→ startExpr = &ast.IntegerLiteral{Token: lexer.Token{Type: lexer.INT, Literal: "0"}, Value: 0}
639→ } else {
640→ p.nextToken() // consume FROM
641→ p.nextToken() // move to start expression
642→ startExpr = p.parseExpression(LOWEST)
643→ }
644→
645→ if !p.expectPeek(lexer.FOR) {
646→ return nil
647→ }
648→ p.nextToken() // move to length expression
649→ lengthExpr := p.parseExpression(LOWEST)
650→
651→ if !p.expectPeek(lexer.RBRACKET) {
652→ return nil
653→ }
654→
655→ if !p.expectPeek(lexer.ASSIGN) {
656→ return nil
657→ }
658→
659→ assignToken := p.curToken
660→ p.nextToken() // move past :=
661→
662→ value := p.parseExpression(LOWEST)
663→
664→ return &ast.Assignment{
665→ Token: assignToken,
666→ SliceTarget: &ast.SliceExpr{
667→ Token: lbracketToken,
668→ Array: arrayExpr,
669→ Start: startExpr,
670→ Length: lengthExpr,
671→ },
672→ Value: value,
673→ }
674→}
675→
676→func (p *Parser) parseIndexedOperation() ast.Statement {
677→ name := p.curToken.Literal
678→
679→ p.nextToken() // move to [
680→ p.nextToken() // move past [
681→ index := p.parseExpression(LOWEST)
682→
683→ // Expect ]
684→ if !p.expectPeek(lexer.RBRACKET) {
685→ return nil
686→ }
687→
688→ // Check what follows ]
689→ if p.peekTokenIs(lexer.COMMA) {
690→ // Multi-assignment starting with indexed target: name[index], ... := ...
691→ firstTarget := ast.MultiAssignTarget{Name: name, Index: index}
692→ return p.parseMultiAssignmentFrom(firstTarget)
693→ }
694→ if p.peekTokenIs(lexer.ASSIGN) {
695→ // Indexed assignment: name[index] := value
696→ p.nextToken() // move to :=
697→ stmt := &ast.Assignment{
698→ Name: name,
699→ Token: p.curToken,
700→ Index: index,
701→ }
702→ p.nextToken() // move past :=
703→ stmt.Value = p.parseExpression(LOWEST)
704→ return stmt
705→ }
706→
707→ if p.peekTokenIs(lexer.SEND) {
708→ // Indexed channel send: cs[i] ! value
709→ p.nextToken() // move to !
710→ sendToken := p.curToken
711→ p.nextToken() // move past !
712→
713→ stmt := &ast.Send{
714→ Token: sendToken,
715→ Channel: name,
716→ ChannelIndex: index,
717→ }
718→
719→ // Check if this is a variant send: first token is an identifier that is a variant tag
720→ if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.SEMICOLON) {
721→ possibleTag := p.curToken.Literal
722→ if p.isVariantTag(possibleTag) {
723→ stmt.VariantTag = possibleTag
724→ p.nextToken() // move to ;
725→ for p.curTokenIs(lexer.SEMICOLON) {
726→ p.nextToken() // move past ;
727→ val := p.parseExpression(LOWEST)
728→ stmt.Values = append(stmt.Values, val)
729→ }
730→ return stmt
731→ }
732→ }
733→
734→ stmt.Value = p.parseExpression(LOWEST)
735→
736→ // Check for sequential send
737→ for p.peekTokenIs(lexer.SEMICOLON) {
738→ p.nextToken() // move to ;
739→ p.nextToken() // move past ;
740→ val := p.parseExpression(LOWEST)
741→ stmt.Values = append(stmt.Values, val)
742→ }
743→
744→ return stmt
745→ }
746→
747→ if p.peekTokenIs(lexer.RECEIVE) {
748→ // Indexed channel receive: cs[i] ? x or cs[i] ? CASE ...
749→ p.nextToken() // move to ?
750→ recvToken := p.curToken
751→
752→ // Check for variant receive: cs[i] ? CASE
753→ if p.peekTokenIs(lexer.CASE) {
754→ p.nextToken() // move to CASE
755→ return p.parseVariantReceiveWithIndex(name, index, recvToken)
756→ }
757→
758→ stmt := &ast.Receive{
759→ Token: recvToken,
760→ Channel: name,
761→ ChannelIndex: index,
762→ }
763→
764→ if !p.expectPeek(lexer.IDENT) {
765→ return nil
766→ }
767→ stmt.Variable = p.curToken.Literal
768→
769→ // Check for sequential receive
770→ for p.peekTokenIs(lexer.SEMICOLON) {
771→ p.nextToken() // move to ;
772→ if !p.expectPeek(lexer.IDENT) {
773→ return nil
774→ }
775→ stmt.Variables = append(stmt.Variables, p.curToken.Literal)
776→ }
777→
778→ return stmt
779→ }
780→
781→ // Default: treat as indexed assignment (shouldn't reach here normally)
782→ p.addError(fmt.Sprintf("expected :=, !, or ? after %s[...], got %s", name, p.peekToken.Type))
783→ return nil
784→}
785→
786→func (p *Parser) parseIndexExpression(left ast.Expression) *ast.IndexExpr {
787→ expr := &ast.IndexExpr{
788→ Token: p.curToken,
789→ Left: left,
790→ }
791→
792→ p.nextToken() // move past [
793→ expr.Index = p.parseExpression(LOWEST)
794→
795→ if !p.expectPeek(lexer.RBRACKET) {
796→ return nil
797→ }
798→
799→ return expr
800→}
801→
802→func (p *Parser) parseChanDecl() *ast.ChanDecl {
803→ decl := &ast.ChanDecl{Token: p.curToken}
804→
805→ // Expect OF (optional — CHAN BYTE is shorthand for CHAN OF BYTE)
806→ if p.peekTokenIs(lexer.OF) {
807→ p.nextToken() // consume OF
808→ }
809→
810→ // Expect type (INT, BYTE, BOOL, etc.) or protocol name (IDENT)
811→ p.nextToken()
812→ if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) ||
813→ p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) ||
814→ p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) {
815→ decl.ElemType = p.curToken.Literal
816→ } else if p.curTokenIs(lexer.IDENT) {
817→ decl.ElemType = p.curToken.Literal
818→ } else {
819→ p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
820→ return nil
821→ }
822→
823→ // Parse channel names
824→ for {
825→ if !p.expectPeek(lexer.IDENT) {
826→ return nil
827→ }
828→ decl.Names = append(decl.Names, p.curToken.Literal)
829→
830→ if p.peekTokenIs(lexer.COMMA) {
831→ p.nextToken() // consume comma
832→ } else {
833→ break
834→ }
835→ }
836→
837→ if !p.expectPeek(lexer.COLON) {
838→ return nil
839→ }
840→
841→ return decl
842→}
843→
844→func (p *Parser) parseProtocolDecl() *ast.ProtocolDecl {
845→ decl := &ast.ProtocolDecl{Token: p.curToken}
846→
847→ // Expect protocol name
848→ if !p.expectPeek(lexer.IDENT) {
849→ return nil
850→ }
851→ decl.Name = p.curToken.Literal
852→
853→ // Check if this is IS form (simple/sequential) or CASE form (variant)
854→ if p.peekTokenIs(lexer.NEWLINE) || p.peekTokenIs(lexer.INDENT) {
855→ // Could be variant: PROTOCOL NAME \n INDENT CASE ...
856→ // Skip newlines
857→ for p.peekTokenIs(lexer.NEWLINE) {
858→ p.nextToken()
859→ }
860→
861→ if p.peekTokenIs(lexer.INDENT) {
862→ savedLevel := p.indentLevel
863→ p.nextToken() // consume INDENT
864→ p.nextToken() // move into block
865→
866→ if p.curTokenIs(lexer.CASE) {
867→ // Variant protocol
868→ decl.Kind = "variant"
869→ decl.Variants = p.parseProtocolVariants()
870→ // Consume remaining DEDENTs back to the level before the INDENT
871→ for p.peekTokenIs(lexer.DEDENT) && p.indentLevel > savedLevel {
872→ p.nextToken()
873→ }
874→ // Optionally consume trailing colon terminator
875→ if p.peekTokenIs(lexer.COLON) {
876→ p.nextToken()
877→ }
878→ p.protocolNames[decl.Name] = true
879→ p.protocolDefs[decl.Name] = decl
880→ return decl
881→ }
882→ }
883→
884→ p.addError("expected IS or CASE in protocol declaration")
885→ return nil
886→ }
887→
888→ // IS form: PROTOCOL NAME IS TYPE [; TYPE]*
889→ if !p.expectPeek(lexer.IS) {
890→ return nil
891→ }
892→
893→ // Parse type list
894→ p.nextToken()
895→ typeName := p.parseProtocolTypeName()
896→ if typeName == "" {
897→ return nil
898→ }
899→ decl.Types = append(decl.Types, typeName)
900→
901→ // Check for sequential: ; TYPE
902→ for p.peekTokenIs(lexer.SEMICOLON) {
903→ p.nextToken() // move to ;
904→ p.nextToken() // move past ;
905→ typeName = p.parseProtocolTypeName()
906→ if typeName == "" {
907→ return nil
908→ }
909→ decl.Types = append(decl.Types, typeName)
910→ }
911→
912→ if len(decl.Types) == 1 {
913→ decl.Kind = "simple"
914→ } else {
915→ decl.Kind = "sequential"
916→ }
917→
918→ // Optionally consume trailing colon terminator
919→ if p.peekTokenIs(lexer.COLON) {
920→ p.nextToken()
921→ }
922→
923→ p.protocolNames[decl.Name] = true
924→ p.protocolDefs[decl.Name] = decl
925→ return decl
926→}
927→
928→func (p *Parser) parseProtocolTypeName() string {
929→ switch p.curToken.Type {
930→ case lexer.INT_TYPE:
931→ return "INT"
932→ case lexer.BYTE_TYPE:
933→ return "BYTE"
934→ case lexer.BOOL_TYPE:
935→ return "BOOL"
936→ case lexer.REAL_TYPE:
937→ return "REAL"
938→ case lexer.REAL32_TYPE:
939→ return "REAL32"
940→ case lexer.REAL64_TYPE:
941→ return "REAL64"
942→ case lexer.IDENT:
943→ return p.curToken.Literal
944→ default:
945→ p.addError(fmt.Sprintf("expected type name in protocol, got %s", p.curToken.Type))
946→ return ""
947→ }
948→}
949→
950→func (p *Parser) parseProtocolVariants() []ast.ProtocolVariant {
951→ var variants []ast.ProtocolVariant
952→
953→ // Skip to next line after CASE
954→ for p.peekTokenIs(lexer.NEWLINE) {
955→ p.nextToken()
956→ }
957→
958→ // Expect INDENT
959→ if !p.peekTokenIs(lexer.INDENT) {
960→ p.addError("expected indented block after CASE in protocol")
961→ return variants
962→ }
963→ p.nextToken() // consume INDENT
964→ startLevel := p.indentLevel
965→ p.nextToken() // move into block
966→
967→ for !p.curTokenIs(lexer.EOF) {
968→ // Skip newlines
969→ for p.curTokenIs(lexer.NEWLINE) {
970→ p.nextToken()
971→ }
972→
973→ // Handle DEDENT tokens
974→ for p.curTokenIs(lexer.DEDENT) {
975→ if p.indentLevel < startLevel {
976→ return variants
977→ }
978→ p.nextToken()
979→ }
980→
981→ // Skip any more newlines after DEDENT
982→ for p.curTokenIs(lexer.NEWLINE) {
983→ p.nextToken()
984→ }
985→
986→ if p.curTokenIs(lexer.EOF) {
987→ break
988→ }
989→
990→ if p.indentLevel < startLevel {
991→ break
992→ }
993→
994→ // Parse variant: tag [; TYPE]*
995→ if !p.curTokenIs(lexer.IDENT) {
996→ p.addError(fmt.Sprintf("expected variant tag name, got %s", p.curToken.Type))
997→ return variants
998→ }
999→{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 500,
"limit": 500
} 500→
501→ // Close output channels and wait for writers to drain
502→ g.writeLine("close(screen)")
503→ g.writeLine("close(_error)")
504→ g.writeLine("wg.Wait()")
505→
506→ g.indent--
507→ g.writeLine("}")
508→}
509→
510→func (g *Generator) containsPar(stmt ast.Statement) bool {
511→ switch s := stmt.(type) {
512→ case *ast.ParBlock:
513→ return true
514→ case *ast.SeqBlock:
515→ for _, inner := range s.Statements {
516→ if g.containsPar(inner) {
517→ return true
518→ }
519→ }
520→ case *ast.AltBlock:
521→ for _, c := range s.Cases {
522→ for _, inner := range c.Body {
523→ if g.containsPar(inner) {
524→ return true
525→ }
526→ }
527→ }
528→ case *ast.ProcDecl:
529→ for _, inner := range s.Body {
530→ if g.containsPar(inner) {
531→ return true
532→ }
533→ }
534→ case *ast.FuncDecl:
535→ for _, inner := range s.Body {
536→ if g.containsPar(inner) {
537→ return true
538→ }
539→ }
540→ case *ast.WhileLoop:
541→ for _, inner := range s.Body {
542→ if g.containsPar(inner) {
543→ return true
544→ }
545→ }
546→ case *ast.IfStatement:
547→ for _, choice := range s.Choices {
548→ if choice.NestedIf != nil {
549→ if g.containsPar(choice.NestedIf) {
550→ return true
551→ }
552→ }
553→ for _, inner := range choice.Body {
554→ if g.containsPar(inner) {
555→ return true
556→ }
557→ }
558→ }
559→ case *ast.CaseStatement:
560→ for _, choice := range s.Choices {
561→ for _, inner := range choice.Body {
562→ if g.containsPar(inner) {
563→ return true
564→ }
565→ }
566→ }
567→ case *ast.VariantReceive:
568→ for _, c := range s.Cases {
569→ if c.Body != nil && g.containsPar(c.Body) {
570→ return true
571→ }
572→ }
573→ }
574→ return false
575→}
576→
577→func (g *Generator) containsPrint(stmt ast.Statement) bool {
578→ switch s := stmt.(type) {
579→ case *ast.ProcCall:
580→ return printBuiltins[s.Name]
581→ case *ast.SeqBlock:
582→ for _, inner := range s.Statements {
583→ if g.containsPrint(inner) {
584→ return true
585→ }
586→ }
587→ case *ast.ParBlock:
588→ for _, inner := range s.Statements {
589→ if g.containsPrint(inner) {
590→ return true
591→ }
592→ }
593→ case *ast.AltBlock:
594→ for _, c := range s.Cases {
595→ for _, inner := range c.Body {
596→ if g.containsPrint(inner) {
597→ return true
598→ }
599→ }
600→ }
601→ case *ast.ProcDecl:
602→ for _, inner := range s.Body {
603→ if g.containsPrint(inner) {
604→ return true
605→ }
606→ }
607→ case *ast.FuncDecl:
608→ for _, inner := range s.Body {
609→ if g.containsPrint(inner) {
610→ return true
611→ }
612→ }
613→ case *ast.WhileLoop:
614→ for _, inner := range s.Body {
615→ if g.containsPrint(inner) {
616→ return true
617→ }
618→ }
619→ case *ast.IfStatement:
620→ for _, choice := range s.Choices {
621→ if choice.NestedIf != nil {
622→ if g.containsPrint(choice.NestedIf) {
623→ return true
624→ }
625→ }
626→ for _, inner := range choice.Body {
627→ if g.containsPrint(inner) {
628→ return true
629→ }
630→ }
631→ }
632→ case *ast.CaseStatement:
633→ for _, choice := range s.Choices {
634→ for _, inner := range choice.Body {
635→ if g.containsPrint(inner) {
636→ return true
637→ }
638→ }
639→ }
640→ case *ast.VariantReceive:
641→ for _, c := range s.Cases {
642→ if c.Body != nil && g.containsPrint(c.Body) {
643→ return true
644→ }
645→ }
646→ }
647→ return false
648→}
649→
650→func (g *Generator) containsTimer(stmt ast.Statement) bool {
651→ switch s := stmt.(type) {
652→ case *ast.TimerDecl, *ast.TimerRead:
653→ return true
654→ case *ast.AltBlock:
655→ for _, c := range s.Cases {
656→ if c.IsTimer {
657→ return true
658→ }
659→ for _, inner := range c.Body {
660→ if g.containsTimer(inner) {
661→ return true
662→ }
663→ }
664→ }
665→ case *ast.SeqBlock:
666→ for _, inner := range s.Statements {
667→ if g.containsTimer(inner) {
668→ return true
669→ }
670→ }
671→ case *ast.ParBlock:
672→ for _, inner := range s.Statements {
673→ if g.containsTimer(inner) {
674→ return true
675→ }
676→ }
677→ case *ast.ProcDecl:
678→ for _, inner := range s.Body {
679→ if g.containsTimer(inner) {
680→ return true
681→ }
682→ }
683→ case *ast.FuncDecl:
684→ for _, inner := range s.Body {
685→ if g.containsTimer(inner) {
686→ return true
687→ }
688→ }
689→ case *ast.WhileLoop:
690→ for _, inner := range s.Body {
691→ if g.containsTimer(inner) {
692→ return true
693→ }
694→ }
695→ case *ast.IfStatement:
696→ for _, choice := range s.Choices {
697→ if choice.NestedIf != nil {
698→ if g.containsTimer(choice.NestedIf) {
699→ return true
700→ }
701→ }
702→ for _, inner := range choice.Body {
703→ if g.containsTimer(inner) {
704→ return true
705→ }
706→ }
707→ }
708→ case *ast.CaseStatement:
709→ for _, choice := range s.Choices {
710→ for _, inner := range choice.Body {
711→ if g.containsTimer(inner) {
712→ return true
713→ }
714→ }
715→ }
716→ case *ast.VariantReceive:
717→ for _, c := range s.Cases {
718→ if c.Body != nil && g.containsTimer(c.Body) {
719→ return true
720→ }
721→ }
722→ }
723→ return false
724→}
725→
726→func (g *Generator) containsStop(stmt ast.Statement) bool {
727→ switch s := stmt.(type) {
728→ case *ast.Stop:
729→ return true
730→ case *ast.SeqBlock:
731→ for _, inner := range s.Statements {
732→ if g.containsStop(inner) {
733→ return true
734→ }
735→ }
736→ case *ast.ParBlock:
737→ for _, inner := range s.Statements {
738→ if g.containsStop(inner) {
739→ return true
740→ }
741→ }
742→ case *ast.AltBlock:
743→ for _, c := range s.Cases {
744→ for _, inner := range c.Body {
745→ if g.containsStop(inner) {
746→ return true
747→ }
748→ }
749→ }
750→ case *ast.ProcDecl:
751→ for _, inner := range s.Body {
752→ if g.containsStop(inner) {
753→ return true
754→ }
755→ }
756→ case *ast.FuncDecl:
757→ for _, inner := range s.Body {
758→ if g.containsStop(inner) {
759→ return true
760→ }
761→ }
762→ case *ast.WhileLoop:
763→ for _, inner := range s.Body {
764→ if g.containsStop(inner) {
765→ return true
766→ }
767→ }
768→ case *ast.IfStatement:
769→ for _, choice := range s.Choices {
770→ if choice.NestedIf != nil {
771→ if g.containsStop(choice.NestedIf) {
772→ return true
773→ }
774→ }
775→ for _, inner := range choice.Body {
776→ if g.containsStop(inner) {
777→ return true
778→ }
779→ }
780→ }
781→ case *ast.CaseStatement:
782→ for _, choice := range s.Choices {
783→ for _, inner := range choice.Body {
784→ if g.containsStop(inner) {
785→ return true
786→ }
787→ }
788→ }
789→ case *ast.VariantReceive:
790→ for _, c := range s.Cases {
791→ if c.Body != nil && g.containsStop(c.Body) {
792→ return true
793→ }
794→ }
795→ }
796→ return false
797→}
798→
799→func (g *Generator) containsMostExpr(stmt ast.Statement) bool {
800→ switch s := stmt.(type) {
801→ case *ast.Assignment:
802→ return g.exprNeedsMath(s.Value) || g.exprNeedsMath(s.Index)
803→ case *ast.MultiAssignment:
804→ for _, t := range s.Targets {
805→ if g.exprNeedsMath(t.Index) {
806→ return true
807→ }
808→ }
809→ for _, v := range s.Values {
810→ if g.exprNeedsMath(v) {
811→ return true
812→ }
813→ }
814→ case *ast.Abbreviation:
815→ return g.exprNeedsMath(s.Value)
816→ case *ast.SeqBlock:
817→ for _, inner := range s.Statements {
818→ if g.containsMostExpr(inner) {
819→ return true
820→ }
821→ }
822→ case *ast.ParBlock:
823→ for _, inner := range s.Statements {
824→ if g.containsMostExpr(inner) {
825→ return true
826→ }
827→ }
828→ case *ast.ProcDecl:
829→ for _, inner := range s.Body {
830→ if g.containsMostExpr(inner) {
831→ return true
832→ }
833→ }
834→ case *ast.FuncDecl:
835→ for _, inner := range s.Body {
836→ if g.containsMostExpr(inner) {
837→ return true
838→ }
839→ }
840→ case *ast.WhileLoop:
841→ if g.exprNeedsMath(s.Condition) {
842→ return true
843→ }
844→ for _, inner := range s.Body {
845→ if g.containsMostExpr(inner) {
846→ return true
847→ }
848→ }
849→ case *ast.IfStatement:
850→ for _, choice := range s.Choices {
851→ if choice.NestedIf != nil {
852→ if g.containsMostExpr(choice.NestedIf) {
853→ return true
854→ }
855→ }
856→ if g.exprNeedsMath(choice.Condition) {
857→ return true
858→ }
859→ for _, inner := range choice.Body {
860→ if g.containsMostExpr(inner) {
861→ return true
862→ }
863→ }
864→ }
865→ case *ast.CaseStatement:
866→ if g.exprNeedsMath(s.Selector) {
867→ return true
868→ }
869→ for _, choice := range s.Choices {
870→ for _, v := range choice.Values {
871→ if g.exprNeedsMath(v) {
872→ return true
873→ }
874→ }
875→ for _, inner := range choice.Body {
876→ if g.containsMostExpr(inner) {
877→ return true
878→ }
879→ }
880→ }
881→ case *ast.Send:
882→ if g.exprNeedsMath(s.Value) {
883→ return true
884→ }
885→ for _, v := range s.Values {
886→ if g.exprNeedsMath(v) {
887→ return true
888→ }
889→ }
890→ case *ast.ProcCall:
891→ for _, arg := range s.Args {
892→ if g.exprNeedsMath(arg) {
893→ return true
894→ }
895→ }
896→ case *ast.AltBlock:
897→ for _, c := range s.Cases {
898→ for _, inner := range c.Body {
899→ if g.containsMostExpr(inner) {
900→ return true
901→ }
902→ }
903→ }
904→ case *ast.VariantReceive:
905→ for _, c := range s.Cases {
906→ if c.Body != nil && g.containsMostExpr(c.Body) {
907→ return true
908→ }
909→ }
910→ }
911→ return false
912→}
913→
914→func (g *Generator) exprNeedsMath(expr ast.Expression) bool {
915→ if expr == nil {
916→ return false
917→ }
918→ switch e := expr.(type) {
919→ case *ast.MostExpr:
920→ // BYTE uses literal 0/255, doesn't need math
921→ return e.ExprType != "BYTE"
922→ case *ast.BinaryExpr:
923→ return g.exprNeedsMath(e.Left) || g.exprNeedsMath(e.Right)
924→ case *ast.UnaryExpr:
925→ return g.exprNeedsMath(e.Right)
926→ case *ast.ParenExpr:
927→ return g.exprNeedsMath(e.Expr)
928→ case *ast.TypeConversion:
929→ if e.Qualifier == "ROUND" && isOccamIntType(e.TargetType) {
930→ return true
931→ }
932→ return g.exprNeedsMath(e.Expr)
933→ case *ast.SizeExpr:
934→ return g.exprNeedsMath(e.Expr)
935→ case *ast.IndexExpr:
936→ return g.exprNeedsMath(e.Left) || g.exprNeedsMath(e.Index)
937→ case *ast.FuncCall:
938→ for _, arg := range e.Args {
939→ if g.exprNeedsMath(arg) {
940→ return true
941→ }
942→ }
943→ case *ast.SliceExpr:
944→ return g.exprNeedsMath(e.Array) || g.exprNeedsMath(e.Start) || g.exprNeedsMath(e.Length)
945→ case *ast.ArrayLiteral:
946→ for _, elem := range e.Elements {
947→ if g.exprNeedsMath(elem) {
948→ return true
949→ }
950→ }
951→ }
952→ return false
953→}
954→
955→func (g *Generator) generateMostExpr(e *ast.MostExpr) {
956→ switch e.ExprType {
957→ case "INT":
958→ if e.IsNeg {
959→ g.write("math.MinInt")
960→ } else {
961→ g.write("math.MaxInt")
962→ }
963→ case "INT16":
964→ if e.IsNeg {
965→ g.write("math.MinInt16")
966→ } else {
967→ g.write("math.MaxInt16")
968→ }
969→ case "INT32":
970→ if e.IsNeg {
971→ g.write("math.MinInt32")
972→ } else {
973→ g.write("math.MaxInt32")
974→ }
975→ case "INT64":
976→ if e.IsNeg {
977→ g.write("math.MinInt64")
978→ } else {
979→ g.write("math.MaxInt64")
980→ }
981→ case "BYTE":
982→ if e.IsNeg {
983→ g.write("0")
984→ } else {
985→ g.write("255")
986→ }
987→ case "REAL32":
988→ if e.IsNeg {
989→ g.write("-math.MaxFloat32")
990→ } else {
991→ g.write("math.MaxFloat32")
992→ }
993→ case "REAL64":
994→ if e.IsNeg {
995→ g.write("-math.MaxFloat64")
996→ } else {
997→ g.write("math.MaxFloat64")
998→ }
999→ }This is a large codebase. Given the scope of changes, let me use a more efficient approach - I'll use agents to read the remaining portions of the files in parallel while I start making AST changes.
{
"prompt": "Read the file /home/david/projects/code-associates/occam2go/parser/parser.go from line 1000 to end. Report back the full content. This file contains the parser for the occam2go transpiler. I need to see all the parsing functions, especially parseAltCase, parseAltBlock, parseProcParams, parseSend, parseReceive, parseVariantReceiveWithIndex, and any other functions that use ChannelIndex, Index, IsChanArray, IsOpenArray fields.",
"subagent_type": "Explore"
}{
"prompt": "Read the file /home/david/projects/code-associates/occam2go/codegen/codegen.go from line 1000 to end. Report back the key functions and their line numbers. I need to see all codegen functions, especially:\n- generateChanDecl\n- generateSend\n- generateReceive\n- generateVariantReceive\n- generateAssignment\n- generateMultiAssignment\n- generateProcParams / generateProcDecl / generateProcCall\n- generateArrayDecl\n- generateFuncDecl / generateFuncCallExpr\n- collectChanProtocols\n- walkStatements\n- ALT codegen functions\n- Any function that references .Index, .ChannelIndex, .IsArray, .Size, .IsChanArray, .IsOpenArray\n\nReport the line numbers and key code patterns for each.",
"subagent_type": "Explore"
}{
"prompt": "Read the test files for the occam2go project to understand current test patterns. Specifically look at:\n1. /home/david/projects/code-associates/occam2go/parser/parser_test.go - search for any references to .Size, .Index, .ChannelIndex, .IsArray, .IsChanArray, .IsOpenArray\n2. /home/david/projects/code-associates/occam2go/codegen/codegen_test.go - search for same patterns\n3. /home/david/projects/code-associates/occam2go/codegen/e2e_test.go - get a sample of the e2e test pattern\n\nReport back:\n- All lines in parser_test.go that reference these old field names, with line numbers and surrounding context\n- All lines in codegen_test.go that reference these old field names, with line numbers and surrounding context\n- A sample e2e test showing the test pattern used",
"subagent_type": "Explore"
}[
{
"type": "text",
"text": "Perfect! I have successfully read the parser.go file from line 1000 to the end. Here's a comprehensive report of all the parsing functions you requested:\n\n## Key Parsing Functions (Lines 1000 - End)\n\n### Channel and Variant Handling\n\n**parseSend() [Lines 1189-1234]**\n- Handles `channel ! value` statements\n- Supports variant sends: detects if value is a variant tag followed by semicolons\n- Uses `isVariantTag()` to identify variant tags\n- Supports sequential sends with multiple values after semicolons\n\n**parseReceive() [Lines 1249-1281]**\n- Handles `channel ? variable` statements \n- Checks for variant receive using `parseVariantReceive()` when `CASE` keyword follows\n- Supports sequential receives with multiple variables separated by semicolons\n\n**parseVariantReceive() [Lines 1283-1382]**\n- Parses `c ? CASE` variant receive blocks\n- Parses variant cases with tags and optional variables\n- Each case has optional body with `parseStatement()`\n- Includes no-progress guard to prevent infinite loops\n\n**parseVariantReceiveWithIndex() [Lines 1384-1476]**\n- Similar to `parseVariantReceive()` but for indexed channels\n- Takes `channelIndex` parameter (ast.Expression)\n- Sets `stmt.ChannelIndex` field in the returned `VariantReceive`\n\n### ALT Parsing\n\n**parseAltBlock() [Lines 1583-1612]**\n- Parses ALT statement blocks\n- Checks for replicators: `ALT i = start FOR count`\n- Calls `parseAltCases()` to parse the case list\n\n**parseAltCases() [Lines 1614-1664]**\n- Parses multiple ALT cases at the same indentation level\n- Calls `parseAltCase()` for each case\n- Handles INDENT/DEDENT/NEWLINE tokens properly\n- Includes no-progress guard\n\n**parseAltCase() [Lines 1679-1809]**\n- Parses individual ALT cases with multiple patterns:\n 1. **Scoped declarations**: VAL/INITIAL at case start (lines 1683-1696)\n 2. **Timer case**: `tim ? AFTER deadline` (lines 1715-1723)\n 3. **Simple channel**: `channel ? var` (lines 1725-1732)\n 4. **Indexed channel**: `cs[i] ? var` (lines 1733-1749)\n - Sets `altCase.ChannelIndex` from parsed bracket expression\n 5. **Guarded cases**: `guard & channel ? var` or `guard & SKIP` (lines 1750-1791)\n - Supports guarded indexed channels: `guard & cs[i] ? var` (lines 1773-1781)\n 6. Each case has optional body via `parseBodyStatements()`\n\n### Procedure Parameter Parsing\n\n**parseProcParams() [Lines 2005-2176]**\nThis is the key function for channel and array handling:\n\n**Shared-type parameters** [Lines 2029-2055]:\n- After comma, if IDENT is not a type keyword, record name, CHAN, VAL, RESULT, or `[`, reuses previous param's type info\n- Copies: `IsVal`, `Type`, `IsChan`, **`IsChanArray`**, **`IsOpenArray`**, `ChanElemType`, `ArraySize`\n\n**Array type handling** [Lines 2069-2123]:\n- **`[]CHAN OF <type>` or `[]CHAN <type>`** [Lines 2071-2089]:\n - Sets `param.IsChanArray = true`\n - Sets `param.IsChan = true`\n - Stores element type in `param.ChanElemType`\n \n- **`[]TYPE` (open array)** [Lines 2090-2101]:\n - Sets `param.IsOpenArray = true`\n - Stores type in `param.Type`\n \n- **`[n]TYPE` (fixed-size array)** [Lines 2102-2123]:\n - Stores size in `param.ArraySize`\n - Stores type in `param.Type`\n\n**Channel parameters** [Lines 2124-2137]:\n- `CHAN OF <type>` or `CHAN <type>`\n- Sets `param.IsChan = true`\n- Stores element type in `param.ChanElemType`\n\n**Channel direction** [Lines 2040-2044, 2159-2163]:\n- After IDENT, checks for `?` (receive/input) or `!` (send/output)\n- Stores direction in `param.ChanDir`\n\n### Supporting Functions\n\n**isTypeToken() [Lines 1998-2003]**\n- Returns true for type keywords: INT, BYTE, BOOL, REAL, REAL32, REAL64, INT16, INT32, INT64\n\n**isVariantTag() [Lines 1236-1247]**\n- Checks if a name is a known protocol variant tag\n- Iterates through `p.protocolDefs` to find matching tags\n\n### Key AST Field References\n\nFrom the code analysis, these fields are used:\n\n- **ast.Send**: `VariantTag`, `Value`, `Values` (for sequential sends)\n- **ast.Receive**: `Channel`, `Variable`, `Variables` (for sequential)\n- **ast.VariantReceive**: `Channel`, `ChannelIndex`, `Cases`\n- **ast.AltCase**: `ChannelIndex`, `Channel`, `Variable`, `Guard`, `IsTimer`, `Timer`, `Deadline`, `Body`, `Declarations`\n- **ast.ProcParam**: `IsChanArray`, `IsOpenArray`, `ChannelIndex`, `ArraySize`, `ChanElemType`, `ChanDir`\n\nAll file paths are absolute: `/home/david/projects/code-associates/occam2go/parser/parser.go`"
},
{
"type": "text",
"text": "agentId: a58cc64a98e003cdf (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 44086\ntool_uses: 1\nduration_ms: 16242</usage>"
}
][
{
"type": "text",
"text": "Perfect! Now let me compile all the findings into a comprehensive report:\n\n## Summary Report: Test File References to Old Field Names\n\n### 1. parser/parser_test.go - All References Found\n\n**Lines referencing `.Size`:**\n- **Line 606**: `sizeLit, ok := decl.Size.(*ast.IntegerLiteral)` (Array declaration size)\n- **Line 2012**: `sizeLit, ok := decl.Size.(*ast.IntegerLiteral)` (Channel array size)\n\nContext (line 603-610):\n```go\nif decl.Type != \"INT\" {\n t.Errorf(\"expected type INT, got %s\", decl.Type)\n}\n\nsizeLit, ok := decl.Size.(*ast.IntegerLiteral)\nif !ok {\n t.Fatalf(\"expected IntegerLiteral for size, got %T\", decl.Size)\n}\nif sizeLit.Value != 5 {\n t.Errorf(\"expected size 5, got %d\", sizeLit.Value)\n```\n\n**Lines referencing `.Index` on Assignment:**\n- **Line 672**: `if assign.Index == nil` (Indexed assignment check)\n- **Line 676**: `indexLit, ok := assign.Index.(*ast.IntegerLiteral)` (Cast index)\n- **Line 962**: `if ma.Targets[0].Index != nil || ma.Targets[1].Index != nil` (Multi-assign targets)\n- **Line 1003**: `if ma.Targets[0].Index == nil || ma.Targets[1].Index == nil` (Indexed multi-assign)\n- **Line 1032-1036**: Multiple checks on indexed vs simple targets in mixed multi-assign\n\nContext (line 669-681):\n```go\nif assign.Name != \"arr\" {\n t.Errorf(\"expected name 'arr', got %s\", assign.Name)\n}\n\nif assign.Index == nil {\n t.Fatal(\"expected index expression, got nil\")\n}\n\nindexLit, ok := assign.Index.(*ast.IntegerLiteral)\nif !ok {\n t.Fatalf(\"expected IntegerLiteral for index, got %T\", assign.Index)\n}\nif indexLit.Value != 2 {\n t.Errorf(\"expected index 2, got %d\", indexLit.Value)\n```\n\n**Lines referencing `.ChannelIndex`:**\n- **Line 2050**: `if send.ChannelIndex == nil` (Send on channel array)\n- **Line 2054**: `idxLit, ok := send.ChannelIndex.(*ast.IntegerLiteral)` (Extract index)\n- **Line 2092**: `if recv.ChannelIndex == nil` (Receive on channel array)\n- **Line 2096**: `idxIdent, ok := recv.ChannelIndex.(*ast.Identifier)` (Receive index ident)\n\nContext (line 2047-2059):\n```go\nif send.Channel != \"cs\" {\n t.Errorf(\"expected channel 'cs', got %s\", send.Channel)\n}\n\nif send.ChannelIndex == nil {\n t.Fatal(\"expected ChannelIndex, got nil\")\n}\n\nidxLit, ok := send.ChannelIndex.(*ast.IntegerLiteral)\nif !ok {\n t.Fatalf(\"expected IntegerLiteral for index, got %T\", send.ChannelIndex)\n}\nif idxLit.Value != 0 {\n t.Errorf(\"expected index 0, got %d\", idxLit.Value)\n```\n\n**Lines referencing `.IsArray`:**\n- **Line 2008**: `if !decl.IsArray` (Channel array flag check)\n- **Line 2675**: `if !decl.IsArray` (Another channel array flag)\n- **Line 3191**: `if rt.IsArray` (Retypes IsArray check)\n- **Line 3225**: `if !rt.IsArray` (Retypes array check)\n\nContext (line 2005-2017):\n```go\nif decl, ok := program.Statements[0].(*ast.ChanDecl); !ok {\n t.Fatalf(\"expected ChanDecl, got %T\", program.Statements[0])\n}\n\nif !decl.IsArray {\n t.Error(\"expected IsArray=true\")\n}\n\nsizeLit, ok := decl.Size.(*ast.IntegerLiteral)\nif !ok {\n t.Fatalf(\"expected IntegerLiteral for size, got %T\", decl.Size)\n}\nif sizeLit.Value != 5 {\n t.Errorf(\"expected size 5, got %d\", sizeLit.Value)\n```\n\n**Lines referencing `.IsChanArray`:**\n- **Line 2135**: `if !p0.IsChanArray` (Param is channel array)\n- **Line 2146**: `if p1.IsChan || p1.IsChanArray` (Param is NOT channel array)\n- **Line 2219**: `if !p0.IsChanArray` (Param is channel array)\n- **Line 2724**: `if !p1.IsChan || !p1.IsChanArray` (Both conditions)\n\nContext (line 2132-2150):\n```go\nif !p0.IsChan {\n t.Error(\"param 0: expected IsChan=true\")\n}\nif !p0.IsChanArray {\n t.Error(\"param 0: expected IsChanArray=true\")\n}\nif p0.ChanElemType != \"INT\" {\n // ...\n}\n\np1 := proc.Params[1]\nif p1.IsChan || p1.IsChanArray {\n t.Error(\"param 1: expected IsChan=false, IsChanArray=false\")\n}\n```\n\n**Lines referencing `.IsOpenArray`:**\n- **Line 2610**: `if !p0.IsOpenArray` (Param is open array)\n- **Line 2621**: `if p1.IsOpenArray` (Param is NOT open array)\n\nContext (line 2607-2624):\n```go\nif !p0.IsVal {\n t.Error(\"param 0: expected IsVal=true\")\n}\nif !p0.IsOpenArray {\n t.Error(\"param 0: expected IsOpenArray=true\")\n}\nif p0.Type != \"INT\" {\n // ...\n}\n\np1 := proc.Params[1]\nif p1.IsOpenArray {\n t.Error(\"param 1: expected IsOpenArray=false\")\n}\n```\n\n**Lines referencing `.Index` on IndexExpr:**\n- **Line 733**: `idxLit, ok := indexExpr.Index.(*ast.IntegerLiteral)` (Extract index from expression)\n- **Line 1982**: `idx, ok := indexExpr.Index.(*ast.Identifier)` (Index as identifier)\n\nContext (line 730-738):\n```go\nident, ok := binExpr.Left.(*ast.Identifier)\nif !ok {\n t.Fatalf(\"expected Identifier, got %T\", binExpr.Left)\n}\nif ident.Value != \"arr\" {\n t.Errorf(\"expected 'arr', got %s\", ident.Value)\n}\n\nidxLit, ok := indexExpr.Index.(*ast.IntegerLiteral)\n```\n\n**Lines referencing `.Channel` and `.ChannelIndex` together:**\n- **Line 3359**: `if c.ChannelIndex == nil` (Channel with index)\n\nContext (line 3356-3362):\n```go\nif c.Channel != \"in\" {\n t.Errorf(\"expected channel 'in', got %q\", c.Channel)\n}\nif c.ChannelIndex == nil {\n t.Fatal(\"expected channel index, got nil\")\n}\n```\n\n---\n\n### 2. codegen/codegen_test.go - No References Found\n\nThe search returned **no matches** in codegen_test.go, meaning this file does not reference any of the old field names (.Size, .Index, .ChannelIndex, .IsArray, .IsChanArray, .IsOpenArray).\n\n---\n\n### 3. Sample E2E Test Pattern from e2e_basic_test.go\n\nHere's a representative e2e test showing the pattern used:\n\n```go\nfunc TestE2E_PrintInt(t *testing.T) {\n\toccam := `SEQ\n INT x:\n x := 42\n print.int(x)\n`\n\toutput := transpileCompileRun(t, occam)\n\texpected := \"42\\n\"\n\tif output != expected {\n\t\tt.Errorf(\"expected %q, got %q\", expected, output)\n\t}\n}\n\nfunc TestE2E_Addition(t *testing.T) {\n\toccam := `SEQ\n INT x, y:\n x := 10\n y := 20\n print.int(x + y)\n`\n\toutput := transpileCompileRun(t, occam)\n\texpected := \"30\\n\"\n\tif output != expected {\n\t\tt.Errorf(\"expected %q, got %q\", expected, output)\n\t}\n}\n\nfunc TestE2E_Expression(t *testing.T) {\n\toccam := `SEQ\n result := (2 + 3) * 4\n print.int(result)\n`\n\toutput := transpileCompileRun(t, occam)\n\texpected := \"20\\n\"\n\tif output != expected {\n\t\tt.Errorf(\"expected %q, got %q\", expected, output)\n\t}\n}\n```\n\n**Pattern Summary:**\n- Test name: `TestE2E_<FeatureName>`\n- Input: Occam source code in backtick string\n- Execution: Call `transpileCompileRun(t, occam)` helper\n- Assertion: Compare output to expected string\n- Error: Use `t.Errorf()` with format \"expected %q, got %q\"\n\nThere are 18 separate e2e test files covering different features (basic, control flow, arrays, concurrency, protocols, parameters, types, etc.), all following this same pattern."
},
{
"type": "text",
"text": "agentId: a3122daf0858eb85a (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 25353\ntool_uses: 6\nduration_ms: 22002</usage>"
}
][
{
"type": "text",
"text": "Perfect! Now I have the complete picture. Let me compile the comprehensive report.\n\n## Key Functions and Line Numbers in codegen.go\n\nHere's a complete mapping of all the key functions you requested, organized by category:\n\n### Main Entry Points\n- **Generate()** - Line 94: Main code generation orchestrator. Initializes state, collects metadata (proc signatures, protocols, records), generates type declarations, procedures, and main function.\n- **generateStatement()** - Line 1016: Dispatcher switch statement that routes each AST statement type to its specific generator (VarDecl, ArrayDecl, ChanDecl, Assignment, Send, Receive, etc.)\n\n### Channel & Protocol Functions\n- **generateChanDecl()** - Line 1117: Generates channel declarations. Handles both single channels (`make(chan T)`) and channel arrays (`make([]chan T, size)` + initialization loop for each element).\n - Key pattern: Uses `decl.IsArray` to distinguish between single channels and arrays\n - Generates loop: `for _i := range name { name[_i] = make(chan elemType) }`\n\n- **generateSend()** - Line 1157: Generates channel sends with protocol handling\n - Uses `send.ChannelIndex` for array indexing: `c[index] <- value`\n - Uses `send.VariantTag` and `send.Values` for variant sends: `c <- _proto_NAME_tag{values}`\n - Uses `proto.Kind` to determine format: simple, sequential, or variant protocols\n - Key pattern: Checks `g.chanProtocols[send.Channel]` to get protocol name\n\n- **generateReceive()** - Line 1204: Generates channel receives with sequential protocol support\n - Uses `recv.ChannelIndex` for array indexing\n - Handles sequential receives: generates temporary variable and extracts fields: `_tmpN := <-c; x = _tmpN._0; y = _tmpN._1`\n - Key fields: `recv.Variable`, `recv.Variables` (for sequential), `recv.ChannelIndex`\n\n- **generateVariantReceive()** - Line 1293: Generates variant receive with type switch\n - Uses `select <-c).(type)` pattern with cases for each variant tag\n - Extracts fields from variant struct: `_v._%d` for each field\n - Key pattern: Iterates `vr.Cases` with `vr.Cases[i].Tag` and `vr.Cases[i].Variables`\n\n- **collectChanProtocols()** - Line 1337: Recursive tree walk that maps channel names to their protocol types\n - Populates `g.chanProtocols` map: `chanProtocols[channelName] = protocolName`\n - Registers both declared channels (ChanDecl) and parameter channels (from proc params with `p.IsChan` or `p.IsChanArray`)\n\n### Declaration Functions\n- **generateArrayDecl()** - Line 1146: Generates array declarations\n - Uses `decl.Size` expression: `name := make([]type, size)`\n - Key pattern: Always generates slices, not fixed-size arrays\n\n- **generateVarDecl()** - Line 1072: Generates variable declarations\n - Tracks BOOL variables in `g.boolVars[name] = true` for type conversion codegen\n - Emits suppression line: `_ = name` to prevent \"declared and not used\" errors\n\n### Assignment Functions\n- **generateAssignment()** - Line 1582: Generates all assignment types\n - Uses `assign.SliceTarget` for slice assignment: `copy(arr[start : start+length], value)`\n - Uses `assign.Index` for array indexing: `arr[index] = value`\n - Checks `g.recordVars[assign.Name]` to detect record field access: `p.fieldname = value`\n - Dereferences reference parameters: `*refParam = value`\n\n- **generateMultiAssignment()** - Line 2210: Generates multiple parallel assignments\n - Iterates `stmt.Targets` (assignment targets)\n - Handles indexed targets (`target.Index`), record fields, and simple variables\n - Key pattern: Checks `g.refParams[target.Name]` for dereferencing\n\n### Procedure & Function Functions\n- **generateProcDecl()** - Line 1929: Generates procedure declarations\n - Scopes `refParams` and `boolVars` per procedure\n - Handles RETYPES parameter shadowing via `g.retypesRenames` (renames parameter like `X → _rp_X`)\n - Calls `collectNestedProcSigsScoped()` to register nested proc signatures\n - Restores previous context after procedure body\n - Key pattern: Saves/restores state: `oldRefParams`, `oldBoolVars`, `oldRenames`, `oldSigs`\n\n- **generateProcParams()** - Line 2036: Generates function parameter list\n - Uses `p.IsChanArray` for `[]chan T` parameters\n - Uses `p.IsChan` with `chanDirPrefix()` for `<-chan T` or `chan<- T`\n - Uses `p.IsOpenArray` for `[]T` parameters\n - Uses `p.ArraySize != \"\"` for fixed-size arrays (maps to `[]T` slices)\n - Non-VAL parameters become `*T` for pass-by-reference\n - Key pattern: Applies `g.retypesRenames` to parameter names when shadowed\n\n- **generateProcCall()** - Line 2077: Generates procedure calls\n - Handles built-in prints (print.int, print.string, etc.)\n - Handles CAUSEERROR → `panic(\"CAUSEERROR\")`\n - Uses `g.procSigs[call.Name]` to look up parameter info\n - Adds `&` for non-VAL reference parameters\n - Wraps string literals with `[]byte()` for `[]BYTE` parameters\n - Key pattern: Checks `params[i].IsVal && params[i].IsChan && params[i].IsOpenArray && params[i].ArraySize`\n\n- **generateFuncDecl()** - Line 2119: Generates function declarations\n - Scopes `boolVars` per function\n - Handles single and multiple return types\n - Emits `return` statement from `fn.ResultExprs`\n - Similar to generateProcDecl but with return type handling\n\n- **generateFuncCallExpr()** - Line 2186: Generates function call expressions\n - Checks `transpIntrinsics[call.Name]` and prefixes with `_`\n - Wraps string literals with `[]byte()` for `[]BYTE` parameters\n - Uses `g.procSigs[call.Name]` parameter lookup\n\n### ALT & Control Flow Functions\n- **generateAltBlock()** - Line 1746: Generates simple (non-replicated) ALT statements\n - Uses `select` statement in Go\n - Handles guards via conditional channel assignment to nil: `_altN := nil; if guard { _altN = chan }`\n - Handles timers: `case <-time.After(duration)`\n - Handles skip: `default:`\n - Handles channel indexing: `case var = <-chan[index]:`\n - Key fields: `c.Guard`, `c.IsSkip`, `c.IsTimer`, `c.ChannelIndex`, `c.Variable`\n\n- **generateReplicatedAlt()** - Line 1809: Generates replicated ALT using reflect.Select\n - Uses `reflect.SelectCase` with `reflect.SelectRecv`\n - Builds case array: `_altCases := make([]reflect.SelectCase, count)`\n - Handles channel indexing in the case setup\n - Generates scoped declarations inside the setup loop\n - Recomputes replicator variable after selection\n - Key pattern: Complex multi-step generation with scoped abbreviations\n\n### Helper Walk & Collection Functions\n- **walkStatements()** - Line 2868: Recursive statement tree walker that applies a function to all expressions\n - Returns true if function returns true for any expression\n - Handles all statement types (Assignment, MultiAssignment, Abbreviation, SeqBlock, etc.)\n - Used by `containsIntrinsics()`, `containsBoolConversion()`, etc.\n\n- **collectChanProtocols()** - Line 1337: Maps channel names to protocol types\n - Recursively walks statement tree\n - Registers from ChanDecl: `g.chanProtocols[channelName] = protocol`\n - Registers from proc params: checks `p.IsChan || p.IsChanArray`\n\n- **collectNestedProcSigs()** - Line 327: Collects procedure/function signatures from nested declarations\n - Pre-pass that builds `g.procSigs` map for all PROCs/FUNCTIONs\n - Recursively descends into nested procs\n\n- **collectNestedProcSigsScoped()** - Line 358: Registers nested signatures and saves old values for restoration\n - Saves old values in `oldSigs` map for restoration after scope\n - Prevents name collisions between same-named nested procs in different parent scopes\n\n### Important State Fields & Patterns\n\n**Key State Maps:**\n- `g.chanProtocols[channelName]` → protocol name\n- `g.protocolDefs[protocolName]` → ProtocolDecl with `.Kind` (\"simple\", \"sequential\", \"variant\")\n- `g.recordVars[varName]` → record type name (used to detect field access)\n- `g.boolVars[varName]` → true (tracks BOOL variables)\n- `g.refParams[paramName]` → true (non-VAL parameters for dereferencing)\n- `g.procSigs[procName]` → []ProcParam\n- `g.retypesRenames[paramName]` → renamed parameter (for RETYPES shadowing)\n\n**Key AST Fields Referenced:**\n- `Assignment.Index` → array index expression\n- `Assignment.SliceTarget` → slice assignment info\n- `Send.ChannelIndex` → optional channel array index\n- `Send.VariantTag` → for variant sends\n- `Send.Values` → for sequential sends\n- `Receive.ChannelIndex` → optional channel array index\n- `Receive.Variables` → for sequential receives\n- `VariantReceive.ChannelIndex` → optional channel array index\n- `AltBlock.Replicator` → for replicated ALT\n- `AltBlock.Cases[i].Guard` → conditional guard\n- `AltBlock.Cases[i].IsSkip` → default case marker\n- `AltBlock.Cases[i].IsTimer` → timer case marker\n- `AltBlock.Cases[i].ChannelIndex` → channel array index\n- `ProcDecl.Params[i].IsVal` → pass by value\n- `ProcDecl.Params[i].IsChan` → single channel parameter\n- `ProcDecl.Params[i].IsChanArray` → channel array parameter\n- `ProcDecl.Params[i].IsOpenArray` → open array parameter\n- `ProcDecl.Params[i].ArraySize` → fixed-size array parameter\n- `ProcDecl.Params[i].ChanElemType` → element type of channel\n- `ProcDecl.Params[i].ChanDir` → channel direction (\"?\", \"!\", or \"\")\n\nAll functions use `g.builder` for output generation and respect indentation levels via `g.indent` counter."
},
{
"type": "text",
"text": "agentId: a0f74054b49fd68ef (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 62112\ntool_uses: 2\nduration_ms: 32111</usage>"
}
]Good, I now have a comprehensive understanding of the codebase. Let me start implementing the changes systematically. I'll begin with the AST changes, then fix compilation errors, then add multi-dim parsing loops and codegen.
Let me start with Step 1: AST changes.
I need to read a few more specific sections of codegen.go to understand the exact patterns before making changes. But I think I have enough context now. Let me proceed.
Let me first make the AST changes, then I'll need to do a comprehensive search-and-replace across all files.
Here's my plan for AST changes:
1. ArrayDecl.Size → ArrayDecl.Sizes []Expression
ChanDecl to support multiple dimensions by replacing the boolean flag and single size with a sizes sliceSend, Receive, AltCase, and VariantReceive to use index slices instead of single indices, including Assignment and MultiAssignTarget to support multiple indices
5. Changing procedure parameter representations from boolean flags to dimension counts for both channel arrays and open arrays, then starting work on the AST file itself
Good, I have a comprehensive understanding of the codebase. Let me start implementing. First, the AST changes.
// ArrayDecl represents an array declaration: [5]INT arr:
type ArrayDecl struct {
Token lexer.Token // the [ token
Size Expression // array size
Type string // element type ("INT", "BYTE", "BOOL", etc.)
Names []string // variable names
}// ArrayDecl represents an array declaration: [5]INT arr: or [5][3]INT arr:
type ArrayDecl struct {
Token lexer.Token // the [ token
Sizes []Expression // array sizes (one per dimension)
Type string // element type ("INT", "BYTE", "BOOL", etc.)
Names []string // variable names
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// Assignment represents an assignment: x := 5 or arr[i] := 5 or [arr FROM n FOR m] := value
type Assignment struct {
Token lexer.Token // the := token
Name string // variable name
Index Expression // optional: index expression for arr[i] := x (nil for simple assignments)
SliceTarget *SliceExpr // optional: slice target for [arr FROM n FOR m] := value
Value Expression // the value being assigned
}// Assignment represents an assignment: x := 5 or arr[i] := 5 or arr[i][j] := 5 or [arr FROM n FOR m] := value
type Assignment struct {
Token lexer.Token // the := token
Name string // variable name
Indices []Expression // optional: index expressions for arr[i][j] := x (nil/empty for simple assignments)
SliceTarget *SliceExpr // optional: slice target for [arr FROM n FOR m] := value
Value Expression // the value being assigned
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// MultiAssignTarget represents one target in a multi-assignment.
// Name is always set. Index is non-nil for indexed targets like arr[i].
type MultiAssignTarget struct {
Name string // variable name
Index Expression // optional: index expression for arr[i] (nil for simple ident)
}// MultiAssignTarget represents one target in a multi-assignment.
// Name is always set. Indices is non-empty for indexed targets like arr[i] or arr[i][j].
type MultiAssignTarget struct {
Name string // variable name
Indices []Expression // optional: index expressions for arr[i][j] (nil/empty for simple ident)
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// ProcParam represents a procedure parameter
type ProcParam struct {
IsVal bool // VAL parameter (pass by value)
Type string // INT, BYTE, BOOL, etc.
Name string
IsChan bool // true if this is a CHAN OF <type> parameter
IsChanArray bool // true for []CHAN OF TYPE params
IsOpenArray bool // true for []TYPE params (open array)
ChanElemType string // element type when IsChan (e.g., "INT")
ChanDir string // "?" for input, "!" for output, "" for bidirectional
ArraySize string // non-empty for fixed-size array params like [2]INT
}// ProcParam represents a procedure parameter
type ProcParam struct {
IsVal bool // VAL parameter (pass by value)
Type string // INT, BYTE, BOOL, etc.
Name string
IsChan bool // true if this is a CHAN OF <type> parameter
ChanArrayDims int // number of [] dimensions for []CHAN, [][]CHAN, etc. (0 = not a chan array)
OpenArrayDims int // number of [] dimensions for []TYPE, [][]TYPE, etc. (0 = not an open array)
ChanElemType string // element type when IsChan (e.g., "INT")
ChanDir string // "?" for input, "!" for output, "" for bidirectional
ArraySize string // non-empty for fixed-size array params like [2]INT
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// ChanDecl represents a channel declaration: CHAN OF INT c: or [n]CHAN OF INT cs:
type ChanDecl struct {
Token lexer.Token // the CHAN token
ElemType string // the element type (INT, BYTE, etc.)
Names []string // channel names
IsArray bool // true for [n]CHAN OF TYPE
Size Expression // array size when IsArray
}// ChanDecl represents a channel declaration: CHAN OF INT c: or [n]CHAN OF INT cs: or [n][m]CHAN OF INT cs:
type ChanDecl struct {
Token lexer.Token // the CHAN token
ElemType string // the element type (INT, BYTE, etc.)
Names []string // channel names
Sizes []Expression // array sizes per dimension (empty = scalar channel)
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// Send represents a channel send: c ! x or c ! x ; y or c ! tag ; x
type Send struct {
Token lexer.Token // the ! token
Channel string // channel name
ChannelIndex Expression // non-nil for cs[i] ! value
Value Expression // value to send (simple send, backward compat)
Values []Expression // additional values for sequential sends (c ! x ; y)
VariantTag string // variant tag name for variant sends (c ! tag ; x)
}// Send represents a channel send: c ! x or c ! x ; y or c ! tag ; x
type Send struct {
Token lexer.Token // the ! token
Channel string // channel name
ChannelIndices []Expression // non-empty for cs[i] ! value or cs[i][j] ! value
Value Expression // value to send (simple send, backward compat)
Values []Expression // additional values for sequential sends (c ! x ; y)
VariantTag string // variant tag name for variant sends (c ! tag ; x)
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// Receive represents a channel receive: c ? x or c ? x ; y
type Receive struct {
Token lexer.Token // the ? token
Channel string // channel name
ChannelIndex Expression // non-nil for cs[i] ? x
Variable string // variable to receive into (simple receive)
Variables []string // additional variables for sequential receives (c ? x ; y)
}// 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)
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// 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
ChannelIndex Expression // non-nil for cs[i] ? x in ALT
Variable string // variable to receive into
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)
Deadline Expression // AFTER deadline expression (when IsTimer)
Declarations []Statement // scoped declarations before channel input (e.g., BYTE ch:)
}// 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
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)
Deadline Expression // AFTER deadline expression (when IsTimer)
Declarations []Statement // scoped declarations before channel input (e.g., BYTE ch:)
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
// VariantReceive represents a variant protocol receive: c ? CASE ...
type VariantReceive struct {
Token lexer.Token // the ? token
Channel string
ChannelIndex Expression // non-nil for cs[i] ? CASE ...
Cases []VariantCase
}// VariantReceive represents a variant protocol receive: c ? CASE ...
type VariantReceive struct {
Token lexer.Token // the ? token
Channel string
ChannelIndices []Expression // non-empty for cs[i] ? CASE ... or cs[i][j] ? CASE ...
Cases []VariantCase
}The file /home/david/projects/code-associates/occam2go/ast/ast.go has been updated successfully.
AST changes are done. Now let me mark task 1 complete and start on task 2 - fixing all compilation errors from the renames. This is a big mechanical change across parser.go, codegen.go, and test files.
Now I need to find and fix all references to the old field names. Let me search for all occurrences across the codebase.
parser/parser_test.go:606: sizeLit, ok := decl.Size.(*ast.IntegerLiteral)
parser/parser_test.go:608: t.Fatalf("expected IntegerLiteral for size, got %T", decl.Size)
parser/parser_test.go:2012: sizeLit, ok := decl.Size.(*ast.IntegerLiteral)
parser/parser_test.go:2014: t.Fatalf("expected IntegerLiteral for size, got %T", decl.Size)
codegen/codegen.go:1124: g.generateExpression(decl.Size)
codegen/codegen.go:1152: g.generateExpression(decl.Size)parser/parser_test.go:2008: if !decl.IsArray {
parser/parser_test.go:2675: if !decl.IsArray {
parser/parser_test.go:3191: if rt.IsArray {
parser/parser_test.go:3225: if !rt.IsArray {
codegen/codegen.go:1119: if decl.IsArray {
codegen/codegen.go:2671: if r.IsArray {parser/parser.go:1739: altCase.ChannelIndex = p.parseExpression(LOWEST)
parser/parser.go:1777: altCase.ChannelIndex = p.parseExpression(LOWEST)
parser/parser_test.go:2050: if send.ChannelIndex == nil {
parser/parser_test.go:2054: idxLit, ok := send.ChannelIndex.(*ast.IntegerLiteral)
parser/parser_test.go:2056: t.Fatalf("expected IntegerLiteral for index, got %T", send.ChannelIndex)
parser/parser_test.go:2092: if recv.ChannelIndex == nil {
parser/parser_test.go:2096: idxIdent, ok := recv.ChannelIndex.(*ast.Identifier)
parser/parser_test.go:2098: t.Fatalf("expected Identifier for index, got %T", recv.ChannelIndex)
parser/parser_test.go:3359: if c.ChannelIndex == nil {
codegen/codegen.go:1160: if send.ChannelIndex != nil {
codegen/codegen.go:1162: g.generateExpression(send.ChannelIndex)
codegen/codegen.go:1206: if recv.ChannelIndex != nil {
codegen/codegen.go:1213: g.generateExpression(recv.ChannelIndex)
codegen/codegen.go:1297: if vr.ChannelIndex != nil {
codegen/codegen.go:1303: g.generateExpression(vr.ChannelIndex)
codegen/codegen.go:1793: } else if c.ChannelIndex != nil {
codegen/codegen.go:1795: g.generateExpression(c.ChannelIndex)
codegen/codegen.go:1873: if c.ChannelIndex != nil {
codegen/codegen.go:1875: g.generateExpression(c.ChannelIndex){
"pattern": "\\bIndex\\b",
"glob": "*.go",
"output_mode": "content",
"path": "/home/david/projects/code-associates/occam2go/parser"
}parser/parser.go:506: target.Index = p.parseExpression(LOWEST)
parser/parser.go:691: firstTarget := ast.MultiAssignTarget{Name: name, Index: index}
parser/parser.go:700: Index: index,
parser/parser.go:793: expr.Index = p.parseExpression(LOWEST)
parser/parser_test.go:672: if assign.Index == nil {
parser/parser_test.go:676: indexLit, ok := assign.Index.(*ast.IntegerLiteral)
parser/parser_test.go:678: t.Fatalf("expected IntegerLiteral for index, got %T", assign.Index)
parser/parser_test.go:733: idxLit, ok := indexExpr.Index.(*ast.IntegerLiteral)
parser/parser_test.go:735: t.Fatalf("expected IntegerLiteral for index, got %T", indexExpr.Index)
parser/parser_test.go:962: if ma.Targets[0].Index != nil || ma.Targets[1].Index != nil {
parser/parser_test.go:1003: if ma.Targets[0].Index == nil || ma.Targets[1].Index == nil {
parser/parser_test.go:1032: if ma.Targets[0].Name != "a" || ma.Targets[0].Index != nil {
parser/parser_test.go:1035: if ma.Targets[1].Name != "x" || ma.Targets[1].Index == nil {
parser/parser_test.go:1933: if assign.Index == nil {
parser/parser_test.go:1937: ident, ok := assign.Index.(*ast.Identifier)
parser/parser_test.go:1939: t.Fatalf("expected Identifier for index, got %T", assign.Index)
parser/parser_test.go:1982: idx, ok := indexExpr.Index.(*ast.Identifier)
parser/parser_test.go:1984: t.Fatalf("expected Identifier for index, got %T", indexExpr.Index)parser/parser.go:2034: param.IsChanArray = prevParam.IsChanArray
parser/parser.go:2041: if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
parser/parser.go:2078: param.IsChanArray = true
parser/parser.go:2160: if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
parser/parser_test.go:2135: if !p0.IsChanArray {
parser/parser_test.go:2146: if p1.IsChan || p1.IsChanArray {
parser/parser_test.go:2219: if !p0.IsChanArray {
parser/parser_test.go:2724: if !p1.IsChan || !p1.IsChanArray {
codegen/codegen.go:1356: if p.IsChan || p.IsChanArray {
codegen/codegen.go:1946: if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
codegen/codegen.go:1953: if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
codegen/codegen.go:1959: if p.IsChan || p.IsChanArray {
codegen/codegen.go:2040: if p.IsChanArray {
codegen/codegen.go:2103: if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray && params[i].ArraySize == "" {
codegen/codegen.go:2143: if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {parser/parser.go:2035: param.IsOpenArray = prevParam.IsOpenArray
parser/parser.go:2091: param.IsOpenArray = true
parser/parser.go:2095: param.IsOpenArray = true
parser/parser_test.go:2610: if !p0.IsOpenArray {
parser/parser_test.go:2621: if p1.IsOpenArray {
codegen/codegen.go:282: if abbr.IsOpenArray {
codegen/codegen.go:288: if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
codegen/codegen.go:1095: if abbr.IsOpenArray {
codegen/codegen.go:1103: if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
codegen/codegen.go:1946: if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
codegen/codegen.go:2044: } else if p.IsOpenArray {
codegen/codegen.go:2103: if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray && params[i].ArraySize == "" {
codegen/codegen.go:2107: if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {
codegen/codegen.go:2199: if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {{
"pattern": "\\.Index\\b",
"glob": "*.go",
"output_mode": "content",
"path": "/home/david/projects/code-associates/occam2go/codegen"
}codegen/codegen.go:802: return g.exprNeedsMath(s.Value) || g.exprNeedsMath(s.Index)
codegen/codegen.go:805: if g.exprNeedsMath(t.Index) {
codegen/codegen.go:936: return g.exprNeedsMath(e.Left) || g.exprNeedsMath(e.Index)
codegen/codegen.go:1602: if assign.Index != nil {
codegen/codegen.go:1605: if ident, ok := assign.Index.(*ast.Identifier); ok {
codegen/codegen.go:1622: g.generateExpression(assign.Index)
codegen/codegen.go:2216: if target.Index != nil {
codegen/codegen.go:2219: if ident, ok := target.Index.(*ast.Identifier); ok {
codegen/codegen.go:2234: g.generateExpression(target.Index)
codegen/codegen.go:2525: if field, ok := e.Index.(*ast.Identifier); ok {
codegen/codegen.go:2535: g.generateExpression(e.Index)
codegen/codegen.go:2871: return g.walkExpr(s.Value, fn) || g.walkExpr(s.Index, fn)
codegen/codegen.go:2996: return g.walkExpr(e.Left, fn) || g.walkExpr(e.Index, fn)Now I have a comprehensive picture of all the changes needed. Let me work through them systematically.
There's a lot to change across multiple files. Let me tackle the most important ones first. I'll need to read specific sections to make precise edits.
Let me note that some .Index references are on IndexExpr (which we are NOT changing) and some are on Assignment and MultiAssignTarget (which we ARE changing). Similarly, .IsOpenArray on Abbreviation is NOT changing, only on ProcParam.
Let me categorize:
parser.go changes needed:
- Line 506: target.Index = → target.Indices = append(target.Indices, ...) (MultiAssignTarget)
MultiAssignTarget{Name: name, Index: index} → MultiAssignTarget{Name: name, Indices: []ast.Expression{index}}Index: index, → Indices: []ast.Expression{index}, (Assignment)expr.Index = - this is IndexExpr, KEEP AS ISaltCase.ChannelIndex = → altCase.ChannelIndices = append(altCase.ChannelIndices, ...)altCase.Channel - need to see the rest of this line to determine the changeNow I'm looking at parameter field updates where I need to replace boolean flags with dimension counters. I'm changing IsChanArray and IsOpenArray boolean fields to ChanArrayDims and OpenArrayDims integer fields, updating assignments and comparisons accordingly throughout the codebase.
For the codegen changes, I need to update how indices are handled—converting from a single Index field to looping through multiple Indices, while keeping the IndexExpr handling unchanged. I'm also replacing array size checks from IsArray boolean to checking if the Sizes slice has elements.
Continuing through the Send/Receive/VariableRef channel operations, I need to convert ChannelIndex references to check if ChannelIndices has values and loop through them where needed. Continuing through the codebase to replace the boolean flags with dimension checks—updating references to IsChanArray and IsOpenArray with their corresponding *ArrayDims fields, and where both flags were used together, consolidating them into a single dimension comparison. I need to be more careful about which IsOpenArray references actually need to change—the ones on Abbreviation should stay as-is, but the ones on ProcParam need to switch to OpenArrayDims > 0. I'm also tracking down where ChannelIndex gets assigned in the parser for the Send/Receive operations.
Now I'm ready to start making the actual edits, beginning with parser.go since it has fewer changes overall, then moving to codegen.go. Let me pull up the specific sections I need to modify.
Now I need to fix all references across parser.go, codegen.go, and test files. Let me read the specific sections I need to change and make targeted edits. I'll work on parser.go first, then codegen.go, then tests.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 500,
"limit": 30
} 500→ p.nextToken() // consume comma
501→ p.nextToken() // move to next target
502→ target := ast.MultiAssignTarget{Name: p.curToken.Literal}
503→ if p.peekTokenIs(lexer.LBRACKET) {
504→ p.nextToken() // move to [
505→ p.nextToken() // move past [
506→ target.Index = p.parseExpression(LOWEST)
507→ if !p.expectPeek(lexer.RBRACKET) {
508→ return nil
509→ }
510→ }
511→ stmt.Targets = append(stmt.Targets, target)
512→ }
513→
514→ p.nextToken() // move to :=
515→ stmt.Token = p.curToken
516→
517→ p.nextToken() // move past :=
518→
519→ // Parse comma-separated values
520→ stmt.Values = []ast.Expression{p.parseExpression(LOWEST)}
521→ for p.peekTokenIs(lexer.COMMA) {
522→ p.nextToken() // consume comma
523→ p.nextToken() // move to next expression
524→ stmt.Values = append(stmt.Values, p.parseExpression(LOWEST))
525→ }
526→
527→ return stmt
528→}
529→{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 540,
"limit": 100
} 540→ return p.parseSliceAssignment(lbracketToken, size)
541→ }
542→
543→ // Expect ]
544→ if !p.expectPeek(lexer.RBRACKET) {
545→ return nil
546→ }
547→
548→ // Check if this is a channel array: [n]CHAN OF TYPE
549→ if p.peekTokenIs(lexer.CHAN) {
550→ p.nextToken() // move to CHAN
551→ chanDecl := &ast.ChanDecl{
552→ Token: p.curToken,
553→ IsArray: true,
554→ Size: size,
555→ }
556→
557→ // Expect OF (optional — CHAN BYTE is shorthand for CHAN OF BYTE)
558→ if p.peekTokenIs(lexer.OF) {
559→ p.nextToken() // consume OF
560→ }
561→
562→ // Expect type (INT, BYTE, BOOL, etc.) or protocol name (IDENT)
563→ p.nextToken()
564→ if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) ||
565→ p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) ||
566→ p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) {
567→ chanDecl.ElemType = p.curToken.Literal
568→ } else if p.curTokenIs(lexer.IDENT) {
569→ chanDecl.ElemType = p.curToken.Literal
570→ } else {
571→ p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
572→ return nil
573→ }
574→
575→ // Parse channel names
576→ for {
577→ if !p.expectPeek(lexer.IDENT) {
578→ return nil
579→ }
580→ chanDecl.Names = append(chanDecl.Names, p.curToken.Literal)
581→
582→ if p.peekTokenIs(lexer.COMMA) {
583→ p.nextToken() // consume comma
584→ } else {
585→ break
586→ }
587→ }
588→
589→ if !p.expectPeek(lexer.COLON) {
590→ return nil
591→ }
592→
593→ return chanDecl
594→ }
595→
596→ // Regular array declaration
597→ decl := &ast.ArrayDecl{Token: lbracketToken, Size: size}
598→
599→ // Expect type (INT, BYTE, BOOL, REAL, REAL32, REAL64)
600→ p.nextToken()
601→ if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
602→ !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) &&
603→ !p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) {
604→ p.addError(fmt.Sprintf("expected type after array size, got %s", p.curToken.Type))
605→ return nil
606→ }
607→ decl.Type = p.curToken.Literal
608→
609→ // Parse variable names
610→ for {
611→ if !p.expectPeek(lexer.IDENT) {
612→ return nil
613→ }
614→ decl.Names = append(decl.Names, p.curToken.Literal)
615→
616→ if p.peekTokenIs(lexer.COMMA) {
617→ p.nextToken() // consume comma
618→ } else {
619→ break
620→ }
621→ }
622→
623→ if !p.expectPeek(lexer.COLON) {
624→ return nil
625→ }
626→
627→ return decl
628→}
629→
630→// parseSliceAssignment parses [arr FROM start FOR length] := value
631→// Also handles [arr FOR length] shorthand (start defaults to 0).
632→// Called from parseArrayDecl when FROM or FOR is detected after the array expression.
633→// lbracketToken is the [ token, arrayExpr is the already-parsed array expression.
634→func (p *Parser) parseSliceAssignment(lbracketToken lexer.Token, arrayExpr ast.Expression) ast.Statement {
635→ var startExpr ast.Expression
636→ if p.peekTokenIs(lexer.FOR) {
637→ // [arr FOR length] shorthand — start is 0
638→ startExpr = &ast.IntegerLiteral{Token: lexer.Token{Type: lexer.INT, Literal: "0"}, Value: 0}
639→ } else {{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 676,
"limit": 120
} 676→func (p *Parser) parseIndexedOperation() ast.Statement {
677→ name := p.curToken.Literal
678→
679→ p.nextToken() // move to [
680→ p.nextToken() // move past [
681→ index := p.parseExpression(LOWEST)
682→
683→ // Expect ]
684→ if !p.expectPeek(lexer.RBRACKET) {
685→ return nil
686→ }
687→
688→ // Check what follows ]
689→ if p.peekTokenIs(lexer.COMMA) {
690→ // Multi-assignment starting with indexed target: name[index], ... := ...
691→ firstTarget := ast.MultiAssignTarget{Name: name, Index: index}
692→ return p.parseMultiAssignmentFrom(firstTarget)
693→ }
694→ if p.peekTokenIs(lexer.ASSIGN) {
695→ // Indexed assignment: name[index] := value
696→ p.nextToken() // move to :=
697→ stmt := &ast.Assignment{
698→ Name: name,
699→ Token: p.curToken,
700→ Index: index,
701→ }
702→ p.nextToken() // move past :=
703→ stmt.Value = p.parseExpression(LOWEST)
704→ return stmt
705→ }
706→
707→ if p.peekTokenIs(lexer.SEND) {
708→ // Indexed channel send: cs[i] ! value
709→ p.nextToken() // move to !
710→ sendToken := p.curToken
711→ p.nextToken() // move past !
712→
713→ stmt := &ast.Send{
714→ Token: sendToken,
715→ Channel: name,
716→ ChannelIndex: index,
717→ }
718→
719→ // Check if this is a variant send: first token is an identifier that is a variant tag
720→ if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.SEMICOLON) {
721→ possibleTag := p.curToken.Literal
722→ if p.isVariantTag(possibleTag) {
723→ stmt.VariantTag = possibleTag
724→ p.nextToken() // move to ;
725→ for p.curTokenIs(lexer.SEMICOLON) {
726→ p.nextToken() // move past ;
727→ val := p.parseExpression(LOWEST)
728→ stmt.Values = append(stmt.Values, val)
729→ }
730→ return stmt
731→ }
732→ }
733→
734→ stmt.Value = p.parseExpression(LOWEST)
735→
736→ // Check for sequential send
737→ for p.peekTokenIs(lexer.SEMICOLON) {
738→ p.nextToken() // move to ;
739→ p.nextToken() // move past ;
740→ val := p.parseExpression(LOWEST)
741→ stmt.Values = append(stmt.Values, val)
742→ }
743→
744→ return stmt
745→ }
746→
747→ if p.peekTokenIs(lexer.RECEIVE) {
748→ // Indexed channel receive: cs[i] ? x or cs[i] ? CASE ...
749→ p.nextToken() // move to ?
750→ recvToken := p.curToken
751→
752→ // Check for variant receive: cs[i] ? CASE
753→ if p.peekTokenIs(lexer.CASE) {
754→ p.nextToken() // move to CASE
755→ return p.parseVariantReceiveWithIndex(name, index, recvToken)
756→ }
757→
758→ stmt := &ast.Receive{
759→ Token: recvToken,
760→ Channel: name,
761→ ChannelIndex: index,
762→ }
763→
764→ if !p.expectPeek(lexer.IDENT) {
765→ return nil
766→ }
767→ stmt.Variable = p.curToken.Literal
768→
769→ // Check for sequential receive
770→ for p.peekTokenIs(lexer.SEMICOLON) {
771→ p.nextToken() // move to ;
772→ if !p.expectPeek(lexer.IDENT) {
773→ return nil
774→ }
775→ stmt.Variables = append(stmt.Variables, p.curToken.Literal)
776→ }
777→
778→ return stmt
779→ }
780→
781→ // Default: treat as indexed assignment (shouldn't reach here normally)
782→ p.addError(fmt.Sprintf("expected :=, !, or ? after %s[...], got %s", name, p.peekToken.Type))
783→ return nil
784→}
785→
786→func (p *Parser) parseIndexExpression(left ast.Expression) *ast.IndexExpr {
787→ expr := &ast.IndexExpr{
788→ Token: p.curToken,
789→ Left: left,
790→ }
791→
792→ p.nextToken() // move past [
793→ expr.Index = p.parseExpression(LOWEST)
794→
795→ if !p.expectPeek(lexer.RBRACKET) {Now let me read the parseAltCase and parseProcParams sections, and the parseVariantReceiveWithIndex.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 1720,
"limit": 100
} 1720→ return nil
1721→ }
1722→ p.nextToken() // move past AFTER
1723→ altCase.Deadline = p.parseExpression(LOWEST)
1724→ } else {
1725→ // Simple case: channel ? var
1726→ altCase.Channel = name
1727→ p.nextToken() // move to ?
1728→ if !p.expectPeek(lexer.IDENT) {
1729→ return nil
1730→ }
1731→ altCase.Variable = p.curToken.Literal
1732→ }
1733→ } else if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.LBRACKET) {
1734→ // Indexed channel case: cs[i] ? var
1735→ name := p.curToken.Literal
1736→ altCase.Channel = name
1737→ p.nextToken() // move to [
1738→ p.nextToken() // move past [
1739→ altCase.ChannelIndex = p.parseExpression(LOWEST)
1740→ if !p.expectPeek(lexer.RBRACKET) {
1741→ return nil
1742→ }
1743→ if !p.expectPeek(lexer.RECEIVE) {
1744→ return nil
1745→ }
1746→ if !p.expectPeek(lexer.IDENT) {
1747→ return nil
1748→ }
1749→ altCase.Variable = p.curToken.Literal
1750→ } else {
1751→ // Guard followed by & channel ? var, or guard & SKIP
1752→ guard := p.parseExpression(LOWEST)
1753→ altCase.Guard = guard
1754→
1755→ // Expect &
1756→ if !p.peekTokenIs(lexer.AMPERSAND) {
1757→ p.addError("expected & after guard in ALT case")
1758→ return nil
1759→ }
1760→ p.nextToken() // move to &
1761→ p.nextToken() // move past &
1762→
1763→ if p.curTokenIs(lexer.SKIP) {
1764→ // Guarded SKIP: guard & SKIP
1765→ altCase.IsSkip = true
1766→ } else if !p.curTokenIs(lexer.IDENT) {
1767→ p.addError(fmt.Sprintf("expected channel name or SKIP after guard, got %s", p.curToken.Type))
1768→ return nil
1769→ } else {
1770→ // Channel operation after guard
1771→ altCase.Channel = p.curToken.Literal
1772→
1773→ if p.peekTokenIs(lexer.LBRACKET) {
1774→ // Indexed channel with guard: guard & cs[i] ? var
1775→ p.nextToken() // move to [
1776→ p.nextToken() // move past [
1777→ altCase.ChannelIndex = p.parseExpression(LOWEST)
1778→ if !p.expectPeek(lexer.RBRACKET) {
1779→ return nil
1780→ }
1781→ }
1782→
1783→ if !p.expectPeek(lexer.RECEIVE) {
1784→ return nil
1785→ }
1786→ if !p.expectPeek(lexer.IDENT) {
1787→ return nil
1788→ }
1789→ altCase.Variable = p.curToken.Literal
1790→ }
1791→ }
1792→
1793→ // Skip to next line for the body
1794→ for p.peekTokenIs(lexer.NEWLINE) {
1795→ p.nextToken()
1796→ }
1797→
1798→ // Expect INDENT for body
1799→ if !p.peekTokenIs(lexer.INDENT) {
1800→ p.addError("expected indented body after ALT case")
1801→ return altCase
1802→ }
1803→ p.nextToken() // consume INDENT
1804→ p.nextToken() // move into body
1805→
1806→ altCase.Body = p.parseBodyStatements()
1807→
1808→ return altCase
1809→}
1810→
1811→func (p *Parser) parseBlockStatements() []ast.Statement {
1812→ var statements []ast.Statement
1813→ startLevel := p.indentLevel
1814→
1815→ p.nextToken() // move past INDENT
1816→
1817→ for !p.curTokenIs(lexer.EOF) {
1818→ // Skip newlines
1819→ for p.curTokenIs(lexer.NEWLINE) {{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 2020,
"limit": 160
} 2020→ for p.curTokenIs(lexer.NEWLINE) {
2021→ p.nextToken()
2022→ }
2023→
2024→ param := ast.ProcParam{}
2025→
2026→ // Check if this is a shared-type parameter: after a comma, if current token
2027→ // is an IDENT that is NOT a type keyword, record name, CHAN, VAL, RESULT, or [,
2028→ // re-use the previous param's type/flags.
2029→ if prevParam != nil && p.curTokenIs(lexer.IDENT) && !p.recordNames[p.curToken.Literal] {
2030→ // This is a shared-type param — re-use type info from previous param
2031→ param.IsVal = prevParam.IsVal
2032→ param.Type = prevParam.Type
2033→ param.IsChan = prevParam.IsChan
2034→ param.IsChanArray = prevParam.IsChanArray
2035→ param.IsOpenArray = prevParam.IsOpenArray
2036→ param.ChanElemType = prevParam.ChanElemType
2037→ param.ArraySize = prevParam.ArraySize
2038→ param.Name = p.curToken.Literal
2039→
2040→ // Check for channel direction marker (? or !)
2041→ if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
2042→ p.nextToken()
2043→ param.ChanDir = p.curToken.Literal
2044→ }
2045→
2046→ params = append(params, param)
2047→ prevParam = ¶ms[len(params)-1]
2048→
2049→ if !p.peekTokenIs(lexer.COMMA) {
2050→ break
2051→ }
2052→ p.nextToken() // consume comma
2053→ p.nextToken() // move to next param
2054→ continue
2055→ }
2056→
2057→ // Check for VAL keyword
2058→ if p.curTokenIs(lexer.VAL) {
2059→ param.IsVal = true
2060→ p.nextToken()
2061→ }
2062→
2063→ // Check for RESULT keyword (output-only parameter — maps to pointer like non-VAL)
2064→ if p.curTokenIs(lexer.RESULT) {
2065→ // RESULT is semantically like non-VAL (pointer param), just skip it
2066→ p.nextToken()
2067→ }
2068→
2069→ // Check for []CHAN OF <type>, []TYPE (open array), or [n]TYPE (fixed-size array)
2070→ if p.curTokenIs(lexer.LBRACKET) {
2071→ if p.peekTokenIs(lexer.RBRACKET) {
2072→ // Open array: []CHAN OF TYPE or []TYPE
2073→ p.nextToken() // consume ]
2074→ p.nextToken() // move past ]
2075→ if p.curTokenIs(lexer.CHAN) {
2076→ // []CHAN OF <type> or []CHAN <type> (channel array parameter)
2077→ param.IsChan = true
2078→ param.IsChanArray = true
2079→ if p.peekTokenIs(lexer.OF) {
2080→ p.nextToken() // consume OF
2081→ }
2082→ p.nextToken() // move to element type
2083→ if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) {
2084→ param.ChanElemType = p.curToken.Literal
2085→ } else {
2086→ p.addError(fmt.Sprintf("expected type after []CHAN, got %s", p.curToken.Type))
2087→ return params
2088→ }
2089→ p.nextToken()
2090→ } else if isTypeToken(p.curToken.Type) {
2091→ param.IsOpenArray = true
2092→ param.Type = p.curToken.Literal
2093→ p.nextToken()
2094→ } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
2095→ param.IsOpenArray = true
2096→ param.Type = p.curToken.Literal
2097→ p.nextToken()
2098→ } else {
2099→ p.addError(fmt.Sprintf("expected type after [], got %s", p.curToken.Type))
2100→ return params
2101→ }
2102→ } else {
2103→ // Fixed-size array: [n]TYPE
2104→ p.nextToken() // move past [
2105→ if !p.curTokenIs(lexer.INT) {
2106→ p.addError(fmt.Sprintf("expected array size, got %s", p.curToken.Type))
2107→ return params
2108→ }
2109→ param.ArraySize = p.curToken.Literal
2110→ if !p.expectPeek(lexer.RBRACKET) {
2111→ return params
2112→ }
2113→ p.nextToken() // move to type
2114→ if isTypeToken(p.curToken.Type) {
2115→ param.Type = p.curToken.Literal
2116→ } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
2117→ param.Type = p.curToken.Literal
2118→ } else {
2119→ p.addError(fmt.Sprintf("expected type after [%s], got %s", param.ArraySize, p.curToken.Type))
2120→ return params
2121→ }
2122→ p.nextToken()
2123→ }
2124→ } else if p.curTokenIs(lexer.CHAN) {
2125→ // Check for CHAN OF <type> or CHAN <type>
2126→ param.IsChan = true
2127→ if p.peekTokenIs(lexer.OF) {
2128→ p.nextToken() // consume OF
2129→ }
2130→ p.nextToken() // move to element type
2131→ if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) {
2132→ param.ChanElemType = p.curToken.Literal
2133→ } else {
2134→ p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
2135→ return params
2136→ }
2137→ p.nextToken()
2138→ } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
2139→ // Record type parameter
2140→ param.Type = p.curToken.Literal
2141→ p.nextToken()
2142→ } else {
2143→ // Expect scalar type
2144→ if !isTypeToken(p.curToken.Type) {
2145→ p.addError(fmt.Sprintf("expected type in parameter, got %s", p.curToken.Type))
2146→ return params
2147→ }
2148→ param.Type = p.curToken.Literal
2149→ p.nextToken()
2150→ }
2151→
2152→ // Expect identifier
2153→ if !p.curTokenIs(lexer.IDENT) {
2154→ p.addError(fmt.Sprintf("expected parameter name, got %s", p.curToken.Type))
2155→ return params
2156→ }
2157→ param.Name = p.curToken.Literal
2158→
2159→ // Check for channel direction marker (? or !)
2160→ if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
2161→ p.nextToken()
2162→ param.ChanDir = p.curToken.Literal
2163→ }
2164→
2165→ params = append(params, param)
2166→ prevParam = ¶ms[len(params)-1]
2167→
2168→ if !p.peekTokenIs(lexer.COMMA) {
2169→ break
2170→ }
2171→ p.nextToken() // consume comma
2172→ p.nextToken() // move to next param
2173→ }
2174→
2175→ return params
2176→}
2177→
2178→func (p *Parser) parseProcCall() *ast.ProcCall {
2179→ call := &ast.ProcCall{{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 1384,
"limit": 100
} 1384→func (p *Parser) parseVariantReceiveWithIndex(channel string, channelIndex ast.Expression, token lexer.Token) *ast.VariantReceive {
1385→ stmt := &ast.VariantReceive{
1386→ Token: token,
1387→ Channel: channel,
1388→ ChannelIndex: channelIndex,
1389→ }
1390→
1391→ // Skip to next line
1392→ for p.peekTokenIs(lexer.NEWLINE) {
1393→ p.nextToken()
1394→ }
1395→
1396→ // Expect INDENT
1397→ if !p.peekTokenIs(lexer.INDENT) {
1398→ p.addError("expected indented block after ? CASE")
1399→ return stmt
1400→ }
1401→ p.nextToken() // consume INDENT
1402→ startLevel := p.indentLevel
1403→ p.nextToken() // move into block
1404→
1405→ for !p.curTokenIs(lexer.EOF) {
1406→ for p.curTokenIs(lexer.NEWLINE) {
1407→ p.nextToken()
1408→ }
1409→
1410→ for p.curTokenIs(lexer.DEDENT) {
1411→ if p.indentLevel < startLevel {
1412→ return stmt
1413→ }
1414→ p.nextToken()
1415→ }
1416→
1417→ for p.curTokenIs(lexer.NEWLINE) {
1418→ p.nextToken()
1419→ }
1420→
1421→ if p.curTokenIs(lexer.EOF) {
1422→ break
1423→ }
1424→
1425→ if p.indentLevel < startLevel {
1426→ break
1427→ }
1428→
1429→ // Safety guard: record position before parsing to detect no-progress
1430→ prevToken := p.curToken
1431→ prevPeek := p.peekToken
1432→
1433→ vc := ast.VariantCase{}
1434→
1435→ if !p.curTokenIs(lexer.IDENT) {
1436→ p.addError(fmt.Sprintf("expected variant tag name, got %s", p.curToken.Type))
1437→ p.nextToken() // skip unrecognized token to avoid infinite loop
1438→ continue
1439→ }
1440→ vc.Tag = p.curToken.Literal
1441→
1442→ for p.peekTokenIs(lexer.SEMICOLON) {
1443→ p.nextToken() // move to ;
1444→ if !p.expectPeek(lexer.IDENT) {
1445→ return stmt
1446→ }
1447→ vc.Variables = append(vc.Variables, p.curToken.Literal)
1448→ }
1449→
1450→ for p.peekTokenIs(lexer.NEWLINE) {
1451→ p.nextToken()
1452→ }
1453→
1454→ if p.peekTokenIs(lexer.INDENT) {
1455→ p.nextToken() // consume INDENT
1456→ p.nextToken() // move to body
1457→ vc.Body = p.parseStatement()
1458→
1459→ if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
1460→ p.nextToken()
1461→ }
1462→ }
1463→
1464→ stmt.Cases = append(stmt.Cases, vc)
1465→
1466→ // No-progress guard: if we haven't moved, break to prevent infinite loop
1467→ if p.curToken == prevToken && p.peekToken == prevPeek {
1468→ p.nextToken() // force progress
1469→ if p.curToken == prevToken {
1470→ break
1471→ }
1472→ }
1473→ }
1474→
1475→ return stmt
1476→}
1477→
1478→func (p *Parser) parseSeqBlock() *ast.SeqBlock {
1479→ block := &ast.SeqBlock{Token: p.curToken}
1480→
1481→ // Check for replicator: SEQ i = start FOR count
1482→ if p.peekTokenIs(lexer.IDENT) {
1483→ // Save position to check if it's a replicatorNow I have all the context I need for parser.go. Let me make all the parser.go edits. I'll start with the changes that are purely mechanical renames (step 2), then I'll make them multi-dim capable (step 3) in the same pass where it makes sense.
if p.peekTokenIs(lexer.LBRACKET) {
p.nextToken() // move to [
p.nextToken() // move past [
target.Index = p.parseExpression(LOWEST)
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
} for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken() // move to [
p.nextToken() // move past [
target.Indices = append(target.Indices, 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.
// Check if this is a channel array: [n]CHAN OF TYPE
if p.peekTokenIs(lexer.CHAN) {
p.nextToken() // move to CHAN
chanDecl := &ast.ChanDecl{
Token: p.curToken,
IsArray: true,
Size: size,
}
// Expect OF (optional — CHAN BYTE is shorthand for CHAN OF BYTE)
if p.peekTokenIs(lexer.OF) {
p.nextToken() // consume OF
}
// Expect type (INT, BYTE, BOOL, etc.) or protocol name (IDENT)
p.nextToken()
if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) ||
p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) ||
p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) {
chanDecl.ElemType = p.curToken.Literal
} else if p.curTokenIs(lexer.IDENT) {
chanDecl.ElemType = p.curToken.Literal
} else {
p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
return nil
}
// Parse channel names
for {
if !p.expectPeek(lexer.IDENT) {
return nil
}
chanDecl.Names = append(chanDecl.Names, p.curToken.Literal)
if p.peekTokenIs(lexer.COMMA) {
p.nextToken() // consume comma
} else {
break
}
}
if !p.expectPeek(lexer.COLON) {
return nil
}
return chanDecl
}
// Regular array declaration
decl := &ast.ArrayDecl{Token: lbracketToken, Size: size}
// Expect type (INT, BYTE, BOOL, REAL, REAL32, REAL64)
p.nextToken()
if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
!p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) &&
!p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) {
p.addError(fmt.Sprintf("expected type after array size, got %s", p.curToken.Type))
return nil
}
decl.Type = p.curToken.Literal // Collect additional dimensions: [n][m]... before CHAN or TYPE
sizes := []ast.Expression{size}
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken() // move to [
p.nextToken() // move past [
nextSize := p.parseExpression(LOWEST)
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
sizes = append(sizes, nextSize)
}
// Check if this is a channel array: [n]CHAN OF TYPE or [n][m]CHAN OF TYPE
if p.peekTokenIs(lexer.CHAN) {
p.nextToken() // move to CHAN
chanDecl := &ast.ChanDecl{
Token: p.curToken,
Sizes: sizes,
}
// Expect OF (optional — CHAN BYTE is shorthand for CHAN OF BYTE)
if p.peekTokenIs(lexer.OF) {
p.nextToken() // consume OF
}
// Expect type (INT, BYTE, BOOL, etc.) or protocol name (IDENT)
p.nextToken()
if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) ||
p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) ||
p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) {
chanDecl.ElemType = p.curToken.Literal
} else if p.curTokenIs(lexer.IDENT) {
chanDecl.ElemType = p.curToken.Literal
} else {
p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type))
return nil
}
// Parse channel names
for {
if !p.expectPeek(lexer.IDENT) {
return nil
}
chanDecl.Names = append(chanDecl.Names, p.curToken.Literal)
if p.peekTokenIs(lexer.COMMA) {
p.nextToken() // consume comma
} else {
break
}
}
if !p.expectPeek(lexer.COLON) {
return nil
}
return chanDecl
}
// Regular array declaration
decl := &ast.ArrayDecl{Token: lbracketToken, Sizes: sizes}
// Expect type (INT, BYTE, BOOL, REAL, REAL32, REAL64)
p.nextToken()
if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
!p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) &&
!p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) {
p.addError(fmt.Sprintf("expected type after array size, got %s", p.curToken.Type))
return nil
}
decl.Type = p.curToken.LiteralThe file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
func (p *Parser) parseIndexedOperation() ast.Statement {
name := p.curToken.Literal
p.nextToken() // move to [
p.nextToken() // move past [
index := p.parseExpression(LOWEST)
// Expect ]
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
// Check what follows ]
if p.peekTokenIs(lexer.COMMA) {
// Multi-assignment starting with indexed target: name[index], ... := ...
firstTarget := ast.MultiAssignTarget{Name: name, Index: index}
return p.parseMultiAssignmentFrom(firstTarget)
}
if p.peekTokenIs(lexer.ASSIGN) {
// Indexed assignment: name[index] := value
p.nextToken() // move to :=
stmt := &ast.Assignment{
Name: name,
Token: p.curToken,
Index: index,
}
p.nextToken() // move past :=
stmt.Value = p.parseExpression(LOWEST)
return stmt
}
if p.peekTokenIs(lexer.SEND) {
// Indexed channel send: cs[i] ! value
p.nextToken() // move to !
sendToken := p.curToken
p.nextToken() // move past !
stmt := &ast.Send{
Token: sendToken,
Channel: name,
ChannelIndex: index,
}
// Check if this is a variant send: first token is an identifier that is a variant tag
if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.SEMICOLON) {
possibleTag := p.curToken.Literal
if p.isVariantTag(possibleTag) {
stmt.VariantTag = possibleTag
p.nextToken() // move to ;
for p.curTokenIs(lexer.SEMICOLON) {
p.nextToken() // move past ;
val := p.parseExpression(LOWEST)
stmt.Values = append(stmt.Values, val)
}
return stmt
}
}
stmt.Value = p.parseExpression(LOWEST)
// Check for sequential send
for p.peekTokenIs(lexer.SEMICOLON) {
p.nextToken() // move to ;
p.nextToken() // move past ;
val := p.parseExpression(LOWEST)
stmt.Values = append(stmt.Values, val)
}
return stmt
}
if p.peekTokenIs(lexer.RECEIVE) {
// Indexed channel receive: cs[i] ? x or cs[i] ? CASE ...
p.nextToken() // move to ?
recvToken := p.curToken
// Check for variant receive: cs[i] ? CASE
if p.peekTokenIs(lexer.CASE) {
p.nextToken() // move to CASE
return p.parseVariantReceiveWithIndex(name, index, recvToken)
}
stmt := &ast.Receive{
Token: recvToken,
Channel: name,
ChannelIndex: index,
}
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
}
// Default: treat as indexed assignment (shouldn't reach here normally)
p.addError(fmt.Sprintf("expected :=, !, or ? after %s[...], got %s", name, p.peekToken.Type))
return nil
}func (p *Parser) parseIndexedOperation() ast.Statement {
name := p.curToken.Literal
p.nextToken() // move to [
p.nextToken() // move past [
index := p.parseExpression(LOWEST)
// Expect ]
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
// Collect additional indices: name[i][j]...
indices := []ast.Expression{index}
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken() // move to [
p.nextToken() // move past [
idx := p.parseExpression(LOWEST)
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
indices = append(indices, idx)
}
// Check what follows ]
if p.peekTokenIs(lexer.COMMA) {
// Multi-assignment starting with indexed target: name[index], ... := ...
firstTarget := ast.MultiAssignTarget{Name: name, Indices: indices}
return p.parseMultiAssignmentFrom(firstTarget)
}
if p.peekTokenIs(lexer.ASSIGN) {
// Indexed assignment: name[index] := value or name[i][j] := value
p.nextToken() // move to :=
stmt := &ast.Assignment{
Name: name,
Token: p.curToken,
Indices: indices,
}
p.nextToken() // move past :=
stmt.Value = p.parseExpression(LOWEST)
return stmt
}
if p.peekTokenIs(lexer.SEND) {
// Indexed channel send: cs[i] ! value or cs[i][j] ! value
p.nextToken() // move to !
sendToken := p.curToken
p.nextToken() // move past !
stmt := &ast.Send{
Token: sendToken,
Channel: name,
ChannelIndices: indices,
}
// Check if this is a variant send: first token is an identifier that is a variant tag
if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.SEMICOLON) {
possibleTag := p.curToken.Literal
if p.isVariantTag(possibleTag) {
stmt.VariantTag = possibleTag
p.nextToken() // move to ;
for p.curTokenIs(lexer.SEMICOLON) {
p.nextToken() // move past ;
val := p.parseExpression(LOWEST)
stmt.Values = append(stmt.Values, val)
}
return stmt
}
}
stmt.Value = p.parseExpression(LOWEST)
// Check for sequential send
for p.peekTokenIs(lexer.SEMICOLON) {
p.nextToken() // move to ;
p.nextToken() // move past ;
val := p.parseExpression(LOWEST)
stmt.Values = append(stmt.Values, val)
}
return stmt
}
if p.peekTokenIs(lexer.RECEIVE) {
// Indexed channel receive: cs[i] ? x or cs[i][j] ? x or cs[i] ? CASE ...
p.nextToken() // move to ?
recvToken := p.curToken
// Check for variant receive: cs[i] ? CASE
if p.peekTokenIs(lexer.CASE) {
p.nextToken() // move to CASE
return p.parseVariantReceiveWithIndex(name, indices, recvToken)
}
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
}
// Default: treat as indexed assignment (shouldn't reach here normally)
p.addError(fmt.Sprintf("expected :=, !, or ? after %s[...], got %s", name, p.peekToken.Type))
return nil
}The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
func (p *Parser) parseVariantReceiveWithIndex(channel string, channelIndex ast.Expression, token lexer.Token) *ast.VariantReceive {
stmt := &ast.VariantReceive{
Token: token,
Channel: channel,
ChannelIndex: channelIndex,
}func (p *Parser) parseVariantReceiveWithIndex(channel string, channelIndices []ast.Expression, token lexer.Token) *ast.VariantReceive {
stmt := &ast.VariantReceive{
Token: token,
Channel: channel,
ChannelIndices: channelIndices,
}The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
} else if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.LBRACKET) {
// Indexed channel case: cs[i] ? var
name := p.curToken.Literal
altCase.Channel = name
p.nextToken() // move to [
p.nextToken() // move past [
altCase.ChannelIndex = p.parseExpression(LOWEST)
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
if !p.expectPeek(lexer.RECEIVE) { } else if p.curTokenIs(lexer.IDENT) && p.peekTokenIs(lexer.LBRACKET) {
// Indexed channel case: cs[i] ? var or cs[i][j] ? var
name := p.curToken.Literal
altCase.Channel = name
for p.peekTokenIs(lexer.LBRACKET) {
p.nextToken() // move to [
p.nextToken() // move past [
altCase.ChannelIndices = append(altCase.ChannelIndices, p.parseExpression(LOWEST))
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
}
if !p.expectPeek(lexer.RECEIVE) {The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
if p.peekTokenIs(lexer.LBRACKET) {
// Indexed channel with guard: guard & cs[i] ? var
p.nextToken() // move to [
p.nextToken() // move past [
altCase.ChannelIndex = p.parseExpression(LOWEST)
if !p.expectPeek(lexer.RBRACKET) {
return nil
}
} for p.peekTokenIs(lexer.LBRACKET) {
// Indexed channel with guard: guard & cs[i] ? var or cs[i][j] ? var
p.nextToken() // move to [
p.nextToken() // move past [
altCase.ChannelIndices = append(altCase.ChannelIndices, 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.
// This is a shared-type param — re-use type info from previous param
param.IsVal = prevParam.IsVal
param.Type = prevParam.Type
param.IsChan = prevParam.IsChan
param.IsChanArray = prevParam.IsChanArray
param.IsOpenArray = prevParam.IsOpenArray
param.ChanElemType = prevParam.ChanElemType
param.ArraySize = prevParam.ArraySize
param.Name = p.curToken.Literal
// Check for channel direction marker (? or !)
if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) { // This is a shared-type param — re-use type info from previous param
param.IsVal = prevParam.IsVal
param.Type = prevParam.Type
param.IsChan = prevParam.IsChan
param.ChanArrayDims = prevParam.ChanArrayDims
param.OpenArrayDims = prevParam.OpenArrayDims
param.ChanElemType = prevParam.ChanElemType
param.ArraySize = prevParam.ArraySize
param.Name = p.curToken.Literal
// Check for channel direction marker (? or !)
if (param.IsChan || param.ChanArrayDims > 0) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
if p.curTokenIs(lexer.CHAN) {
// []CHAN OF <type> or []CHAN <type> (channel array parameter)
param.IsChan = true
param.IsChanArray = true if p.curTokenIs(lexer.CHAN) {
// []CHAN OF <type> or []CHAN <type> (channel array parameter)
param.IsChan = true
param.ChanArrayDims = 1The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
} else if isTypeToken(p.curToken.Type) {
param.IsOpenArray = true
param.Type = p.curToken.Literal
p.nextToken()
} else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
param.IsOpenArray = true } else if isTypeToken(p.curToken.Type) {
param.OpenArrayDims = 1
param.Type = p.curToken.Literal
p.nextToken()
} else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
param.OpenArrayDims = 1The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
// Check for channel direction marker (? or !)
if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
p.nextToken()
param.ChanDir = p.curToken.Literal
}
params = append(params, param)
prevParam = ¶ms[len(params)-1]
if !p.peekTokenIs(lexer.COMMA) {
break
}
p.nextToken() // consume comma
p.nextToken() // move to next param
}
return params
} // Check for channel direction marker (? or !)
if (param.IsChan || param.ChanArrayDims > 0) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) {
p.nextToken()
param.ChanDir = p.curToken.Literal
}
params = append(params, param)
prevParam = ¶ms[len(params)-1]
if !p.peekTokenIs(lexer.COMMA) {
break
}
p.nextToken() // consume comma
p.nextToken() // move to next param
}
return params
}The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
Good. Parser.go is done. Now let me work on codegen.go. Let me read the specific sections that need updating.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 798,
"limit": 18
} 798→
799→func (g *Generator) containsMostExpr(stmt ast.Statement) bool {
800→ switch s := stmt.(type) {
801→ case *ast.Assignment:
802→ return g.exprNeedsMath(s.Value) || g.exprNeedsMath(s.Index)
803→ case *ast.MultiAssignment:
804→ for _, t := range s.Targets {
805→ if g.exprNeedsMath(t.Index) {
806→ return true
807→ }
808→ }
809→ for _, v := range s.Values {
810→ if g.exprNeedsMath(v) {
811→ return true
812→ }
813→ }
814→ case *ast.Abbreviation:
815→ return g.exprNeedsMath(s.Value){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1115,
"limit": 50
} 1115→}
1116→
1117→func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
1118→ goType := g.occamTypeToGo(decl.ElemType)
1119→ if decl.IsArray {
1120→ for _, name := range decl.Names {
1121→ n := goIdent(name)
1122→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1123→ g.write(fmt.Sprintf("%s := make([]chan %s, ", n, goType))
1124→ g.generateExpression(decl.Size)
1125→ g.write(")\n")
1126→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1127→ g.write(fmt.Sprintf("for _i := range %s { %s[_i] = make(chan %s) }\n", n, n, goType))
1128→ }
1129→ } else {
1130→ for _, name := range decl.Names {
1131→ g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
1132→ }
1133→ }
1134→}
1135→
1136→func (g *Generator) generateTimerDecl(decl *ast.TimerDecl) {
1137→ for _, name := range decl.Names {
1138→ g.writeLine(fmt.Sprintf("// TIMER %s", name))
1139→ }
1140→}
1141→
1142→func (g *Generator) generateTimerRead(tr *ast.TimerRead) {
1143→ g.writeLine(fmt.Sprintf("%s = int(time.Now().UnixMicro())", goIdent(tr.Variable)))
1144→}
1145→
1146→func (g *Generator) generateArrayDecl(decl *ast.ArrayDecl) {
1147→ goType := g.occamTypeToGo(decl.Type)
1148→ for _, name := range decl.Names {
1149→ n := goIdent(name)
1150→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1151→ g.write(fmt.Sprintf("%s := make([]%s, ", n, goType))
1152→ g.generateExpression(decl.Size)
1153→ g.write(")\n")
1154→ }
1155→}
1156→
1157→func (g *Generator) generateSend(send *ast.Send) {
1158→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1159→ g.write(goIdent(send.Channel))
1160→ if send.ChannelIndex != nil {
1161→ g.write("[")
1162→ g.generateExpression(send.ChannelIndex)
1163→ g.write("]")
1164→ }{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1155,
"limit": 70
} 1155→}
1156→
1157→func (g *Generator) generateSend(send *ast.Send) {
1158→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1159→ g.write(goIdent(send.Channel))
1160→ if send.ChannelIndex != nil {
1161→ g.write("[")
1162→ g.generateExpression(send.ChannelIndex)
1163→ g.write("]")
1164→ }
1165→ g.write(" <- ")
1166→
1167→ protoName := g.chanProtocols[send.Channel]
1168→ proto := g.protocolDefs[protoName]
1169→ gProtoName := goIdent(protoName)
1170→
1171→ if send.VariantTag != "" && proto != nil && proto.Kind == "variant" {
1172→ // Variant send with explicit tag: c <- _proto_NAME_tag{values...}
1173→ g.write(fmt.Sprintf("_proto_%s_%s{", gProtoName, goIdent(send.VariantTag)))
1174→ for i, val := range send.Values {
1175→ if i > 0 {
1176→ g.write(", ")
1177→ }
1178→ g.generateExpression(val)
1179→ }
1180→ g.write("}")
1181→ } else if proto != nil && proto.Kind == "variant" && send.Value != nil && len(send.Values) == 0 {
1182→ // Check if the send value is a bare identifier matching a variant tag
1183→ if ident, ok := send.Value.(*ast.Identifier); ok && g.isVariantTag(protoName, ident.Value) {
1184→ g.write(fmt.Sprintf("_proto_%s_%s{}", gProtoName, goIdent(ident.Value)))
1185→ } else {
1186→ g.generateExpression(send.Value)
1187→ }
1188→ } else if len(send.Values) > 0 && proto != nil && proto.Kind == "sequential" {
1189→ // Sequential send: c <- _proto_NAME{val1, val2, ...}
1190→ g.write(fmt.Sprintf("_proto_%s{", gProtoName))
1191→ g.generateExpression(send.Value)
1192→ for _, val := range send.Values {
1193→ g.write(", ")
1194→ g.generateExpression(val)
1195→ }
1196→ g.write("}")
1197→ } else {
1198→ // Simple send
1199→ g.generateExpression(send.Value)
1200→ }
1201→ g.write("\n")
1202→}
1203→
1204→func (g *Generator) generateReceive(recv *ast.Receive) {
1205→ chanRef := goIdent(recv.Channel)
1206→ if recv.ChannelIndex != nil {
1207→ var buf strings.Builder
1208→ buf.WriteString(goIdent(recv.Channel))
1209→ buf.WriteString("[")
1210→ // Generate the index expression into a temporary buffer
1211→ oldBuilder := g.builder
1212→ g.builder = strings.Builder{}
1213→ g.generateExpression(recv.ChannelIndex)
1214→ buf.WriteString(g.builder.String())
1215→ g.builder = oldBuilder
1216→ buf.WriteString("]")
1217→ chanRef = buf.String()
1218→ }
1219→
1220→ if len(recv.Variables) > 0 {
1221→ // Sequential receive: _tmpN := <-c; x = _tmpN._0; y = _tmpN._1
1222→ tmpName := fmt.Sprintf("_tmp%d", g.tmpCounter)
1223→ g.tmpCounter++
1224→ g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef)){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1290,
"limit": 70
} 1290→ }
1291→}
1292→
1293→func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) {
1294→ protoName := g.chanProtocols[vr.Channel]
1295→ gProtoName := goIdent(protoName)
1296→ chanRef := goIdent(vr.Channel)
1297→ if vr.ChannelIndex != nil {
1298→ var buf strings.Builder
1299→ buf.WriteString(goIdent(vr.Channel))
1300→ buf.WriteString("[")
1301→ oldBuilder := g.builder
1302→ g.builder = strings.Builder{}
1303→ g.generateExpression(vr.ChannelIndex)
1304→ buf.WriteString(g.builder.String())
1305→ g.builder = oldBuilder
1306→ buf.WriteString("]")
1307→ chanRef = buf.String()
1308→ }
1309→ g.writeLine(fmt.Sprintf("switch _v := (<-%s).(type) {", chanRef))
1310→ for _, vc := range vr.Cases {
1311→ g.writeLine(fmt.Sprintf("case _proto_%s_%s:", gProtoName, goIdent(vc.Tag)))
1312→ g.indent++
1313→ for i, v := range vc.Variables {
1314→ g.writeLine(fmt.Sprintf("%s = _v._%d", goIdent(v), i))
1315→ }
1316→ if vc.Body != nil {
1317→ g.generateStatement(vc.Body)
1318→ }
1319→ g.indent--
1320→ }
1321→ g.writeLine("}")
1322→}
1323→
1324→func (g *Generator) isVariantTag(protoName, tagName string) bool {
1325→ proto := g.protocolDefs[protoName]
1326→ if proto == nil {
1327→ return false
1328→ }
1329→ for _, v := range proto.Variants {
1330→ if v.Tag == tagName {
1331→ return true
1332→ }
1333→ }
1334→ return false
1335→}
1336→
1337→func (g *Generator) collectChanProtocols(stmt ast.Statement) {
1338→ switch s := stmt.(type) {
1339→ case *ast.ChanDecl:
1340→ if _, ok := g.protocolDefs[s.ElemType]; ok {
1341→ for _, name := range s.Names {
1342→ g.chanProtocols[name] = s.ElemType
1343→ }
1344→ }
1345→ case *ast.SeqBlock:
1346→ for _, inner := range s.Statements {
1347→ g.collectChanProtocols(inner)
1348→ }
1349→ case *ast.ParBlock:
1350→ for _, inner := range s.Statements {
1351→ g.collectChanProtocols(inner)
1352→ }
1353→ case *ast.ProcDecl:
1354→ // Register PROC param channels (including channel array params)
1355→ for _, p := range s.Params {
1356→ if p.IsChan || p.IsChanArray {
1357→ if _, ok := g.protocolDefs[p.ChanElemType]; ok {
1358→ g.chanProtocols[p.Name] = p.ChanElemType
1359→ }{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1340,
"limit": 40
} 1340→ if _, ok := g.protocolDefs[s.ElemType]; ok {
1341→ for _, name := range s.Names {
1342→ g.chanProtocols[name] = s.ElemType
1343→ }
1344→ }
1345→ case *ast.SeqBlock:
1346→ for _, inner := range s.Statements {
1347→ g.collectChanProtocols(inner)
1348→ }
1349→ case *ast.ParBlock:
1350→ for _, inner := range s.Statements {
1351→ g.collectChanProtocols(inner)
1352→ }
1353→ case *ast.ProcDecl:
1354→ // Register PROC param channels (including channel array params)
1355→ for _, p := range s.Params {
1356→ if p.IsChan || p.IsChanArray {
1357→ if _, ok := g.protocolDefs[p.ChanElemType]; ok {
1358→ g.chanProtocols[p.Name] = p.ChanElemType
1359→ }
1360→ }
1361→ }
1362→ for _, inner := range s.Body {
1363→ g.collectChanProtocols(inner)
1364→ }
1365→ case *ast.FuncDecl:
1366→ for _, inner := range s.Body {
1367→ g.collectChanProtocols(inner)
1368→ }
1369→ case *ast.WhileLoop:
1370→ for _, inner := range s.Body {
1371→ g.collectChanProtocols(inner)
1372→ }
1373→ case *ast.IfStatement:
1374→ for _, choice := range s.Choices {
1375→ if choice.NestedIf != nil {
1376→ g.collectChanProtocols(choice.NestedIf)
1377→ }
1378→ for _, inner := range choice.Body {
1379→ g.collectChanProtocols(inner){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1580,
"limit": 80
} 1580→}
1581→
1582→func (g *Generator) generateAssignment(assign *ast.Assignment) {
1583→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1584→
1585→ if assign.SliceTarget != nil {
1586→ // Slice assignment: [arr FROM start FOR length] := value
1587→ // Maps to: copy(arr[start : start + length], value)
1588→ g.write("copy(")
1589→ g.generateExpression(assign.SliceTarget.Array)
1590→ g.write("[")
1591→ g.generateExpression(assign.SliceTarget.Start)
1592→ g.write(" : ")
1593→ g.generateExpression(assign.SliceTarget.Start)
1594→ g.write(" + ")
1595→ g.generateExpression(assign.SliceTarget.Length)
1596→ g.write("], ")
1597→ g.generateExpression(assign.Value)
1598→ g.write(")\n")
1599→ return
1600→ }
1601→
1602→ if assign.Index != nil {
1603→ // Check if this is a record field access
1604→ if _, ok := g.recordVars[assign.Name]; ok {
1605→ if ident, ok := assign.Index.(*ast.Identifier); ok {
1606→ // Record field: p.x = value (Go auto-dereferences pointers)
1607→ g.write(goIdent(assign.Name))
1608→ g.write(".")
1609→ g.write(goIdent(ident.Value))
1610→ g.write(" = ")
1611→ g.generateExpression(assign.Value)
1612→ g.write("\n")
1613→ return
1614→ }
1615→ }
1616→ // Array index: dereference if ref param
1617→ if g.refParams[assign.Name] {
1618→ g.write("*")
1619→ }
1620→ g.write(goIdent(assign.Name))
1621→ g.write("[")
1622→ g.generateExpression(assign.Index)
1623→ g.write("]")
1624→ } else {
1625→ // Simple assignment: dereference if ref param
1626→ if g.refParams[assign.Name] {
1627→ g.write("*")
1628→ }
1629→ g.write(goIdent(assign.Name))
1630→ }
1631→ g.write(" = ")
1632→ g.generateExpression(assign.Value)
1633→ g.write("\n")
1634→}
1635→
1636→func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) {
1637→ if seq.Replicator != nil {
1638→ if seq.Replicator.Step != nil {
1639→ // Replicated SEQ with STEP: counter-based loop
1640→ v := goIdent(seq.Replicator.Variable)
1641→ counter := "_repl_" + v
1642→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1643→ g.write(fmt.Sprintf("for %s := 0; %s < ", counter, counter))
1644→ g.generateExpression(seq.Replicator.Count)
1645→ g.write(fmt.Sprintf("; %s++ {\n", counter))
1646→ g.indent++
1647→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1648→ g.write(fmt.Sprintf("%s := ", v))
1649→ g.generateExpression(seq.Replicator.Start)
1650→ g.write(fmt.Sprintf(" + %s * ", counter))
1651→ g.generateExpression(seq.Replicator.Step)
1652→ g.write("\n")
1653→ } else {
1654→ // Replicated SEQ: SEQ i = start FOR count becomes a for loop
1655→ v := goIdent(seq.Replicator.Variable)
1656→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1657→ g.write(fmt.Sprintf("for %s := ", v))
1658→ g.generateExpression(seq.Replicator.Start)
1659→ g.write(fmt.Sprintf("; %s < ", v)){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1780,
"limit": 40
} 1780→ }
1781→
1782→ g.writeLine("select {")
1783→ for i, c := range alt.Cases {
1784→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1785→ if c.IsSkip {
1786→ g.write("default:\n")
1787→ } else if c.IsTimer {
1788→ g.write("case <-time.After(time.Duration(")
1789→ g.generateExpression(c.Deadline)
1790→ g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
1791→ } else if c.Guard != nil {
1792→ g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
1793→ } else if c.ChannelIndex != nil {
1794→ g.write(fmt.Sprintf("case %s = <-%s[", goIdent(c.Variable), goIdent(c.Channel)))
1795→ g.generateExpression(c.ChannelIndex)
1796→ g.write("]:\n")
1797→ } else {
1798→ g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
1799→ }
1800→ g.indent++
1801→ for _, s := range c.Body {
1802→ g.generateStatement(s)
1803→ }
1804→ g.indent--
1805→ }
1806→ g.writeLine("}")
1807→}
1808→
1809→func (g *Generator) generateReplicatedAlt(alt *ast.AltBlock) {
1810→ // Replicated ALT: ALT i = start FOR count
1811→ // Uses reflect.Select for runtime-variable case count
1812→ if len(alt.Cases) == 0 {
1813→ return
1814→ }
1815→ c := alt.Cases[0]
1816→ rep := alt.Replicator
1817→ v := goIdent(rep.Variable)
1818→
1819→ // Determine receive type from scoped declarations{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1860,
"limit": 100
} 1860→ g.write(" + _altI\n")
1861→ }
1862→
1863→ // Generate scoped abbreviations (needed for channel index computation)
1864→ for _, decl := range c.Declarations {
1865→ if abbr, ok := decl.(*ast.Abbreviation); ok {
1866→ g.generateAbbreviation(abbr)
1867→ }
1868→ }
1869→
1870→ // Build select case entry
1871→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1872→ g.write("_altCases[_altI] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(")
1873→ if c.ChannelIndex != nil {
1874→ g.write(goIdent(c.Channel) + "[")
1875→ g.generateExpression(c.ChannelIndex)
1876→ g.write("]")
1877→ } else {
1878→ g.write(goIdent(c.Channel))
1879→ }
1880→ g.write(")}\n")
1881→
1882→ g.indent--
1883→ g.writeLine("}")
1884→
1885→ // Call reflect.Select
1886→ g.writeLine("_altChosen, _altValue, _ := reflect.Select(_altCases)")
1887→
1888→ // Recompute replicator variable from chosen index
1889→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1890→ if rep.Step != nil {
1891→ g.write(fmt.Sprintf("%s := ", v))
1892→ g.generateExpression(rep.Start)
1893→ g.write(" + _altChosen * (")
1894→ g.generateExpression(rep.Step)
1895→ g.write(")\n")
1896→ } else {
1897→ g.write(fmt.Sprintf("%s := ", v))
1898→ g.generateExpression(rep.Start)
1899→ g.write(" + _altChosen\n")
1900→ }
1901→ g.writeLine(fmt.Sprintf("_ = %s", v))
1902→
1903→ // Generate scoped var declarations
1904→ for _, decl := range c.Declarations {
1905→ if vd, ok := decl.(*ast.VarDecl); ok {
1906→ g.generateVarDecl(vd)
1907→ }
1908→ }
1909→
1910→ // Generate scoped abbreviations
1911→ for _, decl := range c.Declarations {
1912→ if abbr, ok := decl.(*ast.Abbreviation); ok {
1913→ g.generateAbbreviation(abbr)
1914→ }
1915→ }
1916→
1917→ // Assign received value from reflect.Value
1918→ g.writeLine(fmt.Sprintf("%s = _altValue.Interface().(%s)", goIdent(c.Variable), recvType))
1919→
1920→ // Generate body
1921→ for _, s := range c.Body {
1922→ g.generateStatement(s)
1923→ }
1924→
1925→ g.indent--
1926→ g.writeLine("}")
1927→}
1928→
1929→func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
1930→ // Track reference parameters for this procedure
1931→ oldRefParams := g.refParams
1932→ newRefParams := make(map[string]bool)
1933→ // Scope boolVars per proc body
1934→ oldBoolVars := g.boolVars
1935→ newBoolVars := make(map[string]bool)
1936→ // Inherit parent's ref params and boolVars for closure captures when nested
1937→ if g.nestingLevel > 0 {
1938→ for k, v := range oldRefParams {
1939→ newRefParams[k] = v
1940→ }
1941→ for k, v := range oldBoolVars {
1942→ newBoolVars[k] = v
1943→ }
1944→ }
1945→ for _, p := range proc.Params {
1946→ if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
1947→ newRefParams[p.Name] = true
1948→ } else {
1949→ // Own param shadows any inherited ref param with same name
1950→ delete(newRefParams, p.Name)
1951→ }
1952→ // Track BOOL params; delete non-BOOL params that shadow inherited names
1953→ if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
1954→ newBoolVars[p.Name] = true
1955→ } else {
1956→ delete(newBoolVars, p.Name)
1957→ }
1958→ // Register chan params with protocol mappings
1959→ if p.IsChan || p.IsChanArray {{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2030,
"limit": 180
} 2030→ // Restore previous context
2031→ g.refParams = oldRefParams
2032→ g.boolVars = oldBoolVars
2033→ g.retypesRenames = oldRenames
2034→}
2035→
2036→func (g *Generator) generateProcParams(params []ast.ProcParam) string {
2037→ var parts []string
2038→ for _, p := range params {
2039→ var goType string
2040→ if p.IsChanArray {
2041→ goType = "[]chan " + g.occamTypeToGo(p.ChanElemType)
2042→ } else if p.IsChan {
2043→ goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
2044→ } else if p.IsOpenArray {
2045→ goType = "[]" + g.occamTypeToGo(p.Type)
2046→ } else if p.ArraySize != "" {
2047→ // Fixed-size array parameter: use slice for Go compatibility
2048→ // (occam [n]TYPE and []TYPE both map to Go slices)
2049→ goType = "[]" + g.occamTypeToGo(p.Type)
2050→ } else {
2051→ goType = g.occamTypeToGo(p.Type)
2052→ if !p.IsVal {
2053→ // Non-VAL parameters are pass by reference in Occam
2054→ goType = "*" + goType
2055→ }
2056→ }
2057→ pName := goIdent(p.Name)
2058→ if renamed, ok := g.retypesRenames[p.Name]; ok {
2059→ pName = renamed
2060→ }
2061→ parts = append(parts, fmt.Sprintf("%s %s", pName, goType))
2062→ }
2063→ return strings.Join(parts, ", ")
2064→}
2065→
2066→func chanDirPrefix(dir string) string {
2067→ switch dir {
2068→ case "?":
2069→ return "<-chan " // input/receive-only
2070→ case "!":
2071→ return "chan<- " // output/send-only
2072→ default:
2073→ return "chan " // bidirectional
2074→ }
2075→}
2076→
2077→func (g *Generator) generateProcCall(call *ast.ProcCall) {
2078→ // Handle built-in print procedures
2079→ if printBuiltins[call.Name] {
2080→ g.generatePrintCall(call)
2081→ return
2082→ }
2083→
2084→ // Handle CAUSEERROR
2085→ if call.Name == "CAUSEERROR" {
2086→ g.writeLine(`panic("CAUSEERROR")`)
2087→ return
2088→ }
2089→
2090→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2091→ g.write(goIdent(call.Name))
2092→ g.write("(")
2093→
2094→ // Look up procedure signature to determine which args need address-of
2095→ params := g.procSigs[call.Name]
2096→
2097→ for i, arg := range call.Args {
2098→ if i > 0 {
2099→ g.write(", ")
2100→ }
2101→ // If this parameter is not VAL (i.e., pass by reference), take address
2102→ // Channels, channel arrays, open arrays, and fixed-size arrays (mapped to slices) are already reference types
2103→ if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray && params[i].ArraySize == "" {
2104→ g.write("&")
2105→ }
2106→ // Wrap string literals with []byte() when passed to []BYTE parameters
2107→ if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {
2108→ g.write("[]byte(")
2109→ g.generateExpression(arg)
2110→ g.write(")")
2111→ } else {
2112→ g.generateExpression(arg)
2113→ }
2114→ }
2115→ g.write(")")
2116→ g.write("\n")
2117→}
2118→
2119→func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
2120→ params := g.generateProcParams(fn.Params)
2121→
2122→ // Build return type string
2123→ var returnTypeStr string
2124→ if len(fn.ReturnTypes) == 1 {
2125→ returnTypeStr = g.occamTypeToGo(fn.ReturnTypes[0])
2126→ } else {
2127→ goTypes := make([]string, len(fn.ReturnTypes))
2128→ for i, rt := range fn.ReturnTypes {
2129→ goTypes[i] = g.occamTypeToGo(rt)
2130→ }
2131→ returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
2132→ }
2133→
2134→ // Scope boolVars per function body
2135→ oldBoolVars := g.boolVars
2136→ newBoolVars := make(map[string]bool)
2137→ if g.nestingLevel > 0 {
2138→ for k, v := range oldBoolVars {
2139→ newBoolVars[k] = v
2140→ }
2141→ }
2142→ for _, p := range fn.Params {
2143→ if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
2144→ newBoolVars[p.Name] = true
2145→ } else {
2146→ delete(newBoolVars, p.Name)
2147→ }
2148→ }
2149→ g.boolVars = newBoolVars
2150→
2151→ gName := goIdent(fn.Name)
2152→ if g.nestingLevel > 0 {
2153→ // Nested FUNCTION: generate as Go closure
2154→ g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr))
2155→ } else {
2156→ g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr))
2157→ }
2158→ g.indent++
2159→ g.nestingLevel++
2160→
2161→ for _, stmt := range fn.Body {
2162→ g.generateStatement(stmt)
2163→ }
2164→
2165→ if len(fn.ResultExprs) > 0 {
2166→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2167→ g.write("return ")
2168→ for i, expr := range fn.ResultExprs {
2169→ if i > 0 {
2170→ g.write(", ")
2171→ }
2172→ g.generateExpression(expr)
2173→ }
2174→ g.write("\n")
2175→ }
2176→
2177→ g.nestingLevel--
2178→ g.indent--
2179→ g.writeLine("}")
2180→ g.writeLine("")
2181→
2182→ // Restore previous boolVars
2183→ g.boolVars = oldBoolVars
2184→}
2185→
2186→func (g *Generator) generateFuncCallExpr(call *ast.FuncCall) {
2187→ if transpIntrinsics[call.Name] {
2188→ g.write("_" + call.Name)
2189→ } else {
2190→ g.write(goIdent(call.Name))
2191→ }
2192→ g.write("(")
2193→ params := g.procSigs[call.Name]
2194→ for i, arg := range call.Args {
2195→ if i > 0 {
2196→ g.write(", ")
2197→ }
2198→ // Wrap string literals with []byte() when passed to []BYTE parameters
2199→ if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {
2200→ g.write("[]byte(")
2201→ g.generateExpression(arg)
2202→ g.write(")")
2203→ } else {
2204→ g.generateExpression(arg)
2205→ }
2206→ }
2207→ g.write(")")
2208→}
2209→{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2209,
"limit": 60
} 2209→
2210→func (g *Generator) generateMultiAssignment(stmt *ast.MultiAssignment) {
2211→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2212→ for i, target := range stmt.Targets {
2213→ if i > 0 {
2214→ g.write(", ")
2215→ }
2216→ if target.Index != nil {
2217→ // Check if this is a record field access
2218→ if _, ok := g.recordVars[target.Name]; ok {
2219→ if ident, ok := target.Index.(*ast.Identifier); ok {
2220→ g.write(goIdent(target.Name))
2221→ g.write(".")
2222→ g.write(goIdent(ident.Value))
2223→ continue
2224→ }
2225→ }
2226→ if g.refParams[target.Name] {
2227→ g.write("(*")
2228→ g.write(goIdent(target.Name))
2229→ g.write(")")
2230→ } else {
2231→ g.write(goIdent(target.Name))
2232→ }
2233→ g.write("[")
2234→ g.generateExpression(target.Index)
2235→ g.write("]")
2236→ } else {
2237→ if g.refParams[target.Name] {
2238→ g.write("*")
2239→ }
2240→ g.write(goIdent(target.Name))
2241→ }
2242→ }
2243→ g.write(" = ")
2244→ for i, val := range stmt.Values {
2245→ if i > 0 {
2246→ g.write(", ")
2247→ }
2248→ g.generateExpression(val)
2249→ }
2250→ g.write("\n")
2251→}
2252→
2253→func (g *Generator) generatePrintCall(call *ast.ProcCall) {
2254→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2255→
2256→ switch call.Name {
2257→ case "print.int", "print.string", "print.bool":
2258→ g.write("fmt.Println(")
2259→ if len(call.Args) > 0 {
2260→ g.generateExpression(call.Args[0])
2261→ }
2262→ g.write(")")
2263→ case "print.newline":
2264→ g.write("fmt.Println()")
2265→ }
2266→
2267→ g.write("\n")
2268→}{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2865,
"limit": 20
} 2865→
2866→// walkStatements recursively walks a statement tree, applying fn to all expressions.
2867→// Returns true if fn returns true for any expression.
2868→func (g *Generator) walkStatements(stmt ast.Statement, fn func(ast.Expression) bool) bool {
2869→ switch s := stmt.(type) {
2870→ case *ast.Assignment:
2871→ return g.walkExpr(s.Value, fn) || g.walkExpr(s.Index, fn)
2872→ case *ast.MultiAssignment:
2873→ for _, v := range s.Values {
2874→ if g.walkExpr(v, fn) {
2875→ return true
2876→ }
2877→ }
2878→ case *ast.Abbreviation:
2879→ return g.walkExpr(s.Value, fn)
2880→ case *ast.SeqBlock:
2881→ for _, inner := range s.Statements {
2882→ if g.walkStatements(inner, fn) {
2883→ return true
2884→ }Now I have all the context. Let me make the codegen.go edits systematically. I'll start from top to bottom:
case *ast.Assignment:
return g.exprNeedsMath(s.Value) || g.exprNeedsMath(s.Index)
case *ast.MultiAssignment:
for _, t := range s.Targets {
if g.exprNeedsMath(t.Index) {
return true
}
} case *ast.Assignment:
result := g.exprNeedsMath(s.Value)
for _, idx := range s.Indices {
result = result || g.exprNeedsMath(idx)
}
return result
case *ast.MultiAssignment:
for _, t := range s.Targets {
for _, idx := range t.Indices {
if g.exprNeedsMath(idx) {
return true
}
}
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
if decl.IsArray {
for _, name := range decl.Names {
n := goIdent(name)
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s := make([]chan %s, ", n, goType))
g.generateExpression(decl.Size)
g.write(")\n")
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for _i := range %s { %s[_i] = make(chan %s) }\n", n, n, goType))
}
} else {
for _, name := range decl.Names {
g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
}
}
}func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
if len(decl.Sizes) > 0 {
for _, name := range decl.Names {
n := goIdent(name)
g.generateMultiDimChanInit(n, goType, decl.Sizes, 0)
}
} else {
for _, name := range decl.Names {
g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType))
}
}
}
// generateMultiDimChanInit generates nested make+init loops for multi-dimensional channel arrays.
// For [w][h]CHAN OF INT link: generates:
//
// link := make([][]chan int, w)
// for _i0 := range link { link[_i0] = make([]chan int, h)
// for _i1 := range link[_i0] { link[_i0][_i1] = make(chan int) }
// }
func (g *Generator) generateMultiDimChanInit(name, goType string, sizes []ast.Expression, depth int) {
if depth == 0 {
// Top-level: name := make([]...[]chan goType, sizes[0])
sliceType := strings.Repeat("[]", len(sizes)) + "chan " + goType
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s := make(%s, ", name, sliceType))
g.generateExpression(sizes[0])
g.write(")\n")
if len(sizes) == 1 {
// Single dim: init each channel
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for _i0 := range %s { %s[_i0] = make(chan %s) }\n", name, name, goType))
} else {
// Multi dim: recurse
ivar := "_i0"
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := range %s {\n", ivar, name))
g.indent++
g.generateMultiDimChanInit(name+"["+ivar+"]", goType, sizes, 1)
g.indent--
g.writeLine("}")
}
} else if depth < len(sizes)-1 {
// Middle dimension: allocate sub-slice
sliceType := strings.Repeat("[]", len(sizes)-depth) + "chan " + goType
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s = make(%s, ", name, sliceType))
g.generateExpression(sizes[depth])
g.write(")\n")
ivar := fmt.Sprintf("_i%d", depth)
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := range %s {\n", ivar, name))
g.indent++
g.generateMultiDimChanInit(name+"["+ivar+"]", goType, sizes, depth+1)
g.indent--
g.writeLine("}")
} else {
// Innermost dimension: allocate sub-slice + init channels
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s = make([]chan %s, ", name, goType))
g.generateExpression(sizes[depth])
g.write(")\n")
ivar := fmt.Sprintf("_i%d", depth)
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := range %s { %s[%s] = make(chan %s) }\n", ivar, name, name, ivar, goType))
}
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateArrayDecl(decl *ast.ArrayDecl) {
goType := g.occamTypeToGo(decl.Type)
for _, name := range decl.Names {
n := goIdent(name)
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s := make([]%s, ", n, goType))
g.generateExpression(decl.Size)
g.write(")\n")
}
}func (g *Generator) generateArrayDecl(decl *ast.ArrayDecl) {
goType := g.occamTypeToGo(decl.Type)
for _, name := range decl.Names {
n := goIdent(name)
if len(decl.Sizes) == 1 {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s := make([]%s, ", n, goType))
g.generateExpression(decl.Sizes[0])
g.write(")\n")
} else {
g.generateMultiDimArrayInit(n, goType, decl.Sizes, 0)
}
}
}
// generateMultiDimArrayInit generates nested make+init loops for multi-dimensional arrays.
// For [5][3]INT arr: generates:
//
// arr := make([][]int, 5)
// for _i0 := range arr { arr[_i0] = make([]int, 3) }
func (g *Generator) generateMultiDimArrayInit(name, goType string, sizes []ast.Expression, depth int) {
if depth == 0 {
sliceType := strings.Repeat("[]", len(sizes)) + goType
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s := make(%s, ", name, sliceType))
g.generateExpression(sizes[0])
g.write(")\n")
if len(sizes) > 1 {
ivar := "_i0"
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := range %s {\n", ivar, name))
g.indent++
g.generateMultiDimArrayInit(name+"["+ivar+"]", goType, sizes, 1)
g.indent--
g.writeLine("}")
}
} else if depth < len(sizes)-1 {
sliceType := strings.Repeat("[]", len(sizes)-depth) + goType
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s = make(%s, ", name, sliceType))
g.generateExpression(sizes[depth])
g.write(")\n")
ivar := fmt.Sprintf("_i%d", depth)
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := range %s {\n", ivar, name))
g.indent++
g.generateMultiDimArrayInit(name+"["+ivar+"]", goType, sizes, depth+1)
g.indent--
g.writeLine("}")
} else {
// Innermost dimension
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("%s = make([]%s, ", name, goType))
g.generateExpression(sizes[depth])
g.write(")\n")
}
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateSend(send *ast.Send) {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(goIdent(send.Channel))
if send.ChannelIndex != nil {
g.write("[")
g.generateExpression(send.ChannelIndex)
g.write("]")
}func (g *Generator) generateSend(send *ast.Send) {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(goIdent(send.Channel))
g.generateIndices(send.ChannelIndices)The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
I need to add the generateIndices helper. Let me find a good place for it - right before generateSend:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1205,
"limit": 5
} 1205→ n := goIdent(name)
1206→ if len(decl.Sizes) == 1 {
1207→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1208→ g.write(fmt.Sprintf("%s := make([]%s, ", n, goType))
1209→ g.generateExpression(decl.Sizes[0]){
"pattern": "^func \\(g \\*Generator\\) generateSend",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content"
}{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1256,
"limit": 6
} 1256→ g.write(")\n")
1257→ }
1258→}
1259→
1260→func (g *Generator) generateSend(send *ast.Send) {
1261→ g.builder.WriteString(strings.Repeat("\t", g.indent))func (g *Generator) generateSend(send *ast.Send) {// generateIndices emits [idx1][idx2]... for multi-dimensional index access.
func (g *Generator) generateIndices(indices []ast.Expression) {
for _, idx := range indices {
g.write("[")
g.generateExpression(idx)
g.write("]")
}
}
// generateIndicesStr generates indices into a buffer and returns the string.
func (g *Generator) generateIndicesStr(indices []ast.Expression) string {
var buf strings.Builder
for _, idx := range indices {
buf.WriteString("[")
oldBuilder := g.builder
g.builder = strings.Builder{}
g.generateExpression(idx)
buf.WriteString(g.builder.String())
g.builder = oldBuilder
buf.WriteString("]")
}
return buf.String()
}
func (g *Generator) generateSend(send *ast.Send) {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateReceive(recv *ast.Receive) {
chanRef := goIdent(recv.Channel)
if recv.ChannelIndex != nil {
var buf strings.Builder
buf.WriteString(goIdent(recv.Channel))
buf.WriteString("[")
// Generate the index expression into a temporary buffer
oldBuilder := g.builder
g.builder = strings.Builder{}
g.generateExpression(recv.ChannelIndex)
buf.WriteString(g.builder.String())
g.builder = oldBuilder
buf.WriteString("]")
chanRef = buf.String()
}func (g *Generator) generateReceive(recv *ast.Receive) {
chanRef := goIdent(recv.Channel)
if len(recv.ChannelIndices) > 0 {
chanRef += g.generateIndicesStr(recv.ChannelIndices)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) {
protoName := g.chanProtocols[vr.Channel]
gProtoName := goIdent(protoName)
chanRef := goIdent(vr.Channel)
if vr.ChannelIndex != nil {
var buf strings.Builder
buf.WriteString(goIdent(vr.Channel))
buf.WriteString("[")
oldBuilder := g.builder
g.builder = strings.Builder{}
g.generateExpression(vr.ChannelIndex)
buf.WriteString(g.builder.String())
g.builder = oldBuilder
buf.WriteString("]")
chanRef = buf.String()
}func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) {
protoName := g.chanProtocols[vr.Channel]
gProtoName := goIdent(protoName)
chanRef := goIdent(vr.Channel)
if len(vr.ChannelIndices) > 0 {
chanRef += g.generateIndicesStr(vr.ChannelIndices)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if p.IsChan || p.IsChanArray {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
g.chanProtocols[p.Name] = p.ChanElemType
} if p.IsChan || p.ChanArrayDims > 0 {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
g.chanProtocols[p.Name] = p.ChanElemType
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if assign.Index != nil {
// Check if this is a record field access
if _, ok := g.recordVars[assign.Name]; ok {
if ident, ok := assign.Index.(*ast.Identifier); ok {
// Record field: p.x = value (Go auto-dereferences pointers)
g.write(goIdent(assign.Name))
g.write(".")
g.write(goIdent(ident.Value))
g.write(" = ")
g.generateExpression(assign.Value)
g.write("\n")
return
}
}
// Array index: dereference if ref param
if g.refParams[assign.Name] {
g.write("*")
}
g.write(goIdent(assign.Name))
g.write("[")
g.generateExpression(assign.Index)
g.write("]")
} else { if len(assign.Indices) > 0 {
// Check if this is a record field access (single index that is an identifier)
if len(assign.Indices) == 1 {
if _, ok := g.recordVars[assign.Name]; ok {
if ident, ok := assign.Indices[0].(*ast.Identifier); ok {
// Record field: p.x = value (Go auto-dereferences pointers)
g.write(goIdent(assign.Name))
g.write(".")
g.write(goIdent(ident.Value))
g.write(" = ")
g.generateExpression(assign.Value)
g.write("\n")
return
}
}
}
// Array index: dereference if ref param
if g.refParams[assign.Name] {
g.write("*")
}
g.write(goIdent(assign.Name))
g.generateIndices(assign.Indices)
} else {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"pattern": "c\\.ChannelIndex",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}1897: } else if c.ChannelIndex != nil {
1899: g.generateExpression(c.ChannelIndex)
1977: if c.ChannelIndex != nil {
1979: g.generateExpression(c.ChannelIndex){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1893,
"limit": 12
} 1893→ g.generateExpression(c.Deadline)
1894→ g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n")
1895→ } else if c.Guard != nil {
1896→ g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i))
1897→ } else if c.ChannelIndex != nil {
1898→ g.write(fmt.Sprintf("case %s = <-%s[", goIdent(c.Variable), goIdent(c.Channel)))
1899→ g.generateExpression(c.ChannelIndex)
1900→ g.write("]:\n")
1901→ } else {
1902→ g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel)))
1903→ }
1904→ g.indent++ } else if c.ChannelIndex != nil {
g.write(fmt.Sprintf("case %s = <-%s[", goIdent(c.Variable), goIdent(c.Channel)))
g.generateExpression(c.ChannelIndex)
g.write("]:\n")
} else { } 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 {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1973,
"limit": 12
} 1973→
1974→ // Build select case entry
1975→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1976→ g.write("_altCases[_altI] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(")
1977→ if c.ChannelIndex != nil {
1978→ g.write(goIdent(c.Channel) + "[")
1979→ g.generateExpression(c.ChannelIndex)
1980→ g.write("]")
1981→ } else {
1982→ g.write(goIdent(c.Channel))
1983→ }
1984→ g.write(")}\n") if c.ChannelIndex != nil {
g.write(goIdent(c.Channel) + "[")
g.generateExpression(c.ChannelIndex)
g.write("]")
} else {
g.write(goIdent(c.Channel))
} if len(c.ChannelIndices) > 0 {
g.write(goIdent(c.Channel))
g.generateIndices(c.ChannelIndices)
} else {
g.write(goIdent(c.Channel))
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
newRefParams[p.Name] = true
} else {
// Own param shadows any inherited ref param with same name
delete(newRefParams, p.Name)
}
// Track BOOL params; delete non-BOOL params that shadow inherited names
if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}
// Register chan params with protocol mappings
if p.IsChan || p.IsChanArray { if !p.IsVal && !p.IsChan && p.ChanArrayDims == 0 && p.OpenArrayDims == 0 && p.ArraySize == "" {
newRefParams[p.Name] = true
} else {
// Own param shadows any inherited ref param with same name
delete(newRefParams, p.Name)
}
// Track BOOL params; delete non-BOOL params that shadow inherited names
if p.Type == "BOOL" && !p.IsChan && p.ChanArrayDims == 0 {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}
// Register chan params with protocol mappings
if p.IsChan || p.ChanArrayDims > 0 {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if p.IsChanArray {
goType = "[]chan " + g.occamTypeToGo(p.ChanElemType)
} else if p.IsChan {
goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
} else if p.IsOpenArray {
goType = "[]" + g.occamTypeToGo(p.Type) if p.ChanArrayDims > 0 {
goType = strings.Repeat("[]", p.ChanArrayDims) + "chan " + g.occamTypeToGo(p.ChanElemType)
} else if p.IsChan {
goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
} else if p.OpenArrayDims > 0 {
goType = strings.Repeat("[]", p.OpenArrayDims) + g.occamTypeToGo(p.Type)The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray && params[i].ArraySize == "" {
g.write("&")
}
// Wrap string literals with []byte() when passed to []BYTE parameters
if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" { if i < len(params) && !params[i].IsVal && !params[i].IsChan && params[i].ChanArrayDims == 0 && params[i].OpenArrayDims == 0 && params[i].ArraySize == "" {
g.write("&")
}
// Wrap string literals with []byte() when passed to []BYTE parameters
if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].OpenArrayDims > 0 && params[i].Type == "BYTE" {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}
}
g.boolVars = newBoolVars
gName := goIdent(fn.Name) if p.Type == "BOOL" && !p.IsChan && p.ChanArrayDims == 0 {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}
}
g.boolVars = newBoolVars
gName := goIdent(fn.Name)The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" { if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].OpenArrayDims > 0 && params[i].Type == "BYTE" {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if target.Index != nil {
// Check if this is a record field access
if _, ok := g.recordVars[target.Name]; ok {
if ident, ok := target.Index.(*ast.Identifier); ok {
g.write(goIdent(target.Name))
g.write(".")
g.write(goIdent(ident.Value))
continue
}
}
if g.refParams[target.Name] {
g.write("(*")
g.write(goIdent(target.Name))
g.write(")")
} else {
g.write(goIdent(target.Name))
}
g.write("[")
g.generateExpression(target.Index)
g.write("]")
} else { if len(target.Indices) > 0 {
// Check if this is a record field access (single index that is an identifier)
if len(target.Indices) == 1 {
if _, ok := g.recordVars[target.Name]; ok {
if ident, ok := target.Indices[0].(*ast.Identifier); ok {
g.write(goIdent(target.Name))
g.write(".")
g.write(goIdent(ident.Value))
continue
}
}
}
if g.refParams[target.Name] {
g.write("(*")
g.write(goIdent(target.Name))
g.write(")")
} else {
g.write(goIdent(target.Name))
}
g.generateIndices(target.Indices)
} else {The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
case *ast.Assignment: return g.walkExpr(s.Value, fn) || g.walkExpr(s.Index, fn)
case *ast.Assignment:
result := g.walkExpr(s.Value, fn)
for _, idx := range s.Indices {
result = result || g.walkExpr(idx, fn)
}
return resultThe file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
Now let me check if there are any remaining references to the old field names in codegen.go:
{
"pattern": "\\.ChannelIndex\\b|\\.IsChanArray\\b|\\.IsOpenArray\\b|\\.IsArray\\b",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}282: if abbr.IsOpenArray {
288: if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
1101: if abbr.IsOpenArray {
1109: if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
2774: if r.IsArray {These are all on Abbreviation.IsOpenArray (which we're NOT changing) and RetypesDecl.IsArray (which we're NOT changing). Good.
{
"pattern": "\\.Index\\b",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}942: return g.exprNeedsMath(e.Left) || g.exprNeedsMath(e.Index)
2628: if field, ok := e.Index.(*ast.Identifier); ok {
2638: g.generateExpression(e.Index)
3103: return g.walkExpr(e.Left, fn) || g.walkExpr(e.Index, fn)All of these are IndexExpr.Index references, which we're NOT changing.
Now let me check parser.go for any remaining old field names:
{
"pattern": "\\.ChannelIndex\\b|\\.IsChanArray\\b|\\.IsOpenArray\\b",
"path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"output_mode": "content",
"-n": true
}{
"pattern": "\\.Size\\b|\\.IsArray\\b",
"path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"output_mode": "content",
"-n": true
}{
"pattern": "\\.Index\\b",
"path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"output_mode": "content",
"-n": true
}That's IndexExpr.Index - correct, not changed. Parser.go is clean. Now let me try to build to catch any remaining issues:
The project compiles. Let me now run the tests to see if the mechanical renames broke anything:
# github.com/codeassociates/occam2go/parser [github.com/codeassociates/occam2go/parser.test]
parser/parser_test.go:606:22: decl.Size undefined (type *ast.ArrayDecl has no field or method Size)
parser/parser_test.go:608:61: decl.Size undefined (type *ast.ArrayDecl has no field or method Size)
parser/parser_test.go:672:12: assign.Index undefined (type *ast.Assignment has no field or method Index)
parser/parser_test.go:676:25: assign.Index undefined (type *ast.Assignment has no field or method Index)
parser/parser_test.go:678:64: assign.Index undefined (type *ast.Assignment has no field or method Index)
parser/parser_test.go:962:19: ma.Targets[0].Index undefined (type ast.MultiAssignTarget has no field or method Index)
parser/parser_test.go:962:49: ma.Targets[1].Index undefined (type ast.MultiAssignTarget has no field or method Index)
parser/parser_test.go:1003:19: ma.Targets[0].Index undefined (type ast.MultiAssignTarget has no field or method Index)
parser/parser_test.go:1003:49: ma.Targets[1].Index undefined (type ast.MultiAssignTarget has no field or method Index)
parser/parser_test.go:1032:48: ma.Targets[0].Index undefined (type ast.MultiAssignTarget has no field or method Index)
parser/parser_test.go:1032:48: too many errors
? github.com/codeassociates/occam2go [no test files]
? github.com/codeassociates/occam2go/ast [no test files]
--- FAIL: TestChanArrayDeclGen (0.00s)
codegen_test.go:591: expected init loop in output, got:
package main
func main() {
cs := make([]chan int, 5)
for _i0 := range cs { cs[_i0] = make(chan int) }
}
FAIL
FAIL github.com/codeassociates/occam2go/codegen 18.328s
ok github.com/codeassociates/occam2go/lexer (cached)
ok github.com/codeassociates/occam2go/modgen (cached)
FAIL github.com/codeassociates/occam2go/parser [build failed]
ok github.com/codeassociates/occam2go/preproc (cached)
FAILTwo issues:
1. Parser tests need updating (expected - old field names)
2. One codegen test failure - the channel init loop changed from _i to _i0
Let me fix the test files. First the parser tests:
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"offset": 600,
"limit": 20
} 600→ }
601→
602→ if decl.Type != "INT" {
603→ t.Errorf("expected type INT, got %s", decl.Type)
604→ }
605→
606→ sizeLit, ok := decl.Size.(*ast.IntegerLiteral)
607→ if !ok {
608→ t.Fatalf("expected IntegerLiteral for size, got %T", decl.Size)
609→ }
610→ if sizeLit.Value != 5 {
611→ t.Errorf("expected size 5, got %d", sizeLit.Value)
612→ }
613→
614→ if len(decl.Names) != 1 || decl.Names[0] != "arr" {
615→ t.Errorf("expected name 'arr', got %v", decl.Names)
616→ }
617→}
618→
619→func TestArrayDeclMultipleNames(t *testing.T) { sizeLit, ok := decl.Size.(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for size, got %T", decl.Size)
}
if sizeLit.Value != 5 {
t.Errorf("expected size 5, got %d", sizeLit.Value)
}
if len(decl.Names) != 1 || decl.Names[0] != "arr" {
t.Errorf("expected name 'arr', got %v", decl.Names)
}
}
func TestArrayDeclMultipleNames(t *testing.T) { if len(decl.Sizes) != 1 {
t.Fatalf("expected 1 size dimension, got %d", len(decl.Sizes))
}
sizeLit, ok := decl.Sizes[0].(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for size, got %T", decl.Sizes[0])
}
if sizeLit.Value != 5 {
t.Errorf("expected size 5, got %d", sizeLit.Value)
}
if len(decl.Names) != 1 || decl.Names[0] != "arr" {
t.Errorf("expected name 'arr', got %v", decl.Names)
}
}
func TestArrayDeclMultipleNames(t *testing.T) {The file /home/david/projects/code-associates/occam2go/parser/parser_test.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"offset": 668,
"limit": 20
} 668→ t.Fatalf("expected Assignment, got %T", program.Statements[0])
669→ }
670→
671→ if assign.Name != "arr" {
672→ t.Errorf("expected name 'arr', got %s", assign.Name)
673→ }
674→
675→ if assign.Index == nil {
676→ t.Fatal("expected index expression, got nil")
677→ }
678→
679→ indexLit, ok := assign.Index.(*ast.IntegerLiteral)
680→ if !ok {
681→ t.Fatalf("expected IntegerLiteral for index, got %T", assign.Index)
682→ }
683→ if indexLit.Value != 2 {
684→ t.Errorf("expected index 2, got %d", indexLit.Value)
685→ }
686→
687→ valLit, ok := assign.Value.(*ast.IntegerLiteral) if assign.Index == nil {
t.Fatal("expected index expression, got nil")
}
indexLit, ok := assign.Index.(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for index, got %T", assign.Index)
}
if indexLit.Value != 2 {
t.Errorf("expected index 2, got %d", indexLit.Value)
}
valLit, ok := assign.Value.(*ast.IntegerLiteral) if len(assign.Indices) != 1 {
t.Fatalf("expected 1 index, got %d", len(assign.Indices))
}
indexLit, ok := assign.Indices[0].(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for index, got %T", assign.Indices[0])
}
if indexLit.Value != 2 {
t.Errorf("expected index 2, got %d", indexLit.Value)
}
valLit, ok := assign.Value.(*ast.IntegerLiteral)The file /home/david/projects/code-associates/occam2go/parser/parser_test.go has been updated successfully.
Now let me find and fix all remaining .Index references on Assignment and MultiAssignTarget in parser_test.go. Let me search more precisely:
{
"pattern": "\\.(Index|Size|IsArray|IsChanArray|IsOpenArray|ChannelIndex)\\b",
"path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"output_mode": "content",
"-n": true
}736: idxLit, ok := indexExpr.Index.(*ast.IntegerLiteral)
738: t.Fatalf("expected IntegerLiteral for index, got %T", indexExpr.Index)
965: if ma.Targets[0].Index != nil || ma.Targets[1].Index != nil {
1006: if ma.Targets[0].Index == nil || ma.Targets[1].Index == nil {
1035: if ma.Targets[0].Name != "a" || ma.Targets[0].Index != nil {
1038: if ma.Targets[1].Name != "x" || ma.Targets[1].Index == nil {
1936: if assign.Index == nil {
1940: ident, ok := assign.Index.(*ast.Identifier)
1942: t.Fatalf("expected Identifier for index, got %T", assign.Index)
1985: idx, ok := indexExpr.Index.(*ast.Identifier)
1987: t.Fatalf("expected Identifier for index, got %T", indexExpr.Index)
2011: if !decl.IsArray {
2015: sizeLit, ok := decl.Size.(*ast.IntegerLiteral)
2017: t.Fatalf("expected IntegerLiteral for size, got %T", decl.Size)
2053: if send.ChannelIndex == nil {
2057: idxLit, ok := send.ChannelIndex.(*ast.IntegerLiteral)
2059: t.Fatalf("expected IntegerLiteral for index, got %T", send.ChannelIndex)
2095: if recv.ChannelIndex == nil {
2099: idxIdent, ok := recv.ChannelIndex.(*ast.Identifier)
2101: t.Fatalf("expected Identifier for index, got %T", recv.ChannelIndex)
2138: if !p0.IsChanArray {
2149: if p1.IsChan || p1.IsChanArray {
2222: if !p0.IsChanArray {
2613: if !p0.IsOpenArray {
2624: if p1.IsOpenArray {
2678: if !decl.IsArray {
2727: if !p1.IsChan || !p1.IsChanArray {
3194: if rt.IsArray {
3228: if !rt.IsArray {
3362: if c.ChannelIndex == nil {There are many to fix. Let me use a targeted agent to handle these bulk test file changes efficiently:
{
"prompt": "I need you to fix all the old AST field references in /home/david/projects/code-associates/occam2go/parser/parser_test.go.\n\nHere are the field renames that happened:\n- `Assignment.Index` → `Assignment.Indices` (slice)\n- `MultiAssignTarget.Index` → `MultiAssignTarget.Indices` (slice)\n- `ChanDecl.IsArray` → removed (use `len(decl.Sizes) > 0`)\n- `ChanDecl.Size` → `ChanDecl.Sizes` (slice)\n- `Send.ChannelIndex` → `Send.ChannelIndices` (slice)\n- `Receive.ChannelIndex` → `Receive.ChannelIndices` (slice)\n- `AltCase.ChannelIndex` → `AltCase.ChannelIndices` (slice)\n- `ProcParam.IsChanArray` → `ProcParam.ChanArrayDims` (int, > 0 means true)\n- `ProcParam.IsOpenArray` → `ProcParam.OpenArrayDims` (int, > 0 means true)\n\nNOTE: `IndexExpr.Index` is NOT changed. `RetypesDecl.IsArray` is NOT changed. `Abbreviation.IsOpenArray` is NOT changed.\n\nHere are ALL the lines that need fixing (from grep results):\n- Line 965: `ma.Targets[0].Index != nil || ma.Targets[1].Index != nil` → `len(ma.Targets[0].Indices) != 0 || len(ma.Targets[1].Indices) != 0`\n- Line 1006: `ma.Targets[0].Index == nil || ma.Targets[1].Index == nil` → `len(ma.Targets[0].Indices) == 0 || len(ma.Targets[1].Indices) == 0`\n- Line 1035: `ma.Targets[0].Index != nil` → `len(ma.Targets[0].Indices) != 0`\n- Line 1038: `ma.Targets[1].Index == nil` → `len(ma.Targets[1].Indices) == 0`\n- Line 1936: `assign.Index == nil` → `len(assign.Indices) == 0`\n- Line 1940: `assign.Index.(*ast.Identifier)` → `assign.Indices[0].(*ast.Identifier)`\n- Line 1942: `assign.Index)` → `assign.Indices[0])`\n- Line 2011: `!decl.IsArray` → `len(decl.Sizes) == 0`\n- Line 2015: `decl.Size.(*ast.IntegerLiteral)` → `decl.Sizes[0].(*ast.IntegerLiteral)`\n- Line 2017: `decl.Size)` → `decl.Sizes[0])`\n- Line 2053: `send.ChannelIndex == nil` → `len(send.ChannelIndices) == 0`\n- Line 2057: `send.ChannelIndex.(*ast.IntegerLiteral)` → `send.ChannelIndices[0].(*ast.IntegerLiteral)`\n- Line 2059: `send.ChannelIndex)` → `send.ChannelIndices[0])`\n- Line 2095: `recv.ChannelIndex == nil` → `len(recv.ChannelIndices) == 0`\n- Line 2099: `recv.ChannelIndex.(*ast.Identifier)` → `recv.ChannelIndices[0].(*ast.Identifier)`\n- Line 2101: `recv.ChannelIndex)` → `recv.ChannelIndices[0])`\n- Line 2138: `!p0.IsChanArray` → `p0.ChanArrayDims == 0`\n- Line 2149: `p1.IsChan || p1.IsChanArray` → `p1.IsChan || p1.ChanArrayDims > 0`\n- Line 2222: `!p0.IsChanArray` → `p0.ChanArrayDims == 0`\n- Line 2613: `!p0.IsOpenArray` → `p0.OpenArrayDims == 0`\n- Line 2624: `p1.IsOpenArray` → `p1.OpenArrayDims > 0`\n- Line 2678: `!decl.IsArray` → `len(decl.Sizes) == 0`\n- Line 2727: `!p1.IsChan || !p1.IsChanArray` → `!p1.IsChan || p1.ChanArrayDims == 0`\n- Line 3362: `c.ChannelIndex == nil` → `len(c.ChannelIndices) == 0`\n\nLines 736, 738, 1985, 1987, 3194, 3228 reference `IndexExpr.Index` or `RetypesDecl.IsArray` which are NOT changed.\n\nMake all these edits using the Edit tool. Read surrounding lines first if needed for unique context.",
"subagent_type": "general-purpose"
}[
{
"type": "text",
"text": "All remaining references are the ones explicitly noted as NOT changed:\n\n- Lines 736, 738, 1985, 1987: `IndexExpr.Index` -- NOT changed (as specified)\n- Lines 3194, 3228: `RetypesDecl.IsArray` -- NOT changed (as specified)\n\nNo stale references remain for `ChannelIndex`, `IsChanArray`, `IsOpenArray`, or `ChanDecl.Size`/`ChanDecl.IsArray`.\n\nHere is a summary of all 14 edits made to `/home/david/projects/code-associates/occam2go/parser/parser_test.go`:\n\n1. **Line 965**: `ma.Targets[0].Index != nil || ma.Targets[1].Index != nil` changed to `len(ma.Targets[0].Indices) != 0 || len(ma.Targets[1].Indices) != 0`\n2. **Line 1006**: `ma.Targets[0].Index == nil || ma.Targets[1].Index == nil` changed to `len(ma.Targets[0].Indices) == 0 || len(ma.Targets[1].Indices) == 0`\n3. **Lines 1035/1038**: `ma.Targets[0].Index != nil` and `ma.Targets[1].Index == nil` changed to `len(...Indices) != 0` and `len(...Indices) == 0`\n4. **Lines 1936/1940/1942**: `assign.Index` references changed to `assign.Indices[0]` (with `len()` nil check)\n5. **Lines 2011/2015/2017**: `decl.IsArray` changed to `len(decl.Sizes) == 0`, `decl.Size` changed to `decl.Sizes[0]`\n6. **Lines 2053/2057/2059**: `send.ChannelIndex` changed to `send.ChannelIndices[0]` (with `len()` nil check)\n7. **Lines 2095/2099/2101**: `recv.ChannelIndex` changed to `recv.ChannelIndices[0]` (with `len()` nil check)\n8. **Line 2138**: `!p0.IsChanArray` changed to `p0.ChanArrayDims == 0`\n9. **Line 2149**: `p1.IsChan || p1.IsChanArray` changed to `p1.IsChan || p1.ChanArrayDims > 0`\n10. **Line 2222**: `!p0.IsChanArray` changed to `p0.ChanArrayDims == 0`\n11. **Line 2613**: `!p0.IsOpenArray` changed to `p0.OpenArrayDims == 0`\n12. **Line 2624**: `p1.IsOpenArray` changed to `p1.OpenArrayDims > 0`\n13. **Line 2678**: `!decl.IsArray` changed to `len(decl.Sizes) == 0`\n14. **Line 2727**: `!p1.IsChan || !p1.IsChanArray` changed to `!p1.IsChan || p1.ChanArrayDims == 0`\n15. **Line 3362**: `c.ChannelIndex == nil` changed to `len(c.ChannelIndices) == 0`"
},
{
"type": "text",
"text": "agentId: a35f019fd813581ef (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 29980\ntool_uses: 31\nduration_ms: 59990</usage>"
}
]Good. Now let me fix the codegen test failure - the channel init loop variable changed from _i to _i0:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen_test.go",
"offset": 585,
"limit": 20
} 585→ output := transpile(t, input)
586→
587→ if !strings.Contains(output, "cs := make([]chan int, 5)") {
588→ t.Errorf("expected 'cs := make([]chan int, 5)' in output, got:\n%s", output)
589→ }
590→ if !strings.Contains(output, "for _i := range cs { cs[_i] = make(chan int) }") {
591→ t.Errorf("expected init loop in output, got:\n%s", output)
592→ }
593→}
594→
595→func TestIndexedSendGen(t *testing.T) {
596→ input := `cs[0] ! 42
597→`
598→ output := transpile(t, input)
599→
600→ if !strings.Contains(output, "cs[0] <- 42") {
601→ t.Errorf("expected 'cs[0] <- 42' in output, got:\n%s", output)
602→ }
603→}
604→ if !strings.Contains(output, "for _i := range cs { cs[_i] = make(chan int) }") { if !strings.Contains(output, "for _i0 := range cs { cs[_i0] = make(chan int) }") {The file /home/david/projects/code-associates/occam2go/codegen/codegen_test.go has been updated successfully.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 18.381s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser 0.002s ok github.com/codeassociates/occam2go/preproc (cached)
go build -o occam2go . && ./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
Tasks 2-4 are done (the parser multi-dim loops and codegen multi-dim generation were done alongside the mechanical renames since they were natural extensions). Now let me add the tests.
/home/david/projects/code-associates/occam2go/codegen/e2e_preproc_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_helpers_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_record_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_proc_func_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_replicator_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_basic_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_phase2_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_course_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_intrinsics_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_strings_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_params_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_retypes_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_concurrency_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_types_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_misc_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_control_test.go /home/david/projects/code-associates/occam2go/codegen/e2e_protocol_test.go
Good, there's already an e2e_array_test.go. Let me read it to see what's there and add multi-dim tests to it:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go"
} 1→package codegen
2→
3→import "testing"
4→
5→func TestE2E_ArrayBasic(t *testing.T) {
6→ // Test basic array: declare, store, load
7→ occam := `SEQ
8→ [5]INT arr:
9→ arr[0] := 42
10→ print.int(arr[0])
11→`
12→ output := transpileCompileRun(t, occam)
13→ expected := "42\n"
14→ if output != expected {
15→ t.Errorf("expected %q, got %q", expected, output)
16→ }
17→}
18→
19→func TestE2E_ArrayWithLoop(t *testing.T) {
20→ // Test filling array with replicated SEQ and printing all elements
21→ occam := `SEQ
22→ [5]INT arr:
23→ SEQ i = 0 FOR 5
24→ arr[i] := i * 10
25→ SEQ i = 0 FOR 5
26→ print.int(arr[i])
27→`
28→ output := transpileCompileRun(t, occam)
29→ expected := "0\n10\n20\n30\n40\n"
30→ if output != expected {
31→ t.Errorf("expected %q, got %q", expected, output)
32→ }
33→}
34→
35→func TestE2E_ArraySum(t *testing.T) {
36→ // Test computing sum of array elements
37→ occam := `SEQ
38→ [4]INT arr:
39→ arr[0] := 10
40→ arr[1] := 20
41→ arr[2] := 30
42→ arr[3] := 40
43→ INT sum:
44→ sum := 0
45→ SEQ i = 0 FOR 4
46→ sum := sum + arr[i]
47→ print.int(sum)
48→`
49→ output := transpileCompileRun(t, occam)
50→ expected := "100\n"
51→ if output != expected {
52→ t.Errorf("expected %q, got %q", expected, output)
53→ }
54→}
55→
56→func TestE2E_ArrayExpressionIndex(t *testing.T) {
57→ // Test using variable and expression as array index
58→ occam := `SEQ
59→ [3]INT arr:
60→ INT idx:
61→ arr[0] := 100
62→ arr[1] := 200
63→ arr[2] := 300
64→ idx := 1
65→ print.int(arr[idx])
66→ print.int(arr[idx + 1])
67→`
68→ output := transpileCompileRun(t, occam)
69→ expected := "200\n300\n"
70→ if output != expected {
71→ t.Errorf("expected %q, got %q", expected, output)
72→ }
73→}
74→
75→func TestE2E_ChanArrayBasic(t *testing.T) {
76→ // Declare channel array, use in replicated PAR to send/receive
77→ occam := `SEQ
78→ [3]CHAN OF INT cs:
79→ INT sum:
80→ sum := 0
81→ PAR
82→ PAR i = 0 FOR 3
83→ cs[i] ! (i + 1) * 10
84→ SEQ i = 0 FOR 3
85→ INT x:
86→ cs[i] ? x
87→ sum := sum + x
88→ print.int(sum)
89→`
90→ output := transpileCompileRun(t, occam)
91→ expected := "60\n"
92→ if output != expected {
93→ t.Errorf("expected %q, got %q", expected, output)
94→ }
95→}
96→
97→func TestE2E_ChanArrayWithProc(t *testing.T) {
98→ // Pass channel array to a PROC
99→ occam := `PROC sender([]CHAN OF INT cs, VAL INT n)
100→ SEQ i = 0 FOR n
101→ cs[i] ! (i + 1) * 100
102→
103→SEQ
104→ [3]CHAN OF INT cs:
105→ INT sum:
106→ sum := 0
107→ PAR
108→ sender(cs, 3)
109→ SEQ i = 0 FOR 3
110→ INT x:
111→ cs[i] ? x
112→ sum := sum + x
113→ print.int(sum)
114→`
115→ output := transpileCompileRun(t, occam)
116→ expected := "600\n"
117→ if output != expected {
118→ t.Errorf("expected %q, got %q", expected, output)
119→ }
120→}
121→
122→func TestE2E_ChanArrayAlt(t *testing.T) {
123→ // Use channel array in ALT
124→ occam := `SEQ
125→ [2]CHAN OF INT cs:
126→ INT result:
127→ result := 0
128→ PAR
129→ cs[0] ! 42
130→ ALT
131→ cs[0] ? result
132→ print.int(result)
133→ cs[1] ? result
134→ print.int(result)
135→`
136→ output := transpileCompileRun(t, occam)
137→ expected := "42\n"
138→ if output != expected {
139→ t.Errorf("expected %q, got %q", expected, output)
140→ }
141→}
142→
143→func TestE2E_SizeArray(t *testing.T) {
144→ occam := `SEQ
145→ [5]INT arr:
146→ INT n:
147→ n := SIZE arr
148→ print.int(n)
149→`
150→ output := transpileCompileRun(t, occam)
151→ expected := "5\n"
152→ if output != expected {
153→ t.Errorf("expected %q, got %q", expected, output)
154→ }
155→}
156→
157→func TestE2E_SizeString(t *testing.T) {
158→ occam := `SEQ
159→ INT n:
160→ n := SIZE "hello"
161→ print.int(n)
162→`
163→ output := transpileCompileRun(t, occam)
164→ expected := "5\n"
165→ if output != expected {
166→ t.Errorf("expected %q, got %q", expected, output)
167→ }
168→}
169→
170→func TestE2E_SliceAsArg(t *testing.T) {
171→ // Pass an array slice to a PROC expecting an open array param
172→ occam := `PROC printarray(VAL []INT arr)
173→ SEQ i = 0 FOR SIZE arr
174→ print.int(arr[i])
175→SEQ
176→ [5]INT nums:
177→ SEQ i = 0 FOR 5
178→ nums[i] := (i + 1) * 10
179→ printarray([nums FROM 1 FOR 3])
180→`
181→ output := transpileCompileRun(t, occam)
182→ expected := "20\n30\n40\n"
183→ if output != expected {
184→ t.Errorf("expected %q, got %q", expected, output)
185→ }
186→}
187→
188→func TestE2E_SliceAssignment(t *testing.T) {
189→ // Copy elements within an array using slice assignment
190→ occam := `SEQ
191→ [6]INT arr:
192→ SEQ i = 0 FOR 6
193→ arr[i] := i + 1
194→ [arr FROM 3 FOR 3] := [arr FROM 0 FOR 3]
195→ SEQ i = 0 FOR 6
196→ print.int(arr[i])
197→`
198→ output := transpileCompileRun(t, occam)
199→ expected := "1\n2\n3\n1\n2\n3\n"
200→ if output != expected {
201→ t.Errorf("expected %q, got %q", expected, output)
202→ }
203→}
204→
205→func TestE2E_SliceSize(t *testing.T) {
206→ // SIZE of a slice expression
207→ occam := `SEQ
208→ [10]INT arr:
209→ INT n:
210→ n := SIZE [arr FROM 2 FOR 5]
211→ print.int(n)
212→`
213→ output := transpileCompileRun(t, occam)
214→ expected := "5\n"
215→ if output != expected {
216→ t.Errorf("expected %q, got %q", expected, output)
217→ }
218→}
219→
220→func TestE2E_SliceFromZero(t *testing.T) {
221→ // Slice starting from index 0 passed to a VAL open array proc
222→ occam := `PROC printsum(VAL []INT arr)
223→ SEQ
224→ INT total:
225→ total := 0
226→ SEQ i = 0 FOR SIZE arr
227→ total := total + arr[i]
228→ print.int(total)
229→SEQ
230→ [5]INT arr:
231→ SEQ i = 0 FOR 5
232→ arr[i] := i + 1
233→ printsum([arr FROM 0 FOR 3])
234→`
235→ output := transpileCompileRun(t, occam)
236→ expected := "6\n"
237→ if output != expected {
238→ t.Errorf("expected %q, got %q", expected, output)
239→ }
240→}
241→
242→func TestE2E_OpenArrayParam(t *testing.T) {
243→ occam := `PROC printarray(VAL []INT arr)
244→ SEQ i = 0 FOR SIZE arr
245→ print.int(arr[i])
246→SEQ
247→ [3]INT nums:
248→ SEQ
249→ nums[0] := 10
250→ nums[1] := 20
251→ nums[2] := 30
252→ printarray(nums)
253→`
254→ output := transpileCompileRun(t, occam)
255→ expected := "10\n20\n30\n"
256→ if output != expected {
257→ t.Errorf("expected %q, got %q", expected, output)
258→ }
259→}
260→
261→func TestE2E_MultiAssignmentSwap(t *testing.T) {
262→ occam := `SEQ
263→ [2]INT arr:
264→ SEQ
265→ arr[0] := 10
266→ arr[1] := 20
267→ arr[0], arr[1] := arr[1], arr[0]
268→ print.int(arr[0])
269→ print.int(arr[1])
270→`
271→ output := transpileCompileRun(t, occam)
272→ expected := "20\n10\n"
273→ if output != expected {
274→ t.Errorf("expected %q, got %q", expected, output)
275→ }
276→}
277→
278→func TestE2E_MultiAssignmentMixed(t *testing.T) {
279→ occam := `SEQ
280→ INT a:
281→ [3]INT arr:
282→ SEQ
283→ arr[0] := 99
284→ a, arr[1] := arr[0], 42
285→ print.int(a)
286→ print.int(arr[1])
287→`
288→ output := transpileCompileRun(t, occam)
289→ expected := "99\n42\n"
290→ if output != expected {
291→ t.Errorf("expected %q, got %q", expected, output)
292→ }
293→}
294→
295→func TestE2E_MultiAssignmentValues(t *testing.T) {
296→ occam := `SEQ
297→ INT a, b, c:
298→ a, b, c := 10, 20, 30
299→ print.int(a)
300→ print.int(b)
301→ print.int(c)
302→`
303→ output := transpileCompileRun(t, occam)
304→ expected := "10\n20\n30\n"
305→ if output != expected {
306→ t.Errorf("expected %q, got %q", expected, output)
307→ }
308→}
309→
310→func TestE2E_ChanArrayDirParam(t *testing.T) {
311→ // Channel arrays passed to direction-annotated params must compile
312→ // (Go slices are not covariant, so direction is dropped for array params)
313→ occam := `PROC sender([]CHAN OF INT out!)
314→ SEQ i = 0 FOR SIZE out
315→ out[i] ! i
316→:
317→PROC receiver([]CHAN OF INT in?)
318→ SEQ i = 0 FOR SIZE in
319→ INT v:
320→ SEQ
321→ in[i] ? v
322→ print.int(v)
323→:
324→SEQ
325→ [3]CHAN OF INT cs:
326→ PAR
327→ sender(cs)
328→ receiver(cs)
329→`
330→ output := transpileCompileRun(t, occam)
331→ expected := "0\n1\n2\n"
332→ if output != expected {
333→ t.Errorf("expected %q, got %q", expected, output)
334→ }
335→}
336→Good. Now let me add multi-dim tests. I'll add parser tests, codegen unit tests, and e2e tests:
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go",
"offset": 330,
"limit": 10
} 330→ output := transpileCompileRun(t, occam)
331→ expected := "0\n1\n2\n"
332→ if output != expected {
333→ t.Errorf("expected %q, got %q", expected, output)
334→ }
335→}
336→ output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
} output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_MultiDimArray(t *testing.T) {
// 2D array: declare, fill with SEQ loops, read back
occam := `SEQ
[3][4]INT grid:
SEQ i = 0 FOR 3
SEQ j = 0 FOR 4
grid[i][j] := (i * 10) + j
SEQ i = 0 FOR 3
SEQ j = 0 FOR 4
print.int(grid[i][j])
`
output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n3\n10\n11\n12\n13\n20\n21\n22\n23\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_MultiDimChanArray(t *testing.T) {
// 2D channel array: send/receive
occam := `SEQ
[2][3]CHAN OF INT cs:
INT sum:
sum := 0
PAR
SEQ i = 0 FOR 2
SEQ j = 0 FOR 3
cs[i][j] ! (i * 10) + j
SEQ
SEQ i = 0 FOR 2
SEQ j = 0 FOR 3
INT x:
cs[i][j] ? x
sum := sum + x
print.int(sum)
`
output := transpileCompileRun(t, occam)
// sum = 0+1+2+10+11+12 = 36
expected := "36\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_MultiDimChanArrayWithProc(t *testing.T) {
// Pass 2D channel array to a PROC
occam := `PROC fill([][]CHAN OF INT grid, VAL INT rows, VAL INT cols)
SEQ i = 0 FOR rows
SEQ j = 0 FOR cols
grid[i][j] ! (i * 100) + j
:
SEQ
[2][3]CHAN OF INT cs:
INT sum:
sum := 0
PAR
fill(cs, 2, 3)
SEQ i = 0 FOR 2
SEQ j = 0 FOR 3
INT v:
cs[i][j] ? v
sum := sum + v
print.int(sum)
`
output := transpileCompileRun(t, occam)
// sum = 0+1+2+100+101+102 = 306
expected := "306\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}The file /home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"limit": 5
}wc -l /home/david/projects/code-associates/occam2go/parser/parser_test.go
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"offset": 3505,
"limit": 10
} 3505→ if most.IsNeg != expectedNeg {
3506→ t.Errorf("for %q: expected IsNeg=%v, got %v", input, expectedNeg, most.IsNeg)
3507→ }
3508→ }
3509→ }
3510→}
3511→ if most.IsNeg != expectedNeg {
t.Errorf("for %q: expected IsNeg=%v, got %v", input, expectedNeg, most.IsNeg)
}
}
}
} if most.IsNeg != expectedNeg {
t.Errorf("for %q: expected IsNeg=%v, got %v", input, expectedNeg, most.IsNeg)
}
}
}
}
func TestMultiDimArrayDecl(t *testing.T) {
input := `[3][4]INT grid:
`
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))
}
decl, ok := program.Statements[0].(*ast.ArrayDecl)
if !ok {
t.Fatalf("expected ArrayDecl, got %T", program.Statements[0])
}
if len(decl.Sizes) != 2 {
t.Fatalf("expected 2 dimensions, got %d", len(decl.Sizes))
}
s0, ok := decl.Sizes[0].(*ast.IntegerLiteral)
if !ok || s0.Value != 3 {
t.Errorf("expected first size 3, got %v", decl.Sizes[0])
}
s1, ok := decl.Sizes[1].(*ast.IntegerLiteral)
if !ok || s1.Value != 4 {
t.Errorf("expected second size 4, got %v", decl.Sizes[1])
}
if decl.Type != "INT" {
t.Errorf("expected type INT, got %s", decl.Type)
}
if len(decl.Names) != 1 || decl.Names[0] != "grid" {
t.Errorf("expected name 'grid', got %v", decl.Names)
}
}
func TestMultiDimChanArrayDecl(t *testing.T) {
input := `[2][3]CHAN OF INT links:
`
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))
}
decl, ok := program.Statements[0].(*ast.ChanDecl)
if !ok {
t.Fatalf("expected ChanDecl, got %T", program.Statements[0])
}
if len(decl.Sizes) != 2 {
t.Fatalf("expected 2 dimensions, got %d", len(decl.Sizes))
}
s0, ok := decl.Sizes[0].(*ast.IntegerLiteral)
if !ok || s0.Value != 2 {
t.Errorf("expected first size 2, got %v", decl.Sizes[0])
}
s1, ok := decl.Sizes[1].(*ast.IntegerLiteral)
if !ok || s1.Value != 3 {
t.Errorf("expected second size 3, got %v", decl.Sizes[1])
}
if decl.ElemType != "INT" {
t.Errorf("expected ElemType INT, got %s", decl.ElemType)
}
if len(decl.Names) != 1 || decl.Names[0] != "links" {
t.Errorf("expected name 'links', got %v", decl.Names)
}
}
func TestMultiDimIndexedAssignment(t *testing.T) {
input := `grid[i][j] := 42
`
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))
}
assign, ok := program.Statements[0].(*ast.Assignment)
if !ok {
t.Fatalf("expected Assignment, got %T", program.Statements[0])
}
if assign.Name != "grid" {
t.Errorf("expected name 'grid', got %s", assign.Name)
}
if len(assign.Indices) != 2 {
t.Fatalf("expected 2 indices, got %d", len(assign.Indices))
}
idx0, ok := assign.Indices[0].(*ast.Identifier)
if !ok || idx0.Value != "i" {
t.Errorf("expected first index 'i', got %v", assign.Indices[0])
}
idx1, ok := assign.Indices[1].(*ast.Identifier)
if !ok || idx1.Value != "j" {
t.Errorf("expected second index 'j', got %v", assign.Indices[1])
}
}
func TestMultiDimIndexedSend(t *testing.T) {
input := `cs[i][j] ! 42
`
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))
}
send, ok := program.Statements[0].(*ast.Send)
if !ok {
t.Fatalf("expected Send, got %T", program.Statements[0])
}
if send.Channel != "cs" {
t.Errorf("expected channel 'cs', got %s", send.Channel)
}
if len(send.ChannelIndices) != 2 {
t.Fatalf("expected 2 indices, got %d", len(send.ChannelIndices))
}
}
func TestMultiDimIndexedReceive(t *testing.T) {
input := `cs[i][j] ? x
`
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) != 2 {
t.Fatalf("expected 2 indices, got %d", len(recv.ChannelIndices))
}
if recv.Variable != "x" {
t.Errorf("expected variable 'x', got %s", recv.Variable)
}
}
func TestMultiDimOpenArrayParam(t *testing.T) {
input := `PROC fill([][]CHAN OF INT grid)
SKIP
`
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))
}
proc, ok := program.Statements[0].(*ast.ProcDecl)
if !ok {
t.Fatalf("expected ProcDecl, got %T", program.Statements[0])
}
if len(proc.Params) != 1 {
t.Fatalf("expected 1 param, got %d", len(proc.Params))
}
p0 := proc.Params[0]
if p0.ChanArrayDims != 2 {
t.Errorf("expected ChanArrayDims=2, got %d", p0.ChanArrayDims)
}
if !p0.IsChan {
t.Error("expected IsChan=true")
}
if p0.ChanElemType != "INT" {
t.Errorf("expected ChanElemType=INT, got %s", p0.ChanElemType)
}
}The file /home/david/projects/code-associates/occam2go/parser/parser_test.go has been updated successfully.
wc -l /home/david/projects/code-associates/occam2go/codegen/codegen_test.go
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen_test.go",
"offset": 785,
"limit": 10
} 785→`
786→ output := transpile(t, input)
787→ if !strings.Contains(output, "_byte") {
788→ t.Errorf("expected '_byte' in output, got:\n%s", output)
789→ }
790→}
791→ output := transpile(t, input)
if !strings.Contains(output, "_byte") {
t.Errorf("expected '_byte' in output, got:\n%s", output)
}
} output := transpile(t, input)
if !strings.Contains(output, "_byte") {
t.Errorf("expected '_byte' in output, got:\n%s", output)
}
}
func TestMultiDimArrayDeclCodegen(t *testing.T) {
input := `[3][4]INT grid:
`
output := transpile(t, input)
if !strings.Contains(output, "grid := make([][]int, 3)") {
t.Errorf("expected 'grid := make([][]int, 3)' in output, got:\n%s", output)
}
if !strings.Contains(output, "grid[_i0] = make([]int, 4)") {
t.Errorf("expected 'grid[_i0] = make([]int, 4)' in output, got:\n%s", output)
}
}
func TestMultiDimChanDeclCodegen(t *testing.T) {
input := `[2][3]CHAN OF INT links:
`
output := transpile(t, input)
if !strings.Contains(output, "links := make([][]chan int, 2)") {
t.Errorf("expected 'links := make([][]chan int, 2)' in output, got:\n%s", output)
}
if !strings.Contains(output, "links[_i0] = make([]chan int, 3)") {
t.Errorf("expected 'links[_i0] = make([]chan int, 3)' in output, got:\n%s", output)
}
if !strings.Contains(output, "make(chan int)") {
t.Errorf("expected 'make(chan int)' in output, got:\n%s", output)
}
}
func TestMultiDimSendCodegen(t *testing.T) {
input := `cs[i][j] ! 42
`
output := transpile(t, input)
if !strings.Contains(output, "cs[i][j] <- 42") {
t.Errorf("expected 'cs[i][j] <- 42' in output, got:\n%s", output)
}
}
func TestMultiDimReceiveCodegen(t *testing.T) {
input := `cs[i][j] ? x
`
output := transpile(t, input)
if !strings.Contains(output, "x = <-cs[i][j]") {
t.Errorf("expected 'x = <-cs[i][j]' in output, got:\n%s", output)
}
}
func TestMultiDimAssignmentCodegen(t *testing.T) {
input := `grid[i][j] := 42
`
output := transpile(t, input)
if !strings.Contains(output, "grid[i][j] = 42") {
t.Errorf("expected 'grid[i][j] = 42' in output, got:\n%s", output)
}
}
func TestMultiDimProcParamCodegen(t *testing.T) {
input := `PROC fill([][]CHAN OF INT grid)
SKIP
`
output := transpile(t, input)
if !strings.Contains(output, "func fill(grid [][]chan int)") {
t.Errorf("expected 'func fill(grid [][]chan int)' in output, got:\n%s", output)
}
}The file /home/david/projects/code-associates/occam2go/codegen/codegen_test.go has been updated successfully.
Now I need to handle the [][]CHAN param parsing. Currently the parser only handles []CHAN, not [][]CHAN. Let me check what happens:
go test ./parser -run TestMultiDimOpenArrayParam -v 2>&1
Exit code 1
=== RUN TestMultiDimOpenArrayParam
parser_test.go:1418: parser has 5 errors:
parser_test.go:1420: parser error: line 1: expected type after [], got [
parser_test.go:1420: parser error: line 1: expected ), got ]
parser_test.go:1420: parser error: line 1: unexpected token: ]
parser_test.go:1420: parser error: line 1: expected :, got )
parser_test.go:1420: parser error: line 1: unexpected token: )
--- FAIL: TestMultiDimOpenArrayParam (0.00s)
FAIL
FAIL github.com/codeassociates/occam2go/parser 0.001s
FAIL
=== RUN TestMultiDimOpenArrayParam
parser_test.go:1418: parser has 5 errors:
parser_test.go:1420: parser error: line 1: expected type after [], got [
parser_test.go:1420: parser error: line 1: expected ), got ]
parser_test.go:1420: parser error: line 1: unexpected token: ]
parser_test.go:1420: parser error: line 1: expected :, got )
parser_test.go:1420: parser error: line 1: unexpected token: )
--- FAIL: TestMultiDimOpenArrayParam (0.00s)
FAIL
FAIL github.com/codeassociates/occam2go/parser 0.001s
FAILRight - parseProcParams currently only handles a single [] before CHAN. I need to add the loop for multiple [] dimensions. Let me read the current code:
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser.go",
"offset": 2069,
"limit": 60
} 2069→ }
2070→
2071→ params = append(params, param)
2072→ prevParam = ¶ms[len(params)-1]
2073→
2074→ if !p.peekTokenIs(lexer.COMMA) {
2075→ break
2076→ }
2077→ p.nextToken() // consume comma
2078→ p.nextToken() // move to next param
2079→ continue
2080→ }
2081→
2082→ // Check for VAL keyword
2083→ if p.curTokenIs(lexer.VAL) {
2084→ param.IsVal = true
2085→ p.nextToken()
2086→ }
2087→
2088→ // Check for RESULT keyword (output-only parameter — maps to pointer like non-VAL)
2089→ if p.curTokenIs(lexer.RESULT) {
2090→ // RESULT is semantically like non-VAL (pointer param), just skip it
2091→ p.nextToken()
2092→ }
2093→
2094→ // Check for []CHAN OF <type>, []TYPE (open array), or [n]TYPE (fixed-size array)
2095→ if p.curTokenIs(lexer.LBRACKET) {
2096→ if p.peekTokenIs(lexer.RBRACKET) {
2097→ // Open array: []CHAN OF TYPE or []TYPE
2098→ p.nextToken() // consume ]
2099→ p.nextToken() // move past ]
2100→ if p.curTokenIs(lexer.CHAN) {
2101→ // []CHAN OF <type> or []CHAN <type> (channel array parameter)
2102→ param.IsChan = true
2103→ param.ChanArrayDims = 1
2104→ if p.peekTokenIs(lexer.OF) {
2105→ p.nextToken() // consume OF
2106→ }
2107→ p.nextToken() // move to element type
2108→ if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) {
2109→ param.ChanElemType = p.curToken.Literal
2110→ } else {
2111→ p.addError(fmt.Sprintf("expected type after []CHAN, got %s", p.curToken.Type))
2112→ return params
2113→ }
2114→ p.nextToken()
2115→ } else if isTypeToken(p.curToken.Type) {
2116→ param.OpenArrayDims = 1
2117→ param.Type = p.curToken.Literal
2118→ p.nextToken()
2119→ } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
2120→ param.OpenArrayDims = 1
2121→ param.Type = p.curToken.Literal
2122→ p.nextToken()
2123→ } else {
2124→ p.addError(fmt.Sprintf("expected type after [], got %s", p.curToken.Type))
2125→ return params
2126→ }
2127→ } else {
2128→ // Fixed-size array: [n]TYPE // Check for []CHAN OF <type>, []TYPE (open array), or [n]TYPE (fixed-size array)
if p.curTokenIs(lexer.LBRACKET) {
if p.peekTokenIs(lexer.RBRACKET) {
// Open array: []CHAN OF TYPE or []TYPE
p.nextToken() // consume ]
p.nextToken() // move past ]
if p.curTokenIs(lexer.CHAN) {
// []CHAN OF <type> or []CHAN <type> (channel array parameter)
param.IsChan = true
param.ChanArrayDims = 1
if p.peekTokenIs(lexer.OF) {
p.nextToken() // consume OF
}
p.nextToken() // move to element type
if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) {
param.ChanElemType = p.curToken.Literal
} else {
p.addError(fmt.Sprintf("expected type after []CHAN, got %s", p.curToken.Type))
return params
}
p.nextToken()
} else if isTypeToken(p.curToken.Type) {
param.OpenArrayDims = 1
param.Type = p.curToken.Literal
p.nextToken()
} else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
param.OpenArrayDims = 1
param.Type = p.curToken.Literal
p.nextToken()
} else {
p.addError(fmt.Sprintf("expected type after [], got %s", p.curToken.Type))
return params
}
} else { // Check for []...CHAN OF <type>, []...TYPE (open array), or [n]TYPE (fixed-size array)
if p.curTokenIs(lexer.LBRACKET) {
if p.peekTokenIs(lexer.RBRACKET) {
// Open array: [][]...CHAN OF TYPE or [][]...TYPE
// Count consecutive [] pairs
dims := 0
for p.curTokenIs(lexer.LBRACKET) && p.peekTokenIs(lexer.RBRACKET) {
dims++
p.nextToken() // consume ]
p.nextToken() // move past ]
}
if p.curTokenIs(lexer.CHAN) {
// []...CHAN OF <type> or []...CHAN <type> (channel array parameter)
param.IsChan = true
param.ChanArrayDims = dims
if p.peekTokenIs(lexer.OF) {
p.nextToken() // consume OF
}
p.nextToken() // move to element type
if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) {
param.ChanElemType = p.curToken.Literal
} else {
p.addError(fmt.Sprintf("expected type after %sCHAN, got %s", strings.Repeat("[]", dims), p.curToken.Type))
return params
}
p.nextToken()
} else if isTypeToken(p.curToken.Type) {
param.OpenArrayDims = dims
param.Type = p.curToken.Literal
p.nextToken()
} else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] {
param.OpenArrayDims = dims
param.Type = p.curToken.Literal
p.nextToken()
} else {
p.addError(fmt.Sprintf("expected type after %s, got %s", strings.Repeat("[]", dims), p.curToken.Type))
return params
}
} else {The file /home/david/projects/code-associates/occam2go/parser/parser.go has been updated successfully.
=== RUN TestMultiDimArrayDecl --- PASS: TestMultiDimArrayDecl (0.00s) === RUN TestMultiDimChanArrayDecl --- PASS: TestMultiDimChanArrayDecl (0.00s) === RUN TestMultiDimIndexedAssignment --- PASS: TestMultiDimIndexedAssignment (0.00s) === RUN TestMultiDimIndexedSend --- PASS: TestMultiDimIndexedSend (0.00s) === RUN TestMultiDimIndexedReceive --- PASS: TestMultiDimIndexedReceive (0.00s) === RUN TestMultiDimOpenArrayParam --- PASS: TestMultiDimOpenArrayParam (0.00s) PASS ok github.com/codeassociates/occam2go/parser 0.001s
=== RUN TestMultiDimArrayDeclCodegen --- PASS: TestMultiDimArrayDeclCodegen (0.00s) === RUN TestMultiDimChanDeclCodegen --- PASS: TestMultiDimChanDeclCodegen (0.00s) === RUN TestMultiDimSendCodegen --- PASS: TestMultiDimSendCodegen (0.00s) === RUN TestMultiDimReceiveCodegen --- PASS: TestMultiDimReceiveCodegen (0.00s) === RUN TestMultiDimAssignmentCodegen --- PASS: TestMultiDimAssignmentCodegen (0.00s) === RUN TestMultiDimProcParamCodegen --- PASS: TestMultiDimProcParamCodegen (0.00s) PASS ok github.com/codeassociates/occam2go/codegen 0.001s
=== RUN TestE2E_MultiDimArray --- PASS: TestE2E_MultiDimArray (0.09s) === RUN TestE2E_MultiDimChanArray --- PASS: TestE2E_MultiDimChanArray (0.09s) === RUN TestE2E_MultiDimChanArrayWithProc --- PASS: TestE2E_MultiDimChanArrayWithProc (0.08s) PASS ok github.com/codeassociates/occam2go/codegen 0.265s
All multi-dim tests pass. Let me now run the full test suite to make sure nothing is broken:
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 18.721s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser 0.002s ok github.com/codeassociates/occam2go/preproc (cached)
./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
{
"pattern": "\\[5\\]CHAN OF INT cs:",
"path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"output_mode": "content",
"-n": true
}{
"file_path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"offset": 110,
"limit": 15
}110→| `/\` / `\/` / `><` | `&` / `\|` / `^` (bitwise AND/OR/XOR) | 111→| `~` | `^` (bitwise NOT) | 112→| `<<` / `>>` | `<<` / `>>` (shifts) | 113→| `[5]CHAN OF INT cs:` | `cs := make([]chan int, 5)` + init loop | 114→| `cs[i] ! 42` | `cs[i] <- 42` | 115→| `cs[i] ? x` | `x = <-cs[i]` | 116→| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | 117→| `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | 118→| `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) | 119→| `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | 120→| `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) | 121→| `f(out!, in?)` (call-site dir) | `f(out, in)` (direction annotations ignored) | 122→| Non-VAL params | `*type` pointer params, callers pass `&arg` | 123→| `PROC f([]INT arr)` | `func f(arr []int)` (open array param, slice) | 124→| `PROC f(VAL []INT arr)` | `func f(arr []int)` (VAL open array, also slice) |
| `[5]CHAN OF INT cs:` | `cs := make([]chan int, 5)` + init loop | | `cs[i] ! 42` | `cs[i] <- 42` | | `cs[i] ? x` | `x = <-cs[i]` | | `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) |
| `[5]CHAN OF INT cs:` | `cs := make([]chan int, 5)` + init loop | | `[w][h]CHAN OF INT cs:` | `cs := make([][]chan int, w)` + nested init loops | | `cs[i] ! 42` | `cs[i] <- 42` | | `cs[i] ? x` | `x = <-cs[i]` | | `cs[i][j] ! 42` | `cs[i][j] <- 42` (multi-dim channel index) | | `cs[i][j] ? x` | `x = <-cs[i][j]` (multi-dim channel index) | | `[3][4]INT grid:` | `grid := make([][]int, 3)` + nested init loops | | `grid[i][j] := 42` | `grid[i][j] = 42` (multi-dim array index) | | `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f([][]CHAN OF INT cs)` | `func f(cs [][]chan int)` (multi-dim chan array) | | `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) |
The file /home/david/projects/code-associates/occam2go/CLAUDE.md has been updated successfully.
{
"pattern": "channel arrays",
"path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"output_mode": "content",
"-n": true
}{
"file_path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"offset": 190,
"limit": 8
} 190→
191→## What's Implemented
192→
193→Preprocessor (`#IF`/`#ELSE`/`#ENDIF`/`#DEFINE`/`#INCLUDE` with search paths, include guards, include-once deduplication, `#COMMENT`/`#PRAGMA`/`#USE` ignored), module file generation from SConscript (`gen-module` subcommand), SEQ, PAR, IF, WHILE, CASE, ALT (with guards, timer timeouts, multi-statement bodies with scoped declarations, and replicators using `reflect.Select`), SKIP, STOP, variable/array/channel/timer declarations, abbreviations (`VAL INT x IS 42:`, `INT y IS z:`, `VAL []BYTE s IS "hi":`, untyped `VAL x IS expr:`), assignments (simple and indexed), channel send/receive, channel arrays (`[n]CHAN OF TYPE` with indexed send/receive and `[]CHAN OF TYPE` proc params), PROC (with VAL, RESULT, reference, CHAN, []CHAN, open array `[]TYPE`, fixed-size array `[n]TYPE`, and shared-type params), channel direction restrictions (`CHAN OF INT c?` → `<-chan int`, `CHAN OF INT c!` → `chan<- int`, call-site annotations `out!`/`in?` accepted), multi-line parameter lists and expressions (lexer suppresses INDENT/DEDENT/NEWLINE inside parens/brackets and after continuation operators), FUNCTION (IS and VALOF forms with multi-statement bodies, including multi-result `INT, INT FUNCTION` with `RESULT a, b`), multi-assignment (`a, b := func(...)` including indexed targets like `x[0], x[1] := x[1], x[0]`), KRoC-style colon terminators on PROC/FUNCTION (optional), INLINE function modifier (accepted and ignored), replicators on SEQ/PAR/IF/ALT (with optional STEP), arithmetic/comparison/logical/AFTER/bitwise operators, type conversions (`INT expr`, `INT16 expr`, `INT32 expr`, `INT64 expr`, `BYTE expr`, `BOOL expr`, `REAL32 expr`, `REAL64 expr`, including BOOL↔numeric via `_boolToInt` helper and `!= 0` comparison, and ROUND/TRUNC qualifiers for float↔int conversions), INT16/INT32/INT64 types, REAL32/REAL64 types, hex integer literals (`#FF`, `#80000000`), string literals, byte literals (`'A'`, `'*n'` with occam escape sequences), built-in print procedures, protocols (simple, sequential, and variant), record types (with field access via bracket syntax), SIZE operator, array slices (`[arr FROM n FOR m]` and shorthand `[arr FOR m]` with slice assignment), array literals (`[1, 2, 3]`), nested PROCs/FUNCTIONs (local definitions as Go closures), MOSTNEG/MOSTPOS (type min/max constants for INT, INT16, INT32, INT64, BYTE, REAL32, REAL64), INITIAL declarations (`INITIAL INT x IS 42:` — mutable variable with initial value), checked (modular) arithmetic (`PLUS`, `MINUS`, `TIMES` — wrapping operators), RETYPES (bit-level type reinterpretation: `VAL INT X RETYPES X :` for float32→int, `VAL [2]INT X RETYPES X :` for float64→int pair), transputer intrinsics (LONGPROD, LONGDIV, LONGSUM, LONGDIFF, NORMALISE, SHIFTRIGHT, SHIFTLEFT — implemented as Go helper functions), CAUSEERROR (maps to `panic("CAUSEERROR")`).
194→
195→## Course Module Testing
196→
197→The KRoC course module (`kroc/modules/course/libsrc/course.module`) is a real-world integration test:channel arrays (`[n]CHAN OF TYPE` with indexed send/receive and `[]CHAN OF TYPE` proc params)
channel arrays (`[n]CHAN OF TYPE` with indexed send/receive, `[]CHAN OF TYPE` proc params, and multi-dimensional `[n][m]CHAN`/`[n][m]TYPE`/`[][]CHAN`/`[][]TYPE`)
The file /home/david/projects/code-associates/occam2go/CLAUDE.md has been updated successfully.
{
"pattern": "multi.dim|Multi.dim|MULTI.DIM|multi-dim|Multi-dim",
"path": "/home/david/projects/code-associates/occam2go/TODO.md",
"output_mode": "content",
"-i": true
}{
"pattern": "dimensional|multi.?dim|array.*dim",
"path": "/home/david/projects/code-associates/occam2go/TODO.md",
"output_mode": "content",
"-i": true
}{
"pattern": "Array|array",
"path": "/home/david/projects/code-associates/occam2go/TODO.md",
"output_mode": "content",
"-n": true
}18:- **Arrays** — `[n]TYPE arr:` with index expressions 20:- **Channel arrays** — `[n]CHAN OF TYPE cs:` with indexed send/receive and `[]CHAN OF TYPE` proc params 29:- **PROC** — Declaration with VAL, reference, CHAN OF, and open array (`[]TYPE`) parameters 46:- **Array indexing** — `arr[i]`, `arr[expr]` 52:- **Array slices** — `[arr FROM n FOR m]` with slice assignment 53:- **Array literals** — `[1, 2, 3]` — inline array/table expressions 90:| **Counted array protocol** | `BYTE::[]BYTE` — length-prefixed array in protocols. | shared_screen.inc, shared_screen.occ |
{
"file_path": "/home/david/projects/code-associates/occam2go/TODO.md",
"offset": 16,
"limit": 10
} 16→- **INT, INT16, INT32, INT64, BYTE, BOOL, REAL, REAL32, REAL64** — Scalar types (INT16/32/64 map to int16/32/64, REAL/REAL64 map to float64, REAL32 maps to float32)
17→- **Variable declarations** — `INT x, y, z:`
18→- **Arrays** — `[n]TYPE arr:` with index expressions
19→- **Channels** — `CHAN OF TYPE c:` with send (`!`) and receive (`?`); `CHAN BYTE` shorthand (without `OF`)
20→- **Channel arrays** — `[n]CHAN OF TYPE cs:` with indexed send/receive and `[]CHAN OF TYPE` proc params
21→- **Channel direction** — `CHAN OF INT c?` (receive-only) and `CHAN OF INT c!` (send-only); direction annotations at call sites (`out!`, `in?`) accepted and ignored
22→- **Timers** — `TIMER tim:` with reads and `AFTER` expressions
23→- **Abbreviations** — `VAL INT x IS 1:`, `INT y IS z:`, untyped `VAL x IS expr:` — named constants and aliases
24→- **INITIAL declarations** — `INITIAL INT x IS 42:` — mutable variables with initial values
25→- **Byte literals** — `'A'`, `'0'` with occam escape sequences (`*n`, `*c`, `*t`)- **Arrays** — `[n]TYPE arr:` with index expressions - **Channels** — `CHAN OF TYPE c:` with send (`!`) and receive (`?`); `CHAN BYTE` shorthand (without `OF`) - **Channel arrays** — `[n]CHAN OF TYPE cs:` with indexed send/receive and `[]CHAN OF TYPE` proc params
- **Arrays** — `[n]TYPE arr:` with index expressions; multi-dimensional `[n][m]TYPE` with nested init loops - **Channels** — `CHAN OF TYPE c:` with send (`!`) and receive (`?`); `CHAN BYTE` shorthand (without `OF`) - **Channel arrays** — `[n]CHAN OF TYPE cs:` with indexed send/receive; multi-dimensional `[n][m]CHAN OF TYPE` with nested init loops; `[]CHAN`, `[][]CHAN`, etc. proc params
The file /home/david/projects/code-associates/occam2go/TODO.md has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/TODO.md",
"offset": 44,
"limit": 12
} 44→- **AFTER** — As boolean expression (maps to `>`)
45→- **Parenthesized expressions**
46→- **Array indexing** — `arr[i]`, `arr[expr]`
47→- **String literals** — Double-quoted strings
48→- **Type conversions** — `INT expr`, `INT16 expr`, `INT32 expr`, `INT64 expr`, `BYTE expr`, `BOOL expr`, `REAL32 expr`, `REAL64 expr` (including BOOL↔numeric conversions, and ROUND/TRUNC qualifiers for float↔int conversions)
49→- **Checked arithmetic** — `PLUS`, `MINUS`, `TIMES` — modular (wrapping) operators
50→- **MOSTNEG/MOSTPOS** — Type min/max constants for INT, INT16, INT32, INT64, BYTE, REAL32, REAL64
51→- **SIZE operator** — `SIZE arr`, `SIZE "str"` maps to `len()`
52→- **Array slices** — `[arr FROM n FOR m]` with slice assignment
53→- **Array literals** — `[1, 2, 3]` — inline array/table expressions
54→- **Multi-assignment** — `a, b := f(...)` including indexed targets like `x[0], x[1] := x[1], x[0]`
55→- **Multi-line expression continuation** — Binary operators and `:=` at end of line continue expression on next line- **Array indexing** — `arr[i]`, `arr[expr]`
- **Array indexing** — `arr[i]`, `arr[expr]`, multi-dimensional `grid[i][j]`
The file /home/david/projects/code-associates/occam2go/TODO.md has been updated successfully.
Now let me check if there's a "Not Yet Implemented" section that mentions multi-dim arrays:
{
"pattern": "Not Yet|TODO|Priority",
"path": "/home/david/projects/code-associates/occam2go/TODO.md",
"output_mode": "content",
"-n": true
}82:## Not Yet Implemented 97:| **PRI ALT / PRI PAR** | Priority variants of ALT and PAR. |
{
"file_path": "/home/david/projects/code-associates/occam2go/TODO.md",
"offset": 82,
"limit": 25
} 82→## Not Yet Implemented
83→
84→### Required for shared_screen module (extends course module)
85→
86→| Feature | Notes | Used in |
87→|---------|-------|---------|
88→| **`DATA TYPE X IS TYPE:`** | Simple type alias (e.g. `DATA TYPE COLOUR IS BYTE:`). | shared_screen.inc |
89→| **`DATA TYPE X RECORD`** | Alternative record syntax (vs current `RECORD X`). | shared_screen.inc |
90→| **Counted array protocol** | `BYTE::[]BYTE` — length-prefixed array in protocols. | shared_screen.inc, shared_screen.occ |
91→| **`RESULT` param qualifier** | `RESULT INT len` on PROC params (output-only, like a write-only reference). | float_io.occ |
92→
93→### Other language features
94→
95→| Feature | Notes |
96→|---------|-------|
97→| **PRI ALT / PRI PAR** | Priority variants of ALT and PAR. |
98→| **PLACED PAR** | Assigning processes to specific hardware. |
99→| **PORT OF** | Hardware port mapping. |
100→| **`VAL []BYTE` abbreviations** | `VAL []BYTE cmap IS "0123456789ABCDEF":` — named string constants. |
101→| **`#PRAGMA DEFINED`** | Compiler hint to suppress definedness warnings. Can be ignored. |
102→No mention of multi-dim arrays in the "not yet implemented" section, so nothing to remove.
go build ./... && go test ./... && ./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
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen (cached) ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser (cached) ok github.com/codeassociates/occam2go/preproc (cached)
Everything passes. Let me summarize what was done:
Multi-dimensional array support has been implemented across the full pipeline:
ast/ast.go)ArrayDecl.Size → Sizes []ExpressionChanDecl.IsArray/Size → Sizes []Expression (empty = scalar)Send/Receive/AltCase/VariantReceive.ChannelIndex → ChannelIndices []ExpressionAssignment.Index → Indices []ExpressionMultiAssignTarget.Index → Indices []ExpressionProcParam.IsChanArray/IsOpenArray → ChanArrayDims int/OpenArrayDims intparser/parser.go)parseArrayDecl(): Loops collecting [n][m]... sizes before CHAN or type keywordparseIndexedOperation(): Loops collecting name[i][j]... indices for assignments, sends, receivesparseProcParams(): Counts consecutive [] pairs for [][]CHAN OF TYPE and [][]TYPE paramsparseAltCase(): Loops collecting channel indices for indexed ALT casescodegen/codegen.go)generateIndices()/generateIndicesStr(): New helpers for emitting [idx1][idx2]...generateArrayDecl(): Generates nested make + init loops for multi-dim arraysgenerateMultiDimChanInit(): Recursive nested make + init loops with innermost make(chan T)generateProcParams(): Uses strings.Repeat("[]", dims) for multi-dim type stringsgenerateIndices()[][]CHAN paramCLAUDE.md mapping table with multi-dim entriesTODO.md implemented features listOn 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: CLAUDE.md modified: TODO.md modified: ast/ast.go modified: codegen/codegen.go modified: codegen/codegen_test.go modified: codegen/e2e_array_test.go modified: parser/parser.go modified: parser/parser_test.go no changes added to commit (use "git add" and/or "git commit -a")
CLAUDE.md | 8 +- TODO.md | 6 +- ast/ast.go | 93 ++++++++------- codegen/codegen.go | 281 ++++++++++++++++++++++++++++++++-------------- codegen/codegen_test.go | 66 ++++++++++- codegen/e2e_array_test.go | 72 ++++++++++++ parser/parser.go | 126 +++++++++++++-------- parser/parser_test.go | 281 ++++++++++++++++++++++++++++++++++++++++------ 8 files changed, 710 insertions(+), 223 deletions(-)
34f69ff Merge pull request #80 from codeassociates/fix/life-occ-issues-73-75-78-79 cec0dac Fix protocol trailing colon, CASE comma values, ALT paren guards, IS continuation 9588006 Merge pull request #71 from codeassociates/dboreham/add-life-example 8acee6e Add Conway Life example program from Programming in occam2 for study of transpiler compatibility 25ad300 Merge pull request #69 from codeassociates/fix/dotted-protocol-tags
<persisted-output> Output too large (60.4KB). Full output saved to: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/f592dfca-107d-4498-99d8-880feeeaa924/tool-results/b737955.txt Preview (first 2KB): diff --git a/CLAUDE.md b/CLAUDE.md index 8ab5188..d1a5ae8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,9 +111,15 @@ Six packages, one pipeline: | `~` | `^` (bitwise NOT) | | `<<` / `>>` | `<<` / `>>` (shifts) | | `[5]CHAN OF INT cs:` | `cs := make([]chan int, 5)` + init loop | +| `[w][h]CHAN OF INT cs:` | `cs := make([][]chan int, w)` + nested init loops | | `cs[i] ! 42` | `cs[i] <- 42` | | `cs[i] ? x` | `x = <-cs[i]` | +| `cs[i][j] ! 42` | `cs[i][j] <- 42` (multi-dim channel index) | +| `cs[i][j] ? x` | `x = <-cs[i][j]` (multi-dim channel index) | +| `[3][4]INT grid:` | `grid := make([][]int, 3)` + nested init loops | +| `grid[i][j] := 42` | `grid[i][j] = 42` (multi-dim array index) | | `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | +| `PROC f([][]CHAN OF INT cs)` | `func f(cs [][]chan int)` (multi-dim chan array) | | `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | @@ -184,7 +190,7 @@ Typical workflow for a new language construct: ## What's Implemented ... </persisted-output>
git add ast/ast.go parser/parser.go parser/parser_test.go codegen/codegen.go codegen/codegen_test.go codegen/e2e_array_test.go CLAUDE.md TODO.md
git commit -m "$(cat <<'EOF' Add multi-dimensional array and channel array support Support [n][m]TYPE and [n][m]CHAN OF TYPE declarations with nested make+init loops, multi-dim indexed send/receive/assignment (grid[i][j]), and [][]CHAN/[][]TYPE proc params. Required for Life example's cell grid and channel network. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
8 files changed, 710 insertions(+), 223 deletions(-)
remote: remote: Create a pull request for 'feature/multi-dim-arrays' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/feature/multi-dim-arrays remote: To associate-1.github.com:codeassociates/occam2go.git * [new branch] feature/multi-dim-arrays -> feature/multi-dim-arrays branch 'feature/multi-dim-arrays' set up to track 'origin/feature/multi-dim-arrays'.
gh pr create --title "Add multi-dimensional array and channel array support" --body "$(cat <<'EOF' ## Summary - Support `[n][m]TYPE` and `[n][m]CHAN OF TYPE` declarations with nested `make` + init loops in generated Go code - Multi-dimensional indexed send/receive/assignment (`grid[i][j] := 42`, `cs[i][j] ! x`, `cs[i][j] ? x`) - Multi-dimensional proc params: `[][]CHAN OF TYPE`, `[][]TYPE` with arbitrary nesting depth - AST fields generalized from single values to slices (`Size→Sizes`, `Index→Indices`, `ChannelIndex→ChannelIndices`, `IsChanArray→ChanArrayDims`, `IsOpenArray→OpenArrayDims`) ## Test plan - [x] All existing tests pass (parser, codegen, e2e, course module) - [x] 6 new parser tests for multi-dim declarations, indexed ops, and `[][]CHAN` params - [x] 6 new codegen unit tests for multi-dim array/chan init, send/receive, assignment, proc params - [x] 3 new e2e tests: 2D array fill+read, 2D channel array send/receive, 2D channel array passed to proc - [x] Course module (`course.module` including `float_io.occ`) still transpiles and passes `go vet` 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"