Compare commits

..

9 Commits

Author SHA1 Message Date
Chris Wanstrath
05cf93ab36 prelude tests 2025-10-28 12:39:12 -07:00
2f54c6e6e5 need you 2025-10-26 15:59:27 -07:00
0bd36438c8 use module 2025-10-26 13:13:59 -07:00
225906179c native -> global 2025-10-26 13:05:31 -07:00
36fe676495 narrow type 2025-10-26 12:29:14 -07:00
f1367b64ef no valueFunctions 2025-10-26 12:29:06 -07:00
580af874c1 update-reef command 2025-10-26 12:27:38 -07:00
f7b429fbfe better echo 2025-10-26 12:12:00 -07:00
298948ac44 start on a prelude of builtin functions 2025-10-25 20:41:17 -07:00
18 changed files with 152 additions and 595 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "packages/ReefVM"]
path = packages/ReefVM
url = git@54.219.130.253:defunkt/ReefVM.git

View File

@ -9,7 +9,7 @@
"bun-plugin-tailwind": "^0.0.15", "bun-plugin-tailwind": "^0.0.15",
"codemirror": "^6.0.2", "codemirror": "^6.0.2",
"hono": "^4.9.8", "hono": "^4.9.8",
"reefvm": "git+https://git.nose.space/defunkt/reefvm", "reefvm": "workspace:*",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
}, },
"devDependencies": { "devDependencies": {
@ -18,6 +18,15 @@
"@types/bun": "latest", "@types/bun": "latest",
}, },
}, },
"packages/ReefVM": {
"name": "reefvm",
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
}, },
"packages": { "packages": {
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg=="], "@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg=="],
@ -62,7 +71,7 @@
"hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="], "hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="],
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#97b6722a113417398a1c47d583bfe07a906f87a0", { "peerDependencies": { "typescript": "^5" } }, "97b6722a113417398a1c47d583bfe07a906f87a0"], "reefvm": ["reefvm@workspace:packages/ReefVM"],
"style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="],
@ -73,5 +82,9 @@
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
"reefvm/@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
"reefvm/@types/bun/bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
} }
} }

View File

@ -13,7 +13,7 @@
"update-reef": "cd packages/ReefVM && git pull origin main" "update-reef": "cd packages/ReefVM && git pull origin main"
}, },
"dependencies": { "dependencies": {
"reefvm": "git+https://git.nose.space/defunkt/reefvm", "reefvm": "workspace:*",
"@codemirror/view": "^6.38.3", "@codemirror/view": "^6.38.3",
"@lezer/generator": "^1.8.0", "@lezer/generator": "^1.8.0",
"bun-plugin-tailwind": "^0.0.15", "bun-plugin-tailwind": "^0.0.15",
@ -31,4 +31,4 @@
"singleQuote": true, "singleQuote": true,
"printWidth": 100 "printWidth": 100
} }
} }

1
packages/ReefVM Submodule

@ -0,0 +1 @@
Subproject commit 17d846b99910a46fc1c7ab98aa41ca8afbc14097

View File

@ -303,8 +303,7 @@ export class Compiler {
return instructions return instructions
} }
case terms.ThenBlock: case terms.ThenBlock: {
case terms.SingleLineThenBlock: {
const instructions = getAllChildren(node) const instructions = getAllChildren(node)
.map((child) => this.#compileNode(child, input)) .map((child) => this.#compileNode(child, input))
.flat() .flat()
@ -469,11 +468,7 @@ export class Compiler {
} }
default: default:
throw new CompilerError( throw new CompilerError(`Unsupported syntax node: ${node.type.name}`, node.from, node.to)
`Compiler doesn't know how to handle a "${node.type.name}" node.`,
node.from,
node.to
)
} }
} }
} }

View File

