@hyperscale0/hsx 1.0.0-alpha.3 → 1.0.0-alpha.5
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/CHANGELOG.md +94 -2
- package/README.md +5 -5
- package/dist/src/archetypes.d.ts +17 -0
- package/dist/src/archetypes.d.ts.map +1 -0
- package/dist/src/archetypes.js +327 -0
- package/dist/src/archetypes.js.map +1 -0
- package/dist/src/ast.d.ts +2 -3
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/check.d.ts +1 -0
- package/dist/src/check.d.ts.map +1 -1
- package/dist/src/check.js +805 -109
- package/dist/src/check.js.map +1 -1
- package/dist/src/entry-overrides.d.ts +44 -0
- package/dist/src/entry-overrides.d.ts.map +1 -0
- package/dist/src/entry-overrides.js +139 -0
- package/dist/src/entry-overrides.js.map +1 -0
- package/dist/src/index.d.ts +2 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lex.d.ts +1 -0
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +1 -1
- package/dist/src/lex.js.map +1 -1
- package/dist/src/lower.d.ts +57 -4
- package/dist/src/lower.d.ts.map +1 -1
- package/dist/src/lower.js +2581 -100
- package/dist/src/lower.js.map +1 -1
- package/dist/src/model.d.ts +167 -7
- package/dist/src/model.d.ts.map +1 -1
- package/dist/src/model.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/spec/hsx-ir.schema.json +407 -9
- package/src/archetypes.ts +340 -0
- package/src/ast.ts +2 -3
- package/src/check.ts +1363 -124
- package/src/entry-overrides.ts +213 -0
- package/src/index.ts +7 -12
- package/src/lex.ts +1 -1
- package/src/lower.ts +2914 -127
- package/src/model.ts +187 -8
- package/src/version.ts +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-driven replacement of existing settlement entries. This is not syntax:
|
|
3
|
+
* callers select a slot that already exists in the parsed program, replace its
|
|
4
|
+
* literal value, then run the ordinary checker and lowerer again.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BlockExpr, Entry, Expr, Program, SettlementDecl } from "./ast.ts";
|
|
8
|
+
|
|
9
|
+
export type ProgramEntryOverrideValue =
|
|
10
|
+
| { readonly kind: "integer"; readonly value: number }
|
|
11
|
+
| { readonly kind: "basis_points"; readonly value: number };
|
|
12
|
+
|
|
13
|
+
export interface ProgramEntryOverrideBounds {
|
|
14
|
+
readonly max: number;
|
|
15
|
+
readonly min: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProgramEntryOverride {
|
|
19
|
+
readonly bounds: ProgramEntryOverrideBounds;
|
|
20
|
+
readonly settlement: string;
|
|
21
|
+
/** Entry keys below the settlement body, for example `["fees", "merchant"]`. */
|
|
22
|
+
readonly path: readonly [string, ...string[]];
|
|
23
|
+
readonly value: ProgramEntryOverrideValue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ProgramEntryOverrideIssue {
|
|
27
|
+
readonly code:
|
|
28
|
+
| "invalid_bounds"
|
|
29
|
+
| "invalid_target"
|
|
30
|
+
| "invalid_value"
|
|
31
|
+
| "missing_settlement";
|
|
32
|
+
readonly message: string;
|
|
33
|
+
readonly settlement: string;
|
|
34
|
+
readonly path: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type OverrideResult =
|
|
38
|
+
| {
|
|
39
|
+
readonly ok: true;
|
|
40
|
+
readonly program: Program;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
readonly issues: readonly ProgramEntryOverrideIssue[];
|
|
44
|
+
readonly ok: false;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function percentRaw(bps: number): string {
|
|
48
|
+
const whole = Math.floor(bps / 100);
|
|
49
|
+
const fraction = bps % 100;
|
|
50
|
+
return fraction === 0
|
|
51
|
+
? `${whole}`
|
|
52
|
+
: `${whole}.${String(fraction).padStart(2, "0").replace(/0$/, "")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function replacementExpr(
|
|
56
|
+
current: Expr,
|
|
57
|
+
value: ProgramEntryOverrideValue,
|
|
58
|
+
): Expr {
|
|
59
|
+
if (value.kind === "integer") {
|
|
60
|
+
return { kind: "number", raw: String(value.value), span: current.span };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
bps: value.value,
|
|
64
|
+
kind: "percent",
|
|
65
|
+
raw: percentRaw(value.value),
|
|
66
|
+
span: current.span,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function replaceEntry(
|
|
71
|
+
block: BlockExpr,
|
|
72
|
+
path: readonly string[],
|
|
73
|
+
value: ProgramEntryOverrideValue,
|
|
74
|
+
): { readonly block?: BlockExpr; readonly issue?: string } {
|
|
75
|
+
const [head, ...tail] = path;
|
|
76
|
+
const matches = block.entries
|
|
77
|
+
.map((entry, index) => ({ entry, index }))
|
|
78
|
+
.filter(({ entry }) => entry.key.name === head);
|
|
79
|
+
if (matches.length === 0) return { issue: `entry ${head} does not exist` };
|
|
80
|
+
if (matches.length > 1) return { issue: `entry ${head} is ambiguous` };
|
|
81
|
+
|
|
82
|
+
const match = matches[0] as { readonly entry: Entry; readonly index: number };
|
|
83
|
+
let nextEntry: Entry;
|
|
84
|
+
if (tail.length === 0) {
|
|
85
|
+
if (
|
|
86
|
+
match.entry.value.kind !== "number" &&
|
|
87
|
+
match.entry.value.kind !== "percent"
|
|
88
|
+
) {
|
|
89
|
+
return { issue: `entry ${head} is not a numeric literal` };
|
|
90
|
+
}
|
|
91
|
+
nextEntry = {
|
|
92
|
+
...match.entry,
|
|
93
|
+
value: replacementExpr(match.entry.value, value),
|
|
94
|
+
};
|
|
95
|
+
} else {
|
|
96
|
+
if (match.entry.value.kind !== "block") {
|
|
97
|
+
return { issue: `entry ${head} is not a block` };
|
|
98
|
+
}
|
|
99
|
+
const nested = replaceEntry(match.entry.value, tail, value);
|
|
100
|
+
if (!nested.block) return nested;
|
|
101
|
+
nextEntry = { ...match.entry, value: nested.block };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
block: {
|
|
106
|
+
...block,
|
|
107
|
+
entries: block.entries.map((entry, index) =>
|
|
108
|
+
index === match.index ? nextEntry : entry,
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function settlementByName(
|
|
115
|
+
program: Program,
|
|
116
|
+
name: string,
|
|
117
|
+
): SettlementDecl | undefined {
|
|
118
|
+
return program.decls.find(
|
|
119
|
+
(decl): decl is SettlementDecl =>
|
|
120
|
+
decl.kind === "settlement" && decl.name.name === name,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateOverride(
|
|
125
|
+
override: ProgramEntryOverride,
|
|
126
|
+
): ProgramEntryOverrideIssue | undefined {
|
|
127
|
+
const { max, min } = override.bounds;
|
|
128
|
+
if (
|
|
129
|
+
!Number.isFinite(min) ||
|
|
130
|
+
!Number.isInteger(min) ||
|
|
131
|
+
min < 0 ||
|
|
132
|
+
!Number.isFinite(max) ||
|
|
133
|
+
!Number.isInteger(max) ||
|
|
134
|
+
max < min
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
137
|
+
code: "invalid_bounds",
|
|
138
|
+
message: `override bounds ${min}..${max} must be finite nonnegative integers with min at most max`,
|
|
139
|
+
path: override.path,
|
|
140
|
+
settlement: override.settlement,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { value } = override.value;
|
|
145
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
146
|
+
return {
|
|
147
|
+
code: "invalid_value",
|
|
148
|
+
message: `override value ${value} must be a finite nonnegative integer`,
|
|
149
|
+
path: override.path,
|
|
150
|
+
settlement: override.settlement,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (value < min || value > max) {
|
|
154
|
+
return {
|
|
155
|
+
code: "invalid_value",
|
|
156
|
+
message: `override value ${value} is outside ${min}..${max}`,
|
|
157
|
+
path: override.path,
|
|
158
|
+
settlement: override.settlement,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Applies every override atomically. A missing or non-literal slot returns
|
|
165
|
+
* issues and no program, so callers never lower a partially configured shape.
|
|
166
|
+
*/
|
|
167
|
+
export function overrideProgramEntries(
|
|
168
|
+
program: Program,
|
|
169
|
+
overrides: readonly ProgramEntryOverride[],
|
|
170
|
+
): OverrideResult {
|
|
171
|
+
let current = program;
|
|
172
|
+
const issues: ProgramEntryOverrideIssue[] = [];
|
|
173
|
+
for (const override of overrides) {
|
|
174
|
+
const validationIssue = validateOverride(override);
|
|
175
|
+
if (validationIssue) {
|
|
176
|
+
issues.push(validationIssue);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const settlement = settlementByName(current, override.settlement);
|
|
180
|
+
if (!settlement) {
|
|
181
|
+
issues.push({
|
|
182
|
+
code: "missing_settlement",
|
|
183
|
+
message: `settlement ${override.settlement} does not exist`,
|
|
184
|
+
path: override.path,
|
|
185
|
+
settlement: override.settlement,
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const replaced = replaceEntry(
|
|
190
|
+
settlement.body,
|
|
191
|
+
override.path,
|
|
192
|
+
override.value,
|
|
193
|
+
);
|
|
194
|
+
if (!replaced.block) {
|
|
195
|
+
issues.push({
|
|
196
|
+
code: "invalid_target",
|
|
197
|
+
message: replaced.issue ?? "entry override failed",
|
|
198
|
+
path: override.path,
|
|
199
|
+
settlement: override.settlement,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
current = {
|
|
204
|
+
...current,
|
|
205
|
+
decls: current.decls.map((decl) =>
|
|
206
|
+
decl === settlement ? { ...settlement, body: replaced.block! } : decl,
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return issues.length > 0
|
|
211
|
+
? { issues, ok: false }
|
|
212
|
+
: { ok: true, program: current };
|
|
213
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
BlockExpr,
|
|
3
|
-
CallExpr,
|
|
4
|
-
ListExpr,
|
|
5
|
-
PercentExpr,
|
|
6
|
-
PortDecl,
|
|
7
|
-
PortRefExpr,
|
|
8
|
-
SettlementDecl,
|
|
9
|
-
} from "./ast.ts";
|
|
10
|
-
export { lineColAt } from "./ast.ts";
|
|
11
1
|
export { checkProgram } from "./check.ts";
|
|
12
2
|
export { compile } from "./compile.ts";
|
|
13
|
-
export
|
|
3
|
+
export {
|
|
4
|
+
overrideProgramEntries,
|
|
5
|
+
type ProgramEntryOverride,
|
|
6
|
+
type ProgramEntryOverrideIssue,
|
|
7
|
+
type ProgramEntryOverrideValue,
|
|
8
|
+
} from "./entry-overrides.ts";
|
|
14
9
|
export { lowerProgram, MONEY_EVENT_BUDGET } from "./lower.ts";
|
|
15
10
|
export { parseProgram } from "./parse.ts";
|
|
16
|
-
export {
|
|
11
|
+
export type { Program } from "./ast.ts";
|