@sdeverywhere/compile 0.7.28 → 0.7.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/_shared/helpers.js +2 -2
- package/src/generate/direct-data-helpers.js +18 -6
- package/src/generate/gen-code.js +3 -2
- package/src/generate/gen-direct-const.js +5 -0
- package/src/generate/gen-equation.js +6 -0
- package/src/generate/gen-expr.js +4 -0
- package/src/generate/gen-lookup-from-direct.js +14 -7
- package/src/model/read-equations-expand.js +72 -0
- package/src/model/read-equations.js +32 -49
- package/src/model/read-variables.js +7 -1
- package/src/parse-and-generate.js +5 -5
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/compile",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.29",
|
|
4
4
|
"description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@sdeverywhere/parse": "^0.1.
|
|
8
|
+
"@sdeverywhere/parse": "^0.1.4",
|
|
9
9
|
"byline": "^5.0.0",
|
|
10
10
|
"csv-parse": "^5.3.3",
|
|
11
11
|
"ramda": "^0.27.0",
|
package/src/_shared/helpers.js
CHANGED
|
@@ -127,10 +127,10 @@ export let listConcat = (a, x, addSpaces = false) => {
|
|
|
127
127
|
}
|
|
128
128
|
// Convert a number or string into a C double constant string.
|
|
129
129
|
// A blank string is converted to zero, following Excel.
|
|
130
|
-
// A string that cannot be converted throws an
|
|
130
|
+
// A string that cannot be converted throws an error.
|
|
131
131
|
export let cdbl = x => {
|
|
132
132
|
function throwError() {
|
|
133
|
-
throw new Error(`
|
|
133
|
+
throw new Error(`Cannot convert "${x}" to a number`)
|
|
134
134
|
}
|
|
135
135
|
let s = '0.0'
|
|
136
136
|
if (typeof x === 'number') {
|
|
@@ -48,7 +48,16 @@ function handleExcelWorkbook(fileOrTag, workbook, tab, dataKind, dataSource) {
|
|
|
48
48
|
if (sheet) {
|
|
49
49
|
return (c, r) => {
|
|
50
50
|
let cell = sheet[XLSX.utils.encode_cell({ c, r })]
|
|
51
|
-
|
|
51
|
+
if (cell == null || cell.v === '') {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return cdbl(cell.v)
|
|
56
|
+
} catch (_error) {
|
|
57
|
+
// Return null when the cell value cannot be converted to a number;
|
|
58
|
+
// the caller will treat this as the end of data.
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
52
61
|
}
|
|
53
62
|
} else {
|
|
54
63
|
throw new Error(`Direct ${dataKind} worksheet ${tab} in ${dataSource} ${fileOrTag} not found`)
|
|
@@ -72,13 +81,16 @@ function handleCsvFile(file, dataPathname, delimiter, dataKind) {
|
|
|
72
81
|
let data = readCsv(dataPathname, delimiter)
|
|
73
82
|
if (data) {
|
|
74
83
|
return (c, r) => {
|
|
75
|
-
|
|
84
|
+
if (data[r] == null || data[r][c] == null || data[r][c] === '') {
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
76
87
|
try {
|
|
77
|
-
|
|
78
|
-
} catch (
|
|
79
|
-
|
|
88
|
+
return cdbl(data[r][c])
|
|
89
|
+
} catch (_error) {
|
|
90
|
+
// Return null when the cell value cannot be converted to a number;
|
|
91
|
+
// the caller will treat this as the end of data.
|
|
92
|
+
return null
|
|
80
93
|
}
|
|
81
|
-
return value
|
|
82
94
|
}
|
|
83
95
|
} else {
|
|
84
96
|
throw new Error(`Direct ${dataKind} file ${file} could not be read`)
|
package/src/generate/gen-code.js
CHANGED
|
@@ -17,8 +17,9 @@ import { generateJS } from './gen-code-js.js'
|
|
|
17
17
|
* @param {Map<string, any>} opts.directData The mapping of dataset name used in a
|
|
18
18
|
* `GET DIRECT DATA` call (e.g., `?data`) to the tabular data contained in the loaded
|
|
19
19
|
* data file.
|
|
20
|
-
* @param {string} opts.modelDirname The path to the directory containing
|
|
21
|
-
* (
|
|
20
|
+
* @param {string} opts.modelDirname The absolute path to the directory containing data
|
|
21
|
+
* (dat, xlsx, csv) files that are referenced by the model. This path is used for
|
|
22
|
+
* resolving data files for `GET DIRECT SUBSCRIPT` calls.
|
|
22
23
|
* @returns A string containing the generated code.
|
|
23
24
|
*/
|
|
24
25
|
export function generateCode(parsedModel, opts) {
|
|
@@ -82,7 +82,12 @@ export function generateDirectConstInit(variable, directData, modelDir) {
|
|
|
82
82
|
for (let i = 0; i < cellOffsets.length; i++) {
|
|
83
83
|
let rowOffset = cellOffsets[i][0] ? cellOffsets[i][0] : 0
|
|
84
84
|
let colOffset = cellOffsets[i][1] ? cellOffsets[i][1] : 0
|
|
85
|
+
// Use 0.0 as a fallback when the cell is missing, empty, or contains a non-numeric value.
|
|
86
|
+
// (Vensim raises an error in this case, but SDE has historically tolerated invalid cells.)
|
|
85
87
|
let dataValue = getCellValue(startCol + colOffset, startRow + rowOffset)
|
|
88
|
+
if (dataValue == null) {
|
|
89
|
+
dataValue = '0.0'
|
|
90
|
+
}
|
|
86
91
|
let lhs = `${variable.varName}${lhsSubscripts[i] || ''}`
|
|
87
92
|
lines.push(` ${lhs} = ${dataValue};`)
|
|
88
93
|
}
|
|
@@ -116,6 +116,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
116
116
|
// Emit decl/init code for the lookup
|
|
117
117
|
const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
|
|
118
118
|
if (lookupDef.length > 0) {
|
|
119
|
+
if (mode === 'decl') {
|
|
120
|
+
// When declaring a lookup, even if the lookup variable includes dimensions (i.e., is
|
|
121
|
+
// partially apply-to-all), the data variable declarations should not be inside for loops,
|
|
122
|
+
// so we omit them in this case
|
|
123
|
+
return [...lookupDef]
|
|
124
|
+
}
|
|
119
125
|
return [...openLoops, ...lookupDef, ...closeLoops]
|
|
120
126
|
} else {
|
|
121
127
|
return []
|
package/src/generate/gen-expr.js
CHANGED
|
@@ -400,8 +400,11 @@ function generateFunctionCall(callExpr, ctx) {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
case '_GET_DIRECT_CONSTANTS':
|
|
403
|
+
case '_GET_XLS_CONSTANTS':
|
|
403
404
|
case '_GET_DIRECT_DATA':
|
|
405
|
+
case '_GET_XLS_DATA':
|
|
404
406
|
case '_GET_DIRECT_LOOKUPS':
|
|
407
|
+
case '_GET_XLS_LOOKUPS':
|
|
405
408
|
// These functions are handled at a higher level, so we should not get here
|
|
406
409
|
throw new Error(`Unexpected function '${fnId}' in code gen for '${ctx.variable.modelLHS}'`)
|
|
407
410
|
|
|
@@ -970,6 +973,7 @@ function visitVariableRefs(expr, onVarRef) {
|
|
|
970
973
|
break
|
|
971
974
|
|
|
972
975
|
case 'lookup-call':
|
|
976
|
+
visitVariableRefs(expr.varRef, onVarRef)
|
|
973
977
|
visitVariableRefs(expr.arg, onVarRef)
|
|
974
978
|
break
|
|
975
979
|
|
|
@@ -89,14 +89,21 @@ function generateDirectDataLookup(varLhs, getCellValue, timeRowOrCol, startCell,
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
// Read time/value pairs, matching Vensim's behavior:
|
|
93
|
+
// - Stop reading when the first non-numeric time value is encountered. This
|
|
94
|
+
// allows additional content (e.g., labels) to follow the data in the row or column.
|
|
95
|
+
// - Skip pairs with a non-numeric data value, but continue reading subsequent pairs.
|
|
96
|
+
while (true) {
|
|
97
|
+
const timeValue = getCellValue(timeCol, timeRow)
|
|
98
|
+
if (timeValue == null) {
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
const dataValue = getCellValue(dataCol, dataRow)
|
|
102
|
+
if (dataValue != null) {
|
|
103
|
+
lookupData = listConcat(lookupData, `${timeValue}, ${dataValue}`, true)
|
|
104
|
+
lookupSize++
|
|
105
|
+
}
|
|
97
106
|
nextCell()
|
|
98
|
-
dataValue = getCellValue(dataCol, dataRow)
|
|
99
|
-
timeValue = getCellValue(timeCol, timeRow)
|
|
100
107
|
}
|
|
101
108
|
if (lookupSize === 0) {
|
|
102
109
|
throw new Error(`Empty lookup data array for ${varLhs}`)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { indexNamesForSubscript } from '../_shared/subscript.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given the array of LHS subscript/dimension IDs (already mapped to correspond to the
|
|
5
|
+
* RHS positions) and a set of RHS variable instances, return the refIds of the RHS
|
|
6
|
+
* instances whose subscript combinations overlap with the LHS combinations at every
|
|
7
|
+
* position.
|
|
8
|
+
*
|
|
9
|
+
* Conceptually, this is equivalent to checking whether any combination in the LHS
|
|
10
|
+
* cartesian product matches any combination in a given RHS instance's cartesian
|
|
11
|
+
* product. But because positions in a cartesian product are independent, we only
|
|
12
|
+
* need to check that each position has at least one index in common between the
|
|
13
|
+
* LHS and RHS index sets. This reduces the complexity of the check from
|
|
14
|
+
* O(product of dimension sizes) to O(sum of dimension sizes).
|
|
15
|
+
*
|
|
16
|
+
* For example, suppose DimA={A1,A2} and DimB={B1,B2}, the LHS accesses `[DimA,DimB]`,
|
|
17
|
+
* and we want to check whether it overlaps with a RHS variable instance `_x[_dima,_b1]`.
|
|
18
|
+
* The full cartesian products look like this:
|
|
19
|
+
* LHS combos: { (A1,B1), (A1,B2), (A2,B1), (A2,B2) }
|
|
20
|
+
* RHS combos: { (A1,B1), (A2,B1) }
|
|
21
|
+
* The two sets share (A1,B1) and (A2,B1), so there is a match. But we don't need
|
|
22
|
+
* to enumerate either set — we can check each position independently:
|
|
23
|
+
* position 0: LHS {A1,A2} ∩ RHS {A1,A2} = {A1,A2} (non-empty)
|
|
24
|
+
* position 1: LHS {B1,B2} ∩ RHS {B1} = {B1} (non-empty)
|
|
25
|
+
* Every position has at least one index in common, so we know a full-combo match
|
|
26
|
+
* must exist (pick any shared index at each position, e.g., (A2,B1), and it is in
|
|
27
|
+
* both products). Conversely, if any position has an empty intersection, no full
|
|
28
|
+
* combo can match — for example, if instead the LHS accessed `[A1,DimB]` (a specific
|
|
29
|
+
* index at position 0) and the RHS instance were `_x[_a2,_dimb]`, position 0 would
|
|
30
|
+
* give LHS {A1} ∩ RHS {A2} = ∅ and we could stop immediately.
|
|
31
|
+
*
|
|
32
|
+
* @param {string[]} mappedLhsSubIds The array of LHS subscript/dimension IDs at each
|
|
33
|
+
* position, mapped to correspond to the RHS variable reference positions.
|
|
34
|
+
* @param {{ subscripts: string[], refId: string }[]} rhsVarInstances The array of RHS
|
|
35
|
+
* variable instances to filter.
|
|
36
|
+
* @returns {string[]} A sorted array of refIds for the RHS instances whose subscripts
|
|
37
|
+
* overlap with the LHS at every position.
|
|
38
|
+
*/
|
|
39
|
+
export function matchingRhsRefIds(mappedLhsSubIds, rhsVarInstances) {
|
|
40
|
+
// Build a Set of LHS index names for each position for quick lookup
|
|
41
|
+
const lhsIndexSets = mappedLhsSubIds.map(id => new Set(indexNamesForSubscript(id)))
|
|
42
|
+
|
|
43
|
+
// For each RHS variable instance, check if there is overlap at every subscript
|
|
44
|
+
// position between the LHS and RHS index sets
|
|
45
|
+
const rhsRefIds = []
|
|
46
|
+
for (const rhsVarInstance of rhsVarInstances) {
|
|
47
|
+
let matches = true
|
|
48
|
+
for (let i = 0; i < rhsVarInstance.subscripts.length; i++) {
|
|
49
|
+
const rhsIndices = indexNamesForSubscript(rhsVarInstance.subscripts[i])
|
|
50
|
+
let hasOverlap = false
|
|
51
|
+
for (const id of rhsIndices) {
|
|
52
|
+
if (lhsIndexSets[i].has(id)) {
|
|
53
|
+
hasOverlap = true
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!hasOverlap) {
|
|
58
|
+
matches = false
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (matches) {
|
|
63
|
+
rhsRefIds.push(rhsVarInstance.refId)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Return the sorted array of relevant refIds
|
|
68
|
+
// TODO: Sorting is not essential here, but the legacy reader sorted so we will keep
|
|
69
|
+
// that behavior now to avoid invalidating tests. Later we should remove this `sort`
|
|
70
|
+
// call and update the tests accordingly.
|
|
71
|
+
return rhsRefIds.sort()
|
|
72
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseVensimModel } from '@sdeverywhere/parse'
|
|
2
2
|
|
|
3
|
-
import { canonicalName,
|
|
3
|
+
import { canonicalName, newDepreciationVarName, newFixedDelayVarName } from '../_shared/helpers.js'
|
|
4
4
|
|
|
5
|
-
import { hasMapping,
|
|
5
|
+
import { hasMapping, isDimension, isIndex, sub } from '../_shared/subscript.js'
|
|
6
6
|
|
|
7
7
|
import Model from './model.js'
|
|
8
8
|
import { generateDelayVariables } from './read-equation-fn-delay.js'
|
|
@@ -11,6 +11,7 @@ import { generateNpvVariables } from './read-equation-fn-npv.js'
|
|
|
11
11
|
import { generateSmoothVariables } from './read-equation-fn-smooth.js'
|
|
12
12
|
import { generateTrendVariables } from './read-equation-fn-trend.js'
|
|
13
13
|
import { generateLookup } from './read-equation-fn-with-lookup.js'
|
|
14
|
+
import { matchingRhsRefIds } from './read-equations-expand.js'
|
|
14
15
|
import { readVariables } from './read-variables.js'
|
|
15
16
|
|
|
16
17
|
class Context {
|
|
@@ -522,7 +523,8 @@ function visitFunctionCall(v, callExpr, context) {
|
|
|
522
523
|
generateGameVariables(v, callExpr, context)
|
|
523
524
|
break
|
|
524
525
|
|
|
525
|
-
case '_GET_DIRECT_CONSTANTS':
|
|
526
|
+
case '_GET_DIRECT_CONSTANTS':
|
|
527
|
+
case '_GET_XLS_CONSTANTS':
|
|
526
528
|
validateCallDepth(callExpr, context)
|
|
527
529
|
validateCallArgs(callExpr, 3)
|
|
528
530
|
validateCallArgType(callExpr, 0, 'string')
|
|
@@ -536,10 +538,11 @@ function visitFunctionCall(v, callExpr, context) {
|
|
|
536
538
|
startCell: callExpr.args[2].text
|
|
537
539
|
}
|
|
538
540
|
break
|
|
539
|
-
}
|
|
540
541
|
|
|
541
542
|
case '_GET_DIRECT_DATA':
|
|
543
|
+
case '_GET_XLS_DATA':
|
|
542
544
|
case '_GET_DIRECT_LOOKUPS':
|
|
545
|
+
case '_GET_XLS_LOOKUPS':
|
|
543
546
|
validateCallDepth(callExpr, context)
|
|
544
547
|
validateCallArgs(callExpr, 4)
|
|
545
548
|
validateCallArgType(callExpr, 0, 'string')
|
|
@@ -967,21 +970,26 @@ function expandedRefIdsForVar(lhsVariable, rhsBaseRefId, rhsSubIds) {
|
|
|
967
970
|
// it must be non-apply-to-all. The goal now is to determine which instances (refIds) are
|
|
968
971
|
// relevant for the given `lhsVariable` context.
|
|
969
972
|
//
|
|
970
|
-
// First,
|
|
971
|
-
//
|
|
973
|
+
// First, determine the set of LHS subscript indices accessed at each position of the RHS
|
|
974
|
+
// variable reference. For example:
|
|
972
975
|
// y[DimA,DimB,DimC] :EXCEPT: [DimA,DimB,C1] = x[DimA,DimC,DimB]
|
|
973
976
|
// In this case the `DimC` on the RHS is only "accessed" by `C2` from the LHS, so we would
|
|
974
|
-
// build
|
|
975
|
-
// _a1,
|
|
976
|
-
//
|
|
977
|
-
//
|
|
978
|
-
//
|
|
977
|
+
// build a per-position set of accessed indices, like this:
|
|
978
|
+
// position 0 (DimA on RHS): { _a1, _a2 }
|
|
979
|
+
// position 1 (DimC on RHS): { _c2 }
|
|
980
|
+
// position 2 (DimB on RHS): { _b1, _b2 }
|
|
981
|
+
//
|
|
982
|
+
// Then, for each RHS variable instance, check whether every subscript position has at
|
|
983
|
+
// least one index in common between the LHS set and the indices that the RHS instance
|
|
984
|
+
// accepts at that position. If so, add the RHS `refId` to the array of variables
|
|
985
|
+
// referenced by the LHS.
|
|
979
986
|
//
|
|
980
|
-
//
|
|
981
|
-
//
|
|
982
|
-
//
|
|
983
|
-
//
|
|
984
|
-
//
|
|
987
|
+
// Conceptually this is equivalent to checking whether any combination in the LHS
|
|
988
|
+
// cartesian product matches any combination in the RHS cartesian product, but we can
|
|
989
|
+
// avoid computing the cartesian products explicitly because positions in a cartesian
|
|
990
|
+
// product are independent: if every position has at least one element in common, then
|
|
991
|
+
// there exists a full combination that matches. This reduces the complexity from
|
|
992
|
+
// O(product of dimension sizes) to O(sum of dimension sizes).
|
|
985
993
|
//
|
|
986
994
|
// In the following examples, suppose the referenced RHS variable is non-apply-to-all and
|
|
987
995
|
// has two instances:
|
|
@@ -1012,43 +1020,18 @@ function expandedRefIdsForVar(lhsVariable, rhsBaseRefId, rhsSubIds) {
|
|
|
1012
1020
|
// _x[_dima,_c2,_dimb]
|
|
1013
1021
|
//
|
|
1014
1022
|
|
|
1015
|
-
// Step 1:
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
// are relevant for the RHS subscripts/dimensions given the context of the LHS variable
|
|
1019
|
-
// may have been separated/expanded).
|
|
1023
|
+
// Step 1: Resolve the LHS subscript/dimension at each position of the RHS variable
|
|
1024
|
+
// reference. Here `rhsSubIds` is the array of parsed subscript/dimension IDs that
|
|
1025
|
+
// appear in the RHS variable reference. We figure out which LHS subscripts/dimensions
|
|
1026
|
+
// are relevant for the RHS subscripts/dimensions given the context of the LHS variable
|
|
1027
|
+
// (which may have been separated/expanded).
|
|
1020
1028
|
const lhsSubRefs = lhsVariable.parsedEqn.lhs.varDef.subscriptRefs
|
|
1021
1029
|
const lhsSubIds = lhsSubRefs?.map(subRef => subRef.subId) || []
|
|
1022
1030
|
const mappedLhsSubIds = rhsSubIds.map(rhsSubId => resolveRhsSubOrDim(lhsVariable, lhsSubIds, rhsSubId))
|
|
1023
1031
|
|
|
1024
|
-
// Step 2:
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
const mappedLhsCombos = cartesianProductOf(mappedLhsSubIdsPerPosition).map(combo => combo.join(','))
|
|
1028
|
-
|
|
1029
|
-
// Step 3: For each RHS variable instance, get all combinations of RHS subscripts that can
|
|
1030
|
-
// be accepted by that particular RHS instance
|
|
1031
|
-
const rhsRefIds = []
|
|
1032
|
-
for (const rhsVarInstance of rhsVarInstances) {
|
|
1033
|
-
// Build RHS subscript combos (one string of comma-separated subscript IDs for each combo)
|
|
1034
|
-
const rhsVarInstanceSubIdsPerPosition = rhsVarInstance.subscripts.map(indexNamesForSubscript)
|
|
1035
|
-
const rhsCombos = cartesianProductOf(rhsVarInstanceSubIdsPerPosition).map(combo => combo.join(','))
|
|
1036
|
-
|
|
1037
|
-
// See if any of the LHS subscript combos match any of the RHS subscript combos
|
|
1038
|
-
for (const lhsCombo of mappedLhsCombos) {
|
|
1039
|
-
if (rhsCombos.includes(lhsCombo)) {
|
|
1040
|
-
// There was a match; add the refId and break out of the inner loop
|
|
1041
|
-
rhsRefIds.push(rhsVarInstance.refId)
|
|
1042
|
-
break
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
// Return the sorted array of relevant refIds
|
|
1048
|
-
// TODO: Sorting is not essential here, but the legacy reader sorted so we will keep that
|
|
1049
|
-
// behavior now to avoid invalidating tests. Later we should remove this `sort` call and
|
|
1050
|
-
// update the tests accordingly.
|
|
1051
|
-
return rhsRefIds.sort()
|
|
1032
|
+
// Step 2: Find the RHS variable instances whose subscripts overlap with the LHS
|
|
1033
|
+
// subscripts at every position
|
|
1034
|
+
return matchingRhsRefIds(mappedLhsSubIds, rhsVarInstances)
|
|
1052
1035
|
}
|
|
1053
1036
|
|
|
1054
1037
|
/**
|
|
@@ -197,7 +197,13 @@ function subscriptPositionsToExpand(subIds, exceptSubIdSets, separationDims, rhs
|
|
|
197
197
|
|
|
198
198
|
if (!expand) {
|
|
199
199
|
// Direct data vars with subscripts are separated because we generate a lookup for each index
|
|
200
|
-
if (
|
|
200
|
+
if (
|
|
201
|
+
isDimension(subId) &&
|
|
202
|
+
(rhsText.includes('GET DIRECT DATA') ||
|
|
203
|
+
rhsText.includes('GET XLS DATA') ||
|
|
204
|
+
rhsText.includes('GET DIRECT LOOKUPS') ||
|
|
205
|
+
rhsText.includes('GET XLS LOOKUPS'))
|
|
206
|
+
) {
|
|
201
207
|
expand = true
|
|
202
208
|
}
|
|
203
209
|
}
|
|
@@ -32,8 +32,8 @@ import { generateCode } from './generate/gen-code.js'
|
|
|
32
32
|
* @param {string[]} operations The set of operations to perform; can include 'generateC', 'generateJS',
|
|
33
33
|
* 'printVarList', 'printRefIdTest', 'convertNames'. If the array is empty, the model will be
|
|
34
34
|
* read but no operation will be performed.
|
|
35
|
-
* @param {string} modelDirname The absolute path to the directory containing
|
|
36
|
-
*
|
|
35
|
+
* @param {string} modelDirname The absolute path to the directory containing data (dat, xlsx, csv)
|
|
36
|
+
* files that are referenced by the model. These files will be resolved relative to this directory.
|
|
37
37
|
* @param {string} modelName The model name (without the mdl extension).
|
|
38
38
|
* @param {string} buildDir The output directory where the C or list files will be written.
|
|
39
39
|
* @param {string} [varname] The variable name passed to the 'sde causes' command.
|
|
@@ -134,8 +134,8 @@ export function printNames(namesPathname, operation) {
|
|
|
134
134
|
*
|
|
135
135
|
* @param {string} input The string containing the model text.
|
|
136
136
|
* @param {string} modelKind The kind of model to parse, either 'vensim' or 'xmile'.
|
|
137
|
-
* @param {string} modelDir The absolute path to the directory containing
|
|
138
|
-
*
|
|
137
|
+
* @param {string} modelDir The absolute path to the directory containing data (dat, xlsx, csv)
|
|
138
|
+
* files that are referenced by the model. These files will be resolved relative to this directory.
|
|
139
139
|
* @param {Object} [options] The options that control parsing.
|
|
140
140
|
* @param {boolean} options.sort Whether to sort definitions alphabetically in the preprocess step.
|
|
141
141
|
* @return {*} A parsed tree representation of the model.
|
|
@@ -147,7 +147,7 @@ export function parseModel(input, modelKind, modelDir, options) {
|
|
|
147
147
|
if (modelDir) {
|
|
148
148
|
parseContext = {
|
|
149
149
|
getDirectSubscripts(fileName, tabOrDelimiter, firstCell, lastCell /*, prefix*/) {
|
|
150
|
-
// Resolve the CSV file relative the model directory
|
|
150
|
+
// Resolve the CSV file relative to the model directory
|
|
151
151
|
const csvPath = path.resolve(modelDir, fileName)
|
|
152
152
|
|
|
153
153
|
// Read the subscripts from the CSV file
|