@coze-editor/lezer-parser-jinja2 0.1.0-alpha.09ffeb

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 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.09ffeb",
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
+ }
@@ -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
@@ -0,0 +1,10 @@
1
+ // Copyright (c) 2025 coze-dev
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { LRParser } from '@lezer/lr'
5
+
6
+ declare const parser: LRParser
7
+
8
+ export {
9
+ parser,
10
+ }
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
+ }