PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/golang/1.22.0/src/text/template/parse
Viewing File: /opt/golang/1.22.0/src/text/template/parse/parse.go
// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package parse builds parse trees for templates as defined by text/template // and html/template. Clients should use those packages to construct templates // rather than this one, which provides shared internal data structures not // intended for general use. package parse import ( "bytes" "fmt" "runtime" "strconv" "strings" ) // Tree is the representation of a single parsed template. type Tree struct { Name string // name of the template represented by the tree. ParseName string // name of the top-level template during parsing, for error messages. Root *ListNode // top-level root of the tree. Mode Mode // parsing mode. text string // text parsed to create the template (or its parent) // Parsing only; cleared after parse. funcs []map[string]any lex *lexer token [3]item // three-token lookahead for parser. peekCount int vars []string // variables defined at the moment. treeSet map[string]*Tree actionLine int // line of left delim starting action rangeDepth int } // A mode value is a set of flags (or 0). Modes control parser behavior. type Mode uint const ( ParseComments Mode = 1 << iota // parse comments and add them to AST SkipFuncCheck // do not check that functions are defined ) // Copy returns a copy of the Tree. Any parsing state is discarded. func (t *Tree) Copy() *Tree { if t == nil { return nil } return &Tree{ Name: t.Name, ParseName: t.ParseName, Root: t.Root.CopyList(), text: t.text, } } // Parse returns a map from template name to parse.Tree, created by parsing the // templates described in the argument string. The top-level template will be // given the specified name. If an error is encountered, parsing stops and an // empty map is returned with the error. func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]any) (map[string]*Tree, error) { treeSet := make(map[string]*Tree) t := New(name) t.text = text _, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) return treeSet, err } // next returns the next token. func (t *Tree) next() item { if t.peekCount > 0 { t.peekCount-- } else { t.token[0] = t.lex.nextItem() } return t.token[t.peekCount] } // backup backs the input stream up one token. func (t *Tree) backup() { t.peekCount++ } // backup2 backs the input stream up two tokens. // The zeroth token is already there. func (t *Tree) backup2(t1 item) { t.token[1] = t1 t.peekCount = 2 } // backup3 backs the input stream up three tokens // The zeroth token is already there. func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. t.token[1] = t1 t.token[2] = t2 t.peekCount = 3 } // peek returns but does not consume the next token. func (t *Tree) peek() item { if t.peekCount > 0 { return t.token[t.peekCount-1] } t.peekCount = 1 t.token[0] = t.lex.nextItem() return t.token[0] } // nextNonSpace returns the next non-space token. func (t *Tree) nextNonSpace() (token item) { for { token = t.next() if token.typ != itemSpace { break } } return token } // peekNonSpace returns but does not consume the next non-space token. func (t *Tree) peekNonSpace() item { token := t.nextNonSpace() t.backup() return token } // Parsing. // New allocates a new parse tree with the given name. func New(name string, funcs ...map[string]any) *Tree { return &Tree{ Name: name, funcs: funcs, } } // ErrorContext returns a textual representation of the location of the node in the input text. // The receiver is only used when the node does not have a pointer to the tree inside, // which can occur in old code. func (t *Tree) ErrorContext(n Node) (location, context string) { pos := int(n.Position()) tree := n.tree() if tree == nil { tree = t } text := tree.text[:pos] byteNum := strings.LastIndex(text, "\n") if byteNum == -1 { byteNum = pos // On first line. } else { byteNum++ // After the newline. byteNum = pos - byteNum } lineNum := 1 + strings.Count(text, "\n") context = n.String() return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context } // errorf formats the error and terminates processing. func (t *Tree) errorf(format string, args ...any) { t.Root = nil format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format) panic(fmt.Errorf(format, args...)) } // error terminates processing. func (t *Tree) error(err error) { t.errorf("%s", err) } // expect consumes the next token and guarantees it has the required type. func (t *Tree) expect(expected itemType, context string) item { token := t.nextNonSpace() if token.typ != expected { t.unexpected(token, context) } return token } // expectOneOf consumes the next token and guarantees it has one of the required types. func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { token := t.nextNonSpace() if token.typ != expected1 && token.typ != expected2 { t.unexpected(token, context) } return token } // unexpected complains about the token and terminates processing. func (t *Tree) unexpected(token item, context string) { if token.typ == itemError { extra := "" if t.actionLine != 0 && t.actionLine != token.line { extra = fmt.Sprintf(" in action started at %s:%d", t.ParseName, t.actionLine) if strings.HasSuffix(token.val, " action") { extra = extra[len(" in action"):] // avoid "action in action" } } t.errorf("%s%s", token, extra) } t.errorf("unexpected %s in %s", token, context) } // recover is the handler that turns panics into returns from the top level of Parse. func (t *Tree) recover(errp *error) { e := recover() if e != nil { if _, ok := e.(runtime.Error); ok { panic(e) } if t != nil { t.stopParse() } *errp = e.(error) } } // startParse initializes the parser, using the lexer. func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string]*Tree) { t.Root = nil t.lex = lex t.vars = []string{"$"} t.funcs = funcs t.treeSet = treeSet lex.options = lexOptions{ emitComment: t.Mode&ParseComments != 0, breakOK: !t.hasFunction("break"), continueOK: !t.hasFunction("continue"), } } // stopParse terminates parsing. func (t *Tree) stopParse() { t.lex = nil t.vars = nil t.funcs = nil t.treeSet = nil } // Parse parses the template definition string to construct a representation of // the template for execution. If either action delimiter string is empty, the // default ("{{" or "}}") is used. Embedded template definitions are added to // the treeSet map. func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) { defer t.recover(&err) t.ParseName = t.Name lexer := lex(t.Name, text, leftDelim, rightDelim) t.startParse(funcs, lexer, treeSet) t.text = text t.parse() t.add() t.stopParse() return t, nil } // add adds tree to t.treeSet. func (t *Tree) add() { tree := t.treeSet[t.Name] if tree == nil || IsEmptyTree(tree.Root) { t.treeSet[t.Name] = t return } if !IsEmptyTree(t.Root) { t.errorf("template: multiple definition of template %q", t.Name) } } // IsEmptyTree reports whether this tree (node) is empty of everything but space or comments. func IsEmptyTree(n Node) bool { switch n := n.(type) { case nil: return true case *ActionNode: case *CommentNode: return true case *IfNode: case *ListNode: for _, node := range n.Nodes { if !IsEmptyTree(node) { return false } } return true case *RangeNode: case *TemplateNode: case *TextNode: return len(bytes.TrimSpace(n.Text)) == 0 case *WithNode: default: panic("unknown node: " + n.String()) } return false } // parse is the top-level parser for a template, essentially the same // as itemList except it also parses {{define}} actions. // It runs to EOF. func (t *Tree) parse() { t.Root = t.newList(t.peek().pos) for t.peek().typ != itemEOF { if t.peek().typ == itemLeftDelim { delim := t.next() if t.nextNonSpace().typ == itemDefine { newT := New("definition") // name will be updated once we know it. newT.text = t.text newT.Mode = t.Mode newT.ParseName = t.ParseName newT.startParse(t.funcs, t.lex, t.treeSet) newT.parseDefinition() continue } t.backup2(delim) } switch n := t.textOrAction(); n.Type() { case nodeEnd, nodeElse: t.errorf("unexpected %s", n) default: t.Root.append(n) } } } // parseDefinition parses a {{define}} ... {{end}} template definition and // installs the definition in t.treeSet. The "define" keyword has already // been scanned. func (t *Tree) parseDefinition() { const context = "define clause" name := t.expectOneOf(itemString, itemRawString, context) var err error t.Name, err = strconv.Unquote(name.val) if err != nil { t.error(err) } t.expect(itemRightDelim, context) var end Node t.Root, end = t.itemList() if end.Type() != nodeEnd { t.errorf("unexpected %s in %s", end, context) } t.add() t.stopParse() } // itemList: // // textOrAction* // // Terminates at {{end}} or {{else}}, returned separately. func (t *Tree) itemList() (list *ListNode, next Node) { list = t.newList(t.peekNonSpace().pos) for t.peekNonSpace().typ != itemEOF { n := t.textOrAction() switch n.Type() { case nodeEnd, nodeElse: return list, n } list.append(n) } t.errorf("unexpected EOF") return } // textOrAction: // // text | comment | action func (t *Tree) textOrAction() Node { switch token := t.nextNonSpace(); token.typ { case itemText: return t.newText(token.pos, token.val) case itemLeftDelim: t.actionLine = token.line defer t.clearActionLine() return t.action() case itemComment: return t.newComment(token.pos, token.val) default: t.unexpected(token, "input") } return nil } func (t *Tree) clearActionLine() { t.actionLine = 0 } // Action: // // control // command ("|" command)* // // Left delim is past. Now get actions. // First word could be a keyword such as range. func (t *Tree) action() (n Node) { switch token := t.nextNonSpace(); token.typ { case itemBlock: return t.blockControl() case itemBreak: return t.breakControl(token.pos, token.line) case itemContinue: return t.continueControl(token.pos, token.line) case itemElse: return t.elseControl() case itemEnd: return t.endControl() case itemIf: return t.ifControl() case itemRange: return t.rangeControl() case itemTemplate: return t.templateControl() case itemWith: return t.withControl() } t.backup() token := t.peek() // Do not pop variables; they persist until "end". return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) } // Break: // // {{break}} // // Break keyword is past. func (t *Tree) breakControl(pos Pos, line int) Node { if token := t.nextNonSpace(); token.typ != itemRightDelim { t.unexpected(token, "{{break}}") } if t.rangeDepth == 0 { t.errorf("{{break}} outside {{range}}") } return t.newBreak(pos, line) } // Continue: // // {{continue}} // // Continue keyword is past. func (t *Tree) continueControl(pos Pos, line int) Node { if token := t.nextNonSpace(); token.typ != itemRightDelim { t.unexpected(token, "{{continue}}") } if t.rangeDepth == 0 { t.errorf("{{continue}} outside {{range}}") } return t.newContinue(pos, line) } // Pipeline: // // declarations? command ('|' command)* func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { token := t.peekNonSpace() pipe = t.newPipeline(token.pos, token.line, nil) // Are there declarations or assignments? decls: if v := t.peekNonSpace(); v.typ == itemVariable { t.next() // Since space is a token, we need 3-token look-ahead here in the worst case: // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an // argument variable rather than a declaration. So remember the token // adjacent to the variable so we can push it back if necessary. tokenAfterVariable := t.peek() next := t.peekNonSpace() switch { case next.typ == itemAssign, next.typ == itemDeclare: pipe.IsAssign = next.typ == itemAssign t.nextNonSpace() pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) t.vars = append(t.vars, v.val) case next.typ == itemChar && next.val == ",": t.nextNonSpace() pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) t.vars = append(t.vars, v.val) if context == "range" && len(pipe.Decl) < 2 { switch t.peekNonSpace().typ { case itemVariable, itemRightDelim, itemRightParen: // second initialized variable in a range pipeline goto decls default: t.errorf("range can only initialize variables") } } t.errorf("too many declarations in %s", context) case tokenAfterVariable.typ == itemSpace: t.backup3(v, tokenAfterVariable) default: t.backup2(v) } } for { switch token := t.nextNonSpace(); token.typ { case end: // At this point, the pipeline is complete t.checkPipeline(pipe, context) return case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: t.backup() pipe.append(t.command()) default: t.unexpected(token, context) } } } func (t *Tree) checkPipeline(pipe *PipeNode, context string) { // Reject empty pipelines if len(pipe.Cmds) == 0 { t.errorf("missing value for %s", context) } // Only the first command of a pipeline can start with a non executable operand for i, c := range pipe.Cmds[1:] { switch c.Args[0].Type() { case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: // With A|B|C, pipeline stage 2 is B t.errorf("non executable command in pipeline stage %d", i+2) } } } func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { defer t.popVars(len(t.vars)) pipe = t.pipeline(context, itemRightDelim) if context == "range" { t.rangeDepth++ } var next Node list, next = t.itemList() if context == "range" { t.rangeDepth-- } switch next.Type() { case nodeEnd: //done case nodeElse: if allowElseIf { // Special case for "else if". If the "else" is followed immediately by an "if", // the elseControl will have left the "if" token pending. Treat // {{if a}}_{{else if b}}_{{end}} // as // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} // is assumed. This technique works even for long if-else-if chains. // TODO: Should we allow else-if in with and range? if t.peek().typ == itemIf { t.next() // Consume the "if" token. elseList = t.newList(next.Position()) elseList.append(t.ifControl()) // Do not consume the next item - only one {{end}} required. break } } elseList, next = t.itemList() if next.Type() != nodeEnd { t.errorf("expected end; found %s", next) } } return pipe.Position(), pipe.Line, pipe, list, elseList } // If: // // {{if pipeline}} itemList {{end}} // {{if pipeline}} itemList {{else}} itemList {{end}} // // If keyword is past. func (t *Tree) ifControl() Node { return t.newIf(t.parseControl(true, "if")) } // Range: // // {{range pipeline}} itemList {{end}} // {{range pipeline}} itemList {{else}} itemList {{end}} // // Range keyword is past. func (t *Tree) rangeControl() Node { r := t.newRange(t.parseControl(false, "range")) return r } // With: // // {{with pipeline}} itemList {{end}} // {{with pipeline}} itemList {{else}} itemList {{end}} // // If keyword is past. func (t *Tree) withControl() Node { return t.newWith(t.parseControl(false, "with")) } // End: // // {{end}} // // End keyword is past. func (t *Tree) endControl() Node { return t.newEnd(t.expect(itemRightDelim, "end").pos) } // Else: // // {{else}} // // Else keyword is past. func (t *Tree) elseControl() Node { // Special case for "else if". peek := t.peekNonSpace() if peek.typ == itemIf { // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". return t.newElse(peek.pos, peek.line) } token := t.expect(itemRightDelim, "else") return t.newElse(token.pos, token.line) } // Block: // // {{block stringValue pipeline}} // // Block keyword is past. // The name must be something that can evaluate to a string. // The pipeline is mandatory. func (t *Tree) blockControl() Node { const context = "block clause" token := t.nextNonSpace() name := t.parseTemplateName(token, context) pipe := t.pipeline(context, itemRightDelim) block := New(name) // name will be updated once we know it. block.text = t.text block.Mode = t.Mode block.ParseName = t.ParseName block.startParse(t.funcs, t.lex, t.treeSet) var end Node block.Root, end = block.itemList() if end.Type() != nodeEnd { t.errorf("unexpected %s in %s", end, context) } block.add() block.stopParse() return t.newTemplate(token.pos, token.line, name, pipe) } // Template: // // {{template stringValue pipeline}} // // Template keyword is past. The name must be something that can evaluate // to a string. func (t *Tree) templateControl() Node { const context = "template clause" token := t.nextNonSpace() name := t.parseTemplateName(token, context) var pipe *PipeNode if t.nextNonSpace().typ != itemRightDelim { t.backup() // Do not pop variables; they persist until "end". pipe = t.pipeline(context, itemRightDelim) } return t.newTemplate(token.pos, token.line, name, pipe) } func (t *Tree) parseTemplateName(token item, context string) (name string) { switch token.typ { case itemString, itemRawString: s, err := strconv.Unquote(token.val) if err != nil { t.error(err) } name = s default: t.unexpected(token, context) } return } // command: // // operand (space operand)* // // space-separated arguments up to a pipeline character or right delimiter. // we consume the pipe character but leave the right delim to terminate the action. func (t *Tree) command() *CommandNode { cmd := t.newCommand(t.peekNonSpace().pos) for { t.peekNonSpace() // skip leading spaces. operand := t.operand() if operand != nil { cmd.append(operand) } switch token := t.next(); token.typ { case itemSpace: continue case itemRightDelim, itemRightParen: t.backup() case itemPipe: // nothing here; break loop below default: t.unexpected(token, "operand") } break } if len(cmd.Args) == 0 { t.errorf("empty command") } return cmd } // operand: // // term .Field* // // An operand is a space-separated component of a command, // a term possibly followed by field accesses. // A nil return means the next item is not an operand. func (t *Tree) operand() Node { node := t.term() if node == nil { return nil } if t.peek().typ == itemField { chain := t.newChain(t.peek().pos, node) for t.peek().typ == itemField { chain.Add(t.next().val) } // Compatibility with original API: If the term is of type NodeField // or NodeVariable, just put more fields on the original. // Otherwise, keep the Chain node. // Obvious parsing errors involving literal values are detected here. // More complex error cases will have to be handled at execution time. switch node.Type() { case NodeField: node = t.newField(chain.Position(), chain.String()) case NodeVariable: node = t.newVariable(chain.Position(), chain.String()) case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot: t.errorf("unexpected . after term %q", node.String()) default: node = chain } } return node } // term: // // literal (number, string, nil, boolean) // function (identifier) // . // .Field // $ // '(' pipeline ')' // // A term is a simple "expression". // A nil return means the next item is not a term. func (t *Tree) term() Node { switch token := t.nextNonSpace(); token.typ { case itemIdentifier: checkFunc := t.Mode&SkipFuncCheck == 0 if checkFunc && !t.hasFunction(token.val) { t.errorf("function %q not defined", token.val) } return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) case itemDot: return t.newDot(token.pos) case itemNil: return t.newNil(token.pos) case itemVariable: return t.useVar(token.pos, token.val) case itemField: return t.newField(token.pos, token.val) case itemBool: return t.newBool(token.pos, token.val == "true") case itemCharConstant, itemComplex, itemNumber: number, err := t.newNumber(token.pos, token.val, token.typ) if err != nil { t.error(err) } return number case itemLeftParen: return t.pipeline("parenthesized pipeline", itemRightParen) case itemString, itemRawString: s, err := strconv.Unquote(token.val) if err != nil { t.error(err) } return t.newString(token.pos, token.val, s) } t.backup() return nil } // hasFunction reports if a function name exists in the Tree's maps. func (t *Tree) hasFunction(name string) bool { for _, funcMap := range t.funcs { if funcMap == nil { continue } if funcMap[name] != nil { return true } } return false } // popVars trims the variable list to the specified length func (t *Tree) popVars(n int) { t.vars = t.vars[:n] } // useVar returns a node for a variable reference. It errors if the // variable is not defined. func (t *Tree) useVar(pos Pos, name string) Node { v := t.newVariable(pos, name) for _, varName := range t.vars { if varName == v.Ident[0] { return v } } t.errorf("undefined variable %q", v.Ident[0]) return nil }