@hyperscale0/hsx 5.2.0 → 5.4.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -2
  3. package/bin/hsx.ts +0 -2
  4. package/dist/bin/hsx.js +0 -2
  5. package/dist/bin/hsx.js.map +1 -1
  6. package/dist/src/ast.d.ts +0 -1
  7. package/dist/src/ast.d.ts.map +1 -1
  8. package/dist/src/ast.js +0 -11
  9. package/dist/src/ast.js.map +1 -1
  10. package/dist/src/cli.d.ts +0 -2
  11. package/dist/src/cli.d.ts.map +1 -1
  12. package/dist/src/cli.js +10 -5
  13. package/dist/src/cli.js.map +1 -1
  14. package/dist/src/compile.d.ts.map +1 -1
  15. package/dist/src/compile.js +204 -107
  16. package/dist/src/compile.js.map +1 -1
  17. package/dist/src/diagnostics.d.ts +14 -0
  18. package/dist/src/diagnostics.d.ts.map +1 -0
  19. package/dist/src/diagnostics.js +20 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/header-source.d.ts +3 -0
  22. package/dist/src/header-source.d.ts.map +1 -0
  23. package/dist/src/header-source.js +41 -0
  24. package/dist/src/header-source.js.map +1 -0
  25. package/dist/src/headers.d.ts.map +1 -1
  26. package/dist/src/headers.js +4 -6
  27. package/dist/src/headers.js.map +1 -1
  28. package/dist/src/keywords.d.ts +8 -2
  29. package/dist/src/keywords.d.ts.map +1 -1
  30. package/dist/src/keywords.js +13 -5
  31. package/dist/src/keywords.js.map +1 -1
  32. package/dist/src/lex.d.ts +1 -1
  33. package/dist/src/lex.d.ts.map +1 -1
  34. package/dist/src/lex.js +11 -3
  35. package/dist/src/lex.js.map +1 -1
  36. package/dist/src/parse.d.ts.map +1 -1
  37. package/dist/src/parse.js +22 -5
  38. package/dist/src/parse.js.map +1 -1
  39. package/dist/src/std-bundle.js +12 -12
  40. package/dist/src/std-bundle.js.map +1 -1
  41. package/dist/src/tunables.d.ts.map +1 -1
  42. package/dist/src/tunables.js +2 -1
  43. package/dist/src/tunables.js.map +1 -1
  44. package/dist/src/version.d.ts +1 -2
  45. package/dist/src/version.d.ts.map +1 -1
  46. package/dist/src/version.js +1 -2
  47. package/dist/src/version.js.map +1 -1
  48. package/docs/README.md +54 -7
  49. package/docs/headers.md +1 -1
  50. package/package.json +3 -3
  51. package/src/ast.ts +0 -10
  52. package/src/cli.ts +13 -7
  53. package/src/compile.ts +296 -133
  54. package/src/diagnostics.ts +28 -0
  55. package/src/header-source.ts +54 -0
  56. package/src/headers.ts +4 -6
  57. package/src/keywords.ts +13 -5
  58. package/src/lex.ts +13 -5
  59. package/src/parse.ts +29 -5
  60. package/src/std-bundle.ts +12 -12
  61. package/src/tunables.ts +6 -1
  62. package/src/version.ts +1 -2
  63. package/std/cards.hsx +12 -0
  64. package/std/collections.hsx +11 -0
  65. package/std/escrow.hsx +16 -2
  66. package/std/financing.hsx +36 -5
  67. package/std/insurance.hsx +20 -5
  68. package/std/lending.hsx +14 -0
  69. package/std/marketplace.hsx +14 -0
  70. package/std/money.hsx +13 -0
  71. package/std/reporting.hsx +10 -0
  72. package/std/savings.hsx +28 -11
  73. package/std/travel.hsx +22 -4
  74. package/std/wallet.hsx +9 -0