@ -85,21 +85,6 @@ describe('compiler', () => {
expect(`bloop = do: 'bloop' end; bloop`).toEvaluateTo('bloop') expect(`bloop = do: 'bloop' end; bloop`).toEvaluateTo('bloop')
}) })
test('function call with if statement and multiple expressions', () => {
expect(`
abc = do:
if false:
echo nope
end
true
end
abc
`)
.toEvaluateTo(true)
})
test('simple conditionals', () => { test('simple conditionals', () => {
expect(`(3 < 6)`).toEvaluateTo(true) expect(`(3 < 6)`).toEvaluateTo(true)
expect(`(10 > 20)`).toEvaluateTo(false) expect(`(10 > 20)`).toEvaluateTo(false)
@ -154,10 +139,6 @@ describe('compiler', () => {
scattered scattered
end`).toEvaluateTo('dwarf') end`).toEvaluateTo('dwarf')
}) })
test('single line if', () => {
expect(`if 3 < 9: shire end`).toEvaluateTo('shire')
})
}) })
describe('errors', () => { describe('errors', () => {

View File

@ -70,9 +70,9 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
} }
const paramNames = getAllChildren(paramsNode).map((param) => { const paramNames = getAllChildren(paramsNode).map((param) => {
if (param.type.id !== terms.Identifier) { if (param.type.id !== terms.AssignableIdentifier) {
throw new CompilerError( throw new CompilerError(
`FunctionDef params must be Identifier, got ${param.type.name}`, `FunctionDef params must be AssignableIdentifiers, got ${param.type.name}`,
param.from, param.from,
param.to param.to
) )
@ -219,9 +219,9 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
) )
} }
if (property.type.id !== terms.Identifier && property.type.id !== terms.Number) { if (property.type.id !== terms.Identifier) {
throw new CompilerError( throw new CompilerError(
`DotGet property must be an Identifier or Number, got ${property.type.name}`, `DotGet property must be an Identifier, got ${property.type.name}`,
property.from, property.from,
property.to property.to
) )

View File

@ -57,30 +57,26 @@ const readIdentifierText = (input: InputStream, start: number, end: number): str
return text return text
} }
let inParams = false
export const trackScope = new ContextTracker<TrackerContext>({ export const trackScope = new ContextTracker<TrackerContext>({
start: new TrackerContext(new Scope(null, new Set())), start: new TrackerContext(new Scope(null, new Set())),
shift(context, term, stack, input) { shift(context, term, stack, input) {
if (term == terms.Do) inParams = true if (term !== terms.AssignableIdentifier) return context
if (term === terms.AssignableIdentifier) { const text = readIdentifierText(input, input.pos, stack.pos)
const text = readIdentifierText(input, input.pos, stack.pos) return new TrackerContext(context.scope, [...context.pendingIds, text])
return new TrackerContext(Scope.add(context.scope, text), context.pendingIds)
}
if (inParams && term === terms.Identifier) {
const text = readIdentifierText(input, input.pos, stack.pos)
return new TrackerContext(context.scope, [...context.pendingIds, text])
}
return context
}, },
reduce(context, term) { reduce(context, term) {
// Add assignment variable to scope
if (term === terms.Assign) {
const varName = context.pendingIds.at(-1)
if (!varName) return context
return new TrackerContext(Scope.add(context.scope, varName), context.pendingIds.slice(0, -1))
}
// Push new scope and add all parameters
if (term === terms.Params) { if (term === terms.Params) {
inParams = false
let newScope = context.scope.push() let newScope = context.scope.push()
if (context.pendingIds.length > 0) { if (context.pendingIds.length > 0) {
newScope = Scope.add(newScope, ...context.pendingIds) newScope = Scope.add(newScope, ...context.pendingIds)

View File

@ -2,7 +2,7 @@
@context trackScope from "./scopeTracker" @context trackScope from "./scopeTracker"
@skip { space | comment } @skip { space }
@top Program { item* } @top Program { item* }
@ -18,7 +18,6 @@
newlineOrSemicolon { "\n" | ";" } newlineOrSemicolon { "\n" | ";" }
eof { @eof } eof { @eof }
space { " " | "\t" } space { " " | "\t" }
comment { "#" ![\n]* }
leftParen { "(" } leftParen { "(" }
rightParen { ")" } rightParen { ")" }
colon[closedBy="end", @name="colon"] { ":" } colon[closedBy="end", @name="colon"] { ":" }
@ -29,7 +28,6 @@
} }
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot } @external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
@external specialize {Identifier} specializeKeyword from "./tokenizer" { Do }
@precedence { @precedence {
pipe @left, pipe @left,
@ -48,6 +46,7 @@ item {
consumeToTerminator { consumeToTerminator {
PipeExpr | PipeExpr |
ambiguousFunctionCall | ambiguousFunctionCall |
DotGet |
IfExpr | IfExpr |
FunctionDef | FunctionDef |
Assign | Assign |
@ -64,7 +63,7 @@ pipeOperand {
} }
FunctionCallOrIdentifier { FunctionCallOrIdentifier {
DotGet | Identifier Identifier
} }
ambiguousFunctionCall { ambiguousFunctionCall {
@ -72,7 +71,7 @@ ambiguousFunctionCall {
} }
FunctionCall { FunctionCall {
(DotGet | Identifier | ParenExpr) arg+ Identifier arg+
} }
arg { arg {
@ -93,11 +92,11 @@ FunctionDef {
} }
singleLineFunctionDef { singleLineFunctionDef {
Do Params colon consumeToTerminator @specialize[@name=keyword]<Identifier, "end"> @specialize[@name=keyword]<Identifier, "do"> Params colon consumeToTerminator @specialize[@name=keyword]<Identifier, "end">
} }
multilineFunctionDef { multilineFunctionDef {
Do Params colon newlineOrSemicolon block @specialize[@name=keyword]<Identifier, "end"> @specialize[@name=keyword]<Identifier, "do"> Params colon newlineOrSemicolon block @specialize[@name=keyword]<Identifier, "end">
} }
IfExpr { IfExpr {
@ -105,7 +104,7 @@ IfExpr {
} }
singleLineIf { singleLineIf {
@specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon SingleLineThenBlock @specialize[@name=keyword]<Identifier, "end"> @specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon ThenBlock { consumeToTerminator }
} }
multilineIf { multilineIf {
@ -124,10 +123,6 @@ ThenBlock {
block block
} }
SingleLineThenBlock {
consumeToTerminator
}
ConditionalOp { ConditionalOp {
expression Eq expression | expression Eq expression |
expression Neq expression | expression Neq expression |
@ -140,7 +135,7 @@ ConditionalOp {
} }
Params { Params {
Identifier* AssignableIdentifier*
} }
Assign { Assign {
@ -169,7 +164,7 @@ expression {
@skip {} { @skip {} {
DotGet { DotGet {
IdentifierBeforeDot dot (Number | Identifier) IdentifierBeforeDot dot Identifier
} }
String { "'" stringContent* "'" } String { "'" stringContent* "'" }
@ -204,5 +199,5 @@ expressionWithoutIdentifier {
} }
block { block {
(consumeToTerminator? newlineOrSemicolon)* (consumeToTerminator newlineOrSemicolon)*
} }

View File

@ -16,33 +16,31 @@ export const
AssignableIdentifier = 14, AssignableIdentifier = 14,
Word = 15, Word = 15,
IdentifierBeforeDot = 16, IdentifierBeforeDot = 16,
Do = 17, Program = 17,
Program = 18, PipeExpr = 18,
PipeExpr = 19, FunctionCall = 19,
FunctionCall = 20, PositionalArg = 20,
DotGet = 21, ParenExpr = 21,
Number = 22, FunctionCallOrIdentifier = 22,
ParenExpr = 23, BinOp = 23,
FunctionCallOrIdentifier = 24, ConditionalOp = 24,
BinOp = 25, FunctionDef = 25,
String = 26,
StringFragment = 27,
Interpolation = 28,
EscapeSeq = 29,
Boolean = 30,
Regex = 31,
Null = 32,
ConditionalOp = 33,
FunctionDef = 34,
Params = 35,
colon = 36,
keyword = 50, keyword = 50,
PositionalArg = 38, Params = 27,
colon = 28,
String = 30,
StringFragment = 31,
Interpolation = 32,
EscapeSeq = 33,
Number = 34,
Boolean = 35,
Regex = 36,
Null = 37,
DotGet = 38,
Underscore = 39, Underscore = 39,
NamedArg = 40, NamedArg = 40,
NamedArgPrefix = 41, NamedArgPrefix = 41,
IfExpr = 43, IfExpr = 43,
SingleLineThenBlock = 45,
ThenBlock = 46, ThenBlock = 46,
ElseIfExpr = 47, ElseIfExpr = 47,
ElseExpr = 49, ElseExpr = 49,

View File

@ -1,27 +1,27 @@
// This file was generated by lezer-generator. You probably shouldn't edit it. // This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser, LocalTokenGroup} from "@lezer/lr" import {LRParser, LocalTokenGroup} from "@lezer/lr"
import {operatorTokenizer} from "./operatorTokenizer" import {operatorTokenizer} from "./operatorTokenizer"
import {tokenizer, specializeKeyword} from "./tokenizer" import {tokenizer} from "./tokenizer"
import {trackScope} from "./scopeTracker" import {trackScope} from "./scopeTracker"
import {highlighting} from "./highlight" import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,null:64, end:74, if:88, elseif:96, else:100} const spec_Identifier = {__proto__:null,do:52, end:58, null:74, if:88, elseif:96, else:100}
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: "/SQYQbOOO!TOpO'#CqO#aQcO'#CtO$ZOSO'#CvO%aQcO'#DsOOQa'#Ds'#DsO&gQcO'#DrO'OQRO'#CuO'^QcO'#DnO'uQbO'#D{OOQ`'#DO'#DOO'}QbO'#CsOOQ`'#Do'#DoO(oQbO'#DnO(}QbO'#EROOQ`'#DX'#DXO)lQRO'#DaOOQ`'#Dn'#DnO)qQQO'#DmOOQ`'#Dm'#DmOOQ`'#Db'#DbQYQbOOO)yObO,59]OOQa'#Dr'#DrOOQ`'#DS'#DSO*RQbO'#DUOOQ`'#EQ'#EQOOQ`'#Df'#DfO*]QbO,59[O*pQbO'#CxO*xQWO'#CyOOOO'#Du'#DuOOOO'#Dc'#DcO+^OSO,59bOOQa,59b,59bO(}QbO,59aO(}QbO,59aOOQ`'#Dd'#DdO+lQbO'#DPO+tQQO,5:gO+yQRO,59_O-`QRO'#CuO-pQRO,59_O-|QQO,59_O.RQQO,59_O.ZQbO'#DgO.fQbO,59ZO.wQRO,5:mO/OQQO,5:mO/TQbO,59{OOQ`,5:X,5:XOOQ`-E7`-E7`OOQa1G.w1G.wOOQ`,59p,59pOOQ`-E7d-E7dOOOO,59d,59dOOOO,59e,59eOOOO-E7a-E7aOOQa1G.|1G.|OOQa1G.{1G.{O/_QcO1G.{OOQ`-E7b-E7bO/yQbO1G0ROOQa1G.y1G.yO(}QbO,59iO(}QbO,59iO!YQbO'#CtO$iQbO'#CpOOQ`,5:R,5:ROOQ`-E7e-E7eO0WQbO1G0XOOQ`1G/g1G/gO0eQbO7+%mO0jQbO7+%nOOQO1G/T1G/TO0zQRO1G/TOOQ`'#DZ'#DZO1UQbO7+%sO1ZQbO7+%tOOQ`<<IX<<IXOOQ`'#De'#DeO1qQQO'#DeO1vQbO'#EOO2^QbO<<IYOOQ`<<I_<<I_OOQ`'#D['#D[O2cQbO<<I`OOQ`,5:P,5:POOQ`-E7c-E7cOOQ`AN>tAN>tO(}QbO'#D]OOQ`'#Dh'#DhO2nQbOAN>zO2yQQO'#D_OOQ`AN>zAN>zO3OQbOAN>zO3TQRO,59wO3[QQO,59wOOQ`-E7f-E7fOOQ`G24fG24fO3aQbOG24fO3fQQO,59yO3kQQO1G/cOOQ`LD*QLD*QO0jQbO1G/eO1ZQbO7+$}OOQ`7+%P7+%POOQ`<<Hi<<Hi", states: ".jQVQbOOO#XQcO'#CrO$RQRO'#CsO$aQcO'#DmO$xQbO'#DsOOQ`'#Cu'#CuO%QQbO'#CqO%rOSO'#CzOOQa'#Dq'#DqO&QOpO'#DSO&VQcO'#DpOOQ`'#Dn'#DnO&nQbO'#DmO&|QbO'#EQOOQ`'#DX'#DXO'kQRO'#DaOOQ`'#Dm'#DmO'pQQO'#DlOOQ`'#Dl'#DlOOQ`'#Db'#DbQVQbOOOOQa'#Dp'#DpOOQ`'#Cp'#CpO'xQbO'#DUOOQ`'#Do'#DoOOQ`'#Dc'#DcO(SQbO,59ZO&|QbO,59_O&|QbO,59_OOQ`'#Dd'#DdO(pQbO'#CwO(xQQO,5:_O)iQRO'#CsO)yQRO,59]O*[QRO,59]O*VQQO,59]O+VQQO,59]O+_QbO'#C|O+gQWO'#C}OOOO'#Dy'#DyOOOO'#Df'#DfO+{OSO,59fOOQa,59f,59fO,ZO`O,59nO,`QbO'#DgO,eQbO,59YO,vQRO,5:lO,}QQO,5:lO-SQbO,59{OOQ`,5:W,5:WOOQ`-E7`-E7`OOQ`,59p,59pOOQ`-E7a-E7aOOQa1G.y1G.yO-^QcO1G.yOOQ`-E7b-E7bO-xQbO1G/yO&|QbO,59`O&|QbO,59`OOQa1G.w1G.wOOOO,59h,59hOOOO,59i,59iOOOO-E7d-E7dOOQa1G/Q1G/QOOQa1G/Y1G/YO!QQbO'#CrOOQ`,5:R,5:ROOQ`-E7e-E7eO.VQbO1G0WOOQ`1G/g1G/gO.dQbO7+%eO.iQbO7+%fOOQO1G.z1G.zO.vQRO1G.zOOQ`'#DZ'#DZOOQ`7+%r7+%rO/QQbO7+%sOOQ`<<IP<<IPO/eQQO'#DeO/jQbO'#DvO/}QbO<<IQOOQ`'#D['#D[O0SQbO<<I_OOQ`,5:P,5:POOQ`-E7c-E7cOOQ`AN>lAN>lO&|QbO'#D]OOQ`'#Dh'#DhO0_QbOAN>yO0jQQO'#D_OOQ`AN>yAN>yO0oQbOAN>yO0tQRO,59wO0{QQO,59wOOQ`-E7f-E7fOOQ`G24eG24eO1QQbOG24eO1VQQO,59yO1[QQO1G/cOOQ`LD*PLD*PO.iQbO1G/eO/QQbO7+$}OOQ`7+%P7+%POOQ`<<Hi<<Hi",
stateData: "3s~O!_OS!`OS~O]QO^`O_TO`POaXOfTOnTOoTOpTO|^O!eZO!hRO!qcO~O!dfO~O]gO_TO`POaXOfTOnTOoTOpTOwhOyiO!eZO!hROzhX!qhX!whX!shXuhX~OP!fXQ!fXR!fXS!fXT!fXU!fXV!fXW!fXX!fXY!fXZ!fX[!fX~P!YOkoO!hrO!jmO!knO~O]gO_TO`POaXOfTOnTOoTOpTOwhOyiO!eZO!hRO~OP!gXQ!gXR!gXS!gX!q!gX!w!gXT!gXU!gXV!gXW!gXX!gXY!gXZ!gX[!gX!s!gXu!gX~P$iOP!fXQ!fXR!fXS!fX!q!bX!w!bXu!bX~OPsOQsORtOStO~OPsOQsORtOStO!q!bX!w!bXu!bX~O]uOtsP~O]QO_TO`POaXOfTOnTOoTOpTO!eZO!hRO~Oz}O!q!bX!w!bXu!bX~O]gO_TO`POfTOnTOoTOpTO!eZO!hRO~OV!RO~O!q!SO!w!SO~O]!UOf!UO~OaXOw!VO~P(}Ozda!qda!wda!sdauda~P$iO]!XO!eZO~O!h!YO!j!YO!k!YO!l!YO!m!YO!n!YO~OkoO!h![O!jmO!knO~O]uOtsX~Ot!`O~O!s!aOP!fXQ!fXR!fXS!fXT!fXU!fXV!fXW!fXX!fXY!fXZ!fX[!fX~OT!cOU!cOV!bOW!bOX!bOY!bOZ!bO[!bO~OPsOQsORtOStO~P,tOPsOQsORtOStO!s!aO~Oz}O!s!aO~O]!dO`PO!eZO~Oz}O!qca!wca!scauca~Ot!hO~P,tOt!hO~O^`O|^O~P'}OPsOQsORiiSii!qii!wii!siiuii~O^`O|^O!q!kO~P'}O^`O|^O!q!pO~P'}Ou!qO~O^`O|^O!q!rOu!rP~P'}O!sqitqi~P,tOu!vO~O^`O|^O!q!rOu!rP!Q!rP!S!rP~P'}O!q!yO~O^`O|^O!q!rOu!rX!Q!rX!S!rX~P'}Ou!{O~Ou#QO!Q!|O!S#PO~Ou#VO!Q!|O!S#PO~Ot#XO~Ou#VO~Ot#YO~P,tOt#YO~Ou#ZO~O!q#[O~O!q#]O~Ofo~", stateData: "1d~O!_OS~O]PO^_O_WO`XOjSOrWOsWOtWOuWO|]O!fUO!ibO!lVO~O]eO_WO`XOjSOrWOsWOtWOuWOwfOygO!fUO!lVOzfX!ifX!vfX!kfXmfX~OP!dXQ!dXR!dXS!dXT!dXU!dXV!dXW!dXX!dXY!dXZ!dX[!dX~P!QOPkOQkORlOSlO~OPkOQkORlOSlO!i!aX!v!aXm!aX~O^mOlkP~O]PO_WO`XOjSOrWOsWOtWOuWO!fUO!lVO~OowO!lzO!nuO!ovO~O!s{O~OP!dXQ!dXR!dXS!dX!i!aX!v!aXm!aX~Oz|O!i!aX!v!aXm!aX~O]eO_WO`XOrWOsWOtWOuWO!fUO!lVO~OV!QO~O!i!RO!v!RO~OjSOw!TO~P&|OjSOwfOygOzca!ica!vca!kcamca~P&|O^mOlkX~Ol!YO~OT![OU![OV!ZOW!ZOX!ZOY!ZOZ!ZO[!ZO~OPkOQkORlOSlO~P(}OPkOQkORlOSlO!k!]O~O!k!]OP!dXQ!dXR!dXS!dXT!dXU!dXV!dXW!dXX!dXY!dXZ!dX[!dX~Oz|O!k!]O~O]!^O!fUO~O!l!_O!n!_O!o!_O!p!_O!q!_O!r!_O~OowO!l!aO!nuO!ovO~O]!bO~O]!cO~Oz|O!iba!vba!kbamba~Ol!fO~P(}Ol!fO~O^_O|]O~P%QOPkOQkORgiSgi!igi!vgi!kgimgi~O^_O|]O!i!iO~P%QO^_O|]O!i!nO~P%QOm!oO~O^_O|]Om!jP~P%QO!khilhi~P(}O^_O|]Om!jP!Q!jP!S!jP~P%QO!i!uO~O^_O|]Om!jX!Q!jX!S!jX~P%QOm!wO~Om!|O!Q!xO!S!{O~Om#RO!Q!xO!S!{O~Ol#TO~Om#RO~Ol#UO~P(}Ol#UO~Om#VO~O!i#WO~O!i#XO~Ort~",
goto: ",`!wPPPPPPPPPPPPPPPPPPP!x#X#gP$V#X$x%_P%x%xPPP%|&Y&sPP&vP&vPP&}P'Z'^'gP'kP&}'q'w'}(T(^(g(nPPPP(t(x)^PP)p*mP+[PPPPP+`+`P+sP+{,S,SdaOe!R!`!h!k!p!t#[#]R{Zi[OZe}!R!`!h!k!p!t#[#]fQOZe!R!`!h!k!p!t#[#]hgQS^ilst!b!c!d!e!|R!d}fSOZe!R!`!h!k!p!t#[#]hTQS^ilst!b!c!d!e!|Q!XmR!e}dWOe!R!`!h!k!p!t#[#]QzZQ!]sR!^t!PTOQSZ^eilst!R!`!b!c!d!e!h!k!p!t!|#[#]ToRqQ{ZQ!Q^Q!l!cR#T!|daOe!R!`!h!k!p!t#[#]YhQSl!d!eQ{ZR!ViRwXZjQSl!d!eeaOe!R!`!h!k!p!t#[#]R!o!hQ!x!pQ#^#[R#_#]T!}!x#OQ#R!xR#W#OQeOR!TeQqRR!ZqQvXR!_vW!t!k!p#[#]R!z!tWlQS!d!eR!WlS!O]|R!g!OQ#O!xR#U#OTdOeSbOeQ!i!RQ!j!`Q!n!hZ!s!k!p!t#[#]d]Oe!R!`!h!k!p!t#[#]Q|ZR!f}dVOe!R!`!h!k!p!t#[#]YhQSl!d!eQyZQ!P^Q!ViQ!]sQ!^tQ!l!bQ!m!cR#S!|dUOe!R!`!h!k!p!t#[#]hgQS^ilst!b!c!d!e!|RxZTpRqsYOQSZeil!R!`!d!e!h!k!p!t#[#]Q!u!kV!w!p#[#]ZkQSl!d!ee_Oe!R!`!h!k!p!t#[#]", goto: "+q!vPPPPPPPPPPPPPPPPPP!w#W#f#k#W$V$l$xP%aPP%dP%{%{PPPP&PP#fPP&jP&v&y'SP'WP&j'^'d'k'q'z(Q(XPPP(_(c(w)Z)`*ZP*v*vP+XPP+aPPPPPP+e+ed`Od!Q!Y!f!i!n!q#W#XRsUiZOUd|!Q!Y!f!i!n!q#W#XVhPj!czWOPU]dgjkl!Q!Y!Z![!c!f!i!n!q!x#W#XR!^udROd!Q!Y!f!i!n!q#W#XQqUQ!VkR!WlQsUQ!P]Q!j![R#P!xd`Od!Q!Y!f!i!n!q#W#XUfPj!cQsUR!TgRoS{WOPU]dgjkl!Q!Y!Z![!c!f!i!n!q!x#W#XTwVydYOd!Q!Y!f!i!n!q#W#XgePU]gjkl!Z![!c!xe`Od!Q!Y!f!i!n!q#W#XR!m!fQ!t!nQ#Y#WR#Z#XT!y!t!zQ!}!tR#S!zQdOR!SdSjP!cR!UjQnSR!XnW!q!i!n#W#XR!v!qQyVR!`yS}[tR!e}Q!z!tR#Q!zTcOdSaOdQ!g!QQ!h!YQ!l!fZ!p!i!n!q#W#Xd[Od!Q!Y!f!i!n!q#W#XQtUR!d|ViPj!cdQOd!Q!Y!f!i!n!q#W#XUfPj!cQpUQ!O]Q!TgQ!VkQ!WlQ!j!ZQ!k![R#O!xdYOd!Q!Y!f!i!n!q#W#XdeP]gjkl!Z![!c!xRrUoTOPUdgj!Q!Y!c!f!i!n!q#W#XQ!r!iV!s!n#W#XTxVye^Od!Q!Y!f!i!n!q#W#X",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq Neq Lt Lte Gt Gte Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Null ConditionalOp FunctionDef Params colon keyword PositionalArg Underscore NamedArg NamedArgPrefix operator IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign", nodeNames: "⚠ Star Slash Plus Minus And Or Eq Neq Lt Lte Gt Gte Identifier AssignableIdentifier Word IdentifierBeforeDot Program PipeExpr FunctionCall PositionalArg ParenExpr FunctionCallOrIdentifier BinOp ConditionalOp FunctionDef keyword Params colon keyword String StringFragment Interpolation EscapeSeq Number Boolean Regex Null DotGet Underscore NamedArg NamedArgPrefix operator IfExpr keyword ThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
maxTerm: 85, maxTerm: 84,
context: trackScope, context: trackScope,
nodeProps: [ nodeProps: [
["closedBy", 36,"end"] ["closedBy", 28,"end"]
], ],
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 7, repeatNodeCount: 7,
tokenData: ">i~RzOX#uXY$dYZ$}Zp#upq$dqs#ust%htu'Puw#uwx'Uxy'Zyz'tz{#u{|(_|}#u}!O(_!O!P#u!P!Q+R!Q![(|![!]3n!]!^$}!^#O#u#O#P4X#P#R#u#R#S4^#S#T#u#T#Y4w#Y#Z6V#Z#b4w#b#c:e#c#f4w#f#g;[#g#h4w#h#i<R#i#o4w#o#p#u#p#q=y#q;'S#u;'S;=`$^<%l~#u~O#u~~>dS#zUkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uS$aP;=`<%l#u^$kUkS!_YOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU%UUkS!qQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u^%oZkS!`YOY%hYZ#uZt%htu&buw%hwx&bx#O%h#O#P&b#P;'S%h;'S;=`&y<%lO%hY&gS!`YOY&bZ;'S&b;'S;=`&s<%lO&bY&vP;=`<%l&b^&|P;=`<%l%h~'UO!j~~'ZO!h~U'bUkS!eQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU'{UkS!sQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU(dWkSOt#uuw#ux!Q#u!Q![(|![#O#u#P;'S#u;'S;=`$^<%lO#uU)TYkSfQOt#uuw#ux!O#u!O!P)s!P!Q#u!Q![(|![#O#u#P;'S#u;'S;=`$^<%lO#uU)xWkSOt#uuw#ux!Q#u!Q![*b![#O#u#P;'S#u;'S;=`$^<%lO#uU*iWkSfQOt#uuw#ux!Q#u!Q![*b![#O#u#P;'S#u;'S;=`$^<%lO#uU+WWkSOt#uuw#ux!P#u!P!Q+p!Q#O#u#P;'S#u;'S;=`$^<%lO#uU+u^kSOY,qYZ#uZt,qtu-tuw,qwx-tx!P,q!P!Q#u!Q!},q!}#O2g#O#P0S#P;'S,q;'S;=`3h<%lO,qU,x^kSoQOY,qYZ#uZt,qtu-tuw,qwx-tx!P,q!P!Q0i!Q!},q!}#O2g#O#P0S#P;'S,q;'S;=`3h<%lO,qQ-yXoQOY-tZ!P-t!P!Q.f!Q!}-t!}#O/T#O#P0S#P;'S-t;'S;=`0c<%lO-tQ.iP!P!Q.lQ.qUoQ#Z#[.l#]#^.l#a#b.l#g#h.l#i#j.l#m#n.lQ/WVOY/TZ#O/T#O#P/m#P#Q-t#Q;'S/T;'S;=`/|<%lO/TQ/pSOY/TZ;'S/T;'S;=`/|<%lO/TQ0PP;=`<%l/TQ0VSOY-tZ;'S-t;'S;=`0c<%lO-tQ0fP;=`<%l-tU0nWkSOt#uuw#ux!P#u!P!Q1W!Q#O#u#P;'S#u;'S;=`$^<%lO#uU1_bkSoQOt#uuw#ux#O#u#P#Z#u#Z#[1W#[#]#u#]#^1W#^#a#u#a#b1W#b#g#u#g#h1W#h#i#u#i#j1W#j#m#u#m#n1W#n;'S#u;'S;=`$^<%lO#uU2l[kSOY2gYZ#uZt2gtu/Tuw2gwx/Tx#O2g#O#P/m#P#Q,q#Q;'S2g;'S;=`3b<%lO2gU3eP;=`<%l2gU3kP;=`<%l,qU3uUkStQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u~4^O!k~U4eUkSwQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU4|YkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#uU5sUyQkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU6[ZkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#U6}#U#o4w#o;'S#u;'S;=`$^<%lO#uU7S[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#`4w#`#a7x#a#o4w#o;'S#u;'S;=`$^<%lO#uU7}[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#g4w#g#h8s#h#o4w#o;'S#u;'S;=`$^<%lO#uU8x[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#X4w#X#Y9n#Y#o4w#o;'S#u;'S;=`$^<%lO#uU9uYnQkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^:lY!lWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^;cY!nWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^<Y[!mWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#f4w#f#g=O#g#o4w#o;'S#u;'S;=`$^<%lO#uU=T[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#i4w#i#j8s#j#o4w#o;'S#u;'S;=`$^<%lO#uU>QUzQkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u~>iO!w~", tokenData: "<}~RyOX#rXY$aYZ$zZp#rpq$aqt#rtu%euw#rwx%jxy%oyz&Yz{#r{|&s|}#r}!O&s!O!P#r!P!Q)g!Q!['b![!]2S!]!^$z!^#O#r#O#P2m#P#R#r#R#S2r#S#T#r#T#Y3]#Y#Z4k#Z#b3]#b#c8y#c#f3]#f#g9p#g#h3]#h#i:g#i#o3]#o#p#r#p#q<_#q;'S#r;'S;=`$Z<%l~#r~O#r~~<xS#wUoSOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rS$^P;=`<%l#r^$hUoS!_YOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rU%RUoS!iQOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#r~%jO!n~~%oO!l~U%vUoS!fQOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rU&aUoS!kQOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rU&xWoSOt#ruw#rx!Q#r!Q!['b![#O#r#P;'S#r;'S;=`$Z<%lO#rU'iYoSrQOt#ruw#rx!O#r!O!P(X!P!Q#r!Q!['b![#O#r#P;'S#r;'S;=`$Z<%lO#rU(^WoSOt#ruw#rx!Q#r!Q![(v![#O#r#P;'S#r;'S;=`$Z<%lO#rU(}WoSrQOt#ruw#rx!Q#r!Q![(v![#O#r#P;'S#r;'S;=`$Z<%lO#rU)lWoSOt#ruw#rx!P#r!P!Q*U!Q#O#r#P;'S#r;'S;=`$Z<%lO#rU*Z^oSOY+VYZ#rZt+Vtu,Yuw+Vwx,Yx!P+V!P!Q#r!Q!}+V!}#O0{#O#P.h#P;'S+V;'S;=`1|<%lO+VU+^^oStQOY+VYZ#rZt+Vtu,Yuw+Vwx,Yx!P+V!P!Q.}!Q!}+V!}#O0{#O#P.h#P;'S+V;'S;=`1|<%lO+VQ,_XtQOY,YZ!P,Y!P!Q,z!Q!},Y!}#O-i#O#P.h#P;'S,Y;'S;=`.w<%lO,YQ,}P!P!Q-QQ-VUtQ#Z#[-Q#]#^-Q#a#b-Q#g#h-Q#i#j-Q#m#n-QQ-lVOY-iZ#O-i#O#P.R#P#Q,Y#Q;'S-i;'S;=`.b<%lO-iQ.USOY-iZ;'S-i;'S;=`.b<%lO-iQ.eP;=`<%l-iQ.kSOY,YZ;'S,Y;'S;=`.w<%lO,YQ.zP;=`<%l,YU/SWoSOt#ruw#rx!P#r!P!Q/l!Q#O#r#P;'S#r;'S;=`$Z<%lO#rU/sboStQOt#ruw#rx#O#r#P#Z#r#Z#[/l#[#]#r#]#^/l#^#a#r#a#b/l#b#g#r#g#h/l#h#i#r#i#j/l#j#m#r#m#n/l#n;'S#r;'S;=`$Z<%lO#rU1Q[oSOY0{YZ#rZt0{tu-iuw0{wx-ix#O0{#O#P.R#P#Q+V#Q;'S0{;'S;=`1v<%lO0{U1yP;=`<%l0{U2PP;=`<%l+VU2ZUoSlQOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#r~2rO!o~U2yUoSwQOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rU3bYoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#o3]#o;'S#r;'S;=`$Z<%lO#rU4XUyQoSOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#rU4pZoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#U5c#U#o3]#o;'S#r;'S;=`$Z<%lO#rU5h[oSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#`3]#`#a6^#a#o3]#o;'S#r;'S;=`$Z<%lO#rU6c[oSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#g3]#g#h7X#h#o3]#o;'S#r;'S;=`$Z<%lO#rU7^[oSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#X3]#X#Y8S#Y#o3]#o;'S#r;'S;=`$Z<%lO#rU8ZYsQoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#o3]#o;'S#r;'S;=`$Z<%lO#r^9QY!pWoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#o3]#o;'S#r;'S;=`$Z<%lO#r^9wY!rWoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#o3]#o;'S#r;'S;=`$Z<%lO#r^:n[!qWoSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#f3]#f#g;d#g#o3]#o;'S#r;'S;=`$Z<%lO#rU;i[oSOt#ruw#rx!_#r!_!`4Q!`#O#r#P#T#r#T#i3]#i#j7X#j#o3]#o;'S#r;'S;=`$Z<%lO#rU<fUzQoSOt#ruw#rx#O#r#P;'S#r;'S;=`$Z<%lO#r~<}O!v~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!d~~", 11)], tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!s~~", 11)],
topRules: {"Program":[0,18]}, topRules: {"Program":[0,17]},
specialized: [{term: 13, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 13, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], specialized: [{term: 13, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 860 tokenPrec: 753
}) })

View File

@ -30,204 +30,6 @@ describe('Identifier', () => {
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier moo-😊-34`) Identifier moo-😊-34`)
}) })
test('parses mathematical unicode symbols like 𝜋 as identifiers', () => {
expect('𝜋').toMatchTree(`
FunctionCallOrIdentifier
Identifier 𝜋`)
})
})
describe('Unicode Symbol Support', () => {
describe('Emoji (currently supported)', () => {
test('Basic Emoticons (U+1F600-U+1F64F)', () => {
expect('😀').toMatchTree(`
FunctionCallOrIdentifier
Identifier 😀`)
expect('😊-counter').toMatchTree(`
FunctionCallOrIdentifier
Identifier 😊-counter`)
})
test('Miscellaneous Symbols and Pictographs (U+1F300-U+1F5FF)', () => {
expect('🌍').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🌍`)
expect('🔥-handler').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🔥-handler`)
})
test('Transport and Map Symbols (U+1F680-U+1F6FF)', () => {
expect('🚀').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🚀`)
expect('🚀-launch').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🚀-launch`)
})
test('Regional Indicator Symbols / Flags (U+1F1E6-U+1F1FF)', () => {
// Note: Flags are typically two regional indicators combined
expect('🇺').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🇺`)
})
test('Supplemental Symbols and Pictographs (U+1F900-U+1F9FF)', () => {
expect('🤖').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🤖`)
expect('🦀-lang').toMatchTree(`
FunctionCallOrIdentifier
Identifier 🦀-lang`)
})
test('Dingbats (U+2700-U+27BF)', () => {
expect('✂').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
expect('✨-magic').toMatchTree(`
FunctionCallOrIdentifier
Identifier -magic`)
})
test('Miscellaneous Symbols (U+2600-U+26FF)', () => {
expect('⚡').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
expect('☀-bright').toMatchTree(`
FunctionCallOrIdentifier
Identifier -bright`)
})
})
describe('Greek Letters (not currently supported)', () => {
test('Greek lowercase alpha α (U+03B1)', () => {
expect('α').toMatchTree(`
FunctionCallOrIdentifier
Identifier α`)
})
test('Greek lowercase beta β (U+03B2)', () => {
expect('β').toMatchTree(`
FunctionCallOrIdentifier
Identifier β`)
})
test('Greek lowercase lambda λ (U+03BB)', () => {
expect('λ').toMatchTree(`
FunctionCallOrIdentifier
Identifier λ`)
})
test('Greek lowercase pi π (U+03C0)', () => {
// Note: This is different from mathematical pi 𝜋
expect('π').toMatchTree(`
FunctionCallOrIdentifier
Identifier π`)
})
})
describe('Mathematical Alphanumeric Symbols (not currently supported)', () => {
test('Mathematical italic small pi 𝜋 (U+1D70B)', () => {
expect('𝜋').toMatchTree(`
FunctionCallOrIdentifier
Identifier 𝜋`)
})
test('Mathematical bold small x 𝐱 (U+1D431)', () => {
expect('𝐱').toMatchTree(`
FunctionCallOrIdentifier
Identifier 𝐱`)
})
test('Mathematical script capital F 𝓕 (U+1D4D5)', () => {
expect('𝓕').toMatchTree(`
FunctionCallOrIdentifier
Identifier 𝓕`)
})
})
describe('Mathematical Operators (not currently supported)', () => {
test('Infinity symbol ∞ (U+221E)', () => {
expect('∞').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('Sum symbol ∑ (U+2211)', () => {
expect('∑').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('Integral symbol ∫ (U+222B)', () => {
expect('∫').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
})
describe('Superscripts and Subscripts (not currently supported)', () => {
test('Superscript two ² (U+00B2)', () => {
expect('x²').toMatchTree(`
FunctionCallOrIdentifier
Identifier x²`)
})
test('Subscript two ₂ (U+2082)', () => {
expect('h₂o').toMatchTree(`
FunctionCallOrIdentifier
Identifier ho`)
})
})
describe('Arrows (not currently supported)', () => {
test('Rightward arrow → (U+2192)', () => {
expect('→').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('Leftward arrow ← (U+2190)', () => {
expect('←').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('Double rightward arrow ⇒ (U+21D2)', () => {
expect('⇒').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
})
describe('CJK Symbols (not currently supported)', () => {
test('Hiragana あ (U+3042)', () => {
expect('あ').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('Katakana カ (U+30AB)', () => {
expect('カ').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
test('CJK Unified Ideograph 中 (U+4E2D)', () => {
expect('中').toMatchTree(`
FunctionCallOrIdentifier
Identifier `)
})
})
}) })
describe('Parentheses', () => { describe('Parentheses', () => {
@ -497,10 +299,10 @@ describe('Assign', () => {
AssignableIdentifier add AssignableIdentifier add
Eq = Eq =
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier a AssignableIdentifier a
Identifier b AssignableIdentifier b
colon : colon :
BinOp BinOp
Identifier a Identifier a
@ -517,10 +319,9 @@ describe('DotGet whitespace sensitivity', () => {
AssignableIdentifier basename AssignableIdentifier basename
Eq = Eq =
Number 5 Number 5
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot basename
IdentifierBeforeDot basename Identifier prop`)
Identifier prop`)
}) })
test('space before dot - NOT DotGet, parses as division', () => { test('space before dot - NOT DotGet, parses as division', () => {
@ -547,27 +348,3 @@ describe('DotGet whitespace sensitivity', () => {
expect('readme.txt').toMatchTree(`Word readme.txt`) expect('readme.txt').toMatchTree(`Word readme.txt`)
}) })
}) })
describe('Comments', () => {
test('are barely there', () => {
expect(`x = 5 # one banana\ny = 2 # two bananas`).toMatchTree(`
Assign
AssignableIdentifier x
Eq =
Number 5
Assign
AssignableIdentifier y
Eq =
Number 2`)
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`)
})
})

View File

@ -4,7 +4,7 @@ import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('if/elseif/else', () => { describe('if/elseif/else', () => {
test('parses single line if', () => { test('parses single line if', () => {
expect(`if y = 1: 'cool' end`).toMatchTree(` expect(`if y = 1: 'cool'`).toMatchTree(`
IfExpr IfExpr
keyword if keyword if
ConditionalOp ConditionalOp
@ -12,13 +12,12 @@ describe('if/elseif/else', () => {
Eq = Eq =
Number 1 Number 1
colon : colon :
SingleLineThenBlock ThenBlock
String String
StringFragment cool StringFragment cool
keyword end
`) `)
expect('a = if x: 2 end').toMatchTree(` expect('a = if x: 2').toMatchTree(`
Assign Assign
AssignableIdentifier a AssignableIdentifier a
Eq = Eq =
@ -26,9 +25,8 @@ describe('if/elseif/else', () => {
keyword if keyword if
Identifier x Identifier x
colon : colon :
SingleLineThenBlock ThenBlock
Number 2 Number 2
keyword end
`) `)
}) })
@ -140,7 +138,7 @@ describe('if/elseif/else', () => {
}) })
test('does not parse identifiers that start with if', () => { test('does not parse identifiers that start with if', () => {
expect('iffy = if true: 2 end').toMatchTree(` expect('iffy = if true: 2').toMatchTree(`
Assign Assign
AssignableIdentifier iffy AssignableIdentifier iffy
Eq = Eq =
@ -148,9 +146,8 @@ describe('if/elseif/else', () => {
keyword if keyword if
Boolean true Boolean true
colon : colon :
SingleLineThenBlock ThenBlock
Number 2 Number 2
keyword end
`) `)
}) })
}) })

View File

@ -20,24 +20,22 @@ describe('DotGet', () => {
AssignableIdentifier obj AssignableIdentifier obj
Eq = Eq =
Number 5 Number 5
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot obj
IdentifierBeforeDot obj Identifier prop
Identifier prop
`) `)
}) })
test('function parameters are in scope within function body', () => { test('function parameters are in scope within function body', () => {
expect('do config: config.path end').toMatchTree(` expect('do config: config.path end').toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier config AssignableIdentifier config
colon : colon :
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot config
IdentifierBeforeDot config Identifier path
Identifier path
keyword end keyword end
`) `)
}) })
@ -45,14 +43,13 @@ describe('DotGet', () => {
test('parameters out of scope outside function', () => { test('parameters out of scope outside function', () => {
expect('do x: x.prop end; x.prop').toMatchTree(` expect('do x: x.prop end; x.prop').toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot x
IdentifierBeforeDot x Identifier prop
Identifier prop
keyword end keyword end
Word x.prop Word x.prop
`) `)
@ -64,19 +61,17 @@ describe('DotGet', () => {
y.bar y.bar
end`).toMatchTree(` end`).toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
Identifier y AssignableIdentifier y
colon : colon :
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot x
IdentifierBeforeDot x Identifier foo
Identifier foo DotGet
FunctionCallOrIdentifier IdentifierBeforeDot y
DotGet Identifier bar
IdentifierBeforeDot y
Identifier bar
keyword end keyword end
`) `)
}) })
@ -87,23 +82,21 @@ end`).toMatchTree(`
do y: y.inner end do y: y.inner end
end`).toMatchTree(` end`).toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot x
IdentifierBeforeDot x Identifier outer
Identifier outer
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier y AssignableIdentifier y
colon : colon :
FunctionCallOrIdentifier DotGet
DotGet IdentifierBeforeDot y
IdentifierBeforeDot y Identifier inner
Identifier inner
keyword end keyword end
keyword end keyword end
`) `)
@ -124,62 +117,6 @@ end`).toMatchTree(`
`) `)
}) })
test('dot get works as bare function', () => {
expect('io = dict print=echo; io.print').toMatchTree(`
Assign
AssignableIdentifier io
Eq =
FunctionCall
Identifier dict
NamedArg
NamedArgPrefix print=
Identifier echo
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot io
Identifier print
`)
})
test('dot get works as function w/ args', () => {
expect('io = dict print=echo; io.print heya').toMatchTree(`
Assign
AssignableIdentifier io
Eq =
FunctionCall
Identifier dict
NamedArg
NamedArgPrefix print=
Identifier echo
FunctionCall
DotGet
IdentifierBeforeDot io
Identifier print
PositionalArg
Identifier heya
`)
})
test('dot get works as function in parens', () => {
expect('io = dict print=echo; (io.print heya)').toMatchTree(`
Assign
AssignableIdentifier io
Eq =
FunctionCall
Identifier dict
NamedArg
NamedArgPrefix print=
Identifier echo
ParenExpr
FunctionCall
DotGet
IdentifierBeforeDot io
Identifier print
PositionalArg
Identifier heya
`)
})
test('mixed file paths and dot get', () => { test('mixed file paths and dot get', () => {
expect('config = 42; cat readme.txt; echo config.path').toMatchTree(` expect('config = 42; cat readme.txt; echo config.path').toMatchTree(`
Assign Assign
@ -208,70 +145,4 @@ end`).toMatchTree(`
PositionalArg PositionalArg
Identifier prop`) Identifier prop`)
}) })
test('readme.1 is Word when readme not in scope', () => {
expect('readme.1').toMatchTree(`Word readme.1`)
})
test('readme.1 is Word when used in function', () => {
expect('echo readme.1').toMatchTree(`
FunctionCall
Identifier echo
PositionalArg
Word readme.1`)
})
test('obj.1 is DotGet when obj is assigned', () => {
expect('obj = 5; obj.1').toMatchTree(`
Assign
AssignableIdentifier obj
Eq =
Number 5
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
Number 1
`)
})
test('obj.1 arg is DotGet when obj is assigned', () => {
expect('obj = 5; obj.1').toMatchTree(`
Assign
AssignableIdentifier obj
Eq =
Number 5
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
Number 1
`)
})
test('dot get index works as function w/ args', () => {
expect(`io = list (do x: echo x end); io.0 heya`).toMatchTree(`
Assign
AssignableIdentifier io
Eq =
FunctionCall
Identifier list
PositionalArg
ParenExpr
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCall
Identifier echo
PositionalArg
Identifier x
keyword end
FunctionCall
DotGet
IdentifierBeforeDot io
Number 0
PositionalArg
Identifier heya
`)
})
}) })

View File

@ -60,7 +60,7 @@ describe('Do', () => {
test('parses function no parameters', () => { test('parses function no parameters', () => {
expect('do: 1 end').toMatchTree(` expect('do: 1 end').toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
colon : colon :
Number 1 Number 1
@ -70,9 +70,9 @@ describe('Do', () => {
test('parses function with single parameter', () => { test('parses function with single parameter', () => {
expect('do x: x + 1 end').toMatchTree(` expect('do x: x + 1 end').toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
BinOp BinOp
Identifier x Identifier x
@ -84,10 +84,10 @@ describe('Do', () => {
test('parses function with multiple parameters', () => { test('parses function with multiple parameters', () => {
expect('do x y: x * y end').toMatchTree(` expect('do x y: x * y end').toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
Identifier y AssignableIdentifier y
colon : colon :
BinOp BinOp
Identifier x Identifier x
@ -102,10 +102,10 @@ describe('Do', () => {
x + 9 x + 9
end`).toMatchTree(` end`).toMatchTree(`
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
Identifier y AssignableIdentifier y
colon : colon :
BinOp BinOp
Identifier x Identifier x
@ -124,9 +124,9 @@ end`).toMatchTree(`
AssignableIdentifier fnnn AssignableIdentifier fnnn
Eq = Eq =
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
@ -139,29 +139,12 @@ end`).toMatchTree(`
AssignableIdentifier enddd AssignableIdentifier enddd
Eq = Eq =
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
keyword end`) keyword end`)
}) })
test('can call a function returned by a parens expression', () => {
expect('(do x: x end) 5').toMatchTree(`
FunctionCall
ParenExpr
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCallOrIdentifier
Identifier x
keyword end
PositionalArg
Number 5
`)
})
}) })

View File

@ -24,10 +24,10 @@ describe('multiline', () => {
AssignableIdentifier add AssignableIdentifier add
Eq = Eq =
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier a AssignableIdentifier a
Identifier b AssignableIdentifier b
colon : colon :
Assign Assign
AssignableIdentifier result AssignableIdentifier result
@ -61,30 +61,14 @@ end
Number 3 Number 3
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
Identifier y AssignableIdentifier y
colon : colon :
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
keyword end keyword end
`) `)
}) })
test('multiline with empty lines', () => {
expect(`
do:
2
end
`).toMatchTree(`
FunctionDef
Do do
Params
colon :
Number 2
keyword end
`)
})
}) })

View File

@ -75,27 +75,13 @@ describe('pipe expressions', () => {
Identifier each Identifier each
PositionalArg PositionalArg
FunctionDef FunctionDef
Do do keyword do
Params Params
Identifier x AssignableIdentifier x
colon : colon :
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
keyword end keyword end
`) `)
}) })
test(`double trouble (do keyword isn't over eager)`, () => {
expect(`
double 2 | double`).toMatchTree(`
PipeExpr
FunctionCall
Identifier double
PositionalArg
Number 2
operator |
FunctionCallOrIdentifier
Identifier double
`)
})
}) })

View File

@ -1,10 +1,5 @@
import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr' import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr'
import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot, Do } from './shrimp.terms' import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot } from './shrimp.terms'
// doobie doobie do (we need the `do` keyword to know when we're defining params)
export function specializeKeyword(ident: string) {
return ident === 'do' ? Do : -1
}
// The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF. // The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF.
@ -19,7 +14,7 @@ export const tokenizer = new ExternalTokenizer(
// Don't consume things that start with - or + followed by a digit (negative/positive numbers) // Don't consume things that start with - or + followed by a digit (negative/positive numbers)
if ((ch === 45 /* - */ || ch === 43) /* + */ && isDigit(input.peek(1))) return if ((ch === 45 /* - */ || ch === 43) /* + */ && isDigit(input.peek(1))) return
const isValidStart = isLowercaseLetter(ch) || isEmojiOrUnicode(ch) const isValidStart = isLowercaseLetter(ch) || isEmoji(ch)
const canBeWord = stack.canShift(Word) const canBeWord = stack.canShift(Word)
// Consume all word characters, tracking if it remains a valid identifier // Consume all word characters, tracking if it remains a valid identifier
@ -111,8 +106,8 @@ const consumeWordToken = (
if (!isWordChar(nextCh)) break if (!isWordChar(nextCh)) break
} }
// Track identifier validity: must be lowercase, digit, dash, or emoji/unicode // Track identifier validity: must be lowercase, digit, dash, or emoji
if (!isLowercaseLetter(ch) && !isDigit(ch) && ch !== 45 /* - */ && !isEmojiOrUnicode(ch)) { if (!isLowercaseLetter(ch) && !isDigit(ch) && ch !== 45 /* - */ && !isEmoji(ch)) {
if (!canBeWord) break if (!canBeWord) break
isValidIdentifier = false isValidIdentifier = false
} }
@ -222,7 +217,7 @@ const getFullCodePoint = (input: InputStream, pos: number): number => {
return ch return ch
} }
const isEmojiOrUnicode = (ch: number): boolean => { const isEmoji = (ch: number): boolean => {
return ( return (
// Basic Emoticons // Basic Emoticons
(ch >= 0x1f600 && ch <= 0x1f64f) || (ch >= 0x1f600 && ch <= 0x1f64f) ||
@ -247,25 +242,7 @@ const isEmojiOrUnicode = (ch: number): boolean => {
// Additional miscellaneous items // Additional miscellaneous items
(ch >= 0x238c && ch <= 0x2454) || (ch >= 0x238c && ch <= 0x2454) ||
// Combining Diacritical Marks for Symbols // Combining Diacritical Marks for Symbols
(ch >= 0x20d0 && ch <= 0x20ff) || (ch >= 0x20d0 && ch <= 0x20ff)
// Latin-1 Supplement (includes ², ³, ¹ and other special chars)
(ch >= 0x00a0 && ch <= 0x00ff) ||
// Greek and Coptic (U+0370-U+03FF)
(ch >= 0x0370 && ch <= 0x03ff) ||
// Mathematical Alphanumeric Symbols (U+1D400-U+1D7FF)
(ch >= 0x1d400 && ch <= 0x1d7ff) ||
// Mathematical Operators (U+2200-U+22FF)
(ch >= 0x2200 && ch <= 0x22ff) ||
// Superscripts and Subscripts (U+2070-U+209F)
(ch >= 0x2070 && ch <= 0x209f) ||
// Arrows (U+2190-U+21FF)
(ch >= 0x2190 && ch <= 0x21ff) ||
// Hiragana (U+3040-U+309F)
(ch >= 0x3040 && ch <= 0x309f) ||
// Katakana (U+30A0-U+30FF)
(ch >= 0x30a0 && ch <= 0x30ff) ||
// CJK Unified Ideographs (U+4E00-U+9FFF)
(ch >= 0x4e00 && ch <= 0x9fff)
) )
} }