@@ -1631,16 +1631,27 @@ extension Parser on ParserState {
16311631
16321632 List <ResultColumn > _resultColumns () {
16331633 final columns = < ResultColumn > [];
1634+ var isFirst = true ;
1635+
16341636 do {
1635- columns.add (_resultColumn ());
1637+ // While there must be at least one column, making the first optional
1638+ // allows for better error recovery in queries like `SELECT FROM ...`.
1639+ final column = _resultColumn (optional: isFirst);
1640+ if (column == null ) {
1641+ errors.add (ParsingError (_peek, 'Expected a result column here.' ));
1642+ break ;
1643+ }
1644+
1645+ columns.add (column);
1646+ isFirst = false ;
16361647 } while (_matchOne (TokenType .comma));
16371648
16381649 return columns;
16391650 }
16401651
16411652 /// Parses a [ResultColumn] or throws if none is found.
16421653 /// https://www.sqlite.org/syntax/result-column.html
1643- ResultColumn _resultColumn () {
1654+ ResultColumn ? _resultColumn ({ bool optional = false } ) {
16441655 if (_matchOne (TokenType .star)) {
16451656 return StarResultColumn (null )..setSpan (_previous, _previous);
16461657 }
@@ -1673,11 +1684,14 @@ extension Parser on ParserState {
16731684 Expression expr;
16741685
16751686 {
1676- final parser = _ExpressionParser (this , allowResultColumn: true );
1687+ final parser =
1688+ _ExpressionParser (this , allowResultColumn: true , optional: optional);
16771689 try {
16781690 expr = parser._or ();
16791691 } on _ParsedResultColumn catch (e) {
16801692 return e._resultColumn;
1693+ } on _NoExpressionFound {
1694+ return null ;
16811695 }
16821696 }
16831697
@@ -2716,7 +2730,15 @@ final class _ExpressionParser extends ParserState {
27162730 /// instead of returning regularly if this is enabled.
27172731 final bool allowResultColumn;
27182732
2719- _ExpressionParser (super .state, {this .allowResultColumn = false })
2733+ /// Whether the expression being parsed is optional, meaning that it's
2734+ /// acceptable for no expression to be parsed too.
2735+ ///
2736+ /// The parser throws a [_NoExpressionFound] exception in this case instead of
2737+ /// emitting a syntax error.
2738+ final bool optional;
2739+
2740+ _ExpressionParser (super .state,
2741+ {this .allowResultColumn = false , this .optional = false })
27202742 : super .fromParent ();
27212743
27222744 /// Parses an expression of the form `a <T> b` , where `<T>` is in [types] and
@@ -3066,6 +3088,10 @@ final class _ExpressionParser extends ParserState {
30663088 return _referenceOrFunctionCall ();
30673089 }
30683090
3091+ if (optional) {
3092+ throw const _NoExpressionFound ();
3093+ }
3094+
30693095 if (_peek is KeywordToken ) {
30703096 // Improve error messages for possible function calls, https://github.com/simolus3/drift/discussions/2277
30713097 final (_, next) = tokens.keywordLookahead2 ();
@@ -3240,6 +3266,10 @@ final class _ParsedResultColumn implements Exception {
32403266 _ParsedResultColumn (this ._resultColumn);
32413267}
32423268
3269+ final class _NoExpressionFound implements Exception {
3270+ const _NoExpressionFound ();
3271+ }
3272+
32433273extension on List <Token > {
32443274 String get lexeme => first.span.expand (last.span).text;
32453275}
0 commit comments