@@ -0,0 +1,28 @@
1
+ import type { Diagnostic, Span } from "./ast.ts";
2
+
3
+ export class CompileFailure extends Error {
4
+ constructor(readonly diagnostic: Diagnostic) {
5
+ super(diagnostic.message);
6
+ }
7
+ }
8
+ export function fail(
9
+ expr: { span: Span; source?: string },
10
+ message: string,
11
+ fix: string,
12
+ ): never {
13
+ return failWithCode(expr, "HSX1001", message, fix);
14
+ }
15
+ export function failWithCode(
16
+ expr: { span: Span; source?: string },
17
+ code: string,
18
+ message: string,
19
+ fix: string,
20
+ ): never {
21
+ throw new CompileFailure({
22
+ code,
23
+ message,
24
+ fix,
25
+ span: expr.span,
26
+ ...(expr.source ? { source: expr.source } : {}),
27
+ });
28
+ }
@@ -0,0 +1,54 @@
1
+ import { parseProgram } from "./parse.ts";
2
+ import { CompileFailure, fail } from "./diagnostics.ts";
3
+
4
+ /** Compilation and editor metadata admit the same named header declarations. */
5
+ export function parseHeader(source: string, name: string) {
6
+ const parsed = parseProgram(source);
7
+ if (parsed.diagnostics.length)
8
+ throw new CompileFailure({ ...parsed.diagnostics[0]!, source: name });
9
+ const program = parsed.program;
10
+ const locate = (value: unknown): void => {
11
+ if (!value || typeof value !== "object") return;
12
+ if ("span" in value) Object.assign(value, { source: name });
13
+ for (const child of Object.values(value)) locate(child);
14
+ };
15
+ locate(program);
16
+ if (!program.header || program.name !== name)
17
+ fail(
18
+ program,
19
+ `${name}: expected header ${name}`,
20
+ `start with header ${name}`,
21
+ );
22
+ const names = new Set<string>();
23
+ for (const decl of program.decls) {
24
+ if (decl.kind !== "instrument") continue;
25
+ if (names.has(decl.name))
26
+ fail(
27
+ decl,
28
+ `duplicate instrument ${name}.${decl.name}`,
29
+ "give each instrument a distinct name",
30
+ );
31
+ names.add(decl.name);
32
+ const parameters = new Set<string>();
33
+ for (const parameter of decl.parameters) {
34
+ if (parameters.has(parameter.key))
35
+ fail(
36
+ parameter,
37
+ `duplicate parameter ${parameter.key}`,
38
+ "declare each parameter once",
39
+ );
40
+ parameters.add(parameter.key);
41
+ const type =
42
+ parameter.value.kind === "default"
43
+ ? parameter.value.type
44
+ : parameter.value;
45
+ if (type.kind === "call" && !["enum", "integer"].includes(type.name))
46
+ fail(
47
+ type,
48
+ `unsupported tunable constructor ${type.name}`,
49
+ "Only enum(choices) and integer(minimum, maximum) are supported tunable constructors. This constructor's restrictions cannot be enforced.",
50
+ );
51
+ }
52
+ }
53
+ return program;
54
+ }
package/src/headers.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  parameterDiagnostics,
4
4
  } from "./binding-contract.ts";
5
5
  import { tunableBounds } from "./tunables.ts";
6
- import { parseProgram } from "./parse.ts";
6
+ import { parseHeader } from "./header-source.ts";
7
7
  import { bundledStandardLibrary, type StandardLibrary } from "./std-library.ts";
8
8
  import type { BlockExpr, Expr } from "./ast.ts";
9
9
 
