stringent 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -0
- package/dist/combinators/index.d.ts +57 -0
- package/dist/combinators/index.js +104 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +13 -0
- package/dist/createParser.d.ts +76 -0
- package/dist/createParser.js +57 -0
- package/dist/grammar/index.d.ts +43 -0
- package/dist/grammar/index.js +12 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +22 -0
- package/dist/parse/index.d.ts +137 -0
- package/dist/parse/index.js +15 -0
- package/dist/primitive/index.d.ts +104 -0
- package/dist/primitive/index.js +97 -0
- package/dist/runtime/infer.d.ts +26 -0
- package/dist/runtime/infer.js +34 -0
- package/dist/runtime/parser.d.ts +34 -0
- package/dist/runtime/parser.js +271 -0
- package/dist/schema/index.d.ts +273 -0
- package/dist/schema/index.js +111 -0
- package/dist/static/infer.d.ts +26 -0
- package/dist/static/infer.js +9 -0
- package/dist/static/parser.d.ts +7 -0
- package/dist/static/parser.js +6 -0
- package/package.json +30 -7
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Stringent
|
|
2
|
+
|
|
3
|
+
A type-safe expression parser for TypeScript with compile-time validation and inference.
|
|
4
|
+
|
|
5
|
+
> **Warning**
|
|
6
|
+
> This library is under active development and not yet ready for production use. APIs may change.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Stringent parses and validates expressions like `values.password == values.confirmPassword` against a schema at both compile-time and runtime, with full TypeScript type inference for expression results.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install stringent
|
|
16
|
+
# or
|
|
17
|
+
pnpm add stringent
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Define Your Grammar
|
|
23
|
+
|
|
24
|
+
Use `defineNode` to create expression nodes with patterns, precedence, and result types:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { defineNode, number, constVal, lhs, rhs, createParser } from 'stringent';
|
|
28
|
+
|
|
29
|
+
// Atomic: number literals
|
|
30
|
+
const numberLit = defineNode({
|
|
31
|
+
name: "number",
|
|
32
|
+
pattern: [number()],
|
|
33
|
+
precedence: "atom",
|
|
34
|
+
resultType: "number",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Binary operators with precedence
|
|
38
|
+
const add = defineNode({
|
|
39
|
+
name: "add",
|
|
40
|
+
pattern: [lhs("number").as("left"), constVal("+"), rhs("number").as("right")],
|
|
41
|
+
precedence: 1, // Lower = binds looser
|
|
42
|
+
resultType: "number",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const mul = defineNode({
|
|
46
|
+
name: "mul",
|
|
47
|
+
pattern: [lhs("number").as("left"), constVal("*"), rhs("number").as("right")],
|
|
48
|
+
precedence: 2, // Higher = binds tighter
|
|
49
|
+
resultType: "number",
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Create a Parser
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const parser = createParser([numberLit, add, mul] as const);
|
|
57
|
+
|
|
58
|
+
// Type-safe parsing - result type is inferred at compile-time
|
|
59
|
+
const result = parser.parse("1+2*3", {});
|
|
60
|
+
// ^? const result: [{ node: "add"; left: { node: "number"; value: "1" }; right: { node: "mul"; left: { node: "number"; value: "2" }; right: { node: "number"; value: "3" } } }, ""]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Pattern Elements
|
|
64
|
+
|
|
65
|
+
| Pattern | Description |
|
|
66
|
+
|---------|-------------|
|
|
67
|
+
| `number()` | Matches numeric literals |
|
|
68
|
+
| `string(quotes)` | Matches quoted strings |
|
|
69
|
+
| `ident()` | Matches identifiers, resolves type from context |
|
|
70
|
+
| `constVal(value)` | Matches exact string (operators, keywords) |
|
|
71
|
+
| `lhs(constraint)` | Left operand (higher precedence, avoids left-recursion) |
|
|
72
|
+
| `rhs(constraint)` | Right operand (same precedence, right-associative) |
|
|
73
|
+
| `expr(constraint)` | Full expression (all grammar levels) |
|
|
74
|
+
|
|
75
|
+
Use `.as(name)` to capture pattern elements as named bindings in the AST:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
lhs("number").as("left") // Captures left operand as "left" in the AST node
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Runtime Evaluation (Coming Soon)
|
|
82
|
+
|
|
83
|
+
> **Note**
|
|
84
|
+
> Runtime evaluation is not yet implemented.
|
|
85
|
+
|
|
86
|
+
Add `eval` to compute values at runtime:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const add = defineNode({
|
|
90
|
+
name: "add",
|
|
91
|
+
pattern: [lhs("number").as("left"), constVal("+"), rhs("number").as("right")],
|
|
92
|
+
precedence: 1,
|
|
93
|
+
resultType: "number",
|
|
94
|
+
eval: ({ left, right }) => left + right,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Key Features
|
|
99
|
+
|
|
100
|
+
- **Compile-time validation**: Invalid expressions fail TypeScript compilation
|
|
101
|
+
- **Type inference**: Expression result types are inferred automatically
|
|
102
|
+
- **Operator precedence**: Correct parsing of complex expressions
|
|
103
|
+
- **Schema-aware**: Validates field references against your schema
|
|
104
|
+
- **Dual API**: Same parsing logic at compile-time (types) and runtime
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser Combinators
|
|
3
|
+
*
|
|
4
|
+
* Composable parsers that work at both runtime and compile-time.
|
|
5
|
+
* Parse methods are generic - return types computed from input literals.
|
|
6
|
+
* All combinators thread context ($) through to child parsers.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: All parse methods MUST be generic in context:
|
|
9
|
+
* parse<TInput extends string, $ extends Context>(input: TInput, $: $)
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from "../context.js";
|
|
12
|
+
import type { IParser, ParseNumber, ParseString, ParseIdent, ParseConst, _Number, _String, _Ident, _Const } from "../primitive/index.js";
|
|
13
|
+
/** Static type for parsing any parser */
|
|
14
|
+
export type Parse<P, TInput extends string, $ extends Context> = P extends _Number ? ParseNumber<TInput> : P extends _String<infer Q> ? ParseString<Q, TInput> : P extends _Const<infer V> ? ParseConst<V, TInput> : P extends _Ident ? ParseIdent<TInput, $> : P extends _Union<infer Parsers> ? ParseUnion<Parsers, TInput, $> : P extends _Tuple<infer Parsers> ? ParseTuple<Parsers, TInput, $> : P extends _Optional<infer Parser> ? ParseOptional<Parser, TInput, $> : P extends _Many<infer Parser> ? ParseMany<Parser, TInput, $> : never;
|
|
15
|
+
/** Static type for Union parsing */
|
|
16
|
+
export type ParseUnion<TParsers extends IParser[], TInput extends string, $ extends Context> = TParsers extends [
|
|
17
|
+
infer First extends IParser,
|
|
18
|
+
...infer Rest extends IParser[]
|
|
19
|
+
] ? Parse<First, TInput, $> extends [infer R, infer Remaining extends string] ? [R, Remaining] : ParseUnion<Rest, TInput, $> : [];
|
|
20
|
+
/** Static type for Tuple parsing */
|
|
21
|
+
export type ParseTuple<TParsers extends IParser[], TInput extends string, $ extends Context, TAcc extends unknown[] = []> = TParsers extends [
|
|
22
|
+
infer First extends IParser,
|
|
23
|
+
...infer Rest extends IParser[]
|
|
24
|
+
] ? Parse<First, TInput, $> extends [infer R, infer Remaining extends string] ? ParseTuple<Rest, Remaining, $, [...TAcc, R]> : [] : [TAcc, TInput];
|
|
25
|
+
/** Static type for Optional parsing */
|
|
26
|
+
export type ParseOptional<TParser extends IParser, TInput extends string, $ extends Context> = Parse<TParser, TInput, $> extends [infer R, infer Remaining extends string] ? [R, Remaining] : [undefined, TInput];
|
|
27
|
+
/** Static type for Many parsing */
|
|
28
|
+
export type ParseMany<TParser extends IParser, TInput extends string, $ extends Context, TAcc extends unknown[] = []> = Parse<TParser, TInput, $> extends [infer R, infer Remaining extends string] ? Remaining extends TInput ? [TAcc, TInput] : ParseMany<TParser, Remaining, $, [...TAcc, R]> : [TAcc, TInput];
|
|
29
|
+
declare class _Union<TParsers extends IParser[]> {
|
|
30
|
+
readonly __combinator: "union";
|
|
31
|
+
readonly parsers: TParsers;
|
|
32
|
+
constructor(parsers: TParsers);
|
|
33
|
+
parse<TInput extends string, $ extends Context>(input: TInput, $: $): ParseUnion<TParsers, TInput, $>;
|
|
34
|
+
}
|
|
35
|
+
declare class _Tuple<TParsers extends IParser[]> {
|
|
36
|
+
readonly __combinator: "tuple";
|
|
37
|
+
readonly parsers: TParsers;
|
|
38
|
+
constructor(parsers: TParsers);
|
|
39
|
+
parse<TInput extends string, $ extends Context>(input: TInput, $: $): ParseTuple<TParsers, TInput, $>;
|
|
40
|
+
}
|
|
41
|
+
declare class _Optional<TParser extends IParser> {
|
|
42
|
+
readonly __combinator: "optional";
|
|
43
|
+
readonly parser: TParser;
|
|
44
|
+
constructor(parser: TParser);
|
|
45
|
+
parse<TInput extends string, $ extends Context>(input: TInput, $: $): ParseOptional<TParser, TInput, $>;
|
|
46
|
+
}
|
|
47
|
+
declare class _Many<TParser extends IParser> {
|
|
48
|
+
readonly __combinator: "many";
|
|
49
|
+
readonly parser: TParser;
|
|
50
|
+
constructor(parser: TParser);
|
|
51
|
+
parse<TInput extends string, $ extends Context>(input: TInput, $: $): ParseMany<TParser, TInput, $>;
|
|
52
|
+
}
|
|
53
|
+
export declare const Union: <TParsers extends IParser[]>(parsers: [...TParsers]) => _Union<TParsers>;
|
|
54
|
+
export declare const Tuple: <TParsers extends IParser[]>(parsers: [...TParsers]) => _Tuple<TParsers>;
|
|
55
|
+
export declare const Optional: <TParser extends IParser>(parser: TParser) => _Optional<TParser>;
|
|
56
|
+
export declare const Many: <TParser extends IParser>(parser: TParser) => _Many<TParser>;
|
|
57
|
+
export type { _Union, _Tuple, _Optional, _Many };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser Combinators
|
|
3
|
+
*
|
|
4
|
+
* Composable parsers that work at both runtime and compile-time.
|
|
5
|
+
* Parse methods are generic - return types computed from input literals.
|
|
6
|
+
* All combinators thread context ($) through to child parsers.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: All parse methods MUST be generic in context:
|
|
9
|
+
* parse<TInput extends string, $ extends Context>(input: TInput, $: $)
|
|
10
|
+
*/
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Union Combinator
|
|
13
|
+
// =============================================================================
|
|
14
|
+
class _Union {
|
|
15
|
+
__combinator = "union";
|
|
16
|
+
parsers;
|
|
17
|
+
constructor(parsers) {
|
|
18
|
+
this.parsers = parsers;
|
|
19
|
+
}
|
|
20
|
+
parse(input, $) {
|
|
21
|
+
for (const parser of this.parsers) {
|
|
22
|
+
const result = parser.parse(input, $);
|
|
23
|
+
if (result.length === 2) {
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Tuple Combinator
|
|
32
|
+
// =============================================================================
|
|
33
|
+
class _Tuple {
|
|
34
|
+
__combinator = "tuple";
|
|
35
|
+
parsers;
|
|
36
|
+
constructor(parsers) {
|
|
37
|
+
this.parsers = parsers;
|
|
38
|
+
}
|
|
39
|
+
parse(input, $) {
|
|
40
|
+
const results = [];
|
|
41
|
+
let remaining = input;
|
|
42
|
+
for (const parser of this.parsers) {
|
|
43
|
+
const result = parser.parse(remaining, $);
|
|
44
|
+
if (result.length !== 2) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
results.push(result[0]);
|
|
48
|
+
remaining = result[1];
|
|
49
|
+
}
|
|
50
|
+
return [results, remaining];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Optional Combinator
|
|
55
|
+
// =============================================================================
|
|
56
|
+
class _Optional {
|
|
57
|
+
__combinator = "optional";
|
|
58
|
+
parser;
|
|
59
|
+
constructor(parser) {
|
|
60
|
+
this.parser = parser;
|
|
61
|
+
}
|
|
62
|
+
parse(input, $) {
|
|
63
|
+
const result = this.parser.parse(input, $);
|
|
64
|
+
if (result.length === 2) {
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
return [undefined, input];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Many Combinator
|
|
72
|
+
// =============================================================================
|
|
73
|
+
class _Many {
|
|
74
|
+
__combinator = "many";
|
|
75
|
+
parser;
|
|
76
|
+
constructor(parser) {
|
|
77
|
+
this.parser = parser;
|
|
78
|
+
}
|
|
79
|
+
parse(input, $) {
|
|
80
|
+
const results = [];
|
|
81
|
+
let remaining = input;
|
|
82
|
+
while (true) {
|
|
83
|
+
const result = this.parser.parse(remaining, $);
|
|
84
|
+
if (result.length !== 2) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
results.push(result[0]);
|
|
88
|
+
const newRemaining = result[1];
|
|
89
|
+
// Prevent infinite loop on zero-width matches
|
|
90
|
+
if (newRemaining === remaining) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
remaining = newRemaining;
|
|
94
|
+
}
|
|
95
|
+
return [results, remaining];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Exported Factories
|
|
100
|
+
// =============================================================================
|
|
101
|
+
export const Union = (parsers) => new _Union(parsers);
|
|
102
|
+
export const Tuple = (parsers) => new _Tuple(parsers);
|
|
103
|
+
export const Optional = (parser) => new _Optional(parser);
|
|
104
|
+
export const Many = (parser) => new _Many(parser);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context - carries schema data for identifier type resolution
|
|
3
|
+
*
|
|
4
|
+
* The context maps variable names to their types, enabling type-safe
|
|
5
|
+
* parsing of expressions like `x + y` where x and y come from a schema.
|
|
6
|
+
*
|
|
7
|
+
* Grammar is now computed from node schemas via ComputeGrammar<Nodes>.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Parse context with schema data.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam TData - Schema mapping variable names to their types
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* type Ctx = Context<{ x: "number"; y: "string" }>;
|
|
17
|
+
* // x resolves to type "number", y resolves to type "string"
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export interface Context<TData extends Record<string, string> = Record<string, string>> {
|
|
21
|
+
/** Schema types for identifier resolution */
|
|
22
|
+
readonly data: TData;
|
|
23
|
+
}
|
|
24
|
+
/** Empty context (no schema variables) */
|
|
25
|
+
export declare const emptyContext: Context<{}>;
|
|
26
|
+
/** Type alias for empty context */
|
|
27
|
+
export type EmptyContext = Context<{}>;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context - carries schema data for identifier type resolution
|
|
3
|
+
*
|
|
4
|
+
* The context maps variable names to their types, enabling type-safe
|
|
5
|
+
* parsing of expressions like `x + y` where x and y come from a schema.
|
|
6
|
+
*
|
|
7
|
+
* Grammar is now computed from node schemas via ComputeGrammar<Nodes>.
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Default Context
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/** Empty context (no schema variables) */
|
|
13
|
+
export const emptyContext = { data: {} };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createParser Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Creates a type-safe parser from node schemas.
|
|
5
|
+
* The returned parser has:
|
|
6
|
+
* - Type-level parsing via Parse<Grammar, Input, Context>
|
|
7
|
+
* - Runtime parsing that mirrors the type structure
|
|
8
|
+
*/
|
|
9
|
+
import type { NodeSchema } from "./schema/index.js";
|
|
10
|
+
import type { ComputeGrammar, Grammar } from "./grammar/index.js";
|
|
11
|
+
import type { Parse } from "./parse/index.js";
|
|
12
|
+
import type { Context } from "./context.js";
|
|
13
|
+
/**
|
|
14
|
+
* Parser interface with type-safe parse method.
|
|
15
|
+
*
|
|
16
|
+
* TGrammar: The computed grammar type from node schemas
|
|
17
|
+
* TNodes: The tuple of node schemas
|
|
18
|
+
*/
|
|
19
|
+
export interface Parser<TGrammar extends Grammar, TNodes extends readonly NodeSchema[]> {
|
|
20
|
+
/**
|
|
21
|
+
* Parse an input string.
|
|
22
|
+
*
|
|
23
|
+
* @param input - The input string to parse
|
|
24
|
+
* @param schema - Schema mapping field names to their types
|
|
25
|
+
* @returns Parse result with computed type
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const result = parser.parse("1+2", {});
|
|
30
|
+
* // Type: Parse<Grammar, "1+2", Context<{}>>
|
|
31
|
+
* // Value: [{ type: "binary", name: "add", left: {...}, right: {...} }, ""]
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
parse<TInput extends string, TSchema extends Record<string, string>>(input: ValidatedInput<TGrammar, TInput, Context<TSchema>>, schema: TSchema): Parse<TGrammar, TInput, Context<TSchema>>;
|
|
35
|
+
/** The node schemas used to create this parser */
|
|
36
|
+
readonly nodes: TNodes;
|
|
37
|
+
}
|
|
38
|
+
type ValidatedInput<TGrammar extends Grammar, TInput extends string, $ extends Context> = Parse<TGrammar, TInput, $> extends [any, any] ? TInput : never;
|
|
39
|
+
/**
|
|
40
|
+
* Create a type-safe parser from node schemas.
|
|
41
|
+
*
|
|
42
|
+
* The returned parser has both:
|
|
43
|
+
* - Compile-time type inference via Parse<Grammar, Input, Context>
|
|
44
|
+
* - Runtime parsing that matches the type structure
|
|
45
|
+
*
|
|
46
|
+
* @param nodes - Tuple of node schemas defining the grammar
|
|
47
|
+
* @returns Parser instance with type-safe parse method
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { defineNode, number, expr, constVal, createParser } from "stringent";
|
|
52
|
+
import { Validate } from '../dist/static/parser';
|
|
53
|
+
*
|
|
54
|
+
* const numberLit = defineNode({
|
|
55
|
+
* name: "number",
|
|
56
|
+
* pattern: [number()],
|
|
57
|
+
* precedence: "atom",
|
|
58
|
+
* resultType: "number",
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* const add = defineNode({
|
|
62
|
+
* name: "add",
|
|
63
|
+
* pattern: [expr("number"), constVal("+"), expr("number")],
|
|
64
|
+
* precedence: 1,
|
|
65
|
+
* resultType: "number",
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* const parser = createParser([numberLit, add] as const);
|
|
69
|
+
*
|
|
70
|
+
* // Type-safe parsing!
|
|
71
|
+
* const result = parser.parse("1+2", {});
|
|
72
|
+
* // Type: [BinaryNode<"add", NumberNode<"1">, NumberNode<"2">, "number">, ""]
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function createParser<const TNodes extends readonly NodeSchema[]>(nodes: TNodes): Parser<ComputeGrammar<TNodes>, TNodes>;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createParser Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Creates a type-safe parser from node schemas.
|
|
5
|
+
* The returned parser has:
|
|
6
|
+
* - Type-level parsing via Parse<Grammar, Input, Context>
|
|
7
|
+
* - Runtime parsing that mirrors the type structure
|
|
8
|
+
*/
|
|
9
|
+
import { parse as runtimeParse } from "./runtime/parser.js";
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// createParser Factory
|
|
12
|
+
// =============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Create a type-safe parser from node schemas.
|
|
15
|
+
*
|
|
16
|
+
* The returned parser has both:
|
|
17
|
+
* - Compile-time type inference via Parse<Grammar, Input, Context>
|
|
18
|
+
* - Runtime parsing that matches the type structure
|
|
19
|
+
*
|
|
20
|
+
* @param nodes - Tuple of node schemas defining the grammar
|
|
21
|
+
* @returns Parser instance with type-safe parse method
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { defineNode, number, expr, constVal, createParser } from "stringent";
|
|
26
|
+
import { Validate } from '../dist/static/parser';
|
|
27
|
+
*
|
|
28
|
+
* const numberLit = defineNode({
|
|
29
|
+
* name: "number",
|
|
30
|
+
* pattern: [number()],
|
|
31
|
+
* precedence: "atom",
|
|
32
|
+
* resultType: "number",
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* const add = defineNode({
|
|
36
|
+
* name: "add",
|
|
37
|
+
* pattern: [expr("number"), constVal("+"), expr("number")],
|
|
38
|
+
* precedence: 1,
|
|
39
|
+
* resultType: "number",
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* const parser = createParser([numberLit, add] as const);
|
|
43
|
+
*
|
|
44
|
+
* // Type-safe parsing!
|
|
45
|
+
* const result = parser.parse("1+2", {});
|
|
46
|
+
* // Type: [BinaryNode<"add", NumberNode<"1">, NumberNode<"2">, "number">, ""]
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function createParser(nodes) {
|
|
50
|
+
return {
|
|
51
|
+
parse(input, schema) {
|
|
52
|
+
const context = { data: schema };
|
|
53
|
+
return runtimeParse(nodes, input, context);
|
|
54
|
+
},
|
|
55
|
+
nodes,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grammar Type Computation
|
|
3
|
+
*
|
|
4
|
+
* Computes a grammar TYPE from node schemas. The grammar is a flat tuple
|
|
5
|
+
* of precedence levels, sorted from lowest to highest precedence, with
|
|
6
|
+
* atoms as the final element.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* [[AddOps], [MulOps], [Atoms]]
|
|
10
|
+
* // level 0 (lowest prec) → level 1 → atoms (last)
|
|
11
|
+
*/
|
|
12
|
+
import type { Pipe, Numbers, Objects, Tuples, Fn, Unions, Call } from "hotscript";
|
|
13
|
+
import type { NodeSchema } from "../schema/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* A grammar is a tuple of levels, where each level is an array of node schemas.
|
|
16
|
+
* Sorted by precedence (lowest first), atoms last.
|
|
17
|
+
*/
|
|
18
|
+
export type Grammar = readonly (readonly NodeSchema[])[];
|
|
19
|
+
/**
|
|
20
|
+
* Compare precedence entries: numbers sort ascending, "atom" always comes last.
|
|
21
|
+
* Entry format: [precedence, nodes[]]
|
|
22
|
+
*/
|
|
23
|
+
interface SortByPrecedence extends Fn {
|
|
24
|
+
return: this["arg0"][0] extends "atom" ? false : this["arg1"][0] extends "atom" ? true : Call<Numbers.LessThanOrEqual, this["arg0"][0], this["arg1"][0]>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compute the grammar tuple from node schemas.
|
|
28
|
+
*
|
|
29
|
+
* 1. Group nodes by precedence
|
|
30
|
+
* 2. Convert to entries and sort (numbers ascending, "atom" last)
|
|
31
|
+
* 3. Extract just the node arrays
|
|
32
|
+
*/
|
|
33
|
+
type ComputeGrammarImpl<TNodes extends readonly NodeSchema[]> = Pipe<[
|
|
34
|
+
...TNodes
|
|
35
|
+
], [
|
|
36
|
+
Tuples.GroupBy<Objects.Get<"precedence">>,
|
|
37
|
+
Objects.Entries,
|
|
38
|
+
Unions.ToTuple,
|
|
39
|
+
Tuples.Sort<SortByPrecedence>,
|
|
40
|
+
Tuples.Map<Tuples.At<1>>
|
|
41
|
+
]>;
|
|
42
|
+
export type ComputeGrammar<TNodes extends readonly NodeSchema[]> = ComputeGrammarImpl<TNodes> extends infer G extends Grammar ? G : never;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grammar Type Computation
|
|
3
|
+
*
|
|
4
|
+
* Computes a grammar TYPE from node schemas. The grammar is a flat tuple
|
|
5
|
+
* of precedence levels, sorted from lowest to highest precedence, with
|
|
6
|
+
* atoms as the final element.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* [[AddOps], [MulOps], [Atoms]]
|
|
10
|
+
* // level 0 (lowest prec) → level 1 → atoms (last)
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stringent - Type-safe Expression Parser
|
|
3
|
+
*
|
|
4
|
+
* Main entry points:
|
|
5
|
+
* - defineNode: Create grammar node schemas
|
|
6
|
+
* - createParser: Build a type-safe parser from nodes
|
|
7
|
+
* - Parse<Grammar, Input, Context>: Type-level parsing
|
|
8
|
+
*/
|
|
9
|
+
export { defineNode, number, string, ident, constVal, lhs, rhs, expr } from "./schema/index.js";
|
|
10
|
+
export type { NodeSchema, PatternSchema, NumberSchema, StringSchema, IdentSchema, ConstSchema, ExprSchema, ExprRole, Precedence, ConfigureFn, EvalFn, SchemaToType, InferBindings, InferEvaluatedBindings, } from "./schema/index.js";
|
|
11
|
+
export { createParser } from "./createParser.js";
|
|
12
|
+
export type { Parser } from "./createParser.js";
|
|
13
|
+
export type { Parse, BinaryNode, ParseError, TypeMismatchError, NoMatchError } from "./parse/index.js";
|
|
14
|
+
export type { ComputeGrammar, Grammar } from "./grammar/index.js";
|
|
15
|
+
export type { Context, EmptyContext } from "./context.js";
|
|
16
|
+
export { emptyContext } from "./context.js";
|
|
17
|
+
export type { ASTNode, LiteralNode, NumberNode, StringNode, IdentNode, ConstNode, } from "./primitive/index.js";
|
|
18
|
+
export { Number, String, Ident, Const, type IParser, type ParseResult, } from "./primitive/index.js";
|
|
19
|
+
export { Union, Tuple, Optional, Many } from "./combinators/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stringent - Type-safe Expression Parser
|
|
3
|
+
*
|
|
4
|
+
* Main entry points:
|
|
5
|
+
* - defineNode: Create grammar node schemas
|
|
6
|
+
* - createParser: Build a type-safe parser from nodes
|
|
7
|
+
* - Parse<Grammar, Input, Context>: Type-level parsing
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Main API: defineNode & createParser
|
|
11
|
+
// =============================================================================
|
|
12
|
+
export { defineNode, number, string, ident, constVal, lhs, rhs, expr } from "./schema/index.js";
|
|
13
|
+
export { createParser } from "./createParser.js";
|
|
14
|
+
export { emptyContext } from "./context.js";
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Legacy Primitives (for backwards compatibility)
|
|
17
|
+
// =============================================================================
|
|
18
|
+
export { Number, String, Ident, Const, } from "./primitive/index.js";
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Legacy Combinators (for backwards compatibility)
|
|
21
|
+
// =============================================================================
|
|
22
|
+
export { Union, Tuple, Optional, Many } from "./combinators/index.js";
|