@yakcc/cli 0.5.0-alpha.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 +214 -0
- package/LICENSE-ATOMS +38 -0
- package/README.md +76 -0
- package/dist/bin.js +10884 -0
- package/dist/bin.js.map +1 -0
- package/dist/blocks/ascii-char/impl.ts +16 -0
- package/dist/blocks/ascii-char/proof/manifest.json +8 -0
- package/dist/blocks/ascii-char/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/ascii-char/spec.yak +80 -0
- package/dist/blocks/ascii-digit-set/impl.ts +9 -0
- package/dist/blocks/ascii-digit-set/proof/manifest.json +8 -0
- package/dist/blocks/ascii-digit-set/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/ascii-digit-set/spec.yak +66 -0
- package/dist/blocks/base64-alphabet/impl.ts +98 -0
- package/dist/blocks/base64-alphabet/proof/manifest.json +8 -0
- package/dist/blocks/base64-alphabet/proof/tests.fast-check.ts +18 -0
- package/dist/blocks/base64-alphabet/spec.yak +110 -0
- package/dist/blocks/bracket/impl.ts +20 -0
- package/dist/blocks/bracket/proof/manifest.json +8 -0
- package/dist/blocks/bracket/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/bracket/spec.yak +85 -0
- package/dist/blocks/char-code/impl.ts +15 -0
- package/dist/blocks/char-code/proof/manifest.json +8 -0
- package/dist/blocks/char-code/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/char-code/spec.yak +80 -0
- package/dist/blocks/comma/impl.ts +20 -0
- package/dist/blocks/comma/proof/manifest.json +8 -0
- package/dist/blocks/comma/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/comma/spec.yak +80 -0
- package/dist/blocks/comma-separated-integers/impl.ts +85 -0
- package/dist/blocks/comma-separated-integers/proof/manifest.json +8 -0
- package/dist/blocks/comma-separated-integers/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/comma-separated-integers/spec.yak +84 -0
- package/dist/blocks/digit/impl.ts +12 -0
- package/dist/blocks/digit/proof/manifest.json +8 -0
- package/dist/blocks/digit/proof/tests.fast-check.ts +21 -0
- package/dist/blocks/digit/spec.yak +79 -0
- package/dist/blocks/digit-or-throw/impl.ts +20 -0
- package/dist/blocks/digit-or-throw/proof/manifest.json +8 -0
- package/dist/blocks/digit-or-throw/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/digit-or-throw/spec.yak +80 -0
- package/dist/blocks/empty-list-content/impl.ts +20 -0
- package/dist/blocks/empty-list-content/proof/manifest.json +8 -0
- package/dist/blocks/empty-list-content/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/empty-list-content/spec.yak +80 -0
- package/dist/blocks/eof-check/impl.ts +16 -0
- package/dist/blocks/eof-check/proof/manifest.json +8 -0
- package/dist/blocks/eof-check/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/eof-check/spec.yak +76 -0
- package/dist/blocks/integer/impl.ts +29 -0
- package/dist/blocks/integer/proof/manifest.json +8 -0
- package/dist/blocks/integer/proof/tests.fast-check.ts +21 -0
- package/dist/blocks/integer/spec.yak +84 -0
- package/dist/blocks/list-of-ints/impl.ts +168 -0
- package/dist/blocks/list-of-ints/proof/manifest.json +8 -0
- package/dist/blocks/list-of-ints/proof/tests.fast-check.ts +23 -0
- package/dist/blocks/list-of-ints/spec.yak +103 -0
- package/dist/blocks/lru-node/impl.ts +50 -0
- package/dist/blocks/lru-node/proof/manifest.json +8 -0
- package/dist/blocks/lru-node/proof/tests.fast-check.ts +16 -0
- package/dist/blocks/lru-node/spec.yak +92 -0
- package/dist/blocks/memoize/impl.ts +60 -0
- package/dist/blocks/memoize/proof/manifest.json +8 -0
- package/dist/blocks/memoize/proof/tests.fast-check.ts +15 -0
- package/dist/blocks/memoize/spec.yak +91 -0
- package/dist/blocks/non-ascii-rejector/impl.ts +14 -0
- package/dist/blocks/non-ascii-rejector/proof/manifest.json +8 -0
- package/dist/blocks/non-ascii-rejector/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/non-ascii-rejector/spec.yak +67 -0
- package/dist/blocks/nonempty-list-content/impl.ts +116 -0
- package/dist/blocks/nonempty-list-content/proof/manifest.json +8 -0
- package/dist/blocks/nonempty-list-content/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/nonempty-list-content/spec.yak +88 -0
- package/dist/blocks/optional-whitespace/impl.ts +21 -0
- package/dist/blocks/optional-whitespace/proof/manifest.json +8 -0
- package/dist/blocks/optional-whitespace/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/optional-whitespace/spec.yak +76 -0
- package/dist/blocks/peek-char/impl.ts +15 -0
- package/dist/blocks/peek-char/proof/manifest.json +8 -0
- package/dist/blocks/peek-char/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/peek-char/spec.yak +76 -0
- package/dist/blocks/position-step/impl.ts +21 -0
- package/dist/blocks/position-step/proof/manifest.json +8 -0
- package/dist/blocks/position-step/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/position-step/spec.yak +85 -0
- package/dist/blocks/queue-drain/impl.ts +74 -0
- package/dist/blocks/queue-drain/proof/manifest.json +8 -0
- package/dist/blocks/queue-drain/proof/tests.fast-check.ts +16 -0
- package/dist/blocks/queue-drain/spec.yak +119 -0
- package/dist/blocks/semver-component-parser/impl.ts +115 -0
- package/dist/blocks/semver-component-parser/proof/manifest.json +8 -0
- package/dist/blocks/semver-component-parser/proof/tests.fast-check.ts +19 -0
- package/dist/blocks/semver-component-parser/spec.yak +109 -0
- package/dist/blocks/signed-integer/impl.ts +40 -0
- package/dist/blocks/signed-integer/proof/manifest.json +8 -0
- package/dist/blocks/signed-integer/proof/tests.fast-check.ts +21 -0
- package/dist/blocks/signed-integer/spec.yak +84 -0
- package/dist/blocks/string-from-position/impl.ts +18 -0
- package/dist/blocks/string-from-position/proof/manifest.json +8 -0
- package/dist/blocks/string-from-position/proof/tests.fast-check.ts +20 -0
- package/dist/blocks/string-from-position/spec.yak +85 -0
- package/dist/blocks/timer-handle/impl.ts +66 -0
- package/dist/blocks/timer-handle/proof/manifest.json +8 -0
- package/dist/blocks/timer-handle/proof/tests.fast-check.ts +18 -0
- package/dist/blocks/timer-handle/spec.yak +67 -0
- package/dist/blocks/whitespace/impl.ts +20 -0
- package/dist/blocks/whitespace/proof/manifest.json +8 -0
- package/dist/blocks/whitespace/proof/tests.fast-check.ts +21 -0
- package/dist/blocks/whitespace/spec.yak +80 -0
- package/dist/bootstrap/expected-failures.json +4 -0
- package/dist/bootstrap/expected-roots.json +49890 -0
- package/dist/bootstrap/yakcc.registry.sqlite +0 -0
- package/dist/index.js +10826 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "char-code",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The full input string."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "position",
|
|
11
|
+
"type": "number",
|
|
12
|
+
"description": "Zero-based index to read from."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "code",
|
|
18
|
+
"type": "number",
|
|
19
|
+
"description": "UTF-16 char code at position, in [0, 65535]."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Return the UTF-16 char code of the character at the given position in the input string. Throws RangeError if position is negative or out of bounds.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "non-negative",
|
|
35
|
+
"description": "Result is always >= 0."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "bounded",
|
|
39
|
+
"description": "Result is in [0, 65535]."
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"errorConditions": [
|
|
43
|
+
{
|
|
44
|
+
"description": "position < 0.",
|
|
45
|
+
"errorType": "RangeError"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"description": "position >= input.length.",
|
|
49
|
+
"errorType": "RangeError"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"nonFunctional": {
|
|
53
|
+
"time": "O(1)",
|
|
54
|
+
"space": "O(1)",
|
|
55
|
+
"purity": "pure",
|
|
56
|
+
"threadSafety": "safe"
|
|
57
|
+
},
|
|
58
|
+
"propertyTests": [
|
|
59
|
+
{
|
|
60
|
+
"id": "char-code-zero",
|
|
61
|
+
"description": "charCode('0abc', 0) returns 48"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "char-code-a",
|
|
65
|
+
"description": "charCode('abc', 0) returns 97"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "char-code-oob",
|
|
69
|
+
"description": "charCode('abc', 3) throws RangeError"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "char-code-negative",
|
|
73
|
+
"description": "charCode('abc', -1) throws RangeError"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "char-code-bracket",
|
|
77
|
+
"description": "charCode('[', 0) returns 91"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-COMMA-001: comma follows the same positional combinator convention as bracket.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: Consistent positional-return API across all terminal matchers means they
|
|
5
|
+
// can be composed without wrapping. Returns next position rather than a boolean.
|
|
6
|
+
|
|
7
|
+
export function comma(input: string, position: number): number {
|
|
8
|
+
if (position < 0) {
|
|
9
|
+
throw new RangeError(`Position ${position} is negative`);
|
|
10
|
+
}
|
|
11
|
+
if (position >= input.length) {
|
|
12
|
+
throw new SyntaxError(`Expected ',' at position ${position} but reached end of input`);
|
|
13
|
+
}
|
|
14
|
+
if (input[position] !== ",") {
|
|
15
|
+
throw new SyntaxError(
|
|
16
|
+
`Expected ',' at position ${position} but found ${JSON.stringify(input[position])}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return position + 1;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the comma block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// The definitive property-test corpus lives in the parent seed package's
|
|
7
|
+
// seed.test.ts (Suite 4: "property-test corpora"). This file satisfies the
|
|
8
|
+
// L0 proof/manifest.json "property_tests" artifact requirement and makes
|
|
9
|
+
// the test IDs declared in spec.yak traceable to this directory.
|
|
10
|
+
//
|
|
11
|
+
// Test IDs declared in spec.yak:
|
|
12
|
+
// comma-match
|
|
13
|
+
// comma-mid
|
|
14
|
+
// comma-mismatch
|
|
15
|
+
// comma-eof
|
|
16
|
+
// comma-negative
|
|
17
|
+
|
|
18
|
+
// Re-export the implementation so runners importing this artifact directly
|
|
19
|
+
// can access the block function.
|
|
20
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "comma",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The full input string."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "position",
|
|
11
|
+
"type": "number",
|
|
12
|
+
"description": "Zero-based position to match at."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "newPosition",
|
|
18
|
+
"type": "number",
|
|
19
|
+
"description": "position + 1 after matching the comma."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Assert that the character at position is ',' (U+002C), then return position + 1. Throws SyntaxError if the character does not match or position is at end of input.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "advance-1",
|
|
35
|
+
"description": "Returns position + 1 on success."
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"errorConditions": [
|
|
39
|
+
{
|
|
40
|
+
"description": "Character at position is not ','.",
|
|
41
|
+
"errorType": "SyntaxError"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"description": "position >= input.length (end of input).",
|
|
45
|
+
"errorType": "SyntaxError"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"description": "position < 0.",
|
|
49
|
+
"errorType": "RangeError"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"nonFunctional": {
|
|
53
|
+
"time": "O(1)",
|
|
54
|
+
"space": "O(1)",
|
|
55
|
+
"purity": "pure",
|
|
56
|
+
"threadSafety": "safe"
|
|
57
|
+
},
|
|
58
|
+
"propertyTests": [
|
|
59
|
+
{
|
|
60
|
+
"id": "comma-match",
|
|
61
|
+
"description": "comma(',abc', 0) returns 1"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "comma-mid",
|
|
65
|
+
"description": "comma('a,b', 1) returns 2"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "comma-mismatch",
|
|
69
|
+
"description": "comma('abc', 0) throws SyntaxError"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "comma-eof",
|
|
73
|
+
"description": "comma('', 0) throws SyntaxError"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "comma-negative",
|
|
77
|
+
"description": "comma(',', -1) throws RangeError"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-COMMASEP-001: comma-separated-integers parses the interior of a list after the first element.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: The import-type declarations below declare the composition graph. Relative "./"
|
|
5
|
+
// imports are used so TypeScript can resolve the sibling block declarations; seedRegistry
|
|
6
|
+
// passes blockPatterns: ["./"] to parseBlock so extractComposition captures these as
|
|
7
|
+
// sub-block references in the provenance manifest. `import type` is used because
|
|
8
|
+
// strict-subset validates each block in an isolated single-file ts-morph project where
|
|
9
|
+
// sibling value imports resolve to `any` and fail no-untyped-imports. Type-only imports
|
|
10
|
+
// are unconditionally skipped by that rule.
|
|
11
|
+
// Composition graph — captured by extractComposition via the @yakcc/seeds/ builtin pattern.
|
|
12
|
+
// WI-T05-fix: import paths use "@yakcc/seeds/blocks/<name>" so the compile resolver's
|
|
13
|
+
// SUB_BLOCK_IMPORT_RE (which matches "@yakcc/seeds/" prefix) can extract sub-block deps.
|
|
14
|
+
// All imports are "import type" so the strict-subset validator skips them unconditionally
|
|
15
|
+
// and vitest/Vite erases them at transpile time (no runtime module resolution needed).
|
|
16
|
+
import type { comma } from "@yakcc/seeds/blocks/comma";
|
|
17
|
+
import type { integer } from "@yakcc/seeds/blocks/integer";
|
|
18
|
+
import type { optionalWhitespace } from "@yakcc/seeds/blocks/optional-whitespace";
|
|
19
|
+
import type { peekChar } from "@yakcc/seeds/blocks/peek-char";
|
|
20
|
+
|
|
21
|
+
// Suppress "imported but never used as a value" by surfacing type aliases.
|
|
22
|
+
type _Comma = typeof comma;
|
|
23
|
+
type _Integer = typeof integer;
|
|
24
|
+
type _OptionalWhitespace = typeof optionalWhitespace;
|
|
25
|
+
type _PeekChar = typeof peekChar;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Implementation
|
|
29
|
+
//
|
|
30
|
+
// Each section mirrors one sub-block's contract boundary exactly:
|
|
31
|
+
// peekChar(input, pos) → return char at pos or null (no advance)
|
|
32
|
+
// comma(input, pos) → assert ','; return pos+1
|
|
33
|
+
// optionalWhitespace(input, pos) → skip spaces/tabs; return new pos
|
|
34
|
+
// integer(input, pos) → parse digits; return [value, newPos]
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export function commaSeparatedIntegers(
|
|
38
|
+
input: string,
|
|
39
|
+
position: number,
|
|
40
|
+
): readonly [ReadonlyArray<number>, number] {
|
|
41
|
+
if (position < 0) {
|
|
42
|
+
throw new RangeError(`Position ${position} is negative`);
|
|
43
|
+
}
|
|
44
|
+
const values: number[] = [];
|
|
45
|
+
let pos = position;
|
|
46
|
+
|
|
47
|
+
// peekChar: inspect next char without advancing; loop while ',' is next.
|
|
48
|
+
while (pos < input.length && input[pos] === ",") {
|
|
49
|
+
// comma: assert ',' and advance past it.
|
|
50
|
+
pos++;
|
|
51
|
+
|
|
52
|
+
// optionalWhitespace: skip spaces/tabs after comma.
|
|
53
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
54
|
+
pos++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// integer: parse one or more digits.
|
|
58
|
+
if (pos >= input.length) {
|
|
59
|
+
throw new SyntaxError(
|
|
60
|
+
`Expected integer after ',' at position ${pos} but reached end of input`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const c = input[pos] as string;
|
|
64
|
+
if (c < "0" || c > "9") {
|
|
65
|
+
throw new SyntaxError(
|
|
66
|
+
`Expected integer after ',' at position ${pos} but found ${JSON.stringify(c)}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
let value = 0;
|
|
70
|
+
while (pos < input.length) {
|
|
71
|
+
const ch = input[pos] as string;
|
|
72
|
+
if (ch < "0" || ch > "9") break;
|
|
73
|
+
value = value * 10 + (ch.charCodeAt(0) - 48);
|
|
74
|
+
pos++;
|
|
75
|
+
}
|
|
76
|
+
values.push(value);
|
|
77
|
+
|
|
78
|
+
// optionalWhitespace: skip spaces/tabs after integer.
|
|
79
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
80
|
+
pos++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [values, pos] as const;
|
|
85
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the comma-separated-integers block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// The definitive property-test corpus lives in the parent seed package's
|
|
7
|
+
// seed.test.ts (Suite 4: "property-test corpora"). This file satisfies the
|
|
8
|
+
// L0 proof/manifest.json "property_tests" artifact requirement and makes
|
|
9
|
+
// the test IDs declared in spec.yak traceable to this directory.
|
|
10
|
+
//
|
|
11
|
+
// Test IDs declared in spec.yak:
|
|
12
|
+
// commasep-none
|
|
13
|
+
// commasep-one
|
|
14
|
+
// commasep-two
|
|
15
|
+
// commasep-trailing-comma
|
|
16
|
+
// commasep-negative-pos
|
|
17
|
+
|
|
18
|
+
// Re-export the implementation so runners importing this artifact directly
|
|
19
|
+
// can access the block function.
|
|
20
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "comma-separated-integers",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The full input string."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "position",
|
|
11
|
+
"type": "number",
|
|
12
|
+
"description": "Position immediately after the first integer."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "result",
|
|
18
|
+
"type": "readonly [ReadonlyArray<number>, number]",
|
|
19
|
+
"description": "Tuple of [additionalValues, newPosition] for zero or more ', integer' pairs."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Parse zero or more ', integer' sequences. Each iteration skips optional whitespace, expects ',', skips optional whitespace, then parses an integer. Returns [values, newPosition] where values is the list of additional integers found. Stops when the next character is not ','.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "greedy",
|
|
35
|
+
"description": "Consumes as many comma-integer pairs as possible."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "empty-ok",
|
|
39
|
+
"description": "Returns [[], position] when no comma follows."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "composition",
|
|
43
|
+
"description": "Implemented by composing peekChar, comma, optionalWhitespace, and integer."
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"errorConditions": [
|
|
47
|
+
{
|
|
48
|
+
"description": "A comma is present but not followed by a valid integer.",
|
|
49
|
+
"errorType": "SyntaxError"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "position < 0.",
|
|
53
|
+
"errorType": "RangeError"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"nonFunctional": {
|
|
57
|
+
"time": "O(n)",
|
|
58
|
+
"space": "O(n)",
|
|
59
|
+
"purity": "pure",
|
|
60
|
+
"threadSafety": "safe"
|
|
61
|
+
},
|
|
62
|
+
"propertyTests": [
|
|
63
|
+
{
|
|
64
|
+
"id": "commasep-none",
|
|
65
|
+
"description": "commaSeparatedIntegers(']', 0) returns [[], 0]"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "commasep-one",
|
|
69
|
+
"description": "commaSeparatedIntegers(',2]', 0) returns [[2], 2]"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "commasep-two",
|
|
73
|
+
"description": "commaSeparatedIntegers(',2,3]', 0) returns [[2, 3], 4]"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "commasep-trailing-comma",
|
|
77
|
+
"description": "commaSeparatedIntegers(',]', 0) throws SyntaxError"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "commasep-negative-pos",
|
|
81
|
+
"description": "commaSeparatedIntegers(',1', -1) throws RangeError"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-DIGIT-001: digit block is the atomic unit of integer parsing.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: Every integer parser reduces to recognizing individual digit characters.
|
|
5
|
+
// This block is the leaf of the composition graph and the simplest possible contract.
|
|
6
|
+
|
|
7
|
+
export function digit(s: string): number {
|
|
8
|
+
if (s.length !== 1 || s < "0" || s > "9") {
|
|
9
|
+
throw new RangeError(`Not a digit: ${JSON.stringify(s)}`);
|
|
10
|
+
}
|
|
11
|
+
return s.charCodeAt(0) - 48;
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the digit block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// The definitive property-test corpus lives in the parent seed package's
|
|
7
|
+
// seed.test.ts (Suite 4: "property-test corpora"). This file satisfies the
|
|
8
|
+
// L0 proof/manifest.json "property_tests" artifact requirement and makes
|
|
9
|
+
// the test IDs declared in spec.yak traceable to this directory.
|
|
10
|
+
//
|
|
11
|
+
// Test IDs declared in spec.yak:
|
|
12
|
+
// digit-zero
|
|
13
|
+
// digit-nine
|
|
14
|
+
// digit-five
|
|
15
|
+
// digit-non-numeric
|
|
16
|
+
// digit-empty
|
|
17
|
+
// digit-multi-char
|
|
18
|
+
|
|
19
|
+
// Re-export the implementation so runners importing this artifact directly
|
|
20
|
+
// can access the block function.
|
|
21
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "digit",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "s",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "A single character string."
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"outputs": [
|
|
11
|
+
{
|
|
12
|
+
"name": "result",
|
|
13
|
+
"type": "number",
|
|
14
|
+
"description": "Integer value 0-9."
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"preconditions": [],
|
|
18
|
+
"postconditions": [],
|
|
19
|
+
"invariants": [],
|
|
20
|
+
"effects": [],
|
|
21
|
+
"level": "L0",
|
|
22
|
+
"behavior": "Parse a single ASCII digit character '0'-'9' to its integer value 0-9. Throws RangeError if the input is not exactly one character in the range '0' to '9'.",
|
|
23
|
+
"guarantees": [
|
|
24
|
+
{
|
|
25
|
+
"id": "pure",
|
|
26
|
+
"description": "Referentially transparent; no side effects."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "range",
|
|
30
|
+
"description": "Result is an integer in the closed range [0, 9]."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "inverse",
|
|
34
|
+
"description": "digit(String.fromCharCode(48 + n)) === n for n in [0,9]."
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"errorConditions": [
|
|
38
|
+
{
|
|
39
|
+
"description": "Input is not exactly one character.",
|
|
40
|
+
"errorType": "RangeError"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"description": "Input character is not in '0'-'9'.",
|
|
44
|
+
"errorType": "RangeError"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"nonFunctional": {
|
|
48
|
+
"time": "O(1)",
|
|
49
|
+
"space": "O(1)",
|
|
50
|
+
"purity": "pure",
|
|
51
|
+
"threadSafety": "safe"
|
|
52
|
+
},
|
|
53
|
+
"propertyTests": [
|
|
54
|
+
{
|
|
55
|
+
"id": "digit-zero",
|
|
56
|
+
"description": "digit('0') returns 0"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "digit-nine",
|
|
60
|
+
"description": "digit('9') returns 9"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "digit-five",
|
|
64
|
+
"description": "digit('5') returns 5"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "digit-non-numeric",
|
|
68
|
+
"description": "digit('a') throws RangeError"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "digit-empty",
|
|
72
|
+
"description": "digit('') throws RangeError"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "digit-multi-char",
|
|
76
|
+
"description": "digit('12') throws RangeError"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-DIGITORTHROW-001: digit-or-throw wraps positional digit reading with context.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: Callers that have already peeked and confirmed a digit want a single call that
|
|
5
|
+
// reads-and-advances with a descriptive error. This combinator adds the position context
|
|
6
|
+
// that the raw digit() function lacks.
|
|
7
|
+
|
|
8
|
+
export function digitOrThrow(input: string, position: number): readonly [number, number] {
|
|
9
|
+
if (position < 0) {
|
|
10
|
+
throw new RangeError(`Position ${position} is negative`);
|
|
11
|
+
}
|
|
12
|
+
if (position >= input.length) {
|
|
13
|
+
throw new SyntaxError(`Expected digit at position ${position} but reached end of input`);
|
|
14
|
+
}
|
|
15
|
+
const c = input[position] as string;
|
|
16
|
+
if (c < "0" || c > "9") {
|
|
17
|
+
throw new SyntaxError(`Expected digit at position ${position} but found ${JSON.stringify(c)}`);
|
|
18
|
+
}
|
|
19
|
+
return [c.charCodeAt(0) - 48, position + 1] as const;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the digit-or-throw block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// The definitive property-test corpus lives in the parent seed package's
|
|
7
|
+
// seed.test.ts (Suite 4: "property-test corpora"). This file satisfies the
|
|
8
|
+
// L0 proof/manifest.json "property_tests" artifact requirement and makes
|
|
9
|
+
// the test IDs declared in spec.yak traceable to this directory.
|
|
10
|
+
//
|
|
11
|
+
// Test IDs declared in spec.yak:
|
|
12
|
+
// digitorthrow-zero
|
|
13
|
+
// digitorthrow-nine
|
|
14
|
+
// digitorthrow-letter
|
|
15
|
+
// digitorthrow-eof
|
|
16
|
+
// digitorthrow-negative
|
|
17
|
+
|
|
18
|
+
// Re-export the implementation so runners importing this artifact directly
|
|
19
|
+
// can access the block function.
|
|
20
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "digit-or-throw",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The full input string."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "position",
|
|
11
|
+
"type": "number",
|
|
12
|
+
"description": "Zero-based position to read from."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "result",
|
|
18
|
+
"type": "readonly [number, number]",
|
|
19
|
+
"description": "[digitValue, newPosition] where newPosition = position + 1."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Read the character at position, parse it as a decimal digit 0-9, and return [value, position + 1]. Throws SyntaxError with position context if the character is not a digit or position is out of bounds.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "advance-1",
|
|
35
|
+
"description": "newPosition is always position + 1 on success."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "range",
|
|
39
|
+
"description": "digitValue is in [0, 9] on success."
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"errorConditions": [
|
|
43
|
+
{
|
|
44
|
+
"description": "position >= input.length or character is not a digit.",
|
|
45
|
+
"errorType": "SyntaxError"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"description": "position < 0.",
|
|
49
|
+
"errorType": "RangeError"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"nonFunctional": {
|
|
53
|
+
"time": "O(1)",
|
|
54
|
+
"space": "O(1)",
|
|
55
|
+
"purity": "pure",
|
|
56
|
+
"threadSafety": "safe"
|
|
57
|
+
},
|
|
58
|
+
"propertyTests": [
|
|
59
|
+
{
|
|
60
|
+
"id": "digitorthrow-zero",
|
|
61
|
+
"description": "digitOrThrow('0x', 0) returns [0, 1]"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "digitorthrow-nine",
|
|
65
|
+
"description": "digitOrThrow('9', 0) returns [9, 1]"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "digitorthrow-letter",
|
|
69
|
+
"description": "digitOrThrow('a', 0) throws SyntaxError"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "digitorthrow-eof",
|
|
73
|
+
"description": "digitOrThrow('', 0) throws SyntaxError"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "digitorthrow-negative",
|
|
77
|
+
"description": "digitOrThrow('5', -1) throws RangeError"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|