@export SahaParser = parsed:SahaStatementNodes $; @no_skip_ws SahaStatementNodes = { statements:SahaStatement }; SahaStatement = @:SlotFor | @:SlotIf | @:SlotExpressionNode | @:UnicodeText ; @position @no_skip_ws SlotIf = start:SlotIfStart body:SahaStatementNodes {else_if:SlotElseIf} [else:SlotElse] end:SlotEndIf; SlotIfStart = left:SlotL "if" cond:ExpressionNode right:SlotR; SlotElseIf = left:SlotL ("else" "if" | "else-if") cond:ExpressionNode right:SlotR body:SahaStatementNodes; SlotElse = left:SlotL "else" right:SlotR body:SahaStatementNodes; SlotEndIf = left:SlotL EndIf right:SlotR; EndIf = "end-if" | "endif" | "end"; @position @no_skip_ws SlotFor = start:SlotForStart body:SahaStatementNodes [else:SlotElse] end:SlotEndFor; SlotForStart = left:SlotL "for" pattern:ValueNode "in" expression:ExpressionNode right:SlotR; SlotEndFor = left:SlotL EndFor right:SlotR; @no_skip_ws EndFor = "end" ['-' "for"]; @check(crate::utils::check_slot_expression) SlotExpressionNode = left:SlotL e:ExpressionNode right:SlotR; @position ExpressionNode = head:TermNode {infix:ExpressionNodeInfix}; ExpressionNodeInfix = op:InfixOp value:TermNode; @position TermNode = {prefix:PrefixOp} term:ValueNode {suffix:Suffix}; @string PrefixOp = "&" | "+" | "-" | "*"; @string InfixOp = "+" | "-" | "==" | "<=" | "<" | ">=" | ">"; Suffix = @:SuffixOp | @:DotCall; @string SuffixOp = "!"; DotCall = '.' call:IdentifierNode [FunctionArgs]; FunctionArgs = '(' args:ExpressionNode {[','] args:ExpressionNode} [','] ')'; ValueNode = @:BooleanNode | @:DecimalNode | @:IntegerNode | @:StringNode | @:NamespaceNode ; @no_skip_ws @position SlotL = "{%" [trim:TrimMode]; @no_skip_ws @position SlotR = [trim:TrimMode] "%}"; @char TrimMode = '_' | '-' | '~' | '=' | '!'; @position NamespaceNode = path:IdentifierNode ['::' path:IdentifierNode]; @string @position @no_skip_ws IdentifierNode = (XID_START | '_' | '-') {XID_CONTINUE | '-'}; @char @check(unicode_ident::is_xid_start) XID_START = char; @char @check(unicode_ident::is_xid_continue) XID_CONTINUE = char; @string @position BooleanNode = "true" | "false"; @position @string IntegerNode = {'0'..'9'}+ ; @position @string DecimalNode = {'0'..'9'}+ ['.' {'0'..'9'}+] [('**'|'e'|'E') ['+'|'-'] {'0'..'9'}+] | '.' {'0'..'9'}+ ; @position @string UnicodeText = {!("{%" | "{#") char}+; @position @no_skip_ws StringNode = dq:DQ {!DQ body:StringItem } DQ | sq:SQ {!SQ body:StringItem } SQ ; @no_skip_ws StringItem = @:EscapeUnicode | @:EscapeOther | @:char ; SQ = "'"; DQ = '"'; EscapeOther = '\\' @:char; EscapeUnicode = '\\' 'u'; @no_skip_ws Comment = left:CommentL {!CommentR|char}+ right:CommentR; @no_skip_ws @position CommentL = "{#" [trim:TrimMode]; @no_skip_ws @position CommentR = [trim:TrimMode] "#}";