Implement the following plan:
Occam variant protocol tags can contain dots (e.g., bar.data, bar.terminate, bar.blank), as seen in real-world code like kroc/modules/course/examples/bar.occ. The issue requests support for this.
After investigation, the lexer already handles dots as part of identifiers (lexer.go:280 — readIdentifier() loops while isLetter || isDigit || ch == '.'), so bar.data is already a single IDENT token. The codegen already calls goIdent() on tag names, converting dots to underscores. The core functionality likely already works, but has no test coverage.
File: parser/parser_test.go
Add TestVariantProtocolDeclDottedTags — a protocol with dotted tag names (bar.data ; INT, bar.terminate, bar.blank ; INT). Verify each ProtocolVariant.Tag preserves the dotted name (e.g., "bar.data").
File: codegen/codegen_test.go
Add TestVariantProtocolDottedTags — verify generated Go output contains underscore-converted names:
- type _proto_BAR_PROTO_bar_data struct { ... }
- type _proto_BAR_PROTO_bar_terminate struct{}
- etc.
File: codegen/e2e_protocol_test.go
Add TestE2E_VariantProtocolDottedTags — a full transpile→compile→run test:
- Protocol with dotted tags (with and without payloads)
- Variant send with dotted tag + payload (c ! bar.data ; 42)
- Variant send with dotted tag + no payload (c ! bar.blank)
- Variant receive with dotted tags in CASE arms
- Verify expected output
If the tests reveal bugs, fix them in the relevant parser/codegen code.
Document that dotted protocol tags are now supported.
go test ./parser -run TestVariantProtocolDeclDottedTags -v
go test ./codegen -run TestVariantProtocolDottedTags -v
go test ./codegen -run TestE2E_VariantProtocolDottedTags -v
go test ./... # full suite to check for regressions
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/e4fce02a-e445-42c2-8859-d822a1765d80.jsonl
Let me implement the plan for supporting dotted protocol tags. I need to:
Let me start by reading the relevant test files to understand existing patterns.
I'll start by reading the existing test files to understand the patterns, then implement all the tests.
{
"prompt": "Read the following files and return their content so I can understand existing test patterns for variant protocols:\n\n1. Find and read existing variant protocol parser tests in `parser/parser_test.go` - search for \"Variant\" or \"variant\" in test function names\n2. Find and read existing variant protocol codegen tests in `codegen/codegen_test.go` - search for \"Variant\" or \"variant\" \n3. Find and read existing variant protocol e2e tests in `codegen/e2e_protocol_test.go` - read the whole file\n4. Read `TODO.md` to see how features are documented\n\nReturn the relevant test code snippets and the TODO.md content.",
"subagent_type": "Explore"
}[
{
"type": "text",
"text": "Perfect! Now let me provide you with a comprehensive summary of the existing test patterns for variant protocols:\n\n## Existing Variant Protocol Test Patterns\n\n### 1. **Parser Tests** (`parser/parser_test.go` - lines 1629-1685)\n\n**Test: `TestVariantProtocolDecl`**\n\nTests parsing of variant protocol declarations with mixed payloads:\n\n```go\nfunc TestVariantProtocolDecl(t *testing.T) {\n\tinput := `PROTOCOL MSG\n CASE\n text; INT\n number; INT; INT\n quit\n`\n\tl := lexer.New(input)\n\tp := New(l)\n\tprogram := p.ParseProgram()\n\tcheckParserErrors(t, p)\n\n\tif len(program.Statements) != 1 {\n\t\tt.Fatalf(\"expected 1 statement, got %d\", len(program.Statements))\n\t}\n\n\tproto, ok := program.Statements[0].(*ast.ProtocolDecl)\n\tif !ok {\n\t\tt.Fatalf(\"expected ProtocolDecl, got %T\", program.Statements[0])\n\t}\n\n\tif proto.Name != \"MSG\" {\n\t\tt.Errorf(\"expected name 'MSG', got %s\", proto.Name)\n\t}\n\n\tif proto.Kind != \"variant\" {\n\t\tt.Errorf(\"expected kind 'variant', got %s\", proto.Kind)\n\t}\n\n\tif len(proto.Variants) != 3 {\n\t\tt.Fatalf(\"expected 3 variants, got %d\", len(proto.Variants))\n\t}\n\n\t// text; INT\n\tif proto.Variants[0].Tag != \"text\" {\n\t\tt.Errorf(\"expected tag 'text', got %s\", proto.Variants[0].Tag)\n\t}\n\tif len(proto.Variants[0].Types) != 1 || proto.Variants[0].Types[0] != \"INT\" {\n\t\tt.Errorf(\"expected types [INT] for text, got %v\", proto.Variants[0].Types)\n\t}\n\n\t// number; INT; INT\n\tif proto.Variants[1].Tag != \"number\" {\n\t\tt.Errorf(\"expected tag 'number', got %s\", proto.Variants[1].Tag)\n\t}\n\tif len(proto.Variants[1].Types) != 2 {\n\t\tt.Errorf(\"expected 2 types for number, got %d\", len(proto.Variants[1].Types))\n\t}\n\n\t// quit (no payload)\n\tif proto.Variants[2].Tag != \"quit\" {\n\t\tt.Errorf(\"expected tag 'quit', got %s\", proto.Variants[2].Tag)\n\t}\n\tif len(proto.Variants[2].Types) != 0 {\n\t\tt.Errorf(\"expected 0 types for quit, got %d\", len(proto.Variants[2].Types))\n\t}\n}\n```\n\n**Key Patterns:**\n- Checks `proto.Kind == \"variant\"`\n- Validates `proto.Variants` slice has expected count\n- Tests each variant's `Tag` and `Types` slice\n- Tests both payloaded and no-payload variants\n\n---\n\n### 2. **Codegen Unit Tests** (`codegen/codegen_test.go` - lines 504-524)\n\n**Test: `TestVariantProtocolType`**\n\nTests code generation of variant protocol type definitions:\n\n```go\nfunc TestVariantProtocolType(t *testing.T) {\n\tinput := `PROTOCOL MSG\n CASE\n text; INT\n quit\n`\n\toutput := transpile(t, input)\n\n\tif !strings.Contains(output, \"type _proto_MSG interface {\") {\n\t\tt.Errorf(\"expected interface declaration in output, got:\\n%s\", output)\n\t}\n\tif !strings.Contains(output, \"_is_MSG()\") {\n\t\tt.Errorf(\"expected marker method in output, got:\\n%s\", output)\n\t}\n\tif !strings.Contains(output, \"type _proto_MSG_text struct {\") {\n\t\tt.Errorf(\"expected text struct in output, got:\\n%s\", output)\n\t}\n\tif !strings.Contains(output, \"type _proto_MSG_quit struct{}\") {\n\t\tt.Errorf(\"expected quit struct in output, got:\\n%s\", output)\n\t}\n}\n```\n\n**Key Patterns:**\n- Uses `transpile()` helper to transpile occam to Go\n- Uses `strings.Contains()` to check for expected Go output patterns\n- Tests for:\n - Interface declaration: `type _proto_MSG interface {`\n - Marker method: `_is_MSG()`\n - Concrete struct for each variant: `type _proto_MSG_<tag> struct {`\n\n---\n\n### 3. **End-to-End Protocol Tests** (`codegen/e2e_protocol_test.go`)\n\n**Test: `TestE2E_VariantProtocol` (lines 44-68)**\n\nEnd-to-end test that transpiles → compiles → runs variant protocol code:\n\n```go\nfunc TestE2E_VariantProtocol(t *testing.T) {\n\t// Variant protocol: tagged union with CASE receive\n\toccam := `PROTOCOL MSG\n CASE\n data; INT\n quit\n\nSEQ\n CHAN OF MSG c:\n INT result:\n result := 0\n PAR\n c ! data ; 42\n c ? CASE\n data ; result\n print.int(result)\n quit\n print.int(0)\n`\n\toutput := transpileCompileRun(t, occam)\n\texpected := \"42\\n\"\n\tif output != expected {\n\t\tt.Errorf(\"expected %q, got %q\", expected, output)\n\t}\n}\n```\n\n**Test: `TestE2E_VariantProtocolNoPayload` (lines 70-94)**\n\nEnd-to-end test for variant without payload:\n\n```go\nfunc TestE2E_VariantProtocolNoPayload(t *testing.T) {\n\t// Variant protocol with no-payload tag\n\toccam := `PROTOCOL MSG\n CASE\n data; INT\n quit\n\nSEQ\n CHAN OF MSG c:\n INT result:\n result := 0\n PAR\n c ! quit\n c ? CASE\n data ; result\n print.int(result)\n quit\n print.int(99)\n`\n\toutput := transpileCompileRun(t, occam)\n\texpected := \"99\\n\"\n\tif output != expected {\n\t\tt.Errorf(\"expected %q, got %q\", expected, output)\n\t}\n}\n```\n\n**Key Patterns:**\n- Uses `transpileCompileRun()` helper\n- Tests complete program execution flow\n- Verifies output matches expected stdout\n- Tests different code paths (different variants matched in switch)\n\n---\n\n### 4. **TODO.md Documentation** (line 60)\n\nVariant protocols are marked as **Fully Implemented**:\n\n```\n### Protocols\n- **Simple** — `PROTOCOL SIG IS INT` (type alias)\n- **Sequential** — `PROTOCOL PAIR IS INT ; BYTE` (struct)\n- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types)\n```\n\n---\n\n## Summary of Test Patterns\n\n| Layer | Test File | Pattern |\n|-------|-----------|---------|\n| **Parser** | `parser_test.go` | Check AST structure: `proto.Kind`, `proto.Variants[i].Tag`, `proto.Variants[i].Types` |\n| **Codegen Unit** | `codegen_test.go` | Use `transpile()` helper + `strings.Contains()` for Go output patterns |\n| **Codegen E2E** | `e2e_protocol_test.go` | Use `transpileCompileRun()` helper + verify stdout output |\n| **Docs** | `TODO.md` | List as \"Fully Implemented\" |\n\n**Test Files Locations:**\n- `/home/david/projects/code-associates/occam2go/parser/parser_test.go` (parser unit tests)\n- `/home/david/projects/code-associates/occam2go/codegen/codegen_test.go` (codegen unit tests)\n- `/home/david/projects/code-associates/occam2go/codegen/e2e_protocol_test.go` (e2e tests)\n- `/home/david/projects/code-associates/occam2go/TODO.md` (feature documentation)"
},
{
"type": "text",
"text": "agentId: a647ffb3b63483cd2 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 28041\ntool_uses: 10\nduration_ms: 19883</usage>"
}
]Now let me read the exact locations where I need to insert the new tests, and the TODO.md.
{
"file_path": "/home/david/projects/code-associates/occam2go/parser/parser_test.go",
"offset": 1680,
"limit": 20
} 1680→ t.Errorf("expected tag 'quit', got %s", proto.Variants[2].Tag)
1681→ }
1682→ if len(proto.Variants[2].Types) != 0 {
1683→ t.Errorf("expected 0 types for quit, got %d", len(proto.Variants[2].Types))
1684→ }
1685→}
1686→
1687→func TestChanDeclWithProtocol(t *testing.T) {
1688→ input := `PROTOCOL SIGNAL IS INT
1689→CHAN OF SIGNAL c:
1690→`
1691→ l := lexer.New(input)
1692→ p := New(l)
1693→ program := p.ParseProgram()
1694→ checkParserErrors(t, p)
1695→
1696→ if len(program.Statements) != 2 {
1697→ t.Fatalf("expected 2 statements, got %d", len(program.Statements))
1698→ }
1699→{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen_test.go",
"offset": 520,
"limit": 20
} 520→ }
521→ if !strings.Contains(output, "type _proto_MSG_quit struct{}") {
522→ t.Errorf("expected quit struct in output, got:\n%s", output)
523→ }
524→}
525→
526→func TestRecordType(t *testing.T) {
527→ input := `RECORD POINT
528→ INT x:
529→ INT y:
530→`
531→ output := transpile(t, input)
532→
533→ if !strings.Contains(output, "type POINT struct {") {
534→ t.Errorf("expected 'type POINT struct {' in output, got:\n%s", output)
535→ }
536→ if !strings.Contains(output, "x int") {
537→ t.Errorf("expected 'x int' field in output, got:\n%s", output)
538→ }
539→ if !strings.Contains(output, "y int") {{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_protocol_test.go",
"offset": 90,
"limit": 20
} 90→ expected := "99\n"
91→ if output != expected {
92→ t.Errorf("expected %q, got %q", expected, output)
93→ }
94→}
95→
96→func TestE2E_ProtocolWithProc(t *testing.T) {
97→ // Protocol channel passed as PROC parameter
98→ occam := `PROTOCOL PAIR IS INT ; INT
99→
100→PROC sender(CHAN OF PAIR out)
101→ out ! 3 ; 7
102→
103→SEQ
104→ CHAN OF PAIR c:
105→ INT a, b:
106→ PAR
107→ sender(c)
108→ c ? a ; b
109→ print.int(a + b){
"pattern": "Variant.*protocol|dotted|Dotted",
"path": "/home/david/projects/code-associates/occam2go/TODO.md",
"output_mode": "content",
"-n": true
} 1→# occam2go — Implementation Status
2→
3→## Fully Implemented
4→
5→### Core Constructs
6→- **SEQ** — Sequential execution, with replicators (`SEQ i = 0 FOR n`) and optional STEP
7→- **PAR** — Parallel execution via goroutines + sync.WaitGroup, with replicators
8→- **IF** — Multi-branch conditionals, maps to if/else if chains, with replicators; supports multi-statement bodies (declarations scoped before process)
9→- **WHILE** — Loops, maps to Go `for` loops; supports multi-statement bodies
10→- **CASE** — Pattern matching with multiple cases and ELSE branch; supports multi-statement bodies
11→- **ALT** — Channel alternation, maps to Go `select`; supports boolean guards, timer timeouts, multi-statement bodies, and replicators (`ALT i = 0 FOR n` using `reflect.Select`)
12→- **SKIP** — No-op process
13→- **STOP** — Error + deadlock
14→
15→### Data Types & Declarations
16→- **INT, INT16, INT32, INT64, BYTE, BOOL, REAL, REAL32, REAL64** — Scalar types (INT16/32/64 map to int16/32/64, REAL/REAL64 map to float64, REAL32 maps to float32)
17→- **Variable declarations** — `INT x, y, z:`
18→- **Arrays** — `[n]TYPE arr:` with index expressions
19→- **Channels** — `CHAN OF TYPE c:` with send (`!`) and receive (`?`); `CHAN BYTE` shorthand (without `OF`)
20→- **Channel arrays** — `[n]CHAN OF TYPE cs:` with indexed send/receive and `[]CHAN OF TYPE` proc params
21→- **Channel direction** — `CHAN OF INT c?` (receive-only) and `CHAN OF INT c!` (send-only); direction annotations at call sites (`out!`, `in?`) accepted and ignored
22→- **Timers** — `TIMER tim:` with reads and `AFTER` expressions
23→- **Abbreviations** — `VAL INT x IS 1:`, `INT y IS z:`, untyped `VAL x IS expr:` — named constants and aliases
24→- **INITIAL declarations** — `INITIAL INT x IS 42:` — mutable variables with initial values
25→- **Byte literals** — `'A'`, `'0'` with occam escape sequences (`*n`, `*c`, `*t`)
26→- **Hex integer literals** — `#FF`, `#80000000`
27→
28→### Procedures & Functions
29→- **PROC** — Declaration with VAL, reference, CHAN OF, and open array (`[]TYPE`) parameters
30→- **PROC calls** — With automatic `&`/`*` for reference params, pass-through for channels
31→- **FUNCTION (IS form)** — `INT FUNCTION square(VAL INT x) IS x * x`
32→- **FUNCTION (VALOF form)** — Local declarations + VALOF body + RESULT
33→- **Multi-result FUNCTIONs** — `INT, INT FUNCTION f(...)` returning multiple values via `RESULT a, b`
34→- **Nested PROCs/FUNCTIONs** — Local definitions inside a PROC body, compiled as Go closures
35→- **KRoC-style colon terminators** — Optional `:` at end of PROC/FUNCTION body
36→- **INLINE modifier** — `INT INLINE FUNCTION f(...)` — accepted and ignored (optimization hint only)
37→- **Built-in print** — `print.int`, `print.bool`, `print.string`, `print.newline`
38→
39→### Expressions & Operators
40→- **Arithmetic** — `+`, `-`, `*`, `/`, `\` (modulo)
41→- **Comparison** — `=`, `<>`, `<`, `>`, `<=`, `>=`
42→- **Logical** — `AND`, `OR`, `NOT`
43→- **Bitwise** — `/\`, `\/`, `><`, `~`, `<<`, `>>`
44→- **AFTER** — As boolean expression (maps to `>`)
45→- **Parenthesized expressions**
46→- **Array indexing** — `arr[i]`, `arr[expr]`
47→- **String literals** — Double-quoted strings
48→- **Type conversions** — `INT expr`, `INT16 expr`, `INT32 expr`, `INT64 expr`, `BYTE expr`, `BOOL expr`, `REAL32 expr`, `REAL64 expr` (including BOOL↔numeric conversions, and ROUND/TRUNC qualifiers for float↔int conversions)
49→- **Checked arithmetic** — `PLUS`, `MINUS`, `TIMES` — modular (wrapping) operators
50→- **MOSTNEG/MOSTPOS** — Type min/max constants for INT, INT16, INT32, INT64, BYTE, REAL32, REAL64
51→- **SIZE operator** — `SIZE arr`, `SIZE "str"` maps to `len()`
52→- **Array slices** — `[arr FROM n FOR m]` with slice assignment
53→- **Array literals** — `[1, 2, 3]` — inline array/table expressions
54→- **Multi-assignment** — `a, b := f(...)` including indexed targets like `x[0], x[1] := x[1], x[0]`
55→- **Multi-line expression continuation** — Binary operators and `:=` at end of line continue expression on next line
56→
57→### Protocols
58→- **Simple** — `PROTOCOL SIG IS INT` (type alias)
59→- **Sequential** — `PROTOCOL PAIR IS INT ; BYTE` (struct)
60→- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types)
61→
62→### Records
63→- **RECORD** — Struct types with field access via bracket syntax (`p[x]`)
64→
65→### Type Reinterpretation & Intrinsics
66→- **RETYPES** — Bit-level type reinterpretation (`VAL INT X RETYPES X :` for float32→int, `VAL [2]INT X RETYPES X :` for float64→int pair)
67→- **Transputer intrinsics** — `LONGPROD`, `LONGDIV`, `LONGSUM`, `LONGDIFF`, `NORMALISE`, `SHIFTLEFT`, `SHIFTRIGHT` — extended-precision arithmetic as Go helper functions
68→- **CAUSEERROR** — Error-raising primitive, maps to `panic("CAUSEERROR")`
69→
70→### Preprocessor
71→- **`#IF` / `#ELSE` / `#ENDIF`** — Conditional compilation with `TRUE`, `FALSE`, `DEFINED()`, `NOT`, equality
72→- **`#DEFINE`** — Symbol definition
73→- **`#INCLUDE`** — File inclusion with search paths and include guards
74→- **`#COMMENT` / `#PRAGMA` / `#USE`** — Ignored (blank lines)
75→- **Predefined symbols** — `TARGET.BITS.PER.WORD = 64`
76→
77→### Tooling
78→- **gen-module** — Generate `.module` files from KRoC SConscript build files
79→
80→---
81→
82→## Not Yet Implemented
83→
84→### Required for shared_screen module (extends course module)
85→
86→| Feature | Notes | Used in |
87→|---------|-------|---------|
88→| **`DATA TYPE X IS TYPE:`** | Simple type alias (e.g. `DATA TYPE COLOUR IS BYTE:`). | shared_screen.inc |
89→| **`DATA TYPE X RECORD`** | Alternative record syntax (vs current `RECORD X`). | shared_screen.inc |
90→| **Counted array protocol** | `BYTE::[]BYTE` — length-prefixed array in protocols. | shared_screen.inc, shared_screen.occ |
91→| **`RESULT` param qualifier** | `RESULT INT len` on PROC params (output-only, like a write-only reference). | float_io.occ |
92→
93→### Other language features
94→
95→| Feature | Notes |
96→|---------|-------|
97→| **PRI ALT / PRI PAR** | Priority variants of ALT and PAR. |
98→| **PLACED PAR** | Assigning processes to specific hardware. |
99→| **PORT OF** | Hardware port mapping. |
100→| **`VAL []BYTE` abbreviations** | `VAL []BYTE cmap IS "0123456789ABCDEF":` — named string constants. |
101→| **`#PRAGMA DEFINED`** | Compiler hint to suppress definedness warnings. Can be ignored. |
102→{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_protocol_test.go"
} 1→package codegen
2→
3→import "testing"
4→
5→func TestE2E_SimpleProtocol(t *testing.T) {
6→ // Simple protocol: just a named type alias
7→ occam := `PROTOCOL SIGNAL IS INT
8→
9→SEQ
10→ CHAN OF SIGNAL c:
11→ INT result:
12→ PAR
13→ c ! 42
14→ c ? result
15→ print.int(result)
16→`
17→ output := transpileCompileRun(t, occam)
18→ expected := "42\n"
19→ if output != expected {
20→ t.Errorf("expected %q, got %q", expected, output)
21→ }
22→}
23→
24→func TestE2E_SequentialProtocol(t *testing.T) {
25→ // Sequential protocol: send/receive multiple values
26→ occam := `PROTOCOL PAIR IS INT ; INT
27→
28→SEQ
29→ CHAN OF PAIR c:
30→ INT x, y:
31→ PAR
32→ c ! 10 ; 20
33→ c ? x ; y
34→ print.int(x)
35→ print.int(y)
36→`
37→ output := transpileCompileRun(t, occam)
38→ expected := "10\n20\n"
39→ if output != expected {
40→ t.Errorf("expected %q, got %q", expected, output)
41→ }
42→}
43→
44→func TestE2E_VariantProtocol(t *testing.T) {
45→ // Variant protocol: tagged union with CASE receive
46→ occam := `PROTOCOL MSG
47→ CASE
48→ data; INT
49→ quit
50→
51→SEQ
52→ CHAN OF MSG c:
53→ INT result:
54→ result := 0
55→ PAR
56→ c ! data ; 42
57→ c ? CASE
58→ data ; result
59→ print.int(result)
60→ quit
61→ print.int(0)
62→`
63→ output := transpileCompileRun(t, occam)
64→ expected := "42\n"
65→ if output != expected {
66→ t.Errorf("expected %q, got %q", expected, output)
67→ }
68→}
69→
70→func TestE2E_VariantProtocolNoPayload(t *testing.T) {
71→ // Variant protocol with no-payload tag
72→ occam := `PROTOCOL MSG
73→ CASE
74→ data; INT
75→ quit
76→
77→SEQ
78→ CHAN OF MSG c:
79→ INT result:
80→ result := 0
81→ PAR
82→ c ! quit
83→ c ? CASE
84→ data ; result
85→ print.int(result)
86→ quit
87→ print.int(99)
88→`
89→ output := transpileCompileRun(t, occam)
90→ expected := "99\n"
91→ if output != expected {
92→ t.Errorf("expected %q, got %q", expected, output)
93→ }
94→}
95→
96→func TestE2E_ProtocolWithProc(t *testing.T) {
97→ // Protocol channel passed as PROC parameter
98→ occam := `PROTOCOL PAIR IS INT ; INT
99→
100→PROC sender(CHAN OF PAIR out)
101→ out ! 3 ; 7
102→
103→SEQ
104→ CHAN OF PAIR c:
105→ INT a, b:
106→ PAR
107→ sender(c)
108→ c ? a ; b
109→ print.int(a + b)
110→`
111→ output := transpileCompileRun(t, occam)
112→ expected := "10\n"
113→ if output != expected {
114→ t.Errorf("expected %q, got %q", expected, output)
115→ }
116→}
117→Now I have all the context I need. Let me create the three tests and update TODO.md. I'll add them all in parallel.
func TestChanDeclWithProtocol(t *testing.T) {func TestVariantProtocolDeclDottedTags(t *testing.T) {
input := `PROTOCOL BAR.PROTO
CASE
bar.data; INT
bar.terminate
bar.blank; INT
`
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))
}
proto, ok := program.Statements[0].(*ast.ProtocolDecl)
if !ok {
t.Fatalf("expected ProtocolDecl, got %T", program.Statements[0])
}
if proto.Name != "BAR.PROTO" {
t.Errorf("expected name 'BAR.PROTO', got %s", proto.Name)
}
if proto.Kind != "variant" {
t.Errorf("expected kind 'variant', got %s", proto.Kind)
}
if len(proto.Variants) != 3 {
t.Fatalf("expected 3 variants, got %d", len(proto.Variants))
}
// bar.data; INT
if proto.Variants[0].Tag != "bar.data" {
t.Errorf("expected tag 'bar.data', got %s", proto.Variants[0].Tag)
}
if len(proto.Variants[0].Types) != 1 || proto.Variants[0].Types[0] != "INT" {
t.Errorf("expected types [INT] for bar.data, got %v", proto.Variants[0].Types)
}
// bar.terminate (no payload)
if proto.Variants[1].Tag != "bar.terminate" {
t.Errorf("expected tag 'bar.terminate', got %s", proto.Variants[1].Tag)
}
if len(proto.Variants[1].Types) != 0 {
t.Errorf("expected 0 types for bar.terminate, got %d", len(proto.Variants[1].Types))
}
// bar.blank; INT
if proto.Variants[2].Tag != "bar.blank" {
t.Errorf("expected tag 'bar.blank', got %s", proto.Variants[2].Tag)
}
if len(proto.Variants[2].Types) != 1 || proto.Variants[2].Types[0] != "INT" {
t.Errorf("expected types [INT] for bar.blank, got %v", proto.Variants[2].Types)
}
}
func TestChanDeclWithProtocol(t *testing.T) {The file /home/david/projects/code-associates/occam2go/parser/parser_test.go has been updated successfully.
func TestRecordType(t *testing.T) {func TestVariantProtocolDottedTags(t *testing.T) {
input := `PROTOCOL BAR.PROTO
CASE
bar.data; INT
bar.terminate
bar.blank; INT
`
output := transpile(t, input)
if !strings.Contains(output, "type _proto_BAR_PROTO interface {") {
t.Errorf("expected interface declaration in output, got:\n%s", output)
}
if !strings.Contains(output, "type _proto_BAR_PROTO_bar_data struct {") {
t.Errorf("expected bar_data struct in output, got:\n%s", output)
}
if !strings.Contains(output, "type _proto_BAR_PROTO_bar_terminate struct{}") {
t.Errorf("expected bar_terminate struct in output, got:\n%s", output)
}
if !strings.Contains(output, "type _proto_BAR_PROTO_bar_blank struct {") {
t.Errorf("expected bar_blank struct in output, got:\n%s", output)
}
}
func TestRecordType(t *testing.T) {The file /home/david/projects/code-associates/occam2go/codegen/codegen_test.go has been updated successfully.
func TestE2E_ProtocolWithProc(t *testing.T) {func TestE2E_VariantProtocolDottedTags(t *testing.T) {
// Variant protocol with dotted tag names (e.g., bar.data)
occam := `PROTOCOL BAR.PROTO
CASE
bar.data; INT
bar.terminate
bar.blank; INT
SEQ
CHAN OF BAR.PROTO c:
INT result:
result := 0
PAR
SEQ
c ! bar.data ; 42
c ! bar.terminate
SEQ
c ? CASE
bar.data ; result
print.int(result)
bar.terminate
print.int(0)
bar.blank ; result
print.int(result)
c ? CASE
bar.data ; result
print.int(result)
bar.terminate
print.int(99)
bar.blank ; result
print.int(result)
`
output := transpileCompileRun(t, occam)
expected := "42\n99\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ProtocolWithProc(t *testing.T) {The file /home/david/projects/code-associates/occam2go/codegen/e2e_protocol_test.go has been updated successfully.
go test ./parser -run TestVariantProtocolDeclDottedTags -v 2>&1
=== RUN TestVariantProtocolDeclDottedTags --- PASS: TestVariantProtocolDeclDottedTags (0.00s) PASS ok github.com/codeassociates/occam2go/parser 0.001s
go test ./codegen -run TestVariantProtocolDottedTags -v 2>&1
=== RUN TestVariantProtocolDottedTags --- PASS: TestVariantProtocolDottedTags (0.00s) PASS ok github.com/codeassociates/occam2go/codegen 0.001s
go test ./codegen -run TestE2E_VariantProtocolDottedTags -v 2>&1
Exit code 1
=== RUN TestE2E_VariantProtocolDottedTags
e2e_protocol_test.go:128: compilation failed: exit status 1
Output: # command-line-arguments
/tmp/occam2go-test-828436456/main.go:26:17: undefined: _proto_BAR
Go code:
package main
import (
"fmt"
"sync"
)
type _proto_BAR_PROTO interface {
_is_BAR_PROTO()
}
type _proto_BAR_PROTO_bar_data struct {
_0 int
}
func (_proto_BAR_PROTO_bar_data) _is_BAR_PROTO() {}
type _proto_BAR_PROTO_bar_terminate struct{}
func (_proto_BAR_PROTO_bar_terminate) _is_BAR_PROTO() {}
type _proto_BAR_PROTO_bar_blank struct {
_0 int
}
func (_proto_BAR_PROTO_bar_blank) _is_BAR_PROTO() {}
func main() {
c := make(chan _proto_BAR.PROTO)
var result int
_ = result
result = 0
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
c <- _proto_BAR_PROTO_bar_data{42}
c <- _proto_BAR_PROTO_bar_terminate{}
}()
go func() {
defer wg.Done()
switch _v := (<-c).(type) {
case _proto_BAR_PROTO_bar_data:
result = _v._0
fmt.Println(result)
case _proto_BAR_PROTO_bar_terminate:
fmt.Println(0)
case _proto_BAR_PROTO_bar_blank:
result = _v._0
fmt.Println(result)
}
switch _v := (<-c).(type) {
case _proto_BAR_PROTO_bar_data:
result = _v._0
fmt.Println(result)
case _proto_BAR_PROTO_bar_terminate:
fmt.Println(99)
case _proto_BAR_PROTO_bar_blank:
result = _v._0
fmt.Println(result)
}
}()
wg.Wait()
}
--- FAIL: TestE2E_VariantProtocolDottedTags (0.03s)
FAIL
FAIL github.com/codeassociates/occam2go/codegen 0.034s
FAIL
=== RUN TestE2E_VariantProtocolDottedTags
e2e_protocol_test.go:128: compilation failed: exit status 1
Output: # command-line-arguments
/tmp/occam2go-test-828436456/main.go:26:17: undefined: _proto_BAR
Go code:
package main
import (
"fmt"
"sync"
)
type _proto_BAR_PROTO interface {
_is_BAR_PROTO()
}
type _proto_BAR_PROTO_bar_data struct {
_0 int
}
func (_proto_BAR_PROTO_bar_data) _is_BAR_PROTO() {}
type _proto_BAR_PROTO_bar_terminate struct{}
func (_proto_BAR_PROTO_bar_terminate) _is_BAR_PROTO() {}
type _proto_BAR_PROTO_bar_blank struct {
_0 int
}
func (_proto_BAR_PROTO_bar_blank) _is_BAR_PROTO() {}
func main() {
c := make(chan _proto_BAR.PROTO)
var result int
_ = result
result = 0
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
c <- _proto_BAR_PROTO_bar_data{42}
c <- _proto_BAR_PROTO_bar_terminate{}
}()
go func() {
defer wg.Done()
switch _v := (<-c).(type) {
case _proto_BAR_PROTO_bar_data:
result = _v._0
fmt.Println(result)
case _proto_BAR_PROTO_bar_terminate:
fmt.Println(0)
case _proto_BAR_PROTO_bar_blank:
result = _v._0
fmt.Println(result)
}
switch _v := (<-c).(type) {
case _proto_BAR_PROTO_bar_data:
result = _v._0
fmt.Println(result)
case _proto_BAR_PROTO_bar_terminate:
fmt.Println(99)
case _proto_BAR_PROTO_bar_blank:
result = _v._0
fmt.Println(result)
}
}()
wg.Wait()
}
--- FAIL: TestE2E_VariantProtocolDottedTags (0.03s)
FAIL
FAIL github.com/codeassociates/occam2go/codegen 0.034s
FAILThe e2e test reveals a bug: CHAN OF BAR.PROTO c: generates c := make(chan _proto_BAR.PROTO) — the dot is not being converted to underscore in the channel type reference. The protocol type definitions themselves are fine (they use _proto_BAR_PROTO), but the channel declaration doesn't call goIdent() on the protocol name.
Let me find where channel declarations with protocols are generated.
The e2e test found a bug: CHAN OF BAR.PROTO c: generates make(chan _proto_BAR.PROTO) — the dot isn't converted to underscore in the channel type. Let me find where channel types reference protocol names in codegen.
{
"pattern": "_proto_.*ChanType|chanType.*proto|ProtocolName|protocolName|CHAN OF.*proto",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}{
"pattern": "\"_proto_\"",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1555,
"limit": 20
} 1555→ case "REAL":
1556→ return "float64"
1557→ case "REAL32":
1558→ return "float32"
1559→ case "REAL64":
1560→ return "float64"
1561→ default:
1562→ // Check if it's a protocol name
1563→ if _, ok := g.protocolDefs[occamType]; ok {
1564→ return "_proto_" + occamType
1565→ }
1566→ // Check if it's a record type name
1567→ if _, ok := g.recordDefs[occamType]; ok {
1568→ return occamType
1569→ }
1570→ return occamType // pass through unknown types
1571→ }
1572→}
1573→
1574→func isOccamIntType(t string) bool {Found it. Line 1564: "_proto_" + occamType doesn't apply goIdent() to convert dots to underscores. Let me fix this.
if _, ok := g.protocolDefs[occamType]; ok {
return "_proto_" + occamType
} if _, ok := g.protocolDefs[occamType]; ok {
return "_proto_" + goIdent(occamType)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
Now let me also check if there are other places where protocol names are used without goIdent() — there could be similar issues in protocol type definitions, variant send/receive, etc.
{
"pattern": "\"_proto_\" \\+",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}Only one place uses the _proto_ prefix directly. Let me also check for the other pattern where protocol/variant names are assembled.
{
"pattern": "proto_.*\\+|_proto_",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}1172: // Variant send with explicit tag: c <- _proto_NAME_tag{values...}
1173: g.write(fmt.Sprintf("_proto_%s_%s{", gProtoName, goIdent(send.VariantTag)))
1184: g.write(fmt.Sprintf("_proto_%s_%s{}", gProtoName, goIdent(ident.Value)))
1189: // Sequential send: c <- _proto_NAME{val1, val2, ...}
1190: g.write(fmt.Sprintf("_proto_%s{", gProtoName))
1251: g.writeLine(fmt.Sprintf("type _proto_%s = %s", gName, goType))
1254: g.writeLine(fmt.Sprintf("type _proto_%s struct {", gName))
1265: g.writeLine(fmt.Sprintf("type _proto_%s interface {", gName))
1276: g.writeLine(fmt.Sprintf("type _proto_%s_%s struct{}", gName, gTag))
1278: g.writeLine(fmt.Sprintf("type _proto_%s_%s struct {", gName, gTag))
1287: g.writeLine(fmt.Sprintf("func (_proto_%s_%s) _is_%s() {}", gName, gTag, gName))
1311: g.writeLine(fmt.Sprintf("case _proto_%s_%s:", gProtoName, goIdent(vc.Tag)))
1564: return "_proto_" + goIdent(occamType){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1155,
"limit": 45
} 1155→}
1156→
1157→func (g *Generator) generateSend(send *ast.Send) {
1158→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1159→ g.write(goIdent(send.Channel))
1160→ if send.ChannelIndex != nil {
1161→ g.write("[")
1162→ g.generateExpression(send.ChannelIndex)
1163→ g.write("]")
1164→ }
1165→ g.write(" <- ")
1166→
1167→ protoName := g.chanProtocols[send.Channel]
1168→ proto := g.protocolDefs[protoName]
1169→ gProtoName := goIdent(protoName)
1170→
1171→ if send.VariantTag != "" && proto != nil && proto.Kind == "variant" {
1172→ // Variant send with explicit tag: c <- _proto_NAME_tag{values...}
1173→ g.write(fmt.Sprintf("_proto_%s_%s{", gProtoName, goIdent(send.VariantTag)))
1174→ for i, val := range send.Values {
1175→ if i > 0 {
1176→ g.write(", ")
1177→ }
1178→ g.generateExpression(val)
1179→ }
1180→ g.write("}")
1181→ } else if proto != nil && proto.Kind == "variant" && send.Value != nil && len(send.Values) == 0 {
1182→ // Check if the send value is a bare identifier matching a variant tag
1183→ if ident, ok := send.Value.(*ast.Identifier); ok && g.isVariantTag(protoName, ident.Value) {
1184→ g.write(fmt.Sprintf("_proto_%s_%s{}", gProtoName, goIdent(ident.Value)))
1185→ } else {
1186→ g.generateExpression(send.Value)
1187→ }
1188→ } else if len(send.Values) > 0 && proto != nil && proto.Kind == "sequential" {
1189→ // Sequential send: c <- _proto_NAME{val1, val2, ...}
1190→ g.write(fmt.Sprintf("_proto_%s{", gProtoName))
1191→ g.generateExpression(send.Value)
1192→ for _, val := range send.Values {
1193→ g.write(", ")
1194→ g.generateExpression(val)
1195→ }
1196→ g.write("}")
1197→ } else {
1198→ // Simple send
1199→ g.generateExpression(send.Value){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1240,
"limit": 55
} 1240→ varRef = "*" + varRef
1241→ }
1242→ g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef))
1243→ }
1244→}
1245→
1246→func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) {
1247→ gName := goIdent(proto.Name)
1248→ switch proto.Kind {
1249→ case "simple":
1250→ goType := g.occamTypeToGoBase(proto.Types[0])
1251→ g.writeLine(fmt.Sprintf("type _proto_%s = %s", gName, goType))
1252→ g.writeLine("")
1253→ case "sequential":
1254→ g.writeLine(fmt.Sprintf("type _proto_%s struct {", gName))
1255→ g.indent++
1256→ for i, t := range proto.Types {
1257→ goType := g.occamTypeToGoBase(t)
1258→ g.writeLine(fmt.Sprintf("_%d %s", i, goType))
1259→ }
1260→ g.indent--
1261→ g.writeLine("}")
1262→ g.writeLine("")
1263→ case "variant":
1264→ // Interface type
1265→ g.writeLine(fmt.Sprintf("type _proto_%s interface {", gName))
1266→ g.indent++
1267→ g.writeLine(fmt.Sprintf("_is_%s()", gName))
1268→ g.indent--
1269→ g.writeLine("}")
1270→ g.writeLine("")
1271→ // Concrete types for each variant
1272→ for _, v := range proto.Variants {
1273→ gTag := goIdent(v.Tag)
1274→ if len(v.Types) == 0 {
1275→ // No-payload variant: empty struct
1276→ g.writeLine(fmt.Sprintf("type _proto_%s_%s struct{}", gName, gTag))
1277→ } else {
1278→ g.writeLine(fmt.Sprintf("type _proto_%s_%s struct {", gName, gTag))
1279→ g.indent++
1280→ for i, t := range v.Types {
1281→ goType := g.occamTypeToGoBase(t)
1282→ g.writeLine(fmt.Sprintf("_%d %s", i, goType))
1283→ }
1284→ g.indent--
1285→ g.writeLine("}")
1286→ }
1287→ g.writeLine(fmt.Sprintf("func (_proto_%s_%s) _is_%s() {}", gName, gTag, gName))
1288→ g.writeLine("")
1289→ }
1290→ }
1291→}
1292→
1293→func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) {
1294→ protoName := g.chanProtocols[vr.Channel]{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1295,
"limit": 25
} 1295→ gProtoName := goIdent(protoName)
1296→ chanRef := goIdent(vr.Channel)
1297→ if vr.ChannelIndex != nil {
1298→ var buf strings.Builder
1299→ buf.WriteString(goIdent(vr.Channel))
1300→ buf.WriteString("[")
1301→ oldBuilder := g.builder
1302→ g.builder = strings.Builder{}
1303→ g.generateExpression(vr.ChannelIndex)
1304→ buf.WriteString(g.builder.String())
1305→ g.builder = oldBuilder
1306→ buf.WriteString("]")
1307→ chanRef = buf.String()
1308→ }
1309→ g.writeLine(fmt.Sprintf("switch _v := (<-%s).(type) {", chanRef))
1310→ for _, vc := range vr.Cases {
1311→ g.writeLine(fmt.Sprintf("case _proto_%s_%s:", gProtoName, goIdent(vc.Tag)))
1312→ g.indent++
1313→ for i, v := range vc.Variables {
1314→ g.writeLine(fmt.Sprintf("%s = _v._%d", goIdent(v), i))
1315→ }
1316→ if vc.Body != nil {
1317→ g.generateStatement(vc.Body)
1318→ }
1319→ g.indent--All the protocol codegen already uses goIdent() on protocol names and tags. The only issue was in occamTypeToGoBase() at line 1564, which I've already fixed. Let me re-run the e2e test.
go test ./codegen -run TestE2E_VariantProtocolDottedTags -v 2>&1
=== RUN TestE2E_VariantProtocolDottedTags --- PASS: TestE2E_VariantProtocolDottedTags (0.09s) PASS ok github.com/codeassociates/occam2go/codegen 0.088s
All three tests pass. Now let me run the full test suite to check for regressions, and update TODO.md.
- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types)
- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types), including dotted tag names (`bar.data`, `bar.terminate`)
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 17.829s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser 0.002s ok github.com/codeassociates/occam2go/preproc (cached)
The file /home/david/projects/code-associates/occam2go/TODO.md has been updated successfully.
All tests pass with no regressions. Let me also update CLAUDE.md to document the dotted tag support.
{
"pattern": "variant protocol|Variant protocol|variant.*tag",
"path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"output_mode": "content",
"-n": true
}The CLAUDE.md "What's Implemented" section mentions protocols but doesn't specifically list variant protocol details. The existing coverage in the mapping table is sufficient. The TODO.md update captures the new feature.
Here's a summary of all changes:
Bug fix:
- codegen/codegen.go:1564 — occamTypeToGoBase() now applies goIdent() to protocol names, converting dots to underscores. Previously CHAN OF BAR.PROTO would generate chan _proto_BAR.PROTO (invalid Go) instead of chan _proto_BAR_PROTO.
Tests added:
- parser/parser_test.go — TestVariantProtocolDeclDottedTags: verifies parser preserves dotted tag names (bar.data, bar.terminate, bar.blank) in the AST
- codegen/codegen_test.go — TestVariantProtocolDottedTags: verifies generated Go type names use underscores (_proto_BAR_PROTO_bar_data, etc.)
- codegen/e2e_protocol_test.go — TestE2E_VariantProtocolDottedTags: full transpile→compile→run test with dotted tags (with and without payloads), variant send, and variant CASE receive
Documentation:
- TODO.md — Updated variant protocol entry to note dotted tag name support
On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: TODO.md modified: codegen/codegen.go modified: codegen/codegen_test.go modified: codegen/e2e_protocol_test.go modified: parser/parser_test.go no changes added to commit (use "git add" and/or "git commit -a")
diff --git a/TODO.md b/TODO.md
index a6de63c..dbfdfe1 100644
--- a/TODO.md
+++ b/TODO.md
@@ -57,7 +57,7 @@
### Protocols
- **Simple** — `PROTOCOL SIG IS INT` (type alias)
- **Sequential** — `PROTOCOL PAIR IS INT ; BYTE` (struct)
-- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types)
+- **Variant** — `PROTOCOL MSG CASE tag; TYPE ...` (interface + concrete types), including dotted tag names (`bar.data`, `bar.terminate`)
### Records
- **RECORD** — Struct types with field access via bracket syntax (`p[x]`)
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 21b3655..dec7efd 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -1561,7 +1561,7 @@ func (g *Generator) occamTypeToGo(occamType string) string {
default:
// Check if it's a protocol name
if _, ok := g.protocolDefs[occamType]; ok {
- return "_proto_" + occamType
+ return "_proto_" + goIdent(occamType)
}
// Check if it's a record type name
if _, ok := g.recordDefs[occamType]; ok {
diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go
index c6cda3d..ccecd68 100644
--- a/codegen/codegen_test.go
+++ b/codegen/codegen_test.go
@@ -523,6 +523,29 @@ func TestVariantProtocolType(t *testing.T) {
}
}
+func TestVariantProtocolDottedTags(t *testing.T) {
+ input := `PROTOCOL BAR.PROTO
+ CASE
+ bar.data; INT
+ bar.terminate
+ bar.blank; INT
+`
+ output := transpile(t, input)
+
+ if !strings.Contains(output, "type _proto_BAR_PROTO interface {") {
+ t.Errorf("expected interface declaration in output, got:\n%s", output)
+ }
+ if !strings.Contains(output, "type _proto_BAR_PROTO_bar_data struct {") {
+ t.Errorf("expected bar_data struct in output, got:\n%s", output)
+ }
+ if !strings.Contains(output, "type _proto_BAR_PROTO_bar_terminate struct{}") {
+ t.Errorf("expected bar_terminate struct in output, got:\n%s", output)
+ }
+ if !strings.Contains(output, "type _proto_BAR_PROTO_bar_blank struct {") {
+ t.Errorf("expected bar_blank struct in output, got:\n%s", output)
+ }
+}
+
func TestRecordType(t *testing.T) {
input := `RECORD POINT
INT x:
diff --git a/codegen/e2e_protocol_test.go b/codegen/e2e_protocol_test.go
index a5e9afb..8b51339 100644
--- a/codegen/e2e_protocol_test.go
+++ b/codegen/e2e_protocol_test.go
@@ -93,6 +93,45 @@ SEQ
}
}
+func TestE2E_VariantProtocolDottedTags(t *testing.T) {
+ // Variant protocol with dotted tag names (e.g., bar.data)
+ occam := `PROTOCOL BAR.PROTO
+ CASE
+ bar.data; INT
+ bar.terminate
+ bar.blank; INT
+
+SEQ
+ CHAN OF BAR.PROTO c:
+ INT result:
+ result := 0
+ PAR
+ SEQ
+ c ! bar.data ; 42
+ c ! bar.terminate
+ SEQ
+ c ? CASE
+ bar.data ; result
+ print.int(result)
+ bar.terminate
+ print.int(0)
+ bar.blank ; result
+ print.int(result)
+ c ? CASE
+ bar.data ; result
+ print.int(result)
+ bar.terminate
+ print.int(99)
+ bar.blank ; result
+ print.int(result)
+`
+ output := transpileCompileRun(t, occam)
+ expected := "42\n99\n"
+ if output != expected {
+ t.Errorf("expected %q, got %q", expected, output)
+ }
+}
+
func TestE2E_ProtocolWithProc(t *testing.T) {
// Protocol channel passed as PROC parameter
occam := `PROTOCOL PAIR IS INT ; INT
diff --git a/parser/parser_test.go b/parser/parser_test.go
index f8ce1fc..b154094 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -1684,6 +1684,64 @@ func TestVariantProtocolDecl(t *testing.T) {
}
}
+func TestVariantProtocolDeclDottedTags(t *testing.T) {
+ input := `PROTOCOL BAR.PROTO
+ CASE
+ bar.data; INT
+ bar.terminate
+ bar.blank; INT
+`
+ 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))
+ }
+
+ proto, ok := program.Statements[0].(*ast.ProtocolDecl)
+ if !ok {
+ t.Fatalf("expected ProtocolDecl, got %T", program.Statements[0])
+ }
+
+ if proto.Name != "BAR.PROTO" {
+ t.Errorf("expected name 'BAR.PROTO', got %s", proto.Name)
+ }
+
+ if proto.Kind != "variant" {
+ t.Errorf("expected kind 'variant', got %s", proto.Kind)
+ }
+
+ if len(proto.Variants) != 3 {
+ t.Fatalf("expected 3 variants, got %d", len(proto.Variants))
+ }
+
+ // bar.data; INT
+ if proto.Variants[0].Tag != "bar.data" {
+ t.Errorf("expected tag 'bar.data', got %s", proto.Variants[0].Tag)
+ }
+ if len(proto.Variants[0].Types) != 1 || proto.Variants[0].Types[0] != "INT" {
+ t.Errorf("expected types [INT] for bar.data, got %v", proto.Variants[0].Types)
+ }
+
+ // bar.terminate (no payload)
+ if proto.Variants[1].Tag != "bar.terminate" {
+ t.Errorf("expected tag 'bar.terminate', got %s", proto.Variants[1].Tag)
+ }
+ if len(proto.Variants[1].Types) != 0 {
+ t.Errorf("expected 0 types for bar.terminate, got %d", len(proto.Variants[1].Types))
+ }
+
+ // bar.blank; INT
+ if proto.Variants[2].Tag != "bar.blank" {
+ t.Errorf("expected tag 'bar.blank', got %s", proto.Variants[2].Tag)
+ }
+ if len(proto.Variants[2].Types) != 1 || proto.Variants[2].Types[0] != "INT" {
+ t.Errorf("expected types [INT] for bar.blank, got %v", proto.Variants[2].Types)
+ }
+}
+
func TestChanDeclWithProtocol(t *testing.T) {
input := `PROTOCOL SIGNAL IS INT
CHAN OF SIGNAL c:6faf05d Merge pull request #59 from codeassociates/fix/chan-array-dir-params 235652f Drop direction from channel array params to fix Go slice covariance issue 5a513cd Merge pull request #58 from codeassociates/fix/scope-boolvars-per-proc 347f176 Scope boolVars per proc/function body to prevent cross-proc leakage 36a462d Merge pull request #55 from codeassociates/fix/typed-abbreviation-type-loss
git add codegen/codegen.go codegen/codegen_test.go codegen/e2e_protocol_test.go parser/parser_test.go TODO.md && git commit -m "$(cat <<'EOF' Support dotted protocol tags (e.g., bar.data, bar.terminate) Apply goIdent() to protocol names in occamTypeToGo() so dotted names like BAR.PROTO get converted to BAR_PROTO in channel type references. The protocol type definitions and variant send/receive already handled this correctly, but channel declarations (CHAN OF BAR.PROTO) did not. Adds parser, codegen, and e2e tests for dotted variant protocol tags. Closes #68 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
5 files changed, 122 insertions(+), 2 deletions(-)
remote: remote: Create a pull request for 'fix/dotted-protocol-tags' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/fix/dotted-protocol-tags remote: To associate-1.github.com:codeassociates/occam2go.git * [new branch] fix/dotted-protocol-tags -> fix/dotted-protocol-tags branch 'fix/dotted-protocol-tags' set up to track 'origin/fix/dotted-protocol-tags'.
gh pr create --title "Support dotted protocol tags" --body "$(cat <<'EOF' ## Summary - Fix `occamTypeToGo()` to apply `goIdent()` on protocol names, converting dots to underscores in channel type references (e.g., `CHAN OF BAR.PROTO` → `chan _proto_BAR_PROTO` instead of broken `chan _proto_BAR.PROTO`) - Add parser, codegen unit, and e2e tests for variant protocols with dotted tag names (`bar.data`, `bar.terminate`, `bar.blank`) - Update TODO.md to document dotted tag name support Closes #68 ## Test plan - [x] `go test ./parser -run TestVariantProtocolDeclDottedTags` — parser preserves dotted tag names in AST - [x] `go test ./codegen -run TestVariantProtocolDottedTags` — codegen converts dots to underscores in type names - [x] `go test ./codegen -run TestE2E_VariantProtocolDottedTags` — full transpile→compile→run with dotted tags (send + CASE receive, with and without payloads) - [x] `go test ./...` — no regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"
Switched to branch 'main' Your branch is up to date with 'origin/main'. From associate-1.github.com:codeassociates/occam2go 6faf05d..25ad300 main -> origin/main Updating 6faf05d..25ad300 Fast-forward TODO.md | 2 +- codegen/codegen.go | 2 +- codegen/codegen_test.go | 23 ++++++++++++++++++ codegen/e2e_protocol_test.go | 39 +++++++++++++++++++++++++++++ parser/parser_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-)