Stirling-Tools/Stirling-PDF · warning · Error

Expected )

Error message

Expected )

What it means

Thrown by parsePrimary when an opening '(' was consumed and a full disjunction parsed, but the next non-whitespace character is not ')'. This indicates an unbalanced or malformed parenthesized sub-expression. As with all ExpressionParser errors, the public parseSelection() swallows it via CSV fallback; it only surfaces through parseSelectionWithDiagnostics in strict mode.

Source

Thrown at frontend/editor/src/core/utils/bulkselection/parseSelection.ts:204

      return complement(inner, this.max);
    }
    // Word-form NOT
    if (this.tryConsumeNot()) {
      const inner = this.parseUnary();
      return complement(inner, this.max);
    }
    return this.parsePrimary();
  }

  private parsePrimary(): Set<number> {
    this.skipWs();

    // Parenthesized expression: '(' expression ')'
    if (this.peek("(")) {
      this.consume(1);
      const inner = this.parseDisjunction();
      this.skipWs();
      if (!this.peek(")")) throw new Error("Expected )");
      this.consume(1);
      return inner;
    }

    // Keywords: even / odd
    const keyword = this.tryReadKeyword();
    if (keyword) {
      if (keyword === "even") return this.buildEven();
      if (keyword === "odd") return this.buildOdd();
    }

    // Progression: k n ( +/- c )?
    const progression = this.tryReadProgression();
    if (progression) {
      return this.buildProgression(progression.k, progression.c);
    }

    // Number or Range

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Count and balance parentheses in the input — every '(' needs a matching ')'.
  2. Use round parentheses only; '[' and '{' are not in the grammar.
  3. Enable strict mode only where you want to reject ambiguous input; otherwise rely on the CSV fallback and the returned warning.
  4. Provide a live syntax hint/preview so users see matching before submitting.

Example fix

// before
parseSelectionWithDiagnostics('(1-5 & odd', max, { strict: true })
// after
parseSelectionWithDiagnostics('(1-5 & odd)', max, { strict: true })
Defensive patterns

Strategy: validation

Validate before calling

function isBalancedParens(input: string): boolean {
  let depth = 0;
  for (const ch of input) {
    if (ch === '(') depth++;
    else if (ch === ')') depth--;
    if (depth < 0) return false;
  }
  return depth === 0;
}

if (!isBalancedParens(input)) {
  showUser('Parentheses are unbalanced.');
}

Type guard

function usesOnlyRoundParens(input: string): boolean {
  return !/[\[\]{}]/.test(input);
}

Try / catch

try {
  const { pages } = parseSelectionWithDiagnostics(input, maxPages, { strict: true });
} catch (e) {
  if (e instanceof Error && e.message === 'Expected )') {
    showUser('Missing a closing parenthesis.');
  } else { throw e; }
}

Prevention

When it happens

Trigger: Input like '(1-5' (missing close paren), '(1-5 , 7-10' (missing close paren before more tokens), or '(1-5] ' (wrong bracket type). Any '(' that is not matched by a ')' at the point the parser expects.

Common situations: User typed a parenthesized expression and forgot the closing ')', used square/curly brackets, or an autocomplete/template inserted an unbalanced paren. Strict mode surfaces it; non-strict mode falls back to CSV (which ignores parentheses entirely).

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/29039a52399cb582. Report an issue: GitHub.