linny-r 1.3.4 → 1.4.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.
@@ -36,36 +36,56 @@ SOFTWARE.
36
36
  // CLASS Expression (for all potentially time-dependent model parameters)
37
37
  class Expression {
38
38
  constructor(obj, attr, text) {
39
+ // Expressions are typically defined for some attribute of some
40
+ // entity -- legacy convention is to refer to a model entity
41
+ // as `object` rather than `entity`.
39
42
  this.object = obj;
40
43
  this.attribute = attr;
41
44
  this.text = text;
42
- // A stack for local time step (to allow lazy evaluation)
45
+ // A stack for local time step (to allow lazy evaluation).
43
46
  this.step = [];
44
- // An operand stack for computation (elements must be numeric)
47
+ // An operand stack for computation (elements must be numeric).
45
48
  this.stack = [];
46
- // NOTE: code = NULL indicates: not compiled yet
49
+ // NOTE: code = NULL indicates: not compiled yet.
47
50
  this.code = null;
48
- // NOTE: use a semaphore to prevent cyclic recursion
51
+ // NOTE: Use a semaphore to prevent cyclic recursion.
49
52
  this.compiling = false;
50
- // While compiling, check whether any operand depends on time
53
+ // While compiling, check whether any operand depends on time.
51
54
  this.is_static = true;
52
- // Likewise, check whether any operand is computed by the solver
55
+ // Likewise, check whether any operand is computed by the solver.
53
56
  this.is_level_based = false;
54
- // NOTE: VM expects result to be an array, even when expression is static
57
+ // NOTE: VM expects result to be an array, even when expression is static.
55
58
  this.vector = [VM.NOT_COMPUTED];
56
59
  // For dataset *wildcard* modifier expressions, results are stored in a
57
- // separate vector for each wildcard number (list expands "lazily")
58
- this.wildcard_vectors = [];
60
+ // separate vector for each wildcard number. The set of vectors expands
61
+ // "lazily", as new entries (number: vector) are added only when the
62
+ // expression is evaluated for a different wildcard number.
63
+ // For all other expressions (typically properties of nodes), the
64
+ // normal expression vector is used.
65
+ // NOTE: Wildcard expressions merit special treatment when plotted in
66
+ // a chart.
67
+ this.wildcard_vectors = {};
68
+ // NOTE: `wildcard_number` is used -- only during code execution --
69
+ // by VMI_push_dataset_modifier (read and/or write) and by
70
+ // VMI_push_contextual_number (read only).
71
+ this.wildcard_vector_index = false;
59
72
  // Special instructions can store results as cache properties to save
60
- // (re)computation time; cache is cleared when expression is reset
73
+ // (re)computation time; cache is cleared when expression is reset.
61
74
  this.cache = {};
62
75
  }
63
76
 
77
+ get isWildcardExpression() {
78
+ // Returns TRUE if the owner is a dataset, and the attribute contains
79
+ // wildcards.
80
+ return this.object instanceof Dataset &&
81
+ this.object.isWildcardSelector(this.attribute);
82
+ }
83
+
64
84
  get variableName() {
65
85
  // Return the name of the variable computed by this expression
66
- if(this.attribute === 'C') return 'note color expression';
67
86
  if(this.object === MODEL.equations_dataset) return 'equation ' + this.attribute;
68
- return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
87
+ if(this.object) return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
88
+ return 'Unknown variable (no object)';
69
89
  }
70
90
 
71
91
  get timeStepDuration() {
@@ -78,7 +98,7 @@ class Expression {
78
98
  }
79
99
 
80
100
  get referencedEntities() {
81
- // Returns a list of entities referenced in this expression
101
+ // Returns a list of entities referenced in this expression.
82
102
  if(this.text.indexOf('[') < 0) return [];
83
103
  const
84
104
  el = [],
@@ -110,11 +130,8 @@ class Expression {
110
130
  // Clears result of previous computation (if any)
111
131
  this.step.length = 0;
112
132
  this.stack.length = 0;
113
- // NOTE: `wildcard_number` is used only during code execution;
114
- // then it may be set only by VMI_push_dataset_modifier,
115
- // and read only by VMI_push_selector_wildcard
116
- this.wildcard_number = false;
117
- this.wildcard_vectors = [];
133
+ this.wildcard_vectors = {};
134
+ this.wildcard_vector_index = false;
118
135
  this.cache = {};
119
136
  this.compile(); // if(!this.compiled) REMOVED to ensure correct isStatic!!
120
137
  // Static expressions only need a vector with one element (having index 0)
@@ -141,20 +158,18 @@ class Expression {
141
158
  }
142
159
 
143
160
  compile() {
144
- // Do not compile recursively
161
+ // Do not compile recursively.
145
162
  if(this.compiling) return;
146
- // Set the "compiling" flag to prevent cyclic recursion
163
+ // Set the "compiling" flag to prevent cyclic recursion.
147
164
  this.compiling = true;
148
- // Clear the VM instruction list
165
+ // Clear the VM instruction list.
149
166
  this.code = null;
150
167
  const xp = new ExpressionParser(this.text, this.object, this.attribute);
151
168
  if(xp.error === '') {
152
- // NOTE: except for dataset modifiers and note colors, expressions
169
+ // NOTE: Except for dataset modifiers and note colors, expressions
153
170
  // should not be based on levels-still-to-be-computed-by-the-solver,
154
- // so caution the modeler when this appears to be the case
155
- // NOTE: when note color expressions are edited, they are compiled
156
- // to check their syntax; then their object is null
157
- if(xp.is_level_based && this.object &&
171
+ // so caution the modeler when this appears to be the case.
172
+ if(xp.is_level_based &&
158
173
  !(this.object instanceof Dataset || this.object instanceof Note)) {
159
174
  // NOTE: this should not occur, so log more details
160
175
  console.log('Level-based issue:',
@@ -176,7 +191,7 @@ class Expression {
176
191
  }
177
192
 
178
193
  get asXML() {
179
- // Returns XML-encoded expression (after replacing "black-boxed" entities)
194
+ // Returns XML-encoded expression after replacing "black-boxed" entities.
180
195
  let text = this.text;
181
196
  if(MODEL.black_box) {
182
197
  // Get all entity names that occur in this expression
@@ -219,17 +234,21 @@ class Expression {
219
234
  }
220
235
 
221
236
  get defined() {
237
+ // Returns TRUE if the expression string is not empty.
222
238
  return this.text !== '';
223
239
  }
224
240
 
225
241
  get compiled() {
242
+ // Returns TRUE if there is code for this expression.
243
+ // NOTE: The expression parser sets `code` to NULL when compiling an
244
+ // empty string.
226
245
  return this.code !== null;
227
246
  }
228
247
 
229
248
  get isStatic() {
230
- // Returns is_static property AFTER compiling if not compiled yet
231
- // NOTE: to prevent cylic recursion, return FALSE if this expression is
232
- // already being compiled
249
+ // Returns is_static property AFTER compiling if not compiled yet.
250
+ // NOTE: To prevent cylic recursion, return FALSE if this expression is
251
+ // already being compiled.
233
252
  if(this.compiling) return false;
234
253
  if(!this.compiled) this.compile();
235
254
  return this.is_static;
@@ -251,53 +270,61 @@ class Expression {
251
270
  }
252
271
 
253
272
  chooseVector(number) {
254
- // Return the vector to use for computation (defaults to "own" vector)
255
- if(number === false || this.isStatic) return this.vector;
256
- if(number) {
257
- // Use the vector for the wildcard number (create it if necessary)
258
- if(!this.wildcard_vectors.hasOwnProperty(number)) {
259
- this.wildcard_vectors[number] = [];
273
+ // Return the vector to use for computation (defaults to "own" vector).
274
+ // NOTE: Static wildcard expressions must also choose a vector!
275
+ if(typeof number !== 'number' ||
276
+ (this.isStatic && !this.isWildcardExpression)) return this.vector;
277
+ // Use the vector for the wildcard number (create it if necessary).
278
+ if(!this.wildcard_vectors.hasOwnProperty(number)) {
279
+ this.wildcard_vectors[number] = [];
280
+ if(this.isStatic) {
281
+ this.wildcard_vectors[number][0] = VM.NOT_COMPUTED;
282
+ } else {
260
283
  MODEL.cleanVector(this.wildcard_vectors[number], VM.NOT_COMPUTED);
261
284
  }
262
- return this.wildcard_vectors[number];
263
285
  }
286
+ return this.wildcard_vectors[number];
264
287
  }
265
288
 
266
289
  compute(t, number=false) {
267
- // Executes the VM code for this expression for time step t
268
- // NOTE: `number` is passed (as integer) only if context for # is defined
290
+ // Executes the VM code for this expression for time step `t`.
291
+ // NOTE: `number` is passed only if context for # is defined.
269
292
  if(!this.compiled) this.compile();
270
- // Return FALSE if compilation resulted in error
293
+ // Return FALSE if compilation resulted in error.
271
294
  if(!this.compiled) return false;
272
- // Compute static expressions as if t = 0
295
+ // Compute static expressions as if t = 0.
273
296
  if(t < 0 || this.isStatic) t = 0;
274
- // Select the vector to use
297
+ // Select the vector to use.
275
298
  const v = this.chooseVector(number);
276
- // Check for potential error (that should NOT occur)
277
- if(!v || v.length === 0 || t >= v.length) {
299
+ // Check for potential error (that should NOT occur).
300
+ if(!Array.isArray(v) || v.length === 0 || t >= v.length) {
278
301
  const msg = 'ERROR: Undefined value during expression evaluation';
279
302
  UI.alert(msg);
280
303
  console.log(this.variableName, ':', this.text, '#', number, '@', t, v);
281
- // Throw exception to permit viewing the function call stack
304
+ // Throw exception to permit viewing the function call stack.
282
305
  throw msg;
283
306
  }
284
307
  // When called while already computing for time step t, signal this
285
308
  // as an error value.
286
309
  if(v[t] === VM.COMPUTING) v[t] = VM.CYCLIC;
287
- // Compute a value only once
288
- if(v[t] !== VM.NOT_COMPUTED) return true;
289
- // Provide selector context for # (number = FALSE => no wildcard match)
290
- this.wildcard_number = number;
291
- // Push this expression onto the call stack
310
+ // Compute a value only once.
311
+ if(v[t] !== VM.NOT_COMPUTED) {
312
+ if(DEBUGGING) console.log('Already computed', this.variableName,
313
+ ':', this.text, '#', number, '@', t, v[t]);
314
+ return true;
315
+ }
316
+ // Provide selector context for # (number = FALSE => no wildcard match).
317
+ this.wildcard_vector_index = number;
318
+ // Push this expression onto the call stack.
292
319
  VM.call_stack.push(this);
293
- // Push time step in case a VMI_push_var instruction references
294
- // this same variable
295
- this.trace('--START: ' + this.variableName);
320
+ // Push time step in case a VMI instruction for another expression
321
+ // references this same variable.
322
+ this.trace(`--START: ${this.variableName} (wvi = ${number})`);
296
323
  this.step.push(t);
297
- // NOTE: trace extression AFTER pushing the time step
324
+ // NOTE: Trace expression AFTER pushing the time step.
298
325
  this.trace(`"${this.text}"`);
299
326
  v[t] = VM.COMPUTING;
300
- // Execute the instructions
327
+ // Execute the instructions.
301
328
  let vmi = null,
302
329
  ok = true,
303
330
  cl = this.code.length;
@@ -306,13 +333,13 @@ class Expression {
306
333
  this.stack.length = 0;
307
334
  while(ok && this.program_counter < cl && v[t] === VM.COMPUTING) {
308
335
  vmi = this.code[this.program_counter];
309
- // Instructions are 2-element arrays [function, [arguments]];
310
- // the function is called with this expression as first parameter,
311
- // and the argument list as second parameter
336
+ // Instructions are 2-element arrays [function, [arguments]].
337
+ // The function is called with this expression as first parameter,
338
+ // and the argument list as second parameter.
312
339
  vmi[0](this, vmi[1]);
313
340
  this.program_counter++;
314
341
  }
315
- // Stack should now have length 1
342
+ // Stack should now have length 1.
316
343
  if(this.stack.length > 1) {
317
344
  v[t] = VM.OVERFLOW;
318
345
  } else if(this.stack.length < 1) {
@@ -321,19 +348,20 @@ class Expression {
321
348
  v[t] = this.stack.pop();
322
349
  }
323
350
  this.trace('RESULT = ' + VM.sig4Dig(v[t]));
324
- // Pop the time step
351
+ // Pop the time step.
325
352
  this.step.pop();
326
353
  this.trace('--STOP: ' + this.variableName);
327
- // Clear context for #
328
- this.wildcard_number = false;
329
- // If error, display the call stack (only once)
354
+ // Clear context for # for this expression (no stack needed, as
355
+ // wildcard expressions cannot reference themselves).
356
+ this.wildcard_vector_index = false;
357
+ // If error, display the call stack (only once).
330
358
  // NOTE: "undefined", "not computed" and "still computing" are NOT
331
359
  // problematic unless they result in an error (stack over/underflow)
332
360
  if(v[t] <= VM.ERROR) {
333
361
  MONITOR.showCallStack(t);
334
362
  VM.logCallStack(t);
335
363
  }
336
- // Always pop the expression from the call stack
364
+ // Always pop the expression from the call stack.
337
365
  VM.call_stack.pop(this);
338
366
  return true;
339
367
  }
@@ -346,6 +374,10 @@ class Expression {
346
374
  // "initial value" (these follow from the variables used in the expression)
347
375
  // Select the vector to use
348
376
  const v = this.chooseVector(number);
377
+ if(!Array.isArray(v)) {
378
+ console.log('ANOMALY: No vector for result(t)');
379
+ return VM.UNDEFINED;
380
+ }
349
381
  if(t < 0 || this.isStatic) t = 0;
350
382
  if(t >= v.length) return VM.UNDEFINED;
351
383
  if(v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING) {
@@ -487,46 +519,157 @@ class Expression {
487
519
 
488
520
  // CLASS ExpressionParser
489
521
  // Instances of ExpressionParser compile expressions into code, i.e.,
490
- // an array of VM instructions. The optional parameters `owner` and `attribute`
491
- // are used to prefix "local" entities, and to implement modifier expressions
492
- // that contain the dot that (when used within brackets) denotes the data value
493
- // of the dataset
522
+ // an array of VM instructions. The optional parameters `owner` and
523
+ // `attribute` are used to prefix "local" entities, and also to implement
524
+ // modifier expressions that contain the "dot" that (when used within
525
+ // brackets) denotes the data value of the dataset.
526
+
527
+ // Since version 1.4.0, a leading colon indicates that the variable
528
+ // "inherits" the prefixes of its owner. Thus, for example, in the expression
529
+ // for the upper bound of proces "Storage: Li-ion: battery 1", the variable
530
+ // [:capacity] will be interpreted as [Storage: Li-ion: capacity].
531
+ // The prefixed name will be parsed normally, so if "Storage: Li-ion: capacity"
532
+ // identifies an array-type dataset, [:capacity@#] will work, since the
533
+ // value of # can be inferred from the expression owner's name
534
+ // "Storage: Li-ion: battery 1".
535
+
536
+ // Also since version 1.4.0, the context sensitive number # can also be used
537
+ // as a "wildcard" in an entity name. This is useful mainly when combined with
538
+ // wildcard equations with names like "eq??ion" which can then be referred to
539
+ // in expressions not only as "eq12ion" (then # in the expression for the
540
+ // wildcard equation evaluates as 12), but also as "eq#ion" (then # in the
541
+ // expression for the wildcard equation will have the value of # in the
542
+ // "calling" expression. This permits, for example, defining as single
543
+ // equation "partial load ??" with expression "[P#|L] / [P#|UB]", and then
544
+ // using the variable [partial load 1] to compute the partial load for
545
+ // process P1.
546
+ // NOTES:
547
+ // (1) This applies recursively, so [partial load #] can be used in some other
548
+ // wildcard equation like, for example, "percent load ??" having expression
549
+ // "100 * [partial load #]".
550
+ // (2) The # may be used in patterns, so when a model comprises processes
551
+ // P1 and P2, and products Q2 and Q3, and a wildcard equation "total level ??"
552
+ // with expression "[SUM$#|L]", then [total level 1] will evaluate as the level
553
+ // of P1, and [total level 2] as the level of P2 plus the level of Q3.
554
+
494
555
  class ExpressionParser {
495
556
  constructor(text, owner=null, attribute='') {
496
- // `text` is the expression string to be parsed
557
+ // Setting TRACE to TRUE will log parsing information to the console.
558
+ this.TRACE = false;
559
+ // `text` is the expression string to be parsed.
497
560
  this.expr = text;
561
+ // NOTE: When expressions for dataset modifiers or equations are
562
+ // parsed, `owner` is their dataset, and `attribute` is their name.
498
563
  this.owner = owner;
564
+ this.owner_prefix = '';
499
565
  this.attribute = attribute;
500
- // If `owner` is a dataset, then it is the dataset denoted by . (dot)
501
- this.dataset = (owner instanceof Dataset ? owner : null);
502
- this.selector = (this.dataset ? attribute : '');
566
+ this.dataset = null;
567
+ this.dot = null;
568
+ this.selector = '';
569
+ this.context_number = '';
570
+ this.wildcard_selector = false;
571
+ // Always infer the value for the context-sensitive number #.
572
+ // NOTE: This this will always be a string. Three possible cases:
573
+ // (1) a question mark "?" if `owner` is a dataset and `attribute`
574
+ // wildcards in its selector; this indicates that the value of # cannot be inferred at
575
+ // compile time.
576
+ //
577
+ if(owner) {
578
+ this.context_number = owner.numberContext;
579
+ // NOTE: The owner prefix includes the trailing colon+space.
580
+ if(owner instanceof Link || owner instanceof Constraint) {
581
+ // For links and constraints, use the longest prefix that
582
+ // their nodes have in common.
583
+ this.owner_prefix = UI.sharedPrefix(owner.from_node.displayName,
584
+ owner.to_node.displayName) + UI.PREFIXER;
585
+ } else if(owner === MODEL.equations_dataset) {
586
+ this.owner_prefix = UI.completePrefix(attribute);
587
+ } else {
588
+ this.owner_prefix = UI.completePrefix(owner.displayName);
589
+ }
590
+ if(owner instanceof Dataset) {
591
+ this.dataset = owner;
592
+ // The attribute (if specified) is a dataset modifier selector.
593
+ // This may be the name of an equation; this can be tested by
594
+ // checking whether the owner is the equations dataset.
595
+ this.selector = attribute;
596
+ // Record whether this selector contains wildcards (? and/or *
597
+ // for selectors, ?? for equations).
598
+ this.wildcard_selector = owner.isWildcardSelector(attribute);
599
+ if(this.wildcard_selector) {
600
+ // NOTE: Wildcard selectors override the context number that
601
+ // may have been inferred from the dataset name.
602
+ this.context_number = '?';
603
+ } else {
604
+ // Normal selectors may have a "tail number". If so, this
605
+ // overrides the tail number of the dataset.
606
+ const tn = UI.tailNumber(attribute);
607
+ if(tn) this.context_number = tn;
608
+ }
609
+ if(owner !== MODEL.equations_dataset) {
610
+ // For "normal" modifier expressions, the "dot" (.) can be used
611
+ // to refer to the dataset of the modifier.
612
+ this.dot = this.dataset;
613
+ }
614
+ }
615
+ }
616
+ // Ensure that context number is either '?' or a number or FALSE.
617
+ if(this.context_number !== '?') {
618
+ this.context_number = parseInt(this.context_number);
619
+ if(isNaN(this.context_number)) this.context_number = false;
620
+ }
621
+ // Immediately compile; this may generate warnings
503
622
  this.compile();
504
623
  }
505
-
506
- log(msg) {
507
- if(false) { // Set to TRUE to profile dynamic expressions
508
- console.log(`Expression for ${this.owner.displayName}|${this.attribute}
509
- ${this.expr}\n${msg}`);
624
+
625
+ get ownerName() {
626
+ // FOR TRACING & DEBUGGING: Returns the owner of this equation (if any).
627
+ if(!this.owner) return '(no owner)';
628
+ let n = this.owner.displayName;
629
+ if(this.attribute) n += '|' + this.attribute;
630
+ if(this.wildcard_selector) {
631
+ n = [n, ' [wildcard ',
632
+ (this.dataset === MODEL.equations_dataset ?
633
+ 'equation' : 'modifier'),
634
+ (this.context_number !== false ?
635
+ ' # = ' + this.context_number : ''),
636
+ ']'].join('');
510
637
  }
638
+ return n;
639
+ }
640
+
641
+ log(msg) {
642
+ // NOTE: This method is used only to profile dynamic expressions.
643
+ if(true) return;
644
+ // Set the above IF condition to FALSE to profile dynamic expressions.
645
+ console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
511
646
  }
512
647
 
513
- // Checks whether name fits this pattern:
648
+ // The method parseVariable(name) checks whether `name` fits this pattern:
514
649
  // {run}statistic$entity|attribute@offset_1:offset_2
515
650
  // allowing spaces within {run} and around | and @ and :
516
651
  // The entity is mandatory, but {run} and statistic$ are optional, and
517
652
  // attribute and offset have defaults.
518
- // Returns array [object, anchor_1, offset_1, anchor_2, offset_2] if the
519
- // pattern matches and no statistic, or the 6-element array
653
+ // It returns array [object, anchor_1, offset_1, anchor_2, offset_2] if
654
+ // the pattern matches and no statistic, or the 6-element array
520
655
  // [statistic, object list, anchor, offset, anchor_2, offset_2]
521
- // if a valid statistic is specified; otherwise FALSE.
656
+ // if a valid statistic is specified; otherwise it returns FALSE.
522
657
  // The object is either a vector or an expression, or a special object
523
658
  // (dataset specifier, experiment run specifier or unit balance specifier)
524
659
  // NOTE: this array is used as argument for the virtual machine instructions
525
- // VMI_push_var, VMI_push_statistic and VMI_push_run_result
660
+ // VMI_push_var, VMI_push_statistic and VMI_push_run_result.
526
661
  parseVariable(name) {
527
- // Reduce whitespace to single space
662
+ // Reduce whitespace to single space.
528
663
  name = name.replace(/\s+/g, ' ');
529
- // Initialize possible components
664
+
665
+ // For debugging, TRACE can be used to log to the console for
666
+ // specific expressions and/or variables, for example:
667
+ // this.TRACE = name.endsWith('losses') || this.ownerName.endsWith('losses');
668
+ if(this.TRACE) console.log(
669
+ `TRACE: Parsing variable "${name}" in expression for`,
670
+ this.ownerName, ' --> ', this.expr);
671
+
672
+ // Initialize possible components.
530
673
  let obj = null,
531
674
  attr = '',
532
675
  use_data = false,
@@ -536,9 +679,11 @@ class ExpressionParser {
536
679
  anchor2 = '',
537
680
  offset2 = 0,
538
681
  msg = '',
682
+ arg0 = null,
539
683
  args = null,
540
684
  s = name.split('@');
541
685
  if(s.length > 1) {
686
+ // [variable@offset] where offset has form (anchor1)number1(:(anchor2)number 2)
542
687
  // Offsets make expression dynamic (for now, ignore exceptional cases)
543
688
  this.is_static = false;
544
689
  this.log('dynamic because of offset');
@@ -586,9 +731,7 @@ class ExpressionParser {
586
731
  }
587
732
  // Check whether # anchor is meaningful for this expression
588
733
  if((anchor1 === '#' || anchor2 === '#') &&
589
- !(this.selector.indexOf('*') >= 0 ||
590
- this.selector.indexOf('?') >= 0 ||
591
- this.owner.numberContext)) {
734
+ !(this.wildcard_selector || this.context_number !== false)) {
592
735
  // Log debugging information for this error
593
736
  console.log(this.owner.displayName, this.owner.type, this.selector);
594
737
  this.error = 'Anchor # is undefined in this context';
@@ -597,7 +740,8 @@ class ExpressionParser {
597
740
  }
598
741
  }
599
742
  // Run specifier (optional) must be leading and braced
600
- // Specifier format: {method$title|run} where method and title are optional
743
+ // Specifier format: {method$title|run} where method and title are
744
+ // optional -- NOTE: # in title or run is NOT seen as a wildcard
601
745
  if(name.startsWith('{')) {
602
746
  s = name.split('}');
603
747
  if(s.length > 1) {
@@ -631,20 +775,29 @@ class ExpressionParser {
631
775
  }
632
776
  s = s.split('#');
633
777
  let rn = (s.length > 1 ? s[1].trim() : false);
634
- // Experiment specifier may contain selectors
778
+ // Experiment specifier may contain modifier selectors.
635
779
  s = s[0].trim().split(UI.OA_SEPARATOR);
636
780
  if(s.length > 1) {
637
- // If so, the selector list may indicate the run number
638
- // NOTE: permit selectors to be separated by various characters
781
+ // If so, the selector list may indicate the run number.
782
+ // NOTE: permit selectors to be separated by various characters.
639
783
  x.r = s.slice(1).join('|').split(/[\|\,\.\:\;\/\s]+/g);
640
784
  }
641
785
  if(rn) {
786
+ // NOTE: Special notation for run numbers to permit modelers
787
+ // to chart results as if run numbers are on the time axis
788
+ // (with a given step size). The chart will be made as usual,
789
+ // i.e., plot a point for each time step t, but the value v[t]
790
+ // will then stay the same for the time interval that corresponds
791
+ // to simulation period length / number of runs.
792
+ // NOTE: This will fail to produce a meaningful chart when the
793
+ // simulation period is small compared to the number of runs.
642
794
  if(rn.startsWith('n')) {
643
- // #n may be followed by a range, or defaults to 0 - last run number
644
- // Of this range, the i-th number will be used, where i equals
795
+ // #n may be followed by a range, or this range defaults to
796
+ // 0 - last run number. Of this range, the i-th number will
797
+ // be used, where i is computes as:
645
798
  // floor(current time step * number of runs / period length)
646
799
  const range = rn.substring(1);
647
- // Call rangeToList only to validate the range syntax
800
+ // Call rangeToList only to validate the range syntax.
648
801
  if(rangeToList(range)) {
649
802
  x.nr = range;
650
803
  this.is_static = false;
@@ -653,12 +806,12 @@ class ExpressionParser {
653
806
  msg = `Invalid experiment run number range "${range}"`;
654
807
  }
655
808
  } else {
656
- // Explicit run number
809
+ // Explicit run number is specified.
657
810
  const n = parseInt(rn);
658
811
  if(isNaN(n)) {
659
812
  msg = `Invalid experiment run number "${rn}"`;
660
813
  } else {
661
- // Explicit run number overrules selector list
814
+ // Explicit run number overrules selector list.
662
815
  x.r = n;
663
816
  }
664
817
  }
@@ -666,7 +819,7 @@ class ExpressionParser {
666
819
  // NOTE: s[0] still holds the experiment title
667
820
  s = s[0].trim();
668
821
  if(s) {
669
- // NOTE: title cannot be parametrized
822
+ // NOTE: title cannot be parametrized with a # wildcard
670
823
  const n = MODEL.indexOfExperiment(s);
671
824
  if(n < 0) {
672
825
  msg = `Unknown experiment "${s}"`;
@@ -674,24 +827,31 @@ class ExpressionParser {
674
827
  x.x = MODEL.experiments[n];
675
828
  }
676
829
  }
677
- // Variable name may start with a (case insensitive) statistic specifier
830
+ // Variable name may start with a (case insensitive) statistic
831
+ // specifier such as SUM or MEAN
678
832
  s = name.split('$');
679
833
  if(s.length > 1) {
680
834
  const stat = s[0].trim().toUpperCase();
681
- // NOTE: simply ignore $ unless it indicates a valid statistic
835
+ // NOTE: simply ignore $ (i.e., consider it as part of the
836
+ // variable name) unless it is preceded by a valid statistic
682
837
  if(VM.outcome_statistics.indexOf(stat) >= 0) {
683
838
  x.s = stat;
684
839
  name = s[1].trim();
685
840
  }
686
841
  }
842
+ // Variable name may start with a colon to denote that the owner
843
+ // prefix should be added.
844
+ name = UI.colonPrefixedName(name, this.owner_prefix);
687
845
  if(x.x) {
688
- // NOTE: variable name may be parametrized
689
- const vpar = name.split('\\');
690
- if(vpar.length > 1) {
691
- // @@ TO DO!
692
- }
693
846
  // Look up name in experiment outcomes list
694
847
  x.v = x.x.resultIndex(name);
848
+ if(x.v < 0 && name.indexOf('#') >= 0 &&
849
+ typeof this.context_number === 'number') {
850
+ // Variable name may be parametrized with #, but not in
851
+ // expressions for wildcard selectors
852
+ name = name.replace('#', this.context_number);
853
+ x.v = x.x.resultIndex(name);
854
+ }
695
855
  if(x.v < 0) {
696
856
  msg = ['Variable "', name, '" is not a result of experiment "',
697
857
  x.x.displayName, '"'].join('');
@@ -699,15 +859,23 @@ class ExpressionParser {
699
859
  } else {
700
860
  // Check outcome list of ALL experiments
701
861
  for(let i = 0; i < MODEL.experiments.length; i++) {
702
- if(MODEL.experiments[i].resultIndex(name) >= 0) {
862
+ let xri = MODEL.experiments[i].resultIndex(name);
863
+ if(xri < 0 && name.indexOf('#') >= 0 &&
864
+ typeof this.context_number === 'number') {
865
+ // Variable name may be parametrized with #, but not in
866
+ // expressions for wildcard selectors.
867
+ name = name.replace('#', this.context_number);
868
+ xri = MODEL.experiments[i].resultIndex(name);
869
+ }
870
+ if(xri >= 0) {
703
871
  // If some match is found, the name specifies a variable
704
- x.v = name;
872
+ x.v = xri;
705
873
  break;
706
874
  }
707
875
  }
708
876
  }
709
- // NOTE: experiment may still be FALSE, as this will be interpreted as
710
- // "use current experiment", but run number should be specified
877
+ // NOTE: experiment may still be FALSE, as this will be interpreted
878
+ // as "use current experiment", but run number should be specified.
711
879
  if(!msg) {
712
880
  if(x.r === false && x.t === false) {
713
881
  msg = 'Experiment run not specified';
@@ -719,107 +887,145 @@ class ExpressionParser {
719
887
  this.error = msg;
720
888
  return false;
721
889
  }
722
- // Notify modeler when two statistics are used
890
+ // Notify modeler when two statistics are used.
723
891
  if(x.s && x.m) {
724
892
  UI.notify(`Method statistic (${x.m}) does not apply to ` +
725
893
  `run result statistic (${x.s})`);
726
894
  }
727
- // NOTE: using AGGREGATED run results does NOT make the expression
728
- // dynamic, so only set is_static to FALSE if NO statistic or method
895
+ // NOTE: Using AGGREGATED run results does NOT make the expression
896
+ // dynamic, so only set is_static to FALSE if NO statistic or method.
729
897
  if(!x.s && !x.m) {
730
898
  this.is_static = false;
731
899
  this.log('dynamic because UNaggregated experiment result');
732
900
  }
733
- // For experiment run results, default anchor is 't'
901
+ // For experiment run results, default anchor is 't'.
734
902
  if(!anchor1) anchor1 = 't';
735
903
  if(!anchor2) anchor2 = 't';
736
- // NOTE: compiler will recognize `x` to indicate "push run results"
904
+ if(this.TRACE) console.log('TRACE: Variable is run result. x =', x);
905
+ // NOTE: compiler will recognize `x` to indicate "push run results".
737
906
  return [x, anchor1, offset1, anchor2, offset2];
738
907
  }
739
908
  }
740
909
 
741
910
  //
742
- // NOTE: for experiment results, the method will ALWAYS have returned
743
- // a result, so what follows does not apply to experiment results
911
+ // NOTE: For experiment results, the method will ALWAYS have returned
912
+ // a result, so what follows does not apply to experiment results.
744
913
  //
745
914
 
746
- // Attribute name (optional) follows object-attribute separator |
915
+ // If reached this stage, variable must be like this:
916
+ // [(statistic$)entity name pattern(|attribute)]
917
+ // Attribute name (optional) follows the object-attribute separator |
747
918
  s = name.split(UI.OA_SEPARATOR);
748
919
  if(s.length > 1) {
749
- // Attribute is string after LAST separator ...
920
+ // Attribute is string after the LAST separator...
750
921
  attr = s.pop().trim();
751
- // ... so restore name if itself contains other separators
922
+ // ... so restore `name` in case itself contains other separators.
752
923
  name = s.join(UI.OA_SEPARATOR).trim();
753
924
  if(!attr) {
754
925
  // Explicit *empty* attribute, e.g., [name|]
755
- // NOTE: this matters for datasets having specifiers: the vertical
926
+ // NOTE: This matters for datasets having specifiers: the vertical
756
927
  // bar indicates "do not infer a modifier from a running experiment,
757
- // but use the data"
928
+ // but use the data".
758
929
  use_data = true;
759
930
  } else if(attr.startsWith('=')) {
760
931
  // Attribute starting with = indicates cluster balance
761
- // NOTE: empty string is considered as "any unit"
932
+ // NOTE: empty string is considered as "any unit".
762
933
  cluster_balance_unit = attr.substring(1).trim();
934
+ } else if(attr.indexOf('?') >= 0 || attr.indexOf('#') >= 0) {
935
+ // Wildcard selectors of dataset modifiers cannot be used.
936
+ this.error = `Invalid attribute "${attr}"`;
937
+ return false;
763
938
  }
764
939
  }
765
940
 
766
- // Check whether statistic is specified
941
+ // Check whether a statistic is specified.
767
942
  let pat = name.split('$');
768
943
  if(pat.length > 1 &&
769
944
  VM.statistic_operators.indexOf(pat[0].toUpperCase()) >= 0) {
770
- // For statistics, default anchor is 't'
945
+ // For statistics, the default anchor is 't'.
771
946
  if(!anchor1) anchor1 = 't';
772
947
  if(!anchor2) anchor2 = 't';
773
- // Check whether unit balance for clusters is asked for
948
+ // Check whether unit balance for clusters is asked for.
774
949
  if(cluster_balance_unit !== false) {
775
950
  this.error = 'Aggregation of unit balance over clusters is not supported';
776
951
  return false;
777
952
  }
778
- // Consider only the first $ as statistic separator ...
953
+ // Consider only the first $ as statistic separator.
779
954
  const stat = pat.shift().toUpperCase();
780
- // Reassemble pattern string, which may itself contain $
955
+ // Reassemble pattern string, which may itself contain $.
781
956
  pat = pat.join('$');
782
- // Special case: dataset "dot" is NOT a pattern
957
+ // Special case: dataset "dot" is NOT a pattern.
783
958
  if(pat === '.') {
784
- // NOTE: "dot" dataset is not level-dependent, and statistics over its
785
- // vector do NOT make the expression dynamic
786
- if(this.dataset) {
787
- return [stat, [this.dataset.vector], anchor1, offset1, anchor2, offset2];
959
+ // NOTE: The "dot" dataset is not level-dependent, and statistics
960
+ // over its vector do NOT make the expression dynamic.
961
+ if(this.dot) {
962
+ args = [stat, [this.dot.vector], anchor1, offset1, anchor2, offset2];
963
+ if(this.TRACE) console.log('TRACE: Variable is a statistic:', args);
964
+ return args;
788
965
  } else {
789
- msg = UI.ERROR.NO_DATASET_DOT;
966
+ this.error = UI.ERROR.NO_DATASET_DOT;
790
967
  return false;
791
968
  }
792
969
  }
793
- // @@@TO DO: also deal with "prefix inheritance" when pattern starts with :
794
- // By default, consider all entity types
970
+ // Deal with "prefix inheritance" when pattern starts with a colon.
971
+ if(pat.startsWith(':') && this.owner_prefix) {
972
+ // Add a "must start with" AND condition to all OR clauses of the
973
+ // pattern.
974
+ // NOTE: Issues may occur when prefix contains &, ^ or #.
975
+ // @@TO DO: See if this can be easily prohibited.
976
+ const oc = pat.substring(1).split('|');
977
+ for(let i = 0; i < oc.length; i++) {
978
+ oc[i] = `~${this.owner_prefix}&${oc[i]}`;
979
+ }
980
+ pat = oc.join('|');
981
+ }
982
+ // NOTE: For patterns, assume that # *always* denotes the context-
983
+ // sensitive number #, because if modelers wishes to include
984
+ // ANY number, they can make their pattern less selective.
985
+ if(typeof this.context_number === 'number') {
986
+ pat = pat.replace('#', this.context_number);
987
+ }
988
+ // By default, consider all entity types.
795
989
  let et = VM.entity_letters,
796
990
  patstr = pat;
797
- // Selection may be limited to specific entity types by prefic "...?"
991
+ // Selection may be limited to specific entity types by prefix "...?"
992
+ // where ... is one or more entity letters (A for actor, etc.).
798
993
  if(/^[ABCDELPQ]+\?/i.test(pat)) {
799
994
  pat = pat.split('?');
800
995
  et = pat[0].toUpperCase();
801
996
  pat = pat.slice(1).join('=');
802
997
  }
803
- // Get the name pattern
998
+ // Get the name pattern.
804
999
  pat = patternList(pat);
805
- // Infer the entity type(s) from the attribute (if defined)
1000
+ // Infer the entity type(s) from the attribute (if defined).
806
1001
  const
807
1002
  list = [],
808
- // NOTE: optional second parameter `et` may limit the returned list
1003
+ // NOTE: The optional second parameter `et` will limit the
1004
+ // returned list to the specified entity types.
809
1005
  ewa = MODEL.entitiesWithAttribute(attr, et);
810
- // Create list of expression objects for the matching entities
1006
+ // Create list of expression objects for the matching entities.
1007
+ // Also create a "dict" with, for each matching wildcard number,
1008
+ // the matching entities as a separate list. This will permit
1009
+ // narrowing the selection at run time, based on the expression's
1010
+ // wildcard number.
1011
+ const wdict = {};
811
1012
  for(let i = 0; i < ewa.length; i++) {
812
1013
  const e = ewa[i];
813
1014
  if(patternMatch(e.displayName, pat)) {
814
- // NOTE: attribute may be a single value, a vector, or an expression
1015
+ const mnr = matchingWildcardNumber(e.displayName, pat);
1016
+ // NOTE: Attribute may be a single value, a vector, or an expression.
815
1017
  obj = e.attributeValue(attr);
816
- // Neither a single value nor a vector => must be an expression
1018
+ // If neither a single value nor a vector, it must be an expression.
817
1019
  if(obj === null) obj = e.attributeExpression(attr);
818
- // Double-check: only add it if it is not NULL
1020
+ // Double-check: only add it if it is not NULL.
819
1021
  if(obj) {
820
1022
  list.push(obj);
1023
+ if(mnr) {
1024
+ if(!wdict[mnr]) wdict[mnr] = [];
1025
+ wdict[mnr].push(obj);
1026
+ }
821
1027
  // Expression becomes dynamic if any element that is added is
822
- // neither a single value nor a static expression
1028
+ // neither a single value nor a static expression.
823
1029
  if(Array.isArray(obj) ||
824
1030
  (obj instanceof Expression && !obj.isStatic)) {
825
1031
  this.is_static = false;
@@ -828,28 +1034,35 @@ class ExpressionParser {
828
1034
  }
829
1035
  }
830
1036
  }
831
- // NOTE: also add expressions for equations that match
832
- const edm = MODEL.equations_dataset.modifiers;
833
- for(let k in edm) if(edm.hasOwnProperty(k)) {
834
- const m = edm[k];
835
- if(patternMatch(m.selector, pat)) {
836
- list.push(m.expression);
837
- if(!m.expression.isStatic) {
838
- this.is_static = false;
839
- this.log('dynamic because matching equation is dynamic');
1037
+ // NOTE: If no attribute is specified, also add expressions for
1038
+ // equations that match UNLESS entity type specifier excludes them.
1039
+ if(!attr && (!et || et.indexOf('E') >= 0)) {
1040
+ const edm = MODEL.equations_dataset.modifiers;
1041
+ for(let k in edm) if(edm.hasOwnProperty(k)) {
1042
+ const m = edm[k];
1043
+ if(patternMatch(m.selector, pat)) {
1044
+ list.push(m.expression);
1045
+ if(!m.expression.isStatic) {
1046
+ this.is_static = false;
1047
+ this.log('dynamic because matching equation is dynamic');
1048
+ }
840
1049
  }
841
1050
  }
842
1051
  }
843
1052
  if(list.length > 0) {
844
- // NOTE: statistic MAY make expression level-based
845
- // NOTE: assume NOT when offset has been specified, as this suggests
846
- // that modelers know what they're doing
1053
+ // NOTE: Statistic MAY make expression level-based.
1054
+ // Assume that this is NOT so when an offset has been specified,
1055
+ // as this suggests that modelers know what they're doing.
847
1056
  this.is_level_based = this.is_level_based ||
848
1057
  VM.level_based_attr.indexOf(attr) >= 0 &&
849
1058
  anchor1 === 't' && offset1 === 0 &&
850
1059
  anchor2 === 't' && offset2 === 0;
851
- // NOTE: compiler will recognize 6-element list as a "statistic"
852
- return [stat, list, anchor1, offset1, anchor2, offset2];
1060
+ args = [stat, list, anchor1, offset1, anchor2, offset2];
1061
+ if(Object.keys(wdict).length > 0) args.push(wdict);
1062
+ if(this.TRACE) console.log('TRACE: Variable is a statistic:', args);
1063
+ // NOTE: Compiler will recognize 6- or 7-element list as a
1064
+ // sign to use the VMI_push_statistic instruction.
1065
+ return args;
853
1066
  }
854
1067
  this.error = `No entities that match pattern "${patstr}"` +
855
1068
  (attr ? ' and have attribute ' + attr : ' when no attribute is specified');
@@ -857,180 +1070,368 @@ class ExpressionParser {
857
1070
  }
858
1071
 
859
1072
  //
860
- // NOTE: for statistics, the method will ALWAYS have returned a result,
861
- // so what follows does not apply to statistics results
1073
+ // NOTE: For statistics, the method will ALWAYS have returned a result,
1074
+ // so what follows does not apply to statistics results, but only to
1075
+ // "plain" variables like [entity name(|attribute)].
862
1076
  //
863
1077
 
864
- let by_reference = false;
865
- if(name === '.') {
866
- // NOTE: when name is a single dot, it refers to the data of a dataset
867
- obj = this.dataset;
868
- if(!obj) msg = UI.ERROR.NO_DATASET_DOT;
869
- } else if(!name && !attr && this.dataset) {
870
- // This accepts auto-referencing variables like [@t-1]
871
- // Dataset modifier expressions can reference themselves, provided that
872
- // an offset is specified
873
- // This makes the expression dynamic
874
- this.is_static = false;
875
- this.log('dynamic because of self-reference');
1078
+ // For all entity types except array-type datasets, the default anchor
1079
+ // for offsets is the current time step `t`.
1080
+ if(!(this.dataset && this.dataset.array)) {
876
1081
  if(!anchor1) anchor1 = 't';
877
1082
  if(!anchor2) anchor2 = 't';
1083
+ }
1084
+ // First handle this special case: no name or attribute. This is valid
1085
+ // only for dataset modifier expressions (and hence also equations).
1086
+ // Variables like [@t-1] are interpreted as a self-reference. This is
1087
+ // meaningful when a *negative* offset is specified to denote "use the
1088
+ // value of this expression for some earlier time step".
1089
+ // NOTES:
1090
+ // (1) This makes the expression dynamic.
1091
+ // (2) It does not apply to array-type datasets, as these have no
1092
+ // time dimension.
1093
+ if(!name && !attr && this.dataset && !this.dataset.array) {
1094
+ this.is_static = false;
1095
+ this.log('dynamic because of self-reference');
878
1096
  if(('cips'.indexOf(anchor1) >= 0 || anchor1 === 't' && offset1 < 0) &&
879
1097
  ('cips'.indexOf(anchor2) >= 0 ||anchor2 === 't' && offset2 < 0)) {
1098
+ if(this.TRACE) console.log('TRACE: Variable is a self-reference.');
880
1099
  // The `xv` attribute will be recognized by VMI_push_var to denote
881
- // "use the vector of the expression for which this VMI is code"
1100
+ // "use the vector of the expression for which this VMI is code".
882
1101
  return [{xv: true, dv: this.dataset.defaultValue},
883
1102
  anchor1, offset1, anchor2, offset2];
884
- } else {
885
- this.error = 'Expression can reference only previous values of itself';
886
- return false;
887
1103
  }
888
- } else {
889
- // Check for special case: pass variable reference instead of by value
890
- if(name.startsWith('!')) {
891
- by_reference = true;
892
- name = name.substring(1);
893
- }
894
- if(!msg) {
895
- // Check whether name refers to a Linny-R entity defined by the model
896
- obj = MODEL.objectByName(name);
897
- // If dataset modifier, it is the name of an equation...
898
- if(obj instanceof DatasetModifier) {
899
- // ... so "map" it onto the equations dataset + selector
900
- attr = obj.selector;
901
- obj = MODEL.equations_dataset;
1104
+ msg = 'Expression can reference only previous values of itself';
1105
+ }
1106
+ // A leading "!" denotes: pass variable reference instead of its value.
1107
+ // NOTE: This also applies to the "dot", so [!.] is a valid variable.
1108
+ let by_reference = name.startsWith('!');
1109
+ if(by_reference) name = name.substring(1);
1110
+ // When `name` is a single dot, it refers to the dataset for which the
1111
+ // modifier expression is being parsed. Like all datasets, the "dot"
1112
+ // may also have an attribute.
1113
+ if(name === '.') {
1114
+ obj = this.dot;
1115
+ if(!obj) msg = UI.ERROR.NO_DATASET_DOT;
1116
+ } else if(name.indexOf('??') >= 0) {
1117
+ msg = 'Use # as wildcard, not ??';
1118
+ }
1119
+ if(msg) {
1120
+ this.error = msg;
1121
+ return false;
1122
+ }
1123
+ // Check whether name refers to a Linny-R entity defined by the model.
1124
+ if(!obj) {
1125
+ // Variable name may start with a colon to denote that the owner
1126
+ // prefix should be added.
1127
+ name = UI.colonPrefixedName(name, this.owner_prefix);
1128
+ // Start with wildcard equations, as these are likely to be few
1129
+ // (so a quick scan) and constitute a special case.
1130
+ const
1131
+ id = UI.nameToID(name),
1132
+ w = MODEL.wildcardEquationByID(id);
1133
+ if(w) {
1134
+ if(this.TRACE) console.log('TRACE: Variable is a wildcard equation:',
1135
+ w[0], '-- number is', w[1], '\nTRACE: Equation expression: ',
1136
+ w[0].expression.text);
1137
+ // Variable matches wildcard equation w[0] with number w[1],
1138
+ // so this equation must be evaluated for that number.
1139
+ return [
1140
+ {d: w[0].dataset, s: w[1], x: w[0].expression},
1141
+ anchor1, offset1, anchor2, offset2];
1142
+ }
1143
+ // If no match, try to match the object ID with any type of entity.
1144
+ obj = MODEL.objectByID(id);
1145
+ }
1146
+ // If not, try whether wildcards can be substituted.
1147
+ if(!obj && name.indexOf('#') >= 0) {
1148
+ if(typeof this.context_number === 'number') {
1149
+ obj = MODEL.objectByName(name.replace('#', this.context_number));
1150
+ }
1151
+ if(obj && TRACE) console.log('TRACE: Matched ', name,
1152
+ 'with entity:', obj.displayName);
1153
+ if(!obj) {
1154
+ // If immediate substitution of # does not identify an entity,
1155
+ // then name may still refer to a wildcard equation.
1156
+ const wcname = name.replace('#', '??');
1157
+ // Check for self-reference.
1158
+ if(wcname === this.attribute) {
1159
+ msg = 'Equation cannot reference itself';
1160
+ } else {
1161
+ obj = MODEL.equationByID(UI.nameToID(wcname));
1162
+ if(obj) {
1163
+ // Special case: the parsed variable references a wildcard
1164
+ // equation, so now `obj` is an instance of DatasetModifier.
1165
+ if(!(this.wildcard_selector || this.context_number)) {
1166
+ msg = UI.ERROR.NO_NUMBER_CONTEXT;
1167
+ } else {
1168
+ // Acceptable reference to a wildcard equation.
1169
+ if(!obj.expression.isStatic) {
1170
+ this.is_static = false;
1171
+ this.log('dynamic because wildcard equation is dynamic');
1172
+ }
1173
+ // NOTE: The referenced expression may be level-dependent.
1174
+ this.is_level_based = this.is_level_based ||
1175
+ obj.expression.is_level_based;
1176
+ if(this.TRACE) console.log('TRACE: Variable ', name,
1177
+ 'is a wildcard equation:', obj.displayName,
1178
+ '-- number is:', this.context_number,
1179
+ '\nTRACE: Expression:', obj.expression.text);
1180
+ // Use the context number as "selector" parameter of the VMI.
1181
+ return [
1182
+ {d: obj.dataset, s: this.context_number, x: obj.expression},
1183
+ anchor1, offset1, anchor2, offset2];
1184
+ }
1185
+ }
902
1186
  }
903
1187
  }
1188
+ if(!obj) {
1189
+ // Final possibility is a match with a tail-numbered entity name.
1190
+ // NOTE: also pass `attr` so that only entities having this
1191
+ // attribute will match.
1192
+ const ame = MODEL.allMatchingEntities(wildcardMatchRegex(name), attr);
1193
+ if(ame.length > 0) {
1194
+ // NOTE: Some attributes make this expression level-dependent.
1195
+ const uca = attr.toUpperCase();
1196
+ this.is_level_based = this.is_level_based ||
1197
+ VM.level_based_attr.indexOf(uca) >= 0;
1198
+ // Pass the eligible entities along with the selector, so that
1199
+ // at run time the VM can match with the value of #.
1200
+ // NOTE: Also pass whether the entity should be pushed
1201
+ // "by reference".
1202
+ if(this.TRACE) console.log('TRACE: Variable', name,
1203
+ 'matches with tail-numbered entities:', ame,
1204
+ '\nTRACE: Attribute used:', uca);
1205
+ return [{n: name, ee: ame, a: uca, br: by_reference},
1206
+ anchor1, offset1, anchor2, offset2];
1207
+ }
1208
+ // Wildcard selector, but no number context for #.
1209
+ msg = UI.ERROR.NO_NUMBER_CONTEXT;
1210
+ }
904
1211
  }
1212
+ if(msg) {
1213
+ this.error = msg;
1214
+ return false;
1215
+ }
1216
+ // Now `obj` refers to a model entity (ABCDELPQ).
1217
+ // This parseVariable(...) function must return a tuple like this:
1218
+ // [object or vector, anchor 1, offset 1, anchor 2, offset 2]
1219
+ // because this will be passed along with the VM instruction that
1220
+ // pushes the variable on the operand stack of this expression.
905
1221
  if(obj === null) {
906
1222
  msg = `Unknown entity "${name}"`;
907
- } else if(obj === this.dataset && attr === this.selector) {
908
- // Prevent cyclic reference
909
- msg = (obj === MODEL.equations_dataset ?
910
- 'Equation' : 'Dataset modifier expression') +
911
- ' must not reference itself';
912
1223
  } else if(obj.array &&
913
1224
  (anchor1 && '#ijk'.indexOf(anchor1) < 0 ||
914
1225
  anchor2 && '#ijk'.indexOf(anchor2) < 0)) {
1226
+ // Only indices (i, j, k) and the # number can index arrays, as arrays
1227
+ // have no time dimension, while all other anchors relate to time.
915
1228
  msg = 'Invalid anchor(s) for array-type dataset ' + obj.displayName;
916
1229
  } else {
917
- // NOTE: except for array-type datasets, the default anchor is 't';
918
- // for array-type datasets in expressions for array-type datasets,
919
- // the SPECIAL anchor is '^' to indicate "use parent anchor"
920
- const default_anchor = (obj.array ?
921
- (this.dataset && this.dataset.array ? '^' : '') : 't');
922
- if(!anchor1) anchor1 = default_anchor;
923
- if(!anchor2) anchor2 = default_anchor;
924
- // If "by reference", return the object itself plus its attribute
925
- if(by_reference) {
926
- return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
927
- }
928
- if(obj === this.dataset && attr === '' && !obj.array) {
929
- // When dataset modifier expression refers to its dataset without
930
- // selector, then this is equivalent to [.] (use the series data
931
- // vector) unless it is an array, since then the series data is
932
- // not a time-scaled vector => special case
933
- args = obj.vector;
934
- } else if(attr === '') {
935
- // For all other variables, assume default attribute
936
- attr = obj.defaultAttribute;
937
- // For a dataset, check whether the VMI_push_dataset_modifier should be
938
- // used. This is the case for array-type datasets, and for datasets
939
- // having modifiers UNLESS the modeler used a vertical bar to indicate
940
- // "use the data"
941
- if(obj instanceof Dataset &&
942
- (obj.array || (!use_data && obj.selectorList.length > 0))) {
943
- if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic ||
944
- !obj.allModifiersAreStatic) {
945
- // No explicit selector => dynamic unless no time series data, and
946
- // ALL modifier expressions are static
947
- this.is_static = false;
948
- this.log('dynamic because dataset without explicit selector is used');
949
- }
950
- // NOTE: also pass the "use data" flag so that experiment selectors
951
- // will be ignored if the modeler coded the vertical bar
952
- return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
953
- }
1230
+ // If the variable denotes an equation or a dataset with a selector,
1231
+ // check whether this is the "owner" of the expression being parsed.
1232
+ let sel = '',
1233
+ xtype = '';
1234
+ if(obj instanceof DatasetModifier) {
1235
+ sel = obj.selector;
1236
+ xtype = 'Equation';
954
1237
  } else if(obj instanceof Dataset) {
955
- const mm = obj.matchingModifiers([attr]);
956
- // If attribute matches a wildcard modifier, pass the modifier
957
- // expression for this wildcard AND the specified attribute
958
- if(mm.length > 0) {
959
- const nr = endsWithDigits(attr);
960
- if(!mm[0].expression.isStatic) {
961
- this.is_static = false;
962
- this.log('dynamic because dataset modifier expression is dynamic');
963
- }
964
- // NOTE: the attribute is relevant only to set the context for #,
965
- // so if it ends on digits, put the number in the VM instruction
966
- // to save execution time
967
- return [{d: obj, s: (nr ? parseInt(nr) : attr),
968
- x: mm[0].expression}, anchor1, offset1, anchor2, offset2];
969
- }
1238
+ sel = attr;
1239
+ xtype = 'Dataset modifier expression';
970
1240
  }
971
- // NOTE: args can now be a single value, a vector, or NULL
972
- if(args === null) args = obj.attributeValue(attr);
973
- if(Array.isArray(args)) {
974
- if(obj instanceof Dataset) {
975
- if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
976
- this.is_static = false;
977
- this.log('dynamic because dataset vector is used');
978
- }
979
- } else if(VM.level_based_attr.indexOf(attr) >= 0) {
1241
+ // In variable names, wildcards are denoted as #, so also check for
1242
+ // the (unlikely) case that [eq#x] is used in the expression for a
1243
+ // wildcard equation or dataset modifier with name "eq??x".
1244
+ if(sel && (sel === this.selector ||
1245
+ sel.replace('#', '??') === this.selector)) {
1246
+ // Match indicates a cyclic reference
1247
+ msg = `${xtype} must not reference itself`;
1248
+ }
1249
+ }
1250
+ if(msg) {
1251
+ this.error = msg;
1252
+ return false;
1253
+ }
1254
+ // If `obj` is a dataset *modifier*, it must be a "normal" equation...
1255
+ if(obj instanceof DatasetModifier) {
1256
+ if(this.TRACE) console.log('TRACE: Dataset modifier "' + obj.displayName +
1257
+ '" mapped to dataset:', obj.dataset.name,
1258
+ 'and selector:', obj.selector);
1259
+ // ... so "map" it onto the equations dataset + selector...
1260
+ attr = obj.selector;
1261
+ obj = obj.dataset;
1262
+ }
1263
+ // ... so now it will be processed the same way dataset modifiers
1264
+ // are processed, especially when they have a tail number.
1265
+
1266
+ // Set default anchors in case no anchors are specified.
1267
+ // Except for array-type datasets, the default anchor is 't';
1268
+ // for array-type datasets in expressions for array-type datasets,
1269
+ // the SPECIAL anchor is '^' to indicate "use parent anchor"
1270
+ // (which will be the parent's context-sensitive number #)
1271
+ const default_anchor = (obj.array ?
1272
+ (this.dataset && this.dataset.array ? '^' : '') : 't');
1273
+ if(!anchor1) anchor1 = default_anchor;
1274
+ if(!anchor2) anchor2 = default_anchor;
1275
+
1276
+ // If "by reference", return the object itself plus its attribute
1277
+ if(by_reference) {
1278
+ if(this.TRACE) console.log('TRACE: Variable is a reference to',
1279
+ obj.displayName, '. Attribute:', attr);
1280
+ return [{r: obj, a: attr}, anchor1, offset1, anchor2, offset2];
1281
+ }
1282
+ if(obj === this.dataset && attr === '' && !obj.array) {
1283
+ // When dataset modifier expression refers to its dataset without
1284
+ // selector, then this is equivalent to [.] (use the series data
1285
+ // vector) unless it is an array, since then the series data is
1286
+ // not a time-scaled vector => special case.
1287
+ if(this.TRACE) console.log(
1288
+ 'TRACE: Dataset without selector, no array:', obj.displayName,
1289
+ 'Use vector:', obj.vector);
1290
+ arg0 = obj.vector;
1291
+ } else if(attr === '') {
1292
+ // For all other variables, assume default attribute if none specified
1293
+ attr = obj.defaultAttribute;
1294
+ // For a dataset, check whether the VMI_push_dataset_modifier should be
1295
+ // used. This is the case for array-type datasets, and for datasets
1296
+ // having modifiers UNLESS the modeler used a vertical bar to indicate
1297
+ // "use the data".
1298
+ if(obj instanceof Dataset &&
1299
+ (obj.array || (!use_data && obj.selectorList.length > 0))) {
1300
+ // No explicit selector means that this variable is dynamic if
1301
+ // the dataset has time series data, or if some of its modifier
1302
+ // expressions are dynamic.
1303
+ if(obj.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
1304
+ !obj.allModifiersAreStatic) {
980
1305
  this.is_static = false;
981
- this.log('dynamic because level-based attribute');
982
- } else {
983
- // Unusual (?) combi, so let's assume dynamic
1306
+ this.log('dynamic because dataset without explicit selector is used');
1307
+ }
1308
+ if(this.TRACE) console.log(
1309
+ 'TRACE: Dataset without explicit selector:',
1310
+ (obj.array ? 'array' : 'has modifiers'), obj.displayName,
1311
+ '\nTRACE: Use VMI_push_dataset_modifier; use-data flag:', use_data);
1312
+ // NOTE: Also pass the "use data" flag so that experiment selectors
1313
+ // will be ignored if the modeler coded the vertical bar.
1314
+ return [{d: obj, ud: use_data}, anchor1, offset1, anchor2, offset2];
1315
+ }
1316
+ } else if(obj instanceof Dataset) {
1317
+ // For datasets, the attribute must be a modifier selector, so first
1318
+ // check if this dataset has a modifier that matches `attr`.
1319
+ const mm = obj.matchingModifiers([attr]);
1320
+ if(mm.length === 0) {
1321
+ // No match indicates unknown attribute.
1322
+ this.error = `Dataset ${obj.displayName} has no modifier with selector "${attr}"`;
1323
+ return false;
1324
+ } else {
1325
+ // NOTE: Multiple matches are impossible because `attr` cannot
1326
+ // contain wildcards; hence this is a unique match, so the modifier
1327
+ // expression is known.
1328
+ const m = mm[0];
1329
+ if(!m.expression.isStatic) {
984
1330
  this.is_static = false;
985
- this.log('probably dynamic -- check below:');
986
- console.log(obj.displayName, obj, attr, args);
1331
+ this.log('dynamic because dataset modifier expression is dynamic');
1332
+ }
1333
+ // NOTE: A single match may be due to wildcard(s) in the modifier,
1334
+ // e.g., a variable [dataset|abc] matches with a modifier having
1335
+ // wildcard selector "a?b", or [dataset|a12] matches with "a*".
1336
+ // In such cases, if the selector matches an integer like "a12"
1337
+ // in the example above, this number (12) should be used as
1338
+ // number context (overriding the number of the dataset, so
1339
+ // for [datset34|a12], the number context is '12' and not '34').
1340
+ let mcn = matchingNumber(attr, m.selector);
1341
+ if(mcn === false) {
1342
+ // NOTE: When no matching number is found, `attr` may still
1343
+ // contain a ?? wildcard. If it indeed identifies a wildcard
1344
+ // equation, then "?" should be passed to the VM instruction.
1345
+ if(obj === MODEL.equations_dataset && attr.indexOf('??') >= 0) {
1346
+ mcn = '?';
1347
+ } else {
1348
+ // Ensure that `mcn` is either an integer value or FALSE.
1349
+ mcn = parseInt(UI.tailNumber(obj.name)) || this.context_number;
1350
+ }
987
1351
  }
1352
+ // Pass the dataset, the context number # (or FALSE) in place,
1353
+ // and the modifier expression.
1354
+ if(this.TRACE) console.log('TRACE: Variable is',
1355
+ (m.dataset === MODEL.equations_dataset ?
1356
+ 'an equation: ' + m.selector :
1357
+ 'a dataset with explicit selector: ' + m.displayName),
1358
+ '\nTRACE: Context number:', mcn, ' Expression:', m.expression.text);
1359
+ return [
1360
+ {d: m.dataset, s: mcn, x: m.expression},
1361
+ anchor1, offset1, anchor2, offset2];
988
1362
  }
989
- // If not a single value or vector, it must be an expression
990
- if(args === null) args = obj.attributeExpression(attr);
991
- if(args === null) {
992
- // Only NOW check whether unit balance for clusters is asked for
993
- if(cluster_balance_unit !== false && obj instanceof Cluster) {
994
- // NOTE: cluster balance ALWAYS makes expression level-based
995
- this.is_level_based = true;
996
- // @TO DO: rethink whether this will indeed also make this expression
997
- // dynamic
1363
+ }
1364
+ // NOTE: `arg0` can now be a single value, a vector, or NULL.
1365
+ if(arg0 === null) arg0 = obj.attributeValue(attr);
1366
+ if(Array.isArray(arg0)) {
1367
+ if(obj instanceof Dataset) {
1368
+ if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
998
1369
  this.is_static = false;
999
- this.log('dynamic because cluster balance is believed to be dynamic');
1000
- // NOTE: VM instructions VMI_push_var will recognize this special case
1001
- return [{c: obj, u: cluster_balance_unit},
1002
- anchor1, offset1, anchor2, offset2];
1370
+ this.log('dynamic because dataset vector is used');
1003
1371
  }
1004
- // Fall-through: invalid attribute for this object
1005
- msg = `${obj.type} entities have no attribute "${attr}"`;
1372
+ } else if(VM.level_based_attr.indexOf(attr) >= 0) {
1373
+ this.is_static = false;
1374
+ this.log('dynamic because level-based attribute');
1006
1375
  } else {
1007
- if(args instanceof Expression) {
1008
- this.is_static = this.is_static && args.isStatic;
1009
- }
1010
- args = [args, anchor1, offset1, anchor2, offset2];
1376
+ // Unusual (?) combi, so let's assume dynamic.
1377
+ this.is_static = false;
1378
+ this.log('probably dynamic -- check below:');
1379
+ console.log('ANOMALY: array for', obj.displayName, obj, attr, arg0);
1380
+ }
1381
+ if(this.TRACE) console.log('TRACE: arg[0] is a vector');
1382
+ }
1383
+ // If not a single value or vector, it must be an expression.
1384
+ if(arg0 === null) arg0 = obj.attributeExpression(attr);
1385
+ if(arg0 === null) {
1386
+ // Only NOW check whether unit balance for clusters is asked for.
1387
+ if(cluster_balance_unit !== false && obj instanceof Cluster) {
1388
+ // NOTE: Cluster balance ALWAYS makes expression level-based
1389
+ // and dynamic.
1390
+ this.is_level_based = true;
1391
+ this.is_static = false;
1392
+ this.log('dynamic because cluster balance is level-based');
1393
+ if(this.TRACE) console.log('TRACE: Variable is a balance:',
1394
+ cluster_balance_unit, 'for cluster', obj.displayName);
1395
+ // NOTE: VM instructions VMI_push_var will recognize this special case
1396
+ return [{c: obj, u: cluster_balance_unit},
1397
+ anchor1, offset1, anchor2, offset2];
1398
+ }
1399
+ // Fall-through: invalid attribute for this object
1400
+ msg = `${obj.type} entities have no attribute "${attr}"`;
1401
+ } else {
1402
+ if(arg0 instanceof Expression) {
1403
+ this.is_static = this.is_static && arg0.isStatic;
1011
1404
  }
1405
+ if(this.TRACE) console.log('TRACE: arg[0] is the expression for',
1406
+ arg0.variableName, '\nTRACE: Expression:', arg0.text);
1407
+ args = [arg0, anchor1, offset1, anchor2, offset2];
1012
1408
  }
1013
- if(msg === '') {
1014
- // Check whether the attribute is level-based (i.e., can be computed only
1015
- // after optimizing a block) while no offset is defined to use prior data
1016
- this.is_level_based = this.is_level_based ||
1017
- // NOTE: dataset modifier expressions may be level_based
1018
- obj instanceof Dataset && attr && args[0].is_level_based ||
1019
- // NOTE: assume NOT level_based if anchor & offset are specified
1020
- VM.level_based_attr.indexOf(attr) >= 0 &&
1021
- anchor1 === 't' && offset1 === 0 &&
1022
- anchor2 === 't' && offset2 === 0;
1023
- return args;
1409
+ if(msg) {
1410
+ this.error = msg;
1411
+ return false;
1024
1412
  }
1025
- this.error = msg;
1026
- return false;
1413
+ // Now `args` should be a valid argument for a VM instruction that
1414
+ // pushes an operand on the evaluation stack.
1415
+ // Check whether the attribute is level-based (i.e., can be computed
1416
+ // only after optimizing a block) while no offset is defined to use
1417
+ // prior data.
1418
+ this.is_level_based = this.is_level_based ||
1419
+ // NOTE: Dataset modifier expressions may be level-based.
1420
+ (obj instanceof Dataset && attr && arg0.is_level_based) ||
1421
+ // Assume NOT level-based if anchor & offset are specified.
1422
+ // NOTE: This is based on the assumption that advanced modelers
1423
+ // know what they are doing.
1424
+ (VM.level_based_attr.indexOf(attr) >= 0 &&
1425
+ anchor1 === 't' && offset1 === 0 &&
1426
+ anchor2 === 't' && offset2 === 0);
1427
+ return args;
1027
1428
  }
1028
1429
 
1029
- // Gets the next substring in the expression that is a valid symbol
1030
- // while advancing the position-in-text (pit) and length-of-symbol (los),
1031
- // which are used to highlight the position of a syntax error in the
1032
- // expression editor
1033
1430
  getSymbol() {
1431
+ // Gets the next substring in the expression that is a valid symbol
1432
+ // while advancing the position-in-text (`pit`) and length-of-symbol
1433
+ // (`los`), which are used to highlight the position of a syntax error
1434
+ // in the expression editor
1034
1435
  let c, f, i, l, v;
1035
1436
  this.prev_sym = this.sym;
1036
1437
  this.sym = null;
@@ -1130,7 +1531,7 @@ class ExpressionParser {
1130
1531
  if(this.selector.indexOf('*') >= 0 ||
1131
1532
  this.selector.indexOf('?') >= 0 ||
1132
1533
  this.owner.numberContext) {
1133
- this.sym = VMI_push_selector_wildcard;
1534
+ this.sym = VMI_push_contextual_number;
1134
1535
  } else {
1135
1536
  this.error = '# is undefined in this context';
1136
1537
  }
@@ -1241,6 +1642,8 @@ class ExpressionParser {
1241
1642
  // Compiles expression into array of VM instructions `code`
1242
1643
  // NOTE: always create a new code array instance, as it will typically
1243
1644
  // become the code attribute of an expression object
1645
+ if(DEBUGGING) console.log('COMPILING', this.ownerName, ':\n',
1646
+ this.expr, '\ncontext number =', this.context_number);
1244
1647
  this.code = [];
1245
1648
  // Position in text
1246
1649
  this.pit = 0;
@@ -1347,7 +1750,7 @@ class ExpressionParser {
1347
1750
  // start by popping the FALSE result of the IF condition
1348
1751
  this.code.push([VMI_pop_false, null]);
1349
1752
  }
1350
- // END of new code
1753
+ // END of new code for IF-THEN-ELSE
1351
1754
 
1352
1755
  this.op_stack.push(this.sym);
1353
1756
  } else if(this.sym !== null) {
@@ -1356,11 +1759,14 @@ class ExpressionParser {
1356
1759
  this.code.push([this.sym, null]);
1357
1760
  } else if(Array.isArray(this.sym)) {
1358
1761
  // Either a statistic, a dataset (array-type or with modifier),
1359
- // an experiment run result, or a variable
1360
- if(this.sym.length === 6) {
1762
+ // an experiment run result, or a variable.
1763
+ if(this.sym.length >= 6) {
1764
+ // 6 or 7 arguments indicates a statistic.
1361
1765
  this.code.push([VMI_push_statistic, this.sym]);
1362
1766
  } else if(this.sym[0].hasOwnProperty('d')) {
1363
1767
  this.code.push([VMI_push_dataset_modifier, this.sym]);
1768
+ } else if(this.sym[0].hasOwnProperty('ee')) {
1769
+ this.code.push([VMI_push_wildcard_entity, this.sym]);
1364
1770
  } else if(this.sym[0].hasOwnProperty('x')) {
1365
1771
  this.code.push([VMI_push_run_result, this.sym]);
1366
1772
  } else if(this.sym[0].hasOwnProperty('r')) {
@@ -1392,8 +1798,7 @@ class ExpressionParser {
1392
1798
  this.error = 'Invalid parameter list';
1393
1799
  }
1394
1800
  }
1395
- if(DEBUGGING) console.log('PARSED',
1396
- this.owner.displayName + '|' + this.attribute, ':',
1801
+ if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
1397
1802
  this.expr, this.code);
1398
1803
  }
1399
1804
 
@@ -4261,9 +4666,8 @@ class VirtualMachine {
4261
4666
  } else if(this.error_codes.indexOf(n) < 0) {
4262
4667
  err += '? value = ' + n;
4263
4668
  }
4264
- const msg = `Numeric issue: ${err} for ${this.numeric_issue}`;
4265
- this.logMessage(msg);
4266
- UI.alert(msg);
4669
+ this.logMessage(this.block_count, err);
4670
+ UI.alert(err);
4267
4671
  }
4268
4672
 
4269
4673
  get actualBlockLength() {
@@ -4844,13 +5248,18 @@ Solver status = ${json.status}`);
4844
5248
  // Show the reset button (GUI only)
4845
5249
  UI.readyToReset();
4846
5250
  // If receiver is active, report results
4847
- if(RECEIVER.solving) RECEIVER.report();
5251
+ if(RECEIVER.solving || MODEL.report_results) RECEIVER.report();
4848
5252
  // If experiment is active, signal the manager
4849
5253
  if(MODEL.running_experiment) EXPERIMENT_MANAGER.processRun();
4850
5254
  // Warn modeler if any issues occurred
4851
5255
  if(this.block_issues) {
4852
- UI.warn('Issues occurred in ' + pluralS(this.block_issues, 'block') +
4853
- ' -- details can be viewed in the monitor and by using \u25C1 \u25B7');
5256
+ let msg = 'Issues occurred in ' +
5257
+ pluralS(this.block_issues, 'block') +
5258
+ ' -- details can be viewed in the monitor';
5259
+ if(VM.issue_list.length) {
5260
+ msg += ' and by using \u25C1 \u25B7';
5261
+ }
5262
+ UI.warn(msg);
4854
5263
  UI.updateIssuePanel();
4855
5264
  }
4856
5265
  if(this.license_expired > 0) {
@@ -5230,11 +5639,11 @@ function VMI_push_second(x, empty) {
5230
5639
  pushTimeStepsPerTimeUnit(x, 'second');
5231
5640
  }
5232
5641
 
5233
- function VMI_push_selector_wildcard(x, empty) {
5234
- // Pushes the numeric value of the wildcard match
5642
+ function VMI_push_contextual_number(x, empty) {
5643
+ // Pushes the numeric value of the context-sensitive number #
5235
5644
  const n = valueOfNumberSign(x);
5236
5645
  if(DEBUGGING) {
5237
- console.log('push selector wildcard = ' + VM.sig2Dig(n));
5646
+ console.log('push contextual number: # = ' + VM.sig2Dig(n));
5238
5647
  }
5239
5648
  x.push(n);
5240
5649
  }
@@ -5242,118 +5651,121 @@ function VMI_push_selector_wildcard(x, empty) {
5242
5651
  /* VM instruction helper functions */
5243
5652
 
5244
5653
  function valueOfNumberSign(x) {
5245
- // Pushes the numeric value of the # sign in its local context
5654
+ // Pushes the numeric value of the # sign for the context of expression `x`
5246
5655
  // NOTE: this can be a wildcard match, an active experiment run selector
5247
- // ending on digits, or an entity name ending on digits
5656
+ // ending on digits, or tne number context of an entity. The latter typically
5657
+ // is the number its name or any of its prefixes ends on, but notes are
5658
+ // more "creative" and can return the number context of nearby entities.
5248
5659
  let s = 'NO SELECTOR',
5249
5660
  m = 'NO MATCH',
5250
- n = 0;
5251
- // NOTE: give wildcard selectors precedence over experiment selectors
5252
- // because wildcard selector is an immediate property of the dataset
5253
- // modifier expressions, so "closer" to the expression than the
5254
- // experiment selectors that identify the run
5255
- if(x.wildcard_number !== false) {
5256
- n = x.wildcard_number;
5661
+ n = VM.UNDEFINED;
5662
+ // NOTE: Give wildcard selectors precedence over experiment selectors
5663
+ // because a wildcard selector is an immediate property of the dataset
5664
+ // modifier expression, and hence "closer" to the expression than the
5665
+ // experiment selectors that identify the run.
5666
+ if(x.wildcard_vector_index !== false) {
5667
+ n = x.wildcard_vector_index;
5257
5668
  s = x.attribute;
5258
5669
  m = 'wildcard';
5259
5670
  } else {
5260
- if(x.object instanceof Dataset && x.attribute) {
5261
- s = x.attribute;
5262
- if(!endsWithDigits(m) && MODEL.running_experiment) {
5263
- const sl = MODEL.running_experiment.activeCombination;
5264
- if(sl) {
5265
- // Let `m` be the selector for the current experiment run
5266
- m = sl[0];
5671
+ // Check whether `x` is a dataset modifier expression.
5672
+ // NOTE: This includes equations.
5673
+ if(x.object instanceof Dataset) {
5674
+ if(x.attribute) {
5675
+ s = x.attribute;
5676
+ } else {
5677
+ // Selector may also be defined by a running experiment.
5678
+ if(MODEL.running_experiment) {
5679
+ // Let `m` be the primary selector for the current experiment run.
5680
+ const sl = MODEL.running_experiment.activeCombination;
5681
+ if(sl) {
5682
+ m = sl[0];
5683
+ s = 'experiment';
5684
+ }
5267
5685
  }
5268
5686
  }
5269
5687
  }
5270
- // If selector contains no wildcards, check if entity name ends with digits
5688
+ // If selector contains no wildcards, get number context (typically
5689
+ // inferred from a number in the name of the object)
5271
5690
  if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
5272
5691
  const d = x.object.numberContext;
5273
5692
  if(d) {
5274
- s = '*';
5693
+ s = x.object.displayName;
5275
5694
  m = d;
5695
+ n = parseInt(d);
5276
5696
  }
5277
- }
5278
- if(m !== 'NO MATCH') {
5279
- // Asterisk matches 0 or more characters, question mark 1 character
5280
- let raw = s.replace(/\*/g, '(.*)').replace(/\?/g, '(.)'),
5281
- match = m.match(new RegExp(raw));
5282
- if(match) {
5283
- // Concatenate all matching characters (need not be digits)
5284
- m = '';
5285
- for(let i = 1; i < match.length; i++) m += match[i];
5286
- // Try to convert to an integer
5287
- n = parseInt(m);
5288
- // Default to 0
5289
- if(isNaN(n)) n = 0;
5290
- }
5697
+ } else if(m !== 'NO MATCH') {
5698
+ // If matching `m` against `s` do not yield an integer string,
5699
+ // use UNDEFINED
5700
+ n = matchingNumber(m, s) || VM.UNDEFINED;
5291
5701
  }
5292
5702
  }
5703
+ // For datasets, set the parent anchor to be the context-sensitive number
5293
5704
  if(x.object instanceof Dataset) x.object.parent_anchor = n;
5294
5705
  if(DEBUGGING) {
5295
- console.log('context for # in expression for ' +
5296
- `${x.object.displayName}${x.attribute ? '|' + x.attribute : ''}
5706
+ console.log(`context for # in expression for ${x.variableName}
5297
5707
  - expression: ${x.text}
5298
- - inferred value of # ${s} => ${m} => ${VM.sig2Dig(n)}`, x.code);
5708
+ - inferred value of # ${s} => ${m} => ${n}`, x.code);
5299
5709
  }
5300
5710
  return n;
5301
5711
  }
5302
5712
 
5303
5713
  function relativeTimeStep(t, anchor, offset, dtm, x) {
5304
- // Returns the relative time step, given t, anchor, offset, delta-t-multiplier
5305
- // and the expression being evaluated (to provide context for anchor #)
5306
- // NOTE: t = 1 corresponds with first time step of simulation period
5307
-
5308
- // Anchors are checked for in order of *expected* frequency of occurrence
5714
+ // Returns the relative time step, given t, anchor, offset,
5715
+ // delta-t-multiplier and the expression being evaluated (to provide
5716
+ // context for anchor #).
5717
+ // NOTE: t = 1 corresponds with first time step of simulation period.
5718
+ // Anchors are checked for in order of *expected* frequency of occurrence.
5309
5719
  if(anchor === 't') {
5310
- // Offset relative to current time step (most likely to occur)
5720
+ // Offset relative to current time step (most likely to occur).
5311
5721
  return Math.floor(t + offset);
5312
5722
  }
5723
+ if(anchor === '#') {
5724
+ // Index: offset is added to the inferred value of the # symbol.
5725
+ return valueOfNumberSign(x) + offset;
5726
+ }
5727
+ if(anchor === '^') {
5728
+ // Inherited index (for dataset modifier expressions): offset is added
5729
+ // to the anchor of the modifier's dataset.
5730
+ if(x.object.array) {
5731
+ if(DEBUGGING) {
5732
+ console.log('Parent anchor', x.object.parent_anchor);
5733
+ }
5734
+ // NOTE: For not array-type datasets, ^ is equivalent to #
5735
+ return x.object.parent_anchor;
5736
+ }
5737
+ return valueOfNumberSign(x) + offset;
5738
+ }
5739
+ if('ijk'.indexOf(anchor) >= 0) {
5740
+ // Index: offset is added to the iterator index i, j or k.
5741
+ return valueOfIndexVariable(anchor) + offset;
5742
+ }
5313
5743
  if(anchor === 'r') {
5314
- // Offset relative to current time step, scaled to time unit of run
5744
+ // Offset relative to current time step, scaled to time unit of run.
5315
5745
  return Math.floor((t + offset) * dtm);
5316
5746
  }
5317
5747
  if(anchor === 'c') {
5318
- // Relative to start of current optimization block
5748
+ // Relative to start of current optimization block.
5319
5749
  return Math.trunc(t / MODEL.block_length) * MODEL.block_length + offset;
5320
5750
  }
5321
5751
  if(anchor === 'p') {
5322
- // Relative to start of previous optimization block
5752
+ // Relative to start of previous optimization block.
5323
5753
  return (Math.trunc(t / MODEL.block_length) - 1) * MODEL.block_length + offset;
5324
5754
  }
5325
5755
  if(anchor === 'n') {
5326
- // Relative to start of next optimization block
5756
+ // Relative to start of next optimization block.
5327
5757
  return (Math.trunc(t / MODEL.block_length) + 1) * MODEL.block_length + offset;
5328
5758
  }
5329
5759
  if(anchor === 'l') {
5330
- // Last: offset relative to the last index in the vector
5760
+ // Last: offset relative to the last index in the vector.
5331
5761
  return MODEL.end_period - MODEL.start_period + 1 + offset;
5332
5762
  }
5333
5763
  if(anchor === 's') {
5334
- // Scaled: offset is scaled to time unit of run
5764
+ // Scaled: offset is scaled to time unit of run.
5335
5765
  return Math.floor(offset * dtm);
5336
5766
  }
5337
- if('ijk'.indexOf(anchor) >= 0) {
5338
- // Index: offset is added to the iterator index i, j or k
5339
- return valueOfIndexVariable(anchor) + offset;
5340
- }
5341
- if(anchor === '#') {
5342
- // Index: offset is added to the inferred value of the # symbol
5343
- return valueOfNumberSign(x) + offset;
5344
- }
5345
- if(anchor === '^') {
5346
- // Inherited index: offset is added to anchor of "parent" dataset
5347
- if(x.object.array) {
5348
- if(DEBUGGING) {
5349
- console.log('Parent anchor', x.object.parent_anchor);
5350
- }
5351
- return x.object.parent_anchor;
5352
- }
5353
- return valueOfNumberSign(x) + offset;
5354
- }
5355
- // Fall-through: offset relative to the initial value index (0)
5356
- // NOTE: this also applies to anchor f (First)
5767
+ // Fall-through: offset relative to the initial value index (0).
5768
+ // NOTE: this also applies to anchor f (First).
5357
5769
  return offset;
5358
5770
  }
5359
5771
 
@@ -5433,9 +5845,9 @@ function VMI_push_var(x, args) {
5433
5845
  }
5434
5846
 
5435
5847
  function VMI_push_entity(x, args) {
5436
- // Pushes a special "entity reference" object based on `args`, being the list
5437
- // [obj, anchor1, offset1, anchor2, offset2] where `obj` has the format
5438
- // {r: entity object, a: attribute}
5848
+ // Pushes a special "entity reference" object based on `args`, being the
5849
+ // list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
5850
+ // format {r: entity object, a: attribute}
5439
5851
  // The object that is pushed on the stack passes the entity, the attribute
5440
5852
  // to use, and the time interval
5441
5853
  const
@@ -5451,35 +5863,92 @@ function VMI_push_entity(x, args) {
5451
5863
  x.push(er);
5452
5864
  }
5453
5865
 
5866
+ function VMI_push_wildcard_entity(x, args) {
5867
+ // Pushes the value of (or reference to) an entity attribute, based on
5868
+ // `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
5869
+ // where `obj` has the format {ee: list of eligible entities,
5870
+ // n: name (with wildcard #), a: attribute, br: by reference (boolean)}
5871
+ // First select the first entity in `ee` that matches the wildcard vector
5872
+ // index of the expression `x` being executed.
5873
+ const el = args[0].ee;
5874
+ let nn = args[0].n.replace('#', x.wildcard_vector_index),
5875
+ obj = null;
5876
+ for(let i = 0; !obj && i < el.length; i++) {
5877
+ if(el[i].name === nn) obj = el[i];
5878
+ }
5879
+ // If no match, then this indicates a bad reference.
5880
+ if(!obj) {
5881
+ console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
5882
+ x.push(VM.BAD_REF);
5883
+ return;
5884
+ }
5885
+ // Otherwise, if args[0] indicates "by reference", then VMI_push_entity
5886
+ // can be called with the appropriate parameters.
5887
+ const attr = args[0].a || obj.defaultAttribute;
5888
+ if(args[0].br) {
5889
+ VMI_push_entity(x, {r: obj, a: attr});
5890
+ return;
5891
+ }
5892
+ // Otherwise, if the entity is a dataset modifier, this must be an
5893
+ // equation (identified by its name, not by a modifier selector) so
5894
+ // push the result of this equation using the wildcard vector index
5895
+ // of the expression that is being computed.
5896
+ if(obj instanceof DatasetModifier) {
5897
+ VMI_push_dataset_modifier(x,
5898
+ [{d: obj.dataset, s: x.wildcard_vector_index, x: obj.expression},
5899
+ args[1], args[2], args[3], args[4]]);
5900
+ return;
5901
+ }
5902
+ // Otherwise, it can be a vector type attribute or an expression.
5903
+ let v = obj.attributeValue(attr);
5904
+ if(v === null) v = obj.attributeExpression(attr);
5905
+ // If no match, then this indicates a bad reference.
5906
+ if(v === null) {
5907
+ console.log(`ERROR: bad variable "${obj.displayName}" with attribute "${attr}"`);
5908
+ x.push(VM.BAD_REF);
5909
+ return;
5910
+ }
5911
+ // Otherwise, VMI_push_var can be called with `v` as first argument.
5912
+ VMI_push_var(x, [v, args[1], args[2], args[3], args[4]]);
5913
+ }
5914
+
5454
5915
  function VMI_push_dataset_modifier(x, args) {
5455
- // NOTE: the first argument specifies the dataset `d` and (optionally!) the
5456
- // modifier selector `s`, and expression `x`;
5457
- // if not specified, the modifier to be used must be inferred from the
5458
- // running experiment UNLESS the field `ud` ("use data") is defined for the
5459
- // first argument and equals TRUE
5916
+ // NOTE: the first argument specifies the dataset `d` and (optionally!)
5917
+ // the modifier selector `s`, and expression `x`.
5918
+ // If `s` is a number, then the result of `x` must be computed with
5919
+ // this number als wildcard number.
5920
+ // If `s` is not specified, the modifier to be used must be inferred from
5921
+ // the running experiment UNLESS the field `ud` ("use data") is defined
5922
+ // for the first argument, and evaluates as TRUE.
5923
+ // NOTE: Ensure that number 0 is not interpreted as FALSE.
5924
+ let wcnr = (args[0].s === undefined ? false : args[0].s);
5460
5925
  const
5461
5926
  ds = args[0].d,
5462
- // NOTE: `ud`, `ms`, and `mx` may get value "undefined"!
5463
- ud = args[0].ud,
5464
- ms = args[0].s,
5465
- mx = args[0].x,
5466
- // NOTE: use the "local" time step for expression x, i.e., the top value
5467
- // of the expression's time step stack `x.step`
5927
+ ud = args[0].ud || false,
5928
+ mx = args[0].x || null,
5929
+ // NOTE: Use the "local" time step for expression x, i.e., the top
5930
+ // value of the expression's time step stack `x.step`.
5468
5931
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5469
5932
  args[1], args[2], args[3], args[4], 1, x);
5933
+ // NOTE: Sanity check to facilitate debugging; if no dataset is provided,
5934
+ // the script will still break at the LET statement below.
5935
+ if(!ds) console.log('ERROR: VMI_push_dataset_modifier without dataset',
5936
+ x.variableName, x.code);
5470
5937
  let t = tot[0],
5938
+ // By default, use the vector of the dataset to compute the value.
5471
5939
  obj = ds.vector;
5940
+
5472
5941
  if(ds.array) {
5473
- // For array, do NOT adjust "index" t to model run period
5474
- // NOTE: indices start at 1, but array is zero-based
5942
+ // For array-type datasets, do NOT adjust "index" t to model run period.
5943
+ // NOTE: Indices start at 1, but arrays are zero-based, so subtract 1.
5475
5944
  t--;
5476
- // When periodic, adjust t to fall within the vector length
5945
+ // When data is periodic, adjust `t` to fall within the vector length.
5477
5946
  if(ds.periodic && obj.length > 0) {
5478
5947
  t = t % obj.length;
5479
5948
  if(t < 0) t += obj.length;
5480
5949
  }
5481
5950
  if(args[1] === '#' || args[3] === '#') {
5482
- // NOTE: add 1, since (parent) anchors are 1-based
5951
+ // NOTE: Add 1 because (parent) anchors are 1-based.
5483
5952
  ds.parent_anchor = t + 1;
5484
5953
  if(DEBUGGING) {
5485
5954
  console.log('ANCHOR for:', ds.displayName, '=', ds.parent_anchor);
@@ -5487,52 +5956,67 @@ function VMI_push_dataset_modifier(x, args) {
5487
5956
  }
5488
5957
  } else {
5489
5958
  // Negative time step is evaluated as t = 0 (initial value), t beyond
5490
- // optimization period is evaluated as its last time step
5491
- // NOTE: By default, use the dataset vector value for t
5959
+ // optimization period is evaluated as its last time step.
5960
+ // NOTE: By default, use the dataset vector value for `t`.
5492
5961
  t = Math.max(0, Math.min(
5493
5962
  MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
5494
5963
  }
5495
- if(ms) {
5496
- // If modifier selector is specified, use the associated expression
5964
+ if(wcnr !== false || ds === MODEL.equations_dataset) {
5965
+ // If a wildcard number is specified, or when a normal (not-wildcard)
5966
+ // equation is referenced, use the modifier expression to calculate
5967
+ // the value to push.
5497
5968
  obj = mx;
5498
- } else if(!ud) {
5969
+ // If '?' is passed as wildcard number, use the wildcard vector index
5970
+ // of the expression that is being computed (this may be FALSE).
5971
+ if(wcnr === '?') {
5972
+ wcnr = x.wildcard_vector_index;
5973
+ }
5974
+ } else if(!ud ) {
5975
+ // In no selector and not "use data", check whether a running experiment
5976
+ // defines the expression to use. If not, `obj` will be the dataset
5977
+ // vector (so same as when "use data" is set).
5499
5978
  obj = ds.activeModifierExpression;
5500
5979
  }
5501
- // By default, use the dataset default value
5980
+ if(!obj) {
5981
+ console.log('ANOMALY: no object. obj, wcnr, args, x', obj, wcnr, args, x);
5982
+ }
5983
+ // Now determine what value `v` should be pushed onto the expression stack.
5984
+ // By default, use the dataset default value.
5502
5985
  let v = ds.defaultValue,
5503
- obstr = (obj instanceof Expression ?
5504
- obj.text : '[' + obj.toString() + ']');
5986
+ // NOTE: `obstr` is used only when debugging, to log `obj` in human-
5987
+ // readable format.
5988
+ obstr = (obj instanceof Expression ? obj.text : `[${obj.toString()}]`);
5505
5989
  if(Array.isArray(obj)) {
5506
- // Object is a vector
5990
+ // `obj` is a vector.
5507
5991
  if(t >= 0 && t < obj.length) {
5508
5992
  v = obj[t];
5509
5993
  } else if(ds.array && t >= obj.length) {
5510
- // Set error value if array index is out of bounds
5994
+ // Set error value if array index is out of bounds.
5511
5995
  v = VM.ARRAY_INDEX;
5512
5996
  VM.out_of_bounds_array = ds.displayName;
5513
- VM.out_of_bounds_msg = `Index ${t + 1} not in array dataset ` +
5997
+ VM.out_of_bounds_msg = `Index ${VM.sig2Dig(t + 1)} not in array dataset ` +
5514
5998
  `${ds.displayName}, which has length ${obj.length}`;
5515
5999
  console.log(VM.out_of_bounds_msg);
5516
6000
  }
5517
- // Fall through: no change to `v` => dataset default value is pushed
6001
+ // Fall through: no change to `v` => dataset default value is pushed.
5518
6002
  } else {
5519
- // Object is an expression
5520
- // NOTE: readjust t when obj is expression for *array-type* dataset modifier
6003
+ // `obj` is an expression.
6004
+ // NOTE: Readjust `t` when `obj` is an expression for an *array-type*
6005
+ // dataset modifier.
5521
6006
  if(obj.object instanceof Dataset && obj.object.array) t++;
5522
- // Pass modifier selector (if specified; may be undefined) so that result
5523
- // will be recomputed with this selector as context for #
5524
- // Also pass equation parameters (if specified; may be undefined) so that
5525
- // these will be used as the actual values of the formal parameters
5526
- v = obj.result(t, ms);
6007
+ // Pass modifier selector (if specified; may be FALSE) so that result
6008
+ // will be recomputed with this selector as context for #.
6009
+ v = obj.result(t, wcnr);
5527
6010
  }
5528
- // Trace only now that time step t has been computed
6011
+ // Trace only now that time step t has been computed.
5529
6012
  if(DEBUGGING) {
5530
6013
  console.log('push dataset modifier:', obstr,
5531
- tot[1] + (tot[2] ? ':' + tot[2] : ''), ' value = ', VM.sig4Dig(v));
5532
- console.log(' --', x.text, ' for owner ', x.object.displayName, x.attribute);
6014
+ tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v),
6015
+ '\nExpression: ', x.text, '\nVariable:', x.variableName,
6016
+ 'Context number:', wcnr);
5533
6017
  }
5534
- // NOTE: if value is exceptional ("undefined", etc.), use default value
5535
- if(v >= VM.PLUS_INFINITY) v = ds.defaultValue;
6018
+ // NOTE: If value is exceptional ("undefined", etc.), use default value.
6019
+ // DEPRECATED !! if(v >= VM.PLUS_INFINITY) v = ds.defaultValue;
5536
6020
  // Finally, push the value onto the expression stack
5537
6021
  x.push(v);
5538
6022
  }
@@ -5654,8 +6138,8 @@ function VMI_push_statistic(x, args) {
5654
6138
  // of MAX, MEAN, MIN, N, SD, SUM, and VAR, and `list` is a list of vectors
5655
6139
  // NOTE: each statistic may also be "suffixed" by NZ to denote that only
5656
6140
  // non-zero numbers should be considered
5657
- let stat = args[0];
5658
- const list = args[1];
6141
+ let stat = args[0],
6142
+ list = args[1];
5659
6143
  if(!list) {
5660
6144
  // Special case: null or empty list => push zero
5661
6145
  if(DEBUGGING) {
@@ -5664,8 +6148,17 @@ function VMI_push_statistic(x, args) {
5664
6148
  x.push(0);
5665
6149
  return;
5666
6150
  }
5667
- const anchor1 = args[2], offset1 = args[3],
5668
- anchor2 = args[4], offset2 = args[5];
6151
+ const
6152
+ anchor1 = args[2],
6153
+ offset1 = args[3],
6154
+ anchor2 = args[4],
6155
+ offset2 = args[5],
6156
+ wdict = args[6] || false;
6157
+ // If defined, the wildcard dictionary provides subsets of `list`
6158
+ // to be used when the wildcard number of the expression is set.
6159
+ if(wdict && x.wildcard_vector_index !== false) {
6160
+ list = wdict[x.wildcard_vector_index] || [];
6161
+ }
5669
6162
  // If no list specified, the result is undefined
5670
6163
  if(!Array.isArray(list) || list.length === 0) {
5671
6164
  x.push(VM.UNDEFINED);
@@ -5724,13 +6217,20 @@ function VMI_push_statistic(x, args) {
5724
6217
  }
5725
6218
  // Push value unless it is zero and NZ is TRUE, or if it is undefined
5726
6219
  // (this will occur when a variable has been deleted)
5727
- if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) < VM.NEAR_ZERO)) {
6220
+ if(v <= VM.PLUS_INFINITY && (!nz || Math.abs(v) > VM.NEAR_ZERO)) {
5728
6221
  vlist.push(v);
5729
6222
  }
5730
6223
  }
5731
6224
  }
5732
- // If no values remain, the result is zero (as ALL values were zero)
5733
- const n = vlist.length;
6225
+ const
6226
+ n = vlist.length,
6227
+ // NOTE: count is the number of values used in the statistic
6228
+ count = (nz ? n : list.length);
6229
+ if(stat === 'N') {
6230
+ x.push(count);
6231
+ return;
6232
+ }
6233
+ // If no non-zero values remain, all statistics are zero (as ALL values were zero)
5734
6234
  if(n === 0) {
5735
6235
  x.push(0);
5736
6236
  return;
@@ -5744,10 +6244,6 @@ function VMI_push_statistic(x, args) {
5744
6244
  x.push(Math.max(...vlist));
5745
6245
  return;
5746
6246
  }
5747
- if(stat === 'N') {
5748
- x.push(n);
5749
- return;
5750
- }
5751
6247
  // For all remaining statistics, the sum must be calculated
5752
6248
  let sum = 0;
5753
6249
  for(let i = 0; i < n; i++) {
@@ -5759,7 +6255,7 @@ function VMI_push_statistic(x, args) {
5759
6255
  }
5760
6256
  // Now statistic must be either MEAN, SD or VAR, so start with the mean
5761
6257
  // NOTE: no more need to check for division by zero
5762
- const mean = sum / n;
6258
+ const mean = sum / count;
5763
6259
  if(stat === 'MEAN') {
5764
6260
  x.push(mean);
5765
6261
  return;
@@ -5770,11 +6266,11 @@ function VMI_push_statistic(x, args) {
5770
6266
  sumsq += Math.pow(vlist[i] - mean, 2);
5771
6267
  }
5772
6268
  if(stat === 'VAR') {
5773
- x.push(sumsq / n);
6269
+ x.push(sumsq / count);
5774
6270
  return;
5775
6271
  }
5776
6272
  if(stat === 'SD') {
5777
- x.push(Math.sqrt(sumsq / n));
6273
+ x.push(Math.sqrt(sumsq / count));
5778
6274
  return;
5779
6275
  }
5780
6276
  // Fall-through: unknown statistic
@@ -7145,7 +7641,7 @@ const
7145
7641
  VMI_push_look_ahead, VMI_push_round, VMI_push_last_round,
7146
7642
  VMI_push_number_of_rounds, VMI_push_run_number, VMI_push_number_of_runs,
7147
7643
  VMI_push_random, VMI_push_delta_t, VMI_push_true, VMI_push_false,
7148
- VMI_push_pi, VMI_push_infinity, VMI_push_selector_wildcard,
7644
+ VMI_push_pi, VMI_push_infinity, VMI_push_contextual_number,
7149
7645
  VMI_push_i, VMI_push_j, VMI_push_k,
7150
7646
  VMI_push_year, VMI_push_week, VMI_push_day, VMI_push_hour,
7151
7647
  VMI_push_minute, VMI_push_second],