conjure-js 0.0.8 → 0.0.10

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.
Files changed (52) hide show
  1. package/dist-cli/conjure-js.mjs +393 -101
  2. package/package.json +6 -10
  3. package/scripts/gen-core-source.mjs +126 -0
  4. package/src/bin/__fixtures__/smoke/repl-smoke.ts +6 -1
  5. package/src/bin/cli.ts +3 -51
  6. package/src/bin/nrepl.ts +244 -58
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj.d.ts +0 -123
  9. package/src/clojure/demo/math.clj +23 -6
  10. package/src/clojure/demo/math.clj.d.ts +2 -2
  11. package/src/clojure/demo.clj +22 -1
  12. package/src/core/assertions.ts +3 -0
  13. package/src/core/core-env.ts +2 -0
  14. package/src/core/env.ts +32 -12
  15. package/src/core/evaluator/evaluate.ts +14 -4
  16. package/src/core/evaluator/special-forms.ts +57 -2
  17. package/src/core/factories.ts +4 -0
  18. package/src/core/index.ts +8 -1
  19. package/src/core/printer.ts +2 -0
  20. package/src/core/reader.ts +16 -0
  21. package/src/core/session.ts +71 -37
  22. package/src/core/stdlib/meta.ts +1 -0
  23. package/src/core/stdlib/utils.ts +1 -0
  24. package/src/core/stdlib/vars.ts +53 -0
  25. package/src/core/tokenizer.ts +4 -0
  26. package/src/core/types.ts +25 -5
  27. package/src/host/browser.ts +12 -0
  28. package/src/host/node.ts +55 -0
  29. package/src/vite-plugin-clj/clj.d.ts +8 -0
  30. package/src/vite-plugin-clj/codegen.ts +201 -0
  31. package/src/vite-plugin-clj/index.ts +190 -0
  32. package/src/vite-plugin-clj/namespace-utils.ts +79 -0
  33. package/README.md +0 -259
  34. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  35. package/dist/assets/editor.worker-CdQrwHl8.js +0 -26
  36. package/dist/assets/main-A7ZMId9A.css +0 -1
  37. package/dist/assets/main-CmI-7epE.js +0 -3137
  38. package/dist/index.html +0 -195
  39. package/dist/vite.svg +0 -1
  40. package/src/main.ts +0 -1
  41. package/src/monaco-esm.d.ts +0 -7
  42. package/src/playground/clojure-tokens.ts +0 -67
  43. package/src/playground/editor.worker.ts +0 -5
  44. package/src/playground/find-form.ts +0 -138
  45. package/src/playground/playground.ts +0 -342
  46. package/src/playground/samples/00-welcome.clj +0 -385
  47. package/src/playground/samples/01-collections.clj +0 -191
  48. package/src/playground/samples/02-higher-order-functions.clj +0 -215
  49. package/src/playground/samples/03-destructuring.clj +0 -194
  50. package/src/playground/samples/04-strings-and-regex.clj +0 -202
  51. package/src/playground/samples/05-error-handling.clj +0 -212
  52. package/src/repl/repl.ts +0 -116
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // src/bin/cli.ts
3
- import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
3
+ import { existsSync as existsSync4, readFileSync as readFileSync4, realpathSync } from "node:fs";
4
4
  import { dirname as dirname2, resolve as resolve3 } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
@@ -24,7 +24,8 @@ var valueKeywords = {
24
24
  atom: "atom",
25
25
  reduced: "reduced",
26
26
  volatile: "volatile",
27
- regex: "regex"
27
+ regex: "regex",
28
+ var: "var"
28
29
  };
