Add canonical grammar and CodeMirror playground

This commit is contained in:
2026-07-08 23:41:26 +09:00
parent a07f4c48aa
commit ee031bd2e8
15 changed files with 709 additions and 81 deletions
@@ -0,0 +1,16 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
export const parser = LRParser.deserialize({
version: 14,
states: "7vQYQPOOO#nQPO'#C`OOQO'#Cn'#CnO$wQPO'#CoO&UQPO'#CpO&^QPO'#CqO&eQPO'#CrO&mQPO'#CmO$wQPO'#CwO&wQPO'#CzOOQO'#Cm'#CmOOQO'#Cl'#ClO&|QPO'#CkO$wQPO'#CkOOQO'#Cj'#CjO)fQPO'#CiO.XQPO'#ChO0]QPO'#CgOOQO'#Cf'#CfO2TQPO'#CeO3xQPO'#CdO5jQPO'#CcO7UQPO'#CbOOQO'#Ca'#CaO7`QPO'#C_O7eQPO'#C^OOQO'#DO'#DOQYQPOOO8xQPO'#DPO8}QPO,58zOOQO,59Z,59ZO9VQPO'#C`OOQO,59[,59[O9_QPO,59[OOQO,59],59]O9gQPO,59]O9oQPO'#DSO9tQPO'#CsO9|QPO,59^O:RQPO'#CmO:`QPO'#CuO:hQQO,59`O:mQPO,59`O:rQPO,59XO:wQPO,59cOOQO,59f,59fO:|QPO'#C{OOQO,59W,59WO;TQPO,59WOOQO,59V,59VO$wQPO,59UO$wQPO,59TO$wQPO,59SOOQO'#C}'#C}O$wQPO,59RO$wQPO,59QO$wQPO,59PO$wQPO,59OO$wQPO,58}O$wQPO,58|O$wQPO,58yOOQO,58x,58xOOQO-E6|-E6|OOQO,59k,59kOOQO-E6}-E6}O;YQPO1G.vO;bQPO1G.vOOQO1G.v1G.vO;jQPO1G.wO;qQPO1G.wOOQO1G.w1G.wOOQO,59n,59nOOQO-E7Q-E7QO$wQPO1G.xO$wQPO,59bO;yQPO,59aO<RQPO,59aO$wQPO1G.zO<ZQQO1G.zOOQO1G.s1G.sO<`QPO1G.}O<jQPO'#C|OOQO,59g,59gO<rQPO,59gOOQO1G.r1G.rOOQO1G.p1G.pO<wQPO1G.oO@_QPO1G.nOAyQPO1G.mOOQO1G.l1G.lOCqQPO1G.kOEfQPO1G.jOFxQPO1G.iOOQO1G.h1G.hOOQO1G.e1G.eOOQO1G.o1G.oOOQO1G.n1G.nOOQO1G.m1G.mOOQO1G.k1G.kOOQO1G.j1G.jOOQO1G.i1G.iOOQO,59l,59lOOQO7+$b7+$bOHfQPO7+$bOOQO-E7O-E7OOOQO,59m,59mOOQO7+$c7+$cOHnQPO7+$cOOQO-E7P-E7POOQO7+$d7+$dOOQO1G.|1G.|OHuQPO'#CvOOQO,59o,59oOHzQPO1G.{OOQO-E7R-E7ROOQO7+$f7+$fO$wQPO7+$fOOQO'#Cy'#CyOISQPO'#CxOOQO7+$i7+$iOIXQPO7+$iOIaQPO,59hOIhQPO,59hOOQO1G/R1G/ROOQO<<G|<<G|PIpQPO'#DQOOQO<<G}<<G}P$wQPO'#DRPIuQPO'#DTOOQO<<HQ<<HQO$wQPO,59dOIzQPO<<HTOJUQPO<<HTOOQO<<HT<<HTOJ^QPO1G/SOOQO1G/O1G/OOOQO,59p,59pOOQOAN=oAN=oOJeQPOAN=oOOQO-E7S-E7SOOQOG23ZG23ZP<cQPO'#DUO$wQPO,59TO$wQPO,59SO$wQPO,59RO$wQPO,59PO$wQPO,59OO$wQPO,58}OJoQPO1G.nOKVQPO'#ChOKjQPO'#CgOL^QPO'#CdOM^QPO'#CcONaQPO'#CbO$wQPO,59SO$wQPO,59QO$wQPO,58|O! jQPO1G.mO!!QQPO'#CgO!#_QPO'#CeO$wQPO,59RO$wQPO,59QO$wQPO,59PO$wQPO,59OO$wQPO,58}O$wQPO,58|O$wQPO,58yO!$[QPO1G.kO!$rQPO1G.jO!%YQPO1G.iO!%zQPO'#CeO!&rQPO'#CdO!'gQPO'#CcO!(XQPO'#CbO!(vQPO'#C_",
stateData: "!)Y~O{OS|OS~O}PO!QQO!RQO!SQO!TQO!UQO!VQO!WRO!XRO!YRO!ZRO![SO!_TO!bUO!dVO!hWO!jXO!k]O!l]O~O!WaX!XaX!YaX!ZaX!daX!laX!maX!naX!oaX!paX!qaX!raX!saX!taX!uaX!vaX!waX~O!OlOyaX}aX!PSX!QaX!RaX!SaX!TaX!UaX!VaX![aX!]aX!_aX!baX!haX!jaX!kaX~P!gO}YO!QQO!RQO!SQO!TQO!UQO!VQO!WRO!XRO!YRO!ZRO![SO!_TO!bUO!dVO!hWO!jXO!k]O!l]O~O}oO!^pO~O!arO~P$wO}oO!cgP~O}wO!fyO~P]O!Q}O~O!O!QO!d!OOy_X}_X!Q_X!R_X!S_X!T_X!U_X!V_X!W_X!X_X!Y_X!Z_X![_X!]_X!__X!b_X!h_X!j_X!k_X!l_X!m_X!n_X!o_X!p_X!q_X!r_X!s_X!t_X!u_X!v_X!w_X!`_X!a_X!f_X!^_X!e_X~O!m!SO!n!SOy]X}]X!Q]X!R]X!S]X!T]X!U]X!V]X!W]X!X]X!Y]X!Z]X![]X!]]X!_]X!b]X!d]X!h]X!j]X!k]X!l]X!o]X!p]X!q]X!r]X!s]X!t]X!u]X!v]X!w]X!O]X!`]X!a]X!f]X!^]X!e]X~Oy[X}[X!Q[X!R[X!S[X!T[X!U[X!V[X!W[X!X[X!Y[X!Z[X![[X!][X!_[X!b[X!d[X!h[X!j[X!k[X!p[X!q[X!r[X!s[X!t[X!u[X!v[X!w[X!`[X!a[X!f[X!^[X!e[X~O!l!TO!o!TO~P,OO!W!VO!X!VO!Y!VO!Z!VO!q!VO!r!VOyZX}ZX!QZX!RZX!SZX!TZX!UZX!VZX![ZX!]ZX!_ZX!bZX!dZX!hZX!jZX!kZX!lZX!sZX!tZX!uZX!vZX!wZX~O!p!UO~P.cOyXX}XX!QXX!RXX!SXX!TXX!UXX!VXX!WXX!XXX!YXX!ZXX![XX!]XX!_XX!bXX!dXX!hXX!jXX!kXX!lXX!tXX!uXX!vXX!wXX~O!s!XO~P0dOyWX}WX!QWX!RWX!SWX!TWX!UWX!VWX!WWX!XWX!YWX!ZWX![WX!]WX!_WX!bWX!dWX!hWX!jWX!kWX!lWX!uWX!vWX!wWX~O!t!YO~P2[OyVX}VX!QVX!RVX!SVX!TVX!UVX!VVX!WVX!XVX!YVX!ZVX![VX!]VX!_VX!bVX!dVX!hVX!jVX!kVX!lVX!vVX!wVX~O!u!ZO~P4POyUX}UX!QUX!RUX!SUX!TUX!UUX!VUX!WUX!XUX!YUX!ZUX![UX!]UX!_UX!bUX!dUX!hUX!jUX!kUX!lUX~O!v![O!w!]O~P5qO!P!^O~O!]!_OyQX}QX!QQX!RQX!SQX!TQX!UQX!VQX!WQX!XQX!YQX!ZQX![QX!_QX!bQX!dQX!hQX!jQX!kQX!lQX~O}!aO~O!OlO!PSa~O!OlO!PSX~O!]!cO!^!eO~O!`!fO!a!hO~O!]!iO~O}oO!cgX~O!c!kO~O!e!lO!OaX!faX~P!gO!`!mO!fiX~O!g!oO~O!f!pO~O!f!qO~O![!rO~O!f!tO~P$wO}!vO~O}oO!^#YO~O!]#ZO!^#YO~O!a#^O~P$wO!`#_O!a#^O~O}#cO!fia~O!`#eO!fia~O!g#hO~O!^#kO!i#iO~P$wO!`#mO!fpX~O!f#oO~O!m!SO!n!SOy]i}]i!Q]i!R]i!S]i!T]i!U]i!V]i!W]i!X]i!Y]i!Z]i![]i!]]i!_]i!b]i!d]i!h]i!j]i!k]i!l]i!o]i!p]i!q]i!r]i!s]i!t]i!u]i!v]i!w]i!`]i!a]i!f]i!^]i!e]i~O!o!TO!W[i!X[i!Y[i!Z[i![[i!][i!p[i!q[i!r[i!s[i!t[i!u[i!v[i!w[i~Oy[i}[i!Q[i!R[i!S[i!T[i!U[i!V[i!_[i!b[i!d[i!h[i!j[i!k[i!l[i~P?^O![Zi!]Zi!sZi!tZi!uZi!vZi!wZi~O!p!UOyZi}Zi!QZi!RZi!SZi!TZi!UZi!VZi!WZi!XZi!YZi!ZZi!_Zi!bZi!dZi!hZi!jZi!kZi!lZi~PAbO![Xi!]Xi!tXi!uXi!vXi!wXi~O!s!XOyXi}Xi!QXi!RXi!SXi!TXi!UXi!VXi!WXi!XXi!YXi!ZXi!_Xi!bXi!dXi!hXi!jXi!kXi!lXi~PC]O![Wi!]Wi!uWi!vWi!wWi~O!t!YOyWi}Wi!QWi!RWi!SWi!TWi!UWi!VWi!WWi!XWi!YWi!ZWi!_Wi!bWi!dWi!hWi!jWi!kWi!lWi~PETO!u!ZOyVi}Vi!QVi!RVi!SVi!TVi!UVi!VVi!WVi!XVi!YVi!ZVi![Vi!]Vi!_Vi!bVi!dVi!hVi!jVi!kVi!lVi!vVi!wVi~O}oO!^#pO~O!a#rO~P$wO!e!lO~O}#cO!fii~O!e#vO~O!]#wO!^#yO~O!fpa~P$wO!`#zO!fpa~O}oO~O}#cO~O!^#}O!i#iO~P$wO!]$OO!^#}O~O!fpi~P$wO!^$QO!i#iO~P$wO!l!TO!`[i!a[i!f[i!^[i!e[i~P?^O!l$SO!o$SO!O[X!m[X!n[X~P,OO!p$TO!OZX!mZX!nZX!oZX!`ZX!aZX!fZX!^ZX!eZX~P.cO!t$VO!OWX!mWX!nWX!oWX!pWX!qWX!rWX!sWX!`WX!aWX!fWX!^WX!eWX~P2[O!u$WO!OVX!mVX!nVX!oVX!pVX!qVX!rVX!sVX!tVX!`VX!aVX!fVX!^VX!eVX~P4PO!v$XO!w$bO!OUX!mUX!nUX!oUX!pUX!qUX!rUX!sUX!tUX!uUX!`UX!aUX!fUX!^UX!eUX~P5qO!p$`O!`Zi!aZi!fZi!^Zi!eZi~PAbO!W!VO!X!VO!Y!VO!Z!VO!p$`O!q!VO!r!VO!`ZX!aZX!sZX!tZX!uZX!vZX!wZX!fZX![ZX!]ZX!^ZX!eZX~O!s$aO!OXX!mXX!nXX!oXX!pXX!qXX!rXX!`XX!aXX!fXX!^XX!eXX~P0dO!s$gO!`Xi!aXi!fXi!^Xi!eXi~PC]O!t$hO!`Wi!aWi!fWi!^Wi!eWi~PETO!u$iO!`Vi!aVi!vVi!wVi!fVi![Vi!]Vi!^Vi!eVi~O!s$gO!`XX!aXX!tXX!uXX!vXX!wXX!fXX![XX!]XX!^XX!eXX~O!t$hO!`WX!aWX!uWX!vWX!wWX!fWX![WX!]WX!^WX!eWX~O!u$iO!`VX!aVX!vVX!wVX!fVX![VX!]VX!^VX!eVX~O!v$jO!w$kO!`UX!aUX!fUX![UX!]UX!^UX!eUX~O!P$lO~O!b!c!h!j!w!T!U!S!R!i}!T~",
goto: "2pyPPz!O!`!l#q$a%R%|&z'{(w*O+W,^-f.j/n/n/n/n/n0r/n0u0x/n1Q1Y/n1`1c1f1o1u1|2S2^2d2jTjOkSiOkQqSStUuV#X!c#Z#qShOk]$tSUu!c#Z#qSiOkQnRQsTQ{VQ|WQ!s!OS#Q!^$lY#]!f#_#m#s#zQ#a!kQ#b!lQ#g!oW#i!r#w$O$RQ#u#hR#{#v!OgORTVWk!O!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$lV#P!]$b$kWfOk!]!^Y$_R!k!o#h$bs$sTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lWeOk!]!^Q#O![Q#W$XY$^R!k!o#h$bQ$o$js$rTVW!O!f!l!r#_#m#s#v#w#z$O$R$k$lYdOk![!]!^Q!}!ZQ#V$W[$]R!k!o#h$X$bQ$n$iu$qTVW!O!f!l!r#_#m#s#v#w#z$O$R$j$k$l[cOk!Z![!]!^Q!|!YQ#U$V^$eR!k!o#h$W$X$bQ$m$hw$pTVW!O!f!l!r#_#m#s#v#w#z$O$R$i$j$k$l!hbORTVWk!O!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$V$W$X$b$h$i$j$k$lV!{!X$a$g`aOk!X!Y!Z![!]!^Q!z!WQ#T$Ub$[R!k!o#h$V$W$X$a$bQ$c$f{$dTVW!O!f!l!r#_#m#s#v#w#z$O$R$g$h$i$j$k$l!``OTVWk!O!W!X!Y!Z![!]!^!f!l!r#_#m#s#v#w#z$O$R$f$g$h$i$j$k$lQ!y!UQ#S$TQ$Y$`e$ZR!k!o#h$U$V$W$X$a$b!z_ORTVWk!O!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!x!TR#R$S#O^ORTVWk!O!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lQ!R]R!w!S#T[ORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TZORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$l#TYORTVW]k!O!S!T!U!W!X!Y!Z![!]!^!f!k!l!o!r#_#h#m#s#v#w#z$O$R$S$T$U$V$W$X$`$a$b$f$g$h$i$j$k$lRvURzVQxVV#d!m#e#tQ#l!rV#|#w$O$RX#j!r#w$O$RR!P[R!u!OQ!WaQ$U$[R$f$dQkOR!`kSmPoR!bmQ!dqR#[!dQ!gsS#`!g#nR#n!sQuUR!juQ!nxR#f!nQ#x#lR$P#x",
nodeNames: "⚠ Source Statement FieldDefinition FieldPath Expression DefaultExpression PatchExpression ComposeExpression LogicalOrExpression LogicalAndExpression ComparisonExpression ConcatExpression AdditiveExpression MultiplicativeExpression UnaryExpression PostfixExpression PrimaryExpression Literal ComparisonConstraint Object Array LetExpression FieldDefinitionList FunctionExpression ParameterList Parameter MatchExpression MatchArm Pattern ImportExpression CallSuffix ArgumentList CompareOperator",
maxTerm: 85,
skippedNodes: [0],
repeatNodeCount: 7,
tokenData: "=T~R!PX^$Upq$Uqr$yrs%Wst&zvw'cxy'pyz'uz{'z{|(P|}(^}!O(c!O!P(h!P!Q(m!Q![+T![!]+n!]!^+s!^!_+x!_!`,V!`!a,l!c!},y!}#O-[#P#Q-a#R#S-f#T#W,y#W#X-y#X#Y,y#Y#Z1Z#Z#],y#]#^3k#^#`,y#`#a6}#a#b8b#b#h,y#h#i:r#i#o,y#o#p<n#p#q<s#q#r=O#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~$ZY{~X^$Upq$U#y#z$U$f$g$U#BY#BZ$U$IS$I_$U$I|$JO$U$JT$JU$U$KV$KW$U&FU&FV$U~%OP!k~!_!`%R~%WO!r~~%ZWOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t<%lO%W~%xO!Q~~%{RO;'S%W;'S;=`&U;=`O%W~&XXOY%WZr%Wrs%ss#O%W#O#P%x#P;'S%W;'S;=`&t;=`<%l%W<%lO%W~&wP;=`<%l%W~'PS|~OY&zZ;'S&z;'S;=`']<%lO&z~'`P;=`<%l&z~'hP!u~vw'k~'pO!s~~'uO!d~~'zO!f~~(PO!m~~(UP!o~{|(X~(^O!p~~(cO!`~~(hO!l~~(mO!O~~(rW!n~OY)[Z!P)[!P!Q+O!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)_WOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x<%lO)[~)|O!V~~*PRO;'S)[;'S;=`*Y;=`O)[~*]XOY)[Z!P)[!P!Q)w!Q#O)[#O#P)|#P;'S)[;'S;=`*x;=`<%l)[<%lO)[~*{P;=`<%l)[~+TO!v~~+YQ!R~!O!P+`!Q![+T~+cP!Q![+f~+kP!S~!Q![+f~+sO!e~~+xO!]~~+}P!W~!_!`,Q~,VO!X~~,[Q!PP!_!`,b!`!a,g~,gO!q~Q,lO!gQ~,qP!Y~!_!`,t~,yO!Z~~-OS}~!Q![,y!c!},y#R#S,y#T#o,y~-aO!_~~-fO!a~~-mS!i~}~!Q![,y!c!},y#R#S,y#T#o,y~.OU}~!Q![,y!c!},y#R#S,y#T#X,y#X#Y.b#Y#o,y~.gU}~!Q![,y!c!},y#R#S,y#T#Y,y#Y#Z.y#Z#o,y~/OT}~!Q![,y!c!},y#R#S,y#T#U/_#U#o,y~/dU}~!Q![,y!c!},y#R#S,y#T#i,y#i#j/v#j#o,y~/{U}~!Q![,y!c!},y#R#S,y#T#`,y#`#a0_#a#o,y~0dU}~!Q![,y!c!},y#R#S,y#T#h,y#h#i0v#i#o,y~0}S!w~}~!Q![,y!c!},y#R#S,y#T#o,y~1`T}~!Q![,y!c!},y#R#S,y#T#U1o#U#o,y~1tU}~!Q![,y!c!},y#R#S,y#T#`,y#`#a2W#a#o,y~2]U}~!Q![,y!c!},y#R#S,y#T#g,y#g#h2o#h#o,y~2tU}~!Q![,y!c!},y#R#S,y#T#X,y#X#Y3W#Y#o,y~3_S!U~}~!Q![,y!c!},y#R#S,y#T#o,y~3pV}~!Q![,y!c!},y#R#S,y#T#a,y#a#b4V#b#c6j#c#o,y~4[U}~!Q![,y!c!},y#R#S,y#T#d,y#d#e4n#e#o,y~4sU}~!Q![,y!c!},y#R#S,y#T#c,y#c#d5V#d#o,y~5[U}~!Q![,y!c!},y#R#S,y#T#f,y#f#g5n#g#o,y~5sU}~!Q![,y!c!},y#R#S,y#T#h,y#h#i6V#i#o,y~6^S!j~}~!Q![,y!c!},y#R#S,y#T#o,y~6qS!c~}~!Q![,y!c!},y#R#S,y#T#o,y~7SU}~!Q![,y!c!},y#R#S,y#T#X,y#X#Y7f#Y#o,y~7kU}~!Q![,y!c!},y#R#S,y#T#h,y#h#i7}#i#o,y~8US!b~}~!Q![,y!c!},y#R#S,y#T#o,y~8gT}~!Q![,y!c!},y#R#S,y#T#U8v#U#o,y~8{U}~!Q![,y!c!},y#R#S,y#T#h,y#h#i9_#i#o,y~9dU}~!Q![,y!c!},y#R#S,y#T#V,y#V#W9v#W#o,y~9{U}~!Q![,y!c!},y#R#S,y#T#[,y#[#]:_#]#o,y~:fS!h~}~!Q![,y!c!},y#R#S,y#T#o,y~:wU}~!Q![,y!c!},y#R#S,y#T#f,y#f#g;Z#g#o,y~;`U}~!Q![,y!c!},y#R#S,y#T#i,y#i#j;r#j#o,y~;wU}~!Q![,y!c!},y#R#S,y#T#X,y#X#Y<Z#Y#o,y~<bS!T~}~!Q![,y!c!},y#R#S,y#T#o,y~<sO![~~<vP#p#q<y~=OO!t~~=TO!^~",
tokenizers: [0, 1],
topRules: {"Source":[0,1]},
tokenPrec: 2481
})
@@ -0,0 +1,35 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Source = 1,
Statement = 2,
FieldDefinition = 3,
FieldPath = 4,
Expression = 5,
DefaultExpression = 6,
PatchExpression = 7,
ComposeExpression = 8,
LogicalOrExpression = 9,
LogicalAndExpression = 10,
ComparisonExpression = 11,
ConcatExpression = 12,
AdditiveExpression = 13,
MultiplicativeExpression = 14,
UnaryExpression = 15,
PostfixExpression = 16,
PrimaryExpression = 17,
Literal = 18,
ComparisonConstraint = 19,
Object = 20,
Array = 21,
LetExpression = 22,
FieldDefinitionList = 23,
FunctionExpression = 24,
ParameterList = 25,
Parameter = 26,
MatchExpression = 27,
MatchArm = 28,
Pattern = 29,
ImportExpression = 30,
CallSuffix = 31,
ArgumentList = 32,
CompareOperator = 33
@@ -0,0 +1,57 @@
import { LRLanguage, LanguageSupport, HighlightStyle, syntaxHighlighting, foldNodeProp, foldInside, indentNodeProp } from '@codemirror/language';
import { styleTags, tags as t } from '@lezer/highlight';
import { parser } from './decodal-parser.js';
const parserWithMetadata = parser.configure({
props: [
styleTags({
'let in match import default': t.keyword,
'true false': t.bool,
identifier: t.variableName,
string: t.string,
regex: t.regexp,
'integer float': t.number,
comment: t.lineComment,
'plus minus star slash plusPlus equal equalEqual bang bangEqual amp ampAmp pipePipe slashSlash gt gte lt lte arrow default': t.operator,
'lBrace rBrace': t.brace,
'lBracket rBracket': t.squareBracket,
'lParen rParen': t.paren,
'semicolon comma dot colon': t.punctuation,
}),
indentNodeProp.add({
Object: context => context.column(context.node.from) + context.unit,
Array: context => context.column(context.node.from) + context.unit,
MatchExpression: context => context.column(context.node.from) + context.unit,
LetExpression: context => context.column(context.node.from) + context.unit,
}),
foldNodeProp.add({
Object: foldInside,
Array: foldInside,
MatchExpression: foldInside,
}),
],
});
export const decodalLanguage = LRLanguage.define({
parser: parserWithMetadata,
languageData: {
commentTokens: { line: '#' },
closeBrackets: { brackets: ['(', '[', '{', '"'] },
},
});
export const decodalHighlightStyle = HighlightStyle.define([
{ tag: t.keyword, color: '#93c5fd', fontWeight: '700' },
{ tag: t.variableName, color: 'inherit' },
{ tag: t.bool, color: '#fbbf24' },
{ tag: t.number, color: '#fbbf24' },
{ tag: t.string, color: '#86efac' },
{ tag: t.regexp, color: '#86efac' },
{ tag: t.lineComment, color: '#94a3b8', fontStyle: 'italic' },
{ tag: t.operator, color: '#f9a8d4' },
{ tag: [t.brace, t.squareBracket, t.paren, t.punctuation], color: '#f9a8d4' },
]);
export function decodal() {
return new LanguageSupport(decodalLanguage, [syntaxHighlighting(decodalHighlightStyle)]);
}
+1
View File
@@ -24,6 +24,7 @@ export const nav = [
slug: 'language',
children: [
{ title: 'Syntax', slug: 'language/syntax' },
{ title: 'Grammar', slug: 'language/grammar' },
{
title: 'Value',
slug: 'language/value',
+3 -6
View File
@@ -29,13 +29,10 @@ import ManualLayout from '../layouts/ManualLayout.astro';
<button id="delete-file" class="danger-button" type="button">Delete file</button>
</aside>
<label class="pane input-pane">
<section class="pane input-pane">
<span id="active-file">Input</span>
<div class="editor-wrap">
<pre id="source-highlight" aria-hidden="true"></pre>
<textarea id="source" spellcheck="false"></textarea>
</div>
</label>
<div id="editor" class="code-editor"></div>
</section>
<section class="pane output-pane">
<span>Output</span>
+81 -42
View File
@@ -1,12 +1,13 @@
import init, { evaluateProject, tokenizeSource } from '../wasm/decodal_wasm.js';
import { highlightDecodal, highlightDecodalTokens } from '../lib/highlight.js';
import init, { evaluateProject } from '../wasm/decodal_wasm.js';
import { EditorView, basicSetup } from 'codemirror';
import { keymap } from '@codemirror/view';
import { decodal } from '../lib/codemirror/decodal.js';
import { playgroundExamples } from './playground-examples.js';
const STORAGE_KEY = 'decodal-playground-project-v1';
const starterProject = playgroundExamples[0];
const source = document.getElementById('source');
const sourceHighlight = document.getElementById('source-highlight');
const editorHost = document.getElementById('editor');
const output = document.getElementById('output');
const run = document.getElementById('run');
const status = document.getElementById('status');
@@ -18,7 +19,63 @@ const exampleSelect = document.getElementById('example-select');
const loadExample = document.getElementById('load-example');
const project = loadProject();
let wasmReady = false;
const editorTheme = EditorView.theme({
'&': {
backgroundColor: '#0f172a',
color: '#e5e7eb',
height: '100%',
},
'.cm-scroller': {
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
fontSize: '14px',
lineHeight: '1.5',
overflow: 'auto',
},
'.cm-content': {
caretColor: '#e5e7eb',
minHeight: '100%',
padding: '12px',
},
'.cm-gutters': {
backgroundColor: '#111827',
borderRight: '1px solid #334155',
color: '#94a3b8',
},
'&.cm-focused': {
outline: 'none',
},
'&.cm-focused .cm-cursor': {
borderLeftColor: '#e5e7eb',
},
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': {
backgroundColor: 'rgb(59 130 246 / 0.35)',
},
}, { dark: true });
const editor = new EditorView({
doc: project.files[project.activePath] ?? '',
parent: editorHost,
extensions: [
basicSetup,
decodal(),
editorTheme,
keymap.of([
{
key: 'Mod-Enter',
run() {
execute();
return true;
},
},
]),
EditorView.updateListener.of((update) => {
if (!update.docChanged) return;
project.files[project.activePath] = update.state.doc.toString();
saveProject();
}),
],
});
for (const example of playgroundExamples) {
const option = document.createElement('option');
@@ -30,7 +87,6 @@ exampleSelect.value = starterProject.id;
setActiveFile(project.activePath);
renderFileTree();
updateHighlight();
function loadProject() {
try {
@@ -84,41 +140,36 @@ function normalizePath(path) {
return parts.join('/');
}
function getEditorText() {
return editor.state.doc.toString();
}
function setEditorText(value) {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: value,
},
});
}
function setActiveFile(path) {
const normalized = normalizePath(path);
if (project.files[normalized] === undefined) return;
if (project.activePath && project.files[project.activePath] !== undefined) {
project.files[project.activePath] = getEditorText();
}
project.activePath = normalized;
source.value = project.files[normalized];
setEditorText(project.files[normalized]);
activeFile.textContent = normalized;
deleteFile.disabled = Object.keys(project.files).length <= 1;
updateHighlight();
syncHighlightScroll();
renderFileTree();
saveProject();
}
function updateHighlight() {
sourceHighlight.innerHTML = `${highlightSource(source.value)}\n`;
}
function highlightSource(value) {
if (!wasmReady) return highlightDecodal(value);
try {
const result = JSON.parse(tokenizeSource(value));
if (result.ok) return highlightDecodalTokens(value, result.tokens);
} catch (_error) {
// Fall back to the lightweight JavaScript highlighter.
}
return highlightDecodal(value);
}
function syncHighlightScroll() {
sourceHighlight.scrollTop = source.scrollTop;
sourceHighlight.scrollLeft = source.scrollLeft;
}
function execute() {
project.files[project.activePath] = source.value;
project.files[project.activePath] = getEditorText();
saveProject();
const result = JSON.parse(evaluateProject(project.activePath, JSON.stringify(project.files)));
output.textContent = result.ok ? result.output : result.error;
@@ -177,8 +228,6 @@ function compareNodes(a, b) {
try {
await init();
wasmReady = true;
updateHighlight();
run.disabled = false;
status.textContent = '';
execute();
@@ -209,13 +258,3 @@ deleteFile.addEventListener('click', () => {
delete project.files[project.activePath];
setActiveFile(Object.keys(project.files).sort()[0]);
});
source.addEventListener('input', () => {
project.files[project.activePath] = source.value;
updateHighlight();
syncHighlightScroll();
saveProject();
});
source.addEventListener('scroll', syncHighlightScroll);
source.addEventListener('keydown', (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') execute();
});
+9 -26
View File
@@ -419,43 +419,26 @@ button:disabled {
text-align: left;
}
.editor-wrap {
.code-editor {
background: var(--editor-bg);
flex: 1;
min-height: 0;
position: relative;
}
.editor-wrap pre,
.editor-wrap textarea {
border: 0;
box-sizing: border-box;
font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
inset: 0;
margin: 0;
overflow: auto;
padding: 12px;
position: absolute;
tab-size: 2;
white-space: pre;
.code-editor .cm-editor {
height: 100%;
}
.editor-wrap pre {
color: var(--editor-text);
pointer-events: none;
.code-editor .cm-line {
padding: 0;
}
.editor-wrap textarea {
background: transparent;
caret-color: var(--editor-text);
color: transparent;
outline: none;
resize: none;
width: 100%;
.code-editor .cm-activeLine {
background: rgb(59 130 246 / 0.08);
}
.editor-wrap textarea::selection {
background: var(--selection);
.code-editor .cm-activeLineGutter {
background: rgb(59 130 246 / 0.12);
}
.output-pane pre {