From 1cee87fe6177c5918015e6d7587974978bfd7263 Mon Sep 17 00:00:00 2001 From: sinclair Date: Fri, 22 Nov 2024 19:47:11 +0900 Subject: [PATCH 1/2] Add Array and Optional Combinators --- example/index.ts | 4 +- example/typebox/parse.ts | 40 ++++--- example/typebox/runtime.ts | 240 ++++++++++++++++++++++++++++++------- example/typebox/static.ts | 206 ++++++++++++++++++++++++++----- package-lock.json | 14 +-- package.json | 2 +- readme.md | 81 +++++++++++-- src/runtime/guard.ts | 42 +++---- src/runtime/module.ts | 23 ++-- src/runtime/parse.ts | 131 ++++++++++++-------- src/runtime/types.ts | 147 ++++++++++++++++------- src/static/parse.ts | 67 +++++++---- src/static/types.ts | 85 +++++++++---- test/runtime/guard.ts | 45 +++++-- test/runtime/parse.ts | 110 ++++++++++------- test/static/parse.ts | 116 ++++++++++-------- 16 files changed, 960 insertions(+), 393 deletions(-) diff --git a/example/index.ts b/example/index.ts index 67b873a..2d8a55f 100644 --- a/example/index.ts +++ b/example/index.ts @@ -8,10 +8,10 @@ import { Parse } from './typebox' const T = Parse(`{ x: number, y: number, - z: number + z: number }`) -console.log(T) +console.dir(T, { depth: 100 }) // ------------------------------------------------------------------ // Example: Expression Parser diff --git a/example/typebox/parse.ts b/example/typebox/parse.ts index 1b6c4ab..b710007 100644 --- a/example/typebox/parse.ts +++ b/example/typebox/parse.ts @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- -@sinclair/typebox +@sinclair/typebox/syntax The MIT License (MIT) @@ -26,29 +26,35 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ +import * as Types from '@sinclair/typebox' import { Static } from '@sinclair/parsebox' -import { CreateType } from '@sinclair/typebox' -import { TSchema, SchemaOptions } from '@sinclair/typebox' import { Module } from './runtime' -import { Type } from './static' +import { Main } from './static' -/** `[Experimental]` Parses a TypeScript type annotation as an inferred TypeBox type */ -export function Parse = {}>(context: Context, code: Code, options?: SchemaOptions): Static.Parse[0] -/** `[Experimental]` Parses a TypeScript type annotation as an inferred TypeBox type */ -export function Parse(code: Code, options?: SchemaOptions): Static.Parse[0] -/** `[Experimental]` Parses a TypeScript type annotation as an inferred TypeBox type */ +/** `[Syntax]` Infers a TypeBox type from TypeScript syntax. */ +export type StaticParseAsSchema, Code extends string> = Static.Parse[0] + +/** `[Syntax]` Infers a TypeScript type from TypeScript syntax. */ +export type StaticParseAsType, Code extends string> = StaticParseAsSchema extends infer Type extends Types.TSchema ? Types.StaticDecode : undefined + +/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */ +export function Parse, Code extends string>(context: Context, code: Code, options?: Types.SchemaOptions): StaticParseAsSchema +/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */ +export function Parse(code: Code, options?: Types.SchemaOptions): StaticParseAsSchema<{}, Code> +/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */ export function Parse(...args: any[]): never { return ParseOnly.apply(null, args as never) as never } -/** `[Experimental]` Parses a TypeScript type annotation as TSchema */ -export function ParseOnly = {}>(context: Context, code: Code, options?: SchemaOptions): TSchema | undefined -/** `[Experimental]` Parses a TypeScript type annotation as TSchema */ -export function ParseOnly(code: Code, options?: SchemaOptions): TSchema | undefined -/** `[Experimental]` Parses a TypeScript type annotation as TSchema */ -export function ParseOnly(...args: any[]): TSchema | undefined { +/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */ +export function ParseOnly, Code extends string>(context: Context, code: Code, options?: Types.SchemaOptions): Types.TSchema | undefined +/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax */ +export function ParseOnly(code: Code, options?: Types.SchemaOptions): Types.TSchema | undefined +/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */ +export function ParseOnly(...args: any[]): Types.TSchema | undefined { const withContext = typeof args[0] === 'string' ? false : true const [context, code, options] = withContext ? [args[0], args[1], args[2] || {}] : [{}, args[0], args[1] || {}] - const type = Module.Parse('Type', code, context)[0] as TSchema | undefined - return (type !== undefined ? CreateType(type, options) : undefined) as never + const type = Module.Parse('Main', code, context)[0] as Types.TSchema | undefined + // Note: Parsing may return either a ModuleInstance or Type. We only apply options on the Type. + return Types.KindGuard.IsSchema(type) ? Types.CloneType(type, options) : type } diff --git a/example/typebox/runtime.ts b/example/typebox/runtime.ts index 29acdca..e01c4fc 100644 --- a/example/typebox/runtime.ts +++ b/example/typebox/runtime.ts @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- -@sinclair/typebox +@sinclair/typebox/syntax The MIT License (MIT) @@ -48,6 +48,7 @@ const SemiColon = ';' const SingleQuote = "'" const DoubleQuote = '"' const Tilde = '`' +const Equals = '=' // ------------------------------------------------------------------ // DestructureRight @@ -59,13 +60,140 @@ function DestructureRight(values: T[]): [T[], T | undefined] { : [values, undefined] } // ------------------------------------------------------------------ -// Reference +// Deref +// ------------------------------------------------------------------ +const Deref = (context: Types.TProperties, key: string): Types.TSchema => { + return key in context ? context[key] : Types.Ref(key) +} +// ------------------------------------------------------------------ +// ExportModifier // ------------------------------------------------------------------ // prettier-ignore -const Reference = Runtime.Ident((value, context: Record) => { - return value in context ? context[value] : Types.Ref(value) -}) +const ExportModifierMapping = (values: unknown[]) => { + return values.length === 1 +} +// prettier-ignore +const ExportModifier = Runtime.Union([ + Runtime.Tuple([Runtime.Const('export')]), Runtime.Tuple([]) +], ExportModifierMapping) + +// ------------------------------------------------------------------ +// TypeAliasDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +const TypeAliasDeclarationMapping = (_Export: boolean, _Keyword: 'type', Ident: string, _Equals: typeof Equals, Type: Types.TSchema) => { + return { [Ident]: Type } +} +// prettier-ignore +const TypeAliasDeclaration = Runtime.Tuple([ + Runtime.Ref('ExportModifier'), + Runtime.Const('type'), + Runtime.Ident(), + Runtime.Const(Equals), + Runtime.Ref('Type') +], value => TypeAliasDeclarationMapping(...value)) + +// ------------------------------------------------------------------ +// HeritageList +// ------------------------------------------------------------------ +// prettier-ignore (note, heritage list should disallow trailing comma) +const HeritageListDelimiter = Runtime.Union([Runtime.Tuple([Runtime.Const(Comma), Runtime.Const(Newline)]), Runtime.Tuple([Runtime.Const(Comma)])]) +// prettier-ignore +const HeritageListMapping = (values: string[], context: Types.TProperties): Types.TSchema[] => { + return ( + values.length === 3 ? [Deref(context, values[0]), ...values[2] as never] : + values.length === 1 ? [Deref(context, values[0])] : + [] + ) as never +} +// prettier-ignore +const HeritageList = Runtime.Union([ + Runtime.Tuple([Runtime.Ident(), HeritageListDelimiter, Runtime.Ref('HeritageList')]), + Runtime.Tuple([Runtime.Ident()]), + Runtime.Tuple([]) +], HeritageListMapping) +// ------------------------------------------------------------------ +// Heritage +// ------------------------------------------------------------------ +// prettier-ignore +const HeritageMapping = (values: unknown[]): unknown[] => { + return (values.length === 2 ? values[1] : []) as never +} +// prettier-ignore +const Heritage = Runtime.Union([ + Runtime.Tuple([Runtime.Const('extends'), Runtime.Ref('HeritageList')]), + Runtime.Tuple([]) +], HeritageMapping) +// ------------------------------------------------------------------ +// InterfaceDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +const InterfaceDeclarationMapping = (_0: boolean, _1: 'interface', Ident: string, Heritage: Types.TRef[], _4: typeof LBrace, Properties: Types.TProperties, _6: typeof RBrace) => { + return { [Ident]: Types.Intersect([...Heritage, Types.Object(Properties)]) } +} +// prettier-ignore +const InterfaceDeclaration = Runtime.Tuple([ + Runtime.Ref('ExportModifier'), + Runtime.Const('interface'), + Runtime.Ident(), + Runtime.Ref('Heritage'), + Runtime.Const(LBrace), + Runtime.Ref('Properties'), + Runtime.Const(RBrace), +], values => InterfaceDeclarationMapping(...values)) +// ------------------------------------------------------------------ +// ModuleType +// ------------------------------------------------------------------ +// prettier-ignore +const ModuleType = Runtime.Union([ + Runtime.Ref('InterfaceDeclaration'), + Runtime.Ref('TypeAliasDeclaration') +]) +// ------------------------------------------------------------------ +// ModuleProperties +// ------------------------------------------------------------------ +// prettier-ignore +const ModulePropertiesDelimiter = Runtime.Union([ + Runtime.Tuple([Runtime.Const(SemiColon), Runtime.Const(Newline)]), + Runtime.Tuple([Runtime.Const(SemiColon)]), + Runtime.Tuple([Runtime.Const(Newline)]), +]) +// prettier-ignore +const ModulePropertiesMapping = (values: unknown[]): Types.TProperties => { + return ( + values.length === 3 ? { ...values[0] as Types.TProperties, ...values[2] as Types.TProperties }: + values.length === 1 ? values[0] as Types.TProperties : + {} as Types.TProperties + ) +} +// prettier-ignore +const ModuleProperties = Runtime.Union([ + Runtime.Tuple([Runtime.Ref('ModuleType'), ModulePropertiesDelimiter, Runtime.Ref('ModuleProperties')]), + Runtime.Tuple([Runtime.Ref('ModuleType')]), + Runtime.Tuple([]), +], ModulePropertiesMapping) +// ------------------------------------------------------------------ +// ModuleDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +const ModuleIdentifier = Runtime.Union([ + Runtime.Tuple([Runtime.Ident()]), + Runtime.Tuple([]) +]) +// prettier-ignore +const ModuleDeclarationMapping = (_1: boolean, _2: 'module', _Ident: string[], _3: typeof LBrace, Properties: Types.TProperties, _5: typeof RBrace) => { + return Types.Module(Properties) +} +// prettier-ignore +const ModuleDeclaration = Runtime.Tuple([ + Runtime.Ref('ExportModifier'), Runtime.Const('module'), ModuleIdentifier, Runtime.Const(LBrace), Runtime.Ref('ModuleProperties'), Runtime.Const(RBrace) +], values => ModuleDeclarationMapping(...values)) +// ------------------------------------------------------------------ +// Reference +// ------------------------------------------------------------------ +// prettier-ignore +const Reference = Runtime.Ident((value, context: Types.TProperties) => Deref(context, value)) // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ @@ -75,7 +203,6 @@ const Literal = Runtime.Union([ Runtime.Number(value => Types.Literal(parseFloat(value))), Runtime.String([SingleQuote, DoubleQuote, Tilde], value => Types.Literal(value)) ]) - // ------------------------------------------------------------------ // Keyword // ------------------------------------------------------------------ @@ -94,7 +221,6 @@ const Keyword = Runtime.Union([ Runtime.Const('unknown', Runtime.As(Types.Unknown())), Runtime.Const('void', Runtime.As(Types.Void())), ]) - // ------------------------------------------------------------------ // KeyOf // ------------------------------------------------------------------ @@ -106,7 +232,6 @@ const KeyOfMapping = (values: unknown[]) => ( const KeyOf = Runtime.Union([ Runtime.Tuple([Runtime.Const('keyof')]), Runtime.Tuple([]) ], KeyOfMapping) - // ------------------------------------------------------------------ // IndexArray // ------------------------------------------------------------------ @@ -137,7 +262,6 @@ const Extends = Runtime.Union([ Runtime.Tuple([Runtime.Const('extends'), Runtime.Ref('Type'), Runtime.Const(Question), Runtime.Ref('Type'), Runtime.Const(Colon), Runtime.Ref('Type')]), Runtime.Tuple([]) ], ExtendsMapping) - // ------------------------------------------------------------------ // Base // ------------------------------------------------------------------ @@ -200,11 +324,12 @@ const FactorIndexArray = (Type: Types.TSchema, IndexArray: unknown[]): Types.TSc const [Left, Right] = DestructureRight(IndexArray) as [unknown[], Types.TSchema[]] return ( !Types.ValueGuard.IsUndefined(Right) ? ( - Right.length === 1 ? Types.Index(FactorIndexArray(Type, Left), Right[0]) : + // note: Indexed types require reimplementation to replace `[number]` indexers + Right.length === 1 ? Types.Index(FactorIndexArray(Type, Left), Right[0]) as never : Right.length === 0 ? Types.Array(FactorIndexArray(Type, Left)) : Types.Never() ) : Type - ) + ) } // prettier-ignore const FactorMapping = (KeyOf: boolean, Type: Types.TSchema, IndexArray: unknown[], Extends: Types.TSchema[]) => { @@ -219,7 +344,6 @@ const Factor = Runtime.Tuple([ Runtime.Ref('IndexArray'), Runtime.Ref('Extends') ], values => FactorMapping(...values)) - // ------------------------------------------------------------------ // Expr // ------------------------------------------------------------------ @@ -266,30 +390,26 @@ const Expr = Runtime.Tuple([ // Type // ------------------------------------------------------------------ const Type = Runtime.Ref('Expr') - // ------------------------------------------------------------------ // Properties // ------------------------------------------------------------------ -// prettier-ignore const PropertyKey = Runtime.Union([Runtime.Ident(), Runtime.String([SingleQuote, DoubleQuote])]) +const Readonly = Runtime.Union([Runtime.Tuple([Runtime.Const('readonly')]), Runtime.Tuple([])], (value) => value.length > 0) +const Optional = Runtime.Union([Runtime.Tuple([Runtime.Const(Question)]), Runtime.Tuple([])], (value) => value.length > 0) // prettier-ignore -const PropertyReadonly = Runtime.Union([Runtime.Tuple([Runtime.Const('readonly')]), Runtime.Tuple([])], value => value.length > 0) -// prettier-ignore -const PropertyOptional = Runtime.Union([Runtime.Tuple([Runtime.Const(Question)]), Runtime.Tuple([])], value => value.length > 0) -// prettier-ignore -const PropertyMapping = (Readonly: boolean, Key: string, Optional: boolean, _: typeof Colon, Type: Types.TSchema) => ({ +const PropertyMapping = (IsReadonly: boolean, Key: string, IsOptional: boolean, _: typeof Colon, Type: Types.TSchema) => ({ [Key]: ( - Readonly && Optional ? Types.ReadonlyOptional(Type) : - Readonly && !Optional ? Types.Readonly(Type) : - !Readonly && Optional ? Types.Optional(Type) : + IsReadonly && IsOptional ? Types.ReadonlyOptional(Type) : + IsReadonly && !IsOptional ? Types.Readonly(Type) : + !IsReadonly && IsOptional ? Types.Optional(Type) : Type ) }) // prettier-ignore const Property = Runtime.Tuple([ - Runtime.Ref('PropertyReadonly'), + Runtime.Ref('Readonly'), Runtime.Ref('PropertyKey'), - Runtime.Ref('PropertyOptional'), + Runtime.Ref('Optional'), Runtime.Const(Colon), Runtime.Ref('Type'), ], value => PropertyMapping(...value)) @@ -303,30 +423,27 @@ const PropertyDelimiter = Runtime.Union([ ]) // prettier-ignore const Properties = Runtime.Union([ - Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter'), Runtime.Ref('Properties')]), - Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter')]), - Runtime.Tuple([Runtime.Ref('Property')]), + Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter'), Runtime.Ref('Properties')]), + Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter')]), + Runtime.Tuple([Runtime.Ref('Property')]), Runtime.Tuple([]) ], values => ( - values.length === 3 ? [values[0], ...values[2] as unknown[]] : - values.length === 2 ? [values[0]] : - values.length === 1 ? [values[0]] : - [] + values.length === 3 ? { ...values[0], ...values[2] } : + values.length === 2 ? values[0] : + values.length === 1 ? values[0] : + {} )) - // ------------------------------------------------------------------ // Object // ------------------------------------------------------------------ // prettier-ignore -const ObjectMapping = (values: Record[]) => Types.Object(values.reduce((properties, record) => { - return { ...properties, ...record } -}, {} as Types.TProperties)) +const ObjectMapping = (_0: typeof LBrace, Properties: Types.TProperties, _2: typeof RBrace) => Types.Object(Properties) // prettier-ignore const _Object = Runtime.Tuple([ Runtime.Const(LBrace), - Runtime.Ref[]>('Properties'), + Runtime.Ref('Properties'), Runtime.Const(RBrace) -], values => ObjectMapping(values[1])) +], values => ObjectMapping(...values)) // ------------------------------------------------------------------ // Tuple @@ -401,7 +518,15 @@ const MappedMapping = (values: unknown[]) => { } // prettier-ignore const Mapped = Runtime.Tuple([ - Runtime.Const(LBrace), Runtime.Const(LBracket), Runtime.Ident(), Runtime.Const('in'), Runtime.Ref('Type'), Runtime.Const(RBracket), Runtime.Const(Colon), Runtime.Ref('Type'), Runtime.Const(RBrace) + Runtime.Const(LBrace), + Runtime.Const(LBracket), + Runtime.Ident(), + Runtime.Const('in'), + Runtime.Ref('Type'), + Runtime.Const(RBracket), + Runtime.Const(Colon), + Runtime.Ref('Type'), + Runtime.Const(RBrace) ], MappedMapping) // ------------------------------------------------------------------ // AsyncIterator @@ -621,11 +746,37 @@ const Date = Runtime.Const('Date', Runtime.As(Types.Date())) // Uint8Array // ------------------------------------------------------------------ const Uint8Array = Runtime.Const('Uint8Array', Runtime.As(Types.Uint8Array())) + +// ------------------------------------------------------------------ +// Main +// ------------------------------------------------------------------ +// prettier-ignore +const Main = Runtime.Union([ + ModuleDeclaration, + TypeAliasDeclaration, + InterfaceDeclaration, + Type +]) // ------------------------------------------------------------------ // Module // ------------------------------------------------------------------ // prettier-ignore export const Module = new Runtime.Module({ + // ---------------------------------------------------------------- + // Modules, Interfaces and Type Aliases + // ---------------------------------------------------------------- + ExportModifier, + HeritageList, + Heritage, + InterfaceDeclaration, + TypeAliasDeclaration, + ModuleType, + ModuleProperties, + ModuleDeclaration, + + // ---------------------------------------------------------------- + // Type Expressions + // ---------------------------------------------------------------- Literal, Keyword, KeyOf, @@ -637,10 +788,10 @@ export const Module = new Runtime.Module({ ExprTerm, ExprTail, Expr, - Type, + Type, // Alias for Expr PropertyKey, - PropertyReadonly, - PropertyOptional, + Readonly, + Optional, Property, PropertyDelimiter, Properties, @@ -674,5 +825,10 @@ export const Module = new Runtime.Module({ Uncapitalize, Date, Uint8Array, - Reference + Reference, + + // ---------------------------------------------------------------- + // Main + // ---------------------------------------------------------------- + Main }) diff --git a/example/typebox/static.ts b/example/typebox/static.ts index 1358321..ca2a379 100644 --- a/example/typebox/static.ts +++ b/example/typebox/static.ts @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- -@sinclair/typebox +@sinclair/typebox/syntax The MIT License (MIT) @@ -48,6 +48,7 @@ type SemiColon = ';' type SingleQuote = "'" type DoubleQuote = '"' type Tilde = '`' +type Equals = '=' // ------------------------------------------------------------------ // Delimit @@ -135,18 +136,156 @@ type Delimit = ], DelimitMapping> ) // ------------------------------------------------------------------ -// Reference +// Deref // ------------------------------------------------------------------ // prettier-ignore -interface ReferenceMapping extends Static.IMapping { - output: this['input'] extends [infer Key extends string] - ? Key extends keyof this['context'] - ? this['context'][Key] - : Types.TRef +type Deref = ( + Ref extends keyof Context ? Context[Ref] : Types.TRef +) +// ------------------------------------------------------------------ +// ExportModifier +// ------------------------------------------------------------------ +// prettier-ignore +interface ExportModifierMapping extends Static.IMapping { + output: this['input'] extends [string] ? true : false +} +// prettier-ignore +type ExportModifier = Static.Union<[ + Static.Tuple<[Static.Const<'export'>]>, + Static.Tuple<[]>, +], ExportModifierMapping> + +// ------------------------------------------------------------------ +// TypeAliasDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +interface TypeAliasDeclarationMapping extends Static.IMapping { + output: this['input'] extends [infer _Export extends boolean, 'type', infer Ident extends string, Equals, infer Type extends Types.TSchema] + ? { [_ in Ident]: Type } : never } -type Reference = Static.Tuple<[Static.Ident], ReferenceMapping> +// prettier-ignore +type TypeAliasDeclaration = Static.Tuple<[ + ExportModifier, Static.Const<'type'>, Static.Ident, Static.Const, Type +], TypeAliasDeclarationMapping> +// ------------------------------------------------------------------ +// HeritageList +// ------------------------------------------------------------------ +// prettier-ignore (note, heritage list should disallow trailing comma) +type HeritageListDelimiter = Static.Union<[Static.Tuple<[Static.Const, Static.Const]>, Static.Tuple<[Static.Const]>]> +// prettier-ignore +type HeritageListReduce = ( + Values extends [infer Ref extends string, ...infer Rest extends string[]] + ? HeritageListReduce]> + : Result +) +// prettier-ignore +interface HeritageListMapping extends Static.IMapping { + output: ( + this['context'] extends Types.TProperties ? + this['input'] extends string[] + ? HeritageListReduce + : [] + : [] + ) +} +// prettier-ignore +type HeritageList = Static.Union<[Delimit], HeritageListMapping> +// ------------------------------------------------------------------ +// Heritage +// ------------------------------------------------------------------ +// prettier-ignore +interface HeritageMapping extends Static.IMapping { + output: this['input'] extends ['extends', infer List extends Types.TSchema[]] ? List : [] +} +// prettier-ignore +type Heritage = Static.Union<[ + Static.Tuple<[Static.Const<'extends'>, HeritageList]>, + Static.Tuple<[]> +], HeritageMapping> +// ------------------------------------------------------------------ +// InterfaceDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +interface InterfaceDeclarationMapping extends Static.IMapping { + output: this['input'] extends [boolean, 'interface', infer Ident extends string, infer Heritage extends Types.TSchema[], LBrace, infer Properties extends Types.TProperties, RBrace] + ? { [_ in Ident]: Types.TIntersectEvaluated<[...Heritage, Types.TObject]> } + : never +} +// prettier-ignore +type InterfaceDeclaration = Static.Tuple<[ + ExportModifier, + Static.Const<'interface'>, + Static.Ident, + Heritage, + Static.Const, + Properties, + Static.Const, +], InterfaceDeclarationMapping> +// ------------------------------------------------------------------ +// ModuleType +// ------------------------------------------------------------------ +// prettier-ignore +type ModuleType = Static.Union<[ + InterfaceDeclaration, + TypeAliasDeclaration +]> +// ------------------------------------------------------------------ +// ModuleProperties +// ------------------------------------------------------------------ +// prettier-ignore +type ModulePropertiesDelimiter = Static.Union<[ + Static.Tuple<[Static.Const, Static.Const]>, + Static.Tuple<[Static.Const]>, + Static.Tuple<[Static.Const]>, +]> +// prettier-ignore +type ModulePropertiesReduce = ( + Value extends [infer ModuleType extends Types.TProperties, unknown[], ...infer Rest extends unknown[]] ? ModulePropertiesReduce : + Value extends [infer ModuleType extends Types.TProperties] ? ModulePropertiesReduce<[], Result & ModuleType> : + Types.Evaluate +) +// prettier-ignore +interface ModulePropertiesMapping extends Static.IMapping { + output: this['input'] extends unknown[] ? ModulePropertiesReduce : never +} +// prettier-ignore +type ModuleProperties = Static.Union<[ + Static.Tuple<[ModuleType, ModulePropertiesDelimiter, ModuleProperties]>, + Static.Tuple<[ModuleType]>, + Static.Tuple<[]>, +], ModulePropertiesMapping> +// ------------------------------------------------------------------ +// ModuleDeclaration +// ------------------------------------------------------------------ +// prettier-ignore +type ModuleIdentifier = Static.Union<[ + Static.Tuple<[Static.Ident]>, + Static.Tuple<[]> +]> +// prettier-ignore +interface ModuleDeclarationMapping extends Static.IMapping { + output: this['input'] extends [boolean, 'module', infer _Ident extends string[], LBrace, infer Properties extends Types.TProperties, RBrace] + ? Types.TModule + : never +} +// prettier-ignore +type ModuleDeclaration = Static.Tuple<[ + ExportModifier, Static.Const<'module'>, ModuleIdentifier, Static.Const, ModuleProperties, Static.Const +], ModuleDeclarationMapping> +// ------------------------------------------------------------------ +// Reference +// ------------------------------------------------------------------ +// prettier-ignore +interface ReferenceMapping extends Static.IMapping { + output: this['context'] extends Types.TProperties + ? this['input'] extends string + ? Deref + : never + : never +} +type Reference = Static.Ident // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ @@ -198,7 +337,6 @@ type KeyOf = Static.Union<[ Static.Tuple<[Static.Const<'keyof'>]>, Static.Tuple<[]> ], KeyOfMapping> - // ------------------------------------------------------------------ // IndexArray // ------------------------------------------------------------------ @@ -231,7 +369,6 @@ type Extends = Static.Union<[ Static.Tuple<[Static.Const<'extends'>, Type, Static.Const, Type, Static.Const, Type]>, Static.Tuple<[]> ], ExtendsMapping> - // ------------------------------------------------------------------ // Base // ------------------------------------------------------------------ @@ -361,7 +498,7 @@ type Expr = Static.Tuple<[ // ------------------------------------------------------------------ // Type // ------------------------------------------------------------------ -export type Type = Expr +type Type = Expr // ------------------------------------------------------------------ // Properties // ------------------------------------------------------------------ @@ -370,34 +507,30 @@ interface PropertyKeyStringMapping extends Static.IMapping { output: this['input'] } type PropertyKeyString = Static.String<[SingleQuote, DoubleQuote], PropertyKeyStringMapping> - type PropertyKey = Static.Union<[Static.Ident, PropertyKeyString]> // prettier-ignore -interface PropertyReadonlyMapping extends Static.IMapping { +interface ReadonlyMapping extends Static.IMapping { output: this['input'] extends ['readonly'] ? true : false } -type PropertyReadonly = Static.Union<[Static.Tuple<[Static.Const<'readonly'>]>, Static.Tuple<[]>], PropertyReadonlyMapping> +type Readonly = Static.Union<[Static.Tuple<[Static.Const<'readonly'>]>, Static.Tuple<[]>], ReadonlyMapping> // prettier-ignore -interface PropertyOptionalMapping extends Static.IMapping { +interface OptionalMapping extends Static.IMapping { output: this['input'] extends [Question] ? true : false } -type PropertyOptional = Static.Union<[Static.Tuple<[Static.Const]>, Static.Tuple<[]>], PropertyOptionalMapping> +type Optional = Static.Union<[Static.Tuple<[Static.Const]>, Static.Tuple<[]>], OptionalMapping> // prettier-ignore interface PropertyMapping extends Static.IMapping { - output: this['input'] extends [infer Readonly extends boolean, infer Key extends string, infer Optional extends boolean, string, infer Type extends Types.TSchema] + output: this['input'] extends [infer IsReadonly extends boolean, infer Key extends string, infer IsOptional extends boolean, string, infer Type extends Types.TSchema] ? { [_ in Key]: ( - [Readonly, Optional] extends [true, true] ? Types.TReadonlyOptional : - [Readonly, Optional] extends [true, false] ? Types.TReadonly : - [Readonly, Optional] extends [false, true] ? Types.TOptional : + [IsReadonly, IsOptional] extends [true, true] ? Types.TReadonlyOptional : + [IsReadonly, IsOptional] extends [true, false] ? Types.TReadonly : + [IsReadonly, IsOptional] extends [false, true] ? Types.TOptional : Type ) } : never } - -type Property = Static.Tuple<[PropertyReadonly, PropertyKey, PropertyOptional, Static.Const, Type], PropertyMapping> - -type PropertiesEvaluate = { [K in keyof T]: T[K] } & {} +type Property = Static.Tuple<[Readonly, PropertyKey, Optional, Static.Const, Type], PropertyMapping> // prettier-ignore type PropertyDelimiter = Static.Union<[ Static.Tuple<[Static.Const, Static.Const]>, @@ -409,7 +542,7 @@ type PropertyDelimiter = Static.Union<[ // prettier-ignore type PropertiesReduce = ( PropertiesArray extends [infer Left extends Types.TProperties, ...infer Right extends Types.TProperties[]] - ? PropertiesReduce> + ? PropertiesReduce> : Result ) // prettier-ignore @@ -485,7 +618,7 @@ type Constructor = Static.Tuple<[ // ------------------------------------------------------------------ // prettier-ignore interface MappedMapping extends Static.IMapping { - output: this['input'] extends [LBrace, LBracket, infer Key extends string, 'in', infer Right extends Types.TSchema, RBracket, Colon, infer Type extends Types.TSchema, RBrace] + output: this['input'] extends [LBrace, LBracket, infer _Key extends string, 'in', infer _Right extends Types.TSchema, RBracket, Colon, infer Type extends Types.TSchema, RBrace] ? Types.TLiteral<'Mapped types not supported'> : this['input'] } @@ -643,7 +776,7 @@ type Partial = Static.Tuple<[ // prettier-ignore interface RequiredMapping extends Static.IMapping { output: this['input'] extends ['Required', LAngle, infer Type extends Types.TSchema, RAngle] - ? Types.TPartial + ? Types.TRequired : never } // prettier-ignore @@ -655,8 +788,8 @@ type Required = Static.Tuple<[ // ------------------------------------------------------------------ // prettier-ignore interface PickMapping extends Static.IMapping { - output: this['input'] extends ['Pick', LAngle, infer Type extends Types.TSchema, Comma, infer PropertyKey extends Types.TSchema, RAngle] - ? Types.TPick> + output: this['input'] extends ['Pick', LAngle, infer Type extends Types.TSchema, Comma, infer Key extends Types.TSchema, RAngle] + ? Types.TPick : never } // prettier-ignore @@ -668,8 +801,8 @@ type Pick = Static.Tuple<[ // ------------------------------------------------------------------ // prettier-ignore interface OmitMapping extends Static.IMapping { - output: this['input'] extends ['Omit', LAngle, infer Type extends Types.TSchema, Comma, infer PropertyKey extends Types.TSchema, RAngle] - ? Types.TOmit> + output: this['input'] extends ['Omit', LAngle, infer Type extends Types.TSchema, Comma, infer Key extends Types.TSchema, RAngle] + ? Types.TOmit : never } // prettier-ignore @@ -762,3 +895,14 @@ type Date = Static.Const<'Date', Static.As> // Uint8Array // ------------------------------------------------------------------ type Uint8Array = Static.Const<'Uint8Array', Static.As> + +// ------------------------------------------------------------------ +// Main +// ------------------------------------------------------------------ +// prettier-ignore +export type Main = Static.Union<[ + ModuleDeclaration, + TypeAliasDeclaration, + InterfaceDeclaration, + Type +]> diff --git a/package-lock.json b/package-lock.json index 8a10c6e..a5839da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.18.0", - "@sinclair/typebox": "^0.33.12", + "@sinclair/typebox": "^0.34.7", "@types/mocha": "^10.0.9", "@types/node": "^22.7.4", "mocha": "^10.8.2", @@ -47,9 +47,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.33.17", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.33.17.tgz", - "integrity": "sha512-75232GRx3wp3P7NP+yc4nRK3XUAnaQShxTAzapgmQrgs0QvSq0/mOJGoZXRpH15cFCKyys+4laCPbBselqJ5Ag==", + "version": "0.34.7", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.7.tgz", + "integrity": "sha512-DUwBGDigO05iE79KzvYj/5qlrX4ZV2ccDN8SAMwqIr37xQTEoaKY2w3PD3pVzkhV9lGaJlyE0YMWC94HuAnftw==", "dev": true }, "node_modules/@types/mocha": { @@ -1317,9 +1317,9 @@ } }, "@sinclair/typebox": { - "version": "0.33.17", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.33.17.tgz", - "integrity": "sha512-75232GRx3wp3P7NP+yc4nRK3XUAnaQShxTAzapgmQrgs0QvSq0/mOJGoZXRpH15cFCKyys+4laCPbBselqJ5Ag==", + "version": "0.34.7", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.7.tgz", + "integrity": "sha512-DUwBGDigO05iE79KzvYj/5qlrX4ZV2ccDN8SAMwqIr37xQTEoaKY2w3PD3pVzkhV9lGaJlyE0YMWC94HuAnftw==", "dev": true }, "@types/mocha": { diff --git a/package.json b/package.json index 8903620..bae0e8a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@sinclair/hammer": "^0.18.0", - "@sinclair/typebox": "^0.33.12", + "@sinclair/typebox": "^0.34.7", "@types/mocha": "^10.0.9", "@types/node": "^22.7.4", "mocha": "^10.8.2", diff --git a/readme.md b/readme.md index 9132c5e..0f6b91e 100644 --- a/readme.md +++ b/readme.md @@ -23,12 +23,12 @@ $ npm install @sinclair/parsebox ## Example -ParseBox provides symmetric parsing in Static and Runtime environments. +ParseBox provides combinators for parsing in Runtime and Static environments. -```typescript -import { Runtime, Static } from '@sinclair/parsebox' +### Runtime -// Runtime Parser +```typescript +import { Runtime } from '@sinclair/parsebox' const T = Runtime.Tuple([ Runtime.Const('X'), @@ -37,8 +37,12 @@ const T = Runtime.Tuple([ ]) const R = Runtime.Parse(T, 'X Y Z W') // const R = [['X', 'Y', 'Z'], ' W'] +``` -// Static Parser +### Static + +```typescript +import { Static } from '@sinclair/parsebox' type T = Static.Tuple<[ Static.Const<'X'>, @@ -49,35 +53,38 @@ type T = Static.Tuple<[ type R = Static.Parse // type R = [['X', 'Y', 'Z'], ' W'] ``` -Which can be used to construct [Expression](https://www.typescriptlang.org/play/?moduleResolution=99&module=199#code/JYWwDg9gTgLgBAbzgZRgQxsAxnAvnAMyghDgHIABAZ2ADssAbNYKAejDSioFMAjCAB5kAUMNas4AWmkzZc+QsVLlKyWIkBRAWnANu6qaqPGTq0TACeYbnABK3KgFcG8ALwp0mLADoACpx4AHi0wKAAacgE4ACo4AAoLOABqOAAvAEoyAD44XNzxOEtrOwdnNzgAbQRhPNq6+obGpuaC3L0CGAAuOAAiAR6AbhrmkdGxuta4CGsoDGhunujB4fHVtYmJXKhgAHMACy7EFfWT8cna9sOei2XTu7HzvOnuWZh53qTb++-Gx63dg4LVJfH6gvKTXBDMHQuAFXARHo9AC6ogMpnRGNMBhCUAcNAgtDg-i4LzRmPJFPkojoMBeBDQWBsADlHCAXtgALJoMBgOg7ODcAS02gAEyoHgw2G8AEkuTy+UdchBHDAwCrujA9sAqBUyHQ1TAyEiBULuKLxQADAAkCDoBBeJScLhNwrFcForN4L1wFrgAH5HWU4N1aNwAG6k3DCIrM1nsnDuVCSnwskBeqCBVPxuW82g7LLmKw2ADyMzQorgic8UoAqrRgATAhUk14ZSKzTAIlntlgkQXRAUAEJ0TgWHN87r2EWORnikAYLB7bgiuAAFRepHLK5xcBpEDgvBHUESgtCeIbhNoEHb0aLcGHtFHU5n3ECABluB0XWa3Y5aABrK8AHdaAiWwAXgQVXXFP9AIgECKj7St4mGcD9kg01zUqO0HVLF45nCXdaHtKA7AgiIcNI+wqAw6C4Fg4DaEQlFagDJBLm6D8OgiZ5XnePC+MI7Z0O6B8n2XF9AjQg4wIcGAcijWpOM-GBhHSYQaTpBkbDE49xzzb8sJbKVZW5XN+WqJUVQNDUtR1PVaANI1DLdCpKLgLjOyIkiyPQlyYIAxjmOOANdIsZ9GXfFSwIggslPdcNIwMAAxBk3igfSdm6OUBWATUHR2YhHGsbcBFCKZSIErcNNoWkoHpRk4FSrB0sy-yJVbUz5QMyypms9V4jgY5NW1XV9RVZyoJ-cVdTiMgKOIh0cQiMhMmNAMd06Ya7LGxyJuNKasLcxbKrLUV1rgKqKy22pQwjKBhnUqMYyatLoGQ4yfDrC8m2GT7vFXYq9Cbf6AGECRowIyDmrIImWjqpXB2hIdW7I+zCP7qx8QGwGBiorpFdHhCRCJmtasy+X7F71ygEBV2YBgPqx7xvsbCpMeTAGgdfZtmaRlHomyUm3sImm6YZoncn+nG8bBiGYCh1ghdelroAiMX6eABhJYR7HuabIm+0LYocU1xmq051naF+qXmZlnm5eRhWyCSZWxbhsqoDNnXpf13nOf553JDdjcPdCb3YY51t7YNyOjdvYoxaZzmY4qMm1bXDdvYiMLMqpu8dwt6O-fduBTYlnOjzHCm837IA) or even [Type Syntax](https://www.typescriptlang.org/play/?moduleResolution=99&module=199#code/JYWwDg9gTgLgBAbzgBQIZQM4FM4F84BmUEIcA5AAIbAB2AxgDarBQD0MAnmFgEYQAerDBxoxU-MgChQkWIjgBlMTGB08hYqUrV6TFuy68BUyXQg0M8AGIQIcALwp02ABQADEBAAmAVwY4bOwRJSTg4Tm44ACEHULCUVQBrAB4AQQAacmAvMgA+OAAyODiwtFhgVAZkgHkQYBg0zLJsvNyQsIicVIdEErhsgC44SyhaAHM+mm8sDCHUgG0AXTjcENw3AEoQswt4GMdAgDoASXBoGBcyKLItyU7onqVUFTpkzogCaPz4n9-WVnChgejmCvzB4IhkPi-1+g2GMFGNDGAG4+lD0Ri4DD8EVQZj8VCYT8pl4ZgB+IZ4gnUv4AiFwkbjVE0lnQungkkzIaHHlLZmsllE+K4JZwAA+cB8NFJBFoWC8-IF1OxQA) parsers at runtime and inside the type system. ## Overview -ParseBox is a TypeScript parsing library designed to embed domain-specific languages (DSLs) within the TypeScript type system. It provides a core set of declarative combinators for parsing in Static and Runtime environments. The parsing structures created with this library can be replicated across environments to ensure symmetric parsing logic in each environment (Static or Runtime). Declarative parsing structures help to mitigate many problems that arise when replicating explicit logic in different languages, with the declarative combinators leading to more managable code overall. +ParseBox is a parsing system designed to embed domain-specific languages (DSLs) within the TypeScript type system. It provides a set of type-level and runtime combinators that directly map to BNF notation, which can be used to parse content either at runtime or statically within the type system. -This project is written as a parsing infrastructure for the [TypeBox](https://github.com/sinclairzx81/typebox) and [LinqBox](https://github.com/sinclairzx81/linqbox) projects. It is offered as a standalone package for experimentation, as well as to provide a foundation for advancing string DSL parsing in the TypeScript type system. +ParseBox is written as a parsing infrastructure for the [TypeBox](https://github.com/sinclairzx81/typebox) and [LinqBox](https://github.com/sinclairzx81/linqbox) projects, enabling these libraries to support advanced runtime parsing for their respective domains. License MIT ## Contents -- [Parsers](#Parsers) +- [Combinators](#Combinators) - [Const](#Const) - [Tuple](#Tuple) - [Union](#Union) + - [Array](#Array) + - [Optional](#Optional) - [Terminals](#Terminals) - [Number](#Number) - [String](#String) - [Ident](#Ident) +- [Extended](#Extended) - [Mapping](#Mapping) -- [Context](#Context) +- [Context](#Context) - [Modules](#Modules) +- [Extended](#Extended) - [Advanced](#Advanced) - [Contribute](#Contribute) -## Parsers +## Combinators -ParseBox provides three main combinators `Const`, `Tuple`, and `Union` which are named after the types of values they parse. These combinators are used to express BNF (Backus-Naur Form) grammar within the type system. These combinators service as fundamental building blocks for constructing higher order parsers. +ParseBox provides a set of combinators that map to notation expressible in (E)BNF (Backus-Naur Form). These combinators are named Const, Tuple, Union, Array, and Optional, respectively, to describe the types produced by each combinator. These combinators serve as building blocks for constructing parsers. ### Const @@ -155,6 +162,54 @@ const R2 = Runtime.Parse(P, 'Y E') // const R2 = ['Y', ' E'] const R3 = Runtime.Parse(P, 'Z E') // const R3 = ['Z', ' E'] ``` +### Array + +The Array combinator will parse for zero or more the interior parser. This combinator will always return a result with an empty array given for no matches. + +**EBNF** + +``` + ::= { "X" } +``` + +**TypeScript** + +```typescript +const T = Runtime.Array( // const T = { + Runtime.Const('X') // type: 'Array', +) // parser: { type: 'Const', value: 'X' } + // } + +const R1 = Runtime.Parse(T, 'X Y Z') // const R1 = [['X'], ' Y Z'] + +const R2 = Runtime.Parse(T, 'X X X Y Z') // const R2 = [['X', 'X', 'X'], ' Y Z'] + +const R3 = Runtime.Parse(T, 'Y Z') // const R3 = [[], 'Y Z'] +``` + +### Optional + +The Optional combinator will parse for zero or one of the interior parser. This combinator always succeeds, returning either a tuple with one element, or zero elements for no match. + +**EBNF** + +``` + ::= [ "X" ] +``` + +**TypeScript** + +```typescript +const T = Runtime.Optional( // const T = { + Runtime.Const('X') // type: 'Optional', +) // parser: { type: 'Const', value: 'X' } + // } + +const R1 = Runtime.Parse(T, 'X Y Z') // const R1 = ['X', ' Y Z'] + +const R2 = Runtime.Parse(T, 'Y Z') // const R2 = [[], 'Y Z'] +``` + ## Terminals ParseBox provides combinators that can be used to parse common terminals. @@ -179,7 +234,7 @@ const E = Runtime.Parse(T, '01') // const E = [] ### String -The String combinator will parse for quoted string literals. This combinator accepts an array of permissable quote characters. The result of this parser is the interior wrapped string. +The String combinator will parse for quoted string literals. Thgit is combinator accepts an array of permissable quote characters. The result of this parser is the interior wrapped string. ```typescript const T = Runtime.String(['"']) diff --git a/src/runtime/guard.ts b/src/runtime/guard.ts index fa002e4..80ef3cf 100644 --- a/src/runtime/guard.ts +++ b/src/runtime/guard.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IIdent, INumber, IRef, IString, IConst, ITuple, IUnion } from './types' +import { IArray, IConst, IIdent, INumber, IOptional, IRef, IString, ITuple, IUnion } from './types' // ------------------------------------------------------------------ // Value Guard @@ -46,51 +46,43 @@ function IsArrayValue(value: unknown): value is unknown[] { // ------------------------------------------------------------------ // Parser Guard // ------------------------------------------------------------------ -/** Returns true if the value is a Tuple Parser */ -// prettier-ignore -export function IsTuple(value: unknown): value is ITuple { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Tuple' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) -} -/** Returns true if the value is a Union Parser */ -// prettier-ignore -export function IsUnion(value: unknown): value is IUnion { - return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Union' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) +/** Returns true if the value is a Array Parser */ +export function IsArray(value: unknown): value is IArray { + return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Array' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser) } /** Returns true if the value is a Const Parser */ -// prettier-ignore export function IsConst(value: unknown): value is IConst { return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Const' && HasPropertyKey(value, 'value') && typeof value.value === 'string' } /** Returns true if the value is a Ident Parser */ -// prettier-ignore export function IsIdent(value: unknown): value is IIdent { return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ident' } /** Returns true if the value is a Number Parser */ -// prettier-ignore export function IsNumber(value: unknown): value is INumber { return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Number' } +/** Returns true if the value is a Optional Parser */ +export function IsOptional(value: unknown): value is IOptional { + return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Optional' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser) +} /** Returns true if the value is a Ref Parser */ -// prettier-ignore export function IsRef(value: unknown): value is IRef { return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ref' && HasPropertyKey(value, 'ref') && typeof value.ref === 'string' } /** Returns true if the value is a String Parser */ -// prettier-ignore export function IsString(value: unknown): value is IString { return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'String' && HasPropertyKey(value, 'options') && IsArrayValue(value.options) } +/** Returns true if the value is a Tuple Parser */ +export function IsTuple(value: unknown): value is ITuple { + return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Tuple' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) +} +/** Returns true if the value is a Union Parser */ +export function IsUnion(value: unknown): value is IUnion { + return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Union' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers) +} /** Returns true if the value is a Parser */ -// prettier-ignore export function IsParser(value: unknown) { - return ( - IsTuple(value) || - IsUnion(value) || - IsConst(value) || - IsIdent(value) || - IsNumber(value) || - IsRef(value) || - IsString(value) - ) + return IsArray(value) || IsConst(value) || IsIdent(value) || IsNumber(value) || IsOptional(value) || IsRef(value) || IsString(value) || IsTuple(value) || IsUnion(value) } diff --git a/src/runtime/module.ts b/src/runtime/module.ts index 4384ce4..6df334c 100644 --- a/src/runtime/module.ts +++ b/src/runtime/module.ts @@ -32,20 +32,23 @@ import { Parse } from './parse' // ------------------------------------------------------------------ // Module // ------------------------------------------------------------------ -// prettier-ignore export class Module { - constructor(private readonly properties: Properties) { } - + constructor(private readonly properties: Properties) {} + /** Parses using one of the parsers defined on this instance */ - public Parse(key: Key, code: string, context: unknown): [] | [Types.StaticParser, string] + public Parse(key: Key, content: string, context: unknown): [] | [Types.StaticParser, string] /** Parses using one of the parsers defined on this instance */ - public Parse(key: Key, code: string): [] | [Types.StaticParser, string] + public Parse(key: Key, content: string): [] | [Types.StaticParser, string] /** Parses using one of the parsers defined on this instance */ public Parse(...args: any[]): never { - const [key, code, context] = - args.length === 3 ? [args[0], args[1], args[2]] : - args.length === 2 ? [args[0], args[1], undefined] : - (() => { throw Error('Invalid parse arguments') })() - return Parse(this.properties[key], this.properties, code, context) as never + const [key, content, context] = + args.length === 3 + ? [args[0], args[1], args[2]] + : args.length === 2 + ? [args[0], args[1], undefined] + : (() => { + throw Error('Invalid parse arguments') + })() + return Parse(this.properties, this.properties[key], content, context) as never } } diff --git a/src/runtime/parse.ts b/src/runtime/parse.ts index 73ecf2f..8993ead 100644 --- a/src/runtime/parse.ts +++ b/src/runtime/parse.ts @@ -31,48 +31,59 @@ import * as Token from './token' import * as Types from './types' // ------------------------------------------------------------------ -// Tuple +// Array // ------------------------------------------------------------------ -// prettier-ignore -function ParseTuple(parsers: [...Parsers], properties: Properties, code: string, context: unknown): [] | [unknown[], string] { +function ParseArray(moduleProperties: ModuleProperties, parser: Parser, code: string, context: unknown): unknown[] { const buffer = [] as unknown[] let rest = code - for(const parser of parsers) { - const result = ParseParser(parser, properties, rest, context) - if(result.length === 0) return [] + while (rest.length > 0) { + const result = ParseParser(moduleProperties, parser, rest, context) + if (result.length === 0) return [buffer, rest] buffer.push(result[0]) rest = result[1] } return [buffer, rest] } + // ------------------------------------------------------------------ -// Union +// Const // ------------------------------------------------------------------ -// prettier-ignore -function ParseUnion(parsers: [...Parsers], properties: Properties, code: string, context: unknown): [] | [unknown, string] { - for(const parser of parsers) { - const result = ParseParser(parser, properties, code, context) - if(result.length === 0) continue - return result - } - return [] +function ParseConst(value: Value, code: string, context: unknown): [] | [Value, string] { + return Token.Const(value, code) as never } + // ------------------------------------------------------------------ -// Const +// Ident +// ------------------------------------------------------------------ +function ParseIdent(code: string, _context: unknown): [] | [string, string] { + return Token.Ident(code) +} + +// ------------------------------------------------------------------ +// Number // ------------------------------------------------------------------ // prettier-ignore -function ParseConst(value: Value, code: string, context: unknown): [] | [Value, string] { - return Token.Const(value, code) as never +function ParseNumber(code: string, _context: unknown): [] | [string, string] { + return Token.Number(code) +} + +// ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +function ParseOptional(moduleProperties: ModuleProperties, parser: Parser, code: string, context: unknown): [] | [[unknown] | [], unknown] { + const result = ParseParser(moduleProperties, parser, code, context) + return (result.length === 2 ? [[result[0]], result[1]] : [[], code]) as never } + // ------------------------------------------------------------------ // Ref // ------------------------------------------------------------------ -// prettier-ignore -function ParseRef(ref: Ref, properties: Properties, code: string, context: unknown): [] | [string, string] { - const parser = properties[ref] - if(!Guard.IsParser(parser)) throw Error(`Cannot dereference parser '${ref}'`) - return ParseParser(parser, properties, code, context) as never +function ParseRef(moduleProperties: ModuleProperties, ref: Ref, code: string, context: unknown): [] | [string, string] { + const parser = moduleProperties[ref] + if (!Guard.IsParser(parser)) throw Error(`Cannot dereference Parser '${ref}'`) + return ParseParser(moduleProperties, parser, code, context) as never } + // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ @@ -80,62 +91,80 @@ function ParseRef(moduleProperties: ModuleProperties, parsers: [...Parsers], code: string, context: unknown): [] | [unknown[], string] { + const buffer = [] as unknown[] + let rest = code + for (const parser of parsers) { + const result = ParseParser(moduleProperties, parser, rest, context) + if (result.length === 0) return [] + buffer.push(result[0]) + rest = result[1] + } + return [buffer, rest] } + // ------------------------------------------------------------------ -// Ident +// Union // ------------------------------------------------------------------ // prettier-ignore -function ParseIdent(code: string, _context: unknown): [] | [string, string] { - return Token.Ident(code) +function ParseUnion(moduleProperties: ModuleProperties, parsers: [...Parsers], code: string, context: unknown): [] | [unknown, string] { + for(const parser of parsers) { + const result = ParseParser(moduleProperties, parser, code, context) + if(result.length === 0) continue + return result + } + return [] } + // ------------------------------------------------------------------ // Parser // ------------------------------------------------------------------ // prettier-ignore -function ParseParser(parser: Parser, properties: Types.IModuleProperties, code: string, context: unknown): [] | [Types.StaticParser, string] { - const result = ( - Guard.IsTuple(parser) ? ParseTuple(parser.parsers, properties, code, context) : - Guard.IsUnion(parser) ? ParseUnion(parser.parsers, properties, code, context) : +function ParseParser(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser, string] { + const production = ( + Guard.IsArray(parser) ? ParseArray(moduleProperties, parser.parser, code, context) : Guard.IsConst(parser) ? ParseConst(parser.value, code, context) : - Guard.IsRef(parser) ? ParseRef(parser.ref, properties, code, context) : - Guard.IsString(parser) ? ParseString(parser.options, code, context) : Guard.IsIdent(parser) ? ParseIdent(code, context) : Guard.IsNumber(parser) ? ParseNumber(code, context) : + Guard.IsOptional(parser) ? ParseOptional(moduleProperties, parser.parser, code, context) : + Guard.IsRef(parser) ? ParseRef(moduleProperties, parser.ref, code, context) : + Guard.IsString(parser) ? ParseString(parser.options, code, context) : + Guard.IsTuple(parser) ? ParseTuple(moduleProperties, parser.parsers, code, context) : + Guard.IsUnion(parser) ? ParseUnion(moduleProperties, parser.parsers, code, context) : [] ) return ( - result.length === 2 - ? [parser.mapping(result[0], context), result[1]] - : result + production.length === 2 + ? [parser.mapping(production[0], context), production[1]] + : production ) as never } + // ------------------------------------------------------------------ // Parse // ------------------------------------------------------------------ -/** Parses content using the given parser */ +/** Parses content using the given Parser */ // prettier-ignore -export function Parse(parser: Parser, properties: Types.IModuleProperties, code: string, context: unknown): [] | [Types.StaticParser, string] -/** Parses content using the given parser */ +export function Parse(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser, string] +/** Parses content using the given Parser */ // prettier-ignore -export function Parse(parser: Parser, properties: Types.IModuleProperties, code: string): [] | [Types.StaticParser, string] -/** Parses content using the given parser */ +export function Parse(moduleProperties: Types.IModuleProperties, parser: Parser, code: string): [] | [Types.StaticParser, string] +/** Parses content using the given Parser */ // prettier-ignore -export function Parse(parser: Parser, code: string, context: unknown): [] | [Types.StaticParser, string] -/** Parses content using the given parser */ +export function Parse(parser: Parser, content: string, context: unknown): [] | [Types.StaticParser, string] +/** Parses content using the given Parser */ // prettier-ignore -export function Parse(parser: Parser, code: string): [] | [Types.StaticParser, string] +export function Parse(parser: Parser, content: string): [] | [Types.StaticParser, string] /** Parses content using the given parser */ // prettier-ignore export function Parse(...args: any[]): never { - const withProperties = typeof args[1] === 'string' ? false : true - const [parser, properties, code, context] = withProperties + const withModuleProperties = typeof args[1] === 'string' ? false : true + const [moduleProperties, parser, content, context] = withModuleProperties ? [args[0], args[1], args[2], args[3]] - : [args[0], {}, args[1], args[2]] - return ParseParser(parser, properties, code, context) as never + : [{}, args[0], args[1], args[2]] + return ParseParser(moduleProperties, parser, content, context) as never } diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 53dd047..fb8e0ac 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -32,6 +32,9 @@ export type IModuleProperties = Record // Static // ------------------------------------------------------------------ +/** Force output static type evaluation for Arrays */ +export type StaticEnsure = T extends infer R ? R : never + /** Infers the Output Parameter for a Parser */ export type StaticParser = Parser extends IParser ? Output : unknown @@ -44,10 +47,8 @@ export type IMapping value /** Maps the output as the given parameter T */ -export const As = - (mapping: T): ((value: unknown) => T) => - (_: unknown) => - mapping +// prettier-ignore +export const As = (mapping: T): ((value: unknown) => T) => (_: unknown) => mapping // ------------------------------------------------------------------ // Parser @@ -56,51 +57,40 @@ export interface IParser { type: string mapping: IMapping } -// ------------------------------------------------------------------ -// Tuple -// ------------------------------------------------------------------ -export type TupleParameter = Parsers extends [infer L extends IParser, ...infer R extends IParser[]] ? TupleParameter]> : Result -export interface ITuple extends IParser { - type: 'Tuple' - parsers: IParser[] -} -/** Creates a Tuple parser */ -export function Tuple>>(parsers: [...Parsers], mapping: Mapping): ITuple> -/** Creates a Tuple parser */ -export function Tuple(parsers: [...Parsers]): ITuple> -export function Tuple(...args: unknown[]): never { - const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] - return { type: 'Tuple', parsers, mapping } as never -} // ------------------------------------------------------------------ -// Union +// Array // ------------------------------------------------------------------ -export type UnionParameter = Parsers extends [infer L extends IParser, ...infer R extends IParser[]] ? UnionParameter> : Result -export interface IUnion extends IParser { - type: 'Union' - parsers: IParser[] +// prettier-ignore +export type ArrayParameter = StaticEnsure< + StaticParser[] +> +export interface IArray extends IParser { + type: 'Array' + parser: IParser } -/** Creates a Union parser */ -export function Union>>(parsers: [...Parsers], mapping: Mapping): IUnion> -/** Creates a Union parser */ -export function Union(parsers: [...Parsers]): IUnion> -export function Union(...args: unknown[]): never { - const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] - return { type: 'Union', parsers, mapping } as never +/** `[EBNF]` Creates an Array parser */ +export function Array>>(parser: Parser, mapping: Mapping): IArray> +/** `[EBNF]` Creates an Array parser */ +export function Array(parser: Parser): IArray> +/** `[EBNF]` Creates an Array parser */ +export function Array(...args: unknown[]): never { + const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Array', parser, mapping } as never } // ------------------------------------------------------------------ -// Token +// Const // ------------------------------------------------------------------ export interface IConst extends IParser { type: 'Const' value: string } -/** Creates a Const parser */ +/** `[TERM]` Creates a Const parser */ export function Const>(value: Value, mapping: Mapping): IConst> -/** Creates a Const parser */ +/** `[TERM]` Creates a Const parser */ export function Const(value: Value): IConst +/** `[TERM]` Creates a Const parser */ export function Const(...args: unknown[]): never { const [value, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Const', value, mapping } as never @@ -113,10 +103,11 @@ export interface IRef extends IParser type: 'Ref' ref: string } -/** Creates a Ref parser */ +/** `[BNF]` Creates a Ref parser. This Parser can only be used in the context of a Module */ export function Ref>(ref: string, mapping: Mapping): IRef> -/** Creates a Ref parser */ +/** `[BNF]` Creates a Ref parser. This Parser can only be used in the context of a Module */ export function Ref(ref: string): IRef +/** `[BNF]` Creates a Ref parser. This Parser can only be used in the context of a Module */ export function Ref(...args: unknown[]): never { const [ref, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] return { type: 'Ref', ref, mapping } as never @@ -129,10 +120,11 @@ export interface IString extends IParser>(options: string[], mapping: Mapping): IString> -/** Creates a String Parser. Options are an array of permissable quote characters */ +/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */ export function String(options: string[]): IString +/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */ export function String(...params: unknown[]): never { const [options, mapping] = params.length === 2 ? [params[0], params[1]] : [params[0], Identity] return { type: 'String', options, mapping } as never @@ -144,10 +136,11 @@ export function String(...params: unknown[]): never { export interface IIdent extends IParser { type: 'Ident' } -/** Creates an Ident parser */ +/** `[TERM]` Creates an Ident parser where Ident matches any valid JavaScript identifier */ export function Ident>(mapping: Mapping): IIdent> -/** Creates an Ident parser */ +/** `[TERM]` Creates an Ident parser where Ident matches any valid JavaScript identifier */ export function Ident(): IIdent +/** `[TERM]` Creates an Ident parser where Ident matches any valid JavaScript identifier */ export function Ident(...params: unknown[]): never { const mapping = params.length === 1 ? params[0] : Identity return { type: 'Ident', mapping } as never @@ -159,11 +152,79 @@ export function Ident(...params: unknown[]): never { export interface INumber extends IParser { type: 'Number' } -/** Creates a Number parser */ +/** `[TERM]` Creates an Number parser */ export function Number>(mapping: Mapping): INumber> -/** Creates a Number parser */ +/** `[TERM]` Creates an Number parser */ export function Number(): INumber +/** `[TERM]` Creates an Number parser */ export function Number(...params: unknown[]): never { const mapping = params.length === 1 ? params[0] : Identity return { type: 'Number', mapping } as never } + +// ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +// prettier-ignore +export type OptionalParameter] | []> = ( + Result +) +export interface IOptional extends IParser { + type: 'Optional' + parser: IParser +} +/** `[EBNF]` Creates an Optional parser */ +export function Optional>>(parser: Parser, mapping: Mapping): IOptional> +/** `[EBNF]` Creates an Optional parser */ +export function Optional(parser: Parser): IOptional> +/** `[EBNF]` Creates an Optional parser */ +export function Optional(...args: unknown[]): never { + const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Optional', parser, mapping } as never +} + +// ------------------------------------------------------------------ +// Tuple +// ------------------------------------------------------------------ +// prettier-ignore +export type TupleParameter = StaticEnsure< + Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] + ? TupleParameter>]> + : Result +> +export interface ITuple extends IParser { + type: 'Tuple' + parsers: IParser[] +} +/** `[BNF]` Creates a Tuple parser */ +export function Tuple>>(parsers: [...Parsers], mapping: Mapping): ITuple> +/** `[BNF]` Creates a Tuple parser */ +export function Tuple(parsers: [...Parsers]): ITuple> +/** `[BNF]` Creates a Tuple parser */ +export function Tuple(...args: unknown[]): never { + const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Tuple', parsers, mapping } as never +} + +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +// prettier-ignore +export type UnionParameter = StaticEnsure< + Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]] + ? UnionParameter> + : Result +> +export interface IUnion extends IParser { + type: 'Union' + parsers: IParser[] +} +/** `[BNF]` Creates a Union parser */ +export function Union>>(parsers: [...Parsers], mapping: Mapping): IUnion> +/** `[BNF]` Creates a Union parser */ +export function Union(parsers: [...Parsers]): IUnion> +/** `[BNF]` Creates a Union parser */ +export function Union(...args: unknown[]): never { + const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity] + return { type: 'Union', parsers, mapping } as never +} diff --git a/src/static/parse.ts b/src/static/parse.ts index f4fc12e..4ee8430 100644 --- a/src/static/parse.ts +++ b/src/static/parse.ts @@ -30,29 +30,15 @@ import * as Tokens from './token' import * as Types from './types' // ------------------------------------------------------------------ -// Tuple +// Array // ------------------------------------------------------------------ // prettier-ignore -type TupleParser = ( - Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] - ? Parse extends [infer Value extends unknown, infer Rest extends string] - ? TupleParser - : [] +type ArrayParser = ( + Parse extends [infer Value1 extends unknown, infer Rest extends string] + ? ArrayParser : [Result, Code] ) -// ------------------------------------------------------------------ -// Union -// ------------------------------------------------------------------ -// prettier-ignore -type UnionParser = ( - Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] - ? Parse extends [infer Value extends unknown, infer Rest extends string] - ? [Value, Rest] - : UnionParser - : [] -) - // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ @@ -83,6 +69,16 @@ type NumberParser = ( : [] ) +// ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +// prettier-ignore +type OptionalParser = ( + Parse extends [infer Value extends unknown, infer Rest extends string] + ? [[Value], Rest] + : [[], Code] +) + // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ @@ -93,23 +89,50 @@ type StringParser = ( + Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] + ? Parse extends [infer Value extends unknown, infer Rest extends string] + ? TupleParser + : [] + : [Result, Code] +) + +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +// prettier-ignore +type UnionParser = ( + Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]] + ? Parse extends [infer Value extends unknown, infer Rest extends string] + ? [Value, Rest] + : UnionParser + : [] +) + // ------------------------------------------------------------------ // Parse // ------------------------------------------------------------------ // prettier-ignore type ParseCode = ( - Type extends Types.Union ? UnionParser : - Type extends Types.Tuple ? TupleParser : - Type extends Types.Const ? ConstParser : - Type extends Types.String ? StringParser : + Type extends Types.Array ? ArrayParser : + Type extends Types.Const ? ConstParser : Type extends Types.Ident ? IdentParser : Type extends Types.Number ? NumberParser : + Type extends Types.Optional ? OptionalParser : + Type extends Types.String ? StringParser : + Type extends Types.Tuple ? TupleParser : + Type extends Types.Union ? UnionParser : [] ) // prettier-ignore type ParseMapping = ( (Parser['mapping'] & { input: Result, context: Context })['output'] ) + /** Parses code with the given parser */ // prettier-ignore export type Parse = ( diff --git a/src/static/types.ts b/src/static/types.ts index 551d1ac..fdfc2d9 100644 --- a/src/static/types.ts +++ b/src/static/types.ts @@ -29,16 +29,25 @@ THE SOFTWARE. // ------------------------------------------------------------------ // Mapping // ------------------------------------------------------------------ +/** + * `[ACTION]` Inference mapping base type. Used to specify semantic actions for + * Parser productions. This type is implemented as a higher-kinded type where + * productions are received on the `input` property with mapping assigned + * the `output` property. The parsing context is available on the `context` + * property. + */ export interface IMapping { context: unknown input: unknown output: unknown } -/** Maps input to output. This is the default Mapping */ + +/** `[ACTION]` Default inference mapping. */ export interface Identity extends IMapping { output: this['input'] } -/** Maps the output as the given parameter T */ + +/** `[ACTION]` Maps the given argument `T` as the mapping output */ export interface As extends IMapping { output: T } @@ -50,51 +59,75 @@ export interface IParser { type: string mapping: Mapping } + // ------------------------------------------------------------------ -// Tuple -// ------------------------------------------------------------------ -/** Creates a Tuple Parser */ -export interface Tuple extends IParser { - type: 'Tuple' - parsers: [...Parsers] -} -// ------------------------------------------------------------------ -// Union +// Array // ------------------------------------------------------------------ -/** Creates a Union Parser */ -export interface Union extends IParser { - type: 'Union' - parsers: [...Parsers] +/** `[EBNF]` Creates an Array Parser */ +export interface Array extends IParser { + type: 'Array' + parser: Parser } + // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ -/** Creates a Const Parser */ +/** `[TERM]` Creates a Const Parser */ export interface Const extends IParser { type: 'Const' value: Value } -// ------------------------------------------------------------------ -// String -// ------------------------------------------------------------------ -/** Creates a String Parser. Options are an array of permissable quote characters */ -export interface String extends IParser { - type: 'String' - quote: Options -} + // ------------------------------------------------------------------ // Ident // ------------------------------------------------------------------ -/** Creates an Ident Parser. */ +/** `[TERM]` Creates an Ident Parser. */ // prettier-ignore export interface Ident extends IParser { type: 'Ident' } + // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ -/** Creates a Number Parser. */ +/** `[TERM]` Creates a Number Parser. */ // prettier-ignore export interface Number extends IParser { type: 'Number' } + +// ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +/** `[EBNF]` Creates a Optional Parser */ +export interface Optional extends IParser { + type: 'Optional' + parser: Parser +} + +// ------------------------------------------------------------------ +// String +// ------------------------------------------------------------------ +/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */ +export interface String extends IParser { + type: 'String' + quote: Options +} + +// ------------------------------------------------------------------ +// Tuple +// ------------------------------------------------------------------ +/** `[BNF]` Creates a Tuple Parser */ +export interface Tuple extends IParser { + type: 'Tuple' + parsers: [...Parsers] +} + +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +/** `[BNF]` Creates a Union Parser */ +export interface Union extends IParser { + type: 'Union' + parsers: [...Parsers] +} diff --git a/test/runtime/guard.ts b/test/runtime/guard.ts index 4a7f6b1..f04629d 100644 --- a/test/runtime/guard.ts +++ b/test/runtime/guard.ts @@ -6,29 +6,52 @@ function Assert(left: unknown, right: unknown) { } // prettier-ignore describe('Guard', () => { + it('IsArray', () => { + // @ts-ignore + Assert(Runtime.Guard.IsArray(Runtime.Array(1)), false) + Assert(Runtime.Guard.IsArray(Runtime.Array(Runtime.Const('A'))), true) + }) + it('IsConst', () => { Assert(Runtime.Guard.IsConst(Runtime.Const('A')), true) // @ts-ignore Assert(Runtime.Guard.IsConst(Runtime.Const(undefined)), false) }) - it('IsTuple', () => { - Assert(Runtime.Guard.IsTuple(Runtime.Const('A')), false) - Assert(Runtime.Guard.IsTuple(Runtime.Tuple([Runtime.Const('A')])), true) - }) - it('IsUnion', () => { - Assert(Runtime.Guard.IsUnion(Runtime.Const('A')), false) - Assert(Runtime.Guard.IsUnion(Runtime.Union([Runtime.Const('A')])), true) - }) + it('IsIdent', () => { Assert(Runtime.Guard.IsIdent(Runtime.Const('A')), false) Assert(Runtime.Guard.IsIdent(Runtime.Ident()), true) }) + + it('IsNumber', () => { + Assert(Runtime.Guard.IsNumber(Runtime.Const('A')), false) + Assert(Runtime.Guard.IsNumber(Runtime.Number()), true) + }) + + it('IsOptional', () => { + // @ts-ignore + Assert(Runtime.Guard.IsOptional(Runtime.Optional(1)), false) + Assert(Runtime.Guard.IsOptional(Runtime.Optional(Runtime.Const('A'))), true) + }) + + it('IsRef', () => { + // @ts-ignore + Assert(Runtime.Guard.IsRef(Runtime.Ref(1)), false) + Assert(Runtime.Guard.IsRef(Runtime.Ref('A')), true) + }) + it('IsString', () => { Assert(Runtime.Guard.IsString(Runtime.Const('A')), false) Assert(Runtime.Guard.IsString(Runtime.String(['"'])), true) }) - it('IsNumber', () => { - Assert(Runtime.Guard.IsNumber(Runtime.Const('A')), false) - Assert(Runtime.Guard.IsNumber(Runtime.Number()), true) + + it('IsTuple', () => { + Assert(Runtime.Guard.IsTuple(Runtime.Const('A')), false) + Assert(Runtime.Guard.IsTuple(Runtime.Tuple([Runtime.Const('A')])), true) + }) + + it('IsUnion', () => { + Assert(Runtime.Guard.IsUnion(Runtime.Const('A')), false) + Assert(Runtime.Guard.IsUnion(Runtime.Union([Runtime.Const('A')])), true) }) }) diff --git a/test/runtime/parse.ts b/test/runtime/parse.ts index f30ba8e..1a5c951 100644 --- a/test/runtime/parse.ts +++ b/test/runtime/parse.ts @@ -6,36 +6,45 @@ function Assert(left: unknown, right: unknown) { } // prettier-ignore describe('Parse', () => { + + it('Array', () => { + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('A')), ''), [[], '']) + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('A')), 'AB'), [['A'], 'B']) + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('A')), 'AAB'), [['A', 'A'], 'B']) + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('AA')), 'AAB'), [['AA'], 'B']) + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('AA')), 'AAAB'), [['AA'], 'AB']) + Assert(Runtime.Parse(Runtime.Array(Runtime.Const('AA')), 'B'), [[], 'B']) + }) + it('Const', () => { Assert(Runtime.Parse(Runtime.Const('A'), ''), []) Assert(Runtime.Parse(Runtime.Const('A'), 'A'), ['A', '']) Assert(Runtime.Parse(Runtime.Const('A'), ' A'), ['A', '']) Assert(Runtime.Parse(Runtime.Const('A'), ' A '), ['A', ' ']) }) - it('Tuple', () => { - const Tuple = Runtime.Tuple([Runtime.Const('A'), Runtime.Const('B'), Runtime.Const('C')]) - Assert(Runtime.Parse(Tuple, ''), []) - Assert(Runtime.Parse(Tuple, 'A'), []) - Assert(Runtime.Parse(Tuple, 'A B C'), [['A', 'B', 'C'], '']) - Assert(Runtime.Parse(Tuple, 'A B C '), [['A', 'B', 'C'], ' ']) - Assert(Runtime.Parse(Tuple, 'ABC'), [['A', 'B', 'C'], '']) - Assert(Runtime.Parse(Tuple, ' ABC'), [['A', 'B', 'C'], '']) - Assert(Runtime.Parse(Tuple, ' ABC '), [['A', 'B', 'C'], ' ']) - }) - it('Union', () => { - const Union = Runtime.Union([Runtime.Const('A'), Runtime.Const('B'), Runtime.Const('C')]) - Assert(Runtime.Parse(Union, ''), []) - Assert(Runtime.Parse(Union, 'A B C'), ['A', ' B C']) - Assert(Runtime.Parse(Union, 'A B C '), ['A', ' B C ']) - Assert(Runtime.Parse(Union, 'ABC'), ['A', 'BC']) - Assert(Runtime.Parse(Union, ' ABC'), ['A', 'BC']) - Assert(Runtime.Parse(Union, ' ABC '), ['A', 'BC ']) - Assert(Runtime.Parse(Union, 'B B C'), ['B', ' B C']) - Assert(Runtime.Parse(Union, 'B B C '), ['B', ' B C ']) - Assert(Runtime.Parse(Union, 'BBC'), ['B', 'BC']) - Assert(Runtime.Parse(Union, ' BBC'), ['B', 'BC']) - Assert(Runtime.Parse(Union, ' BBC '), ['B', 'BC ']) + + it('Ident', () => { + Assert(Runtime.Parse(Runtime.Ident(), ''), []) + Assert(Runtime.Parse(Runtime.Ident(), '0'), []) + Assert(Runtime.Parse(Runtime.Ident(), '#'), []) + Assert(Runtime.Parse(Runtime.Ident(), '_'), ['_', '']) + Assert(Runtime.Parse(Runtime.Ident(), ' _'), ['_', '']) + Assert(Runtime.Parse(Runtime.Ident(), '_ '), ['_', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), ' _ '), ['_', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), '$'), ['$', '']) + Assert(Runtime.Parse(Runtime.Ident(), ' $'), ['$', '']) + Assert(Runtime.Parse(Runtime.Ident(), '$ '), ['$', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), ' $ '), ['$', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), 'A'), ['A', '']) + Assert(Runtime.Parse(Runtime.Ident(), ' A'), ['A', '']) + Assert(Runtime.Parse(Runtime.Ident(), 'A '), ['A', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), ' A '), ['A', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), 'A1'), ['A1', '']) + Assert(Runtime.Parse(Runtime.Ident(), ' A1'), ['A1', '']) + Assert(Runtime.Parse(Runtime.Ident(), 'A1 '), ['A1', ' ']) + Assert(Runtime.Parse(Runtime.Ident(), ' A1 '), ['A1', ' ']) }) + it('Number', () => { Assert(Runtime.Parse(Runtime.Number(), ''), []) Assert(Runtime.Parse(Runtime.Number(), '01'), []) @@ -83,6 +92,14 @@ describe('Parse', () => { Assert(Runtime.Parse(Runtime.Number(), ' -0.1'), ['-0.1', '']) Assert(Runtime.Parse(Runtime.Number(), ' -0.1 '), ['-0.1', ' ']) }) + + it('Optional', () => { + Assert(Runtime.Parse(Runtime.Optional(Runtime.Const('A')), ''), [[], '']) + Assert(Runtime.Parse(Runtime.Optional(Runtime.Const('A')), 'A'), [['A'], '']) + Assert(Runtime.Parse(Runtime.Optional(Runtime.Const('A')), 'AA'), [['A'], 'A']) + Assert(Runtime.Parse(Runtime.Optional(Runtime.Const('A')), 'B'), [[], 'B']) + }) + it('String', () => { Assert(Runtime.Parse(Runtime.String(['"']), ''), []) Assert(Runtime.Parse(Runtime.String(['"']), '"A"'), ['A', '']) @@ -99,32 +116,39 @@ describe('Parse', () => { Assert(Runtime.Parse(Runtime.String(['*', '"']), '"A" '), ['A', ' ']) Assert(Runtime.Parse(Runtime.String(['*', '"']), ' "A" '), ['A', ' ']) }) - it('Ident', () => { - Assert(Runtime.Parse(Runtime.Ident(), ''), []) - Assert(Runtime.Parse(Runtime.Ident(), '0'), []) - Assert(Runtime.Parse(Runtime.Ident(), '#'), []) - Assert(Runtime.Parse(Runtime.Ident(), '_'), ['_', '']) - Assert(Runtime.Parse(Runtime.Ident(), ' _'), ['_', '']) - Assert(Runtime.Parse(Runtime.Ident(), '_ '), ['_', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), ' _ '), ['_', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), '$'), ['$', '']) - Assert(Runtime.Parse(Runtime.Ident(), ' $'), ['$', '']) - Assert(Runtime.Parse(Runtime.Ident(), '$ '), ['$', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), ' $ '), ['$', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), 'A'), ['A', '']) - Assert(Runtime.Parse(Runtime.Ident(), ' A'), ['A', '']) - Assert(Runtime.Parse(Runtime.Ident(), 'A '), ['A', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), ' A '), ['A', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), 'A1'), ['A1', '']) - Assert(Runtime.Parse(Runtime.Ident(), ' A1'), ['A1', '']) - Assert(Runtime.Parse(Runtime.Ident(), 'A1 '), ['A1', ' ']) - Assert(Runtime.Parse(Runtime.Ident(), ' A1 '), ['A1', ' ']) + + it('Tuple', () => { + const Tuple = Runtime.Tuple([Runtime.Const('A'), Runtime.Const('B'), Runtime.Const('C')]) + Assert(Runtime.Parse(Tuple, ''), []) + Assert(Runtime.Parse(Tuple, 'A'), []) + Assert(Runtime.Parse(Tuple, 'A B C'), [['A', 'B', 'C'], '']) + Assert(Runtime.Parse(Tuple, 'A B C '), [['A', 'B', 'C'], ' ']) + Assert(Runtime.Parse(Tuple, 'ABC'), [['A', 'B', 'C'], '']) + Assert(Runtime.Parse(Tuple, ' ABC'), [['A', 'B', 'C'], '']) + Assert(Runtime.Parse(Tuple, ' ABC '), [['A', 'B', 'C'], ' ']) + }) + + it('Union', () => { + const Union = Runtime.Union([Runtime.Const('A'), Runtime.Const('B'), Runtime.Const('C')]) + Assert(Runtime.Parse(Union, ''), []) + Assert(Runtime.Parse(Union, 'A B C'), ['A', ' B C']) + Assert(Runtime.Parse(Union, 'A B C '), ['A', ' B C ']) + Assert(Runtime.Parse(Union, 'ABC'), ['A', 'BC']) + Assert(Runtime.Parse(Union, ' ABC'), ['A', 'BC']) + Assert(Runtime.Parse(Union, ' ABC '), ['A', 'BC ']) + Assert(Runtime.Parse(Union, 'B B C'), ['B', ' B C']) + Assert(Runtime.Parse(Union, 'B B C '), ['B', ' B C ']) + Assert(Runtime.Parse(Union, 'BBC'), ['B', 'BC']) + Assert(Runtime.Parse(Union, ' BBC'), ['B', 'BC']) + Assert(Runtime.Parse(Union, ' BBC '), ['B', 'BC ']) }) + it('Mapping', () => { const Mapping = (_0: 'A', _1: 'B', _2: 'C') => [_2, _1, _0] as const const Mapped = Runtime.Tuple([Runtime.Const('A'), Runtime.Const('B'), Runtime.Const('C')], values => Mapping(...values)) Assert(Runtime.Parse(Mapped, ' A B C '), [['C', 'B', 'A'], ' ']) }) + it('Context', () => { const ContextMapping = (input: 'A' | 'B' | 'C', context: Record) => { return input in context diff --git a/test/static/parse.ts b/test/static/parse.ts index ce2c625..82ff553 100644 --- a/test/static/parse.ts +++ b/test/static/parse.ts @@ -2,6 +2,16 @@ import { Static } from '@sinclair/parsebox' declare function Assert(): void +// ------------------------------------------------------------------ +// Array +// ------------------------------------------------------------------ +Assert>, ''>, [[], '']>() +Assert>, 'AB'>, [['A'], 'B']>() +Assert>, 'AAB'>, [['A', 'A'], 'B']>() +Assert>, 'AAB'>, [['AA'], 'B']>() +Assert>, 'AAAB'>, [['AA'], 'AB']>() +Assert>, 'B'>, [[], 'B']>() + // ------------------------------------------------------------------ // Const // ------------------------------------------------------------------ @@ -12,36 +22,27 @@ Assert, ' A'>, ['A', '']>() Assert, ' A '>, ['A', ' ']>() // ------------------------------------------------------------------ -// Tuple -// ------------------------------------------------------------------ -// prettier-ignore -type Tuple = Static.Tuple<[Static.Const<'A'>, Static.Const<'B'>, Static.Const<'C'>]> - -Assert, []>() -Assert, []>() -Assert, [['A', 'B', 'C'], '']>() -Assert, [['A', 'B', 'C'], ' ']>() -Assert, [['A', 'B', 'C'], '']>() -Assert, [['A', 'B', 'C'], '']>() -Assert, [['A', 'B', 'C'], ' ']>() - -// ------------------------------------------------------------------ -// Union +// Ident // ------------------------------------------------------------------ -// prettier-ignore -type Union = Static.Union<[Static.Const<'A'>, Static.Const<'B'>, Static.Const<'C'>]> - -Assert, []>() -Assert, ['A', ' B C']>() -Assert, ['A', ' B C ']>() -Assert, ['A', 'BC']>() -Assert, ['A', 'BC']>() -Assert, ['A', 'BC ']>() -Assert, ['B', ' B C']>() -Assert, ['B', ' B C ']>() -Assert, ['B', 'BC']>() -Assert, ['B', 'BC']>() -Assert, ['B', 'BC ']>() +Assert, []>() +Assert, []>() +Assert, []>() +Assert, ['_', '']>() +Assert, ['_', '']>() +Assert, ['_', ' ']>() +Assert, ['_', ' ']>() +Assert, ['$', '']>() +Assert, ['$', '']>() +Assert, ['$', ' ']>() +Assert, ['$', ' ']>() +Assert, ['A', '']>() +Assert, ['A', '']>() +Assert, ['A', ' ']>() +Assert, ['A', ' ']>() +Assert, ['A1', '']>() +Assert, ['A1', '']>() +Assert, ['A1', ' ']>() +Assert, ['A1', ' ']>() // ------------------------------------------------------------------ // Number @@ -92,6 +93,14 @@ Assert, ['-0.1', ' ']>() Assert, ['-0.1', '']>() Assert, ['-0.1', ' ']>() +// ------------------------------------------------------------------ +// Optional +// ------------------------------------------------------------------ +Assert>, ''>, [[], '']>() +Assert>, 'A'>, [['A'], '']>() +Assert>, 'AA'>, [['A'], 'A']>() +Assert>, 'B'>, [[], 'B']>() + // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ @@ -111,27 +120,36 @@ Assert, '"A" '>, ['A', ' ']>() Assert, ' "A" '>, ['A', ' ']>() // ------------------------------------------------------------------ -// Ident +// Tuple // ------------------------------------------------------------------ -Assert, []>() -Assert, []>() -Assert, []>() -Assert, ['_', '']>() -Assert, ['_', '']>() -Assert, ['_', ' ']>() -Assert, ['_', ' ']>() -Assert, ['$', '']>() -Assert, ['$', '']>() -Assert, ['$', ' ']>() -Assert, ['$', ' ']>() -Assert, ['A', '']>() -Assert, ['A', '']>() -Assert, ['A', ' ']>() -Assert, ['A', ' ']>() -Assert, ['A1', '']>() -Assert, ['A1', '']>() -Assert, ['A1', ' ']>() -Assert, ['A1', ' ']>() +// prettier-ignore +type Tuple = Static.Tuple<[Static.Const<'A'>, Static.Const<'B'>, Static.Const<'C'>]> + +Assert, []>() +Assert, []>() +Assert, [['A', 'B', 'C'], '']>() +Assert, [['A', 'B', 'C'], ' ']>() +Assert, [['A', 'B', 'C'], '']>() +Assert, [['A', 'B', 'C'], '']>() +Assert, [['A', 'B', 'C'], ' ']>() + +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +// prettier-ignore +type Union = Static.Union<[Static.Const<'A'>, Static.Const<'B'>, Static.Const<'C'>]> + +Assert, []>() +Assert, ['A', ' B C']>() +Assert, ['A', ' B C ']>() +Assert, ['A', 'BC']>() +Assert, ['A', 'BC']>() +Assert, ['A', 'BC ']>() +Assert, ['B', ' B C']>() +Assert, ['B', ' B C ']>() +Assert, ['B', 'BC']>() +Assert, ['B', 'BC']>() +Assert, ['B', 'BC ']>() // ------------------------------------------------------------------ // Mapping From 7974cd8402223332c21aea905a37629b0713b9d3 Mon Sep 17 00:00:00 2001 From: sinclair Date: Fri, 22 Nov 2024 20:11:41 +0900 Subject: [PATCH 2/2] Version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5839da..1c266c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/parsebox", - "version": "0.8.1", + "version": "0.8.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/parsebox", - "version": "0.8.1", + "version": "0.8.2", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.18.0", diff --git a/package.json b/package.json index bae0e8a..2de7d82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/parsebox", - "version": "0.8.1", + "version": "0.8.2", "description": "Parser Combinators in the TypeScript Type System", "keywords": [ "typescript",