@mojir/lits 2.4.0 → 2.5.0

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/README.md CHANGED
@@ -41,17 +41,47 @@ npm install --global @mojir/lits
41
41
  $ lits
42
42
 
43
43
  # Evaluate Lits code directly
44
- $ lits -e "5 + 3"
45
- $ lits -e "[1, 2, 3, 4] filter odd? map inc"
44
+ $ lits eval "5 + 3"
45
+ $ lits eval "[1, 2, 3, 4] filter odd? map inc"
46
46
 
47
47
  # Run a Lits file
48
- $ lits -f script.lits
49
- $ lits -f examples/factorial.lits
48
+ $ lits run script.lits
49
+
50
+ # Bundle a multi-file project into a single .json file
51
+ $ lits bundle main.lits -o bundle.lits.json
52
+
53
+ # Run a bundle
54
+ $ lits run-bundle bundle.lits.json
55
+
56
+ # Run tests
57
+ $ lits test tests.test.lits
50
58
 
51
59
  # Get help
52
60
  $ lits --help
53
61
  ```
54
62
 
63
+ **Subcommands:**
64
+
65
+ | Subcommand | Description |
66
+ |---|---|
67
+ | `run <file>` | Run a `.lits` source file |
68
+ | `run-bundle <file>` | Run a `.json` bundle (with validation) |
69
+ | `eval <expression>` | Evaluate a Lits expression |
70
+ | `bundle <entry>` | Bundle a multi-file project into a single JSON file |
71
+ | `test <file>` | Run a `.test.lits` test file |
72
+ | `repl` | Start an interactive REPL (default when no subcommand) |
73
+
74
+ **Common options:**
75
+
76
+ | Option | Applies to | Description |
77
+ |---|---|---|
78
+ | `-c, --context=<json>` | `run`, `run-bundle`, `eval`, `repl` | Provide context as a JSON string |
79
+ | `-C, --context-file=<file>` | `run`, `run-bundle`, `eval`, `repl` | Provide context from a `.json` file |
80
+ | `-s, --silent` | `run`, `run-bundle`, `eval` | Suppress printing the result |
81
+ | `-o, --output=<file>` | `bundle` | Write bundle to file (default: stdout) |
82
+ | `--pattern=<regex>` | `test` | Only run tests matching pattern |
83
+ | `-l, --load=<file>` | `repl` | Preload a `.lits` file into the REPL |
84
+
55
85
  The REPL provides an interactive environment where you can experiment with Lits code, test functions, and explore the language features in real-time.
56
86
 
57
87
  ## Quick Start
package/dist/cli/cli.js CHANGED
@@ -7,7 +7,7 @@ var readline = require('node:readline');
7
7
  var os = require('node:os');
8
8
  var process$1 = require('node:process');
9
9
 
10
- var version = "2.4.0";
10
+ var version = "2.5.0";
11
11
 
12
12
  function getCodeMarker(sourceCodeInfo) {
13
13
  if (!sourceCodeInfo.position || !sourceCodeInfo.code)
@@ -3677,6 +3677,7 @@ const miscNormalExpression = {
3677
3677
  return asAny(params[params.length - 1], sourceCodeInfo);
3678
3678
  return null;
3679
3679
  },
3680
+ pure: false,
3680
3681
  arity: {},
3681
3682
  docs: {
3682
3683
  category: 'misc',
@@ -6757,7 +6758,10 @@ function isNumberReservedSymbol(symbol) {
6757
6758
  }
6758
6759
 
6759
6760
  const functionExecutors = {
6760
- NativeJsFunction: (fn, params, sourceCodeInfo) => {
6761
+ NativeJsFunction: (fn, params, sourceCodeInfo, contextStack) => {
6762
+ if (contextStack.pure && !fn.nativeFn.pure) {
6763
+ throw new LitsError(`Cannot call impure native function '${fn.name}' in pure mode`, sourceCodeInfo);
6764
+ }
6761
6765
  try {
6762
6766
  const result = fn.nativeFn.fn(...params);
6763
6767
  // If the native function returns a Promise, await it transparently
@@ -6938,6 +6942,9 @@ const functionExecutors = {
6938
6942
  },
6939
6943
  Builtin: (fn, params, sourceCodeInfo, contextStack, { executeFunction }) => {
6940
6944
  const normalExpression = asNonUndefined(allNormalExpressions[fn.normalBuiltinSymbolType], sourceCodeInfo);
6945
+ if (contextStack.pure && normalExpression.pure === false) {
6946
+ throw new LitsError(`Cannot call impure function '${fn.name}' in pure mode`, sourceCodeInfo);
6947
+ }
6941
6948
  return normalExpression.evaluate(params, sourceCodeInfo, contextStack, { executeFunction });
6942
6949
  },
6943
6950
  SpecialBuiltin: (fn, params, sourceCodeInfo, contextStack, { executeFunction }) => {
@@ -6958,6 +6965,9 @@ const functionExecutors = {
6958
6965
  if (!expression) {
6959
6966
  throw new LitsError(`Function '${fn.functionName}' not found in module '${fn.moduleName}'.`, sourceCodeInfo);
6960
6967
  }
6968
+ if (contextStack.pure && expression.pure === false) {
6969
+ throw new LitsError(`Cannot call impure function '${fn.functionName}' in pure mode`, sourceCodeInfo);
6970
+ }
6961
6971
  assertNumberOfParams(expression.arity, params.length, sourceCodeInfo);
6962
6972
  return expression.evaluate(params, sourceCodeInfo, contextStack, { executeFunction });
6963
6973
  },
@@ -7056,6 +7066,9 @@ function evaluateNormalExpression(node, contextStack) {
7056
7066
  if (isNormalBuiltinSymbolNode(nameSymbol)) {
7057
7067
  const type = nameSymbol[1];
7058
7068
  const normalExpression = builtin.allNormalExpressions[type];
7069
+ if (contextStack.pure && normalExpression.pure === false) {
7070
+ throw new LitsError(`Cannot call impure function '${normalExpression.name}' in pure mode`, node[2]);
7071
+ }
7059
7072
  return normalExpression.evaluate(params, node[2], contextStack, { executeFunction });
7060
7073
  }
7061
7074
  else {
@@ -7152,13 +7165,15 @@ class ContextStackImpl {
7152
7165
  nativeJsFunctions;
7153
7166
  modules;
7154
7167
  valueModules;
7155
- constructor({ contexts, values: hostValues, nativeJsFunctions, modules, valueModules, }) {
7168
+ pure;
7169
+ constructor({ contexts, values: hostValues, nativeJsFunctions, modules, valueModules, pure, }) {
7156
7170
  this.globalContext = asNonUndefined(contexts[0]);
7157
7171
  this.contexts = contexts;
7158
7172
  this.values = hostValues;
7159
7173
  this.nativeJsFunctions = nativeJsFunctions;
7160
7174
  this.modules = modules ?? new Map();
7161
7175
  this.valueModules = valueModules ?? new Map();
7176
+ this.pure = pure ?? false;
7162
7177
  }
7163
7178
  getModule(name) {
7164
7179
  return this.modules.get(name);
@@ -7180,13 +7195,14 @@ class ContextStackImpl {
7180
7195
  nativeJsFunctions: this.nativeJsFunctions,
7181
7196
  modules: this.modules,
7182
7197
  valueModules: this.valueModules,
7198
+ pure: this.pure,
7183
7199
  });
7184
7200
  contextStack.globalContext = globalContext;
7185
7201
  return contextStack;
7186
7202
  }
7187
7203
  new(context) {
7188
7204
  const contexts = [{}, context];
7189
- return new ContextStackImpl({ contexts, modules: this.modules, valueModules: this.valueModules });
7205
+ return new ContextStackImpl({ contexts, modules: this.modules, valueModules: this.valueModules, pure: this.pure });
7190
7206
  }
7191
7207
  addValues(values, sourceCodeInfo) {
7192
7208
  const currentContext = this.contexts[0];
@@ -7292,7 +7308,7 @@ function assertNotShadowingBuiltin(name) {
7292
7308
  throw new LitsError(`Cannot shadow ${shadowedName}`, undefined);
7293
7309
  }
7294
7310
  }
7295
- function createContextStack(params = {}, modules) {
7311
+ function createContextStack(params = {}, modules, pure) {
7296
7312
  const globalContext = params.globalContext ?? {};
7297
7313
  // Contexts are checked from left to right
7298
7314
  const contexts = params.contexts ? [globalContext, ...params.contexts] : [globalContext];
@@ -7336,6 +7352,7 @@ function createContextStack(params = {}, modules) {
7336
7352
  values: hostValues,
7337
7353
  modules,
7338
7354
  nativeJsFunctions,
7355
+ pure,
7339
7356
  });
7340
7357
  return params.globalModuleScope ? contextStack : contextStack.create({});
7341
7358
  }
@@ -9443,9 +9460,13 @@ class Lits {
9443
9460
  return result;
9444
9461
  }
9445
9462
  runBundle(bundle, params = {}) {
9446
- const contextStack = createContextStack(params, this.modules);
9463
+ const contextStack = createContextStack(params, this.modules, params.pure);
9447
9464
  // Evaluate file modules in dependency order and register as value modules.
9448
9465
  // Each file module is evaluated in its own scope so local bindings don't leak.
9466
+ // File modules are always evaluated in pure mode to ensure deterministic,
9467
+ // side-effect-free initialization regardless of the caller's pure setting.
9468
+ const savedPure = contextStack.pure;
9469
+ contextStack.pure = true;
9449
9470
  for (const [name, source] of bundle.fileModules) {
9450
9471
  const ast = this.generateAst(source, params);
9451
9472
  const moduleContextStack = contextStack.create({});
@@ -9455,6 +9476,7 @@ class Lits {
9455
9476
  }
9456
9477
  contextStack.registerValueModule(name, result);
9457
9478
  }
9479
+ contextStack.pure = savedPure;
9458
9480
  // Parse and evaluate the main program
9459
9481
  const ast = this.generateAst(bundle.program, params);
9460
9482
  const result = evaluate(ast, contextStack);
@@ -9482,7 +9504,7 @@ class Lits {
9482
9504
  return ast;
9483
9505
  }
9484
9506
  evaluate(ast, params) {
9485
- const contextStack = createContextStack(params, this.modules);
9507
+ const contextStack = createContextStack(params, this.modules, params.pure);
9486
9508
  return evaluate(ast, contextStack);
9487
9509
  }
9488
9510
  transformSymbols(tokenStream, transformer) {
@@ -13256,6 +13278,7 @@ const randomFunctions = {
13256
13278
  evaluate: () => {
13257
13279
  return Math.random();
13258
13280
  },
13281
+ pure: false,
13259
13282
  arity: toFixedArity(0),
13260
13283
  },
13261
13284
  'random-int!': {
@@ -13264,6 +13287,7 @@ const randomFunctions = {
13264
13287
  assertNumber(max, sourceCodeInfo, { integer: true, gt: min });
13265
13288
  return Math.floor(Math.random() * (max - min)) + min;
13266
13289
  },
13290
+ pure: false,
13267
13291
  arity: toFixedArity(2),
13268
13292
  },
13269
13293
  'random-int-inclusive!': {
@@ -13272,6 +13296,7 @@ const randomFunctions = {
13272
13296
  assertNumber(max, sourceCodeInfo, { integer: true, gte: min });
13273
13297
  return Math.floor(Math.random() * (max - min + 1)) + min;
13274
13298
  },
13299
+ pure: false,
13275
13300
  arity: toFixedArity(2),
13276
13301
  },
13277
13302
  'random-float!': {
@@ -13280,6 +13305,7 @@ const randomFunctions = {
13280
13305
  assertNumber(max, sourceCodeInfo, { gt: min });
13281
13306
  return Math.random() * (max - min) + min;
13282
13307
  },
13308
+ pure: false,
13283
13309
  arity: toFixedArity(2),
13284
13310
  },
13285
13311
  'random-boolean!': {
@@ -13288,6 +13314,7 @@ const randomFunctions = {
13288
13314
  assertNumber(probability, sourceCodeInfo, { gte: 0, lte: 1 });
13289
13315
  return Math.random() < probability;
13290
13316
  },
13317
+ pure: false,
13291
13318
  arity: { min: 0, max: 1 },
13292
13319
  },
13293
13320
  'random-item!': {
@@ -13296,6 +13323,7 @@ const randomFunctions = {
13296
13323
  const index = Math.floor(Math.random() * array.length);
13297
13324
  return asAny(array[index]);
13298
13325
  },
13326
+ pure: false,
13299
13327
  arity: toFixedArity(1),
13300
13328
  },
13301
13329
  'random-sample!': {
@@ -13314,6 +13342,7 @@ const randomFunctions = {
13314
13342
  }
13315
13343
  return result;
13316
13344
  },
13345
+ pure: false,
13317
13346
  arity: toFixedArity(2),
13318
13347
  },
13319
13348
  'random-sample-unique!': {
@@ -13335,6 +13364,7 @@ const randomFunctions = {
13335
13364
  }
13336
13365
  return result;
13337
13366
  },
13367
+ pure: false,
13338
13368
  arity: toFixedArity(2),
13339
13369
  },
13340
13370
  'shuffle!': {
@@ -13347,6 +13377,7 @@ const randomFunctions = {
13347
13377
  }
13348
13378
  return shuffledArray;
13349
13379
  },
13380
+ pure: false,
13350
13381
  arity: toFixedArity(1),
13351
13382
  },
13352
13383
  'random-normal!': {
@@ -13358,6 +13389,7 @@ const randomFunctions = {
13358
13389
  const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
13359
13390
  return z0 * stdDev + mean;
13360
13391
  },
13392
+ pure: false,
13361
13393
  arity: toFixedArity(2),
13362
13394
  },
13363
13395
  'random-exponential!': {
@@ -13366,6 +13398,7 @@ const randomFunctions = {
13366
13398
  const u = Math.random();
13367
13399
  return -Math.log(u) / lambda;
13368
13400
  },
13401
+ pure: false,
13369
13402
  arity: toFixedArity(1),
13370
13403
  },
13371
13404
  'random-binomial!': {
@@ -13380,6 +13413,7 @@ const randomFunctions = {
13380
13413
  }
13381
13414
  return k;
13382
13415
  },
13416
+ pure: false,
13383
13417
  arity: toFixedArity(2),
13384
13418
  },
13385
13419
  'random-poisson!': {
@@ -13394,6 +13428,7 @@ const randomFunctions = {
13394
13428
  } while (p > L);
13395
13429
  return k - 1;
13396
13430
  },
13431
+ pure: false,
13397
13432
  arity: toFixedArity(1),
13398
13433
  },
13399
13434
  'random-gamma!': {
@@ -13402,6 +13437,7 @@ const randomFunctions = {
13402
13437
  assertNumber(scale, sourceCodeInfo, { gt: 0 });
13403
13438
  return randomGamma(shape, scale);
13404
13439
  },
13440
+ pure: false,
13405
13441
  arity: toFixedArity(2),
13406
13442
  },
13407
13443
  'random-pareto!': {
@@ -13410,6 +13446,7 @@ const randomFunctions = {
13410
13446
  const u = Math.random();
13411
13447
  return (1 / u) ** (1 / alpha);
13412
13448
  },
13449
+ pure: false,
13413
13450
  arity: toFixedArity(1),
13414
13451
  },
13415
13452
  'uuid!': {
@@ -13420,6 +13457,7 @@ const randomFunctions = {
13420
13457
  return value.toString(16);
13421
13458
  });
13422
13459
  },
13460
+ pure: false,
13423
13461
  arity: toFixedArity(0),
13424
13462
  },
13425
13463
  'random-char!': {
@@ -13431,6 +13469,7 @@ const randomFunctions = {
13431
13469
  const randomIndex = Math.floor(Math.random() * charSet.length);
13432
13470
  return charSet[randomIndex];
13433
13471
  },
13472
+ pure: false,
13434
13473
  arity: toFixedArity(1),
13435
13474
  },
13436
13475
  'random-string!': {
@@ -13447,6 +13486,7 @@ const randomFunctions = {
13447
13486
  }
13448
13487
  return result;
13449
13488
  },
13489
+ pure: false,
13450
13490
  arity: toFixedArity(2),
13451
13491
  },
13452
13492
  'random-id!': {
@@ -13460,6 +13500,7 @@ const randomFunctions = {
13460
13500
  }
13461
13501
  return result;
13462
13502
  },
13503
+ pure: false,
13463
13504
  arity: toFixedArity(1),
13464
13505
  },
13465
13506
  'random-color!': {
@@ -13467,6 +13508,7 @@ const randomFunctions = {
13467
13508
  const randomColor = Math.floor(Math.random() * 0x1000000).toString(16);
13468
13509
  return `#${randomColor.padStart(6, '0')}`;
