@sdeverywhere/compile 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +12 -0
- package/package.json +40 -0
- package/src/_shared/helpers.js +351 -0
- package/src/_shared/read-dat.js +71 -0
- package/src/_shared/subscript.js +373 -0
- package/src/generate/code-gen.js +356 -0
- package/src/generate/equation-gen.js +1188 -0
- package/src/generate/loop-index-vars.js +27 -0
- package/src/generate/model-lhs-reader.js +88 -0
- package/src/index.js +8 -0
- package/src/model/equation-reader.js +708 -0
- package/src/model/expr-reader.js +202 -0
- package/src/model/model.js +1045 -0
- package/src/model/subscript-range-reader.js +123 -0
- package/src/model/toposort.js +100 -0
- package/src/model/var-name-reader.js +40 -0
- package/src/model/variable-reader.js +172 -0
- package/src/model/variable.js +111 -0
- package/src/parse/model-reader.js +141 -0
- package/src/parse/parser.js +32 -0
- package/src/parse-and-generate.js +114 -0
- package/src/preprocess/preprocessor.js +247 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import util from 'util'
|
|
2
|
+
import B from 'bufx'
|
|
3
|
+
import yaml from 'js-yaml'
|
|
4
|
+
import R from 'ramda'
|
|
5
|
+
import { canonicalName, asort, vlog } from './helpers.js'
|
|
6
|
+
|
|
7
|
+
// A subscript is a dimension or an index.
|
|
8
|
+
// Both have the same properties: model name, canonical name, family, values.
|
|
9
|
+
|
|
10
|
+
// |Property |Description |
|
|
11
|
+
// |-------------|------------------------------------------------------------------|
|
|
12
|
+
// |modelName |subscript name in Vensim format |
|
|
13
|
+
// |modelValue |subscript value in Vensim format |
|
|
14
|
+
// |modelMappings|mappings from a dimension to a mapping value in Vensim format |
|
|
15
|
+
// |name |subscript name in canonical C format |
|
|
16
|
+
// |value |subscript value in canonical C format |
|
|
17
|
+
// |mappings |mappings from a dimension to a mapping value in canonical C format|
|
|
18
|
+
// |size |number of indices in a dimension or 1 for an index |
|
|
19
|
+
// |family |subscript family on canonical C format (finalized later) |
|
|
20
|
+
|
|
21
|
+
// A family is a dimension that is a reference to the maximal dimension containing an index or subdimension.
|
|
22
|
+
// The family of the maximal dimension is itself.
|
|
23
|
+
// Values are a single number for indices and an array of subscripts for dimensions.
|
|
24
|
+
// A Variable includes a list of subscripts in normal order.
|
|
25
|
+
// Mappings give a list of indices in the mapping value that map in order to to-dim indices.
|
|
26
|
+
|
|
27
|
+
// Subscript API
|
|
28
|
+
//
|
|
29
|
+
// Subscript(modelName, modelValue, modelFamily, modelMappings)
|
|
30
|
+
// with modelMappings of the form [ { toDim: dimension, value: indexArray } ]
|
|
31
|
+
// sets a subscript dimension that maps the index elements in indexArray
|
|
32
|
+
// to the index elements in another dimension
|
|
33
|
+
// Subscript(modelName)
|
|
34
|
+
// gets the subscript with the model name
|
|
35
|
+
// Subscript(modelName, modelValue) with modelValue as array
|
|
36
|
+
// sets a subscript dimension and its indices
|
|
37
|
+
// Subscript(modelName, modelValue, modelFamily) with modelValue as number
|
|
38
|
+
// sets a subscript element and its index value in the subscript family
|
|
39
|
+
// Subscript(modelName, '', modelFamily, [])
|
|
40
|
+
// sets modelName as an alias for the dimension named as modelFamily
|
|
41
|
+
//
|
|
42
|
+
// Call Subscript with an array of subscript indices to establish a dimension.
|
|
43
|
+
// If there is a mapping to another dimension, give modelMappings in the call.
|
|
44
|
+
// Then call Subscript with each individual subscript index name and its numeric index value.
|
|
45
|
+
// All dimension and index names are in model form, not canonical form.
|
|
46
|
+
// They must be converted to canonical form later after all subscripts are read.
|
|
47
|
+
|
|
48
|
+
// The Subscript module maintains a subscript map with the canonical
|
|
49
|
+
// subscript name as the key and a subscript object as the value.
|
|
50
|
+
let subscripts = new Map()
|
|
51
|
+
|
|
52
|
+
export function Subscript(modelName, modelValue = null, modelFamily = null, modelMappings = null) {
|
|
53
|
+
let name = canonicalName(modelName)
|
|
54
|
+
if (modelValue === null) {
|
|
55
|
+
// Look up a subscript by its model name.
|
|
56
|
+
return sub(name)
|
|
57
|
+
}
|
|
58
|
+
let value, size
|
|
59
|
+
if (Array.isArray(modelValue)) {
|
|
60
|
+
// Map the subscript value array into canonical form.
|
|
61
|
+
value = R.map(x => canonicalName(x), modelValue)
|
|
62
|
+
size = value.length
|
|
63
|
+
} else if (typeof modelValue === 'number') {
|
|
64
|
+
// The value is an index.
|
|
65
|
+
value = modelValue
|
|
66
|
+
size = 1
|
|
67
|
+
} else if (modelValue === '') {
|
|
68
|
+
// An empty value string indicates a subscript alias given by the modelFamily.
|
|
69
|
+
value = ''
|
|
70
|
+
size = 0
|
|
71
|
+
}
|
|
72
|
+
// Convert the model family into canonical form.
|
|
73
|
+
if (modelFamily === null) {
|
|
74
|
+
// The default family is the subscript itself.
|
|
75
|
+
modelFamily = modelName
|
|
76
|
+
}
|
|
77
|
+
let family = canonicalName(modelFamily)
|
|
78
|
+
// Convert the subscript mappings into canonical form.
|
|
79
|
+
let mappings = {}
|
|
80
|
+
if (modelMappings && !R.isEmpty(modelMappings)) {
|
|
81
|
+
for (let m of modelMappings) {
|
|
82
|
+
mappings[canonicalName(m.toDim)] = R.map(subName => canonicalName(subName), m.value)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Save the subscript as an object in the subscripts store.
|
|
86
|
+
let subscript = {
|
|
87
|
+
modelName,
|
|
88
|
+
modelValue,
|
|
89
|
+
modelMappings,
|
|
90
|
+
name,
|
|
91
|
+
value,
|
|
92
|
+
size,
|
|
93
|
+
family,
|
|
94
|
+
mappings
|
|
95
|
+
}
|
|
96
|
+
subscripts.set(name, subscript)
|
|
97
|
+
return subscript
|
|
98
|
+
}
|
|
99
|
+
export function sub(name) {
|
|
100
|
+
// Look up a subscript by its canonical name.
|
|
101
|
+
// Return undefined if the name is not a subscript name.
|
|
102
|
+
let result
|
|
103
|
+
try {
|
|
104
|
+
result = subscripts.get(name)
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error(`sub name ${name} not found`)
|
|
107
|
+
}
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
export function isIndex(name) {
|
|
111
|
+
let s = sub(name)
|
|
112
|
+
return s && typeof s.value === 'number'
|
|
113
|
+
}
|
|
114
|
+
export function isDimension(name) {
|
|
115
|
+
let s = sub(name)
|
|
116
|
+
return s && Array.isArray(s.value)
|
|
117
|
+
}
|
|
118
|
+
export function isSubdimension(name) {
|
|
119
|
+
let result = false
|
|
120
|
+
let s = sub(name)
|
|
121
|
+
if (s && Array.isArray(s.value)) {
|
|
122
|
+
result = s.size < sub(s.family).size
|
|
123
|
+
}
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
export function isTrivialDimension(name) {
|
|
127
|
+
// Return true if the dimension values are trivial, i.e., {0, 1, 2, ..., n-1}
|
|
128
|
+
let s = sub(name)
|
|
129
|
+
if (!s || !Array.isArray(s.value)) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
// The following evaluates to true when all sub-dimensions match their position in the array
|
|
133
|
+
return R.addIndex(R.all)((subdim, idx) => sub(subdim).value === idx, s.value)
|
|
134
|
+
}
|
|
135
|
+
export function indexInSepDim(ind, v) {
|
|
136
|
+
// Find the separation dim in the variable that includes the index, or return null.
|
|
137
|
+
let result = null
|
|
138
|
+
for (let sepDim of v.separationDims) {
|
|
139
|
+
if (sub(sepDim).value.includes(ind)) {
|
|
140
|
+
result = sepDim
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result
|
|
145
|
+
}
|
|
146
|
+
export function subscriptsMatch(s1, s2) {
|
|
147
|
+
// Return true when subscript s1 matches subscript s2.
|
|
148
|
+
let matches = false
|
|
149
|
+
if (isIndex(s1) && isIndex(s2)) {
|
|
150
|
+
matches = s1 === s2
|
|
151
|
+
} else if (isDimension(s1) && isDimension(s2)) {
|
|
152
|
+
matches = s1 === s2
|
|
153
|
+
if (!matches) {
|
|
154
|
+
// Also match when s2 is a subdimension of s1.
|
|
155
|
+
matches = sub(s2).family === sub(s1).family && sub(s2).value.length < sub(s1).value.length
|
|
156
|
+
}
|
|
157
|
+
} else if (isDimension(s1) && isIndex(s2)) {
|
|
158
|
+
matches = sub(s1).value.includes(s2)
|
|
159
|
+
} else if (isIndex(s1) && isDimension(s2)) {
|
|
160
|
+
matches = sub(s2).value.includes(s1)
|
|
161
|
+
}
|
|
162
|
+
return matches
|
|
163
|
+
}
|
|
164
|
+
export function addIndex(name, value, family) {
|
|
165
|
+
// Add an index with arguments in canonical form.
|
|
166
|
+
let subscript = {
|
|
167
|
+
name: name,
|
|
168
|
+
value: value,
|
|
169
|
+
size: 1,
|
|
170
|
+
family: family,
|
|
171
|
+
mappings: {}
|
|
172
|
+
}
|
|
173
|
+
subscripts.set(name, subscript)
|
|
174
|
+
}
|
|
175
|
+
export function hasMapping(fromSubscript, toSubscript) {
|
|
176
|
+
let subFrom = sub(fromSubscript)
|
|
177
|
+
let subTo = sub(toSubscript)
|
|
178
|
+
if (subFrom === undefined) {
|
|
179
|
+
vlog('ERROR: undefined hasMapping fromSubscript', fromSubscript)
|
|
180
|
+
}
|
|
181
|
+
if (subTo === undefined) {
|
|
182
|
+
vlog('ERROR: undefined hasMapping toSubscript', toSubscript)
|
|
183
|
+
}
|
|
184
|
+
if (subFrom.mappings[toSubscript]) {
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
export function mapIndex(fromSubName, fromIndexName, toSubName) {
|
|
190
|
+
// Return the index names that the fromSubName dimension maps from fromIndexName
|
|
191
|
+
// to the toSubName dimension. Return an empty array if there is no such mapping.
|
|
192
|
+
let toIndexNames = []
|
|
193
|
+
let fromSub = sub(fromSubName)
|
|
194
|
+
let toSub = sub(toSubName)
|
|
195
|
+
if (fromSub && toSub && isDimension(fromSubName) && isDimension(toSubName)) {
|
|
196
|
+
let mapping = fromSub.mappings[toSubName]
|
|
197
|
+
if (mapping) {
|
|
198
|
+
// Find the positions of fromIndexName in the mapping.
|
|
199
|
+
for (let pos = 0; pos < mapping.length; pos++) {
|
|
200
|
+
if (mapping[pos] === fromIndexName) {
|
|
201
|
+
// Return the index name at the same position in the toSub dimension.
|
|
202
|
+
toIndexNames.push(toSub.value[pos])
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return toIndexNames
|
|
208
|
+
}
|
|
209
|
+
export function printSubscripts() {
|
|
210
|
+
B.clearBuf()
|
|
211
|
+
for (let [k, v] of subscripts) {
|
|
212
|
+
B.emitLine(`${k}:\n${util.inspect(v, { depth: null })}\n`)
|
|
213
|
+
}
|
|
214
|
+
return B.getBuf()
|
|
215
|
+
}
|
|
216
|
+
export function yamlSubsList() {
|
|
217
|
+
let subs = {}
|
|
218
|
+
for (let [k, v] of subscripts) {
|
|
219
|
+
subs[k] = v
|
|
220
|
+
}
|
|
221
|
+
return yaml.safeDump(subs)
|
|
222
|
+
}
|
|
223
|
+
export function loadSubscriptsFromYaml(yamlSubs) {
|
|
224
|
+
// Load the subscripts map from subscripts serialized to a YAML file by yamlSubsList.
|
|
225
|
+
// This function should be called instead of adding subscripts through the constructor.
|
|
226
|
+
let subs = yaml.safeLoad(yamlSubs)
|
|
227
|
+
for (const k in subs) {
|
|
228
|
+
subscripts.set(k, subs[k])
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export function normalizeSubscripts(subscripts) {
|
|
232
|
+
// Sort a list of subscript names already in canonical form according to the subscript family.
|
|
233
|
+
let subs = R.map(name => sub(name), subscripts)
|
|
234
|
+
subs = R.sortBy(R.prop('family'), subs)
|
|
235
|
+
let normalizedSubs
|
|
236
|
+
try {
|
|
237
|
+
normalizedSubs = R.map(R.prop('name'), subs)
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error(`normalizeSubscripts fails for ${subscripts}`)
|
|
240
|
+
}
|
|
241
|
+
return normalizedSubs
|
|
242
|
+
}
|
|
243
|
+
export function extractMarkedDims(subscripts) {
|
|
244
|
+
// Extract all marked dimensions and update subscripts.
|
|
245
|
+
let dims = []
|
|
246
|
+
for (let i = 0; i < subscripts.length; i++) {
|
|
247
|
+
if (subscripts[i].includes('!')) {
|
|
248
|
+
// Remove the "!" from the subscript name and save it as a marked dimension.
|
|
249
|
+
subscripts[i] = subscripts[i].replace('!', '')
|
|
250
|
+
dims.push(subscripts[i])
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return dims
|
|
254
|
+
}
|
|
255
|
+
export function subscriptFamilies(subscripts) {
|
|
256
|
+
// Return a list of the subscript families for each subscript.
|
|
257
|
+
try {
|
|
258
|
+
return R.map(subscriptName => sub(subscriptName).family, subscripts)
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.error(`ERROR: subscript not found in "${subscripts.join(',')}" in subscriptFamilies`)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export function subscriptFamily(subscriptName) {
|
|
264
|
+
// Return the subscript family object for the subscript name.
|
|
265
|
+
let family = sub(subscriptName).family
|
|
266
|
+
return sub(family)
|
|
267
|
+
}
|
|
268
|
+
export function allSubscripts() {
|
|
269
|
+
// Return an array of all subscript objects.
|
|
270
|
+
return [...subscripts.values()]
|
|
271
|
+
}
|
|
272
|
+
export function allDimensions() {
|
|
273
|
+
// Return an array of all dimension subscript objects.
|
|
274
|
+
return R.filter(subscript => Array.isArray(subscript.value), allSubscripts())
|
|
275
|
+
}
|
|
276
|
+
export function allAliases() {
|
|
277
|
+
// Return an array of all subscript aliases.
|
|
278
|
+
return R.filter(subscript => subscript.value === '', allSubscripts())
|
|
279
|
+
}
|
|
280
|
+
export function allMappings() {
|
|
281
|
+
// Return an array of all subscript mappings as objects.
|
|
282
|
+
let mappings = []
|
|
283
|
+
R.forEach(subscript => {
|
|
284
|
+
R.forEach(mapTo => {
|
|
285
|
+
mappings.push({
|
|
286
|
+
mapFrom: subscript.name,
|
|
287
|
+
mapTo: mapTo,
|
|
288
|
+
value: subscript.mappings[mapTo]
|
|
289
|
+
})
|
|
290
|
+
}, Object.keys(subscript.mappings))
|
|
291
|
+
}, allSubscripts())
|
|
292
|
+
return mappings
|
|
293
|
+
}
|
|
294
|
+
export function indexNamesForSubscript(subscript) {
|
|
295
|
+
// Return a list of index names for a subscript in canonical form.
|
|
296
|
+
if (isIndex(subscript)) {
|
|
297
|
+
// The subscript is an index, so just return it.
|
|
298
|
+
return [subscript]
|
|
299
|
+
} else {
|
|
300
|
+
// Return a list of index names for the dimension.
|
|
301
|
+
let dim = sub(subscript)
|
|
302
|
+
if (!dim) {
|
|
303
|
+
vlog('ERROR: no indexNamesForSubscript', subscript)
|
|
304
|
+
console.trace()
|
|
305
|
+
return []
|
|
306
|
+
}
|
|
307
|
+
return dim.value
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
export function separatedVariableIndex(rhsSub, variable, rhsSubscripts) {
|
|
311
|
+
// Given an RHS subscript, find an LHS index in the separation dimension that matches it.
|
|
312
|
+
// The LHS and RHS subscripts need not be in normal order or have the same number of subscripts in a var.
|
|
313
|
+
// The search proceeds through three stages:
|
|
314
|
+
// 1. Find a sepDim that matches rhsSub.
|
|
315
|
+
// 2. Then find an lhsSub in the same family as the sepDim.
|
|
316
|
+
// 3. Further qualify the lhsSub.
|
|
317
|
+
|
|
318
|
+
// If rhsSub is found on the LHS, don't convert it into an index.
|
|
319
|
+
if (!variable.subscripts.includes(rhsSub)) {
|
|
320
|
+
// (1)
|
|
321
|
+
for (let sepDim of variable.separationDims) {
|
|
322
|
+
if (rhsSub === sepDim || hasMapping(rhsSub, sepDim)) {
|
|
323
|
+
// (2)
|
|
324
|
+
for (let lhsSub of variable.subscripts) {
|
|
325
|
+
if (sub(lhsSub).family === sub(sepDim).family) {
|
|
326
|
+
if (!isIndex(lhsSub)) {
|
|
327
|
+
console.error(`ERROR: ${variable.refId} subscript in separation dimension ${sepDim} is not an index`)
|
|
328
|
+
} else {
|
|
329
|
+
// (3)
|
|
330
|
+
if (rhsSub === sepDim) {
|
|
331
|
+
// There may be more than one lhsSub in the same family as rhsSub.
|
|
332
|
+
// Pick the one that belongs to the rhsSub.
|
|
333
|
+
// If there are two LHS subs both in the same family, choose by position instead.
|
|
334
|
+
if (
|
|
335
|
+
rhsSubscripts &&
|
|
336
|
+
variable.subscripts.length === 2 &&
|
|
337
|
+
rhsSubscripts.length === 2 &&
|
|
338
|
+
sub(variable.subscripts[0]).family === sub(variable.subscripts[1]).family
|
|
339
|
+
) {
|
|
340
|
+
let pos = rhsSubscripts.indexOf(rhsSub)
|
|
341
|
+
return variable.subscripts[pos]
|
|
342
|
+
} else {
|
|
343
|
+
if (indexNamesForSubscript(rhsSub).includes(lhsSub)) {
|
|
344
|
+
return lhsSub
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
// Find the index that maps from the subscript dimension to the separated var index.
|
|
349
|
+
for (let fromIndexName of sub(rhsSub).value) {
|
|
350
|
+
let mappedIndices = mapIndex(rhsSub, fromIndexName, sepDim)
|
|
351
|
+
if (mappedIndices.includes(lhsSub)) {
|
|
352
|
+
return fromIndexName
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
// Function to filter canonical dimension names from a list of names
|
|
365
|
+
export let dimensionNames = R.pipe(
|
|
366
|
+
R.filter(subscript => isDimension(subscript)),
|
|
367
|
+
asort
|
|
368
|
+
)
|
|
369
|
+
// Function to filter canonical index names from a list of names
|
|
370
|
+
export let indexNames = R.pipe(
|
|
371
|
+
R.filter(subscript => isIndex(subscript)),
|
|
372
|
+
asort
|
|
373
|
+
)
|