linny-r 1.5.8 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,7 +33,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
33
  SOFTWARE.
34
34
  */
35
35
 
36
- // CLASS Expression (for all potentially time-dependent model parameters)
36
+ // CLASS Expression
37
37
  class Expression {
38
38
  constructor(obj, attr, text) {
39
39
  // Expressions are typically defined for some attribute of some
@@ -42,12 +42,22 @@ class Expression {
42
42
  this.object = obj;
43
43
  this.attribute = attr;
44
44
  this.text = text;
45
+ // For method expressions only: the object to which they apply.
46
+ // This will be set dynamically by the VMI_push_method instruction.
47
+ this.method_object = null;
48
+ // Likewise, VMI_push_method may set the method object prefix if
49
+ // the specific entity needs to be inferred dynamically.
50
+ this.method_object_prefix = '';
45
51
  // A stack for local time step (to allow lazy evaluation).
46
52
  this.step = [];
47
53
  // An operand stack for computation (elements must be numeric).
48
54
  this.stack = [];
49
55
  // NOTE: code = NULL indicates: not compiled yet.
50
56
  this.code = null;
57
+ // Error message when last compiled.
58
+ this.compile_issue = '';
59
+ // Error message when last computed.
60
+ this.compute_issue = '';
51
61
  // NOTE: Use a semaphore to prevent cyclic recursion.
52
62
  this.compiling = false;
53
63
  // While compiling, check whether any operand depends on time.
@@ -69,28 +79,67 @@ class Expression {
69
79
  // by VMI_push_dataset_modifier (read and/or write) and by
70
80
  // VMI_push_contextual_number (read only).
71
81
  this.wildcard_vector_index = false;
82
+ // Method expressions are similar to wildcard expressions, but have
83
+ // not natural numbering scheme. By keeping a list of all objects
84
+ // to which a method has been applied, the index of such an object
85
+ // in this list serves as the vector number.
86
+ this.method_object_list = [];
72
87
  // Special instructions can store results as cache properties to save
73
88
  // (re)computation time; cache is cleared when expression is reset.
74
89
  this.cache = {};
75
90
  }
76
91
 
77
92
  get isWildcardExpression() {
78
- // Returns TRUE if the owner is a dataset, and the attribute contains
93
+ // Return TRUE if the owner is a dataset, and the attribute contains
79
94
  // wildcards.
80
95
  return this.object instanceof Dataset &&
81
96
  this.object.isWildcardSelector(this.attribute);
82
97
  }
83
98
 
99
+ get isMethod() {
100
+ // Return TRUE if the owner is the equations dataset, and the
101
+ // attribute starts with a colon.
102
+ return this.object === MODEL.equations_dataset &&
103
+ this.attribute.startsWith(':');
104
+ }
105
+
106
+ get noMethodObject() {
107
+ // Return TRUE if expression is a method that does not apply to
108
+ // any entity group.
109
+ return this.isMethod && !(this.eligible_prefixes &&
110
+ Object.keys(this.eligible_prefixes).length > 0);
111
+ }
112
+
113
+ matchWithEligiblePrefixes(pref) {
114
+ // Return the entity for which `pref` matches with an eligible prefix
115
+ // of this expression.
116
+ // NOTE: This expression must have been compiled to "know" its
117
+ // eligible prefixes.
118
+ this.compile();
119
+ // NOTE: Prevent infinite recursion, but do not generate a warning;
120
+ // the expression parser will do this.
121
+ if(this.compiling || !this.eligible_prefixes) return false;
122
+ return this.eligible_prefixes[pref.toLowerCase()] || false;
123
+ }
124
+
125
+ isEligible(prefix) {
126
+ // Return TRUE if `prefix` is an eligible prefix for this method.
127
+ if(this.eligible_prefixes) {
128
+ return this.eligible_prefixes[prefix.toLowerCase()] || false;
129
+ }
130
+ return false;
131
+ }
132
+
84
133
  get variableName() {
85
- // Return the name of the variable computed by this expression
134
+ // Return the name of the variable computed by this expression.
86
135
  if(this.object === MODEL.equations_dataset) return 'equation ' + this.attribute;
87
136
  if(this.object) return this.object.displayName + UI.OA_SEPARATOR + this.attribute;
88
137
  return 'Unknown variable (no object)';
89
138
  }
90
139
 
91
140
  get timeStepDuration() {
92
- // Returns dt for dataset if this is a dataset modifier expression;
93
- // otherwise dt for the current model
141
+ // Return dt for dataset if this is a dataset modifier expression;
142
+ // otherwise dt for the current model.
94
143
  if(this.object instanceof Dataset) {
95
144
  return this.object.time_scale * VM.time_unit_values[this.object.time_unit];
96
145
  }
@@ -98,47 +147,39 @@ class Expression {
98
147
  }
99
148
 
100
149
  get referencedEntities() {
101
- // Returns a list of entities referenced in this expression.
102
- if(this.text.indexOf('[') < 0) return [];
103
- const
104
- el = [],
105
- ml = [...this.text.matchAll(/\[(\{[^\}]+\}){0,1}([^\]]+)\]/g)];
106
- for(let i = 0; i < ml.length; i++) {
107
- const n = ml[i][2].trim();
108
- let sep = n.lastIndexOf('|');
109
- if(sep < 0) sep = n.lastIndexOf('@');
110
- const
111
- en = (sep < 0 ? n : n.substring(0, sep)),
112
- e = MODEL.objectByName(en.trim());
113
- if(e) addDistinct(e, el);
114
- }
115
- return el;
150
+ // Return a list of entities referenced in this expression.
151
+ return MODEL.entitiesInString(this.text);
116
152
  }
117
-
153
+
118
154
  update(parser) {
119
- // Must be called after successful compilation by the expression parser
155
+ // Must be called after successful compilation by the expression parser.
120
156
  this.text = parser.expr;
121
157
  this.code = parser.code;
122
- // NOTE: overrule `is_static` to make that the "initial level" attribute
123
- // is always evaluated for t=1
158
+ this.eligible_prefixes = parser.eligible_prefixes;
159
+ // NOTE: Overrule `is_static` to make that the "initial level" attribute
160
+ // is always evaluated for t=1.
124
161
  this.is_static = (this.attribute === 'IL' ? true : parser.is_static);
125
162
  this.is_level_based = parser.is_level_based;
126
163
  this.reset();
127
164
  }
128
165
 
129
166
  reset(default_value=VM.NOT_COMPUTED) {
130
- // Clears result of previous computation (if any)
167
+ // Clear result of previous computation (if any).
168
+ this.method_object = null;
169
+ this.compile_issue = '';
170
+ this.compute_issue = '';
131
171
  this.step.length = 0;
132
172
  this.stack.length = 0;
133
173
  this.wildcard_vectors = {};
134
174
  this.wildcard_vector_index = false;
175
+ this.method_object_list.length = 0;
135
176
  this.cache = {};
136
177
  this.compile(); // if(!this.compiled) REMOVED to ensure correct isStatic!!
137
178
  // Static expressions only need a vector with one element (having index 0)
138
179
  if(this.is_static) {
139
- // NOTE: empty expressions (i.e., no text) may default to different
180
+ // NOTE: Empty expressions (i.e., no text) may default to different
140
181
  // values: typically 0 for lower bounds, infinite for upper process
141
- // bounds, etc., so this value must be passed as parameter
182
+ // bounds, etc., so this value must be passed as parameter.
142
183
  this.vector.length = 1;
143
184
  if(this.text.length === 0) {
144
185
  this.vector[0] = default_value;
@@ -180,6 +221,7 @@ class Expression {
180
221
  }
181
222
  this.update(xp);
182
223
  } else {
224
+ this.compile_issue = xp.error;
183
225
  this.is_static = true;
184
226
  this.vector.length = 0;
185
227
  this.vector[0] = VM.INVALID;
@@ -271,9 +313,22 @@ class Expression {
271
313
 
272
314
  chooseVector(number) {
273
315
  // 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;
316
+ // NOTE: Static wildcard and method expressions must also choose a vector!
317
+ if((typeof number !== 'number' ||
318
+ (this.isStatic && !this.isWildcardExpression)) &&
319
+ !this.isMethod) return this.vector;
320
+ // Method expressions are not "numbered" but differentiate by the
321
+ // entity to which they are applied. Their "vector number" is then
322
+ // inferred by looking up this entity in a method object list.
323
+ const mop = (this.method_object && this.method_object.identifier) ||
324
+ this.method_object_prefix || '';
325
+ if(mop) {
326
+ number = this.method_object_list.indexOf(mop);
327
+ if(number < 0) {
328
+ this.method_object_list.push(mop);
329
+ number = this.method_object_list.length - 1;
330
+ }
331
+ }
277
332
  // Use the vector for the wildcard number (create it if necessary).
278
333
  if(!this.wildcard_vectors.hasOwnProperty(number)) {
279
334
  this.wildcard_vectors[number] = [];
@@ -358,8 +413,10 @@ class Expression {
358
413
  this.wildcard_vector_index = false;
359
414
  // If error, display the call stack (only once).
360
415
  // NOTE: "undefined", "not computed" and "still computing" are NOT
361
- // problematic unless they result in an error (stack over/underflow)
416
+ // problematic unless they result in an error (stack over/underflow).
362
417
  if(v[t] <= VM.ERROR) {
418
+ // NOTE: Record the first issue that is detected.
419
+ if(!this.compute_issue) this.compute_issue = VM.errorMessage(v[t]);
363
420
  MONITOR.showCallStack(t);
364
421
  VM.logCallStack(t);
365
422
  }
@@ -397,17 +454,19 @@ class Expression {
397
454
  }
398
455
 
399
456
  get asAttribute() {
400
- // Returns the result for the current time step if the model has been solved
401
- // (special values as human-readable string), or the expression as text
457
+ // Return the result for the current time step if the model has been
458
+ // solved (with special values as human-readable string), or the
459
+ // expression as text.
402
460
  if(!(MODEL.solved || this.isStatic)) return this.text;
403
461
  const sv = VM.specialValue(this.result(MODEL.t))[1];
404
- // NOTE: ?? is replaced by empty string (undefined => empty cell in Excel)
462
+ // NOTE: ?? is replaced by empty string to facilitate copy/paste to
463
+ // Excel-like spreadsheets, where an empty cell indicates "undefined".
405
464
  if(sv === '\u2047') return '';
406
465
  return sv;
407
466
  }
408
467
 
409
468
  push(value) {
410
- // Pushes a numeric value onto the computation stack
469
+ // Push a numeric value onto the computation stack.
411
470
  if(this.stack.length >= VM.MAX_STACK) {
412
471
  this.trace('STACK OVERFLOW');
413
472
  this.stack.push(VM.OVERFLOW);
@@ -419,7 +478,7 @@ class Expression {
419
478
  }
420
479
 
421
480
  top(no_check=false) {
422
- // Returns the top element of the stack, or FALSE if the stack was empty
481
+ // Return the top element of the stack, or FALSE if the stack was empty.
423
482
  if(this.stack.length < 1) {
424
483
  this.trace('TOP: UNDERFLOW');
425
484
  this.stack = [VM.UNDERFLOW];
@@ -427,12 +486,12 @@ class Expression {
427
486
  return false;
428
487
  }
429
488
  const top = this.stack[this.stack.length - 1];
430
- // Check for errors, "undefined", "not computed", and "still computing"
489
+ // Check for errors, "undefined", "not computed", and "still computing".
431
490
  if(top < VM.MINUS_INFINITY || top > VM.EXCEPTION) {
432
- // If error or exception, ignore UNDEFINED if `no_check` is TRUE)
491
+ // If error or exception, ignore UNDEFINED if `no_check` is TRUE.
433
492
  if(no_check && top <= VM.UNDEFINED) return top;
434
493
  // Otherwise, leave the special value on top of the stack, and
435
- // return FALSE so that the VM instruction will not alter it
494
+ // return FALSE so that the VM instruction will not alter it.
436
495
  this.trace(
437
496
  VM.errorMessage(top) + ' at top of stack: ' + this.stack.toString());
438
497
  return false;
@@ -441,26 +500,26 @@ class Expression {
441
500
  }
442
501
 
443
502
  pop(no_check=false) {
444
- // Returns the two top elements A and B as [A, B] after popping the top
445
- // element B from the stack, or FALSE if the stack contains fewer than 2
446
- // elements, or if A and/or B are error values
503
+ // Return the two top elements A and B as [A, B] after popping the
504
+ // top element B from the stack, or FALSE if the stack contains fewer
505
+ // than 2 elements, or if A and/or B are error values.
447
506
  if(this.stack.length < 2) {
448
507
  this.trace('POP: UNDERFLOW');
449
508
  this.stack.push(VM.UNDERFLOW);
450
509
  this.computed = true;
451
510
  return false;
452
511
  }
453
- // Get the top two numbers on the stack as a list
512
+ // Get the top two numbers on the stack as a list.
454
513
  const dyad = this.stack.slice(-2);
455
- // Pop only the top one
514
+ // Pop only the top one.
456
515
  this.stack.pop();
457
- // Check whether either number is an error code
516
+ // Check whether either number is an error code.
458
517
  let check = Math.min(dyad[0], dyad[1]);
459
518
  if(check < VM.MINUS_INFINITY &&
460
519
  // Exception: "array index out of bounds" error may also be
461
520
  // ignored by using the | operator.
462
521
  !(no_check && check === VM.ARRAY_INDEX)) {
463
- // If error, leave the severest error on top of the stack
522
+ // If error, leave the most severe error on top of the stack.
464
523
  this.retop(check);
465
524
  this.trace(VM.errorMessage(check) + ' in dyad: ' + dyad.toString());
466
525
  return false;
@@ -482,38 +541,40 @@ class Expression {
482
541
  this.trace(VM.errorMessage(check) + ' in dyad: ' + dyad.toString());
483
542
  return false;
484
543
  }
485
- // No problem(s)? Then return the dyad.
544
+ // No issue(s)? Then return the dyad.
486
545
  return dyad;
487
546
  }
488
547
 
489
548
  retop(value) {
490
- // Replaces the top element of the stack by the new value
491
- // NOTE: does not check the stack length, as this instruction typically
492
- // follows a TOP or POP instruction
549
+ // Replace the top element of the stack by the new value.
550
+ // NOTE: Do not check the stack length, as this instruction typically
551
+ // follows a TOP or POP instruction.
493
552
  this.stack[this.stack.length - 1] = value;
494
553
  return true;
495
554
  }
496
555
 
497
556
  replaceAttribute(re, a1, a2) {
498
- // Replaces occurrences of attribute `a1` by `a2` for all variables that
499
- // match the regular expression `re`
557
+ // Replace occurrences of attribute `a1` by `a2` for all variables
558
+ // that match the regular expression `re`.
500
559
  let n = 0;
501
560
  const matches = this.text.match(re);
502
561
  if(matches) {
503
- // Match is case-insensitive, so check each for matching case of attribute
562
+ // Match is case-insensitive, so check each for matching case of
563
+ // attribute.
504
564
  for(let i = 0; i < matches.length; i++) {
505
565
  const
506
566
  m = matches[i],
507
567
  e = m.split('|');
508
- // Let `ao` be attribute + offset (if any) without right bracket
568
+ // Let `ao` be attribute + offset (if any) without right bracket.
509
569
  let ao = e.pop().slice(0, -1),
510
- // Then also trim offset and spaces)
570
+ // Then also trim offset and spaces.
511
571
  a = ao.split('@')[0].trim();
512
- // Check if `a` (without bracket and without spaces) indeed matches `a1`
572
+ // Check whether `a` (without bracket and without spaces) indeed
573
+ // matches `a1`.
513
574
  if(a === a1) {
514
575
  // If so, append new attribute plus offset plus right bracket...
515
576
  e.push(ao.replace(a, a2) + ']');
516
- // ... and replace the original match by the ensemble
577
+ // ... and replace the original match by the ensemble.
517
578
  this.text = this.text.replace(m, e.join('|'));
518
579
  n += 1;
519
580
  }
@@ -564,13 +625,20 @@ class ExpressionParser {
564
625
  constructor(text, owner=null, attribute='') {
565
626
  // Setting TRACE to TRUE will log parsing information to the console.
566
627
  this.TRACE = false;
567
- // `text` is the expression string to be parsed.
568
- this.expr = text;
569
628
  // NOTE: When expressions for dataset modifiers or equations are
570
629
  // parsed, `owner` is their dataset, and `attribute` is their name.
571
630
  this.owner = owner;
572
631
  this.owner_prefix = '';
573
632
  this.attribute = attribute;
633
+ // `text` is the expression string to be parsed.
634
+ this.expr = text;
635
+ this.expansions = [];
636
+ // Initialize eligible entities as NULL so it will be initialized
637
+ // when the first method expression variable is parsed.
638
+ this.eligible_prefixes = null;
639
+ // When parsing a method expression, keep a list of all attributes
640
+ // used in variables.
641
+ this.method_attributes = [];
574
642
  this.dataset = null;
575
643
  this.dot = null;
576
644
  this.selector = '';
@@ -652,7 +720,7 @@ class ExpressionParser {
652
720
  // Set the above IF condition to FALSE to profile dynamic expressions.
653
721
  console.log(`Expression for ${this.ownerName}: ${this.expr}\n${msg}`);
654
722
  }
655
-
723
+
656
724
  // The method parseVariable(name) checks whether `name` fits this pattern:
657
725
  // {run}statistic$entity|attribute@offset_1:offset_2
658
726
  // allowing spaces within {run} and around | and @ and :
@@ -667,8 +735,8 @@ class ExpressionParser {
667
735
  // NOTE: this array is used as argument for the virtual machine instructions
668
736
  // VMI_push_var, VMI_push_statistic and VMI_push_run_result.
669
737
  parseVariable(name) {
670
- // Reduce whitespace to single space.
671
- name = name.replace(/\s+/g, ' ');
738
+ // Remove non-functional whitespace.
739
+ name = name.replace(/\s+/g, ' ').trim();
672
740
 
673
741
  // For debugging, TRACE can be used to log to the console for
674
742
  // specific expressions and/or variables, for example:
@@ -975,23 +1043,6 @@ class ExpressionParser {
975
1043
  return false;
976
1044
  }
977
1045
  }
978
- /*
979
- // DEPRECATED -- Modeler can deal with this by smartly using AND
980
- // clauses like "&x: &y:" to limit set to specific prefixes.
981
-
982
- // Deal with "prefix inheritance" when pattern starts with a colon.
983
- if(pat.startsWith(':') && this.owner_prefix) {
984
- // Add a "must start with" AND condition to all OR clauses of the
985
- // pattern.
986
- // NOTE: Issues may occur when prefix contains &, ^ or #.
987
- // @@TO DO: See if this can be easily prohibited.
988
- const oc = pat.substring(1).split('|');
989
- for(let i = 0; i < oc.length; i++) {
990
- oc[i] = `~${this.owner_prefix}&${oc[i]}`;
991
- }
992
- pat = oc.join('|');
993
- }
994
- */
995
1046
  // NOTE: For patterns, assume that # *always* denotes the context-
996
1047
  // sensitive number #, because if modelers wishes to include
997
1048
  // ANY number, they can make their pattern less selective.
@@ -1118,7 +1169,7 @@ class ExpressionParser {
1118
1169
  }
1119
1170
  // A leading "!" denotes: pass variable reference instead of its value.
1120
1171
  // NOTE: This also applies to the "dot", so [!.] is a valid variable.
1121
- let by_reference = name.startsWith('!');
1172
+ const by_reference = name.startsWith('!');
1122
1173
  if(by_reference) name = name.substring(1);
1123
1174
  // When `name` is a single dot, it refers to the dataset for which the
1124
1175
  // modifier expression is being parsed. Like all datasets, the "dot"
@@ -1134,11 +1185,163 @@ class ExpressionParser {
1134
1185
  return false;
1135
1186
  }
1136
1187
  // Check whether name refers to a Linny-R entity defined by the model.
1188
+
1189
+ // NOTE: When parsing the expression of a "method", variables starting
1190
+ // with a colon may be special cases.
1191
+ if(this.attribute.startsWith(':') && name.startsWith(':')) {
1192
+ // When `name` identifies a method ":m" then this method can be
1193
+ // called "as is".
1194
+ const method = MODEL.equationByID(UI.nameToID(name));
1195
+ if(method) {
1196
+ // Check for auto-reference.
1197
+ if(method.selector === this.attribute) {
1198
+ this.error = 'Method cannot reference itself';
1199
+ return false;
1200
+ }
1201
+ if(attr) {
1202
+ // Methods are expressions and hence always return a number,
1203
+ // not an entity.
1204
+ this.error = 'Method cannot have an attribute';
1205
+ return false;
1206
+ }
1207
+ // NOTE: If it has no eligible prefixes yet, the method being
1208
+ // parsed "inherits" those of an "as is" method, or should
1209
+ // intersect its eligible prefixes with the "inherited" ones.
1210
+ method.expression.compile();
1211
+ if(method.expression.compiling) {
1212
+ this.error = 'Cannot resolve method "' + method.selector +
1213
+ '" because this would create a cyclic reference';
1214
+ return false;
1215
+ }
1216
+ const
1217
+ ep = {},
1218
+ mep = method.expression.eligible_prefixes,
1219
+ prefs = Object.keys(mep);
1220
+ // NOTE: Prefix keys will always be in lower case.
1221
+ for(let i = 0; i < prefs.length; i++) {
1222
+ const pref = prefs[i];
1223
+ if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
1224
+ ep[pref] = true;
1225
+ }
1226
+ }
1227
+ this.eligible_prefixes = ep;
1228
+ // NOTE: The method may be dynamic and/or level-dependent.
1229
+ if(!method.expression.isStatic) {
1230
+ this.is_static = false;
1231
+ this.log('dynamic because dynamic method is used');
1232
+ }
1233
+ this.is_level_based = this.is_level_based ||
1234
+ method.expression.is_level_based;
1235
+ // Generate "call method" VM instruction with no additional
1236
+ // arguments; the method equation will be applied to the object
1237
+ // of the calling method.
1238
+ return [{meq: method}, anchor1, offset1, anchor2, offset2];
1239
+ }
1240
+ // If `name` does not identify a method, it must match the "tail"
1241
+ // of some prefixed entity "prefix: name", because a method can
1242
+ // only be used as [prefix: method name] in another expression.
1243
+ // When compiling a method, a list of eligible prefixes is made.
1244
+ // This should not be empty when a method reference is parsed.
1245
+ const
1246
+ tail = UI.PREFIXER + name.substring(1).trim(),
1247
+ ee = MODEL.entitiesEndingOn(tail, attr),
1248
+ ep = {};
1249
+ for(let i = 0; i < ee.length; i++) {
1250
+ const
1251
+ en = ee[i].displayName,
1252
+ pref = en.substring(0, en.length - tail.length).toLowerCase();
1253
+ if(this.eligible_prefixes === null || this.eligible_prefixes[pref]) {
1254
+ ep[pref] = true;
1255
+ }
1256
+ }
1257
+ this.eligible_prefixes = ep;
1258
+ const uca = attr.toUpperCase();
1259
+ // Capitalize `attr` if it is a standard entity attribute.
1260
+ if(VM.attribute_names[uca]) attr = uca;
1261
+ // Add attribute to method attribute list (for post-parsing check).
1262
+ this.method_attributes.push(attr);
1263
+ if(Object.keys(this.eligible_prefixes).length <= 0) {
1264
+ const n = name + (attr ? `|${attr}` : '');
1265
+ this.error =
1266
+ `No match for variable [${n}] in this method expression`;
1267
+ return false;
1268
+ }
1269
+ // NOTE: Some attributes make the method expression level-dependent.
1270
+ this.is_level_based = this.is_level_based ||
1271
+ VM.level_based_attr.indexOf(attr) >= 0;
1272
+ // NOTE: Postpone check whether method will make the expression
1273
+ // dynamic to after the expression has been parsed and the exact
1274
+ // set of eligible entities and the set of attributes is known.
1275
+
1276
+ // Colon-prefixed variables in method expressions are similar to
1277
+ // wildcard variables, so the same VM instruction is coded for,
1278
+ // except that the entity that is the object of the method will
1279
+ // be set (as `method_object` property) for expressions that "call"
1280
+ // the method. The distinction is indicated by passing the string
1281
+ // "MO" instead of the list of eligible entities.
1282
+ if(this.TRACE) console.log('TRACE: Variable', name,
1283
+ 'references the method object. Attribute used:', attr);
1284
+ return [{n: name, ee: 'MO', a: attr, br: by_reference},
1285
+ anchor1, offset1, anchor2, offset2];
1286
+ }
1287
+
1288
+ // Special "method-parsing" cases will now have been handled.
1289
+ // The other cases apply also to normal expressions.
1137
1290
  if(!obj) {
1138
- // Variable name may start with a colon to denote that the owner
1139
- // prefix should be added.
1291
+ // If variable name starts with a colon, then the owner prefix
1292
+ // should be added.
1140
1293
  name = UI.colonPrefixedName(name, this.owner_prefix);
1141
- // Start with wildcard equations, as these are likely to be few
1294
+ // Now check whether the variable appends a method.
1295
+ const
1296
+ parts = name.split(UI.PREFIXER),
1297
+ tail = parts.pop();
1298
+ if(parts.length > 0) {
1299
+ // Name contains at least one prefix => last part *could* be a
1300
+ // method name, so look it up after adding a leading colon.
1301
+ const method = MODEL.equationByID(UI.nameToID(':' + tail));
1302
+ // If tail matches with a method, the head must identify an
1303
+ // entity.
1304
+ if(method) {
1305
+ const
1306
+ en = parts.join(UI.PREFIXER),
1307
+ mep = method.expression.matchWithEligiblePrefixes(en);
1308
+ if(!mep) {
1309
+ if(method.expression.compiling) {
1310
+ this.error = `Cannot resolve "${en}", possibly because ` +
1311
+ `method "${method.selector}" creates a cyclic reference`;
1312
+ } else {
1313
+ this.error = 'Method "'+ method.selector +
1314
+ `" does not apply to "${en}"`;
1315
+ }
1316
+ return false;
1317
+ }
1318
+ // NOTE: The method may be dynamic and/or level-dependent.
1319
+ if(!method.expression.isStatic) {
1320
+ this.is_static = false;
1321
+ this.log('dynamic because dynamic method is used');
1322
+ }
1323
+ this.is_level_based = this.is_level_based ||
1324
+ method.expression.is_level_based;
1325
+ // NOTE: `en` may be an incomplete identification of the object
1326
+ // of the method, which can be completed only at execution time.
1327
+ // For example: [x: m] is a valid method call when "x: a: b"
1328
+ // and "x: c" identify model entities, and the expression of
1329
+ // method "m" contains variables [:a :b] and [:c] as operands.
1330
+ // These operands code as VMI_push_dataset_modifier instructions
1331
+ // with specifier {n: name, ee: "MO"} where, for the examples
1332
+ // above, "name" would be ":a :b" and ":c". By passing `en`
1333
+ // as the "method object prefix" to VMI_push_method, this
1334
+ // instruction can set the `method_object_prefix` attribute
1335
+ // of the expression so that it can be used by the VMI_push_
1336
+ // _dataset_modifier instruction to identify the method object
1337
+ // by assembling prefix + name (with its leading colon replaced
1338
+ // by the prefixer ": ").
1339
+ return [{meq: method, mo: en}, anchor1, offset1, anchor2, offset2];
1340
+ }
1341
+ }
1342
+ }
1343
+ if(!obj) {
1344
+ // Now check wildcard equations, as these are likely to be few
1142
1345
  // (so a quick scan) and constitute a special case.
1143
1346
  const
1144
1347
  id = UI.nameToID(name),
@@ -1151,7 +1354,7 @@ class ExpressionParser {
1151
1354
  // so this equation must be evaluated for that number.
1152
1355
  return [
1153
1356
  {d: w[0].dataset, s: w[1], x: w[0].expression},
1154
- anchor1, offset1, anchor2, offset2];
1357
+ anchor1, offset1, anchor2, offset2];
1155
1358
  }
1156
1359
  // If no match, try to match the object ID with any type of entity.
1157
1360
  obj = MODEL.objectByID(id);
@@ -1313,8 +1516,7 @@ class ExpressionParser {
1313
1516
  // No explicit selector means that this variable is dynamic if
1314
1517
  // the dataset has time series data, or if some of its modifier
1315
1518
  // expressions are dynamic.
1316
- if(obj.data.length > 1 || (obj.data.length > 0 && !obj.periodic) ||
1317
- !obj.allModifiersAreStatic) {
1519
+ if(obj.mayBeDynamic) {
1318
1520
  this.is_static = false;
1319
1521
  this.log('dynamic because dataset without explicit selector is used');
1320
1522
  }
@@ -1377,19 +1579,18 @@ class ExpressionParser {
1377
1579
  // NOTE: `arg0` can now be a single value, a vector, or NULL.
1378
1580
  if(arg0 === null) arg0 = obj.attributeValue(attr);
1379
1581
  if(Array.isArray(arg0)) {
1380
- if(obj instanceof Dataset) {
1381
- if(obj.data.length > 1 || obj.data.length > 0 && !obj.periodic) {
1382
- this.is_static = false;
1383
- this.log('dynamic because dataset vector is used');
1384
- }
1582
+ if(obj instanceof Dataset && obj.mayBeDynamic) {
1583
+ this.is_static = false;
1584
+ this.log('dynamic because dataset vector is used');
1385
1585
  } else if(VM.level_based_attr.indexOf(attr) >= 0) {
1386
1586
  this.is_static = false;
1387
1587
  this.log('dynamic because level-based attribute');
1388
- } else {
1389
- // Unusual (?) combi, so let's assume dynamic.
1588
+ } else if(new Set(arg0).size > 1) {
1589
+ // Not all values qre equal => dynamic.
1390
1590
  this.is_static = false;
1391
- this.log('probably dynamic -- check below:');
1392
- console.log('ANOMALY: array for', obj.displayName, obj, attr, arg0);
1591
+ this.log('Dynamic because array contains different values');
1592
+ // console.log('ANOMALY: array for', obj.type, obj.displayName, obj, attr, arg0);
1593
+ // console.log('Expression for', this.ownerName, '; text =', this.expr);
1393
1594
  }
1394
1595
  if(this.TRACE) console.log('TRACE: arg[0] is a vector');
1395
1596
  }
@@ -1441,10 +1642,10 @@ class ExpressionParser {
1441
1642
  }
1442
1643
 
1443
1644
  getSymbol() {
1444
- // Gets the next substring in the expression that is a valid symbol
1645
+ // Get the next substring in the expression that is a valid symbol
1445
1646
  // while advancing the position-in-text (`pit`) and length-of-symbol
1446
1647
  // (`los`), which are used to highlight the position of a syntax error
1447
- // in the expression editor
1648
+ // in the expression editor.
1448
1649
  let c, f, i, l, v;
1449
1650
  this.prev_sym = this.sym;
1450
1651
  this.sym = null;
@@ -1652,35 +1853,35 @@ class ExpressionParser {
1652
1853
  }
1653
1854
 
1654
1855
  compile() {
1655
- // Compiles expression into array of VM instructions `code`
1656
- // NOTE: always create a new code array instance, as it will typically
1657
- // become the code attribute of an expression object
1856
+ // Compile expression into array of VM instructions `code`.
1857
+ // NOTE: Always create a new code array instance, as it will typically
1858
+ // become the code attribute of an expression object.
1658
1859
  if(DEBUGGING) console.log('COMPILING', this.ownerName, ':\n',
1659
1860
  this.expr, '\ncontext number =', this.context_number);
1660
1861
  this.code = [];
1661
- // Position in text
1862
+ // Position in text.
1662
1863
  this.pit = 0;
1663
- // Length of symbol
1864
+ // Length of symbol.
1664
1865
  this.los = 0;
1665
- // Error message also serves as flag: stop compiling if not empty
1866
+ // Error message also serves as flag: stop compiling if not empty.
1666
1867
  this.error = '';
1667
- // `is_static` becomes FALSE when a time-dependent operand is detected
1868
+ // `is_static` becomes FALSE when a time-dependent operand is detected.
1668
1869
  this.is_static = true;
1669
- // `is_level_based` becomes TRUE when a level-based variable is detected
1870
+ // `is_level_based` becomes TRUE when a level-based variable is detected.
1670
1871
  this.is_level_based = false;
1671
- // `concatenating` becomes TRUE when a concatenation operator (semicolon)
1672
- // is pushed, and FALSE when a reducing operator (min, max, normal, weibull,
1673
- // triangular) is pushed
1872
+ // `concatenating` becomes TRUE when a concatenation operator
1873
+ // (semicolon) is pushed, and FALSE when a reducing operator (min, max,
1874
+ // normal, weibull, triangular) is pushed.
1674
1875
  this.concatenating = false;
1675
- // An empty expression should return the "undefined" value
1876
+ // An empty expression should return the "undefined" value.
1676
1877
  if(this.expr.trim() === '') {
1677
1878
  this.code.push([VMI_push_number, VM.UNDEFINED]);
1678
1879
  return;
1679
1880
  }
1680
- // Parse the expression using Edsger Dijkstra's shunting-yard algorithm
1681
- // vmi = virtual machine instruction (a function)
1881
+ // Parse the expression using Edsger Dijkstra's shunting-yard algorithm.
1882
+ // vmi = virtual machine instruction (a function).
1682
1883
  let vmi;
1683
- // eot = end of text (index of last character in string)
1884
+ // eot = end of text (index of last character in string).
1684
1885
  this.eot = this.expr.length - 1;
1685
1886
  this.sym = null; // current symbol
1686
1887
  this.prev_sym = null; // previous symbol
@@ -1693,27 +1894,27 @@ class ExpressionParser {
1693
1894
  this.getSymbol();
1694
1895
  if(this.error !== '') break;
1695
1896
  if(this.sym === '(') {
1696
- // Opening parenthesis is ALWAYS pushed onto the stack
1897
+ // Opening parenthesis is ALWAYS pushed onto the stack.
1697
1898
  this.op_stack.push(this.sym);
1698
1899
  } else if(this.sym === ')') {
1699
1900
  // Closing parenthesis => pop all operators until its matching
1700
- // opening parenthesis is found
1901
+ // opening parenthesis is found.
1701
1902
  if(this.op_stack.indexOf('(') < 0) {
1702
1903
  this.error = 'Unmatched \')\'';
1703
1904
  } else if(this.prev_sym === '(' ||
1704
1905
  OPERATOR_CODES.indexOf(this.prev_sym) >= 0) {
1705
- // Parenthesis immediately after an operator => missing operand
1906
+ // Parenthesis immediately after an operator => missing operand.
1706
1907
  this.error = 'Missing operand';
1707
1908
  } else {
1708
- // Pop all operators up to and including the matching parenthesis
1909
+ // Pop all operators up to and including the matching parenthesis.
1709
1910
  vmi = null;
1710
1911
  while(this.op_stack.length > 0 &&
1711
1912
  this.op_stack[this.op_stack.length - 1] !== '(') {
1712
- // Pop the operator
1913
+ // Pop the operator.
1713
1914
  vmi = this.op_stack.pop();
1714
1915
  this.codeOperation(vmi);
1715
1916
  }
1716
- // Also pop the opening parenthesis
1917
+ // Also pop the opening parenthesis.
1717
1918
  this.op_stack.pop();
1718
1919
  }
1719
1920
  } else if(this.sym === VMI_if_else &&
@@ -1725,13 +1926,13 @@ class ExpressionParser {
1725
1926
  this.op_stack[this.op_stack.length - 1] : null),
1726
1927
  topprio = PRIORITIES[OPERATOR_CODES.indexOf(topop)],
1727
1928
  symprio = PRIORITIES[OPERATOR_CODES.indexOf(this.sym)];
1728
- // Pop all operators having a higher or equal priority than the one
1729
- // to be pushed EXCEPT when this priority equals 9, as monadic operators
1730
- // bind right-to-left
1929
+ // Pop all operators having a higher or equal priority than the
1930
+ // one to be pushed EXCEPT when this priority equals 9, as monadic
1931
+ // operators bind right-to-left.
1731
1932
  while(this.op_stack.length > 0 && OPERATOR_CODES.indexOf(topop) >= 0 &&
1732
1933
  topprio >= symprio && symprio !== 9) {
1733
1934
  // The stack may be emptied, but if it contains a (, this
1734
- // parenthesis is unmatched
1935
+ // parenthesis is unmatched.
1735
1936
  if(topop === '(') {
1736
1937
  this.error = 'Missing \')\'';
1737
1938
  } else {
@@ -1747,27 +1948,27 @@ class ExpressionParser {
1747
1948
  }
1748
1949
  }
1749
1950
 
1750
- // NOTE: as of version 1.0.14, (a ? b : c) is implemented with
1951
+ // NOTE: As of version 1.0.14, (a ? b : c) is implemented with
1751
1952
  // "jump"-instructions so that only b OR c is evaluated instead
1752
- // of both
1953
+ // of both.
1753
1954
  if(this.sym === VMI_if_then) {
1754
1955
  // Push index of JUMP-IF-FALSE instruction on if_stack so that
1755
1956
  // later its dummy argument (NULL) can be replaced by the
1756
- // index of the first instruction after the THEN part
1957
+ // index of the first instruction after the THEN part.
1757
1958
  this.if_stack.push(this.code.length);
1758
1959
  this.code.push([VMI_jump_if_false, null]);
1759
1960
  } else if(this.sym === VMI_if_else) {
1760
1961
  this.then_stack.push(this.code.length);
1761
1962
  this.code.push([VMI_jump, null]);
1762
- // NOTE: if : is not omitted, the code for the ELSE part must
1763
- // start by popping the FALSE result of the IF condition
1963
+ // NOTE: If : is not omitted, the code for the ELSE part must
1964
+ // start by popping the FALSE result of the IF condition.
1764
1965
  this.code.push([VMI_pop_false, null]);
1765
1966
  }
1766
1967
  // END of new code for IF-THEN-ELSE
1767
1968
 
1768
1969
  this.op_stack.push(this.sym);
1769
1970
  } else if(this.sym !== null) {
1770
- // Symbol is an operand
1971
+ // Symbol is an operand.
1771
1972
  if(CONSTANT_CODES.indexOf(this.sym) >= 0) {
1772
1973
  this.code.push([this.sym, null]);
1773
1974
  } else if(Array.isArray(this.sym)) {
@@ -1778,6 +1979,8 @@ class ExpressionParser {
1778
1979
  this.code.push([VMI_push_statistic, this.sym]);
1779
1980
  } else if(this.sym[0].hasOwnProperty('d')) {
1780
1981
  this.code.push([VMI_push_dataset_modifier, this.sym]);
1982
+ } else if(this.sym[0].hasOwnProperty('meq')) {
1983
+ this.code.push([VMI_push_method, this.sym]);
1781
1984
  } else if(this.sym[0].hasOwnProperty('ee')) {
1782
1985
  this.code.push([VMI_push_wildcard_entity, this.sym]);
1783
1986
  } else if(this.sym[0].hasOwnProperty('x')) {
@@ -1811,6 +2014,21 @@ class ExpressionParser {
1811
2014
  this.error = 'Invalid parameter list';
1812
2015
  }
1813
2016
  }
2017
+ // When compiling a method, check for all eligible prefixes whether
2018
+ // they might make the expression dynamic.
2019
+ if(this.is_static && this.eligible_prefixes) {
2020
+ const epl = Object.keys(this.eligible_prefixes);
2021
+ for(let i = 0; i < epl.length; i++) {
2022
+ const ep = epl[i];
2023
+ for(let j = 0; j < ep.length; j++) {
2024
+ if(ep[j] instanceof Dataset && ep[j].mayBeDynamic) {
2025
+ this.is_static = false;
2026
+ this.log('dynamic because some modifiers of eligible datasets are dynamic');
2027
+ break;
2028
+ }
2029
+ }
2030
+ }
2031
+ }
1814
2032
  if(this.TRACE || DEBUGGING) console.log('PARSED', this.ownerName, ':',
1815
2033
  this.expr, this.code);
1816
2034
  }
@@ -2741,7 +2959,7 @@ class VirtualMachine {
2741
2959
  return prior_level;
2742
2960
  }
2743
2961
 
2744
- variablesLegend(block) {
2962
+ variablesLegend() {
2745
2963
  // Return a string with each variable code and full name on a
2746
2964
  // separate line.
2747
2965
  const
@@ -5533,96 +5751,99 @@ Solver status = ${json.status}`);
5533
5751
  // automaton instruction has parameters x and a, where x is the computing
5534
5752
  // expression and a the argument, which may be a single number or a list
5535
5753
  // (array) of objects. When no arguments need to be passed, the second
5536
- // parameter is named 'empty' (and is not used).
5754
+ // parameter is omitted.
5537
5755
 
5538
5756
  function VMI_push_number(x, number) {
5539
- // Pushes a numeric constant on the VM stack
5757
+ // Push a numeric constant on the VM stack.
5540
5758
  if(DEBUGGING) console.log('push number = ' + number);
5541
5759
  x.push(number);
5542
5760
  }
5543
5761
 
5544
- function VMI_push_time_step(x, empty) {
5545
- // Pushes the current time step.
5546
- // NOTE: this is the "local" time step for expression `x` (which always
5547
- // starts at 1), adjusted for the first time step of the simulation period
5762
+ function VMI_push_time_step(x) {
5763
+ // Push the current time step.
5764
+ // NOTE: This is the "local" time step for expression `x` (which always
5765
+ // starts at 1), adjusted for the first time step of the simulation period.
5548
5766
  const t = x.step[x.step.length - 1] + MODEL.start_period - 1;
5549
5767
  if(DEBUGGING) console.log('push absolute t = ' + t);
5550
5768
  x.push(t);
5551
5769
  }
5552
5770
 
5553
- function VMI_push_delta_t(x, empty) {
5554
- // Pushes the duration of 1 time step (in hours).
5771
+ function VMI_push_delta_t(x) {
5772
+ // Push the duration of 1 time step (in hours).
5555
5773
  const dt = MODEL.time_scale * VM.time_unit_values[MODEL.time_unit];
5556
5774
  if(DEBUGGING) console.log('push delta-t = ' + dt);
5557
5775
  x.push(dt);
5558
5776
  }
5559
5777
 
5560
- function VMI_push_relative_time(x, empty) {
5561
- // Pushes the "local" time step for expression `x` (which always starts at 1)
5778
+ function VMI_push_relative_time(x) {
5779
+ // Push the "local" time step for expression `x`.
5780
+ // NOTE: Time step for optimization period always starts at 1.
5562
5781
  const t = x.step[x.step.length - 1];
5563
5782
  if(DEBUGGING) console.log('push relative t = ' + t);
5564
5783
  x.push(t);
5565
5784
  }
5566
5785
 
5567
- function VMI_push_block_time(x, empty) {
5568
- // Pushes the "local" time step for expression `x` (which always starts at 1)
5569
- // adjusted for the first time step of the current block
5570
- const lt = x.step[x.step.length - 1] - 1,
5571
- bnr = Math.floor(lt / MODEL.block_length),
5572
- t = lt - bnr * MODEL.block_length + 1;
5786
+ function VMI_push_block_time(x) {
5787
+ // Push the "local" time step for expression `x` (which always starts
5788
+ // at 1) adjusted for the first time step of the current block.
5789
+ const
5790
+ lt = x.step[x.step.length - 1] - 1,
5791
+ bnr = Math.floor(lt / MODEL.block_length),
5792
+ t = lt - bnr * MODEL.block_length + 1;
5573
5793
  if(DEBUGGING) console.log('push block time bt = ' + t);
5574
5794
  x.push(t);
5575
5795
  }
5576
5796
 
5577
- function VMI_push_block_number(x, empty) {
5578
- // Pushes the block currently being optimized (block numbering starts at 1)
5797
+ function VMI_push_block_number(x) {
5798
+ // Push the number of the block currently being optimized.
5799
+ // NOTE: Block numbering starts at 1.
5579
5800
  const local_t = x.step[x.step.length - 1] - 1,
5580
5801
  bnr = Math.floor(local_t / MODEL.block_length) + 1;
5581
5802
  if(DEBUGGING) console.log('push current block number = ' + bnr);
5582
5803
  x.push(bnr);
5583
5804
  }
5584
5805
 
5585
- function VMI_push_run_length(x, empty) {
5586
- // Pushes the run length (excl. look-ahead!)
5806
+ function VMI_push_run_length(x) {
5807
+ // Push the run length (excl. look-ahead!).
5587
5808
  const n = MODEL.end_period - MODEL.start_period + 1;
5588
5809
  if(DEBUGGING) console.log('push run length N = ' + n);
5589
5810
  x.push(n);
5590
5811
  }
5591
5812
 
5592
- function VMI_push_block_length(x, empty) {
5593
- // Pushes the block length (is set via model settings dialog)
5813
+ function VMI_push_block_length(x) {
5814
+ // Push the block length.
5594
5815
  if(DEBUGGING) console.log('push block length n = ' + MODEL.block_length);
5595
5816
  x.push(MODEL.block_length);
5596
5817
  }
5597
5818
 
5598
- function VMI_push_look_ahead(x, empty) {
5599
- // Pushes the look-ahead
5819
+ function VMI_push_look_ahead(x) {
5820
+ // Push the look-ahead.
5600
5821
  if(DEBUGGING) console.log('push look-ahead l = ' + MODEL.look_ahead);
5601
5822
  x.push(MODEL.look_ahead);
5602
5823
  }
5603
5824
 
5604
- function VMI_push_round(x, empty) {
5605
- // Pushes the current round number (a=1, z=26, etc.)
5825
+ function VMI_push_round(x) {
5826
+ // Push the current round number (a=1, z=26, etc.).
5606
5827
  const r = VM.round_letters.indexOf(VM.round_sequence[VM.current_round]);
5607
5828
  if(DEBUGGING) console.log('push round number R = ' + r);
5608
5829
  x.push(r);
5609
5830
  }
5610
5831
 
5611
- function VMI_push_last_round(x, empty) {
5612
- // Pushes the last round number (a=1, z=26, etc.)
5832
+ function VMI_push_last_round(x) {
5833
+ // Push the last round number (a=1, z=26, etc.).
5613
5834
  const r = VM.round_letters.indexOf(VM.round_sequence[MODEL.rounds - 1]);
5614
5835
  if(DEBUGGING) console.log('push last round number LR = ' + r);
5615
5836
  x.push(r);
5616
5837
  }
5617
5838
 
5618
- function VMI_push_number_of_rounds(x, empty) {
5619
- // Pushes the number of rounds (= length of round sequence)
5839
+ function VMI_push_number_of_rounds(x) {
5840
+ // Push the number of rounds (= length of round sequence).
5620
5841
  if(DEBUGGING) console.log('push number of rounds NR = ' + MODEL.rounds);
5621
5842
  x.push(MODEL.rounds);
5622
5843
  }
5623
5844
 
5624
- function VMI_push_run_number(x, empty) {
5625
- // Pushes the number of the current run in the selected experiment (or 0)
5845
+ function VMI_push_run_number(x) {
5846
+ // Push the number of the current run in the selected experiment (or 0).
5626
5847
  const
5627
5848
  sx = EXPERIMENT_MANAGER.selected_experiment,
5628
5849
  nox = (sx ? ` (in ${sx.title})` : ' (no experiment)'),
@@ -5631,8 +5852,8 @@ function VMI_push_run_number(x, empty) {
5631
5852
  x.push(xr);
5632
5853
  }
5633
5854
 
5634
- function VMI_push_number_of_runs(x, empty) {
5635
- // Pushes the number of runs in the current experiment (0 if no experiment)
5855
+ function VMI_push_number_of_runs(x) {
5856
+ // Push the number of runs in the current experiment (0 if no experiment).
5636
5857
  const
5637
5858
  sx = EXPERIMENT_MANAGER.selected_experiment,
5638
5859
  nox = (sx ? `(in ${sx.title})` : '(no experiment)'),
@@ -5641,40 +5862,41 @@ function VMI_push_number_of_runs(x, empty) {
5641
5862
  x.push(nx);
5642
5863
  }
5643
5864
 
5644
- function VMI_push_random(x, empty) {
5645
- // Pushes a random number from the interval [0, 1)
5865
+ function VMI_push_random(x) {
5866
+ // Push a random number from the interval [0, 1).
5646
5867
  const r = Math.random();
5647
5868
  if(DEBUGGING) console.log('push random =', r);
5648
5869
  x.push(r);
5649
5870
  }
5650
5871
 
5651
- function VMI_push_pi(x, empty) {
5652
- // Pushes the goniometric constant pi
5872
+ function VMI_push_pi(x) {
5873
+ // Push the goniometric constant pi.
5653
5874
  if(DEBUGGING) console.log('push pi');
5654
5875
  x.push(Math.PI);
5655
5876
  }
5656
5877
 
5657
- function VMI_push_true(x, empty) {
5658
- // pushes the Boolean constant TRUE
5878
+ function VMI_push_true(x) {
5879
+ // Push the Boolean constant TRUE.
5659
5880
  if(DEBUGGING) console.log('push TRUE');
5660
5881
  x.push(1);
5661
5882
  }
5662
5883
 
5663
- function VMI_push_false(x, empty) {
5664
- // Pushes the Boolean constant FALSE
5884
+ function VMI_push_false(x) {
5885
+ // Push the Boolean constant FALSE.
5665
5886
  if(DEBUGGING) console.log('push FALSE');
5666
5887
  x.push(0);
5667
5888
  }
5668
5889
 
5669
- function VMI_push_infinity(x, empty) {
5670
- // Pushes the constant representing infinity for the solver
5890
+ function VMI_push_infinity(x) {
5891
+ // Push the constant representing infinity for the solver.
5671
5892
  if(DEBUGGING) console.log('push +INF');
5672
5893
  x.push(VM.PLUS_INFINITY);
5673
5894
  }
5674
5895
 
5675
5896
  function valueOfIndexVariable(v) {
5676
- // AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions
5677
- // Returns value of iterator index variable for the current experiment
5897
+ // AUXILIARY FUNCTION for the VMI_push_(i, j or k) instructions.
5898
+ // Return the value of the iterator index variable for the current
5899
+ // experiment.
5678
5900
  if(MODEL.running_experiment) {
5679
5901
  const
5680
5902
  lead = v + '=',
@@ -5687,68 +5909,69 @@ function valueOfIndexVariable(v) {
5687
5909
  return 0;
5688
5910
  }
5689
5911
 
5690
- function VMI_push_i(x, empty) {
5691
- // Pushes the value of iterator index i
5912
+ function VMI_push_i(x) {
5913
+ // Push the value of iterator index i.
5692
5914
  const i = valueOfIndexVariable('i');
5693
5915
  if(DEBUGGING) console.log('push i = ' + i);
5694
5916
  x.push(i);
5695
5917
  }
5696
5918
 
5697
- function VMI_push_j(x, empty) {
5698
- // Pushes the value of iterator index j
5919
+ function VMI_push_j(x) {
5920
+ // Push the value of iterator index j.
5699
5921
  const j = valueOfIndexVariable('j');
5700
5922
  if(DEBUGGING) console.log('push j = ' + j);
5701
5923
  x.push(j);
5702
5924
  }
5703
5925
 
5704
- function VMI_push_k(x, empty) {
5705
- // Pushes the value of iterator index k
5926
+ function VMI_push_k(x) {
5927
+ // Push the value of iterator index k.
5706
5928
  const k = valueOfIndexVariable('k');
5707
5929
  if(DEBUGGING) console.log('push k = ' + k);
5708
5930
  x.push(k);
5709
5931
  }
5710
5932
 
5711
5933
  function pushTimeStepsPerTimeUnit(x, unit) {
5712
- // AUXILIARY FUNCTION for the VMI_push_(time unit) instructions
5713
- // Pushes the number of model time steps represented by 1 unit
5934
+ // AUXILIARY FUNCTION for the VMI_push_(time unit) instructions.
5935
+ // Push the number of model time steps represented by 1 unit.
5936
+ // NOTE: This will typically be a real number -- no rounding.
5714
5937
  const t = VM.time_unit_values[unit] / MODEL.time_scale /
5715
5938
  VM.time_unit_values[MODEL.time_unit];
5716
5939
  if(DEBUGGING) console.log(`push ${unit} = ${VM.sig4Dig(t)}`);
5717
5940
  x.push(t);
5718
5941
  }
5719
5942
 
5720
- function VMI_push_year(x, empty) {
5721
- // Pushes the number of time steps in one year
5943
+ function VMI_push_year(x) {
5944
+ // Push the number of time steps in one year.
5722
5945
  pushTimeStepsPerTimeUnit(x, 'year');
5723
5946
  }
5724
5947
 
5725
- function VMI_push_week(x, empty) {
5726
- // Pushes the number of time steps in one week
5948
+ function VMI_push_week(x) {
5949
+ // Push the number of time steps in one week.
5727
5950
  pushTimeStepsPerTimeUnit(x, 'week');
5728
5951
  }
5729
5952
 
5730
- function VMI_push_day(x, empty) {
5731
- // Pushes the number of time steps in one day
5953
+ function VMI_push_day(x) {
5954
+ // Push the number of time steps in one day.
5732
5955
  pushTimeStepsPerTimeUnit(x, 'day');
5733
5956
  }
5734
5957
 
5735
- function VMI_push_hour(x, empty) {
5736
- // Pushes the number of time steps in one hour
5958
+ function VMI_push_hour(x) {
5959
+ // Push the number of time steps in one hour.
5737
5960
  pushTimeStepsPerTimeUnit(x, 'hour');
5738
5961
  }
5739
5962
 
5740
- function VMI_push_minute(x, empty) {
5741
- // Pushes the number of time steps in one minute
5963
+ function VMI_push_minute(x) {
5964
+ // Push the number of time steps in one minute.
5742
5965
  pushTimeStepsPerTimeUnit(x, 'minute');
5743
5966
  }
5744
5967
 
5745
- function VMI_push_second(x, empty) {
5746
- // Pushes the number of time steps in one minute
5968
+ function VMI_push_second(x) {
5969
+ // Push the number of time steps in one second.
5747
5970
  pushTimeStepsPerTimeUnit(x, 'second');
5748
5971
  }
5749
5972
 
5750
- function VMI_push_contextual_number(x, empty) {
5751
- // Pushes the numeric value of the context-sensitive number #
5973
+ function VMI_push_contextual_number(x) {
5974
+ // Push the numeric value of the context-sensitive number #.
5752
5975
  const n = valueOfNumberSign(x);
5753
5976
  if(DEBUGGING) {
5754
5977
  console.log('push contextual number: # = ' + VM.sig2Dig(n));
@@ -5759,11 +5982,12 @@ function VMI_push_contextual_number(x, empty) {
5759
5982
  /* VM instruction helper functions */
5760
5983
 
5761
5984
  function valueOfNumberSign(x) {
5762
- // Pushes the numeric value of the # sign for the context of expression `x`
5763
- // NOTE: this can be a wildcard match, an active experiment run selector
5764
- // ending on digits, or tne number context of an entity. The latter typically
5765
- // is the number its name or any of its prefixes ends on, but notes are
5766
- // more "creative" and can return the number context of nearby entities.
5985
+ // Push the numeric value of the # sign for the context of expression `x`.
5986
+ // NOTE: This can be a wildcard match, an active experiment run selector
5987
+ // ending on digits, or the number context of an entity. The latter
5988
+ // typically is the number its name or any of its prefixes ends on, but
5989
+ // notes are more "creative" and can return the number context of nearby
5990
+ // entities.
5767
5991
  let s = '!NO SELECTOR',
5768
5992
  m = '!NO MATCH',
5769
5993
  n = VM.UNDEFINED;
@@ -5792,7 +6016,7 @@ function valueOfNumberSign(x) {
5792
6016
  }
5793
6017
  }
5794
6018
  // If selector contains no wildcards, get number context (typically
5795
- // inferred from a number in the name of the object)
6019
+ // inferred from a number in the name of the object).
5796
6020
  if(s.indexOf('*') < 0 && s.indexOf('?') < 0) {
5797
6021
  const d = x.object.numberContext;
5798
6022
  if(d) {
@@ -5802,7 +6026,7 @@ function valueOfNumberSign(x) {
5802
6026
  }
5803
6027
  }
5804
6028
  }
5805
- // For datasets, set the parent anchor to be the context-sensitive number
6029
+ // For datasets, set the parent anchor to be the context-sensitive number.
5806
6030
  if(x.object instanceof Dataset) x.object.parent_anchor = n;
5807
6031
  if(DEBUGGING) {
5808
6032
  console.log(`context for # in expression for ${x.variableName}
@@ -5813,7 +6037,7 @@ function valueOfNumberSign(x) {
5813
6037
  }
5814
6038
 
5815
6039
  function relativeTimeStep(t, anchor, offset, dtm, x) {
5816
- // Returns the relative time step, given t, anchor, offset,
6040
+ // Return the relative time step, given t, anchor, offset,
5817
6041
  // delta-t-multiplier and the expression being evaluated (to provide
5818
6042
  // context for anchor #).
5819
6043
  // NOTE: t = 1 corresponds with first time step of simulation period.
@@ -5833,7 +6057,7 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5833
6057
  if(DEBUGGING) {
5834
6058
  console.log('Parent anchor', x.object.parent_anchor);
5835
6059
  }
5836
- // NOTE: For not array-type datasets, ^ is equivalent to #
6060
+ // NOTE: For not array-type datasets, ^ is equivalent to #.
5837
6061
  return x.object.parent_anchor;
5838
6062
  }
5839
6063
  return valueOfNumberSign(x) + offset;
@@ -5872,17 +6096,18 @@ function relativeTimeStep(t, anchor, offset, dtm, x) {
5872
6096
  }
5873
6097
 
5874
6098
  function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5875
- // Returns the list [rt, ao1, ao2] where rt is the time step, and ao1 and ao2
5876
- // are anchor-offset shorthand for the debugging message, given t, two anchors
5877
- // and offsets, and the delta-t-multiplier
5878
- // NOTE: `dtm` will differ from 1 only for experiment results
5879
- // NOTE: expression `x` is passed to provide context for evaluation of #
6099
+ // Return the list [rt, ao1, ao2] where `rt` is the time step, and
6100
+ // `ao1` and `ao2` are anchor-offset shorthand for the debugging message,
6101
+ // given `t`, the two anchors plus offsets, and the delta-t-multiplier.
6102
+ // NOTES:
6103
+ // (1) `dtm` will differ from 1 only for experiment results.
6104
+ // (2) Expression `x` is passed to provide context for evaluation of #.
5880
6105
  let t1 = relativeTimeStep(t, a1, o1, dtm, x),
5881
6106
  ao1 = [' @ ', a1, (o1 > 0 ? '+' : ''), (o1 ? o1 : ''),
5882
6107
  ' = ', t1].join(''),
5883
6108
  ao2 = '';
5884
6109
  if(o2 !== o1 || a2 !== a1) {
5885
- // Two different offsets => use the midpoint as time (NO aggregation!)
6110
+ // Two different offsets => use the midpoint as time (NO aggregation!).
5886
6111
  const t2 = relativeTimeStep(t, a2, o2, dtm, x);
5887
6112
  ao2 = [' : ', a2, (o2 > 0 ? '+' : ''), (o2 ? o2 : ''), ' = ', t2].join('');
5888
6113
  t1 = Math.floor((t1 + t2) / 2);
@@ -5894,51 +6119,51 @@ function twoOffsetTimeStep(t, a1, o1, a2, o2, dtm, x) {
5894
6119
  /* VM instructions (continued) */
5895
6120
 
5896
6121
  function VMI_push_var(x, args) {
5897
- // Pushes the value of the variable specified by `args`, being the list
6122
+ // Push the value of the variable specified by `args`, being the list
5898
6123
  // [obj, anchor1, offset1, anchor2, offset2] where `obj` can be a vector
5899
- // or an expression, or a cluster unit balance specifier
6124
+ // or an expression, or a cluster unit balance specifier.
5900
6125
  const
5901
6126
  obj = args[0],
5902
- // NOTE: use the "local" time step for expression x
6127
+ // NOTE: Use the "local" time step for expression `x`.
5903
6128
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5904
6129
  args[1], args[2], args[3], args[4], 1, x);
5905
6130
  let t = tot[0];
5906
- // Negative time step is evaluated as t = 0 (initial value), while t beyond
5907
- // optimization period is evaluated as its last time step UNLESS t is
5908
- // used in a self-referencing variable
6131
+ // Negative time step is evaluated as t = 0 (initial value), while t
6132
+ // beyond the optimization period is evaluated as its last time step
6133
+ // UNLESS t is used in a self-referencing variable.
5909
6134
  const xv = obj.hasOwnProperty('xv');
5910
6135
  if(!xv) {
5911
6136
  t = Math.max(0, Math.min(
5912
6137
  MODEL.end_period - MODEL.start_period + MODEL.look_ahead + 1, t));
5913
6138
  }
5914
- // Trace only now that time step t has been computed
6139
+ // Trace only now that time step t has been computed.
5915
6140
  if(DEBUGGING) {
5916
6141
  console.log('push var:', (xv ? '[SELF]' :
5917
6142
  (obj instanceof Expression ? obj.text : '[' + obj.toString() + ']')),
5918
6143
  tot[1] + ' ' + tot[2]);
5919
6144
  }
5920
6145
  if(Array.isArray(obj)) {
5921
- // Object is a vector
6146
+ // Object is a vector.
5922
6147
  let v = t < obj.length ? obj[t] : VM.UNDEFINED;
5923
- // NOTE: when the vector is the "active" parameter for sensitivity
5924
- // analysis, the value is multiplied by 1 + delta %
6148
+ // NOTE: When the vector is the "active" parameter for sensitivity
6149
+ // analysis, the value is multiplied by 1 + delta %.
5925
6150
  if(obj === MODEL.active_sensitivity_parameter) {
5926
- // NOTE: do NOT scale exceptional values
6151
+ // NOTE: Do NOT scale exceptional values.
5927
6152
  if(v > VM.MINUS_INFINITY && v < VM.PLUS_INFINITY) {
5928
6153
  v *= (1 + MODEL.sensitivity_delta * 0.01);
5929
6154
  }
5930
6155
  }
5931
6156
  x.push(v);
5932
6157
  } else if(xv) {
5933
- // Variable references an earlier value computed for this expression `x`
6158
+ // Variable references an earlier value computed for this expression `x`.
5934
6159
  x.push(t >= 0 && t < x.vector.length ? x.vector[t] : obj.dv);
5935
6160
  } else if(obj.hasOwnProperty('c') && obj.hasOwnProperty('u')) {
5936
- // Object holds link lists for cluster balance computation
6161
+ // Object holds link lists for cluster balance computation.
5937
6162
  x.push(MODEL.flowBalance(obj, t));
5938
6163
  } else if(obj instanceof Expression) {
5939
6164
  x.push(obj.result(t));
5940
6165
  } else if(typeof obj === 'number') {
5941
- // Object is a number
6166
+ // Object is a number.
5942
6167
  x.push(obj);
5943
6168
  } else {
5944
6169
  console.log('ERROR: VMI_push_var object =', obj);
@@ -5947,17 +6172,17 @@ function VMI_push_var(x, args) {
5947
6172
  }
5948
6173
 
5949
6174
  function VMI_push_entity(x, args) {
5950
- // Pushes a special "entity reference" object based on `args`, being the
6175
+ // Push a special "entity reference" object based on `args`, being the
5951
6176
  // list [obj, anchor1, offset1, anchor2, offset2] where `obj` has the
5952
- // format {r: entity object, a: attribute}
5953
- // The object that is pushed on the stack passes the entity, the attribute
5954
- // to use, and the time interval
6177
+ // format {r: entity object, a: attribute}.
6178
+ // The object that is pushed on the stack passes the entity, the
6179
+ // attribute to use, and the time interval.
5955
6180
  const
5956
- // NOTE: use the "local" time step for expression x
6181
+ // NOTE: Use the "local" time step for expression `x`.
5957
6182
  tot = twoOffsetTimeStep(x.step[x.step.length - 1],
5958
6183
  args[1], args[2], args[3], args[4], 1, x),
5959
6184
  er = {entity: args[0].r, attribute: args[0].a, t1: tot[0], t2: tot[1]};
5960
- // Trace only now that time step t has been computed
6185
+ // Trace only now that time step t has been computed.
5961
6186
  if(DEBUGGING) {
5962
6187
  console.log(['push entity: ', er.entity.displayName, '|', er.attribute,
5963
6188
  ', t = ', er.t1, ' - ', er.t2].join(''));
@@ -5965,27 +6190,94 @@ function VMI_push_entity(x, args) {
5965
6190
  x.push(er);
5966
6191
  }
5967
6192
 
6193
+ function VMI_push_method(x, args) {
6194
+ // Push the result of the expression associated with the method (a
6195
+ // dataset modifier with a selector that starts with a colon).
6196
+ // The first element of the argument list specifies the method,
6197
+ // and possibly also the entity to be used as its object.
6198
+ // NOTE: Methods can only be called "as is" (without prefix) in a
6199
+ // method expression. The object of such "as is" method calls is
6200
+ // the object of the calling method expression `x`.
6201
+ const
6202
+ method = args[0].meq,
6203
+ mex = method.expression,
6204
+ // NOTE: If method object prefix is not specified in the first
6205
+ // argument, use the MOP of the calling method (if specified).
6206
+ mo_prefix = args[0].mo || x.method_object_prefix,
6207
+ // NOTE: Use the "local" time step for expression `x`.
6208
+ tot = twoOffsetTimeStep(x.step[x.step.length - 1],
6209
+ args[1], args[2], args[3], args[4], 1, x);
6210
+ if(!x.method_object && !mo_prefix) {
6211
+ console.log('ERROR: Undefined method object', x);
6212
+ x.push(VM.BAD_REF);
6213
+ return;
6214
+ }
6215
+ if(x.method_object) {
6216
+ // Set the method object to be used in VMI_push_wildcard_entity.
6217
+ mex.method_object = x.method_object;
6218
+ } else if(mex.isEligible(mo_prefix)) {
6219
+ mex.method_object_prefix = mo_prefix;
6220
+ } else {
6221
+ console.log('ERROR: ', mo_prefix, 'is not in eligible list of',
6222
+ method.selector, method.eligible_prefixes);
6223
+ x.push(VM.BAD_REF);
6224
+ return;
6225
+ }
6226
+ const
6227
+ t = tot[0],
6228
+ v = mex.result(t);
6229
+ // Clear the method object & prefix -- just to be neat.
6230
+ mex.method_object = null;
6231
+ mex.method_object_prefix = '';
6232
+ // Trace only now that time step t has been computed.
6233
+ if(DEBUGGING) {
6234
+ console.log('push method:', obj.displayName, method.selector,
6235
+ tot[1] + (tot[2] ? ':' + tot[2] : ''), 'value =', VM.sig4Dig(v));
6236
+ }
6237
+ x.push(v);
6238
+ }
6239
+
5968
6240
  function VMI_push_wildcard_entity(x, args) {
5969
- // Pushes the value of (or reference to) an entity attribute, based on
6241
+ // Push the value of (or reference to) an entity attribute, based on
5970
6242
  // `args`, being the list [obj, anchor1, offset1, anchor2, offset2]
5971
6243
  // where `obj` has the format {ee: list of eligible entities,
5972
6244
  // n: name (with wildcard #), a: attribute, br: by reference (boolean)}
5973
- // First select the first entity in `ee` that matches the wildcard vector
5974
- // index of the expression `x` being executed.
6245
+ let obj = null,
6246
+ nn = args[0].n;
5975
6247
  const el = args[0].ee;
5976
- let nn = args[0].n.replace('#', x.wildcard_vector_index),
5977
- obj = null;
5978
- for(let i = 0; !obj && i < el.length; i++) {
5979
- if(el[i].name === nn) obj = el[i];
5980
- }
5981
- // If no match, then this indicates a bad reference.
5982
- if(!obj) {
5983
- console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
5984
- x.push(VM.BAD_REF);
5985
- return;
6248
+ // NOTE: Variables in method expressions that reference the object of
6249
+ // the method also code with this VM instruction, but then pass the
6250
+ // string "MO" instead of the list of eligible entities. This indicates
6251
+ // that the `method_object` property of expression `x` should be used.
6252
+ if(el === 'MO') {
6253
+ obj = x.method_object;
6254
+ if(!obj && x.method_object_prefix) {
6255
+ // Try to identity the object by the prefixed name.
6256
+ obj = MODEL.objectByName(x.method_object_prefix +
6257
+ UI.PREFIXER + nn.substring(1));
6258
+ }
6259
+ if(!obj) {
6260
+ console.log(`ERROR: Undefined method object`, x);
6261
+ x.push(VM.BAD_REF);
6262
+ return;
6263
+ }
6264
+ } else {
6265
+ // Select the first entity in `ee` that matches the wildcard vector
6266
+ // index of the expression `x` being executed.
6267
+ nn = nn.replace('#', x.wildcard_vector_index);
6268
+ for(let i = 0; !obj && i < el.length; i++) {
6269
+ if(el[i].name === nn) obj = el[i];
6270
+ }
6271
+ // If no match, then this indicates a bad reference.
6272
+ if(!obj) {
6273
+ console.log(`ERROR: no match for "${nn}" in eligible entity list`, el);
6274
+ x.push(VM.BAD_REF);
6275
+ return;
6276
+ }
5986
6277
  }
5987
- // Otherwise, if args[0] indicates "by reference", then VMI_push_entity
5988
- // can be called with the appropriate parameters.
6278
+ // Now `obj` should be an existing model entity.
6279
+ // If args[0] indicates "by reference", then VMI_push_entity can be
6280
+ // called with the appropriate parameters.
5989
6281
  const attr = args[0].a || obj.defaultAttribute;
5990
6282
  if(args[0].br) {
5991
6283
  VMI_push_entity(x, {r: obj, a: attr});
@@ -6395,7 +6687,7 @@ function VMI_push_statistic(x, args) {
6395
6687
  x.push(VM.UNDEFINED);
6396
6688
  }
6397
6689
 
6398
- function VMI_replace_undefined(x, empty) {
6690
+ function VMI_replace_undefined(x) {
6399
6691
  // Replaces one of the two top numbers on the stack by the other if the one
6400
6692
  // is undefined
6401
6693
  const d = x.pop(true); // TRUE denotes that "undefined" should be ignored as issue
@@ -6408,7 +6700,7 @@ function VMI_replace_undefined(x, empty) {
6408
6700
  // NOTE: when the VM computes logical OR, AND and NOT, any non-zero number
6409
6701
  // is interpreted as TRUE
6410
6702
 
6411
- function VMI_or(x, empty) {
6703
+ function VMI_or(x) {
6412
6704
  // Performs a logical OR on the two top numbers on the stack
6413
6705
  const d = x.pop();
6414
6706
  if(d !== false) {
@@ -6417,7 +6709,7 @@ function VMI_or(x, empty) {
6417
6709
  }
6418
6710
  }
6419
6711
 
6420
- function VMI_and(x, empty) {
6712
+ function VMI_and(x) {
6421
6713
  // Performs a logical AND on the two top numbers on the stack
6422
6714
  const d = x.pop();
6423
6715
  if(d !== false) {
@@ -6426,7 +6718,7 @@ function VMI_and(x, empty) {
6426
6718
  }
6427
6719
  }
6428
6720
 
6429
- function VMI_not(x, empty) {
6721
+ function VMI_not(x) {
6430
6722
  // Performs a logical NOT on the top number of the stack
6431
6723
  const d = x.top();
6432
6724
  if(d !== false) {
@@ -6435,7 +6727,7 @@ function VMI_not(x, empty) {
6435
6727
  }
6436
6728
  }
6437
6729
 
6438
- function VMI_abs(x, empty) {
6730
+ function VMI_abs(x) {
6439
6731
  // Replaces the top number of the stack by its absolute value
6440
6732
  const d = x.top();
6441
6733
  if(d !== false) {
@@ -6444,7 +6736,7 @@ function VMI_abs(x, empty) {
6444
6736
  }
6445
6737
  }
6446
6738
 
6447
- function VMI_eq(x, empty) {
6739
+ function VMI_eq(x) {
6448
6740
  // Tests equality of the two top numbers on the stack
6449
6741
  const d = x.pop();
6450
6742
  if(d !== false) {
@@ -6453,7 +6745,7 @@ function VMI_eq(x, empty) {
6453
6745
  }
6454
6746
  }
6455
6747
 
6456
- function VMI_ne(x, empty) {
6748
+ function VMI_ne(x) {
6457
6749
  // Tests inequality of the two top numbers on the stack
6458
6750
  const d = x.pop();
6459
6751
  if(d !== false) {
@@ -6462,7 +6754,7 @@ function VMI_ne(x, empty) {
6462
6754
  }
6463
6755
  }
6464
6756
 
6465
- function VMI_lt(x, empty) {
6757
+ function VMI_lt(x) {
6466
6758
  // Tests whether second number on the stack is less than the top number
6467
6759
  const d = x.pop();
6468
6760
  if(d !== false) {
@@ -6471,7 +6763,7 @@ function VMI_lt(x, empty) {
6471
6763
  }
6472
6764
  }
6473
6765
 
6474
- function VMI_gt(x, empty) {
6766
+ function VMI_gt(x) {
6475
6767
  // Tests whether second number on the stack is greater than the top number
6476
6768
  const d = x.pop();
6477
6769
  if(d !== false) {
@@ -6480,7 +6772,7 @@ function VMI_gt(x, empty) {
6480
6772
  }
6481
6773
  }
6482
6774
 
6483
- function VMI_le(x, empty) {
6775
+ function VMI_le(x) {
6484
6776
  // Tests whether second number on the stack is less than, or equal to,
6485
6777
  // the top number
6486
6778
  const d = x.pop();
@@ -6490,7 +6782,7 @@ function VMI_le(x, empty) {
6490
6782
  }
6491
6783
  }
6492
6784
 
6493
- function VMI_ge(x, empty) {
6785
+ function VMI_ge(x) {
6494
6786
  // Tests whether second number on the stack is greater than, or equal to,
6495
6787
  // the top number
6496
6788
  const d = x.pop();
@@ -6500,7 +6792,7 @@ function VMI_ge(x, empty) {
6500
6792
  }
6501
6793
  }
6502
6794
 
6503
- function VMI_add(x, empty) {
6795
+ function VMI_add(x) {
6504
6796
  // Pops the top number on the stack and adds it to the new top number
6505
6797
  const d = x.pop();
6506
6798
  if(d !== false) {
@@ -6509,7 +6801,7 @@ function VMI_add(x, empty) {
6509
6801
  }
6510
6802
  }
6511
6803
 
6512
- function VMI_sub(x, empty) {
6804
+ function VMI_sub(x) {
6513
6805
  // Pops the top number on the stack and subtracts it from the new
6514
6806
  // top number
6515
6807
  const d = x.pop();
@@ -6519,7 +6811,7 @@ function VMI_sub(x, empty) {
6519
6811
  }
6520
6812
  }
6521
6813
 
6522
- function VMI_mul(x, empty) {
6814
+ function VMI_mul(x) {
6523
6815
  // Pops the top number on the stack and multiplies it with the new
6524
6816
  // top number
6525
6817
  const d = x.pop();
@@ -6529,7 +6821,7 @@ function VMI_mul(x, empty) {
6529
6821
  }
6530
6822
  }
6531
6823
 
6532
- function VMI_div(x, empty) {
6824
+ function VMI_div(x) {
6533
6825
  // Pops the top number on the stack and divides the new top number
6534
6826
  // by it. In case of division by zero, the top is replaced by #DIV0!
6535
6827
  const d = x.pop();
@@ -6543,7 +6835,7 @@ function VMI_div(x, empty) {
6543
6835
  }
6544
6836
  }
6545
6837
 
6546
- function VMI_mod(x, empty) {
6838
+ function VMI_mod(x) {
6547
6839
  // Pops the top number on the stack, divides the new top number by it
6548
6840
  // (if non-zero, or it pushes error code #DIV0!), takes the fraction
6549
6841
  // part, and multiplies this with the divider; in other words, it
@@ -6559,7 +6851,7 @@ function VMI_mod(x, empty) {
6559
6851
  }
6560
6852
  }
6561
6853
 
6562
- function VMI_negate(x, empty) {
6854
+ function VMI_negate(x) {
6563
6855
  // Performs a negation on the top number of the stack
6564
6856
  const d = x.top();
6565
6857
  if(d !== false) {
@@ -6568,7 +6860,7 @@ function VMI_negate(x, empty) {
6568
6860
  }
6569
6861
  }
6570
6862
 
6571
- function VMI_power(x, empty) {
6863
+ function VMI_power(x) {
6572
6864
  // Pops the top number on the stack and raises the new top number
6573
6865
  // to its power
6574
6866
  const d = x.pop();
@@ -6578,7 +6870,7 @@ function VMI_power(x, empty) {
6578
6870
  }
6579
6871
  }
6580
6872
 
6581
- function VMI_sqrt(x, empty) {
6873
+ function VMI_sqrt(x) {
6582
6874
  // Replaces the top number of the stack by its square root, or by
6583
6875
  // error code #VALUE! if the top number is negative
6584
6876
  const d = x.top();
@@ -6592,7 +6884,7 @@ function VMI_sqrt(x, empty) {
6592
6884
  }
6593
6885
  }
6594
6886
 
6595
- function VMI_sin(x, empty) {
6887
+ function VMI_sin(x) {
6596
6888
  // Replaces the top number X of the stack by sin(X)
6597
6889
  const d = x.top();
6598
6890
  if(d !== false) {
@@ -6601,7 +6893,7 @@ function VMI_sin(x, empty) {
6601
6893
  }
6602
6894
  }
6603
6895
 
6604
- function VMI_cos(x, empty) {
6896
+ function VMI_cos(x) {
6605
6897
  // Replaces the top number X of the stack by cos(X)
6606
6898
  const d = x.top();
6607
6899
  if(d !== false) {
@@ -6610,7 +6902,7 @@ function VMI_cos(x, empty) {
6610
6902
  }
6611
6903
  }
6612
6904
 
6613
- function VMI_atan(x, empty) {
6905
+ function VMI_atan(x) {
6614
6906
  // Replaces the top number X of the stack by atan(X)
6615
6907
  const d = x.top();
6616
6908
  if(d !== false) {
@@ -6619,7 +6911,7 @@ function VMI_atan(x, empty) {
6619
6911
  }
6620
6912
  }
6621
6913
 
6622
- function VMI_ln(x, empty) {
6914
+ function VMI_ln(x) {
6623
6915
  // Replaces the top number X of the stack by ln(X), or by error
6624
6916
  // code #VALUE! if X is negative
6625
6917
  const d = x.top();
@@ -6633,7 +6925,7 @@ function VMI_ln(x, empty) {
6633
6925
  }
6634
6926
  }
6635
6927
 
6636
- function VMI_exp(x, empty) {
6928
+ function VMI_exp(x) {
6637
6929
  // Replaces the top number X of the stack by exp(X)
6638
6930
  const d = x.top();
6639
6931
  if(d !== false) {
@@ -6642,7 +6934,7 @@ function VMI_exp(x, empty) {
6642
6934
  }
6643
6935
  }
6644
6936
 
6645
- function VMI_log(x, empty) {
6937
+ function VMI_log(x) {
6646
6938
  // Pops the top number B from the stack and replaces the new top
6647
6939
  // number A by A log B. NOTE: x = A log B <=> x = ln(B) / ln(A)
6648
6940
  let d = x.pop();
@@ -6657,7 +6949,7 @@ function VMI_log(x, empty) {
6657
6949
  }
6658
6950
  }
6659
6951
 
6660
- function VMI_round(x, empty) {
6952
+ function VMI_round(x) {
6661
6953
  // Replaces the top number X of the stack by round(X)
6662
6954
  const d = x.top();
6663
6955
  if(d !== false) {
@@ -6666,7 +6958,7 @@ function VMI_round(x, empty) {
6666
6958
  }
6667
6959
  }
6668
6960
 
6669
- function VMI_int(x, empty) {
6961
+ function VMI_int(x) {
6670
6962
  // Replaces the top number X of the stack by its integer part
6671
6963
  const d = x.top();
6672
6964
  if(d !== false) {
@@ -6675,7 +6967,7 @@ function VMI_int(x, empty) {
6675
6967
  }
6676
6968
  }
6677
6969
 
6678
- function VMI_fract(x, empty) {
6970
+ function VMI_fract(x) {
6679
6971
  // Replaces the top number X of the stack by its fraction part
6680
6972
  const d = x.top();
6681
6973
  if(d !== false) {
@@ -6684,7 +6976,7 @@ function VMI_fract(x, empty) {
6684
6976
  }
6685
6977
  }
6686
6978
 
6687
- function VMI_exponential(x, empty) {
6979
+ function VMI_exponential(x) {
6688
6980
  // Replaces the top number X of the stack by a random number from the
6689
6981
  // negative exponential distribution with parameter X (so X is the lambda,
6690
6982
  // and the mean will be 1/X)
@@ -6696,7 +6988,7 @@ function VMI_exponential(x, empty) {
6696
6988
  }
6697
6989
  }
6698
6990
 
6699
- function VMI_poisson(x, empty) {
6991
+ function VMI_poisson(x) {
6700
6992
  // Replaces the top number X of the stack by a random number from the
6701
6993
  // poisson distribution with parameter X (so X is the mean value lambda)
6702
6994
  const d = x.top();
@@ -6707,7 +6999,7 @@ function VMI_poisson(x, empty) {
6707
6999
  }
6708
7000
  }
6709
7001
 
6710
- function VMI_binomial(x, empty) {
7002
+ function VMI_binomial(x) {
6711
7003
  // Replaces the top list (!) A of the stack by Bin(A[0], A[1]), i.e., a random
6712
7004
  // number from the binomial distribution with n = A[0] and p = A[1]
6713
7005
  const d = x.top();
@@ -6723,7 +7015,7 @@ function VMI_binomial(x, empty) {
6723
7015
  }
6724
7016
  }
6725
7017
 
6726
- function VMI_normal(x, empty) {
7018
+ function VMI_normal(x) {
6727
7019
  // Replaces the top list (!) A of the stack by N(A[0], A[1]), i.e., a random
6728
7020
  // number from the normal distribution with mu = A[0] and sigma = A[1]
6729
7021
  const d = x.top();
@@ -6739,7 +7031,7 @@ function VMI_normal(x, empty) {
6739
7031
  }
6740
7032
  }
6741
7033
 
6742
- function VMI_weibull(x, empty) {
7034
+ function VMI_weibull(x) {
6743
7035
  // Replaces the top list (!) A of the stack by Weibull(A[0], A[1]), i.e., a
6744
7036
  // random number from the Weibull distribution with lambda = A[0] and k = A[1]
6745
7037
  const d = x.top();
@@ -6755,7 +7047,7 @@ function VMI_weibull(x, empty) {
6755
7047
  }
6756
7048
  }
6757
7049
 
6758
- function VMI_triangular(x, empty) {
7050
+ function VMI_triangular(x) {
6759
7051
  // Replaces the top list (!) A of the stack by Tri(A[0], A[1]), A[2]), i.e.,
6760
7052
  // a random number from the triangular distribution with a = A[0], b = A[1],
6761
7053
  // and c = A[2]. NOTE: if only 2 parameters are passed, c is assumed to equal
@@ -6773,7 +7065,7 @@ function VMI_triangular(x, empty) {
6773
7065
  }
6774
7066
  }
6775
7067
 
6776
- function VMI_npv(x, empty) {
7068
+ function VMI_npv(x) {
6777
7069
  // Replaces the top list (!) A of the stack by the net present value (NPV)
6778
7070
  // of the arguments in A. A[0] is the interest rate r, A[1] is the number of
6779
7071
  // time periods n. If A has only 1 or 2 elements, the NPV is 0. If A has 3
@@ -6813,7 +7105,7 @@ function VMI_npv(x, empty) {
6813
7105
  }
6814
7106
  }
6815
7107
 
6816
- function VMI_min(x, empty) {
7108
+ function VMI_min(x) {
6817
7109
  // Replaces the top list (!) A of the stack by the lowest value in this list
6818
7110
  // NOTE: if A is not a list, A is left on the stack
6819
7111
  const d = x.top();
@@ -6825,7 +7117,7 @@ function VMI_min(x, empty) {
6825
7117
  }
6826
7118
  }
6827
7119
 
6828
- function VMI_max(x, empty) {
7120
+ function VMI_max(x) {
6829
7121
  // Replaces the top list (!) A of the stack by the highest value in this list
6830
7122
  // NOTE: if A is not a list, A is left on the stack
6831
7123
  const d = x.top();
@@ -6837,7 +7129,7 @@ function VMI_max(x, empty) {
6837
7129
  }
6838
7130
  }
6839
7131
 
6840
- function VMI_concat(x, empty) {
7132
+ function VMI_concat(x) {
6841
7133
  // Pops the top number B from the stack, and then replaces the new top
6842
7134
  // element A by [A, B] if A is a number, or adds B to A is A is a list
6843
7135
  // of numbers (!) or
@@ -6882,20 +7174,20 @@ function VMI_jump_if_false(x, index) {
6882
7174
  }
6883
7175
  }
6884
7176
 
6885
- function VMI_pop_false(x, empty) {
7177
+ function VMI_pop_false(x) {
6886
7178
  // Removes the top value from the stack, which should be 0 or
6887
7179
  // VM.UNDEFINED (but this is not checked)
6888
7180
  const r = x.stack.pop();
6889
7181
  if(DEBUGGING) console.log(`POP-FALSE (${r})`);
6890
7182
  }
6891
7183
 
6892
- function VMI_if_then(x, empty) {
7184
+ function VMI_if_then(x) {
6893
7185
  // NO operation -- as of version 1.0.14, this function only serves as
6894
7186
  // operator symbol, and its executions would indicate an error
6895
7187
  console.log('WARNING: this IF-THEN instruction is obsolete!');
6896
7188
  }
6897
7189
 
6898
- function VMI_if_else(x, empty) {
7190
+ function VMI_if_else(x) {
6899
7191
  // NO operation -- as of version 1.0.14, this function only serves as
6900
7192
  // operator symbol, and its executions would indicate an error
6901
7193
  console.log('WARNING: this IF-THEN instruction is obsolete!');
@@ -7824,7 +8116,7 @@ const
7824
8116
 
7825
8117
  // Each custom operator must have its own Virtual Machine instruction
7826
8118
 
7827
- function VMI_profitable_units(x, empty) {
8119
+ function VMI_profitable_units(x) {
7828
8120
  // Replaces the argument list that should be at the top of the stack by the
7829
8121
  // number of profitable units having a standard capacity (number), given the
7830
8122
  // level (vector) of the process that represents multiple such units, the
@@ -7958,7 +8250,7 @@ DYNAMIC_SYMBOLS.push('npu');
7958
8250
  LEVEL_BASED_CODES.push(VMI_profitable_units);
7959
8251
 
7960
8252
 
7961
- function VMI_highest_cumulative_consecutive_deviation(x, empty) {
8253
+ function VMI_highest_cumulative_consecutive_deviation(x) {
7962
8254
  // Replaces the argument list that should be at the top of the stack by
7963
8255
  // the HCCD (as in the function name) of the vector V that is passed as
7964
8256
  // the first argument of this function. The HCCD value is computed by