This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.
Analysis: Let me chronologically analyze the conversation:
Initial Request: User wants to create a transpiler from Occam to Go, written in Go. They explain the historical connection between Occam (1980s) and Go (CSP-influenced).
Phase 1 - CLI Setup: User asked for a basic CLI program first. I created:
Had to install Go toolchain (user did it, I just added PATH)
Phase 2 - Core Implementation Plan: User suggested implementing:
Defer channels and ALT for later
Lexer Implementation: Created lexer with indentation handling (like Python):
Fixed infinite loop bug in blank line handling
Parser Implementation: Created Pratt parser:
parser/parser.go - parser with precedence
Code Generator: Created codegen that produces Go code:
Handles PAR → goroutines with sync.WaitGroup
Bug Fix - Nested Blocks: Fixed parsing of nested blocks in PAR (e.g., PAR containing multiple SEQ blocks). The issue was breaking on inner DEDENT instead of continuing.
End-to-End Testing: User noted there was no end-to-end testing. I added:
codegen/e2e_test.go - tests that transpile, compile, and run
Documentation: Added .gitignore, updated README with build/run instructions, removed license section per user request.
Channels Implementation: Added:
ALT Implementation: Added:
WHILE Implementation: Was already implemented, just added tests and documentation.
Current Work - Replicators: Started implementing PAR i = 0 FOR n and SEQ i = 0 FOR n:
Key files: - main.go - CLI entry point - lexer/token.go, lexer/lexer.go, lexer/lexer_test.go - ast/ast.go - parser/parser.go, parser/parser_test.go - codegen/codegen.go, codegen/codegen_test.go, codegen/e2e_test.go - examples/*.occ
Errors fixed: - Infinite loop in lexer (EOF handling in blank line skipping) - Nested block parsing (inner DEDENT breaking outer loop) - Missing fmt import for print in ALT cases
Summary: 1. Primary Request and Intent: The user requested creation of a transpiler from Occam to Go (golang), written in Go itself. Occam is a 1980s concurrent programming language with CSP-influenced constructs (PAR, channels, ALT) that map naturally to Go's goroutines, channels, and select. The user outlined a phased approach: basic variables/expressions → procedures → PAR → channels → ALT. They emphasized end-to-end testing (transpile → compile → run → verify output) and documentation.
Replicators: PAR i = 0 FOR n, SEQ i = 0 FOR n
Files and Code Sections:
ast/ast.go - AST nodes including recent addition of Replicator: ```go // Replicator represents a replication spec: i = start FOR count type Replicator struct { Variable string // loop variable name Start Expression // start value Count Expression // number of iterations }
// SeqBlock and ParBlock now have optional Replicator field type SeqBlock struct { Token lexer.Token Statements []Statement Replicator *Replicator // optional replicator }
type ParBlock struct { Token lexer.Token Statements []Statement Replicator Replicator // optional replicator } ``` - parser/parser.go - Pratt parser with parseBlockStatements fixed for nested blocks - codegen/codegen.go - Generates Go code, handles PAR→goroutines, ALT→select, channels - codegen/e2e_test.go* - End-to-end tests that transpile, compile, run, and verify output
Errors and fixes:
l.ch != 0 in the loop condition and returning false for EOF in isBlankLine().go
for p.curTokenIs(lexer.DEDENT) {
if p.indentLevel < startLevel {
return statements
}
p.nextToken() // skip nested block's DEDENT
}Missing fmt import: containsPrint() didn't handle AltBlock, so print calls inside ALT didn't trigger fmt import. Added AltBlock case to containsPrint() and containsPar().
Problem Solving:
Created end-to-end test framework that creates temp files, compiles, runs, and captures output
All user messages:
"Yes please." (for replicators)
Pending Tasks:
Code generator needs to handle replicated PAR/SEQ
Current Work:
I was implementing replicators when the summary was requested. I had just updated ast/ast.go to add the Replicator struct and modified SeqBlock and ParBlock to include an optional Replicator field. The next step was to update the parser to detect replicator syntax (checking if after PAR/SEQ there's an identifier followed by =).
I was reading parser/parser.go at the parseSeqBlock and parseParBlock functions to modify them to detect replicators:
go
func (p *Parser) parseSeqBlock() *ast.SeqBlock {
block := &ast.SeqBlock{Token: p.curToken}
// Need to add: check for replicator (IDENT = expr FOR expr)
...
}
User's last message: "Yes please." in response to my suggestion to implement replicators.
My last action was reading parser.go at the parseSeqBlock/parseParBlock functions to add replicator detection.
If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-codeassociates-occam2go/e2588b64-87aa-42a0-8d5d-b7804538d146.jsonl Please continue the conversation from where we left it off without asking the user any further questions. Continue with the last task that you were asked to work on.
The user wants me to continue implementing replicators. Based on the summary, I had: 1. Updated ast/ast.go with Replicator struct and modified SeqBlock/ParBlock to include optional Replicator field 2. Was about to update the parser to detect replicator syntax
Let me continue by: 1. First reading the parser.go to see the current state of parseSeqBlock and parseParBlock 2. Modify them to detect and parse replicator syntax 3. Update the code generator to handle replicated PAR/SEQ 4. Add tests
Let me start by reading the parser to see the current implementation.
I'll continue implementing replicators. Let me first read the parser to see the current parseSeqBlock and parseParBlock implementations.
1→package parser
2→
3→import (
4→ "fmt"
5→ "strconv"
6→
7→ "github.com/codeassociates/occam2go/ast"
8→ "github.com/codeassociates/occam2go/lexer"
9→)
10→
11→// Operator precedence levels
12→const (
13→ _ int = iota
14→ LOWEST
15→ OR_PREC // OR
16→ AND_PREC // AND
17→ EQUALS // =, <>
18→ LESSGREATER // <, >, <=, >=
19→ SUM // +, -
20→ PRODUCT // *, /, \
21→ PREFIX // -x, NOT x
22→)
23→
24→var precedences = map[lexer.TokenType]int{
25→ lexer.OR: OR_PREC,
26→ lexer.AND: AND_PREC,
27→ lexer.EQ: EQUALS,
28→ lexer.NEQ: EQUALS,
29→ lexer.LT: LESSGREATER,
30→ lexer.GT: LESSGREATER,
31→ lexer.LE: LESSGREATER,
32→ lexer.GE: LESSGREATER,
33→ lexer.PLUS: SUM,
34→ lexer.MINUS: SUM,
35→ lexer.MULTIPLY: PRODUCT,
36→ lexer.DIVIDE: PRODUCT,
37→ lexer.MODULO: PRODUCT,
38→}
39→
40→type Parser struct {
41→ l *lexer.Lexer
42→ errors []string
43→
44→ curToken lexer.Token
45→ peekToken lexer.Token
46→
47→ // Track current indentation level
48→ indentLevel int
49→}
50→
51→func New(l *lexer.Lexer) *Parser {
52→ p := &Parser{
53→ l: l,
54→ errors: []string{},
55→ }
56→ // Read two tokens to initialize curToken and peekToken
57→ p.nextToken()
58→ p.nextToken()
59→ return p
60→}
61→
62→func (p *Parser) Errors() []string {
63→ return p.errors
64→}
65→
66→func (p *Parser) addError(msg string) {
67→ p.errors = append(p.errors, fmt.Sprintf("line %d: %s", p.curToken.Line, msg))
68→}
69→
70→func (p *Parser) nextToken() {
71→ p.curToken = p.peekToken
72→ p.peekToken = p.l.NextToken()
73→
74→ // Track indentation level
75→ if p.curToken.Type == lexer.INDENT {
76→ p.indentLevel++
77→ } else if p.curToken.Type == lexer.DEDENT {
78→ p.indentLevel--
79→ }
80→}
81→
82→func (p *Parser) curTokenIs(t lexer.TokenType) bool {
83→ return p.curToken.Type == t
84→}
85→
86→func (p *Parser) peekTokenIs(t lexer.TokenType) bool {
87→ return p.peekToken.Type == t
88→}
89→
90→func (p *Parser) expectPeek(t lexer.TokenType) bool {
91→ if p.peekTokenIs(t) {
92→ p.nextToken()
93→ return true
94→ }
95→ p.addError(fmt.Sprintf("expected %s, got %s", t, p.peekToken.Type))
96→ return false
97→}
98→
99→func (p *Parser) peekPrecedence() int {
100→ if prec, ok := precedences[p.peekToken.Type]; ok {
101→ return prec
102→ }
103→ return LOWEST
104→}
105→
106→func (p *Parser) curPrecedence() int {
107→ if prec, ok := precedences[p.curToken.Type]; ok {
108→ return prec
109→ }
110→ return LOWEST
111→}
112→
113→// ParseProgram parses the entire program
114→func (p *Parser) ParseProgram() *ast.Program {
115→ program := &ast.Program{}
116→ program.Statements = []ast.Statement{}
117→
118→ for !p.curTokenIs(lexer.EOF) {
119→ stmt := p.parseStatement()
120→ if stmt != nil {
121→ program.Statements = append(program.Statements, stmt)
122→ }
123→ p.nextToken()
124→ }
125→
126→ return program
127→}
128→
129→func (p *Parser) parseStatement() ast.Statement {
130→ // Skip newlines
131→ for p.curTokenIs(lexer.NEWLINE) {
132→ p.nextToken()
133→ }
134→
135→ switch p.curToken.Type {
136→ case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE:
137→ return p.parseVarDecl()
138→ case lexer.CHAN:
139→ return p.parseChanDecl()
140→ case lexer.SEQ:
141→ return p.parseSeqBlock()
142→ case lexer.PAR:
143→ return p.parseParBlock()
144→ case lexer.ALT:
145→ return p.parseAltBlock()
146→ case lexer.SKIP:
147→ return &ast.Skip{Token: p.curToken}
148→ case lexer.PROC:
149→ return p.parseProcDecl()
150→ case lexer.WHILE:
151→ return p.parseWhileLoop()
152→ case lexer.IF:
153→ return p.parseIfStatement()
154→ case lexer.IDENT:
155→ // Could be assignment, send, receive, or procedure call
156→ if p.peekTokenIs(lexer.ASSIGN) {
157→ return p.parseAssignment()
158→ }
159→ if p.peekTokenIs(lexer.SEND) {
160→ return p.parseSend()
161→ }
162→ if p.peekTokenIs(lexer.RECEIVE) {
163→ return p.parseReceive()
164→ }
165→ return p.parseProcCall()
166→ case lexer.INDENT, lexer.DEDENT, lexer.EOF:
167→ return nil
168→ default:
169→ p.addError(fmt.Sprintf("unexpected token: %s", p.curToken.Type))
170→ return nil
171→ }
172→}
173→
174→func (p *Parser) parseVarDecl() *ast.VarDecl {
175→ decl := &ast.VarDecl{
176→ Token: p.curToken,
177→ Type: p.curToken.Literal,
178→ }
179→
180→ // Parse variable names
181→ for {
182→ if !p.expectPeek(lexer.IDENT) {
183→ return nil
184→ }
185→ decl.Names = append(decl.Names, p.curToken.Literal)
186→
187→ if p.peekTokenIs(lexer.COMMA) {
188→ p.nextToken() // consume comma
189→ } else {
190→ break
191→ }
192→ }
193→
194→ if !p.expectPeek(lexer.COLON) {
195→ return nil
196→ }
197→
198→ return decl
199→}
200→
201→func (p *Parser) parseAssignment() *ast.Assignment {
202→ stmt := &ast.Assignment{
203→ Name: p.curToken.Literal,
204→ }
205→
206→ p.nextToken() // move to :=
207→ stmt.Token = p.curToken
208→
209→ p.nextToken() // move past :=
210→ stmt.Value = p.parseExpression(LOWEST)
211→
212→ return stmt
213→}
214→
215→func (p *Parser) parseChanDecl() *ast.ChanDecl {
216→ decl := &ast.ChanDecl{Token: p.curToken}
217→
218→ // Expect OF
219→ if !p.expectPeek(lexer.OF) {
220→ return nil
221→ }
222→
223→ // Expect type (INT, BYTE, BOOL, etc.)
224→ p.nextToken()
225→ if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
226→ !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) {
227→ p.addError(fmt.Sprintf("expected type after CHAN OF, got %s", p.curToken.Type))
228→ return nil
229→ }
230→ decl.ElemType = p.curToken.Literal
231→
232→ // Parse channel names
233→ for {
234→ if !p.expectPeek(lexer.IDENT) {
235→ return nil
236→ }
237→ decl.Names = append(decl.Names, p.curToken.Literal)
238→
239→ if p.peekTokenIs(lexer.COMMA) {
240→ p.nextToken() // consume comma
241→ } else {
242→ break
243→ }
244→ }
245→
246→ if !p.expectPeek(lexer.COLON) {
247→ return nil
248→ }
249→
250→ return decl
251→}
252→
253→func (p *Parser) parseSend() *ast.Send {
254→ stmt := &ast.Send{
255→ Channel: p.curToken.Literal,
256→ }
257→
258→ p.nextToken() // move to !
259→ stmt.Token = p.curToken
260→
261→ p.nextToken() // move past !
262→ stmt.Value = p.parseExpression(LOWEST)
263→
264→ return stmt
265→}
266→
267→func (p *Parser) parseReceive() *ast.Receive {
268→ stmt := &ast.Receive{
269→ Channel: p.curToken.Literal,
270→ }
271→
272→ p.nextToken() // move to ?
273→ stmt.Token = p.curToken
274→
275→ if !p.expectPeek(lexer.IDENT) {
276→ return nil
277→ }
278→ stmt.Variable = p.curToken.Literal
279→
280→ return stmt
281→}
282→
283→func (p *Parser) parseSeqBlock() *ast.SeqBlock {
284→ block := &ast.SeqBlock{Token: p.curToken}
285→
286→ // Skip to next line
287→ for p.peekTokenIs(lexer.NEWLINE) {
288→ p.nextToken()
289→ }
290→
291→ // Expect INDENT
292→ if !p.peekTokenIs(lexer.INDENT) {
293→ p.addError("expected indented block after SEQ")
294→ return block
295→ }
296→ p.nextToken() // consume INDENT
297→
298→ block.Statements = p.parseBlockStatements()
299→
300→ return block
301→}
302→
303→func (p *Parser) parseParBlock() *ast.ParBlock {
304→ block := &ast.ParBlock{Token: p.curToken}
305→
306→ // Skip to next line
307→ for p.peekTokenIs(lexer.NEWLINE) {
308→ p.nextToken()
309→ }
310→
311→ // Expect INDENT
312→ if !p.peekTokenIs(lexer.INDENT) {
313→ p.addError("expected indented block after PAR")
314→ return block
315→ }
316→ p.nextToken() // consume INDENT
317→
318→ block.Statements = p.parseBlockStatements()
319→
320→ return block
321→}
322→
323→func (p *Parser) parseAltBlock() *ast.AltBlock {
324→ block := &ast.AltBlock{Token: p.curToken}
325→
326→ // Skip to next line
327→ for p.peekTokenIs(lexer.NEWLINE) {
328→ p.nextToken()
329→ }
330→
331→ // Expect INDENT
332→ if !p.peekTokenIs(lexer.INDENT) {
333→ p.addError("expected indented block after ALT")
334→ return block
335→ }
336→ p.nextToken() // consume INDENT
337→
338→ block.Cases = p.parseAltCases()
339→
340→ return block
341→}
342→
343→func (p *Parser) parseAltCases() []ast.AltCase {
344→ var cases []ast.AltCase
345→ startLevel := p.indentLevel
346→
347→ p.nextToken() // move past INDENT
348→
349→ for !p.curTokenIs(lexer.EOF) {
350→ // Skip newlines
351→ for p.curTokenIs(lexer.NEWLINE) {
352→ p.nextToken()
353→ }
354→
355→ // Handle DEDENT tokens
356→ for p.curTokenIs(lexer.DEDENT) {
357→ if p.indentLevel < startLevel {
358→ return cases
359→ }
360→ p.nextToken()
361→ }
362→
363→ // Skip any more newlines after DEDENT
364→ for p.curTokenIs(lexer.NEWLINE) {
365→ p.nextToken()
366→ }
367→
368→ if p.curTokenIs(lexer.EOF) {
369→ break
370→ }
371→
372→ if p.indentLevel < startLevel {
373→ break
374→ }
375→
376→ // Parse an ALT case: [guard &] channel ? var
377→ altCase := p.parseAltCase()
378→ if altCase != nil {
379→ cases = append(cases, *altCase)
380→ }
381→ }
382→
383→ return cases
384→}
385→
386→func (p *Parser) parseAltCase() *ast.AltCase {
387→ altCase := &ast.AltCase{}
388→
389→ // Check for guard: expression & channel ? var
390→ // For now, we expect: channel ? var (no guard support yet)
391→ // or: guard & channel ? var
392→
393→ // First token should be identifier (channel name or guard start)
394→ if !p.curTokenIs(lexer.IDENT) && !p.curTokenIs(lexer.TRUE) && !p.curTokenIs(lexer.FALSE) {
395→ p.addError(fmt.Sprintf("expected channel name or guard in ALT case, got %s", p.curToken.Type))
396→ return nil
397→ }
398→
399→ // Look ahead to determine if this is a guard or channel
400→ // If next token is & then we have a guard
401→ // If next token is ? then it's a channel receive
402→ if p.peekTokenIs(lexer.RECEIVE) {
403→ // Simple case: channel ? var
404→ altCase.Channel = p.curToken.Literal
405→ p.nextToken() // move to ?
406→ if !p.expectPeek(lexer.IDENT) {
407→ return nil
408→ }
409→ altCase.Variable = p.curToken.Literal
410→ } else {
411→ // Could be a guard followed by & channel ? var
412→ // For simplicity, parse expression until we hit &
413→ // For now, only support simple TRUE/FALSE or identifier guards
414→ guard := p.parseExpression(LOWEST)
415→ altCase.Guard = guard
416→
417→ // Expect &
418→ if !p.peekTokenIs(lexer.AMPERSAND) {
419→ p.addError("expected & after guard in ALT case")
420→ return nil
421→ }
422→ p.nextToken() // move to &
423→ p.nextToken() // move past &
424→
425→ // Now expect channel ? var
426→ if !p.curTokenIs(lexer.IDENT) {
427→ p.addError(fmt.Sprintf("expected channel name after guard, got %s", p.curToken.Type))
428→ return nil
429→ }
430→ altCase.Channel = p.curToken.Literal
431→
432→ if !p.expectPeek(lexer.RECEIVE) {
433→ return nil
434→ }
435→ if !p.expectPeek(lexer.IDENT) {
436→ return nil
437→ }
438→ altCase.Variable = p.curToken.Literal
439→ }
440→
441→ // Skip to next line for the body
442→ for p.peekTokenIs(lexer.NEWLINE) {
443→ p.nextToken()
444→ }
445→
446→ // Expect INDENT for body
447→ if !p.peekTokenIs(lexer.INDENT) {
448→ p.addError("expected indented body after ALT case")
449→ return altCase
450→ }
451→ p.nextToken() // consume INDENT
452→ p.nextToken() // move into body
453→
454→ // Parse the body (first statement)
455→ altCase.Body = p.parseStatement()
456→
457→ // Skip to end of body block
458→ for !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
459→ p.nextToken()
460→ }
461→
462→ return altCase
463→}
464→
465→func (p *Parser) parseBlockStatements() []ast.Statement {
466→ var statements []ast.Statement
467→ startLevel := p.indentLevel
468→
469→ p.nextToken() // move past INDENT
470→
471→ for !p.curTokenIs(lexer.EOF) {
472→ // Skip newlines
473→ for p.curTokenIs(lexer.NEWLINE) {
474→ p.nextToken()
475→ }
476→
477→ // Handle DEDENT tokens
478→ // If we're at a DEDENT and indentLevel has dropped below startLevel,
479→ // this DEDENT ends our block - stop parsing
480→ // If indentLevel >= startLevel, this DEDENT is from a nested block - skip it
481→ for p.curTokenIs(lexer.DEDENT) {
482→ if p.indentLevel < startLevel {
483→ return statements
484→ }
485→ p.nextToken() // skip nested block's DEDENT
486→ }
487→
488→ // Skip any more newlines after DEDENT
489→ for p.curTokenIs(lexer.NEWLINE) {
490→ p.nextToken()
491→ }
492→
493→ if p.curTokenIs(lexer.EOF) {
494→ break
495→ }
496→
497→ // Double-check we haven't gone below our level
498→ if p.indentLevel < startLevel {
499→ break
500→ }
501→
502→ stmt := p.parseStatement()
503→ if stmt != nil {
504→ statements = append(statements, stmt)
505→ }
506→
507→ // After parsing a statement, we need to advance.
508→ // But if we're already at NEWLINE/DEDENT/EOF, the next iteration will handle it.
509→ // Only advance if we're still on the last token of the statement.
510→ if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
511→ p.nextToken()
512→ }
513→ }
514→
515→ return statements
516→}
517→
518→func (p *Parser) parseProcDecl() *ast.ProcDecl {
519→ proc := &ast.ProcDecl{Token: p.curToken}
520→
521→ if !p.expectPeek(lexer.IDENT) {
522→ return nil
523→ }
524→ proc.Name = p.curToken.Literal
525→
526→ if !p.expectPeek(lexer.LPAREN) {
527→ return nil
528→ }
529→
530→ proc.Params = p.parseProcParams()
531→
532→ if !p.expectPeek(lexer.RPAREN) {
533→ return nil
534→ }
535→
536→ // Skip to next line and expect indented body
537→ for p.peekTokenIs(lexer.NEWLINE) {
538→ p.nextToken()
539→ }
540→
541→ if !p.peekTokenIs(lexer.INDENT) {
542→ p.addError("expected indented body after PROC declaration")
543→ return proc
544→ }
545→ p.nextToken() // consume INDENT
546→
547→ // Parse the procedure body (first statement in the indented block)
548→ p.nextToken()
549→ proc.Body = p.parseStatement()
550→
551→ // Consume remaining statements at this level and the DEDENT
552→ for !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
553→ p.nextToken()
554→ }
555→
556→ return proc
557→}
558→
559→func (p *Parser) parseProcParams() []ast.ProcParam {
560→ var params []ast.ProcParam
561→
562→ if p.peekTokenIs(lexer.RPAREN) {
563→ return params
564→ }
565→
566→ p.nextToken()
567→
568→ for {
569→ param := ast.ProcParam{}
570→
571→ // Check for VAL keyword
572→ if p.curTokenIs(lexer.VAL) {
573→ param.IsVal = true
574→ p.nextToken()
575→ }
576→
577→ // Expect type
578→ if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
579→ !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) {
580→ p.addError(fmt.Sprintf("expected type in parameter, got %s", p.curToken.Type))
581→ return params
582→ }
583→ param.Type = p.curToken.Literal
584→ p.nextToken()
585→
586→ // Expect identifier
587→ if !p.curTokenIs(lexer.IDENT) {
588→ p.addError(fmt.Sprintf("expected parameter name, got %s", p.curToken.Type))
589→ return params
590→ }
591→ param.Name = p.curToken.Literal
592→
593→ params = append(params, param)
594→
595→ if !p.peekTokenIs(lexer.COMMA) {
596→ break
597→ }
598→ p.nextToken() // consume comma
599→ p.nextToken() // move to next param
600→ }
601→
602→ return params
603→}
604→
605→func (p *Parser) parseProcCall() *ast.ProcCall {
606→ call := &ast.ProcCall{
607→ Token: p.curToken,
608→ Name: p.curToken.Literal,
609→ }
610→
611→ if !p.peekTokenIs(lexer.LPAREN) {
612→ // No arguments
613→ return call
614→ }
615→
616→ p.nextToken() // consume (
617→
618→ if p.peekTokenIs(lexer.RPAREN) {
619→ p.nextToken() // consume )
620→ return call
621→ }
622→
623→ p.nextToken() // move to first arg
624→ call.Args = append(call.Args, p.parseExpression(LOWEST))
625→
626→ for p.peekTokenIs(lexer.COMMA) {
627→ p.nextToken() // consume comma
628→ p.nextToken() // move to next arg
629→ call.Args = append(call.Args, p.parseExpression(LOWEST))
630→ }
631→
632→ if !p.expectPeek(lexer.RPAREN) {
633→ return nil
634→ }
635→
636→ return call
637→}
638→
639→func (p *Parser) parseWhileLoop() *ast.WhileLoop {
640→ loop := &ast.WhileLoop{Token: p.curToken}
641→
642→ p.nextToken()
643→ loop.Condition = p.parseExpression(LOWEST)
644→
645→ // Skip to next line
646→ for p.peekTokenIs(lexer.NEWLINE) {
647→ p.nextToken()
648→ }
649→
650→ // Expect INDENT
651→ if !p.peekTokenIs(lexer.INDENT) {
652→ p.addError("expected indented block after WHILE condition")
653→ return loop
654→ }
655→ p.nextToken() // consume INDENT
656→ p.nextToken() // move to first statement
657→
658→ loop.Body = p.parseStatement()
659→
660→ // Consume until DEDENT
661→ for !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
662→ p.nextToken()
663→ }
664→
665→ return loop
666→}
667→
668→func (p *Parser) parseIfStatement() *ast.IfStatement {
669→ stmt := &ast.IfStatement{Token: p.curToken}
670→
671→ // Skip to next line
672→ for p.peekTokenIs(lexer.NEWLINE) {
673→ p.nextToken()
674→ }
675→
676→ // Expect INDENT
677→ if !p.peekTokenIs(lexer.INDENT) {
678→ p.addError("expected indented block after IF")
679→ return stmt
680→ }
681→ p.nextToken() // consume INDENT
682→ p.nextToken() // move into block
683→
684→ // Parse if choices (condition -> body pairs)
685→ for !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
686→ // Skip newlines
687→ for p.curTokenIs(lexer.NEWLINE) {
688→ p.nextToken()
689→ }
690→
691→ if p.curTokenIs(lexer.DEDENT) {
692→ break
693→ }
694→
695→ choice := ast.IfChoice{}
696→ choice.Condition = p.parseExpression(LOWEST)
697→
698→ // Skip newlines and expect INDENT for body
699→ for p.peekTokenIs(lexer.NEWLINE) {
700→ p.nextToken()
701→ }
702→
703→ if p.peekTokenIs(lexer.INDENT) {
704→ p.nextToken() // consume INDENT
705→ p.nextToken() // move to body
706→ choice.Body = p.parseStatement()
707→
708→ // Consume until DEDENT
709→ for !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
710→ p.nextToken()
711→ }
712→ }
713→
714→ stmt.Choices = append(stmt.Choices, choice)
715→ p.nextToken()
716→ }
717→
718→ return stmt
719→}
720→
721→// Expression parsing using Pratt parsing
722→
723→func (p *Parser) parseExpression(precedence int) ast.Expression {
724→ var left ast.Expression
725→
726→ switch p.curToken.Type {
727→ case lexer.IDENT:
728→ left = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
729→ case lexer.INT:
730→ val, err := strconv.ParseInt(p.curToken.Literal, 10, 64)
731→ if err != nil {
732→ p.addError(fmt.Sprintf("could not parse %q as integer", p.curToken.Literal))
733→ return nil
734→ }
735→ left = &ast.IntegerLiteral{Token: p.curToken, Value: val}
736→ case lexer.TRUE:
737→ left = &ast.BooleanLiteral{Token: p.curToken, Value: true}
738→ case lexer.FALSE:
739→ left = &ast.BooleanLiteral{Token: p.curToken, Value: false}
740→ case lexer.LPAREN:
741→ p.nextToken()
742→ left = p.parseExpression(LOWEST)
743→ if !p.expectPeek(lexer.RPAREN) {
744→ return nil
745→ }
746→ case lexer.MINUS:
747→ token := p.curToken
748→ p.nextToken()
749→ left = &ast.UnaryExpr{
750→ Token: token,
751→ Operator: "-",
752→ Right: p.parseExpression(PREFIX),
753→ }
754→ case lexer.NOT:
755→ token := p.curToken
756→ p.nextToken()
757→ left = &ast.UnaryExpr{
758→ Token: token,
759→ Operator: "NOT",
760→ Right: p.parseExpression(PREFIX),
761→ }
762→ default:
763→ p.addError(fmt.Sprintf("unexpected token in expression: %s", p.curToken.Type))
764→ return nil
765→ }
766→
767→ // Parse infix expressions
768→ for !p.peekTokenIs(lexer.NEWLINE) && !p.peekTokenIs(lexer.EOF) &&
769→ precedence < p.peekPrecedence() {
770→
771→ switch p.peekToken.Type {
772→ case lexer.PLUS, lexer.MINUS, lexer.MULTIPLY, lexer.DIVIDE, lexer.MODULO,
773→ lexer.EQ, lexer.NEQ, lexer.LT, lexer.GT, lexer.LE, lexer.GE,
774→ lexer.AND, lexer.OR:
775→ p.nextToken()
776→ left = p.parseBinaryExpr(left)
777→ default:
778→ return left
779→ }
780→ }
781→
782→ return left
783→}
784→
785→func (p *Parser) parseBinaryExpr(left ast.Expression) ast.Expression {
786→ expr := &ast.BinaryExpr{
787→ Token: p.curToken,
788→ Left: left,
789→ Operator: p.curToken.Literal,
790→ }
791→
792→ prec := p.curPrecedence()
793→ p.nextToken()
794→ expr.Right = p.parseExpression(prec)
795→
796→ return expr
797→}
798→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I need to modify the parser to detect and parse replicator syntax. The syntax is:
- SEQ i = 0 FOR n - replicated SEQ
- PAR i = 0 FOR n - replicated PAR
I need to:
1. Add a FOR token to the lexer if it doesn't exist
2. Modify parseSeqBlock and parseParBlock to detect replicator syntax
3. Add a parseReplicator function
Let me first check if FOR is already a token in the lexer.
1→package lexer
2→
3→type TokenType int
4→
5→const (
6→ // Special tokens
7→ ILLEGAL TokenType = iota
8→ EOF
9→ NEWLINE
10→ INDENT
11→ DEDENT
12→
13→ // Literals
14→ IDENT // variable names, procedure names
15→ INT // integer literal
16→ STRING // string literal
17→
18→ // Operators
19→ ASSIGN // :=
20→ PLUS // +
21→ MINUS // -
22→ MULTIPLY // *
23→ DIVIDE // /
24→ MODULO // \ (backslash in Occam)
25→ EQ // =
26→ NEQ // <>
27→ LT // <
28→ GT // >
29→ LE // <=
30→ GE // >=
31→ SEND // !
32→ RECEIVE // ?
33→ AMPERSAND // & (guard separator in ALT)
34→
35→ // Delimiters
36→ LPAREN // (
37→ RPAREN // )
38→ LBRACKET // [
39→ RBRACKET // ]
40→ COMMA // ,
41→ COLON // :
42→ SEMICOLON // ;
43→
44→ // Keywords
45→ keyword_beg
46→ SEQ
47→ PAR
48→ ALT
49→ IF
50→ WHILE
51→ FOR
52→ PROC
53→ FUNC
54→ CHAN
55→ OF
56→ TRUE
57→ FALSE
58→ NOT
59→ AND
60→ OR
61→ SKIP
62→ STOP
63→ INT_TYPE
64→ BYTE_TYPE
65→ BOOL_TYPE
66→ REAL_TYPE
67→ VAL
68→ keyword_end
69→)
70→
71→var tokenNames = map[TokenType]string{
72→ ILLEGAL: "ILLEGAL",
73→ EOF: "EOF",
74→ NEWLINE: "NEWLINE",
75→ INDENT: "INDENT",
76→ DEDENT: "DEDENT",
77→
78→ IDENT: "IDENT",
79→ INT: "INT",
80→ STRING: "STRING",
81→
82→ ASSIGN: ":=",
83→ PLUS: "+",
84→ MINUS: "-",
85→ MULTIPLY: "*",
86→ DIVIDE: "/",
87→ MODULO: "\\",
88→ EQ: "=",
89→ NEQ: "<>",
90→ LT: "<",
91→ GT: ">",
92→ LE: "<=",
93→ GE: ">=",
94→ SEND: "!",
95→ RECEIVE: "?",
96→ AMPERSAND: "&",
97→
98→ LPAREN: "(",
99→ RPAREN: ")",
100→ LBRACKET: "[",
101→ RBRACKET: "]",
102→ COMMA: ",",
103→ COLON: ":",
104→ SEMICOLON: ";",
105→
106→ SEQ: "SEQ",
107→ PAR: "PAR",
108→ ALT: "ALT",
109→ IF: "IF",
110→ WHILE: "WHILE",
111→ FOR: "FOR",
112→ PROC: "PROC",
113→ FUNC: "FUNC",
114→ CHAN: "CHAN",
115→ OF: "OF",
116→ TRUE: "TRUE",
117→ FALSE: "FALSE",
118→ NOT: "NOT",
119→ AND: "AND",
120→ OR: "OR",
121→ SKIP: "SKIP",
122→ STOP: "STOP",
123→ INT_TYPE: "INT",
124→ BYTE_TYPE: "BYTE",
125→ BOOL_TYPE: "BOOL",
126→ REAL_TYPE: "REAL",
127→ VAL: "VAL",
128→}
129→
130→var keywords = map[string]TokenType{
131→ "SEQ": SEQ,
132→ "PAR": PAR,
133→ "ALT": ALT,
134→ "IF": IF,
135→ "WHILE": WHILE,
136→ "FOR": FOR,
137→ "PROC": PROC,
138→ "FUNC": FUNC,
139→ "CHAN": CHAN,
140→ "OF": OF,
141→ "TRUE": TRUE,
142→ "FALSE": FALSE,
143→ "NOT": NOT,
144→ "AND": AND,
145→ "OR": OR,
146→ "SKIP": SKIP,
147→ "STOP": STOP,
148→ "INT": INT_TYPE,
149→ "BYTE": BYTE_TYPE,
150→ "BOOL": BOOL_TYPE,
151→ "REAL": REAL_TYPE,
152→ "VAL": VAL,
153→}
154→
155→func (t TokenType) String() string {
156→ if name, ok := tokenNames[t]; ok {
157→ return name
158→ }
159→ return "UNKNOWN"
160→}
161→
162→func LookupIdent(ident string) TokenType {
163→ if tok, ok := keywords[ident]; ok {
164→ return tok
165→ }
166→ return IDENT
167→}
168→
169→type Token struct {
170→ Type TokenType
171→ Literal string
172→ Line int
173→ Column int
174→}
175→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
FOR is already in the lexer. Now I need to:
parseSeqBlock to detect replicator syntax: SEQ i = 0 FOR nparseParBlock to detect replicator syntax: PAR i = 0 FOR nparseReplicator helper functionThe replicator syntax is: SEQ/PAR identifier = expression FOR expression
After seeing SEQ or PAR, I need to check if the next token is an identifier followed by =. If so, it's a replicator.
Let me also check the codegen to understand how it currently handles these blocks.
FOR is already in the lexer. Now let me check the codegen to see how it handles SEQ/PAR blocks before I update the parser.
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→
17→ // Track procedure signatures for proper pointer handling
18→ procSigs map[string][]ast.ProcParam
19→ // Track current procedure's reference parameters
20→ refParams map[string]bool
21→}
22→
23→// Built-in print procedures
24→var printBuiltins = map[string]bool{
25→ "print.int": true,
26→ "print.string": true,
27→ "print.bool": true,
28→ "print.newline": true,
29→}
30→
31→// New creates a new code generator
32→func New() *Generator {
33→ return &Generator{}
34→}
35→
36→// Generate produces Go code from the AST
37→func (g *Generator) Generate(program *ast.Program) string {
38→ g.builder.Reset()
39→ g.needSync = false
40→ g.needFmt = false
41→ g.procSigs = make(map[string][]ast.ProcParam)
42→ g.refParams = make(map[string]bool)
43→
44→ // First pass: collect procedure signatures and check for PAR/print
45→ for _, stmt := range program.Statements {
46→ if g.containsPar(stmt) {
47→ g.needSync = true
48→ }
49→ if g.containsPrint(stmt) {
50→ g.needFmt = true
51→ }
52→ if proc, ok := stmt.(*ast.ProcDecl); ok {
53→ g.procSigs[proc.Name] = proc.Params
54→ }
55→ }
56→
57→ // Write package declaration
58→ g.writeLine("package main")
59→ g.writeLine("")
60→
61→ // Write imports
62→ if g.needSync || g.needFmt {
63→ g.writeLine("import (")
64→ g.indent++
65→ if g.needFmt {
66→ g.writeLine(`"fmt"`)
67→ }
68→ if g.needSync {
69→ g.writeLine(`"sync"`)
70→ }
71→ g.indent--
72→ g.writeLine(")")
73→ g.writeLine("")
74→ }
75→
76→ // Separate procedure declarations from other statements
77→ var procDecls []ast.Statement
78→ var mainStatements []ast.Statement
79→
80→ for _, stmt := range program.Statements {
81→ if _, ok := stmt.(*ast.ProcDecl); ok {
82→ procDecls = append(procDecls, stmt)
83→ } else {
84→ mainStatements = append(mainStatements, stmt)
85→ }
86→ }
87→
88→ // Generate procedure declarations first (at package level)
89→ for _, stmt := range procDecls {
90→ g.generateStatement(stmt)
91→ }
92→
93→ // Generate main function with other statements
94→ if len(mainStatements) > 0 {
95→ g.writeLine("func main() {")
96→ g.indent++
97→ for _, stmt := range mainStatements {
98→ g.generateStatement(stmt)
99→ }
100→ g.indent--
101→ g.writeLine("}")
102→ }
103→
104→ return g.builder.String()
105→}
106→
107→func (g *Generator) containsPar(stmt ast.Statement) bool {
108→ switch s := stmt.(type) {
109→ case *ast.ParBlock:
110→ return true
111→ case *ast.SeqBlock:
112→ for _, inner := range s.Statements {
113→ if g.containsPar(inner) {
114→ return true
115→ }
116→ }
117→ case *ast.AltBlock:
118→ for _, c := range s.Cases {
119→ if c.Body != nil && g.containsPar(c.Body) {
120→ return true
121→ }
122→ }
123→ case *ast.ProcDecl:
124→ if s.Body != nil && g.containsPar(s.Body) {
125→ return true
126→ }
127→ case *ast.WhileLoop:
128→ if s.Body != nil && g.containsPar(s.Body) {
129→ return true
130→ }
131→ case *ast.IfStatement:
132→ for _, choice := range s.Choices {
133→ if choice.Body != nil && g.containsPar(choice.Body) {
134→ return true
135→ }
136→ }
137→ }
138→ return false
139→}
140→
141→func (g *Generator) containsPrint(stmt ast.Statement) bool {
142→ switch s := stmt.(type) {
143→ case *ast.ProcCall:
144→ return printBuiltins[s.Name]
145→ case *ast.SeqBlock:
146→ for _, inner := range s.Statements {
147→ if g.containsPrint(inner) {
148→ return true
149→ }
150→ }
151→ case *ast.ParBlock:
152→ for _, inner := range s.Statements {
153→ if g.containsPrint(inner) {
154→ return true
155→ }
156→ }
157→ case *ast.AltBlock:
158→ for _, c := range s.Cases {
159→ if c.Body != nil && g.containsPrint(c.Body) {
160→ return true
161→ }
162→ }
163→ case *ast.ProcDecl:
164→ if s.Body != nil && g.containsPrint(s.Body) {
165→ return true
166→ }
167→ case *ast.WhileLoop:
168→ if s.Body != nil && g.containsPrint(s.Body) {
169→ return true
170→ }
171→ case *ast.IfStatement:
172→ for _, choice := range s.Choices {
173→ if choice.Body != nil && g.containsPrint(choice.Body) {
174→ return true
175→ }
176→ }
177→ }
178→ return false
179→}
180→
181→func (g *Generator) writeLine(s string) {
182→ if s == "" {
183→ g.builder.WriteString("\n")
184→ return
185→ }
186→ g.builder.WriteString(strings.Repeat("\t", g.indent))
187→ g.builder.WriteString(s)
188→ g.builder.WriteString("\n")
189→}
190→
191→func (g *Generator) write(s string) {
192→ g.builder.WriteString(s)
193→}
194→
195→func (g *Generator) generateStatement(stmt ast.Statement) {
196→ switch s := stmt.(type) {
197→ case *ast.VarDecl:
198→ g.generateVarDecl(s)
199→ case *ast.ChanDecl:
200→ g.generateChanDecl(s)
201→ case *ast.Assignment:
202→ g.generateAssignment(s)
203→ case *ast.Send:
204→ g.generateSend(s)
205→ case *ast.Receive:
206→ g.generateReceive(s)
207→ case *ast.SeqBlock:
208→ g.generateSeqBlock(s)
209→ case *ast.ParBlock:
210→ g.generateParBlock(s)
211→ case *ast.AltBlock:
212→ g.generateAltBlock(s)
213→ case *ast.Skip:
214→ g.writeLine("// SKIP")
215→ case *ast.ProcDecl:
216→ g.generateProcDecl(s)
217→ case *ast.ProcCall:
218→ g.generateProcCall(s)
219→ case *ast.WhileLoop:
220→ g.generateWhileLoop(s)
221→ case *ast.IfStatement:
222→ g.generateIfStatement(s)
223→ }
224→}
225→
226→func (g *Generator) generateVarDecl(decl *ast.VarDecl) {
227→ goType := g.occamTypeToGo(decl.Type)
228→ g.writeLine(fmt.Sprintf("var %s %s", strings.Join(decl.Names, ", "), goType))
229→}
230→
231→func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
232→ goType := g.occamTypeToGo(decl.ElemType)
233→ for _, name := range decl.Names {
234→ g.writeLine(fmt.Sprintf("%s := make(chan %s)", name, goType))
235→ }
236→}
237→
238→func (g *Generator) generateSend(send *ast.Send) {
239→ g.builder.WriteString(strings.Repeat("\t", g.indent))
240→ g.write(send.Channel)
241→ g.write(" <- ")
242→ g.generateExpression(send.Value)
243→ g.write("\n")
244→}
245→
246→func (g *Generator) generateReceive(recv *ast.Receive) {
247→ g.writeLine(fmt.Sprintf("%s = <-%s", recv.Variable, recv.Channel))
248→}
249→
250→func (g *Generator) occamTypeToGo(occamType string) string {
251→ switch occamType {
252→ case "INT":
253→ return "int"
254→ case "BYTE":
255→ return "byte"
256→ case "BOOL":
257→ return "bool"
258→ case "REAL":
259→ return "float64"
260→ default:
261→ return occamType // pass through unknown types
262→ }
263→}
264→
265→func (g *Generator) generateAssignment(assign *ast.Assignment) {
266→ g.builder.WriteString(strings.Repeat("\t", g.indent))
267→ // Dereference if assigning to a reference parameter
268→ if g.refParams[assign.Name] {
269→ g.write("*")
270→ }
271→ g.write(assign.Name)
272→ g.write(" = ")
273→ g.generateExpression(assign.Value)
274→ g.write("\n")
275→}
276→
277→func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) {
278→ // SEQ just becomes sequential Go code (Go's default)
279→ for _, stmt := range seq.Statements {
280→ g.generateStatement(stmt)
281→ }
282→}
283→
284→func (g *Generator) generateParBlock(par *ast.ParBlock) {
285→ // PAR becomes goroutines with WaitGroup
286→ g.writeLine("var wg sync.WaitGroup")
287→ g.writeLine(fmt.Sprintf("wg.Add(%d)", len(par.Statements)))
288→
289→ for _, stmt := range par.Statements {
290→ g.writeLine("go func() {")
291→ g.indent++
292→ g.writeLine("defer wg.Done()")
293→ g.generateStatement(stmt)
294→ g.indent--
295→ g.writeLine("}()")
296→ }
297→
298→ g.writeLine("wg.Wait()")
299→}
300→
301→func (g *Generator) generateAltBlock(alt *ast.AltBlock) {
302→ // ALT becomes Go select statement
303→ // For guards, we use a pattern with nil channels
304→
305→ // Check if any cases have guards
306→ hasGuards := false
307→ for _, c := range alt.Cases {
308→ if c.Guard != nil {
309→ hasGuards = true
310→ break
311→ }
312→ }
313→
314→ if hasGuards {
315→ // Generate channel variables for guarded cases
316→ for i, c := range alt.Cases {
317→ if c.Guard != nil {
318→ g.builder.WriteString(strings.Repeat("\t", g.indent))
319→ g.write(fmt.Sprintf("var _alt%d chan ", i))
320→ // We don't know the channel type here, so use interface{}
321→ // Actually, we should use the same type as the original channel
322→ // For now, let's just reference the original channel conditionally
323→ g.write(fmt.Sprintf("int = nil\n")) // Assuming int for now
324→ g.builder.WriteString(strings.Repeat("\t", g.indent))
325→ g.write(fmt.Sprintf("if "))
326→ g.generateExpression(c.Guard)
327→ g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, c.Channel))
328→ }
329→ }
330→ }
331→
332→ g.writeLine("select {")
333→ for i, c := range alt.Cases {
334→ g.builder.WriteString(strings.Repeat("\t", g.indent))
335→ if c.Guard != nil {
336→ g.write(fmt.Sprintf("case %s = <-_alt%d:\n", c.Variable, i))
337→ } else {
338→ g.write(fmt.Sprintf("case %s = <-%s:\n", c.Variable, c.Channel))
339→ }
340→ g.indent++
341→ if c.Body != nil {
342→ g.generateStatement(c.Body)
343→ }
344→ g.indent--
345→ }
346→ g.writeLine("}")
347→}
348→
349→func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
350→ // Track reference parameters for this procedure
351→ oldRefParams := g.refParams
352→ g.refParams = make(map[string]bool)
353→ for _, p := range proc.Params {
354→ if !p.IsVal {
355→ g.refParams[p.Name] = true
356→ }
357→ }
358→
359→ // Generate function signature
360→ params := g.generateProcParams(proc.Params)
361→ g.writeLine(fmt.Sprintf("func %s(%s) {", proc.Name, params))
362→ g.indent++
363→
364→ if proc.Body != nil {
365→ g.generateStatement(proc.Body)
366→ }
367→
368→ g.indent--
369→ g.writeLine("}")
370→ g.writeLine("")
371→
372→ // Restore previous context
373→ g.refParams = oldRefParams
374→}
375→
376→func (g *Generator) generateProcParams(params []ast.ProcParam) string {
377→ var parts []string
378→ for _, p := range params {
379→ goType := g.occamTypeToGo(p.Type)
380→ if !p.IsVal {
381→ // Non-VAL parameters are pass by reference in Occam
382→ goType = "*" + goType
383→ }
384→ parts = append(parts, fmt.Sprintf("%s %s", p.Name, goType))
385→ }
386→ return strings.Join(parts, ", ")
387→}
388→
389→func (g *Generator) generateProcCall(call *ast.ProcCall) {
390→ // Handle built-in print procedures
391→ if printBuiltins[call.Name] {
392→ g.generatePrintCall(call)
393→ return
394→ }
395→
396→ g.builder.WriteString(strings.Repeat("\t", g.indent))
397→ g.write(call.Name)
398→ g.write("(")
399→
400→ // Look up procedure signature to determine which args need address-of
401→ params := g.procSigs[call.Name]
402→
403→ for i, arg := range call.Args {
404→ if i > 0 {
405→ g.write(", ")
406→ }
407→ // If this parameter is not VAL (i.e., pass by reference), take address
408→ if i < len(params) && !params[i].IsVal {
409→ g.write("&")
410→ }
411→ g.generateExpression(arg)
412→ }
413→ g.write(")")
414→ g.write("\n")
415→}
416→
417→func (g *Generator) generatePrintCall(call *ast.ProcCall) {
418→ g.builder.WriteString(strings.Repeat("\t", g.indent))
419→
420→ switch call.Name {
421→ case "print.int", "print.string", "print.bool":
422→ g.write("fmt.Println(")
423→ if len(call.Args) > 0 {
424→ g.generateExpression(call.Args[0])
425→ }
426→ g.write(")")
427→ case "print.newline":
428→ g.write("fmt.Println()")
429→ }
430→
431→ g.write("\n")
432→}
433→
434→func (g *Generator) generateWhileLoop(loop *ast.WhileLoop) {
435→ g.builder.WriteString(strings.Repeat("\t", g.indent))
436→ g.write("for ")
437→ g.generateExpression(loop.Condition)
438→ g.write(" {\n")
439→ g.indent++
440→
441→ if loop.Body != nil {
442→ g.generateStatement(loop.Body)
443→ }
444→
445→ g.indent--
446→ g.writeLine("}")
447→}
448→
449→func (g *Generator) generateIfStatement(stmt *ast.IfStatement) {
450→ for i, choice := range stmt.Choices {
451→ g.builder.WriteString(strings.Repeat("\t", g.indent))
452→ if i == 0 {
453→ g.write("if ")
454→ } else {
455→ g.write("} else if ")
456→ }
457→ g.generateExpression(choice.Condition)
458→ g.write(" {\n")
459→ g.indent++
460→
461→ if choice.Body != nil {
462→ g.generateStatement(choice.Body)
463→ }
464→
465→ g.indent--
466→ }
467→ g.writeLine("}")
468→}
469→
470→func (g *Generator) generateExpression(expr ast.Expression) {
471→ switch e := expr.(type) {
472→ case *ast.Identifier:
473→ g.write(e.Value)
474→ case *ast.IntegerLiteral:
475→ g.write(fmt.Sprintf("%d", e.Value))
476→ case *ast.BooleanLiteral:
477→ if e.Value {
478→ g.write("true")
479→ } else {
480→ g.write("false")
481→ }
482→ case *ast.BinaryExpr:
483→ g.generateBinaryExpr(e)
484→ case *ast.UnaryExpr:
485→ g.generateUnaryExpr(e)
486→ case *ast.ParenExpr:
487→ g.write("(")
488→ g.generateExpression(e.Expr)
489→ g.write(")")
490→ }
491→}
492→
493→func (g *Generator) generateBinaryExpr(expr *ast.BinaryExpr) {
494→ g.write("(")
495→ g.generateExpression(expr.Left)
496→ g.write(" ")
497→ g.write(g.occamOpToGo(expr.Operator))
498→ g.write(" ")
499→ g.generateExpression(expr.Right)
500→ g.write(")")
501→}
502→
503→func (g *Generator) generateUnaryExpr(expr *ast.UnaryExpr) {
504→ op := g.occamOpToGo(expr.Operator)
505→ g.write(op)
506→ if op == "!" {
507→ // Go's NOT doesn't need space
508→ } else {
509→ g.write(" ")
510→ }
511→ g.generateExpression(expr.Right)
512→}
513→
514→func (g *Generator) occamOpToGo(op string) string {
515→ switch op {
516→ case "=":
517→ return "=="
518→ case "<>":
519→ return "!="
520→ case "AND":
521→ return "&&"
522→ case "OR":
523→ return "||"
524→ case "NOT":
525→ return "!"
526→ case "\\":
527→ return "%"
528→ default:
529→ return op // +, -, *, /, <, >, <=, >= are the same
530→ }
531→}
532→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I have a complete picture. I need to:
parseSeqBlock and parseParBlockFor the parser, the replicator syntax is:
- SEQ i = 0 FOR n - after SEQ, if we see IDENT followed by EQ, it's a replicator
- PAR i = 0 FOR n - same logic
The replicator has: - Variable name (the loop counter) - Start expression - Count expression (number of iterations)
For the code generator:
- Replicated SEQ: generate a for loop
- Replicated PAR: generate goroutines in a for loop with WaitGroup
Let me implement this step by step:
parseSeqBlock and parseParBlock functionsparseReplicator helper functionLet me start with the parser modifications.
Now I have a complete picture. Let me implement the replicator support. I'll update the parser first to detect and parse replicator syntax, then update the code generator.
func (p *Parser) parseSeqBlock() *ast.SeqBlock {
block := &ast.SeqBlock{Token: p.curToken}
// Skip to next line
for p.peekTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Expect INDENT
if !p.peekTokenIs(lexer.INDENT) {
p.addError("expected indented block after SEQ")
return block
}
p.nextToken() // consume INDENT
block.Statements = p.parseBlockStatements()
return block
}func (p *Parser) parseSeqBlock() *ast.SeqBlock {
block := &ast.SeqBlock{Token: p.curToken}
// Check for replicator: SEQ i = start FOR count
if p.peekTokenIs(lexer.IDENT) {
// Save position to check if it's a replicator
p.nextToken() // move to identifier
if p.peekTokenIs(lexer.EQ) {
// This is a replicator
block.Replicator = p.parseReplicator()
} else {
// Not a replicator, this shouldn't happen in valid Occam
// (SEQ followed by identifier at same indentation level)
p.addError("unexpected identifier after SEQ")
return block
}
}
// Skip to next line
for p.peekTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Expect INDENT
if !p.peekTokenIs(lexer.INDENT) {
p.addError("expected indented block after SEQ")
return block
}
p.nextToken() // consume INDENT
block.Statements = p.parseBlockStatements()
return block
}The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
func (p *Parser) parseParBlock() *ast.ParBlock {
block := &ast.ParBlock{Token: p.curToken}
// Skip to next line
for p.peekTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Expect INDENT
if !p.peekTokenIs(lexer.INDENT) {
p.addError("expected indented block after PAR")
return block
}
p.nextToken() // consume INDENT
block.Statements = p.parseBlockStatements()
return block
}func (p *Parser) parseParBlock() *ast.ParBlock {
block := &ast.ParBlock{Token: p.curToken}
// Check for replicator: PAR i = start FOR count
if p.peekTokenIs(lexer.IDENT) {
// Save position to check if it's a replicator
p.nextToken() // move to identifier
if p.peekTokenIs(lexer.EQ) {
// This is a replicator
block.Replicator = p.parseReplicator()
} else {
// Not a replicator, this shouldn't happen in valid Occam
// (PAR followed by identifier at same indentation level)
p.addError("unexpected identifier after PAR")
return block
}
}
// Skip to next line
for p.peekTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Expect INDENT
if !p.peekTokenIs(lexer.INDENT) {
p.addError("expected indented block after PAR")
return block
}
p.nextToken() // consume INDENT
block.Statements = p.parseBlockStatements()
return block
}The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
Now I need to add the parseReplicator function. It should parse: i = start FOR count where i is the variable name (already consumed), start is an expression, and count is an expression.
block.Statements = p.parseBlockStatements()
return block
}
func (p *Parser) parseAltBlock() *ast.AltBlock { block.Statements = p.parseBlockStatements()
return block
}
// parseReplicator parses: variable = start FOR count
// Assumes the variable identifier has already been consumed and is in curToken
func (p *Parser) parseReplicator() *ast.Replicator {
rep := &ast.Replicator{
Variable: p.curToken.Literal,
}
// Expect =
if !p.expectPeek(lexer.EQ) {
return nil
}
// Parse start expression
p.nextToken()
rep.Start = p.parseExpression(LOWEST)
// Expect FOR
if !p.expectPeek(lexer.FOR) {
return nil
}
// Parse count expression
p.nextToken()
rep.Count = p.parseExpression(LOWEST)
return rep
}
func (p *Parser) parseAltBlock() *ast.AltBlock {The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
Now I need to update the code generator to handle replicated SEQ and PAR blocks. Let me update the codegen.go file.
func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) {
// SEQ just becomes sequential Go code (Go's default)
for _, stmt := range seq.Statements {
g.generateStatement(stmt)
}
}func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) {
if seq.Replicator != nil {
// Replicated SEQ: SEQ i = start FOR count becomes a for loop
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := ", seq.Replicator.Variable))
g.generateExpression(seq.Replicator.Start)
g.write(fmt.Sprintf("; %s < ", seq.Replicator.Variable))
g.generateExpression(seq.Replicator.Start)
g.write(" + ")
g.generateExpression(seq.Replicator.Count)
g.write(fmt.Sprintf("; %s++ {\n", seq.Replicator.Variable))
g.indent++
for _, stmt := range seq.Statements {
g.generateStatement(stmt)
}
g.indent--
g.writeLine("}")
} else {
// SEQ just becomes sequential Go code (Go's default)
for _, stmt := range seq.Statements {
g.generateStatement(stmt)
}
}
}The file /home/david/projects/codeassociates/occam2go/codegen/codegen.go has been updated successfully.
func (g *Generator) generateParBlock(par *ast.ParBlock) {
// PAR becomes goroutines with WaitGroup
g.writeLine("var wg sync.WaitGroup")
g.writeLine(fmt.Sprintf("wg.Add(%d)", len(par.Statements)))
for _, stmt := range par.Statements {
g.writeLine("go func() {")
g.indent++
g.writeLine("defer wg.Done()")
g.generateStatement(stmt)
g.indent--
g.writeLine("}()")
}
g.writeLine("wg.Wait()")
}func (g *Generator) generateParBlock(par *ast.ParBlock) {
if par.Replicator != nil {
// Replicated PAR: PAR i = start FOR count becomes goroutines in a loop
g.writeLine("var wg sync.WaitGroup")
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write("wg.Add(int(")
g.generateExpression(par.Replicator.Count)
g.write("))\n")
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(fmt.Sprintf("for %s := ", par.Replicator.Variable))
g.generateExpression(par.Replicator.Start)
g.write(fmt.Sprintf("; %s < ", par.Replicator.Variable))
g.generateExpression(par.Replicator.Start)
g.write(" + ")
g.generateExpression(par.Replicator.Count)
g.write(fmt.Sprintf("; %s++ {\n", par.Replicator.Variable))
g.indent++
// Capture loop variable to avoid closure issues
g.writeLine(fmt.Sprintf("%s := %s", par.Replicator.Variable, par.Replicator.Variable))
g.writeLine("go func() {")
g.indent++
g.writeLine("defer wg.Done()")
for _, stmt := range par.Statements {
g.generateStatement(stmt)
}
g.indent--
g.writeLine("}()")
g.indent--
g.writeLine("}")
g.writeLine("wg.Wait()")
} else {
// PAR becomes goroutines with WaitGroup
g.writeLine("var wg sync.WaitGroup")
g.writeLine(fmt.Sprintf("wg.Add(%d)", len(par.Statements)))
for _, stmt := range par.Statements {
g.writeLine("go func() {")
g.indent++
g.writeLine("defer wg.Done()")
g.generateStatement(stmt)
g.indent--
g.writeLine("}()")
}
g.writeLine("wg.Wait()")
}
}The file /home/david/projects/codeassociates/occam2go/codegen/codegen.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 3.013s ok github.com/codeassociates/occam2go/lexer 0.003s ok github.com/codeassociates/occam2go/parser 0.002s
All tests pass. Now let me add tests for the replicator feature. I'll add parser tests first, then e2e tests.
func checkParserErrors(t *testing.T, p *Parser) {func TestReplicatedSeq(t *testing.T) {
input := `SEQ i = 0 FOR 5
print.int(i)
`
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))
}
seq, ok := program.Statements[0].(*ast.SeqBlock)
if !ok {
t.Fatalf("expected SeqBlock, got %T", program.Statements[0])
}
if seq.Replicator == nil {
t.Fatal("expected replicator on SEQ block")
}
if seq.Replicator.Variable != "i" {
t.Errorf("expected variable 'i', got %s", seq.Replicator.Variable)
}
startLit, ok := seq.Replicator.Start.(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for start, got %T", seq.Replicator.Start)
}
if startLit.Value != 0 {
t.Errorf("expected start 0, got %d", startLit.Value)
}
countLit, ok := seq.Replicator.Count.(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral for count, got %T", seq.Replicator.Count)
}
if countLit.Value != 5 {
t.Errorf("expected count 5, got %d", countLit.Value)
}
}
func TestReplicatedPar(t *testing.T) {
input := `PAR i = 0 FOR 3
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))
}
par, ok := program.Statements[0].(*ast.ParBlock)
if !ok {
t.Fatalf("expected ParBlock, got %T", program.Statements[0])
}
if par.Replicator == nil {
t.Fatal("expected replicator on PAR block")
}
if par.Replicator.Variable != "i" {
t.Errorf("expected variable 'i', got %s", par.Replicator.Variable)
}
}
func checkParserErrors(t *testing.T, p *Parser) {The file /home/david/projects/codeassociates/occam2go/parser/parser_test.go has been updated successfully.
func TestE2E_WhileNested(t *testing.T) {
// Test nested WHILE loops (multiplication table style)
occam := `SEQ
INT i, j, product:
i := 1
WHILE i <= 2
SEQ
j := 1
WHILE j <= 2
SEQ
product := i * j
print.int(product)
j := j + 1
i := i + 1
`
output := transpileCompileRun(t, occam)
expected := "1\n2\n2\n4\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}func TestE2E_WhileNested(t *testing.T) {
// Test nested WHILE loops (multiplication table style)
occam := `SEQ
INT i, j, product:
i := 1
WHILE i <= 2
SEQ
j := 1
WHILE j <= 2
SEQ
product := i * j
print.int(product)
j := j + 1
i := i + 1
`
output := transpileCompileRun(t, occam)
expected := "1\n2\n2\n4\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ReplicatedSeq(t *testing.T) {
// Test replicated SEQ: SEQ i = 0 FOR 5 prints 0, 1, 2, 3, 4
occam := `SEQ i = 0 FOR 5
print.int(i)
`
output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n3\n4\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ReplicatedSeqWithExpression(t *testing.T) {
// Test replicated SEQ with expression for count
occam := `SEQ
INT n:
n := 3
SEQ i = 0 FOR n
print.int(i)
`
output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ReplicatedSeqWithStartOffset(t *testing.T) {
// Test replicated SEQ with non-zero start
occam := `SEQ i = 5 FOR 3
print.int(i)
`
output := transpileCompileRun(t, occam)
expected := "5\n6\n7\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ReplicatedSeqSum(t *testing.T) {
// Test replicated SEQ computing sum: 1+2+3+4+5 = 15
occam := `SEQ
INT sum:
sum := 0
SEQ i = 1 FOR 5
sum := sum + i
print.int(sum)
`
output := transpileCompileRun(t, occam)
expected := "15\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ReplicatedPar(t *testing.T) {
// Test replicated PAR: PAR i = 0 FOR n spawns n goroutines
// Since PAR is concurrent, we use channels to verify all goroutines ran
occam := `SEQ
CHAN OF INT c:
INT sum:
sum := 0
PAR
PAR i = 0 FOR 5
c ! i
SEQ j = 0 FOR 5
INT x:
c ? x
sum := sum + x
print.int(sum)
`
output := transpileCompileRun(t, occam)
// sum should be 0+1+2+3+4 = 10
expected := "10\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}The file /home/david/projects/codeassociates/occam2go/codegen/e2e_test.go has been updated successfully.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] === RUN TestSimpleVarDecl --- PASS: TestSimpleVarDecl (0.00s) === RUN TestMultipleVarDecl --- PASS: TestMultipleVarDecl (0.00s) === RUN TestAssignment --- PASS: TestAssignment (0.00s) === RUN TestBinaryExpression --- PASS: TestBinaryExpression (0.00s) === RUN TestComparisonOperators --- PASS: TestComparisonOperators (0.00s) === RUN TestSeqBlock --- PASS: TestSeqBlock (0.00s) === RUN TestParBlock --- PASS: TestParBlock (0.00s) === RUN TestProcDecl --- PASS: TestProcDecl (0.00s) === RUN TestProcDeclWithRefParam --- PASS: TestProcDeclWithRefParam (0.00s) === RUN TestE2E_PrintInt --- PASS: TestE2E_PrintInt (0.15s) === RUN TestE2E_Addition --- PASS: TestE2E_Addition (0.16s) === RUN TestE2E_Multiplication --- PASS: TestE2E_Multiplication (0.15s) === RUN TestE2E_Expression --- PASS: TestE2E_Expression (0.15s) === RUN TestE2E_Procedure --- PASS: TestE2E_Procedure (0.13s) === RUN TestE2E_PAR --- PASS: TestE2E_PAR (0.16s) === RUN TestE2E_MultiplePrints --- PASS: TestE2E_MultiplePrints (0.14s) === RUN TestE2E_Boolean --- PASS: TestE2E_Boolean (0.16s) === RUN TestE2E_Comparison --- PASS: TestE2E_Comparison (0.16s) === RUN TestE2E_ComplexExpression --- PASS: TestE2E_ComplexExpression (0.13s) === RUN TestE2E_Channel --- PASS: TestE2E_Channel (0.15s) === RUN TestE2E_ChannelExpression --- PASS: TestE2E_ChannelExpression (0.18s) === RUN TestE2E_ChannelPingPong --- PASS: TestE2E_ChannelPingPong (0.13s) === RUN TestE2E_AltBasic --- PASS: TestE2E_AltBasic (0.17s) === RUN TestE2E_AltSecondChannel --- PASS: TestE2E_AltSecondChannel (0.15s) === RUN TestE2E_AltWithBody --- PASS: TestE2E_AltWithBody (0.18s) === RUN TestE2E_WhileBasic --- PASS: TestE2E_WhileBasic (0.16s) === RUN TestE2E_WhileSum --- PASS: TestE2E_WhileSum (0.17s) === RUN TestE2E_WhileNested --- PASS: TestE2E_WhileNested (0.17s) === RUN TestE2E_ReplicatedSeq --- PASS: TestE2E_ReplicatedSeq (0.16s) === RUN TestE2E_ReplicatedSeqWithExpression --- PASS: TestE2E_ReplicatedSeqWithExpression (0.15s) === RUN TestE2E_ReplicatedSeqWithStartOffset --- PASS: TestE2E_ReplicatedSeqWithStartOffset (0.15s) === RUN TestE2E_ReplicatedSeqSum --- PASS: TestE2E_ReplicatedSeqSum (0.15s) === RUN TestE2E_ReplicatedPar --- PASS: TestE2E_ReplicatedPar (0.14s) PASS ok github.com/codeassociates/occam2go/codegen 3.698s === RUN TestBasicTokens --- PASS: TestBasicTokens (0.00s) === RUN TestIndentation --- PASS: TestIndentation (0.00s) === RUN TestOperators --- PASS: TestOperators (0.00s) === RUN TestKeywords --- PASS: TestKeywords (0.00s) === RUN TestComments --- PASS: TestComments (0.00s) === RUN TestNestedIndentation --- PASS: TestNestedIndentation (0.00s) PASS ok github.com/codeassociates/occam2go/lexer 0.003s === RUN TestVarDecl --- PASS: TestVarDecl (0.00s) === RUN TestMultipleVarDecl --- PASS: TestMultipleVarDecl (0.00s) === RUN TestAssignment --- PASS: TestAssignment (0.00s) === RUN TestBinaryExpression --- PASS: TestBinaryExpression (0.00s) === RUN TestSeqBlock --- PASS: TestSeqBlock (0.00s) === RUN TestParBlock --- PASS: TestParBlock (0.00s) === RUN TestChanDecl --- PASS: TestChanDecl (0.00s) === RUN TestSend --- PASS: TestSend (0.00s)
All tests pass. Let me see the rest of the test output to make sure all tests passed.
=== RUN TestVarDecl --- PASS: TestVarDecl (0.00s) === RUN TestMultipleVarDecl --- PASS: TestMultipleVarDecl (0.00s) === RUN TestAssignment --- PASS: TestAssignment (0.00s) === RUN TestBinaryExpression --- PASS: TestBinaryExpression (0.00s) === RUN TestSeqBlock --- PASS: TestSeqBlock (0.00s) === RUN TestParBlock --- PASS: TestParBlock (0.00s) === RUN TestChanDecl --- PASS: TestChanDecl (0.00s) === RUN TestSend --- PASS: TestSend (0.00s) === RUN TestReceive --- PASS: TestReceive (0.00s) === RUN TestAltBlock --- PASS: TestAltBlock (0.00s) === RUN TestAltBlockWithGuard --- PASS: TestAltBlockWithGuard (0.00s) === RUN TestWhileLoop --- PASS: TestWhileLoop (0.00s) === RUN TestReplicatedSeq --- PASS: TestReplicatedSeq (0.00s) === RUN TestReplicatedPar --- PASS: TestReplicatedPar (0.00s) PASS ok github.com/codeassociates/occam2go/parser (cached)
All tests pass. Now let me create an example file to demonstrate the replicator feature and update the README.
All tests pass. Let me create an example file and update the README to document the new replicator feature.
-- Demonstrate replicated SEQ and PAR -- Simple replicated SEQ: print 0 to 4 SEQ i = 0 FOR 5 print.int(i)
File created successfully at: /home/david/projects/codeassociates/occam2go/examples/replicator.occ
| `ALT` | `select` | | `guard & c ? x` | Conditional channel with nil pattern |
| `ALT` | `select` | | `guard & c ? x` | Conditional channel with nil pattern | | `SEQ i = 0 FOR n` | `for i := 0; i < n; i++` | | `PAR i = 0 FOR n` | Parallel `for` loop with goroutines |
The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
Now let me update the "Not Yet Implemented" section to remove replicators since they're now implemented.
## Not Yet Implemented - Replicators (`PAR i = 0 FOR n`, `SEQ i = 0 FOR n`) - `IF` (guarded commands) - Arrays
## Not Yet Implemented - `IF` (guarded commands) - Arrays
The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
### Built-in I/O Procedures
### Replicators Replicators allow you to repeat a block of code a specified number of times. | Occam | Go | |-------|-----| | `SEQ i = 0 FOR n` | `for i := 0; i < n; i++` | | `PAR i = 0 FOR n` | Parallel for loop with goroutines | Example with replicated SEQ: ```occam SEQ i = 1 FOR 5 print.int(i) ``` This prints 1, 2, 3, 4, 5. Example with replicated PAR (spawns n concurrent processes): ```occam PAR i = 0 FOR 4 c ! i ``` ### Built-in I/O Procedures
The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
go build -o occam2go && ./occam2go examples/replicator.occ
package main
import (
"fmt"
)
func main() {
for i := 0; i < 0 + 5; i++ {
fmt.Println(i)
}
}./occam2go examples/replicator.occ -o /tmp/replicator.go && go run /tmp/replicator.go
Exit code 1
stat /tmp/replicator.go: no such file or directory
package main
import (
"fmt"
)
func main() {
for i := 0; i < 0 + 5; i++ {
fmt.Println(i)
}
}The output flag isn't working as expected - it looks like it's printing to stdout instead of writing to the file. Let me check the main.go file.
1→package main
2→
3→import (
4→ "flag"
5→ "fmt"
6→ "os"
7→
8→ "github.com/codeassociates/occam2go/codegen"
9→ "github.com/codeassociates/occam2go/lexer"
10→ "github.com/codeassociates/occam2go/parser"
11→)
12→
13→const version = "0.1.0"
14→
15→func main() {
16→ showVersion := flag.Bool("version", false, "Print version and exit")
17→ outputFile := flag.String("o", "", "Output file (default: stdout)")
18→
19→ flag.Usage = func() {
20→ fmt.Fprintf(os.Stderr, "occam2go - An Occam to Go transpiler\n\n")
21→ fmt.Fprintf(os.Stderr, "Usage: %s [options] <input.occ>\n\n", os.Args[0])
22→ fmt.Fprintf(os.Stderr, "Options:\n")
23→ flag.PrintDefaults()
24→ }
25→
26→ flag.Parse()
27→
28→ if *showVersion {
29→ fmt.Printf("occam2go version %s\n", version)
30→ os.Exit(0)
31→ }
32→
33→ args := flag.Args()
34→ if len(args) < 1 {
35→ flag.Usage()
36→ os.Exit(1)
37→ }
38→
39→ inputFile := args[0]
40→
41→ // Read input file
42→ input, err := os.ReadFile(inputFile)
43→ if err != nil {
44→ fmt.Fprintf(os.Stderr, "Error reading file: %s\n", err)
45→ os.Exit(1)
46→ }
47→
48→ // Lex
49→ l := lexer.New(string(input))
50→
51→ // Parse
52→ p := parser.New(l)
53→ program := p.ParseProgram()
54→
55→ if len(p.Errors()) > 0 {
56→ fmt.Fprintf(os.Stderr, "Parse errors:\n")
57→ for _, err := range p.Errors() {
58→ fmt.Fprintf(os.Stderr, " %s\n", err)
59→ }
60→ os.Exit(1)
61→ }
62→
63→ // Generate Go code
64→ gen := codegen.New()
65→ output := gen.Generate(program)
66→
67→ // Write output
68→ if *outputFile != "" {
69→ err := os.WriteFile(*outputFile, []byte(output), 0644)
70→ if err != nil {
71→ fmt.Fprintf(os.Stderr, "Error writing file: %s\n", err)
72→ os.Exit(1)
73→ }
74→ } else {
75→ fmt.Print(output)
76→ }
77→}
78→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Ah, the issue is that I used -o /tmp/replicator.go but the flag parsing expects the options before the positional argument. Let me run it correctly.
./occam2go -o /tmp/replicator.go examples/replicator.occ && go run /tmp/replicator.go
The replicator feature works correctly. Let me run all tests one more time to confirm everything is working.
The replicator feature is now fully implemented. Let me run all tests once more to confirm.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 3.610s ok github.com/codeassociates/occam2go/lexer 0.004s ok github.com/codeassociates/occam2go/parser 0.004s
All tests pass. The replicator feature has been successfully implemented. Let me provide a summary to the user.
Replicators are now implemented. Here's a summary of the changes:
Parser updates (parser/parser.go):
- Modified parseSeqBlock and parseParBlock to detect replicator syntax (when an identifier follows SEQ/PAR and is followed by =)
- Added parseReplicator() function that parses variable = start FOR count
Code generator updates (codegen/codegen.go):
- Replicated SEQ generates a for loop: for i := start; i < start + count; i++ { ... }
- Replicated PAR generates goroutines in a loop with proper loop variable capture to avoid closure issues
Tests added: - Parser tests for replicated SEQ and PAR - E2E tests covering: - Basic replicated SEQ - Replicated SEQ with variable count - Replicated SEQ with non-zero start offset - Replicated SEQ computing a sum - Replicated PAR with channel communication
Example (examples/replicator.occ):
SEQ i = 0 FOR 5
print.int(i)
Outputs: 0 1 2 3 4 (one per line)
The user is asking for recommendations on what features to implement next. Let me look at what's currently not implemented based on the README and my knowledge of Occam.
From the README's "Not Yet Implemented" section: - IF (guarded commands) - Arrays
Other Occam features that might be useful: - PRI PAR / PRI ALT (priority versions) - CASE statements - Abbreviations (renaming) - Protocol types for channels - Channel arrays - Timers - PLACED PAR (for hardware mapping - probably not relevant for Go) - Nested/replicated ALT
Let me think about which would be most useful and practical to implement next.
Based on the README and typical Occam usage, here are the remaining unimplemented features:
Currently listed as not implemented: 1. IF (guarded commands) - Occam's IF is different from most languages; it's a list of guarded choices where the first true guard executes. Unlike Go's if/else, if no guard is true, the process stops (STOP semantics).
[10]INT arr: declarations, indexed access arr[i], and array slicing. This would enable more realistic programs.Other useful features not yet covered:
PRI ALT - Priority alternation where earlier cases are preferred over later ones. Would require a different code generation strategy than simple select.
Replicated ALT - ALT i = 0 FOR n to select from an array of channels.
CASE statement - Pattern matching on values.
Timers - TIMER t: and t ? now for timing operations, maps to Go's time package.
Recommendation: Arrays would likely provide the most value, as they're fundamental for non-trivial programs and would combine well with the replicators you just implemented (e.g., SEQ i = 0 FOR 10 operating on arr[i]). IF would also be practical since it's a core control structure.
Would you like me to implement arrays or IF next?
The user is asking about the effort to implement IF. Let me think about what's already in place and what needs to be done.
Looking at the existing code:
Lexer: IF is already a token (lexer/token.go line 49)
AST: IfStatement and IfChoice are already defined (ast/ast.go lines 131-143):
type IfStatement struct {
Token lexer.Token // the IF token
Choices []IfChoice
}
type IfChoice struct {
Condition Expression
Body Statement
}
Parser: parseIfStatement already exists (parser/parser.go lines 668-719)
Code Generator: generateIfStatement already exists (codegen/codegen.go lines 449-468)
So IF is actually already implemented! Let me check the code more carefully to see if it's working or if there are issues.
Looking at the parser's parseIfStatement: - It expects INDENT after IF - It parses condition/body pairs - Each choice has a condition and an indented body
Looking at the codegen's generateIfStatement: - It generates Go if/else if chains
So the basic structure is there. Let me check if there are any tests for IF.
Looking at the e2e tests - I don't see any IF tests. And the README lists IF as "Not Yet Implemented".
Let me think about what might be missing or broken:
if/else if chains, which is reasonable for Occam's IFThe user is asking about the work needed, so I should: 1. Explain that the basic structure exists 2. Note what might need fixing or testing 3. Estimate the remaining work
Actually, let me look more carefully at what's there.
The code exists but it's listed as "Not Yet Implemented" - this suggests it might be incomplete or untested. The skeleton is in place with lexer tokens, AST nodes, parser logic, and code generation, but there's no test coverage and the README indicates it's not ready. The main gaps appear to be proper handling of Occam's STOP semantics when no guard succeeds, and verification that the parsing and generation actually work correctly.
The user wants me to test the existing IF implementation and fix any issues. Let me start by writing some tests to see if it works.
First, let me write a simple e2e test to see if IF works at all.
cat << 'EOF' | ./occam2go /dev/stdin
SEQ
INT x:
x := 5
IF
x > 3
print.int(1)
x <= 3
print.int(0)
EOFpackage main
import (
"fmt"
)
func main() {
var x int
x = 5
if (x > 3) {
fmt.Println(1)
} else if (x <= 3) {
fmt.Println(0)
}
}{
"prompt": "Explore the codebase to understand how IF statements are implemented. Look at:\n1. The main transpiler/parser code\n2. Any test files related to IF\n3. How other control structures (like WHILE, ALT) are implemented for comparison\n4. The overall project structure\n\nReport back on:\n- What files exist and their purposes\n- How IF is currently implemented\n- Any test files that exist\n- The general architecture of the transpiler",
"subagent_type": "Explore"
}[
{
"type": "text",
"text": "Perfect! Now I have a comprehensive view of the codebase. Let me create a summary report.\n\n## Exploration Report: IF Statement Implementation in occam2go\n\nI've thoroughly explored the occam2go codebase to understand how IF statements are implemented and the overall transpiler architecture. Here's my detailed report:\n\n### Project Structure\n\nThe transpiler is organized in a clean four-layer architecture:\n\n1. **Lexer** (`/home/david/projects/codeassociates/occam2go/lexer/`)\n - `token.go` - Token types and definitions\n - `lexer.go` - Tokenization with indentation tracking\n - `lexer_test.go` - Lexer tests\n\n2. **Parser** (`/home/david/projects/codeassociates/occam2go/parser/`)\n - `parser.go` - Recursive descent parser with Pratt expression parsing\n - `parser_test.go` - Parser tests\n\n3. **AST** (`/home/david/projects/codeassociates/occam2go/ast/`)\n - `ast.go` - Abstract Syntax Tree node definitions\n\n4. **Code Generator** (`/home/david/projects/codeassociates/occam2go/codegen/`)\n - `codegen.go` - AST to Go code generation\n - `codegen_test.go` - Unit tests\n - `e2e_test.go` - End-to-end integration tests\n\n### IF Statement Implementation\n\n**Current Status:** IF is fully implemented (not marked as \"Not Yet Implemented\" as the README mistakenly claims - that line appears to be outdated).\n\n#### 1. Lexer (`lexer/token.go`)\n- IF token is defined and recognized as a keyword\n- Returns `lexer.IF` token type when \"IF\" is encountered\n\n#### 2. AST Nodes (`ast/ast.go`, lines 131-143)\n```go\n// IfStatement represents an IF statement\ntype IfStatement struct {\n Token lexer.Token // the IF token\n Choices []IfChoice\n}\n\ntype IfChoice struct {\n Condition Expression\n Body Statement\n}\n```\n\nThe AST models Occam's guarded if-then-else structure as a sequence of condition-body pairs, allowing multiple choices (if-else if chains).\n\n#### 3. Parser (`parser/parser.go`, lines 726-777)\nThe `parseIfStatement()` function:\n- Expects an INDENT token after IF\n- Parses multiple IfChoice entries (condition followed by indented body)\n- Each choice consists of:\n - A condition expression (parsed with `parseExpression()`)\n - A NEWLINE\n - An INDENT\n - A body statement\n - Consumes until DEDENT\n\nThe parser handles the indentation-based syntax structure where choices are represented as indented blocks.\n\n#### 4. Code Generator (`codegen/codegen.go`, lines 501-520)\nThe `generateIfStatement()` function:\n```go\nfunc (g *Generator) generateIfStatement(stmt *ast.IfStatement) {\n for i, choice := range stmt.Choices {\n g.builder.WriteString(strings.Repeat(\"\\t\", g.indent))\n if i == 0 {\n g.write(\"if \")\n } else {\n g.write(\"} else if \")\n }\n g.generateExpression(choice.Condition)\n g.write(\" {\\n\")\n g.indent++\n if choice.Body != nil {\n g.generateStatement(choice.Body)\n }\n g.indent--\n }\n g.writeLine(\"}\")\n}\n```\n\nThis generates a Go `if-else if` chain from the Occam IF statement.\n\n### Comparison with Other Control Structures\n\nI traced how similar control structures are implemented to understand the pattern:\n\n**WHILE Loop** (lines 697-724 parser, 486-499 codegen):\n- Similar pattern: WHILE condition, NEWLINE, INDENT, body, DEDENT\n- Generates Go `for` loop (Go uses for for all loops)\n\n**ALT Block** (lines 381-521 parser, 353-399 codegen):\n- More complex: expects multiple channel receive cases with optional guards\n- Each case: optional guard & channel ? variable, NEWLINE, INDENT, body\n- Generates Go `select` statement\n- Supports guards via nil channel patterns\n\n**SEQ/PAR Blocks** (lines 283-351 parser, 277-351 codegen):\n- Both support optional replicators: `SEQ i = start FOR count`\n- SEQ just executes statements sequentially (Go's default)\n- PAR spawns goroutines with `sync.WaitGroup`\n\n### Test Coverage\n\n**Parser Tests** (`parser/parser_test.go`):\n- Tests for SEQ, PAR, ALT, WHILE with replicators\n- Tests for variable declarations, assignments, binary expressions\n- **No explicit IF statement parser tests** - this is a gap\n\n**Code Generator Tests** (`codegen/codegen_test.go`):\n- Tests for variable declarations, assignments, binary expressions, SEQ, PAR, PROC\n- Tests for WHILE loops with conditions\n- **No explicit IF statement codegen tests**\n\n**End-to-End Tests** (`codegen/e2e_test.go`):\n- Tests for all major constructs with full transpile-compile-run cycle\n- Tests for WHILE loops (multiple variations)\n- Tests for channels and ALT\n- **No explicit IF statement e2e tests**\n\n### Architectural Patterns\n\n1. **Indentation-based Parsing**: The lexer emits INDENT/DEDENT tokens, allowing the parser to handle Python-like indented blocks\n\n2. **Multi-pass Code Generation**:\n - First pass: Collects procedure signatures and checks for PAR/print usage\n - Second pass: Separates procedures from main statements\n - Third pass: Generates code\n\n3. **Operator Precedence**: Uses a precedence-climbing algorithm for expression parsing\n\n4. **Type Mapping**: Occam types → Go types:\n - INT → int\n - BYTE → byte\n - BOOL → bool\n - REAL → float64\n\n5. **Operator Mapping**: \n - Occam `=` → Go `==`\n - Occam `<>` → Go `!=`\n - Occam `AND`/`OR`/`NOT` → Go `&&`/`||`/`!`\n - Occam `\\` (backslash) → Go `%` (modulo)\n\n### Recent Commits\n\n- a202d06: Add replicator support (SEQ/PAR i = start FOR count)\n- f180676: Add WHILE support\n- 6fbccb0: Add support for ALT\n- 6a36874: Add support for channels\n\n### Key Observations\n\n1. **IF is Implemented**: Despite the README claiming it's \"Not Yet Implemented\", the IF statement has full parsing and code generation support\n\n2. **Test Gap**: There are no dedicated tests for IF statement parsing or code generation, which may explain the discrepancy with the README\n\n3. **Occam IF vs Go if**: Occam's IF uses guarded commands (condition → body pairs), which maps naturally to Go's if-else if chains\n\n4. **Architecture is Clean**: The separation between lexer, parser, AST, and codegen makes it easy to add new features and understand the transpilation process\n\n5. **Indentation Tracking**: The lexer's indentation stack is crucial for parsing Python-like Occam syntax\n\n### Files and Their Purposes\n\n| File | Purpose | Lines |\n|------|---------|-------|\n| `ast/ast.go` | AST node definitions including IfStatement | 250 |\n| `lexer/token.go` | Token types and keyword mapping | 175 |\n| `lexer/lexer.go` | Lexer with indentation tracking | ~250 |\n| `parser/parser.go` | Recursive descent parser, Pratt expression parsing | 856 |\n| `codegen/codegen.go` | AST to Go code generation | 584 |\n| `main.go` | CLI entry point | 78 |"
},
{
"type": "text",
"text": "agentId: a2e2b33 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 59667\ntool_uses: 23\nduration_ms: 62737</usage>"
}
]cat << 'EOF' | go run main.go /dev/stdin
SEQ
INT x:
x := 5
IF
x > 3
print.int(1)
x <= 3
print.int(0)
EOFpackage main
import (
"fmt"
)
func main() {
var x int
x = 5
if (x > 3) {
fmt.Println(1)
} else if (x <= 3) {
fmt.Println(0)
}
}