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
- Look at the reported position range and fix or remove the unexpected character
- Replace SQL-isms with PRQL equivalents (e.g. `derive`, `select` instead of `SELECT *`)
- 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
- Sanitize pasted text: replace smart quotes, strip BOM/control chars
- Write PRQL syntax, not SQL fragments
- Run queries through prqlc fmt to catch stray characters early
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
- Expected , found
- Currently `lex` only works with a single source, but found…
- Expected , but didn't find anything before the end.
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)