rip-lang 3.17.1 → 3.17.2

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.17.1",
3
+ "version": "3.17.2",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/scripts/serve.js CHANGED
@@ -27,6 +27,30 @@ const MIME_TYPES = {
27
27
  '.rip': 'text/plain; charset=utf-8'
28
28
  };
29
29
 
30
+ // ETag (size+mtime, plus encoding so brotli/plain get distinct tags) +
31
+ // no-cache for cheap 304 revalidation, and Vary so caches don't mix encodings.
32
+ // Returns null if unreadable, letting the caller fall back.
33
+ function serveFile(req, diskPath, contentType, encoding) {
34
+ let stat;
35
+ try { stat = statSync(diskPath); } catch { return null; }
36
+ const etag = `"${stat.size.toString(16)}-${Math.round(stat.mtimeMs).toString(16)}${encoding ? '-' + encoding : ''}"`;
37
+ const headers = {
38
+ 'Content-Type': contentType,
39
+ 'ETag': etag,
40
+ 'Cache-Control': 'no-cache',
41
+ 'Vary': 'Accept-Encoding'
42
+ };
43
+ if (encoding) headers['Content-Encoding'] = encoding;
44
+ if (req.headers.get('if-none-match') === etag) {
45
+ return new Response(null, { status: 304, headers });
46
+ }
47
+ try {
48
+ return new Response(readFileSync(diskPath), { headers });
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
30
54
  // Request handler for serving files
31
55
  function handleRequest(req) {
32
56
  const url = new URL(req.url);
@@ -50,34 +74,19 @@ function handleRequest(req) {
50
74
  const filePath = join(ROOT, pathname);
51
75
  const ext = extname(pathname);
52
76
  const acceptEncoding = req.headers.get('accept-encoding') || '';
77
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
53
78
 
54
- // Check for brotli compressed version (.br)
79
+ // Prefer the pre-compressed Brotli sidecar when the client accepts it.
55
80
  if (acceptEncoding.includes('br') && existsSync(filePath + '.br')) {
56
- try {
57
- const compressed = readFileSync(filePath + '.br');
58
- return new Response(compressed, {
59
- headers: {
60
- 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
61
- 'Content-Encoding': 'br',
62
- 'Cache-Control': 'no-cache'
63
- }
64
- });
65
- } catch (e) {
66
- // Fall through to regular file
67
- }
81
+ const res = serveFile(req, filePath + '.br', contentType, 'br');
82
+ if (res) return res;
68
83
  }
69
84
 
70
- // Serve regular file
71
- try {
72
- const file = readFileSync(filePath);
73
- return new Response(file, {
74
- headers: {
75
- 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream'
76
- }
77
- });
78
- } catch (e) {
79
- return new Response('404 Not Found', { status: 404 });
80
- }
85
+ // Plain file — also the failover when no .br sidecar exists.
86
+ const res = serveFile(req, filePath, contentType, null);
87
+ if (res) return res;
88
+
89
+ return new Response('404 Not Found', { status: 404 });
81
90
  }
82
91
 
83
92
  // Try to start server on preferred port, fallback to OS-assigned port
package/src/types.js CHANGED
@@ -405,12 +405,23 @@ function looksLikeBareFunctionType(typeTokens) {
405
405
  // left alone and any side effects survive.
406
406
  function isCompleteTypeExpr(tokens, a, b) {
407
407
  if (b <= a) return false;
408
- const SEP = new Set(['|', '&', ',', ':', '?', '.', '...', '=>']);
408
+ const SEP = new Set(['|', '&', ',', ':', '?', '.', '...']);
409
409
  let par = 0, brk = 0, brc = 0, gen = 0, atomEnd = false;
410
+ const parInfo = []; // per-paren-depth: { colon, open }
411
+ let lastClosedParen = null; // { colon, empty } of the most recently closed group
410
412
  for (let j = a; j < b; j++) {
411
413
  const t = tokens[j][0], v = tokens[j][1];
412
- if (t === '(' || t === 'PARAM_START') { par++; atomEnd = false; continue; }
413
- if (t === ')' || t === 'PARAM_END') { if (--par < 0) return false; atomEnd = true; continue; }
414
+ // Function-type arrow: valid only after a closed param group that is empty
415
+ // `()` or typed `(x: T)`. An untyped `(e) =>` (or leading `=>`) is a value
416
+ // arrow, so context B won't misread `@input: (e) => x = y` as a typed decl.
417
+ if (t === '=>') {
418
+ const p = j > a ? tokens[j - 1][0] : null;
419
+ if ((p === ')' || p === 'PARAM_END') && lastClosedParen &&
420
+ (lastClosedParen.colon || lastClosedParen.empty)) { atomEnd = false; continue; }
421
+ return false;
422
+ }
423
+ if (t === '(' || t === 'PARAM_START') { parInfo.push({ colon: false, open: j }); par++; atomEnd = false; continue; }
424
+ if (t === ')' || t === 'PARAM_END') { if (--par < 0) return false; const pi = parInfo.pop(); lastClosedParen = pi ? { colon: pi.colon, empty: j === pi.open + 1 } : null; atomEnd = true; continue; }
414
425
  if (t === '[' || t === 'INDEX_START') { brk++; atomEnd = false; continue; }
415
426
  if (t === ']' || t === 'INDEX_END') { if (--brk < 0) return false; atomEnd = true; continue; }
416
427
  if (t === '{') { brc++; atomEnd = false; continue; }
@@ -425,7 +436,7 @@ function isCompleteTypeExpr(tokens, a, b) {
425
436
  return false;
426
437
  }
427
438
  if (t === '=') { if (gen > 0) { atomEnd = false; continue; } return false; } // generic default only
428
- if (SEP.has(t)) { atomEnd = false; continue; }
439
+ if (SEP.has(t)) { if (t === ':' && parInfo.length) parInfo[parInfo.length - 1].colon = true; atomEnd = false; continue; }
429
440
  if (t === 'IDENTIFIER' || t === 'PROPERTY' || t === 'NUMBER' ||
430
441
  t === 'STRING' || t === 'NULL' || t === 'UNDEFINED' || t === 'BOOL') {
431
442
  if (atomEnd) return false; // two atoms, no separator → not a type