stringent 0.0.2 → 0.0.4
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 +61 -73
- package/dist/context.d.ts +20 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -0
- package/dist/createParser.d.ts +109 -26
- package/dist/createParser.d.ts.map +1 -0
- package/dist/createParser.js +80 -19
- package/dist/createParser.js.map +1 -0
- package/dist/errors.d.ts +121 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +186 -0
- package/dist/errors.js.map +1 -0
- package/dist/grammar/index.d.ts +19 -14
- package/dist/grammar/index.d.ts.map +1 -0
- package/dist/grammar/index.js +4 -3
- package/dist/grammar/index.js.map +1 -0
- package/dist/index.d.ts +19 -11
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -7
- package/dist/index.js.map +1 -0
- package/dist/parse/index.d.ts +101 -27
- package/dist/parse/index.d.ts.map +1 -0
- package/dist/parse/index.js +1 -0
- package/dist/parse/index.js.map +1 -0
- package/dist/performance.bench.d.ts +10 -0
- package/dist/performance.bench.d.ts.map +1 -0
- package/dist/performance.bench.js +379 -0
- package/dist/performance.bench.js.map +1 -0
- package/dist/primitive/index.d.ts +27 -35
- package/dist/primitive/index.d.ts.map +1 -0
- package/dist/primitive/index.js +22 -17
- package/dist/primitive/index.js.map +1 -0
- package/dist/runtime/eval.d.ts +157 -0
- package/dist/runtime/eval.d.ts.map +1 -0
- package/dist/runtime/eval.js +206 -0
- package/dist/runtime/eval.js.map +1 -0
- package/dist/runtime/infer.d.ts +2 -1
- package/dist/runtime/infer.d.ts.map +1 -0
- package/dist/runtime/infer.js +3 -2
- package/dist/runtime/infer.js.map +1 -0
- package/dist/runtime/parser.d.ts +92 -11
- package/dist/runtime/parser.d.ts.map +1 -0
- package/dist/runtime/parser.js +522 -47
- package/dist/runtime/parser.js.map +1 -0
- package/dist/schema/index.d.ts +230 -27
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +54 -28
- package/dist/schema/index.js.map +1 -0
- package/dist/static/infer.d.ts +4 -3
- package/dist/static/infer.d.ts.map +1 -0
- package/dist/static/infer.js +1 -0
- package/dist/static/infer.js.map +1 -0
- package/package.json +35 -4
- package/dist/combinators/index.d.ts +0 -57
- package/dist/combinators/index.js +0 -104
- package/dist/static/parser.d.ts +0 -7
- package/dist/static/parser.js +0 -6
package/README.md
CHANGED
|
@@ -1,107 +1,95 @@
|
|
|
1
1
|
# Stringent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe expression parser for TypeScript with compile-time validation.
|
|
4
4
|
|
|
5
|
-
> **Warning
|
|
6
|
-
> This library is under active development and not yet ready for production use. APIs may change.
|
|
5
|
+
> **Warning:** Under active development. APIs may change.
|
|
7
6
|
|
|
8
|
-
##
|
|
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
|
|
7
|
+
## Install
|
|
13
8
|
|
|
14
9
|
```bash
|
|
15
10
|
npm install stringent
|
|
16
|
-
# or
|
|
17
|
-
pnpm add stringent
|
|
18
11
|
```
|
|
19
12
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
### Define Your Grammar
|
|
23
|
-
|
|
24
|
-
Use `defineNode` to create expression nodes with patterns, precedence, and result types:
|
|
13
|
+
## Quick Start
|
|
25
14
|
|
|
26
15
|
```typescript
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
// Atomic: number literals
|
|
30
|
-
const numberLit = defineNode({
|
|
31
|
-
name: "number",
|
|
32
|
-
pattern: [number()],
|
|
33
|
-
precedence: "atom",
|
|
34
|
-
resultType: "number",
|
|
35
|
-
});
|
|
16
|
+
import { createParser, defineNode, constVal, lhs, rhs } from 'stringent';
|
|
36
17
|
|
|
37
|
-
//
|
|
18
|
+
// Define operators
|
|
38
19
|
const add = defineNode({
|
|
39
|
-
name:
|
|
40
|
-
pattern: [lhs(
|
|
41
|
-
precedence: 1,
|
|
42
|
-
resultType:
|
|
20
|
+
name: 'add',
|
|
21
|
+
pattern: [lhs('number').as('left'), constVal('+'), rhs('number').as('right')],
|
|
22
|
+
precedence: 1,
|
|
23
|
+
resultType: 'number',
|
|
24
|
+
eval: ({ left, right }) => left + right,
|
|
43
25
|
});
|
|
44
26
|
|
|
45
27
|
const mul = defineNode({
|
|
46
|
-
name:
|
|
47
|
-
pattern: [lhs(
|
|
48
|
-
precedence: 2,
|
|
49
|
-
resultType:
|
|
28
|
+
name: 'mul',
|
|
29
|
+
pattern: [lhs('number').as('left'), constVal('*'), rhs('number').as('right')],
|
|
30
|
+
precedence: 2,
|
|
31
|
+
resultType: 'number',
|
|
32
|
+
eval: ({ left, right }) => left * right,
|
|
50
33
|
});
|
|
34
|
+
|
|
35
|
+
// Create parser
|
|
36
|
+
const parser = createParser([add, mul]);
|
|
37
|
+
|
|
38
|
+
// Parse and evaluate
|
|
39
|
+
const [evaluator, err] = parser.parse('x + 2 * 3', { x: 'number' });
|
|
40
|
+
if (!err) {
|
|
41
|
+
const result = evaluator({ x: 1 }); // 7
|
|
42
|
+
// ^? number
|
|
43
|
+
}
|
|
51
44
|
```
|
|
52
45
|
|
|
53
|
-
|
|
46
|
+
## Key Features
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
- **Compile-time validation** - Invalid expressions fail TypeScript compilation
|
|
49
|
+
- **Type inference** - Return types flow through parsing to evaluation
|
|
50
|
+
- **ArkType integration** - Schema types validated at compile-time and runtime
|
|
51
|
+
- **Operator precedence** - Configurable precedence for correct parsing
|
|
57
52
|
|
|
58
|
-
|
|
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
|
-
```
|
|
53
|
+
## Pattern Elements
|
|
62
54
|
|
|
63
|
-
|
|
55
|
+
| Element | Description |
|
|
56
|
+
| --------------- | ----------------------------------- |
|
|
57
|
+
| `lhs(type?)` | Left operand (higher precedence) |
|
|
58
|
+
| `rhs(type?)` | Right operand (same precedence) |
|
|
59
|
+
| `expr(type?)` | Full expression (resets precedence) |
|
|
60
|
+
| `constVal(str)` | Exact string match |
|
|
64
61
|
|
|
65
|
-
|
|
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) |
|
|
62
|
+
## API
|
|
74
63
|
|
|
75
|
-
|
|
64
|
+
### `createParser(nodes)`
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
lhs("number").as("left") // Captures left operand as "left" in the AST node
|
|
79
|
-
```
|
|
66
|
+
Creates a parser from node definitions.
|
|
80
67
|
|
|
81
|
-
###
|
|
68
|
+
### `defineNode(config)`
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
> Runtime evaluation is not yet implemented.
|
|
70
|
+
Defines a grammar node:
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
- `name` - Unique identifier
|
|
73
|
+
- `pattern` - Array of pattern elements
|
|
74
|
+
- `precedence` - Lower binds looser
|
|
75
|
+
- `resultType` - Output type (e.g., `'number'`, `'boolean'`)
|
|
76
|
+
- `eval` - Evaluation function
|
|
87
77
|
|
|
88
|
-
|
|
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
|
-
```
|
|
78
|
+
### `parser.parse(input, schema)`
|
|
97
79
|
|
|
98
|
-
|
|
80
|
+
Returns `[evaluator, null]` on success or `[null, error]` on failure.
|
|
81
|
+
|
|
82
|
+
The evaluator is callable with data matching the schema and has `ast` and `schema` properties.
|
|
83
|
+
|
|
84
|
+
## Examples
|
|
85
|
+
|
|
86
|
+
See [`examples/`](./examples) for more:
|
|
99
87
|
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
88
|
+
- Basic arithmetic with precedence
|
|
89
|
+
- Comparison operators
|
|
90
|
+
- Ternary expressions
|
|
91
|
+
- Custom domain operators
|
|
92
|
+
- Form validation
|
|
105
93
|
|
|
106
94
|
## License
|
|
107
95
|
|
package/dist/context.d.ts
CHANGED
|
@@ -6,18 +6,35 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Grammar is now computed from node schemas via ComputeGrammar<Nodes>.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Schema value type: either a valid arktype type string or a nested object schema.
|
|
11
|
+
* This recursive type allows for nested object schemas like `{ x: { y: 'boolean' } }`.
|
|
12
|
+
*/
|
|
13
|
+
export type SchemaValue = string | {
|
|
14
|
+
readonly [key: string]: SchemaValue;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A schema record maps variable names to their type specifications.
|
|
18
|
+
* Values can be arktype type strings or nested object schemas.
|
|
19
|
+
*/
|
|
20
|
+
export type SchemaRecord = {
|
|
21
|
+
readonly [key: string]: SchemaValue;
|
|
22
|
+
};
|
|
9
23
|
/**
|
|
10
24
|
* Parse context with schema data.
|
|
11
25
|
*
|
|
12
|
-
* @typeParam TData - Schema mapping variable names to their types
|
|
26
|
+
* @typeParam TData - Schema mapping variable names to their types (strings or nested objects)
|
|
13
27
|
*
|
|
14
28
|
* @example
|
|
15
29
|
* ```ts
|
|
16
30
|
* type Ctx = Context<{ x: "number"; y: "string" }>;
|
|
17
31
|
* // x resolves to type "number", y resolves to type "string"
|
|
32
|
+
*
|
|
33
|
+
* type NestedCtx = Context<{ user: { name: "string"; age: "number" } }>;
|
|
34
|
+
* // user resolves to type { name: string; age: number }
|
|
18
35
|
* ```
|
|
19
36
|
*/
|
|
20
|
-
export interface Context<TData extends
|
|
37
|
+
export interface Context<TData extends SchemaRecord = SchemaRecord> {
|
|
21
38
|
/** Schema types for identifier resolution */
|
|
22
39
|
readonly data: TData;
|
|
23
40
|
}
|
|
@@ -25,3 +42,4 @@ export interface Context<TData extends Record<string, string> = Record<string, s
|
|
|
25
42
|
export declare const emptyContext: Context<{}>;
|
|
26
43
|
/** Type alias for empty context */
|
|
27
44
|
export type EmptyContext = Context<{}>;
|
|
45
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,CAAC;AAMnE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,YAAY,GAAG,YAAY;IAChE,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACtB;AAMD,0CAA0C;AAC1C,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,EAAE,CAAgB,CAAC;AAEtD,mCAAmC;AACnC,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC"}
|
package/dist/context.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAyCH,gFAAgF;AAChF,kBAAkB;AAClB,gFAAgF;AAEhF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,YAAY,GAAgB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC"}
|
package/dist/createParser.d.ts
CHANGED
|
@@ -5,11 +5,75 @@
|
|
|
5
5
|
* The returned parser has:
|
|
6
6
|
* - Type-level parsing via Parse<Grammar, Input, Context>
|
|
7
7
|
* - Runtime parsing that mirrors the type structure
|
|
8
|
+
* - Bound evaluator with type-safe data requirements
|
|
8
9
|
*/
|
|
9
|
-
import type { NodeSchema } from
|
|
10
|
-
import type { ComputeGrammar, Grammar } from
|
|
11
|
-
import type { Parse } from
|
|
12
|
-
import type { Context } from
|
|
10
|
+
import type { NodeSchema, SchemaToType } from './schema/index.js';
|
|
11
|
+
import type { ComputeGrammar, Grammar } from './grammar/index.js';
|
|
12
|
+
import type { Parse } from './parse/index.js';
|
|
13
|
+
import type { Context, SchemaRecord } from './context.js';
|
|
14
|
+
import { type } from 'arktype';
|
|
15
|
+
export type { SchemaValue, SchemaRecord } from './context.js';
|
|
16
|
+
/**
|
|
17
|
+
* Extract the outputSchema from an AST node type.
|
|
18
|
+
* Returns the literal schema string if present, otherwise 'unknown'.
|
|
19
|
+
*/
|
|
20
|
+
type ExtractOutputSchema<T> = T extends {
|
|
21
|
+
outputSchema: infer S;
|
|
22
|
+
} ? S : 'unknown';
|
|
23
|
+
/**
|
|
24
|
+
* Convert a SchemaRecord to its corresponding TypeScript data type.
|
|
25
|
+
* Maps each schema value to its runtime type using arktype.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* type Data = SchemaRecordToData<{ x: 'number'; y: 'string' }>;
|
|
29
|
+
* // { x: number; y: string }
|
|
30
|
+
*/
|
|
31
|
+
export type SchemaRecordToData<TSchema extends SchemaRecord> = {
|
|
32
|
+
[K in keyof TSchema]: TSchema[K] extends string ? SchemaToType<TSchema[K]> : TSchema[K] extends SchemaRecord ? SchemaRecordToData<TSchema[K]> : unknown;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A bound evaluator function returned by parser.parse().
|
|
36
|
+
*
|
|
37
|
+
* The evaluator:
|
|
38
|
+
* - Has the nodes pre-bound from the parser
|
|
39
|
+
* - Has the schema captured from the parse call
|
|
40
|
+
* - Requires data that matches the schema types
|
|
41
|
+
* - Returns a value with the type inferred from the AST's outputSchema
|
|
42
|
+
*
|
|
43
|
+
* @typeParam TAST - The parsed AST type
|
|
44
|
+
* @typeParam TSchema - The schema record type
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const [evaluator, err] = parser.parse('x + 1', { x: 'number' });
|
|
49
|
+
* if (!err) {
|
|
50
|
+
* const value = evaluator({ x: 5 }); // value is number
|
|
51
|
+
* const ast = evaluator.ast; // Access the parsed AST
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface Evaluator<TAST, TSchema extends SchemaRecord> {
|
|
56
|
+
/**
|
|
57
|
+
* Evaluate the parsed expression with the given data.
|
|
58
|
+
*
|
|
59
|
+
* @param data - Variable values matching the schema types
|
|
60
|
+
* @returns The evaluated result with type inferred from outputSchema
|
|
61
|
+
*/
|
|
62
|
+
(data: SchemaRecordToData<TSchema>): SchemaToType<ExtractOutputSchema<TAST>>;
|
|
63
|
+
/** The parsed AST */
|
|
64
|
+
readonly ast: TAST;
|
|
65
|
+
/** The schema used during parsing */
|
|
66
|
+
readonly schema: TSchema;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Result of parsing an expression.
|
|
70
|
+
*
|
|
71
|
+
* Returns a tuple of [evaluator, null] on success, or [null, error] on failure.
|
|
72
|
+
*
|
|
73
|
+
* @typeParam TAST - The parsed AST type
|
|
74
|
+
* @typeParam TSchema - The schema record type
|
|
75
|
+
*/
|
|
76
|
+
export type ParseResult<TAST, TSchema extends SchemaRecord> = [Evaluator<TAST, TSchema>, null] | [null, Error];
|
|
13
77
|
/**
|
|
14
78
|
* Parser interface with type-safe parse method.
|
|
15
79
|
*
|
|
@@ -18,30 +82,53 @@ import type { Context } from "./context.js";
|
|
|
18
82
|
*/
|
|
19
83
|
export interface Parser<TGrammar extends Grammar, TNodes extends readonly NodeSchema[]> {
|
|
20
84
|
/**
|
|
21
|
-
* Parse an input string.
|
|
85
|
+
* Parse an input string and return a bound evaluator.
|
|
86
|
+
*
|
|
87
|
+
* Schema values are validated at compile time using arktype.
|
|
88
|
+
* Invalid type strings like 'garbage' will cause TypeScript errors.
|
|
22
89
|
*
|
|
23
90
|
* @param input - The input string to parse
|
|
24
|
-
* @param schema - Schema mapping field names to
|
|
25
|
-
* @returns
|
|
91
|
+
* @param schema - Schema mapping field names to valid arktype type strings or nested object schemas
|
|
92
|
+
* @returns A tuple of [evaluator, null] on success, or [null, error] on failure
|
|
26
93
|
*
|
|
27
94
|
* @example
|
|
28
95
|
* ```ts
|
|
29
|
-
* const
|
|
30
|
-
*
|
|
31
|
-
*
|
|
96
|
+
* const [evaluator, err] = parser.parse("x + 1", { x: 'number' });
|
|
97
|
+
* if (!err) {
|
|
98
|
+
* const value = evaluator({ x: 5 }); // value is number
|
|
99
|
+
* const ast = evaluator.ast;
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* // Valid schema types:
|
|
103
|
+
* parser.parse("x + 1", { x: 'number' }); // primitive
|
|
104
|
+
* parser.parse("x + 1", { x: 'string.email' }); // subtype
|
|
105
|
+
* parser.parse("x + 1", { x: 'number >= 0' }); // constraint
|
|
106
|
+
* parser.parse("x + 1", { x: 'string | number' }); // union
|
|
107
|
+
*
|
|
108
|
+
* // Nested object schemas:
|
|
109
|
+
* parser.parse("user", { user: { name: 'string', age: 'number' } });
|
|
110
|
+
*
|
|
111
|
+
* // Invalid - causes compile error:
|
|
112
|
+
* // parser.parse("x + 1", { x: 'garbage' });
|
|
32
113
|
* ```
|
|
33
114
|
*/
|
|
34
|
-
parse<TInput extends string, TSchema extends
|
|
115
|
+
parse<TInput extends string, const TSchema extends SchemaRecord>(input: ValidatedInput<TGrammar, TInput, Context<TSchema>>, schema: type.validate<TSchema>): ParseResult<ExtractAST<Parse<TGrammar, TInput, Context<TSchema>>>, TSchema>;
|
|
35
116
|
/** The node schemas used to create this parser */
|
|
36
117
|
readonly nodes: TNodes;
|
|
37
118
|
}
|
|
38
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Extract the AST from a Parse result type.
|
|
121
|
+
* Parse returns [AST, remaining] where remaining is '' on success.
|
|
122
|
+
*/
|
|
123
|
+
type ExtractAST<T> = T extends [infer AST, ''] ? AST : never;
|
|
124
|
+
type ValidatedInput<TGrammar extends Grammar, TInput extends string, $ extends Context> = Parse<TGrammar, TInput, $> extends [unknown, ''] ? TInput : never;
|
|
39
125
|
/**
|
|
40
126
|
* Create a type-safe parser from node schemas.
|
|
41
127
|
*
|
|
42
128
|
* The returned parser has both:
|
|
43
129
|
* - Compile-time type inference via Parse<Grammar, Input, Context>
|
|
44
130
|
* - Runtime parsing that matches the type structure
|
|
131
|
+
* - Bound evaluator with type-safe data requirements
|
|
45
132
|
*
|
|
46
133
|
* @param nodes - Tuple of node schemas defining the grammar
|
|
47
134
|
* @returns Parser instance with type-safe parse method
|
|
@@ -49,28 +136,24 @@ type ValidatedInput<TGrammar extends Grammar, TInput extends string, $ extends C
|
|
|
49
136
|
* @example
|
|
50
137
|
* ```ts
|
|
51
138
|
* 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
139
|
*
|
|
61
140
|
* const add = defineNode({
|
|
62
141
|
* name: "add",
|
|
63
|
-
* pattern: [
|
|
142
|
+
* pattern: [lhs("number").as("left"), constVal("+"), rhs("number").as("right")],
|
|
64
143
|
* precedence: 1,
|
|
65
144
|
* resultType: "number",
|
|
145
|
+
* eval: ({ left, right }) => left + right,
|
|
66
146
|
* });
|
|
67
147
|
*
|
|
68
|
-
* const parser = createParser([
|
|
148
|
+
* const parser = createParser([add] as const);
|
|
69
149
|
*
|
|
70
|
-
* // Type-safe parsing!
|
|
71
|
-
* const
|
|
72
|
-
*
|
|
150
|
+
* // Type-safe parsing with bound evaluator!
|
|
151
|
+
* const [evaluator, err] = parser.parse("x + 1", { x: 'number' });
|
|
152
|
+
* if (!err) {
|
|
153
|
+
* const value = evaluator({ x: 5 }); // value is number, equals 6
|
|
154
|
+
* const ast = evaluator.ast;
|
|
155
|
+
* }
|
|
73
156
|
* ```
|
|
74
157
|
*/
|
|
75
158
|
export declare function createParser<const TNodes extends readonly NodeSchema[]>(nodes: TNodes): Parser<ComputeGrammar<TNodes>, TNodes>;
|
|
76
|
-
|
|
159
|
+
//# sourceMappingURL=createParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createParser.d.ts","sourceRoot":"","sources":["../src/createParser.ts"],"names":[],"mappings":"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"}
|
package/dist/createParser.js
CHANGED
|
@@ -5,8 +5,42 @@
|
|
|
5
5
|
* The returned parser has:
|
|
6
6
|
* - Type-level parsing via Parse<Grammar, Input, Context>
|
|
7
7
|
* - Runtime parsing that mirrors the type structure
|
|
8
|
+
* - Bound evaluator with type-safe data requirements
|
|
8
9
|
*/
|
|
9
|
-
import { parse as runtimeParse } from
|
|
10
|
+
import { parse as runtimeParse } from './runtime/parser.js';
|
|
11
|
+
import { type } from 'arktype';
|
|
12
|
+
import { evaluate } from './runtime/eval.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Runtime Validation
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Cache for arktype validators to avoid re-creating them for each validation.
|
|
18
|
+
*/
|
|
19
|
+
const validatorCache = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Get or create an arktype validator for a schema.
|
|
22
|
+
*/
|
|
23
|
+
function getValidator(schema) {
|
|
24
|
+
// For nested objects, create a composite key
|
|
25
|
+
const key = typeof schema === 'string' ? schema : JSON.stringify(schema);
|
|
26
|
+
let validator = validatorCache.get(key);
|
|
27
|
+
if (!validator) {
|
|
28
|
+
validator = type(schema);
|
|
29
|
+
validatorCache.set(key, validator);
|
|
30
|
+
}
|
|
31
|
+
return validator;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate data against a schema at runtime.
|
|
35
|
+
* Throws an error if validation fails.
|
|
36
|
+
*/
|
|
37
|
+
function validateData(data, schema) {
|
|
38
|
+
const validator = getValidator(schema);
|
|
39
|
+
const result = validator(data);
|
|
40
|
+
if (result instanceof type.errors) {
|
|
41
|
+
throw new Error(`Data validation failed: ${result.summary}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
10
44
|
// =============================================================================
|
|
11
45
|
// createParser Factory
|
|
12
46
|
// =============================================================================
|
|
@@ -16,6 +50,7 @@ import { parse as runtimeParse } from "./runtime/parser.js";
|
|
|
16
50
|
* The returned parser has both:
|
|
17
51
|
* - Compile-time type inference via Parse<Grammar, Input, Context>
|
|
18
52
|
* - Runtime parsing that matches the type structure
|
|
53
|
+
* - Bound evaluator with type-safe data requirements
|
|
19
54
|
*
|
|
20
55
|
* @param nodes - Tuple of node schemas defining the grammar
|
|
21
56
|
* @returns Parser instance with type-safe parse method
|
|
@@ -23,35 +58,61 @@ import { parse as runtimeParse } from "./runtime/parser.js";
|
|
|
23
58
|
* @example
|
|
24
59
|
* ```ts
|
|
25
60
|
* 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
61
|
*
|
|
35
62
|
* const add = defineNode({
|
|
36
63
|
* name: "add",
|
|
37
|
-
* pattern: [
|
|
64
|
+
* pattern: [lhs("number").as("left"), constVal("+"), rhs("number").as("right")],
|
|
38
65
|
* precedence: 1,
|
|
39
66
|
* resultType: "number",
|
|
67
|
+
* eval: ({ left, right }) => left + right,
|
|
40
68
|
* });
|
|
41
69
|
*
|
|
42
|
-
* const parser = createParser([
|
|
70
|
+
* const parser = createParser([add] as const);
|
|
43
71
|
*
|
|
44
|
-
* // Type-safe parsing!
|
|
45
|
-
* const
|
|
46
|
-
*
|
|
72
|
+
* // Type-safe parsing with bound evaluator!
|
|
73
|
+
* const [evaluator, err] = parser.parse("x + 1", { x: 'number' });
|
|
74
|
+
* if (!err) {
|
|
75
|
+
* const value = evaluator({ x: 5 }); // value is number, equals 6
|
|
76
|
+
* const ast = evaluator.ast;
|
|
77
|
+
* }
|
|
47
78
|
* ```
|
|
48
79
|
*/
|
|
49
80
|
export function createParser(nodes) {
|
|
50
|
-
|
|
51
|
-
|
|
81
|
+
// Implementation function with explicit typing to avoid deep instantiation
|
|
82
|
+
const parse = (input, schema) => {
|
|
83
|
+
try {
|
|
84
|
+
// type.validate ensures all string values are valid arktype types
|
|
52
85
|
const context = { data: schema };
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
const result = runtimeParse(nodes, input, context);
|
|
87
|
+
// Check for parse failure (empty result or non-empty remaining)
|
|
88
|
+
if (!Array.isArray(result) || result.length !== 2) {
|
|
89
|
+
return [null, new Error(`Parse failed: unexpected result format`)];
|
|
90
|
+
}
|
|
91
|
+
const [ast, remaining] = result;
|
|
92
|
+
if (remaining !== '') {
|
|
93
|
+
return [null, new Error(`Parse failed: unexpected input at '${remaining}'`)];
|
|
94
|
+
}
|
|
95
|
+
// Create the bound evaluator
|
|
96
|
+
const evaluatorFn = (data) => {
|
|
97
|
+
// Validate data against schema at runtime
|
|
98
|
+
validateData(data, schema);
|
|
99
|
+
// Evaluate with the bound nodes - cast for internal use
|
|
100
|
+
// The type system ensures data matches the schema at compile time
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
return evaluate(ast, { data, nodes });
|
|
103
|
+
};
|
|
104
|
+
// Create the evaluator object with call signature and properties
|
|
105
|
+
const evaluator = Object.assign(evaluatorFn, {
|
|
106
|
+
ast: ast,
|
|
107
|
+
schema: schema,
|
|
108
|
+
});
|
|
109
|
+
return [evaluator, null];
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return [null, error instanceof Error ? error : new Error(String(error))];
|
|
113
|
+
}
|
|
56
114
|
};
|
|
115
|
+
// Return the parser object - cast to avoid deep type checking
|
|
116
|
+
return { parse, nodes };
|
|
57
117
|
}
|
|
118
|
+
//# sourceMappingURL=createParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createParser.js","sourceRoot":"","sources":["../src/createParser.ts"],"names":[],"mappings":"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"}
|