@vertekum/schema-builder 0.1.6 → 0.2.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/docs/scales.md CHANGED
@@ -1,22 +1,25 @@
1
1
  # Scale expressions
2
2
 
3
3
  A scale expression enumerates a numeric name set at build time — `100-900/100` instead
4
- of nine names by hand. Two modes:
4
+ of nine names by hand. Two modes, plus word affixes around either:
5
5
 
6
6
  | Syntax | Mode |
7
7
  | ---------------------- | --------------------------------------------------------------------- |
8
8
  | [`min-max/step`](#additive-scales) | **additive** — arithmetic steps |
9
9
  | [`min-max*factor~quantum`](#geometric-scales) | **geometric** — a ratio, optionally quantized |
10
+ | [`(formula)xs`, `xs(formula)`, `x(formula)s`](#affixed-scales) | **affixed** — word fragments wrapping the names |
10
11
 
11
12
  Both are inclusive at both ends, and both enumerate **names** — every step must land on
12
13
  a whole number, because a token name cannot be `31.25`.
13
14
 
14
15
  ## Additive scales
15
16
 
16
- `min-max/step`: from `min`, adding `step`, up to and including `max`.
17
+ `min-max/step`: from `min`, adding `step`, up to and including `max`. The step is
18
+ optional — `2-4` means `2-4/1`.
17
19
 
18
20
  ```dfn
19
21
  scale = 100-900/100 # 100 200 300 400 500 600 700 800 900
22
+ steps = 2-4 # 2 3 4
20
23
  ```
21
24
 
22
25
  Different regions of one scale are alternation:
@@ -59,6 +62,30 @@ type-scale = 16-64*1.25~4
59
62
 
60
63
  (The next raw step, 76.29…, exceeds 64 — the series ends.)
61
64
 
65
+ ## Affixed scales
66
+
67
+ A parenthesized formula may carry word fragments on either or both ends, as **one
68
+ term** — no whitespace between affix and parenthesis. The full formula grammar applies
69
+ inside the parentheses:
70
+
71
+ | Expression | Names |
72
+ | ---------------- | ------------------------------- |
73
+ | `(2-4)xs` | `2xs 3xs 4xs` |
74
+ | `xs(2-4)` | `xs2 xs3 xs4` |
75
+ | `x(2-8/2)s` | `x2s x4s x6s x8s` |
76
+ | `(02-08/2)xxl` | `02xxl 04xxl 06xxl 08xxl` |
77
+ | `(16-64*1.25~4)` | `16 20 24 32 40 48 60` — bare parentheses are grouping only |
78
+
79
+ An affix is a word fragment: letters first, then letters, digits, and hyphens. The
80
+ affixes wrap the **name**; the numeric series underneath is unchanged — padding sits
81
+ inside the affixes (`02xxl`, never `xxl02` from a suffix form), and every rule below
82
+ (inclusive bounds, integrality, collisions) binds on the numbers exactly as if the
83
+ affixes were not there. One formula per term — `(2-4)x(1-2)` is refused.
84
+
85
+ ```dfn
86
+ t-shirt = (2-4)xs | xsmall | small | medium | large | x(2-8/2)l
87
+ ```
88
+
62
89
  ## The semantics, precisely
63
90
 
64
91
  These rules are fixed; the same evaluator (in `@vertekum/core`) generates the names
@@ -34,7 +34,7 @@
34
34
  "patterns": []
35
35
  },
36
36
  "pragma": {
37
- "match": "^(id|title|description|scope)\\s+(?=\")",
37
+ "match": "^(id|title|description|scope|sealed)\\s+(?=\")",
38
38
  "captures": {
39
39
  "1": {
40
40
  "name": "keyword.other.pragma.dfn"
@@ -61,12 +61,15 @@
61
61
  }
62
62
  },
63
63
  "production": {
64
- "match": "^(\\d+[A-Za-z][A-Za-z0-9-]*|[A-Za-z][A-Za-z0-9-]*)\\s*(=)",
64
+ "match": "^(:)?(\\d+[A-Za-z][A-Za-z0-9-]*|[A-Za-z][A-Za-z0-9-]*)\\s*(=)",
65
65
  "captures": {
66
66
  "1": {
67
- "name": "entity.name.function.production.dfn"
67
+ "name": "storage.modifier.private.dfn"
68
68
  },
69
69
  "2": {
70
+ "name": "entity.name.function.production.dfn"
71
+ },
72
+ "3": {
70
73
  "name": "keyword.operator.assignment.dfn"
71
74
  }
72
75
  }
@@ -79,6 +82,9 @@
79
82
  {
80
83
  "include": "#reference"
81
84
  },
85
+ {
86
+ "include": "#affixed-range"
87
+ },
82
88
  {
83
89
  "include": "#range"
84
90
  },
@@ -195,6 +201,9 @@
195
201
  }
196
202
  },
197
203
  "patterns": [
204
+ {
205
+ "include": "#reference"
206
+ },
198
207
  {
199
208
  "name": "variable.parameter.member.dfn",
200
209
  "match": "\\d+[A-Za-z][A-Za-z0-9-]*|[A-Za-z][A-Za-z0-9-]*|\\d+"
@@ -209,7 +218,7 @@
209
218
  },
210
219
  "range": {
211
220
  "name": "constant.numeric.range.dfn",
212
- "match": "\\d+-\\d+[*/]\\d+(?:\\.\\d+)?(?:~\\d+)?"
221
+ "match": "\\d+-\\d+(?:[*/]\\d+(?:\\.\\d+)?)?(?:~\\d+)?"
213
222
  },
214
223
  "number": {
215
224
  "name": "constant.numeric.dfn",
@@ -246,6 +255,26 @@
246
255
  "name": {
247
256
  "name": "constant.language.name.dfn",
248
257
  "match": "\\d+[A-Za-z][A-Za-z0-9-]*|[A-Za-z][A-Za-z0-9-]*"
258
+ },
259
+ "affixed-range": {
260
+ "match": "([A-Za-z][A-Za-z0-9-]*)?(\\()(\\d+-\\d+(?:[*/]\\d+(?:\\.\\d+)?)?(?:~\\d+)?)(\\))([A-Za-z][A-Za-z0-9-]*)?",
261
+ "captures": {
262
+ "1": {
263
+ "name": "constant.language.name.dfn"
264
+ },
265
+ "2": {
266
+ "name": "punctuation.definition.scale.begin.dfn"
267
+ },
268
+ "3": {
269
+ "name": "constant.numeric.range.dfn"
270
+ },
271
+ "4": {
272
+ "name": "punctuation.definition.scale.end.dfn"
273
+ },
274
+ "5": {
275
+ "name": "constant.language.name.dfn"
276
+ }
277
+ }
249
278
  }
250
279
  }
251
280
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertekum/schema-builder",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "Declare token vocabularies as grammar; build them into JSON Schema files",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -37,13 +37,13 @@
37
37
  "./cli": "./src/cli.ts"
38
38
  },
39
39
  "peerDependencies": {
40
- "@vertekum/core": "0.1.2"
40
+ "@vertekum/core": "0.2.0"
41
41
  },
42
42
  "dependencies": {
43
43
  "zod": "^3.25.76"
44
44
  },
45
45
  "devDependencies": {
46
- "@vertekum/core": "0.1.2",
46
+ "@vertekum/core": "0.2.0",
47
47
  "@vertekum/schema-vocabulary": "0.1.0"
48
48
  }
49
49
  }
package/src/api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ActivateContext } from '@vertekum/core';
2
- import { schemaBuildCommand, schemaLintCommand } from './cli';
2
+ import { schemaBuildCommand, schemaFmtCommand, schemaLintCommand } from './cli';
3
3
  import type { schemaBuilderManifest } from './index';
