@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.
@@ -0,0 +1,202 @@
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
+ }