diff --git a/readme.md b/readme.md index d76109b..93c58f4 100644 --- a/readme.md +++ b/readme.md @@ -69,9 +69,11 @@ License: MIT - [Tuple](#Tuple) - [Union](#Union) - [Array](#Array) - - [Until](#Until) - [Optional](#Optional) - [Epsilon](#Epsilon) +- [Range](#Range) + - [Until](#Until) + - [UntilNonEmpty](#UntilNonEmpty) - [Terminals](#Terminals) - [Number](#Number) - [String](#String) @@ -187,27 +189,6 @@ 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 will parse characters up to (but not including) one of the specified sentinel string values. If a sentinel value is not found, parsing fails. - -**BNF** - -```bnf - ::= ? any character until ['Z'] ? -``` - -**TypeScript** - -```typescript -const T = Runtime.Until(['Z']) // const T = { - // type: 'Until', - // values: ['Z'] - // } - -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. @@ -256,7 +237,58 @@ const R1 = Runtime.Parse(T, 'X Y Z') // const R1 = [['X', 'Y'], ' const R2 = Runtime.Parse(T, 'Y Z') // const R2 = [[], 'Y Z'] ``` -## Terminals +## Range Combinators + +ParseBox range combinators match character sequences up to one or more terminating sentinel strings. These combinators are used to match arbituary Unicode (UTF-16) sequences. + + +### Until + +The Until combinator parses characters up to (but not including) one of the specified sentinel string values. It captures all characters encountered before the sentinel. If a sentinel value is not found in the input, parsing fails. Until succeeds even if it matches a zero-length string. This occurs if a sentinel is found immediately at the current parsing position. + +**BNF** + +```bnf + ::= ? any character sequence (0 or more) until 'Z' ? +``` + +**TypeScript** + +```typescript +const T = Runtime.Until(['Z']) // const T = { + // type: 'Until', + // values: ['Z'] + // } + +const R = Runtime.Parse(T, 'X Y Z') // const R = ['X Y ', 'Z'] +``` + +### UntilNonEmpty + +The UntilNonEmpty combinator works the same as Until, but it fails if the parsed content yields a zero-length string. + +**BNF** + +```bnf + ::= ? any character sequence (1 or more) until 'Z'. fails on zero length ? +``` + +**TypeScript** + +```typescript +const T = Runtime.UntilNonEmpty(['Z']) // const T = { + // type: 'UntilNonEmpty', + // values: ['Z'] + // } + +const R1 = Runtime.Parse(T, 'X Y Z') // const R1 = ['X Y ', 'Z'] + +const R2 = Runtime.Parse(T, ' Z') // const R2 = [' ', 'Z'] + +const R3 = Runtime.Parse(T, 'Z') // const R3 = [] +``` + +## Terminal Combinators ParseBox provides combinators for parsing common lexical tokens, such as numbers, identifiers, and strings, enabling static, optimized parsing of typical JavaScript constructs. diff --git a/src/compile/common/comment.ts b/src/compile/common/comment.ts index 8c8c292..9fb6323 100644 --- a/src/compile/common/comment.ts +++ b/src/compile/common/comment.ts @@ -27,23 +27,26 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ // deno-fmt-ignore-file -// deno-lint-ignore-file no-unused-vars +// deno-lint-ignore-file import { Runtime } from '../../runtime/index.ts' import { Unreachable } from './unreachable.ts' import { Escape } from './escape.ts' +function FromArray(parser: Runtime.IArray): string { + return `${FromParser(parser.parser)}[]` +} function FromContext(parser: Runtime.IContext): string { return `${FromParser(parser.left)} -> ${FromParser(parser.right)}` } -function FromTuple(parser: Runtime.ITuple): string { - return `[${parser.parsers.map((parser) => `${FromParser(parser)}`).join(', ')}]` +function FromConst(parser: Runtime.IConst): string { + return `'${Escape(parser.value)}'` } -function FromUnion(parser: Runtime.IUnion): string { - return parser.parsers.map((parser) => `${FromParser(parser)}`).join(' | ') +function FromIdent(parser: Runtime.IIdent): string { + return `` } -function FromArray(parser: Runtime.IArray): string { - return `${FromParser(parser.parser)}[]` +function FromNumber(parser: Runtime.INumber): string { + return `` } function FromOptional(parser: Runtime.IOptional): string { return `${FromParser(parser.parser)}?` @@ -51,34 +54,35 @@ function FromOptional(parser: Runtime.IOptional): string { function FromRef(parser: Runtime.IRef): string { return `${parser.ref}` } -function FromConst(parser: Runtime.IConst): string { - return `'${Escape(parser.value)}'` +function FromString(parser: Runtime.IString): string { + return `` } -function FromUntil(parser: Runtime.IUntil): string { - return `string` +function FromTuple(parser: Runtime.ITuple): string { + return `[${parser.parsers.map((parser) => `${FromParser(parser)}`).join(', ')}]` } -function FromIdent(parser: Runtime.IIdent): string { - return `` +function FromUnion(parser: Runtime.IUnion): string { + return parser.parsers.map((parser) => `${FromParser(parser)}`).join(' | ') } -function FromString(parser: Runtime.IString): string { - return `` +function FromUntil(parser: Runtime.IUntil): string { + return `string` } -function FromNumber(parser: Runtime.INumber): string { - return `` +function FromUntilNonEmpty(parser: Runtime.IUntilNonEmpty): string { + return `string` } function FromParser(parser: Runtime.IParser): string { return ( - Runtime.IsContext(parser) ? FromContext(parser) : - Runtime.IsTuple(parser) ? FromTuple(parser) : - Runtime.IsUnion(parser) ? FromUnion(parser) : Runtime.IsArray(parser) ? FromArray(parser) : + Runtime.IsContext(parser) ? FromContext(parser) : + Runtime.IsConst(parser) ? FromConst(parser) : + Runtime.IsIdent(parser) ? FromIdent(parser) : + Runtime.IsNumber(parser) ? FromNumber(parser) : Runtime.IsOptional(parser) ? FromOptional(parser) : + Runtime.IsRef(parser) ? FromRef(parser) : Runtime.IsString(parser) ? FromString(parser) : - Runtime.IsConst(parser) ? FromConst(parser) : + Runtime.IsTuple(parser) ? FromTuple(parser) : + Runtime.IsUnion(parser) ? FromUnion(parser) : Runtime.IsUntil(parser) ? FromUntil(parser) : - Runtime.IsRef(parser) ? FromRef(parser) : - Runtime.IsIdent(parser) ? FromIdent(parser) : - Runtime.IsNumber(parser) ? FromNumber(parser) : + Runtime.IsUntilNonEmpty(parser) ? FromUntilNonEmpty(parser) : Unreachable(parser) ) } diff --git a/src/compile/common/infer.ts b/src/compile/common/infer.ts index 7b22e0c..0b06b09 100644 --- a/src/compile/common/infer.ts +++ b/src/compile/common/infer.ts @@ -32,40 +32,58 @@ THE SOFTWARE. import { Runtime } from '../../runtime/index.ts' import { Unreachable } from './unreachable.ts' -function InferUnion(parsers: Runtime.IParser[]): string { - return [...new Set(parsers.map((parser) => Infer(parser)))].join(' | ') -} -function InferTuple(parsers: Runtime.IParser[]): string { - return `[${parsers.map(() => 'unknown').join(', ')}]` -} function InferArray(parser: Runtime.IParser): string { return `(${Infer(parser)})[]` } function InferContext(left: Runtime.IParser, right: Runtime.IParser) { return Infer(right) } +function InferConst(parser: Runtime.IConst) { + return `'${parser.value}'` +} function InferOptional(parser: Runtime.IParser) { return `([${Infer(parser)}] | [])` } -function InferConst(parser: Runtime.IConst) { - return `'${parser.value}'` + + +function InferUnion(parsers: Runtime.IParser[]): string { + return [...new Set(parsers.map((parser) => Infer(parser)))].join(' | ') +} +function InferString(parser: Runtime.IString) { + return `string` +} +function InferRef(parser: Runtime.IRef) { + return `unknown` +} +function InferIdent(parser: Runtime.IIdent) { + return `string` +} +function InferNumber(parser: Runtime.INumber) { + return `string` +} +function InferTuple(parsers: Runtime.IParser[]): string { + return `[${parsers.map(() => 'unknown').join(', ')}]` } function InferUntil(parser: Runtime.IUntil) { return `string` } +function InferUntilNonEmpty(parser: Runtime.IUntilNonEmpty) { + return `string` +} export function Infer(parser: Runtime.IParser): string { return ( + Runtime.IsArray(parser) ? InferArray(parser.parser) : Runtime.IsContext(parser) ? InferContext(parser.right, parser.right) : + Runtime.IsConst(parser) ? InferConst(parser) : + Runtime.IsIdent(parser) ? InferIdent(parser) : + Runtime.IsNumber(parser) ? InferNumber(parser) : + Runtime.IsOptional(parser) ? InferOptional(parser.parser) : + Runtime.IsRef(parser) ? InferRef(parser) : + Runtime.IsString(parser) ? InferString(parser) : Runtime.IsTuple(parser) ? InferTuple(parser.parsers) : Runtime.IsUnion(parser) ? InferUnion(parser.parsers) : - Runtime.IsArray(parser) ? InferArray(parser.parser) : - 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` : + Runtime.IsUntilNonEmpty(parser) ? InferUntilNonEmpty(parser) : Unreachable(parser) ) } diff --git a/src/compile/func/func.ts b/src/compile/func/func.ts index 45fca2d..fb33b6c 100644 --- a/src/compile/func/func.ts +++ b/src/compile/func/func.ts @@ -44,20 +44,6 @@ function FromContext(options: Options, name: string, left: Runtime.IParser, righ return `If(${FromParser(options, name, left)}, ([_0, input]) => ${FromParser(options, name, right).replace('context', `_0 as ${options.contextType}`)}, () => [])` } // ------------------------------------------------------------------ -// Tuple -// ------------------------------------------------------------------ -function FromTuple(options: Options, name: string, parsers: Runtime.IParser[]): string { - const parameters = `[${parsers.map((_, index) => `_${index}`).join(', ')}]` - const initial = `[${parameters}, input]` - return parsers.reduceRight((result, right, index) => `If(${FromParser(options, name, right)}, ([_${index}, input]) => ${result})`, initial) -} -// ------------------------------------------------------------------ -// Union -// ------------------------------------------------------------------ -function FromUnion(options: Options, name: string, parsers: Runtime.IParser[]): string { - return parsers.length === 0 ? '[]' : parsers.reduceRight((result, right) => `If(${FromParser(options, name, right)}, ([_0, input]) => [_0, input], () => ${result})`, '[]') -} -// ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ function FromArrayReducer(options: Options, name: string, parser: Runtime.IParser): string { @@ -71,25 +57,12 @@ function FromArray(options: Options, name: string, parser: Runtime.IParser): str return `${reducer_name}(input, context)` } // ------------------------------------------------------------------ -// Optional -// ------------------------------------------------------------------ -function FromOptional(options: Options, name: string, parser: Runtime.IOptional): string { - return `If(${FromParser(options, name, parser.parser)}, ([_0, input]) => [[_0], input], () => [[], input])` -} -// ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ function FromConst(options: Options, name: string, value: string): string { return `Runtime.Token.Const('${Escape(value)}', input)` } // ------------------------------------------------------------------ -// Const -// ------------------------------------------------------------------ -function FromUntil(options: Options, name: string, values: string[]): string { - const escaped = values.map(value => `'${Escape(value)}'`) - return `Runtime.Token.Until([${escaped.join(', ')}], input)` -} -// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ function FromIdent(options: Options, name: string): string { @@ -102,6 +75,18 @@ function FromNumber(options: Options, name: string): string { return `Runtime.Token.Number(input)` } // ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +function FromOptional(options: Options, name: string, parser: Runtime.IOptional): string { + return `If(${FromParser(options, name, parser.parser)}, ([_0, input]) => [[_0], input], () => [[], input])` +} +// ------------------------------------------------------------------ +// Ref +// ------------------------------------------------------------------ +function FromRef(options: Options, name: string, ref: string): string { + return `${ref}(input, context)` +} +// ------------------------------------------------------------------ // String // ------------------------------------------------------------------ function FromString(options: Options, name: string, string_options: string[]): string { @@ -109,27 +94,50 @@ function FromString(options: Options, name: string, string_options: string[]): s return `Runtime.Token.String([${_options}], input)` } // ------------------------------------------------------------------ -// Ref +// Tuple // ------------------------------------------------------------------ -function FromRef(options: Options, name: string, ref: string): string { - return `${ref}(input, context)` +function FromTuple(options: Options, name: string, parsers: Runtime.IParser[]): string { + const parameters = `[${parsers.map((_, index) => `_${index}`).join(', ')}]` + const initial = `[${parameters}, input]` + return parsers.reduceRight((result, right, index) => `If(${FromParser(options, name, right)}, ([_${index}, input]) => ${result})`, initial) } // ------------------------------------------------------------------ -// Ref +// Union +// ------------------------------------------------------------------ +function FromUnion(options: Options, name: string, parsers: Runtime.IParser[]): string { + return parsers.length === 0 ? '[]' : parsers.reduceRight((result, right) => `If(${FromParser(options, name, right)}, ([_0, input]) => [_0, input], () => ${result})`, '[]') +} +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function FromUntil(options: Options, name: string, values: string[]): string { + const escaped = values.map(value => `'${Escape(value)}'`) + return `Runtime.Token.Until([${escaped.join(', ')}], input)` +} +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +function FromUntilNonEmpty(options: Options, name: string, values: string[]): string { + const escaped = values.map(value => `'${Escape(value)}'`) + return `Runtime.Token.UntilNonEmpty([${escaped.join(', ')}], input)` +} +// ------------------------------------------------------------------ +// Parser // ------------------------------------------------------------------ function FromParser(options: Options, name: string, parser: Runtime.IParser): string { return ( - Runtime.IsContext(parser) ? FromContext(options, name, parser.left, parser.right) : - Runtime.IsTuple(parser) ? FromTuple(options, name, parser.parsers) : - Runtime.IsUnion(parser) ? FromUnion(options, name, parser.parsers) : Runtime.IsArray(parser) ? FromArray(options, name, parser.parser) : - Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : - Runtime.IsString(parser) ? FromString(options, name, parser.options) : + Runtime.IsContext(parser) ? FromContext(options, name, parser.left, parser.right) : Runtime.IsConst(parser) ? FromConst(options, name, parser.value) : - Runtime.IsUntil(parser) ? FromUntil(options, name, parser.values) : - Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : Runtime.IsIdent(parser) ? FromIdent(options, name) : Runtime.IsNumber(parser) ? FromNumber(options, name) : + Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : + Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : + Runtime.IsString(parser) ? FromString(options, name, parser.options) : + Runtime.IsTuple(parser) ? FromTuple(options, name, parser.parsers) : + Runtime.IsUnion(parser) ? FromUnion(options, name, parser.parsers) : + Runtime.IsUntil(parser) ? FromUntil(options, name, parser.values) : + Runtime.IsUntilNonEmpty(parser) ? FromUntilNonEmpty(options, name, parser.values) : Unreachable(parser) ) } diff --git a/src/compile/type/type.ts b/src/compile/type/type.ts index 7aa4046..2d11652 100644 --- a/src/compile/type/type.ts +++ b/src/compile/type/type.ts @@ -45,20 +45,6 @@ const state = { reducers: new Set() } function FromContext(options: Options, name: string, left: Runtime.IParser, right: Runtime.IParser): string { return `${FromParser(options, name, left)} extends [infer _0 extends ${options.contextType}, infer Input extends string] ? ${FromParser(options, name, right).replace('Context', '_0')} : []` } -// type C = A extends [infer _0, infer Input extends string] ? B : [] -// ------------------------------------------------------------------ -// Tuple -// ------------------------------------------------------------------ -function FromTuple(options: Options, name: string, parsers: Runtime.IParser[]): string { - const initial = `[[${parsers.map((_, index) => `_${index}`).join(', ')}], Input]` - return parsers.reduceRight((result, right, index) => `(${FromParser(options, name, right)} extends [infer _${index}, infer Input extends string] ? ${result} : [])`, initial) -} -// ------------------------------------------------------------------ -// Union -// ------------------------------------------------------------------ -function FromUnion(options: Options, name: string, parsers: Runtime.IParser[]): string { - return parsers.length === 0 ? '[]' : `(${parsers.reduceRight((result, right) => `${FromParser(options, name, right)} extends [infer _0, infer Input extends string] ? [_0, Input] : ${result}`, '[]')})` -} // ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ @@ -73,25 +59,12 @@ function FromArray(options: Options, name: string, parser: Runtime.IParser): str return `${reducer_name}` } // ------------------------------------------------------------------ -// Optional -// ------------------------------------------------------------------ -function FromOptional(options: Options, name: string, parser: Runtime.IOptional): string { - return `(${FromParser(options, name, parser.parser)} extends [infer _0, infer Input extends string] ? [[_0], Input]: [[], Input])` -} -// ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ function FromConst(options: Options, name: string, value: string): string { return `Static.Token.Const<'${Escape(value)}', Input>` } // ------------------------------------------------------------------ -// Until -// ------------------------------------------------------------------ -function FromUntil(options: Options, name: string, values: string[]): string { - const escaped = values.map(value => `'${Escape(value)}'`) - return `Static.Token.Until<[${escaped.join(', ')}], Input>` -} -// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ function FromIdent(options: Options, name: string): string { @@ -104,6 +77,18 @@ function FromNumber(options: Options, name: string): string { return `Static.Token.Number` } // ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +function FromOptional(options: Options, name: string, parser: Runtime.IOptional): string { + return `(${FromParser(options, name, parser.parser)} extends [infer _0, infer Input extends string] ? [[_0], Input]: [[], Input])` +} +// ------------------------------------------------------------------ +// Ref +// ------------------------------------------------------------------ +function FromRef(options: Options, name: string, ref: string): string { + return `${Name(ref)}` +} +// ------------------------------------------------------------------ // String // ------------------------------------------------------------------ function FromString(options: Options, name: string, string_options: string[]): string { @@ -111,27 +96,49 @@ function FromString(options: Options, name: string, string_options: string[]): s return `Static.Token.String<[${_options}], Input>` } // ------------------------------------------------------------------ -// Ref +// Tuple // ------------------------------------------------------------------ -function FromRef(options: Options, name: string, ref: string): string { - return `${Name(ref)}` +function FromTuple(options: Options, name: string, parsers: Runtime.IParser[]): string { + const initial = `[[${parsers.map((_, index) => `_${index}`).join(', ')}], Input]` + return parsers.reduceRight((result, right, index) => `(${FromParser(options, name, right)} extends [infer _${index}, infer Input extends string] ? ${result} : [])`, initial) +} +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +function FromUnion(options: Options, name: string, parsers: Runtime.IParser[]): string { + return parsers.length === 0 ? '[]' : `(${parsers.reduceRight((result, right) => `${FromParser(options, name, right)} extends [infer _0, infer Input extends string] ? [_0, Input] : ${result}`, '[]')})` +} +// ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function FromUntil(options: Options, name: string, values: string[]): string { + const escaped = values.map(value => `'${Escape(value)}'`) + return `Static.Token.Until<[${escaped.join(', ')}], Input>` +} +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +function FromUntilNonEmpty(options: Options, name: string, values: string[]): string { + const escaped = values.map(value => `'${Escape(value)}'`) + return `Static.Token.UntilNonEmpty<[${escaped.join(', ')}], Input>` } // ------------------------------------------------------------------ // Parser // ------------------------------------------------------------------ function FromParser(options: Options, name: string, parser: Runtime.IParser): string { return ( - Runtime.IsContext(parser) ? FromContext(options, name, parser.left, parser.right) : - Runtime.IsTuple(parser) ? FromTuple(options, name, parser.parsers) : - Runtime.IsUnion(parser) ? FromUnion(options, name, parser.parsers) : Runtime.IsArray(parser) ? FromArray(options, name, parser.parser) : - Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : - Runtime.IsString(parser) ? FromString(options, name, parser.options) : + Runtime.IsContext(parser) ? FromContext(options, name, parser.left, parser.right) : Runtime.IsConst(parser) ? FromConst(options, name, parser.value) : - Runtime.IsUntil(parser) ? FromUntil(options, name, parser.values) : - Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : Runtime.IsIdent(parser) ? FromIdent(options, name) : Runtime.IsNumber(parser) ? FromNumber(options, name) : + Runtime.IsOptional(parser) ? FromOptional(options, name, parser) : + Runtime.IsRef(parser) ? FromRef(options, name, parser.ref) : + Runtime.IsString(parser) ? FromString(options, name, parser.options) : + Runtime.IsTuple(parser) ? FromTuple(options, name, parser.parsers) : + Runtime.IsUnion(parser) ? FromUnion(options, name, parser.parsers) : + Runtime.IsUntil(parser) ? FromUntil(options, name, parser.values) : + Runtime.IsUntilNonEmpty(parser) ? FromUntilNonEmpty(options, name, parser.values) : Unreachable(parser) ) } diff --git a/src/runtime/parse.ts b/src/runtime/parse.ts index cfc2c67..71f8637 100644 --- a/src/runtime/parse.ts +++ b/src/runtime/parse.ts @@ -129,6 +129,12 @@ function ParseUntil(values: [...Values], code: string, return Token.Until(values, code) as never } // ------------------------------------------------------------------ +// Until +// ------------------------------------------------------------------ +function ParseUntilNonEmpty(values: [...Values], code: string, context: unknown): [] | [string, string] { + return Token.UntilNonEmpty(values, code) as never +} +// ------------------------------------------------------------------ // Parser // ------------------------------------------------------------------ function ParseParser(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser, string] { @@ -144,6 +150,7 @@ function ParseParser(moduleProperties: Types.IModu Types.IsTuple(parser) ? ParseTuple(moduleProperties, parser.parsers, code, context) : Types.IsUnion(parser) ? ParseUnion(moduleProperties, parser.parsers, code, context) : Types.IsUntil(parser) ? ParseUntil(parser.values, code, context) : + Types.IsUntilNonEmpty(parser) ? ParseUntilNonEmpty(parser.values, code, context) : [] ) return (result.length === 2 ? [parser.mapping(result[0], context), result[1]] : result) as never diff --git a/src/runtime/token.ts b/src/runtime/token.ts index ae29a6b..07626a0 100644 --- a/src/runtime/token.ts +++ b/src/runtime/token.ts @@ -251,4 +251,15 @@ export function Until(value: string[], input: string, result: string = ''): [] | return Until(value, right, `${result}${left}`) })() ) +} +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +export function UntilNonEmpty(value: string[], input: string): [] | [string, string] { + const result = Until(value, input) + return ( + result.length === 2 + ? result[0].length === 0 ? []: result + : [] + ) } \ No newline at end of file diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 8b67023..241f54f 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -326,11 +326,11 @@ export interface IUntil extends IParser>(values: string[], mapping: Mapping): IUntil -/** `[TERM]` Creates a Until Parser */ +/** Creates a Until Parser */ export function Until(values: string[]): IUntil -/** `[TERM]` Creates a Until Parser */ +/** Creates a Until Parser */ export function Until(...args: unknown[]): never { const [values, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Until', values, mapping } as never @@ -342,4 +342,28 @@ export function IsUntil(value: unknown): value is IUntil { && Guard.HasPropertyKey(value, 'values') && Guard.IsEqual(value.type, 'Until') && Guard.IsArray(value.values) +} +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +export interface IUntilNonEmpty extends IParser { + type: 'UntilNonEmpty' + values: string[] +} +/** Creates a UntilNonEmpty Parser */ +export function UntilNonEmpty>(values: string[], mapping: Mapping): IUntilNonEmpty +/** Creates a UntilNonEmpty Parser */ +export function UntilNonEmpty(values: string[]): IUntilNonEmpty +/** Creates a UntilNonEmpty Parser */ +export function UntilNonEmpty(...args: unknown[]): never { + const [values, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Until1', values, mapping } as never +} +/** Returns true if the value is a UntilNonEmpty Parser */ +export function IsUntilNonEmpty(value: unknown): value is IUntilNonEmpty { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.HasPropertyKey(value, 'values') + && Guard.IsEqual(value.type, 'UntilNonEmpty') + && Guard.IsArray(value.values) } \ No newline at end of file diff --git a/src/static/parse.ts b/src/static/parse.ts index 494941a..93e56c9 100644 --- a/src/static/parse.ts +++ b/src/static/parse.ts @@ -116,6 +116,14 @@ type UntilParser = ( + Tokens.UntilNonEmpty extends [infer Match extends string, infer Rest extends string] + ? [Match, Rest] + : [] +) +// ------------------------------------------------------------------ // Parse // ------------------------------------------------------------------ type ParseCode = ( @@ -129,6 +137,7 @@ type ParseCode ? TupleParser : Parser extends Types.Union ? UnionParser : Parser extends Types.Until ? UntilParser : + Parser extends Types.UntilNonEmpty ? UntilNonEmptyParser : [] ) type ParseMapping = ( diff --git a/src/static/token.ts b/src/static/token.ts index 443ce18..281ed8f 100644 --- a/src/static/token.ts +++ b/src/static/token.ts @@ -218,4 +218,12 @@ export type Until : never +) +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +export type UntilNonEmpty = ( + Until extends [infer Left extends string, infer Right extends string] + ? Left extends '' ? [] : [Left, Right] + : [] ) \ No newline at end of file diff --git a/src/static/types.ts b/src/static/types.ts index fea46f7..0bfb112 100644 --- a/src/static/types.ts +++ b/src/static/types.ts @@ -85,14 +85,6 @@ export interface Const extends IParser { - type: 'Until' - values: Values -} -// ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ /** Creates an Ident Parser */ @@ -138,3 +130,19 @@ export interface Union extends IParser { + type: 'Until' + values: Values +} +// ------------------------------------------------------------------ +// UntilNonEmpty +// ------------------------------------------------------------------ +/** Creates a UntilNonEmpty Parser */ +export interface UntilNonEmpty extends IParser { + type: 'UntilNonEmpty' + values: Values +} \ No newline at end of file diff --git a/tasks.ts b/tasks.ts index 77f1ff7..cbd9f79 100644 --- a/tasks.ts +++ b/tasks.ts @@ -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.6', + version: '0.9.7', keywords: ['typescript', 'parser', 'combinator'], license: 'MIT', author: 'sinclairzx81', diff --git a/test/__tests__/runtime/token.ts b/test/__tests__/runtime/token.ts index e4c8560..8707779 100644 --- a/test/__tests__/runtime/token.ts +++ b/test/__tests__/runtime/token.ts @@ -192,4 +192,115 @@ Deno.test('Until: Multi Sentinal Test', () => { Assert(Runtime.Token.Until(['B', 'A'], ' BA'), [' ', 'BA']) Assert(Runtime.Token.Until(['B', ' A'], ' BA'), [' ', 'BA']) Assert(Runtime.Token.Until([' B', 'A'], ' BA'), [' ', ' BA']) +}) + +Deno.test('UntilNonEmpty: Empty', () => { + Assert(Runtime.Token.UntilNonEmpty([''], ''), []) + Assert(Runtime.Token.UntilNonEmpty([''], 'A'), []) + Assert(Runtime.Token.UntilNonEmpty([''], ' A'), []) +}) + +Deno.test('UntilNonEmpty: Single-Char', () => { + Assert(Runtime.Token.UntilNonEmpty(['A'], 'A'), []) + Assert(Runtime.Token.UntilNonEmpty(['A'], 'A '), []) + Assert(Runtime.Token.UntilNonEmpty(['A'], 'AA'), []) + Assert(Runtime.Token.UntilNonEmpty(['A'], 'AA '), []) +}) + +Deno.test('UntilNonEmpty: Multi-Char', () => { + Assert(Runtime.Token.UntilNonEmpty(['AB'], 'AB'), []) + Assert(Runtime.Token.UntilNonEmpty(['AB'], 'AB '), []) + Assert(Runtime.Token.UntilNonEmpty(['AB'], 'ABA'), []) + Assert(Runtime.Token.UntilNonEmpty(['AB'], 'ABA '), []) +}) + +Deno.test('UntilNonEmpty: Single-Char -> Ignore-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty(['A'], ' A'), [' ', 'A']) + Assert(Runtime.Token.UntilNonEmpty(['A'], ' A '), [' ', 'A ']) + Assert(Runtime.Token.UntilNonEmpty(['A'], ' AA'), [' ', 'AA']) + Assert(Runtime.Token.UntilNonEmpty(['A'], ' AA '), [' ', 'AA ']) + Assert(Runtime.Token.UntilNonEmpty(['A'], '\nAA '), ['\n', 'AA ']) + Assert(Runtime.Token.UntilNonEmpty(['A'], ' \nAA '), [' \n', 'AA ']) + Assert(Runtime.Token.UntilNonEmpty(['A'], '\n AA '), ['\n ', 'AA ']) + Assert(Runtime.Token.UntilNonEmpty(['A'], ' \n AA '), [' \n ', 'AA ']) +}) + +Deno.test('UntilNonEmpty: Multi-Char -> Ignore-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' AB'), [' ', 'AB']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' AB '), [' ', 'AB ']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' ABA'), [' ', 'ABA']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' ABA '), [' ', 'ABA ']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], '\nABA '), ['\n', 'ABA ']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' \nABA '), [' \n', 'ABA ']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], '\n ABA '), ['\n ', 'ABA ']) + Assert(Runtime.Token.UntilNonEmpty(['AB'], ' \n ABA '), [' \n ', 'ABA ']) +}) + +Deno.test('UntilNonEmpty: Single-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty([' '], ''), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' '), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' A'), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' A '), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' AA'), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' AA '), []) +}) + +Deno.test('UntilNonEmpty: Multi-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty([' '], ''), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' '), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' A'), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' A '), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' AA'), []) + Assert(Runtime.Token.UntilNonEmpty([' '], ' AA '), []) +}) + +Deno.test('UntilNonEmpty: Newline', () => { + Assert(Runtime.Token.UntilNonEmpty(['\n'], ''), []) + Assert(Runtime.Token.UntilNonEmpty(['\n'], ' '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n'], '\nA'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n'], ' \nA '), [' ', '\nA ']) + Assert(Runtime.Token.UntilNonEmpty(['\n'], ' \nAA'), [' ', '\nAA']) + Assert(Runtime.Token.UntilNonEmpty(['\n'], ' \nAA '), [' ', '\nAA ']) +}) + +Deno.test('UntilNonEmpty: Newline-Single-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty(['\n '], ''), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], '\nA'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nA '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nAA'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nAA '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], '\n A'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n A '), [' ', '\n A ']) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n AA'), [' ', '\n AA']) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n AA '), [' ', '\n AA ']) +}) + +Deno.test('UntilNonEmpty: Newline-Multi-Whitespace', () => { + Assert(Runtime.Token.UntilNonEmpty(['\n '], ''), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], '\nA'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nA '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nAA'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \nAA '), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], '\n A'), []) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n A '), [' ', '\n A ']) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n AA'), [' ', '\n AA']) + Assert(Runtime.Token.UntilNonEmpty(['\n '], ' \n AA '), [' ', '\n AA ']) +}) + +Deno.test('UntilNonEmpty: Multi Sentinal Test', () => { + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], ''), []) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], 'A'), []) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], 'B'), []) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], 'AB'), []) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], 'BA'), []) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], ' AB'), [' ', 'AB']) + Assert(Runtime.Token.UntilNonEmpty(['A', 'B'], ' BA'), [' ', 'BA']) + Assert(Runtime.Token.UntilNonEmpty(['A', ' B'], ' BA'), [' ', ' BA']) + Assert(Runtime.Token.UntilNonEmpty([' A', 'B'], ' BA'), [' ', 'BA']) + Assert(Runtime.Token.UntilNonEmpty(['B', 'A'], ' AB'), [' ', 'AB']) + Assert(Runtime.Token.UntilNonEmpty(['B', 'A'], ' BA'), [' ', 'BA']) + Assert(Runtime.Token.UntilNonEmpty(['B', ' A'], ' BA'), [' ', 'BA']) + Assert(Runtime.Token.UntilNonEmpty([' B', 'A'], ' BA'), [' ', ' BA']) }) \ No newline at end of file diff --git a/test/__tests__/static/token.ts b/test/__tests__/static/token.ts index b99ef42..b4aff90 100644 --- a/test/__tests__/static/token.ts +++ b/test/__tests__/static/token.ts @@ -229,3 +229,126 @@ Assert, [' ', 'BA']> Assert, [' ', 'BA']> Assert, [' ', ' BA']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Empty +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Single-Char +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Multi-Char +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Single-Char -> Ignore-Whitespace +// ------------------------------------------------------------------ +Assert, [' ', 'A']> +Assert, [' ', 'A ']> +Assert, [' ', 'AA']> +Assert, [' ', 'AA ']> +Assert, ['\n', 'AA ']> +Assert, [' \n', 'AA ']> +Assert, ['\n ', 'AA ']> +Assert, [' \n ', 'AA ']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Multi-Char -> Ignore-Whitespace +// ------------------------------------------------------------------ +Assert, [' ', 'AB']> +Assert, [' ', 'AB ']> +Assert, [' ', 'ABA']> +Assert, [' ', 'ABA ']> +Assert, ['\n', 'ABA ']> +Assert, [' \n', 'ABA ']> +Assert, ['\n ', 'ABA ']> +Assert, [' \n ', 'ABA ']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Single-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Multi-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Newline +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, [' ', '\nA ']> +Assert, [' ', '\nAA']> +Assert, [' ', '\nAA ']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Newline-Single-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, [' ', '\n A ']> +Assert, [' ', '\n AA']> +Assert, [' ', '\n AA ']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Newline-Multi-Whitespace +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, [' ', '\n A ']> +Assert, [' ', '\n AA']> +Assert, [' ', '\n AA ']> + +// ------------------------------------------------------------------ +// UntilNonEmpty: Multi Sentinal Test +// ------------------------------------------------------------------ +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, []> +Assert, [' ', 'AB']> +Assert, [' ', 'BA']> +Assert, [' ', ' BA']> +Assert, [' ', 'BA']> +Assert, [' ', 'AB']> +Assert, [' ', 'BA']> +Assert, [' ', 'BA']> +Assert, [' ', ' BA']> +