@mojir/lits 2.4.1 → 2.5.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/README.md CHANGED
@@ -48,10 +48,10 @@ $ lits eval "[1, 2, 3, 4] filter odd? map inc"
48
48
  $ lits run script.lits
49
49
 
50
50
  # Bundle a multi-file project into a single .json file
51
- $ lits bundle main.lits -o bundle.json
51
+ $ lits bundle main.lits -o bundle.lits.json
52
52
 
53
53
  # Run a bundle
54
- $ lits run-bundle bundle.json
54
+ $ lits run-bundle bundle.lits.json
55
55
 
56
56
  # Run tests
57
57
  $ lits test tests.test.lits
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.1";
10
+ var version = "2.5.1";
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
  };
@@ -34626,6 +34668,7 @@ const HIST_SIZE = 1000;
34626
34668
  const PROMPT = fmt.bright.gray('> ');
34627
34669
  const historyResults = [];
34628
34670
  const formatValue = getInlineCodeFormatter(fmt);
34671
+ const booleanFlags = new Set(['-s', '--silent', '--pure']);
34629
34672
  const commands = ['`help', '`quit', '`builtins', '`context'];
34630
34673
  const expressionRegExp = new RegExp(`^(.*\\(\\s*)(${polishSymbolFirstCharacterClass}${polishSymbolCharacterClass}*)$`);
34631
34674
  const nameRegExp = new RegExp(`^(.*?)(${polishSymbolFirstCharacterClass}${polishSymbolCharacterClass}*)$`);
