diff --git a/example/wasm/wasm.ts b/example/wasm/wasm.ts new file mode 100644 index 0000000..e2e7ad8 --- /dev/null +++ b/example/wasm/wasm.ts @@ -0,0 +1,225 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 function test(math: Math) { + const a = math.$add(1, 2) + const b = math.$sub(1, 2) + const c = math.$mul(1, 2) + const d = math.$div(1, 2) + + const expr = math.$add( // (1 - 2) + (3 + ((4 * 5) / 6)) + math.$sub(1, 2), + math.$add(3, + math.$div( + math.$mul(4, 5), + 6 + ) + ) + ) +} + +type Math = Wat<`(module + (func $add (param $x f32) (param $y f32) (result f32) + local.get $x + local.get $y + f32.add + ) + (func $sub (param $x f32) (param $y f32) (result f32) + local.get $x + local.get $y + f32.sub + ) + (func $mul (param $x f32) (param $y f32) (result f32) + local.get $x + local.get $y + f32.mul + ) + (func $div (param $x f32) (param $y f32) (result f32) + local.get $x + local.get $y + f32.div + ) +)`> + +import { Static } from '@sinclair/parsebox' + +// --------------------------------------------------- +// Wat: Parse +// --------------------------------------------------- +export type Wat[0], + Ast extends TModule = Parsed extends TModule ? Parsed : TModule<[]>, + Exports extends Record = GetExports +> = Exports + +// --------------------------------------------------- +// Query: Exports +// --------------------------------------------------- +type GetExportsParams = ( + Params extends [infer _ extends TParam, ...infer Right extends TParam[]] + ? GetExportsParams // i32, i64, f32, f64 + : Result +) +type GetExportsFunc, + Return extends unknown = number, // single return type + Result extends Record = { + [K in Func['name']]: (...args: [...Params]) => Return + } +> = Result +type GetExportsQuery = {}> = ( + Funcs extends [infer Left extends TFunc, ...infer Right extends TFunc[]] + ? GetExportsQuery> + : {[K in keyof Result]: Result[K]} +) +type GetExports = GetExportsQuery + +// --------------------------------------------------- +// NativeType +// --------------------------------------------------- +type NativeType = Static.Union<[ + Static.Const<'i32'>, + Static.Const<'i64'>, + Static.Const<'f32'>, + Static.Const<'i64'> +]> +// --------------------------------------------------- +// Param +// --------------------------------------------------- +interface TParam { + kind: 'Param' + name: Name + type: Type +} +interface ParamMapping extends Static.IMapping { + output: this['input'] extends ['(', 'param', infer Name extends string, infer Type extends string, ')'] + ? TParam + : never +} +type Param = Static.Tuple<[ + Static.Const<'('>, + Static.Const<'param'>, + Static.Ident, + NativeType, + Static.Const<')'> +], ParamMapping> +type Params = Static.Array +// --------------------------------------------------- +// Result +// --------------------------------------------------- +interface TResult { + kind: 'Result' + type: Type +} +interface ResultMapping extends Static.IMapping { + output: this['input'] extends ['(', 'result', infer Type extends string, ')'] + ? TResult + : never +} +type Result = Static.Tuple<[ + Static.Const<'('>, + Static.Const<'result'>, + NativeType, + Static.Const<')'>, +], ResultMapping> +// --------------------------------------------------- +// Instruction +// --------------------------------------------------- +type Oprand = Static.Union<[ + Static.Ident, + Static.Number +]> +interface TInstruction< + Namespace extends string = string, + Operator extends string = string, + Operand extends string[] = string[] +> { + kind: 'Instruction' + namespace: Namespace + operator: Operator + operand: Operand +} +interface InstructionMapping extends Static.IMapping { + output: this['input'] extends [infer Namespace extends string, '.', infer Operator extends string, infer Operand extends string[]] + ? TInstruction + : never +} +type Instruction = Static.Tuple<[ + Static.Union<[NativeType, Static.Const<'local'>]>, + Static.Const<'.'>, + Static.Ident, + Static.Optional +], InstructionMapping> +type Instructions = Static.Array +// --------------------------------------------------- +// Func +// --------------------------------------------------- +interface TFunc< + Name extends string = string, + Params extends TParam[] = TParam[], + Result extends TResult = TResult, + Instructions extends TInstruction[] = TInstruction[]> { + kind: 'Func' + name: Name + params: Params + result: Result + instructions: Instructions +} +interface FuncMapping extends Static.IMapping { + output: this['input'] extends ['(', 'func', infer Name extends string, infer Params extends TParam[], infer Result extends TResult, infer Instructions extends TInstruction[], ')'] + ? TFunc + : never +} +type Func = Static.Tuple<[ + Static.Const<'('>, + Static.Const<'func'>, + Static.Ident, + Params, + Result, + Instructions, + Static.Const<')'> +], FuncMapping> +type Funcs = Static.Array +// --------------------------------------------------- +// Module +// --------------------------------------------------- +interface TModule { + kind: 'Module' + funcs: Funcs +} +interface ModuleMapping extends Static.IMapping { + output: this['input'] extends ['(', 'module', infer Funcs extends TFunc[], ')'] + ? TModule + : never +} +type Module = Static.Tuple<[ + Static.Const<'('>, + Static.Const<'module'>, + Funcs, + Static.Const<')'> +], ModuleMapping> \ No newline at end of file diff --git a/src/build/common/comment.ts b/src/build/common/comment.ts index f1ab3dc..2046216 100644 --- a/src/build/common/comment.ts +++ b/src/build/common/comment.ts @@ -68,6 +68,12 @@ function FromTuple(parser: Runtime.ITuple): string { function FromUnion(parser: Runtime.IUnion): string { return parser.parsers.map((parser) => `${FromParser(parser)}`).join(' | ') } +function FromUnsignedInteger(parser: Runtime.IUnsignedInteger): string { + return `` +} +function FromUnsignedNumber(parser: Runtime.IUnsignedNumber): string { + return `` +} function FromUntil_1(parser: Runtime.IUntil_1): string { return `string` } @@ -79,8 +85,8 @@ function FromParser(parser: Runtime.IParser): string { Runtime.IsArray(parser) ? FromArray(parser) : Runtime.IsBigInt(parser) ? FromBigInt(parser) : Runtime.IsConst(parser) ? FromConst(parser) : - Runtime.IsInteger(parser) ? FromInteger(parser) : Runtime.IsIdent(parser) ? FromIdent(parser) : + Runtime.IsInteger(parser) ? FromInteger(parser) : Runtime.IsNumber(parser) ? FromNumber(parser) : Runtime.IsOptional(parser) ? FromOptional(parser) : Runtime.IsRef(parser) ? FromRef(parser) : @@ -88,6 +94,8 @@ function FromParser(parser: Runtime.IParser): string { Runtime.IsString(parser) ? FromString(parser) : Runtime.IsTuple(parser) ? FromTuple(parser) : Runtime.IsUnion(parser) ? FromUnion(parser) : + Runtime.IsUnsignedInteger(parser) ? FromUnsignedInteger(parser) : + Runtime.IsUnsignedNumber(parser) ? FromUnsignedNumber(parser) : Runtime.IsUntil_1(parser) ? FromUntil_1(parser) : Runtime.IsUntil(parser) ? FromUntil(parser) : Unreachable(parser) diff --git a/src/build/common/infer.ts b/src/build/common/infer.ts index e85a0d1..6ac8322 100644 --- a/src/build/common/infer.ts +++ b/src/build/common/infer.ts @@ -47,12 +47,12 @@ function InferIdent(parser: Runtime.IIdent) { function InferInteger(parser: Runtime.IInteger) { return `string` } +function InferNumber(parser: Runtime.INumber) { + return `string` +} function InferOptional(parser: Runtime.IParser) { return `([${Infer(parser)}] | [])` } -function InferUnion(parsers: Runtime.IParser[]): string { - return [...new Set(parsers.map((parser) => Infer(parser)))].join(' | ') -} function InferString(parser: Runtime.IString) { return `string` } @@ -62,12 +62,18 @@ function InferRef(parser: Runtime.IRef) { function InferRest(parser: Runtime.IRest) { return `string` } -function InferNumber(parser: Runtime.INumber) { - return `string` -} function InferTuple(parsers: Runtime.IParser[]): string { return `[${parsers.map(() => 'unknown').join(', ')}]` } +function InferUnion(parsers: Runtime.IParser[]): string { + return [...new Set(parsers.map((parser) => Infer(parser)))].join(' | ') +} +function InferUnsignedInteger(parser: Runtime.IUnsignedInteger) { + return `number` +} +function InferUnsignedNumber(parser: Runtime.IUnsignedNumber) { + return `number` +} function InferUntil_1(parser: Runtime.IUntil_1) { return `string` } @@ -79,8 +85,8 @@ export function Infer(parser: Runtime.IParser): string { Runtime.IsArray(parser) ? InferArray(parser.parser) : Runtime.IsBigInt(parser) ? InferBigInt(parser) : Runtime.IsConst(parser) ? InferConst(parser) : - Runtime.IsInteger(parser) ? InferInteger(parser) : Runtime.IsIdent(parser) ? InferIdent(parser) : + Runtime.IsInteger(parser) ? InferInteger(parser) : Runtime.IsNumber(parser) ? InferNumber(parser) : Runtime.IsOptional(parser) ? InferOptional(parser.parser) : Runtime.IsRef(parser) ? InferRef(parser) : @@ -88,6 +94,8 @@ export function Infer(parser: Runtime.IParser): string { Runtime.IsString(parser) ? InferString(parser) : Runtime.IsTuple(parser) ? InferTuple(parser.parsers) : Runtime.IsUnion(parser) ? InferUnion(parser.parsers) : + Runtime.IsUnsignedInteger(parser) ? InferUnsignedInteger(parser) : + Runtime.IsUnsignedNumber(parser) ? InferUnsignedNumber(parser) : Runtime.IsUntil_1(parser) ? InferUntil_1(parser) : Runtime.IsUntil(parser) ? InferUntil(parser) : Unreachable(parser) diff --git a/src/build/runtime/parse.ts b/src/build/runtime/parse.ts index 8335251..73f537e 100644 --- a/src/build/runtime/parse.ts +++ b/src/build/runtime/parse.ts @@ -122,6 +122,18 @@ function FromUnion(name: string, parsers: Runtime.IParser[]): string { return parsers.length === 0 ? '[]' : parsers.reduceRight((result, right) => `If(${FromParser(name, right)}, ([_0, input]) => [_0, input], () => ${result})`, '[]') } // ------------------------------------------------------------------ +// UnsignedInteger +// ------------------------------------------------------------------ +function FromUnsignedInteger(name: string): string { + return `Token.UnsignedInteger(input)` +} +// ------------------------------------------------------------------ +// UnsignedNumber +// ------------------------------------------------------------------ +function FromUnsignedNumber(name: string): string { + return `Token.UnsignedNumber(input)` +} +// ------------------------------------------------------------------ // Until_1 // ------------------------------------------------------------------ function FromUntil_1(name: string, end: string[]): string { @@ -152,6 +164,8 @@ function FromParser(name: string, parser: Runtime.IParser): string { Runtime.IsString(parser) ? FromString(name, parser.quotes) : Runtime.IsTuple(parser) ? FromTuple(name, parser.parsers) : Runtime.IsUnion(parser) ? FromUnion(name, parser.parsers) : + Runtime.IsUnsignedInteger(parser) ? FromUnsignedInteger(name) : + Runtime.IsUnsignedNumber(parser) ? FromUnsignedNumber(name) : Runtime.IsUntil_1(parser) ? FromUntil_1(name, parser.end) : Runtime.IsUntil(parser) ? FromUntil(name, parser.end) : Unreachable(parser) diff --git a/src/build/static/parse.ts b/src/build/static/parse.ts index ce72db9..14409fe 100644 --- a/src/build/static/parse.ts +++ b/src/build/static/parse.ts @@ -121,6 +121,18 @@ function FromUnion(name: string, parsers: Runtime.IParser[]): string { return parsers.length === 0 ? '[]' : `(${parsers.reduceRight((result, right) => `${FromParser(name, right)} extends [infer _0, infer Input extends string] ? [_0, Input] : ${result}`, '[]')})` } // ------------------------------------------------------------------ +// UnsignedInteger +// ------------------------------------------------------------------ +function FromUnsignedInteger(name: string): string { + return `Token.TUnsignedInteger` +} +// ------------------------------------------------------------------ +// UnsignedNumber +// ------------------------------------------------------------------ +function FromUnsignedNumber(name: string): string { + return `Token.TUnsignedNumber` +} +// ------------------------------------------------------------------ // Until_1 // ------------------------------------------------------------------ function FromUntil_1(name: string, end: string[]): string { @@ -142,8 +154,8 @@ function FromParser(name: string, parser: Runtime.IParser): string { Runtime.IsArray(parser) ? FromArray(name, parser.parser) : Runtime.IsBigInt(parser) ? FromBigInt(name) : Runtime.IsConst(parser) ? FromConst(name, parser.const) : - Runtime.IsInteger(parser) ? FromInteger(name) : Runtime.IsIdent(parser) ? FromIdent(name) : + Runtime.IsInteger(parser) ? FromInteger(name) : Runtime.IsNumber(parser) ? FromNumber(name) : Runtime.IsOptional(parser) ? FromOptional(name, parser) : Runtime.IsRef(parser) ? FromRef(name, parser.ref) : @@ -151,6 +163,8 @@ function FromParser(name: string, parser: Runtime.IParser): string { Runtime.IsString(parser) ? FromString(name, parser.quotes) : Runtime.IsTuple(parser) ? FromTuple(name, parser.parsers) : Runtime.IsUnion(parser) ? FromUnion(name, parser.parsers) : + Runtime.IsUnsignedInteger(parser) ? FromUnsignedInteger(name) : + Runtime.IsUnsignedNumber(parser) ? FromUnsignedNumber(name) : Runtime.IsUntil_1(parser) ? FromUntil_1(name, parser.end) : Runtime.IsUntil(parser) ? FromUntil(name, parser.end) : Unreachable(parser) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 9530328..df6a748 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -37,11 +37,13 @@ export { Module } from './module.ts' export { type INumber, IsNumber, Number } from './number.ts' export { type IOptional, IsOptional, Optional } from './optional.ts' export { Parse } from './parse.ts' -export { type IParser, type IProperties, type IMapping, As, Identity } from './parser.ts' +export { type IParser, type IProperties, type IMapping, Identity } from './parser.ts' export { type IRef, Ref, IsRef, } from './ref.ts' export { type IRest, Rest, IsRest, } from './rest.ts' export { type IString, IsString, String } from './string.ts' export { type ITuple, IsTuple, Tuple } from './tuple.ts' export { type IUnion, IsUnion, Union } from './union.ts' +export { type IUnsignedInteger, IsUnsignedInteger, UnsignedInteger } from './unsigned_integer.ts' +export { type IUnsignedNumber, IsUnsignedNumber, UnsignedNumber } from './unsigned_number.ts' export { type IUntil_1, IsUntil_1, Until_1 } from './until_1.ts' export { type IUntil, IsUntil, Until } from './until.ts' diff --git a/src/runtime/parse.ts b/src/runtime/parse.ts index 3b0c22e..14fc5f1 100644 --- a/src/runtime/parse.ts +++ b/src/runtime/parse.ts @@ -46,6 +46,8 @@ import { ParseRest, IsRest } from './rest.ts' import { ParseString, IsString } from './string.ts' import { ParseTuple, IsTuple } from './tuple.ts' import { ParseUnion, IsUnion } from './union.ts' +import { ParseUnsignedInteger, IsUnsignedInteger } from './unsigned_integer.ts' +import { ParseUnsignedNumber, IsUnsignedNumber} from './unsigned_number.ts' import { ParseUntil_1, IsUntil_1 } from './until_1.ts' import { ParseUntil, IsUntil } from './until.ts' @@ -66,6 +68,8 @@ export function ParseParser(context: IProperties, parser IsString(parser) ? ParseString(parser.quotes, input) : IsTuple(parser) ? ParseTuple(context, parser.parsers, input) : IsUnion(parser) ? ParseUnion(context, parser.parsers, input) : + IsUnsignedInteger(parser) ? ParseUnsignedInteger(input) : + IsUnsignedNumber(parser) ? ParseUnsignedNumber(input) : IsUntil(parser) ? ParseUntil(parser.end, input) : IsUntil_1(parser) ? ParseUntil_1(parser.end, input) : [] diff --git a/src/runtime/parser.ts b/src/runtime/parser.ts index a45695c..240b9fc 100644 --- a/src/runtime/parser.ts +++ b/src/runtime/parser.ts @@ -38,10 +38,6 @@ export type IMapping value -/** Maps the output as the given parameter T */ -export function As(mapping: T): ((value: unknown) => T) { - return (_: unknown) => mapping -} // ------------------------------------------------------------------ // Parser // ------------------------------------------------------------------ diff --git a/src/runtime/unsigned_integer.ts b/src/runtime/unsigned_integer.ts new file mode 100644 index 0000000..8fb9395 --- /dev/null +++ b/src/runtime/unsigned_integer.ts @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import * as Token from '../token/index.ts' + +import { Arguments } from '../system/arguments/index.ts' +import { Guard } from '../guard/index.ts' +import { type IParser, type IMapping, Identity } from './parser.ts' + +// ------------------------------------------------------------------ +// Type +// ------------------------------------------------------------------ +export interface IUnsignedInteger extends IParser { + type: 'UnsignedInteger' +} +// ------------------------------------------------------------------ +// Factory +// ------------------------------------------------------------------ +export function UnsignedInteger>(mapping: Mapping): IUnsignedInteger> +export function UnsignedInteger(): IUnsignedInteger +export function UnsignedInteger(...args: unknown[]): never { + const [mapping] = Arguments.Match<[IParser, IMapping]>(args, { + 1: (mapping) => [mapping], + 0: () => [Identity] + }) + return { type: 'UnsignedInteger', mapping } as never +} +// ------------------------------------------------------------------ +// Guard +// ------------------------------------------------------------------ +export function IsUnsignedInteger(value: unknown): value is IUnsignedInteger { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.IsEqual(value.type, 'UnsignedInteger') +} +// ------------------------------------------------------------------ +// Parse +// ------------------------------------------------------------------ +export function ParseUnsignedInteger(input: string): [] | [string, string] { + return Token.UnsignedInteger(input) +} \ No newline at end of file diff --git a/src/runtime/unsigned_number.ts b/src/runtime/unsigned_number.ts new file mode 100644 index 0000000..da9a2b2 --- /dev/null +++ b/src/runtime/unsigned_number.ts @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import * as Token from '../token/index.ts' + +import { Arguments } from '../system/arguments/index.ts' +import { Guard } from '../guard/index.ts' +import { type IParser, type IMapping, Identity } from './parser.ts' + +// ------------------------------------------------------------------ +// Type +// ------------------------------------------------------------------ +export interface IUnsignedNumber extends IParser { + type: 'UnsignedNumber' +} +// ------------------------------------------------------------------ +// Factory +// ------------------------------------------------------------------ +export function UnsignedNumber>(mapping: Mapping): IUnsignedNumber> +export function UnsignedNumber(): IUnsignedNumber +export function UnsignedNumber(...args: unknown[]): never { + const [mapping] = Arguments.Match<[IParser, IMapping]>(args, { + 1: (mapping) => [mapping], + 0: () => [Identity] + }) + return { type: 'UnsignedNumber', mapping } as never +} +// ------------------------------------------------------------------ +// Guard +// ------------------------------------------------------------------ +export function IsUnsignedNumber(value: unknown): value is IUnsignedNumber { + return Guard.IsObject(value) + && Guard.HasPropertyKey(value, 'type') + && Guard.IsEqual(value.type, 'UnsignedNumber') +} +// ------------------------------------------------------------------ +// Parse +// ------------------------------------------------------------------ +export function ParseUnsignedNumber(input: string): [] | [string, string] { + return Token.UnsignedNumber(input) +} \ No newline at end of file diff --git a/src/static/index.ts b/src/static/index.ts index d30923f..6956d88 100644 --- a/src/static/index.ts +++ b/src/static/index.ts @@ -39,5 +39,7 @@ export type { Rest } from './rest.ts' export type { String } from './string.ts' export type { Tuple } from './tuple.ts' export type { Union } from './union.ts' +export type { UnsignedInteger } from './unsigned_integer.ts' +export type { UnsignedNumber } from './unsigned_number.ts' export type { Until_1 } from './until_1.ts' export type { Until } from './until.ts' diff --git a/src/static/parse.ts b/src/static/parse.ts index f9f7c05..51bd8fe 100644 --- a/src/static/parse.ts +++ b/src/static/parse.ts @@ -39,6 +39,8 @@ import type { ParseRest, Rest } from './rest.ts' import type { ParseString, String } from './string.ts' import type { ParseTuple, Tuple } from './tuple.ts' import type { ParseUnion, Union } from './union.ts' +import type { ParseUnsignedInteger, UnsignedInteger } from './unsigned_integer.ts' +import type { ParseUnsignedNumber, UnsignedNumber } from './unsigned_number.ts' import type { ParseUntil_1, Until_1 } from './until_1.ts' import type { ParseUntil, Until } from './until.ts' @@ -58,10 +60,12 @@ type ParseInput = ( Parser extends Rest ? ParseRest : Parser extends String ? ParseString : Parser extends Tuple ? ParseTuple : - Parser extends Union ? ParseUnion : + Parser extends Union ? ParseUnion : + Parser extends UnsignedInteger ? ParseUnsignedInteger : + Parser extends UnsignedNumber ? ParseUnsignedNumber : Parser extends Until ? ParseUntil : - Parser extends Until_1 ? ParseUntil_1 - : [] + Parser extends Until_1 ? ParseUntil_1 : + [] ) // ------------------------------------------------------------------ // ParseMapping diff --git a/src/static/unsigned_integer.ts b/src/static/unsigned_integer.ts new file mode 100644 index 0000000..0abee4f --- /dev/null +++ b/src/static/unsigned_integer.ts @@ -0,0 +1,45 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import type { Identity, IMapping, IParser } from './parser.ts' +import * as Token from '../token/index.ts' + +// ------------------------------------------------------------------ +// Type +// ------------------------------------------------------------------ +export interface UnsignedInteger extends IParser { + type: 'UnsignedInteger' +} +// ------------------------------------------------------------------ +// Parse +// ------------------------------------------------------------------ +export type ParseUnsignedInteger = ( + Token.TUnsignedInteger +) diff --git a/src/static/unsigned_number.ts b/src/static/unsigned_number.ts new file mode 100644 index 0000000..0275916 --- /dev/null +++ b/src/static/unsigned_number.ts @@ -0,0 +1,45 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import type { Identity, IMapping, IParser } from './parser.ts' +import * as Token from '../token/index.ts' + +// ------------------------------------------------------------------ +// Type +// ------------------------------------------------------------------ +export interface UnsignedNumber extends IParser { + type: 'UnsignedNumber' +} +// ------------------------------------------------------------------ +// Parse +// ------------------------------------------------------------------ +export type ParseUnsignedNumber = ( + Token.TUnsignedNumber +) diff --git a/src/token/bigint.ts b/src/token/bigint.ts index b686a0f..1632b1c 100644 --- a/src/token/bigint.ts +++ b/src/token/bigint.ts @@ -28,7 +28,7 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTake, Take } from './internal/take.ts' import { type TInteger, Integer } from './integer.ts' @@ -37,21 +37,17 @@ import { type TInteger, Integer } from './integer.ts' // ------------------------------------------------------------------ type TTakeBigInt = ( TInteger extends [infer Integer extends string, infer IntegerRest extends string] - ? TTake<['n'], IntegerRest> extends [infer N extends string, infer NRest extends string] + ? TTake<['n'], IntegerRest> extends [infer _N extends string, infer NRest extends string] ? [`${Integer}`, NRest] : [] // fail: did not match 'n' : [] // fail: did not match Integer ) function TakeBigInt(input: Input): TTakeBigInt { - const integer = Integer(input) - return ( - IsResult(integer) ? (() => { - const n = Take(['n'], integer[1]) - return IsResult(n) - ? [`${integer[0]}`, n[1]] - : [] // fail: did not match 'n' - })() : [] // fail: did not match Integer - ) as never + return Match(Integer(input), (Integer, IntegerRest) => + Match(Take(['n'], IntegerRest), (_N, NRest) => + [`${Integer}`, NRest], + () => []), // fail: did not match 'n' + () => []) as never // fail: did not match Integer } // ------------------------------------------------------------------ // BigInt diff --git a/src/token/ident.ts b/src/token/ident.ts index af6fded..aee7b4c 100644 --- a/src/token/ident.ts +++ b/src/token/ident.ts @@ -28,7 +28,7 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTrim, Trim } from './internal/trim.ts' import { type TTake, Take } from './internal/take.ts' @@ -61,12 +61,9 @@ type TTakeRemaining = ( : [Result, Input] ) function TakeRemaining(input: Input, result: string = ''): TTakeRemaining { - const remaining = Take(Remaining, input) as string[] - return ( - IsResult(remaining) - ? TakeRemaining(remaining[1], `${result}${remaining[0]}`) - : [result, input] - ) as never + return Match(Take(Remaining, input), (Remaining, RemainingRest) => + TakeRemaining(RemainingRest, `${result}${Remaining}`), + () => [result, input]) as never } // ------------------------------------------------------------------ // TakeIdent @@ -79,15 +76,11 @@ type TTakeIdent = ( : [] // fail: did not match Initial ) function TakeIdent(input: Input): TTakeIdent { - const initial = TakeInitial(input) as string[] - return ( - IsResult(initial) ? (() => { - const remaining = TakeRemaining(initial[1]) - return IsResult(remaining) - ? [`${initial[0]}${remaining[0]}`, remaining[1]] - : [] // fail: did not match Remaining - })() : [] // fail: did not match Initial - ) as never + return Match(TakeInitial(input), (Initial, InitialRest) => + Match(TakeRemaining(InitialRest), (Remaining, RemainingRest) => + [`${Initial}${Remaining}`, RemainingRest], + () => []), // fail: did not match Remaining + () => []) as never // fail: did not match Initial } // ------------------------------------------------------------------ // Ident diff --git a/src/token/index.ts b/src/token/index.ts index caf3824..6ca102b 100644 --- a/src/token/index.ts +++ b/src/token/index.ts @@ -34,5 +34,7 @@ export * from './number.ts' export * from './rest.ts' export * from './span.ts' export * from './string.ts' +export * from './unsigned_integer.ts' +export * from './unsigned_number.ts' export * from './until_1.ts' export * from './until.ts' diff --git a/src/token/integer.ts b/src/token/integer.ts index b707b73..27cd44f 100644 --- a/src/token/integer.ts +++ b/src/token/integer.ts @@ -28,17 +28,12 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTrim, Trim } from './internal/trim.ts' -import { type TTake, Take } from './internal/take.ts' -import { type TMany, Many } from './internal/many.ts' import { type TOptional, Optional } from './internal/optional.ts' - -import { type TDigit, Digit } from './internal/char.ts' import { type THyphen, Hyphen } from './internal/char.ts' -import { type TZero, Zero } from './internal/char.ts' -import { type TNonZero, NonZero } from './internal/char.ts' -import { type TUnderScore, UnderScore } from './internal/char.ts' + +import { type TUnsignedInteger, UnsignedInteger } from './unsigned_integer.ts' // ------------------------------------------------------------------ // TakeSign @@ -50,68 +45,30 @@ function TakeSign(input: Input): TTakeSign { return Optional(Hyphen, input) as never } // ------------------------------------------------------------------ -// TakeNonZero -// ------------------------------------------------------------------ -type TTakeNonZero = ( - TTake -) -function TakeNonZero(input: Input): TTakeNonZero { - return Take(NonZero, input) -} -// ------------------------------------------------------------------ -// TakeDigits +// TakeSignedInteger // ------------------------------------------------------------------ -type TAllowedDigits = [...TDigit, TUnderScore] -const AllowedDigits = [...Digit, UnderScore] as TAllowedDigits -// ... -type TTakeDigits = ( - TMany -) -function TakeDigits(input: Input): TTakeDigits { - return Many(AllowedDigits, [UnderScore], input) as never -} -// ------------------------------------------------------------------ -// TakeInteger -// ------------------------------------------------------------------ -type TTakeInteger = ( +type TTakeSignedInteger = ( TTakeSign extends [infer Sign extends string, infer SignRest extends string] - ? TTake<[TZero], SignRest> extends [infer Zero extends string, infer ZeroRest extends string] - ? [`${Sign}${Zero}`, ZeroRest] - : TTakeNonZero extends [infer NonZero extends string, infer NonZeroRest extends string] - ? TTakeDigits extends [infer Digits extends string, infer DigitsRest extends string] - ? [`${Sign}${NonZero}${Digits}`, DigitsRest] - : [] // fail: did not match Digits - : [] // fail: did not match NonZero + ? TUnsignedInteger extends [infer UnsignedInteger extends string, infer UnsignedIntegerRest extends string] + ? [`${Sign}${UnsignedInteger}`, UnsignedIntegerRest] + : [] // fail: did not match unsigned integer : [] // fail: did not match Sign ) -function TakeInteger(input: Input): TTakeInteger { - const sign = TakeSign(input) - return ( - IsResult(sign) ? (() => { - const zero = Take([Zero], sign[1]) - return IsResult(zero) - ? [`${sign[0]}${zero[0]}`, zero[1]] - : (() => { - const nonZero = TakeNonZero(sign[1]) - return IsResult(nonZero) ? (() => { - const digits = TakeDigits(nonZero[1]) - return IsResult(digits) - ? [`${sign[0]}${nonZero[0]}${digits[0]}`, digits[1]] - : [] // fail: did not match Digits - })() : [] // fail: did not match NonZero - })() - })() : [] // fail: did not match Sign - ) as never +function TakeSignedInteger(input: Input): TTakeSignedInteger { + return Match(TakeSign(input), (Sign, SignRest) => + Match(UnsignedInteger(SignRest), (UnsignedInteger, UnsignedIntegerRest) => + [`${Sign}${UnsignedInteger}`, UnsignedIntegerRest], + () => []), // fail: did not match unsigned integer + () => []) as never // fail: did not match Sign } // ------------------------------------------------------------------ // Integer // ------------------------------------------------------------------ -/** Matches if next is a Integer */ +/** Matches if next is a signed or unsigned Integer */ export type TInteger = ( - TTakeInteger> + TTakeSignedInteger> ) -/** Matches if next is a Integer */ +/** Matches if next is a signed or unsigned Integer */ export function Integer(input: Input): TInteger { - return TakeInteger(Trim(input)) as never -} - + return TakeSignedInteger(Trim(input)) as never +} \ No newline at end of file diff --git a/src/token/internal/many.ts b/src/token/internal/many.ts index 48c08d2..21de6f8 100644 --- a/src/token/internal/many.ts +++ b/src/token/internal/many.ts @@ -28,7 +28,7 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './result.ts' +import { Match } from './match.ts' import { type TTake, Take } from './take.ts' // ------------------------------------------------------------------ @@ -58,15 +58,10 @@ export type TMany - (allowed: [...Allowed], discard: [...Discard], input: Input, result: string = ''): - TMany { - const takeResult = Take(allowed, input) as [string, string] - return ( - IsResult(takeResult) - ? IsDiscard(discard, takeResult[0]) - ? Many(allowed, discard, takeResult[1], result) - : Many(allowed, discard, takeResult[1], `${result}${takeResult[0]}`) - : [result, input] - ) as never +export function Many(allowed: [...Allowed], discard: [...Discard], input: Input, result: string = ''): TMany { + return Match(Take(allowed, input), (Char, Rest) => + IsDiscard(discard, Char) + ? Many(allowed, discard, Rest, result) + : Many(allowed, discard, Rest, `${result}${Char}`), + () => [result, input]) as never } \ No newline at end of file diff --git a/src/token/internal/result.ts b/src/token/internal/match.ts similarity index 75% rename from src/token/internal/result.ts rename to src/token/internal/match.ts index 305124f..b7b6eeb 100644 --- a/src/token/internal/result.ts +++ b/src/token/internal/match.ts @@ -26,9 +26,15 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IsArray, IsEqual } from './guard.ts' +import { IsEqual } from './guard.ts' + +type TResult = [string, string] | [] /** Checks the value is a Tuple-2 [string, string] result */ -export function IsResult(value: unknown): value is [string, string] { - return IsArray(value) && IsEqual(value.length, 2) +export function IsMatch(value: TResult): value is [string, string] { + return IsEqual(value.length, 2) +} +/** Matches on a result and dispatches either left or right arm */ +export function Match(input: TResult, ok: (value: string, rest: string) => TResult, fail: () => TResult): TResult { + return IsMatch(input) ? ok(input[0], input[1]) : fail() } diff --git a/src/token/internal/optional.ts b/src/token/internal/optional.ts index 5a5a744..cd5d4b4 100644 --- a/src/token/internal/optional.ts +++ b/src/token/internal/optional.ts @@ -28,7 +28,7 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './result.ts' +import { Match } from './match.ts' import { type TTake, Take } from './take.ts' /** Matches the given Value or empty string if no match. This function never fails */ @@ -38,12 +38,8 @@ export type TOptional = ( : ['', Input] ) /** Matches the given Value or empty string if no match. This function never fails */ -export function Optional - (value: Value, input: Input): TOptional { - const result = Take([value], input) - return ( - IsResult(result) - ? result - : ['', input] - ) as never +export function Optional(value: Value, input: Input): TOptional { + return Match(Take([value], input), (Optional, Rest) => + [Optional, Rest], + () => ['', input]) as never } \ No newline at end of file diff --git a/src/token/internal/take.ts b/src/token/internal/take.ts index 611167d..cfcf0c0 100644 --- a/src/token/internal/take.ts +++ b/src/token/internal/take.ts @@ -28,7 +28,8 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsEqual, IsString } from './guard.ts' +import { IsMatch } from './match.ts' +import { IsEqual } from './guard.ts' // ------------------------------------------------------------------ // TakeString @@ -58,13 +59,25 @@ export type TTake = ( ) /** Takes one of the given variants or fail */ export function Take(variants: [...Variants], input: Input): TTake { - const [left, ...right] = variants - return ( - IsString(left) - ? (() => { - const result = TakeVariant(left, input) - return IsEqual(result.length, 2) ? result : Take(right, input) - })() - : [] - ) as never + // ---------------------------------------------------------------- + // Symmetric + // ---------------------------------------------------------------- + // const [left, ...right] = variants + // return ( + // IsString(left) + // ? (() => { + // const result = TakeVariant(left, input) + // return IsEqual(result.length, 2) ? result : Take(right, input) + // })() + // : [] + // ) as never + // ---------------------------------------------------------------- + // Inline + // ---------------------------------------------------------------- + for (let i = 0; i < variants.length; i++) { + const result = TakeVariant(variants[i], input) + if (IsMatch(result)) return result as never + } + return [] as never + } diff --git a/src/token/number.ts b/src/token/number.ts index f4b9cab..3610e3b 100644 --- a/src/token/number.ts +++ b/src/token/number.ts @@ -28,23 +28,12 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsEqual } from './internal/guard.ts' -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTrim, Trim } from './internal/trim.ts' -import { type TTake, Take } from './internal/take.ts' -import { type TMany, Many } from './internal/many.ts' import { type TOptional, Optional } from './internal/optional.ts' - -import { type TDigit, Digit, TUnderScore, UnderScore } from './internal/char.ts' -import { type TDot, Dot } from './internal/char.ts' import { type THyphen, Hyphen } from './internal/char.ts' -import { type TInteger, Integer } from './integer.ts' -// ------------------------------------------------------------------ -// AllowedDigits -// ------------------------------------------------------------------ -type TAllowedDigits = [...TDigit, TUnderScore] -const AllowedDigits = [...Digit, UnderScore] as TAllowedDigits +import { type TUnsignedNumber, UnsignedNumber } from './unsigned_number.ts' // ------------------------------------------------------------------ // TakeSign @@ -56,113 +45,30 @@ function TakeSign(input: Input): TTakeSign { return Optional(Hyphen, input) as never } // ------------------------------------------------------------------ -// IsLeadingDot -// ------------------------------------------------------------------ -type TIsLeadingDot = ( - TTake<[TDot], Input> extends [string, string] ? true : false -) -function IsLeadingDot(input: Input): TIsLeadingDot { - return IsResult(Take([Dot], input)) as never -} -// ------------------------------------------------------------------ -// TakeFractional -// ------------------------------------------------------------------ -type TTakeFractional = ( - TMany extends [infer Digits extends string, infer Rest extends string] - ? Digits extends '' - ? [] // fail: no Digits - : [Digits, Rest] - : [] // fail: did not match Digits -) -function TakeFractional(input: Input): TTakeFractional { - const digits = Many(AllowedDigits, [UnderScore], input) - return ( - IsResult(digits) - ? IsEqual(digits[0], '') - ? [] // fail: no Digits - : [digits[0], digits[1]] - : [] // fail: did not match Digits - ) as never -} -// ------------------------------------------------------------------ -// LeadingDot -// ------------------------------------------------------------------ -type TLeadingDot = ( - TTake<[TDot], Input> extends [infer Dot extends string, infer Rest extends string] - ? TTakeFractional extends [infer Fractional extends string, infer Rest extends string] - ? [`${Sign}0${Dot}${Fractional}`, Rest] - : [] // fail: did not match Fractional - : [] // fail: did not match Dot -) -function LeadingDot - (sign: Sign, input: Input): - TLeadingDot { - const dot = Take([Dot], input) - return ( - IsResult(dot) ? (() => { - const fractional = TakeFractional(dot[1]) - return IsResult(fractional) - ? [`${sign}0${dot[0]}${fractional[0]}`, fractional[1]] - : [] // fail: did not match Fractional - })() : [] // fail: did not match Dot - ) as never -} -// ------------------------------------------------------------------ -// TakeLeadingInteger -// ------------------------------------------------------------------ -type TLeadingInteger = ( - TInteger extends [infer Integer extends string, infer IntegerRest extends string] - ? TTake<[TDot], IntegerRest> extends [infer Dot extends string, infer DotRest extends string] - ? TTakeFractional extends [infer Fractional extends string, infer FractionalRest extends string] - ? [`${Sign}${Integer}${Dot}${Fractional}`, FractionalRest] - : [`${Sign}${Integer}`, DotRest] // fail: did not match Fractional, use Integer - : [`${Sign}${Integer}`, IntegerRest] // fail: did not match Dot, use Integer - : [] // fail: did not match Integer -) -function LeadingInteger - (sign: Sign, input: Input): - TLeadingInteger { - const integer = Integer(input) - return ( - IsResult(integer) ? (() => { - const dot = Take([Dot], integer[1]) - return IsResult(dot) ? (() => { - const fractional = TakeFractional(dot[1]) - return IsResult(fractional) - ? [`${sign}${integer[0]}${dot[0]}${fractional[0]}`, fractional[1]] - : [`${sign}${integer[0]}`, dot[1]] // fail: did not match Fractional, use Integer - })() : [`${sign}${integer[0]}`, integer[1]] // fail: did not match Dot, use Integer - })() : [] // fail: did not match Integer - ) as never -} -// ------------------------------------------------------------------ -// TakeNumber +// TakeSignedNumber // ------------------------------------------------------------------ -type TTakeNumber = ( +type TTakeSignedNumber = ( TTakeSign extends [infer Sign extends string, infer SignRest extends string] - ? TIsLeadingDot extends true - ? TLeadingDot - : TLeadingInteger + ? TUnsignedNumber extends [infer UnsignedInteger extends string, infer UnsignedIntegerRest extends string] + ? [`${Sign}${UnsignedInteger}`, UnsignedIntegerRest] + : [] // fail: did not match unsigned integer : [] // fail: did not match Sign ) -function TakeNumber(input: Input): TTakeNumber{ - const sign = TakeSign(input) - return ( - IsResult(sign) - ? IsLeadingDot(sign[1]) - ? LeadingDot(sign[0], sign[1]) - : LeadingInteger(sign[0], sign[1]) - : [] // fail: did not match Sign - ) as never +function TakeSignedNumber(input: Input): TTakeSignedNumber { + return Match(TakeSign(input), (Sign, SignRest) => + Match(UnsignedNumber(SignRest), (UnsignedInteger, UnsignedIntegerRest) => + [`${Sign}${UnsignedInteger}`, UnsignedIntegerRest], + () => []), // fail: did not match unsigned integer + () => []) as never // fail: did not match Sign } // ------------------------------------------------------------------ -// Number +// Integer // ------------------------------------------------------------------ -/** Matches if next is a literal Number */ +/** Matches if next is a signed or unsigned Number */ export type TNumber = ( - TTakeNumber> + TTakeSignedNumber> ) -/** Matches if next is a literal Number */ +/** Matches if next is a signed or unsigned Number */ export function Number(input: Input): TNumber { - return TakeNumber(Trim(input)) as never + return TakeSignedNumber(Trim(input)) as never } \ No newline at end of file diff --git a/src/token/span.ts b/src/token/span.ts index 3c5a590..0af0f91 100644 --- a/src/token/span.ts +++ b/src/token/span.ts @@ -28,62 +28,53 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTrim, Trim } from './internal/trim.ts' import { type TNewLine, NewLine } from './internal/char.ts' +import { type TTake, Take } from './internal/take.ts' import { type TUntil, Until } from './until.ts' // ------------------------------------------------------------------ // MultiLine // ------------------------------------------------------------------ type TMultiLine = ( - Input extends `${Start}${infer Rest extends string}` + TTake<[Start], Input> extends [infer _, infer Rest extends string] ? TUntil<[End], Rest> extends [infer Until extends string, infer UntilRest extends string] - ? UntilRest extends `${End}${infer Rest extends string}` + ? TTake<[End], UntilRest> extends [infer _ extends string, infer Rest extends string] ? [`${Until}`, Rest] : [] // fail: did not match End : [] // fail: did not match Until : [] // fail: did not match Start ) -function MultiLine - (start: Start, end: End, input: Input): - TMultiLine { - return ( - input.startsWith(start) ? (() => { - const until = Until([end], input.slice(start.length)) - return IsResult(until) ? (() => { - return until[1].startsWith(end) - ? [`${until[0]}`, until[1].slice(end.length)] - : [] // fail: did not match End - })() : [] // fail: did not match Until - })() : [] // fail: did not match Start - ) as never +function MultiLine(start: Start, end: End, input: Input): TMultiLine { + return Match(Take([start], input), (_, Rest) => + Match(Until([end], Rest), (Until, UntilRest) => + Match(Take([end], UntilRest), (_, Rest) => + [`${Until}`, Rest], + () => []), // fail: did not match End + () => []), // fail: did not match Until + () => []) as never // fail: did not match Start } // ------------------------------------------------------------------ // SingleLine // ------------------------------------------------------------------ type TSingleLine = ( - Input extends `${Start}${infer Rest extends string}` + TTake<[Start], Input> extends [infer _ extends string, infer Rest extends string] ? TUntil<[TNewLine, End], Rest> extends [infer Until extends string, infer UntilRest extends string] - ? UntilRest extends `${End}${infer EndRest extends string}` + ? TTake<[End], UntilRest> extends [infer _ extends string, infer EndRest extends string] ? [`${Until}`, EndRest] : [] // fail: did not match End : [] // fail: did not match Until : [] // fail: not match Start ) -function SingleLine - (start: Start, end: End, input: Input): - TSingleLine { - return ( - input.startsWith(start) ? (() => { - const until = Until([NewLine, end], input.slice(start.length)) - return IsResult(until) ? (() => { - return until[1].startsWith(end) - ? [`${until[0]}`, until[1].slice(end.length)] - : [] // fail: did not match End - })() : [] // fail: did not match Until - })() : [] // fail: not match Start - ) as never +function SingleLine(start: Start, end: End, input: Input): TSingleLine { + return Match(Take([start], input), (_, Rest) => + Match(Until([NewLine, end], Rest), (Until, UntilRest) => + Match(Take([end], UntilRest), (_, EndRest) => + [`${Until}`, EndRest], + () => []), // fail: did not match End + () => []), // fail: did not match Until + () => []) as never // fail: not match Start } // ------------------------------------------------------------------ // Span diff --git a/src/token/string.ts b/src/token/string.ts index b1ddf23..d99b454 100644 --- a/src/token/string.ts +++ b/src/token/string.ts @@ -28,7 +28,7 @@ THE SOFTWARE. // deno-fmt-ignore-file -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TTake, Take } from './internal/take.ts' import { type TTrim, Trim } from './internal/trim.ts' import { type TSpan, Span } from './span.ts' @@ -60,12 +60,9 @@ type TTakeString = ( : [] // fail: did not match Initial ) function TakeString(quotes: [...Quotes], input: Input): TTakeString { - const initial = TakeInitial(quotes, input) as [string, string] - return ( - IsResult(initial) - ? TakeSpan(initial[0], `${initial[0]}${initial[1]}`) - : [] // fail: did not match Initial - ) as never + return Match(TakeInitial(quotes, input), (Initial, InitialRest) => + TakeSpan(Initial, `${Initial}${InitialRest}`), + () => []) as never // fail: did not match Initial } /** Matches a literal String with the given quotes */ export type TString = ( diff --git a/src/token/unsigned_integer.ts b/src/token/unsigned_integer.ts new file mode 100644 index 0000000..2f2533c --- /dev/null +++ b/src/token/unsigned_integer.ts @@ -0,0 +1,92 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import { Match } from './internal/match.ts' +import { type TTrim, Trim } from './internal/trim.ts' +import { type TTake, Take } from './internal/take.ts' +import { type TMany, Many } from './internal/many.ts' +import { type TDigit, Digit } from './internal/char.ts' +import { type TZero, Zero } from './internal/char.ts' +import { type TNonZero, NonZero } from './internal/char.ts' +import { type TUnderScore, UnderScore } from './internal/char.ts' + +// ------------------------------------------------------------------ +// TakeNonZero +// ------------------------------------------------------------------ +type TTakeNonZero = ( + TTake +) +function TakeNonZero(input: Input): TTakeNonZero { + return Take(NonZero, input) +} +// ------------------------------------------------------------------ +// TakeDigits +// ------------------------------------------------------------------ +type TAllowedDigits = [...TDigit, TUnderScore] +const AllowedDigits = [...Digit, UnderScore] as TAllowedDigits +// ... +type TTakeDigits = ( + TMany +) +function TakeDigits(input: Input): TTakeDigits { + return Many(AllowedDigits, [UnderScore], input) as never +} +// ------------------------------------------------------------------ +// TakeUnsignedInteger +// ------------------------------------------------------------------ +type TTakeUnsignedInteger = ( + TTake<[TZero], Input> extends [infer Zero extends string, infer ZeroRest extends string] + ? [Zero, ZeroRest] + : TTakeNonZero extends [infer NonZero extends string, infer NonZeroRest extends string] + ? TTakeDigits extends [infer Digits extends string, infer DigitsRest extends string] + ? [`${NonZero}${Digits}`, DigitsRest] + : [] // fail: did not match Digits + : [] // fail: did not match NonZero +) +function TakeUnsignedInteger(input: Input): TTakeUnsignedInteger { + return Match(Take([Zero], input), (Zero, ZeroRest) => + [Zero, ZeroRest], + () => Match(TakeNonZero(input), (NonZero, NonZeroRest) => + Match(TakeDigits(NonZeroRest), (Digits, DigitsRest) => + [`${NonZero}${Digits}`, DigitsRest], + () => []), // fail: did not match Digits + () => [])) as never // fail: did not match NonZero +} +// ------------------------------------------------------------------ +// UnsignedInteger +// ------------------------------------------------------------------ +/** Matches if next is a UnsignedInteger */ +export type TUnsignedInteger = ( + TTakeUnsignedInteger> +) +/** Matches if next is a UnsignedInteger */ +export function UnsignedInteger(input: Input): TUnsignedInteger { + return TakeUnsignedInteger(Trim(input)) as never +} \ No newline at end of file diff --git a/src/token/unsigned_number.ts b/src/token/unsigned_number.ts new file mode 100644 index 0000000..e095e93 --- /dev/null +++ b/src/token/unsigned_number.ts @@ -0,0 +1,134 @@ +/*-------------------------------------------------------------------------- + +ParseBox + +The MIT License (MIT) + +Copyright (c) 2024-2026 Haydn Paterson + +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 + +import { IsEqual } from './internal/guard.ts' +import { IsMatch, Match } from './internal/match.ts' +import { type TTrim, Trim } from './internal/trim.ts' +import { type TTake, Take } from './internal/take.ts' +import { type TMany, Many } from './internal/many.ts' +import { type TDigit, type TUnderScore, Digit, UnderScore } from './internal/char.ts' +import { type TDot, Dot } from './internal/char.ts' + +import { type TUnsignedInteger, UnsignedInteger } from './unsigned_integer.ts' + +// ------------------------------------------------------------------ +// AllowedDigits +// ------------------------------------------------------------------ +type TAllowedDigits = [...TDigit, TUnderScore] +const AllowedDigits = [...Digit, UnderScore] as TAllowedDigits + +// ------------------------------------------------------------------ +// IsLeadingDot +// ------------------------------------------------------------------ +type TIsLeadingDot = ( + TTake<[TDot], Input> extends [string, string] ? true : false +) +function IsLeadingDot(input: Input): TIsLeadingDot { + return IsMatch(Take([Dot], input)) as never +} +// ------------------------------------------------------------------ +// TakeFractional +// ------------------------------------------------------------------ +type TTakeFractional = ( + TMany extends [infer Digits extends string, infer DigitsRest extends string] + ? Digits extends '' + ? [] // fail: no Digits + : [Digits, DigitsRest] + : [] // fail: did not match Digits +) +function TakeFractional(input: Input): TTakeFractional { + return Match(Many(AllowedDigits, [UnderScore], input), (Digits, DigitsRest) => + IsEqual(Digits, '') + ? [] // fail: no Digits + : [Digits, DigitsRest] + , () => []) as never // fail: did not match Digits +} +// ------------------------------------------------------------------ +// LeadingDot +// ------------------------------------------------------------------ +type TLeadingDot = ( + TTake<[TDot], Input> extends [infer Dot extends string, infer DotRest extends string] + ? TTakeFractional extends [infer Fractional extends string, infer FractionalRest extends string] + ? [`0${Dot}${Fractional}`, FractionalRest] + : [] // fail: did not match Fractional + : [] // fail: did not match Dot +) +function LeadingDot(input: Input): TLeadingDot { + return Match(Take([Dot], input), (Dot, DotRest) => + Match(TakeFractional(DotRest), (Fractional, FractionalRest) => + [`0${Dot}${Fractional}`, FractionalRest], + () => []), // fail: did not match Fractional + () => []) as never // fail: did not match Dot +} +// ------------------------------------------------------------------ +// TakeLeadingInteger +// ------------------------------------------------------------------ +type TLeadingInteger = ( + TUnsignedInteger extends [infer Integer extends string, infer IntegerRest extends string] + ? TTake<[TDot], IntegerRest> extends [infer Dot extends string, infer DotRest extends string] + ? TTakeFractional extends [infer Fractional extends string, infer FractionalRest extends string] + ? [`${Integer}${Dot}${Fractional}`, FractionalRest] + : [`${Integer}`, DotRest] // fail: did not match Fractional, use Integer + : [`${Integer}`, IntegerRest] // fail: did not match Dot, use Integer + : [] // fail: did not match Integer +) +function LeadingInteger(input: Input): TLeadingInteger { + return Match(UnsignedInteger(input), (Integer, IntegerRest) => + Match(Take([Dot], IntegerRest), (Dot, DotRest) => + Match(TakeFractional(DotRest), (Fractional, FractionalRest) => + [`${Integer}${Dot}${Fractional}`, FractionalRest], + () => [`${Integer}`, DotRest]), // fail: did not match Fractional, use Integer + () => [`${Integer}`, IntegerRest]), // fail: did not match Dot, use Integer + () => []) as never // fail: did not match Integer +} +// ------------------------------------------------------------------ +// TakeUnsignedNumber +// ------------------------------------------------------------------ +type TTakeUnsignedNumber = ( + TIsLeadingDot extends true + ? TLeadingDot + : TLeadingInteger +) +function TakeUnsignedNumber(input: Input): TTakeUnsignedNumber { + return (IsLeadingDot(input) + ? LeadingDot(input) + : LeadingInteger(input)) as never +} +// ------------------------------------------------------------------ +// UnsignedNumber +// ------------------------------------------------------------------ +/** Matches if next is a UnsignedNumber */ +export type TUnsignedNumber = ( + TTakeUnsignedNumber> +) +/** Matches if next is a UnsignedNumber */ +export function UnsignedNumber(input: Input): TUnsignedNumber { + return TakeUnsignedNumber(Trim(input)) as never +} \ No newline at end of file diff --git a/src/token/until.ts b/src/token/until.ts index 00ffcdd..28f5899 100644 --- a/src/token/until.ts +++ b/src/token/until.ts @@ -28,52 +28,69 @@ THE SOFTWARE. // deno-fmt-ignore-file +import { Match } from './internal/match.ts' import { IsEqual, IsString } from './internal/guard.ts' // ------------------------------------------------------------------ -// IsEnd +// TakeOne // ------------------------------------------------------------------ -type TIsEnd = ( +type TTakeOne = ( + Input extends `${infer Left extends string}${infer Right extends string}` + ? [Left, Right] + : [] +) +function TakeOne(input: string): TTakeOne { + const result = IsEqual(input, '') ? [] : [input.slice(0, 1), input.slice(1)] + return result as never +} +// ------------------------------------------------------------------ +// IsInputMatchSentinal +// ------------------------------------------------------------------ +type TIsInputMatchSentinal = ( End extends [infer Left extends string, ...infer Right extends string[]] ? Input extends `${Left}${string}` ? true - : TIsEnd + : TIsInputMatchSentinal : false ) -function IsEnd - (end: [...End], input: Input): - TIsEnd { +function IsInputMatchSentinal(end: [...End], input: Input): TIsInputMatchSentinal { const [left, ...right] = end return ( IsString(left) ? input.startsWith(left) ? true - : IsEnd(right, input) + : IsInputMatchSentinal(right, input) : false ) as never } + // ------------------------------------------------------------------ // Until +// +// Implementation Note: This function performs a 1-character lookahead +// check against the Sentinel set. We must check IsInputMatchSentinal +// against the current Input *before* consuming the next character via +// TUntil recursion. +// +// We cannot check the Sentinel before TakeOne because the Sentinel +// length is variable; we only know when to stop (Match), not how +// far to advance on a non-match. Therefore, we advance by a +// fixed 1-character increment until a Sentinel prefix is detected. +// // ------------------------------------------------------------------ /** Match Input until but not including End. No match if End not found. */ export type TUntil = ( - Input extends `` - ? [] // fail: Input is empty - : TIsEnd extends true - ? [Result, Input] - : Input extends `${infer Left extends string}${infer Right extends string}` - ? TUntil - : [] + TTakeOne extends [infer One extends string, infer Rest extends string] + ? TIsInputMatchSentinal extends true + ? [Result, Input] // ok: at sentinal + : TUntil // fail: advance + 1 + : [] ) /** Match Input until but not including End. No match if End not found. */ -export function Until - (end: [...End], input: Input, result: string = ''): TUntil { - return ( - IsEqual(input, '') - ? [] // fail: Input is empty - : IsEnd(end, input) ? [result, input] : (() => { - const [left, right] = [input.slice(0, 1), input.slice(1)] - return Until(end, right, `${result}${left}`) - })() - ) as never +export function Until(end: [...End], input: Input, result: string = ''): TUntil { + return Match(TakeOne(input), (One, Rest) => + IsInputMatchSentinal(end, input) + ? [result, input] // ok: at sentinal + : Until(end, Rest, `${result}${One}`) // fail: advance + 1 + , () => []) as never } diff --git a/src/token/until_1.ts b/src/token/until_1.ts index 55bf017..e1b8c8b 100644 --- a/src/token/until_1.ts +++ b/src/token/until_1.ts @@ -29,7 +29,7 @@ THE SOFTWARE. // deno-fmt-ignore-file import { IsEqual } from './internal/guard.ts' -import { IsResult } from './internal/result.ts' +import { Match } from './internal/match.ts' import { type TUntil, Until } from './until.ts' // ------------------------------------------------------------------ @@ -44,15 +44,10 @@ export type TUntil_1 = ( : [] // fail: did not match Until ) /** Match Input until but not including End. No match if End not found or match is zero-length. */ -export function Until_1 - (end: [...End], input: Input): - TUntil_1 { - const until = Until(end, input) - return ( - IsResult(until) - ? IsEqual(until[0], '') - ? [] // fail: match has no characters - : until - : [] // fail: did not match Until - ) as never +export function Until_1(end: [...End], input: Input): TUntil_1 { + return Match(Until(end, input), (Until, UntilRest) => + IsEqual(Until, '') + ? [] // fail: match has no characters + : [Until, UntilRest], + () => []) as never // fail: did not match Until } diff --git a/test/parsebox/build/json.ts b/test/parsebox/build/json.ts index 9acf7b1..783aae7 100644 --- a/test/parsebox/build/json.ts +++ b/test/parsebox/build/json.ts @@ -112,6 +112,8 @@ const Coverage = Runtime.Union([ Runtime.Until_1(['x']), Runtime.Union([]), Runtime.Optional(Runtime.Ident()), + Runtime.UnsignedInteger(), + Runtime.UnsignedNumber(), Runtime.Rest(), ]) export const JsonModule = new Runtime.Module({ diff --git a/test/parsebox/runtime/unsigned_integer.ts b/test/parsebox/runtime/unsigned_integer.ts new file mode 100644 index 0000000..93fb2bd --- /dev/null +++ b/test/parsebox/runtime/unsigned_integer.ts @@ -0,0 +1,10 @@ +import { Runtime } from '@sinclair/parsebox' +import { Assert } from 'test' + +const Test = Assert.Context('Runtime.Parse.UnsignedInteger') + +Test('Should UnsignedInteger 1', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedInteger(), '-0'), [])) +Test('Should UnsignedInteger 2', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedInteger(), '0'), ['0', ''])) +Test('Should UnsignedInteger 3', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedInteger(), '00'), ['0', '0'])) +Test('Should UnsignedInteger 4', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedInteger(), '10'), ['10', ''])) +Test('Should UnsignedInteger 5', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedInteger(), ' 0'), ['0', ''])) diff --git a/test/parsebox/runtime/unsigned_number.ts b/test/parsebox/runtime/unsigned_number.ts new file mode 100644 index 0000000..d7e7540 --- /dev/null +++ b/test/parsebox/runtime/unsigned_number.ts @@ -0,0 +1,33 @@ +import { Runtime } from '@sinclair/parsebox' +import { Assert } from 'test' + +const Test = Assert.Context('Runtime.Parse.UnsignedNumber') + +Test('Should UnsignedNumber 1', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '-1'), [])) +Test('Should UnsignedNumber 2', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '-1.0'), [])) +Test('Should UnsignedNumber 3', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' -.0'), [])) +Test('Should UnsignedNumber 4', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ''), [])) +Test('Should UnsignedNumber 5', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '01'), ['0', '1'])) +Test('Should UnsignedNumber 6', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 01'), ['0', '1'])) +Test('Should UnsignedNumber 7', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '01 '), ['0', '1 '])) +Test('Should UnsignedNumber 8', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 01 '), ['0', '1 '])) +Test('Should UnsignedNumber 9', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '0'), ['0', ''])) +Test('Should UnsignedNumber 10', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '0 '), ['0', ' '])) +Test('Should UnsignedNumber 11', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 0'), ['0', ''])) +Test('Should UnsignedNumber 12', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 0 '), ['0', ' '])) +Test('Should UnsignedNumber 13', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100'), ['100', ''])) +Test('Should UnsignedNumber 14', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100 '), ['100', ' '])) +Test('Should UnsignedNumber 15', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100'), ['100', ''])) +Test('Should UnsignedNumber 16', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100 '), ['100', ' '])) +Test('Should UnsignedNumber 17', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '0.1'), ['0.1', ''])) +Test('Should UnsignedNumber 18', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '0.1 '), ['0.1', ' '])) +Test('Should UnsignedNumber 19', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 0.1'), ['0.1', ''])) +Test('Should UnsignedNumber 20', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 0.1 '), ['0.1', ' '])) +Test('Should UnsignedNumber 21', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100.1'), ['100.1', ''])) +Test('Should UnsignedNumber 22', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100.1 '), ['100.1', ' '])) +Test('Should UnsignedNumber 23', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100.1'), ['100.1', ''])) +Test('Should UnsignedNumber 24', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100.1 '), ['100.1', ' '])) +Test('Should UnsignedNumber 25', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100.1.1'), ['100.1', '.1'])) +Test('Should UnsignedNumber 26', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), '100.1.1 '), ['100.1', '.1 '])) +Test('Should UnsignedNumber 27', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100.1.1'), ['100.1', '.1'])) +Test('Should UnsignedNumber 28', () => Assert.IsEqual(Runtime.Parse(Runtime.UnsignedNumber(), ' 100.1.1 '), ['100.1', '.1 '])) diff --git a/test/parsebox/static/unsigned_integer.ts b/test/parsebox/static/unsigned_integer.ts new file mode 100644 index 0000000..d7249eb --- /dev/null +++ b/test/parsebox/static/unsigned_integer.ts @@ -0,0 +1,12 @@ +// deno-fmt-ignore-file + +import { Static } from '@sinclair/parsebox' +function Assert(): void {} + +Assert, []>() +Assert, ['0', '']>() +Assert, ['0', '0']>() +Assert, ['10', '']>() +Assert, ['0', '']>() + + diff --git a/test/parsebox/static/unsigned_number.ts b/test/parsebox/static/unsigned_number.ts new file mode 100644 index 0000000..40b60e5 --- /dev/null +++ b/test/parsebox/static/unsigned_number.ts @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file + +import { Static } from '@sinclair/parsebox' +function Assert(): void {} + +Assert, []>() +Assert, []>() +Assert, []>() +Assert, []>() +Assert, ['0', '1']>() +Assert, ['0', '1']>() +Assert, ['0', '1 ']>() +Assert, ['0', '1 ']>() +Assert, ['0', '']>() +Assert, ['0', ' ']>() +Assert, ['0', '']>() +Assert, ['0', ' ']>() +Assert, ['100', '']>() +Assert, ['100', ' ']>() +Assert, ['100', '']>() +Assert, ['100', ' ']>() +Assert, ['0.1', '']>() +Assert, ['0.1', ' ']>() +Assert, ['0.1', '']>() +Assert, ['0.1', ' ']>() +Assert, ['100.1', '']>() +Assert, ['100.1', ' ']>() +Assert, ['100.1', '']>() +Assert, ['100.1', ' ']>() +Assert, ['100.1', '.1']>() +Assert, ['100.1', '.1 ']>() +Assert, ['100.1', '.1']>() +Assert, ['100.1', '.1 ']>() \ No newline at end of file diff --git a/test/parsebox/token/unsigned_integer.ts b/test/parsebox/token/unsigned_integer.ts new file mode 100644 index 0000000..678344c --- /dev/null +++ b/test/parsebox/token/unsigned_integer.ts @@ -0,0 +1,69 @@ +import { Token } from '@sinclair/parsebox' +import { Assert } from 'test' + +const Test = Assert.Context('Token.UnsignedInteger') + +// ------------------------------------------------------------------ +// Unsigned +// ------------------------------------------------------------------ +Test('Should Integer 1', () => Assert.IsExact(Token.UnsignedInteger('0'), ['0', ''])) +Test('Should Integer 2', () => Assert.IsExact(Token.UnsignedInteger('00'), ['0', '0'])) +Test('Should Integer 3', () => Assert.IsExact(Token.UnsignedInteger('10'), ['10', ''])) +Test('Should Integer 4', () => Assert.IsExact(Token.UnsignedInteger(' 0'), ['0', ''])) +Test('Should Integer 5', () => Assert.IsExact(Token.UnsignedInteger('0 '), ['0', ' '])) +Test('Should Integer 6', () => Assert.IsExact(Token.UnsignedInteger(' 0 '), ['0', ' '])) +Test('Should Integer 7', () => Assert.IsExact(Token.UnsignedInteger(' 00'), ['0', '0'])) +Test('Should Integer 8', () => Assert.IsExact(Token.UnsignedInteger('00 '), ['0', '0 '])) +Test('Should Integer 9', () => Assert.IsExact(Token.UnsignedInteger(' 00 '), ['0', '0 '])) +Test('Should Integer 10', () => Assert.IsExact(Token.UnsignedInteger(' 10'), ['10', ''])) +Test('Should Integer 11', () => Assert.IsExact(Token.UnsignedInteger('10 '), ['10', ' '])) +Test('Should Integer 12', () => Assert.IsExact(Token.UnsignedInteger(' 10 '), ['10', ' '])) + +// ------------------------------------------------------------------ +// Digits +// ------------------------------------------------------------------ +Test('Should Integer 13', () => Assert.IsExact(Token.UnsignedInteger('0'), ['0', ''])) +Test('Should Integer 14', () => Assert.IsExact(Token.UnsignedInteger('1'), ['1', ''])) +Test('Should Integer 15', () => Assert.IsExact(Token.UnsignedInteger('2'), ['2', ''])) +Test('Should Integer 16', () => Assert.IsExact(Token.UnsignedInteger('3'), ['3', ''])) +Test('Should Integer 17', () => Assert.IsExact(Token.UnsignedInteger('4'), ['4', ''])) +Test('Should Integer 18', () => Assert.IsExact(Token.UnsignedInteger('5'), ['5', ''])) +Test('Should Integer 19', () => Assert.IsExact(Token.UnsignedInteger('6'), ['6', ''])) +Test('Should Integer 20', () => Assert.IsExact(Token.UnsignedInteger('7'), ['7', ''])) +Test('Should Integer 21', () => Assert.IsExact(Token.UnsignedInteger('8'), ['8', ''])) +Test('Should Integer 22', () => Assert.IsExact(Token.UnsignedInteger('9'), ['9', ''])) + +Test('Should Integer 23', () => Assert.IsExact(Token.UnsignedInteger('10'), ['10', ''])) +Test('Should Integer 24', () => Assert.IsExact(Token.UnsignedInteger('11'), ['11', ''])) +Test('Should Integer 25', () => Assert.IsExact(Token.UnsignedInteger('12'), ['12', ''])) +Test('Should Integer 26', () => Assert.IsExact(Token.UnsignedInteger('13'), ['13', ''])) +Test('Should Integer 27', () => Assert.IsExact(Token.UnsignedInteger('14'), ['14', ''])) +Test('Should Integer 28', () => Assert.IsExact(Token.UnsignedInteger('15'), ['15', ''])) +Test('Should Integer 29', () => Assert.IsExact(Token.UnsignedInteger('16'), ['16', ''])) +Test('Should Integer 30', () => Assert.IsExact(Token.UnsignedInteger('17'), ['17', ''])) +Test('Should Integer 31', () => Assert.IsExact(Token.UnsignedInteger('18'), ['18', ''])) +Test('Should Integer 32', () => Assert.IsExact(Token.UnsignedInteger('19'), ['19', ''])) + +// ------------------------------------------------------------------ +// Invariant +// ------------------------------------------------------------------ +Test('Should Integer 33', () => Assert.IsExact(Token.UnsignedInteger('x'), [])) +Test('Should Integer 34', () => Assert.IsExact(Token.UnsignedInteger(''), [])) + +// ------------------------------------------------------------------ +// Additional +// ------------------------------------------------------------------ +Test('Should Integer 35', () => Assert.IsExact(Token.UnsignedInteger('000'), ['0', '00'])) +Test('Should Integer 36', () => Assert.IsExact(Token.UnsignedInteger('0000'), ['0', '000'])) +Test('Should Integer 37', () => Assert.IsExact(Token.UnsignedInteger('1000'), ['1000', ''])) +Test('Should Integer 38', () => Assert.IsExact(Token.UnsignedInteger('1000x'), ['1000', 'x'])) + +// ------------------------------------------------------------------ +// Underscore: We support multiple underscore (should we?) +// ------------------------------------------------------------------ +Test('Should Integer 39', () => Assert.IsExact(Token.UnsignedInteger('10_000_000'), ['10000000', ''])) +Test('Should Integer 40', () => Assert.IsExact(Token.UnsignedInteger('10_000 _000'), ['10000', ' _000'])) +Test('Should Integer 41', () => Assert.IsExact(Token.UnsignedInteger('10_000_ 000'), ['10000', ' 000'])) +Test('Should Integer 42', () => Assert.IsExact(Token.UnsignedInteger('10__000 000'), ['10000', ' 000'])) +Test('Should Integer 43', () => Assert.IsExact(Token.UnsignedInteger('10__000__ 000'), ['10000', ' 000'])) +Test('Should Integer 44', () => Assert.IsExact(Token.UnsignedInteger('10__000 __000'), ['10000', ' __000'])) diff --git a/test/parsebox/token/unsigned_number.ts b/test/parsebox/token/unsigned_number.ts new file mode 100644 index 0000000..d2ff230 --- /dev/null +++ b/test/parsebox/token/unsigned_number.ts @@ -0,0 +1,62 @@ +import { Token } from '@sinclair/parsebox' +import { Assert } from 'test' + +const Test = Assert.Context('Token.UnsignedNumber') + +Test('Should Number 1', () => Assert.IsExact(Token.UnsignedNumber(''), [])) +Test('Should Number 2', () => Assert.IsExact(Token.UnsignedNumber('01'), ['0', '1'])) +Test('Should Number 3', () => Assert.IsExact(Token.UnsignedNumber(' 01'), ['0', '1'])) +Test('Should Number 4', () => Assert.IsExact(Token.UnsignedNumber('01 '), ['0', '1 '])) +Test('Should Number 5', () => Assert.IsExact(Token.UnsignedNumber(' 01 '), ['0', '1 '])) +Test('Should Number 6', () => Assert.IsExact(Token.UnsignedNumber('0'), ['0', ''])) +Test('Should Number 7', () => Assert.IsExact(Token.UnsignedNumber('0 '), ['0', ' '])) +Test('Should Number 8', () => Assert.IsExact(Token.UnsignedNumber(' 0'), ['0', ''])) +Test('Should Number 9', () => Assert.IsExact(Token.UnsignedNumber(' 0 '), ['0', ' '])) +Test('Should Number 10', () => Assert.IsExact(Token.UnsignedNumber('100'), ['100', ''])) +Test('Should Number 11', () => Assert.IsExact(Token.UnsignedNumber('100 '), ['100', ' '])) +Test('Should Number 12', () => Assert.IsExact(Token.UnsignedNumber(' 100'), ['100', ''])) +Test('Should Number 13', () => Assert.IsExact(Token.UnsignedNumber(' 100 '), ['100', ' '])) +Test('Should Number 14', () => Assert.IsExact(Token.UnsignedNumber('0.1'), ['0.1', ''])) +Test('Should Number 15', () => Assert.IsExact(Token.UnsignedNumber('0.1 '), ['0.1', ' '])) +Test('Should Number 16', () => Assert.IsExact(Token.UnsignedNumber(' 0.1'), ['0.1', ''])) +Test('Should Number 17', () => Assert.IsExact(Token.UnsignedNumber(' 0.1 '), ['0.1', ' '])) +Test('Should Number 18', () => Assert.IsExact(Token.UnsignedNumber('100.1'), ['100.1', ''])) +Test('Should Number 19', () => Assert.IsExact(Token.UnsignedNumber('100.1 '), ['100.1', ' '])) +Test('Should Number 20', () => Assert.IsExact(Token.UnsignedNumber(' 100.1'), ['100.1', ''])) +Test('Should Number 21', () => Assert.IsExact(Token.UnsignedNumber(' 100.1 '), ['100.1', ' '])) +Test('Should Number 22', () => Assert.IsExact(Token.UnsignedNumber('100.1.1'), ['100.1', '.1'])) +Test('Should Number 23', () => Assert.IsExact(Token.UnsignedNumber('100.1.1 '), ['100.1', '.1 '])) +Test('Should Number 24', () => Assert.IsExact(Token.UnsignedNumber(' 100.1.1'), ['100.1', '.1'])) +Test('Should Number 25', () => Assert.IsExact(Token.UnsignedNumber(' 100.1.1 '), ['100.1', '.1 '])) + +// ------------------------------------------------------------------ +// Large numbers +// ------------------------------------------------------------------ +Test('Should Number 26', () => Assert.IsExact(Token.UnsignedNumber('1234567890'), ['1234567890', ''])) + +// ------------------------------------------------------------------ +// Decimal without leading zero +// ------------------------------------------------------------------ +Test('Should Number 27', () => Assert.IsExact(Token.UnsignedNumber('.5'), ['0.5', ''])) +Test('Should Number 28', () => Assert.IsExact(Token.UnsignedNumber('+.5'), [])) + +// ------------------------------------------------------------------ +// Multiple leading zeros +// ------------------------------------------------------------------ +Test('Should Number 29', () => Assert.IsExact(Token.UnsignedNumber('000123'), ['0', '00123'])) + +// ------------------------------------------------------------------ +// Numbers next to text +// ------------------------------------------------------------------ +Test('Should Number 30', () => Assert.IsExact(Token.UnsignedNumber('100abc'), ['100', 'abc'])) + +// ------------------------------------------------------------------ +// Numbers with spaces inside (should stop at first space) +// ------------------------------------------------------------------ +Test('Should Number 31', () => Assert.IsExact(Token.UnsignedNumber('123 456'), ['123', ' 456'])) + +// ------------------------------------------------------------------ +// Just a dot (invalid number) +// ------------------------------------------------------------------ +Test('Should Number 32', () => Assert.IsExact(Token.UnsignedNumber('.'), [])) +Test('Should Number 33', () => Assert.IsExact(Token.UnsignedNumber(' . '), []))