@fraczak/k 6.0.0 → 6.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraczak/k",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
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": {
@@ -10,22 +10,19 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/fraczak/k.git"
14
- },
15
- "publishConfig": {
16
- "access": "public"
13
+ "url": "https://github.com/fraczak/k.git"
17
14
  },
18
15
  "author": "Wojciech Fraczak",
19
16
  "license": "ISC",
20
17
  "bin": {
21
- "k": "k.mjs",
22
- "k-compile-object": "objects/compile.mjs",
23
- "k-decompile-object": "objects/decompile.mjs",
24
- "k-parse": "codecs/k-parse.mjs",
25
- "k-pattern": "codecs/k-pattern.mjs",
26
- "k-print": "codecs/k-print.mjs",
27
- "k-repl": "repl2.mjs",
28
- "k-repl2": "repl2.mjs"
18
+ "k": "./k.mjs",
19
+ "k-compile-object": "./objects/compile.mjs",
20
+ "k-decompile-object": "./objects/decompile.mjs",
21
+ "k-parse": "./codecs/k-parse.mjs",
22
+ "k-pattern": "./codecs/k-pattern.mjs",
23
+ "k-print": "./codecs/k-print.mjs",
24
+ "k-repl": "./repl2.mjs",
25
+ "k-repl2": "./repl2.mjs"
29
26
  },
30
27
  "devDependencies": {
31
28
  "jiwson": "^0.1.2"
package/repl2.mjs CHANGED
@@ -376,6 +376,15 @@ function createCompleter(state) {
376
376
  return (line) => completeInput(line, state);
377
377
  }
378
378
 
379
+ function isMainEntrypoint(entryArg = argv[1]) {
380
+ if (!entryArg) return false;
381
+ try {
382
+ return fs.realpathSync(entryArg) === fs.realpathSync(fileURLToPath(import.meta.url));
383
+ } catch {
384
+ return false;
385
+ }
386
+ }
387
+
379
388
  function ensureSemicolon(source) {
380
389
  return source.trim().endsWith(";") ? source : `${source};`;
381
390
  }
@@ -635,7 +644,7 @@ function startRepl() {
635
644
  });
636
645
  }
637
646
 
638
- if (argv[1] && fileURLToPath(import.meta.url) === argv[1]) {
647
+ if (isMainEntrypoint()) {
639
648
  startRepl();
640
649
  }
641
650
 
@@ -646,6 +655,7 @@ export {
646
655
  createState,
647
656
  evaluateInput,
648
657
  helpText,
658
+ isMainEntrypoint,
649
659
  printValue,
650
660
  propertyListToFilter,
651
661
  valueToK
package/test-repl2.mjs CHANGED
@@ -2,8 +2,9 @@ import assert from "node:assert";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
5
6
 
6
- import { completeInput, createState, evaluateInput } from "./repl2.mjs";
7
+ import { completeInput, createState, evaluateInput, isMainEntrypoint } from "./repl2.mjs";
7
8
  import { decodeObject, objectToFunction } from "./object.mjs";
8
9
  import { Product } from "./Value.mjs";
9
10
 
@@ -44,6 +45,7 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "k-repl2-"));
44
45
  const libPath = path.join(tmpDir, "session.klib");
45
46
  const koPath = path.join(tmpDir, "succ.ko");
46
47
  const sourcePath = path.join(tmpDir, "session.k");
48
+ const symlinkPath = path.resolve(".test-k-repl2-link");
47
49
  output = await evaluateInput(`:save ${libPath}`, state);
48
50
  assert.equal(output[0], `saved ${libPath}`);
49
51
 
@@ -65,6 +67,14 @@ assert.match(output[0], /^nat = @/m);
65
67
  output = await evaluateInput(":rels", loadedSource);
66
68
  assert.match(output[0], /^succ = @/m);
67
69
 
70
+ const replPath = fileURLToPath(new URL("./repl2.mjs", import.meta.url));
71
+ try {
72
+ fs.rmSync(symlinkPath, { force: true });
73
+ } catch {}
74
+ fs.symlinkSync(replPath, symlinkPath);
75
+ fs.chmodSync(replPath, 0o755);
76
+ assert.equal(isMainEntrypoint(symlinkPath), true);
77
+
68
78
  const reloaded = createState();