13469
13510
  },
13511
+ pure: false,
13470
13512
  arity: toFixedArity(0),
13471
13513
  },
13472
13514
  };
@@ -34633,32 +34675,50 @@ const helpRegExp = new RegExp(`^\`help\\s+(${polishSymbolFirstCharacterClass}${p
34633
34675
  const expressions = [...normalExpressionKeys, ...specialExpressionKeys];
34634
34676
  const config = processArguments(process.argv.slice(2));
34635
34677
  const cliModules = getCliModules();
34636
- function createLits(context) {
34678
+ function createLits(context, pure) {
34637
34679
  const _lits = new Lits({ debug: true, modules: [...allBuiltinModules, ...cliModules] });
34638
34680
  return {
34639
34681
  run: (program) => _lits.run(program, {
34640
34682
  globalContext: context,
34641
34683
  globalModuleScope: true,
34684
+ pure,
34642
34685
  }),
34643
34686
  };
34644
34687
  }
34645
34688
  switch (config.subcommand) {
34646
34689
  case 'run': {
34647
- const lits = createLits(config.context);
34690
+ const lits = createLits(config.context, config.pure);
34648
34691
  try {
34649
34692
  const content = fs.readFileSync(config.filename, { encoding: 'utf-8' });
34650
- let programOrBundle = content;
34651
- // Try to parse as a JSON bundle
34693
+ const result = lits.run(content);
34694
+ if (config.printResult) {
34695
+ console.log(result);
34696
+ }
34697
+ process.exit(0);
34698
+ }
34699
+ catch (error) {
34700
+ printErrorMessage(`${error}`);
34701
+ process.exit(1);
34702
+ }
34703
+ break;
34704
+ }
34705
+ case 'run-bundle': {
34706
+ const lits = createLits(config.context, config.pure);
34707
+ try {
34708
+ const content = fs.readFileSync(config.filename, { encoding: 'utf-8' });
34709
+ let parsed;
34652
34710
  try {
34653
- const parsed = JSON.parse(content);
34654
- if (isLitsBundle(parsed)) {
34655
- programOrBundle = parsed;
34656
- }
34711
+ parsed = JSON.parse(content);
34657
34712
  }
34658
34713
  catch {
34659
- // Not JSON treat as plain Lits source
34714
+ printErrorMessage(`Invalid bundle: ${config.filename} is not valid JSON`);
34715
+ process.exit(1);
34716
+ }
34717
+ if (!isLitsBundle(parsed)) {
34718
+ printErrorMessage(`Invalid bundle: ${config.filename} is not a valid Lits bundle (expected "program" string and "fileModules" array)`);
34719
+ process.exit(1);
34660
34720
  }
34661
- const result = lits.run(programOrBundle);
34721
+ const result = lits.run(parsed);
34662
34722
  if (config.printResult) {
34663
34723
  console.log(result);
34664
34724
  }
@@ -34671,7 +34731,7 @@ switch (config.subcommand) {
34671
34731
  break;
34672
34732
  }
34673
34733
  case 'eval': {
34674
- const lits = createLits(config.context);
34734
+ const lits = createLits(config.context, config.pure);
34675
34735
  try {
34676
34736
  const result = lits.run(config.expression);
34677
34737
  if (config.printResult) {
@@ -34711,7 +34771,7 @@ switch (config.subcommand) {
34711
34771
  }
34712
34772
  case 'repl': {
34713
34773
  if (config.loadFilename) {
34714
- const lits = createLits(config.context);
34774
+ const lits = createLits(config.context, false);
34715
34775
  const content = fs.readFileSync(config.loadFilename, { encoding: 'utf-8' });
34716
34776
  const result = lits.run(content);
34717
34777
  if (result !== null && typeof result === 'object' && !Array.isArray(result)) {
@@ -34748,7 +34808,7 @@ function runLitsTest(testPath, testNamePattern) {
34748
34808
  process.exit(1);
34749
34809
  }
34750
34810
  function execute(expression, context) {
34751
- const lits = createLits(context);
34811
+ const lits = createLits(context, false);
34752
34812
  try {
34753
34813
  const result = lits.run(expression);
34754
34814
  historyResults.unshift(result);
@@ -34786,7 +34846,7 @@ function setReplHistoryVariables(context) {
34786
34846
  }
34787
34847
  function parseOption(args, i) {
34788
34848
  const option = args[i];
34789
- if (option === '-p') {
34849
+ if (option === '-s') {
34790
34850
  return { option, argument: null, count: 1 };
34791
34851
  }
34792
34852
  if (/^-[a-z]$/i.test(option))
@@ -34846,16 +34906,16 @@ function parseContextOptions(args, startIndex) {
34846
34906
  return { options, nextIndex: i };
34847
34907
  }
34848
34908
  function parsePrintOptions(args, startIndex) {
34849
- const options = { printResult: false };
34909
+ const options = { printResult: true };
34850
34910
  let i = startIndex;
34851
34911
  while (i < args.length) {
34852
34912
  const parsed = parseOption(args, i);
34853
34913
  if (!parsed)
34854
34914
  break;
34855
34915
  switch (parsed.option) {
34856
- case '-p':
34857
- case '--print-result':
34858
- options.printResult = true;
34916
+ case '-s':
34917
+ case '--silent':
34918
+ options.printResult = false;
34859
34919
  i += parsed.count;
34860
34920
  break;
34861
34921
  default:
@@ -34866,7 +34926,8 @@ function parsePrintOptions(args, startIndex) {
34866
34926
  }
34867
34927
  function parseRunEvalOptions(args, startIndex) {
34868
34928
  let context = {};
34869
- let printResult = false;
34929
+ let printResult = true;
34930
+ let pure = false;
34870
34931
  let i = startIndex;
34871
34932
  while (i < args.length) {
34872
34933
  const parsed = parseOption(args, i);
@@ -34882,13 +34943,17 @@ function parseRunEvalOptions(args, startIndex) {
34882
34943
  i = result.nextIndex;
34883
34944
  break;
34884
34945
  }
34885
- case '-p':
34886
- case '--print-result': {
34946
+ case '-s':
34947
+ case '--silent': {
34887
34948
  const result = parsePrintOptions(args, i);
34888
34949
  printResult = result.options.printResult;
34889
34950
  i = result.nextIndex;
34890
34951
  break;
34891
34952
  }
34953
+ case '--pure':
34954
+ pure = true;
34955
+ i += parsed.count;
34956
+ break;
34892
34957
  default:
34893
34958
  printErrorMessage(`Unknown option "${parsed.option}"`);
34894
34959
  process.exit(1);
@@ -34898,7 +34963,7 @@ function parseRunEvalOptions(args, startIndex) {
34898
34963
  printErrorMessage(`Unknown argument "${args[i]}"`);
34899
34964
  process.exit(1);
34900
34965
  }
34901
- return { context, printResult, nextIndex: i };
34966
+ return { context, printResult, pure, nextIndex: i };
34902
34967
  }
34903
34968
  function processArguments(args) {
34904
34969
  // Global flags (no subcommand)
@@ -34919,8 +34984,17 @@ function processArguments(args) {
34919
34984
  printErrorMessage('Missing filename after "run"');
34920
34985
  process.exit(1);
34921
34986
  }
34922
- const { context, printResult } = parseRunEvalOptions(args, 2);
34923
- return { subcommand: 'run', filename, context, printResult };
34987
+ const { context, printResult, pure } = parseRunEvalOptions(args, 2);
34988
+ return { subcommand: 'run', filename, context, printResult, pure };
34989
+ }
34990
+ case 'run-bundle': {
34991
+ const filename = args[1];
34992
+ if (!filename || filename.startsWith('-')) {
34993
+ printErrorMessage('Missing filename after "run-bundle"');
34994
+ process.exit(1);
34995
+ }
34996
+ const { context, printResult, pure } = parseRunEvalOptions(args, 2);
34997
+ return { subcommand: 'run-bundle', filename, context, printResult, pure };
34924
34998
  }
34925
34999
  case 'eval': {
34926
35000
  const expression = args[1];
@@ -34928,8 +35002,8 @@ function processArguments(args) {
34928
35002
  printErrorMessage('Missing expression after "eval"');
34929
35003
  process.exit(1);
34930
35004
  }
34931
- const { context, printResult } = parseRunEvalOptions(args, 2);
34932
- return { subcommand: 'eval', expression, context, printResult };
35005
+ const { context, printResult, pure } = parseRunEvalOptions(args, 2);
35006
+ return { subcommand: 'eval', expression, context, printResult, pure };
34933
35007
  }
34934
35008
  case 'bundle': {
34935
35009
  const filename = args[1];
@@ -35109,17 +35183,19 @@ function printUsage() {
35109
35183
  Usage: lits [subcommand] [options]
35110
35184
 
35111
35185
  Subcommands:
35112
- run <file> [options] Run a .lits file or .json bundle
35186
+ run <file> [options] Run a .lits file
35187
+ run-bundle <file> [options] Run a .json bundle
35113
35188
  eval <expression> [options] Evaluate a Lits expression
35114
35189
  bundle <entry> [options] Bundle a multi-file project
35115
35190
  test <file> [options] Run a .test.lits test file
35116
35191
  repl [options] Start an interactive REPL
35117
35192
  help Show this help
35118
35193
 
35119
- Run/Eval options:
35194
+ Run/Run-bundle/Eval options:
35120
35195
  -c, --context=<json> Context as a JSON string
35121
35196
  -C, --context-file=<file> Context from a .json file
35122
- -p, --print-result Print the result
35197
+ -s, --silent Suppress printing the result
35198
+ --pure Enforce pure mode (no side effects or non-determinism)
35123
35199
 
35124
35200
  Bundle options:
35125
35201
  -o, --output=<file> Write bundle to file (default: stdout)
@@ -16,6 +16,7 @@ export interface LitsRuntimeInfo {
16
16
  export interface JsFunction {
17
17
  fn: (...args: any[]) => unknown;
18
18
  arity?: Arity;
19
+ pure?: boolean;
19
20
  docString?: string;
20
21
  }
21
22
  export interface ContextParams {
@@ -30,6 +31,9 @@ export interface MinifyParams {
30
31
  export interface FilePathParams {
31
32
  filePath?: string;
32
33
  }
34
+ export interface PureParams {
35
+ pure?: boolean;
36
+ }
33
37
  interface LitsConfig {
34
38
  initialCache?: Record<string, Ast>;
35
39
  astCacheSize?: number | null;
@@ -44,10 +48,10 @@ export declare class Lits {
44
48
  constructor(config?: LitsConfig);
45
49
  getRuntimeInfo(): LitsRuntimeInfo;
46
50
  readonly async: {
47
- run: (programOrBundle: string | LitsBundle, params?: ContextParams & FilePathParams) => Promise<unknown>;
48
- apply: (fn: LitsFunction, fnParams: unknown[], params?: ContextParams) => Promise<unknown>;
51
+ run: (programOrBundle: string | LitsBundle, params?: ContextParams & FilePathParams & PureParams) => Promise<unknown>;
52
+ apply: (fn: LitsFunction, fnParams: unknown[], params?: ContextParams & PureParams) => Promise<unknown>;
49
53
  };
50
- run(programOrBundle: string | LitsBundle, params?: ContextParams & FilePathParams): unknown;
54
+ run(programOrBundle: string | LitsBundle, params?: ContextParams & FilePathParams & PureParams): unknown;
51
55
  private runBundle;
52
56
  getUndefinedSymbols(programOrAst: string | Ast, params?: ContextParams): Set<string>;
53
57
  tokenize(program: string, tokenizeParams?: FilePathParams & MinifyParams): TokenStream;
@@ -55,7 +59,7 @@ export declare class Lits {
55
59
  private evaluate;
56
60
  transformSymbols(tokenStream: TokenStream, transformer: (symbol: string) => string): TokenStream;
57
61
  untokenize(tokenStream: TokenStream): string;
58
- apply(fn: LitsFunction, fnParams: unknown[], params?: ContextParams): MaybePromise<Any>;
62
+ apply(fn: LitsFunction, fnParams: unknown[], params?: ContextParams & PureParams): MaybePromise<Any>;
59
63
  private generateApplyFunctionCall;
60
64
  private generateAst;
61
65
  getAutoCompleter(program: string, position: number, params?: ContextParams): AutoCompleter;
@@ -80,6 +80,7 @@ type NormalExpressionEvaluator<T> = (params: Arr, sourceCodeInfo: SourceCodeInfo
80
80
  }) => MaybePromise<T>;
81
81
  export interface BuiltinNormalExpression<T> {
82
82
  evaluate: NormalExpressionEvaluator<T>;
83
+ pure?: boolean;
83
84
  name?: string;
84
85
  arity: Arity;
85
86
  docs?: FunctionDocs;
@@ -12,12 +12,14 @@ export declare class ContextStackImpl {
12
12
  private nativeJsFunctions?;
13
13
  private modules;
14
14
  private valueModules;
15
- constructor({ contexts, values: hostValues, nativeJsFunctions, modules, valueModules, }: {
15
+ pure: boolean;
16
+ constructor({ contexts, values: hostValues, nativeJsFunctions, modules, valueModules, pure, }: {
16
17
  contexts: Context[];
17
18
  values?: Record<string, unknown>;
18
19
  nativeJsFunctions?: Record<string, NativeJsFunction>;
19
20
  modules?: Map<string, LitsModule>;
20
21
  valueModules?: Map<string, unknown>;
22
+ pure?: boolean;
21
23
  });
22
24
  getModule(name: string): LitsModule | undefined;
23
25
  getValueModule(name: string): {
@@ -32,4 +34,4 @@ export declare class ContextStackImpl {
32
34
  lookUp(node: UserDefinedSymbolNode): LookUpResult;
33
35
  evaluateSymbol(node: SymbolNode): Any;
34
36
  }
35
- export declare function createContextStack(params?: ContextParams, modules?: Map<string, LitsModule>): ContextStack;
37
+ export declare function createContextStack(params?: ContextParams, modules?: Map<string, LitsModule>, pure?: boolean): ContextStack;
@@ -11,6 +11,6 @@ export type { LitsModule } from './builtin/modules/interface';
11
11
  export type { LitsBundle } from './bundler/interface';
12
12
  export { isLitsBundle } from './bundler/interface';
13
13
  export { type LitsError, isLitsError } from './errors';
14
- export type { ContextParams, FilePathParams, MinifyParams, LitsRuntimeInfo, JsFunction } from './Lits/Lits';
14
+ export type { ContextParams, FilePathParams, MinifyParams, PureParams, LitsRuntimeInfo, JsFunction } from './Lits/Lits';
15
15
  export { isGrid, isMatrix, isVector } from './typeGuards/annotatedArrays';
16
16
  export type { AutoCompleter } from './AutoCompleter/AutoCompleter';