@@ -34633,18 +34676,19 @@ const helpRegExp = new RegExp(`^\`help\\s+(${polishSymbolFirstCharacterClass}${p
34633
34676
  const expressions = [...normalExpressionKeys, ...specialExpressionKeys];
34634
34677
  const config = processArguments(process.argv.slice(2));
34635
34678
  const cliModules = getCliModules();
34636
- function createLits(context) {
34679
+ function createLits(context, pure) {
34637
34680
  const _lits = new Lits({ debug: true, modules: [...allBuiltinModules, ...cliModules] });
34638
34681
  return {
34639
34682
  run: (program) => _lits.run(program, {
34640
34683
  globalContext: context,
34641
34684
  globalModuleScope: true,
34685
+ pure,
34642
34686
  }),
34643
34687
  };
34644
34688
  }
34645
34689
  switch (config.subcommand) {
34646
34690
  case 'run': {
34647
- const lits = createLits(config.context);
34691
+ const lits = createLits(config.context, config.pure);
34648
34692
  try {
34649
34693
  const content = fs.readFileSync(config.filename, { encoding: 'utf-8' });
34650
34694
  const result = lits.run(content);
@@ -34660,7 +34704,7 @@ switch (config.subcommand) {
34660
34704
  break;
34661
34705
  }
34662
34706
  case 'run-bundle': {
34663
- const lits = createLits(config.context);
34707
+ const lits = createLits(config.context, config.pure);
34664
34708
  try {
34665
34709
  const content = fs.readFileSync(config.filename, { encoding: 'utf-8' });
34666
34710
  let parsed;
@@ -34688,7 +34732,7 @@ switch (config.subcommand) {
34688
34732
  break;
34689
34733
  }
34690
34734
  case 'eval': {
34691
- const lits = createLits(config.context);
34735
+ const lits = createLits(config.context, config.pure);
34692
34736
  try {
34693
34737
  const result = lits.run(config.expression);
34694
34738
  if (config.printResult) {
@@ -34728,7 +34772,7 @@ switch (config.subcommand) {
34728
34772
  }
34729
34773
  case 'repl': {
34730
34774
  if (config.loadFilename) {
34731
- const lits = createLits(config.context);
34775
+ const lits = createLits(config.context, false);
34732
34776
  const content = fs.readFileSync(config.loadFilename, { encoding: 'utf-8' });
34733
34777
  const result = lits.run(content);
34734
34778
  if (result !== null && typeof result === 'object' && !Array.isArray(result)) {
@@ -34765,7 +34809,7 @@ function runLitsTest(testPath, testNamePattern) {
34765
34809
  process.exit(1);
34766
34810
  }
34767
34811
  function execute(expression, context) {
34768
- const lits = createLits(context);
34812
+ const lits = createLits(context, false);
34769
34813
  try {
34770
34814
  const result = lits.run(expression);
34771
34815
  historyResults.unshift(result);
@@ -34803,14 +34847,26 @@ function setReplHistoryVariables(context) {
34803
34847
  }
34804
34848
  function parseOption(args, i) {
34805
34849
  const option = args[i];
34806
- if (option === '-s') {
34807
- return { option, argument: null, count: 1 };
34808
- }
34809
- if (/^-[a-z]$/i.test(option))
34850
+ // Short option: -x
34851
+ if (/^-[a-z]$/i.test(option)) {
34852
+ if (booleanFlags.has(option)) {
34853
+ return { option, argument: null, count: 1 };
34854
+ }
34810
34855
  return { option, argument: args[i + 1] ?? null, count: 2 };
34856
+ }
34857
+ // Long option: --foo or --foo=value
34811
34858
  const match = /^(--[a-z-]+)(?:=(.*))?$/i.exec(option);
34812
- if (match)
34813
- return { option: match[1], argument: match[2] ?? null, count: 1 };
34859
+ if (match) {
34860
+ const name = match[1];
34861
+ const inlineArg = match[2];
34862
+ if (inlineArg !== undefined) {
34863
+ return { option: name, argument: inlineArg, count: 1 };
34864
+ }
34865
+ if (booleanFlags.has(name)) {
34866
+ return { option: name, argument: null, count: 1 };
34867
+ }
34868
+ return { option: name, argument: args[i + 1] ?? null, count: 2 };
34869
+ }
34814
34870
  return null;
34815
34871
  }
34816
34872
  function parseContextOptions(args, startIndex) {
@@ -34884,11 +34940,20 @@ function parsePrintOptions(args, startIndex) {
34884
34940
  function parseRunEvalOptions(args, startIndex) {
34885
34941
  let context = {};
34886
34942
  let printResult = true;
34943
+ let pure = false;
34944
+ let positional = null;
34887
34945
  let i = startIndex;
34888
34946
  while (i < args.length) {
34889
34947
  const parsed = parseOption(args, i);
34890
- if (!parsed)
34891
- break;
34948
+ if (!parsed) {
34949
+ if (positional !== null) {
34950
+ printErrorMessage(`Unexpected argument "${args[i]}"`);
34951
+ process.exit(1);
34952
+ }
34953
+ positional = args[i];
34954
+ i += 1;
34955
+ continue;
34956
+ }
34892
34957
  switch (parsed.option) {
34893
34958
  case '-c':
34894
34959
  case '--context':
@@ -34906,16 +34971,16 @@ function parseRunEvalOptions(args, startIndex) {
34906
34971
  i = result.nextIndex;
34907
34972
  break;
34908
34973
  }
34974
+ case '--pure':
34975
+ pure = true;
34976
+ i += parsed.count;
34977
+ break;
34909
34978
  default:
34910
34979
  printErrorMessage(`Unknown option "${parsed.option}"`);
34911
34980
  process.exit(1);
34912
34981
  }
34913
34982
  }
34914
- if (i < args.length) {
34915
- printErrorMessage(`Unknown argument "${args[i]}"`);
34916
- process.exit(1);
34917
- }
34918
- return { context, printResult, nextIndex: i };
34983
+ return { context, printResult, pure, positional, nextIndex: i };
34919
34984
  }
34920
34985
  function processArguments(args) {
34921
34986
  // Global flags (no subcommand)
@@ -34931,45 +34996,43 @@ function processArguments(args) {
34931
34996
  }
34932
34997
  switch (first) {
34933
34998
  case 'run': {
34934
- const filename = args[1];
34935
- if (!filename || filename.startsWith('-')) {
34999
+ const { positional: filename, context, printResult, pure } = parseRunEvalOptions(args, 1);
35000
+ if (!filename) {
34936
35001
  printErrorMessage('Missing filename after "run"');
34937
35002
  process.exit(1);
34938
35003
  }
34939
- const { context, printResult } = parseRunEvalOptions(args, 2);
34940
- return { subcommand: 'run', filename, context, printResult };
35004
+ return { subcommand: 'run', filename, context, printResult, pure };
34941
35005
  }
34942
35006
  case 'run-bundle': {
34943
- const filename = args[1];
34944
- if (!filename || filename.startsWith('-')) {
35007
+ const { positional: filename, context, printResult, pure } = parseRunEvalOptions(args, 1);
35008
+ if (!filename) {
34945
35009
  printErrorMessage('Missing filename after "run-bundle"');
34946
35010
  process.exit(1);
34947
35011
  }
34948
- const { context, printResult } = parseRunEvalOptions(args, 2);
34949
- return { subcommand: 'run-bundle', filename, context, printResult };
35012
+ return { subcommand: 'run-bundle', filename, context, printResult, pure };
34950
35013
  }
34951
35014
  case 'eval': {
34952
- const expression = args[1];
34953
- if (!expression || expression.startsWith('-')) {
35015
+ const { positional: expression, context, printResult, pure } = parseRunEvalOptions(args, 1);
35016
+ if (!expression) {
34954
35017
  printErrorMessage('Missing expression after "eval"');
34955
35018
  process.exit(1);
34956
35019
  }
34957
- const { context, printResult } = parseRunEvalOptions(args, 2);
34958
- return { subcommand: 'eval', expression, context, printResult };
35020
+ return { subcommand: 'eval', expression, context, printResult, pure };
34959
35021
  }
34960
35022
  case 'bundle': {
34961
- const filename = args[1];
34962
- if (!filename || filename.startsWith('-')) {
34963
- printErrorMessage('Missing filename after "bundle"');
34964
- process.exit(1);
34965
- }
35023
+ let filename = null;
34966
35024
  let output = null;
34967
- let i = 2;
35025
+ let i = 1;
34968
35026
  while (i < args.length) {
34969
35027
  const parsed = parseOption(args, i);
34970
35028
  if (!parsed) {
34971
- printErrorMessage(`Unknown argument "${args[i]}"`);
34972
- process.exit(1);
35029
+ if (filename !== null) {
35030
+ printErrorMessage(`Unexpected argument "${args[i]}"`);
35031
+ process.exit(1);
35032
+ }
35033
+ filename = args[i];
35034
+ i += 1;
35035
+ continue;
34973
35036
  }
34974
35037
  switch (parsed.option) {
34975
35038
  case '-o':
@@ -34986,21 +35049,26 @@ function processArguments(args) {
34986
35049
  process.exit(1);
34987
35050
  }
34988
35051
  }
35052
+ if (!filename) {
35053
+ printErrorMessage('Missing filename after "bundle"');
35054
+ process.exit(1);
35055
+ }
34989
35056
  return { subcommand: 'bundle', filename, output };
34990
35057
  }
34991
35058
  case 'test': {
34992
- const filename = args[1];
34993
- if (!filename || filename.startsWith('-')) {
34994
- printErrorMessage('Missing filename after "test"');
34995
- process.exit(1);
34996
- }
35059
+ let filename = null;
34997
35060
  let testPattern = null;
34998
- let i = 2;
35061
+ let i = 1;
34999
35062
  while (i < args.length) {
35000
35063
  const parsed = parseOption(args, i);
35001
35064
  if (!parsed) {
35002
- printErrorMessage(`Unknown argument "${args[i]}"`);
35003
- process.exit(1);
35065
+ if (filename !== null) {
35066
+ printErrorMessage(`Unexpected argument "${args[i]}"`);
35067
+ process.exit(1);
35068
+ }
35069
+ filename = args[i];
35070
+ i += 1;
35071
+ continue;
35004
35072
  }
35005
35073
  switch (parsed.option) {
35006
35074
  case '--pattern':
@@ -35016,6 +35084,10 @@ function processArguments(args) {
35016
35084
  process.exit(1);
35017
35085
  }
35018
35086
  }
35087
+ if (!filename) {
35088
+ printErrorMessage('Missing filename after "test"');
35089
+ process.exit(1);
35090
+ }
35019
35091
  return { subcommand: 'test', filename, testPattern };
35020
35092
  }
35021
35093
  case 'repl': {
@@ -35147,6 +35219,7 @@ Run/Run-bundle/Eval options:
35147
35219
  -c, --context=<json> Context as a JSON string
35148
35220
  -C, --context-file=<file> Context from a .json file
35149
35221
  -s, --silent Suppress printing the result
35222
+ --pure Enforce pure mode (no side effects or non-determinism)
35150
35223
 
35151
35224
  Bundle options:
35152
35225
  -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';