@sdeverywhere/compile 0.7.17 → 0.7.19
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 +1 -3
- package/src/generate/expand-var-names.js +0 -10
- package/src/generate/{code-gen.js → gen-code-c.js} +188 -89
- package/src/generate/gen-code-js.js +616 -0
- package/src/generate/gen-code.js +36 -0
- package/src/generate/gen-equation.js +16 -6
- package/src/generate/gen-expr.js +205 -48
- package/src/generate/gen-lookup-from-direct.js +14 -6
- package/src/generate/gen-lookup-from-external.js +20 -5
- package/src/generate/gen-lookup-from-points.js +28 -6
- package/src/index.js +38 -3
- package/src/model/model.js +169 -170
- package/src/model/read-equation-fn-game.js +50 -0
- package/src/model/read-equations.js +9 -10
- package/src/model/read-variables.js +2 -2
- package/src/model/variable.js +6 -21
- package/src/parse-and-generate.js +22 -19
- package/src/preprocess/preprocessor.js +2 -2
- package/src/generate/equation-gen.js +0 -1268
- package/src/generate/model-lhs-reader.js +0 -88
- package/src/model/equation-reader.js +0 -723
- package/src/model/expr-reader.js +0 -202
- package/src/model/subscript-range-reader.js +0 -143
- package/src/model/var-name-reader.js +0 -40
- package/src/model/variable-reader.js +0 -172
- package/src/parse/model-reader.js +0 -141
- package/src/parse/parser.js +0 -36
package/src/model/expr-reader.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { ModelLexer, ModelVisitor } from 'antlr4-vensim'
|
|
2
|
-
|
|
3
|
-
import { canonicalName } from '../_shared/helpers.js'
|
|
4
|
-
import { createParser } from '../parse/parser.js'
|
|
5
|
-
|
|
6
|
-
import Model from './model.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Reads an expression and determines if it resolves to a constant numeric value.
|
|
10
|
-
* This depends on having access to the model variables, so this should be used
|
|
11
|
-
* only after the `readVariables` process has been completed and the spec file
|
|
12
|
-
* has been loaded.
|
|
13
|
-
*/
|
|
14
|
-
export default class ExprReader extends ModelVisitor {
|
|
15
|
-
constructor() {
|
|
16
|
-
super()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
read(exprText) {
|
|
20
|
-
let parser = createParser(exprText)
|
|
21
|
-
let expr = parser.expr()
|
|
22
|
-
expr.accept(this)
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
constantValue: this.constantValue
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Constants
|
|
30
|
-
|
|
31
|
-
visitConst(ctx) {
|
|
32
|
-
const constantValue = parseFloat(ctx.Const().getText())
|
|
33
|
-
if (!Number.isNaN(constantValue)) {
|
|
34
|
-
this.constantValue = constantValue
|
|
35
|
-
} else {
|
|
36
|
-
this.constantValue = undefined
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
visitConstList() {
|
|
40
|
-
this.constantValue = undefined
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Function calls and variables
|
|
44
|
-
|
|
45
|
-
visitCall() {
|
|
46
|
-
// Treat function calls as non-constant (can't easily determine if they
|
|
47
|
-
// resolve to a constant)
|
|
48
|
-
this.constantValue = undefined
|
|
49
|
-
}
|
|
50
|
-
visitExprList() {
|
|
51
|
-
// Treat function calls as non-constant (can't easily determine if they
|
|
52
|
-
// resolve to a constant)
|
|
53
|
-
this.constantValue = undefined
|
|
54
|
-
}
|
|
55
|
-
visitVar(ctx) {
|
|
56
|
-
// Determine whether this variable has a constant value
|
|
57
|
-
const varName = ctx.Id().getText().trim()
|
|
58
|
-
const cName = canonicalName(varName)
|
|
59
|
-
const v = Model.varWithName(cName)
|
|
60
|
-
const modelFormula = v?.modelFormula?.trim() || ''
|
|
61
|
-
const isNumber = modelFormula.match(/^[+-]?\d+(\.\d+)?$/)
|
|
62
|
-
const canBeOverridden = Model.isInputVar(cName)
|
|
63
|
-
if (v && isNumber && !canBeOverridden) {
|
|
64
|
-
const numValue = parseFloat(modelFormula)
|
|
65
|
-
if (!Number.isNaN(numValue)) {
|
|
66
|
-
this.constantValue = numValue
|
|
67
|
-
} else {
|
|
68
|
-
this.constantValue = undefined
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
this.constantValue = undefined
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Lookups
|
|
76
|
-
|
|
77
|
-
visitLookup() {
|
|
78
|
-
this.constantValue = undefined
|
|
79
|
-
}
|
|
80
|
-
visitLookupCall() {
|
|
81
|
-
this.constantValue = undefined
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Unary operators
|
|
85
|
-
|
|
86
|
-
visitNegative(ctx) {
|
|
87
|
-
super.visitNegative(ctx)
|
|
88
|
-
if (this.constantValue !== undefined) {
|
|
89
|
-
this.constantValue = -this.constantValue
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
visitPositive(ctx) {
|
|
93
|
-
super.visitPositive(ctx)
|
|
94
|
-
if (this.constantValue !== undefined) {
|
|
95
|
-
this.constantValue = +this.constantValue
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
visitNot(ctx) {
|
|
99
|
-
super.visitNot(ctx)
|
|
100
|
-
if (this.constantValue !== undefined) {
|
|
101
|
-
this.constantValue = !this.constantValue
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Binary operators
|
|
106
|
-
|
|
107
|
-
visitBinaryArgs(ctx, combine) {
|
|
108
|
-
ctx.expr(0).accept(this)
|
|
109
|
-
const constantValue0 = this.constantValue
|
|
110
|
-
ctx.expr(1).accept(this)
|
|
111
|
-
const constantValue1 = this.constantValue
|
|
112
|
-
if (constantValue0 !== undefined && constantValue1 !== undefined) {
|
|
113
|
-
this.constantValue = combine(constantValue0, constantValue1)
|
|
114
|
-
} else {
|
|
115
|
-
this.constantValue = undefined
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
visitPower(ctx) {
|
|
120
|
-
this.visitBinaryArgs(ctx, (a, b) => Math.pow(a, b))
|
|
121
|
-
}
|
|
122
|
-
visitMulDiv(ctx) {
|
|
123
|
-
this.visitBinaryArgs(ctx, (a, b) => {
|
|
124
|
-
if (ctx.op.type === ModelLexer.Star) {
|
|
125
|
-
return a * b
|
|
126
|
-
} else {
|
|
127
|
-
return a / b
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
visitAddSub(ctx) {
|
|
132
|
-
this.visitBinaryArgs(ctx, (a, b) => {
|
|
133
|
-
if (ctx.op.type === ModelLexer.Plus) {
|
|
134
|
-
return a + b
|
|
135
|
-
} else {
|
|
136
|
-
return a - b
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
visitRelational(ctx) {
|
|
141
|
-
this.visitBinaryArgs(ctx, (a, b) => {
|
|
142
|
-
if (ctx.op.type === ModelLexer.Less) {
|
|
143
|
-
return a < b ? 1 : 0
|
|
144
|
-
} else if (ctx.op.type === ModelLexer.Greater) {
|
|
145
|
-
return a > b ? 1 : 0
|
|
146
|
-
} else if (ctx.op.type === ModelLexer.LessEqual) {
|
|
147
|
-
return a <= b ? 1 : 0
|
|
148
|
-
} else {
|
|
149
|
-
return a >= b ? 1 : 0
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
visitEquality(ctx) {
|
|
154
|
-
this.visitBinaryArgs(ctx, (a, b) => {
|
|
155
|
-
if (ctx.op.type === ModelLexer.Equal) {
|
|
156
|
-
return a === b ? 1 : 0
|
|
157
|
-
} else {
|
|
158
|
-
return a !== b ? 1 : 0
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
visitAnd(ctx) {
|
|
163
|
-
// For AND expressions, we don't need both sides to have a constant value; as
|
|
164
|
-
// long as one side is known to be false, then the expression resolves to false
|
|
165
|
-
ctx.expr(0).accept(this)
|
|
166
|
-
const constantValue0 = this.constantValue
|
|
167
|
-
ctx.expr(1).accept(this)
|
|
168
|
-
const constantValue1 = this.constantValue
|
|
169
|
-
if (constantValue0 !== undefined && constantValue1 !== undefined) {
|
|
170
|
-
this.constantValue = constantValue0 && constantValue1
|
|
171
|
-
} else if (constantValue0 !== undefined && !constantValue0) {
|
|
172
|
-
this.constantValue = 0
|
|
173
|
-
} else if (constantValue1 !== undefined && !constantValue1) {
|
|
174
|
-
this.constantValue = 0
|
|
175
|
-
} else {
|
|
176
|
-
this.constantValue = undefined
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
visitOr(ctx) {
|
|
180
|
-
// For OR expressions, we don't need both sides to have a constant value; as
|
|
181
|
-
// long as one side is known to be true, then the expression resolves to true
|
|
182
|
-
ctx.expr(0).accept(this)
|
|
183
|
-
const constantValue0 = this.constantValue
|
|
184
|
-
ctx.expr(1).accept(this)
|
|
185
|
-
const constantValue1 = this.constantValue
|
|
186
|
-
if (constantValue0 !== undefined && constantValue1 !== undefined) {
|
|
187
|
-
this.constantValue = constantValue0 || constantValue1
|
|
188
|
-
} else if (constantValue0 !== undefined && constantValue0) {
|
|
189
|
-
this.constantValue = 1
|
|
190
|
-
} else if (constantValue1 !== undefined && constantValue1) {
|
|
191
|
-
this.constantValue = 1
|
|
192
|
-
} else {
|
|
193
|
-
this.constantValue = undefined
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Tokens
|
|
198
|
-
|
|
199
|
-
visitParens(ctx) {
|
|
200
|
-
super.visitParens(ctx)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { ModelParser } from 'antlr4-vensim'
|
|
3
|
-
import * as R from 'ramda'
|
|
4
|
-
import XLSX from 'xlsx'
|
|
5
|
-
|
|
6
|
-
import { cFunctionName, matchRegex, readCsv } from '../_shared/helpers.js'
|
|
7
|
-
import { Subscript } from '../_shared/subscript.js'
|
|
8
|
-
import ModelReader from '../parse/model-reader.js'
|
|
9
|
-
|
|
10
|
-
export default class SubscriptRangeReader extends ModelReader {
|
|
11
|
-
constructor(modelDirname) {
|
|
12
|
-
super()
|
|
13
|
-
// The model directory is required when reading data files for GET DIRECT SUBSCRIPT.
|
|
14
|
-
this.modelDirname = modelDirname
|
|
15
|
-
// Index names from a subscript list or GET DIRECT SUBSCRIPT
|
|
16
|
-
this.indNames = []
|
|
17
|
-
// Dimension mappings with model names
|
|
18
|
-
this.modelMappings = []
|
|
19
|
-
}
|
|
20
|
-
visitModel(ctx) {
|
|
21
|
-
let subscriptRanges = ctx.subscriptRange()
|
|
22
|
-
if (subscriptRanges) {
|
|
23
|
-
for (let subscriptRange of subscriptRanges) {
|
|
24
|
-
subscriptRange.accept(this)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
visitSubscriptRange(ctx) {
|
|
29
|
-
// When entering a new subscript range definition, reset the properties that will be filled in.
|
|
30
|
-
this.indNames = []
|
|
31
|
-
this.modelMappings = []
|
|
32
|
-
// A subscript alias has two Ids, while a regular subscript range definition has just one.
|
|
33
|
-
if (ctx.Id().length === 1) {
|
|
34
|
-
// Subscript range definitions have a dimension name.
|
|
35
|
-
let modelName = ctx.Id()[0].getText()
|
|
36
|
-
// Visit children to fill in the subscript range definition.
|
|
37
|
-
super.visitSubscriptRange(ctx)
|
|
38
|
-
// Create a new subscript range definition from Vensim-format names.
|
|
39
|
-
// The family is provisionally set to the dimension name.
|
|
40
|
-
// It will be updated to the maximal dimension if this is a subdimension.
|
|
41
|
-
// The mapping value contains dimensions and indices in the toDim.
|
|
42
|
-
// It will be expanded and inverted to fromDim indices later.
|
|
43
|
-
Subscript(modelName, this.indNames, modelName, this.modelMappings)
|
|
44
|
-
} else {
|
|
45
|
-
let modelName = ctx.Id()[0].getText()
|
|
46
|
-
let modelFamily = ctx.Id()[1].getText()
|
|
47
|
-
Subscript(modelName, '', modelFamily, [])
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
visitSubscriptList(ctx) {
|
|
51
|
-
// Get the subscripts from each subscript index in the list.
|
|
52
|
-
for (let child of ctx.children) {
|
|
53
|
-
if (child.symbol?.type === ModelParser.Id) {
|
|
54
|
-
this.addSubscriptIndex(ctx, child)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
visitSubscriptDefList(ctx) {
|
|
59
|
-
// Subscript range definitions can have indices and numeric subscript sequences.
|
|
60
|
-
for (let child of ctx.children) {
|
|
61
|
-
if (child.symbol?.type === ModelParser.Id) {
|
|
62
|
-
this.addSubscriptIndex(ctx, child)
|
|
63
|
-
} else if (child.ruleIndex === ModelParser.RULE_subscriptSequence) {
|
|
64
|
-
this.visitSubscriptSequence(child)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
addSubscriptIndex(ctx, child) {
|
|
69
|
-
let subscript = child.getText()
|
|
70
|
-
if (ctx.parentCtx.ruleIndex === ModelParser.RULE_subscriptRange) {
|
|
71
|
-
this.indNames.push(subscript)
|
|
72
|
-
} else if (ctx.parentCtx.ruleIndex === ModelParser.RULE_subscriptMapping) {
|
|
73
|
-
this.mappingValue.push(subscript)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
visitSubscriptMapping(ctx) {
|
|
77
|
-
let toDim = ctx.Id().getText()
|
|
78
|
-
// If a subscript list is part of the mapping, mappingValue will be set by visitSubscriptList.
|
|
79
|
-
this.mappingValue = []
|
|
80
|
-
super.visitSubscriptMapping(ctx)
|
|
81
|
-
this.modelMappings.push({ toDim, value: this.mappingValue })
|
|
82
|
-
}
|
|
83
|
-
visitSubscriptSequence(ctx) {
|
|
84
|
-
// Construct index names from the sequence start and end indices.
|
|
85
|
-
// This assumes the indices begin with the same string and end with numbers.
|
|
86
|
-
let r = /^(.*?)(\d+)$/
|
|
87
|
-
let ids = R.map(id => id.getText(), ctx.Id())
|
|
88
|
-
let matches = R.map(id => r.exec(id), ids)
|
|
89
|
-
if (matches[0][1] === matches[1][1]) {
|
|
90
|
-
let prefix = matches[0][1]
|
|
91
|
-
let start = parseInt(matches[0][2])
|
|
92
|
-
let end = parseInt(matches[1][2])
|
|
93
|
-
// TODO get this to work with subscript mappings too
|
|
94
|
-
for (let i = start; i <= end; i++) {
|
|
95
|
-
this.indNames.push(prefix + i)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
visitCall(ctx) {
|
|
100
|
-
// A subscript range can have a GET DIRECT SUBSCRIPT call on the RHS.
|
|
101
|
-
let fn = cFunctionName(ctx.Id().getText())
|
|
102
|
-
if (fn === '_GET_DIRECT_SUBSCRIPT') {
|
|
103
|
-
super.visitCall(ctx)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
visitExprList(ctx) {
|
|
107
|
-
// We assume the only call that ends up here is GET DIRECT SUBSCRIPT.
|
|
108
|
-
let args = R.map(
|
|
109
|
-
arg => matchRegex(arg, /'(.*)'/),
|
|
110
|
-
R.map(expr => expr.getText(), ctx.expr())
|
|
111
|
-
)
|
|
112
|
-
let pathname = args[0]
|
|
113
|
-
let delimiter = args[1]
|
|
114
|
-
let firstCell = args[2]
|
|
115
|
-
let lastCell = args[3]
|
|
116
|
-
// let prefix = args[4]
|
|
117
|
-
// If lastCell is a column letter, scan the column, else scan the row.
|
|
118
|
-
let dataAddress = XLSX.utils.decode_cell(firstCell.toUpperCase())
|
|
119
|
-
let col = dataAddress.c
|
|
120
|
-
let row = dataAddress.r
|
|
121
|
-
if (col < 0 || row < 0) {
|
|
122
|
-
throw new Error(`Failed to parse 'firstcell' argument for GET DIRECT SUBSCRIPT call: ${firstCell}`)
|
|
123
|
-
}
|
|
124
|
-
let nextCell
|
|
125
|
-
if (isNaN(parseInt(lastCell))) {
|
|
126
|
-
nextCell = () => row++
|
|
127
|
-
} else {
|
|
128
|
-
nextCell = () => col++
|
|
129
|
-
}
|
|
130
|
-
// Read subscript names from the CSV file at the given position.
|
|
131
|
-
let csvPathname = path.resolve(this.modelDirname, pathname)
|
|
132
|
-
let data = readCsv(csvPathname, delimiter)
|
|
133
|
-
if (data) {
|
|
134
|
-
let indexName = data[row][col]
|
|
135
|
-
while (indexName != null) {
|
|
136
|
-
this.indNames.push(indexName)
|
|
137
|
-
nextCell()
|
|
138
|
-
indexName = data[row] != null ? data[row][col] : null
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
super.visitExprList(ctx)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as R from 'ramda'
|
|
2
|
-
|
|
3
|
-
import { canonicalName } from '../_shared/helpers.js'
|
|
4
|
-
import { sub, isIndex, normalizeSubscripts } from '../_shared/subscript.js'
|
|
5
|
-
import ModelReader from '../parse/model-reader.js'
|
|
6
|
-
import { createParser } from '../parse/parser.js'
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
// VarNameReader reads a model var name using the parser to get the var name in C format.
|
|
10
|
-
// This is used to generate a variable output in the output section.
|
|
11
|
-
//
|
|
12
|
-
export default class VarNameReader extends ModelReader {
|
|
13
|
-
constructor() {
|
|
14
|
-
super()
|
|
15
|
-
this.varName = ''
|
|
16
|
-
}
|
|
17
|
-
read(modelVarName) {
|
|
18
|
-
// Parse an individual model var name and convert it into a a canonical C var name.
|
|
19
|
-
// Parse a single var name, which may include subscripts.
|
|
20
|
-
let parser = createParser(modelVarName)
|
|
21
|
-
let tree = parser.lhs()
|
|
22
|
-
// Generate and return the canonical name.
|
|
23
|
-
this.visitLhs(tree)
|
|
24
|
-
return this.varName
|
|
25
|
-
}
|
|
26
|
-
visitLhs(ctx) {
|
|
27
|
-
let varName = ctx.Id().getText()
|
|
28
|
-
this.varName = canonicalName(varName)
|
|
29
|
-
super.visitLhs(ctx)
|
|
30
|
-
}
|
|
31
|
-
visitSubscriptList(ctx) {
|
|
32
|
-
// Get the canonical form of subscripts found in the var name.
|
|
33
|
-
let subscripts = R.map(id => canonicalName(id.getText()), ctx.Id())
|
|
34
|
-
subscripts = normalizeSubscripts(subscripts)
|
|
35
|
-
// If a subscript is an index, convert it to an index number to match Vensim data exports.
|
|
36
|
-
this.varName += R.map(subName => {
|
|
37
|
-
return isIndex(subName) ? `[${sub(subName).value}]` : `[${subName}]`
|
|
38
|
-
}, subscripts).join('')
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { ModelParser } from 'antlr4-vensim'
|
|
2
|
-
import * as R from 'ramda'
|
|
3
|
-
|
|
4
|
-
import { canonicalName, vlog, strlist, cartesianProductOf } from '../_shared/helpers.js'
|
|
5
|
-
import {
|
|
6
|
-
sub,
|
|
7
|
-
isDimension,
|
|
8
|
-
isIndex,
|
|
9
|
-
normalizeSubscripts,
|
|
10
|
-
subscriptsMatch,
|
|
11
|
-
isSubdimension
|
|
12
|
-
} from '../_shared/subscript.js'
|
|
13
|
-
import ModelReader from '../parse/model-reader.js'
|
|
14
|
-
|
|
15
|
-
import Model from './model.js'
|
|
16
|
-
import Variable from './variable.js'
|
|
17
|
-
|
|
18
|
-
// Set true to print extra debugging information to stderr.
|
|
19
|
-
const DEBUG_LOG = false
|
|
20
|
-
let debugLog = (title, value) => !DEBUG_LOG || vlog(title, value)
|
|
21
|
-
|
|
22
|
-
export default class VariableReader extends ModelReader {
|
|
23
|
-
constructor(specialSeparationDims, directData) {
|
|
24
|
-
super()
|
|
25
|
-
// specialSeparationDims are var names that need to be separated because of
|
|
26
|
-
// circular references, mapped to the dimension subscript to separate on.
|
|
27
|
-
// '{c-variable-name}': '{c-dimension-name}'
|
|
28
|
-
this.specialSeparationDims = specialSeparationDims || {}
|
|
29
|
-
this.directData = directData || {}
|
|
30
|
-
}
|
|
31
|
-
visitModel(ctx) {
|
|
32
|
-
let equations = ctx.equation()
|
|
33
|
-
if (equations) {
|
|
34
|
-
for (let equation of equations) {
|
|
35
|
-
equation.accept(this)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
visitEquation(ctx) {
|
|
40
|
-
// Start a new variable defined by this equation.
|
|
41
|
-
this.var = new Variable(ctx)
|
|
42
|
-
// Allow for an alternate array of variables that are expanded over subdimensions.
|
|
43
|
-
this.expandedVars = []
|
|
44
|
-
// Fill in the variable by visiting the equation parse context.
|
|
45
|
-
super.visitEquation(ctx)
|
|
46
|
-
if (R.isEmpty(this.expandedVars)) {
|
|
47
|
-
// Add a single variable defined by the equation.
|
|
48
|
-
Model.addVariable(this.var)
|
|
49
|
-
} else {
|
|
50
|
-
// Add variables expanded over indices to the model.
|
|
51
|
-
R.forEach(v => Model.addVariable(v), this.expandedVars)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
visitLhs(ctx) {
|
|
55
|
-
this.var.varName = canonicalName(ctx.Id().getText())
|
|
56
|
-
super.visitLhs(ctx)
|
|
57
|
-
// Possibly expand the var on subdimensions.
|
|
58
|
-
if (!R.isEmpty(this.var.subscripts)) {
|
|
59
|
-
// Expand on LHS subscripts alone.
|
|
60
|
-
let expanding = this.subscriptPosToExpand()
|
|
61
|
-
this.expandVars(expanding)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
subscriptPosToExpand() {
|
|
65
|
-
// Decide whether we need to expand each subscript on the LHS.
|
|
66
|
-
// Construct an array of booleans in each subscript position.
|
|
67
|
-
let expanding = []
|
|
68
|
-
for (let iLhsSub = 0; iLhsSub < this.var.subscripts.length; iLhsSub++) {
|
|
69
|
-
let subscript = this.var.subscripts[iLhsSub]
|
|
70
|
-
let expand = false
|
|
71
|
-
// Expand a subdimension and special separation dims in the LHS.
|
|
72
|
-
if (isDimension(subscript)) {
|
|
73
|
-
expand = isSubdimension(subscript)
|
|
74
|
-
if (!expand) {
|
|
75
|
-
let specialSeparationDims = this.specialSeparationDims[this.var.varName] || []
|
|
76
|
-
expand = specialSeparationDims.includes(subscript)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (!expand) {
|
|
80
|
-
// Direct data vars with subscripts are separated because we generate a lookup for each index.
|
|
81
|
-
if (
|
|
82
|
-
isDimension(subscript) &&
|
|
83
|
-
(this.var.modelFormula.includes('GET DIRECT DATA') || this.var.modelFormula.includes('GET DIRECT LOOKUPS'))
|
|
84
|
-
) {
|
|
85
|
-
expand = true
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Also expand on exception subscripts that are indices or subdimensions.
|
|
89
|
-
if (!expand) {
|
|
90
|
-
for (let exceptSubs of this.var.exceptSubscripts) {
|
|
91
|
-
expand = isIndex(exceptSubs[iLhsSub]) || isSubdimension(exceptSubs[iLhsSub])
|
|
92
|
-
if (expand) {
|
|
93
|
-
break
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
expanding.push(expand)
|
|
98
|
-
}
|
|
99
|
-
return expanding
|
|
100
|
-
}
|
|
101
|
-
expandVars(expanding) {
|
|
102
|
-
// Expand the indicated subscripts into variable objects in the expandedVars list.
|
|
103
|
-
debugLog(`expanding ${this.var.varName}[${strlist(this.var.subscripts)}] subscripts`, strlist(this.var.subscripts))
|
|
104
|
-
let expansion = []
|
|
105
|
-
let separationDims = []
|
|
106
|
-
// Construct an array with an array at each subscript position. If the subscript is expanded at that position,
|
|
107
|
-
// it will become an array of indices. Otherwise, it remains an index or dimension as a single-valued array.
|
|
108
|
-
for (let i = 0; i < this.var.subscripts.length; i++) {
|
|
109
|
-
let subscript = this.var.subscripts[i]
|
|
110
|
-
let value
|
|
111
|
-
if (expanding[i]) {
|
|
112
|
-
separationDims.push(subscript)
|
|
113
|
-
if (isDimension(subscript)) {
|
|
114
|
-
value = sub(subscript).value
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
expansion.push(value || [subscript])
|
|
118
|
-
}
|
|
119
|
-
// Generate an array of fully expanded subscripts, which may be indices or dimensions.
|
|
120
|
-
let expandedSubs = cartesianProductOf(expansion)
|
|
121
|
-
let skipExpansion = subs => {
|
|
122
|
-
// Check the subscripts against each set of except subscripts. Skip expansion if one of them matches.
|
|
123
|
-
let subsRange = R.range(0, subs.length)
|
|
124
|
-
for (let exceptSubscripts of this.var.exceptSubscripts) {
|
|
125
|
-
if (subs.length === exceptSubscripts.length) {
|
|
126
|
-
if (R.all(i => subscriptsMatch(subs[i], exceptSubscripts[i]), subsRange)) {
|
|
127
|
-
return true
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
console.error(`WARNING: expandedSubs length ≠ exceptSubscripts length in ${this.var.varName}`)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return false
|
|
134
|
-
}
|
|
135
|
-
for (let subs of expandedSubs) {
|
|
136
|
-
// Skip expansions that match exception subscripts.
|
|
137
|
-
if (!skipExpansion(subs)) {
|
|
138
|
-
// Add a new variable to the expanded vars.
|
|
139
|
-
let v = new Variable(this.var.eqnCtx)
|
|
140
|
-
v.varName = this.var.varName
|
|
141
|
-
v.subscripts = subs
|
|
142
|
-
v.separationDims = separationDims
|
|
143
|
-
debugLog(` ${strlist(v.subscripts)}`)
|
|
144
|
-
this.expandedVars.push(v)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
visitSubscriptList(ctx) {
|
|
149
|
-
if (ctx.parentCtx.ruleIndex === ModelParser.RULE_lhs) {
|
|
150
|
-
let subscripts = normalizeSubscripts(R.map(id => canonicalName(id.getText()), ctx.Id()))
|
|
151
|
-
// Save subscripts in the Variable instance. Subscripts after the first one are exception subscripts.
|
|
152
|
-
if (R.isEmpty(this.var.subscripts)) {
|
|
153
|
-
this.var.subscripts = subscripts
|
|
154
|
-
} else {
|
|
155
|
-
this.var.exceptSubscripts.push(subscripts)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
super.visitSubscriptList(ctx)
|
|
159
|
-
}
|
|
160
|
-
visitConstList(ctx) {
|
|
161
|
-
// Expand a subscripted equation with a constant list.
|
|
162
|
-
let exprs = ctx.expr()
|
|
163
|
-
if (exprs.length > 1) {
|
|
164
|
-
let expanding = R.map(subscript => isDimension(subscript), this.var.subscripts)
|
|
165
|
-
// If the var was already expanded, do it over to make sure we expand on all subscripts.
|
|
166
|
-
if (!R.isEmpty(this.expandedVars)) {
|
|
167
|
-
this.expandedVars = []
|
|
168
|
-
}
|
|
169
|
-
this.expandVars(expanding)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { ModelVisitor } from 'antlr4-vensim'
|
|
2
|
-
|
|
3
|
-
export default class ModelReader extends ModelVisitor {
|
|
4
|
-
constructor() {
|
|
5
|
-
super()
|
|
6
|
-
// stack of function names and argument indices encountered on the RHS
|
|
7
|
-
this.callStack = []
|
|
8
|
-
}
|
|
9
|
-
currentFunctionName() {
|
|
10
|
-
// Return the name of the current function on top of the call stack.
|
|
11
|
-
let n = this.callStack.length
|
|
12
|
-
return n > 0 ? this.callStack[n - 1].fn : ''
|
|
13
|
-
}
|
|
14
|
-
setArgIndex(argIndex) {
|
|
15
|
-
// Set the argument index in the current function call on top of the call stack.
|
|
16
|
-
// This may be set in the exprList visitor and picked up in the var visitor to facilitate special argument handling.
|
|
17
|
-
let n = this.callStack.length
|
|
18
|
-
if (n > 0) {
|
|
19
|
-
this.callStack[n - 1].argIndex = argIndex
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
argIndexForFunctionName(name) {
|
|
23
|
-
// Search the call stack for the function name. Return the current argument index or undefined if not found.
|
|
24
|
-
let argIndex
|
|
25
|
-
for (let i = this.callStack.length - 1; i >= 0; i--) {
|
|
26
|
-
if (this.callStack[i].fn === name) {
|
|
27
|
-
argIndex = this.callStack[i].argIndex
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return argIndex
|
|
32
|
-
}
|
|
33
|
-
visitEquation(ctx) {
|
|
34
|
-
ctx.lhs().accept(this)
|
|
35
|
-
if (ctx.expr()) {
|
|
36
|
-
ctx.expr().accept(this)
|
|
37
|
-
} else if (ctx.constList()) {
|
|
38
|
-
ctx.constList().accept(this)
|
|
39
|
-
} else if (ctx.lookup()) {
|
|
40
|
-
ctx.lookup().accept(this)
|
|
41
|
-
} else {
|
|
42
|
-
this.var.varType = 'data'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
visitLhs(ctx) {
|
|
46
|
-
// An LHS may have a subscript list after the var name.
|
|
47
|
-
// If it has an EXCEPT clause, it will have one or more other subscript lists there too.
|
|
48
|
-
let subscriptLists = ctx.subscriptList()
|
|
49
|
-
if (subscriptLists.length > 0) {
|
|
50
|
-
for (let i = 0; i < subscriptLists.length; i++) {
|
|
51
|
-
subscriptLists[i].accept(this)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Function calls and variables
|
|
57
|
-
|
|
58
|
-
visitCall(ctx) {
|
|
59
|
-
if (ctx.exprList()) {
|
|
60
|
-
ctx.exprList().accept(this)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
visitExprList(ctx) {
|
|
64
|
-
let exprs = ctx.expr()
|
|
65
|
-
// Set the argument index in an instance property so derived classes can determine argument position.
|
|
66
|
-
for (let i = 0; i < exprs.length; i++) {
|
|
67
|
-
this.setArgIndex(i)
|
|
68
|
-
exprs[i].accept(this)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
visitVar(ctx) {
|
|
72
|
-
if (ctx.subscriptList()) {
|
|
73
|
-
ctx.subscriptList().accept(this)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Lookups
|
|
78
|
-
|
|
79
|
-
visitLookup(ctx) {
|
|
80
|
-
if (ctx.lookupRange()) {
|
|
81
|
-
ctx.lookupRange().accept(this)
|
|
82
|
-
}
|
|
83
|
-
if (ctx.lookupPointList()) {
|
|
84
|
-
ctx.lookupPointList().accept(this)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
visitLookupCall(ctx) {
|
|
88
|
-
if (ctx.subscriptList()) {
|
|
89
|
-
ctx.subscriptList().accept(this)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Unary operators
|
|
94
|
-
|
|
95
|
-
visitNegative(ctx) {
|
|
96
|
-
ctx.expr().accept(this)
|
|
97
|
-
}
|
|
98
|
-
visitPositive(ctx) {
|
|
99
|
-
ctx.expr().accept(this)
|
|
100
|
-
}
|
|
101
|
-
visitNot(ctx) {
|
|
102
|
-
ctx.expr().accept(this)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Binary operators
|
|
106
|
-
|
|
107
|
-
visitPower(ctx) {
|
|
108
|
-
ctx.expr(0).accept(this)
|
|
109
|
-
ctx.expr(1).accept(this)
|
|
110
|
-
}
|
|
111
|
-
visitMulDiv(ctx) {
|
|
112
|
-
ctx.expr(0).accept(this)
|
|
113
|
-
ctx.expr(1).accept(this)
|
|
114
|
-
}
|
|
115
|
-
visitAddSub(ctx) {
|
|
116
|
-
ctx.expr(0).accept(this)
|
|
117
|
-
ctx.expr(1).accept(this)
|
|
118
|
-
}
|
|
119
|
-
visitRelational(ctx) {
|
|
120
|
-
ctx.expr(0).accept(this)
|
|
121
|
-
ctx.expr(1).accept(this)
|
|
122
|
-
}
|
|
123
|
-
visitEquality(ctx) {
|
|
124
|
-
ctx.expr(0).accept(this)
|
|
125
|
-
ctx.expr(1).accept(this)
|
|
126
|
-
}
|
|
127
|
-
visitAnd(ctx) {
|
|
128
|
-
ctx.expr(0).accept(this)
|
|
129
|
-
ctx.expr(1).accept(this)
|
|
130
|
-
}
|
|
131
|
-
visitOr(ctx) {
|
|
132
|
-
ctx.expr(0).accept(this)
|
|
133
|
-
ctx.expr(1).accept(this)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Tokens
|
|
137
|
-
|
|
138
|
-
visitParens(ctx) {
|
|
139
|
-
ctx.expr().accept(this)
|
|
140
|
-
}
|
|
141
|
-
}
|