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.
Files changed (58) hide show
  1. package/README.md +61 -73
  2. package/dist/context.d.ts +20 -2
  3. package/dist/context.d.ts.map +1 -0
  4. package/dist/context.js +1 -0
  5. package/dist/context.js.map +1 -0
  6. package/dist/createParser.d.ts +109 -26
  7. package/dist/createParser.d.ts.map +1 -0
  8. package/dist/createParser.js +80 -19
  9. package/dist/createParser.js.map +1 -0
  10. package/dist/errors.d.ts +121 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +186 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/grammar/index.d.ts +19 -14
  15. package/dist/grammar/index.d.ts.map +1 -0
  16. package/dist/grammar/index.js +4 -3
  17. package/dist/grammar/index.js.map +1 -0
  18. package/dist/index.d.ts +19 -11
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +16 -7
  21. package/dist/index.js.map +1 -0
  22. package/dist/parse/index.d.ts +101 -27
  23. package/dist/parse/index.d.ts.map +1 -0
  24. package/dist/parse/index.js +1 -0
  25. package/dist/parse/index.js.map +1 -0
  26. package/dist/performance.bench.d.ts +10 -0
  27. package/dist/performance.bench.d.ts.map +1 -0
  28. package/dist/performance.bench.js +379 -0
  29. package/dist/performance.bench.js.map +1 -0
  30. package/dist/primitive/index.d.ts +27 -35
  31. package/dist/primitive/index.d.ts.map +1 -0
  32. package/dist/primitive/index.js +22 -17
  33. package/dist/primitive/index.js.map +1 -0
  34. package/dist/runtime/eval.d.ts +157 -0
  35. package/dist/runtime/eval.d.ts.map +1 -0
  36. package/dist/runtime/eval.js +206 -0
  37. package/dist/runtime/eval.js.map +1 -0
  38. package/dist/runtime/infer.d.ts +2 -1
  39. package/dist/runtime/infer.d.ts.map +1 -0
  40. package/dist/runtime/infer.js +3 -2
  41. package/dist/runtime/infer.js.map +1 -0
  42. package/dist/runtime/parser.d.ts +92 -11
  43. package/dist/runtime/parser.d.ts.map +1 -0
  44. package/dist/runtime/parser.js +522 -47
  45. package/dist/runtime/parser.js.map +1 -0
  46. package/dist/schema/index.d.ts +230 -27
  47. package/dist/schema/index.d.ts.map +1 -0
  48. package/dist/schema/index.js +54 -28
  49. package/dist/schema/index.js.map +1 -0
  50. package/dist/static/infer.d.ts +4 -3
  51. package/dist/static/infer.d.ts.map +1 -0
  52. package/dist/static/infer.js +1 -0
  53. package/dist/static/infer.js.map +1 -0
  54. package/package.json +35 -4
  55. package/dist/combinators/index.d.ts +0 -57
  56. package/dist/combinators/index.js +0 -104
  57. package/dist/static/parser.d.ts +0 -7
  58. package/dist/static/parser.js +0 -6
package/README.md CHANGED
@@ -1,107 +1,95 @@
1
1
  # Stringent
2
2
 
3
- A type-safe expression parser for TypeScript with compile-time validation and inference.
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
- ## 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
7
+ ## Install
13
8
 
14
9
  ```bash
15
10
  npm install stringent
16
- # or
17
- pnpm add stringent
18
11
  ```
19
12
 
20
- ## Usage
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 { 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
- });
16
+ import { createParser, defineNode, constVal, lhs, rhs } from 'stringent';
36
17
 
37
- // Binary operators with precedence
18
+ // Define operators
38
19
  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",
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: "mul",
47
- pattern: [lhs("number").as("left"), constVal("*"), rhs("number").as("right")],
48
- precedence: 2, // Higher = binds tighter
49
- resultType: "number",
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
- ### Create a Parser
46
+ ## Key Features
54
47
 
55
- ```typescript
56
- const parser = createParser([numberLit, add, mul] as const);
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
- // 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
- ```
53
+ ## Pattern Elements
62
54
 
63
- ### Pattern Elements
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
- | 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) |
62
+ ## API
74
63
 
75
- Use `.as(name)` to capture pattern elements as named bindings in the AST:
64
+ ### `createParser(nodes)`
76
65
 
77
- ```typescript
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
- ### Runtime Evaluation (Coming Soon)
68
+ ### `defineNode(config)`
82
69
 
83
- > **Note**
84
- > Runtime evaluation is not yet implemented.
70
+ Defines a grammar node:
85
71
 
86
- Add `eval` to compute values at runtime:
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
- ```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
- ```
78
+ ### `parser.parse(input, schema)`
97
79
 
98
- ## Key Features
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
- - **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
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 Record<string, string> = Record<string, string>> {
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
@@ -11,3 +11,4 @@
11
11
  // =============================================================================
12
12
  /** Empty context (no schema variables) */
13
13
  export const emptyContext = { data: {} };
14
+ //# sourceMappingURL=context.js.map
@@ -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"}
@@ -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 "./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";
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 their types
25
- * @returns Parse result with computed type
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 result = parser.parse("1+2", {});
30
- * // Type: Parse<Grammar, "1+2", Context<{}>>
31
- * // Value: [{ type: "binary", name: "add", left: {...}, right: {...} }, ""]
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 Record<string, string>>(input: ValidatedInput<TGrammar, TInput, Context<TSchema>>, schema: TSchema): Parse<TGrammar, TInput, Context<TSchema>>;
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
- type ValidatedInput<TGrammar extends Grammar, TInput extends string, $ extends Context> = Parse<TGrammar, TInput, $> extends [any, any] ? TInput : never;
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: [expr("number"), constVal("+"), expr("number")],
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([numberLit, add] as const);
148
+ * const parser = createParser([add] as const);
69
149
  *
70
- * // Type-safe parsing!
71
- * const result = parser.parse("1+2", {});
72
- * // Type: [BinaryNode<"add", NumberNode<"1">, NumberNode<"2">, "number">, ""]
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
- export {};
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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"}
@@ -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 "./runtime/parser.js";
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: [expr("number"), constVal("+"), expr("number")],
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([numberLit, add] as const);
70
+ * const parser = createParser([add] as const);
43
71
  *
44
- * // Type-safe parsing!
45
- * const result = parser.parse("1+2", {});
46
- * // Type: [BinaryNode<"add", NumberNode<"1">, NumberNode<"2">, "number">, ""]
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
- return {
51
- parse(input, schema) {
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
- return runtimeParse(nodes, input, context);
54
- },
55
- nodes,
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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"}