PRQL/prql · error · Error

Unexpected at position ..

Error message

Unexpected {found_display} at position {char_start}..{char_end}

What it means

The PRQL lexer produces `Reason::Unexpected` when a character cannot start any valid token at a given position. The message reports the found text and the character range, wrapped with span info so the CLI can render a source-annotated diagnostic.

Solutions

  1. Look at the reported position range and fix or remove the unexpected character
  2. Replace SQL-isms with PRQL equivalents (e.g. `derive`, `select` instead of `SELECT *`)
  3. Check for invisible/smart-quote characters after pasting

Example fix

// before
from employees | filter salary > 100k?
// after
from employees | filter salary > 100000
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try { compile(prql) } catch (e) { if (e.reason === 'Unexpected') { showAtSpan(e.span, e.found) } else { throw e } }

Prevention

When it happens

Trigger: Any lexically invalid input: stray characters (e.g. `#` isn't a comment? actually `#` in wrong place), unterminated strings/quotes, unmatched brackets, or control characters inside a query fed to `prqlc compile` or the parser API.

Common situations: Copy-pasted SQL syntax into PRQL (e.g. `*` from `SELECT *` misuse), smart quotes from word processors, non-ASCII characters in identifiers, accidental keyboard input.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of PRQL/prql@e164e249b9 (2026-09-09). Data as JSON: /api/errors/20208fcb7f76020a. Report an issue: GitHub.

Appendix: source

Thrown at prqlc/prqlc-parser/src/lexer/mod.rs:52

        .skip(char_start)
        .take(char_end - char_start)
        .collect();

    // If found is empty, report as "end of input", otherwise wrap in quotes
    let found_display = if found.is_empty() {
        "end of input".to_string()
    } else {
        format!("'{}'", found)
    };

    // Create a new Error with the extracted information
    let error_source = format!(
        "Unexpected {} at position {}..{}",
        found_display, char_start, char_end
    );

    WithErrorInfo::with_span(
        Error::new(Reason::Unexpected {
            found: found_display,
        }),
        Some(crate::span::Span {
            start: char_start,
            end: char_end,
            source_id,
        }),
    )
    .with_source(ErrorSource::Lexer(error_source))
}

/// Lex PRQL into LR, returning the tokens if the whole source lexed and the
/// errors if it didn't. The lexer doesn't resume after an error, so tokens and
/// errors never come back together.
pub fn lex_source_recovery(source: &str, source_id: u16) -> (Option<Vec<Token>>, Vec<E>) {
    let result = lexer().parse(source).into_result();

    match result {

View on GitHub (pinned to e164e249b9)