@@ -233,6 +233,13 @@ func (p *parser) tabularExpr() (*TabularExpr, error) {
233233 expr .Operators = append (expr .Operators , op )
234234 }
235235 finalError = joinErrors (finalError , err )
236+ case "render" :
237+ op , err := opParser .renderOperator (pipeToken , operatorName )
238+ if op != nil {
239+ expr .Operators = append (expr .Operators , op )
240+ }
241+ finalError = joinErrors (finalError , err )
242+
236243 default :
237244 finalError = joinErrors (finalError , & parseError {
238245 source : opParser .source ,
@@ -495,6 +502,110 @@ func (p *parser) extendOperator(pipe, keyword Token) (*ExtendOperator, error) {
495502 }
496503}
497504
505+ func (p * parser ) renderOperator (pipe , keyword Token ) (* RenderOperator , error ) {
506+ op := & RenderOperator {
507+ Pipe : pipe .Span ,
508+ Keyword : keyword .Span ,
509+ With : nullSpan (),
510+ Lparen : nullSpan (),
511+ Rparen : nullSpan (),
512+ }
513+
514+ // Parse chart type (required)
515+ chartType , err := p .ident ()
516+ if err != nil {
517+ return op , & parseError {
518+ source : p .source ,
519+ span : keyword .Span ,
520+ err : fmt .Errorf ("expected chart type after render, got %v" , err ),
521+ }
522+ }
523+ op .ChartType = chartType
524+
525+ // Look for optional "with" clause
526+ tok , ok := p .next ()
527+ if ! ok {
528+ return op , nil
529+ }
530+
531+ if tok .Kind != TokenIdentifier || tok .Value != "with" {
532+ p .prev ()
533+ return op , nil
534+ }
535+ op .With = tok .Span
536+
537+ // Parse opening parenthesis
538+ tok , _ = p .next ()
539+ if tok .Kind != TokenLParen {
540+ return op , & parseError {
541+ source : p .source ,
542+ span : tok .Span ,
543+ err : fmt .Errorf ("expected '(' after with, got %s" , formatToken (p .source , tok )),
544+ }
545+ }
546+ op .Lparen = tok .Span
547+
548+ // Parse properties
549+ for {
550+ prop , err := p .renderProperty ()
551+ if err != nil {
552+ return op , makeErrorOpaque (err )
553+ }
554+ if prop != nil {
555+ op .Props = append (op .Props , prop )
556+ }
557+
558+ // Check for comma or closing parenthesis
559+ tok , _ = p .next ()
560+ if tok .Kind == TokenRParen {
561+ op .Rparen = tok .Span
562+ break
563+ }
564+ if tok .Kind != TokenComma {
565+ return op , & parseError {
566+ source : p .source ,
567+ span : tok .Span ,
568+ err : fmt .Errorf ("expected ',' or ')', got %s" , formatToken (p .source , tok )),
569+ }
570+ }
571+ }
572+
573+ return op , nil
574+ }
575+
576+ func (p * parser ) renderProperty () (* RenderProperty , error ) {
577+ prop := & RenderProperty {
578+ Assign : nullSpan (),
579+ }
580+
581+ // Parse property name
582+ name , err := p .ident ()
583+ if err != nil {
584+ return nil , err
585+ }
586+ prop .Name = name
587+
588+ // Parse equals sign
589+ tok , _ := p .next ()
590+ if tok .Kind != TokenAssign {
591+ return nil , & parseError {
592+ source : p .source ,
593+ span : tok .Span ,
594+ err : fmt .Errorf ("expected '=' after property name, got %s" , formatToken (p .source , tok )),
595+ }
596+ }
597+ prop .Assign = tok .Span
598+
599+ // Parse property value
600+ value , err := p .expr ()
601+ if err != nil {
602+ return nil , err
603+ }
604+ prop .Value = value
605+
606+ return prop , nil
607+ }
608+
498609func (p * parser ) extendColumn () (* ExtendColumn , error ) {
499610 restorePos := p .pos
500611
0 commit comments