@@ -176,6 +176,11 @@ function tokenize(input: string): Token[] {
176176 if ( ch === ")" ) { tokens . push ( { type : TokenType . RPAREN , value : ch , pos } ) ; i ++ ; continue ; }
177177 if ( ch === "[" ) { tokens . push ( { type : TokenType . LBRACKET , value : ch , pos } ) ; i ++ ; continue ; }
178178 if ( ch === "]" ) { tokens . push ( { type : TokenType . RBRACKET , value : ch , pos } ) ; i ++ ; continue ; }
179+ // Curly braces as alternative to square brackets (LaTeX-style σ_{cond})
180+ if ( ch === "{" ) { tokens . push ( { type : TokenType . LBRACKET , value : ch , pos } ) ; i ++ ; continue ; }
181+ if ( ch === "}" ) { tokens . push ( { type : TokenType . RBRACKET , value : ch , pos } ) ; i ++ ; continue ; }
182+ // Underscore: skip silently (allows LaTeX-style σ_{cond} — the _ is just decoration)
183+ if ( ch === "_" ) { i ++ ; continue ; }
179184 if ( ch === "," ) { tokens . push ( { type : TokenType . COMMA , value : ch , pos } ) ; i ++ ; continue ; }
180185 if ( ch === "." ) { tokens . push ( { type : TokenType . DOT , value : ch , pos } ) ; i ++ ; continue ; }
181186 if ( ch === ";" ) { tokens . push ( { type : TokenType . SEMICOLON , value : ch , pos } ) ; i ++ ; continue ; }
@@ -531,60 +536,136 @@ class Parser {
531536 return left ;
532537 }
533538
539+ /**
540+ * Check if the next token starts a subscript (bracket or implicit).
541+ * Returns true if it's `[`, `{`, or any non-`(` token that could begin
542+ * an implicit subscript (i.e., the subscript content before `(`).
543+ */
544+ private hasSubscript ( ) : boolean {
545+ return this . peek ( ) . type === TokenType . LBRACKET || this . peek ( ) . type !== TokenType . LPAREN ;
546+ }
547+
548+ /**
549+ * Parse a subscript that may be:
550+ * 1. Bracketed: `[...]` or `{...}`
551+ * 2. Implicit: tokens up to the next unmatched `(`
552+ *
553+ * For implicit subscripts, we temporarily inject LBRACKET/RBRACKET
554+ * around the subscript tokens so the same parsing logic can be reused.
555+ */
556+ private parseSubscriptCondition ( ) : ConditionNode {
557+ if ( this . peek ( ) . type === TokenType . LBRACKET ) {
558+ return this . parseBracketedCondition ( ) ;
559+ }
560+ // Implicit subscript: parse condition tokens until we hit LPAREN
561+ // We parse directly — the condition ends when we encounter LPAREN at depth 0
562+ const cond = this . parseOrCondition ( ) ;
563+ return cond ;
564+ }
565+
566+ private parseSubscriptColumns ( ) : ColumnRef [ ] {
567+ if ( this . peek ( ) . type === TokenType . LBRACKET ) {
568+ this . expect ( TokenType . LBRACKET ) ;
569+ const cols = this . parseColumnList ( ) ;
570+ this . expect ( TokenType . RBRACKET ) ;
571+ return cols ;
572+ }
573+ // Implicit: parse column list until LPAREN
574+ return this . parseColumnList ( ) ;
575+ }
576+
577+ private parseSubscriptRenameMappings ( ) : RenameMapping [ ] {
578+ if ( this . peek ( ) . type === TokenType . LBRACKET ) {
579+ this . expect ( TokenType . LBRACKET ) ;
580+ const mappings = this . parseRenameMappings ( ) ;
581+ this . expect ( TokenType . RBRACKET ) ;
582+ return mappings ;
583+ }
584+ return this . parseRenameMappings ( ) ;
585+ }
586+
587+ private parseSubscriptGroupSpec ( ) : { groupBy : ColumnRef [ ] ; aggregates : AggregateExpr [ ] } {
588+ if ( this . peek ( ) . type === TokenType . LBRACKET ) {
589+ this . expect ( TokenType . LBRACKET ) ;
590+ const spec = this . parseGroupSpec ( ) ;
591+ this . expect ( TokenType . RBRACKET ) ;
592+ return spec ;
593+ }
594+ return this . parseGroupSpec ( ) ;
595+ }
596+
597+ private parseSubscriptSortColumns ( ) : SortColumn [ ] {
598+ if ( this . peek ( ) . type === TokenType . LBRACKET ) {
599+ this . expect ( TokenType . LBRACKET ) ;
600+ const cols = this . parseSortColumns ( ) ;
601+ this . expect ( TokenType . RBRACKET ) ;
602+ return cols ;
603+ }
604+ return this . parseSortColumns ( ) ;
605+ }
606+
534607 private parseUnaryExpr ( ) : RANode {
535608 const t = this . peek ( ) . type ;
536609
537610 if ( t === TokenType . SIGMA ) {
538611 this . advance ( ) ;
539- const condition = this . parseBracketedCondition ( ) ;
540- this . expect ( TokenType . LPAREN ) ;
541- const relation = this . parseUnionExpr ( ) ;
542- this . expect ( TokenType . RPAREN ) ;
543- return { type : "selection" , condition, relation } ;
612+ if ( this . hasSubscript ( ) ) {
613+ const condition = this . parseSubscriptCondition ( ) ;
614+ this . expect ( TokenType . LPAREN ) ;
615+ const relation = this . parseUnionExpr ( ) ;
616+ this . expect ( TokenType . RPAREN ) ;
617+ return { type : "selection" , condition, relation } ;
618+ }
619+ // σ(R) with no subscript — error
620+ throw new RAError ( "Selection (σ) requires a condition — use σ[condition](R) or σ condition (R)" ) ;
544621 }
545622
546623 if ( t === TokenType . PI ) {
547624 this . advance ( ) ;
548- this . expect ( TokenType . LBRACKET ) ;
549- const columns = this . parseColumnList ( ) ;
550- this . expect ( TokenType . RBRACKET ) ;
551- this . expect ( TokenType . LPAREN ) ;
552- const relation = this . parseUnionExpr ( ) ;
553- this . expect ( TokenType . RPAREN ) ;
554- return { type : "projection" , columns, relation } ;
625+ if ( this . hasSubscript ( ) ) {
626+ const columns = this . parseSubscriptColumns ( ) ;
627+ this . expect ( TokenType . LPAREN ) ;
628+ const relation = this . parseUnionExpr ( ) ;
629+ this . expect ( TokenType . RPAREN ) ;
630+ return { type : "projection" , columns, relation } ;
631+ }
632+ throw new RAError ( "Projection (π) requires column list — use π[cols](R) or π cols (R)" ) ;
555633 }
556634
557635 if ( t === TokenType . RHO ) {
558636 this . advance ( ) ;
559- this . expect ( TokenType . LBRACKET ) ;
560- const mappings = this . parseRenameMappings ( ) ;
561- this . expect ( TokenType . RBRACKET ) ;
562- this . expect ( TokenType . LPAREN ) ;
563- const relation = this . parseUnionExpr ( ) ;
564- this . expect ( TokenType . RPAREN ) ;
565- return { type : "rename" , mappings, relation } ;
637+ if ( this . hasSubscript ( ) ) {
638+ const mappings = this . parseSubscriptRenameMappings ( ) ;
639+ this . expect ( TokenType . LPAREN ) ;
640+ const relation = this . parseUnionExpr ( ) ;
641+ this . expect ( TokenType . RPAREN ) ;
642+ return { type : "rename" , mappings, relation } ;
643+ }
644+ throw new RAError ( "Rename (ρ) requires mappings — use ρ[old→new](R) or ρ old→new (R)" ) ;
566645 }
567646
568647 if ( t === TokenType . GAMMA ) {
569648 this . advance ( ) ;
570- this . expect ( TokenType . LBRACKET ) ;
571- const { groupBy, aggregates } = this . parseGroupSpec ( ) ;
572- this . expect ( TokenType . RBRACKET ) ;
573- this . expect ( TokenType . LPAREN ) ;
574- const relation = this . parseUnionExpr ( ) ;
575- this . expect ( TokenType . RPAREN ) ;
576- return { type : "group" , groupBy, aggregates, relation } ;
649+ if ( this . hasSubscript ( ) ) {
650+ const { groupBy, aggregates } = this . parseSubscriptGroupSpec ( ) ;
651+ this . expect ( TokenType . LPAREN ) ;
652+ const relation = this . parseUnionExpr ( ) ;
653+ this . expect ( TokenType . RPAREN ) ;
654+ return { type : "group" , groupBy, aggregates, relation } ;
655+ }
656+ throw new RAError ( "Grouping (γ) requires specification — use γ[groupCols; AGG(col)](R)" ) ;
577657 }
578658
579659 if ( t === TokenType . TAU ) {
580660 this . advance ( ) ;
581- this . expect ( TokenType . LBRACKET ) ;
582- const columns = this . parseSortColumns ( ) ;
583- this . expect ( TokenType . RBRACKET ) ;
584- this . expect ( TokenType . LPAREN ) ;
585- const relation = this . parseUnionExpr ( ) ;
586- this . expect ( TokenType . RPAREN ) ;
587- return { type : "sort" , columns, relation } ;
661+ if ( this . hasSubscript ( ) ) {
662+ const columns = this . parseSubscriptSortColumns ( ) ;
663+ this . expect ( TokenType . LPAREN ) ;
664+ const relation = this . parseUnionExpr ( ) ;
665+ this . expect ( TokenType . RPAREN ) ;
666+ return { type : "sort" , columns, relation } ;
667+ }
668+ throw new RAError ( "Sort (τ) requires column list — use τ[col](R) or τ col (R)" ) ;
588669 }
589670
590671 if ( t === TokenType . DELTA ) {
0 commit comments