PRQL/prql · error · Error

Expected , but didn't find anything before the end.

Error message

Expected {expected_str}, but didn't find anything before the end.

What it means

When the parser expected a construct but reached end-of-input, `rich_error_to_error` builds a `Reason::Simple` with this message instead of `Expected X, found Y`. It means the query ended abruptly in the middle of a construct.

Solutions

  1. Complete the unfinished expression or close the open bracket at the reported location
  2. Check the file/stdin wasn't truncated (compare against source)
  3. Avoid a trailing `|` with nothing after it

Example fix

// before
from employees | filter
// after
from employees | filter age > 30
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try { compile(prql) } catch (e) { if (String(e).includes("didn't find anything before the end")) { checkUnbalancedBrackets(prql) } else { throw e } }

Prevention

When it happens

Trigger: Truncated input: unclosed parenthesis/bracket, dangling pipe at end of file, incomplete function definition, or empty argument at EOF passed to the parser.

Common situations: File cut off during save; heredoc/stdin piping lost trailing lines; half-typed query in watch mode; copy-paste that dropped the last line.

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/ef5f7b1429070a7d. Report an issue: GitHub.

Appendix: source

Thrown at prqlc/prqlc-parser/src/parser/perror.rs:72

                let mut expected_strs = expected_strs;
                expected_strs.sort();

                let expected_str = match expected_strs.len() {
                    1 => expected_strs[0].clone(),
                    2 => expected_strs.join(" or "),
                    _ => {
                        let last = expected_strs.pop().unwrap();
                        format!("one of {} or {last}", expected_strs.join(", "))
                    }
                };

                match found {
                    Some(_) => Error::new(Reason::Expected {
                        who: None,
                        expected: expected_str,
                        found: found_str,
                    }),
                    None => Error::new(Reason::Simple(format!(
                        "Expected {expected_str}, but didn't find anything before the end."
                    ))),
                }
            }
        }
        RichReason::Custom(msg) => Error::new_simple(msg.to_string()),
    };

    error.with_span(Some(span))
}

impl<'a> From<Rich<'a, crate::lexer::lr::Token, Span>> for Error {
    fn from(rich: Rich<'a, crate::lexer::lr::Token, Span>) -> Error {
        rich_error_to_error(
            *rich.span(),
            rich.reason(),
            |token| format!("{}", token.kind),
            |token| matches!(token.kind, TokenKind::NewLine | TokenKind::Start),

View on GitHub (pinned to e164e249b9)