29
30
  var tokenKeywords = {
30
31
  LParen: "LParen",
@@ -45,7 +46,8 @@ var tokenKeywords = {
45
46
  Symbol: "Symbol",
46
47
  AnonFnStart: "AnonFnStart",
47
48
  Deref: "Deref",
48
- Regex: "Regex"
49
+ Regex: "Regex",
50
+ VarQuote: "VarQuote"
49
51
  };
50
52
  var tokenSymbols = {
51
53
  Quote: "quote",
@@ -108,6 +110,12 @@ class EnvError extends Error {
108
110
  this.name = "EnvError";
109
111
  }
110
112
  }
113
+ function derefValue(val) {
114
+ return val.kind === "var" ? val.value : val;
115
+ }
116
+ function makeNamespace(name) {
117
+ return { name, vars: new Map, aliases: new Map, readerAliases: new Map };
118
+ }
111
119
  function makeEnv(outer) {
112
120
  return {
113
121
  bindings: new Map,
@@ -117,9 +125,12 @@ function makeEnv(outer) {
117
125
  function lookup(name, env) {
118
126
  let current = env;
119
127
  while (current) {
120
- if (current.bindings.has(name)) {
121
- return current.bindings.get(name);
122
- }
128
+ const raw = current.bindings.get(name);
129
+ if (raw !== undefined)
130
+ return derefValue(raw);
131
+ const v = current.ns?.vars.get(name);
132
+ if (v !== undefined)
133
+ return v.value;
123
134
  current = current.outer;
124
135
  }
125
136
  throw new EvaluationError(`Symbol ${name} not found`, { name });
@@ -127,9 +138,25 @@ function lookup(name, env) {
127
138
  function tryLookup(name, env) {
128
139
  let current = env;
129
140
  while (current) {
130
- if (current.bindings.has(name)) {
131
- return current.bindings.get(name);
132
- }
141
+ const raw = current.bindings.get(name);
142
+ if (raw !== undefined)
143
+ return derefValue(raw);
144
+ const v = current.ns?.vars.get(name);
145
+ if (v !== undefined)
146
+ return v.value;
147
+ current = current.outer;
148
+ }
149
+ return;
150
+ }
151
+ function lookupVar(name, env) {
152
+ let current = env;
153
+ while (current) {
154
+ const raw = current.bindings.get(name);
155
+ if (raw !== undefined && raw.kind === "var")
156
+ return raw;
157
+ const v = current.ns?.vars.get(name);
158
+ if (v !== undefined)
159
+ return v;
133
160
  current = current.outer;
134
161
  }
135
162
  return;
@@ -161,7 +188,7 @@ function getRootEnv(env) {
161
188
  function getNamespaceEnv(env) {
162
189
  let current = env;
163
190
  while (current) {
164
- if (current.namespace)
191
+ if (current.ns)
165
192
  return current;
166
193
  current = current.outer;
167
194
  }
@@ -204,6 +231,7 @@ var cljRegex = (pattern, flags = "") => ({
204
231
  pattern,
205
232
  flags
206
233
  });
234
+ var cljVar = (ns, name, value, meta) => ({ kind: "var", ns, name, value, meta });
207
235
  var cljAtom = (value) => ({ kind: "atom", value });
208
236
  var cljReduced = (value) => ({
209
237
  kind: "reduced",
@@ -651,7 +679,8 @@ var specialFormKeywords = {
651
679
  recur: "recur",
652
680
  defmulti: "defmulti",
653
681
  defmethod: "defmethod",
654
- try: "try"
682
+ try: "try",
683
+ var: "var"
655
684
  };
656
685
  function keywordToDispatchFn(kw) {
657
686
  return cljNativeFunction(`kw:${kw.name}`, (...args) => {
@@ -777,7 +806,15 @@ function evaluateDef(list, env, ctx) {
777
806
  }
778
807
  if (list.value[2] === undefined)
779
808
  return cljNil();
780
- define(name.name, ctx.evaluate(list.value[2], env), getNamespaceEnv(env));
809
+ const nsEnv = getNamespaceEnv(env);
810
+ const cljNs = nsEnv.ns;
811
+ const newValue = ctx.evaluate(list.value[2], env);
812
+ const existing = cljNs.vars.get(name.name);
813
+ if (existing) {
814
+ existing.value = newValue;
815
+ } else {
816
+ cljNs.vars.set(name.name, cljVar(cljNs.name, name.name, newValue));
817
+ }
781
818
  return cljNil();
782
819
  }
783
820
  var evaluateNs = (_list, _env, _ctx) => {
@@ -939,6 +976,38 @@ function evaluateDefmethod(list, env, ctx) {
939
976
  define(mmName.name, updated, getNamespaceEnv(env));
940
977
  return cljNil();
941
978
  }
979
+ function evaluateVar(list, env, _ctx) {
980
+ const sym = list.value[1];
981
+ if (!isSymbol(sym)) {
982
+ throw new EvaluationError("var expects a symbol", { list });
983
+ }
984
+ const slashIdx = sym.name.indexOf("/");
985
+ if (slashIdx > 0 && slashIdx < sym.name.length - 1) {
986
+ const alias = sym.name.slice(0, slashIdx);
987
+ const localName = sym.name.slice(slashIdx + 1);
988
+ const nsEnv = getNamespaceEnv(env);
989
+ const aliasCljNs = nsEnv.ns?.aliases.get(alias);
990
+ if (aliasCljNs) {
991
+ const v3 = aliasCljNs.vars.get(localName);
992
+ if (!v3)
993
+ throw new EvaluationError(`Var ${sym.name} not found`, { sym });
994
+ return v3;
995
+ }
996
+ const targetEnv = getRootEnv(env).resolveNs?.(alias) ?? null;
997
+ if (!targetEnv) {
998
+ throw new EvaluationError(`No such namespace: ${alias}`, { sym });
999
+ }
1000
+ const v2 = lookupVar(localName, targetEnv);
1001
+ if (!v2)
1002
+ throw new EvaluationError(`Var ${sym.name} not found`, { sym });
1003
+ return v2;
1004
+ }
1005
+ const v = lookupVar(sym.name, env);
1006
+ if (!v) {
1007
+ throw new EvaluationError(`Unable to resolve var: ${sym.name} in this context`, { sym });
1008
+ }
1009
+ return v;
1010
+ }
942
1011
  var specialFormEvaluatorEntries = {
943
1012
  try: evaluateTry,
944
1013
  quote: evaluateQuote,
@@ -953,7 +1022,8 @@ var specialFormEvaluatorEntries = {
953
1022
  loop: evaluateLoop,
954
1023
  recur: evaluateRecur,
955
1024
  defmulti: evaluateDefmulti,
956
- defmethod: evaluateDefmethod
1025
+ defmethod: evaluateDefmethod,
1026
+ var: evaluateVar
957
1027
  };
958
1028
  function evaluateSpecialForm(symbol, list, env, ctx) {
959
1029
  const evalFn = specialFormEvaluatorEntries[symbol];
@@ -995,6 +1065,7 @@ var isAtom = (value) => value.kind === "atom";
995
1065
  var isReduced = (value) => value.kind === "reduced";
996
1066
  var isVolatile = (value) => value.kind === "volatile";
997
1067
  var isRegex = (value) => value.kind === "regex";
1068
+ var isVar = (value) => value.kind === "var";
998
1069
  var isCollection = (value) => isVector(value) || isMap(value) || isList(value);
999
1070
  var isSeqable = (value) => isCollection(value) || value.kind === "string";
1000
1071
  var equalityHandlers = {
@@ -1036,7 +1107,8 @@ var equalityHandlers = {
1036
1107
  [valueKeywords.atom]: (a, b) => a === b,
1037
1108
  [valueKeywords.reduced]: (a, b) => isEqual(a.value, b.value),
1038
1109
  [valueKeywords.volatile]: (a, b) => a === b,
1039
- [valueKeywords.regex]: (a, b) => a === b
1110
+ [valueKeywords.regex]: (a, b) => a === b,
1111
+ [valueKeywords.var]: (a, b) => a === b
1040
1112
  };
1041
1113
  var isEqual = (a, b) => {
1042
1114
  if (a.kind !== b.kind)
@@ -1118,6 +1190,8 @@ function printString(value) {
1118
1190
  const prefix = value.flags ? `(?${value.flags})` : "";
1119
1191
  return `#"${prefix}${escaped}"`;
1120
1192
  }
1193
+ case valueKeywords.var:
1194
+ return `#'${value.ns}/${value.name}`;
1121
1195
  default:
1122
1196
  throw new EvaluationError(`unhandled value type: ${value.kind}`, {
1123
1197
  value
@@ -2041,6 +2115,8 @@ var metaFunctions = {
2041
2115
  if (val.kind === "function" || val.kind === "native-function") {
2042
2116
  return val.meta ?? cljNil();
2043
2117
  }
2118
+ if (val.kind === "var")
2119
+ return val.meta ?? cljNil();
2044
2120
  return cljNil();
2045
2121
  }), "Returns the metadata map of a value, or nil if the value has no metadata.", [["val"]]),
2046
2122
  "with-meta": withDoc(cljNativeFunction("with-meta", (val, m) => {
@@ -2257,7 +2333,18 @@ function evaluateWithContext(expr, env, ctx) {
2257
2333
  const alias = expr.name.slice(0, slashIdx);
2258
2334
  const sym = expr.name.slice(slashIdx + 1);
2259
2335
  const nsEnv = getNamespaceEnv(env);
2260
- const targetEnv = nsEnv.aliases?.get(alias) ?? getRootEnv(env).resolveNs?.(alias) ?? null;
2336
+ const aliasCljNs = nsEnv.ns?.aliases.get(alias);
2337
+ if (aliasCljNs) {
2338
+ const v = aliasCljNs.vars.get(sym);
2339
+ if (v === undefined) {
2340
+ throw new EvaluationError(`Symbol ${expr.name} not found`, {
2341
+ symbol: expr.name,
2342
+ env
2343
+ });
2344
+ }
2345
+ return v.value;
2346
+ }
2347
+ const targetEnv = getRootEnv(env).resolveNs?.(alias) ?? null;
2261
2348
  if (!targetEnv) {
2262
2349
  throw new EvaluationError(`No such namespace or alias: ${alias}`, {
2263
2350
  symbol: expr.name,
@@ -2819,6 +2906,7 @@ var utilFunctions = {
2819
2906
  map: ":map",
2820
2907
  function: ":function",
2821
2908
  regex: ":regex",
2909
+ var: ":var",
2822
2910
  "native-function": ":function"
2823
2911
  };
2824
2912
  const name = kindToKeyword[x.kind];
@@ -2951,6 +3039,28 @@ var utilFunctions = {
2951
3039
  }), "Coerces to boolean. Everything is true except false and nil.", [["x"]])
2952
3040
  };
2953
3041
 
3042
+ // src/core/stdlib/vars.ts
3043
+ var varFunctions = {
3044
+ "var?": withDoc(cljNativeFunction("var?", (x) => cljBoolean(isVar(x))), "Returns true if x is a Var.", [["x"]]),
3045
+ "var-get": withDoc(cljNativeFunction("var-get", (x) => {
3046
+ if (!isVar(x)) {
3047
+ throw new EvaluationError(`var-get expects a Var, got ${x.kind}`, { x });
3048
+ }
3049
+ return x.value;
3050
+ }), "Returns the value in the Var object.", [["x"]]),
3051
+ "alter-var-root": withDoc(cljNativeFunctionWithContext("alter-var-root", (ctx, callEnv, varVal, f, ...args) => {
3052
+ if (!isVar(varVal)) {
3053
+ throw new EvaluationError(`alter-var-root expects a Var as its first argument, got ${varVal.kind}`, { varVal });
3054
+ }
3055
+ if (!isAFunction(f)) {
3056
+ throw new EvaluationError(`alter-var-root expects a function as its second argument, got ${f.kind}`, { f });
3057
+ }
3058
+ const newVal = ctx.applyFunction(f, [varVal.value, ...args], callEnv);
3059
+ varVal.value = newVal;
3060
+ return newVal;
3061
+ }), "Atomically alters the root binding of var v by applying f to its current value plus any additional args.", [["v", "f", "&", "args"]])
3062
+ };
3063
+
2954
3064
  // src/core/core-env.ts
2955
3065
  var nativeFunctions = {
2956
3066
  ...arithmeticFunctions,
@@ -2963,7 +3073,8 @@ var nativeFunctions = {
2963
3073
  ...transducerFunctions,
2964
3074
  ...regexFunctions,
2965
3075
  ...stringFunctions,
2966
- ...utilFunctions
3076
+ ...utilFunctions,
3077
+ ...varFunctions
2967
3078
  };
2968
3079
  function loadCoreFunctions(env, output) {
2969
3080
  for (const [key, value] of Object.entries(nativeFunctions)) {
@@ -3287,6 +3398,10 @@ function parseDispatch(ctx) {
3287
3398
  if (next === '"') {
3288
3399
  return parseRegexLiteral(ctx, start);
3289
3400
  }
3401
+ if (next === "'") {
3402
+ scanner.advance();
3403
+ return { kind: tokenKeywords.VarQuote, start, end: scanner.position() };
3404
+ }
3290
3405
  if (next === "{") {
3291
3406
  throw new TokenizerError("Set literals are not yet supported", start);
3292
3407
  }
@@ -3497,6 +3612,16 @@ var readUnquote = (ctx) => {
3497
3612
  }
3498
3613
  return { kind: valueKeywords.list, value: [cljSymbol("unquote"), value] };
3499
3614
  };
3615
+ var readVarQuote = (ctx) => {
3616
+ const scanner = ctx.scanner;
3617
+ const token = scanner.peek();
3618
+ if (!token) {
3619
+ throw new ReaderError("Unexpected end of input while parsing var quote", scanner.position());
3620
+ }
3621
+ scanner.advance();
3622
+ const value = readForm(ctx);
3623
+ return cljList([cljSymbol("var"), value]);
3624
+ };
3500
3625
  var readDeref = (ctx) => {
3501
3626
  const scanner = ctx.scanner;
3502
3627
  const token = scanner.peek();
@@ -3818,6 +3943,8 @@ function readForm(ctx) {
3818
3943
  return readAnonFn(ctx);
3819
3944
  case tokenKeywords.Deref:
3820
3945
  return readDeref(ctx);
3946
+ case tokenKeywords.VarQuote:
3947
+ return readVarQuote(ctx);
3821
3948
  case tokenKeywords.Regex:
3822
3949
  return readRegex(ctx);
3823
3950
  default:
@@ -4764,10 +4891,7 @@ function processRequireSpec(spec, currentEnv, registry, resolveNamespace) {
4764
4891
  position: i2
4765
4892
  });
4766
4893
  }
4767
- if (!currentEnv.readerAliases) {
4768
- currentEnv.readerAliases = new Map;
4769
- }
4770
- currentEnv.readerAliases.set(alias.name, nsName);
4894
+ currentEnv.ns.readerAliases.set(alias.name, nsName);
4771
4895
  i2++;
4772
4896
  } else {
4773
4897
  throw new EvaluationError(`:as-alias specs only support :as-alias, got ${kw.name}`, { spec });
@@ -4798,10 +4922,7 @@ function processRequireSpec(spec, currentEnv, registry, resolveNamespace) {
4798
4922
  position: i
4799
4923
  });
4800
4924
  }
4801
- if (!currentEnv.aliases) {
4802
- currentEnv.aliases = new Map;
4803
- }
4804
- currentEnv.aliases.set(alias.name, targetEnv);
4925
+ currentEnv.ns.aliases.set(alias.name, targetEnv.ns);
4805
4926
  i++;
4806
4927
  } else if (kw.name === ":refer") {
4807
4928
  i++;
@@ -4819,13 +4940,18 @@ function processRequireSpec(spec, currentEnv, registry, resolveNamespace) {
4819
4940
  sym
4820
4941
  });
4821
4942
  }
4822
- let value;
4823
- try {
4824
- value = lookup(sym.name, targetEnv);
4825
- } catch {
4826
- throw new EvaluationError(`Symbol ${sym.name} not found in namespace ${nsName}`, { nsName, symbol: sym.name });
4943
+ const v = lookupVar(sym.name, targetEnv);
4944
+ if (v !== undefined) {
4945
+ currentEnv.ns.vars.set(sym.name, v);
4946
+ } else {
4947
+ let value;
4948
+ try {
4949
+ value = lookup(sym.name, targetEnv);
4950
+ } catch {
4951
+ throw new EvaluationError(`Symbol ${sym.name} not found in namespace ${nsName}`, { nsName, symbol: sym.name });
4952
+ }
4953
+ define(sym.name, value, currentEnv);
4827
4954
  }
4828
- define(sym.name, value, currentEnv);
4829
4955
  }
4830
4956
  i++;
4831
4957
  } else {
@@ -4833,21 +4959,31 @@ function processRequireSpec(spec, currentEnv, registry, resolveNamespace) {
4833
4959
  }
4834
4960
  }
4835
4961
  }
4962
+ function cloneBindings(bindings) {
4963
+ const out = new Map;
4964
+ for (const [k, v] of bindings) {
4965
+ out.set(k, v.kind === "var" ? { ...v } : v);
4966
+ }
4967
+ return out;
4968
+ }
4836
4969
  function cloneEnv(env, memo) {
4837
4970
  if (memo.has(env))
4838
4971
  return memo.get(env);
4839
4972
  const cloned = {
4840
- bindings: new Map(env.bindings),
4841
- outer: null,
4842
- namespace: env.namespace
4973
+ bindings: cloneBindings(env.bindings),
4974
+ outer: null
4843
4975
  };
4976
+ if (env.ns) {
4977
+ cloned.ns = {
4978
+ name: env.ns.name,
4979
+ vars: new Map([...env.ns.vars].map(([k, v]) => [k, { ...v }])),
4980
+ aliases: new Map,
4981
+ readerAliases: new Map(env.ns.readerAliases)
4982
+ };
4983
+ }
4844
4984
  memo.set(env, cloned);
4845
4985
  if (env.outer)
4846
4986
  cloned.outer = cloneEnv(env.outer, memo);
4847
- if (env.aliases)
4848
- cloned.aliases = new Map([...env.aliases].map(([k, v]) => [k, cloneEnv(v, memo)]));
4849
- if (env.readerAliases)
4850
- cloned.readerAliases = new Map(env.readerAliases);
4851
4987
  return cloned;
4852
4988
  }
4853
4989
  function cloneRegistry(registry) {
@@ -4855,6 +4991,16 @@ function cloneRegistry(registry) {
4855
4991
  const next = new Map;
4856
4992
  for (const [name, env] of registry)
4857
4993
  next.set(name, cloneEnv(env, memo));
4994
+ for (const [name, env] of registry) {
4995
+ const clonedEnv = next.get(name);
4996
+ if (env.ns && clonedEnv.ns) {
4997
+ for (const [alias, origNs] of env.ns.aliases) {
4998
+ const targetCloned = next.get(origNs.name);
4999
+ if (targetCloned?.ns)
5000
+ clonedEnv.ns.aliases.set(alias, targetCloned.ns);
5001
+ }
5002
+ }
5003
+ }
4858
5004
  return next;
4859
5005
  }
4860
5006
  function buildSessionApi(state, options) {
@@ -4902,7 +5048,7 @@ function buildSessionApi(state, options) {
4902
5048
  function ensureNs(name) {
4903
5049
  if (!registry.has(name)) {
4904
5050
  const nsEnv = makeEnv(coreEnv);
4905
- nsEnv.namespace = name;
5051
+ nsEnv.ns = makeNamespace(name);
4906
5052
  registry.set(name, nsEnv);
4907
5053
  }
4908
5054
  return registry.get(name);
@@ -4912,6 +5058,9 @@ function buildSessionApi(state, options) {
4912
5058
  currentNs = name;
4913
5059
  }
4914
5060
  function getNs(name) {
5061
+ return registry.get(name)?.ns ?? null;
5062
+ }
5063
+ function getNsEnv(name) {
4915
5064
  return registry.get(name) ?? null;
4916
5065
  }
4917
5066
  define("require", cljNativeFunction("require", (...args) => {
@@ -4969,13 +5118,17 @@ function buildSessionApi(state, options) {
4969
5118
  evaluate(source) {
4970
5119
  try {
4971
5120
  const tokens = tokenize(source);
4972
- const env = getNs(currentNs);
5121
+ const declaredNs = extractNsNameFromTokens(tokens);
5122
+ if (declaredNs) {
5123
+ ensureNs(declaredNs);
5124
+ currentNs = declaredNs;
5125
+ }
5126
+ const env = getNsEnv(currentNs);
4973
5127
  const aliasMap = extractAliasMapFromTokens(tokens);
4974
- env.aliases?.forEach((nsEnv, alias) => {
4975
- if (nsEnv.namespace)
4976
- aliasMap.set(alias, nsEnv.namespace);
5128
+ env.ns?.aliases.forEach((ns, alias) => {
5129
+ aliasMap.set(alias, ns.name);
4977
5130
  });
4978
- env.readerAliases?.forEach((nsName, alias) => {
5131
+ env.ns?.readerAliases.forEach((nsName, alias) => {
4979
5132
  aliasMap.set(alias, nsName);
4980
5133
  });
4981
5134
  const forms = readForms(tokens, currentNs, aliasMap);
@@ -5003,7 +5156,7 @@ function buildSessionApi(state, options) {
5003
5156
  },
5004
5157
  evaluateForms(forms) {
5005
5158
  try {
5006
- const env = getNs(currentNs);
5159
+ const env = getNsEnv(currentNs);
5007
5160
  let result = cljNil();
5008
5161
  for (const form of forms) {
5009
5162
  const expanded = ctx.expandAll(form, env);
@@ -5028,6 +5181,9 @@ function buildSessionApi(state, options) {
5028
5181
  while (env) {
5029
5182
  for (const key of env.bindings.keys())
5030
5183
  seen.add(key);
5184
+ if (env.ns)
5185
+ for (const key of env.ns.vars.keys())
5186
+ seen.add(key);
5031
5187
  env = env.outer;
5032
5188
  }
5033
5189
  const candidates = [...seen];
@@ -5041,11 +5197,11 @@ function buildSessionApi(state, options) {
5041
5197
  function createSession(options) {
5042
5198
  const registry = new Map;
5043
5199
  const coreEnv = makeEnv();
5044
- coreEnv.namespace = "clojure.core";
5200
+ coreEnv.ns = makeNamespace("clojure.core");
5045
5201
  loadCoreFunctions(coreEnv, options?.output);
5046
5202
  registry.set("clojure.core", coreEnv);
5047
5203
  const userEnv = makeEnv(coreEnv);
5048
- userEnv.namespace = "user";
5204
+ userEnv.ns = makeNamespace("user");
5049
5205
  registry.set("user", userEnv);
5050
5206
  const session = buildSessionApi({ registry, currentNs: "user" }, options);
5051
5207
  const coreLoader = builtInNamespaceSources["clojure.core"];
@@ -5121,8 +5277,8 @@ function discoverSourceRoots(startDir) {
5121
5277
 
5122
5278
  // src/bin/nrepl.ts
5123
5279
  import * as net from "net";
5124
- import { readFileSync as readFileSync2, writeFileSync, unlinkSync, existsSync as existsSync2 } from "node:fs";
5125
- import { join as join2, resolve as resolve2 } from "node:path";
5280
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3 } from "node:fs";
5281
+ import { join as join2 } from "node:path";
5126
5282
 
5127
5283
  // src/bin/bencode.ts
5128
5284
  import * as stream from "stream";
@@ -5275,15 +5431,13 @@ class BDecoderStream extends stream.Transform {
5275
5431
  }
5276
5432
 
5277
5433
  // src/bin/version.ts
5278
- var VERSION = "0.0.8";
5434
+ var VERSION = "0.0.10";
5279
5435
 
5280
- // src/bin/nrepl.ts
5281
- var CONJURE_VERSION = VERSION;
5282
- function makeSessionId() {
5283
- return crypto.randomUUID();
5284
- }
5285
- function injectHostFunctions(session) {
5286
- const coreEnv = session.getNs("clojure.core");
5436
+ // src/host/node.ts
5437
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
5438
+ import { resolve as resolve2 } from "node:path";
5439
+ function injectNodeHostFunctions(session) {
5440
+ const coreEnv = session.registry.get("clojure.core");
5287
5441
  define("slurp", cljNativeFunction("slurp", (pathVal) => {
5288
5442
  const filePath = resolve2(valueToString(pathVal));
5289
5443
  if (!existsSync2(filePath)) {
@@ -5310,15 +5464,22 @@ function injectHostFunctions(session) {
5310
5464
  return cljNil();
5311
5465
  }), coreEnv);
5312
5466
  }
5313
- function createManagedSession(id, snapshot, encoder) {
5467
+
5468
+ // src/bin/nrepl.ts
5469
+ var CONJURE_VERSION = VERSION;
5470
+ function makeSessionId() {
5471
+ return crypto.randomUUID();
5472
+ }
5473
+ function createManagedSession(id, snapshot, encoder, sourceRoots) {
5314
5474
  let currentMsgId = "";
5315
5475
  const session = createSessionFromSnapshot(snapshot, {
5316
5476
  output: (text) => {
5317
5477
  send(encoder, { id: currentMsgId, session: id, out: text });
5318
5478
  },
5319
- readFile: (filePath) => readFileSync2(filePath, "utf8")
5479
+ readFile: (filePath) => readFileSync3(filePath, "utf8"),
5480
+ sourceRoots
5320
5481
  });
5321
- injectHostFunctions(session);
5482
+ injectNodeHostFunctions(session);
5322
5483
  return {
5323
5484
  id,
5324
5485
  session,
@@ -5327,7 +5488,8 @@ function createManagedSession(id, snapshot, encoder) {
5327
5488
  },
5328
5489
  set currentMsgId(v) {
5329
5490
  currentMsgId = v;
5330
- }
5491
+ },
5492
+ nsToFile: new Map
5331
5493
  };
5332
5494
  }
5333
5495
  function send(encoder, msg) {
@@ -5341,10 +5503,10 @@ function done(encoder, id, sessionId, extra = {}) {
5341
5503
  ...extra
5342
5504
  });
5343
5505
  }
5344
- function handleClone(msg, sessions, snapshot, encoder) {
5506
+ function handleClone(msg, sessions, snapshot, encoder, sourceRoots) {
5345
5507
  const id = msg["id"] ?? "";
5346
5508
  const newId = makeSessionId();
5347
- const managed = createManagedSession(newId, snapshot, encoder);
5509
+ const managed = createManagedSession(newId, snapshot, encoder, sourceRoots);
5348
5510
  sessions.set(newId, managed);
5349
5511
  done(encoder, id, undefined, { "new-session": newId });
5350
5512
  }
@@ -5358,6 +5520,9 @@ function handleDescribe(msg, encoder) {
5358
5520
  close: {},
5359
5521
  complete: {},
5360
5522
  describe: {},
5523
+ eldoc: {},
5524
+ info: {},
5525
+ lookup: {},
5361
5526
  "load-file": {}
5362
5527
  },
5363
5528
  versions: {
@@ -5401,6 +5566,9 @@ function handleLoadFile(msg, managed, encoder) {
5401
5566
  }
5402
5567
  const nsHint = fileName.replace(/\.clj$/, "").replace(/\//g, ".") || undefined;
5403
5568
  const loadedNs = managed.session.loadFile(source, nsHint);
5569
+ if (filePath && loadedNs) {
5570
+ managed.nsToFile.set(loadedNs, filePath);
5571
+ }
5404
5572
  managed.session.setNs(loadedNs);
5405
5573
  done(encoder, id, managed.id, {
5406
5574
  value: "nil",
@@ -5432,17 +5600,159 @@ function handleClose(msg, sessions, encoder) {
5432
5600
  sessions.delete(sessionId);
5433
5601
  send(encoder, { id, session: sessionId, status: ["done"] });
5434
5602
  }
5603
+ function resolveSymbol(sym, managed, contextNs) {
5604
+ const ns = contextNs ?? managed.session.currentNs;
5605
+ const slashIdx = sym.indexOf("/");
5606
+ if (slashIdx > 0) {
5607
+ const qualifier = sym.slice(0, slashIdx);
5608
+ const localName2 = sym.slice(slashIdx + 1);
5609
+ const nsEnvFull2 = managed.session.registry.get(qualifier);
5610
+ if (nsEnvFull2) {
5611
+ const value2 = tryLookup(localName2, nsEnvFull2);
5612
+ if (value2 !== undefined)
5613
+ return { value: value2, resolvedNs: qualifier, localName: localName2 };
5614
+ }
5615
+ const currentNsData = managed.session.getNs(ns);
5616
+ const aliasedNs = currentNsData?.aliases.get(qualifier);
5617
+ if (aliasedNs) {
5618
+ const v = aliasedNs.vars.get(localName2);
5619
+ if (v !== undefined)
5620
+ return { value: v.value, resolvedNs: aliasedNs.name, localName: localName2 };
5621
+ }
5622
+ return null;
5623
+ }
5624
+ const localName = sym;
5625
+ const nsEnvFull = managed.session.registry.get(ns);
5626
+ if (!nsEnvFull)
5627
+ return null;
5628
+ const value = tryLookup(sym, nsEnvFull);
5629
+ if (value === undefined)
5630
+ return null;
5631
+ const varObj = lookupVar(sym, nsEnvFull);
5632
+ let resolvedNs;
5633
+ if (varObj) {
5634
+ resolvedNs = varObj.ns;
5635
+ } else if (value.kind === "function" || value.kind === "macro") {
5636
+ resolvedNs = getNamespaceEnv(value.env).ns?.name ?? ns;
5637
+ } else if (value.kind === "native-function") {
5638
+ const i = value.name.indexOf("/");
5639
+ resolvedNs = i > 0 ? value.name.slice(0, i) : ns;
5640
+ } else {
5641
+ resolvedNs = ns;
5642
+ }
5643
+ return { value, resolvedNs, localName };
5644
+ }
5645
+ function extractMeta(value) {
5646
+ const type = value.kind === "macro" ? "macro" : value.kind === "function" || value.kind === "native-function" ? "function" : "var";
5647
+ const meta = value.kind === "function" ? value.meta : value.kind === "native-function" ? value.meta : undefined;
5648
+ let doc = "";
5649
+ let arglistsStr = "";
5650
+ let eldocArgs = null;
5651
+ if (meta) {
5652
+ const docEntry = meta.entries.find(([k]) => k.kind === "keyword" && k.name === ":doc");
5653
+ if (docEntry && docEntry[1].kind === "string")
5654
+ doc = docEntry[1].value;
5655
+ const argsEntry = meta.entries.find(([k]) => k.kind === "keyword" && k.name === ":arglists");
5656
+ if (argsEntry && argsEntry[1].kind === "vector") {
5657
+ const arglists = argsEntry[1];
5658
+ arglistsStr = "(" + arglists.value.map((al) => printString(al)).join(" ") + ")";
5659
+ eldocArgs = arglists.value.map((al) => {
5660
+ if (al.kind !== "vector")
5661
+ return [printString(al)];
5662
+ return al.value.map((p) => p.kind === "symbol" ? p.name : printString(p));
5663
+ });
5664
+ }
5665
+ }
5666
+ if (arglistsStr === "" && (value.kind === "function" || value.kind === "macro")) {
5667
+ const arityStrs = value.arities.map((arity) => {
5668
+ const params = arity.params.map((p) => printString(p));
5669
+ if (arity.restParam)
5670
+ params.push("&", printString(arity.restParam));
5671
+ return "[" + params.join(" ") + "]";
5672
+ });
5673
+ arglistsStr = "(" + arityStrs.join(" ") + ")";
5674
+ eldocArgs = value.arities.map((arity) => {
5675
+ const params = arity.params.map((p) => printString(p));
5676
+ if (arity.restParam)
5677
+ params.push("&", printString(arity.restParam));
5678
+ return params;
5679
+ });
5680
+ }
5681
+ return { doc, arglistsStr, eldocArgs, type };
5682
+ }
5683
+ function handleInfo(msg, managed, encoder) {
5684
+ const id = msg["id"] ?? "";
5685
+ const sym = msg["sym"];
5686
+ const nsOverride = msg["ns"];
5687
+ if (!sym) {
5688
+ done(encoder, id, managed.id, { status: ["no-info", "done"] });
5689
+ return;
5690
+ }
5691
+ const resolved = resolveSymbol(sym, managed, nsOverride);
5692
+ if (!resolved) {
5693
+ const nsFile = managed.nsToFile.get(sym);
5694
+ if (nsFile) {
5695
+ done(encoder, id, managed.id, {
5696
+ ns: sym,
5697
+ name: sym,
5698
+ type: "namespace",
5699
+ file: nsFile
5700
+ });
5701
+ return;
5702
+ }
5703
+ done(encoder, id, managed.id, { status: ["no-info", "done"] });
5704
+ return;
5705
+ }
5706
+ const meta = extractMeta(resolved.value);
5707
+ const file = managed.nsToFile.get(resolved.resolvedNs);
5708
+ done(encoder, id, managed.id, {
5709
+ ns: resolved.resolvedNs,
5710
+ name: resolved.localName,
5711
+ doc: meta.doc,
5712
+ "arglists-str": meta.arglistsStr,
5713
+ type: meta.type,
5714
+ ...file ? { file } : {}
5715
+ });
5716
+ }
5717
+ function handleLookup(msg, managed, encoder) {
5718
+ handleInfo(msg, managed, encoder);
5719
+ }
5720
+ function handleEldoc(msg, managed, encoder) {
5721
+ const id = msg["id"] ?? "";
5722
+ const sym = msg["sym"];
5723
+ const nsOverride = msg["ns"];
5724
+ if (!sym) {
5725
+ done(encoder, id, managed.id, { status: ["no-eldoc", "done"] });
5726
+ return;
5727
+ }
5728
+ const resolved = resolveSymbol(sym, managed, nsOverride);
5729
+ if (!resolved) {
5730
+ done(encoder, id, managed.id, { status: ["no-eldoc", "done"] });
5731
+ return;
5732
+ }
5733
+ const meta = extractMeta(resolved.value);
5734
+ if (!meta.eldocArgs) {
5735
+ done(encoder, id, managed.id, { status: ["no-eldoc", "done"] });
5736
+ return;
5737
+ }
5738
+ done(encoder, id, managed.id, {
5739
+ name: resolved.localName,
5740
+ ns: resolved.resolvedNs,
5741
+ type: meta.type,
5742
+ eldoc: meta.eldocArgs
5743
+ });
5744
+ }
5435
5745
  function handleUnknown(msg, encoder) {
5436
5746
  const id = msg["id"] ?? "";
5437
5747
  send(encoder, { id, status: ["unknown-op", "done"] });
5438
5748
  }
5439
- function handleMessage(msg, sessions, snapshot, encoder, defaultSession) {
5749
+ function handleMessage(msg, sessions, snapshot, encoder, defaultSession, sourceRoots) {
5440
5750
  const op = msg["op"];
5441
5751
  const sessionId = msg["session"];
5442
5752
  const managed = sessionId ? sessions.get(sessionId) ?? defaultSession : defaultSession;
5443
5753
  switch (op) {
5444
5754
  case "clone":
5445
- handleClone(msg, sessions, snapshot, encoder);
5755
+ handleClone(msg, sessions, snapshot, encoder, sourceRoots);
5446
5756
  break;
5447
5757
  case "describe":
5448
5758
  handleDescribe(msg, encoder);
@@ -5459,6 +5769,15 @@ function handleMessage(msg, sessions, snapshot, encoder, defaultSession) {
5459
5769
  case "close":
5460
5770
  handleClose(msg, sessions, encoder);
5461
5771
  break;
5772
+ case "info":
5773
+ handleInfo(msg, managed, encoder);
5774
+ break;
5775
+ case "lookup":
5776
+ handleLookup(msg, managed, encoder);
5777
+ break;
5778
+ case "eldoc":
5779
+ handleEldoc(msg, managed, encoder);
5780
+ break;
5462
5781
  default:
5463
5782
  handleUnknown(msg, encoder);
5464
5783
  }
@@ -5468,7 +5787,7 @@ function startNreplServer(options = {}) {
5468
5787
  const host = options.host ?? "127.0.0.1";
5469
5788
  const warmSession = createSession({
5470
5789
  sourceRoots: options.sourceRoots,
5471
- readFile: (filePath) => readFileSync2(filePath, "utf8")
5790
+ readFile: (filePath) => readFileSync3(filePath, "utf8")
5472
5791
  });
5473
5792
  const snapshot = snapshotSession(warmSession);
5474
5793
  const server = net.createServer((socket) => {
@@ -5478,10 +5797,10 @@ function startNreplServer(options = {}) {
5478
5797
  socket.pipe(decoder);
5479
5798
  const sessions = new Map;
5480
5799
  const defaultId = makeSessionId();
5481
- const defaultSession = createManagedSession(defaultId, snapshot, encoder);
5800
+ const defaultSession = createManagedSession(defaultId, snapshot, encoder, options.sourceRoots);
5482
5801
  sessions.set(defaultId, defaultSession);
5483
5802
  decoder.on("data", (msg) => {
5484
- handleMessage(msg, sessions, snapshot, encoder, defaultSession);
5803
+ handleMessage(msg, sessions, snapshot, encoder, defaultSession, options.sourceRoots);
5485
5804
  });
5486
5805
  socket.on("error", () => {});
5487
5806
  socket.on("close", () => {
@@ -5490,14 +5809,15 @@ function startNreplServer(options = {}) {
5490
5809
  });
5491
5810
  const portFile = join2(process.cwd(), ".nrepl-port");
5492
5811
  server.listen(port, host, () => {
5493
- writeFileSync(portFile, String(port), "utf8");
5812
+ writeFileSync2(portFile, String(port), "utf8");
5494
5813
  process.stdout.write(`Conjure nREPL server v${VERSION} started on port ${port}
5495
5814
  `);
5496
5815
  });
5497
5816
  const cleanup = () => {
5498
- if (existsSync2(portFile))
5817
+ if (existsSync3(portFile))
5499
5818
  unlinkSync(portFile);
5500
5819
  };
5820
+ server.on("close", cleanup);
5501
5821
  process.on("exit", cleanup);
5502
5822
  process.on("SIGINT", () => {
5503
5823
  cleanup();
@@ -5519,41 +5839,13 @@ function makeCliIo() {
5519
5839
  `)
5520
5840
  };
5521
5841
  }
5522
- function injectHostFunctions2(session) {
5523
- const coreEnv = session.getNs("clojure.core");
5524
- define("slurp", cljNativeFunction("slurp", (pathVal) => {
5525
- const filePath = resolve3(valueToString(pathVal));
5526
- if (!existsSync3(filePath)) {
5527
- throw new Error(`slurp: file not found: ${filePath}`);
5528
- }
5529
- return cljString(readFileSync3(filePath, "utf8"));
5530
- }), coreEnv);
5531
- define("spit", cljNativeFunction("spit", (pathVal, content) => {
5532
- const filePath = resolve3(valueToString(pathVal));
5533
- writeFileSync2(filePath, valueToString(content), "utf8");
5534
- return cljNil();
5535
- }), coreEnv);
5536
- define("load", cljNativeFunction("load", (pathVal) => {
5537
- const filePath = resolve3(valueToString(pathVal));
5538
- if (!existsSync3(filePath)) {
5539
- throw new Error(`load: file not found: ${filePath}`);
5540
- }
5541
- const source = readFileSync3(filePath, "utf8");
5542
- const inferred = inferSourceRoot(filePath, source);
5543
- if (inferred)
5544
- session.addSourceRoot(inferred);
5545
- const loadedNs = session.loadFile(source);
5546
- session.setNs(loadedNs);
5547
- return cljNil();
5548
- }), coreEnv);
5549
- }
5550
5842
  function createCliSession(sourceRoots, io) {
5551
5843
  const session = createSession({
5552
5844
  output: (text) => io.writeLine(text),
5553
5845
  sourceRoots,
5554
- readFile: (filePath) => readFileSync3(filePath, "utf8")
5846
+ readFile: (filePath) => readFileSync4(filePath, "utf8")
5555
5847
  });
5556
- injectHostFunctions2(session);
5848
+ injectNodeHostFunctions(session);
5557
5849
  return session;
5558
5850
  }
5559
5851
  function getSourceRoots(filePath) {
@@ -5572,12 +5864,12 @@ function printUsage(io) {
5572
5864
  }
5573
5865
  function runFile(fileArg, io = makeCliIo()) {
5574
5866
  const filePath = resolve3(fileArg);
5575
- if (!existsSync3(filePath)) {
5867
+ if (!existsSync4(filePath)) {
5576
5868
  io.writeError(`File not found: ${fileArg}`);
5577
5869
  return 1;
5578
5870
  }
5579
5871
  try {
5580
- const source = readFileSync3(filePath, "utf8");
5872
+ const source = readFileSync4(filePath, "utf8");
5581
5873
  const inferredRoot = inferSourceRoot(filePath, source);
5582
5874
  const sourceRoots = inferredRoot ? [...new Set([inferredRoot, ...getSourceRoots(filePath)])] : getSourceRoots(filePath);
5583
5875
  const session = createCliSession(sourceRoots, io);