@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,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "list-of-ints",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "A JSON-style list of integers string."
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"outputs": [
|
|
11
|
+
{
|
|
12
|
+
"name": "result",
|
|
13
|
+
"type": "ReadonlyArray<number>",
|
|
14
|
+
"description": "The parsed list of non-negative integers."
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"preconditions": [],
|
|
18
|
+
"postconditions": [],
|
|
19
|
+
"invariants": [],
|
|
20
|
+
"effects": [],
|
|
21
|
+
"level": "L0",
|
|
22
|
+
"behavior": "Parse a string of the form '[i1,i2,...,iN]' where each element is a non-negative decimal integer. Surrounding whitespace around elements is allowed. Returns the parsed numbers as a readonly array. Throws SyntaxError on malformed input and RangeError on non-ASCII input.",
|
|
23
|
+
"guarantees": [
|
|
24
|
+
{
|
|
25
|
+
"id": "pure",
|
|
26
|
+
"description": "Referentially transparent; no side effects."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "empty-ok",
|
|
30
|
+
"description": "Accepts '[]' and returns an empty array."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "composition",
|
|
34
|
+
"description": "Implemented by composing nonAsciiRejector, bracket, optionalWhitespace, peekChar, emptyListContent, nonemptyListContent, and eofCheck."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "no-trailing",
|
|
38
|
+
"description": "Rejects input with characters after the closing ']'."
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"errorConditions": [
|
|
42
|
+
{
|
|
43
|
+
"description": "Input does not start with '['.",
|
|
44
|
+
"errorType": "SyntaxError"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Input contains non-ASCII characters.",
|
|
48
|
+
"errorType": "RangeError"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "List elements are not valid non-negative integers.",
|
|
52
|
+
"errorType": "SyntaxError"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"description": "Trailing characters after closing ']'.",
|
|
56
|
+
"errorType": "SyntaxError"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Input ends before closing ']'.",
|
|
60
|
+
"errorType": "SyntaxError"
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"nonFunctional": {
|
|
64
|
+
"time": "O(n)",
|
|
65
|
+
"space": "O(n)",
|
|
66
|
+
"purity": "pure",
|
|
67
|
+
"threadSafety": "safe"
|
|
68
|
+
},
|
|
69
|
+
"propertyTests": [
|
|
70
|
+
{
|
|
71
|
+
"id": "list-empty",
|
|
72
|
+
"description": "listOfInts('[]') returns []"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "list-single",
|
|
76
|
+
"description": "listOfInts('[1]') returns [1]"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "list-multiple",
|
|
80
|
+
"description": "listOfInts('[1,2,3]') returns [1, 2, 3]"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "list-spaces",
|
|
84
|
+
"description": "listOfInts('[ 42 ]') returns [42]"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "list-incomplete",
|
|
88
|
+
"description": "listOfInts('[1,2,') throws SyntaxError"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "list-non-digit",
|
|
92
|
+
"description": "listOfInts('[abc]') throws SyntaxError"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "list-no-open",
|
|
96
|
+
"description": "listOfInts('1,2,3]') throws SyntaxError"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "list-trailing",
|
|
100
|
+
"description": "listOfInts('[1]x') throws SyntaxError"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// @decision DEC-V0-B4-SEED-lru-node-001
|
|
4
|
+
// @title lru-node: doubly-linked list node for O(1) LRU eviction
|
|
5
|
+
// @status accepted
|
|
6
|
+
// @rationale
|
|
7
|
+
// The lru-cache-with-ttl B4 task requires a doubly-linked list as the ordered
|
|
8
|
+
// eviction structure. O(1) insert-at-head, O(1) remove-any, and O(1)
|
|
9
|
+
// move-to-head all depend on prev/next pointer wiring. This atom is the
|
|
10
|
+
// fundamental allocation unit: create a node, wire its pointers externally.
|
|
11
|
+
//
|
|
12
|
+
// Design decisions:
|
|
13
|
+
// (A) MUTABLE FIELDS: prev and next are mutable (not readonly) because the
|
|
14
|
+
// LRU algorithm must rewire pointers in-place on every cache hit. Readonly
|
|
15
|
+
// fields would require allocating a new node on every access -- O(n) space
|
|
16
|
+
// for an O(n) operation sequence, defeating the purpose.
|
|
17
|
+
//
|
|
18
|
+
// (B) PLAIN OBJECT RETURN: Returns a plain JS object that satisfies the
|
|
19
|
+
// LruNode interface. No class needed; the interface provides the type
|
|
20
|
+
// contract and the plain-object implementation avoids prototype overhead.
|
|
21
|
+
//
|
|
22
|
+
// (C) UNKNOWN VALUE TYPE: value is typed as unknown (not generic) to keep
|
|
23
|
+
// the atom strictly within the validated TypeScript subset. Callers cast
|
|
24
|
+
// to their concrete type after retrieval, which is idiomatic for a low-
|
|
25
|
+
// level building block.
|
|
26
|
+
//
|
|
27
|
+
// Reference: Cormen et al., "Introduction to Algorithms" 4th ed., Section 10.2
|
|
28
|
+
// (doubly linked lists) and Section 20.1 (hash table + list for O(1) LRU).
|
|
29
|
+
|
|
30
|
+
/** A node in a doubly-linked intrusive list used by the LRU eviction policy. */
|
|
31
|
+
export interface LruNode {
|
|
32
|
+
key: string;
|
|
33
|
+
value: unknown;
|
|
34
|
+
prev: LruNode | null;
|
|
35
|
+
next: LruNode | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Allocate a new LRU list node for the given key-value pair.
|
|
40
|
+
*
|
|
41
|
+
* Both prev and next are initialised to null. The caller is responsible for
|
|
42
|
+
* wiring pointers when inserting the node into the eviction list.
|
|
43
|
+
*
|
|
44
|
+
* @param key - Cache key. Stored by reference (no copy).
|
|
45
|
+
* @param value - Cached value. Stored by reference (no copy).
|
|
46
|
+
* @returns A new LruNode with null prev/next pointers.
|
|
47
|
+
*/
|
|
48
|
+
export function makeLruNode(key: string, value: unknown): LruNode {
|
|
49
|
+
return { key, value, prev: null, next: null };
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the lru-node block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// Test IDs declared in spec.yak:
|
|
7
|
+
// lru-node-key-stored
|
|
8
|
+
// lru-node-value-stored
|
|
9
|
+
// lru-node-prev-null
|
|
10
|
+
// lru-node-next-null
|
|
11
|
+
// lru-node-independent
|
|
12
|
+
// lru-node-mutable-prev
|
|
13
|
+
|
|
14
|
+
// Re-export implementation so runners importing this artifact directly
|
|
15
|
+
// can access the block functions.
|
|
16
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lru-node",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "key",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "Cache key identifying this entry."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "value",
|
|
11
|
+
"type": "unknown",
|
|
12
|
+
"description": "Cached value held by this node."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "node",
|
|
18
|
+
"type": "{ key: string; value: unknown; prev: LruNode | null; next: LruNode | null }",
|
|
19
|
+
"description": "A new doubly-linked list node with prev and next initialised to null."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [],
|
|
23
|
+
"postconditions": [
|
|
24
|
+
{
|
|
25
|
+
"description": "Returned node.key === key"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "Returned node.value === value"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"description": "Returned node.prev === null"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "Returned node.next === null"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"invariants": [],
|
|
38
|
+
"effects": [],
|
|
39
|
+
"level": "L0",
|
|
40
|
+
"behavior": "Allocate a doubly-linked list node that carries a key-value pair. Both prev and next are initialised to null; the caller is responsible for wiring pointers when inserting the node into a list. The function is pure — it only allocates and returns a plain object.",
|
|
41
|
+
"guarantees": [
|
|
42
|
+
{
|
|
43
|
+
"id": "pure",
|
|
44
|
+
"description": "Referentially transparent; allocates and returns a plain object with no side effects."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "null-pointers",
|
|
48
|
+
"description": "node.prev and node.next are null immediately after construction."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "key-identity",
|
|
52
|
+
"description": "node.key === the key argument passed in (strict identity, not a copy)."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "value-identity",
|
|
56
|
+
"description": "node.value === the value argument passed in (strict identity, not a copy)."
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"errorConditions": [],
|
|
60
|
+
"nonFunctional": {
|
|
61
|
+
"time": "O(1)",
|
|
62
|
+
"space": "O(1)",
|
|
63
|
+
"purity": "pure",
|
|
64
|
+
"threadSafety": "safe"
|
|
65
|
+
},
|
|
66
|
+
"propertyTests": [
|
|
67
|
+
{
|
|
68
|
+
"id": "lru-node-key-stored",
|
|
69
|
+
"description": "makeLruNode('k', 1).key === 'k'"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "lru-node-value-stored",
|
|
73
|
+
"description": "makeLruNode('k', 42).value === 42"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "lru-node-prev-null",
|
|
77
|
+
"description": "makeLruNode('k', 1).prev === null"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "lru-node-next-null",
|
|
81
|
+
"description": "makeLruNode('k', 1).next === null"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "lru-node-independent",
|
|
85
|
+
"description": "Two separate makeLruNode calls produce distinct objects"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "lru-node-mutable-prev",
|
|
89
|
+
"description": "node.prev can be assigned to another node (pointer wiring works)"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// @decision DEC-V0-B4-SEED-memoize-001
|
|
4
|
+
// @title memoize: generic Map-backed memoization wrapper
|
|
5
|
+
// @status accepted
|
|
6
|
+
// @rationale
|
|
7
|
+
// The levenshtein-with-memo B4 task requires a memoization primitive.
|
|
8
|
+
// Levenshtein DP has overlapping subproblems: edit(i, j) is recomputed
|
|
9
|
+
// O(m*n) times in a naive recursion without caching. A memoize wrapper
|
|
10
|
+
// reduces this to O(m*n) distinct calls with O(1) lookups on repeats.
|
|
11
|
+
//
|
|
12
|
+
// Design decisions:
|
|
13
|
+
// (A) CALLER-SUPPLIED KEY FUNCTION: Rather than JSON.stringify(args) by
|
|
14
|
+
// default, the caller supplies keyFn. This is intentional -- it lets
|
|
15
|
+
// the caller control key space (e.g., `${i},${j}` for two integers
|
|
16
|
+
// is cheaper and collision-free compared to JSON.stringify([i, j])).
|
|
17
|
+
// It also keeps the atom free of assumptions about argument types.
|
|
18
|
+
//
|
|
19
|
+
// (B) MAP OVER OBJECT: Map is used instead of a plain object cache because
|
|
20
|
+
// Map does not inherit prototype keys and has better performance for
|
|
21
|
+
// high-cardinality key sets (V8 optimises Map internally).
|
|
22
|
+
//
|
|
23
|
+
// (C) EXCEPTION NOT CACHED: If fn throws, the error is re-thrown and no
|
|
24
|
+
// entry is written to the cache. The next call will re-invoke fn. This
|
|
25
|
+
// is the standard memoize contract; caching exceptions would hide bugs.
|
|
26
|
+
//
|
|
27
|
+
// (D) UNKNOWN TYPES: Inputs and outputs are typed as unknown[] / unknown to
|
|
28
|
+
// keep the atom within the strict-subset validator's validated surface.
|
|
29
|
+
// Callers cast to concrete types at the usage site.
|
|
30
|
+
//
|
|
31
|
+
// Reference: Memoization pattern attributed to Donald Michie (1968);
|
|
32
|
+
// application to Levenshtein distance in Wagner & Fischer (1974).
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wrap a pure function with a Map-backed cache keyed by keyFn(...args).
|
|
36
|
+
*
|
|
37
|
+
* fn is called at most once per unique key. Exceptions from fn propagate
|
|
38
|
+
* to the caller and are NOT cached -- subsequent calls with the same key
|
|
39
|
+
* will re-invoke fn.
|
|
40
|
+
*
|
|
41
|
+
* @param fn - Pure function to memoize.
|
|
42
|
+
* @param keyFn - Serialises args to a string cache key.
|
|
43
|
+
* @returns A memoized wrapper sharing a single internal cache.
|
|
44
|
+
*/
|
|
45
|
+
export function memoize(
|
|
46
|
+
fn: (...args: unknown[]) => unknown,
|
|
47
|
+
keyFn: (...args: unknown[]) => string,
|
|
48
|
+
): (...args: unknown[]) => unknown {
|
|
49
|
+
const cache: Map<string, unknown> = new Map();
|
|
50
|
+
|
|
51
|
+
return function memoized(...args: unknown[]): unknown {
|
|
52
|
+
const key = keyFn(...args);
|
|
53
|
+
if (cache.has(key)) {
|
|
54
|
+
return cache.get(key);
|
|
55
|
+
}
|
|
56
|
+
const result = fn(...args);
|
|
57
|
+
cache.set(key, result);
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the memoize block.
|
|
3
|
+
// These tests exercise the contract declared in ../spec.yak against the
|
|
4
|
+
// implementation in ../impl.ts.
|
|
5
|
+
//
|
|
6
|
+
// Test IDs declared in spec.yak:
|
|
7
|
+
// memoize-returns-same-value
|
|
8
|
+
// memoize-calls-fn-once
|
|
9
|
+
// memoize-different-keys-call-fn
|
|
10
|
+
// memoize-cache-hit-identity
|
|
11
|
+
// memoize-exception-not-cached
|
|
12
|
+
|
|
13
|
+
// Re-export implementation so runners importing this artifact directly
|
|
14
|
+
// can access the block functions.
|
|
15
|
+
export * from "../impl.js";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memoize",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "fn",
|
|
6
|
+
"type": "(...args: unknown[]) => unknown",
|
|
7
|
+
"description": "A pure function whose return value depends only on its arguments."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "keyFn",
|
|
11
|
+
"type": "(...args: unknown[]) => string",
|
|
12
|
+
"description": "Serialises the argument list to a string cache key. Caller supplies this to control key space."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"outputs": [
|
|
16
|
+
{
|
|
17
|
+
"name": "memoized",
|
|
18
|
+
"type": "(...args: unknown[]) => unknown",
|
|
19
|
+
"description": "A wrapped version of fn that returns the cached result on repeat calls with equal keys."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"preconditions": [
|
|
23
|
+
{
|
|
24
|
+
"description": "fn must be referentially transparent: same arguments must always produce the same return value."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"postconditions": [
|
|
28
|
+
{
|
|
29
|
+
"description": "memoized(...args) === fn(...args) for any args (return value identity)."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "fn is called at most once per unique key string produced by keyFn."
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"invariants": [
|
|
36
|
+
{
|
|
37
|
+
"description": "The internal cache grows monotonically; entries are never evicted."
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"effects": [],
|
|
41
|
+
"level": "L0",
|
|
42
|
+
"behavior": "Wrap a pure function fn with a Map-backed cache. On each call, compute the cache key via keyFn. If the key is present in the cache, return the stored value without calling fn. Otherwise call fn, store the result, and return it. The returned function is a closure that shares one cache across all invocations.",
|
|
43
|
+
"guarantees": [
|
|
44
|
+
{
|
|
45
|
+
"id": "pure-result",
|
|
46
|
+
"description": "The returned value is identical to what fn would return for the same arguments."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "call-once",
|
|
50
|
+
"description": "fn is called at most once per unique key."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "cache-hit",
|
|
54
|
+
"description": "Second call with same key returns the same object reference (no recomputation)."
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"errorConditions": [
|
|
58
|
+
{
|
|
59
|
+
"description": "If fn throws, the exception propagates and no entry is stored in the cache.",
|
|
60
|
+
"errorType": "Error"
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"nonFunctional": {
|
|
64
|
+
"time": "O(1) amortized per call (Map lookup)",
|
|
65
|
+
"space": "O(k) where k is the number of distinct keys seen",
|
|
66
|
+
"purity": "impure (closes over mutable Map)",
|
|
67
|
+
"threadSafety": "safe (single-threaded JS)"
|
|
68
|
+
},
|
|
69
|
+
"propertyTests": [
|
|
70
|
+
{
|
|
71
|
+
"id": "memoize-returns-same-value",
|
|
72
|
+
"description": "memoized(2, 3) returns the same value as fn(2, 3)"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "memoize-calls-fn-once",
|
|
76
|
+
"description": "fn is called exactly once for repeated identical arguments"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "memoize-different-keys-call-fn",
|
|
80
|
+
"description": "fn is called for each distinct key"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "memoize-cache-hit-identity",
|
|
84
|
+
"description": "Second call with same args returns the exact same object reference"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "memoize-exception-not-cached",
|
|
88
|
+
"description": "If fn throws, the exception propagates and next call re-invokes fn"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @decision DEC-SEEDS-NONASCII-001: non-ascii-rejector is a full-input validation gate.
|
|
3
|
+
// Status: implemented (WI-006)
|
|
4
|
+
// Rationale: The seed corpus parsers only handle ASCII. Failing fast on non-ASCII at the
|
|
5
|
+
// entry point gives a clear error rather than a cryptic failure mid-parse.
|
|
6
|
+
|
|
7
|
+
export function nonAsciiRejector(input: string): void {
|
|
8
|
+
for (let i = 0; i < input.length; i++) {
|
|
9
|
+
const code = input.charCodeAt(i);
|
|
10
|
+
if (code > 127) {
|
|
11
|
+
throw new RangeError(`Non-ASCII character at position ${i}: code ${code}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Property tests for the non-ascii-rejector 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
|
+
// non-ascii-rejector-clean
|
|
13
|
+
// non-ascii-rejector-empty
|
|
14
|
+
// non-ascii-rejector-unicode
|
|
15
|
+
// non-ascii-rejector-digits
|
|
16
|
+
// non-ascii-rejector-mid
|
|
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,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "non-ascii-rejector",
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "input",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The full input string to validate."
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"outputs": [
|
|
11
|
+
{
|
|
12
|
+
"name": "result",
|
|
13
|
+
"type": "void",
|
|
14
|
+
"description": "Returns undefined if all bytes are ASCII."
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"preconditions": [],
|
|
18
|
+
"postconditions": [],
|
|
19
|
+
"invariants": [],
|
|
20
|
+
"effects": [],
|
|
21
|
+
"level": "L0",
|
|
22
|
+
"behavior": "Scan the entire input string and throw RangeError at the first character with code > 127. Returns undefined if all characters are ASCII (code <= 127).",
|
|
23
|
+
"guarantees": [
|
|
24
|
+
{
|
|
25
|
+
"id": "pure",
|
|
26
|
+
"description": "Referentially transparent; no side effects."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "first-violation",
|
|
30
|
+
"description": "Error message includes the position and code of the first non-ASCII character."
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"errorConditions": [
|
|
34
|
+
{
|
|
35
|
+
"description": "Any character in input has char code > 127.",
|
|
36
|
+
"errorType": "RangeError"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"nonFunctional": {
|
|
40
|
+
"time": "O(n)",
|
|
41
|
+
"space": "O(1)",
|
|
42
|
+
"purity": "pure",
|
|
43
|
+
"threadSafety": "safe"
|
|
44
|
+
},
|
|
45
|
+
"propertyTests": [
|
|
46
|
+
{
|
|
47
|
+
"id": "non-ascii-rejector-clean",
|
|
48
|
+
"description": "nonAsciiRejector('hello') returns undefined"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "non-ascii-rejector-empty",
|
|
52
|
+
"description": "nonAsciiRejector('') returns undefined"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "non-ascii-rejector-unicode",
|
|
56
|
+
"description": "nonAsciiRejector('caf\\u00e9') throws RangeError"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "non-ascii-rejector-digits",
|
|
60
|
+
"description": "nonAsciiRejector('[1,2,3]') returns undefined"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "non-ascii-rejector-mid",
|
|
64
|
+
"description": "nonAsciiRejector('ab\\u0080c') throws RangeError"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|