@@ -32,9 +32,7 @@ export function headerManifest(
32
32
  headers: names.map((name) => {
33
33
  const source = library.source(name);
34
34
  if (!source) throw new Error(`Missing standard header ${name}`);
35
- const parsed = parseProgram(source);
36
- if (parsed.diagnostics.length)
37
- throw new Error(`${name}: ${parsed.diagnostics[0]!.message}`);
35
+ const parsed = parseHeader(source, name);
38
36
  const spelling = (expression: Expr) =>
39
37
  source.slice(expression.span.start, expression.span.end).trim();
40
38
  const actions = (
@@ -66,7 +64,7 @@ export function headerManifest(
66
64
  };
67
65
  return {
68
66
  name,
69
- objects: parsed.program.decls
67
+ objects: parsed.decls
70
68
  .filter((d) => d.kind === "instrument")
71
69
  .map((decl) => {
72
70
  const tunables = decl.parameters.map((parameter) => {
@@ -184,7 +182,7 @@ export function headerManifest(
184
182
  if (subject?.kind !== "block") continue;
185
183
  for (const field of subject.entries) {
186
184
  const type = spelling(field.value);
187
- if (type !== "adapter") fields.set(field.key, type);
185
+ if (field.key !== "adapter") fields.set(field.key, type);
188
186
  }
189
187
  }
190
188
  return [...fields].map(([name, type]) => ({ name, type }));
package/src/keywords.ts CHANGED
@@ -9,11 +9,11 @@ export const KEYWORD_HELP = {
9
9
  header: ["Names a reusable library of instruments."],
10
10
  use: ["Makes a header's instruments available to this program."],
11
11
  party: [
12
- "Declares a person or business that can own accounts or act in an agreement.",
12
+ "Declares a person, business or staff party. Staff authority requires a permission role.",
13
13
  "Parties",
14
14
  ],
15
15
  role: ["Names the responsibility a declared party has in this product."],
16
- currency: ["Sets the currency used by the program's money amounts."],
16
+ currency: ["Declares SAR, the currency supported by this compiler."],
17
17
  instrument: [
18
18
  "Defines an agreement's fields, states and permitted actions.",
19
19
  "Instruments",
@@ -37,7 +37,9 @@ export const KEYWORD_HELP = {
37
37
  of: [
38
38
  "Names the owner of an account, or the records included in a count or sum.",
39
39
  ],
40
- when: ["Includes rules only when a parameter selects the stated option."],
40
+ when: [
41
+ "Includes clauses for a selected enum value or a field present on a bound reference.",
42
+ ],
41
43
  constraints: [
42
44
  "Requires parameter values to agree with each other before the program can be used.",
43
45
  ],
@@ -61,9 +63,15 @@ export const KEYWORD_HELP = {
61
63
  "Requires a referenced agreement to be in one of the listed states.",
62
64
  "State requirement",
63
65
  ],
64
- by: ["Names who performs the stated operation."],
65
- for: ["Identifies the subject of the stated rule."],
66
66
  is: ["Selects rules for one named parameter choice."],
67
+ has: ["Selects clauses when a bound reference declares the named field."],
68
+ family: ["Names an instrument family or the family of an evidence check."],
69
+ check: ["Names the provider check whose evidence the action requires."],
70
+ result: ["Names the required outcome of an evidence check."],
71
+ maxAge: ["Limits how old the required evidence may be."],
72
+ instruction: ["Binds evidence to the captured boundary instruction."],
73
+ boundary: ["Dispatches a reserved move through a bound ADL adapter."],
74
+ key: ["Names a move within its action."],
67
75
  unique: [
68
76
  "Rejects a repeated combination of values in the named namespace.",
69
77
  "Unique requirement",
package/src/lex.ts CHANGED
@@ -23,9 +23,8 @@ export const KEYWORDS = [
23
23
  "from",
24
24
  "to",
25
25
  "in",
26
- "by",
27
- "for",
28
26
  "is",
27
+ "has",
29
28
  "unique",
30
29
  "on",
31
30
  "count",
@@ -35,10 +34,17 @@ export const KEYWORDS = [
35
34
  "and",
36
35
  "timezone",
37
36
  "evidence",
37
+ "family",
38
+ "check",
39
+ "result",
40
+ "maxAge",
41
+ "instruction",
38
42
  "reserve",
39
43
  "post",
40
44
  "void",
41
45
  "capture",
46
+ "boundary",
47
+ "key",
42
48
  "fee",
43
49
  "shares",
44
50
  "object",
@@ -99,9 +105,11 @@ export function scan(
99
105
  kind = "name";
100
106
  text = name[0];
101
107
  } else if (tail[0] === '"') {
102
- const quoted = /^"(?:[^"\\\n]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*"/.exec(
103
- tail,
104
- );
108
+ const quoted =
109
+ // oxlint-disable-next-line no-control-regex -- JSON strings must escape raw control characters.
110
+ /^"(?:[^"\\\u0000-\u001f]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*"/.exec(
111
+ tail,
112
+ );
105
113
  if (!quoted) {
106
114
  diagnostics.push({
107
115
  code: "HSX1000",
package/src/parse.ts CHANGED
@@ -37,6 +37,18 @@ export function parseProgram(source: string): {
37
37
  },
38
38
  ],
39
39
  };
40
+ if (/^\s*[[{]/.test(source))
41
+ return {
42
+ program: empty,
43
+ diagnostics: [
44
+ {
45
+ code: "HSX1014",
46
+ message: "JSON is not HSX source",
47
+ fix: 'write program name "Title" followed by HSX declarations',
48
+ span: { start: source.search(/\S/), end: source.search(/\S/) + 1 },
49
+ },
50
+ ],
51
+ };
40
52
  const result = lex(source);
41
53
  if (result.diagnostics.length)
42
54
  return { program: empty, diagnostics: result.diagnostics };
@@ -71,12 +83,12 @@ class Parser {
71
83
  this.take();
72
84
  return true;
73
85
  }
74
- private fail(message: string, fix: string): never {
86
+ private fail(message: string, fix: string, span = this.peek().span): never {
75
87
  throw new ParseFailure({
76
88
  code: "HSX1000",
77
89
  message,
78
90
  fix,
79
- span: this.peek().span,
91
+ span,
80
92
  });
81
93
  }
82
94
  private expect(text: string): void {
@@ -114,11 +126,19 @@ class Parser {
114
126
  );
115
127
  program.title = JSON.parse(this.take().text) as string;
116
128
  }
129
+ let currencyDeclared = false;
117
130
  while (this.peek().kind !== "eof") {
118
131
  this.separators();
119
132
  if (this.peek().kind === "eof") break;
120
133
  const start = this.peek().span.start;
121
- if (this.eat("currency")) {
134
+ if (this.at("currency")) {
135
+ if (currencyDeclared)
136
+ this.fail(
137
+ "currency is declared twice",
138
+ "keep one currency declaration",
139
+ );
140
+ this.take();
141
+ currencyDeclared = true;
122
142
  program.currency = this.identifier();
123
143
  continue;
124
144
  }
@@ -326,8 +346,12 @@ class Parser {
326
346
  private operator(): Expr {
327
347
  const operator = this.take();
328
348
  if (!["==", "!=", "<", "<=", ">", ">="].includes(operator.text))
329
- this.fail("expected a comparison", "write ==, !=, <, <=, >, or >=");
330
- return this.node(operator.text);
349
+ this.fail(
350
+ "expected a comparison",
351
+ "write ==, !=, <, <=, >, or >=",
352
+ operator.span,
353
+ );
354
+ return { kind: "text", value: operator.text, span: operator.span };
331
355
  }
332
356
  private requirement(): BlockExpr {
333
357
  if (this.eat("unique")) {