69
79
  output = await evaluateInput(`:load ${libPath}`, reloaded);
70
80
  assert.equal(output[0], `loaded ${libPath}`);
@@ -92,4 +102,5 @@ await assert.rejects(
92
102
  );
93
103
 
94
104
  fs.rmSync(tmpDir, { recursive: true, force: true });
105
+ fs.rmSync(symlinkPath, { force: true });
95
106
  console.log("OK");
@@ -0,0 +1,105 @@
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import codes from '../codes.mjs';
6
+ import { parse } from './typeParser.mjs';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const REGISTRY_FILE = path.join(__dirname, 'registry.json');
10
+ const PORT = 3001;
11
+
12
+ // Load existing registry if it exists
13
+ if (fs.existsSync(REGISTRY_FILE)) {
14
+ console.log('Loading existing registry...');
15
+ try {
16
+ const data = fs.readFileSync(REGISTRY_FILE, 'utf8');
17
+ const json = JSON.parse(data);
18
+ codes.load(json);
19
+ console.log(`Loaded ${Object.keys(json).length} types.`);
20
+ } catch (err) {
21
+ console.error('Error loading registry:', err);
22
+ }
23
+ } else {
24
+ console.log('No existing registry found. Starting fresh.');
25
+ }
26
+
27
+ const server = http.createServer((req, res) => {
28
+ // Helper to send JSON response
29
+ const sendJSON = (data, status = 200) => {
30
+ res.writeHead(status, { 'Content-Type': 'application/json' });
31
+ res.end(JSON.stringify(data));
32
+ };
33
+
34
+ // Helper to send Error
35
+ const sendError = (message, status = 400) => {
36
+ res.writeHead(status, { 'Content-Type': 'application/json' });
37
+ res.end(JSON.stringify({ error: message }));
38
+ };
39
+
40
+ // Route: POST
41
+ if (req.method === 'POST') {
42
+ let body = '';
43
+ req.on('data', chunk => {
44
+ body += chunk.toString();
45
+ });
46
+ req.on('end', () => {
47
+ try {
48
+ // 1. Parse the input string using the Jison parser
49
+ // The parser expects a string of type definitions
50
+ console.log("Received body:", body);
51
+ const definitions = parse(body);
52
+ console.log("Parsed definitions:", JSON.stringify(definitions, null, 2));
53
+
54
+ // 2. Register the parsed codes
55
+ // codes.register returns the representatives map { temp_name: canonical_hash }
56
+ const representatives = codes.register(definitions);
57
+
58
+ // Filter out generated names (_gen_...) and canonical names (@...)
59
+ const filteredRepresentatives = Object.fromEntries(
60
+ Object.entries(representatives).filter(([key, value]) =>
61
+ !key.startsWith('_gen_') && !key.startsWith('@')
62
+ )
63
+ );
64
+
65
+ // 3. Save to disk
66
+ const dump = codes.dump();
67
+ fs.writeFileSync(REGISTRY_FILE, JSON.stringify(dump, null, 2));
68
+
69
+ // 4. Return the mapping
70
+ sendJSON(filteredRepresentatives);
71
+
72
+ } catch (err) {
73
+ console.error(err);
74
+ sendError(`Registration failed: ${err.message}`);
75
+ }
76
+ });
77
+ }
78
+ // Route: GET
79
+ else if (req.method === 'GET') {
80
+ const typeId = req.url.split('/').pop();
81
+ console.log("Requested type ID:", typeId);
82
+ if (!typeId) {
83
+ sendError('Missing type ID');
84
+ return;
85
+ }
86
+ // Decode URI component in case @ is encoded
87
+ const decodedId = decodeURIComponent(typeId);
88
+ console.log("Decoded type ID:", decodedId);
89
+
90
+ const typeDef = codes.find(decodedId);
91
+ if (typeDef.code === 'undefined') {
92
+ sendError('Type not found', 404);
93
+ } else {
94
+ sendJSON(typeDef);
95
+ }
96
+ }
97
+ // 404 Not Found
98
+ else {
99
+ sendError('Not Found', 404);
100
+ }
101
+ });
102
+
103
+ server.listen(PORT, () => {
104
+ console.log(`Type Registry running at http://localhost:${PORT}/`);
105
+ });
@@ -0,0 +1,132 @@
1
+ %{
2
+
3
+ let defs = {};
4
+ let genCount = 0;
5
+ let genNameId;
6
+
7
+ function genName() {
8
+ return `_gen_${genCount++}`;
9
+ }
10
+
11
+ function fromEscString(escString) {
12
+ const isSingleQuoted = escString.startsWith("'");
13
+ let str = escString.substring(1, escString.length - 1);
14
+ if (isSingleQuoted) {
15
+ str = str.replace(/\\'/g, "'");
16
+ } else {
17
+ str = str.replace(/\\"/g, '"');
18
+ }
19
+ str = str.replace(/\\\\/g, '\\');
20
+ str = str.replace(/\\\//g, '/');
21
+ str = str.replace(/\\b/g, '\b');
22
+ str = str.replace(/\\f/g, '\f');
23
+ str = str.replace(/\\n/g, '\n');
24
+ str = str.replace(/\\r/g, '\r');
25
+ str = str.replace(/\\t/g, '\t');
26
+ str = str.replace(/\\u([\dA-F]{4})/gi, (match, p1) => String.fromCharCode(parseInt(p1, 16)));
27
+
28
+ return str;
29
+ }
30
+
31
+ %}
32
+
33
+ %lex
34
+
35
+ %%
36
+ \s+ /* blanks */
37
+ "<" return 'LA';
38
+ ">" return 'RA';
39
+ "{" return 'LC';
40
+ "}" return 'RC';
41
+ "=" return 'EQ';
42
+ "," return 'COMMA';
43
+ ";" return 'SC';
44
+ "@" return 'AT';
45
+ "$" return 'DOLLAR';
46
+ \"([^"\\]|\\(.|\n))*\"|\'([^'\\]|\\(.|\n))*\' return 'STRING';
47
+ [a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]* return 'NAME';
48
+ <<EOF>> return 'EOF';
49
+ . return 'INVALID';
50
+
51
+ /lex
52
+
53
+ %token NAME STRING
54
+ %token LA RA LC RC EQ COMMA SC AT DOLLAR
55
+ %token EOF
56
+
57
+ %start expressions
58
+
59
+ %%
60
+
61
+ expressions
62
+ : initialize_defs definitions EOF
63
+ { return defs; }
64
+ ;
65
+
66
+ initialize_defs
67
+ : /* empty */
68
+ { defs = {}; genCount = 0; }
69
+ ;
70
+
71
+ definitions
72
+ : definition
73
+ | definitions definition
74
+ ;
75
+
76
+ definition
77
+ : DOLLAR name EQ LA field_list RA SC
78
+ { defs[$2] = { code: 'union', union: $5 }; }
79
+ | DOLLAR name EQ LC field_list RC SC
80
+ { defs[$2] = { code: 'product', product: $5 }; }
81
+ ;
82
+
83
+ type_expr
84
+ : LA field_list RA
85
+ {
86
+ genNameId = genName();
87
+ defs[genNameId] = { code: 'union', union: $2 };
88
+ $$ = genNameId;
89
+ }
90
+ | LC field_list RC
91
+ {
92
+ genNameId = genName();
93
+ defs[genNameId] = { code: 'product', product: $2 };
94
+ $$ = genNameId;
95
+ }
96
+ | ref
97
+ { $$ = $1; }
98
+ ;
99
+
100
+ ref
101
+ : name
102
+ { $$ = $1; }
103
+ | AT name
104
+ { $$ = '@' + $2; }
105
+ ;
106
+
107
+ field_list
108
+ : /* empty */
109
+ { $$ = {}; }
110
+ | field
111
+ { $$ = {}; $$[$1.label] = $1.type; }
112
+ | field_list COMMA field
113
+ { $$ = $1; $$[$3.label] = $3.type; }
114
+ ;
115
+
116
+ field
117
+ : type_expr label
118
+ { $$ = { type: $1, label: $2 }; }
119
+ ;
120
+
121
+ label
122
+ : name
123
+ { $$ = $1; }
124
+ | str
125
+ { $$ = $1; }
126
+ ;
127
+
128
+ name: NAME { $$ = yytext; };
129
+ str: STRING { $$ = fromEscString(yytext); };
130
+
131
+ %%
132
+