Compare commits
5 Commits
d074b59a89
...
03c7bfee39
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c7bfee39 | |||
| fa67c26c0a | |||
| 7589518ca7 | |||
| e39b67c87c | |||
| f57b1c985e |
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@context trackScope from "./scopeTracker"
|
||||
|
||||
@skip { space | comment }
|
||||
@skip { space | Comment }
|
||||
|
||||
@top Program { item* }
|
||||
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
newlineOrSemicolon { "\n" | ";" }
|
||||
eof { @eof }
|
||||
space { " " | "\t" }
|
||||
comment { "#" ![\n]* }
|
||||
Comment { "#" " " ![\n]* }
|
||||
leftParen { "(" }
|
||||
rightParen { ")" }
|
||||
colon[closedBy="end", @name="colon"] { ":" }
|
||||
|
|
|
|||
|
|
@ -24,42 +24,43 @@ export const
|
|||
Word = 22,
|
||||
IdentifierBeforeDot = 23,
|
||||
Do = 24,
|
||||
Program = 25,
|
||||
PipeExpr = 26,
|
||||
FunctionCall = 27,
|
||||
DotGet = 28,
|
||||
Number = 29,
|
||||
ParenExpr = 30,
|
||||
FunctionCallOrIdentifier = 31,
|
||||
BinOp = 32,
|
||||
String = 33,
|
||||
StringFragment = 34,
|
||||
Interpolation = 35,
|
||||
EscapeSeq = 36,
|
||||
Boolean = 37,
|
||||
Regex = 38,
|
||||
Dict = 39,
|
||||
NamedArg = 40,
|
||||
NamedArgPrefix = 41,
|
||||
FunctionDef = 42,
|
||||
Params = 43,
|
||||
NamedParam = 44,
|
||||
Null = 45,
|
||||
colon = 46,
|
||||
CatchExpr = 47,
|
||||
keyword = 68,
|
||||
Block = 49,
|
||||
FinallyExpr = 50,
|
||||
Underscore = 53,
|
||||
Array = 54,
|
||||
ConditionalOp = 55,
|
||||
PositionalArg = 56,
|
||||
WhileExpr = 58,
|
||||
FunctionCallWithBlock = 60,
|
||||
TryExpr = 61,
|
||||
Throw = 63,
|
||||
IfExpr = 65,
|
||||
ElseIfExpr = 67,
|
||||
ElseExpr = 69,
|
||||
CompoundAssign = 70,
|
||||
Assign = 71
|
||||
Comment = 25,
|
||||
Program = 26,
|
||||
PipeExpr = 27,
|
||||
FunctionCall = 28,
|
||||
DotGet = 29,
|
||||
Number = 30,
|
||||
ParenExpr = 31,
|
||||
FunctionCallOrIdentifier = 32,
|
||||
BinOp = 33,
|
||||
String = 34,
|
||||
StringFragment = 35,
|
||||
Interpolation = 36,
|
||||
EscapeSeq = 37,
|
||||
Boolean = 38,
|
||||
Regex = 39,
|
||||
Dict = 40,
|
||||
NamedArg = 41,
|
||||
NamedArgPrefix = 42,
|
||||
FunctionDef = 43,
|
||||
Params = 44,
|
||||
NamedParam = 45,
|
||||
Null = 46,
|
||||
colon = 47,
|
||||
CatchExpr = 48,
|
||||
keyword = 69,
|
||||
Block = 50,
|
||||
FinallyExpr = 51,
|
||||
Underscore = 54,
|
||||
Array = 55,
|
||||
ConditionalOp = 56,
|
||||
PositionalArg = 57,
|
||||
WhileExpr = 59,
|
||||
FunctionCallWithBlock = 61,
|
||||
TryExpr = 62,
|
||||
Throw = 64,
|
||||
IfExpr = 66,
|
||||
ElseIfExpr = 68,
|
||||
ElseExpr = 70,
|
||||
CompoundAssign = 71,
|
||||
Assign = 72
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer"
|
|||
import {tokenizer, specializeKeyword} from "./tokenizer"
|
||||
import {trackScope} from "./scopeTracker"
|
||||
import {highlighting} from "./highlight"
|
||||
const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, else:136}
|
||||
const spec_Identifier = {__proto__:null,null:92, catch:98, finally:104, end:106, while:120, try:126, throw:130, if:134, else:138}
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "9OQYQbOOO#zQcO'#C{O$zOSO'#C}OOQa'#DT'#DTO&TQbO'#DdO'iQcO'#E]OOQa'#E]'#E]O(lQcO'#E]O)nQcO'#E[O*UQRO'#C|O+eQcO'#EWO+uQcO'#EWO,PQbO'#CzO,wOpO'#CxOOQ`'#EX'#EXO,|QbO'#EWO-WQRO'#DtOOQ`'#EW'#EWO-lQQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO-tQbO'#DWO.PQbO'#DhO.tQQO'#DkO.PQbO'#DmO.PQbO'#DoO.yQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Ek'#EkOOQ`'#EO'#EOO/TQbO,59cO/}QbO'#DPO0VQWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0kOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO0yQbO,5:OO1QQQO,59oOOQa,5:O,5:OO1]QbO,5:OO1gQbO,5:aO.PQbO,59hO.PQbO,59hO.PQbO,59hO.PQbO,5:PO.PQbO,5:PO.PQbO,5:PO1zQRO,59fO2RQRO,59fO2dQRO,59fO2_QQO,59fO2oQQO,59fO2wObO,59dO3SQbO'#EPO3_QbO,59bO3yQbO,5:UO1gQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`'#Dy'#DyO4aQbO'#DXO4lQbO'#DYOOQO'#Dz'#DzO4dQQO'#DXO4zQQO,59rO5kQRO,5:SO5rQRO,5:SO3yQbO,5:VO5}QcO,5:XO6yQcO,5:XO7ZQcO,5:XO7eQRO,5:ZO7lQRO,5:ZOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7v-E7vO7wQQO1G/ZOOQa1G/j1G/jO8SQbO1G/jOOQO'#D|'#D|O7wQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O8SQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO9OQcO1G/SO9YQcO1G/SO9dQcO1G/SOOQa1G/k1G/kO;YQcO1G/kO;aQcO1G/kO;hQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;oQbO'#CwOOQ`,5:k,5:kOOQ`-E7}-E7}OOQ`'#D_'#D_O;|QbO'#D_O<pQbO1G/pOOQ`1G/z1G/zOOQ`-E7w-E7wO<{QQO,59sOOQO,59t,59tOOQO-E7x-E7xO=TQbO1G/^O3yQbO1G/nO=kQbO1G/qO3yQbO1G/uO=vQQO7+$uOOQa7+$u7+$uO>RQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O>]QQO'#D{O>bQbO'#EhOOQ`,59y,59yO?UQbO'#D]O?ZQQO'#D`OOQ`7+%[7+%[O?`QbO7+%[O?eQbO7+%[O?mQbO7+$xO?xQbO7+$xO@iQbO7+%YOOQ`7+%]7+%]O@nQbO7+%]O@sQbO7+%]O@{QbO7+%aOOQa<<Ha<<HaOOQa<<Hp<<HpOOQ`,5:g,5:gOOQ`-E7y-E7yOATQQO,59wO3yQbO,59zOOQ`<<Hv<<HvOAYQbO<<HvOOQ`<<Hd<<HdOA_QbO<<HdOAdQbO<<HdOAlQbO<<HdOOQ`<<Ht<<HtOOQ`<<Hw<<HwOAwQbO<<HwOOQ`'#EQ'#EQOA|QbO<<H{OBUQbO'#DsOOQ`<<H{<<H{OB^QbO<<H{O3yQbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`AN>OAN>OOBcQbOAN>OOBhQbOAN>OOOQ`AN>cAN>cOOQ`-E8O-E8OOOQ`AN>gAN>gOBpQbOAN>gO.PQbO,5:]O3yQbO,5:_OOQ`7+$}7+$}OOQ`G23jG23jOBuQbOG23jPBXQbO'#DqOOQ`G24RG24ROBzQRO1G/wOCRQRO1G/wOOQ`1G/y1G/yOOQ`LD)ULD)UO3yQbO7+%cOOQ`<<H}<<H}",
|
||||
stateData: "Ca~O!wOS!xOS~OdPOe`OfUOg]OhfOmUOuUOvUO}UO!]gO!`hO!biO!djO!}[O#QQO#XRO#YSO#ZcO~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO!ZoX#ZoX#`oX#^oX!QoX!ToX!UoX!foX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX!OoX~P!dOrsO#QvO#SqO#TrO~OdlOfUOg]OmUOuUOvUOykO}UO!}[O#QQO#XRO#YSO#ZwO~O#]zO~P%YOP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#`#PX!Q#PX!T#PX!U#PX!f#PX~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO#^#PX~P&[OV|O~P&[OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!f!zX~P(sOP!OOQ!OOR!POS!POT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO^}O~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!f!zX~OP!OOQ!OOR!POS!PO~P+POT!ROU!SO~P+POdPOfUOg]OhfOmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!|!YO~O!O!]O!Z!ZO~P+POV|O_!^O`!^Oa!^Ob!^Oc!^O~O#Z!_O#`!_O~Od!aOy!cO!O{P~OdlOfUOg]OmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!O!iO~OhfO!V!oO~P.POhfOykO!VmO!Oka!Zka#Zka#`ka#^ka!Qka!Tka!Uka!fka~P.POd!qO!}[O~O#Q!rO#S!rO#T!rO#U!rO#V!rO#W!rO~OrsO#Q!tO#SqO#TrO~O#]!wO~P%YOykO#Z!yO#]!{O~O#Z!|O#]!wO~P.POe`O!]gO!`hO!biO!djO~P,PO#^#XO~P(sOP!OOQ!OOR!POS!PO#^#XO~OT!ROU!SO#^#XO~O!Z!ZO#^#XO~Od#YOm#YO!}[O~Od#ZOg]O!}[O~O!Z!ZO#Zja#`ja#^ja!Qja!Tja!Uja!fja~Oe`O!]gO!`hO!biO!djO#Z#`O~P,POd!aOy!cO!O{X~Om#eOu#eO}#eO#QQO~O!O#gO~OT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~O!O#hO~P5POT!ROU!SO!O#hO~O#Z!aa#`!aa!Q!aa!T!aa!U!aa!f!aa~P*UO#Z!aa#`!aa!Q!aa!T!aa!U!aa!f!aa~OP!OOQ!OOR!POS!PO~P6eOT!ROU!SO~P6eO!O#jO~P5POT!ROU!SO!O#jO~OykO#Z!yO#]#lO~O#Z!|O#]#nO~P.PO^}ORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!fpi~OPpiQpi~P8^OP!OOQ!OO~P8^OP!OOQ!OORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!fpi~OW!QOX!QOY!QOZ!QO[!QO]!QOT!Xi#Z!Xi#`!Xi#^!Xi!O!Xi!Q!Xi!T!Xi!U!Xi!f!Xi~OU!SO~P:XOU!SO~P:kOU!Xi~P:XOhfOykO!VmO~P.POe`O!]gO!`hO!biO!djO#Z#qO!Q#[P!T#[P!U#[P!f#[P~P,PO!Q#uO!T#vO!U#wO~Oy!cO!O{a~Oe`O!]gO!`hO!biO!djO#Z#{O~P,PO!Q#uO!T#vO!U#}O~OykO#Z!yO#]$RO~O#Z!|O#]$SO~P.PO#Z$TO~Oe`O!]gO!`hO!biO!djO#Z#qO!Q#[X!T#[X!U#[X!f#[X~P,POd$VO~O!O$WO~O!U$XO~O!T#vO!U$XO~O!Q#uO!T#vO!U$ZO~Oe`O!]gO!`hO!biO!djO#Z#qO!Q#[P!T#[P!U#[P~P,PO!U$_O~O!U$`O~O!T#vO!U$`O~O!U$eO!f$dO~O!O$gO~O!U$iO~O!U$jO~O!T#vO!U$jO~O!Q#uO!T#vO!U$jO~O!U$mO~O!U$oO!f$dO~O!O$rO!d$qO~O!U$oO~O!U$tO~O!T#vO!U$tO~O!U$wO~O!U${O~O!O$|O~P5POT!ROU!SO!O$|O~Omv~",
|
||||
goto: "3}#`PPPPPPPPPPPPPPPPPPPPPPPPPP#a#v$[P%[#v&b'QP(O(OPP(S(}P)b*R*UPP*[P*h+QPPP+h,e-^P-eP-e-eP-eP-eP-wP-{-e-e.R.X._.e.k.u.|/W/b/k/rPPPP/x/|0jPP1S2mP3lPPPPPPPP3pPP3vpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|R!W[u^O[e|!Z!]!^!i#`#g#h#j#s#{$W$g$r$|rPO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR#Z!ZrTO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||UPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qQ!qqQ#Y!YR#[!ZpYOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!U[Q!kiQ#P!OR#S!P!pUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|R#e!cTsQu!qUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|YnPTp#Z#[QySQ!vxX!yy!v!z#kpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q!W[R!okR!ffX!df!b!e#dQ#y#aQ$P#iQ$]#zR$l$^Q#a!]Q#i!iQ#|#hQ$Q#jQ$h$WQ$s$gQ$z$rR$}$|Q#x#aQ$O#iQ$Y#yQ$[#zQ$a$PS$k$]$^R$u$l!OUPST[gijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qqVOe|!]!^!i#`#g#h#j#s#{$W$g$r$|pZOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!V[Q!hgQ!liQ!njQ#T!SQ#V!RR$y$qZnPTp#Z#[qaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|T$b$Q$cQ$f$QR$p$cQeOR!`eQuQR!suQxSR!uxQ!bfR#c!bQ!efQ#d!bT#f!e#dS#s#`#{R$U#sQ!zyQ#k!vT#o!z#kQ!}{Q#m!xT#p!}#mWpPT#Z#[R!ppS![_!XR#^![Q$c$QR$n$cTdOeSbOeQ#O|`#_!]!i#h#j$W$g$r$|Q#b!^U#r#`#s#{R#z#gp_Oe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!X[R#]!ZrXO[e|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q{SQ!ggQ!jiQ!mjQ!okQ!xxW!|{!x!}#mQ#P}Q#Q!OQ#R!PQ#T!QQ#U!RQ#W!SR$x$qpWOe|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR!T[TtQuQ#t#`R$^#{ZoPTp#Z#[",
|
||||
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword FunctionCallWithBlock TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr CompoundAssign Assign",
|
||||
states: "9OQYQbOOO#zQcO'#C|O$zOSO'#DOOOQa'#DU'#DUO&TQbO'#DeO'iQcO'#E]OOQa'#E]'#E]O(lQcO'#E]O)nQcO'#E[O*UQRO'#C}O+eQcO'#EWO+uQcO'#EWO,PQbO'#C{O,wOpO'#CyOOQ`'#EX'#EXO,|QbO'#EWO-WQRO'#DuOOQ`'#EW'#EWO-lQQO'#EVOOQ`'#EV'#EVOOQ`'#Dw'#DwQYQbOOO-tQbO'#DXO.PQbO'#DiO.tQQO'#DlO.PQbO'#DnO.PQbO'#DpO.yQbO'#DVOOQa'#E['#E[OOQ`'#Dg'#DgOOQ`'#Ek'#EkOOQ`'#EP'#EPO/TQbO,59dO/}QbO'#DQO0VQWO'#DROOOO'#E_'#E_OOOO'#Dx'#DxO0kOSO,59jOOQa,59j,59jOOQ`'#Dy'#DyO0yQbO,5:PO1QQQO,59pOOQa,5:P,5:PO1]QbO,5:PO1gQbO,5:bO.PQbO,59iO.PQbO,59iO.PQbO,59iO.PQbO,5:QO.PQbO,5:QO.PQbO,5:QO1zQRO,59gO2RQRO,59gO2dQRO,59gO2_QQO,59gO2oQQO,59gO2wObO,59eO3SQbO'#EQO3_QbO,59cO3yQbO,5:VO1gQbO,5:aOOQ`,5:q,5:qOOQ`-E7u-E7uOOQ`'#Dz'#DzO4aQbO'#DYO4lQbO'#DZOOQO'#D{'#D{O4dQQO'#DYO4zQQO,59sO5kQRO,5:TO5rQRO,5:TO3yQbO,5:WO5}QcO,5:YO6yQcO,5:YO7ZQcO,5:YO7eQRO,5:[O7lQRO,5:[OOQ`,59q,59qOOQ`-E7}-E7}OOOO,59l,59lOOOO,59m,59mOOOO-E7v-E7vOOQa1G/U1G/UOOQ`-E7w-E7wO7wQQO1G/[OOQa1G/k1G/kO8SQbO1G/kOOQO'#D}'#D}O7wQQO1G/[OOQa1G/[1G/[OOQ`'#EO'#EOO8SQbO1G/kOOQ`1G/|1G/|OOQa1G/T1G/TO9OQcO1G/TO9YQcO1G/TO9dQcO1G/TOOQa1G/l1G/lO;YQcO1G/lO;aQcO1G/lO;hQcO1G/lOOQa1G/R1G/ROOQa1G/P1G/PO!dQbO'#C|O;oQbO'#CxOOQ`,5:l,5:lOOQ`-E8O-E8OOOQ`'#D`'#D`O;|QbO'#D`O<pQbO1G/qOOQ`1G/{1G/{OOQ`-E7x-E7xO<{QQO,59tOOQO,59u,59uOOQO-E7y-E7yO=TQbO1G/_O3yQbO1G/oO=kQbO1G/rO3yQbO1G/vO=vQQO7+$vOOQa7+$v7+$vO>RQbO7+%VOOQa7+%V7+%VOOQO-E7{-E7{OOQ`-E7|-E7|OOQ`'#D|'#D|O>]QQO'#D|O>bQbO'#EhOOQ`,59z,59zO?UQbO'#D^O?ZQQO'#DaOOQ`7+%]7+%]O?`QbO7+%]O?eQbO7+%]O?mQbO7+$yO?xQbO7+$yO@iQbO7+%ZOOQ`7+%^7+%^O@nQbO7+%^O@sQbO7+%^O@{QbO7+%bOOQa<<Hb<<HbOOQa<<Hq<<HqOOQ`,5:h,5:hOOQ`-E7z-E7zOATQQO,59xO3yQbO,59{OOQ`<<Hw<<HwOAYQbO<<HwOOQ`<<He<<HeOA_QbO<<HeOAdQbO<<HeOAlQbO<<HeOOQ`<<Hu<<HuOOQ`<<Hx<<HxOAwQbO<<HxOOQ`'#ER'#EROA|QbO<<H|OBUQbO'#DtOOQ`<<H|<<H|OB^QbO<<H|O3yQbO1G/dOOQ`1G/g1G/gOOQ`AN>cAN>cOOQ`AN>PAN>POBcQbOAN>POBhQbOAN>POOQ`AN>dAN>dOOQ`-E8P-E8POOQ`AN>hAN>hOBpQbOAN>hO.PQbO,5:^O3yQbO,5:`OOQ`7+%O7+%OOOQ`G23kG23kOBuQbOG23kPBXQbO'#DrOOQ`G24SG24SOBzQRO1G/xOCRQRO1G/xOOQ`1G/z1G/zOOQ`LD)VLD)VO3yQbO7+%dOOQ`<<IO<<IO",
|
||||
stateData: "Ca~O!xOSiOS~OdPOe`OfUOg]OhfOnUOvUOwUO!OUO!^gO!ahO!ciO!ejO!}[O#QQO#XRO#YSO#ZcO~OdlOfUOg]OhfOnUOvUOwUOzkO!OUO!WmO!}[O#QQO#XRO#YSO![pX#ZpX#`pX#^pX!RpX!UpX!VpX!gpX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX!PpX~P!dOssO#QvO#SqO#TrO~OdlOfUOg]OnUOvUOwUOzkO!OUO!}[O#QQO#XRO#YSO#ZwO~O#]zO~P%YOP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#`#PX!R#PX!U#PX!V#PX!g#PX~OdlOfUOg]OhfOnUOvUOwUOzkO!OUO!WmO!}[O#QQO#XRO#YSO#^#PX~P&[OV|O~P&[OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#`!zX!R!zX!U!zX!V!zX!g!zX~P(sOP!OOQ!OOR!POS!POT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO^}O~O#Z!zX#`!zX!R!zX!U!zX!V!zX!g!zX~OP!OOQ!OOR!POS!PO~P+POT!ROU!SO~P+POdPOfUOg]OhfOnUOvUOwUO!OUO!}[O#QQO#XRO#YSO~O!|!YO~O!P!]O![!ZO~P+POV|O_!^O`!^Oa!^Ob!^Oc!^O~O#Z!_O#`!_O~Od!aOz!cO!P|P~OdlOfUOg]OnUOvUOwUO!OUO!}[O#QQO#XRO#YSO~O!P!iO~OhfO!W!oO~P.POhfOzkO!WmO!Pla![la#Zla#`la#^la!Rla!Ula!Vla!gla~P.POd!qO!}[O~O#Q!rO#S!rO#T!rO#U!rO#V!rO#W!rO~OssO#Q!tO#SqO#TrO~O#]!wO~P%YOzkO#Z!yO#]!{O~O#Z!|O#]!wO~P.POe`O!^gO!ahO!ciO!ejO~P,PO#^#XO~P(sOP!OOQ!OOR!POS!PO#^#XO~OT!ROU!SO#^#XO~O![!ZO#^#XO~Od#YOn#YO!}[O~Od#ZOg]O!}[O~O![!ZO#Zka#`ka#^ka!Rka!Uka!Vka!gka~Oe`O!^gO!ahO!ciO!ejO#Z#`O~P,POd!aOz!cO!P|X~On#eOv#eO!O#eO#QQO~O!P#gO~OT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~O!P#hO~P5POT!ROU!SO!P#hO~O#Z!ba#`!ba!R!ba!U!ba!V!ba!g!ba~P*UO#Z!ba#`!ba!R!ba!U!ba!V!ba!g!ba~OP!OOQ!OOR!POS!PO~P6eOT!ROU!SO~P6eO!P#jO~P5POT!ROU!SO!P#jO~OzkO#Z!yO#]#lO~O#Z!|O#]#nO~P.PO^}ORqiSqi#Zqi#`qi#^qi!Rqi!Uqi!Vqi!gqi~OPqiQqi~P8^OP!OOQ!OO~P8^OP!OOQ!OORqiSqi#Zqi#`qi#^qi!Rqi!Uqi!Vqi!gqi~OW!QOX!QOY!QOZ!QO[!QO]!QOT!Yi#Z!Yi#`!Yi#^!Yi!P!Yi!R!Yi!U!Yi!V!Yi!g!Yi~OU!SO~P:XOU!SO~P:kOU!Yi~P:XOhfOzkO!WmO~P.POe`O!^gO!ahO!ciO!ejO#Z#qO!R#[P!U#[P!V#[P!g#[P~P,PO!R#uO!U#vO!V#wO~Oz!cO!P|a~Oe`O!^gO!ahO!ciO!ejO#Z#{O~P,PO!R#uO!U#vO!V#}O~OzkO#Z!yO#]$RO~O#Z!|O#]$SO~P.PO#Z$TO~Oe`O!^gO!ahO!ciO!ejO#Z#qO!R#[X!U#[X!V#[X!g#[X~P,POd$VO~O!P$WO~O!V$XO~O!U#vO!V$XO~O!R#uO!U#vO!V$ZO~Oe`O!^gO!ahO!ciO!ejO#Z#qO!R#[P!U#[P!V#[P~P,PO!V$_O~O!V$`O~O!U#vO!V$`O~O!V$eO!g$dO~O!P$gO~O!V$iO~O!V$jO~O!U#vO!V$jO~O!R#uO!U#vO!V$jO~O!V$mO~O!V$oO!g$dO~O!P$rO!e$qO~O!V$oO~O!V$tO~O!U#vO!V$tO~O!V$wO~O!V${O~O!P$|O~P5POT!ROU!SO!P$|O~Onw~",
|
||||
goto: "3}#`PPPPPPPPPPPPPPPPPPPPPPPPPPP#a#v$[P%[#v&b'QP(O(OPP(S(}P)b*R*UPP*[P*h+QPPP+h,e-^P-eP-e-eP-eP-eP-wP-{-e-e.R.X._.e.k.u.|/W/b/k/rPPP/x/|0jPP1S2mP3lPPPPPPPP3pPP3vpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|R!W[u^O[e|!Z!]!^!i#`#g#h#j#s#{$W$g$r$|rPO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR#Z!ZrTO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||UPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qQ!qqQ#Y!YR#[!ZpYOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!U[Q!kiQ#P!OR#S!P!pUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|R#e!cTsQu!qUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|YnPTp#Z#[QySQ!vxX!yy!v!z#kpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q!W[R!okR!ffX!df!b!e#dQ#y#aQ$P#iQ$]#zR$l$^Q#a!]Q#i!iQ#|#hQ$Q#jQ$h$WQ$s$gQ$z$rR$}$|Q#x#aQ$O#iQ$Y#yQ$[#zQ$a$PS$k$]$^R$u$l!OUPST[gijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qqVOe|!]!^!i#`#g#h#j#s#{$W$g$r$|pZOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!V[Q!hgQ!liQ!njQ#T!SQ#V!RR$y$qZnPTp#Z#[qaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|T$b$Q$cQ$f$QR$p$cQeOR!`eQuQR!suQxSR!uxQ!bfR#c!bQ!efQ#d!bT#f!e#dS#s#`#{R$U#sQ!zyQ#k!vT#o!z#kQ!}{Q#m!xT#p!}#mWpPT#Z#[R!ppS![_!XR#^![Q$c$QR$n$cTdOeSbOeQ#O|`#_!]!i#h#j$W$g$r$|Q#b!^U#r#`#s#{R#z#gp_Oe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!X[R#]!ZrXO[e|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q{SQ!ggQ!jiQ!mjQ!okQ!xxW!|{!x!}#mQ#P}Q#Q!OQ#R!PQ#T!QQ#U!RQ#W!SR$x$qpWOe|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR!T[TtQuQ#t#`R$^#{ZoPTp#Z#[",
|
||||
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword FunctionCallWithBlock TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr CompoundAssign Assign",
|
||||
maxTerm: 108,
|
||||
context: trackScope,
|
||||
nodeProps: [
|
||||
["closedBy", 46,"end"]
|
||||
["closedBy", 47,"end"]
|
||||
],
|
||||
propSources: [highlighting],
|
||||
skippedNodes: [0],
|
||||
skippedNodes: [0,25],
|
||||
repeatNodeCount: 11,
|
||||
tokenData: "C_~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O+X!O!P#{!P!Q-n!Q![)S![!]6Z!]!^%T!^!}#{!}#O6t#O#P8j#P#Q8o#Q#R#{#R#S9Y#S#T#{#T#Y,Y#Y#Z9s#Z#b,Y#b#c>q#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#h<s#h#o,Y#o;'S#{;'S;=`$d<%lO#{U<x^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#X,Y#X#Y=t#Y#o,Y#o;'S#{;'S;=`$d<%lO#{U={[uQrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^>x[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j<s#j#o,Y#o;'S#{;'S;=`$d<%lO#{UBvU!ZQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C_O#`~",
|
||||
tokenData: "C|~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'tuw#{wx'yxy(Oyz(iz{#{{|)S|}#{}!O+v!O!P#{!P!Q.]!Q![)q![!]6x!]!^%T!^!}#{!}#O7c#O#P9X#P#Q9^#Q#R#{#R#S9w#S#T#{#T#Y,w#Y#Z:b#Z#b,w#b#c?`#c#f,w#f#g@]#g#h,w#h#iAY#i#o,w#o#p#{#p#qC^#q;'S#{;'S;=`$d<%l~#{~O#{~~CwS$QUsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUsS!xYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UsS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%sWsSOp#{pq&]qt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^&dZiYsSOY&]YZ#{Zt&]tu'Vuw&]wx'Vx#O&]#O#P'V#P;'S&];'S;=`'n<%lO&]Y'[SiYOY'VZ;'S'V;'S;=`'h<%lO'VY'kP;=`<%l'V^'qP;=`<%l&]~'yO#S~~(OO#Q~U(VUsS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(pUsS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U)XWsSOt#{uw#{x!Q#{!Q![)q![#O#{#P;'S#{;'S;=`$d<%lO#{U)xYsSnQOt#{uw#{x!O#{!O!P*h!P!Q#{!Q![)q![#O#{#P;'S#{;'S;=`$d<%lO#{U*mWsSOt#{uw#{x!Q#{!Q![+V![#O#{#P;'S#{;'S;=`$d<%lO#{U+^WsSnQOt#{uw#{x!Q#{!Q![+V![#O#{#P;'S#{;'S;=`$d<%lO#{U+{^sSOt#{uw#{x}#{}!O,w!O!Q#{!Q![)q![!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{U,|[sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{U-yUzQsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U.bWsSOt#{uw#{x!P#{!P!Q.z!Q#O#{#P;'S#{;'S;=`$d<%lO#{U/P^sSOY/{YZ#{Zt/{tu1Ouw/{wx1Ox!P/{!P!Q#{!Q!}/{!}#O5q#O#P3^#P;'S/{;'S;=`6r<%lO/{U0S^sSwQOY/{YZ#{Zt/{tu1Ouw/{wx1Ox!P/{!P!Q3s!Q!}/{!}#O5q#O#P3^#P;'S/{;'S;=`6r<%lO/{Q1TXwQOY1OZ!P1O!P!Q1p!Q!}1O!}#O2_#O#P3^#P;'S1O;'S;=`3m<%lO1OQ1sP!P!Q1vQ1{UwQ#Z#[1v#]#^1v#a#b1v#g#h1v#i#j1v#m#n1vQ2bVOY2_Z#O2_#O#P2w#P#Q1O#Q;'S2_;'S;=`3W<%lO2_Q2zSOY2_Z;'S2_;'S;=`3W<%lO2_Q3ZP;=`<%l2_Q3aSOY1OZ;'S1O;'S;=`3m<%lO1OQ3pP;=`<%l1OU3xWsSOt#{uw#{x!P#{!P!Q4b!Q#O#{#P;'S#{;'S;=`$d<%lO#{U4ibsSwQOt#{uw#{x#O#{#P#Z#{#Z#[4b#[#]#{#]#^4b#^#a#{#a#b4b#b#g#{#g#h4b#h#i#{#i#j4b#j#m#{#m#n4b#n;'S#{;'S;=`$d<%lO#{U5v[sSOY5qYZ#{Zt5qtu2_uw5qwx2_x#O5q#O#P2w#P#Q/{#Q;'S5q;'S;=`6l<%lO5qU6oP;=`<%l5qU6uP;=`<%l/{U7PUsS!PQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7jW#YQsSOt#{uw#{x!_#{!_!`8S!`#O#{#P;'S#{;'S;=`$d<%lO#{U8XVsSOt#{uw#{x#O#{#P#Q8n#Q;'S#{;'S;=`$d<%lO#{U8uU#XQsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~9^O#T~U9eU#]QsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:OUsS!WQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:g]sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#U;`#U#o,w#o;'S#{;'S;=`$d<%lO#{U;e^sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#`,w#`#a<a#a#o,w#o;'S#{;'S;=`$d<%lO#{U<f^sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#g,w#g#h=b#h#o,w#o;'S#{;'S;=`$d<%lO#{U=g^sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#X,w#X#Y>c#Y#o,w#o;'S#{;'S;=`$d<%lO#{U>j[vQsSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^?g[#UWsSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^@d[#WWsSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^Aa^#VWsSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#f,w#f#gB]#g#o,w#o;'S#{;'S;=`$d<%lO#{UBb^sSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#i,w#i#j=b#j#o,w#o;'S#{;'S;=`$d<%lO#{UCeU![QsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C|O#`~",
|
||||
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!|~~", 11)],
|
||||
topRules: {"Program":[0,25]},
|
||||
topRules: {"Program":[0,26]},
|
||||
specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
|
||||
tokenPrec: 1578
|
||||
})
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ describe('Identifier', () => {
|
|||
FunctionCallOrIdentifier
|
||||
Identifier even?`)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('Unicode Symbol Support', () => {
|
||||
|
|
@ -637,26 +636,51 @@ describe('DotGet whitespace sensitivity', () => {
|
|||
})
|
||||
|
||||
describe('Comments', () => {
|
||||
test('are barely there', () => {
|
||||
expect(`x = 5 # one banana\ny = 2 # two bananas`).toMatchTree(`
|
||||
test('are greedy', () => {
|
||||
expect(`
|
||||
x = 5 # one banana
|
||||
y = 2 # two bananas`).toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier x
|
||||
Eq =
|
||||
Number 5
|
||||
Comment # one banana
|
||||
Assign
|
||||
AssignableIdentifier y
|
||||
Eq =
|
||||
Number 2`)
|
||||
Number 2
|
||||
Comment # two bananas`)
|
||||
|
||||
expect('# some comment\nbasename = 5 # very astute\n basename / prop\n# good info').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier basename
|
||||
Eq =
|
||||
Number 5
|
||||
BinOp
|
||||
Identifier basename
|
||||
Slash /
|
||||
Identifier prop`)
|
||||
expect(`
|
||||
# some comment
|
||||
basename = 5 # very astute
|
||||
basename / prop
|
||||
# good info`).toMatchTree(`
|
||||
Comment # some comment
|
||||
Assign
|
||||
AssignableIdentifier basename
|
||||
Eq =
|
||||
Number 5
|
||||
Comment # very astute
|
||||
BinOp
|
||||
Identifier basename
|
||||
Slash /
|
||||
Identifier prop
|
||||
Comment # good info`)
|
||||
})
|
||||
|
||||
test('words with # are not considered comments', () => {
|
||||
expect('find #hashtag-file.txt').toMatchTree(`
|
||||
FunctionCall
|
||||
Identifier find
|
||||
PositionalArg
|
||||
Word #hashtag-file.txt`)
|
||||
})
|
||||
|
||||
test('hastags in strings are not comments', () => {
|
||||
expect("'this is not a #comment'").toMatchTree(`
|
||||
String
|
||||
StringFragment this is not a #comment`)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -697,7 +721,6 @@ describe('Array destructuring', () => {
|
|||
Number 2`)
|
||||
})
|
||||
|
||||
|
||||
test('works with dotget', () => {
|
||||
expect('[ a ] = [ [1 2 3] ]; a.1').toMatchTree(`
|
||||
Assign
|
||||
|
|
@ -800,4 +823,4 @@ Assign
|
|||
keyword end
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
2
vscode-extension/.gitignore
vendored
2
vscode-extension/.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
node_modules
|
||||
client/dist
|
||||
server/dist
|
||||
*.vsix
|
||||
*.vsix
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"compile": "bun run compile:client && bun run compile:server",
|
||||
"compile:client": "bun build client/src/extension.ts --outdir client/dist --target node --format cjs --external vscode",
|
||||
"compile:server": "bun build server/src/server.ts --outdir server/dist --target node --format cjs",
|
||||
"watch": "bun run compile:client --watch",
|
||||
"watch": "bun run compile:client --watch & bun run compile:server --watch",
|
||||
"package": "bun run compile:client --minify && bun run compile:server --minify",
|
||||
"check-types": "tsc --noEmit",
|
||||
"build-and-install": "bun run package && bunx @vscode/vsce package --allow-missing-repository && code --install-extension shrimp-*.vsix"
|
||||
|
|
|
|||
145
vscode-extension/server/src/scopeTracker.test.ts
Normal file
145
vscode-extension/server/src/scopeTracker.test.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import { test, expect, describe } from 'bun:test'
|
||||
import { ScopeTracker } from './scopeTracker'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { parser } from '../../../src/parser/shrimp'
|
||||
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||
|
||||
describe('ScopeTracker', () => {
|
||||
test('top-level assignment is in scope', () => {
|
||||
const code = 'x = 5\necho x'
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find the 'x' identifier in 'echo x'
|
||||
const identifiers: any[] = []
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
identifiers.push(node.node)
|
||||
}
|
||||
})
|
||||
|
||||
// Second identifier should be the 'x' in 'echo x'
|
||||
const xInEcho = identifiers[1]
|
||||
expect(xInEcho).toBeDefined()
|
||||
expect(tracker.isInScope('x', xInEcho)).toBe(true)
|
||||
})
|
||||
|
||||
test('undeclared variable is not in scope', () => {
|
||||
const code = 'echo x'
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find the 'x' identifier
|
||||
let xNode: any = null
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
xNode = node.node
|
||||
}
|
||||
})
|
||||
|
||||
expect(xNode).toBeDefined()
|
||||
expect(tracker.isInScope('x', xNode)).toBe(false)
|
||||
})
|
||||
|
||||
test('function parameter is in scope inside function', () => {
|
||||
const code = `greet = do name:
|
||||
echo name
|
||||
end`
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find all identifiers
|
||||
const identifiers: any[] = []
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
identifiers.push(node.node)
|
||||
}
|
||||
})
|
||||
|
||||
// Find the 'name' in 'echo name' (should be last identifier)
|
||||
const nameInEcho = identifiers[identifiers.length - 1]
|
||||
expect(tracker.isInScope('name', nameInEcho)).toBe(true)
|
||||
})
|
||||
|
||||
test('assignment before usage is in scope', () => {
|
||||
const code = `x = 5
|
||||
y = 10
|
||||
echo x y`
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find identifiers
|
||||
const identifiers: any[] = []
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
identifiers.push(node.node)
|
||||
}
|
||||
})
|
||||
|
||||
// Last two identifiers should be 'x' and 'y' in 'echo x y'
|
||||
const xInEcho = identifiers[identifiers.length - 2]
|
||||
const yInEcho = identifiers[identifiers.length - 1]
|
||||
|
||||
expect(tracker.isInScope('x', xInEcho)).toBe(true)
|
||||
expect(tracker.isInScope('y', yInEcho)).toBe(true)
|
||||
})
|
||||
|
||||
test('assignment after usage is not in scope', () => {
|
||||
const code = `echo x
|
||||
x = 5`
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find the first 'x' identifier (in echo)
|
||||
let xNode: any = null
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier && !xNode) {
|
||||
xNode = node.node
|
||||
}
|
||||
})
|
||||
|
||||
expect(tracker.isInScope('x', xNode)).toBe(false)
|
||||
})
|
||||
|
||||
test('nested function has access to outer scope', () => {
|
||||
const code = `x = 5
|
||||
greet = do:
|
||||
echo x
|
||||
end`
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find all identifiers
|
||||
const identifiers: any[] = []
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
identifiers.push(node.node)
|
||||
}
|
||||
})
|
||||
|
||||
// Find the 'x' in 'echo x' (should be last identifier)
|
||||
const xInEcho = identifiers[identifiers.length - 1]
|
||||
expect(tracker.isInScope('x', xInEcho)).toBe(true)
|
||||
})
|
||||
|
||||
test('inner function parameter shadows outer variable', () => {
|
||||
const code = `x = 5
|
||||
greet = do x:
|
||||
echo x
|
||||
end`
|
||||
const { tree, tracker } = parseAndGetScope(code)
|
||||
|
||||
// Find all identifiers
|
||||
const identifiers: any[] = []
|
||||
tree.topNode.cursor().iterate((node: any) => {
|
||||
if (node.type.id === Terms.Identifier) {
|
||||
identifiers.push(node.node)
|
||||
}
|
||||
})
|
||||
|
||||
// The 'x' in 'echo x' should have 'x' in scope (from parameter)
|
||||
const xInEcho = identifiers[identifiers.length - 1]
|
||||
expect(tracker.isInScope('x', xInEcho)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
const parseAndGetScope = (code: string) => {
|
||||
const document = TextDocument.create('test://test.sh', 'shrimp', 1, code)
|
||||
const tree = parser.parse(code)
|
||||
const tracker = new ScopeTracker(document)
|
||||
return { document, tree, tracker }
|
||||
}
|
||||
135
vscode-extension/server/src/scopeTracker.ts
Normal file
135
vscode-extension/server/src/scopeTracker.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { SyntaxNode } from '@lezer/common'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||
|
||||
/**
|
||||
* Tracks variables in scope at a given position in the parse tree.
|
||||
* Used to distinguish identifiers (in scope) from words (not in scope).
|
||||
*/
|
||||
export class ScopeTracker {
|
||||
private document: TextDocument
|
||||
private scopeCache = new Map<number, Set<string>>()
|
||||
|
||||
constructor(document: TextDocument) {
|
||||
this.document = document
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a name is in scope at the given node's position.
|
||||
*/
|
||||
isInScope(name: string, node: SyntaxNode): boolean {
|
||||
const scope = this.getScopeAt(node)
|
||||
return scope.has(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all variables in scope at the given node's position.
|
||||
*/
|
||||
private getScopeAt(node: SyntaxNode): Set<string> {
|
||||
const position = node.from
|
||||
|
||||
// Check cache first
|
||||
if (this.scopeCache.has(position)) {
|
||||
return this.scopeCache.get(position)!
|
||||
}
|
||||
|
||||
const scope = new Set<string>()
|
||||
|
||||
// Find all containing function definitions
|
||||
const containingFunctions = this.findContainingFunctions(node)
|
||||
|
||||
// Collect scope from each containing function (inner to outer)
|
||||
for (const fnNode of containingFunctions) {
|
||||
this.collectParams(fnNode, scope)
|
||||
this.collectAssignments(fnNode, position, scope)
|
||||
}
|
||||
|
||||
// Collect top-level assignments
|
||||
const root = this.getRoot(node)
|
||||
this.collectAssignments(root, position, scope)
|
||||
|
||||
this.scopeCache.set(position, scope)
|
||||
return scope
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all function definitions that contain the given node.
|
||||
*/
|
||||
private findContainingFunctions(node: SyntaxNode): SyntaxNode[] {
|
||||
const functions: SyntaxNode[] = []
|
||||
let current = node.parent
|
||||
|
||||
while (current) {
|
||||
if (current.type.id === Terms.FunctionDef) {
|
||||
functions.unshift(current) // Add to beginning for outer-to-inner order
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
|
||||
return functions
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root node of the tree.
|
||||
*/
|
||||
private getRoot(node: SyntaxNode): SyntaxNode {
|
||||
let current = node
|
||||
while (current.parent) {
|
||||
current = current.parent
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect parameter names from a function definition.
|
||||
*/
|
||||
private collectParams(fnNode: SyntaxNode, scope: Set<string>) {
|
||||
let child = fnNode.firstChild
|
||||
while (child) {
|
||||
if (child.type.id === Terms.Params) {
|
||||
let param = child.firstChild
|
||||
while (param) {
|
||||
if (param.type.id === Terms.Identifier) {
|
||||
const text = this.document.getText({
|
||||
start: this.document.positionAt(param.from),
|
||||
end: this.document.positionAt(param.to),
|
||||
})
|
||||
scope.add(text)
|
||||
}
|
||||
param = param.nextSibling
|
||||
}
|
||||
break
|
||||
}
|
||||
child = child.nextSibling
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect assignment names from a scope node that occur before the given position.
|
||||
*/
|
||||
private collectAssignments(scopeNode: SyntaxNode, beforePosition: number, scope: Set<string>) {
|
||||
const cursor = scopeNode.cursor()
|
||||
|
||||
cursor.iterate((node) => {
|
||||
// Stop if we've passed the position we're checking
|
||||
if (node.from >= beforePosition) return false
|
||||
|
||||
if (node.type.id === Terms.Assign) {
|
||||
const assignNode = node.node
|
||||
const child = assignNode.firstChild
|
||||
if (child?.type.id === Terms.AssignableIdentifier) {
|
||||
const text = this.document.getText({
|
||||
start: this.document.positionAt(child.from),
|
||||
end: this.document.positionAt(child.to),
|
||||
})
|
||||
scope.add(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't descend into nested functions unless it's the current scope
|
||||
if (node.type.id === Terms.FunctionDef && node.node !== scopeNode) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,12 @@ import { parser } from '../../../src/parser/shrimp'
|
|||
import * as Terms from '../../../src/parser/shrimp.terms'
|
||||
import { SyntaxNode } from '@lezer/common'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { SemanticTokensBuilder, SemanticTokenTypes } from 'vscode-languageserver/node'
|
||||
import {
|
||||
SemanticTokensBuilder,
|
||||
SemanticTokenTypes,
|
||||
SemanticTokenModifiers,
|
||||
} from 'vscode-languageserver/node'
|
||||
import { ScopeTracker } from './scopeTracker'
|
||||
|
||||
export const TOKEN_TYPES = [
|
||||
SemanticTokenTypes.function,
|
||||
|
|
@ -14,56 +19,154 @@ export const TOKEN_TYPES = [
|
|||
SemanticTokenTypes.parameter,
|
||||
SemanticTokenTypes.property,
|
||||
SemanticTokenTypes.regexp,
|
||||
SemanticTokenTypes.comment,
|
||||
]
|
||||
|
||||
export const TOKEN_MODIFIERS: string[] = []
|
||||
export const TOKEN_MODIFIERS = [
|
||||
SemanticTokenModifiers.declaration,
|
||||
SemanticTokenModifiers.modification,
|
||||
SemanticTokenModifiers.readonly,
|
||||
]
|
||||
|
||||
export function buildSemanticTokens(document: TextDocument): number[] {
|
||||
const text = document.getText()
|
||||
const tree = parser.parse(text)
|
||||
const builder = new SemanticTokensBuilder()
|
||||
const scopeTracker = new ScopeTracker(document)
|
||||
|
||||
walkTree(tree.topNode, document, builder)
|
||||
walkTree(tree.topNode, document, builder, scopeTracker)
|
||||
|
||||
return builder.build().data
|
||||
}
|
||||
|
||||
// Walk the tree and collect tokens
|
||||
function walkTree(node: SyntaxNode, document: TextDocument, builder: SemanticTokensBuilder) {
|
||||
const tokenType = getTokenType(node.type.id)
|
||||
function walkTree(
|
||||
node: SyntaxNode,
|
||||
document: TextDocument,
|
||||
builder: SemanticTokensBuilder,
|
||||
scopeTracker: ScopeTracker
|
||||
) {
|
||||
const tokenInfo = getTokenType(node, document, scopeTracker)
|
||||
|
||||
if (tokenType !== undefined) {
|
||||
if (tokenInfo !== undefined) {
|
||||
const start = document.positionAt(node.from)
|
||||
const length = node.to - node.from
|
||||
builder.push(start.line, start.character, length, tokenType, 0)
|
||||
builder.push(start.line, start.character, length, tokenInfo.type, tokenInfo.modifiers)
|
||||
}
|
||||
|
||||
let child = node.firstChild
|
||||
while (child) {
|
||||
walkTree(child, document, builder)
|
||||
walkTree(child, document, builder, scopeTracker)
|
||||
child = child.nextSibling
|
||||
}
|
||||
}
|
||||
|
||||
// Map Lezer node IDs to semantic token type indices
|
||||
function getTokenType(nodeTypeId: number): number | undefined {
|
||||
switch (nodeTypeId) {
|
||||
case Terms.FunctionCall:
|
||||
case Terms.FunctionDef:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.function)
|
||||
// Map Lezer node IDs to semantic token type indices and modifiers
|
||||
type TokenInfo = { type: number; modifiers: number } | undefined
|
||||
function getTokenType(
|
||||
node: SyntaxNode,
|
||||
document: TextDocument,
|
||||
scopeTracker: ScopeTracker
|
||||
): TokenInfo {
|
||||
const nodeTypeId = node.type.id
|
||||
const parentTypeId = node.parent?.type.id
|
||||
|
||||
// Special case for now, eventually keywords will go away
|
||||
if (node.type.name === 'keyword') {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.keyword),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
|
||||
switch (nodeTypeId) {
|
||||
case Terms.Identifier:
|
||||
// Check parent to determine context
|
||||
if (parentTypeId === Terms.FunctionCall) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.function),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
if (parentTypeId === Terms.FunctionDef) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.function),
|
||||
modifiers: getModifierBits(SemanticTokenModifiers.declaration),
|
||||
}
|
||||
}
|
||||
if (parentTypeId === Terms.FunctionCallOrIdentifier) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.function),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
if (parentTypeId === Terms.Params) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.parameter),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
if (parentTypeId === Terms.DotGet) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.property),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: Identifier in PositionalArg - check scope
|
||||
if (parentTypeId === Terms.PositionalArg) {
|
||||
const identifierText = document.getText({
|
||||
start: document.positionAt(node.from),
|
||||
end: document.positionAt(node.to),
|
||||
})
|
||||
|
||||
// If not in scope, treat as string (like a Word)
|
||||
if (!scopeTracker.isInScope(identifierText, node)) {
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.string),
|
||||
modifiers: 0,
|
||||
}
|
||||
}
|
||||
// If in scope, fall through to treat as variable
|
||||
}
|
||||
|
||||
// Otherwise it's a regular variable
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.variable),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.IdentifierBeforeDot:
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.variable),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.NamedArgPrefix:
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.property),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.AssignableIdentifier:
|
||||
case Terms.FunctionCallOrIdentifier:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.variable)
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.variable),
|
||||
modifiers: getModifierBits(SemanticTokenModifiers.modification),
|
||||
}
|
||||
|
||||
case Terms.String:
|
||||
case Terms.StringFragment:
|
||||
case Terms.Word:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.string)
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.string),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.Number:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.number)
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.number),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.Plus:
|
||||
case Terms.Minus:
|
||||
|
|
@ -79,23 +182,40 @@ function getTokenType(nodeTypeId: number): number | undefined {
|
|||
case Terms.Modulo:
|
||||
case Terms.And:
|
||||
case Terms.Or:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.operator)
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.operator),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.keyword:
|
||||
case Terms.Do:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.keyword)
|
||||
|
||||
case Terms.Params:
|
||||
case Terms.NamedParam:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.parameter)
|
||||
|
||||
case Terms.DotGet:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.property)
|
||||
case Terms.colon:
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.keyword),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.Regex:
|
||||
return TOKEN_TYPES.indexOf(SemanticTokenTypes.regexp)
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.regexp),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
case Terms.Comment:
|
||||
return {
|
||||
type: TOKEN_TYPES.indexOf(SemanticTokenTypes.comment),
|
||||
modifiers: 0,
|
||||
}
|
||||
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const getModifierBits = (...modifiers: SemanticTokenModifiers[]): number => {
|
||||
let bits = 0
|
||||
for (const modifier of modifiers) {
|
||||
const index = TOKEN_MODIFIERS.indexOf(modifier)
|
||||
if (index !== -1) bits |= 1 << index
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,27 +86,38 @@ function handleParseTree(params: { uri: string }) {
|
|||
|
||||
const text = document.getText()
|
||||
const tree = parser.parse(text)
|
||||
const treeString = tree.toString()
|
||||
const cursor = tree.cursor()
|
||||
|
||||
// Format with indentation, without parentheses
|
||||
let formatted = ''
|
||||
let indent = 0
|
||||
for (let i = 0; i < treeString.length; i++) {
|
||||
const char = treeString[i]
|
||||
if (char === '(') {
|
||||
formatted += '\n'
|
||||
indent++
|
||||
formatted += ' '.repeat(indent)
|
||||
} else if (char === ')') {
|
||||
indent--
|
||||
} else if (char === ',') {
|
||||
formatted += '\n'
|
||||
formatted += ' '.repeat(indent)
|
||||
} else {
|
||||
formatted += char
|
||||
let depth = 0
|
||||
|
||||
const printNode = () => {
|
||||
const nodeName = cursor.name
|
||||
const nodeText = text.slice(cursor.from, cursor.to)
|
||||
const indent = ' '.repeat(depth)
|
||||
|
||||
formatted += `${indent}${nodeName}`
|
||||
if (nodeText) {
|
||||
const escapedText = nodeText.replace(/\n/g, '\\n').replace(/\r/g, '\\r')
|
||||
formatted += ` "${escapedText}"`
|
||||
}
|
||||
formatted += '\n'
|
||||
}
|
||||
|
||||
const traverse = (): void => {
|
||||
printNode()
|
||||
|
||||
if (cursor.firstChild()) {
|
||||
depth++
|
||||
do {
|
||||
traverse()
|
||||
} while (cursor.nextSibling())
|
||||
cursor.parent()
|
||||
depth--
|
||||
}
|
||||
}
|
||||
|
||||
traverse()
|
||||
return formatted
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user