@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] "#}";