@hatua/expressions 0.0.0 → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Gomes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/ast.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The AST.
3
+ *
4
+ * Every node carries `at`, the offset of its first character in the Template.
5
+ * That is what lets a diagnostic point at the failing sub-expression rather
6
+ * than at the whole field, and it is why the grammar's one per-language rule
7
+ * exists at all — reading the current offset is the single construct pigeon and
8
+ * Peggy spell differently.
9
+ */
10
+ export interface TemplateNode {
11
+ readonly kind: 'Template';
12
+ readonly segments: readonly Segment[];
13
+ }
14
+ export type Segment = TextNode | HoleNode;
15
+ /** Literal text between holes. */
16
+ export interface TextNode {
17
+ readonly kind: 'Text';
18
+ readonly value: string;
19
+ }
20
+ /** One `{{ … }}`. */
21
+ export interface HoleNode {
22
+ readonly kind: 'Hole';
23
+ readonly at: number;
24
+ readonly expr: Expression;
25
+ }
26
+ export type Expression = NameNode | MemberNode | IndexNode | ProjectNode | CallNode | UnaryNode | BinaryNode | TernaryNode | LiteralNode;
27
+ /** A bare identifier: the root of a path, or a function's namespace. */
28
+ export interface NameNode {
29
+ readonly kind: 'Name';
30
+ readonly at: number;
31
+ readonly name: string;
32
+ }
33
+ export interface MemberNode {
34
+ readonly kind: 'Member';
35
+ readonly at: number;
36
+ readonly object: Expression;
37
+ readonly name: string;
38
+ }
39
+ export interface IndexNode {
40
+ readonly kind: 'Index';
41
+ readonly at: number;
42
+ readonly object: Expression;
43
+ readonly index: Expression;
44
+ }
45
+ /** `a[]` — "that field of every element". */
46
+ export interface ProjectNode {
47
+ readonly kind: 'Project';
48
+ readonly at: number;
49
+ readonly object: Expression;
50
+ }
51
+ /**
52
+ * A call. `object` is whatever the `(` was applied to, which is a Member for
53
+ * every well-formed call — `dt.now()` is Call(Member(Name(dt), now)). Keeping
54
+ * it structural rather than special-casing `namespace.name` is what lets
55
+ * `f(a)(b)` and `json.parse(x)['k']` compose without extra grammar.
56
+ */
57
+ export interface CallNode {
58
+ readonly kind: 'Call';
59
+ readonly at: number;
60
+ readonly object: Expression;
61
+ readonly args: readonly Expression[];
62
+ }
63
+ export type UnaryOperator = '!' | '-';
64
+ export interface UnaryNode {
65
+ readonly kind: 'Unary';
66
+ readonly at: number;
67
+ readonly op: UnaryOperator;
68
+ readonly operand: Expression;
69
+ }
70
+ export type BinaryOperator = '??' | '||' | '&&' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '+' | '-' | '*' | '/' | '%';
71
+ export interface BinaryNode {
72
+ readonly kind: 'Binary';
73
+ readonly at: number;
74
+ readonly op: BinaryOperator;
75
+ readonly left: Expression;
76
+ readonly right: Expression;
77
+ }
78
+ export interface TernaryNode {
79
+ readonly kind: 'Ternary';
80
+ readonly at: number;
81
+ readonly cond: Expression;
82
+ /**
83
+ * Named `whenTrue`/`whenFalse` rather than `then`/`otherwise`: an object with
84
+ * a `then` property is a thenable, and an AST node that changes behaviour
85
+ * when it happens to be awaited is a trap nobody would find twice.
86
+ */
87
+ readonly whenTrue: Expression;
88
+ readonly whenFalse: Expression;
89
+ }
90
+ export type LiteralType = 'text' | 'number' | 'boolean' | 'null';
91
+ export interface LiteralNode {
92
+ readonly kind: 'Literal';
93
+ readonly at: number;
94
+ readonly type: LiteralType;
95
+ readonly value: string | number | boolean | null;
96
+ }
97
+ /** Every node that carries an offset. */
98
+ export type Located = HoleNode | Expression;
99
+ export declare const isExpression: (node: Segment | Expression) => node is Expression;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { DiagnosticCode, Severity } from '#generated/diagnostics.js';
2
+ export type { DiagnosticCode, Phase, Severity } from '#generated/diagnostics.js';
3
+ export { DIAGNOSTICS } from '#generated/diagnostics.js';
4
+ export interface Diagnostic {
5
+ readonly code: DiagnosticCode;
6
+ /** Errors block Publish; warnings inform and block nothing. */
7
+ readonly severity: Severity;
8
+ /** Offset of the failing construct within the Template. */
9
+ readonly at: number;
10
+ /** The Slot this Template was being resolved into, when there is one. */
11
+ readonly slot?: string;
12
+ readonly message: string;
13
+ }
14
+ /**
15
+ * Fill a `{name}` template.
16
+ *
17
+ * Two lines rather than a formatting library because the Go half has to produce
18
+ * byte-identical output: a message that reads one way in the builder and
19
+ * another in a runner's logs is a support ticket nobody can close.
20
+ */
21
+ export declare function formatMessage(template: string, args?: Readonly<Record<string, string>>): string;
22
+ export declare function diagnostic(code: DiagnosticCode, at: number, args?: Readonly<Record<string, string>>, slot?: string): Diagnostic;
23
+ /**
24
+ * Thrown by `resolve` and `resolveAll`.
25
+ *
26
+ * It carries a list, not a single failure: `resolveAll` does a whole `with:`
27
+ * map in one call and reports every failure together rather than stopping at
28
+ * the first, because a user fixing one field at a time is a user running the
29
+ * workflow five times to find five mistakes.
30
+ */
31
+ export declare class ExpressionError extends Error {
32
+ readonly diagnostics: readonly Diagnostic[];
33
+ constructor(diagnostics: readonly Diagnostic[]);
34
+ /** The first failure's code — the common case is exactly one. */
35
+ get code(): DiagnosticCode | undefined;
36
+ }
37
+ export declare const errorsIn: (diagnostics: readonly Diagnostic[]) => readonly Diagnostic[];
38
+ /** Whether a set of diagnostics blocks Publish. Warnings never do. */
39
+ export declare const blocksPublish: (diagnostics: readonly Diagnostic[]) => boolean;
@@ -0,0 +1,2 @@
1
+ import { FunctionImpl } from '../resolve.js';
2
+ export declare const dtFunctions: Record<string, FunctionImpl>;
@@ -0,0 +1,2 @@
1
+ import { FunctionImpl } from '../resolve.js';
2
+ export declare const jsonFunctions: Record<string, FunctionImpl>;
@@ -0,0 +1,2 @@
1
+ import { FunctionImpl } from '../resolve.js';
2
+ export declare const listFunctions: Record<string, FunctionImpl>;
@@ -0,0 +1,2 @@
1
+ import { FunctionImpl } from '../resolve.js';
2
+ export declare const numFunctions: Record<string, FunctionImpl>;
@@ -0,0 +1,29 @@
1
+ import { FunctionSpec } from '#generated/builtins.js';
2
+ import { ExpressionError } from '../errors.js';
3
+ import { FunctionImpl, FunctionRegistry } from '../resolve.js';
4
+ import { Value } from '../value.js';
5
+ /** A well-typed argument that is nonetheless unusable. */
6
+ export declare function badArgument(name: string, param: string, actual: string): ExpressionError;
7
+ /**
8
+ * Hatua's own functions, checked against the declaration.
9
+ *
10
+ * The check is the point of decision 9: each language supplies implementations
11
+ * only, and verifies its registry against the shared YAML at load time. A
12
+ * function implemented here and not declared — or declared and not implemented
13
+ * — is a divergence between the two runtimes waiting to happen, and it fails
14
+ * here rather than at the call site in production.
15
+ */
16
+ export declare function coreFunctions(): FunctionRegistry;
17
+ /**
18
+ * Merge a Host's functions into Hatua's.
19
+ *
20
+ * A collision is a loud error rather than a silent winner. Either answer —
21
+ * Hatua wins, or the Host wins — is a workflow that behaves differently
22
+ * depending on which registry was built first, and neither is discoverable from
23
+ * the workflow.
24
+ */
25
+ export declare function mergeRegistries(...registries: readonly FunctionRegistry[]): FunctionRegistry;
26
+ /** Build a registry from Host-declared signatures and their implementations. */
27
+ export declare function hostFunctions(specs: readonly FunctionSpec[], implementations: Readonly<Record<string, FunctionImpl>>): FunctionRegistry;
28
+ /** Everything a function implementation is allowed to assume about its list arguments. */
29
+ export declare const asList: (value: Value) => readonly Value[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { FunctionImpl } from '../resolve.js';
2
+ export declare const textFunctions: Record<string, FunctionImpl>;
@@ -0,0 +1,33 @@
1
+ import { ValueType } from '../value.js';
2
+ export interface ParamSpec {
3
+ readonly name: string;
4
+ readonly type: ValueType;
5
+ /** One sentence saying what this parameter is for. */
6
+ readonly description: string;
7
+ readonly optional: boolean;
8
+ readonly variadic: boolean;
9
+ }
10
+ export interface NamespaceSpec {
11
+ readonly namespace: string;
12
+ /** One sentence describing the group, shown above its functions. */
13
+ readonly summary: string;
14
+ }
15
+ /**
16
+ * The namespaces, for a picker that groups functions before listing them.
17
+ * Declarations only — reading this pulls in no implementation.
18
+ */
19
+ export declare const CORE_NAMESPACES: readonly NamespaceSpec[];
20
+ export interface FunctionSpec {
21
+ readonly namespace: string;
22
+ readonly name: string;
23
+ /** `namespace.name` — how an expression addresses it. */
24
+ readonly qualified: string;
25
+ readonly summary: string;
26
+ readonly params: readonly ParamSpec[];
27
+ readonly returns: ValueType;
28
+ }
29
+ /**
30
+ * What every implementation is checked against at load time. Neither
31
+ * language may add, rename or re-sign a function on its own.
32
+ */
33
+ export declare const CORE_FUNCTIONS: readonly FunctionSpec[];
@@ -0,0 +1,17 @@
1
+ /** Errors block Publish. Warnings inform and block nothing. */
2
+ export type Severity = 'error' | 'warning';
3
+ /** `check` is design time; `eval` is run time. */
4
+ export type Phase = 'check' | 'eval';
5
+ export type DiagnosticCode = 'EXPR_PARSE_ERROR' | 'EXPR_UNKNOWN_REFERENCE' | 'EXPR_UNKNOWN_FUNCTION' | 'EXPR_ARITY_MISMATCH' | 'EXPR_ARGUMENT_TYPE' | 'EXPR_TYPE_MISMATCH' | 'EXPR_TYPE_UNKNOWN' | 'EXPR_OPERAND_TYPE' | 'EXPR_FUNCTION_COLLISION' | 'EVAL_MISSING_PATH' | 'EVAL_TYPE_MISMATCH' | 'EVAL_OPERAND_TYPE' | 'EVAL_COMPARE_NULL' | 'EVAL_COMPARE_TYPES' | 'EVAL_NUMERIC_OVERFLOW' | 'EVAL_DIVISION_BY_ZERO' | 'EVAL_UNKNOWN_FUNCTION' | 'EVAL_ARITY_MISMATCH' | 'EVAL_BAD_ARGUMENT';
6
+ export interface DiagnosticSpec {
7
+ readonly code: DiagnosticCode;
8
+ readonly severity: Severity;
9
+ readonly phase: Phase;
10
+ /** Template. `{name}` holes are filled by `formatMessage`. */
11
+ readonly message: string;
12
+ }
13
+ /**
14
+ * Severity is part of the shared contract. A code that errors here and
15
+ * warns in Go would let a workflow publish from one builder and not another.
16
+ */
17
+ export declare const DIAGNOSTICS: Record<DiagnosticCode, DiagnosticSpec>;
@@ -0,0 +1,209 @@
1
+ /** Provides information pointing to a location within a source. */
2
+ export interface Location {
3
+ /** Line in the parsed source (1-based). */
4
+ readonly line: number
5
+ /** Column in the parsed source (1-based). */
6
+ readonly column: number
7
+ /** Offset in the parsed source (0-based). */
8
+ readonly offset: number
9
+ }
10
+
11
+ /**
12
+ * Anything that can successfully be converted to a string with `String()`
13
+ * so that it can be used in error messages.
14
+ *
15
+ * The GrammarLocation class in Peggy is a good example.
16
+ */
17
+ export interface GrammarSourceObject {
18
+ readonly toString: () => string
19
+
20
+ /**
21
+ * If specified, allows the grammar source to be embedded in a larger file
22
+ * at some offset.
23
+ */
24
+ readonly offset?: undefined | ((loc: Location) => Location)
25
+ }
26
+
27
+ /**
28
+ * Most often, you just use a string with the file name.
29
+ */
30
+ export type GrammarSource = string | GrammarSourceObject
31
+
32
+ /** The `start` and `end` position's of an object within the source. */
33
+ export interface LocationRange {
34
+ /**
35
+ * A string or object that was supplied to the `parse()` call as the
36
+ * `grammarSource` option.
37
+ */
38
+ readonly source: GrammarSource
39
+ /** Position at the beginning of the expression. */
40
+ readonly start: Location
41
+ /** Position after the end of the expression. */
42
+ readonly end: Location
43
+ }
44
+
45
+ /**
46
+ * Expected a literal string, like `"foo"i`.
47
+ */
48
+ export interface LiteralExpectation {
49
+ readonly type: 'literal'
50
+ readonly text: string
51
+ readonly ignoreCase: boolean
52
+ }
53
+
54
+ /**
55
+ * Range of characters, like `a-z`
56
+ */
57
+ export type ClassRange = [start: string, end: string]
58
+
59
+ export interface ClassParts extends Array<string | ClassRange> {}
60
+
61
+ /**
62
+ * Expected a class, such as `[^acd-gz]i`
63
+ */
64
+ export interface ClassExpectation {
65
+ readonly type: 'class'
66
+ readonly parts: ClassParts
67
+ readonly inverted: boolean
68
+ readonly ignoreCase: boolean
69
+ }
70
+
71
+ /**
72
+ * Expected any character, with `.`
73
+ */
74
+ export interface AnyExpectation {
75
+ readonly type: 'any'
76
+ }
77
+
78
+ /**
79
+ * Expected the end of input.
80
+ */
81
+ export interface EndExpectation {
82
+ readonly type: 'end'
83
+ }
84
+
85
+ /**
86
+ * Expected some other input. These are specified with a rule's
87
+ * "human-readable name", or with the `expected(message, location)`
88
+ * function.
89
+ */
90
+ export interface OtherExpectation {
91
+ readonly type: 'other'
92
+ readonly description: string
93
+ }
94
+
95
+ export type Expectation =
96
+ | AnyExpectation
97
+ | ClassExpectation
98
+ | EndExpectation
99
+ | LiteralExpectation
100
+ | OtherExpectation
101
+
102
+ /**
103
+ * Pass an array of these into `SyntaxError.prototype.format()`
104
+ */
105
+ export interface SourceText {
106
+ /**
107
+ * Identifier of an input that was used as a grammarSource in parse().
108
+ */
109
+ readonly source: GrammarSource
110
+ /** Source text of the input. */
111
+ readonly text: string
112
+ }
113
+
114
+ export declare class SyntaxError extends globalThis.SyntaxError {
115
+ /**
116
+ * Constructs the human-readable message from the machine representation.
117
+ *
118
+ * @param expected Array of expected items, generated by the parser
119
+ * @param found Any text that will appear as found in the input instead of
120
+ * expected
121
+ */
122
+ static buildMessage(expected: Expectation[], found?: string | null | undefined): string
123
+ readonly expected: Expectation[]
124
+ readonly found: string | null | undefined
125
+ readonly location: LocationRange
126
+ readonly name: string
127
+ constructor(
128
+ message: string,
129
+ expected: Expectation[],
130
+ found: string | null,
131
+ location: LocationRange,
132
+ )
133
+
134
+ /**
135
+ * With good sources, generates a feature-rich error message pointing to the
136
+ * error in the input.
137
+ * @param sources List of {source, text} objects that map to the input.
138
+ */
139
+ format(sources: SourceText[]): string
140
+ }
141
+
142
+ /**
143
+ * Trace execution of the parser.
144
+ */
145
+ export interface ParserTracer {
146
+ trace: (event: ParserTracerEvent) => void
147
+ }
148
+
149
+ export type ParserTracerEvent =
150
+ | {
151
+ readonly type: 'rule.enter'
152
+ readonly rule: string
153
+ readonly location: LocationRange
154
+ }
155
+ | {
156
+ readonly type: 'rule.fail'
157
+ readonly rule: string
158
+ readonly location: LocationRange
159
+ }
160
+ | {
161
+ readonly type: 'rule.match'
162
+ readonly rule: string
163
+ readonly location: LocationRange
164
+ /** Return value from the rule. */
165
+ readonly result: unknown
166
+ }
167
+
168
+ export type StartRuleNames = 'Template' | 'ExpressionEntry'
169
+ export interface ParseOptions<T extends StartRuleNames = 'Template'> {
170
+ /**
171
+ * String or object that will be attached to the each `LocationRange` object
172
+ * created by the parser. For example, this can be path to the parsed file
173
+ * or even the File object.
174
+ */
175
+ readonly grammarSource?: GrammarSource
176
+ readonly startRule?: T
177
+ readonly tracer?: ParserTracer
178
+
179
+ // Internal use only:
180
+ readonly peg$library?: boolean
181
+ // Internal use only:
182
+ peg$currPos?: number
183
+ // Internal use only:
184
+ peg$silentFails?: number
185
+ // Internal use only:
186
+ peg$maxFailExpected?: Expectation[]
187
+ // Extra application-specific properties
188
+ [key: string]: unknown
189
+ }
190
+
191
+ export declare const StartRules: StartRuleNames[]
192
+ export declare const parse: typeof ParseFunction
193
+
194
+ // Overload of ParseFunction for each allowedStartRule
195
+
196
+ declare function ParseFunction<Options extends ParseOptions<'Template'>>(
197
+ input: string,
198
+ options?: Options,
199
+ ): any
200
+
201
+ declare function ParseFunction<Options extends ParseOptions<'ExpressionEntry'>>(
202
+ input: string,
203
+ options?: Options,
204
+ ): any
205
+
206
+ declare function ParseFunction<Options extends ParseOptions<StartRuleNames>>(
207
+ input: string,
208
+ options?: Options,
209
+ ): any
@@ -0,0 +1,11 @@
1
+ export { CORE_FUNCTIONS, CORE_NAMESPACES, type FunctionSpec, type NamespaceSpec, type ParamSpec, } from '#generated/builtins.js';
2
+ export type * from './ast.js';
3
+ export * from './errors.js';
4
+ export { coreFunctions, hostFunctions, mergeRegistries, } from './functions/registry.js';
5
+ export * from './parse.js';
6
+ export * from './reference.js';
7
+ export * from './resolve.js';
8
+ export { templateToSexp, toSexp } from './sexp.js';
9
+ export * from './types.js';
10
+ export * from './validate.js';
11
+ export * from './value.js';