diff --git a/CLAUDE.md b/CLAUDE.md index 069043a..580f785 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,9 +36,9 @@ Six packages, one pipeline: 1. **`preproc/`** — Textual preprocessor (pre-lexer pass). Handles `#IF`/`#ELSE`/`#ENDIF`/`#DEFINE` conditional compilation, `#INCLUDE` file inclusion with search paths, and ignores `#COMMENT`/`#PRAGMA`/`#USE`. Produces a single expanded string for the lexer. - `preproc.go` — Preprocessor with condition stack and expression evaluator -2. **`lexer/`** — Tokenizer with indentation tracking. Produces `INDENT`/`DEDENT` tokens from whitespace changes (2-space indent = 1 level). Key files: +2. **`lexer/`** — Tokenizer with indentation tracking. Produces `INDENT`/`DEDENT` tokens from whitespace changes (2-space indent = 1 level). Suppresses INDENT/DEDENT/NEWLINE inside parentheses (`parenDepth` tracking, like Python). Key files: - `token.go` — Token types and keyword lookup - - `lexer.go` — Lexer with `indentStack` and `pendingTokens` queue + - `lexer.go` — Lexer with `indentStack`, `pendingTokens` queue, and `parenDepth` counter 3. **`parser/`** — Recursive descent parser with Pratt expression parsing. Produces AST. - `parser.go` — All parsing logic in one file @@ -111,7 +111,11 @@ Six packages, one pipeline: | Non-VAL params | `*type` pointer params, callers pass `&arg` | | `PROC f([]INT arr)` | `func f(arr []int)` (open array param, slice) | | `PROC f(VAL []INT arr)` | `func f(arr []int)` (VAL open array, also slice) | +| `PROC f([2]INT arr)` | `func f(arr *[2]int)` (fixed-size array param) | +| `PROC f(RESULT INT x)` | `func f(x *int)` (RESULT qualifier, same as non-VAL) | +| `PROC f(CHAN INT a?, b?)` | Shared-type params (type applies to all until next type) | | `VAL INT x IS 42:` | `x := 42` (abbreviation/named constant) | +| `VAL []BYTE s IS "hi":` | `var s []byte = []byte("hi")` (open array abbreviation) | | `INT y IS z:` | `y := z` (non-VAL abbreviation) | | `INITIAL INT x IS 42:` | `x := 42` (mutable variable with initial value) | | `#INCLUDE "file"` | Textual inclusion (preprocessor, pre-lexer) | @@ -125,6 +129,7 @@ Six packages, one pipeline: | `MOSTNEG REAL32` / `MOSTPOS REAL32` | `-math.MaxFloat32` / `math.MaxFloat32` | | `MOSTNEG REAL64` / `MOSTPOS REAL64` | `-math.MaxFloat64` / `math.MaxFloat64` | | `[arr FROM n FOR m]` | `arr[n : n+m]` (array slice) | +| `[arr FOR m]` | `arr[0 : m]` (shorthand slice, FROM 0 implied) | | `[arr FROM n FOR m] := src` | `copy(arr[n:n+m], src)` (slice assignment) | | Nested `PROC`/`FUNCTION` | `name := func(...) { ... }` (Go closure) | @@ -159,8 +164,20 @@ Typical workflow for a new language construct: ## What's Implemented -Preprocessor (`#IF`/`#ELSE`/`#ENDIF`/`#DEFINE`/`#INCLUDE` with search paths, include guards, `#COMMENT`/`#PRAGMA`/`#USE` ignored), module file generation from SConscript (`gen-module` subcommand), SEQ, PAR, IF, WHILE, CASE, ALT (with guards, timer timeouts, and multi-statement bodies with scoped declarations), SKIP, STOP, variable/array/channel/timer declarations, abbreviations (`VAL INT x IS 42:`, `INT y IS z:`), assignments (simple and indexed), channel send/receive, channel arrays (`[n]CHAN OF TYPE` with indexed send/receive and `[]CHAN OF TYPE` proc params), PROC (with VAL, reference, CHAN, []CHAN, and open array `[]TYPE` params), channel direction restrictions (`CHAN OF INT c?` → `<-chan int`, `CHAN OF INT c!` → `chan<- int`, call-site annotations `out!`/`in?` accepted), FUNCTION (IS and VALOF forms, 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), replicators on SEQ/PAR/IF (with optional STEP), arithmetic/comparison/logical/AFTER/bitwise operators, type conversions (`INT expr`, `BYTE expr`, `REAL32 expr`, `REAL64 expr`, etc.), 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]` with slice assignment), nested PROCs/FUNCTIONs (local definitions as Go closures), MOSTNEG/MOSTPOS (type min/max constants for INT, BYTE, REAL32, REAL64), INITIAL declarations (`INITIAL INT x IS 42:` — mutable variable with initial value), checked (modular) arithmetic (`PLUS`, `MINUS`, `TIMES` — wrapping operators). +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, and multi-statement bodies with scoped declarations), SKIP, STOP, variable/array/channel/timer declarations, abbreviations (`VAL INT x IS 42:`, `INT y IS z:`, `VAL []BYTE s IS "hi":`), assignments (simple and indexed), channel send/receive, channel arrays (`[n]CHAN OF TYPE` with indexed send/receive and `[]CHAN OF TYPE` proc params), PROC (with VAL, RESULT, reference, CHAN, []CHAN, open array `[]TYPE`, fixed-size array `[n]TYPE`, and shared-type params), channel direction restrictions (`CHAN OF INT c?` → `<-chan int`, `CHAN OF INT c!` → `chan<- int`, call-site annotations `out!`/`in?` accepted), multi-line parameter lists (lexer suppresses INDENT/DEDENT/NEWLINE inside parens), 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), replicators on SEQ/PAR/IF (with optional STEP), arithmetic/comparison/logical/AFTER/bitwise operators, type conversions (`INT expr`, `BYTE expr`, `REAL32 expr`, `REAL64 expr`, etc.), 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), nested PROCs/FUNCTIONs (local definitions as Go closures), MOSTNEG/MOSTPOS (type min/max constants for INT, BYTE, REAL32, REAL64), INITIAL declarations (`INITIAL INT x IS 42:` — mutable variable with initial value), checked (modular) arithmetic (`PLUS`, `MINUS`, `TIMES` — wrapping operators). + +## Course Module Testing + +The KRoC course module (`kroc/modules/course/libsrc/course.module`) is a real-world integration test. A reduced version excluding `float_io.occ` is provided: + +```bash +# Transpile course module (without float_io.occ) +./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go course_nofloat.module + +# Verify Go output compiles (will only fail with "no main" since it's a library) +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. +RETYPES (bit-level type reinterpretation), transputer intrinsics (LONGPROD, LONGDIV, LONGSUM, LONGDIFF, NORMALISE, SHIFTRIGHT, SHIFTLEFT), CAUSEERROR, PRI ALT/PRI PAR, PLACED PAR, PORT OF. These are needed to transpile `float_io.occ` (Phase 2). See `TODO.md` for the full list with priorities. diff --git a/ast/ast.go b/ast/ast.go index 3363ffd..8f5c269 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -150,6 +150,7 @@ type ProcParam struct { IsOpenArray bool // true for []TYPE params (open array) ChanElemType string // element type when IsChan (e.g., "INT") ChanDir string // "?" for input, "!" for output, "" for bidirectional + ArraySize string // non-empty for fixed-size array params like [2]INT } // ProcCall represents a procedure call @@ -482,12 +483,13 @@ func (se *SliceExpr) TokenLiteral() string { return se.Token.Literal } // Abbreviation represents an abbreviation: VAL INT x IS 42:, INT y IS z:, or INITIAL INT x IS 42: type Abbreviation struct { - Token lexer.Token // VAL, INITIAL, or type token - IsVal bool // true for VAL abbreviations - IsInitial bool // true for INITIAL declarations - Type string // "INT", "BYTE", "BOOL", etc. - Name string // variable name - Value Expression // the expression + Token lexer.Token // VAL, INITIAL, or type token + IsVal bool // true for VAL abbreviations + IsInitial bool // true for INITIAL declarations + IsOpenArray bool // true for []TYPE abbreviations (e.g. VAL []BYTE) + Type string // "INT", "BYTE", "BOOL", etc. + Name string // variable name + Value Expression // the expression } func (a *Abbreviation) statementNode() {} diff --git a/codegen/codegen.go b/codegen/codegen.go index 0e8c8a3..5cd8626 100644 --- a/codegen/codegen.go +++ b/codegen/codegen.go @@ -48,6 +48,12 @@ func New() *Generator { return &Generator{} } +// goIdent converts an occam identifier to a valid Go identifier. +// Occam allows dots in identifiers (e.g., out.repeat); Go does not. +func goIdent(name string) string { + return strings.ReplaceAll(name, ".", "_") +} + // Generate produces Go code from the AST func (g *Generator) Generate(program *ast.Program) string { g.builder.Reset() @@ -132,12 +138,34 @@ func (g *Generator) Generate(program *ast.Program) string { var procDecls []ast.Statement var mainStatements []ast.Statement + // First pass: check if there are any proc/func declarations + hasProcDecls := false + for _, stmt := range program.Statements { + if _, ok := stmt.(*ast.ProcDecl); ok { + hasProcDecls = true + break + } + if _, ok := stmt.(*ast.FuncDecl); ok { + hasProcDecls = true + break + } + } + + var abbrDecls []ast.Statement for _, stmt := range program.Statements { switch stmt.(type) { case *ast.ProtocolDecl, *ast.RecordDecl: typeDecls = append(typeDecls, stmt) case *ast.ProcDecl, *ast.FuncDecl: procDecls = append(procDecls, stmt) + case *ast.Abbreviation: + if hasProcDecls { + // Top-level abbreviations need to be at package level + // so PROCs can reference them + abbrDecls = append(abbrDecls, stmt) + } else { + mainStatements = append(mainStatements, stmt) + } default: mainStatements = append(mainStatements, stmt) } @@ -148,6 +176,22 @@ func (g *Generator) Generate(program *ast.Program) string { g.generateStatement(stmt) } + // Generate package-level abbreviations (constants) + for _, stmt := range abbrDecls { + abbr := stmt.(*ast.Abbreviation) + goType := g.occamTypeToGo(abbr.Type) + if abbr.IsOpenArray { + goType = "[]" + goType + } + g.builder.WriteString("var ") + g.write(fmt.Sprintf("%s %s = ", goIdent(abbr.Name), goType)) + g.generateExpression(abbr.Value) + g.write("\n") + } + if len(abbrDecls) > 0 { + g.writeLine("") + } + // Generate procedure declarations (at package level) for _, stmt := range procDecls { g.generateStatement(stmt) @@ -718,12 +762,20 @@ func (g *Generator) generateStatement(stmt ast.Statement) { func (g *Generator) generateVarDecl(decl *ast.VarDecl) { goType := g.occamTypeToGo(decl.Type) - g.writeLine(fmt.Sprintf("var %s %s", strings.Join(decl.Names, ", "), goType)) + goNames := make([]string, len(decl.Names)) + for i, n := range decl.Names { + goNames[i] = goIdent(n) + } + g.writeLine(fmt.Sprintf("var %s %s", strings.Join(goNames, ", "), goType)) + // Suppress "declared and not used" for each variable + for _, n := range goNames { + g.writeLine(fmt.Sprintf("_ = %s", n)) + } } func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) { g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(fmt.Sprintf("%s := ", abbr.Name)) + g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name))) g.generateExpression(abbr.Value) g.write("\n") } @@ -732,16 +784,17 @@ func (g *Generator) generateChanDecl(decl *ast.ChanDecl) { goType := g.occamTypeToGo(decl.ElemType) if decl.IsArray { for _, name := range decl.Names { + n := goIdent(name) g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(fmt.Sprintf("%s := make([]chan %s, ", name, goType)) + g.write(fmt.Sprintf("%s := make([]chan %s, ", n, goType)) g.generateExpression(decl.Size) g.write(")\n") g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(fmt.Sprintf("for _i := range %s { %s[_i] = make(chan %s) }\n", name, name, goType)) + g.write(fmt.Sprintf("for _i := range %s { %s[_i] = make(chan %s) }\n", n, n, goType)) } } else { for _, name := range decl.Names { - g.writeLine(fmt.Sprintf("%s := make(chan %s)", name, goType)) + g.writeLine(fmt.Sprintf("%s := make(chan %s)", goIdent(name), goType)) } } } @@ -753,14 +806,15 @@ func (g *Generator) generateTimerDecl(decl *ast.TimerDecl) { } func (g *Generator) generateTimerRead(tr *ast.TimerRead) { - g.writeLine(fmt.Sprintf("%s = int(time.Now().UnixMicro())", tr.Variable)) + g.writeLine(fmt.Sprintf("%s = int(time.Now().UnixMicro())", goIdent(tr.Variable))) } func (g *Generator) generateArrayDecl(decl *ast.ArrayDecl) { goType := g.occamTypeToGo(decl.Type) for _, name := range decl.Names { + n := goIdent(name) g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(fmt.Sprintf("%s := make([]%s, ", name, goType)) + g.write(fmt.Sprintf("%s := make([]%s, ", n, goType)) g.generateExpression(decl.Size) g.write(")\n") } @@ -768,7 +822,7 @@ func (g *Generator) generateArrayDecl(decl *ast.ArrayDecl) { func (g *Generator) generateSend(send *ast.Send) { g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(send.Channel) + g.write(goIdent(send.Channel)) if send.ChannelIndex != nil { g.write("[") g.generateExpression(send.ChannelIndex) @@ -778,10 +832,11 @@ func (g *Generator) generateSend(send *ast.Send) { protoName := g.chanProtocols[send.Channel] proto := g.protocolDefs[protoName] + gProtoName := goIdent(protoName) if send.VariantTag != "" && proto != nil && proto.Kind == "variant" { // Variant send with explicit tag: c <- _proto_NAME_tag{values...} - g.write(fmt.Sprintf("_proto_%s_%s{", protoName, send.VariantTag)) + g.write(fmt.Sprintf("_proto_%s_%s{", gProtoName, goIdent(send.VariantTag))) for i, val := range send.Values { if i > 0 { g.write(", ") @@ -792,13 +847,13 @@ func (g *Generator) generateSend(send *ast.Send) { } else if proto != nil && proto.Kind == "variant" && send.Value != nil && len(send.Values) == 0 { // Check if the send value is a bare identifier matching a variant tag if ident, ok := send.Value.(*ast.Identifier); ok && g.isVariantTag(protoName, ident.Value) { - g.write(fmt.Sprintf("_proto_%s_%s{}", protoName, ident.Value)) + g.write(fmt.Sprintf("_proto_%s_%s{}", gProtoName, goIdent(ident.Value))) } else { g.generateExpression(send.Value) } } else if len(send.Values) > 0 && proto != nil && proto.Kind == "sequential" { // Sequential send: c <- _proto_NAME{val1, val2, ...} - g.write(fmt.Sprintf("_proto_%s{", protoName)) + g.write(fmt.Sprintf("_proto_%s{", gProtoName)) g.generateExpression(send.Value) for _, val := range send.Values { g.write(", ") @@ -813,10 +868,10 @@ func (g *Generator) generateSend(send *ast.Send) { } func (g *Generator) generateReceive(recv *ast.Receive) { - chanRef := recv.Channel + chanRef := goIdent(recv.Channel) if recv.ChannelIndex != nil { var buf strings.Builder - buf.WriteString(recv.Channel) + buf.WriteString(goIdent(recv.Channel)) buf.WriteString("[") // Generate the index expression into a temporary buffer oldBuilder := g.builder @@ -833,23 +888,36 @@ func (g *Generator) generateReceive(recv *ast.Receive) { tmpName := fmt.Sprintf("_tmp%d", g.tmpCounter) g.tmpCounter++ g.writeLine(fmt.Sprintf("%s := <-%s", tmpName, chanRef)) - g.writeLine(fmt.Sprintf("%s = %s._0", recv.Variable, tmpName)) + varRef := goIdent(recv.Variable) + if g.refParams[recv.Variable] { + varRef = "*" + varRef + } + g.writeLine(fmt.Sprintf("%s = %s._0", varRef, tmpName)) for i, v := range recv.Variables { - g.writeLine(fmt.Sprintf("%s = %s._%d", v, tmpName, i+1)) + vRef := goIdent(v) + if g.refParams[v] { + vRef = "*" + vRef + } + g.writeLine(fmt.Sprintf("%s = %s._%d", vRef, tmpName, i+1)) } } else { - g.writeLine(fmt.Sprintf("%s = <-%s", recv.Variable, chanRef)) + varRef := goIdent(recv.Variable) + if g.refParams[recv.Variable] { + varRef = "*" + varRef + } + g.writeLine(fmt.Sprintf("%s = <-%s", varRef, chanRef)) } } func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) { + gName := goIdent(proto.Name) switch proto.Kind { case "simple": goType := g.occamTypeToGoBase(proto.Types[0]) - g.writeLine(fmt.Sprintf("type _proto_%s = %s", proto.Name, goType)) + g.writeLine(fmt.Sprintf("type _proto_%s = %s", gName, goType)) g.writeLine("") case "sequential": - g.writeLine(fmt.Sprintf("type _proto_%s struct {", proto.Name)) + g.writeLine(fmt.Sprintf("type _proto_%s struct {", gName)) g.indent++ for i, t := range proto.Types { goType := g.occamTypeToGoBase(t) @@ -860,19 +928,20 @@ func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) { g.writeLine("") case "variant": // Interface type - g.writeLine(fmt.Sprintf("type _proto_%s interface {", proto.Name)) + g.writeLine(fmt.Sprintf("type _proto_%s interface {", gName)) g.indent++ - g.writeLine(fmt.Sprintf("_is_%s()", proto.Name)) + g.writeLine(fmt.Sprintf("_is_%s()", gName)) g.indent-- g.writeLine("}") g.writeLine("") // Concrete types for each variant for _, v := range proto.Variants { + gTag := goIdent(v.Tag) if len(v.Types) == 0 { // No-payload variant: empty struct - g.writeLine(fmt.Sprintf("type _proto_%s_%s struct{}", proto.Name, v.Tag)) + g.writeLine(fmt.Sprintf("type _proto_%s_%s struct{}", gName, gTag)) } else { - g.writeLine(fmt.Sprintf("type _proto_%s_%s struct {", proto.Name, v.Tag)) + g.writeLine(fmt.Sprintf("type _proto_%s_%s struct {", gName, gTag)) g.indent++ for i, t := range v.Types { goType := g.occamTypeToGoBase(t) @@ -881,7 +950,7 @@ func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) { g.indent-- g.writeLine("}") } - g.writeLine(fmt.Sprintf("func (_proto_%s_%s) _is_%s() {}", proto.Name, v.Tag, proto.Name)) + g.writeLine(fmt.Sprintf("func (_proto_%s_%s) _is_%s() {}", gName, gTag, gName)) g.writeLine("") } } @@ -889,10 +958,11 @@ func (g *Generator) generateProtocolDecl(proto *ast.ProtocolDecl) { func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) { protoName := g.chanProtocols[vr.Channel] - chanRef := vr.Channel + gProtoName := goIdent(protoName) + chanRef := goIdent(vr.Channel) if vr.ChannelIndex != nil { var buf strings.Builder - buf.WriteString(vr.Channel) + buf.WriteString(goIdent(vr.Channel)) buf.WriteString("[") oldBuilder := g.builder g.builder = strings.Builder{} @@ -904,10 +974,10 @@ func (g *Generator) generateVariantReceive(vr *ast.VariantReceive) { } g.writeLine(fmt.Sprintf("switch _v := (<-%s).(type) {", chanRef)) for _, vc := range vr.Cases { - g.writeLine(fmt.Sprintf("case _proto_%s_%s:", protoName, vc.Tag)) + g.writeLine(fmt.Sprintf("case _proto_%s_%s:", gProtoName, goIdent(vc.Tag))) g.indent++ for i, v := range vc.Variables { - g.writeLine(fmt.Sprintf("%s = _v._%d", v, i)) + g.writeLine(fmt.Sprintf("%s = _v._%d", goIdent(v), i)) } if vc.Body != nil { g.generateStatement(vc.Body) @@ -1050,11 +1120,11 @@ func (g *Generator) collectRecordVars(stmt ast.Statement) { } func (g *Generator) generateRecordDecl(rec *ast.RecordDecl) { - g.writeLine(fmt.Sprintf("type %s struct {", rec.Name)) + g.writeLine(fmt.Sprintf("type %s struct {", goIdent(rec.Name))) g.indent++ for _, f := range rec.Fields { goType := g.occamTypeToGoBase(f.Type) - g.writeLine(fmt.Sprintf("%s %s", f.Name, goType)) + g.writeLine(fmt.Sprintf("%s %s", goIdent(f.Name), goType)) } g.indent-- g.writeLine("}") @@ -1134,9 +1204,9 @@ func (g *Generator) generateAssignment(assign *ast.Assignment) { if _, ok := g.recordVars[assign.Name]; ok { if ident, ok := assign.Index.(*ast.Identifier); ok { // Record field: p.x = value (Go auto-dereferences pointers) - g.write(assign.Name) + g.write(goIdent(assign.Name)) g.write(".") - g.write(ident.Value) + g.write(goIdent(ident.Value)) g.write(" = ") g.generateExpression(assign.Value) g.write("\n") @@ -1147,7 +1217,7 @@ func (g *Generator) generateAssignment(assign *ast.Assignment) { if g.refParams[assign.Name] { g.write("*") } - g.write(assign.Name) + g.write(goIdent(assign.Name)) g.write("[") g.generateExpression(assign.Index) g.write("]") @@ -1156,7 +1226,7 @@ func (g *Generator) generateAssignment(assign *ast.Assignment) { if g.refParams[assign.Name] { g.write("*") } - g.write(assign.Name) + g.write(goIdent(assign.Name)) } g.write(" = ") g.generateExpression(assign.Value) @@ -1167,7 +1237,7 @@ func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) { if seq.Replicator != nil { if seq.Replicator.Step != nil { // Replicated SEQ with STEP: counter-based loop - v := seq.Replicator.Variable + v := goIdent(seq.Replicator.Variable) counter := "_repl_" + v g.builder.WriteString(strings.Repeat("\t", g.indent)) g.write(fmt.Sprintf("for %s := 0; %s < ", counter, counter)) @@ -1182,14 +1252,15 @@ func (g *Generator) generateSeqBlock(seq *ast.SeqBlock) { g.write("\n") } else { // Replicated SEQ: SEQ i = start FOR count becomes a for loop + v := goIdent(seq.Replicator.Variable) g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(fmt.Sprintf("for %s := ", seq.Replicator.Variable)) + g.write(fmt.Sprintf("for %s := ", v)) g.generateExpression(seq.Replicator.Start) - g.write(fmt.Sprintf("; %s < ", seq.Replicator.Variable)) + g.write(fmt.Sprintf("; %s < ", v)) g.generateExpression(seq.Replicator.Start) g.write(" + ") g.generateExpression(seq.Replicator.Count) - g.write(fmt.Sprintf("; %s++ {\n", seq.Replicator.Variable)) + g.write(fmt.Sprintf("; %s++ {\n", v)) g.indent++ } for _, stmt := range seq.Statements { @@ -1214,7 +1285,7 @@ func (g *Generator) generateParBlock(par *ast.ParBlock) { g.generateExpression(par.Replicator.Count) g.write("))\n") - v := par.Replicator.Variable + v := goIdent(par.Replicator.Variable) if par.Replicator.Step != nil { counter := "_repl_" + v g.builder.WriteString(strings.Repeat("\t", g.indent)) @@ -1298,7 +1369,7 @@ func (g *Generator) generateAltBlock(alt *ast.AltBlock) { g.builder.WriteString(strings.Repeat("\t", g.indent)) g.write(fmt.Sprintf("if ")) g.generateExpression(c.Guard) - g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, c.Channel)) + g.write(fmt.Sprintf(" { _alt%d = %s }\n", i, goIdent(c.Channel))) } } } @@ -1311,13 +1382,13 @@ func (g *Generator) generateAltBlock(alt *ast.AltBlock) { g.generateExpression(c.Deadline) g.write(" - int(time.Now().UnixMicro())) * time.Microsecond):\n") } else if c.Guard != nil { - g.write(fmt.Sprintf("case %s = <-_alt%d:\n", c.Variable, i)) + g.write(fmt.Sprintf("case %s = <-_alt%d:\n", goIdent(c.Variable), i)) } else if c.ChannelIndex != nil { - g.write(fmt.Sprintf("case %s = <-%s[", c.Variable, c.Channel)) + g.write(fmt.Sprintf("case %s = <-%s[", goIdent(c.Variable), goIdent(c.Channel))) g.generateExpression(c.ChannelIndex) g.write("]:\n") } else { - g.write(fmt.Sprintf("case %s = <-%s:\n", c.Variable, c.Channel)) + g.write(fmt.Sprintf("case %s = <-%s:\n", goIdent(c.Variable), goIdent(c.Channel))) } g.indent++ for _, s := range c.Body { @@ -1362,11 +1433,12 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) { // Generate function signature params := g.generateProcParams(proc.Params) + gName := goIdent(proc.Name) if g.nestingLevel > 0 { // Nested PROC: generate as Go closure - g.writeLine(fmt.Sprintf("%s := func(%s) {", proc.Name, params)) + g.writeLine(fmt.Sprintf("%s := func(%s) {", gName, params)) } else { - g.writeLine(fmt.Sprintf("func %s(%s) {", proc.Name, params)) + g.writeLine(fmt.Sprintf("func %s(%s) {", gName, params)) } g.indent++ g.nestingLevel++ @@ -1394,6 +1466,12 @@ func (g *Generator) generateProcParams(params []ast.ProcParam) string { goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType) } else if p.IsOpenArray { goType = "[]" + g.occamTypeToGo(p.Type) + } else if p.ArraySize != "" { + // Fixed-size array parameter: [n]TYPE + goType = "[" + p.ArraySize + "]" + g.occamTypeToGo(p.Type) + if !p.IsVal { + goType = "*" + goType + } } else { goType = g.occamTypeToGo(p.Type) if !p.IsVal { @@ -1401,7 +1479,7 @@ func (g *Generator) generateProcParams(params []ast.ProcParam) string { goType = "*" + goType } } - parts = append(parts, fmt.Sprintf("%s %s", p.Name, goType)) + parts = append(parts, fmt.Sprintf("%s %s", goIdent(p.Name), goType)) } return strings.Join(parts, ", ") } @@ -1425,7 +1503,7 @@ func (g *Generator) generateProcCall(call *ast.ProcCall) { } g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write(call.Name) + g.write(goIdent(call.Name)) g.write("(") // Look up procedure signature to determine which args need address-of @@ -1437,10 +1515,17 @@ func (g *Generator) generateProcCall(call *ast.ProcCall) { } // If this parameter is not VAL (i.e., pass by reference), take address // Channels and channel arrays are already reference types, so no & needed - if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray { + if i < len(params) && !params[i].IsVal && !params[i].IsChan && !params[i].IsChanArray && !params[i].IsOpenArray && params[i].ArraySize == "" { g.write("&") } - g.generateExpression(arg) + // Wrap string literals with []byte() when passed to []BYTE parameters + if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" { + g.write("[]byte(") + g.generateExpression(arg) + g.write(")") + } else { + g.generateExpression(arg) + } } g.write(")") g.write("\n") @@ -1461,11 +1546,12 @@ func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) { returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")" } + gName := goIdent(fn.Name) if g.nestingLevel > 0 { // Nested FUNCTION: generate as Go closure - g.writeLine(fmt.Sprintf("%s := func(%s) %s {", fn.Name, params, returnTypeStr)) + g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr)) } else { - g.writeLine(fmt.Sprintf("func %s(%s) %s {", fn.Name, params, returnTypeStr)) + g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr)) } g.indent++ g.nestingLevel++ @@ -1493,13 +1579,21 @@ func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) { } func (g *Generator) generateFuncCallExpr(call *ast.FuncCall) { - g.write(call.Name) + g.write(goIdent(call.Name)) g.write("(") + params := g.procSigs[call.Name] for i, arg := range call.Args { if i > 0 { g.write(", ") } - g.generateExpression(arg) + // Wrap string literals with []byte() when passed to []BYTE parameters + if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" { + g.write("[]byte(") + g.generateExpression(arg) + g.write(")") + } else { + g.generateExpression(arg) + } } g.write(")") } @@ -1514,18 +1608,18 @@ func (g *Generator) generateMultiAssignment(stmt *ast.MultiAssignment) { // Check if this is a record field access if _, ok := g.recordVars[target.Name]; ok { if ident, ok := target.Index.(*ast.Identifier); ok { - g.write(target.Name) + g.write(goIdent(target.Name)) g.write(".") - g.write(ident.Value) + g.write(goIdent(ident.Value)) continue } } if g.refParams[target.Name] { g.write("(*") - g.write(target.Name) + g.write(goIdent(target.Name)) g.write(")") } else { - g.write(target.Name) + g.write(goIdent(target.Name)) } g.write("[") g.generateExpression(target.Index) @@ -1534,7 +1628,7 @@ func (g *Generator) generateMultiAssignment(stmt *ast.MultiAssignment) { if g.refParams[target.Name] { g.write("*") } - g.write(target.Name) + g.write(goIdent(target.Name)) } } g.write(" = ") @@ -1606,10 +1700,10 @@ func (g *Generator) flattenIfChoices(choices []ast.IfChoice) []ast.IfChoice { } // generateReplicatedIfLoop emits a for loop that breaks on first matching choice. -// When withinFlag is true, it sets _ifmatched = true before breaking. -func (g *Generator) generateReplicatedIfLoop(stmt *ast.IfStatement, withinFlag bool) { +// When withinFlag is true, it sets the named flag to true before breaking. +func (g *Generator) generateReplicatedIfLoop(stmt *ast.IfStatement, withinFlag bool, flagName ...string) { repl := stmt.Replicator - v := repl.Variable + v := goIdent(repl.Variable) if repl.Step != nil { counter := "_repl_" + v g.builder.WriteString(strings.Repeat("\t", g.indent)) @@ -1649,8 +1743,8 @@ func (g *Generator) generateReplicatedIfLoop(stmt *ast.IfStatement, withinFlag b for _, s := range choice.Body { g.generateStatement(s) } - if withinFlag { - g.writeLine("_ifmatched = true") + if withinFlag && len(flagName) > 0 { + g.writeLine(fmt.Sprintf("%s = true", flagName[0])) } g.writeLine("break") @@ -1730,15 +1824,17 @@ func (g *Generator) generateIfChoiceChain(choices []ast.IfChoice, isFirst bool) // Emit the replicated nested IF with a flag needFlag := len(after) > 0 + flagName := fmt.Sprintf("_ifmatched%d", g.tmpCounter) + g.tmpCounter++ if needFlag { - g.writeLine("_ifmatched := false") + g.writeLine(fmt.Sprintf("%s := false", flagName)) } - g.generateReplicatedIfLoop(replChoice.NestedIf, needFlag) + g.generateReplicatedIfLoop(replChoice.NestedIf, needFlag, flagName) - // Emit remaining choices inside if !_ifmatched (recursive for multiple) + // Emit remaining choices inside if !flagName (recursive for multiple) if len(after) > 0 { g.builder.WriteString(strings.Repeat("\t", g.indent)) - g.write("if !_ifmatched {\n") + g.write(fmt.Sprintf("if !%s {\n", flagName)) g.indent++ g.generateIfChoiceChain(after, true) // recursive for remaining g.indent-- @@ -1784,7 +1880,11 @@ func (g *Generator) generateCaseStatement(stmt *ast.CaseStatement) { func (g *Generator) generateExpression(expr ast.Expression) { switch e := expr.(type) { case *ast.Identifier: - g.write(e.Value) + if g.refParams[e.Value] { + g.write("*" + goIdent(e.Value)) + } else { + g.write(goIdent(e.Value)) + } case *ast.IntegerLiteral: g.write(fmt.Sprintf("%d", e.Value)) case *ast.StringLiteral: @@ -1816,7 +1916,7 @@ func (g *Generator) generateExpression(expr ast.Expression) { if field, ok := e.Index.(*ast.Identifier); ok { g.generateExpression(e.Left) g.write(".") - g.write(field.Value) + g.write(goIdent(field.Value)) break } } diff --git a/course_nofloat.module b/course_nofloat.module new file mode 100644 index 0000000..26eef55 --- /dev/null +++ b/course_nofloat.module @@ -0,0 +1,10 @@ +#IF NOT (DEFINED (COURSE.MODULE)) +#DEFINE COURSE.MODULE +#INCLUDE "consts.inc" +#INCLUDE "utils.occ" +#INCLUDE "string.occ" +#INCLUDE "demo_cycles.occ" +#INCLUDE "demo_nets.occ" +#INCLUDE "file_in.occ" +#INCLUDE "random.occ" +#ENDIF diff --git a/lexer/lexer.go b/lexer/lexer.go index 440c5e5..a7743ac 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -16,6 +16,9 @@ type Lexer struct { indentStack []int // stack of indentation levels pendingTokens []Token // tokens to emit before reading more input atLineStart bool + + // Parenthesis depth: suppress INDENT/DEDENT/NEWLINE inside (...) + parenDepth int } func New(input string) *Lexer { @@ -62,7 +65,10 @@ func (l *Lexer) NextToken() Token { indent := l.measureIndent() currentIndent := l.indentStack[len(l.indentStack)-1] - if indent > currentIndent { + if l.parenDepth > 0 { + // Inside parentheses: suppress INDENT/DEDENT tokens + // (don't modify indentStack — resume normal tracking after close paren) + } else if indent > currentIndent { l.indentStack = append(l.indentStack, indent) return Token{Type: INDENT, Literal: "", Line: l.line, Column: 1} } else if indent < currentIndent { @@ -87,8 +93,12 @@ func (l *Lexer) NextToken() Token { switch l.ch { case '(': + l.parenDepth++ tok = l.newToken(LPAREN, l.ch) case ')': + if l.parenDepth > 0 { + l.parenDepth-- + } tok = l.newToken(RPAREN, l.ch) case '[': tok = l.newToken(LBRACKET, l.ch) @@ -193,7 +203,6 @@ func (l *Lexer) NextToken() Token { tok.Line = l.line tok.Column = l.column case '\n': - tok = Token{Type: NEWLINE, Literal: "\\n", Line: l.line, Column: l.column} l.line++ l.column = 0 l.atLineStart = true @@ -208,6 +217,11 @@ func (l *Lexer) NextToken() Token { l.skipToEndOfLine() } } + if l.parenDepth > 0 { + // Inside parentheses: suppress NEWLINE, get next real token + return l.NextToken() + } + tok = Token{Type: NEWLINE, Literal: "\\n", Line: l.line, Column: l.column} return tok case 0: // Emit any remaining DEDENTs before EOF diff --git a/parser/parser.go b/parser/parser.go index accf746..d81d674 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -313,15 +313,23 @@ func (p *Parser) parseVarDeclOrAbbreviation() ast.Statement { } // parseAbbreviation parses a VAL abbreviation: VAL INT x IS expr: +// Also handles VAL []BYTE x IS "string": (open array abbreviation) // Current token is VAL. func (p *Parser) parseAbbreviation() *ast.Abbreviation { token := p.curToken // VAL token - // Expect a type keyword p.nextToken() - if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) && - !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) && - !p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) { + + // Check for []TYPE (open array abbreviation) + isOpenArray := false + if p.curTokenIs(lexer.LBRACKET) && p.peekTokenIs(lexer.RBRACKET) { + isOpenArray = true + p.nextToken() // consume ] + p.nextToken() // move to type + } + + // Expect a type keyword + if !isTypeToken(p.curToken.Type) { p.addError(fmt.Sprintf("expected type after VAL, got %s", p.curToken.Type)) return nil } @@ -348,11 +356,12 @@ func (p *Parser) parseAbbreviation() *ast.Abbreviation { } return &ast.Abbreviation{ - Token: token, - IsVal: true, - Type: typeName, - Name: name, - Value: value, + Token: token, + IsVal: true, + IsOpenArray: isOpenArray, + Type: typeName, + Name: name, + Value: value, } } @@ -467,7 +476,8 @@ func (p *Parser) parseArrayDecl() ast.Statement { size := p.parseExpression(LOWEST) // Check if this is a slice assignment: [arr FROM start FOR length] := value - if p.peekTokenIs(lexer.FROM) { + // Also handles [arr FOR length] shorthand (FROM 0) + if p.peekTokenIs(lexer.FROM) || p.peekTokenIs(lexer.FOR) { return p.parseSliceAssignment(lbracketToken, size) } @@ -559,12 +569,19 @@ func (p *Parser) parseArrayDecl() ast.Statement { } // parseSliceAssignment parses [arr FROM start FOR length] := value -// Called from parseArrayDecl when FROM is detected after the array expression. +// Also handles [arr FOR length] shorthand (start defaults to 0). +// Called from parseArrayDecl when FROM or FOR is detected after the array expression. // lbracketToken is the [ token, arrayExpr is the already-parsed array expression. func (p *Parser) parseSliceAssignment(lbracketToken lexer.Token, arrayExpr ast.Expression) ast.Statement { - p.nextToken() // consume FROM - p.nextToken() // move to start expression - startExpr := p.parseExpression(LOWEST) + var startExpr ast.Expression + if p.peekTokenIs(lexer.FOR) { + // [arr FOR length] shorthand — start is 0 + startExpr = &ast.IntegerLiteral{Token: lexer.Token{Type: lexer.INT, Literal: "0"}, Value: 0} + } else { + p.nextToken() // consume FROM + p.nextToken() // move to start expression + startExpr = p.parseExpression(LOWEST) + } if !p.expectPeek(lexer.FOR) { return nil @@ -1861,6 +1878,13 @@ procBodyDone: return proc } +// isTypeToken returns true if the token type is a scalar type keyword. +func isTypeToken(t lexer.TokenType) bool { + return t == lexer.INT_TYPE || t == lexer.BYTE_TYPE || + t == lexer.BOOL_TYPE || t == lexer.REAL_TYPE || + t == lexer.REAL32_TYPE || t == lexer.REAL64_TYPE +} + func (p *Parser) parseProcParams() []ast.ProcParam { var params []ast.ProcParam @@ -1870,53 +1894,115 @@ func (p *Parser) parseProcParams() []ast.ProcParam { p.nextToken() + // Track the previous param's type info for shared-type parameters + var prevParam *ast.ProcParam + for { + // Skip newlines inside parameter lists (multi-line params) + // Note: INDENT/DEDENT/NEWLINE inside (...) are suppressed by the lexer + for p.curTokenIs(lexer.NEWLINE) { + p.nextToken() + } + param := ast.ProcParam{} + // Check if this is a shared-type parameter: after a comma, if current token + // is an IDENT that is NOT a type keyword, record name, CHAN, VAL, RESULT, or [, + // re-use the previous param's type/flags. + if prevParam != nil && p.curTokenIs(lexer.IDENT) && !p.recordNames[p.curToken.Literal] { + // This is a shared-type param — re-use type info from previous param + param.IsVal = prevParam.IsVal + param.Type = prevParam.Type + param.IsChan = prevParam.IsChan + param.IsChanArray = prevParam.IsChanArray + param.IsOpenArray = prevParam.IsOpenArray + param.ChanElemType = prevParam.ChanElemType + param.ArraySize = prevParam.ArraySize + param.Name = p.curToken.Literal + + // Check for channel direction marker (? or !) + if (param.IsChan || param.IsChanArray) && (p.peekTokenIs(lexer.RECEIVE) || p.peekTokenIs(lexer.SEND)) { + p.nextToken() + param.ChanDir = p.curToken.Literal + } + + params = append(params, param) + prevParam = ¶ms[len(params)-1] + + if !p.peekTokenIs(lexer.COMMA) { + break + } + p.nextToken() // consume comma + p.nextToken() // move to next param + continue + } + // Check for VAL keyword if p.curTokenIs(lexer.VAL) { param.IsVal = true p.nextToken() } - // Check for []CHAN OF or []TYPE (open array parameter) - if p.curTokenIs(lexer.LBRACKET) && p.peekTokenIs(lexer.RBRACKET) { - p.nextToken() // consume ] - p.nextToken() // move past ] - if p.curTokenIs(lexer.CHAN) { - // []CHAN OF or []CHAN (channel array parameter) - param.IsChan = true - param.IsChanArray = true - if p.peekTokenIs(lexer.OF) { - p.nextToken() // consume OF + // Check for RESULT keyword (output-only parameter — maps to pointer like non-VAL) + if p.curTokenIs(lexer.RESULT) { + // RESULT is semantically like non-VAL (pointer param), just skip it + p.nextToken() + } + + // Check for []CHAN OF , []TYPE (open array), or [n]TYPE (fixed-size array) + if p.curTokenIs(lexer.LBRACKET) { + if p.peekTokenIs(lexer.RBRACKET) { + // Open array: []CHAN OF TYPE or []TYPE + p.nextToken() // consume ] + p.nextToken() // move past ] + if p.curTokenIs(lexer.CHAN) { + // []CHAN OF or []CHAN (channel array parameter) + param.IsChan = true + param.IsChanArray = true + if p.peekTokenIs(lexer.OF) { + p.nextToken() // consume OF + } + p.nextToken() // move to element type + if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) { + param.ChanElemType = p.curToken.Literal + } else { + p.addError(fmt.Sprintf("expected type after []CHAN, got %s", p.curToken.Type)) + return params + } + p.nextToken() + } else if isTypeToken(p.curToken.Type) { + param.IsOpenArray = true + param.Type = p.curToken.Literal + p.nextToken() + } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] { + param.IsOpenArray = true + param.Type = p.curToken.Literal + p.nextToken() + } else { + p.addError(fmt.Sprintf("expected type after [], got %s", p.curToken.Type)) + return params } - p.nextToken() // move to element type - if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) || - p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) || - p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) { - param.ChanElemType = p.curToken.Literal - } else if p.curTokenIs(lexer.IDENT) { - param.ChanElemType = p.curToken.Literal + } else { + // Fixed-size array: [n]TYPE + p.nextToken() // move past [ + if !p.curTokenIs(lexer.INT) { + p.addError(fmt.Sprintf("expected array size, got %s", p.curToken.Type)) + return params + } + param.ArraySize = p.curToken.Literal + if !p.expectPeek(lexer.RBRACKET) { + return params + } + p.nextToken() // move to type + if isTypeToken(p.curToken.Type) { + param.Type = p.curToken.Literal + } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] { + param.Type = p.curToken.Literal } else { - p.addError(fmt.Sprintf("expected type after []CHAN, got %s", p.curToken.Type)) + p.addError(fmt.Sprintf("expected type after [%s], got %s", param.ArraySize, p.curToken.Type)) return params } p.nextToken() - } else if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) || - p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) || - p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) { - // []TYPE (open array parameter) - param.IsOpenArray = true - param.Type = p.curToken.Literal - p.nextToken() - } else if p.curTokenIs(lexer.IDENT) && p.recordNames[p.curToken.Literal] { - // []RECORD (open array of record type) - param.IsOpenArray = true - param.Type = p.curToken.Literal - p.nextToken() - } else { - p.addError(fmt.Sprintf("expected type after [], got %s", p.curToken.Type)) - return params } } else if p.curTokenIs(lexer.CHAN) { // Check for CHAN OF or CHAN @@ -1925,11 +2011,7 @@ func (p *Parser) parseProcParams() []ast.ProcParam { p.nextToken() // consume OF } p.nextToken() // move to element type - if p.curTokenIs(lexer.INT_TYPE) || p.curTokenIs(lexer.BYTE_TYPE) || - p.curTokenIs(lexer.BOOL_TYPE) || p.curTokenIs(lexer.REAL_TYPE) || - p.curTokenIs(lexer.REAL32_TYPE) || p.curTokenIs(lexer.REAL64_TYPE) { - param.ChanElemType = p.curToken.Literal - } else if p.curTokenIs(lexer.IDENT) { + if isTypeToken(p.curToken.Type) || p.curTokenIs(lexer.IDENT) { param.ChanElemType = p.curToken.Literal } else { p.addError(fmt.Sprintf("expected type after CHAN, got %s", p.curToken.Type)) @@ -1942,9 +2024,7 @@ func (p *Parser) parseProcParams() []ast.ProcParam { p.nextToken() } else { // Expect scalar type - if !p.curTokenIs(lexer.INT_TYPE) && !p.curTokenIs(lexer.BYTE_TYPE) && - !p.curTokenIs(lexer.BOOL_TYPE) && !p.curTokenIs(lexer.REAL_TYPE) && - !p.curTokenIs(lexer.REAL32_TYPE) && !p.curTokenIs(lexer.REAL64_TYPE) { + if !isTypeToken(p.curToken.Type) { p.addError(fmt.Sprintf("expected type in parameter, got %s", p.curToken.Type)) return params } @@ -1966,6 +2046,7 @@ func (p *Parser) parseProcParams() []ast.ProcParam { } params = append(params, param) + prevParam = ¶ms[len(params)-1] if !p.peekTokenIs(lexer.COMMA) { break @@ -2120,17 +2201,32 @@ func (p *Parser) parseFuncDecl() *ast.FuncDecl { return fn } p.nextToken() // consume INDENT + startLevel := p.indentLevel p.nextToken() // move into VALOF body - // Parse the body statement (e.g., SEQ, IF, etc.) - bodyStmt := p.parseStatement() - if bodyStmt != nil { - fn.Body = append(fn.Body, bodyStmt) - } - - // Advance past nested DEDENTs/newlines to RESULT + // Parse the VALOF body — declarations and statements until RESULT for !p.curTokenIs(lexer.RESULT) && !p.curTokenIs(lexer.EOF) { - p.nextToken() + // Skip newlines + for p.curTokenIs(lexer.NEWLINE) { + p.nextToken() + } + // Handle DEDENTs + for p.curTokenIs(lexer.DEDENT) { + if p.indentLevel < startLevel { + break + } + p.nextToken() + } + if p.curTokenIs(lexer.EOF) || p.curTokenIs(lexer.RESULT) { + break + } + stmt := p.parseStatement() + if stmt != nil { + fn.Body = append(fn.Body, stmt) + } + if !p.curTokenIs(lexer.NEWLINE) && !p.curTokenIs(lexer.DEDENT) && !p.curTokenIs(lexer.EOF) && !p.curTokenIs(lexer.RESULT) { + p.nextToken() + } } // Parse RESULT expression(s) — comma-separated for multi-result functions @@ -2468,7 +2564,7 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { if !p.expectPeek(lexer.RPAREN) { return nil } - case lexer.MINUS: + case lexer.MINUS, lexer.MINUS_KW: token := p.curToken p.nextToken() left = &ast.UnaryExpr{ @@ -2493,15 +2589,21 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { Right: p.parseExpression(PREFIX), } case lexer.LBRACKET: - // Slice expression: [arr FROM start FOR length] + // Slice expression: [arr FROM start FOR length] or [arr FOR length] lbracket := p.curToken p.nextToken() // move past [ arrayExpr := p.parseExpression(LOWEST) - if !p.expectPeek(lexer.FROM) { - return nil + var startExpr ast.Expression + if p.peekTokenIs(lexer.FOR) { + // [arr FOR length] shorthand — start is 0 + startExpr = &ast.IntegerLiteral{Token: lexer.Token{Type: lexer.INT, Literal: "0"}, Value: 0} + } else { + if !p.expectPeek(lexer.FROM) { + return nil + } + p.nextToken() // move past FROM + startExpr = p.parseExpression(LOWEST) } - p.nextToken() // move past FROM - startExpr := p.parseExpression(LOWEST) if !p.expectPeek(lexer.FOR) { return nil } diff --git a/preproc/preproc.go b/preproc/preproc.go index dd3ce87..b0faea3 100644 --- a/preproc/preproc.go +++ b/preproc/preproc.go @@ -36,6 +36,7 @@ type Preprocessor struct { includePaths []string errors []string processing map[string]bool // absolute paths currently being processed (circular include detection) + included map[string]bool // absolute paths already included (prevent duplicate inclusion) } // New creates a new Preprocessor with the given options. @@ -43,6 +44,7 @@ func New(opts ...Option) *Preprocessor { pp := &Preprocessor{ defines: map[string]string{}, processing: map[string]bool{}, + included: map[string]bool{}, } // Predefined symbols pp.defines["TARGET.BITS.PER.WORD"] = "64" @@ -219,6 +221,15 @@ func (pp *Preprocessor) resolveAndInclude(rest string, baseDir string) (string, return "", fmt.Errorf("cannot find included file %q", filename) } + // Skip files that have already been included (prevent duplicate definitions) + absPath, err := filepath.Abs(resolved) + if err == nil && pp.included[absPath] { + return "", nil + } + if err == nil { + pp.included[absPath] = true + } + return pp.ProcessFile(resolved) }