@coze-editor/lezer-parser-jinja2 0.1.0-alpha.0fd19e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +41 -0
- package/src/highlight.js +9 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +21 -0
- package/src/index.terms.js +28 -0
- package/src/jinja2.grammar +175 -0
- package/src/test.ts +51 -0
- package/src/tokens.js +325 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 coze-dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coze-editor/lezer-parser-jinja2",
|
|
3
|
+
"version": "0.1.0-alpha.0fd19e",
|
|
4
|
+
"description": "lezer-parser-jinja2",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "fengzilong",
|
|
7
|
+
"maintainers": [],
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"**/*.css",
|
|
10
|
+
"**/*.less",
|
|
11
|
+
"**/*.sass",
|
|
12
|
+
"**/*.scss"
|
|
13
|
+
],
|
|
14
|
+
"main": "./src/index.js",
|
|
15
|
+
"module": "./src/index.js",
|
|
16
|
+
"files": [
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "lezer-generator src/jinja2.grammar -o src/index.js",
|
|
21
|
+
"jinja:test": "npm run build & npx tsx src/test.ts",
|
|
22
|
+
"lint": "eslint"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@lezer/highlight": "~1.2.0",
|
|
26
|
+
"@lezer/lr": "^1.4.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@coze-arch/ts-config": "workspace:*",
|
|
30
|
+
"@coze-editor/eslint-config": "workspace:*",
|
|
31
|
+
"@lezer-unofficial/printer": "^1.0.1",
|
|
32
|
+
"@lezer/generator": "^1.7.0",
|
|
33
|
+
"@types/node": "^22",
|
|
34
|
+
"eslint": "9.14.0",
|
|
35
|
+
"typescript": "^5.8.2"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/highlight.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright (c) 2025 coze-dev
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { styleTags, tags as t } from '@lezer/highlight'
|
|
5
|
+
|
|
6
|
+
export const jinjaHighlighting = styleTags({
|
|
7
|
+
'JinjaExpressionStart JinjaExpressionEnd JinjaStatementStart JinjaStatementEnd': t.angleBracket,
|
|
8
|
+
'JinjaCommentStart JinjaCommentContent JinjaCommentEnd': t.blockComment,
|
|
9
|
+
})
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
2
|
+
import {LRParser} from "@lezer/lr"
|
|
3
|
+
import {tokenizeSingleQuoteString, tokenizeDoubleQuoteString, tokenizeExpression, tokenizeStatement, tokenizeComment, tokenizeRawText, tokenizeText} from "./tokens"
|
|
4
|
+
import {jinjaHighlighting} from "./highlight"
|
|
5
|
+
const spec_JinjaIdentifier = {__proto__:null,if:76, elif:78, else:80, endif:82, is:84, not:86, set:88, for:90, in:92, endfor:94, extends:96, macro:98, endmacro:100, block:102, endblock:104, call:106, endcall:108, include:110, from:112, import:114, as:116, raw:42, endraw:46}
|
|
6
|
+
export const parser = LRParser.deserialize({
|
|
7
|
+
version: 14,
|
|
8
|
+
states: "&`QYO%WOOOhO&zO'#CcO#jQ#xO'#CfO#qO$fO'#CnO%pQ,^O'#CtOOO!b'#DR'#DROOO!b'#Cu'#CuQYO%WOOOOO`'#Cv'#CvO%zO&zO,58}OOO!b,58},58}O&SOPO'#CjO&[OQO'#CjOOQS'#Cw'#CwO&dQ#xO,59QOOO!b,59Q,59QOOQ['#Ch'#ChO&kQ7[O'#CwOOO!b,59Y,59YO#tO#tO,59YO&pQ#tO'#CrOOQW'#Cx'#CxO&uQ,^O,59`OOO!b,59`,59`O&|Q#|O,59ZOOO!b-E6s-E6sOOO`-E6t-E6tOOO!b1G.i1G.iOOQ[,59U,59UO'ROPO,59UO'WOQO,59UOOQS-E6u-E6uOOO!b1G.l1G.lOOQS,59c,59cOOO!b1G.t1G.tO']Q#|O,59^OOQW-E6v-E6vOOO!b1G.z1G.zOOOp1G.u1G.uOOQ[1G.p1G.pOOO!b1G.x1G.x",
|
|
9
|
+
stateData: "'m~OsOStOS~OTTOWPOZQOdSO~ORYOXWO~O]]O_]Oa]Ov`Ow`Ox`Oy`Oz`O{`O|`O}`O!O`O!P`O!Q`O!R`O!S`O!T`O!U`O!V`O!W`O!X`O!Y`O!Z`O![`O!]ZO!^[O!_aO!`]O~OP_O~PpOScOddO~O]eO_eOv`Ow`Ox`Oy`Oz`O{`O|`O}`O!O`O!P`O!Q`O!R`O!S`O!T`O!U`O!V`O!W`O!X`O!Y`O!Z`O![`O!]ZO!^[O!`eO!aeO~OQgOehO~P#yORkOXWO~OnmOolO~OpnOqlO~OPpO~PpO`qO~OgsO~OQuO~P#yOQvO~OowO~OqwO~OQxO~Os`dt]_!`!aa]~",
|
|
10
|
+
goto: "!{vPPPPPPPwPPwP{P{PPPw!TPP!XPw!_!e!k!qPPPPPPPP!wTTOVS]Q^TeSfTROVQbRRrcQVORiVQXPRjXQ^QRo^QfSRtfTUOV",
|
|
11
|
+
nodeNames: "⚠ JinjaExpressionEnd JinjaStatementEnd JinjaCommentEnd RawText JinjaText JinjaProgram JinjaComment JinjaCommentStart JinjaCommentContent JinjaExpression JinjaExpressionStart JinjaKeyword JinjaIdentifier JinjaStringLiteral JinjaNumberLiteral JinjaFilterName JinjaExpressionUnknownContent JinjaRawBlock JinjaRawOpenStatement JinjaStatementStart JinjaKeyword JinjaRawCloseStatement JinjaKeyword JinjaStatement",
|
|
12
|
+
maxTerm: 63,
|
|
13
|
+
propSources: [jinjaHighlighting],
|
|
14
|
+
skippedNodes: [0],
|
|
15
|
+
repeatNodeCount: 4,
|
|
16
|
+
tokenData: "!)f_R!^OX$}XY;_YZ>QZ[;_[]$}]^?e^p$}pq;_qrAOrsBcst'|tuCPuvEUvw$}wxFZxyAOyzAOz{AO{|Fw|}AO}!OFw!O!PGv!P!QAO!Q![KO![!]AO!]!^$}!^!_AO!_!`AO!`!aAO!a!bAO!b!c$}!c!}CP!}#OAO#O#P$}#P#QAO#Q#RAO#R#SCP#S#T$}#T#oCP#o#pLr#p#q!$}#q#r8f#r#sAO#s$f$}$f$g;_$g#BYCP#BY#BZ!&Y#BZ$ISCP$IS$I_!&Y$I_$JTCP$JT$JU!&Y$JU$KVCP$KV$KW!&Y$KW&FUCP&FU&FV!&Y&FV;'SCP;'S;=`EO<%l?HTCP?HT?HU!&Y?HUOCPV%WbXQ!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}Q&eVXQOs&`st&zt#o&`#o#p'g#p;'S&`;'S;=`'a<%lO&`Q&}UO#q&`#r;'S&`;'S;=`'a<%l~&`~O&`~~&`Q'dP;=`<%l&`Q'jUOs&`t;'S&`;'S;=`'a<%l~&`~O&`~~&`V(Tb!aSaPOr$}rs&`su$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#q*`#q#r5W#r;'S$};'S;=`5Q<%l~$}~O$}~~&`V)d^XQaPOr$}rs*`st'|tw$}wx*`x#o$}#o#p-k#p#q*`#q#r0V#r;'S$};'S;=`5Q<%l~$}~O$}~~.rU*g`XQ!aSOr*`rs&`st+itu*`uv,svw*`wx&`x{*`{|&`|}*`}!O&`!O#o*`#o#p-k#p;'S*`;'S;=`0P<%lO*`U+na!aSOr*`rs&`su*`uv,svw*`wx&`x{*`{|&`|}*`}!O&`!O#q*`#q#r.r#r;'S*`;'S;=`0P<%l~*`~O*`~~&`U,xZXQOs*`st+it#o*`#o#p-k#p#q*`#q#r&`#r;'S*`;'S;=`0P<%l~*`~O*`~~.rU-p`!aSOr*`rs&`st.rtu*`uv,svw*`wx&`x{*`{|&`|}*`}!O&`!O;'S*`;'S;=`0P<%l~*`~O*`~~&`S.wX!aSOr.rsu.ruv/dvw.rx{.r|}.r!O;'S.r;'S;=`/y<%lO.rS/gUO#q.r#r;'S.r;'S;=`/y<%l~.r~O.r~~.rS/|P;=`<%l.rU0SP;=`<%l*`R0[ZXQOs0}st1zt#o0}#o#p4S#p#q0}#q#r&`#r;'S0};'S;=`3|<%l~0}~O0}~~3[R1U[XQaPOr0}rs&`st1ztw0}wx&`x#o0}#o#p'g#p#q&`#q#r0V#r;'S0};'S;=`3|<%lO0}R2P[aPOr0}rs&`sw0}wx&`x#o0}#o#q&`#q#r2u#r;'S0};'S;=`3|<%l~0}~O0}~~&`P2xUO#q3[#r;'S3[;'S;=`3v<%l~3[~O3[~~3[P3aVaPOr3[sw3[x#o3[#q#r2u#r;'S3[;'S;=`3v<%lO3[P3yP;=`<%l3[R4PP;=`<%l0}R4X]aPOr0}rs&`st3[tw0}wx&`x#o0}#o#q&`#q#r0V#r;'S0};'S;=`3|<%l~0}~O0}~~&`V5TP;=`<%l$}T5]a!aSOr6brs3[su6buv7evw6bwx3[x{6b{|3[|}6b}!O3[!O#q6b#q#r.r#r;'S6b;'S;=`8`<%l~6b~O6b~~3[T6i^!aSaPOr6bsu6buv7evw6bx{6b{|3[|}6b}!O3[!O#o6b#o#q.r#q#r5W#r;'S6b;'S;=`8`<%lO6bT7j[aPOr6brs.rsw6bwx.rx#o6b#o#q.r#q#r2u#r;'S6b;'S;=`8`<%l~6b~O6b~~.rT8cP;=`<%l6bV8mdXQ!aSOr$}rs0}st'|tu$}uv)]vw$}wx0}x{$}{|0}|}$}}!O0}!O#o$}#o#p9{#p#q$}#q#r*`#r;'S$};'S;=`5Q<%l~$}~O$}~~3[V:Sc!aSaPOr$}rs&`st6btu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#q*`#q#r8f#r;'S$};'S;=`5Q<%l~$}~O$}~~&`_;jvXQs]!aSaPOX$}XY;_YZ$}Z[;_[p$}pq;_qr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#p-k#p#q*`#q#r8f#r$f$}$f$g;_$g#BY$}#BY#BZ;_#BZ$IS$}$IS$I_;_$I_$JT$}$JT$JU;_$JU$KV$}$KV$KW;_$KW&FU$}&FU&FV;_&FV;'S$};'S;=`5Q<%l?HT$}?HT?HU;_?HUO$}_>]bXQt]!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}_?pdXQt]!aSaPOY$}YZ>QZr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}VAZbXQ!`T!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}VBjV!^TXQOs&`st&zt#o&`#o#p'g#p;'S&`;'S;=`'a<%lO&`_C^jXQ`W]T!aSaPOr$}rs&`st'|tuCPuv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O!Q$}!Q![CP![!c$}!c!}CP!}#R$}#R#SCP#S#T$}#T#oCP#o#p-k#p#q*`#q#r8f#r$g$}$g;'SCP;'S;=`EO<%lOCP_ERP;=`<%lCPVE_^XQ!`TaPOr$}rs*`st'|tw$}wx*`x#o$}#o#p-k#p#q*`#q#r0V#r;'S$};'S;=`5Q<%l~$}~O$}~~.rVFbV!]TXQOs&`st&zt#o&`#o#p'g#p;'S&`;'S;=`'a<%lO&`VGQ[XQ!`TaPOr0}rs&`st1ztw0}wx&`x#o0}#o#p'g#p#q&`#q#r0V#r;'S0};'S;=`3|<%lO0}VHPdXQ!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O!Q$}!Q![I_![#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}VIjfXQ_T!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O!Q$}!Q![I_![#R$}#R#SI_#S#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}VKZgXQ_T!aSaPOr$}rs&`st'|tu$}uv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O!PI_!P!Q$}!Q![KO![#R$}#R#SKO#S#o$}#o#p-k#p#q*`#q#r8f#r;'S$};'S;=`5Q<%lO$}VLwb!aSOr*`rs&`stNPtu*`uvNsvw*`wx&`x{*`{|&`|}*`}!O&`!O#o*`#o#p!#U#p;'S*`;'S;=`0P<%l~*`~O*`~~&`TNWXWP!aSOr.rsu.ruv/dvw.rx{.r|}.r!O;'S.r;'S;=`/y<%lO.rVNz_XQdPOs*`st+it{*`{|! y|}*`}!O! y!O#o*`#o#p-k#p#q*`#q#r&`#r;'S*`;'S;=`0P<%l~*`~O*`~~.rV!!S`XQdP!aSOr*`rs&`st+itu*`uv,svw*`wx&`x{*`{|&`|}*`}!O&`!O#o*`#o#p-k#p;'S*`;'S;=`0P<%lO*`V!#_`XQZP!aSOr*`rs&`st+itu*`uv,svw*`wx&`x{*`{|!$a|}*`}!O!$a!O#o*`#o#p-k#p;'S*`;'S;=`0P<%lO*`R!$hVXQZPOs&`st&zt#o&`#o#p'g#p;'S&`;'S;=`'a<%lO&`V!%W`!_PXQ!aSOr*`rs&`st+itu*`uv,svw*`wx&`x{*`{|&`|}*`}!O&`!O#o*`#o#p-k#p;'S*`;'S;=`0P<%lO*`_!&i}XQs]`W]T!aSaPOX$}XY;_YZ$}Z[;_[p$}pq;_qr$}rs&`st'|tuCPuv)]vw$}wx&`x{$}{|0}|}$}}!O0}!O!Q$}!Q![CP![!c$}!c!}CP!}#R$}#R#SCP#S#T$}#T#oCP#o#p-k#p#q*`#q#r8f#r$f$}$f$g;_$g#BYCP#BY#BZ!&Y#BZ$ISCP$IS$I_!&Y$I_$JTCP$JT$JU!&Y$JU$KVCP$KV$KW!&Y$KW&FUCP&FU&FV!&Y&FV;'SCP;'S;=`EO<%l?HTCP?HT?HU!&Y?HUOCP",
|
|
17
|
+
tokenizers: [tokenizeSingleQuoteString, tokenizeDoubleQuoteString, tokenizeExpression, tokenizeStatement, tokenizeComment, tokenizeRawText, tokenizeText, 0, 1, 2, 3],
|
|
18
|
+
topRules: {"JinjaProgram":[0,6]},
|
|
19
|
+
specialized: [{term: 13, get: (value) => spec_JinjaIdentifier[value] || -1}],
|
|
20
|
+
tokenPrec: 294
|
|
21
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
2
|
+
export const
|
|
3
|
+
stringContentSingleQuote = 30,
|
|
4
|
+
stringEndSingleQuote = 31,
|
|
5
|
+
stringContentDoubleQuote = 32,
|
|
6
|
+
stringEndDoubleQuote = 33,
|
|
7
|
+
JinjaExpressionEnd = 1,
|
|
8
|
+
JinjaStatementEnd = 2,
|
|
9
|
+
JinjaCommentEnd = 3,
|
|
10
|
+
RawText = 4,
|
|
11
|
+
JinjaText = 5,
|
|
12
|
+
JinjaProgram = 6,
|
|
13
|
+
JinjaComment = 7,
|
|
14
|
+
JinjaCommentStart = 8,
|
|
15
|
+
JinjaCommentContent = 9,
|
|
16
|
+
JinjaExpression = 10,
|
|
17
|
+
JinjaExpressionStart = 11,
|
|
18
|
+
JinjaKeyword = 23,
|
|
19
|
+
JinjaIdentifier = 13,
|
|
20
|
+
JinjaStringLiteral = 14,
|
|
21
|
+
JinjaNumberLiteral = 15,
|
|
22
|
+
JinjaFilterName = 16,
|
|
23
|
+
JinjaExpressionUnknownContent = 17,
|
|
24
|
+
JinjaRawBlock = 18,
|
|
25
|
+
JinjaRawOpenStatement = 19,
|
|
26
|
+
JinjaStatementStart = 20,
|
|
27
|
+
JinjaRawCloseStatement = 22,
|
|
28
|
+
JinjaStatement = 24
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
@top JinjaProgram { entity* }
|
|
2
|
+
|
|
3
|
+
entity {
|
|
4
|
+
JinjaText |
|
|
5
|
+
JinjaComment |
|
|
6
|
+
JinjaExpression |
|
|
7
|
+
JinjaRawBlock |
|
|
8
|
+
JinjaStatement
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
JinjaComment {
|
|
12
|
+
JinjaCommentStart JinjaCommentContent* JinjaCommentEnd
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@skip { spaces | newline } {
|
|
16
|
+
JinjaExpression {
|
|
17
|
+
JinjaExpressionStart (JinjaKeyword | JinjaIdentifier | JinjaStringLiteral | JinjaNumberLiteral | jinjaFilter | jinjaSign | JinjaExpressionUnknownContent)* JinjaExpressionEnd
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
jinjaFilter {
|
|
21
|
+
"|"
|
|
22
|
+
JinjaFilterName
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
JinjaStatement {
|
|
26
|
+
JinjaStatementStart (JinjaKeyword | JinjaIdentifier | JinjaStringLiteral | JinjaNumberLiteral | jinjaSign | jinjaStatementUnknownContent)* JinjaStatementEnd
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
JinjaRawOpenStatement {
|
|
30
|
+
JinjaStatementStart namedKw<"raw", JinjaKeyword> JinjaStatementEnd
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
JinjaRawCloseStatement {
|
|
34
|
+
JinjaStatementStart namedKw<"endraw", JinjaKeyword> JinjaStatementEnd
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
JinjaKeyword {
|
|
39
|
+
kw<"if"> | kw<"elif"> | kw<"else"> | kw<"endif"> |
|
|
40
|
+
kw<"is"> | kw<"not"> |
|
|
41
|
+
kw<"set"> |
|
|
42
|
+
kw<"for"> | kw<"in"> | kw<"endfor"> |
|
|
43
|
+
kw<"extends"> |
|
|
44
|
+
kw<"macro"> | kw<"endmacro"> |
|
|
45
|
+
kw<"block"> | kw<"endblock"> |
|
|
46
|
+
kw<"call"> | kw<"endcall"> |
|
|
47
|
+
kw<"include"> |
|
|
48
|
+
kw<"from"> | kw<"import"> | kw<"as">
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
JinjaStringLiteral {
|
|
52
|
+
"'" stringContentSingleQuote? stringEndSingleQuote |
|
|
53
|
+
'"' stringContentDoubleQuote? stringEndDoubleQuote
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@external tokens tokenizeSingleQuoteString from "./tokens" {
|
|
57
|
+
stringContentSingleQuote,
|
|
58
|
+
stringEndSingleQuote
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@external tokens tokenizeDoubleQuoteString from "./tokens" {
|
|
62
|
+
stringContentDoubleQuote,
|
|
63
|
+
stringEndDoubleQuote
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@external tokens tokenizeExpression from "./tokens" {
|
|
67
|
+
JinjaExpressionEnd
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@external tokens tokenizeStatement from "./tokens" {
|
|
71
|
+
JinjaStatementEnd
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@external tokens tokenizeComment from "./tokens" {
|
|
75
|
+
JinjaCommentEnd
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
JinjaRawBlock {
|
|
79
|
+
JinjaRawOpenStatement RawText? JinjaRawCloseStatement
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@external tokens tokenizeRawText from "./tokens" {
|
|
83
|
+
RawText
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@external tokens tokenizeText from "./tokens" {
|
|
87
|
+
JinjaText
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@tokens {
|
|
91
|
+
newline {
|
|
92
|
+
"\r" | "\n" | "\r\n"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
JinjaStatementStart { "{%" $[-+]? }
|
|
96
|
+
|
|
97
|
+
jinjaStatementUnknownContent {
|
|
98
|
+
![-+'"%] jinjaStatementUnknownContent? |
|
|
99
|
+
"%" (@eof | ![}]) jinjaStatementUnknownContent?
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
JinjaExpressionStart { "{{" $[-+]? }
|
|
103
|
+
JinjaExpressionUnknownContent {
|
|
104
|
+
![{'"}|] JinjaExpressionUnknownContent? |
|
|
105
|
+
"}" (@eof | ![}]) JinjaExpressionUnknownContent?
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
JinjaCommentStart { "{#" }
|
|
109
|
+
JinjaCommentContent {
|
|
110
|
+
![#{] JinjaCommentContent? |
|
|
111
|
+
"#" (@eof | ![}]) JinjaCommentContent? |
|
|
112
|
+
"{" (@eof | ![#]) JinjaCommentContent?
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
|
|
116
|
+
word { identifierChar (identifierChar | @digit)* }
|
|
117
|
+
JinjaIdentifier { word }
|
|
118
|
+
|
|
119
|
+
spaces { $[\u0009 \u000b\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+ }
|
|
120
|
+
|
|
121
|
+
jinjaSign {
|
|
122
|
+
"-" |
|
|
123
|
+
"+" |
|
|
124
|
+
"*" |
|
|
125
|
+
"/" |
|
|
126
|
+
"=" |
|
|
127
|
+
"%" |
|
|
128
|
+
">" |
|
|
129
|
+
"<" |
|
|
130
|
+
"(" |
|
|
131
|
+
")" |
|
|
132
|
+
"[" |
|
|
133
|
+
"]" |
|
|
134
|
+
"," |
|
|
135
|
+
"!" |
|
|
136
|
+
"~" |
|
|
137
|
+
"?" |
|
|
138
|
+
":" |
|
|
139
|
+
"," |
|
|
140
|
+
"^"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
JinjaNumberLiteral {
|
|
144
|
+
@digit ("_" | @digit)* ("." ("_" | @digit)*)? | "." @digit ("_" | @digit)*
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
JinjaFilterName { word }
|
|
148
|
+
|
|
149
|
+
@precedence {
|
|
150
|
+
spaces,
|
|
151
|
+
JinjaFilterName
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@precedence {
|
|
155
|
+
JinjaText,
|
|
156
|
+
JinjaStatementStart
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@precedence {
|
|
160
|
+
spaces,
|
|
161
|
+
newline,
|
|
162
|
+
JinjaIdentifier,
|
|
163
|
+
JinjaStringLiteral,
|
|
164
|
+
JinjaNumberLiteral,
|
|
165
|
+
jinjaSign,
|
|
166
|
+
jinjaStatementUnknownContent,
|
|
167
|
+
JinjaExpressionUnknownContent,
|
|
168
|
+
expressionUnknownContent
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
kw<term> { @specialize<JinjaIdentifier, term> }
|
|
173
|
+
namedKw<term, name> { @specialize[@name={name}]<JinjaIdentifier, term> }
|
|
174
|
+
|
|
175
|
+
@external propSource jinjaHighlighting from "./highlight"
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright (c) 2025 coze-dev
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/* eslint-disable */
|
|
5
|
+
import { printTree } from "@lezer-unofficial/printer"
|
|
6
|
+
import { parser } from './index'
|
|
7
|
+
|
|
8
|
+
// const source = `a {# 注释 #} a {%- for xxx -%}hello{{- foo + 1 +}}world{%+ endfor %} dasdas bb`
|
|
9
|
+
// const source = `{{ '}}' }} {{ '{{' }} {{ "}}" }}`
|
|
10
|
+
// const source = `{#
|
|
11
|
+
// multiple line
|
|
12
|
+
// multiple line
|
|
13
|
+
// multiple line
|
|
14
|
+
// #} haha {# single line #}
|
|
15
|
+
// `
|
|
16
|
+
// const source = `a {% for item in items %} {% if a is defined %}`
|
|
17
|
+
// const source = `a {% if a is * defined(a + 1, "hello") 'world' if a is %}`
|
|
18
|
+
// const source = `
|
|
19
|
+
// a {% hello world %} {% if a is * defined(a + 1, "hello") 'world' if a is %} b
|
|
20
|
+
// {% raw %}
|
|
21
|
+
// {% if a is * defined(a + 1, "hello") 'world' if a is %}
|
|
22
|
+
// {% endraw %} b
|
|
23
|
+
// `.trim()
|
|
24
|
+
// const source = `{% if "hello\\"" x%} foo
|
|
25
|
+
// Empty slot: {#slot id="foo" placeholder="请输入"#}{#/slot#}
|
|
26
|
+
// `.trim()
|
|
27
|
+
// const source = `{% if "a\\"" is true %}`
|
|
28
|
+
// const source = `{{a + 1} ???`
|
|
29
|
+
// const source = `{% hello + 123 + ??? world {#comment#}aaa{#hello#}`
|
|
30
|
+
// 换行处理
|
|
31
|
+
// const source = `slot{% if a is none
|
|
32
|
+
// {#slot id="foo" placeholder="请输入"#}prefilled value{#/slot#}
|
|
33
|
+
// foo
|
|
34
|
+
// `
|
|
35
|
+
// 多余 {
|
|
36
|
+
// const source = `{{#slot id="foo" placeholder="请输入"#}{#/slot#}`
|
|
37
|
+
// const source = `{{}{#slot id="foo" placeholder="请输入"#}{#/slot#}`
|
|
38
|
+
const source = `{{in}}`
|
|
39
|
+
// const source = `{%in%}`
|
|
40
|
+
|
|
41
|
+
// const source = `{{a + 1 ??? aaa {#comment#}aaa{#hello#}`
|
|
42
|
+
// const source = `{{hello|default('hello')}}`
|
|
43
|
+
// const source = `foo{#hello {# slot #}`
|
|
44
|
+
// const source = `foo{#hell{o#}`
|
|
45
|
+
|
|
46
|
+
// const source = `{% set designer_type ="`
|
|
47
|
+
|
|
48
|
+
console.log(
|
|
49
|
+
'tree',
|
|
50
|
+
printTree(parser.parse(source), source)
|
|
51
|
+
)
|
package/src/tokens.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Copyright (c) 2025 coze-dev
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { ExternalTokenizer } from '@lezer/lr'
|
|
5
|
+
import {
|
|
6
|
+
RawText,
|
|
7
|
+
stringContentSingleQuote,
|
|
8
|
+
stringEndSingleQuote,
|
|
9
|
+
stringContentDoubleQuote,
|
|
10
|
+
stringEndDoubleQuote,
|
|
11
|
+
JinjaExpressionEnd,
|
|
12
|
+
JinjaStatementEnd,
|
|
13
|
+
JinjaCommentEnd,
|
|
14
|
+
JinjaText,
|
|
15
|
+
} from './index.terms'
|
|
16
|
+
|
|
17
|
+
// scan to {% endraw %}
|
|
18
|
+
const sequence = [ '{%', 'endraw', '%}' ]
|
|
19
|
+
const tokenizeRawText = new ExternalTokenizer((input) => {
|
|
20
|
+
const first = sequence[0].charCodeAt(0)
|
|
21
|
+
let len = 0
|
|
22
|
+
let foundSequence = false
|
|
23
|
+
|
|
24
|
+
while (true) {
|
|
25
|
+
if (input.next < 0) {
|
|
26
|
+
break
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (input.next === first) {
|
|
30
|
+
let n = 0
|
|
31
|
+
let index = 0
|
|
32
|
+
for (; index < sequence.length; index++) {
|
|
33
|
+
const segment = sequence[index]
|
|
34
|
+
if (match(input, n, segment)) {
|
|
35
|
+
n = n + segment.length
|
|
36
|
+
if (index < sequence.length - 1) {
|
|
37
|
+
const spaceLen = skipSpaces(input, n)
|
|
38
|
+
n = n + spaceLen
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// all match
|
|
46
|
+
if (index === sequence.length) {
|
|
47
|
+
foundSequence = true
|
|
48
|
+
break
|
|
49
|
+
} else {
|
|
50
|
+
input.advance()
|
|
51
|
+
len++
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
input.advance()
|
|
55
|
+
len++
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// not found at end
|
|
60
|
+
if (!foundSequence && input.next < 0) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (len > 0) {
|
|
65
|
+
input.acceptToken(RawText)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function match(input, from, text) {
|
|
70
|
+
let i = 0
|
|
71
|
+
let len = 0
|
|
72
|
+
|
|
73
|
+
while (true) {
|
|
74
|
+
if (input.peek(from + i) === text.charCodeAt(i) && len < text.length) {
|
|
75
|
+
i++
|
|
76
|
+
len++
|
|
77
|
+
} else {
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return len === text.length
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const spaceReg = /^\s+$/
|
|
86
|
+
function skipSpaces(input, from) {
|
|
87
|
+
let i = from
|
|
88
|
+
let len = 0
|
|
89
|
+
|
|
90
|
+
while (true) {
|
|
91
|
+
const char = String.fromCharCode(input.peek(i))
|
|
92
|
+
if (spaceReg.test(char)) {
|
|
93
|
+
i++
|
|
94
|
+
len++
|
|
95
|
+
} else {
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return len
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const singleQuote = 39
|
|
104
|
+
const doubleQuote = 34
|
|
105
|
+
const tokenizeSingleQuoteString = scanString(singleQuote)
|
|
106
|
+
const tokenizeDoubleQuoteString = scanString(doubleQuote)
|
|
107
|
+
|
|
108
|
+
function scanString(quote) {
|
|
109
|
+
const endToken = quote === singleQuote ?
|
|
110
|
+
stringEndSingleQuote :
|
|
111
|
+
stringEndDoubleQuote
|
|
112
|
+
const contentToken = quote === singleQuote ?
|
|
113
|
+
stringContentSingleQuote :
|
|
114
|
+
stringContentDoubleQuote
|
|
115
|
+
|
|
116
|
+
return new ExternalTokenizer((input) => {
|
|
117
|
+
let len = 0
|
|
118
|
+
while (true) {
|
|
119
|
+
if (input.next < 0) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
// \\
|
|
125
|
+
(input.next === 92 && input.peek(1) === 92) ||
|
|
126
|
+
// \"
|
|
127
|
+
(input.next === 92 && input.peek(1) === quote)
|
|
128
|
+
) {
|
|
129
|
+
input.advance(2)
|
|
130
|
+
len += 2
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
(input.next === quote) ||
|
|
136
|
+
// % }
|
|
137
|
+
(input.next === 37 && input.peek(1) === 125) ||
|
|
138
|
+
// \n | \r
|
|
139
|
+
(input.next === 10 || input.next === 13)
|
|
140
|
+
) {
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
len++
|
|
145
|
+
input.advance()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (len > 0) {
|
|
149
|
+
input.acceptToken(contentToken)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (input.next === quote) {
|
|
153
|
+
input.advance()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
input.acceptToken(endToken)
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// {
|
|
161
|
+
const leftBracket = 123
|
|
162
|
+
// }
|
|
163
|
+
const rightBracket = 125
|
|
164
|
+
// #
|
|
165
|
+
const hash = 35
|
|
166
|
+
// %
|
|
167
|
+
const percent = 37
|
|
168
|
+
const minus = 45
|
|
169
|
+
const plus = 43
|
|
170
|
+
|
|
171
|
+
const tokenizeExpression = new ExternalTokenizer((input) => {
|
|
172
|
+
if (input.next === minus || input.next === plus) {
|
|
173
|
+
// skip spaces between -+ and }}
|
|
174
|
+
const n = skipSpaces(input, 1)
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
input.peek(n + 1) === rightBracket &&
|
|
178
|
+
input.peek(n + 2) === rightBracket
|
|
179
|
+
) {
|
|
180
|
+
input.advance(n + 3)
|
|
181
|
+
input.acceptToken(JinjaExpressionEnd)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
input.next === rightBracket && input.peek(1) === rightBracket
|
|
188
|
+
) {
|
|
189
|
+
input.advance(2)
|
|
190
|
+
input.acceptToken(JinjaExpressionEnd)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (input.next === leftBracket && input.peek(1) === hash) {
|
|
195
|
+
input.acceptToken(JinjaExpressionEnd)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (input.next < 0) {
|
|
200
|
+
input.acceptToken(JinjaExpressionEnd)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// function debug(input) {
|
|
206
|
+
// console.log(
|
|
207
|
+
// 'next',
|
|
208
|
+
// [
|
|
209
|
+
// String.fromCharCode(input.peek(0)),
|
|
210
|
+
// String.fromCharCode(input.peek(1)),
|
|
211
|
+
// String.fromCharCode(input.peek(2)),
|
|
212
|
+
// String.fromCharCode(input.peek(3)),
|
|
213
|
+
// String.fromCharCode(input.peek(4)),
|
|
214
|
+
// String.fromCharCode(input.peek(5)),
|
|
215
|
+
// ]
|
|
216
|
+
// )
|
|
217
|
+
// }
|
|
218
|
+
|
|
219
|
+
const tokenizeStatement = new ExternalTokenizer(input => {
|
|
220
|
+
if (input.next === minus || input.next === plus) {
|
|
221
|
+
// skip spaces between -+ and }}
|
|
222
|
+
const n = skipSpaces(input, 1)
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
input.peek(n + 1) === percent &&
|
|
226
|
+
input.peek(n + 2) === rightBracket
|
|
227
|
+
) {
|
|
228
|
+
input.advance(n + 3)
|
|
229
|
+
input.acceptToken(JinjaStatementEnd)
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
input.next === percent &&
|
|
236
|
+
input.peek(1) === rightBracket
|
|
237
|
+
) {
|
|
238
|
+
input.advance(2)
|
|
239
|
+
input.acceptToken(JinjaStatementEnd)
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (input.next === leftBracket && input.peek(1) === hash) {
|
|
244
|
+
input.acceptToken(JinjaStatementEnd)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (input.next < 0) {
|
|
249
|
+
input.acceptToken(JinjaStatementEnd)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const tokenizeComment = new ExternalTokenizer(input => {
|
|
255
|
+
if (
|
|
256
|
+
input.next === hash &&
|
|
257
|
+
input.peek(1) === rightBracket
|
|
258
|
+
) {
|
|
259
|
+
input.advance(2)
|
|
260
|
+
input.acceptToken(JinjaCommentEnd)
|
|
261
|
+
} else if (
|
|
262
|
+
input.next === leftBracket &&
|
|
263
|
+
input.peek(1) === hash
|
|
264
|
+
) {
|
|
265
|
+
input.acceptToken(JinjaCommentEnd)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const tokenizeText = new ExternalTokenizer(input => {
|
|
270
|
+
let offset = 0
|
|
271
|
+
let len = 0
|
|
272
|
+
|
|
273
|
+
while (true) {
|
|
274
|
+
const next = input.peek(offset)
|
|
275
|
+
|
|
276
|
+
// eof
|
|
277
|
+
if (next < 0) {
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ![{]
|
|
282
|
+
if (next !== leftBracket) {
|
|
283
|
+
offset++
|
|
284
|
+
len++
|
|
285
|
+
} else if (
|
|
286
|
+
// { ![{#%]
|
|
287
|
+
next === leftBracket &&
|
|
288
|
+
input.peek(offset + 1) !== leftBracket &&
|
|
289
|
+
input.peek(offset + 1) !== hash &&
|
|
290
|
+
input.peek(offset + 1) !== percent
|
|
291
|
+
) {
|
|
292
|
+
offset++
|
|
293
|
+
len++
|
|
294
|
+
} else if (
|
|
295
|
+
// {{#
|
|
296
|
+
next === leftBracket &&
|
|
297
|
+
input.peek(offset + 1) === leftBracket &&
|
|
298
|
+
(
|
|
299
|
+
input.peek(offset + 2) === leftBracket ||
|
|
300
|
+
input.peek(offset + 2) === hash ||
|
|
301
|
+
input.peek(offset + 2) === percent
|
|
302
|
+
)
|
|
303
|
+
) {
|
|
304
|
+
offset++
|
|
305
|
+
len++
|
|
306
|
+
} else {
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (len > 0) {
|
|
312
|
+
input.advance(len)
|
|
313
|
+
input.acceptToken(JinjaText)
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
export {
|
|
318
|
+
tokenizeRawText,
|
|
319
|
+
tokenizeSingleQuoteString,
|
|
320
|
+
tokenizeDoubleQuoteString,
|
|
321
|
+
tokenizeExpression,
|
|
322
|
+
tokenizeStatement,
|
|
323
|
+
tokenizeComment,
|
|
324
|
+
tokenizeText,
|
|
325
|
+
}
|