@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,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-EMPTYLIST-001: empty-list-content recognizes the ']' that closes an empty list.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: Splitting empty vs nonempty list content into two blocks keeps each block's
|
|
5
|
+
// contract minimal. list-of-ints dispatches based on the first character after '['.
|
|
6
|
+
|
|
7
|
+
export function emptyListContent(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 empty-list-content 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
|
+
// empty-list-ok
|
|
13
|
+
// empty-list-mid
|
|
14
|
+
// empty-list-nonempty
|
|
15
|
+
// empty-list-eof
|
|
16
|
+
// empty-list-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": "empty-list-content",
|
|
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 '['."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "newPosition",
|
|
18
|
+
"type": "number",
|
|
19
|
+
"description": "Position after the closing ']'."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Assert that the character at position is ']', indicating an empty list. Returns position + 1. Throws SyntaxError if anything other than ']' is found.",
|
|
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.",
|
|
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": "empty-list-ok",
|
|
61
|
+
"description": "emptyListContent(']', 0) returns 1"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "empty-list-mid",
|
|
65
|
+
"description": "emptyListContent('[]', 1) returns 2"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "empty-list-nonempty",
|
|
69
|
+
"description": "emptyListContent('[1]', 1) throws SyntaxError"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "empty-list-eof",
|
|
73
|
+
"description": "emptyListContent('', 0) throws SyntaxError"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "empty-list-negative",
|
|
77
|
+
"description": "emptyListContent(']', -1) throws RangeError"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-EOFCHECK-001: eof-check is the trailing-input rejection gate.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: A parser that accepts input must also reject trailing garbage. This block
|
|
5
|
+
// is the explicit end-of-input assertion that composition blocks call after parsing.
|
|
6
|
+
|
|
7
|
+
export function eofCheck(input: string, position: number): void {
|
|
8
|
+
if (position > input.length) {
|
|
9
|
+
throw new RangeError(`Position ${position} overruns input of length ${input.length}`);
|
|
10
|
+
}
|
|
11
|
+
if (position < input.length) {
|
|
12
|
+
throw new SyntaxError(
|
|
13
|
+
`Expected end of input at position ${position} but found ${JSON.stringify(input.slice(position))}`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the eof-check 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
|
+
// eof-check-exact
|
|
13
|
+
// eof-check-trailing
|
|
14
|
+
// eof-check-overrun
|
|
15
|
+
// eof-check-empty
|
|
16
|
+
// eof-check-empty-nonzero
|
|
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,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eof-check",
|
|
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": "Expected end position."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "result",
|
|
18
|
+
"type": "void",
|
|
19
|
+
"description": "Returns undefined on success."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Assert that position equals input.length, indicating no trailing input remains. Throws SyntaxError if any input remains after position.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "exact",
|
|
35
|
+
"description": "Succeeds if and only if position === input.length."
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"errorConditions": [
|
|
39
|
+
{
|
|
40
|
+
"description": "position < input.length — trailing characters remain.",
|
|
41
|
+
"errorType": "SyntaxError"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"description": "position > input.length — position overran input.",
|
|
45
|
+
"errorType": "RangeError"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"nonFunctional": {
|
|
49
|
+
"time": "O(1)",
|
|
50
|
+
"space": "O(1)",
|
|
51
|
+
"purity": "pure",
|
|
52
|
+
"threadSafety": "safe"
|
|
53
|
+
},
|
|
54
|
+
"propertyTests": [
|
|
55
|
+
{
|
|
56
|
+
"id": "eof-check-exact",
|
|
57
|
+
"description": "eofCheck('abc', 3) returns undefined"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "eof-check-trailing",
|
|
61
|
+
"description": "eofCheck('abc', 2) throws SyntaxError"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "eof-check-overrun",
|
|
65
|
+
"description": "eofCheck('abc', 4) throws RangeError"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "eof-check-empty",
|
|
69
|
+
"description": "eofCheck('', 0) returns undefined"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "eof-check-empty-nonzero",
|
|
73
|
+
"description": "eofCheck('', 1) throws RangeError"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-INTEGER-001: integer composes digit; returns value + new position as a tuple.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: Returning [value, newPosition] keeps the block pure while providing both the
|
|
5
|
+
// parsed value and the advanced cursor to the caller without a mutable context object.
|
|
6
|
+
|
|
7
|
+
export function integer(input: string, position: number): readonly [number, number] {
|
|
8
|
+
if (position < 0) {
|
|
9
|
+
throw new RangeError(`Position ${position} is negative`);
|
|
10
|
+
}
|
|
11
|
+
if (position >= input.length || input[position] === undefined) {
|
|
12
|
+
throw new SyntaxError(`Expected digit at position ${position} but reached end of input`);
|
|
13
|
+
}
|
|
14
|
+
const first = input[position] as string;
|
|
15
|
+
if (first < "0" || first > "9") {
|
|
16
|
+
throw new SyntaxError(
|
|
17
|
+
`Expected digit at position ${position} but found ${JSON.stringify(first)}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
let value = 0;
|
|
21
|
+
let pos = position;
|
|
22
|
+
while (pos < input.length) {
|
|
23
|
+
const c = input[pos] as string;
|
|
24
|
+
if (c < "0" || c > "9") break;
|
|
25
|
+
value = value * 10 + (c.charCodeAt(0) - 48);
|
|
26
|
+
pos++;
|
|
27
|
+
}
|
|
28
|
+
return [value, pos] as const;
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the integer 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
|
+
// integer-single
|
|
13
|
+
// integer-multi
|
|
14
|
+
// integer-mid
|
|
15
|
+
// integer-no-digit
|
|
16
|
+
// integer-eof
|
|
17
|
+
// integer-negative-pos
|
|
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,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "integer",
|
|
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 start position."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "result",
|
|
18
|
+
"type": "readonly [number, number]",
|
|
19
|
+
"description": "[parsedValue, newPosition] tuple."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [],
|
|
24
|
+
"invariants": [],
|
|
25
|
+
"effects": [],
|
|
26
|
+
"level": "L0",
|
|
27
|
+
"behavior": "Parse a sequence of one or more ASCII decimal digits starting at position. Returns [value, newPosition] where value is the decimal integer and newPosition is one past the last digit. Throws SyntaxError if no digit is found at position.",
|
|
28
|
+
"guarantees": [
|
|
29
|
+
{
|
|
30
|
+
"id": "pure",
|
|
31
|
+
"description": "Referentially transparent; no side effects."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "greedy",
|
|
35
|
+
"description": "Consumes as many digits as possible from position."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "non-negative",
|
|
39
|
+
"description": "Parsed value is always >= 0."
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"errorConditions": [
|
|
43
|
+
{
|
|
44
|
+
"description": "No digit character found at position.",
|
|
45
|
+
"errorType": "SyntaxError"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"description": "position < 0.",
|
|
49
|
+
"errorType": "RangeError"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"nonFunctional": {
|
|
53
|
+
"time": "O(n)",
|
|
54
|
+
"space": "O(1)",
|
|
55
|
+
"purity": "pure",
|
|
56
|
+
"threadSafety": "safe"
|
|
57
|
+
},
|
|
58
|
+
"propertyTests": [
|
|
59
|
+
{
|
|
60
|
+
"id": "integer-single",
|
|
61
|
+
"description": "integer('5', 0) returns [5, 1]"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "integer-multi",
|
|
65
|
+
"description": "integer('123', 0) returns [123, 3]"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "integer-mid",
|
|
69
|
+
"description": "integer('a42b', 1) returns [42, 3]"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "integer-no-digit",
|
|
73
|
+
"description": "integer('abc', 0) throws SyntaxError"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "integer-eof",
|
|
77
|
+
"description": "integer('', 0) throws SyntaxError"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "integer-negative-pos",
|
|
81
|
+
"description": "integer('1', -1) throws RangeError"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-LISTOFINTS-001: list-of-ints composes bracket/whitespace/nonempty/empty blocks.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: This is the top-level compositor block. The import-type declarations below
|
|
5
|
+
// declare the composition graph. Relative "./" imports are used so TypeScript can resolve
|
|
6
|
+
// the sibling block declarations; seedRegistry passes blockPatterns: ["./"] to parseBlock
|
|
7
|
+
// so extractComposition captures these as sub-block references in the provenance manifest.
|
|
8
|
+
// Because strict-subset validates each block in an isolated single-file ts-morph project,
|
|
9
|
+
// `import type` is used — type-only imports are unconditionally skipped by no-untyped-imports.
|
|
10
|
+
// Composition graph — captured by extractComposition via the @yakcc/seeds/ builtin pattern.
|
|
11
|
+
// WI-T05-fix: import paths use "@yakcc/seeds/blocks/<name>" so the compile resolver's
|
|
12
|
+
// SUB_BLOCK_IMPORT_RE (which matches "@yakcc/seeds/" prefix) can extract sub-block deps.
|
|
13
|
+
// All imports are "import type" so the strict-subset validator skips them unconditionally
|
|
14
|
+
// and vitest/Vite erases them at transpile time (no runtime module resolution needed).
|
|
15
|
+
import type { bracket } from "@yakcc/seeds/blocks/bracket";
|
|
16
|
+
import type { emptyListContent } from "@yakcc/seeds/blocks/empty-list-content";
|
|
17
|
+
import type { eofCheck } from "@yakcc/seeds/blocks/eof-check";
|
|
18
|
+
import type { nonAsciiRejector } from "@yakcc/seeds/blocks/non-ascii-rejector";
|
|
19
|
+
import type { nonemptyListContent } from "@yakcc/seeds/blocks/nonempty-list-content";
|
|
20
|
+
import type { optionalWhitespace } from "@yakcc/seeds/blocks/optional-whitespace";
|
|
21
|
+
import type { peekChar } from "@yakcc/seeds/blocks/peek-char";
|
|
22
|
+
|
|
23
|
+
// Suppress "imported but never used as a value" by surfacing type aliases.
|
|
24
|
+
// These are type-level witnesses documenting which sub-block signatures are mirrored below.
|
|
25
|
+
type _Bracket = typeof bracket;
|
|
26
|
+
type _EmptyListContent = typeof emptyListContent;
|
|
27
|
+
type _EofCheck = typeof eofCheck;
|
|
28
|
+
type _NonAsciiRejector = typeof nonAsciiRejector;
|
|
29
|
+
type _NonemptyListContent = typeof nonemptyListContent;
|
|
30
|
+
type _OptionalWhitespace = typeof optionalWhitespace;
|
|
31
|
+
type _PeekChar = typeof peekChar;
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Implementation
|
|
35
|
+
//
|
|
36
|
+
// Each section mirrors one sub-block's contract boundary exactly:
|
|
37
|
+
// nonAsciiRejector(input) → scan all chars; throw RangeError on code > 127
|
|
38
|
+
// bracket(input, pos, '[') → assert '[' at pos; return pos+1
|
|
39
|
+
// optionalWhitespace(input, pos) → skip spaces/tabs; return new pos
|
|
40
|
+
// peekChar(input, pos) → return char at pos or null (no advance)
|
|
41
|
+
// emptyListContent(input, pos) → assert ']'; return pos+1
|
|
42
|
+
// nonemptyListContent(input, pos) → parse ints+commas+']'; return [values, newPos]
|
|
43
|
+
// eofCheck(input, pos) → assert pos === input.length
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export function listOfInts(input: string): ReadonlyArray<number> {
|
|
47
|
+
// nonAsciiRejector: scan full input; throw RangeError on first code > 127.
|
|
48
|
+
for (let i = 0; i < input.length; i++) {
|
|
49
|
+
if (input.charCodeAt(i) > 127) {
|
|
50
|
+
throw new RangeError(`Non-ASCII character at position ${i}: code ${input.charCodeAt(i)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// bracket(input, 0, '['): assert '[' at position 0.
|
|
55
|
+
if (input.length === 0 || input[0] !== "[") {
|
|
56
|
+
throw new SyntaxError(
|
|
57
|
+
`Expected '[' at position 0 but found ${JSON.stringify(input[0] ?? "EOF")}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
let pos = 1;
|
|
61
|
+
|
|
62
|
+
// optionalWhitespace(input, pos): skip spaces/tabs after '['.
|
|
63
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
64
|
+
pos++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// peekChar(input, pos): inspect next character without advancing.
|
|
68
|
+
const next = pos < input.length ? input[pos] : null;
|
|
69
|
+
|
|
70
|
+
let values: ReadonlyArray<number>;
|
|
71
|
+
if (next === "]") {
|
|
72
|
+
// emptyListContent(input, pos): assert ']', return pos+1.
|
|
73
|
+
pos++;
|
|
74
|
+
values = [] as const;
|
|
75
|
+
} else {
|
|
76
|
+
// nonemptyListContent(input, pos): parse one or more comma-separated ints + closing ']'.
|
|
77
|
+
const result = _nonemptyListContent(input, pos);
|
|
78
|
+
values = result[0];
|
|
79
|
+
pos = result[1];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// eofCheck(input, pos): assert pos === input.length.
|
|
83
|
+
if (pos > input.length) {
|
|
84
|
+
throw new RangeError(`Position ${pos} overruns input of length ${input.length}`);
|
|
85
|
+
}
|
|
86
|
+
if (pos < input.length) {
|
|
87
|
+
throw new SyntaxError(
|
|
88
|
+
`Expected end of input at position ${pos} but found ${JSON.stringify(input.slice(pos))}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return values;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Internal: nonemptyListContent sub-block (mirrors nonempty-list-content.ts contract)
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function _nonemptyListContent(
|
|
100
|
+
input: string,
|
|
101
|
+
position: number,
|
|
102
|
+
): readonly [ReadonlyArray<number>, number] {
|
|
103
|
+
const values: number[] = [];
|
|
104
|
+
let pos = position;
|
|
105
|
+
|
|
106
|
+
// optionalWhitespace: skip leading spaces/tabs.
|
|
107
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
108
|
+
pos++;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// integer: parse first integer (one or more digits required).
|
|
112
|
+
if (pos >= input.length || (input[pos] as string) < "0" || (input[pos] as string) > "9") {
|
|
113
|
+
throw new SyntaxError(
|
|
114
|
+
`Expected digit at position ${pos} but found ${JSON.stringify(input[pos] ?? "EOF")}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
let first = 0;
|
|
118
|
+
while (pos < input.length) {
|
|
119
|
+
const c = input[pos] as string;
|
|
120
|
+
if (c < "0" || c > "9") break;
|
|
121
|
+
first = first * 10 + (c.charCodeAt(0) - 48);
|
|
122
|
+
pos++;
|
|
123
|
+
}
|
|
124
|
+
values.push(first);
|
|
125
|
+
|
|
126
|
+
// optionalWhitespace: skip whitespace after first integer.
|
|
127
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
128
|
+
pos++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// commaSeparatedIntegers: loop over ", integer" pairs.
|
|
132
|
+
while (pos < input.length && input[pos] === ",") {
|
|
133
|
+
// comma: consume ','.
|
|
134
|
+
pos++;
|
|
135
|
+
// optionalWhitespace: skip after comma.
|
|
136
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
137
|
+
pos++;
|
|
138
|
+
}
|
|
139
|
+
// integer: parse next integer.
|
|
140
|
+
if (pos >= input.length || (input[pos] as string) < "0" || (input[pos] as string) > "9") {
|
|
141
|
+
throw new SyntaxError(
|
|
142
|
+
`Expected digit after ',' at position ${pos} but found ${JSON.stringify(input[pos] ?? "EOF")}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
let val = 0;
|
|
146
|
+
while (pos < input.length) {
|
|
147
|
+
const c = input[pos] as string;
|
|
148
|
+
if (c < "0" || c > "9") break;
|
|
149
|
+
val = val * 10 + (c.charCodeAt(0) - 48);
|
|
150
|
+
pos++;
|
|
151
|
+
}
|
|
152
|
+
values.push(val);
|
|
153
|
+
// optionalWhitespace: skip after integer.
|
|
154
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === "\t")) {
|
|
155
|
+
pos++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// bracket(input, pos, ']'): expect closing ']'.
|
|
160
|
+
if (pos >= input.length || input[pos] !== "]") {
|
|
161
|
+
throw new SyntaxError(
|
|
162
|
+
`Expected ']' at position ${pos} but found ${JSON.stringify(input[pos] ?? "EOF")}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
pos++;
|
|
166
|
+
|
|
167
|
+
return [values, pos] as const;
|
|
168
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the list-of-ints 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
|
+
// list-empty
|
|
13
|
+
// list-single
|
|
14
|
+
// list-multiple
|
|
15
|
+
// list-spaces
|
|
16
|
+
// list-incomplete
|
|
17
|
+
// list-non-digit
|
|
18
|
+
// list-no-open
|
|
19
|
+
// list-trailing
|
|
20
|
+
|
|
21
|
+
// Re-export the implementation so runners importing this artifact directly
|
|
22
|
+
// can access the block function.
|
|
23
|
+
export * from "../impl.js";
|