diff --git a/CLAUDE.md b/CLAUDE.md index d1a5ae8..528a3ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,8 +69,9 @@ Six packages, one pipeline: | `WHILE cond` | `for cond` | | `CASE x` | `switch x` | | `STOP` | `fmt.Fprintln(os.Stderr, ...)` + `select {}` | -| `ALT` | `select` | +| `ALT` / `PRI ALT` | `select` | | `ALT i = 0 FOR n` | `reflect.Select` with runtime case slice | +| `PRI PAR` | goroutines + `sync.WaitGroup` (same as `PAR`) | | `CHAN OF INT c:` | `c := make(chan int)` | | `c ! expr` | `c <- expr` | | `c ? x` | `x = <-c` | @@ -190,7 +191,7 @@ Typical workflow for a new language construct: ## What's Implemented -Preprocessor (`#IF`/`#ELSE`/`#ENDIF`/`#DEFINE`/`#INCLUDE` with search paths, include guards, include-once deduplication, `#COMMENT`/`#PRAGMA`/`#USE` ignored), module file generation from SConscript (`gen-module` subcommand), SEQ, PAR, IF, WHILE, CASE, ALT (with guards, timer timeouts, multi-statement bodies with scoped declarations, and replicators using `reflect.Select`), SKIP, STOP, variable/array/channel/timer declarations, abbreviations (`VAL INT x IS 42:`, `INT y IS z:`, `VAL []BYTE s IS "hi":`, untyped `VAL x IS expr:`), assignments (simple and indexed), channel send/receive, channel arrays (`[n]CHAN OF TYPE` with indexed send/receive, `[]CHAN OF TYPE` proc params, and multi-dimensional `[n][m]CHAN`/`[n][m]TYPE`/`[][]CHAN`/`[][]TYPE`), PROC (with VAL, RESULT, reference, CHAN, []CHAN, open array `[]TYPE`, fixed-size array `[n]TYPE`, and shared-type params), channel direction restrictions (`CHAN OF INT c?` → `<-chan int`, `CHAN OF INT c!` → `chan<- int`, call-site annotations `out!`/`in?` accepted), multi-line parameter lists and expressions (lexer suppresses INDENT/DEDENT/NEWLINE inside parens/brackets and after continuation operators), FUNCTION (IS and VALOF forms with multi-statement bodies, including multi-result `INT, INT FUNCTION` with `RESULT a, b`), multi-assignment (`a, b := func(...)` including indexed targets like `x[0], x[1] := x[1], x[0]`), KRoC-style colon terminators on PROC/FUNCTION (optional), INLINE function modifier (accepted and ignored), replicators on SEQ/PAR/IF/ALT (with optional STEP), arithmetic/comparison/logical/AFTER/bitwise operators, type conversions (`INT expr`, `INT16 expr`, `INT32 expr`, `INT64 expr`, `BYTE expr`, `BOOL expr`, `REAL32 expr`, `REAL64 expr`, including BOOL↔numeric via `_boolToInt` helper and `!= 0` comparison, and ROUND/TRUNC qualifiers for float↔int conversions), INT16/INT32/INT64 types, REAL32/REAL64 types, hex integer literals (`#FF`, `#80000000`), string literals, byte literals (`'A'`, `'*n'` with occam escape sequences), built-in print procedures, protocols (simple, sequential, and variant), record types (with field access via bracket syntax), SIZE operator, array slices (`[arr FROM n FOR m]` and shorthand `[arr FOR m]` with slice assignment), array literals (`[1, 2, 3]`), nested PROCs/FUNCTIONs (local definitions as Go closures), MOSTNEG/MOSTPOS (type min/max constants for INT, INT16, INT32, INT64, BYTE, REAL32, REAL64), INITIAL declarations (`INITIAL INT x IS 42:` — mutable variable with initial value), checked (modular) arithmetic (`PLUS`, `MINUS`, `TIMES` — wrapping operators), RETYPES (bit-level type reinterpretation: `VAL INT X RETYPES X :` for float32→int, `VAL [2]INT X RETYPES X :` for float64→int pair), transputer intrinsics (LONGPROD, LONGDIV, LONGSUM, LONGDIFF, NORMALISE, SHIFTRIGHT, SHIFTLEFT — implemented as Go helper functions), CAUSEERROR (maps to `panic("CAUSEERROR")`). +Preprocessor (`#IF`/`#ELSE`/`#ENDIF`/`#DEFINE`/`#INCLUDE` with search paths, include guards, include-once deduplication, `#COMMENT`/`#PRAGMA`/`#USE` ignored), module file generation from SConscript (`gen-module` subcommand), SEQ, PAR, PRI PAR, IF, WHILE, CASE, ALT, PRI ALT (with guards, timer timeouts, multi-statement bodies with scoped declarations, and replicators using `reflect.Select`), SKIP, STOP, variable/array/channel/timer declarations, abbreviations (`VAL INT x IS 42:`, `INT y IS z:`, `VAL []BYTE s IS "hi":`, untyped `VAL x IS expr:`), assignments (simple and indexed), channel send/receive, channel arrays (`[n]CHAN OF TYPE` with indexed send/receive, `[]CHAN OF TYPE` proc params, and multi-dimensional `[n][m]CHAN`/`[n][m]TYPE`/`[][]CHAN`/`[][]TYPE`), PROC (with VAL, RESULT, reference, CHAN, []CHAN, open array `[]TYPE`, fixed-size array `[n]TYPE`, and shared-type params), channel direction restrictions (`CHAN OF INT c?` → `<-chan int`, `CHAN OF INT c!` → `chan<- int`, call-site annotations `out!`/`in?` accepted), multi-line parameter lists and expressions (lexer suppresses INDENT/DEDENT/NEWLINE inside parens/brackets and after continuation operators), FUNCTION (IS and VALOF forms with multi-statement bodies, including multi-result `INT, INT FUNCTION` with `RESULT a, b`), multi-assignment (`a, b := func(...)` including indexed targets like `x[0], x[1] := x[1], x[0]`), KRoC-style colon terminators on PROC/FUNCTION (optional), INLINE function modifier (accepted and ignored), replicators on SEQ/PAR/IF/ALT (with optional STEP), arithmetic/comparison/logical/AFTER/bitwise operators, type conversions (`INT expr`, `INT16 expr`, `INT32 expr`, `INT64 expr`, `BYTE expr`, `BOOL expr`, `REAL32 expr`, `REAL64 expr`, including BOOL↔numeric via `_boolToInt` helper and `!= 0` comparison, and ROUND/TRUNC qualifiers for float↔int conversions), INT16/INT32/INT64 types, REAL32/REAL64 types, hex integer literals (`#FF`, `#80000000`), string literals, byte literals (`'A'`, `'*n'` with occam escape sequences), built-in print procedures, protocols (simple, sequential, and variant), record types (with field access via bracket syntax), SIZE operator, array slices (`[arr FROM n FOR m]` and shorthand `[arr FOR m]` with slice assignment), array literals (`[1, 2, 3]`), nested PROCs/FUNCTIONs (local definitions as Go closures), MOSTNEG/MOSTPOS (type min/max constants for INT, INT16, INT32, INT64, BYTE, REAL32, REAL64), INITIAL declarations (`INITIAL INT x IS 42:` — mutable variable with initial value), checked (modular) arithmetic (`PLUS`, `MINUS`, `TIMES` — wrapping operators), RETYPES (bit-level type reinterpretation: `VAL INT X RETYPES X :` for float32→int, `VAL [2]INT X RETYPES X :` for float64→int pair), transputer intrinsics (LONGPROD, LONGDIV, LONGSUM, LONGDIFF, NORMALISE, SHIFTRIGHT, SHIFTLEFT — implemented as Go helper functions), CAUSEERROR (maps to `panic("CAUSEERROR")`). ## Course Module Testing @@ -206,4 +207,4 @@ go vet /tmp/course_out.go ## Not Yet Implemented -PRI ALT/PRI PAR, PLACED PAR, PORT OF. See `TODO.md` for the full list with priorities. +PLACED PAR, PORT OF. See `TODO.md` for the full list with priorities. diff --git a/TODO.md b/TODO.md index 7290161..9605b87 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,8 @@ - **IF** — Multi-branch conditionals, maps to if/else if chains, with replicators; supports multi-statement bodies (declarations scoped before process) - **WHILE** — Loops, maps to Go `for` loops; supports multi-statement bodies - **CASE** — Pattern matching with multiple cases and ELSE branch; supports multi-statement bodies -- **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`) +- **ALT / PRI 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`). PRI ALT treated identically (Go has no priority select). +- **PRI PAR** — Priority parallel, treated identically to PAR (Go goroutines have no priority) - **SKIP** — No-op process - **STOP** — Error + deadlock @@ -94,7 +95,7 @@ | Feature | Notes | |---------|-------| -| **PRI ALT / PRI PAR** | Priority variants of ALT and PAR. | +| ~~**PRI ALT / PRI PAR**~~ | ~~Priority variants of ALT and PAR.~~ **Implemented** — treated as ALT/PAR (Go has no priority select). | | **PLACED PAR** | Assigning processes to specific hardware. | | **PORT OF** | Hardware port mapping. | | **`VAL []BYTE` abbreviations** | `VAL []BYTE cmap IS "0123456789ABCDEF":` — named string constants. | diff --git a/ast/ast.go b/ast/ast.go index 678035d..95752e6 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -100,6 +100,7 @@ type ParBlock struct { Token lexer.Token // the PAR token Statements []Statement Replicator *Replicator // optional replicator + Priority bool // true for PRI PAR } func (p *ParBlock) statementNode() {} @@ -387,6 +388,7 @@ type AltBlock struct { Token lexer.Token // the ALT token Cases []AltCase Replicator *Replicator // optional replicator + Priority bool // true for PRI ALT } func (a *AltBlock) statementNode() {} diff --git a/codegen/e2e_concurrency_test.go b/codegen/e2e_concurrency_test.go index af86288..229120a 100644 --- a/codegen/e2e_concurrency_test.go +++ b/codegen/e2e_concurrency_test.go @@ -353,3 +353,42 @@ func TestE2E_ReplicatedAltByte(t *testing.T) { t.Errorf("expected %q, got %q", expected, output) } } + +func TestE2E_PriAlt(t *testing.T) { + // Test PRI ALT: behaves the same as ALT in Go (no priority semantics) + occam := `SEQ + CHAN OF INT c1: + CHAN OF INT c2: + INT result: + PAR + c1 ! 42 + PRI ALT + c1 ? result + print.int(result) + c2 ? result + print.int(result) +` + output := transpileCompileRun(t, occam) + expected := "42\n" + if output != expected { + t.Errorf("expected %q, got %q", expected, output) + } +} + +func TestE2E_PriPar(t *testing.T) { + // Test PRI PAR: behaves the same as PAR in Go (no priority semantics) + occam := `SEQ + CHAN OF INT c: + INT result: + PRI PAR + c ! 99 + SEQ + c ? result + print.int(result) +` + output := transpileCompileRun(t, occam) + expected := "99\n" + if output != expected { + t.Errorf("expected %q, got %q", expected, output) + } +} diff --git a/lexer/token.go b/lexer/token.go index 65a9c39..f514684 100644 --- a/lexer/token.go +++ b/lexer/token.go @@ -100,6 +100,7 @@ const ( TIMES // TIMES (modular multiplication keyword) ROUND_KW // ROUND (type conversion qualifier) TRUNC_KW // TRUNC (type conversion qualifier) + PRI // PRI (priority modifier for ALT/PAR) keyword_end ) @@ -195,6 +196,7 @@ var tokenNames = map[TokenType]string{ TIMES: "TIMES", ROUND_KW: "ROUND", TRUNC_KW: "TRUNC", + PRI: "PRI", } var keywords = map[string]TokenType{ @@ -248,6 +250,7 @@ var keywords = map[string]TokenType{ "TIMES": TIMES, "ROUND": ROUND_KW, "TRUNC": TRUNC_KW, + "PRI": PRI, } func (t TokenType) String() string { diff --git a/parser/parser.go b/parser/parser.go index 3bbf4a0..3f78b71 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -187,6 +187,8 @@ func (p *Parser) parseStatement() ast.Statement { return p.parseParBlock() case lexer.ALT: return p.parseAltBlock() + case lexer.PRI: + return p.parsePriBlock() case lexer.SKIP: return &ast.Skip{Token: p.curToken} case lexer.STOP: @@ -1603,6 +1605,24 @@ func (p *Parser) parseReplicator() *ast.Replicator { return rep } +func (p *Parser) parsePriBlock() ast.Statement { + // curToken is PRI, expect ALT or PAR next + if p.peekTokenIs(lexer.ALT) { + p.nextToken() // consume ALT + block := p.parseAltBlock() + block.Priority = true + return block + } + if p.peekTokenIs(lexer.PAR) { + p.nextToken() // consume PAR + block := p.parseParBlock() + block.Priority = true + return block + } + p.addError(fmt.Sprintf("expected ALT or PAR after PRI, got %s", p.peekToken.Type)) + return nil +} + func (p *Parser) parseAltBlock() *ast.AltBlock { block := &ast.AltBlock{Token: p.curToken} diff --git a/parser/parser_test.go b/parser/parser_test.go index d4b8bb1..ad499ac 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -381,6 +381,72 @@ func TestAltBlockWithGuardedSkip(t *testing.T) { } } +func TestPriAltBlock(t *testing.T) { + input := `PRI ALT + c1 ? x + SKIP + c2 ? y + SKIP +` + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("expected 1 statement, got %d", len(program.Statements)) + } + + alt, ok := program.Statements[0].(*ast.AltBlock) + if !ok { + t.Fatalf("expected AltBlock, got %T", program.Statements[0]) + } + + if !alt.Priority { + t.Error("expected Priority to be true for PRI ALT") + } + + if len(alt.Cases) != 2 { + t.Fatalf("expected 2 cases, got %d", len(alt.Cases)) + } + + if alt.Cases[0].Channel != "c1" { + t.Errorf("expected channel 'c1', got %s", alt.Cases[0].Channel) + } + + if alt.Cases[1].Channel != "c2" { + t.Errorf("expected channel 'c2', got %s", alt.Cases[1].Channel) + } +} + +func TestPriParBlock(t *testing.T) { + input := `PRI PAR + x := 1 + y := 2 +` + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("expected 1 statement, got %d", len(program.Statements)) + } + + par, ok := program.Statements[0].(*ast.ParBlock) + if !ok { + t.Fatalf("expected ParBlock, got %T", program.Statements[0]) + } + + if !par.Priority { + t.Error("expected Priority to be true for PRI PAR") + } + + if len(par.Statements) != 2 { + t.Fatalf("expected 2 statements, got %d", len(par.Statements)) + } +} + func TestWhileLoop(t *testing.T) { input := `WHILE x > 0 x := x - 1