From 409620f5331c9823405a6173f2fb9959a0f407ad Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 19 May 2025 13:37:12 -0700 Subject: [PATCH] More advanced template grammar Fixes https://feedback.yaak.app/p/cannot-escape-call-to-variable-in-json-body --- .../components/core/Editor/twig/highlight.ts | 13 +++- .../components/core/Editor/twig/twig.grammar | 71 ++++++++++++++++--- .../components/core/Editor/twig/twig.terms.ts | 15 ++-- src-web/components/core/Editor/twig/twig.ts | 24 ++++--- 4 files changed, 96 insertions(+), 27 deletions(-) diff --git a/src-web/components/core/Editor/twig/highlight.ts b/src-web/components/core/Editor/twig/highlight.ts index 0d3b3834..e33381ea 100644 --- a/src-web/components/core/Editor/twig/highlight.ts +++ b/src-web/components/core/Editor/twig/highlight.ts @@ -1,7 +1,14 @@ import { styleTags, tags as t } from '@lezer/highlight'; export const highlight = styleTags({ - TagOpen: t.tagName, - TagClose: t.tagName, - TagContent: t.keyword, + "${[": t.bracket, + "]}": t.bracket, + "(": t.bracket, + ")": t.bracket, + "=": t.bracket, + ",": t.bracket, + Tag: t.keyword, + Identifier: t.variableName, + ChainedIdentifier: t.variableName, + Boolean: t.bool, }); diff --git a/src-web/components/core/Editor/twig/twig.grammar b/src-web/components/core/Editor/twig/twig.grammar index 5706e200..6d31100d 100644 --- a/src-web/components/core/Editor/twig/twig.grammar +++ b/src-web/components/core/Editor/twig/twig.grammar @@ -1,17 +1,70 @@ -@top Template { (Tag | Text)* } - -@local tokens { - TagClose { "]}" } - @else TagContent +@precedence { + top, + mid, + lesser } -@skip { } { - TagOpen { "${[" } - Tag { TagOpen (TagContent)+ TagClose } +@top Template { (Tag | PlainText)* } + +@skip { space } + +Tag { "${[" expression* "]}" } + +commaSep { "" | content ("," content?)* } + +expression { + Function | + Assignment | + Identifier | + ChainedIdentifier | + basicType | + hashStructure } +basicType { + String | + Boolean | + Number +} + +functionParam { + Identifier | + Assignment | + basicType +} + +FunctionParamList { + "(" commaSep ")" +} + +Assignment { Identifier "=" expression } + +Function { (Identifier | ChainedIdentifier) !top FunctionParamList} + @tokens { - Text { ![$] Text? | "$" (@eof | ![{] Text?) } + PlainText { ![$] PlainText? | "$" (@eof | ![{[] PlainText?) } + + Identifier { $[a-zA-Z_\-0-9]+ } + ChainedIdentifier { Identifier ("." Identifier)+ } + + hashStructure { "{" | "}" | ":" | ","} + + Boolean { "true" | "false" } + String { ("'" (![\\'] | "\\" _)* "'"?) | ("\"" (![\\"] | "\\" _)* "\""?) } + Number { '-'? int frac? } + int { '0' | $[1-9] @digit* } + frac { '.' @digit+ } + + @precedence { PlainText, Boolean, Number, ChainedIdentifier, Identifier, space, String } + + space { $[ \t\n\r]+ } + + "${[" "]}" + "(" ")" + "=" + "," } @external propSource highlight from "./highlight" + +@detectDelim diff --git a/src-web/components/core/Editor/twig/twig.terms.ts b/src-web/components/core/Editor/twig/twig.terms.ts index d6bda1a3..5011c081 100644 --- a/src-web/components/core/Editor/twig/twig.terms.ts +++ b/src-web/components/core/Editor/twig/twig.terms.ts @@ -1,8 +1,13 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. export const Template = 1, - Tag = 2, - TagOpen = 3, - TagContent = 4, - TagClose = 5, - Text = 6 + Tag = 4, + Function = 5, + Identifier = 6, + ChainedIdentifier = 7, + FunctionParamList = 10, + Assignment = 11, + String = 13, + Boolean = 14, + Number = 15, + PlainText = 17 diff --git a/src-web/components/core/Editor/twig/twig.ts b/src-web/components/core/Editor/twig/twig.ts index 04452432..2d16ccf1 100644 --- a/src-web/components/core/Editor/twig/twig.ts +++ b/src-web/components/core/Editor/twig/twig.ts @@ -1,18 +1,22 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. -import {LRParser, LocalTokenGroup} from "@lezer/lr" +import {LRParser} from "@lezer/lr" import {highlight} from "./highlight" export const parser = LRParser.deserialize({ version: 14, - states: "!^QQOPOOOOOO'#C_'#C_OYOQO'#C^OOOO'#Cc'#CcQQOPOOOOOO'#Cd'#CdO_OQO,58xOOOO-E6a-E6aOOOO-E6b-E6bOOOO1G.d1G.d", - stateData: "g~OUROYPO~OSTO~OSTOTXO~O", - goto: "nXPPY^PPPbhTROSTQOSQSORVSQUQRWU", - nodeNames: "⚠ Template Tag TagOpen TagContent TagClose Text", - maxTerm: 10, + states: "$zQVQPOOOsQQO'#C`OOQO'#Cn'#CnQVQPOOO!fQQO'#CtOOQO'#Cw'#CwOzQQO'#CtOOQO'#Ct'#CtOOQO'#Co'#CoO!mQQO,58zOOQO,58z,58zOOQO-E6l-E6lO_QQO,59RO!tQQO'#CfOOQO,58{,58{OOQO-E6m-E6mOOQO1G.f1G.fOOQO1G.m1G.mO#VQSO'#CvOOQO'#Cv'#CvO#bQSO'#CuO#jQQO,59QO#oQSO'#CpO$TQSO,59aOOQO1G.l1G.lO$]QSO'#CtO$kQSO'#CtOOQO,59[,59[OOQO-E6n-E6nO$vQQO,59R", + stateData: "%e~OgOS~ORPOaQO~OUSOVUO]TO^TO_TOlVO~OQYO~P_OX]OQhXUhXVhX]hX^hX_hXlhX~O[[O~PzOQ`O~P_OUbO]TO^TO_TOWiP~O[mOWjX`jX~O`fOWiX~OWhO~OUbO]TO^TO_TOWdX`dX~O`fOWia~OX]O[mOWhX`hX~OX]OWhX`hX~OUiOVjO]TO^TO_TOlVO~Oa^_VUg]V~", + goto: "!|lPPPPmqPPPPw}PPPPPP!X!_!ePPP!k!s!v}TQORXVPX[mX^SUijWVPX[mTc]fQRORZRQXPR_XQgdRlgSWPXTa[mRe]Qd]Rkf", + nodeNames: "⚠ Template ]} ${[ Tag Function Identifier ChainedIdentifier ) ( FunctionParamList Assignment = String Boolean Number , PlainText", + maxTerm: 28, + nodeProps: [ + ["openedBy", 2,"${[",8,"("], + ["closedBy", 3,"]}",9,")"] + ], propSources: [highlight], skippedNodes: [0], - repeatNodeCount: 2, - tokenData: "#]~RTOtbtu!hu;'Sb;'S;=`!]<%lOb~gTU~Otbtuvu;'Sb;'S;=`!]<%lOb~yUO#ob#p;'Sb;'S;=`!]<%l~b~Ob~~!c~!`P;=`<%lb~!hOU~~!kVO#ob#o#p#Q#p;'Sb;'S;=`!]<%l~b~Ob~~!c~#TP!}#O#W~#]OY~", - tokenizers: [1, new LocalTokenGroup("b~RP#P#QU~XP#q#r[~aOT~~", 17, 4)], + repeatNodeCount: 3, + tokenData: "Fe~RzOX#uXY%OYZ%OZ]#u]^%O^p#upq%Oqr#urs%{st#utu+Ouw#uwx+vxy0^yz0tz|#u|}1[}!O1t!O!Q#u!Q!R6h!R![:S![!];_!]!_#u!_!`;u!`!c#u!c!}3Q!}#P#u#P#Q<]#Q#R#u#R#S3Q#S#T#u#T#Y3Q#Y#Z=_#Z#h3Q#h#iCu#i#o3Q#o#p;_#p#q#u#q#r;_#r;'S#u;'S;=`$s<%lO#uP#zTaPOt#utu$Zu;'S#u;'S;=`$s<%lO#uP$^VO!}#u#O#o#u#p;'S#u;'S;=`$s<%l~#u~O#u~~$yP$vP;=`<%l#uP%OOaP~%V[aPg~OX#uXY%OYZ%OZ]#u]^%O^p#upq%Oqt#utu$Zu;'S#u;'S;=`$s<%lO#u~&SXaP]~Or%{rs&ost%{tu'Vu#O%{#O#P)r#P;'S%{;'S;=`*x<%lO%{~&vTaP]~Ot#utu$Zu;'S#u;'S;=`$s<%lO#u~'[[]~Or%{rs&os!}%{!}#O(Q#O#P)r#P#o%{#o#p(Q#p;'S%{;'S;=`*x<%l~%{~O%{~~$y~(VV]~Or(Qrs(ls#O(Q#O#P(q#P;'S(Q;'S;=`)l<%lO(Q~(qO]~~(tRO;'S(Q;'S;=`(};=`O(Q~)SW]~Or(Qrs(ls#O(Q#O#P(q#P;'S(Q;'S;=`)l;=`<%l(Q<%lO(Q~)oP;=`<%l(Q~)wUaPOt%{tu'Vu;'S%{;'S;=`*Z;=`<%l(Q<%lO%{~*`W]~Or(Qrs(ls#O(Q#O#P(q#P;'S(Q;'S;=`)l;=`<%l%{<%lO(Q~*{P;=`<%l%{~+RWO!}#u#O#o#u#o#p+k#p;'S#u;'S;=`$s<%l~#u~O#u~~$y~+nP!}#O+q~+vOR~~+}XaP]~Ot+vtu,juw+vwx&ox#O+v#O#P/Q#P;'S+v;'S;=`0W<%lO+v~,o[]~Ow+vwx&ox!}+v!}#O-e#O#P/Q#P#o+v#o#p-e#p;'S+v;'S;=`0W<%l~+v~O+v~~$y~-jV]~Ow-ewx(lx#O-e#O#P.P#P;'S-e;'S;=`.z<%lO-e~.SRO;'S-e;'S;=`.];=`O-e~.bW]~Ow-ewx(lx#O-e#O#P.P#P;'S-e;'S;=`.z;=`<%l-e<%lO-e~.}P;=`<%l-e~/VUaPOt+vtu,ju;'S+v;'S;=`/i;=`<%l-e<%lO+v~/nW]~Ow-ewx(lx#O-e#O#P.P#P;'S-e;'S;=`.z;=`<%l+v<%lO-e~0ZP;=`<%l+vV0eTXUaPOt#utu$Zu;'S#u;'S;=`$s<%lO#uV0{TWUaPOt#utu$Zu;'S#u;'S;=`$s<%lO#uV1eT`SlQaPOt#utu$Zu;'S#u;'S;=`$s<%lO#u~1{aaPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q!R6h!R![:S![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#o3Q#o;'S#u;'S;=`$s<%lO#u~3X`aPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#o3Q#o;'S#u;'S;=`$s<%lO#u~4`_aPOt#utu$Zu}#u}!O5_!O!Q#u!Q![5_![!c#u!c!}5_!}#R#u#R#S5_#S#T#u#T#o5_#o;'S#u;'S;=`$s<%lO#u~5f`aPV~Ot#utu$Zu}#u}!O5_!O!P4Z!P!Q#u!Q![5_![!c#u!c!}5_!}#R#u#R#S5_#S#T#u#T#o5_#o;'S#u;'S;=`$s<%lO#u~6q`aP_~U~Ot#utu$Zu}#u}!O3Q!O!P7s!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#o3Q#o;'S#u;'S;=`$s<%lO#u~7x_aPOt#utu$Zu}#u}!O5_!O!Q#u!Q![8w![!c#u!c!}5_!}#R#u#R#S5_#S#T#u#T#o5_#o;'S#u;'S;=`$s<%lO#u~9Q`aP_~V~Ot#utu$Zu}#u}!O5_!O!P4Z!P!Q#u!Q![8w![!c#u!c!}5_!}#R#u#R#S5_#S#T#u#T#o5_#o;'S#u;'S;=`$s<%lO#u~:]`aP_~U~Ot#utu$Zu}#u}!O3Q!O!P7s!P!Q#u!Q![:S![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#o3Q#o;'S#u;'S;=`$s<%lO#uR;fTlQaPOt#utu$Zu;'S#u;'S;=`$s<%lO#uV;|T[UaPOt#utu$Zu;'S#u;'S;=`$s<%lO#uRk#U#o3Q#o;'S#u;'S;=`$s<%lO#u~>rbaPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#`3Q#`#a?z#a#o3Q#o;'S#u;'S;=`$s<%lO#u~@RbaPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#g3Q#g#hAZ#h#o3Q#o;'S#u;'S;=`$s<%lO#u~AbbaPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#X3Q#X#YBj#Y#o3Q#o;'S#u;'S;=`$s<%lO#u~Bs`aP^~U~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#o3Q#o;'S#u;'S;=`$s<%lO#u~C|baPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#f3Q#f#gEU#g#o3Q#o;'S#u;'S;=`$s<%lO#u~E]baPU~Ot#utu$Zu}#u}!O3Q!O!P4Z!P!Q#u!Q![3Q![!c#u!c!}3Q!}#R#u#R#S3Q#S#T#u#T#i3Q#i#jAZ#j#o3Q#o;'S#u;'S;=`$s<%lO#u", + tokenizers: [0, 1, 2], topRules: {"Template":[0,1]}, - tokenPrec: 0 + tokenPrec: 196 })