@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,123 @@
1
+ import path from 'path'
2
+ import { ModelParser } from 'antlr4-vensim'
3
+ import 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
+ // A subscript list can appear in either a subscript range or mapping.
52
+ let subscripts = R.map(id => id.getText(), ctx.Id())
53
+ if (ctx.parentCtx.ruleIndex === ModelParser.RULE_subscriptRange) {
54
+ this.indNames = subscripts
55
+ }
56
+ if (ctx.parentCtx.ruleIndex === ModelParser.RULE_subscriptMapping) {
57
+ this.mappingValue = subscripts
58
+ }
59
+ }
60
+ visitSubscriptMapping(ctx) {
61
+ let toDim = ctx.Id().getText()
62
+ // If a subscript list is part of the mapping, mappingValue will be set by visitSubscriptList.
63
+ this.mappingValue = []
64
+ super.visitSubscriptMapping(ctx)
65
+ this.modelMappings.push({ toDim, value: this.mappingValue })
66
+ }
67
+ visitSubscriptSequence(ctx) {
68
+ // Construct index names from the sequence start and end indices.
69
+ // This assumes the indices begin with the same string and end with numbers.
70
+ let r = /^(.*?)(\d+)$/
71
+ let ids = R.map(id => id.getText(), ctx.Id())
72
+ let matches = R.map(id => r.exec(id), ids)
73
+ if (matches[0][1] === matches[1][1]) {
74
+ let prefix = matches[0][1]
75
+ let start = parseInt(matches[0][2])
76
+ let end = parseInt(matches[1][2])
77
+ for (let i = start; i <= end; i++) {
78
+ this.indNames.push(prefix + i)
79
+ }
80
+ }
81
+ }
82
+ visitCall(ctx) {
83
+ // A subscript range can have a GET DIRECT SUBSCRIPT call on the RHS.
84
+ let fn = cFunctionName(ctx.Id().getText())
85
+ if (fn === '_GET_DIRECT_SUBSCRIPT') {
86
+ super.visitCall(ctx)
87
+ }
88
+ }
89
+ visitExprList(ctx) {
90
+ // We assume the only call that ends up here is GET DIRECT SUBSCRIPT.
91
+ let args = R.map(
92
+ arg => matchRegex(arg, /'(.*)'/),
93
+ R.map(expr => expr.getText(), ctx.expr())
94
+ )
95
+ let pathname = args[0]
96
+ let delimiter = args[1]
97
+ let firstCell = args[2]
98
+ let lastCell = args[3]
99
+ // let prefix = args[4]
100
+ // If lastCell is a column letter, scan the column, else scan the row.
101
+ let dataAddress = XLSX.utils.decode_cell(firstCell)
102
+ let col = dataAddress.c
103
+ let row = dataAddress.r
104
+ let nextCell
105
+ if (isNaN(parseInt(lastCell))) {
106
+ nextCell = () => row++
107
+ } else {
108
+ nextCell = () => col++
109
+ }
110
+ // Read subscript names from the CSV file at the given position.
111
+ let csvPathname = path.resolve(this.modelDirname, pathname)
112
+ let data = readCsv(csvPathname, delimiter)
113
+ if (data) {
114
+ let indexName = data[row][col]
115
+ while (indexName != null) {
116
+ this.indNames.push(indexName)
117
+ nextCell()
118
+ indexName = data[row] != null ? data[row][col] : null
119
+ }
120
+ }
121
+ super.visitExprList(ctx)
122
+ }
123
+ }
@@ -0,0 +1,100 @@
1
+ // Modified from https://github.com/marcelklehr/toposort version 2.0.2
2
+
3
+ /**
4
+ * Topological sorting function
5
+ *
6
+ * @param {Array} edges
7
+ * @returns {Array}
8
+ */
9
+
10
+ export default function (edges) {
11
+ return toposort(uniqueNodes(edges), edges)
12
+ }
13
+
14
+ function toposort(nodes, edges) {
15
+ var cursor = nodes.length,
16
+ sorted = new Array(cursor),
17
+ visited = {},
18
+ i = cursor,
19
+ // Better data structures make algorithm much faster.
20
+ outgoingEdges = makeOutgoingEdges(edges),
21
+ nodesHash = makeNodesHash(nodes)
22
+
23
+ // check for unknown nodes
24
+ edges.forEach(function (edge) {
25
+ if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
26
+ throw new Error('Unknown node. There is an unknown node in the supplied edges.')
27
+ }
28
+ })
29
+
30
+ while (i--) {
31
+ if (!visited[i]) visit(nodes[i], i, new Set())
32
+ }
33
+
34
+ return sorted
35
+
36
+ function visit(node, i, predecessors) {
37
+ if (predecessors.has(node)) {
38
+ // debugger
39
+ var nodeRep
40
+ try {
41
+ nodeRep = '\n' + node + '\n'
42
+ } catch (e) {
43
+ nodeRep = ''
44
+ }
45
+ throw new Error('toposort cyclic dependency:\n' + [...predecessors].join(' →\n') + nodeRep)
46
+ }
47
+
48
+ if (!nodesHash.has(node)) {
49
+ throw new Error(
50
+ 'Found unknown node. Make sure to provided all involved nodes. Unknown node: ' + JSON.stringify(node)
51
+ )
52
+ }
53
+
54
+ if (visited[i]) return
55
+ visited[i] = true
56
+
57
+ var outgoing = outgoingEdges.get(node) || new Set()
58
+ outgoing = Array.from(outgoing)
59
+
60
+ if ((i = outgoing.length)) {
61
+ predecessors.add(node)
62
+ do {
63
+ var child = outgoing[--i]
64
+ visit(child, nodesHash.get(child), predecessors)
65
+ } while (i)
66
+ predecessors.delete(node)
67
+ }
68
+
69
+ sorted[--cursor] = node
70
+ }
71
+ }
72
+
73
+ function uniqueNodes(arr) {
74
+ var res = new Set()
75
+ for (var i = 0, len = arr.length; i < len; i++) {
76
+ var edge = arr[i]
77
+ res.add(edge[0])
78
+ res.add(edge[1])
79
+ }
80
+ return Array.from(res)
81
+ }
82
+
83
+ function makeOutgoingEdges(arr) {
84
+ var edges = new Map()
85
+ for (var i = 0, len = arr.length; i < len; i++) {
86
+ var edge = arr[i]
87
+ if (!edges.has(edge[0])) edges.set(edge[0], new Set())
88
+ if (!edges.has(edge[1])) edges.set(edge[1], new Set())
89
+ edges.get(edge[0]).add(edge[1])
90
+ }
91
+ return edges
92
+ }
93
+
94
+ function makeNodesHash(arr) {
95
+ var res = new Map()
96
+ for (var i = 0, len = arr.length; i < len; i++) {
97
+ res.set(arr[i], i)
98
+ }
99
+ return res
100
+ }
@@ -0,0 +1,40 @@
1
+ import 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
+ }
@@ -0,0 +1,172 @@
1
+ import { ModelParser } from 'antlr4-vensim'
2
+ import 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
+ }
@@ -0,0 +1,111 @@
1
+ export default class Variable {
2
+ constructor(eqnCtx) {
3
+ // The equation rule context allows us to generate code by visiting the parse tree.
4
+ this.eqnCtx = eqnCtx
5
+ // Save both sides of the equation text in the model for documentation purposes.
6
+ this.modelLHS = eqnCtx ? eqnCtx.lhs().getText() : ''
7
+ this.modelFormula = this.formula(eqnCtx)
8
+ // An equation defines a variable with a var name, saved in canonical form here.
9
+ this.varName = ''
10
+ // Subscripts are canonical dimension or index names on the LHS in normal order.
11
+ this.subscripts = []
12
+ // Exception subscripts are subscript lists given in an EXCEPT clause on the LHS.
13
+ this.exceptSubscripts = []
14
+ // Array variables that are separated in VariableReader keep the original dimensions here.
15
+ this.separationDims = []
16
+ // Direct data function arguments are saved here for use in code generation.
17
+ this.directDataArgs = null
18
+ // Direct constants function arguments are saved here for use in code generation.
19
+ this.directConstArgs = null
20
+ // Lookup vars have lookup points and an optional range.
21
+ this.range = []
22
+ this.points = []
23
+ // The reference id appears in lists of references.
24
+ this.refId = ''
25
+ // The default varType is aux, but may be overridden later.
26
+ this.varType = 'aux'
27
+ // The variable subtype accommodates special handling needed by some Vensim functions.
28
+ this.varSubtype = ''
29
+ // A variable may reference other variable names at eval time.
30
+ this.references = []
31
+ // Levels and certain other variables have an initial value that may reference other variable names.
32
+ this.initReferences = []
33
+ // Set true when a variable has an initial value (e.g. levels and initials).
34
+ this.hasInitValue = false
35
+ // Lookup args generate vars that are substituted into the call during code generation.
36
+ this.lookupArgVarName = ''
37
+ // SMOOTH* calls are expanded into new level vars and substituted during code generation.
38
+ this.smoothVarRefId = ''
39
+ // TREND calls are expanded into new level vars and substituted during code generation.
40
+ this.trendVarName = ''
41
+ // NPV calls are expanded into new level vars and substituted during code generation.
42
+ this.npvVarName = ''
43
+ // DELAY3* calls are expanded into new level vars and substituted during code generation.
44
+ this.delayVarRefId = ''
45
+ this.delayTimeVarName = ''
46
+ // DELAY FIXED calls generate a FixedDelay support var.
47
+ this.fixedDelayVarName = ''
48
+ // Variables generated by special expansions are not included in output.
49
+ this.includeInOutput = true
50
+ }
51
+ copy() {
52
+ let c = new Variable()
53
+ c.eqnCtx = this.eqnCtx
54
+ c.modelLHS = this.modelLHS
55
+ c.modelFormula = this.modelFormula
56
+ c.varName = this.varName
57
+ c.subscripts = this.subscripts.slice()
58
+ c.separationDims = this.separationDims
59
+ c.range = this.range.slice()
60
+ c.points = this.points.slice()
61
+ c.refId = this.refId
62
+ c.varType = this.varType
63
+ c.references = this.references.slice()
64
+ c.initReferences = this.initReferences.slice()
65
+ c.hasInitValue = this.hasInitValue
66
+ c.lookupArgVarName = this.lookupArgVarName
67
+ c.smoothVarRefId = this.smoothVarRefId
68
+ c.trendVarName = this.trendVarName
69
+ c.delayVarRefId = this.delayVarRefId
70
+ c.delayTimeVarName = this.delayTimeVarName
71
+ c.includeInOutput = this.includeInOutput
72
+ return c
73
+ }
74
+ formula(eqnCtx) {
75
+ if (eqnCtx) {
76
+ if (eqnCtx.expr()) {
77
+ return eqnCtx.expr().getText()
78
+ } else if (eqnCtx.constList()) {
79
+ return eqnCtx.constList().getText()
80
+ }
81
+ }
82
+ return ''
83
+ }
84
+ hasSubscripts() {
85
+ return this.subscripts.length > 0
86
+ }
87
+ hasPoints() {
88
+ return this.points.length > 0
89
+ }
90
+ isConst() {
91
+ return this.varType === 'const'
92
+ }
93
+ isAux() {
94
+ return this.varType === 'aux'
95
+ }
96
+ isLevel() {
97
+ return this.varType === 'level'
98
+ }
99
+ isFixedDelay() {
100
+ return this.varSubtype === 'fixedDelay'
101
+ }
102
+ isInitial() {
103
+ return this.varType === 'initial'
104
+ }
105
+ isLookup() {
106
+ return this.varType === 'lookup'
107
+ }
108
+ isData() {
109
+ return this.varType === 'data'
110
+ }
111
+ }
@@ -0,0 +1,141 @@
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
+ }