diff --git a/deno.lock b/deno.lock index 2e05d0c..afce2bc 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,5 @@ { - "version": "4", + "version": "5", "specifiers": { "jsr:@std/assert@*": "1.0.12", "jsr:@std/internal@^1.0.6": "1.0.6", @@ -60,6 +60,44 @@ "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/shell/shell.ts": "736ae813e84a300650af3fbcb632518b6978bf0a0f2d32f0b69a7248310ab1f2", "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/task.ts": "aebf7b1195167b32c8f5ae13446daef4315f89d6eb5700d4927e54a0ed5bbc25", "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/tsc/index.ts": "c8b602ebe53807b894852c07a461b4b5cbf4dd54f728d82c57f64b207c4819ce", - "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/tsc/tsc.ts": "547134d42cb45d17053a8fd4259c205b17c221cca41c4a1846fb0be7ba20fe95" + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/tsc/tsc.ts": "547134d42cb45d17053a8fd4259c205b17c221cca41c4a1846fb0be7ba20fe95", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/attw/attw.ts": "731ba1fdabb73e1fedb4f0870d711ec7afa2a8dacc951b9d5b7deb7ff7011799", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/attw/index.ts": "49c9cfcf1908a5c732210262367baf638ebc9a5c276bd7d8eb0d3abc4df22e05", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/build/build.ts": "b14dbd7f17cd9de993a615bca4260a2c5427948569fc42dfc16f0f9a05c2eef0", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/build/index.ts": "14a09ed261e81fd145d79652950d5aa874e7198e0380a2bb2de678da823008bf", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/compress/compress.ts": "81b9078f96341f781da13c039435d11ec9892597bbaecf5ea17ddebd6ffd720c", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/compress/format-size.ts": "56da82f861eb3234ebc2e8c05fa5b060d1659f78c8692b879d525d2561d17f33", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/compress/gzip-size.ts": "fb0603f8d09809c200577a660c7eda078f6185cfa848eb08104e841c6fd89213", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/compress/index.ts": "2ff1da4acf7310beae6148577f81d36351576a3608fc4e4c85736f1d166d34ea", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/esbuild/esbuild.ts": "f6abda02bda8975222197187a084778581d1867588f85bbe08ef9b6fc7d6df51", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/esbuild/index.ts": "13bfba425b42d924d437912f93f2164e8d95426730f11b5c08e45cbda9b56aed", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/append.ts": "ec82b66b16830f31c6981fb46f28101912aec5831de978ca34dca979e0946249", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/create.ts": "1fd59581978b83d9e2aefaeb321e1ba61c868914d7c00854b795367dcc63fc5b", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/delete.ts": "a2785db01de8f44493e6e9a7ce62a432c08fbc2bcc7f621c1e39d594cbc6faea", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/file.ts": "c6def713b6f47168858daf433d55994f00531a49ed9cfa9368a11480ca8b636e", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/index.ts": "c7802b56b7386aa7023436cc27f26e39a2dde7e7744b166ebb3a48c1c77b8a48", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/read.ts": "f163b5d8cd6541ae2549abdd8b85914c4b41455824256b711f5a3e083aba8b13", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/replace.ts": "f547bd75de33425b2360a8bbecad5a22ae0f2b5f822b438dc2566cebf6f6291d", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/file/write.ts": "484a3e400e2cab4d976c01ff57af996cc61e6930a7b2ec92679955f6ab348374", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/add.ts": "eec03dfadcaddc63a1c872356583d45e2dc6d8606807529ddb3e149df30f3492", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/create.ts": "3dea8bc3d1dfb6088bd72010c05b9c7ebca803b953f5c720b4dcd8c826237416", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/delete.ts": "bcfd835e466c3d17c6592136f4e6fa94ac50c6ca951da441b2e473e72bc26fdc", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/folder.ts": "3d9df0555638690cb332acfaa6180cf88b9b21841d97e832ed3bc80510b34f21", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/index-list.ts": "90a71860159549b2e9af89c1565d54586d74e063a0185cc349577e43ccaca523", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/folder/index.ts": "a6bee7cb175733e7a7cb241ae158076d62b65c86d93402461e20780a17f39302", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/index.ts": "3478734f5e8aacc3aba8b0ed1863d98f968e7bbdba04894bbfa74609115cd43c", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/path/index.ts": "a54f30e47f830ab8195624df02f401c59107a6a877d8a541fd5449c66abca68e", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/path/path.ts": "c22bb9d76c082dc26434739570b7c324e0db91b0674fc84fb347c607ce0f2950", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/run/index.ts": "4681a49e9d2667e965ecfd229dab762cd84bf29d36e8251b4dcf4e34150485e5", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/run/run.ts": "2cf532dff8a2dd31455964ff107d0957e75852afda653dcd29bbc240fbdfc618", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/serve/index.ts": "7bfc8c74a7ed2b55d821bec758e6b3a8a623febdde646bafafb91e4e3398ed40", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/serve/serve.ts": "602480de00c4d5b7d91837c8b2b4f57955e088922d3bf39bbb0b6f23fda0f52d", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/settings/index.ts": "35d4ea33ad01546435f8a66a49603527849b0b59d625c65a7a5a4c7ed2248477", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/settings/settings.ts": "d571fcdf2857b5196b24dacac5f75c98e279e7f3c1bc487afce3b03ed1a0ce24", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/shell/index.ts": "db9a5f9a747d41a5aa7a43e2fe606f5ac7365459af697148a9680935c1d39bb7", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/shell/shell.ts": "736ae813e84a300650af3fbcb632518b6978bf0a0f2d32f0b69a7248310ab1f2", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/task.ts": "8346009a1032f4ffcf44b2d587db562c7949e74694cf9367b10db6489085d5f3", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/tsc/index.ts": "c8b602ebe53807b894852c07a461b4b5cbf4dd54f728d82c57f64b207c4819ce", + "https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/tsc/tsc.ts": "890cac855658225d3dbcd57658ab44f65932446fe5632cb1818b1095f5c1e936" } } diff --git a/example/index.ts b/example/index.ts index 764ac86..4395658 100644 --- a/example/index.ts +++ b/example/index.ts @@ -142,4 +142,4 @@ console.log(Json) }) console.log(project.parser) // parser file content console.log(project.mapping) // semantic mapping file content -} +} \ No newline at end of file diff --git a/example/typebox/compiled/module.ts b/example/typebox/compiled/module.ts index 055133f..5161420 100644 --- a/example/typebox/compiled/module.ts +++ b/example/typebox/compiled/module.ts @@ -99,11 +99,40 @@ const GenericReference = Runtime.Tuple([ // Reference // ------------------------------------------------------------------ const Reference = Runtime.Ident() - +// ------------------------------------------------------------------ +// TemplateText +// ------------------------------------------------------------------ +const TemplateText = Runtime.Union([ + Runtime.Until('${'), + Runtime.Until('`'), +]) +// ------------------------------------------------------------------ +// TemplateInterpolate +// ------------------------------------------------------------------ +const TemplateInterpolate = Runtime.Tuple([ + Runtime.Const('${'), + Runtime.Ref('Type'), + Runtime.Const('}') +]) +// ------------------------------------------------------------------ +// TemplateBody +// ------------------------------------------------------------------ +const TemplateBody = Runtime.Union([ + Runtime.Tuple([Runtime.Ref('TemplateText'), Runtime.Ref('TemplateInterpolate'), Runtime.Ref('TemplateBody')]), + Runtime.Tuple([Runtime.Ref('TemplateText')]), +]) +// ------------------------------------------------------------------ +// TemplateLiteral +// ------------------------------------------------------------------ +const TemplateLiteral = Runtime.Tuple([ + Runtime.Const('`'), + Runtime.Ref('TemplateBody'), + Runtime.Const('`'), +]) // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ -const LiteralString = Runtime.String([SingleQuote, DoubleQuote, Tilde]) +const LiteralString = Runtime.String([SingleQuote, DoubleQuote]) const LiteralNumber = Runtime.Number() const LiteralBoolean = Runtime.Union([Runtime.Const('true'), Runtime.Const('false')]) const Literal = Runtime.Union([ @@ -177,6 +206,7 @@ const Base = Runtime.Union([ Runtime.Ref('Object'), Runtime.Ref('Tuple'), Runtime.Ref('Literal'), + Runtime.Ref('TemplateLiteral'), Runtime.Ref('Constructor'), Runtime.Ref('Function'), Runtime.Ref('Mapped'), @@ -589,6 +619,10 @@ export const SyntaxModule = new Runtime.Module({ KeywordSymbol, KeywordVoid, Keyword, + TemplateInterpolate, + TemplateBody, + TemplateText, + TemplateLiteral, LiteralString, LiteralNumber, LiteralBoolean, diff --git a/example/typebox/interpreted/runtime.ts b/example/typebox/interpreted/runtime.ts index 5bc1ca6..3d9ab01 100644 --- a/example/typebox/interpreted/runtime.ts +++ b/example/typebox/interpreted/runtime.ts @@ -133,13 +133,60 @@ function ReferenceMapping(result: string, context: T.TProperties) { return target } const Reference = Runtime.Ident((result, context) => ReferenceMapping(result, context)) +// ------------------------------------------------------------------ +// TemplateText +// ------------------------------------------------------------------ +function TemplateTextMapping(input: string) { + return T.Literal(input) +} +const TemplateText = Runtime.Union([ + Runtime.Until('${'), + Runtime.Until('`'), +], TemplateTextMapping) +// ------------------------------------------------------------------ +// TemplateInterpolate +// ------------------------------------------------------------------ +function TemplateInterpolateMapping(input: [unknown, unknown, unknown]) { + return input[1] +} +const TemplateInterpolate = Runtime.Tuple([ + Runtime.Const('${'), + Runtime.Ref('Type'), + Runtime.Const('}') +], TemplateInterpolateMapping) +// ------------------------------------------------------------------ +// TemplateBody +// ------------------------------------------------------------------ +function TemplateBodyMapping(input: [unknown] | [unknown, unknown, unknown]) { + return ( + input.length === 3 + ? [input[0], input[1], ...input[2] as unknown[]] + : [input[0]] + ) +} +const TemplateBody = Runtime.Union([ + Runtime.Tuple([Runtime.Ref('TemplateText'), Runtime.Ref('TemplateInterpolate'), Runtime.Ref('TemplateBody')]), + Runtime.Tuple([Runtime.Ref('TemplateText')]), +], TemplateBodyMapping) +// ------------------------------------------------------------------ +// TemplateLiteral +// ------------------------------------------------------------------ +function TemplateLiteralMapping(input: [unknown, unknown, unknown]) { + return T.TemplateLiteral(input[1] as T.TTemplateLiteralKind[]) +} +const TemplateLiteral = Runtime.Tuple([ + Runtime.Const('`'), + Runtime.Ref('TemplateBody'), + Runtime.Const('`'), +], TemplateLiteralMapping) + // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ const Literal = Runtime.Union([ Runtime.Union([Runtime.Const('true'), Runtime.Const('false')], value => T.Literal(value === 'true')), Runtime.Number(value => T.Literal(parseFloat(value))), - Runtime.String([SingleQuote, DoubleQuote, Tilde], value => T.Literal(value)) + Runtime.String([SingleQuote, DoubleQuote], value => T.Literal(value)) ]) // ------------------------------------------------------------------ // Keyword @@ -208,6 +255,7 @@ const Base = Runtime.Union([ Runtime.Ref('Object'), Runtime.Ref('Tuple'), Runtime.Ref('Literal'), + Runtime.Ref('TemplateLiteral'), Runtime.Ref('Constructor'), Runtime.Ref('Function'), Runtime.Ref('Mapped'), @@ -630,12 +678,17 @@ const Date = Runtime.Const('Date', Runtime.As(T.Date())) // Uint8Array // ------------------------------------------------------------------ const Uint8Array = Runtime.Const('Uint8Array', Runtime.As(T.Uint8Array())) + // ------------------------------------------------------------------ // Module // ------------------------------------------------------------------ export const Module = new Runtime.Module({ GenericArgumentsList, GenericArguments, + TemplateInterpolate, + TemplateBody, + TemplateText, + TemplateLiteral, Literal, Keyword, KeyOf, diff --git a/example/typebox/interpreted/static.ts b/example/typebox/interpreted/static.ts index db59615..d552b9e 100644 --- a/example/typebox/interpreted/static.ts +++ b/example/typebox/interpreted/static.ts @@ -91,6 +91,56 @@ type Dereference = ( Ref extends keyof Context ? Context[Ref] : T.TRef ) // ------------------------------------------------------------------ +// TemplateText +// ------------------------------------------------------------------ +interface TemplateTextMapping extends Static.IMapping { + output: this['input'] extends string ? T.TLiteral : never +} +type TemplateText = Static.Union<[ + Static.Until<'${'>, + Static.Until<'`'>, +], TemplateTextMapping> +// ------------------------------------------------------------------ +// TemplateInterpolate +// ------------------------------------------------------------------ +interface TemplateInterpolateMapping extends Static.IMapping { + output: this['input'] extends ['${', infer Type extends T.TSchema, '}'] + ? Type + : never +} +type TemplateInterpolate = Static.Tuple<[ + Static.Const<'${'>, + Type, + Static.Const<'}'> +], TemplateInterpolateMapping> +// ------------------------------------------------------------------ +// TemplateBody +// ------------------------------------------------------------------ +interface TemplateBodyMapping extends Static.IMapping { + output: ( + this['input'] extends [infer Text extends T.TSchema, infer Type extends T.TSchema, infer Rest extends T.TSchema[]] ? [Text, Type, ...Rest] : + this['input'] extends [infer Text extends T.TSchema] ? [Text] : + [] + ) +} +type TemplateBody = Static.Union<[ + Static.Tuple<[TemplateText, TemplateInterpolate, TemplateBody]>, + Static.Tuple<[TemplateText]>, +], TemplateBodyMapping> +// ------------------------------------------------------------------ +// TemplateLiteral +// ------------------------------------------------------------------ +interface TemplateLiteralMapping extends Static.IMapping { + output: this['input'] extends ['`', infer Types extends T.TTemplateLiteralKind[], '`'] + ? T.TTemplateLiteral + : never +} +type TemplateLiteral = Static.Tuple<[ + Static.Const<'`'>, + TemplateBody, + Static.Const<'`'>, +], TemplateLiteralMapping> +// ------------------------------------------------------------------ // GenericArguments // ------------------------------------------------------------------ type GenericArgumentsContext = ( @@ -154,7 +204,7 @@ interface LiteralStringMapping extends Static.IMapping { type Literal = Static.Union<[ Static.Union<[Static.Const<'true'>, Static.Const<'false'>], LiteralBooleanMapping>, Static.Number, - Static.String<[DoubleQuote, SingleQuote, Tilde], LiteralStringMapping>, + Static.String<[DoubleQuote, SingleQuote], LiteralStringMapping>, ]> // ------------------------------------------------------------------ // Keyword @@ -230,6 +280,7 @@ type Base = Static.Union<[ Object, Tuple, Literal, + TemplateLiteral, Function, Constructor, Mapped, diff --git a/readme.md b/readme.md index 2c1bfaf..c57cfb1 100644 --- a/readme.md +++ b/readme.md @@ -69,6 +69,7 @@ License: MIT - [Tuple](#Tuple) - [Union](#Union) - [Array](#Array) + - [Until](#Until) - [Optional](#Optional) - [Epsilon](#Epsilon) - [Terminals](#Terminals) @@ -186,6 +187,27 @@ const R2 = Runtime.Parse(T, 'X X X Y Z') // const R2 = [['X', 'X', ' const R3 = Runtime.Parse(T, 'Y Z') // const R3 = [[], 'Y Z'] ``` +### Until + +The Until combinator parses all characters up to (but not including) the specified string. The specified string remains unconsumed in the input. If the string is not found, parsing fails. + +**BNF** + +```bnf + ::= ? any character until 'Z' ? +``` + +**TypeScript** + +```typescript +const T = Runtime.Until('Z') // const T = { + // type: 'Until', + // value: 'X' + // } + +const R = Runtime.Parse(T, 'X Y Z') // const R = ['X Y ', 'Z'] +``` + ### Optional The Optional combinator parses zero or one occurrence of the interior parser, returning a tuple with one element or an empty tuple if there is no match. diff --git a/src/compile/common/comment.ts b/src/compile/common/comment.ts index 0e5fc17..8667c3d 100644 --- a/src/compile/common/comment.ts +++ b/src/compile/common/comment.ts @@ -53,6 +53,9 @@ function FromRef(parser: Runtime.IRef): string { function FromConst(parser: Runtime.IConst): string { return `'${Escape(parser.value)}'` } +function FromUntil(parser: Runtime.IUntil): string { + return `string` +} function FromIdent(parser: Runtime.IIdent): string { return `` } @@ -71,6 +74,7 @@ function FromParser(parser: Runtime.IParser): string { Runtime.IsOptional(parser) ? FromOptional(parser) : Runtime.IsString(parser) ? FromString(parser) : Runtime.IsConst(parser) ? FromConst(parser) : + Runtime.IsUntil(parser) ? FromUntil(parser) : Runtime.IsRef(parser) ? FromRef(parser) : Runtime.IsIdent(parser) ? FromIdent(parser) : Runtime.IsNumber(parser) ? FromNumber(parser) : diff --git a/src/compile/common/infer.ts b/src/compile/common/infer.ts index 12cadb7..af48d9a 100644 --- a/src/compile/common/infer.ts +++ b/src/compile/common/infer.ts @@ -49,6 +49,9 @@ function InferOptional(parser: Runtime.IParser) { function InferConst(parser: Runtime.IConst) { return `'${parser.value}'` } +function InferUntil(parser: Runtime.IUntil) { + return `string` +} export function Infer(parser: Runtime.IParser): string { return ( Runtime.IsContext(parser) ? InferContext(parser.right, parser.right) : @@ -58,6 +61,7 @@ export function Infer(parser: Runtime.IParser): string { Runtime.IsOptional(parser) ? InferOptional(parser.parser) : Runtime.IsRef(parser) ? `unknown` : Runtime.IsConst(parser) ? InferConst(parser) : + Runtime.IsUntil(parser) ? InferUntil(parser) : Runtime.IsString(parser) ? `string` : Runtime.IsIdent(parser) ? `string` : Runtime.IsNumber(parser) ? `string` : diff --git a/src/compile/func/func.ts b/src/compile/func/func.ts index 41c5a6b..34dd056 100644 --- a/src/compile/func/func.ts +++ b/src/compile/func/func.ts @@ -83,6 +83,12 @@ function FromConst(options: Options, name: string, value: string): string { return `Runtime.Token.Const('${Escape(value)}', input)` } // ------------------------------------------------------------------ +// Const +// ------------------------------------------------------------------ +function FromUntil(options: Options, name: string, value: string): string { + return `Runtime.Token.Until('${Escape(value)}', input)` +} +// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ function FromIdent(options: Options, name: string): string { @@ -119,6 +125,7 @@ function FromParser(options: Options, name: string, parser: Runtime.IParser): st Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : Runtime.IsString(parser) ? FromString(options, name, parser.options) : Runtime.IsConst(parser) ? FromConst(options, name, parser.value) : + Runtime.IsUntil(parser) ? FromUntil(options, name, parser.value) : Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : Runtime.IsIdent(parser) ? FromIdent(options, name) : Runtime.IsNumber(parser) ? FromNumber(options, name) : diff --git a/src/compile/type/type.ts b/src/compile/type/type.ts index c6bda2b..d7ef668 100644 --- a/src/compile/type/type.ts +++ b/src/compile/type/type.ts @@ -85,6 +85,12 @@ function FromConst(options: Options, name: string, value: string): string { return `Static.Token.Const<'${Escape(value)}', Input>` } // ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function FromUntil(options: Options, name: string, value: string): string { + return `Static.Token.Until<'${Escape(value)}', Input>` +} +// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ function FromIdent(options: Options, name: string): string { @@ -121,6 +127,7 @@ function FromParser(options: Options, name: string, parser: Runtime.IParser): st Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : Runtime.IsString(parser) ? FromString(options, name, parser.options) : Runtime.IsConst(parser) ? FromConst(options, name, parser.value) : + Runtime.IsUntil(parser) ? FromUntil(options, name, parser.value) : Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : Runtime.IsIdent(parser) ? FromIdent(options, name) : Runtime.IsNumber(parser) ? FromNumber(options, name) : diff --git a/src/guard/guard.ts b/src/guard/guard.ts new file mode 100644 index 0000000..489b06d --- /dev/null +++ b/src/guard/guard.ts @@ -0,0 +1,45 @@ +/*-------------------------------------------------------------------------- + +@sinclair/parsebox + +The MIT License (MIT) + +Copyright (c) 2024-2025 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +// deno-fmt-ignore-file + +export function IsEqual(left: unknown, right: unknown): boolean { + return left === right +} +export function HasPropertyKey(value: Record, key: Key): value is Record & { [_ in Key]: unknown } { + return key in value +} +export function IsObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} +export function IsArray(value: unknown): value is unknown[] { + return globalThis.Array.isArray(value) +} +export function IsString(value: unknown): value is string { + return typeof value === 'string' +} \ No newline at end of file diff --git a/src/guard/index.ts b/src/guard/index.ts new file mode 100644 index 0000000..517f3d5 --- /dev/null +++ b/src/guard/index.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------- + +@sinclair/parsebox + +The MIT License (MIT) + +Copyright (c) 2024-2025 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * as Guard from './guard.ts' \ No newline at end of file diff --git a/src/runtime/guard.ts b/src/runtime/guard.ts index fd4b309..489b06d 100644 --- a/src/runtime/guard.ts +++ b/src/runtime/guard.ts @@ -28,75 +28,18 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IArray, IConst, IContext, IIdent, INumber, IOptional, IRef, IString, ITuple, IUnion } from './types.ts' - -// ------------------------------------------------------------------ -// Value Guard -// ------------------------------------------------------------------ -function HasPropertyKey(value: Record, key: Key): value is Record & { [_ in Key]: unknown } { +export function IsEqual(left: unknown, right: unknown): boolean { + return left === right +} +export function HasPropertyKey(value: Record, key: Key): value is Record & { [_ in Key]: unknown } { return key in value } -function IsObjectValue(value: unknown): value is Record { +export function IsObject(value: unknown): value is Record { return typeof value === 'object' && value !== null } -function IsArrayValue(value: unknown): value is unknown[] { +export function IsArray(value: unknown): value is unknown[] { return globalThis.Array.isArray(value) } -// ------------------------------------------------------------------ -// Parser Guard -// ------------------------------------------------------------------ -/** Returns true if the value is a Array Parser */ -export function IsArray(value: unknown): value is IArray { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Array' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser) -} -/** Returns true if the value is a Const Parser */ -export function IsConst(value: unknown): value is IConst { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Const' && HasPropertyKey(value, 'value') && typeof value.value === 'string' -} -/** Returns true if the value is a Context Parser */ -export function IsContext(value: unknown): value is IContext { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Context' && HasPropertyKey(value, 'left') && IsParser(value.left) && HasPropertyKey(value, 'right') && IsParser(value.right) -} -/** Returns true if the value is a Ident Parser */ -export function IsIdent(value: unknown): value is IIdent { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ident' -} -/** Returns true if the value is a Number Parser */ -export function IsNumber(value: unknown): value is INumber { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Number' -} -/** Returns true if the value is a Optional Parser */ -export function IsOptional(value: unknown): value is IOptional { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Optional' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser) -} -/** Returns true if the value is a Ref Parser */ -export function IsRef(value: unknown): value is IRef { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ref' && HasPropertyKey(value, 'ref') && typeof value.ref === 'string' -} -/** Returns true if the value is a String Parser */ -export function IsString(value: unknown): value is IString { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'String' && HasPropertyKey(value, 'options') && IsArrayValue(value.options) -} -/** Returns true if the value is a Tuple Parser */ -export function IsTuple(value: unknown): value is ITuple { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Tuple' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) -} -/** Returns true if the value is a Union Parser */ -export function IsUnion(value: unknown): value is IUnion { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Union' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) -} -/** Returns true if the value is a Parser */ -export function IsParser(value: unknown) { - return ( - IsArray(value) - || IsConst(value) - || IsContext(value) - || IsIdent(value) - || IsNumber(value) - || IsOptional(value) - || IsRef(value) - || IsString(value) - || IsTuple(value) - || IsUnion(value) - ) -} +export function IsString(value: unknown): value is string { + return typeof value === 'string' +} \ No newline at end of file diff --git a/src/runtime/parse.ts b/src/runtime/parse.ts index 05bad1a..0c269d8 100644 --- a/src/runtime/parse.ts +++ b/src/runtime/parse.ts @@ -29,10 +29,15 @@ THE SOFTWARE. // deno-fmt-ignore-file // deno-lint-ignore-file no-unused-vars no-explicit-any -import * as Guard from './guard.ts' +import { Type } from "../../example/typebox/compiled/parser.ts" +import { Guard } from '../guard/index.ts' import * as Token from './token.ts' import * as Types from './types.ts' +export function Throw(message: string): never { + throw new Error(message) +} + // ------------------------------------------------------------------ // Context // ------------------------------------------------------------------ @@ -63,6 +68,12 @@ function ParseConst(value: Value, code: string, context: u return Token.Const(value, code) as never } // ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function ParseUntil(value: Value, code: string, context: unknown): [] | [Value, string] { + return Token.Until(value, code) as never +} +// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ function ParseIdent(code: string, _context: unknown): [] | [string, string] { @@ -85,8 +96,7 @@ function ParseOptional(moduleProperties: ModuleProperties, ref: Ref, code: string, context: unknown): [] | [string, string] { - const parser = moduleProperties[ref] - if (!Guard.IsParser(parser)) throw Error(`Cannot dereference Parser '${ref}'`) + const parser = ref in moduleProperties ? moduleProperties[ref] : Throw(`Cannot dereference Parser '${ref}'`) return ParseParser(moduleProperties, parser, code, context) as never } // ------------------------------------------------------------------ @@ -125,16 +135,17 @@ function ParseUnion(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser, string] { const result = ( - Guard.IsContext(parser) ? ParseContext(moduleProperties, parser.left, parser.right, code, context) : - Guard.IsArray(parser) ? ParseArray(moduleProperties, parser.parser, code, context) : - Guard.IsConst(parser) ? ParseConst(parser.value, code, context) : - Guard.IsIdent(parser) ? ParseIdent(code, context) : - Guard.IsNumber(parser) ? ParseNumber(code, context) : - Guard.IsOptional(parser) ? ParseOptional(moduleProperties, parser.parser, code, context) : - Guard.IsRef(parser) ? ParseRef(moduleProperties, parser.ref, code, context) : - Guard.IsString(parser) ? ParseString(parser.options, code, context) : - Guard.IsTuple(parser) ? ParseTuple(moduleProperties, parser.parsers, code, context) : - Guard.IsUnion(parser) ? ParseUnion(moduleProperties, parser.parsers, code, context) : + Types.IsContext(parser) ? ParseContext(moduleProperties, parser.left, parser.right, code, context) : + Types.IsArray(parser) ? ParseArray(moduleProperties, parser.parser, code, context) : + Types.IsConst(parser) ? ParseConst(parser.value, code, context) : + Types.IsIdent(parser) ? ParseIdent(code, context) : + Types.IsNumber(parser) ? ParseNumber(code, context) : + Types.IsOptional(parser) ? ParseOptional(moduleProperties, parser.parser, code, context) : + Types.IsRef(parser) ? ParseRef(moduleProperties, parser.ref, code, context) : + Types.IsString(parser) ? ParseString(parser.options, code, context) : + Types.IsTuple(parser) ? ParseTuple(moduleProperties, parser.parsers, code, context) : + Types.IsUnion(parser) ? ParseUnion(moduleProperties, parser.parsers, code, context) : + Types.IsUntil(parser) ? ParseUntil(parser.value, code, context) : [] ) return (result.length === 2 ? [parser.mapping(result[0], context), result[1]] : result) as never diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index 5b28227..54391f9 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -27,7 +27,6 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ export * as Token from "./token.ts" -export * from "./guard.ts" export * from "./types.ts" export * from "./module.ts" export * from "./parse.ts" diff --git a/src/runtime/token.ts b/src/runtime/token.ts index 185f76b..62361e0 100644 --- a/src/runtime/token.ts +++ b/src/runtime/token.ts @@ -27,209 +27,224 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ // deno-fmt-ignore-file -// deno-lint-ignore-file no-namespace +// deno-lint-ignore-file // ------------------------------------------------------------------ // Chars // ------------------------------------------------------------------ -namespace Chars { - /** Returns true if the char code is a whitespace */ - export function IsWhitespace(value: number): boolean { - return value === 32 - } - /** Returns true if the char code is a newline */ - export function IsNewline(value: number): boolean { - return value === 10 - } - /** Returns true if the char code is a alpha */ - export function IsAlpha(value: number): boolean { - return ( - (value >= 65 && value <= 90) || // A-Z - (value >= 97 && value <= 122) // a-z - ) - } - /** Returns true if the char code is zero */ - export function IsZero(value: number): boolean { - return value === 48 - } - /** Returns true if the char code is non-zero */ - export function IsNonZero(value: number): boolean { - return value >= 49 && value <= 57 - } - /** Returns true if the char code is a digit */ - export function IsDigit(value: number): boolean { - return ( - IsNonZero(value) || - IsZero(value) - ) - } - /** Returns true if the char code is a dot */ - export function IsDot(value: number): boolean { - return value === 46 - } - /** Returns true if this char code is a underscore */ - export function IsUnderscore(value: unknown): boolean { - return value === 95 - } - /** Returns true if this char code is a dollar sign */ - export function IsDollarSign(value: unknown): boolean { - return value === 36 - } +/** Returns true if the char code is a whitespace */ +export function IsWhitespace(charCode: number): boolean { + return charCode === 32 +} +/** Returns true if the char code is a newline */ +export function IsNewline(charCode: number): boolean { + return charCode === 10 +} +/** Returns true if the char code is a alpha */ +export function IsAlpha(charCode: number): boolean { + return ( + (charCode >= 65 && charCode <= 90) || // A-Z + (charCode >= 97 && charCode <= 122) // a-z + ) +} +/** Returns true if the char code is zero */ +export function IsZero(charCode: number): boolean { + return charCode === 48 } +/** Returns true if the char code is non-zero */ +export function IsNonZero(charCode: number): boolean { + return charCode >= 49 && charCode <= 57 +} +/** Returns true if the char code is a digit */ +export function IsDigit(charCode: number): boolean { + return ( + IsNonZero(charCode) || + IsZero(charCode) + ) +} +/** Returns true if the char code is a dot */ +export function IsDot(charCode: number): boolean { + return charCode === 46 +} +/** Returns true if this char code is a underscore */ +export function IsUnderscore(charCode: number): boolean { + return charCode === 95 +} +/** Returns true if this char code is a dollar sign */ +export function IsDollarSign(charCode: number): boolean { + return charCode === 36 +} + // ------------------------------------------------------------------ // Trim // ------------------------------------------------------------------ -namespace Trim { - /** Trims Whitespace and retains Newline, Tabspaces, etc. */ - export function TrimWhitespaceOnly(code: string): string { - for (let i = 0; i < code.length; i++) { - if (Chars.IsWhitespace(code.charCodeAt(i))) continue - return code.slice(i) - } - return code - } - /** Trims Whitespace including Newline, Tabspaces, etc. */ - export function TrimAll(code: string): string { - return code.trimStart() +/** Trims Whitespace and retains Newline, Tabspaces, etc. */ +export function TrimWhitespaceOnly(input: string): string { + for (let i = 0; i < input.length; i++) { + if (IsWhitespace(input.charCodeAt(i))) continue + return input.slice(i) } + return input +} +/** Trims Whitespace including Newline, Tabspaces, etc. */ +export function TrimAll(input: string): string { + return input.trimStart() } // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ /** Checks the value matches the next string */ -function NextTokenCheck(value: string, code: string): boolean { - if (value.length > code.length) return false +function NextTokenCheck(value: string, input: string): boolean { + if (value.length > input.length) return false for (let i = 0; i < value.length; i++) { - if (value.charCodeAt(i) !== code.charCodeAt(i)) return false + if (value.charCodeAt(i) !== input.charCodeAt(i)) return false } return true } /** Gets the next constant string value or empty if no match */ -function NextConst(value: string, code: string, ): [] | [string, string] { - return NextTokenCheck(value, code) - ? [code.slice(0, value.length), code.slice(value.length)] +function NextConst(value: string, input: string, ): [] | [string, string] { + return NextTokenCheck(value, input) + ? [input.slice(0, value.length), input.slice(value.length)] : [] } /** Takes the next constant string value skipping any whitespace */ -export function Const(value: string, code: string): [] | [string, string] { - if(value.length === 0) return ['', code] +export function Const(value: string, input: string): [] | [string, string] { + if(value.length === 0) return ['', input] const char_0 = value.charCodeAt(0) return ( - Chars.IsNewline(char_0) ? NextConst(value, Trim.TrimWhitespaceOnly(code)) : - Chars.IsWhitespace(char_0) ? NextConst(value, code) : - NextConst(value, Trim.TrimAll(code)) + IsNewline(char_0) ? NextConst(value, TrimWhitespaceOnly(input)) : + IsWhitespace(char_0) ? NextConst(value, input) : + NextConst(value, TrimAll(input)) ) } // ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ -function IdentIsFirst(char: number) { +function IdentIsFirst(charCode: number) { return ( - Chars.IsAlpha(char) || - Chars.IsDollarSign(char) || - Chars.IsUnderscore(char) + IsAlpha(charCode) || + IsDollarSign(charCode) || + IsUnderscore(charCode) ) } -function IdentIsRest(char: number) { +function IdentIsRest(charCode: number) { return ( - Chars.IsAlpha(char) || - Chars.IsDigit(char) || - Chars.IsDollarSign(char) || - Chars.IsUnderscore(char) + IsAlpha(charCode) || + IsDigit(charCode) || + IsDollarSign(charCode) || + IsUnderscore(charCode) ) } -function NextIdent(code: string): [] | [string, string] { - if (!IdentIsFirst(code.charCodeAt(0))) return [] - for (let i = 1; i < code.length; i++) { - const char = code.charCodeAt(i) +function NextIdent(input: string): [] | [string, string] { + if (!IdentIsFirst(input.charCodeAt(0))) return [] + for (let i = 1; i < input.length; i++) { + const char = input.charCodeAt(i) if (IdentIsRest(char)) continue - const slice = code.slice(0, i) - const rest = code.slice(i) + const slice = input.slice(0, i) + const rest = input.slice(i) return [slice, rest] } - return [code, ''] + return [input, ''] } /** Scans for the next Ident token */ -export function Ident(code: string): [] | [string, string] { - return NextIdent(Trim.TrimAll(code)) +export function Ident(input: string): [] | [string, string] { + return NextIdent(TrimAll(input)) } // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ /** Checks that the next number is not a leading zero */ -function NumberLeadingZeroCheck(code: string, index: number) { - const char_0 = code.charCodeAt(index + 0) - const char_1 = code.charCodeAt(index + 1) +function NumberLeadingZeroCheck(input: string, index: number) { + const char_0 = input.charCodeAt(index + 0) + const char_1 = input.charCodeAt(index + 1) return ( ( // 1-9 - Chars.IsNonZero(char_0) + IsNonZero(char_0) ) || ( // 0 - Chars.IsZero(char_0) && - !Chars.IsDigit(char_1) + IsZero(char_0) && + !IsDigit(char_1) ) || ( // 0. - Chars.IsZero(char_0) && - Chars.IsDot(char_1) + IsZero(char_0) && + IsDot(char_1) ) || ( // .0 - Chars.IsDot(char_0) && - Chars.IsDigit(char_1) + IsDot(char_0) && + IsDigit(char_1) ) ) } /** Gets the next number token */ -function NextNumber(code: string): [] | [string, string] { - const negated = code.charAt(0) === '-' +function NextNumber(input: string): [] | [string, string] { + const negated = input.charAt(0) === '-' const index = negated ? 1 : 0 - if (!NumberLeadingZeroCheck(code, index)) { + if (!NumberLeadingZeroCheck(input, index)) { return [] } const dash = negated ? '-' : '' let hasDot = false - for (let i = index; i < code.length; i++) { - const char_i = code.charCodeAt(i) - if (Chars.IsDigit(char_i)) { + for (let i = index; i < input.length; i++) { + const char_i = input.charCodeAt(i) + if (IsDigit(char_i)) { continue } - if (Chars.IsDot(char_i)) { + if (IsDot(char_i)) { if (hasDot) { - const slice = code.slice(index, i) - const rest = code.slice(i) + const slice = input.slice(index, i) + const rest = input.slice(i) return [`${dash}${slice}`, rest] } hasDot = true continue } - const slice = code.slice(index, i) - const rest = code.slice(i) + const slice = input.slice(index, i) + const rest = input.slice(i) return [`${dash}${slice}`, rest] } - return [code, ''] + return [input, ''] } /** Scans for the next number token */ export function Number(code: string) { - return NextNumber(Trim.TrimAll(code)) + return NextNumber(TrimAll(code)) } // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ -function NextString(options: string[], code: string): [] | [string, string] { - const first = code.charAt(0) +function NextString(options: string[], input: string): [] | [string, string] { + const first = input.charAt(0) if(!options.includes(first)) return [] const quote = first - for(let i = 1; i < code.length; i++) { - const char = code.charAt(i) + for(let i = 1; i < input.length; i++) { + const char = input.charAt(i) if(char === quote) { - const slice = code.slice(1, i) - const rest = code.slice(i + 1) + const slice = input.slice(1, i) + const rest = input.slice(i + 1) return [slice, rest] } } return [] } /** Scans the next Literal String value */ -export function String(options: string[], code: string) { - return NextString(options, Trim.TrimAll(code)) +export function String(options: string[], input: string) { + return NextString(options, TrimAll(input)) +} +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function UntilStartsWith(value: string, input: string) { + return input.startsWith(value) } +export function Until(value: string, input: string, result: string = ''): [] | [string, string] { + return ( + input === '' ? [] : (() => { + return UntilStartsWith(value, input) + ? [result, input] + : (() => { + const [left, right] = [input.slice(0, 1), input.slice(1)] + return Until(value, right, `${result}${left}`) + })() + })() + ) +} \ No newline at end of file diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 475162e..d89b7d6 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -26,8 +26,10 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ +// deno-lint-ignore-file // deno-fmt-ignore-file -// deno-lint-ignore-file no-explicit-any + +import { Guard } from '../guard/index.ts' export type IModuleProperties = Record @@ -81,6 +83,16 @@ export function Context(...args: unknown[]): never { const [left, right, mapping] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], args[1], Identity] return { type: 'Context', left, right, mapping } as never } +/** Returns true if the value is a Context Parser */ +export function IsContext(value: unknown): value is IContext { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'left') + && Guard.HasPropertyKey(value, 'right') + && Guard.IsEqual(value.type, 'Context') + && Guard.IsObject(value.left) + && Guard.IsObject(value.right) +} // ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ @@ -100,7 +112,14 @@ export function Array(...args: unknown[]): never { const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Array', parser, mapping } as never } - +/** Returns true if the value is a Array Parser */ +export function IsArray(value: unknown): value is IArray { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'parser') + && Guard.IsEqual(value.type, 'Array') + && Guard.IsObject(value.parser) +} // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ @@ -117,7 +136,14 @@ export function Const(...args: unknown[]): never { const [value, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Const', value, mapping } as never } - +/** Returns true if the value is a Const Parser */ +export function IsConst(value: unknown): value is IConst { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'value') + && Guard.IsEqual(value.type, 'Const') + && Guard.IsString(value.value) +} // ------------------------------------------------------------------ // Ref // ------------------------------------------------------------------ @@ -134,7 +160,14 @@ export function Ref(...args: unknown[]): never { const [ref, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Ref', ref, mapping } as never } - +/** Returns true if the value is a Ref Parser */ +export function IsRef(value: unknown): value is IRef { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'ref') + && Guard.IsEqual(value.type, 'Ref') + && Guard.IsString(value.ref) +} // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ @@ -151,7 +184,14 @@ export function String(...params: unknown[]): never { const [options, mapping] = params.length === 2 ? [params[0], params[1]] : [params[0], Identity] return { type: 'String', options, mapping } as never } - +/** Returns true if the value is a String Parser */ +export function IsString(value: unknown): value is IString { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.IsEqual(value.type, 'String') + && Guard.HasPropertyKey(value, 'options') + && Guard.IsArray(value.options) +} // ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ @@ -167,7 +207,12 @@ export function Ident(...params: unknown[]): never { const mapping = params.length === 1 ? params[0] : Identity return { type: 'Ident', mapping } as never } - +/** Returns true if the value is a Ident Parser */ +export function IsIdent(value: unknown): value is IIdent { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.IsEqual(value.type, 'Ident') +} // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ @@ -183,7 +228,12 @@ export function Number(...params: unknown[]): never { const mapping = params.length === 1 ? params[0] : Identity return { type: 'Number', mapping } as never } - +/** Returns true if the value is a Number Parser */ +export function IsNumber(value: unknown): value is INumber { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.IsEqual(value.type, 'Number') +} // ------------------------------------------------------------------ // Optional // ------------------------------------------------------------------ @@ -203,14 +253,21 @@ export function Optional(...args: unknown[]): never { const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Optional', parser, mapping } as never } - +/** Returns true if the value is a Optional Parser */ +export function IsOptional(value: unknown): value is IOptional { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'parser') + && Guard.IsEqual(value.type, 'Optional') + && Guard.IsObject(value.parser) +} // ------------------------------------------------------------------ // Tuple // ------------------------------------------------------------------ export type TupleParameter = StaticEnsure< - Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] - ? TupleParameter>]> - : Result + Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] + ? TupleParameter>]> + : Result > export interface ITuple extends IParser { type: 'Tuple' @@ -225,15 +282,21 @@ export function Tuple(...args: unknown[]): never { const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Tuple', parsers, mapping } as never } - +/** Returns true if the value is a Tuple Parser */ +export function IsTuple(value: unknown): value is ITuple { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'parsers') + && Guard.IsEqual(value.type, 'Tuple') + && Guard.IsArray(value.parsers) +} // ------------------------------------------------------------------ // Union // ------------------------------------------------------------------ - export type UnionParameter = StaticEnsure< - Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] - ? UnionParameter> - : Result + Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] + ? UnionParameter> + : Result > export interface IUnion extends IParser { type: 'Union' @@ -248,3 +311,35 @@ export function Union(...args: unknown[]): never { const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Union', parsers, mapping } as never } +/** Returns true if the value is a Union Parser */ +export function IsUnion(value: unknown): value is IUnion { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'parsers') + && Guard.IsEqual(value.type, 'Union') + && Guard.IsArray(value.parsers) +} +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +export interface IUntil extends IParser { + type: 'Until' + value: string +} +/** `[TERM]` Creates a Until Parser */ +export function Until>(value: string, mapping: Mapping): IUntil +/** `[TERM]` Creates a Until Parser */ +export function Until(value: string): IUntil +/** `[TERM]` Creates a Until Parser */ +export function Until(...args: unknown[]): never { + const [value, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Until', value, mapping } as never +} +/** Returns true if the value is a Until Parser */ +export function IsUntil(value: unknown): value is IUntil { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'value') + && Guard.IsEqual(value.type, 'Until') + && Guard.IsString(value.value) +} \ No newline at end of file diff --git a/src/static/parse.ts b/src/static/parse.ts index a7356f2..e2e8b0d 100644 --- a/src/static/parse.ts +++ b/src/static/parse.ts @@ -34,100 +34,109 @@ import * as Types from './types.ts' // ------------------------------------------------------------------ // Context // ------------------------------------------------------------------ -type ContextParser = ( - Parse extends [infer Context extends unknown, infer Rest extends string] +type ContextParser = ( + Parse extends [infer Context extends unknown, infer Rest extends string] ? Parse : [] ) // ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ -type ArrayParser = ( - Parse extends [infer Value1 extends unknown, infer Rest extends string] +type ArrayParser = ( + Parse extends [infer Value1 extends unknown, infer Rest extends string] ? ArrayParser - : [Result, Code] + : [Result, Input] ) // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ -type ConstParser = ( - Tokens.Const extends [infer Match extends Value, infer Rest extends string] +type ConstParser = ( + Tokens.Const extends [infer Match extends Value, infer Rest extends string] + ? [Match, Rest] + : [] +) +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +type UntilParser = ( + Tokens.Until extends [infer Match extends string, infer Rest extends string] ? [Match, Rest] : [] ) // ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ -type IdentParser = ( - Tokens.Ident extends [infer Match extends string, infer Rest extends string] +type IdentParser = ( + Tokens.Ident extends [infer Match extends string, infer Rest extends string] ? [Match, Rest] : [] ) // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ -type NumberParser = ( - Tokens.Number extends [infer Match extends string, infer Rest extends string] +type NumberParser = ( + Tokens.Number extends [infer Match extends string, infer Rest extends string] ? [Match, Rest] : [] ) // ------------------------------------------------------------------ // Optional // ------------------------------------------------------------------ -type OptionalParser = ( - Parse extends [infer Value extends unknown, infer Rest extends string] +type OptionalParser = ( + Parse extends [infer Value extends unknown, infer Rest extends string] ? [[Value], Rest] - : [[], Code] + : [[], Input] ) // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ -type StringParser = ( - Tokens.String extends [infer Match extends string, infer Rest extends string] +type StringParser = ( + Tokens.String extends [infer Match extends string, infer Rest extends string] ? [Match, Rest] : [] ) // ------------------------------------------------------------------ // Tuple // ------------------------------------------------------------------ -type TupleParser = ( +type TupleParser = ( Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] - ? Parse extends [infer Value extends unknown, infer Rest extends string] + ? Parse extends [infer Value extends unknown, infer Rest extends string] ? TupleParser : [] - : [Result, Code] + : [Result, Input] ) // ------------------------------------------------------------------ // Union // ------------------------------------------------------------------ -type UnionParser = ( +type UnionParser = ( Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] - ? Parse extends [infer Value extends unknown, infer Rest extends string] + ? Parse extends [infer Value extends unknown, infer Rest extends string] ? [Value, Rest] - : UnionParser + : UnionParser : [] ) // ------------------------------------------------------------------ // Parse // ------------------------------------------------------------------ -type ParseCode = ( - Type extends Types.Context ? ContextParser : - Type extends Types.Array ? ArrayParser : - Type extends Types.Const ? ConstParser : - Type extends Types.Ident ? IdentParser : - Type extends Types.Number ? NumberParser : - Type extends Types.Optional ? OptionalParser : - Type extends Types.String ? StringParser : - Type extends Types.Tuple ? TupleParser : - Type extends Types.Union ? UnionParser : +type ParseCode = ( + Parser extends Types.Context ? ContextParser : + Parser extends Types.Array ? ArrayParser : + Parser extends Types.Const ? ConstParser : + Parser extends Types.Ident ? IdentParser : + Parser extends Types.Number ? NumberParser : + Parser extends Types.Optional ? OptionalParser : + Parser extends Types.String ? StringParser : + Parser extends Types.Tuple ? TupleParser : + Parser extends Types.Union ? UnionParser : + Parser extends Types.Until ? UntilParser : [] ) type ParseMapping = ( (Parser['mapping'] & { input: Result, context: Context })['output'] ) /** Parses code with the given parser */ -export type Parse = ( - ParseCode extends [infer L extends unknown, infer R extends string] - ? [ParseMapping, R] +export type Parse = ( + ParseCode extends [infer Code extends unknown, infer Rest extends string] + ? [ParseMapping, Rest] : [] ) diff --git a/src/static/token.ts b/src/static/token.ts index bcddb8a..f4ff5d2 100644 --- a/src/static/token.ts +++ b/src/static/token.ts @@ -113,7 +113,6 @@ type NextConst = ( : [] ) /** Scans for the next constant value */ - export type Const = ( Value extends '' ? ['', Code] : Value extends `${infer First extends string}${string}` @@ -126,19 +125,16 @@ export type Const = ( // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ - type NextNumberNegate = ( Code extends `${Chars.Hyphen}${infer Rest extends string}` ? [Chars.Hyphen, Rest] : [Chars.Empty, Code] ) - type NextNumberZeroCheck = ( Code extends `0${infer Rest}` ? NextUnion extends [string, string] ? false : true : true ) - type NextNumberScan = ( NextUnion<[...Chars.Digit, Chars.Dot], Code> extends [infer Char extends string, infer Rest extends string] ? Char extends Chars.Dot @@ -148,7 +144,6 @@ type NextNumberScan : [Result, Code] ) - export type NextNumber = ( NextNumberNegate extends [infer Negate extends string, infer Rest extends string] ? NextNumberZeroCheck extends true @@ -205,3 +200,18 @@ type NextIdent = ( ) /** Scans for the next Ident */ export type Ident = NextIdent> + +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +type UntilStartsWith = ( + Input extends `${Value}${string}` ? true : false +) +export type Until = ( + Input extends `` ? [] : + UntilStartsWith extends true + ? [Result, Input] + : Input extends `${infer Left extends string}${infer Right extends string}` + ? Until + : never +) \ No newline at end of file diff --git a/src/static/types.ts b/src/static/types.ts index c5ce9b8..7aaca37 100644 --- a/src/static/types.ts +++ b/src/static/types.ts @@ -85,16 +85,24 @@ export interface Const extends IParser { + type: 'Until' + value: Value +} +// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ -/** `[TERM]` Creates an Ident Parser. */ +/** Creates an Ident Parser. */ export interface Ident extends IParser { type: 'Ident' } // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ -/** `[TERM]` Creates a Number Parser. */ +/** Creates a Number Parser. */ export interface Number extends IParser { type: 'Number' } diff --git a/tasks.ts b/tasks.ts index e877d68..45c6e03 100644 --- a/tasks.ts +++ b/tasks.ts @@ -1,4 +1,4 @@ -import { Task } from 'https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.0/src/index.ts' +import { Task } from 'https://raw.githubusercontent.com/sinclairzx81/tasksmith/0.8.2/src/index.ts' // ------------------------------------------------------------------ // Clean @@ -34,7 +34,7 @@ Task.run('build', () => Task.build('src', { packageJson: { name: '@sinclair/parsebox', description: 'Parser Combinators in the TypeScript Type System', - version: '0.9.2', + version: '0.9.3', keywords: ['typescript', 'parser', 'combinator'], license: 'MIT', author: 'sinclairzx81', diff --git a/test/__tests__/runtime/parse.ts b/test/__tests__/runtime/parse.ts index 9983182..75f2322 100644 --- a/test/__tests__/runtime/parse.ts +++ b/test/__tests__/runtime/parse.ts @@ -22,6 +22,15 @@ Deno.test('Const', () => { Assert(Runtime.Parse(Runtime.Const('A'), ' A '), ['A', ' ']) }) // ---------------------------------------------------------------- +// Until +// ---------------------------------------------------------------- +Deno.test('Until', () => { + Assert(Runtime.Parse(Runtime.Until('A'), ''), []) + Assert(Runtime.Parse(Runtime.Until('A'), 'A'), ['', 'A']) + Assert(Runtime.Parse(Runtime.Until('A'), ' A'), [' ', 'A']) + Assert(Runtime.Parse(Runtime.Until('A'), ' A '), [' ', 'A ']) +}) +// ---------------------------------------------------------------- // Ident // ---------------------------------------------------------------- Deno.test('Ident', () => { diff --git a/test/__tests__/runtime/token.ts b/test/__tests__/runtime/token.ts index 1b74073..dd4bc7e 100644 --- a/test/__tests__/runtime/token.ts +++ b/test/__tests__/runtime/token.ts @@ -1,24 +1,27 @@ import { Runtime } from '@sinclair/parsebox' import { Assert } from './assert.ts' -Deno.test('Empty', () => { +// ------------------------------------------------------------------ +// Const +// ------------------------------------------------------------------ +Deno.test('Const: Empty', () => { Assert(Runtime.Token.Const('', ''), ['', '']) Assert(Runtime.Token.Const('', 'A'), ['', 'A']) Assert(Runtime.Token.Const('', ' A'), ['', ' A']) }) -Deno.test('Single-Char', () => { +Deno.test('Const: Single-Char', () => { Assert(Runtime.Token.Const('A', 'A'), ['A', '']) Assert(Runtime.Token.Const('A', 'A '), ['A', ' ']) Assert(Runtime.Token.Const('A', 'AA'), ['A', 'A']) Assert(Runtime.Token.Const('A', 'AA '), ['A', 'A ']) }) -Deno.test('Multi-Char', () => { +Deno.test('Const: Multi-Char', () => { Assert(Runtime.Token.Const('AB', 'AB'), ['AB', '']) Assert(Runtime.Token.Const('AB', 'AB '), ['AB', ' ']) Assert(Runtime.Token.Const('AB', 'ABA'), ['AB', 'A']) Assert(Runtime.Token.Const('AB', 'ABA '), ['AB', 'A ']) }) -Deno.test('Single-Char -> Ignore-Whitespace', () => { +Deno.test('Const: Single-Char -> Ignore-Whitespace', () => { Assert(Runtime.Token.Const('A', ' A'), ['A', '']) Assert(Runtime.Token.Const('A', ' A '), ['A', ' ']) Assert(Runtime.Token.Const('A', ' AA'), ['A', 'A']) @@ -28,7 +31,7 @@ Deno.test('Single-Char -> Ignore-Whitespace', () => { Assert(Runtime.Token.Const('A', '\n AA '), ['A', 'A ']) Assert(Runtime.Token.Const('A', ' \n AA '), ['A', 'A ']) }) -Deno.test('Multi-Char -> Ignore-Whitespace', () => { +Deno.test('Const: Multi-Char -> Ignore-Whitespace', () => { Assert(Runtime.Token.Const('AB', ' AB'), ['AB', '']) Assert(Runtime.Token.Const('AB', ' AB '), ['AB', ' ']) Assert(Runtime.Token.Const('AB', ' ABA'), ['AB', 'A']) @@ -38,7 +41,7 @@ Deno.test('Multi-Char -> Ignore-Whitespace', () => { Assert(Runtime.Token.Const('AB', '\n ABA '), ['AB', 'A ']) Assert(Runtime.Token.Const('AB', ' \n ABA '), ['AB', 'A ']) }) -Deno.test('Single-Whitespace', () => { +Deno.test('Const: Single-Whitespace', () => { Assert(Runtime.Token.Const(' ', ''), []) Assert(Runtime.Token.Const(' ', ' '), [' ', '']) Assert(Runtime.Token.Const(' ', ' A'), [' ', 'A']) @@ -46,7 +49,7 @@ Deno.test('Single-Whitespace', () => { Assert(Runtime.Token.Const(' ', ' AA'), [' ', 'AA']) Assert(Runtime.Token.Const(' ', ' AA '), [' ', 'AA ']) }) -Deno.test('Multi-Whitespace', () => { +Deno.test('Const: Multi-Whitespace', () => { Assert(Runtime.Token.Const(' ', ''), []) Assert(Runtime.Token.Const(' ', ' '), []) Assert(Runtime.Token.Const(' ', ' A'), [' ', 'A']) @@ -54,7 +57,7 @@ Deno.test('Multi-Whitespace', () => { Assert(Runtime.Token.Const(' ', ' AA'), [' ', 'AA']) Assert(Runtime.Token.Const(' ', ' AA '), [' ', 'AA ']) }) -Deno.test('Newline', () => { +Deno.test('Const: Newline', () => { Assert(Runtime.Token.Const('\n', ''), []) Assert(Runtime.Token.Const('\n', ' '), []) Assert(Runtime.Token.Const('\n', '\nA'), ['\n', 'A']) @@ -62,7 +65,7 @@ Deno.test('Newline', () => { Assert(Runtime.Token.Const('\n', ' \nAA'), ['\n', 'AA']) Assert(Runtime.Token.Const('\n', ' \nAA '), ['\n', 'AA ']) }) -Deno.test('Newline-Single-Whitespace', () => { +Deno.test('Const: Newline-Single-Whitespace', () => { Assert(Runtime.Token.Const('\n ', ''), []) Assert(Runtime.Token.Const('\n ', ' '), []) Assert(Runtime.Token.Const('\n ', '\nA'), []) @@ -74,7 +77,7 @@ Deno.test('Newline-Single-Whitespace', () => { Assert(Runtime.Token.Const('\n ', ' \n AA'), ['\n ', 'AA']) Assert(Runtime.Token.Const('\n ', ' \n AA '), ['\n ', 'AA ']) }) -Deno.test('Newline-Multi-Whitespace', () => { +Deno.test('Const: Newline-Multi-Whitespace', () => { Assert(Runtime.Token.Const('\n ', ''), []) Assert(Runtime.Token.Const('\n ', ' '), []) Assert(Runtime.Token.Const('\n ', '\nA'), []) @@ -86,3 +89,91 @@ Deno.test('Newline-Multi-Whitespace', () => { Assert(Runtime.Token.Const('\n ', ' \n AA'), ['\n ', 'AA']) Assert(Runtime.Token.Const('\n ', ' \n AA '), ['\n ', 'AA ']) }) +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +Deno.test('Until: Empty', () => { + Assert(Runtime.Token.Until('', ''), []) + Assert(Runtime.Token.Until('', 'A'), ['', 'A']) + Assert(Runtime.Token.Until('', ' A'), ['', ' A']) +}) +Deno.test('Until: Single-Char', () => { + Assert(Runtime.Token.Until('A', 'A'), ['', 'A']) + Assert(Runtime.Token.Until('A', 'A '), ['', 'A ']) + Assert(Runtime.Token.Until('A', 'AA'), ['', 'AA']) + Assert(Runtime.Token.Until('A', 'AA '), ['', 'AA ']) +}) +Deno.test('Until: Multi-Char', () => { + Assert(Runtime.Token.Until('AB', 'AB'), ['', 'AB']) + Assert(Runtime.Token.Until('AB', 'AB '), ['', 'AB ']) + Assert(Runtime.Token.Until('AB', 'ABA'), ['', 'ABA']) + Assert(Runtime.Token.Until('AB', 'ABA '), ['', 'ABA ']) +}) +Deno.test('Until: Single-Char -> Ignore-Whitespace', () => { + Assert(Runtime.Token.Until('A', ' A'), [' ', 'A']) + Assert(Runtime.Token.Until('A', ' A '), [' ', 'A ']) + Assert(Runtime.Token.Until('A', ' AA'), [' ', 'AA']) + Assert(Runtime.Token.Until('A', ' AA '), [' ', 'AA ']) + Assert(Runtime.Token.Until('A', '\nAA '), ['\n', 'AA ']) + Assert(Runtime.Token.Until('A', ' \nAA '), [' \n', 'AA ']) + Assert(Runtime.Token.Until('A', '\n AA '), ['\n ', 'AA ']) + Assert(Runtime.Token.Until('A', ' \n AA '), [' \n ', 'AA ']) +}) +Deno.test('Until: Multi-Char -> Ignore-Whitespace', () => { + Assert(Runtime.Token.Until('AB', ' AB'), [' ', 'AB']) + Assert(Runtime.Token.Until('AB', ' AB '), [' ', 'AB ']) + Assert(Runtime.Token.Until('AB', ' ABA'), [' ', 'ABA']) + Assert(Runtime.Token.Until('AB', ' ABA '), [' ', 'ABA ']) + Assert(Runtime.Token.Until('AB', '\nABA '), ['\n', 'ABA ']) + Assert(Runtime.Token.Until('AB', ' \nABA '), [' \n', 'ABA ']) + Assert(Runtime.Token.Until('AB', '\n ABA '), ['\n ', 'ABA ']) + Assert(Runtime.Token.Until('AB', ' \n ABA '), [' \n ', 'ABA ']) +}) +Deno.test('Until: Single-Whitespace', () => { + Assert(Runtime.Token.Until(' ', ''), []) + Assert(Runtime.Token.Until(' ', ' '), ['', ' ']) + Assert(Runtime.Token.Until(' ', ' A'), ['', ' A']) + Assert(Runtime.Token.Until(' ', ' A '), ['', ' A ']) + Assert(Runtime.Token.Until(' ', ' AA'), ['', ' AA']) + Assert(Runtime.Token.Until(' ', ' AA '), ['', ' AA ']) +}) +Deno.test('Until: Multi-Whitespace', () => { + Assert(Runtime.Token.Until(' ', ''), []) + Assert(Runtime.Token.Until(' ', ' '), []) + Assert(Runtime.Token.Until(' ', ' A'), ['', ' A']) + Assert(Runtime.Token.Until(' ', ' A '), ['', ' A ']) + Assert(Runtime.Token.Until(' ', ' AA'), ['', ' AA']) + Assert(Runtime.Token.Until(' ', ' AA '), ['', ' AA ']) +}) +Deno.test('Until: Newline', () => { + Assert(Runtime.Token.Until('\n', ''), []) + Assert(Runtime.Token.Until('\n', ' '), []) + Assert(Runtime.Token.Until('\n', '\nA'), ['', '\nA']) + Assert(Runtime.Token.Until('\n', ' \nA '), [' ', '\nA ']) + Assert(Runtime.Token.Until('\n', ' \nAA'), [' ', '\nAA']) + Assert(Runtime.Token.Until('\n', ' \nAA '), [' ', '\nAA ']) +}) +Deno.test('Until: Newline-Single-Whitespace', () => { + Assert(Runtime.Token.Until('\n ', ''), []) + Assert(Runtime.Token.Until('\n ', ' '), []) + Assert(Runtime.Token.Until('\n ', '\nA'), []) + Assert(Runtime.Token.Until('\n ', ' \nA '), []) + Assert(Runtime.Token.Until('\n ', ' \nAA'), []) + Assert(Runtime.Token.Until('\n ', ' \nAA '), []) + Assert(Runtime.Token.Until('\n ', '\n A'), ['', '\n A']) + Assert(Runtime.Token.Until('\n ', ' \n A '), [' ', '\n A ']) + Assert(Runtime.Token.Until('\n ', ' \n AA'), [' ', '\n AA']) + Assert(Runtime.Token.Until('\n ', ' \n AA '), [' ', '\n AA ']) +}) +Deno.test('Until: Newline-Multi-Whitespace', () => { + Assert(Runtime.Token.Until('\n ', ''), []) + Assert(Runtime.Token.Until('\n ', ' '), []) + Assert(Runtime.Token.Until('\n ', '\nA'), []) + Assert(Runtime.Token.Until('\n ', ' \nA '), []) + Assert(Runtime.Token.Until('\n ', ' \nAA'), []) + Assert(Runtime.Token.Until('\n ', ' \nAA '), []) + Assert(Runtime.Token.Until('\n ', '\n A'), ['', '\n A']) + Assert(Runtime.Token.Until('\n ', ' \n A '), [' ', '\n A ']) + Assert(Runtime.Token.Until('\n ', ' \n AA'), [' ', '\n AA']) + Assert(Runtime.Token.Until('\n ', ' \n AA '), [' ', '\n AA ']) +}) \ No newline at end of file diff --git a/test/__tests__/static/parse.ts b/test/__tests__/static/parse.ts index fde124d..235efa5 100644 --- a/test/__tests__/static/parse.ts +++ b/test/__tests__/static/parse.ts @@ -21,7 +21,13 @@ Assert, ''>, []>() Assert, 'A'>, ['A', '']>() Assert, ' A'>, ['A', '']>() Assert, ' A '>, ['A', ' ']>() - +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +Assert, ''>, []>() +Assert, 'A'>, ['', 'A']>() +Assert, ' A'>, [' ', 'A']>() +Assert, ' A '>, [' ', 'A ']>() // ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ diff --git a/test/__tests__/static/token.ts b/test/__tests__/static/token.ts index c1d9a09..3b0f72f 100644 --- a/test/__tests__/static/token.ts +++ b/test/__tests__/static/token.ts @@ -3,14 +3,14 @@ import { Static } from '@sinclair/parsebox' function Assert(): void {} // ------------------------------------------------------------------ -// Empty +// Const: Empty // ------------------------------------------------------------------ Assert, ['', '']>() Assert, ['', 'A']>() Assert, ['', ' A']>() // ------------------------------------------------------------------ -// Single-Char +// Const: Single-Char // ------------------------------------------------------------------ Assert, ['A', '']>() Assert, ['A', ' ']>() @@ -18,7 +18,7 @@ Assert, ['A', 'A']>() Assert, ['A', 'A ']>() // ------------------------------------------------------------------ -// Multi-Char +// Const: Multi-Char // ------------------------------------------------------------------ Assert, ['AB', '']>() Assert, ['AB', ' ']>() @@ -26,7 +26,7 @@ Assert, ['AB', 'A']>() Assert, ['AB', 'A ']>() // ------------------------------------------------------------------ -// Single-Char -> Ignore-Whitespace +// Const: Single-Char -> Ignore-Whitespace // ------------------------------------------------------------------ Assert, ['A', '']>() Assert, ['A', ' ']>() @@ -38,7 +38,7 @@ Assert, ['A', 'A ']>() Assert, ['A', 'A ']>() // ------------------------------------------------------------------ -// Multi-Char -> Ignore-Whitespace +// Const: Multi-Char -> Ignore-Whitespace // ------------------------------------------------------------------ Assert, ['AB', '']>() Assert, ['AB', ' ']>() @@ -50,7 +50,7 @@ Assert, ['AB', 'A ']>() Assert, ['AB', 'A ']>() // ------------------------------------------------------------------ -// Single-Whitespace +// Const: Single-Whitespace // ------------------------------------------------------------------ Assert, []>() Assert, [' ', '']>() @@ -60,7 +60,7 @@ Assert, [' ', 'AA']>() Assert, [' ', 'AA ']>() // ------------------------------------------------------------------ -// Multi-Whitespace +// Const: Multi-Whitespace // ------------------------------------------------------------------ Assert, []>() Assert, []>() @@ -70,7 +70,7 @@ Assert, [' ', 'AA']>() Assert, [' ', 'AA ']>() // ------------------------------------------------------------------ -// Newline +// Const: Newline // ------------------------------------------------------------------ Assert, []>() Assert, []>() @@ -80,7 +80,7 @@ Assert, ['\n', 'AA']>() Assert, ['\n', 'AA ']>() // ------------------------------------------------------------------ -// Newline-Single-Whitespace +// Const: Newline-Single-Whitespace // ------------------------------------------------------------------ Assert, []>() Assert, []>() @@ -93,7 +93,9 @@ Assert, ['\n ', 'A ']>() Assert, ['\n ', 'AA']>() Assert, ['\n ', 'AA ']>() -// Newline-Multi-Whitespace +// ------------------------------------------------------------------ +// Const: Newline-Multi-Whitespace +// ------------------------------------------------------------------ Assert, []>() Assert, []>() Assert, []>() @@ -104,3 +106,108 @@ Assert, ['\n ', 'A']>() Assert, ['\n ', 'A ']>() Assert, ['\n ', 'AA']>() Assert, ['\n ', 'AA ']>() + +// ------------------------------------------------------------------ +// Until: Empty +// ------------------------------------------------------------------ +Assert, []> +Assert, ['', 'A']> +Assert, ['', ' A']> + +// ------------------------------------------------------------------ +// Until: Single-Char +// ------------------------------------------------------------------ +Assert, ['', 'A']> +Assert, ['', 'A ']> +Assert, ['', 'AA']> +Assert, ['', 'AA ']> + +// ------------------------------------------------------------------ +// Until: Multi-Char +// ------------------------------------------------------------------ +Assert, ['', 'AB']> +Assert, ['', 'AB ']> +Assert, ['', 'ABA']> +Assert, ['', 'ABA ']> + +// ------------------------------------------------------------------ +// Until: Single-Char -> Ignore-Whitespace +// ------------------------------------------------------------------ +Assert, [' ', 'A']> +Assert, [' ', 'A ']> +Assert, [' ', 'AA']> +Assert, [' ', 'AA ']> +Assert, ['\n', 'AA ']> +Assert, [' \n', 'AA ']> +Assert, ['\n ', 'AA ']> +Assert, [' \n ', 'AA ']> + +// ------------------------------------------------------------------ +// Until: Multi-Char -> Ignore-Whitespace +// ------------------------------------------------------------------ +Assert, [' ', 'AB']> +Assert, [' ', 'AB ']> +Assert, [' ', 'ABA']> +Assert, [' ', 'ABA ']> +Assert, ['\n', 'ABA ']> +Assert, [' \n', 'ABA ']> +Assert, ['\n ', 'ABA ']> +Assert, [' \n ', 'ABA ']> + +// ------------------------------------------------------------------ +// Until: Single-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, ['', ' ']> +Assert, ['', ' A']> +Assert, ['', ' A ']> +Assert, ['', ' AA']> +Assert, ['', ' AA ']> + +// ------------------------------------------------------------------ +// Until: Multi-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, ['', ' A']> +Assert, ['', ' A ']> +Assert, ['', ' AA']> +Assert, ['', ' AA ']> + +// ------------------------------------------------------------------ +// Until: Newline +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, ['', '\nA']> +Assert, [' ', '\nA ']> +Assert, [' ', '\nAA']> +Assert, [' ', '\nAA ']> + +// ------------------------------------------------------------------ +// Until: Newline-Single-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, ['', '\n A']> +Assert, [' ', '\n A ']> +Assert, [' ', '\n AA']> +Assert, [' ', '\n AA ']> + +// ------------------------------------------------------------------ +// Until: Newline-Multi-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, ['', '\n A']> +Assert, [' ', '\n A ']> +Assert, [' ', '\n AA']> +Assert, [' ', '\n AA ']> \ No newline at end of file