@fraczak/k 6.3.2 → 6.3.3
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/REPL.md +15 -10
- package/codecs/json.mjs +16 -2
- package/package.json +1 -1
- package/repl-codecs.mjs +28 -14
- package/repl.mjs +74 -11
- package/tests/test-ieee-arithmetic.mjs +2 -2
- package/tests/test-repl.mjs +32 -1
package/DOCS/REPL.md
CHANGED
|
@@ -47,7 +47,7 @@ complete.
|
|
|
47
47
|
| `:rels` | List relation aliases |
|
|
48
48
|
| `:codec load file` | Load an importable REPL codec module |
|
|
49
49
|
| `:codec list` | List loaded REPL codecs |
|
|
50
|
-
| `:input type [codec]` | Read the next line as codec input for a
|
|
50
|
+
| `:input type [codec]` | Read the next line as codec input for a type |
|
|
51
51
|
| `:load [--no-alias] file` | Load `.k` source or `.klib` into the current state |
|
|
52
52
|
| `:klib file` | Export the active relation closure as a `.klib` library |
|
|
53
53
|
| `:ko file expr` | Export a `.ko` executable with `expr` as main |
|
|
@@ -139,7 +139,7 @@ Tab completion covers:
|
|
|
139
139
|
Type aliases also complete in `$name` position inside raw k input.
|
|
140
140
|
|
|
141
141
|
For codec commands, completion covers `:codec load`, `:codec list`, file paths
|
|
142
|
-
after `:codec load`,
|
|
142
|
+
after `:codec load`, type names after `:input`, and loaded codec names
|
|
143
143
|
after the input type.
|
|
144
144
|
|
|
145
145
|
## Loading
|
|
@@ -158,8 +158,10 @@ unless `--no-alias` is used.
|
|
|
158
158
|
|
|
159
159
|
## Codecs
|
|
160
160
|
|
|
161
|
-
REPL codecs are keyed by canonical
|
|
162
|
-
|
|
161
|
+
REPL codecs are usually keyed by canonical code hashes. Patterns are not used
|
|
162
|
+
for codec dispatch. A codec may also export `universal = true` to make it
|
|
163
|
+
available for any type selected by `:input`; the parsed value is still validated
|
|
164
|
+
against the requested type before it enters the session.
|
|
163
165
|
|
|
164
166
|
### `:codec load file`
|
|
165
167
|
|
|
@@ -172,15 +174,18 @@ export function parse(text) { /* text -> Value */ }
|
|
|
172
174
|
export function print(value) { /* Value -> text */ }
|
|
173
175
|
```
|
|
174
176
|
|
|
175
|
-
A codec may export `patterns` instead of `codes`; each pattern must be a
|
|
176
|
-
|
|
177
|
-
REPL.
|
|
177
|
+
A codec may export `patterns` instead of `codes`; each pattern must be a closed
|
|
178
|
+
property-list pattern that can be canonicalized to a code hash, and that hash is
|
|
179
|
+
recalculated by the REPL. A universal codec exports `universal = true` instead
|
|
180
|
+
of `codes` or `patterns`.
|
|
178
181
|
|
|
179
182
|
### `:input type [codec]`
|
|
180
183
|
|
|
181
|
-
Resolves `type` to a canonical
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
Resolves `type` to a canonical code hash, selects the registered codec, and
|
|
185
|
+
consumes the next line verbatim as codec input. `type` may be a type alias, a
|
|
186
|
+
canonical code hash, or an inline type expression such as
|
|
187
|
+
`<{} true, {} false>`. The parsed value is validated against that type before
|
|
188
|
+
becoming the current value.
|
|
184
189
|
|
|
185
190
|
## Export
|
|
186
191
|
|
package/codecs/json.mjs
CHANGED
|
@@ -5,6 +5,9 @@ import { decodeWire, encodeToWire } from "./runtime/prefix-codec.mjs";
|
|
|
5
5
|
import { isMainEntrypoint } from "./runtime/cli-entry.mjs";
|
|
6
6
|
import { fromJsonValue, toJsonValue, patternFromJsonValue } from "./json-codec.mjs";
|
|
7
7
|
|
|
8
|
+
const name = "json";
|
|
9
|
+
const universal = true;
|
|
10
|
+
|
|
8
11
|
function usage(stream = console.error) {
|
|
9
12
|
stream(`Usage: ${argv[1]} --parse | --print`);
|
|
10
13
|
stream(" --parse Read JSON from stdin, write binary pattern+value stream.");
|
|
@@ -21,6 +24,14 @@ function readAll(stream) {
|
|
|
21
24
|
});
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
function parse(text) {
|
|
28
|
+
return fromJsonValue(JSON.parse(text));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function print(value) {
|
|
32
|
+
return JSON.stringify(toJsonValue(value));
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
async function main() {
|
|
25
36
|
const args = argv.slice(2);
|
|
26
37
|
if (args.includes("-h") || args.includes("--help")) {
|
|
@@ -36,12 +47,13 @@ async function main() {
|
|
|
36
47
|
const buf = await readAll(stdin);
|
|
37
48
|
|
|
38
49
|
if (args[0] === "--parse") {
|
|
39
|
-
const
|
|
50
|
+
const text = buf.toString("utf8");
|
|
51
|
+
const json = JSON.parse(text);
|
|
40
52
|
const value = fromJsonValue(json);
|
|
41
53
|
stdout.write(encodeToWire(value, patternFromJsonValue(json)));
|
|
42
54
|
} else {
|
|
43
55
|
const { value } = decodeWire(buf);
|
|
44
|
-
stdout.write(`${
|
|
56
|
+
stdout.write(`${print(value)}\n`);
|
|
45
57
|
}
|
|
46
58
|
}
|
|
47
59
|
|
|
@@ -51,3 +63,5 @@ if (isMainEntrypoint(import.meta.url, argv[1])) {
|
|
|
51
63
|
exit(1);
|
|
52
64
|
});
|
|
53
65
|
}
|
|
66
|
+
|
|
67
|
+
export { name, universal, parse, print };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraczak/k",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.3",
|
|
4
4
|
"description": "k — a language for composing first-order partial functions over algebraic data types (products and tagged unions); includes runtime, type derivation, REPL, and codec toolchain",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
package/repl-codecs.mjs
CHANGED
|
@@ -7,11 +7,13 @@ import { deriveClosedPattern } from "./codecs/runtime/codec.mjs";
|
|
|
7
7
|
import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
|
|
8
8
|
import { finalize } from "./codes.mjs";
|
|
9
9
|
|
|
10
|
+
const UNIVERSAL_CODE = "*";
|
|
11
|
+
|
|
10
12
|
function isCanonicalCodeName(name) {
|
|
11
13
|
return typeof name === "string" && name.startsWith("@");
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
function
|
|
16
|
+
function closedPatternToCodeHash(pattern) {
|
|
15
17
|
if (!Array.isArray(pattern) || pattern.length === 0) {
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
@@ -23,7 +25,7 @@ function singletonPatternToCodeHash(pattern) {
|
|
|
23
25
|
return null;
|
|
24
26
|
}
|
|
25
27
|
if (!Array.isArray(edges)) {
|
|
26
|
-
throw new Error(`Invalid
|
|
28
|
+
throw new Error(`Invalid closed pattern node ${i}`);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const code = kind === "closed-product" ? "product" : "union";
|
|
@@ -31,7 +33,7 @@ function singletonPatternToCodeHash(pattern) {
|
|
|
31
33
|
code,
|
|
32
34
|
[code]: Object.fromEntries(edges.map(([label, target]) => {
|
|
33
35
|
if (!Number.isInteger(target) || target < 0 || target >= pattern.length) {
|
|
34
|
-
throw new Error(`Invalid
|
|
36
|
+
throw new Error(`Invalid closed pattern edge target ${target}`);
|
|
35
37
|
}
|
|
36
38
|
return [label, `C${target}`];
|
|
37
39
|
}))
|
|
@@ -64,17 +66,18 @@ function valueForCode(value, codeHash, findCode) {
|
|
|
64
66
|
function normalizeCodecModule(mod, fallbackName) {
|
|
65
67
|
const codec = mod.replCodec || mod.codec || mod.default || mod;
|
|
66
68
|
const name = codec.name || mod.name || fallbackName;
|
|
69
|
+
const universal = codec.universal === true;
|
|
67
70
|
const patterns = codec.patterns || (codec.pattern ? [codec.pattern] : null);
|
|
68
71
|
const codes = codec.codes || codec.codeHashes || (codec.code ? [codec.code] : null) ||
|
|
69
72
|
patterns?.map((pattern, index) => {
|
|
70
|
-
const codeHash =
|
|
71
|
-
if (!codeHash) throw new Error(`Codec '${name}' pattern ${index}
|
|
73
|
+
const codeHash = closedPatternToCodeHash(pattern);
|
|
74
|
+
if (!codeHash) throw new Error(`Codec '${name}' pattern ${index} must be a closed property-list pattern`);
|
|
72
75
|
return codeHash;
|
|
73
76
|
});
|
|
74
77
|
if (!name || typeof name !== "string") {
|
|
75
78
|
throw new Error("Codec module must export a string name");
|
|
76
79
|
}
|
|
77
|
-
if (!Array.isArray(codes) || codes.length === 0) {
|
|
80
|
+
if (!universal && (!Array.isArray(codes) || codes.length === 0)) {
|
|
78
81
|
throw new Error(`Codec '${name}' must export a non-empty codes array`);
|
|
79
82
|
}
|
|
80
83
|
if (codec.parse != null && typeof codec.parse !== "function") {
|
|
@@ -85,7 +88,8 @@ function normalizeCodecModule(mod, fallbackName) {
|
|
|
85
88
|
}
|
|
86
89
|
return {
|
|
87
90
|
name,
|
|
88
|
-
codes,
|
|
91
|
+
codes: universal ? [UNIVERSAL_CODE] : codes,
|
|
92
|
+
universal,
|
|
89
93
|
parse: codec.parse,
|
|
90
94
|
print: codec.print
|
|
91
95
|
};
|
|
@@ -100,13 +104,15 @@ function registerCodec(state, codec, source = null) {
|
|
|
100
104
|
const registered = [];
|
|
101
105
|
const store = codecStore(state);
|
|
102
106
|
for (const codeHash of codec.codes) {
|
|
103
|
-
|
|
107
|
+
const universal = codeHash === UNIVERSAL_CODE;
|
|
108
|
+
if (!universal && !isCanonicalCodeName(codeHash)) {
|
|
104
109
|
throw new Error(`Codec '${codec.name}' has non-canonical code '${codeHash}'`);
|
|
105
110
|
}
|
|
106
111
|
if (!store[codeHash]) store[codeHash] = [];
|
|
107
112
|
const entry = {
|
|
108
113
|
name: codec.name,
|
|
109
114
|
source,
|
|
115
|
+
universal,
|
|
110
116
|
parse: codec.parse,
|
|
111
117
|
print: codec.print
|
|
112
118
|
};
|
|
@@ -137,20 +143,27 @@ function listCodecs(state) {
|
|
|
137
143
|
.flatMap(([codeHash, codecs]) => codecs.map((codec) => ({ codeHash, ...codec })))
|
|
138
144
|
.sort((a, b) => a.name.localeCompare(b.name) || a.codeHash.localeCompare(b.codeHash));
|
|
139
145
|
if (entries.length === 0) return "(none)";
|
|
140
|
-
return entries.map((entry) => `${entry.name} ${entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
|
|
146
|
+
return entries.map((entry) => `${entry.name} ${entry.codeHash === UNIVERSAL_CODE ? "all" : entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
function codecNames(state, codeHash = null) {
|
|
144
150
|
const store = codecStore(state);
|
|
145
|
-
const codecs = codeHash
|
|
151
|
+
const codecs = codeHash
|
|
152
|
+
? [...(store[codeHash] || []), ...(store[UNIVERSAL_CODE] || [])]
|
|
153
|
+
: Object.values(store).flat();
|
|
146
154
|
return [...new Set(codecs.map((codec) => codec.name))].sort();
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
function resolveCodec(state, codeHash, codecName = null, capability = null) {
|
|
150
|
-
const
|
|
158
|
+
const store = codecStore(state);
|
|
159
|
+
const matchesCodec = (codec) =>
|
|
151
160
|
(codecName == null || codec.name === codecName) &&
|
|
152
|
-
(capability == null || typeof codec[capability] === "function")
|
|
153
|
-
);
|
|
161
|
+
(capability == null || typeof codec[capability] === "function");
|
|
162
|
+
const exactMatches = (store[codeHash] || []).filter(matchesCodec);
|
|
163
|
+
const universalMatches = (store[UNIVERSAL_CODE] || []).filter(matchesCodec);
|
|
164
|
+
const matches = codecName == null && exactMatches.length > 0
|
|
165
|
+
? exactMatches
|
|
166
|
+
: [...exactMatches, ...universalMatches];
|
|
154
167
|
if (matches.length === 0) {
|
|
155
168
|
const suffix = codecName ? ` named '${codecName}'` : "";
|
|
156
169
|
throw new Error(`No ${capability || "usable"} codec${suffix} for ${codeHash}`);
|
|
@@ -168,6 +181,7 @@ export {
|
|
|
168
181
|
loadCodecModule,
|
|
169
182
|
registerCodec,
|
|
170
183
|
resolveCodec,
|
|
171
|
-
|
|
184
|
+
closedPatternToCodeHash,
|
|
185
|
+
UNIVERSAL_CODE,
|
|
172
186
|
valueForCode
|
|
173
187
|
};
|
package/repl.mjs
CHANGED
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
listCodecs,
|
|
30
30
|
loadCodecModule,
|
|
31
31
|
resolveCodec,
|
|
32
|
-
|
|
32
|
+
closedPatternToCodeHash,
|
|
33
|
+
UNIVERSAL_CODE,
|
|
33
34
|
valueForCode
|
|
34
35
|
} from "./repl-codecs.mjs";
|
|
35
36
|
|
|
@@ -43,6 +44,7 @@ const COMMAND_NAMES = [
|
|
|
43
44
|
];
|
|
44
45
|
const CODEC_COMMAND_NAMES = ["load", "list"];
|
|
45
46
|
const PATH_COMMANDS = new Set(["klib", "ko", "load"]);
|
|
47
|
+
const INPUT_TYPE_NAME = "__input__";
|
|
46
48
|
const initialCodes = codes.dump();
|
|
47
49
|
|
|
48
50
|
function cloneJSON(value) {
|
|
@@ -323,14 +325,19 @@ function printValue(value, state = null) {
|
|
|
323
325
|
const lines = [valueWithEnvelopeToK(value)];
|
|
324
326
|
if (!state || value === undefined) return lines[0];
|
|
325
327
|
|
|
326
|
-
const codeHash =
|
|
328
|
+
const codeHash = closedPatternToCodeHash(value.pattern);
|
|
327
329
|
if (!codeHash) return lines[0];
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
const codecs = [
|
|
332
|
+
...(state.codecs?.[codeHash] || []),
|
|
333
|
+
...(state.codecs?.[UNIVERSAL_CODE] || [])
|
|
334
|
+
];
|
|
335
|
+
for (const codec of codecs) {
|
|
330
336
|
if (typeof codec.print !== "function") continue;
|
|
331
337
|
try {
|
|
332
338
|
lines.push(`${codec.name}: ${formatCodecOutput(codec.print(value, { codeHash, state }))}`);
|
|
333
339
|
} catch (error) {
|
|
340
|
+
if (codec.universal) continue;
|
|
334
341
|
lines.push(`${codec.name}: <error: ${error.message || String(error)}>`);
|
|
335
342
|
}
|
|
336
343
|
}
|
|
@@ -609,6 +616,40 @@ function resolveTypeHash(state, rawName) {
|
|
|
609
616
|
return hash;
|
|
610
617
|
}
|
|
611
618
|
|
|
619
|
+
function isSimpleTypeReference(rawType) {
|
|
620
|
+
return /^\$?[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/.test(rawType.trim());
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function resolveTypeExpressionHash(state, rawType) {
|
|
624
|
+
restoreCodes(state);
|
|
625
|
+
const preamble = aliasPreamble(state, INPUT_TYPE_NAME);
|
|
626
|
+
const source = [
|
|
627
|
+
preamble,
|
|
628
|
+
ensureSemicolon(`$ ${INPUT_TYPE_NAME} = ${rawType.trim()}`),
|
|
629
|
+
"()"
|
|
630
|
+
].filter(Boolean).join("\n");
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
const annotated = annotate(source, { libraries: [stateLibrary(state)] });
|
|
634
|
+
const hash = annotated.representatives[INPUT_TYPE_NAME];
|
|
635
|
+
if (!hash) throw new Error(":input type expression did not produce a code hash");
|
|
636
|
+
state.codes = codes.dump();
|
|
637
|
+
return hash;
|
|
638
|
+
} catch (error) {
|
|
639
|
+
restoreCodes(state);
|
|
640
|
+
throw remapError(error, preambleLineCount(preamble));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function resolveInputTypeHash(state, rawType) {
|
|
645
|
+
try {
|
|
646
|
+
return resolveTypeHash(state, rawType);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (isSimpleTypeReference(rawType)) throw error;
|
|
649
|
+
}
|
|
650
|
+
return resolveTypeExpressionHash(state, rawType);
|
|
651
|
+
}
|
|
652
|
+
|
|
612
653
|
function completeInputCommand(line, state, argStart, arg) {
|
|
613
654
|
const match = arg.match(/^(\s*)(\S+)?(\s+)?(\S*)?$/);
|
|
614
655
|
if (!match) return [[], line];
|
|
@@ -900,7 +941,9 @@ async function loadCodec(input, state) {
|
|
|
900
941
|
const filePath = rest.join(" ");
|
|
901
942
|
if (!filePath) throw new Error(":codec load requires a file path");
|
|
902
943
|
const registered = await loadCodecModule(state, expandHome(filePath));
|
|
903
|
-
return registered.map(({ name, codeHash }) =>
|
|
944
|
+
return registered.map(({ name, codeHash }) =>
|
|
945
|
+
`loaded codec ${name} for ${codeHash === UNIVERSAL_CODE ? "all types" : codeHash}`
|
|
946
|
+
);
|
|
904
947
|
}
|
|
905
948
|
case "list":
|
|
906
949
|
if (rest.length > 0) throw new Error(":codec list does not accept arguments");
|
|
@@ -911,15 +954,28 @@ async function loadCodec(input, state) {
|
|
|
911
954
|
}
|
|
912
955
|
|
|
913
956
|
async function requestCodecInput(input, state) {
|
|
914
|
-
const
|
|
915
|
-
if (
|
|
916
|
-
throw new Error(":input requires a
|
|
957
|
+
const trimmed = input.trim();
|
|
958
|
+
if (!trimmed) {
|
|
959
|
+
throw new Error(":input requires a type and optional codec name");
|
|
917
960
|
}
|
|
918
961
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
962
|
+
let codeHash;
|
|
963
|
+
let codecName = null;
|
|
964
|
+
try {
|
|
965
|
+
codeHash = resolveInputTypeHash(state, trimmed);
|
|
966
|
+
} catch (wholeError) {
|
|
967
|
+
const split = trimmed.match(/^(.*\S)\s+([a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*)$/);
|
|
968
|
+
if (!split) throw wholeError;
|
|
969
|
+
try {
|
|
970
|
+
codeHash = resolveInputTypeHash(state, split[1]);
|
|
971
|
+
codecName = split[2];
|
|
972
|
+
} catch (typeError) {
|
|
973
|
+
throw typeError;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const codec = resolveCodec(state, codeHash, codecName, "parse");
|
|
978
|
+
state.pendingInput = { codeHash, codecName, promptName: codec.name };
|
|
923
979
|
return [`input ${codeHash}${codecName ? ` using ${codecName}` : ""}: enter value text`];
|
|
924
980
|
}
|
|
925
981
|
|
|
@@ -1097,6 +1153,11 @@ function helpText() {
|
|
|
1097
1153
|
].join("\n");
|
|
1098
1154
|
}
|
|
1099
1155
|
|
|
1156
|
+
function promptForState(state) {
|
|
1157
|
+
const promptName = state.pendingInput?.promptName;
|
|
1158
|
+
return promptName ? `${promptName}> ` : "> ";
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1100
1161
|
function cliUsage() {
|
|
1101
1162
|
console.log("Usage: k-repl");
|
|
1102
1163
|
console.log(" k-repl -h");
|
|
@@ -1151,6 +1212,7 @@ function startRepl() {
|
|
|
1151
1212
|
} catch (error) {
|
|
1152
1213
|
console.error(error.message || String(error));
|
|
1153
1214
|
}
|
|
1215
|
+
rl.setPrompt(promptForState(state));
|
|
1154
1216
|
if (!closed) rl.prompt();
|
|
1155
1217
|
return;
|
|
1156
1218
|
}
|
|
@@ -1223,6 +1285,7 @@ export {
|
|
|
1223
1285
|
lineHasExplicitContinuation,
|
|
1224
1286
|
lineTerminatesSnippet,
|
|
1225
1287
|
printValue,
|
|
1288
|
+
promptForState,
|
|
1226
1289
|
propertyListToFilter,
|
|
1227
1290
|
valueToK
|
|
1228
1291
|
};
|
|
@@ -4,7 +4,7 @@ import { createState, evaluateInput } from "../repl.mjs";
|
|
|
4
4
|
import { Value, isProduct } from "../Value.mjs";
|
|
5
5
|
import codes from "../codes.mjs";
|
|
6
6
|
import { parse as parseFloat64, print as printFloat64 } from "../codecs/ieee.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { closedPatternToCodeHash, valueForCode } from "../repl-codecs.mjs";
|
|
8
8
|
|
|
9
9
|
const state = createState();
|
|
10
10
|
await evaluateInput(":load Examples/ieee.k", state);
|
|
@@ -40,7 +40,7 @@ async function runProjectedResult(op, x, y) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function assertFloatResult(value, expected, label) {
|
|
43
|
-
assert.equal(
|
|
43
|
+
assert.equal(closedPatternToCodeHash(value.pattern), float64Hash, `${label}: result should keep float64 envelope`);
|
|
44
44
|
assert.equal(printFloat64(value), expected, label);
|
|
45
45
|
}
|
|
46
46
|
|
package/tests/test-repl.mjs
CHANGED
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
helpText,
|
|
16
16
|
isMainEntrypoint,
|
|
17
17
|
lineHasExplicitContinuation,
|
|
18
|
-
lineTerminatesSnippet
|
|
18
|
+
lineTerminatesSnippet,
|
|
19
|
+
promptForState
|
|
19
20
|
} from "../repl.mjs";
|
|
20
21
|
import { decodeObject, objectToFunction } from "../object.mjs";
|
|
21
22
|
import { Value } from "../Value.mjs";
|
|
@@ -194,6 +195,36 @@ assert.equal(output[0], `input ${utf8State.typeAliases.string} using utf8: enter
|
|
|
194
195
|
output = await evaluateInput("hello", utf8State);
|
|
195
196
|
assert.match(output[0], /utf8: hello/);
|
|
196
197
|
|
|
198
|
+
const jsonState = createState();
|
|
199
|
+
output = await evaluateInput(":type bool = <{} true, {} false>", jsonState);
|
|
200
|
+
assert.match(output[0], /^\$ bool = @/);
|
|
201
|
+
output = await evaluateInput(":codec load ./codecs/json.mjs", jsonState);
|
|
202
|
+
assert.equal(output[0], "loaded codec json for all types");
|
|
203
|
+
output = await evaluateInput(":codec list", jsonState);
|
|
204
|
+
assert.match(output[0], /^json all /);
|
|
205
|
+
completions = completeInput(":input bool j", jsonState)[0];
|
|
206
|
+
assert(completions.includes(":input bool json"));
|
|
207
|
+
output = await evaluateInput(":input bool", jsonState);
|
|
208
|
+
assert.equal(output[0], `input ${jsonState.typeAliases.bool}: enter value text`);
|
|
209
|
+
assert.equal(promptForState(jsonState), "json> ");
|
|
210
|
+
output = await evaluateInput("true", jsonState);
|
|
211
|
+
assert.match(output[0], /\{\}\|true \?</);
|
|
212
|
+
assert.match(output[0], /json: true/);
|
|
213
|
+
assert.equal(promptForState(jsonState), "> ");
|
|
214
|
+
output = await evaluateInput(":input <{}true,{}false>", jsonState);
|
|
215
|
+
assert.equal(output[0], `input ${jsonState.typeAliases.bool}: enter value text`);
|
|
216
|
+
assert.equal(promptForState(jsonState), "json> ");
|
|
217
|
+
output = await evaluateInput("false", jsonState);
|
|
218
|
+
assert.match(output[0], /\{\}\|false \?</);
|
|
219
|
+
assert.match(output[0], /json: false/);
|
|
220
|
+
assert.equal(promptForState(jsonState), "> ");
|
|
221
|
+
output = await evaluateInput(":input {bool left, bool right} json", jsonState);
|
|
222
|
+
assert.match(output[0], /^input @[^ ]+ using json: enter value text$/);
|
|
223
|
+
assert.equal(promptForState(jsonState), "json> ");
|
|
224
|
+
output = await evaluateInput('{"left":true,"right":false}', jsonState);
|
|
225
|
+
assert.match(output[0], /json: \{"left":true,"right":false\}/);
|
|
226
|
+
assert.equal(promptForState(jsonState), "> ");
|
|
227
|
+
|
|
197
228
|
const replPath = fileURLToPath(new URL("../repl.mjs", import.meta.url));
|
|
198
229
|
try {
|
|
199
230
|
fs.rmSync(symlinkPath, { force: true });
|