@slim-lang/core 1.2.2 → 1.2.4

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": "@slim-lang/core",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Slim extends JavaScript with runtime types, structs, operators, and components, compiling to plain Javascript",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "main": "path/to/your/files",
3
3
  "usePackages": true,
4
- "uses": "import"
4
+ "uses": "import",
5
+ "packages": "packages"
5
6
  }
@@ -2,6 +2,9 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { rm, mkdir } from 'node:fs/promises';
4
4
  import chalk from "chalk";
5
+ import { projectPackagesDir } from "../modulePaths.js";
6
+
7
+ export { projectPackagesDir };
5
8
 
6
9
  export const formatError = chalk.red.bold
7
10
  export const formatSuccess = chalk.green.bold
@@ -30,10 +33,10 @@ export function error(...text) {
30
33
  }
31
34
 
32
35
  export function isPackageExists(name) {
33
- return fs.existsSync(path.join(rootPath, "packages", name))
36
+ return fs.existsSync(path.join(projectPackagesDir(), name))
34
37
  }
35
38
  export function getPackagePath(name) {
36
- return path.join(rootPath, "packages", name)
39
+ return path.join(projectPackagesDir(), name)
37
40
  }
38
41
 
39
42
  export async function deleteDirectory(dirPath) {
package/src/bin/spm.js CHANGED
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { Command } from "commander";
4
4
  import { formatError, formatBold, formatItalic, formatSuccess, createFolder, createFile } from "./helpers.js"
5
5
  import pkg from "../../package.json" with { type: "json" };
6
- import { log, isPackageExists, rootPath, deleteDirectory, loading, delay } from "./helpers.js";
6
+ import { log, isPackageExists, rootPath, deleteDirectory, loading, delay, projectPackagesDir } from "./helpers.js";
7
7
 
8
8
  import fs from "node:fs"
9
9
  import path from "node:path"
@@ -27,7 +27,7 @@ function errlog(...args) {
27
27
  console.log(formatError(...args))
28
28
  }
29
29
 
30
- const packagesPath = path.join(rootPath, "packages")
30
+ const packagesPath = projectPackagesDir()
31
31
 
32
32
  function renderObject(obj, indent = 0) {
33
33
  let output = "";
@@ -73,7 +73,7 @@ spm
73
73
  spm
74
74
  .command("list")
75
75
  .action(() => {
76
- const targetDir = path.join(rootPath, "packages")
76
+ const targetDir = packagesPath
77
77
  if (!fs.existsSync(targetDir)) {
78
78
  spmlog("No packages installed (the packages/ directory does not exist)")
79
79
  return
@@ -186,7 +186,7 @@ spm
186
186
  }
187
187
  }
188
188
 
189
- const res = await deleteDirectory(path.join(rootPath, "packages", name))
189
+ const res = await deleteDirectory(path.join(packagesPath, name))
190
190
 
191
191
  if (res) {
192
192
  removeLockEntry(name)
@@ -257,7 +257,7 @@ spm
257
257
  await loading({
258
258
  startMsg: `Creating path for ${name}`,
259
259
  callback: async ({ fail, ok }) => {
260
- const packagePath = path.join(rootPath, "packages", name)
260
+ const packagePath = path.join(packagesPath, name)
261
261
 
262
262
  if (isExists) return fail("Package is already exists")
263
263
  else {
@@ -336,7 +336,7 @@ spm
336
336
  const githubRepo = SPMContent.github.repo
337
337
  const version = SPMContent.version
338
338
  const description = SPMContent.description
339
- const packagePath = path.join("packages", name)
339
+ const packagePath = path.join(packagesPath, name)
340
340
 
341
341
  if ("github" in SPMContent && "repo" in SPMContent.github) {
342
342
  await loading({
@@ -445,7 +445,7 @@ spm
445
445
  spmlog(formatError("No GitHub repository specified"));
446
446
  }
447
447
  } else {
448
- spmlog(formatError(`No package founded: /packages/${name}`));
448
+ spmlog(formatError(`No package founded: ${path.relative(process.cwd(), path.join(packagesPath, name)) || name}`));
449
449
  }
450
450
  });
451
451
 
package/src/checker.js CHANGED
@@ -5,8 +5,6 @@ import { computeLineStarts, offsetToLineCol } from "./sourcemap.js"
5
5
 
6
6
  const traverse = _traverse.default ?? _traverse
7
7
 
8
- // Check annotations before lowering; report only definite type conflicts.
9
-
10
8
  const NUMERIC = new Set(["int", "float", "number"])
11
9
  const KNOWN = new Set([
12
10
  "int", "float", "number", "string", "bool", "null", "undefined",
@@ -87,7 +85,6 @@ function normalize(label) {
87
85
  return trimmed
88
86
  }
89
87
 
90
- // Resolve inherited fields so structs can cross module boundaries.
91
88
  function resolveStruct(name, env, seen = new Set()) {
92
89
  const definition = env.structs.get(name)
93
90
  if (!definition) return null
@@ -135,7 +132,6 @@ export function createEnvironment() {
135
132
  }
136
133
  }
137
134
 
138
- // Module declarations keyed by absolute source path.
139
135
  const moduleTypes = new Map()
140
136
 
141
137
  function signatureFor(name, path_, env) {
@@ -149,7 +145,7 @@ const BUILTIN_CLASSES = new Set(["Map", "Set", "Date", "Promise", "RegExp", "Err
149
145
  function isOpen(label, env) {
150
146
  const base = baseName(label)
151
147
  if (!base) return true
152
- // Custom validators shadow built-ins and cannot be evaluated statically.
148
+
153
149
  if (env.customTypes.has(base)) return true
154
150
  if (KNOWN.has(base) || BUILTIN_CLASSES.has(base)) return false
155
151
  if (env.structs.has(base) || env.enums.has(base)) return false
@@ -217,7 +213,6 @@ function acceptsAtom(target, source, env) {
217
213
  return false
218
214
  }
219
215
 
220
- // A union needs one compatible arm; array literals need every element to fit.
221
216
  export function accepts(target, source, env, requireAll = false) {
222
217
  if (!target || !source) return true
223
218
 
@@ -598,7 +593,6 @@ function checkStructLiteral(structName, node, path_, env, report) {
598
593
  }
599
594
  }
600
595
 
601
- // Inspect lowered match expressions to preserve exhaustiveness checks.
602
596
  function matchExpression(node) {
603
597
  if (!t.isCallExpression(node) || node.arguments.length !== 1) return null
604
598
 
@@ -839,7 +833,6 @@ function buildModuleTypes(ast, env) {
839
833
  return record
840
834
  }
841
835
 
842
- // Import known declarations; unresolved modules remain unchecked.
843
836
  function seedImports(env, imports, wildcards) {
844
837
  const merge = (record, name, local) => {
845
838
  if (record.structs.has(name)) env.structs.set(local, record.structs.get(name))
@@ -850,7 +843,6 @@ function seedImports(env, imports, wildcards) {
850
843
 
851
844
  const sources = new Set([...wildcards, ...imports.map(entry => entry.source)])
852
845
 
853
- // Local declarations override explicit and transitive imports.
854
846
  for (const source of sources) {
855
847
  const record = moduleTypes.get(source)
856
848
  if (!record) continue
package/src/compile.js CHANGED
@@ -18,8 +18,6 @@ let check = true
18
18
  let useStyle = "import"
19
19
 
20
20
  function syncExternal() {
21
- // The runtime ships inside the package; the compiled output goes to the
22
- // project's own dist/ (so this works both locally and when installed).
23
21
  const srcExternal = path.join(PACKAGE_ROOT, "src/external")
24
22
  const distExternal = path.resolve("dist/external")
25
23
 
@@ -5,16 +5,24 @@ import { fileURLToPath } from "node:url"
5
5
 
6
6
  const slimExtension = ".slim"
7
7
 
8
- // Root of the installed toolchain package (…/src/modulePaths.js -> package root).
9
- // Locally this equals the project root; when installed it points into
10
- // node_modules/@slim-lang/core, where the shipped stdlib (@slim/*) lives.
11
8
  export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
12
9
 
13
- // `@`-imports resolve against the project's own packages/ (spm installs) first,
14
- // then the stdlib shipped inside the package. When run locally the two are the
15
- // same directory.
10
+ let projectPackagesCache = null
11
+ export function projectPackagesDir() {
12
+ if (projectPackagesCache) return projectPackagesCache
13
+
14
+ let dir = "packages"
15
+ try {
16
+ const config = JSON.parse(fs.readFileSync(path.join(process.cwd(), "slimconfig.json"), "utf8"))
17
+ if (typeof config.packages === "string" && config.packages.trim()) dir = config.packages
18
+ } catch {}
19
+
20
+ projectPackagesCache = path.resolve(dir)
21
+ return projectPackagesCache
22
+ }
23
+
16
24
  function packageRoots() {
17
- const projectPackages = path.resolve("packages")
25
+ const projectPackages = projectPackagesDir()
18
26
  const shippedPackages = path.join(PACKAGE_ROOT, "packages")
19
27
 
20
28
  return projectPackages === shippedPackages
@@ -41,16 +49,28 @@ export function getDistPath(slimFile) {
41
49
  const abs = path.resolve(slimFile)
42
50
  const srcRoot = path.resolve("src")
43
51
  const projectRoot = path.resolve(".")
44
- const relative = isWithin(srcRoot, abs)
45
- ? path.relative(srcRoot, abs)
46
- : path.relative(projectRoot, abs)
52
+ const shippedPackages = path.join(PACKAGE_ROOT, "packages")
53
+ const projectPackages = projectPackagesDir()
54
+
55
+ let relative
56
+
57
+ if (isWithin(shippedPackages, abs)) {
58
+ relative = path.join("packages", path.relative(shippedPackages, abs))
59
+ } else if (isWithin(projectPackages, abs)) {
60
+ relative = path.join(path.basename(projectPackages), path.relative(projectPackages, abs))
61
+ } else if (isWithin(srcRoot, abs)) {
62
+ relative = path.relative(srcRoot, abs)
63
+ } else if (isWithin(projectRoot, abs)) {
64
+ relative = path.relative(projectRoot, abs)
65
+ } else {
66
+ const stripped = path.relative(projectRoot, abs).split(path.sep).filter(seg => seg !== "..")
67
+ relative = stripped.length ? path.join(...stripped) : path.basename(abs)
68
+ }
47
69
 
48
70
  return path.resolve("dist", relative.replace(/\.slim$/, ".js"))
49
71
  }
50
72
 
51
73
  export function resolveSlimSource(raw, fromFile) {
52
- // Node builtins (e.g. "node:crypto", "fs", "path") are not Slim sources;
53
- // leave them for the JS import to resolve untouched.
54
74
  if (isBuiltin(raw)) return null
55
75
 
56
76
  if (raw.startsWith("@")) {
@@ -67,8 +87,6 @@ export function resolveSlimSource(raw, fromFile) {
67
87
 
68
88
  if (isNodeModule(raw)) return null
69
89
 
70
- // Not found in any root: return the project-local path so errors point
71
- // at the user's own packages/ directory.
72
90
  return path.join(roots[0], packageName + slimExtension)
73
91
  }
74
92
 
package/src/parser.js CHANGED
@@ -43,7 +43,6 @@ function wordOperatorEdits(code) {
43
43
  for (const token of tokenize(code)) {
44
44
  if (token.type === "ws" || token.type === "newline" || token.type === "comment") continue
45
45
 
46
- // Lower word operators only after an operand.
47
46
  const replacement = token.type === "name" ? replacements[token.value] : undefined
48
47
  if (replacement && endsExpression(prev)) {
49
48
  edits.push({ start: token.start, end: token.end, replacement })
@@ -290,7 +289,6 @@ function functionEdits(text) {
290
289
  })
291
290
  }
292
291
 
293
- // Cache tokens for whole-file and extracted-body scans.
294
292
  const tokenCache = new Map()
295
293
 
296
294
  function tokensFor(src) {
@@ -309,7 +307,6 @@ function tokensFor(src) {
309
307
  return entry
310
308
  }
311
309
 
312
- // Match brackets by tokens so literals and comments do not affect depth.
313
310
  function readBalanced(src, pos, open = "{", close = "}") {
314
311
  const { tokens, starts } = tokensFor(src)
315
312
  let index = starts.get(pos)
@@ -329,8 +326,6 @@ function readBalanced(src, pos, open = "{", close = "}") {
329
326
  return -1
330
327
  }
331
328
 
332
- // Read balanced arguments after matching a construct head.
333
- // Return type may be introduced by `->` or `:` (e.g. `func f(): number`).
334
329
  const returnArrow = /\s*(?:->|:)\s*/y
335
330
 
336
331
  function headMatcher(head, tail) {
@@ -462,7 +457,6 @@ function topLevelWhen(str) {
462
457
  return -1
463
458
  }
464
459
 
465
- // Recursively lower nested match expressions without touching strings.
466
460
  function lowerMatches(str) {
467
461
  if (!str.includes("match")) return str
468
462
 
@@ -627,8 +621,6 @@ function structuralEdits(text) {
627
621
  let rest = line.slice(idx + 1).trim()
628
622
  if (!field) continue
629
623
 
630
- // Optional fields may be written as `*key` or `key?`; normalize
631
- // both to the `*key` form the struct runtime understands.
632
624
  let optional = false
633
625
  if (field.startsWith("*")) { optional = true; field = field.slice(1).trim() }
634
626
  if (field.endsWith("?")) { optional = true; field = field.slice(0, -1).trim() }
@@ -688,8 +680,6 @@ function structuralEdits(text) {
688
680
  const head = declarationHead.exec(src)
689
681
  if (!head) return null
690
682
 
691
- // `export default const X: T = ...` is not valid JS as a single
692
- // statement; lower it to a typed declaration plus `export default X`.
693
683
  const leadingDefault = src.slice(0, i).match(/export\s+default\s+$/)
694
684
  const start = leadingDefault ? i - leadingDefault[0].length : i
695
685
 
@@ -708,10 +698,6 @@ function structuralEdits(text) {
708
698
  },
709
699
  ({ keyword, name, type, expr, exprStart, defaultExport }) => {
710
700
  const pattern = name.startsWith("{") || name.startsWith("[")
711
- // Keep the declaration exported so the module wrapper leaves it at
712
- // top level, then re-export the binding as default. Emitting a bare
713
- // `const` plus `export default X` would trap the `const` in the
714
- // module's try/catch, leaving the default export undefined.
715
701
  const prefix = defaultExport ? "export " : ""
716
702
  const head = pattern
717
703
  ? `${prefix}${keyword} ${name} = __typed_pattern__(`
@@ -730,7 +716,6 @@ function structuralEdits(text) {
730
716
  /(\w[\w$.]*(?:\[.*?\])?)\s*(?:=>\s*([\w$]+))?\s*\n((?:\s*\|(?!\|)[^\n]+\n?)+)/g,
731
717
  (match, source, alias, pipes) => {
732
718
  const steps = [...pipes.matchAll(/\|\s*([\w$]+)\(([^)]*)\)/g)]
733
- // Preserve non-pipe bars, such as multiline union types.
734
719
  if (steps.length === 0) return match
735
720
 
736
721
  const callbackMethods = new Set([
package/src/transform.js CHANGED
@@ -28,9 +28,6 @@ function getTypeReferenceName(typeText) {
28
28
  return withoutGeneric.split("::")[0]
29
29
  }
30
30
 
31
- // Builtin type names are always matched by label; they must never be resolved
32
- // through a same-named binding in scope (e.g. a `const string` variable would
33
- // otherwise shadow the primitive `string` type).
34
31
  const BUILTIN_TYPE_NAMES = new Set([
35
32
  "int", "float", "number", "string", "bool", "null", "undefined",
36
33
  "object", "array", "function", "any", "element"
@@ -439,7 +436,6 @@ function getRuntimePath(sourceFile, entry) {
439
436
  return relFixed.startsWith(".") ? relFixed : "./" + relFixed
440
437
  }
441
438
 
442
- // DOM globals select the full runtime; pure code uses the portable core.
443
439
  const DOM_RUNTIME = new Set([
444
440
  "htmlToVdom", "HTMLElement", "__html__",
445
441
  "__flush_events__", "__bind_events__", "__lifecycle__",
@@ -460,13 +456,6 @@ function usesDom(ast) {
460
456
  return found
461
457
  }
462
458
 
463
- // `use` mirrors `import`: `use { X } from Y` is a named import, `use * as X`
464
- // a namespace import, and a bare `use X from Y` a default import — exactly like
465
- // `import X from Y`. Named exports (including Slim's `export const`/`export func`)
466
- // must therefore be brought in with braces: `use { X } from Y`.
467
- //
468
- // When `bareIsDefault` is false (the legacy `"uses": "named"` config style), a
469
- // bare `use X from Y` instead lowers to a named import `import { X } from Y`.
470
459
  function parseSpecifiers(name, bareIsDefault = true) {
471
460
  const trimmed = name.trim()
472
461
 
@@ -485,7 +474,6 @@ function parseSpecifiers(name, bareIsDefault = true) {
485
474
  })
486
475
  }
487
476
 
488
- // `use X as Y from Z` renames a named export (there is no `import X as Y`).
489
477
  const aliasMatch = trimmed.match(/^([\w$]+)\s+as\s+([\w$]+)$/)
490
478
  if (aliasMatch) {
491
479
  return [t.importSpecifier(t.identifier(aliasMatch[2]), t.identifier(aliasMatch[1]))]
@@ -530,8 +518,6 @@ function formatSyntaxError(err, originalCode, sourceFile, mapped) {
530
518
  }
531
519
 
532
520
  export function transform(code, sourceFile = "input.ps", options = {}) {
533
- // `"uses": "named"` (or "legacy") restores the old bare-import style where a
534
- // bare `use X from Y` is a named import; the default mirrors `import`.
535
521
  const bareIsDefault = options.uses !== "named" && options.uses !== "legacy"
536
522
  const asyncFunctions = new Set()
537
523
  const imports = new Map()
@@ -601,7 +587,6 @@ export function transform(code, sourceFile = "input.ps", options = {}) {
601
587
  }
602
588
  })
603
589
 
604
- // Check annotations before lowering or writing output.
605
590
  if (options.check !== false) {
606
591
  const moduleImports = []
607
592
  const moduleWildcards = []