Stirling-Tools/Stirling-PDF · warning · Error

Expected primary

Error message

Expected primary

What it means

Thrown by parsePrimary when the current token is not any recognizable primary element: not '(', not a keyword (even/odd), not a progression (kn+c), and not a leading number. The parser has exhausted all primary alternatives, so it cannot proceed. As with other parser errors, only surfaces in strict mode; otherwise CSV fallback applies.

Source

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

      return this.buildProgression(progression.k, progression.c);
    }

    // Number or Range
    const num = this.tryReadNumber();
    if (num !== null) {
      this.skipWs();
      if (this.peek("-")) {
        // Range
        this.consume(1);
        this.skipWs();
        const end = this.readRequiredNumber();
        return this.buildRange(num, end);
      }
      return this.buildSingleton(num);
    }

    // If nothing matched, error
    throw new Error("Expected primary");
  }

  private buildSingleton(n: number): Set<number> {
    const set = new Set<number>();
    if (n >= 1 && n <= this.max) set.add(n);
    return set;
  }

  private buildRange(a: number, b: number): Set<number> {
    const set = new Set<number>();
    let start = a,
      end = b;
    if (!Number.isFinite(start) || !Number.isFinite(end)) return set;
    if (start > end) [start, end] = [end, start];
    start = Math.max(1, start);
    end = Math.min(this.max, end);
    for (let i = start; i <= end; i++) set.add(i);
    return set;

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Ensure each operator ('&', '|', ',', '-') is surrounded by valid primaries, not by another operator.
  2. Start the expression with a number, keyword (even/odd), '(', or '!'.
  3. Remove stray symbols; the grammar accepts only the documented operators.
  4. Use the diagnostics warning (non-strict) to locate the malformed token before enabling strict rejection.

Example fix

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

Strategy: validation

Validate before calling

function hasNoDoubledOperators(input: string): boolean {
  return !/[&|]\s*[&|]/.test(input) && !/^[&|]/.test(input);
}

if (!hasNoDoubledOperators(input)) {
  showUser('Remove doubled or leading operators.');
}

Type guard

function startsWithValidPrimary(input: string): boolean {
  const trimmed = input.trimStart();
  return /^[\d(!]|^(even|odd)\b/i.test(trimmed);
}

Try / catch

try {
  const { pages } = parseSelectionWithDiagnostics(input, maxPages, { strict: true });
} catch (e) {
  if (e instanceof Error && e.message === 'Expected primary') {
    showUser('Each part of the expression must be a number, range, keyword, or group.');
  } else { throw e; }
}

Prevention

When it happens

Trigger: Input like '& 5' (operator with no left operand at primary position), '1-5 & & 7' (double operator), '*5' (bare multiplication), or an expression starting with a symbol that is not '(' or '!'. Also a leading '&', '|', or '-' where a primary is expected after an operator.

Common situations: User typed a doubled operator, started the expression with an operator, or used a symbol not in the grammar at a position requiring a value. Pasted text containing special characters. Non-strict mode swallows this into the CSV fallback.

Related errors


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