Thanks. Can you please add something to the README file that tells someone who isn't familiar with golang tooling how to build and run one of the examples? Not just the command to transpile but also the subsequent commands to build and run the golang code.
The user wants me to add instructions to the README that explain how to: 1. Transpile an Occam file to Go 2. Compile the Go code 3. Run the resulting binary
This is for someone unfamiliar with Go tooling, so I should be clear and explicit.
1→# occam2go
2→
3→A transpiler from Occam to Go, written in Go.
4→
5→Occam was developed in the 1980s to support concurrent programming on the Transputer. Go, created decades later, shares similar CSP-influenced concurrency primitives. This transpiler bridges the two.
6→
7→## Building
8→
9→```bash
10→go build -o occam2go
11→```
12→
13→## Usage
14→
15→```bash
16→./occam2go [options] <input.occ>
17→```
18→
19→Options:
20→- `-o <file>` - Write output to file (default: stdout)
21→- `-version` - Print version and exit
22→
23→## Example
24→
25→Input (`example.occ`):
26→```occam
27→SEQ
28→ INT x, y:
29→ PAR
30→ x := 1
31→ y := 2
32→ x := x + y
33→```
34→
35→Output:
36→```go
37→package main
38→
39→import (
40→ "sync"
41→)
42→
43→func main() {
44→ var x, y int
45→ var wg sync.WaitGroup
46→ wg.Add(2)
47→ go func() {
48→ defer wg.Done()
49→ x = 1
50→ }()
51→ go func() {
52→ defer wg.Done()
53→ y = 2
54→ }()
55→ wg.Wait()
56→ x = (x + y)
57→}
58→```
59→
60→## Implemented Features
61→
62→| Occam | Go |
63→|-------|-----|
64→| `INT`, `BYTE`, `BOOL`, `REAL` | `int`, `byte`, `bool`, `float64` |
65→| `SEQ` | Sequential code |
66→| `PAR` | Goroutines with `sync.WaitGroup` |
67→| `PROC` with `VAL` params | Functions with value/pointer params |
68→| `:=` assignment | `=` assignment |
69→| Arithmetic: `+`, `-`, `*`, `/`, `\` | `+`, `-`, `*`, `/`, `%` |
70→| Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=` | `==`, `!=`, `<`, `>`, `<=`, `>=` |
71→| Logic: `AND`, `OR`, `NOT` | `&&`, `\|\|`, `!` |
72→
73→### Built-in I/O Procedures
74→
75→| Occam | Go |
76→|-------|-----|
77→| `print.int(x)` | `fmt.Println(x)` |
78→| `print.bool(x)` | `fmt.Println(x)` |
79→| `print.string(x)` | `fmt.Println(x)` |
80→| `print.newline()` | `fmt.Println()` |
81→
82→## Not Yet Implemented
83→
84→- Channels (`CHAN`, `!`, `?`)
85→- `ALT` (alternation) → `select`
86→- Replicators (`PAR i = 0 FOR n`)
87→- Arrays
88→- `WHILE`, `IF` (partial)
<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>
I'll add a section after "Usage" that shows the full workflow: transpile, compile, and run. I'll use the print.occ example since it produces visible output.
Options: - `-o <file>` - Write output to file (default: stdout) - `-version` - Print version and exit ## Example
Options: - `-o <file>` - Write output to file (default: stdout) - `-version` - Print version and exit ## Running an Example Here's how to transpile, compile, and run an Occam program: ```bash # 1. Build the transpiler (only needed once) go build -o occam2go # 2. Transpile an Occam file to Go ./occam2go examples/print.occ -o output.go # 3. Compile the generated Go code go build -o output output.go # 4. Run the compiled program ./output ``` Or as a one-liner to see the output immediately: ```bash ./occam2go examples/print.occ -o output.go && go run output.go ``` ## Example
The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
Done. I've added a "Running an Example" section that explains the full workflow step by step, plus a one-liner for convenience.
Done. Added a "Running an Example" section with step-by-step instructions for the full transpile → compile → run workflow.
The user is thanking me for the work done. I should acknowledge this briefly. They may want to commit these changes or continue with more work on the project.
The user wants me to implement channels. Let me plan what needs to be done:
Lexer: Already has SEND (!) and RECEIVE (?) tokens, but I need to check if CHAN and OF keywords exist.
Parser: Need to add:
CHAN OF INT c:c ! xReceive statement parsing: c ? y
AST: Need to add:
ChanDecl node for channel declarationsSend node for send statementsReceive node for receive statements
Codegen: Need to:
c := make(chan int) for channel declarationsc <- x for sendy = <-c for receiveLet me start by checking the lexer to see what's already there.
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→
34→ // Delimiters
35→ LPAREN // (
36→ RPAREN // )
37→ LBRACKET // [
38→ RBRACKET // ]
39→ COMMA // ,
40→ COLON // :
41→ SEMICOLON // ;
42→
43→ // Keywords
44→ keyword_beg
45→ SEQ
46→ PAR
47→ ALT
48→ IF
49→ WHILE
50→ FOR
51→ PROC
52→ FUNC
53→ CHAN
54→ OF
55→ TRUE
56→ FALSE
57→ NOT
58→ AND
59→ OR
60→ SKIP
61→ STOP
62→ INT_TYPE
63→ BYTE_TYPE
64→ BOOL_TYPE
65→ REAL_TYPE
66→ VAL
67→ keyword_end
68→)
69→
70→var tokenNames = map[TokenType]string{
71→ ILLEGAL: "ILLEGAL",
72→ EOF: "EOF",
73→ NEWLINE: "NEWLINE",
74→ INDENT: "INDENT",
75→ DEDENT: "DEDENT",
76→
77→ IDENT: "IDENT",
78→ INT: "INT",
79→ STRING: "STRING",
80→
81→ ASSIGN: ":=",
82→ PLUS: "+",
83→ MINUS: "-",
84→ MULTIPLY: "*",
85→ DIVIDE: "/",
86→ MODULO: "\\",
87→ EQ: "=",
88→ NEQ: "<>",
89→ LT: "<",
90→ GT: ">",
91→ LE: "<=",
92→ GE: ">=",
93→ SEND: "!",
94→ RECEIVE: "?",
95→
96→ LPAREN: "(",
97→ RPAREN: ")",
98→ LBRACKET: "[",
99→ RBRACKET: "]",
100→ COMMA: ",",
101→ COLON: ":",
102→ SEMICOLON: ";",
103→
104→ SEQ: "SEQ",
105→ PAR: "PAR",
106→ ALT: "ALT",
107→ IF: "IF",
108→ WHILE: "WHILE",
109→ FOR: "FOR",
110→ PROC: "PROC",
111→ FUNC: "FUNC",
112→ CHAN: "CHAN",
113→ OF: "OF",
114→ TRUE: "TRUE",
115→ FALSE: "FALSE",
116→ NOT: "NOT",
117→ AND: "AND",
118→ OR: "OR",
119→ SKIP: "SKIP",
120→ STOP: "STOP",
121→ INT_TYPE: "INT",
122→ BYTE_TYPE: "BYTE",
123→ BOOL_TYPE: "BOOL",
124→ REAL_TYPE: "REAL",
125→ VAL: "VAL",
126→}
127→
128→var keywords = map[string]TokenType{
129→ "SEQ": SEQ,
130→ "PAR": PAR,
131→ "ALT": ALT,
132→ "IF": IF,
133→ "WHILE": WHILE,
134→ "FOR": FOR,
135→ "PROC": PROC,
136→ "FUNC": FUNC,
137→ "CHAN": CHAN,
138→ "OF": OF,
139→ "TRUE": TRUE,
140→ "FALSE": FALSE,
141→ "NOT": NOT,
142→ "AND": AND,
143→ "OR": OR,
144→ "SKIP": SKIP,
145→ "STOP": STOP,
146→ "INT": INT_TYPE,
147→ "BYTE": BYTE_TYPE,
148→ "BOOL": BOOL_TYPE,
149→ "REAL": REAL_TYPE,
150→ "VAL": VAL,
151→}
152→
153→func (t TokenType) String() string {
154→ if name, ok := tokenNames[t]; ok {
155→ return name
156→ }
157→ return "UNKNOWN"
158→}
159→
160→func LookupIdent(ident string) TokenType {
161→ if tok, ok := keywords[ident]; ok {
162→ return tok
163→ }
164→ return IDENT
165→}
166→
167→type Token struct {
168→ Type TokenType
169→ Literal string
170→ Line int
171→ Column int
172→}
173→
<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>
The lexer already has:
- CHAN keyword
- OF keyword
- SEND (!) token
- RECEIVE (?) token
So the lexer is ready. Now I need to:
Let me start with the AST.
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→// Assignment represents an assignment: x := 5
47→type Assignment struct {
48→ Token lexer.Token // the := token
49→ Name string // variable name
50→ Value Expression // the value being assigned
51→}
52→
53→func (a *Assignment) statementNode() {}
54→func (a *Assignment) TokenLiteral() string { return a.Token.Literal }
55→
56→// SeqBlock represents a SEQ block (sequential execution)
57→type SeqBlock struct {
58→ Token lexer.Token // the SEQ token
59→ Statements []Statement
60→}
61→
62→func (s *SeqBlock) statementNode() {}
63→func (s *SeqBlock) TokenLiteral() string { return s.Token.Literal }
64→
65→// ParBlock represents a PAR block (parallel execution)
66→type ParBlock struct {
67→ Token lexer.Token // the PAR token
68→ Statements []Statement
69→}
70→
71→func (p *ParBlock) statementNode() {}
72→func (p *ParBlock) TokenLiteral() string { return p.Token.Literal }
73→
74→// Skip represents the SKIP statement (no-op)
75→type Skip struct {
76→ Token lexer.Token
77→}
78→
79→func (s *Skip) statementNode() {}
80→func (s *Skip) TokenLiteral() string { return s.Token.Literal }
81→
82→// ProcDecl represents a procedure declaration
83→type ProcDecl struct {
84→ Token lexer.Token // the PROC token
85→ Name string
86→ Params []ProcParam
87→ Body Statement // usually a SEQ block
88→}
89→
90→func (p *ProcDecl) statementNode() {}
91→func (p *ProcDecl) TokenLiteral() string { return p.Token.Literal }
92→
93→// ProcParam represents a procedure parameter
94→type ProcParam struct {
95→ IsVal bool // VAL parameter (pass by value)
96→ Type string // INT, BYTE, BOOL, etc.
97→ Name string
98→}
99→
100→// ProcCall represents a procedure call
101→type ProcCall struct {
102→ Token lexer.Token // the procedure name token
103→ Name string
104→ Args []Expression
105→}
106→
107→func (p *ProcCall) statementNode() {}
108→func (p *ProcCall) TokenLiteral() string { return p.Token.Literal }
109→
110→// WhileLoop represents a WHILE loop
111→type WhileLoop struct {
112→ Token lexer.Token // the WHILE token
113→ Condition Expression
114→ Body Statement
115→}
116→
117→func (w *WhileLoop) statementNode() {}
118→func (w *WhileLoop) TokenLiteral() string { return w.Token.Literal }
119→
120→// IfStatement represents an IF statement
121→type IfStatement struct {
122→ Token lexer.Token // the IF token
123→ Choices []IfChoice
124→}
125→
126→type IfChoice struct {
127→ Condition Expression
128→ Body Statement
129→}
130→
131→func (i *IfStatement) statementNode() {}
132→func (i *IfStatement) TokenLiteral() string { return i.Token.Literal }
133→
134→// Expressions
135→
136→// Identifier represents a variable reference
137→type Identifier struct {
138→ Token lexer.Token
139→ Value string
140→}
141→
142→func (i *Identifier) expressionNode() {}
143→func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
144→
145→// IntegerLiteral represents an integer literal
146→type IntegerLiteral struct {
147→ Token lexer.Token
148→ Value int64
149→}
150→
151→func (il *IntegerLiteral) expressionNode() {}
152→func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
153→
154→// BooleanLiteral represents TRUE or FALSE
155→type BooleanLiteral struct {
156→ Token lexer.Token
157→ Value bool
158→}
159→
160→func (bl *BooleanLiteral) expressionNode() {}
161→func (bl *BooleanLiteral) TokenLiteral() string { return bl.Token.Literal }
162→
163→// BinaryExpr represents a binary operation: x + y
164→type BinaryExpr struct {
165→ Token lexer.Token // the operator token
166→ Left Expression
167→ Operator string
168→ Right Expression
169→}
170→
171→func (be *BinaryExpr) expressionNode() {}
172→func (be *BinaryExpr) TokenLiteral() string { return be.Token.Literal }
173→
174→// UnaryExpr represents a unary operation: NOT x, -x
175→type UnaryExpr struct {
176→ Token lexer.Token // the operator token
177→ Operator string
178→ Right Expression
179→}
180→
181→func (ue *UnaryExpr) expressionNode() {}
182→func (ue *UnaryExpr) TokenLiteral() string { return ue.Token.Literal }
183→
184→// ParenExpr represents a parenthesized expression
185→type ParenExpr struct {
186→ Token lexer.Token
187→ Expr Expression
188→}
189→
190→func (pe *ParenExpr) expressionNode() {}
191→func (pe *ParenExpr) TokenLiteral() string { return pe.Token.Literal }
192→
<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'll add the channel-related AST nodes:
1. ChanDecl - for channel declarations like CHAN OF INT c:
2. Send - for send statements like c ! x
3. Receive - for receive statements like c ? y
// ParenExpr represents a parenthesized expression
type ParenExpr struct {
Token lexer.Token
Expr Expression
}
func (pe *ParenExpr) expressionNode() {}
func (pe *ParenExpr) TokenLiteral() string { return pe.Token.Literal }// ParenExpr represents a parenthesized expression
type ParenExpr struct {
Token lexer.Token
Expr Expression
}
func (pe *ParenExpr) expressionNode() {}
func (pe *ParenExpr) TokenLiteral() string { return pe.Token.Literal }
// ChanDecl represents a channel declaration: CHAN OF INT c:
type ChanDecl struct {
Token lexer.Token // the CHAN token
ElemType string // the element type (INT, BYTE, etc.)
Names []string // channel names
}
func (c *ChanDecl) statementNode() {}
func (c *ChanDecl) TokenLiteral() string { return c.Token.Literal }
// Send represents a channel send: c ! x
type Send struct {
Token lexer.Token // the ! token
Channel string // channel name
Value Expression // value to send
}
func (s *Send) statementNode() {}
func (s *Send) TokenLiteral() string { return s.Token.Literal }
// Receive represents a channel receive: c ? x
type Receive struct {
Token lexer.Token // the ? token
Channel string // channel name
Variable string // variable to receive into
}
func (r *Receive) statementNode() {}
func (r *Receive) TokenLiteral() string { return r.Token.Literal }The file /home/david/projects/codeassociates/occam2go/ast/ast.go has been updated successfully.
Now I need to update the parser to handle:
1. Channel declarations: CHAN OF INT c:
2. Send statements: c ! x
3. Receive statements: c ? y
Let me look at the parser to see where to add these.
{
"file_path": "/home/david/projects/codeassociates/occam2go/parser/parser.go",
"offset": 130,
"limit": 80
} 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.SEQ:
139→ return p.parseSeqBlock()
140→ case lexer.PAR:
141→ return p.parseParBlock()
142→ case lexer.SKIP:
143→ return &ast.Skip{Token: p.curToken}
144→ case lexer.PROC:
145→ return p.parseProcDecl()
146→ case lexer.WHILE:
147→ return p.parseWhileLoop()
148→ case lexer.IF:
149→ return p.parseIfStatement()
150→ case lexer.IDENT:
151→ // Could be assignment or procedure call
152→ if p.peekTokenIs(lexer.ASSIGN) {
153→ return p.parseAssignment()
154→ }
155→ return p.parseProcCall()
156→ case lexer.INDENT, lexer.DEDENT, lexer.EOF:
157→ return nil
158→ default:
159→ p.addError(fmt.Sprintf("unexpected token: %s", p.curToken.Type))
160→ return nil
161→ }
162→}
163→
164→func (p *Parser) parseVarDecl() *ast.VarDecl {
165→ decl := &ast.VarDecl{
166→ Token: p.curToken,
167→ Type: p.curToken.Literal,
168→ }
169→
170→ // Parse variable names
171→ for {
172→ if !p.expectPeek(lexer.IDENT) {
173→ return nil
174→ }
175→ decl.Names = append(decl.Names, p.curToken.Literal)
176→
177→ if p.peekTokenIs(lexer.COMMA) {
178→ p.nextToken() // consume comma
179→ } else {
180→ break
181→ }
182→ }
183→
184→ if !p.expectPeek(lexer.COLON) {
185→ return nil
186→ }
187→
188→ return decl
189→}
190→
191→func (p *Parser) parseAssignment() *ast.Assignment {
192→ stmt := &ast.Assignment{
193→ Name: p.curToken.Literal,
194→ }
195→
196→ p.nextToken() // move to :=
197→ stmt.Token = p.curToken
198→
199→ p.nextToken() // move past :=
200→ stmt.Value = p.parseExpression(LOWEST)
201→
202→ return stmt
203→}
204→
205→func (p *Parser) parseSeqBlock() *ast.SeqBlock {
206→ block := &ast.SeqBlock{Token: p.curToken}
207→
208→ // Skip to next line
209→ for p.peekTokenIs(lexer.NEWLINE) {
<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>
I need to:
1. Add handling for CHAN in parseStatement to parse channel declarations
2. Modify the IDENT case to check for ! (send) and ? (receive) as well as := (assignment)
Let me update parseStatement first.
switch p.curToken.Type {
case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE:
return p.parseVarDecl()
case lexer.SEQ:
return p.parseSeqBlock()
case lexer.PAR:
return p.parseParBlock()
case lexer.SKIP:
return &ast.Skip{Token: p.curToken}
case lexer.PROC:
return p.parseProcDecl()
case lexer.WHILE:
return p.parseWhileLoop()
case lexer.IF:
return p.parseIfStatement()
case lexer.IDENT:
// Could be assignment or procedure call
if p.peekTokenIs(lexer.ASSIGN) {
return p.parseAssignment()
}
return p.parseProcCall()
case lexer.INDENT, lexer.DEDENT, lexer.EOF:
return nil
default:
p.addError(fmt.Sprintf("unexpected token: %s", p.curToken.Type))
return nil
} switch p.curToken.Type {
case lexer.INT_TYPE, lexer.BYTE_TYPE, lexer.BOOL_TYPE, lexer.REAL_TYPE:
return p.parseVarDecl()
case lexer.CHAN:
return p.parseChanDecl()
case lexer.SEQ:
return p.parseSeqBlock()
case lexer.PAR:
return p.parseParBlock()
case lexer.SKIP:
return &ast.Skip{Token: p.curToken}
case lexer.PROC:
return p.parseProcDecl()
case lexer.WHILE:
return p.parseWhileLoop()
case lexer.IF:
return p.parseIfStatement()
case lexer.IDENT:
// Could be assignment, send, receive, or procedure call
if p.peekTokenIs(lexer.ASSIGN) {
return p.parseAssignment()
}
if p.peekTokenIs(lexer.SEND) {
return p.parseSend()
}
if p.peekTokenIs(lexer.RECEIVE) {
return p.parseReceive()
}
return p.parseProcCall()
case lexer.INDENT, lexer.DEDENT, lexer.EOF:
return nil
default:
p.addError(fmt.Sprintf("unexpected token: %s", p.curToken.Type))
return nil
}The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
Now I need to add the parsing functions: parseChanDecl, parseSend, and parseReceive. Let me add them after parseVarDecl.
func (p *Parser) parseAssignment() *ast.Assignment {
stmt := &ast.Assignment{
Name: p.curToken.Literal,
}
p.nextToken() // move to :=
stmt.Token = p.curToken
p.nextToken() // move past :=
stmt.Value = p.parseExpression(LOWEST)
return stmt
}func (p *Parser) parseAssignment() *ast.Assignment {
stmt := &ast.Assignment{
Name: p.curToken.Literal,
}
p.nextToken() // move to :=
stmt.Token = p.curToken
p.nextToken() // move past :=
stmt.Value = p.parseExpression(LOWEST)
return stmt
}
func (p *Parser) parseChanDecl() *ast.ChanDecl {
decl := &ast.ChanDecl{Token: p.curToken}
// Expect OF
if !p.expectPeek(lexer.OF) {
return nil
}
// Expect type (INT, BYTE, BOOL, etc.)
p.nextToken()
if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) &&
!p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) {
p.addError(fmt.Sprintf("expected type after CHAN OF, got %s", p.curToken.Type))
return nil
}
decl.ElemType = p.curToken.Literal
// Parse channel names
for {
if !p.expectPeek(lexer.IDENT) {
return nil
}
decl.Names = append(decl.Names, p.curToken.Literal)
if p.peekTokenIs(lexer.COMMA) {
p.nextToken() // consume comma
} else {
break
}
}
if !p.expectPeek(lexer.COLON) {
return nil
}
return decl
}
func (p *Parser) parseSend() *ast.Send {
stmt := &ast.Send{
Channel: p.curToken.Literal,
}
p.nextToken() // move to !
stmt.Token = p.curToken
p.nextToken() // move past !
stmt.Value = p.parseExpression(LOWEST)
return stmt
}
func (p *Parser) parseReceive() *ast.Receive {
stmt := &ast.Receive{
Channel: p.curToken.Literal,
}
p.nextToken() // move to ?
stmt.Token = p.curToken
if !p.expectPeek(lexer.IDENT) {
return nil
}
stmt.Variable = p.curToken.Literal
return stmt
}The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
Now I need to update the code generator to handle channels. Let me read the code generator and add the necessary generation functions.
{
"file_path": "/home/david/projects/codeassociates/occam2go/codegen/codegen.go",
"offset": 155,
"limit": 40
} 155→ case *ast.WhileLoop:
156→ if s.Body != nil && g.containsPrint(s.Body) {
157→ return true
158→ }
159→ case *ast.IfStatement:
160→ for _, choice := range s.Choices {
161→ if choice.Body != nil && g.containsPrint(choice.Body) {
162→ return true
163→ }
164→ }
165→ }
166→ return false
167→}
168→
169→func (g *Generator) writeLine(s string) {
170→ if s == "" {
171→ g.builder.WriteString("\n")
172→ return
173→ }
174→ g.builder.WriteString(strings.Repeat("\t", g.indent))
175→ g.builder.WriteString(s)
176→ g.builder.WriteString("\n")
177→}
178→
179→func (g *Generator) write(s string) {
180→ g.builder.WriteString(s)
181→}
182→
183→func (g *Generator) generateStatement(stmt ast.Statement) {
184→ switch s := stmt.(type) {
185→ case *ast.VarDecl:
186→ g.generateVarDecl(s)
187→ case *ast.Assignment:
188→ g.generateAssignment(s)
189→ case *ast.SeqBlock:
190→ g.generateSeqBlock(s)
191→ case *ast.ParBlock:
192→ g.generateParBlock(s)
193→ case *ast.Skip:
194→ g.writeLine("// SKIP")
<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>
I need to add cases for ChanDecl, Send, and Receive in generateStatement. Let me see the full generateStatement function.
func (g *Generator) generateStatement(stmt ast.Statement) {
switch s := stmt.(type) {
case *ast.VarDecl:
g.generateVarDecl(s)
case *ast.Assignment:
g.generateAssignment(s)
case *ast.SeqBlock:
g.generateSeqBlock(s)
case *ast.ParBlock:
g.generateParBlock(s)
case *ast.Skip:
g.writeLine("// SKIP")func (g *Generator) generateStatement(stmt ast.Statement) {
switch s := stmt.(type) {
case *ast.VarDecl:
g.generateVarDecl(s)
case *ast.ChanDecl:
g.generateChanDecl(s)
case *ast.Assignment:
g.generateAssignment(s)
case *ast.Send:
g.generateSend(s)
case *ast.Receive:
g.generateReceive(s)
case *ast.SeqBlock:
g.generateSeqBlock(s)
case *ast.ParBlock:
g.generateParBlock(s)
case *ast.Skip:
g.writeLine("// SKIP")The file /home/david/projects/codeassociates/occam2go/codegen/codegen.go has been updated successfully.
Now I need to add the generation functions for channels. Let me find a good place to add them - after generateVarDecl.
{
"file_path": "/home/david/projects/codeassociates/occam2go/codegen/codegen.go",
"offset": 205,
"limit": 30
} 205→ case *ast.WhileLoop:
206→ g.generateWhileLoop(s)
207→ case *ast.IfStatement:
208→ g.generateIfStatement(s)
209→ }
210→}
211→
212→func (g *Generator) generateVarDecl(decl *ast.VarDecl) {
213→ goType := g.occamTypeToGo(decl.Type)
214→ g.writeLine(fmt.Sprintf("var %s %s", strings.Join(decl.Names, ", "), goType))
215→}
216→
217→func (g *Generator) occamTypeToGo(occamType string) string {
218→ switch occamType {
219→ case "INT":
220→ return "int"
221→ case "BYTE":
222→ return "byte"
223→ case "BOOL":
224→ return "bool"
225→ case "REAL":
226→ return "float64"
227→ default:
228→ return occamType // pass through unknown types
229→ }
230→}
231→
232→func (g *Generator) generateAssignment(assign *ast.Assignment) {
233→ g.builder.WriteString(strings.Repeat("\t", g.indent))
234→ // Dereference if assigning to a reference parameter
<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>
func (g *Generator) generateVarDecl(decl *ast.VarDecl) {
goType := g.occamTypeToGo(decl.Type)
g.writeLine(fmt.Sprintf("var %s %s", strings.Join(decl.Names, ", "), goType))
}
func (g *Generator) occamTypeToGo(occamType string) string {func (g *Generator) generateVarDecl(decl *ast.VarDecl) {
goType := g.occamTypeToGo(decl.Type)
g.writeLine(fmt.Sprintf("var %s %s", strings.Join(decl.Names, ", "), goType))
}
func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
goType := g.occamTypeToGo(decl.ElemType)
for _, name := range decl.Names {
g.writeLine(fmt.Sprintf("%s := make(chan %s)", name, goType))
}
}
func (g *Generator) generateSend(send *ast.Send) {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write(send.Channel)
g.write(" <- ")
g.generateExpression(send.Value)
g.write("\n")
}
func (g *Generator) generateReceive(recv *ast.Receive) {
g.writeLine(fmt.Sprintf("%s = <-%s", recv.Variable, recv.Channel))
}
func (g *Generator) occamTypeToGo(occamType string) string {The file /home/david/projects/codeassociates/occam2go/codegen/codegen.go has been updated successfully.
Now I need to make sure the parser tests and code still compiles. Let me run the tests.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 1.540s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/parser 0.002s
All tests pass. Now let me create a test for channels and add an end-to-end test to verify the functionality.
{
"file_path": "/home/david/projects/codeassociates/occam2go/parser/parser_test.go"
} 1→package parser
2→
3→import (
4→ "testing"
5→
6→ "github.com/codeassociates/occam2go/ast"
7→ "github.com/codeassociates/occam2go/lexer"
8→)
9→
10→func TestVarDecl(t *testing.T) {
11→ input := `INT x:
12→`
13→ l := lexer.New(input)
14→ p := New(l)
15→ program := p.ParseProgram()
16→ checkParserErrors(t, p)
17→
18→ if len(program.Statements) != 1 {
19→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
20→ }
21→
22→ decl, ok := program.Statements[0].(*ast.VarDecl)
23→ if !ok {
24→ t.Fatalf("expected VarDecl, got %T", program.Statements[0])
25→ }
26→
27→ if decl.Type != "INT" {
28→ t.Errorf("expected type INT, got %s", decl.Type)
29→ }
30→
31→ if len(decl.Names) != 1 || decl.Names[0] != "x" {
32→ t.Errorf("expected name 'x', got %v", decl.Names)
33→ }
34→}
35→
36→func TestMultipleVarDecl(t *testing.T) {
37→ input := `INT x, y, z:
38→`
39→ l := lexer.New(input)
40→ p := New(l)
41→ program := p.ParseProgram()
42→ checkParserErrors(t, p)
43→
44→ if len(program.Statements) != 1 {
45→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
46→ }
47→
48→ decl, ok := program.Statements[0].(*ast.VarDecl)
49→ if !ok {
50→ t.Fatalf("expected VarDecl, got %T", program.Statements[0])
51→ }
52→
53→ expected := []string{"x", "y", "z"}
54→ if len(decl.Names) != len(expected) {
55→ t.Fatalf("expected %d names, got %d", len(expected), len(decl.Names))
56→ }
57→ for i, name := range expected {
58→ if decl.Names[i] != name {
59→ t.Errorf("expected name %s at position %d, got %s", name, i, decl.Names[i])
60→ }
61→ }
62→}
63→
64→func TestAssignment(t *testing.T) {
65→ input := `x := 5
66→`
67→ l := lexer.New(input)
68→ p := New(l)
69→ program := p.ParseProgram()
70→ checkParserErrors(t, p)
71→
72→ if len(program.Statements) != 1 {
73→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
74→ }
75→
76→ assign, ok := program.Statements[0].(*ast.Assignment)
77→ if !ok {
78→ t.Fatalf("expected Assignment, got %T", program.Statements[0])
79→ }
80→
81→ if assign.Name != "x" {
82→ t.Errorf("expected name 'x', got %s", assign.Name)
83→ }
84→
85→ intLit, ok := assign.Value.(*ast.IntegerLiteral)
86→ if !ok {
87→ t.Fatalf("expected IntegerLiteral, got %T", assign.Value)
88→ }
89→
90→ if intLit.Value != 5 {
91→ t.Errorf("expected value 5, got %d", intLit.Value)
92→ }
93→}
94→
95→func TestBinaryExpression(t *testing.T) {
96→ input := `x := a + b * c
97→`
98→ l := lexer.New(input)
99→ p := New(l)
100→ program := p.ParseProgram()
101→ checkParserErrors(t, p)
102→
103→ if len(program.Statements) != 1 {
104→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
105→ }
106→
107→ assign, ok := program.Statements[0].(*ast.Assignment)
108→ if !ok {
109→ t.Fatalf("expected Assignment, got %T", program.Statements[0])
110→ }
111→
112→ // Should be: a + (b * c) due to precedence
113→ binExpr, ok := assign.Value.(*ast.BinaryExpr)
114→ if !ok {
115→ t.Fatalf("expected BinaryExpr, got %T", assign.Value)
116→ }
117→
118→ if binExpr.Operator != "+" {
119→ t.Errorf("expected +, got %s", binExpr.Operator)
120→ }
121→
122→ // Right side should be b * c
123→ rightBin, ok := binExpr.Right.(*ast.BinaryExpr)
124→ if !ok {
125→ t.Fatalf("expected right to be BinaryExpr, got %T", binExpr.Right)
126→ }
127→
128→ if rightBin.Operator != "*" {
129→ t.Errorf("expected *, got %s", rightBin.Operator)
130→ }
131→}
132→
133→func TestSeqBlock(t *testing.T) {
134→ input := `SEQ
135→ INT x:
136→ x := 10
137→`
138→ l := lexer.New(input)
139→ p := New(l)
140→ program := p.ParseProgram()
141→ checkParserErrors(t, p)
142→
143→ if len(program.Statements) != 1 {
144→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
145→ }
146→
147→ seq, ok := program.Statements[0].(*ast.SeqBlock)
148→ if !ok {
149→ t.Fatalf("expected SeqBlock, got %T", program.Statements[0])
150→ }
151→
152→ if len(seq.Statements) != 2 {
153→ t.Fatalf("expected 2 statements in SEQ, got %d", len(seq.Statements))
154→ }
155→
156→ _, ok = seq.Statements[0].(*ast.VarDecl)
157→ if !ok {
158→ t.Errorf("expected first statement to be VarDecl, got %T", seq.Statements[0])
159→ }
160→
161→ _, ok = seq.Statements[1].(*ast.Assignment)
162→ if !ok {
163→ t.Errorf("expected second statement to be Assignment, got %T", seq.Statements[1])
164→ }
165→}
166→
167→func TestParBlock(t *testing.T) {
168→ input := `PAR
169→ x := 1
170→ y := 2
171→`
172→ l := lexer.New(input)
173→ p := New(l)
174→ program := p.ParseProgram()
175→ checkParserErrors(t, p)
176→
177→ if len(program.Statements) != 1 {
178→ t.Fatalf("expected 1 statement, got %d", len(program.Statements))
179→ }
180→
181→ par, ok := program.Statements[0].(*ast.ParBlock)
182→ if !ok {
183→ t.Fatalf("expected ParBlock, got %T", program.Statements[0])
184→ }
185→
186→ if len(par.Statements) != 2 {
187→ t.Fatalf("expected 2 statements in PAR, got %d", len(par.Statements))
188→ }
189→}
190→
191→func checkParserErrors(t *testing.T, p *Parser) {
192→ errors := p.Errors()
193→ if len(errors) == 0 {
194→ return
195→ }
196→
197→ t.Errorf("parser has %d errors:", len(errors))
198→ for _, msg := range errors {
199→ t.Errorf(" parser error: %s", msg)
200→ }
201→ t.FailNow()
202→}
203→
<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>
func checkParserErrors(t *testing.T, p *Parser) {
errors := p.Errors()
if len(errors) == 0 {
return
}
t.Errorf("parser has %d errors:", len(errors))
for _, msg := range errors {
t.Errorf(" parser error: %s", msg)
}
t.FailNow()
}func TestChanDecl(t *testing.T) {
input := `CHAN OF INT c:
`
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 decl.ElemType != "INT" {
t.Errorf("expected element type INT, got %s", decl.ElemType)
}
if len(decl.Names) != 1 || decl.Names[0] != "c" {
t.Errorf("expected name 'c', got %v", decl.Names)
}
}
func TestSend(t *testing.T) {
input := `c ! 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 != "c" {
t.Errorf("expected channel 'c', got %s", send.Channel)
}
intLit, ok := send.Value.(*ast.IntegerLiteral)
if !ok {
t.Fatalf("expected IntegerLiteral, got %T", send.Value)
}
if intLit.Value != 42 {
t.Errorf("expected value 42, got %d", intLit.Value)
}
}
func TestReceive(t *testing.T) {
input := `c ? 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 != "c" {
t.Errorf("expected channel 'c', got %s", recv.Channel)
}
if recv.Variable != "x" {
t.Errorf("expected variable 'x', got %s", recv.Variable)
}
}
func checkParserErrors(t *testing.T, p *Parser) {
errors := p.Errors()
if len(errors) == 0 {
return
}
t.Errorf("parser has %d errors:", len(errors))
for _, msg := range errors {
t.Errorf(" parser error: %s", msg)
}
t.FailNow()
}The file /home/david/projects/codeassociates/occam2go/parser/parser_test.go has been updated successfully.
{
"file_path": "/home/david/projects/codeassociates/occam2go/codegen/e2e_test.go",
"offset": 100,
"limit": 50
} 100→ output := transpileCompileRun(t, occam)
101→ expected := "12\n"
102→ if output != expected {
103→ t.Errorf("expected %q, got %q", expected, output)
104→ }
105→}
106→
107→func TestE2E_Expression(t *testing.T) {
108→ occam := `SEQ
109→ INT result:
110→ result := (2 + 3) * 4
111→ print.int(result)
112→`
113→ output := transpileCompileRun(t, occam)
114→ expected := "20\n"
115→ if output != expected {
116→ t.Errorf("expected %q, got %q", expected, output)
117→ }
118→}
119→
120→func TestE2E_Procedure(t *testing.T) {
121→ occam := `PROC double(VAL INT x, INT result)
122→ SEQ
123→ result := x * 2
124→
125→SEQ
126→ INT n, doubled:
127→ n := 21
128→ double(n, doubled)
129→ print.int(doubled)
130→`
131→ output := transpileCompileRun(t, occam)
132→ expected := "42\n"
133→ if output != expected {
134→ t.Errorf("expected %q, got %q", expected, output)
135→ }
136→}
137→
138→func TestE2E_PAR(t *testing.T) {
139→ // Test that PAR executes both branches
140→ // We can't guarantee order, but both should run
141→ occam := `SEQ
142→ INT x, y:
143→ PAR
144→ x := 10
145→ y := 20
146→ print.int(x + y)
147→`
148→ output := transpileCompileRun(t, occam)
149→ expected := "30\n"
<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>
{
"file_path": "/home/david/projects/codeassociates/occam2go/codegen/e2e_test.go",
"offset": 170,
"limit": 30
} 170→ BOOL flag:
171→ flag := TRUE
172→ print.bool(flag)
173→`
174→ output := transpileCompileRun(t, occam)
175→ expected := "true\n"
176→ if output != expected {
177→ t.Errorf("expected %q, got %q", expected, output)
178→ }
179→}
180→
181→func TestE2E_Comparison(t *testing.T) {
182→ occam := `SEQ
183→ INT a, b:
184→ a := 5
185→ b := 3
186→ print.bool(a > b)
187→`
188→ output := transpileCompileRun(t, occam)
189→ expected := "true\n"
190→ if output != expected {
191→ t.Errorf("expected %q, got %q", expected, output)
192→ }
193→}
194→
195→func TestE2E_ComplexExpression(t *testing.T) {
196→ // Test: (10 + 5) * 2 - 6 / 3 = 15 * 2 - 2 = 30 - 2 = 28
197→ occam := `SEQ
198→ INT result:
199→ result := ((10 + 5) * 2) - (6 / 3)
<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>
{
"file_path": "/home/david/projects/codeassociates/occam2go/codegen/e2e_test.go",
"offset": 195,
"limit": 20
} 195→func TestE2E_ComplexExpression(t *testing.T) {
196→ // Test: (10 + 5) * 2 - 6 / 3 = 15 * 2 - 2 = 30 - 2 = 28
197→ occam := `SEQ
198→ INT result:
199→ result := ((10 + 5) * 2) - (6 / 3)
200→ print.int(result)
201→`
202→ output := transpileCompileRun(t, occam)
203→ // Note: Need to verify Go's integer division matches expectation
204→ output = strings.TrimSpace(output)
205→ if output != "28" {
206→ t.Errorf("expected 28, got %q", output)
207→ }
208→}
209→
<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>
func TestE2E_ComplexExpression(t *testing.T) {
// Test: (10 + 5) * 2 - 6 / 3 = 15 * 2 - 2 = 30 - 2 = 28
occam := `SEQ
INT result:
result := ((10 + 5) * 2) - (6 / 3)
print.int(result)
`
output := transpileCompileRun(t, occam)
// Note: Need to verify Go's integer division matches expectation
output = strings.TrimSpace(output)
if output != "28" {
t.Errorf("expected 28, got %q", output)
}
}func TestE2E_ComplexExpression(t *testing.T) {
// Test: (10 + 5) * 2 - 6 / 3 = 15 * 2 - 2 = 30 - 2 = 28
occam := `SEQ
INT result:
result := ((10 + 5) * 2) - (6 / 3)
print.int(result)
`
output := transpileCompileRun(t, occam)
// Note: Need to verify Go's integer division matches expectation
output = strings.TrimSpace(output)
if output != "28" {
t.Errorf("expected 28, got %q", output)
}
}
func TestE2E_Channel(t *testing.T) {
// Test basic channel communication between parallel processes
occam := `SEQ
CHAN OF INT c:
INT result:
PAR
c ! 42
c ? result
print.int(result)
`
output := transpileCompileRun(t, occam)
expected := "42\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ChannelExpression(t *testing.T) {
// Test sending an expression over a channel
occam := `SEQ
CHAN OF INT c:
INT x, result:
x := 10
PAR
c ! x * 2
c ? result
print.int(result)
`
output := transpileCompileRun(t, occam)
expected := "20\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ChannelPingPong(t *testing.T) {
// Test two-way communication: send a value, double it, send back
occam := `SEQ
CHAN OF INT request:
CHAN OF INT response:
INT result:
PAR
SEQ
request ! 21
response ? result
SEQ
INT x:
request ? x
response ! x * 2
print.int(result)
`
output := transpileCompileRun(t, occam)
expected := "42\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.
export PATH=$PATH:/usr/local/go/bin && go test ./... -v -timeout 60s 2>&1 | grep -E "^(=== RUN|--- PASS|--- FAIL|PASS|FAIL|ok|panic)"
Exit code 1
? github.com/codeassociates/occam2go [no test files]
? github.com/codeassociates/occam2go/ast [no test files]
--- FAIL: TestE2E_ChannelPingPong (0.14s)
e2e_test.go:261: execution failed: exit status 2
Output: fatal error: all goroutines are asleep - deadlock!
goroutine 1 [sync.WaitGroup.Wait]:
sync.runtime_SemacquireWaitGroup(0x57530?, 0xf0?)
/usr/local/go/src/runtime/sema.go:114 +0x38
sync.(*WaitGroup).Wait(0x4000108020)
/usr/local/go/src/sync/waitgroup.go:206 +0xa8
main.main()
/tmp/occam2go-test-1812193358/main.go:19 +0xd0
goroutine 19 [chan send]:
main.main.func1()
/tmp/occam2go-test-1812193358/main.go:16 +0x5c
created by main.main in goroutine 1
/tmp/occam2go-test-1812193358/main.go:14 +0xc8
FAIL
FAIL github.com/codeassociates/occam2go/codegen 1.884s
ok github.com/codeassociates/occam2go/lexer (cached)
ok github.com/codeassociates/occam2go/parser 0.002s
FAILThere's a deadlock in the ping-pong test. The issue is that when we have nested PAR blocks or SEQ within PAR, the current code generation might not be handling it correctly. Let me check what code is being generated for the ping-pong test.
The problem is likely that when we have:
PAR
SEQ
request ! 21
response ? result
SEQ
INT x:
request ? x
response ! x * 2
Each branch of the PAR has its own SEQ block. The issue might be in how variables are captured by the goroutine closures. In Go, closures capture variables by reference, so if both goroutines are reading/writing the same variable (result, x), there could be issues.
But actually, looking at the deadlock, the problem is different. Let me think about what's happening: 1. Goroutine 1: sends 21 on request, then waits to receive on response 2. Goroutine 2: waits to receive on request, then sends x*2 on response
This should work fine. Let me look at what code is actually being generated.
Actually, wait - the issue might be simpler. Let me trace through the code: - The main goroutine calls wg.Wait() before doing anything else - But the channels need to be created before the PAR, and the PAR branches need to properly be goroutines
Let me check what the generated code looks like for this test case.
SEQ
CHAN OF INT request:
CHAN OF INT response:
INT result:
PAR
SEQ
request ! 21
response ? result
SEQ
INT x:
request ? x
response ! x * 2
print.int(result)
File created successfully at: /home/david/projects/codeassociates/occam2go/examples/pingpong.occ
export PATH=$PATH:/usr/local/go/bin && go build -o occam2go && ./occam2go examples/pingpong.occ
package main
import (
"fmt"
"sync"
)
func main() {
request := make(chan int)
response := make(chan int)
var result int
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
request <- 21
result = <-response
}()
wg.Wait()
var x int
x = <-request
response <- (x * 2)
fmt.Println(result)
}I see the issue! The code generation for nested SEQ blocks inside PAR is not correct. Looking at the generated code:1. Only ONE goroutine is being created (wg.Add(1)) 2. The second SEQ block is being executed in the main goroutine, but AFTER wg.Wait()!This is because when we generate a PAR block, we iterate over its statements and generate each as a goroutine. But the nested SEQ blocks are not being properly handled as separate parallel processes.
The issue is that the PAR block has 2 statements (two SEQ blocks), so wg.Add should be called with 2, and both SEQ blocks should be in goroutines.
Let me check the generateParBlock function:
gofunc (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()")
}This looks correct - it should iterate over all statements and generate a goroutine for each. But looking at the generated code, it only generated one goroutine with the first SEQ block.
Oh wait, I see the issue now. Looking at the output more carefully:```go var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() request <- 21 result = <-response }() wg.Wait() var x int
x = <-request response <- (x * 2) fmt.Println(result)
The `wg.Add(1)` indicates `len(par.Statements)` is only 1, meaning the parser is treating the PAR block as containing just a single statement instead of two separate SEQ blocks. The second SEQ block is being parsed outside the PAR entirely. I need to examine how parseBlockStatements handles the parsing of multiple SEQ blocks within a PAR block to understand why it's not recognizing both as distinct statements.
Looking at the token stream, after the first SEQ block completes with its DEDENT, the parser should continue the loop and encounter the second SEQ. The problem likely stems from indentation tracking—once the first SEQ's block ends, the indentLevel might be dropping prematurely, causing parseBlockStatements to exit before processing the second statement.
Examining the parseBlockStatements logic, startLevel captures the indentation before consuming the INDENT token. The loop continues until indentLevel falls below startLevel or encounters DEDENT/EOF. If indentLevel is being decremented incorrectly after the first SEQ's DEDENT, the condition `p.indentLevel < startLevel` would trigger early, terminating the loop before the second SEQ can be parsed. After consuming PAR's INDENT token, indentLevel moves to 1. When we encounter the SEQ token and call parseSeqBlock, it consumes another INDENT, pushing indentLevel to 2. Inside SEQ's parseBlockStatements, we set startLevel to 1 before that second INDENT consumption. We parse SEQ's statements until hitting its DEDENT token, which exits the loop and returns us to PAR's parseBlockStatements with the SEQ's DEDENT as the current token. in parseSeqBlock, after parseBlockStatements returns, we're still at the DEDENT token and simply return the block without consuming it. This means the DEDENT remains for the caller to handle.
Back in PAR's parseBlockStatements loop, curToken is still DEDENT. The loop checks `if p.curTokenIs(lexer.DEDENT) || p.curTokenIs(lexer.EOF)` and breaks immediately. But this DEDENT belongs to the inner SEQ block, not PAR's block—we should continue parsing the second SEQ instead. The problem is we're checking for DEDENT without verifying it's at the correct indentation level for our current block. We need to check indentLevel first to see if we've actually dedented below the block's starting level before breaking out.
When the DEDENT token for SEQ's block arrives, indentLevel has already been decremented by nextToken's tracking, so it might equal startLevel rather than fall below it. At that point, checking curTokenIs(DEDENT) causes us to break prematurely. The fix is to only break if the DEDENT actually brings us below startLevel; otherwise, we should consume it and keep parsing. Looking at the token sequence, after SEQ's DEDENT, we're back at PAR's indentation level, so we should continue and parse the next statement in PAR's block. be 2 (because we haven't consumed this DEDENT yet). Actually no, that's not how my lexer tracking works. Let me check nextToken:
```gofunc (p *Parser) nextToken() {
p.curToken = p.peekToken
p.peekToken = p.l.NextToken() // Track indentation level
if p.curToken.Type == lexer.INDENT { p.indentLevel++
} else if p.curToken.Type == lexer.DEDENT {
p.indentLevel--
}
}
The indentLevel gets updated when curToken is set to INDENT or DEDENT, not during the nextToken call itself. So when SEQ's parseBlockStatements exits with curToken = DEDENT, the indentLevel has already been decremented. This means when we return to PAR's parseBlockStatements at step 4, indentLevel is already 1, not 2.
Since PAR's startLevel was 0 (captured before consuming PAR's INDENT), the condition indentLevel (1) < startLevel (0) is false. Then we hit the break condition on DEDENT and exit the loop prematurely. But this DEDENT belongs to SEQ's block, not PAR's block, so we shouldn't break here. The logic needs to either consume DEDENTs when indentLevel >= startLevel and keep going, or restructure how we handle block termination.
The real issue is that we're breaking on any DEDENT without checking whether it actually ends our block. We should only break when a DEDENT brings us below our starting indentation level. Otherwise, if we're still at or above startLevel, that DEDENT is closing a nested block and we should consume it and continue parsing.
The corrected approach checks the indentLevel after each DEDENT—if it drops below startLevel, we've reached the end of our block and return. If not, we skip the DEDENT and keep going. However, I need to trace through the token flow more carefully. When we encounter a DEDENT token, the indentLevel gets decremented, so I should verify exactly when that happens and whether we're consuming DEDENTs correctly in the initial state. the previous nextToken() call. Let me trace the exact token-by-token flow. Actually this is getting confusing. Let me just make the fix and test it.
The key insight is: when we see a DEDENT token, we should check if consuming it would bring us below startLevel. The indentLevel has already been decremented when curToken was set to DEDENT. So if indentLevel is less than startLevel, this DEDENT ends our block and shouldn't be consumed, but if it's still at or above startLevel, the DEDENT belongs to a nested block and we should consume it and continue parsing. I'll update parseBlockStatements with this logic.
I see the issue. When parsing nested blocks, we're breaking on the inner DEDENT instead of continuing. Let me fix parseBlockStatements.
{
"file_path": "/home/david/projects/codeassociates/occam2go/parser/parser.go",
"offset": 258,
"limit": 40
} 258→
259→ p.nextToken() // move past !
260→ stmt.Value = p.parseExpression(LOWEST)
261→
262→ return stmt
263→}
264→
265→func (p *Parser) parseReceive() *ast.Receive {
266→ stmt := &ast.Receive{
267→ Channel: p.curToken.Literal,
268→ }
269→
270→ p.nextToken() // move to ?
271→ stmt.Token = p.curToken
272→
273→ if !p.expectPeek(lexer.IDENT) {
274→ return nil
275→ }
276→ stmt.Variable = p.curToken.Literal
277→
278→ return stmt
279→}
280→
281→func (p *Parser) parseSeqBlock() *ast.SeqBlock {
282→ block := &ast.SeqBlock{Token: p.curToken}
283→
284→ // Skip to next line
285→ for p.peekTokenIs(lexer.NEWLINE) {
286→ p.nextToken()
287→ }
288→
289→ // Expect INDENT
290→ if !p.peekTokenIs(lexer.INDENT) {
291→ p.addError("expected indented block after SEQ")
292→ return block
293→ }
294→ p.nextToken() // consume INDENT
295→
296→ block.Statements = p.parseBlockStatements()
297→
<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>
{
"file_path": "/home/david/projects/codeassociates/occam2go/parser/parser.go",
"offset": 310,
"limit": 50
} 310→ if !p.peekTokenIs(lexer.INDENT) {
311→ p.addError("expected indented block after PAR")
312→ return block
313→ }
314→ p.nextToken() // consume INDENT
315→
316→ block.Statements = p.parseBlockStatements()
317→
318→ return block
319→}
320→
321→func (p *Parser) parseBlockStatements() []ast.Statement {
322→ var statements []ast.Statement
323→ startLevel := p.indentLevel
324→
325→ p.nextToken() // move past INDENT
326→
327→ for !p.curTokenIs(lexer.EOF) {
328→ // Skip newlines
329→ for p.curTokenIs(lexer.NEWLINE) {
330→ p.nextToken()
331→ }
332→
333→ // Stop if we've dedented below the block's level
334→ if p.indentLevel < startLevel {
335→ break
336→ }
337→ if p.curTokenIs(lexer.DEDENT) || p.curTokenIs(lexer.EOF) {
338→ break
339→ }
340→
341→ stmt := p.parseStatement()
342→ if stmt != nil {
343→ statements = append(statements, stmt)
344→ }
345→
346→ // After parsing a statement, we need to advance.
347→ // But if we're already at NEWLINE/DEDENT/EOF, the next iteration will handle it.
348→ // Only advance if we're still on the last token of the statement.
349→ if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
350→ p.nextToken()
351→ }
352→ }
353→
354→ return statements
355→}
356→
357→func (p *Parser) parseProcDecl() *ast.ProcDecl {
358→ proc := &ast.ProcDecl{Token: p.curToken}
359→
<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 understand the issue. When we hit a DEDENT, we break immediately without checking if it's actually the end of our block. The fix is to consume DEDENTs from nested blocks and only break when we truly reach our block's end.
The issue is on line 337: if p.curTokenIs(lexer.DEDENT) || p.curTokenIs(lexer.EOF).
When we're at a DEDENT, the indentLevel has already been decremented. So if indentLevel >= startLevel, this DEDENT is from a nested block and we should skip it.
Let me rewrite the logic:
func (p *Parser) parseBlockStatements() []ast.Statement {
var statements []ast.Statement
startLevel := p.indentLevel
p.nextToken() // move past INDENT
for !p.curTokenIs(lexer.EOF) {
// Skip newlines
for p.curTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Stop if we've dedented below the block's level
if p.indentLevel < startLevel {
break
}
if p.curTokenIs(lexer.DEDENT) || p.curTokenIs(lexer.EOF) {
break
}
stmt := p.parseStatement()
if stmt != nil {
statements = append(statements, stmt)
}
// After parsing a statement, we need to advance.
// But if we're already at NEWLINE/DEDENT/EOF, the next iteration will handle it.
// Only advance if we're still on the last token of the statement.
if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
p.nextToken()
}
}
return statements
}func (p *Parser) parseBlockStatements() []ast.Statement {
var statements []ast.Statement
startLevel := p.indentLevel
p.nextToken() // move past INDENT
for !p.curTokenIs(lexer.EOF) {
// Skip newlines
for p.curTokenIs(lexer.NEWLINE) {
p.nextToken()
}
// Handle DEDENT tokens
// If we're at a DEDENT and indentLevel has dropped below startLevel,
// this DEDENT ends our block - stop parsing
// If indentLevel >= startLevel, this DEDENT is from a nested block - skip it
for p.curTokenIs(lexer.DEDENT) {
if p.indentLevel < startLevel {
return statements
}
p.nextToken() // skip nested block's DEDENT
}
// Skip any more newlines after DEDENT
for p.curTokenIs(lexer.NEWLINE) {
p.nextToken()
}
if p.curTokenIs(lexer.EOF) {
break
}
// Double-check we haven't gone below our level
if p.indentLevel < startLevel {
break
}
stmt := p.parseStatement()
if stmt != nil {
statements = append(statements, stmt)
}
// After parsing a statement, we need to advance.
// But if we're already at NEWLINE/DEDENT/EOF, the next iteration will handle it.
// Only advance if we're still on the last token of the statement.
if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) {
p.nextToken()
}
}
return statements
}The file /home/david/projects/codeassociates/occam2go/parser/parser.go has been updated successfully.
export PATH=$PATH:/usr/local/go/bin && go build -o occam2go && ./occam2go examples/pingpong.occ
package main
import (
"fmt"
"sync"
)
func main() {
request := make(chan int)
response := make(chan int)
var result int
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
request <- 21
result = <-response
}()
go func() {
defer wg.Done()
var x int
x = <-request
response <- (x * 2)
}()
wg.Wait()
fmt.Println(result)
}Now it's generating the correct code with two goroutines. Let me run the tests to confirm everything works.
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 1.919s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/parser 0.002s
All tests pass now. Let me create a channel example file and update the README with the channel documentation.
-- Channel communication example
SEQ
CHAN OF INT c:
INT result:
PAR
c ! 42
c ? result
print.int(result)
File created successfully at: /home/david/projects/codeassociates/occam2go/examples/channel.occ
1→# occam2go
2→
3→A transpiler from Occam to Go, written in Go.
4→
5→Occam was developed in the 1980s to support concurrent programming on the Transputer. Go, created decades later, shares similar CSP-influenced concurrency primitives. This transpiler bridges the two.
6→
7→## Building
8→
9→```bash
10→go build -o occam2go
11→```
12→
13→## Usage
14→
15→```bash
16→./occam2go [options] <input.occ>
17→```
18→
19→Options:
20→- `-o <file>` - Write output to file (default: stdout)
21→- `-version` - Print version and exit
22→
23→## Running an Example
24→
25→Here's how to transpile, compile, and run an Occam program:
26→
27→```bash
28→# 1. Build the transpiler (only needed once)
29→go build -o occam2go
30→
31→# 2. Transpile an Occam file to Go
32→./occam2go examples/print.occ -o output.go
33→
34→# 3. Compile the generated Go code
35→go build -o output output.go
36→
37→# 4. Run the compiled program
38→./output
39→```
40→
41→Or as a one-liner to see the output immediately:
42→
43→```bash
44→./occam2go examples/print.occ -o output.go && go run output.go
45→```
46→
47→## Example
48→
49→Input (`example.occ`):
50→```occam
51→SEQ
52→ INT x, y:
53→ PAR
54→ x := 1
55→ y := 2
56→ x := x + y
57→```
58→
59→Output:
60→```go
61→package main
62→
63→import (
64→ "sync"
65→)
66→
67→func main() {
68→ var x, y int
69→ var wg sync.WaitGroup
70→ wg.Add(2)
71→ go func() {
72→ defer wg.Done()
73→ x = 1
74→ }()
75→ go func() {
76→ defer wg.Done()
77→ y = 2
78→ }()
79→ wg.Wait()
80→ x = (x + y)
81→}
82→```
83→
84→## Implemented Features
85→
86→| Occam | Go |
87→|-------|-----|
88→| `INT`, `BYTE`, `BOOL`, `REAL` | `int`, `byte`, `bool`, `float64` |
89→| `SEQ` | Sequential code |
90→| `PAR` | Goroutines with `sync.WaitGroup` |
91→| `PROC` with `VAL` params | Functions with value/pointer params |
92→| `:=` assignment | `=` assignment |
93→| Arithmetic: `+`, `-`, `*`, `/`, `\` | `+`, `-`, `*`, `/`, `%` |
94→| Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=` | `==`, `!=`, `<`, `>`, `<=`, `>=` |
95→| Logic: `AND`, `OR`, `NOT` | `&&`, `\|\|`, `!` |
96→
97→### Built-in I/O Procedures
98→
99→| Occam | Go |
100→|-------|-----|
101→| `print.int(x)` | `fmt.Println(x)` |
102→| `print.bool(x)` | `fmt.Println(x)` |
103→| `print.string(x)` | `fmt.Println(x)` |
104→| `print.newline()` | `fmt.Println()` |
105→
106→## Not Yet Implemented
107→
108→- Channels (`CHAN`, `!`, `?`)
109→- `ALT` (alternation) → `select`
110→- Replicators (`PAR i = 0 FOR n`)
111→- Arrays
112→- `WHILE`, `IF` (partial)
<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>
I need to update the README to:1. Add channels to the implemented features table2. Remove channels from "Not Yet Implemented"3. Add a section showing channel usage
## Implemented Features | Occam | Go | |-------|-----| | `INT`, `BYTE`, `BOOL`, `REAL` | `int`, `byte`, `bool`, `float64` | | `SEQ` | Sequential code | | `PAR` | Goroutines with `sync.WaitGroup` | | `PROC` with `VAL` params | Functions with value/pointer params | | `:=` assignment | `=` assignment | | Arithmetic: `+`, `-`, `*`, `/`, `\` | `+`, `-`, `*`, `/`, `%` | | Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=` | `==`, `!=`, `<`, `>`, `<=`, `>=` | | Logic: `AND`, `OR`, `NOT` | `&&`, `\|\|`, `!` | ### Built-in I/O Procedures | Occam | Go | |-------|-----| | `print.int(x)` | `fmt.Println(x)` | | `print.bool(x)` | `fmt.Println(x)` | | `print.string(x)` | `fmt.Println(x)` | | `print.newline()` | `fmt.Println()` | ## Not Yet Implemented - Channels (`CHAN`, `!`, `?`) - `ALT` (alternation) → `select` - Replicators (`PAR i = 0 FOR n`) - Arrays - `WHILE`, `IF` (partial)
## Implemented Features
| Occam | Go |
|-------|-----|
| `INT`, `BYTE`, `BOOL`, `REAL` | `int`, `byte`, `bool`, `float64` |
| `SEQ` | Sequential code |
| `PAR` | Goroutines with `sync.WaitGroup` |
| `PROC` with `VAL` params | Functions with value/pointer params |
| `:=` assignment | `=` assignment |
| Arithmetic: `+`, `-`, `*`, `/`, `\` | `+`, `-`, `*`, `/`, `%` |
| Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=` | `==`, `!=`, `<`, `>`, `<=`, `>=` |
| Logic: `AND`, `OR`, `NOT` | `&&`, `\|\|`, `!` |
### Channels
| Occam | Go |
|-------|-----|
| `CHAN OF INT c:` | `c := make(chan int)` |
| `c ! x` (send) | `c <- x` |
| `c ? y` (receive) | `y = <-c` |
Example:
```occam
SEQ
CHAN OF INT c:
INT result:
PAR
c ! 42
c ? result
print.int(result)
```
### Built-in I/O Procedures
| Occam | Go |
|-------|-----|
| `print.int(x)` | `fmt.Println(x)` |
| `print.bool(x)` | `fmt.Println(x)` |
| `print.string(x)` | `fmt.Println(x)` |
| `print.newline()` | `fmt.Println()` |
## Not Yet Implemented
- `ALT` (alternation) → `select`
- Replicators (`PAR i = 0 FOR n`)
- Arrays
- `WHILE`, `IF` (partial)The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
Channels are now implemented and documented. Let me also remove the pingpong.occ example file since we have a simpler channel.occ example.
Channels are now implemented. Here's a summary:
New features:
- CHAN OF <type> name: - channel declaration → make(chan <type>)
- c ! x - send → c <- x
- c ? y - receive → y = <-c
Files changed:
- ast/ast.go - added ChanDecl, Send, Receive nodes
- parser/parser.go - added channel parsing, fixed nested block handling
- codegen/codegen.go - added channel code generation
- parser/parser_test.go - added channel parser tests
- codegen/e2e_test.go - added 3 end-to-end channel tests
- examples/channel.occ - channel example
- README.md - documented channels
Bug fix:
- Fixed parsing of nested blocks in PAR (e.g., PAR containing multiple SEQ blocks)
End-to-end tests added: - Basic channel send/receive - Sending expressions over channels - Two-way ping-pong communication
Thank you. Can you add a documentation section that explains a bit about how channels are mapped from Occam to golang in the project?
The user wants me to add a documentation section explaining how channels are mapped from Occam to Go. This should explain the conceptual mapping and any implementation details that are relevant.
1→# occam2go
2→
3→A transpiler from Occam to Go, written in Go.
4→
5→Occam was developed in the 1980s to support concurrent programming on the Transputer. Go, created decades later, shares similar CSP-influenced concurrency primitives. This transpiler bridges the two.
6→
7→## Building
8→
9→```bash
10→go build -o occam2go
11→```
12→
13→## Usage
14→
15→```bash
16→./occam2go [options] <input.occ>
17→```
18→
19→Options:
20→- `-o <file>` - Write output to file (default: stdout)
21→- `-version` - Print version and exit
22→
23→## Running an Example
24→
25→Here's how to transpile, compile, and run an Occam program:
26→
27→```bash
28→# 1. Build the transpiler (only needed once)
29→go build -o occam2go
30→
31→# 2. Transpile an Occam file to Go
32→./occam2go examples/print.occ -o output.go
33→
34→# 3. Compile the generated Go code
35→go build -o output output.go
36→
37→# 4. Run the compiled program
38→./output
39→```
40→
41→Or as a one-liner to see the output immediately:
42→
43→```bash
44→./occam2go examples/print.occ -o output.go && go run output.go
45→```
46→
47→## Example
48→
49→Input (`example.occ`):
50→```occam
51→SEQ
52→ INT x, y:
53→ PAR
54→ x := 1
55→ y := 2
56→ x := x + y
57→```
58→
59→Output:
60→```go
61→package main
62→
63→import (
64→ "sync"
65→)
66→
67→func main() {
68→ var x, y int
69→ var wg sync.WaitGroup
70→ wg.Add(2)
71→ go func() {
72→ defer wg.Done()
73→ x = 1
74→ }()
75→ go func() {
76→ defer wg.Done()
77→ y = 2
78→ }()
79→ wg.Wait()
80→ x = (x + y)
81→}
82→```
83→
84→## Implemented Features
85→
86→| Occam | Go |
87→|-------|-----|
88→| `INT`, `BYTE`, `BOOL`, `REAL` | `int`, `byte`, `bool`, `float64` |
89→| `SEQ` | Sequential code |
90→| `PAR` | Goroutines with `sync.WaitGroup` |
91→| `PROC` with `VAL` params | Functions with value/pointer params |
92→| `:=` assignment | `=` assignment |
93→| Arithmetic: `+`, `-`, `*`, `/`, `\` | `+`, `-`, `*`, `/`, `%` |
94→| Comparison: `=`, `<>`, `<`, `>`, `<=`, `>=` | `==`, `!=`, `<`, `>`, `<=`, `>=` |
95→| Logic: `AND`, `OR`, `NOT` | `&&`, `\|\|`, `!` |
96→
97→### Channels
98→
99→| Occam | Go |
100→|-------|-----|
101→| `CHAN OF INT c:` | `c := make(chan int)` |
102→| `c ! x` (send) | `c <- x` |
103→| `c ? y` (receive) | `y = <-c` |
104→
105→Example:
106→```occam
107→SEQ
108→ CHAN OF INT c:
109→ INT result:
110→ PAR
111→ c ! 42
112→ c ? result
113→ print.int(result)
114→```
115→
116→### Built-in I/O Procedures
117→
118→| Occam | Go |
119→|-------|-----|
120→| `print.int(x)` | `fmt.Println(x)` |
121→| `print.bool(x)` | `fmt.Println(x)` |
122→| `print.string(x)` | `fmt.Println(x)` |
123→| `print.newline()` | `fmt.Println()` |
124→
125→## Not Yet Implemented
126→
127→- `ALT` (alternation) → `select`
128→- Replicators (`PAR i = 0 FOR n`)
129→- Arrays
130→- `WHILE`, `IF` (partial)
<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>
I'll add a new section after "Not Yet Implemented" that explains how channels are mapped from Occam to Go, including the conceptual similarities and any differences or limitations.
## Not Yet Implemented - `ALT` (alternation) → `select` - Replicators (`PAR i = 0 FOR n`) - Arrays - `WHILE`, `IF` (partial)
## Not Yet Implemented - `ALT` (alternation) → `select` - Replicators (`PAR i = 0 FOR n`) - Arrays - `WHILE`, `IF` (partial) ## How Channels are Mapped Both Occam and Go draw from Tony Hoare's Communicating Sequential Processes (CSP) model, making channel communication a natural fit for transpilation. ### Conceptual Mapping In Occam, channels are the primary mechanism for communication between parallel processes. A channel is a synchronous, unbuffered, point-to-point connection. Go channels share these characteristics by default. | Concept | Occam | Go | |---------|-------|-----| | Declaration | `CHAN OF INT c:` | `c := make(chan int)` | | Send (blocks until receiver ready) | `c ! value` | `c <- value` | | Receive (blocks until sender ready) | `c ? variable` | `variable = <-c` | | Synchronisation | Implicit in `!` and `?` | Implicit in `<-` | ### Synchronous Communication Both languages use synchronous (rendezvous) communication by default: ```occam PAR c ! 42 -- blocks until receiver is ready c ? x -- blocks until sender is ready ``` The sender and receiver must both be ready before the communication occurs. This is preserved in the generated Go code, where unbuffered channels have the same semantics. ### Differences and Limitations 1. **Channel direction**: Occam channels are inherently unidirectional. Go channels can be bidirectional but can be restricted using types (`chan<-` for send-only, `<-chan` for receive-only). The transpiler currently generates bidirectional Go channels. 2. **Protocol types**: Occam 2 and later versions support protocol types for structured channel communication. These are not currently supported. 3. **Channel arrays**: Occam allows arrays of channels. Not yet implemented. 4. **ALT construct**: Occam's `ALT` allows a process to wait on multiple channels and proceed with whichever is ready first. This maps to Go's `select` statement but is not yet implemented.
The file /home/david/projects/codeassociates/occam2go/README.md has been updated successfully.
I've added a comprehensive documentation section explaining how channels are mapped from Occam to Go, including:1. The CSP heritage shared by both languages2. A conceptual mapping table3. Explanation of synchronous communication 4. Differences and limitations
Let me verify the README looks good.
The user is asking for suggestions on what to implement next. Let me think about what would be most valuable given what's already implemented:
Implemented: - Basic types (INT, BYTE, BOOL, REAL) - Variable declarations - Assignments and expressions - SEQ blocks - PAR blocks (with goroutines and WaitGroup) - Procedures with VAL and reference parameters - Channels (CHAN, !, ?) - Built-in print procedures - End-to-end testing
Not yet implemented: - ALT → select - Replicators (PAR i = 0 FOR n) - Arrays - WHILE, IF (partial)
Let me think about the priorities:
ALT → select: This is the natural next step after channels. It's the core of Occam's ability to handle multiple channel communications. It maps well to Go's select statement. This would complete the core CSP primitives.
Replicators: PAR i = 0 FOR n and SEQ i = 0 FOR n are very useful for spawning multiple parallel processes or iterating. These are commonly used in Occam programs.
WHILE loops: The parser has partial support. Completing this would allow iterative programs.
IF statements: Similarly has partial support. Occam's IF is a bit different from most languages (guarded commands), so this needs care.
Arrays: Important for real programs but adds complexity with indexing and bounds checking.
String literals: For print.string to be useful, we need string support.
Other considerations: - Error handling improvements - More comprehensive type checking - Better error messages with line numbers - Input procedures (to complement print)
I think the most valuable next steps would be: 1. ALT - completes the CSP model 2. Replicators - very useful for parallel programming 3. WHILE - enables loops
Let me present these options to the user with reasoning.
Given what's implemented, here are my suggestions in order of impact:
select (High value)Completes the core CSP primitives. ALT allows a process to wait on multiple channels and proceed with whichever is ready first.
ALT
c1 ? x
process1()
c2 ? y
process2()
→
select {
case x = <-c1:
process1()
case y = <-c2:
process2()
}
This is powerful but has subtleties (guards, priority ALT, ALT with timeout).
PAR i = 0 FOR n spawns n parallel processes. Very common in Occam for data parallelism.
PAR i = 0 FOR 4
worker(i)
→
for i := 0; i < 4; i++ {
i := i // capture
go func() { worker(i) }()
}
Also useful: SEQ i = 0 FOR n for sequential loops.
Parser has partial support. Straightforward to complete.
WHILE x > 0
x := x - 1
Complement to print.* for interactive programs:
- input.int(x) → fmt.Scan(&x)
Important but adds complexity. Could be deferred.
My recommendation: ALT would be the most impactful, as it completes the CSP model and enables proper concurrent program patterns. Replicators would be a close second for practical utility.
Which direction interests you?