4
4
 
5
5
  export {
@@ -8,16 +8,29 @@ export {
8
8
  evaluateProduction,
9
9
  type TreeNode,
10
10
  } from './build';
11
- export { buildModule, schemaBuildCommand, schemaLintCommand } from './cli';
11
+ export {
12
+ buildModule,
13
+ schemaBuildCommand,
14
+ schemaFmtCommand,
15
+ schemaLintCommand,
16
+ } from './cli';
12
17
  export { emit, isStamped, STAMP_PREFIX, stamp } from './emit';
18
+ export {
19
+ type DfnFix,
20
+ type FormatOptions,
21
+ fixSource,
22
+ formatSource,
23
+ resolveIndent,
24
+ } from './format';
13
25
  export { type DfnDiagnostic, lintModule } from './lint';
14
26
  export { parse } from './parser';
15
27
  export { type ResolvedModule, resolveModule } from './resolve';
16
28
 
17
- /** Headless activation: contributes the `schema build` and `schema lint` commands. */
29
+ /** Headless activation: contributes the `schema build`, `schema lint`, and `schema fmt` commands. */
18
30
  export function activate(
19
31
  ctx: ActivateContext<typeof schemaBuilderManifest>,
20
32
  ): void {
21
33
  ctx.commands.register(schemaBuildCommand);
22
34
  ctx.commands.register(schemaLintCommand);
35
+ ctx.commands.register(schemaFmtCommand);
23
36
  }
package/src/ast.ts CHANGED
@@ -9,12 +9,16 @@
9
9
  export interface Module {
10
10
  /** `use "<specifier>" [as <alias>]` statements, in order. */
11
11
  uses: Array<{ spec: string; alias?: string }>;
12
- /** Productions by name. `root` is never stored here. */
12
+ /** Productions by name, declaration order. `root` is never stored here. */
13
13
  productions: Map<string, Node>;
14
+ /** Productions declared `:name = …` — invisible to importers, always inlined. */
15
+ private: Set<string>;
14
16
  /** The reserved `root` production, when declared. */
15
17
  root: Node | undefined;
16
- /** Pragmas (`id "…"`, `title "…"`, `description "…"`) — document metadata for emission. */
18
+ /** Pragmas — document metadata and the file's nature. */
17
19
  meta: ModuleMeta;
20
+ /** Lint-surfaced notes recorded while parsing (deprecated forms), with positions. */
21
+ notes: Array<{ line: number; column: number; message: string }>;
18
22
  }
19
23
 
20
24
  /** The pragma statements a module may carry, each at most once. */
@@ -23,11 +27,17 @@ export interface ModuleMeta {
23
27
  title?: string;
24
28
  description?: string;
25
29
  /**
26
- * `scope "branch"`: the schema governs only the top-level branches it names — the document
27
- * root stays unsealed, so sibling vocabularies can bind over the same files. The default,
28
- * `document`, seals the root: nothing beyond the granted names may exist.
30
+ * The file's NATURE. `document`: emitted, root required — consumers reference its
31
+ * top-level names. `def`: emitted, root optional — a pattern source; its root also
32
+ * lands as `$defs.<filename>`. `inline`: never emitted — expansion only. Absent:
33
+ * root → document, rootless → def.
29
34
  */
30
- scope?: 'document' | 'branch';
35
+ scope?: 'document' | 'def' | 'inline';
36
+ /**
37
+ * `sealed "false"` leaves the document top unsealed so sibling vocabularies can bind
38
+ * over the same files. Default true. (`scope "branch"` parses as a deprecated alias.)
39
+ */
40
+ sealed?: boolean;
31
41
  }
32
42
 
33
43
  export type Node = Alt | Path | Name | Range | Ref | Group;
@@ -80,12 +90,16 @@ export interface Range extends Position {
80
90
  step: number;
81
91
  quantum?: number;
82
92
  pad?: number;
93
+ /** Name affixes from the parenthesized form (`(2-4)xs`); values stay numeric. */
94
+ prefix?: string;
95
+ suffix?: string;
83
96
  }
84
97
 
85
98
  /**
86
99
  * `<name>` / `<@name>`; `open` records a trailing `*` (the set admits additions). Set
87
100
  * modifiers: `[a, b]` is PICK (only the listed members), `![a, b]` is OMIT (the set minus
88
- * them) — a modified reference is a new set.
101
+ * them) — a modified reference is a new set. A member may itself be a REFERENCE, denoting
102
+ * every name in the referenced set (set algebra with sets as operands).
89
103
  */
90
104
  export interface Ref extends Position {
91
105
  kind: 'ref';
@@ -94,8 +108,8 @@ export interface Ref extends Position {
94
108
  /** `<@module/production>` — resolve `name` in this import alone (the collision resolver). */
95
109
  from?: string;
96
110
  open: boolean;
97
- pick: string[];
98
- omit: string[];
111
+ pick: Array<string | Ref>;
112
+ omit: Array<string | Ref>;
99
113
  }
100
114
 
101
115
  /** `[ … ]`; `open` records a trailing `*` before the closing bracket. */