diff --git a/compiler/pp/command.go b/compiler/pp/command.go index 5b91d94..ecf28c6 100644 --- a/compiler/pp/command.go +++ b/compiler/pp/command.go @@ -1326,18 +1326,23 @@ func tokenizeLine(line string) []string { } switch line[i] { - case ',', '(', ')', '[', ']': + case ',', '(', ')', '[', ']', '{', '}': tokens = append(tokens, string(line[i])) i++ continue } - // Word — stop at whitespace, brackets, parens, comma, quotes. + // Word — stop at whitespace, brackets, parens, braces, comma, quotes. + // Braces split out so codeblock literals `{|| ... }` and array + // literals `{1, 2}` balance correctly during capture: without this + // `{||` fuses into one word that fails the depth-tracker's exact + // `{` match, while a trailing `}` token (alone before `)`) does + // match `case "}":` and falsely decrements depth. start := i for i < len(line) { c := line[i] if c == ' ' || c == '\t' || c == ',' || c == '(' || c == ')' || - c == '[' || c == ']' || c == '"' || c == '\'' { + c == '[' || c == ']' || c == '{' || c == '}' || c == '"' || c == '\'' { break } i++ diff --git a/compiler/pp/pp.go b/compiler/pp/pp.go index ba5d062..d689899 100644 --- a/compiler/pp/pp.go +++ b/compiler/pp/pp.go @@ -212,6 +212,51 @@ func (pp *Preprocessor) processLines(filename, source string, depth int) string continue // skip lines in inactive #ifdef sections } + // `;`-continuation in user code. Join physical lines ending in + // a top-level `;` (paren/string-balanced) so multi-line + // `#command` invocations like + // TEST t004 STATIC s_once := NIL ; + // INIT ... ; + // CODE x := S_C + // match the rule pattern as a single logical line. Without this + // the pattern only sees the first physical line, fails to match, + // and the residual `TEST t004 STATIC ...` falls through to the + // parser as a bare expression — silently merged into the + // previous function's body, producing duplicate static decls + // tagged with the wrong function name. Insert blank fillers + // for each consumed line so post-PP source line numbers still + // align with the original file for error reporting. + consumedFiller := 0 + for i+1 < len(lines) { + t := stripTrailingLineComment(strings.TrimRight(line, " \t")) + t = strings.TrimRight(t, " \t") + if !strings.HasSuffix(t, ";") { + break + } + // Strip block comments from the next line *the same way* + // the main loop will. Without this, an inline `/* ... */` + // at the end of a continuation row hides the trailing + // `;` from our HasSuffix check below — the joined chain + // truncates after just one row, leaving a dangling comma + // at end of line that the parser later mis-reports. + rawNext := lines[i+1] + strippedNext := stripBlockComments(rawNext, &inBlockComment) + nextTrim := strings.TrimSpace(strippedNext) + if strings.HasPrefix(nextTrim, "#") { + inBlockComment = false // not actually consumed + break + } + // Don't fold across an unterminated `/*` — the rest of + // the file would be treated as code by the join. + if inBlockComment { + inBlockComment = false + break + } + line = strings.TrimSuffix(t, ";") + " " + nextTrim + i++ + consumedFiller++ + } + // Apply #command/#translate rules if len(pp.commands) > 0 || len(pp.translates) > 0 { line = pp.applyRules(line) @@ -223,6 +268,9 @@ func (pp *Preprocessor) processLines(filename, source string, depth int) string } result = append(result, line) + for k := 0; k < consumedFiller; k++ { + result = append(result, "") + } } if len(ifStack) > 0 { @@ -465,6 +513,31 @@ func (pp *Preprocessor) resolveInclude(currentFile, inclFile string) string { return "" } +// stripTrailingLineComment removes a trailing `// ...` from s, but only +// if the `//` sits outside any string literal. Block comments are +// already handled by stripBlockComments before continuation joining. +func stripTrailingLineComment(s string) string { + inStr := byte(0) + for i := 0; i < len(s); i++ { + c := s[i] + if inStr != 0 { + if c == inStr { + inStr = 0 + } + continue + } + switch c { + case '"', '\'': + inStr = c + case '/': + if i+1 < len(s) && s[i+1] == '/' { + return s[:i] + } + } + } + return s +} + // hasTopLevelSemi reports whether s contains a `;` outside of any // string literal or paren/bracket/brace nesting. Used by applyRules // to decide whether a line carries multiple PRG statements.