Skip to content

Commit

Permalink
Array and Optional Combinators
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Nov 22, 2024
1 parent f1f623d commit 4ff50dc
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 254 deletions.
74 changes: 14 additions & 60 deletions example/index.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,25 @@
import { Static } from '@sinclair/parsebox'
import { Static, Runtime } from '@sinclair/parsebox'
import { Parse } from './typebox'

// ------------------------------------------------------------------
// Example: TypeBox
// ------------------------------------------------------------------
// "[" [ element { "," element } [ "," ] ] "]"

const T = Parse(`{
x: number,
y: number,
z: number
}`)
const Element = Runtime.Number()

console.log(T)

// ------------------------------------------------------------------
// Example: Expression Parser
// ------------------------------------------------------------------

type Result = Static.Parse<Expr, 'x * (y + z)'> // hover

// prettier-ignore
type BinaryReduce<Left extends unknown, Right extends unknown[]> = (
Right extends [infer Operator, infer Right, infer Rest extends unknown[]]
? { left: Left; operator: Operator; right: BinaryReduce<Right, Rest> }
: Left
)

// prettier-ignore
interface BinaryMapping extends Static.IMapping {
output: this['input'] extends [infer Left, infer Right extends unknown[]]
? BinaryReduce<Left, Right>
: never
const ElementsMapping = (_0: unknown, _1: [',', unknown][], _2: [','] | []) => {
return [_0, ..._1.map((value) => value[1])] as const
}

// prettier-ignore
interface FactorMapping extends Static.IMapping {
output: (
this['input'] extends ['(', infer Expr, ')'] ? Expr :
this['input'] extends [infer Operand] ? Operand :
never
)
}

type Operand = Static.Ident
// elements ::= element { "," element } [ "," ]

// prettier-ignore
type Factor = Static.Union<[
Static.Tuple<[Static.Const<'('>, Expr, Static.Const<')'>]>,
Static.Tuple<[Operand]>
], FactorMapping>
const Elements = Runtime.Tuple([Element, Runtime.Array(Runtime.Tuple([Runtime.Const(','), Element])), Runtime.Optional(Runtime.Const(','))], (values) => ElementsMapping(...values))

// prettier-ignore
type TermTail = Static.Union<[
Static.Tuple<[Static.Const<'*'>, Factor, TermTail]>,
Static.Tuple<[Static.Const<'/'>, Factor, TermTail]>,
Static.Tuple<[]>
]>
// array ::= "[" [ elements ] "]"

// prettier-ignore
type ExprTail = Static.Union<[
Static.Tuple<[Static.Const<'+'>, Term, ExprTail]>,
Static.Tuple<[Static.Const<'-'>, Term, ExprTail]>,
Static.Tuple<[]>
]>
const ArrayMapping = (_0: '[', _1: unknown[], _2: ']') => {
return _1
}
const Array = Runtime.Tuple([Runtime.Const('['), Runtime.Optional(Elements), Runtime.Const(']')], (values) => ArrayMapping(...values))

// prettier-ignore
type Term = Static.Tuple<[Factor, TermTail], BinaryMapping>
const R = Runtime.Parse(Array, '[1, 2, 3]')

// prettier-ignore
type Expr = Static.Tuple<[Term, ExprTail], BinaryMapping>
console.dir(R, { depth: 100 })
141 changes: 124 additions & 17 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,24 @@ $ npm install @sinclair/parsebox

## Example

ParseBox provides symmetric parsing in Static and Runtime environments.
ParseBox provides encoding of BNF notation in Static and Runtime environments.

```typescript
import { Runtime, Static } from '@sinclair/parsebox'
**BNF**

// Runtime Parser
```bnf
<T> ::= "X" "Y" "X"
```

const T = Runtime.Tuple([
Runtime.Const('X'),
Runtime.Const('Y'),
Runtime.Const('Z')
])
**Type**

const R = Runtime.Parse(T, 'X Y Z W') // const R = [['X', 'Y', 'Z'], ' W']
```typescript
const T = ["X", "Y", "Z"]
```

// Static Parser
**Static**

```typescript
import { Static } from '@sinclair/parsebox'

type T = Static.Tuple<[
Static.Const<'X'>,
Expand All @@ -49,35 +51,58 @@ type T = Static.Tuple<[
type R = Static.Parse<T, 'X Y Z W'> // type R = [['X', 'Y', 'Z'], ' W']
```
**Runtime**
```typescript

import { Runtime } from '@sinclair/parsebox'

const T = Runtime.Tuple([
Runtime.Const('X'),
Runtime.Const('Y'),
Runtime.Const('Z')
])

const R = Runtime.Parse(T, 'X Y Z W') // const 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 library 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. Once defined, these combinators can parse content either at runtime or statically within the type system.

ParseBox is designed to be general-purpose, offering foundational combinators for expressing BNF grammars both at runtime and statically within the type system. These combinators are symmetric, allowing runtime parsers to be easily ported to operate statically by mirroring their structures.

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.
The project is written as 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)
- [Standard](#Standard)
- [Const](#Const)
- [Tuple](#Tuple)
- [Union](#Union)
- [Extended](#Extended)
- [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
## Standard BNF

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 three standard combinators that map to notation expressable in standard BNF (Backus-Naur Form). These combinators are named Const, Tuple and Union respectively to describe the type of production each combinator produces (as observed by the type system). These combinators service as fundamental building blocks for constructing parsers.

### Const

Expand All @@ -89,6 +114,12 @@ The Const combinator parses for the next occurrence of the specified string. Whi
<T> ::= "X"
```

**Type**

```typescript
type T = 'X'
```
**TypeScript**
```typescript
Expand All @@ -110,6 +141,12 @@ The Tuple parser matches a sequence of interior parsers. An empty tuple can be u
<T> ::= "X" "Y" "Z"
```

**Type**

```typescript
type T = ['X', 'Y', 'Z']
```
**TypeScript**
```typescript
Expand All @@ -136,6 +173,12 @@ The Union combinator parses using one of the interior parsers, attempting each i
<T> ::= "X" | "Y" | "Z"
```

**Type**

```typescript
type T = 'X' | 'Y' | 'Z'
```
**TypeScript**
```typescript
Expand All @@ -155,6 +198,70 @@ const R2 = Runtime.Parse(P, 'Y E') // const R2 = ['Y', ' E']
const R3 = Runtime.Parse(P, 'Z E') // const R3 = ['Z', ' E']
```

## Extended BNF

ParseBox provides support for the following Extended BNF combinators.

### Array

The Array combinator will parse for zero or more the interior parser. This combinator will always return a result with an empty array for no matches.

**EBNF**

```
<T> ::= { "X" }
```

**Type**

```typescript
type T = '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 for no match.

**EBNF**

```
<T> ::= [ "X" ]
```

**Type**

```typescript
type T = ['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.
Expand Down
42 changes: 17 additions & 25 deletions src/runtime/guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Loading

0 comments on commit 4ff50dc

Please sign in to comment.