From c4d0fae0382233fe1f399badedf3536a9d75c618 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Tue, 6 Jul 2021 11:56:08 -0700 Subject: [PATCH 01/11] feat: remove inline comments in the preprocessor (#73) --- models/comments/comments.dat | 23 +++++++++++ models/comments/comments.mdl | 53 ++++++++++++++++++++++++ models/comments/comments_vars.txt | 69 +++++++++++++++++++++++++++++++ src/Helpers.js | 30 +++++++++++++- src/Preprocessor.js | 4 +- 5 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 models/comments/comments.dat create mode 100644 models/comments/comments.mdl create mode 100644 models/comments/comments_vars.txt diff --git a/models/comments/comments.dat b/models/comments/comments.dat new file mode 100644 index 00000000..f281bfb1 --- /dev/null +++ b/models/comments/comments.dat @@ -0,0 +1,23 @@ +a[A1] +0 0 +a[A2] +0 1 +a[A3] +0 2 +b +0 3 +c +0 4 +d +0 8760 +e +0 42 +1 42 +FINAL TIME +0 1 +INITIAL TIME +0 0 +SAVEPER +0 1 +TIME STEP +0 1 diff --git a/models/comments/comments.mdl b/models/comments/comments.mdl new file mode 100644 index 00000000..44bd36c1 --- /dev/null +++ b/models/comments/comments.mdl @@ -0,0 +1,53 @@ +{UTF-8} +DimA: A1, A2, A3 ~~| + +a[DimA]= + 0, {transportation sector} + 1, {electricity sector} + 2 {geoengineering sector - NOT USED} + ~ + ~ ~ :SUPPLEMENTARY + | + +b = 3 {transportation sector} + ~ + ~ ~ :SUPPLEMENTARY + | + +c = 4 + {first part and + the second part} + ~ + ~ ~ :SUPPLEMENTARY + | + +d= + 8760 + + { + We ignore leap years so that we can get consistent results + for various values used in the model that should not change + every four years. If we want to change this value for leap + years, instead use the following code: + + + IF THEN ELSE( + (MODULO(Time, 4) = 0 :AND: MODULO(Time, 100) <> 0) :OR: MODULO(Time, 400) = 0, + 8784 {leap year}, + 8760 {normal year} + ) + } + ~ hours + ~ Calculates the number of hours in the year, ignoring leap years. + ~ :SUPPLEMENTARY + | + +e = 41 {first part} + 1 {second part} + ~ + ~ ~ :SUPPLEMENTARY + | + +INITIAL TIME = 0 ~~| +FINAL TIME = 1 ~~| +SAVEPER = 1 ~~| +TIME STEP = 1 ~~| diff --git a/models/comments/comments_vars.txt b/models/comments/comments_vars.txt new file mode 100644 index 00000000..fa2fa26f --- /dev/null +++ b/models/comments/comments_vars.txt @@ -0,0 +1,69 @@ +a[DimA]: const (non-apply-to-all) += 0,1,2 +refId(_a[_a1]) +families(_dima) +subscripts(_a1) +separationDims(_dima) +hasInitValue(false) + +a[DimA]: const (non-apply-to-all) += 0,1,2 +refId(_a[_a2]) +families(_dima) +subscripts(_a2) +separationDims(_dima) +hasInitValue(false) + +a[DimA]: const (non-apply-to-all) += 0,1,2 +refId(_a[_a3]) +families(_dima) +subscripts(_a3) +separationDims(_dima) +hasInitValue(false) + +b: const += 3 +refId(_b) +hasInitValue(false) + +c: const += 4 +refId(_c) +hasInitValue(false) + +d: const += 8760 +refId(_d) +hasInitValue(false) + +e: const += 41+1 +refId(_e) +hasInitValue(false) + +FINAL TIME: const += 1 +refId(_final_time) +hasInitValue(false) + +INITIAL TIME: const += 0 +refId(_initial_time) +hasInitValue(false) + +SAVEPER: const += 1 +refId(_saveper) +hasInitValue(false) + +Time: const += +refId(_time) +hasInitValue(false) + +TIME STEP: const += 1 +refId(_time_step) +hasInitValue(false) + diff --git a/src/Helpers.js b/src/Helpers.js index feae3cc0..1de032c1 100644 --- a/src/Helpers.js +++ b/src/Helpers.js @@ -6,7 +6,6 @@ const sh = require('shelljs') const split = require('split-string') const byline = require('byline') const XLSX = require('xlsx') -const num = require('numbro') const B = require('bufx') // Set true to print a stack trace in vlog @@ -379,6 +378,34 @@ let matchRegexCaptures = (str, regex) => { return [] } } +// Match delimiters recursively. Replace delimited strings globally. +let replaceDelimitedStrings = (str, open, close, newStr) => { + // str is the string to operate on. + // open and close are the opening and closing delimiter characters. + // newStr is the string to replace delimited substrings with. + let result = '' + let start = 0 + let depth = 0 + let n = str.length + for (let i = 0; i < n; i++) { + if (str.charAt(i) === open) { + if (depth === 0) { + result += str.substring(start, i) + } + depth++ + } else if (str.charAt(i) === close && depth > 0) { + depth-- + if (depth === 0) { + result += newStr + start = i + 1 + } + } + } + if (start < n) { + result += str.substring(start) + } + return result +} /** * Return the cartesian product of the given array of arrays. @@ -474,6 +501,7 @@ module.exports = { readDat, readXlsx, replaceInArray, + replaceDelimitedStrings, rest, splitEquations, strings, diff --git a/src/Preprocessor.js b/src/Preprocessor.js index 4c46941d..9c5fdb79 100644 --- a/src/Preprocessor.js +++ b/src/Preprocessor.js @@ -1,7 +1,7 @@ const path = require('path') const R = require('ramda') const B = require('bufx') -const { splitEquations } = require('./Helpers') +const { splitEquations, replaceDelimitedStrings } = require('./Helpers') let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, outDecls = []) => { const MACROS_FILENAME = 'macros.txt' @@ -134,6 +134,8 @@ let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, eqn = eqn.replace('{UTF-8}', '') // Remove ":RAW:" flag; it is not needed by SDE and causes problems if left in eqn = eqn.replace(/:RAW:/g, '') + // Remove inline comments + eqn = replaceDelimitedStrings(eqn, '{', '}', '') // Remove whitespace eqn = eqn.trim() if (eqn.length > 0) { From 0468865bc2a6948b6deeef91ee3cf6d0ce4df13e Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Tue, 6 Jul 2021 12:15:13 -0700 Subject: [PATCH 02/11] build: update antlr4-vensim dependency to 0.5.1 (#75) --- package-lock.json | 269 ++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 166 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa272c21..ec451c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, "adler-32": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", @@ -24,11 +19,10 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" }, "dependencies": { @@ -48,11 +42,74 @@ "integrity": "sha512-en/MxQ4OkPgGJQ3wD/muzj1uDnFSzdFIhc2+c6bHZokWkuBb6RRvFjpWhPxWLbgQvaEzldJZ0GSQpfSAaE3hqg==" }, "antlr4-vensim": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/antlr4-vensim/-/antlr4-vensim-0.5.0.tgz", - "integrity": "sha512-lSw2czOumXMbeSBvDS8/pX2gFsf1awZEkExQgJq9DnjXRuRN8nDrMH1TS1vE3oQFh0ywmJAx7MR6Yn11SvWUCQ==", + "version": "0.5.1", "requires": { "antlr4": "4.8.0" + }, + "dependencies": { + "antlr4": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.8.0.tgz", + "integrity": "sha512-en/MxQ4OkPgGJQ3wD/muzj1uDnFSzdFIhc2+c6bHZokWkuBb6RRvFjpWhPxWLbgQvaEzldJZ0GSQpfSAaE3hqg==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "bufx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bufx/-/bufx-1.0.5.tgz", + "integrity": "sha512-AzOd+vXDVhRAIR4k0ZopOLef+XjqLU6h3buAqVXTUrZ5IYWmoPqLtIoITeye174Uq5qiDS+83Rx9U9ItXgNE+A==", + "requires": { + "js-yaml": "^3.13.1", + "prettier": "^2.0.4", + "ramda": "^0.27.0", + "strip-bom": "^4.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "prettier": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", + "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==" + }, + "ramda": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", + "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" + } } }, "argparse": { @@ -69,9 +126,9 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { "version": "1.1.11", @@ -91,23 +148,6 @@ "prettier": "^2.0.4", "ramda": "^0.27.0", "strip-bom": "^4.0.0" - }, - "dependencies": { - "prettier": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", - "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==" - }, - "ramda": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", - "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==" - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" - } } }, "byline": { @@ -121,20 +161,19 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "cfb": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.1.4.tgz", - "integrity": "sha512-rwFkl3aFO3f+ljR27YINwC0x8vPjyiEVbYbrTCKzspEf7Q++3THdfHVgJYNUbxNcupJECrLX+L40Mjm9hm/Bgw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.0.tgz", + "integrity": "sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ==", "requires": { "adler-32": "~1.2.0", - "commander": "^2.16.0", "crc-32": "~1.2.0", "printj": "~1.1.2" } }, "chart.js": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", - "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", "requires": { "chartjs-color": "^2.1.0", "moment": "^2.10.2" @@ -223,9 +262,9 @@ } }, "csv-parse": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.15.4.tgz", - "integrity": "sha512-OdBbFc0yZhOm17lSxqkirrHlFFVpKRT0wp4DAGoJelsP3LbGzV9LNr7XmM/lrr0uGkCtaqac9UhP8PDHXOAbMg==" + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.0.tgz", + "integrity": "sha512-Zb4tGPANH4SW0LgC9+s9Mnequs9aqn7N3/pCqNbVjs2XhEF6yWNU2Vm4OGl1v2Go9nw8rXt87Cm2QN/o6Vpqgg==" }, "decamelize": { "version": "1.2.0", @@ -283,34 +322,29 @@ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" }, "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" }, "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, @@ -319,15 +353,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -338,9 +377,17 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } }, "he": { "version": "1.2.0", @@ -357,14 +404,22 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "requires": { + "has": "^1.0.3" + } }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -372,9 +427,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -410,9 +465,9 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "numbro": { "version": "1.11.1", @@ -459,14 +514,14 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "prettier": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", - "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==" }, "printj": { "version": "1.1.2", @@ -474,9 +529,9 @@ "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "ramda": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", - "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==" + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==" }, "rechoir": { "version": "0.6.2", @@ -497,10 +552,11 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", - "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "requires": { + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, @@ -510,9 +566,9 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -538,9 +594,9 @@ } }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -555,6 +611,11 @@ "ansi-regex": "^5.0.0" } }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -606,14 +667,14 @@ } }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -625,13 +686,13 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "yargs-parser": "^18.1.2" } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index 577e5349..aaa37e42 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "antlr4": "4.8.0", - "antlr4-vensim": "0.5.0", + "antlr4-vensim": "0.5.1", "bufx": "^1.0.5", "byline": "^5.0.0", "chart.js": "^2.9.3", From 39a88478fc02968a27f9fae269e04686ed53a5c9 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Wed, 7 Jul 2021 23:02:47 -0700 Subject: [PATCH 03/11] build: change from CommonJS to ES modules to use ANTLR 4.9.2 (#75) --- package-lock.json | 14 +-- package.json | 3 +- src/CodeGen.js | 16 ++-- src/EquationGen.js | 24 ++--- src/EquationReader.js | 22 ++--- src/Helpers.js | 181 ++++++++++++------------------------ src/LoopIndexVars.js | 4 +- src/MakeConfig.js | 29 +++--- src/Model.js | 33 ++++--- src/ModelLHSReader.js | 14 +-- src/ModelReader.js | 4 +- src/Preprocessor.js | 12 +-- src/Subscript.js | 78 ++++++---------- src/SubscriptRangeReader.js | 11 +-- src/VarNameReader.js | 14 +-- src/Variable.js | 5 +- src/VariableReader.js | 21 ++--- src/index.js | 2 +- src/sde-build.js | 16 ++-- src/sde-causes.js | 15 ++- src/sde-clean.js | 7 +- src/sde-compare.js | 18 ++-- src/sde-compile.js | 18 ++-- src/sde-exec.js | 18 ++-- src/sde-flatten.js | 10 +- src/sde-generate.js | 46 ++++----- src/sde-log.js | 24 ++--- src/sde-names.js | 21 ++--- src/sde-run.js | 16 ++-- src/sde-test.js | 22 ++--- src/sde-which.js | 6 +- src/sde.js | 47 ++++++---- src/toposort.js | 4 +- 33 files changed, 340 insertions(+), 435 deletions(-) diff --git a/package-lock.json b/package-lock.json index ec451c57..5dd78d1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,20 +37,20 @@ } }, "antlr4": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.8.0.tgz", - "integrity": "sha512-en/MxQ4OkPgGJQ3wD/muzj1uDnFSzdFIhc2+c6bHZokWkuBb6RRvFjpWhPxWLbgQvaEzldJZ0GSQpfSAaE3hqg==" + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.9.2.tgz", + "integrity": "sha512-UjMSlenUORL+a+6g4RNZxRh5LcFWybRi2g0ASDBpgXBY6nlavg0BRVAVEQF0dz8jH6SyX3lV7uP5y/krJzc+Hw==" }, "antlr4-vensim": { "version": "0.5.1", "requires": { - "antlr4": "4.8.0" + "antlr4": "4.9.2" }, "dependencies": { "antlr4": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.8.0.tgz", - "integrity": "sha512-en/MxQ4OkPgGJQ3wD/muzj1uDnFSzdFIhc2+c6bHZokWkuBb6RRvFjpWhPxWLbgQvaEzldJZ0GSQpfSAaE3hqg==" + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.9.2.tgz", + "integrity": "sha512-UjMSlenUORL+a+6g4RNZxRh5LcFWybRi2g0ASDBpgXBY6nlavg0BRVAVEQF0dz8jH6SyX3lV7uP5y/krJzc+Hw==" }, "argparse": { "version": "1.0.10", diff --git a/package.json b/package.json index aaa37e42..f0b4d9ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "sdeverywhere", "version": "0.6.0", + "type": "module", "description": "SDEverywhere translates System Dynamics models from Vensim to C and WebAssembly", "homepage": "https://sdeverywhere.org", "author": "Climate Interactive", @@ -17,7 +18,7 @@ "test": "cd src/tests && ./modeltests" }, "dependencies": { - "antlr4": "4.8.0", + "antlr4": "4.9.2", "antlr4-vensim": "0.5.1", "bufx": "^1.0.5", "byline": "^5.0.0", diff --git a/src/CodeGen.js b/src/CodeGen.js index 43f2fab6..44351959 100644 --- a/src/CodeGen.js +++ b/src/CodeGen.js @@ -1,11 +1,11 @@ -const R = require('ramda') -const ModelLHSReader = require('./ModelLHSReader') -const EquationGen = require('./EquationGen') -const Model = require('./Model') -const { sub, allDimensions, allMappings, subscriptFamilies } = require('./Subscript') -const { asort, lines, strlist, abend, mapIndexed } = require('./Helpers') +import R from 'ramda' +import ModelLHSReader from './ModelLHSReader.js' +import EquationGen from './EquationGen.js' +import Model from './Model.js' +import { sub, allDimensions, allMappings, subscriptFamilies } from './Subscript.js' +import { asort, lines, strlist, abend, mapIndexed } from './Helpers.js' -let codeGenerator = (parseTree, opts) => { +export let codeGenerator = (parseTree, opts) => { const { spec, operation, extData, directData } = opts // Set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated. let mode = '' @@ -337,5 +337,3 @@ ${postStep} generate: generate, } } - -module.exports = { codeGenerator } diff --git a/src/EquationGen.js b/src/EquationGen.js index 12040cf5..52be92f5 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -1,11 +1,11 @@ -const R = require('ramda') -const XLSX = require('xlsx') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const ModelReader = require('./ModelReader') -const ModelLHSReader = require('./ModelLHSReader') -const LoopIndexVars = require('./LoopIndexVars') -const Model = require('./Model') -const { +import R from 'ramda' +import XLSX from 'xlsx' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import ModelReader from './ModelReader.js' +import ModelLHSReader from './ModelLHSReader.js' +import LoopIndexVars from './LoopIndexVars.js' +import Model from './Model.js' +import { dimensionNames, extractMarkedDims, hasMapping, @@ -15,8 +15,8 @@ const { normalizeSubscripts, separatedVariableIndex, sub -} = require('./Subscript') -const { +} from './Subscript.js' +import { canonicalName, cartesianProductOf, cdbl, @@ -30,9 +30,9 @@ const { permutationsOf, strToConst, vlog -} = require('./Helpers') +} from './Helpers.js' -module.exports = class EquationGen extends ModelReader { +export default class EquationGen extends ModelReader { constructor(variable, extData, directData, mode) { super() // the variable we are generating code for diff --git a/src/EquationReader.js b/src/EquationReader.js index c08bbd32..9c4cf204 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -1,17 +1,17 @@ -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const R = require('ramda') -const Model = require('./Model') -const ModelReader = require('./ModelReader') -const VariableReader = require('./VariableReader') -const { +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import R from 'ramda' +import Model from './Model.js' +import ModelReader from './ModelReader.js' +import VariableReader from './VariableReader.js' +import { extractMarkedDims, indexNamesForSubscript, normalizeSubscripts, separatedVariableIndex, isDimension -} = require('./Subscript') -const { +} from './Subscript.js' +import { canonicalName, canonicalVensimName, cFunctionName, @@ -24,12 +24,12 @@ const { newAuxVarName, newLevelVarName, newLookupVarName -} = require('./Helpers') +} from './Helpers.js' // Set this true to get a list of functions used in the model. This may include lookups. const PRINT_FUNCTION_NAMES = false -module.exports = class EquationReader extends ModelReader { +export default class EquationReader extends ModelReader { constructor(variable) { super() // variable that will be read diff --git a/src/Helpers.js b/src/Helpers.js index 1de032c1..8a023dab 100644 --- a/src/Helpers.js +++ b/src/Helpers.js @@ -1,15 +1,15 @@ -const fs = require('fs-extra') -const path = require('path') -const util = require('util') -const R = require('ramda') -const sh = require('shelljs') -const split = require('split-string') -const byline = require('byline') -const XLSX = require('xlsx') -const B = require('bufx') +import fs from 'fs-extra' +import path from 'path' +import util from 'util' +import R from 'ramda' +import sh from 'shelljs' +import split from 'split-string' +import byline from 'byline' +import XLSX from 'xlsx' +import B from 'bufx' // Set true to print a stack trace in vlog -const PRINT_VLOG_TRACE = false +export const PRINT_VLOG_TRACE = false // next sequence number for generated temporary variable names let nextTmpVarSeq = 1 @@ -20,9 +20,9 @@ let nextLevelVarSeq = 1 // next sequence number for generated aux variable names let nextAuxVarSeq = 1 // string table for web apps -let strings = [] +export let strings = [] -let canonicalName = name => { +export let canonicalName = name => { // Format a model variable name into a valid C identifier. return ( '_' + @@ -43,7 +43,7 @@ let canonicalName = name => { .toLowerCase() ) } -let decanonicalize = name => { +export let decanonicalize = name => { // Decanonicalize the var name. try { name = name.replace(/^_/, '').replace(/_/g, ' ') @@ -59,21 +59,21 @@ let decanonicalize = name => { } return name } -let cFunctionName = name => { +export let cFunctionName = name => { return canonicalName(name).toUpperCase() } -let isSeparatedVar = v => { +export let isSeparatedVar = v => { return v.separationDims.length > 0 } -let newTmpVarName = () => { +export let newTmpVarName = () => { // Return a unique temporary variable name return `__t${nextTmpVarSeq++}` } -let newLookupVarName = () => { +export let newLookupVarName = () => { // Return a unique lookup arg variable name return `_lookup${nextLookupVarSeq++}` } -let newLevelVarName = (basename = null, levelNumber = 0) => { +export let newLevelVarName = (basename = null, levelNumber = 0) => { // Return a unique level variable name. let levelName = basename || nextLevelVarSeq++ if (levelNumber) { @@ -81,7 +81,7 @@ let newLevelVarName = (basename = null, levelNumber = 0) => { } return `_level${levelName}` } -let newAuxVarName = (basename = null, auxNumber = 0) => { +export let newAuxVarName = (basename = null, auxNumber = 0) => { // Return a unique aux variable name. let auxName = basename || nextAuxVarSeq++ if (auxNumber) { @@ -89,23 +89,23 @@ let newAuxVarName = (basename = null, auxNumber = 0) => { } return `_aux${auxName}` } -let isSmoothFunction = fn => { +export let isSmoothFunction = fn => { // Return true if fn is a Vensim smooth function. return fn === '_SMOOTH' || fn === '_SMOOTHI' || fn === '_SMOOTH3' || fn === '_SMOOTH3I' } -let isTrendFunction = fn => { +export let isTrendFunction = fn => { // Return true if fn is a Vensim trend function. return fn === '_TREND' } -let isDelayFunction = fn => { +export let isDelayFunction = fn => { // Return true if fn is a Vensim delay function. return fn === '_DELAY1' || fn === '_DELAY1I' || fn === '_DELAY3' || fn === '_DELAY3I' } -let isArrayFunction = fn => { +export let isArrayFunction = fn => { // Return true if fn is a Vensim array function. return fn === '_SUM' || fn === '_VECTOR_SELECT' || fn === '_VMAX' || fn === '_VMIN' } -let listConcat = (a, x, addSpaces = false) => { +export let listConcat = (a, x, addSpaces = false) => { // Append a string x to string a with comma delimiters let s = addSpaces ? ' ' : '' if (R.isEmpty(x)) { @@ -114,7 +114,7 @@ let listConcat = (a, x, addSpaces = false) => { return a + (R.isEmpty(a) ? '' : `,${s}`) + x } } -let cdbl = x => { +export let cdbl = x => { // Convert a number into a C double constant. let s = x.toString() if (!s.includes('.') && !s.includes('e')) { @@ -122,7 +122,7 @@ let cdbl = x => { } return s } -let strToConst = c => { +export let strToConst = c => { let str = matchRegex(c, /'(.*)'/) if (str) { // Convert a Vensim string constant into a C string literal. @@ -133,9 +133,9 @@ let strToConst = c => { return cdbl(d) } } -let first = a => R.head(a) -let rest = a => R.tail(a) -let extractMatch = (fn, list) => { +export let first = a => R.head(a) +export let rest = a => R.tail(a) +export let extractMatch = (fn, list) => { // Return the first element of a list that matches the predicate and remove it from the list, // or return undefined if no element matches. let i = R.findIndex(fn, list) @@ -145,7 +145,7 @@ let extractMatch = (fn, list) => { return undefined } } -let replaceInArray = (oldStr, newStr, a) => { +export let replaceInArray = (oldStr, newStr, a) => { // Replace the first occurrence of oldStr with newStr in an array of strings a. // A new array is constructed. The original array remains unchanged. let i = R.indexOf(oldStr, a) @@ -157,20 +157,20 @@ let replaceInArray = (oldStr, newStr, a) => { return a } } -let mapObjProps = (f, obj) => { +export let mapObjProps = (f, obj) => { // Map the key and value for each of the object's properties through function f. let result = {} R.forEach(k => (result[f(k)] = f(obj[k])), Object.keys(obj)) return result } -let isIterable = obj => { +export let isIterable = obj => { // Return true of the object is iterable. if (obj == null) { return false } return typeof obj[Symbol.iterator] === 'function' } -let stringToId = str => { +export let stringToId = str => { // Look up a string id. Create the id from the string if it is not found. let stringIndex = R.indexOf(str, strings) if (stringIndex < 0) { @@ -180,35 +180,35 @@ let stringToId = str => { return `id${stringIndex}` } // Command helpers -let outputDir = (outfile, modelDirname) => { +export let outputDir = (outfile, modelDirname) => { if (outfile) { outfile = path.dirname(outfile) } return ensureDir(outfile, 'output', modelDirname) } -let buildDir = (build, modelDirname) => { +export let buildDir = (build, modelDirname) => { // Ensure the given build directory or {modelDir}/build exists. return ensureDir(build, 'build', modelDirname) } -let webDir = buildDirname => { +export let webDir = buildDirname => { // Ensure a web directory exists under the build directory. return ensureDir(null, 'web', buildDirname) } -let ensureDir = (dir, defaultDir, modelDirname) => { +export let ensureDir = (dir, defaultDir, modelDirname) => { // Ensure the directory exists as given or under the model directory. let dirName = dir || path.join(modelDirname, defaultDir) fs.ensureDirSync(dirName) return dirName } -let fileExists = pathname => { +export let fileExists = pathname => { let exists = fs.existsSync(pathname) if (!exists) { console.error(`${pathname} not found`) } return exists } -let linkCSourceFiles = (modelDirname, buildDirname) => { - let cDirname = path.join(__dirname, 'c') +export let linkCSourceFiles = (modelDirname, buildDirname) => { + let cDirname = path.join(new URL('.', import.meta.url).pathname, 'c') sh.ls(cDirname).forEach(filename => { // If a C source file is present in the model directory, link to it instead // as an override. @@ -220,10 +220,10 @@ let linkCSourceFiles = (modelDirname, buildDirname) => { fs.ensureSymlinkSync(srcPathname, dstPathname) }) } -let filesExcept = (glob, exceptionFn) => { +export let filesExcept = (glob, exceptionFn) => { return R.reject(exceptionFn, sh.ls(glob)) } -let modelPathProps = model => { +export let modelPathProps = model => { // Normalize a model pathname that may or may not include the .mdl extension. // If there is not a path in the model argument, default to the current working directory. // Return an object with properties that look like this: @@ -240,7 +240,7 @@ let modelPathProps = model => { modelPathname: path.format(p), } } -let execCmd = cmd => { +export let execCmd = cmd => { // Run a command line silently in the "sh" shell. Print error output on error. let exitCode = 0 let result = sh.exec(cmd, { silent: true }) @@ -250,7 +250,7 @@ let execCmd = cmd => { } return exitCode } -let readDat = async (pathname, prefix = '') => { +export let readDat = async (pathname, prefix = '') => { // Read a Vensim DAT file into a Map. // Key: variable name in canonical format // Value: Map from numeric time value to numeric variable value @@ -307,22 +307,11 @@ let readDat = async (pathname, prefix = '') => { }) }) } -let readXlsx = pathname => { +export let readXlsx = pathname => { return XLSX.readFile(pathname, { cellDates: true }) } -let execCmdAsync = cmd => { - // Run a command line asynchronously and silently in the "sh" shell. Print error output on error. - let exitCode = 0 - sh.exec(cmd, { silent: true }, (status, stdout, stderr) => { - if (status) { - console.log(stderr) - exitCode = 1 - } - }) - return exitCode -} // Convert the var name and subscript names to canonical form separately. -let canonicalVensimName = vname => { +export let canonicalVensimName = vname => { let result = vname let m = vname.match(/([^\[]+)(?:\[([^\]]+)\])?/) if (m) { @@ -337,27 +326,27 @@ let canonicalVensimName = vname => { // Split a model string into an array of equations without the "|" terminator. // Allow "|" to occur in quoted variable names across line breaks. // Retain the backslash character. -let splitEquations = mdl => { +export let splitEquations = mdl => { return split(mdl, { separator: '|', quotes: ['"'], keep: () => true }) } // Function to map over lists's value and index -let mapIndexed = R.addIndex(R.map) +export let mapIndexed = R.addIndex(R.map) // Function to sort an array of strings -let asort = R.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)) +export let asort = R.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)) // Function to alpha sort an array of variables on the model LHS -let vsort = R.sort((a, b) => (a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0)) +export let vsort = R.sort((a, b) => (a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0)) // Function to list an array to stderr -let printArray = R.forEach(x => console.error(x)) +export let printArray = R.forEach(x => console.error(x)) // Function to expand an array of strings into a comma-delimited list of strings -let strlist = a => { +export let strlist = a => { return a.join(', ') } // Function to join an array with newlines -let lines = R.join('\n') +export let lines = R.join('\n') // Match a string against a regular expression and return the first match. // If a capturing group was present, return the first group, otherwise // return the entire match. If the string did not match, return the empty string. -let matchRegex = (str, regex) => { +export let matchRegex = (str, regex) => { let m = str.match(regex) if (!m) { return '' @@ -370,7 +359,7 @@ let matchRegex = (str, regex) => { // Match a string against a regular expression with capture groups. // Return an array of matches for each capture group. // If the string did not match, return the empty string. -let matchRegexCaptures = (str, regex) => { +export let matchRegexCaptures = (str, regex) => { let m = str.match(regex) if (m && m.length > 0) { return m.splice(1) @@ -379,7 +368,7 @@ let matchRegexCaptures = (str, regex) => { } } // Match delimiters recursively. Replace delimited strings globally. -let replaceDelimitedStrings = (str, open, close, newStr) => { +export let replaceDelimitedStrings = (str, open, close, newStr) => { // str is the string to operate on. // open and close are the opening and closing delimiter characters. // newStr is the string to replace delimited substrings with. @@ -418,7 +407,7 @@ let replaceDelimitedStrings = (str, open, close, newStr) => { * This can be used in place of nested for loops and has the benefit of working * for multi-dimensional inputs. */ -const cartesianProductOf = arr => { + export const cartesianProductOf = arr => { // Implementation based on: https://stackoverflow.com/a/36234242 return arr.reduce((a, b) => { return a @@ -435,7 +424,7 @@ const cartesianProductOf = arr => { * this function will return all the permutations, e.g.: * [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] */ -const permutationsOf = (elems, subperms = [[]]) => { + export const permutationsOf = (elems, subperms = [[]]) => { // Implementation based on: https://gist.github.com/CrossEye/f7c2f77f7db7a94af209 return R.isEmpty(elems) ? subperms : @@ -448,7 +437,7 @@ const permutationsOf = (elems, subperms = [[]]) => { // // Debugging helpers // -let vlog = (title, value, depth = 1) => { +export let vlog = (title, value, depth = 1) => { if (value) { console.error(title, ':', util.inspect(value, { depth: depth, colors: false })) } else { @@ -458,57 +447,7 @@ let vlog = (title, value, depth = 1) => { console.trace() } } -let abend = error => { +export let abend = error => { console.error(error) process.exit(1) } -module.exports = { - abend, - asort, - buildDir, - canonicalName, - canonicalVensimName, - cartesianProductOf, - cdbl, - cFunctionName, - decanonicalize, - execCmd, - extractMatch, - filesExcept, - fileExists, - first, - isArrayFunction, - isDelayFunction, - isIterable, - isSeparatedVar, - isSmoothFunction, - isTrendFunction, - lines, - linkCSourceFiles, - listConcat, - mapIndexed, - mapObjProps, - matchRegex, - matchRegexCaptures, - modelPathProps, - newAuxVarName, - newLevelVarName, - newLookupVarName, - newTmpVarName, - outputDir, - permutationsOf, - printArray, - readDat, - readXlsx, - replaceInArray, - replaceDelimitedStrings, - rest, - splitEquations, - strings, - stringToId, - strlist, - strToConst, - vlog, - vsort, - webDir, -} diff --git a/src/LoopIndexVars.js b/src/LoopIndexVars.js index 022f17c9..a9c5fd17 100644 --- a/src/LoopIndexVars.js +++ b/src/LoopIndexVars.js @@ -1,6 +1,6 @@ -const { sub, isDimension } = require('./Subscript') +import { isDimension } from './Subscript.js' -module.exports = class LoopIndexVars { +export default class LoopIndexVars { constructor(indexVars) { this.loopIndexVars = this.loopIndexVarGen(indexVars) this.loopIndices = {} diff --git a/src/MakeConfig.js b/src/MakeConfig.js index c93b0027..b9b43f6d 100755 --- a/src/MakeConfig.js +++ b/src/MakeConfig.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -const fs = require('fs-extra') -const path = require('path') -const R = require('ramda') -const parseCsv = require('csv-parse/lib/sync') -const byline = require('byline') -const B = require('bufx') -const { canonicalName, strings, stringToId, matchRegex } = require('./Helpers') +import fs from 'fs-extra' +import path from 'path' +import R from 'ramda' +import parseCsv from 'csv-parse/lib/sync.js' +import byline from 'byline' +import B from 'bufx' +import { canonicalName, strings, stringToId, matchRegex } from './Helpers.js' let cfg, inputVarNames, outputVarNames, chartVarnames, chartDatfiles let configDirname, specPathname, cfgPathname, stringsPathname, chartDataPathname, datDirname @@ -14,7 +14,7 @@ const CONFIG_FILES = ['app.csv', 'colors.csv', 'graphs.csv', 'sliders.csv', 'vie const CSV_PARSE_OPTS = { columns: true, trim: true, skip_empty_lines: true, skip_lines_with_empty_values: true } const MAX_PLOTS = 8 -let initConfig = (modelDir, webDir) => { +export let initConfig = (modelDir, webDir) => { configDirname = path.join(modelDir, 'config') datDirname = modelDir specPathname = path.join(modelDir, 'app_spec.json') @@ -313,7 +313,7 @@ let emitSpec = currentSpec => { } B.emitPrettyJson(spec) } -let makeModelSpec = () => { +export let makeModelSpec = () => { // Read an existing spec file to pick up and maintain extra properties. let currentSpec = {} try { @@ -331,7 +331,7 @@ let makeModelSpec = () => { console.error(e.stack) } } -let makeModelConfig = () => { +export let makeModelConfig = () => { try { // Write appcfg.js B.clearBuf() @@ -352,7 +352,7 @@ let makeModelConfig = () => { console.error(e.stack) } } -let makeChartData = async () => { +export let makeChartData = async () => { // Read the dat files given in graph config and extract data to a JS file. // Skip this if the chart_data.js file already exists, since it normally only needs // to be created when the model changes, when we do a clean and rebuild. @@ -486,10 +486,3 @@ let exportObj = (name, obj) => { let js = `exports.${name} = ${JSON.stringify(obj)}` B.emitJs(js) } - -module.exports = { - initConfig, - makeModelSpec, - makeModelConfig, - makeChartData -} diff --git a/src/Model.js b/src/Model.js index 97f75450..057d2d8b 100644 --- a/src/Model.js +++ b/src/Model.js @@ -1,14 +1,15 @@ -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const R = require('ramda') -const B = require('bufx') -const yaml = require('js-yaml') -const toposort = require('./toposort') -const VariableReader = require('./VariableReader') -const VarNameReader = require('./VarNameReader') -const SubscriptRangeReader = require('./SubscriptRangeReader') -const Variable = require('./Variable') -const { +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import R from 'ramda' +import B from 'bufx' +import yaml from 'js-yaml' +import toposort from './toposort.js' +import VariableReader from './VariableReader.js' +import VarNameReader from './VarNameReader.js' +import SubscriptRangeReader from './SubscriptRangeReader.js' +import EquationReader from './EquationReader.js' +import Variable from './Variable.js' +import { addIndex, allDimensions, indexNamesForSubscript, @@ -17,8 +18,8 @@ const { normalizeSubscripts, sub, subscriptFamilies -} = require('./Subscript') -const { decanonicalize, isIterable, listConcat, strlist, vlog, vsort } = require('./Helpers') +} from './Subscript.js' +import { decanonicalize, isIterable, listConcat, strlist, vlog, vsort } from './Helpers.js' let variables = [] @@ -393,7 +394,6 @@ function setRefIds() { function readEquations() { // Augment variables with information from their equations. // This requires a refId for each var so that actual refIds can be resolved for the reference list. - const EquationReader = require('./EquationReader') R.forEach(v => { let equationReader = new EquationReader(v) equationReader.read() @@ -401,7 +401,6 @@ function readEquations() { } function addEquation(modelEquation) { // Add an equation in Vensim model format. - const EquationReader = require('./EquationReader') let chars = new antlr4.InputStream(modelEquation) let lexer = new ModelLexer(chars) let tokens = new antlr4.CommonTokenStream(lexer) @@ -478,7 +477,7 @@ function varWithRefId(refId) { return v } } - + // Failing that, chop off the subscript part of the ref id and // find the variables that share that name const varNamePart = rid.split('[')[0] @@ -951,7 +950,7 @@ function printDepsGraph(graph, varType) { console.error(`${dep[0]} → ${dep[1]}`) } } -module.exports = { +export default { addEquation, addNonAtoAVar, addVariable, diff --git a/src/ModelLHSReader.js b/src/ModelLHSReader.js index 2a9833e0..dcfb32df 100644 --- a/src/ModelLHSReader.js +++ b/src/ModelLHSReader.js @@ -1,15 +1,15 @@ -const antlr4 = require('antlr4') -const R = require('ramda') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const ModelReader = require('./ModelReader') -const { sub, isIndex, isDimension } = require('./Subscript') -const { canonicalName, subscripts, listConcat } = require('./Helpers') +import antlr4 from 'antlr4' +import R from 'ramda' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import ModelReader from './ModelReader.js' +import { sub, isDimension } from './Subscript.js' +import { canonicalName } from './Helpers.js' // // ModelLHSReader parses the LHS of a var in Vensim format and // constructs a list of var names with indices for subscripted vars. // -module.exports = class ModelLHSReader extends ModelReader { +export default class ModelLHSReader extends ModelReader { constructor() { super() this.varName = '' diff --git a/src/ModelReader.js b/src/ModelReader.js index 9c845aec..cc12f516 100644 --- a/src/ModelReader.js +++ b/src/ModelReader.js @@ -1,6 +1,6 @@ -const { ModelVisitor } = require('antlr4-vensim') +import { ModelVisitor } from 'antlr4-vensim' -module.exports = class ModelReader extends ModelVisitor { +export default class ModelReader extends ModelVisitor { constructor() { super() // stack of function names and argument indices diff --git a/src/Preprocessor.js b/src/Preprocessor.js index 9c5fdb79..42540011 100644 --- a/src/Preprocessor.js +++ b/src/Preprocessor.js @@ -1,9 +1,9 @@ -const path = require('path') -const R = require('ramda') -const B = require('bufx') -const { splitEquations, replaceDelimitedStrings } = require('./Helpers') +import path from 'path' +import R from 'ramda' +import B from 'bufx' +import { splitEquations, replaceDelimitedStrings } from './Helpers.js' -let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, outDecls = []) => { +export let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, outDecls = []) => { const MACROS_FILENAME = 'macros.txt' const REMOVALS_FILENAME = 'removals.txt' const INSERTIONS_FILENAME = 'mdl-edits.txt' @@ -240,5 +240,3 @@ let preprocessModel = (mdlFilename, spec, profile = 'genc', writeFiles = false, // Return the preprocessed model as a string. return mdl } - -module.exports = { preprocessModel } diff --git a/src/Subscript.js b/src/Subscript.js index 0989e169..095b1688 100644 --- a/src/Subscript.js +++ b/src/Subscript.js @@ -1,8 +1,8 @@ -const util = require('util') -const R = require('ramda') -const B = require('bufx') -const yaml = require('js-yaml') -const { canonicalName, asort, vlog } = require('./Helpers') +import util from 'util' +import R from 'ramda' +import B from 'bufx' +import yaml from 'js-yaml' +import { canonicalName, asort, vlog } from './Helpers.js' // A subscript is a dimension or an index. // Both have the same properties: model name, canonical name, family, values. @@ -47,7 +47,7 @@ const { canonicalName, asort, vlog } = require('./Helpers') // subscript name as the key and a subscript object as the value. let subscripts = new Map() -function Subscript(modelName, modelValue = null, modelFamily = null, modelMappings = null) { +export function Subscript(modelName, modelValue = null, modelFamily = null, modelMappings = null) { let name = canonicalName(modelName) if (modelValue === null) { // Look up a subscript by its model name. @@ -89,7 +89,7 @@ function Subscript(modelName, modelValue = null, modelFamily = null, modelMappin subscripts.set(name, subscript) return subscript } -function sub(name) { +export function sub(name) { // Look up a subscript by its canonical name. // Return undefined if the name is not a subscript name. let result @@ -101,15 +101,15 @@ function sub(name) { } return result } -function isIndex(name) { +export function isIndex(name) { let s = sub(name) return s && typeof s.value === 'number' } -function isDimension(name) { +export function isDimension(name) { let s = sub(name) return s && Array.isArray(s.value) } -function isTrivialDimension(name) { +export function isTrivialDimension(name) { // Return true if the dimension values are trivial, i.e., {0, 1, 2, ..., n-1} let s = sub(name) if (!s || !Array.isArray(s.value)) { @@ -118,7 +118,7 @@ function isTrivialDimension(name) { // The following evaluates to true when all sub-dimensions match their position in the array return R.addIndex(R.all)((subdim, idx) => sub(subdim).value === idx, s.value) } -function addIndex(name, value, family) { +export function addIndex(name, value, family) { // Add an index with arguments in canonical form. let subscript = { name: name, @@ -129,7 +129,7 @@ function addIndex(name, value, family) { } subscripts.set(name, subscript) } -function hasMapping(fromSubscript, toSubscript) { +export function hasMapping(fromSubscript, toSubscript) { let result = false let subFrom = sub(fromSubscript) let subTo = sub(toSubscript) @@ -144,7 +144,7 @@ function hasMapping(fromSubscript, toSubscript) { } return false } -function mapIndex(fromSubName, fromIndexName, toSubName) { +export function mapIndex(fromSubName, fromIndexName, toSubName) { // Return the index names that the fromSubName dimension maps from fromIndexName // to the toSubName dimension. Return an empty array if there is no such mapping. let toIndexNames = [] @@ -165,21 +165,21 @@ function mapIndex(fromSubName, fromIndexName, toSubName) { } return toIndexNames } -function printSubscripts() { +export function printSubscripts() { B.clearBuf() for (let [k, v] of subscripts) { B.emitLine(`${k}:\n${util.inspect(v, { depth: null })}\n`) } return B.getBuf() } -function yamlSubsList() { +export function yamlSubsList() { let subs = {} for (let [k, v] of subscripts) { subs[k] = v } return yaml.safeDump(subs) } -function loadSubscriptsFromYaml(yamlSubs) { +export function loadSubscriptsFromYaml(yamlSubs) { // Load the subscripts map from subscripts serialized to a YAML file by yamlSubsList. // This function should be called instead of adding subscripts through the constructor. let subs = yaml.safeLoad(yamlSubs) @@ -187,7 +187,7 @@ function loadSubscriptsFromYaml(yamlSubs) { subscripts.set(k, subs[k]) } } -function normalizeSubscripts(subscripts) { +export function normalizeSubscripts(subscripts) { // Sort a list of subscript names already in canonical form according to the subscript family. let subs = R.map(name => sub(name), subscripts) subs = R.sortBy(R.prop('family'), subs) @@ -200,7 +200,7 @@ function normalizeSubscripts(subscripts) { } return normalizedSubs } -function extractMarkedDims(subscripts) { +export function extractMarkedDims(subscripts) { // Extract all marked dimensions and update subscripts. let dims = [] for (let i = 0; i < subscripts.length; i++) { @@ -212,7 +212,7 @@ function extractMarkedDims(subscripts) { } return dims } -function subscriptFamilies(subscripts) { +export function subscriptFamilies(subscripts) { // Return a list of the subscript families for each subscript. try { return R.map(subscriptName => sub(subscriptName).family, subscripts) @@ -221,20 +221,20 @@ function subscriptFamilies(subscripts) { debugger } } -function subscriptFamily(subscriptName) { +export function subscriptFamily(subscriptName) { // Return the subscript family object for the subscript name. let family = sub(subscriptName).family return sub(family) } -function allSubscripts() { +export function allSubscripts() { // Return an array of all subscript objects. return [...subscripts.values()] } -function allDimensions() { +export function allDimensions() { // Return an array of all dimension subscript objects. return R.filter(subscript => Array.isArray(subscript.value), allSubscripts()) } -function allMappings() { +export function allMappings() { // Return an array of all subscript mappings as objects. let mappings = [] R.forEach(subscript => { @@ -248,7 +248,7 @@ function allMappings() { }, allSubscripts()) return mappings } -function indexNamesForSubscript(subscript) { +export function indexNamesForSubscript(subscript) { // Return a list of index names for a subscript in canonical form. if (isIndex(subscript)) { // The subscript is an index, so just return it. @@ -264,7 +264,7 @@ function indexNamesForSubscript(subscript) { return dim.value } } -function separatedVariableIndex(rhsSub, variable, rhsSubscripts) { +export function separatedVariableIndex(rhsSub, variable, rhsSubscripts) { // Given an RHS subscript, find an LHS index in the separation dimension that matches it. // The LHS and RHS subscripts need not be in normal order or have the same number of subscripts in a var. // The search proceeds through three stages: @@ -319,36 +319,12 @@ function separatedVariableIndex(rhsSub, variable, rhsSubscripts) { return null } // Function to filter canonical dimension names from a list of names -let dimensionNames = R.pipe( +export let dimensionNames = R.pipe( R.filter(subscript => isDimension(subscript)), asort ) // Function to filter canonical index names from a list of names -let indexNames = R.pipe( +export let indexNames = R.pipe( R.filter(subscript => isIndex(subscript)), asort ) - -module.exports = { - Subscript, - addIndex, - allDimensions, - allMappings, - dimensionNames, - extractMarkedDims, - hasMapping, - indexNames, - indexNamesForSubscript, - isDimension, - isIndex, - isTrivialDimension, - loadSubscriptsFromYaml, - mapIndex, - normalizeSubscripts, - printSubscripts, - separatedVariableIndex, - sub, - subscriptFamilies, - subscriptFamily, - yamlSubsList -} diff --git a/src/SubscriptRangeReader.js b/src/SubscriptRangeReader.js index 61a9c5a8..ebe7e3f4 100644 --- a/src/SubscriptRangeReader.js +++ b/src/SubscriptRangeReader.js @@ -1,10 +1,9 @@ -const antlr4 = require('antlr4') -const { ModelParser } = require('antlr4-vensim') -const R = require('ramda') -const ModelReader = require('./ModelReader') -const { Subscript } = require('./Subscript') +import { ModelParser } from 'antlr4-vensim' +import R from 'ramda' +import ModelReader from './ModelReader.js' +import { Subscript } from './Subscript.js' -module.exports = class SubscriptRangeReader extends ModelReader { +export default class SubscriptRangeReader extends ModelReader { constructor() { super() } diff --git a/src/VarNameReader.js b/src/VarNameReader.js index 28506f4d..b2b3310c 100644 --- a/src/VarNameReader.js +++ b/src/VarNameReader.js @@ -1,14 +1,14 @@ -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const ModelReader = require('./ModelReader') -const R = require('ramda') -const { sub, isIndex, normalizeSubscripts } = require('./Subscript') -const { canonicalName, vlog, subscripts } = require('./Helpers') +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import ModelReader from './ModelReader.js' +import R from 'ramda' +import { sub, isIndex, normalizeSubscripts } from './Subscript.js' +import { canonicalName } from './Helpers.js' // // VarNameReader reads a model var name using the parser to get the var name in C format. // This is used to generate a variable output in the output section. // -module.exports = class VarNameReader extends ModelReader { +export default class VarNameReader extends ModelReader { constructor() { super() this.varName = '' diff --git a/src/Variable.js b/src/Variable.js index b82fe758..6df29739 100644 --- a/src/Variable.js +++ b/src/Variable.js @@ -1,7 +1,4 @@ -const R = require('ramda') -const { subscripts } = require('./Helpers') - -module.exports = class Variable { +export default class Variable { constructor(eqnCtx) { // The equation rule context allows us to generate code by visiting the parse tree. this.eqnCtx = eqnCtx diff --git a/src/VariableReader.js b/src/VariableReader.js index 56255a9a..98a19fe4 100644 --- a/src/VariableReader.js +++ b/src/VariableReader.js @@ -1,16 +1,16 @@ -const antlr4 = require('antlr4') -const { ModelParser } = require('antlr4-vensim') -const R = require('ramda') -const ModelReader = require('./ModelReader') -const Variable = require('./Variable') -const { sub, isDimension, isIndex, normalizeSubscripts } = require('./Subscript') -const { canonicalName, vlog, replaceInArray, strlist } = require('./Helpers') +import { ModelParser } from 'antlr4-vensim' +import R from 'ramda' +import ModelReader from './ModelReader.js' +import Model from './Model.js' +import Variable from './Variable.js' +import { sub, isDimension, isIndex, normalizeSubscripts } from './Subscript.js' +import { canonicalName, vlog, replaceInArray, strlist } from './Helpers.js' // Set true to print extra debugging information to stderr. const DEBUG_LOG = false let debugLog = (title, value) => !DEBUG_LOG || vlog(title, value) -module.exports = class VariableReader extends ModelReader { +export default class VariableReader extends ModelReader { constructor(specialSeparationDims, directData) { super() // specialSeparationDims are var names that need to be separated because of @@ -29,7 +29,6 @@ module.exports = class VariableReader extends ModelReader { } visitEquation(ctx) { // Start a new variable defined by this equation. - const { addVariable } = require('./Model') this.var = new Variable(ctx) // Allow for an alternate array of variables that are expanded over subdimensions. this.expandedVars = [] @@ -37,10 +36,10 @@ module.exports = class VariableReader extends ModelReader { super.visitEquation(ctx) if (R.isEmpty(this.expandedVars)) { // Add a single variable defined by the equation. - addVariable(this.var) + Model.addVariable(this.var) } else { // Add variables expanded over indices to the model. - R.forEach(v => addVariable(v), this.expandedVars) + R.forEach(v => Model.addVariable(v), this.expandedVars) } } visitLhs(ctx) { diff --git a/src/index.js b/src/index.js index 8930ba65..f270d120 100755 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -module.exports = require('./sde'); +import './sde.js' diff --git a/src/sde-build.js b/src/sde-build.js index d976ce2c..26c7c337 100644 --- a/src/sde-build.js +++ b/src/sde-build.js @@ -1,9 +1,9 @@ -const { generate } = require('./sde-generate') -const { compile } = require('./sde-compile') +import { generate } from './sde-generate.js' +import { compile } from './sde-compile.js' -let command = 'build [options] ' -let describe = 'generate model code and compile it' -let builder = { +export let command = 'build [options] ' +export let describe = 'generate model code and compile it' +export let builder = { spec: { describe: 'pathname of the I/O specification JSON file', type: 'string', @@ -15,16 +15,16 @@ let builder = { alias: 'b' } } -let handler = argv => { +export let handler = argv => { build(argv.model, argv) } -let build = async (model, opts) => { +export let build = async (model, opts) => { opts.genc = true await generate(model, opts) compile(model, opts) } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-causes.js b/src/sde-causes.js index e605cb4e..bb9efd42 100644 --- a/src/sde-causes.js +++ b/src/sde-causes.js @@ -1,10 +1,9 @@ -const path = require('path') -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const { codeGenerator } = require('./CodeGen') -const { preprocessModel } = require('./Preprocessor') -const { modelPathProps } = require('./Helpers') -const B = require('bufx') +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import { codeGenerator } from './CodeGen.js' +import { preprocessModel } from './Preprocessor.js' +import { modelPathProps } from './Helpers.js' +import B from 'bufx' let command = 'causes [options] ' let describe = 'print dependencies for a C variable name' @@ -61,7 +60,7 @@ let writeOutput = (outputPathname, outputText) => { console.log(e.message) } } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-clean.js b/src/sde-clean.js index 961ee6ea..047bf5f5 100644 --- a/src/sde-clean.js +++ b/src/sde-clean.js @@ -1,6 +1,5 @@ -const path = require('path') -const sh = require('shelljs') -const { modelPathProps, buildDir } = require('./Helpers') +import path from 'path' +import sh from 'shelljs' let command = 'clean [options]' let describe = 'clean out the build, output, and html directories for a model' @@ -27,7 +26,7 @@ let clean = (opts) => { sh.rm('-r', htmlDirname) sh.config.silent = silentState } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-compare.js b/src/sde-compare.js index f5dadd21..f440b3cd 100644 --- a/src/sde-compare.js +++ b/src/sde-compare.js @@ -1,13 +1,13 @@ -const R = require('ramda') -const { pr } = require('bufx') -const { readDat, fileExists } = require('./Helpers') +import R from 'ramda' +import { pr } from 'bufx' +import { readDat, fileExists } from './Helpers.js' // The epsilon value determines the required precision for value comparisons. let ε = 1e-5 -let command = 'compare [options] ' -let describe = 'compare Vensim and SDEverywhere log files in DAT format' -let builder = { +export let command = 'compare [options] ' +export let describe = 'compare Vensim and SDEverywhere log files in DAT format' +export let builder = { precision: { describe: 'precision to which values must agree (default 1e-5)', type: 'number', @@ -24,10 +24,10 @@ let builder = { alias: 't' } } -let handler = argv => { +export let handler = argv => { compare(argv.vensimlog, argv.sdelog, argv) } -let compare = async (vensimfile, sdefile, opts) => { +export let compare = async (vensimfile, sdefile, opts) => { if (opts.precision) { ε = opts.precision } @@ -89,7 +89,7 @@ let isEqual = (x, y) => { return difference(x, y) < ε } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-compile.js b/src/sde-compile.js index a00c2aef..d36709e5 100644 --- a/src/sde-compile.js +++ b/src/sde-compile.js @@ -1,21 +1,19 @@ -const path = require('path') -const sh = require('shelljs') -const R = require('ramda') -const { modelPathProps, buildDir, linkCSourceFiles, execCmd } = require('./Helpers') +import sh from 'shelljs' +import { modelPathProps, buildDir, linkCSourceFiles, execCmd } from './Helpers.js' -let command = 'compile [options] ' -let describe = 'compile the generated model to an executable file' -let builder = { +export let command = 'compile [options] ' +export let describe = 'compile the generated model to an executable file' +export let builder = { builddir: { describe: 'build directory', type: 'string', alias: 'b' } } -let handler = argv => { +export let handler = argv => { compile(argv.model, argv) } -let compile = (model, opts) => { +export let compile = (model, opts) => { let { modelDirname, modelName, modelPathname } = modelPathProps(model) // Ensure the build directory exists. let buildDirname = buildDir(opts.builddir, modelDirname) @@ -33,7 +31,7 @@ let compile = (model, opts) => { } return 0 } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-exec.js b/src/sde-exec.js index 5dbcdd8b..659fc575 100644 --- a/src/sde-exec.js +++ b/src/sde-exec.js @@ -1,10 +1,10 @@ -const path = require('path') -const moment = require('moment') -const { modelPathProps, buildDir, outputDir, execCmd } = require('./Helpers') +import path from 'path' +import moment from 'moment' +import { modelPathProps, buildDir, outputDir, execCmd } from './Helpers.js' -let command = 'exec [options] ' -let describe = 'execute the model and capture its output to a file' -let builder = { +export let command = 'exec [options] ' +export let describe = 'execute the model and capture its output to a file' +export let builder = { builddir: { describe: 'build directory', type: 'string', @@ -16,10 +16,10 @@ let builder = { alias: 'o' } } -let handler = argv => { +export let handler = argv => { exec(argv.model, argv) } -let exec = (model, opts) => { +export let exec = (model, opts) => { let { modelDirname, modelName, modelPathname } = modelPathProps(model) // Ensure the build and output directories exist. let buildDirname = buildDir(opts.builddir, modelDirname) @@ -39,7 +39,7 @@ let exec = (model, opts) => { return 0 } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-flatten.js b/src/sde-flatten.js index f8adbc93..73b559be 100644 --- a/src/sde-flatten.js +++ b/src/sde-flatten.js @@ -1,7 +1,7 @@ -const B = require('bufx') -const path = require('path') -const { modelPathProps, buildDir } = require('./Helpers') -const { preprocessModel } = require('./Preprocessor') +import B from 'bufx' +import path from 'path' +import { modelPathProps, buildDir } from './Helpers.js' +import { preprocessModel } from './Preprocessor.js' const command = 'flatten [options] ' @@ -184,7 +184,7 @@ const flatten = async (outFile, inFiles, opts) => { B.writeBuf(outputPathname, 'pp') } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-generate.js b/src/sde-generate.js index 3642620f..e2c8a8d6 100644 --- a/src/sde-generate.js +++ b/src/sde-generate.js @@ -1,11 +1,12 @@ -const fs = require('fs-extra') -const path = require('path') -const sh = require('shelljs') -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const { codeGenerator } = require('./CodeGen') -const { preprocessModel } = require('./Preprocessor') -const { +import fs from 'fs-extra' +import path from 'path' +import sh from 'shelljs' +import B from 'bufx' +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import { codeGenerator } from './CodeGen.js' +import { preprocessModel } from './Preprocessor.js' +import { canonicalName, modelPathProps, buildDir, @@ -15,20 +16,19 @@ const { execCmd, readDat, readXlsx -} = require('./Helpers') -const { initConfig, makeModelSpec, makeModelConfig, makeChartData } = require('./MakeConfig') -const Model = require('./Model') -const Subscript = require('./Subscript') -const B = require('bufx') +} from './Helpers.js' +import { initConfig, makeModelSpec, makeModelConfig, makeChartData } from './MakeConfig.js' +import Model from './Model.js' +import { printSubscripts, yamlSubsList } from './Subscript.js' // Set true to retain generated source files during development. const RETAIN_GENERATED_SOURCE_FILES = false // A custom CSS file may be provided to override built-in styles. const CUSTOM_CSS = 'custom.css' -let command = 'generate [options] ' -let describe = 'generate model code' -let builder = { +export let command = 'generate [options] ' +export let describe = 'generate model code' +export let builder = { genc: { describe: 'generate C code for the model', type: 'boolean' @@ -68,11 +68,11 @@ let builder = { alias: 'r' } } -let handler = argv => { +export let handler = argv => { generate(argv.model, argv) } -let generate = async (model, opts) => { +export let generate = async (model, opts) => { // Get the model name and directory from the model argument. let { modelDirname, modelName, modelPathname } = modelPathProps(model) // Ensure the build directory exists. @@ -149,7 +149,7 @@ let generate = async (model, opts) => { writeOutput(outputPathname, outputText) // Write subscripts to a text file. outputPathname = path.join(buildDirname, `${modelName}_subs.txt`) - outputText = Subscript.printSubscripts() + outputText = printSubscripts() writeOutput(outputPathname, outputText) // Write variables to a YAML file. outputPathname = path.join(buildDirname, `${modelName}_vars.yaml`) @@ -157,7 +157,7 @@ let generate = async (model, opts) => { writeOutput(outputPathname, outputText) // Write subscripts to a YAML file. outputPathname = path.join(buildDirname, `${modelName}_subs.yaml`) - outputText = Subscript.yamlSubsList() + outputText = yamlSubsList() writeOutput(outputPathname, outputText) } // Generate a web app for the model. @@ -210,7 +210,7 @@ let generateWASM = (buildDirname, webDirname) => { } let copyTemplate = buildDirname => { // Copy template files from the src/web directory. - let templateDirname = path.join(__dirname, 'web') + let templateDirname = path.join(new URL('.', import.meta.url).pathname, 'web') sh.cp('-Rf', templateDirname, buildDirname) } let customizeApp = (modelDirname, webDirname) => { @@ -241,7 +241,7 @@ let packApp = webDirname => { let sourcePathname = path.join(webDirname, 'index.js') let minPathname = path.join(webDirname, 'index.min.js') // Resolve module imports against the SDEverywhere node_modules. - let nodePath = path.resolve(__dirname, '..', 'node_modules') + let nodePath = path.join(new URL('..', import.meta.url).pathname, 'node_modules') let b = browserify(sourcePathname, { paths: nodePath }) let writable = fs.createWriteStream(minPathname) b.bundle() @@ -296,7 +296,7 @@ let writeOutput = (outputPathname, outputText) => { console.log(e.message) } } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-log.js b/src/sde-log.js index 34768fe9..ec7473da 100644 --- a/src/sde-log.js +++ b/src/sde-log.js @@ -1,23 +1,23 @@ -const fs = require('fs-extra') -const path = require('path') -const R = require('ramda') -const B = require('bufx') -const byline = require('byline') -const { canonicalName } = require('./Helpers') +import fs from 'fs-extra' +import path from 'path' +import R from 'ramda' +import B from 'bufx' +import byline from 'byline' +import { canonicalName } from './Helpers.js' -let command = 'log [options] ' -let describe = 'process an SDEverywhere log file' -let builder = { +export let command = 'log [options] ' +export let describe = 'process an SDEverywhere log file' +export let builder = { dat: { describe: 'convert a TSV log file to a Vensim DAT file', type: 'boolean', alias: 'd' } } -let handler = argv => { +export let handler = argv => { log(argv.logfile, argv) } -let log = async (logPathname, opts) => { +export let log = async (logPathname, opts) => { if (opts.dat) { let p = path.parse(logPathname) let datPathname = path.format({ dir: p.dir, name: p.name, ext: '.dat' }) @@ -87,7 +87,7 @@ let exportDat = async (logPathname, datPathname) => { await readLog() await writeDat() } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-names.js b/src/sde-names.js index 820796c5..318988dd 100644 --- a/src/sde-names.js +++ b/src/sde-names.js @@ -1,11 +1,10 @@ -const path = require('path') -const antlr4 = require('antlr4') -const { ModelLexer, ModelParser } = require('antlr4-vensim') -const { codeGenerator } = require('./CodeGen') -const { preprocessModel } = require('./Preprocessor') -const { vensimName, cName } = require('./Model') -const { modelPathProps } = require('./Helpers') -const B = require('bufx') +import antlr4 from 'antlr4' +import { ModelLexer, ModelParser } from 'antlr4-vensim' +import { codeGenerator } from './CodeGen.js' +import { preprocessModel } from './Preprocessor.js' +import Model from './Model.js' +import { modelPathProps } from './Helpers.js' +import B from 'bufx' let command = 'names [options] ' let describe = 'convert variable names in a model' @@ -42,9 +41,9 @@ let names = (model, namesPathname, opts) => { for (let line of lines) { if (line.length > 0) { if (opts.toc) { - B.emitLine(cName(line)) + B.emitLine(Model.cName(line)) } else if (opts.tovml) { - B.emitLine(vensimName(line)) + B.emitLine(Model.vensimName(line)) } } } @@ -82,7 +81,7 @@ let writeOutput = (outputPathname, outputText) => { console.log(e.message) } } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-run.js b/src/sde-run.js index df9bcc3d..f75482e2 100644 --- a/src/sde-run.js +++ b/src/sde-run.js @@ -1,9 +1,9 @@ -const { build } = require('./sde-build') -const { exec } = require('./sde-exec') +import { build } from './sde-build.js' +import { exec } from './sde-exec.js' -let command = 'run [options] ' -let describe = 'build a model, run it, and capture its output to a file' -let builder = { +export let command = 'run [options] ' +export let describe = 'build a model, run it, and capture its output to a file' +export let builder = { spec: { describe: 'pathname of the I/O specification JSON file', type: 'string', @@ -20,15 +20,15 @@ let builder = { alias: 'o' } } -let handler = argv => { +export let handler = argv => { run(argv.model, argv) } -let run = async (model, opts) => { +export let run = async (model, opts) => { await build(model, opts) exec(model, opts) } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-test.js b/src/sde-test.js index 2e68ef80..f9f52e92 100644 --- a/src/sde-test.js +++ b/src/sde-test.js @@ -1,12 +1,12 @@ -const path = require('path') -const { run } = require('./sde-run') -const { log } = require('./sde-log') -const { compare } = require('./sde-compare') -const { modelPathProps, outputDir } = require('./Helpers') +import path from 'path' +import { run } from './sde-run.js' +import { log } from './sde-log.js' +import { compare } from './sde-compare.js' +import { modelPathProps, outputDir } from './Helpers.js' -let command = 'test [options] ' -let describe = 'build the model, run it, process the log, and compare to Vensim data' -let builder = { +export let command = 'test [options] ' +export let describe = 'build the model, run it, process the log, and compare to Vensim data' +export let builder = { spec: { describe: 'pathname of the I/O specification JSON file', type: 'string', @@ -28,10 +28,10 @@ let builder = { alias: 'p' } } -let handler = argv => { +export let handler = argv => { test(argv.model, argv) } -let test = async (model, opts) => { +export let test = async (model, opts) => { // Run the model and save output to an SDE log file. let { modelDirname, modelName } = modelPathProps(model) let logPathname @@ -54,7 +54,7 @@ let test = async (model, opts) => { await compare(vensimPathname, sdePathname, opts) } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde-which.js b/src/sde-which.js index 8656bb10..f9fcb527 100644 --- a/src/sde-which.js +++ b/src/sde-which.js @@ -1,4 +1,4 @@ -const path = require('path') +import path from 'path' let command = 'which' let describe = 'print the SDEverywhere home directory' @@ -8,10 +8,10 @@ let handler = argv => { } let which = opts => { // The SDEverywhere home directory is one level above the src directory where this code runs. - let homeDir = path.join(__dirname, '..') + let homeDir = path.resolve(new URL('..', import.meta.url).pathname) console.log(homeDir) } -module.exports = { +export default { command, describe, builder, diff --git a/src/sde.js b/src/sde.js index 866e2541..830790b0 100644 --- a/src/sde.js +++ b/src/sde.js @@ -1,6 +1,6 @@ // SDEverywhere -// http://sdeverywhere.org -// Copyright © 2016-17 Todd Fincannon and Climate Interactive +// https://sdeverywhere.org +// Copyright © 2021 Todd Fincannon and Climate Interactive // SDEverywhere may be freely distributed under the MIT license. // Commands: @@ -16,24 +16,37 @@ // names // causes -const util = require('util') +import yargs from 'yargs' +import sdeGenerate from './sde-generate.js' +import sdeFlatten from './sde-flatten.js' +import sdeCompile from './sde-compile.js' +import sdeExec from './sde-exec.js' +import sdeLog from './sde-log.js' +import sdeCompare from './sde-compare.js' +import sdeClean from './sde-clean.js' +import sdeBuild from './sde-build.js' +import sdeRun from './sde-run.js' +import sdeTest from './sde-test.js' +import sdeNames from './sde-names.js' +import sdeCauses from './sde-causes.js' +import sdeWhich from './sde-which.js' -let exitCode = require('yargs') +yargs .strict() .usage('usage: $0 ') - .command(require('./sde-generate')) - .command(require('./sde-flatten')) - .command(require('./sde-compile')) - .command(require('./sde-exec')) - .command(require('./sde-log')) - .command(require('./sde-compare')) - .command(require('./sde-clean')) - .command(require('./sde-build')) - .command(require('./sde-run')) - .command(require('./sde-test')) - .command(require('./sde-names')) - .command(require('./sde-causes')) - .command(require('./sde-which')) + .command(sdeGenerate) + .command(sdeFlatten) + .command(sdeCompile) + .command(sdeExec) + .command(sdeLog) + .command(sdeCompare) + .command(sdeClean) + .command(sdeBuild) + .command(sdeRun) + .command(sdeTest) + .command(sdeNames) + .command(sdeCauses) + .command(sdeWhich) .demandCommand(1) .help() .version() diff --git a/src/toposort.js b/src/toposort.js index 66260073..4f53b294 100644 --- a/src/toposort.js +++ b/src/toposort.js @@ -7,12 +7,10 @@ * @returns {Array} */ -module.exports = function(edges) { +export default function(edges) { return toposort(uniqueNodes(edges), edges) } -module.exports.array = toposort - function toposort(nodes, edges) { var cursor = nodes.length , sorted = new Array(cursor) From d43e4ef9c98cdcd4eb3164870543baa1e5d13705 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Thu, 8 Jul 2021 17:55:30 -0700 Subject: [PATCH 04/11] build: replace dynamic requires for genhtml (#75) --- README.md | 4 +-- ...e a Vensim Model into a Web Application.md | 14 ++++++-- ... a Vensim Model into a Web Application.pdf | Bin 95071 -> 67142 bytes src/MakeConfig.js | 6 ++++ src/sde-generate.js | 31 +++++++++--------- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 39918390..a8839f01 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SDEverywhere Guide -Revised: 2020-04-06 +Revised: 2021-07-08 @@ -73,7 +73,7 @@ Using SDEverywhere requires the macOS operating system and the free [Xcode](http ### Install Node.js -Install [Node.js](https://nodejs.org/) version 8.9.4 LTS or later. This will also install the `npm` Node Package Manager. +Install [Node.js](https://nodejs.org/) version 14 or later. This will also install the `npm` Node Package Manager. ### Install SDEverywhere diff --git a/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.md b/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.md index 460b0cbd..cb19f716 100644 --- a/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.md +++ b/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.md @@ -1,6 +1,6 @@ # Using SDEverywhere to Make a Vensim Model into a Web Application -Revised: 2019-07-24 +Revised: 2021-07-08 This tutorial shows you how to take your Vensim model and turn it into an interactive web application using the open-source SDEverywhere toolkit. SDEverywhere currently requires the macOS operating system in development, but the resulting web app can be run on any web server, or even offline using the Firefox browser. @@ -10,13 +10,21 @@ This tutorial shows you how to take your Vensim model and turn it into an intera First install SDEverywhere using the instructions in the "Installing" section of the README. +### Install Browserify + +HTML generation uses [Browserify](http://browserify.org) to bundle the web app. The `browserify` package is not included with SDEverywhere by default, so you will need to install it first as a dev dependency. + +``` +npm install --save-dev browserify +``` + ### Install Emscripten The [Emscripten toolchain](https://emscripten.org/) converts the C code generated by SDEverywhere into JavaScript, and then compiles it into WebAssembly that runs in a browser. 1. Install the Java JDK. Emscripten uses Java to run the Google Closure compiler, which shrinks generated JavaScript code size considerably. Download and install the JDK from the [Oracle download page](https://www.oracle.com/technetwork/java/javase/downloads/index.html). Be sure to install the JDK and not the JRE. -2. Install Python 3. The easiest way to get the necessary SSL certificates for downloading Emscripten tools from GitHub using the Emscripten SDK is to install Python 3 with the [pyenv Python version manager](https://github.com/pyenv/pyenv). Follow the instructions for installing pyenv using Homebrew. +2. Install Python 3. This is now the default on recent versions of macOS. Check with `python --version`. If you need to install Python 3, the easiest way is to use the [pyenv Python version manager](https://github.com/pyenv/pyenv). Follow the instructions for installing pyenv using Homebrew. 3. Clone the Emscripten SDK from GitHub. @@ -70,7 +78,7 @@ sde test -p 1e-3 {model} ## Designing your web app -SDEverywhere generates a web application based on a standard template merged with app configuration in CSV files. Refer to the *SDEverywhere Web App Configuration Reference* document for details. There is a fully worked-out example in the `models/sir` directory. +SDEverywhere generates a web application based on a standard template merged with app configuration in CSV files. Refer to the *SDEverywhere Web App Configuration Reference* document for details. There is a fully worked-out example in the `models/sir/model` directory. Copy the `config` directory from the `notes` directory to your model directory. This will give you empty CSV files with the proper headers. diff --git a/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.pdf b/notes/Using SDEverywhere to Make a Vensim Model into a Web Application.pdf index fed97efe5fb3a4eb405a55f45aa76302c646005e..5a239cf8cbd9ee49fb8ca080aa2a6e8edad3703a 100644 GIT binary patch literal 67142 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ï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 literal 95071 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 diff --git a/src/MakeConfig.js b/src/MakeConfig.js index b9b43f6d..c13555b6 100755 --- a/src/MakeConfig.js +++ b/src/MakeConfig.js @@ -352,6 +352,12 @@ export let makeModelConfig = () => { console.error(e.stack) } } +export let readModelConfig = () => { + // Evaluate the model config file into an app object. Resolve the exports. + let cfg = B.read(cfgPathname) + cfg = `let exports = {}; ${cfg}; return exports` + return Function(cfg)() +} export let makeChartData = async () => { // Read the dat files given in graph config and extract data to a JS file. // Skip this if the chart_data.js file already exists, since it normally only needs diff --git a/src/sde-generate.js b/src/sde-generate.js index e2c8a8d6..be96aa73 100644 --- a/src/sde-generate.js +++ b/src/sde-generate.js @@ -17,7 +17,7 @@ import { readDat, readXlsx } from './Helpers.js' -import { initConfig, makeModelSpec, makeModelConfig, makeChartData } from './MakeConfig.js' +import { initConfig, makeModelSpec, makeModelConfig, makeChartData, readModelConfig } from './MakeConfig.js' import Model from './Model.js' import { printSubscripts, yamlSubsList } from './Subscript.js' @@ -216,8 +216,7 @@ let copyTemplate = buildDirname => { let customizeApp = (modelDirname, webDirname) => { try { // Read the newly generated model config to customize app files. - let cfgPathname = `${webDirname}/appcfg` - const { app } = require(cfgPathname) + let app = readModelConfig().app if (app && app.logo) { let logoPathname = `${modelDirname}/${app.logo}` sh.cp('-f', logoPathname, webDirname) @@ -236,23 +235,25 @@ let customizeApp = (modelDirname, webDirname) => { } } let packApp = webDirname => { - const browserify = require('browserify') // Concatenate JS source files for the browser. let sourcePathname = path.join(webDirname, 'index.js') let minPathname = path.join(webDirname, 'index.min.js') // Resolve module imports against the SDEverywhere node_modules. let nodePath = path.join(new URL('..', import.meta.url).pathname, 'node_modules') - let b = browserify(sourcePathname, { paths: nodePath }) - let writable = fs.createWriteStream(minPathname) - b.bundle() - .pipe(writable) - .on('finish', error => { - // Remove JavaScript source files. - if (!RETAIN_GENERATED_SOURCE_FILES) { - let sourceFiles = filesExcept(`${webDirname}/*.js`, name => name.endsWith('index.min.js') || name.endsWith('model_sde.js')) - sh.rm(sourceFiles) - } - }) + // Browserify is an optional install that we only import when generating HTML. + import('browserify').then(browserify => { + let b = browserify.default(sourcePathname, { paths: nodePath }) + let writable = fs.createWriteStream(minPathname) + b.bundle() + .pipe(writable) + .on('finish', error => { + // Remove JavaScript source files. + if (!RETAIN_GENERATED_SOURCE_FILES) { + let sourceFiles = filesExcept(`${webDirname}/*.js`, name => name.endsWith('index.min.js') || name.endsWith('model_sde.js')) + sh.rm(sourceFiles) + } + }) + }) } let parseModel = input => { // Read the model text and return a parse tree. From d989365c4212b9869f58559e00a4bc502b274673 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 9 Jul 2021 16:30:16 -0700 Subject: [PATCH 05/11] feat: implement GET DIRECT SUBSCRIPT for CSV (#77) --- models/directsubs/b_subs.csv | 4 ++ models/directsubs/c_subs.csv | 2 + models/directsubs/directsubs.dat | 30 ++++++++++ models/directsubs/directsubs.mdl | 61 ++++++++++++++++++++ models/directsubs/directsubs_subs.txt | 63 ++++++++++++++++++++ models/directsubs/directsubs_vars.txt | 82 +++++++++++++++++++++++++++ src/Helpers.js | 58 +++++++++++++------ src/SubscriptRangeReader.js | 44 ++++++++++++++ 8 files changed, 328 insertions(+), 16 deletions(-) create mode 100644 models/directsubs/b_subs.csv create mode 100644 models/directsubs/c_subs.csv create mode 100644 models/directsubs/directsubs.dat create mode 100644 models/directsubs/directsubs.mdl create mode 100644 models/directsubs/directsubs_subs.txt create mode 100644 models/directsubs/directsubs_vars.txt diff --git a/models/directsubs/b_subs.csv b/models/directsubs/b_subs.csv new file mode 100644 index 00000000..2516ad76 --- /dev/null +++ b/models/directsubs/b_subs.csv @@ -0,0 +1,4 @@ +DimB +B1 +B2 +B3 \ No newline at end of file diff --git a/models/directsubs/c_subs.csv b/models/directsubs/c_subs.csv new file mode 100644 index 00000000..b5b62b93 --- /dev/null +++ b/models/directsubs/c_subs.csv @@ -0,0 +1,2 @@ +DimC,, +C1,C2,C3 \ No newline at end of file diff --git a/models/directsubs/directsubs.dat b/models/directsubs/directsubs.dat new file mode 100644 index 00000000..72008991 --- /dev/null +++ b/models/directsubs/directsubs.dat @@ -0,0 +1,30 @@ +a[A1] +0 10 +a[A2] +0 20 +a[A3] +0 30 +b[B1] +0 1 +b[B2] +0 2 +b[B3] +0 3 +c[C1] +0 11 +1 11 +c[C2] +0 21 +1 21 +c[C3] +0 31 +1 31 +FINAL TIME +0 1 +INITIAL TIME +0 0 +SAVEPER +0 1 +1 1 +TIME STEP +0 1 diff --git a/models/directsubs/directsubs.mdl b/models/directsubs/directsubs.mdl new file mode 100644 index 00000000..9f51ee78 --- /dev/null +++ b/models/directsubs/directsubs.mdl @@ -0,0 +1,61 @@ +{UTF-8} +DimA: A1, A2, A3 -> DimB, DimC ~~| +DimB: + GET DIRECT SUBSCRIPT( + 'b_subs.csv', + ',', + 'A2', + 'A', + '' + ) + ~~| +DimC: + GET DIRECT SUBSCRIPT( + 'c_subs.csv', + ',', + 'A2', + '2', + '' + ) + ~~| +a[DimA] = 10, 20, 30 + ~~| +b[DimB] = 1, 2, 3 + ~~~:SUPPLEMENTARY| +c[DimC] = a[DimA] + 1 + ~~~:SUPPLEMENTARY| + +******************************************************** + .Control +********************************************************~ + Simulation Control Parameters + | + +FINAL TIME = 1 ~~| +INITIAL TIME = 0 ~~| +SAVEPER = TIME STEP ~~| +TIME STEP = 1 ~~| + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1 +$0-0-0,0,|0||0-0-0|0-0-0|0-0-0|0-0-0|0-0-0|0,0,100,0 +///---\\\ +:L<%^E!@ +9:Current +15:0,0,0,0,0,0 +19:100,0 +27:2, +34:0, +5:FINAL TIME +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +42:1 +24:0 +25:0 +26:0 diff --git a/models/directsubs/directsubs_subs.txt b/models/directsubs/directsubs_subs.txt new file mode 100644 index 00000000..c420a758 --- /dev/null +++ b/models/directsubs/directsubs_subs.txt @@ -0,0 +1,63 @@ +_dima: +{ + modelName: 'DimA', + modelValue: [ 'A1', 'A2', 'A3' ], + modelMappings: [ { toDim: 'DimB', value: [] }, { toDim: 'DimC', value: [] } ], + name: '_dima', + value: [ '_a1', '_a2', '_a3' ], + size: 3, + family: '_dima', + mappings: { _dimb: [ '_a1', '_a2', '_a3' ], _dimc: [ '_a1', '_a2', '_a3' ] } +} + +_dimb: +{ + modelName: 'DimB', + modelValue: [ 'B1', 'B2', 'B3' ], + modelMappings: [], + name: '_dimb', + value: [ '_b1', '_b2', '_b3' ], + size: 3, + family: '_dimb', + mappings: {} +} + +_dimc: +{ + modelName: 'DimC', + modelValue: [ 'C1', 'C2', 'C3' ], + modelMappings: [], + name: '_dimc', + value: [ '_c1', '_c2', '_c3' ], + size: 3, + family: '_dimc', + mappings: {} +} + +_a1: +{ name: '_a1', value: 0, size: 1, family: '_dima', mappings: {} } + +_a2: +{ name: '_a2', value: 1, size: 1, family: '_dima', mappings: {} } + +_a3: +{ name: '_a3', value: 2, size: 1, family: '_dima', mappings: {} } + +_b1: +{ name: '_b1', value: 0, size: 1, family: '_dimb', mappings: {} } + +_b2: +{ name: '_b2', value: 1, size: 1, family: '_dimb', mappings: {} } + +_b3: +{ name: '_b3', value: 2, size: 1, family: '_dimb', mappings: {} } + +_c1: +{ name: '_c1', value: 0, size: 1, family: '_dimc', mappings: {} } + +_c2: +{ name: '_c2', value: 1, size: 1, family: '_dimc', mappings: {} } + +_c3: +{ name: '_c3', value: 2, size: 1, family: '_dimc', mappings: {} } + diff --git a/models/directsubs/directsubs_vars.txt b/models/directsubs/directsubs_vars.txt new file mode 100644 index 00000000..cb77bbcf --- /dev/null +++ b/models/directsubs/directsubs_vars.txt @@ -0,0 +1,82 @@ +a[DimA]: const (non-apply-to-all) += 10,20,30 +refId(_a[_a1]) +families(_dima) +subscripts(_a1) +separationDims(_dima) +hasInitValue(false) + +a[DimA]: const (non-apply-to-all) += 10,20,30 +refId(_a[_a2]) +families(_dima) +subscripts(_a2) +separationDims(_dima) +hasInitValue(false) + +a[DimA]: const (non-apply-to-all) += 10,20,30 +refId(_a[_a3]) +families(_dima) +subscripts(_a3) +separationDims(_dima) +hasInitValue(false) + +b[DimB]: const (non-apply-to-all) += 1,2,3 +refId(_b[_b1]) +families(_dimb) +subscripts(_b1) +separationDims(_dimb) +hasInitValue(false) + +b[DimB]: const (non-apply-to-all) += 1,2,3 +refId(_b[_b2]) +families(_dimb) +subscripts(_b2) +separationDims(_dimb) +hasInitValue(false) + +b[DimB]: const (non-apply-to-all) += 1,2,3 +refId(_b[_b3]) +families(_dimb) +subscripts(_b3) +separationDims(_dimb) +hasInitValue(false) + +c[DimC]: aux += a[DimA]+1 +refId(_c) +families(_dimc) +subscripts(_dimc) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3]) + +FINAL TIME: const += 1 +refId(_final_time) +hasInitValue(false) + +INITIAL TIME: const += 0 +refId(_initial_time) +hasInitValue(false) + +SAVEPER: aux += TIME STEP +refId(_saveper) +hasInitValue(false) +refs(_time_step) + +Time: const += +refId(_time) +hasInitValue(false) + +TIME STEP: const += 1 +refId(_time_step) +hasInitValue(false) + diff --git a/src/Helpers.js b/src/Helpers.js index 8a023dab..ca229fdd 100644 --- a/src/Helpers.js +++ b/src/Helpers.js @@ -6,6 +6,7 @@ import sh from 'shelljs' import split from 'split-string' import byline from 'byline' import XLSX from 'xlsx' +import parseCsv from 'csv-parse/lib/sync.js' import B from 'bufx' // Set true to print a stack trace in vlog @@ -237,7 +238,7 @@ export let modelPathProps = model => { return { modelDirname: p.dir, modelName: p.name, - modelPathname: path.format(p), + modelPathname: path.format(p) } } export let execCmd = cmd => { @@ -293,7 +294,9 @@ export let readDat = async (pathname, prefix = '') => { if (Number.isNaN(t)) { console.error(`DAT file ${pathname}:${lineNum} time value is NaN`) } else if (Number.isNaN(value)) { - console.error(`DAT file ${pathname}:${lineNum} var "${varName}" value is NaN at time=${t}`) + console.error( + `DAT file ${pathname}:${lineNum} var "${varName}" value is NaN at time=${t}` + ) } else { varValues.set(t, value) } @@ -310,6 +313,26 @@ export let readDat = async (pathname, prefix = '') => { export let readXlsx = pathname => { return XLSX.readFile(pathname, { cellDates: true }) } +export let readCsv = (pathname, delimiter = ',') => { + // Read the CSV file at the pathname and parse it with the given delimiter. + // Return an array of rows that are each an array of columns. + // If there is a header row, it is returned as the first row. + let result = null + const CSV_PARSE_OPTS = { + delimiter, + columns: false, + trim: true, + skip_empty_lines: true, + skip_lines_with_empty_values: true + } + try { + let data = B.read(pathname) + result = parseCsv(data, CSV_PARSE_OPTS) + } catch (error) { + console.error(`ERROR: CSV file ${pathname} not found`) + } + return result +} // Convert the var name and subscript names to canonical form separately. export let canonicalVensimName = vname => { let result = vname @@ -334,7 +357,9 @@ export let mapIndexed = R.addIndex(R.map) // Function to sort an array of strings export let asort = R.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)) // Function to alpha sort an array of variables on the model LHS -export let vsort = R.sort((a, b) => (a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0)) +export let vsort = R.sort((a, b) => + a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0 +) // Function to list an array to stderr export let printArray = R.forEach(x => console.error(x)) // Function to expand an array of strings into a comma-delimited list of strings @@ -407,13 +432,14 @@ export let replaceDelimitedStrings = (str, open, close, newStr) => { * This can be used in place of nested for loops and has the benefit of working * for multi-dimensional inputs. */ - export const cartesianProductOf = arr => { +export const cartesianProductOf = arr => { // Implementation based on: https://stackoverflow.com/a/36234242 - return arr.reduce((a, b) => { - return a - .map(x => b.map(y => x.concat([y]))) - .reduce((v, w) => v.concat(w), []) - }, [[]]) + return arr.reduce( + (a, b) => { + return a.map(x => b.map(y => x.concat([y]))).reduce((v, w) => v.concat(w), []) + }, + [[]] + ) } /** @@ -424,14 +450,14 @@ export let replaceDelimitedStrings = (str, open, close, newStr) => { * this function will return all the permutations, e.g.: * [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] */ - export const permutationsOf = (elems, subperms = [[]]) => { +export const permutationsOf = (elems, subperms = [[]]) => { // Implementation based on: https://gist.github.com/CrossEye/f7c2f77f7db7a94af209 - return R.isEmpty(elems) ? - subperms : - R.addIndex(R.chain)((elem, idx) => permutationsOf( - R.remove(idx, 1, elems), - R.map(R.append(elem), subperms) - ), elems) + return R.isEmpty(elems) + ? subperms + : R.addIndex(R.chain)( + (elem, idx) => permutationsOf(R.remove(idx, 1, elems), R.map(R.append(elem), subperms)), + elems + ) } // diff --git a/src/SubscriptRangeReader.js b/src/SubscriptRangeReader.js index ebe7e3f4..c48880aa 100644 --- a/src/SubscriptRangeReader.js +++ b/src/SubscriptRangeReader.js @@ -1,11 +1,17 @@ import { ModelParser } from 'antlr4-vensim' import R from 'ramda' +import XLSX from 'xlsx' import ModelReader from './ModelReader.js' import { Subscript } from './Subscript.js' +import { cFunctionName, matchRegex, readCsv } from './Helpers.js' export default class SubscriptRangeReader extends ModelReader { constructor() { super() + // Index names from a subscript list or GET DIRECT SUBSCRIPT + this.indNames = [] + // Dimension mappings with model names + this.modelMappings = [] } visitModel(ctx) { let subscriptRanges = ctx.subscriptRange() @@ -62,4 +68,42 @@ export default class SubscriptRangeReader extends ModelReader { } } } + visitCall(ctx) { + // A subscript range can have a GET DIRECT SUBSCRIPT call on the RHS. + let fn = cFunctionName(ctx.Id().getText()) + if (fn === '_GET_DIRECT_SUBSCRIPT') { + super.visitCall(ctx) + } + } + visitExprList(ctx) { + // We assume the only call that ends up here is GET DIRECT SUBSCRIPT. + let args = R.map( + arg => matchRegex(arg, /'(.*)'/), + R.map(expr => expr.getText(), ctx.expr()) + ) + let pathname = args[0] + let delimiter = args[1] + let firstCell = args[2] + let lastCell = args[3] + // let prefix = args[4] + // If lastCell is a column letter, scan the column, else scan the row. + let dataAddress = XLSX.utils.decode_cell(firstCell) + let col = dataAddress.c + let row = dataAddress.r + let nextCell + if (isNaN(parseInt(lastCell))) { + nextCell = () => row++ + } else { + nextCell = () => col++ + } + // Read subscript names from the CSV file at the given position. + let data = readCsv(pathname, delimiter) + let indexName = data[row][col] + while (indexName != null) { + this.indNames.push(indexName) + nextCell() + indexName = data[row] != null ? data[row][col] : null + } + super.visitExprList(ctx) + } } From 1dc68a98d8d97c8ac2a744f5ac3ed9b3139841f4 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 9 Jul 2021 23:25:25 -0700 Subject: [PATCH 06/11] feat: implement the <-> subscript alias operator (#78) --- models/subalias/subalias.dat | 21 +++++++++ models/subalias/subalias.mdl | 11 +++++ models/subalias/subalias_subs.txt | 33 ++++++++++++++ models/subalias/subalias_vars.txt | 74 +++++++++++++++++++++++++++++++ src/Model.js | 50 +++++++++++++++------ src/Subscript.js | 13 +++++- src/SubscriptRangeReader.js | 27 ++++++----- src/tests/modeltests | 6 ++- 8 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 models/subalias/subalias.dat create mode 100644 models/subalias/subalias.mdl create mode 100644 models/subalias/subalias_subs.txt create mode 100644 models/subalias/subalias_vars.txt diff --git a/models/subalias/subalias.dat b/models/subalias/subalias.dat new file mode 100644 index 00000000..344f9328 --- /dev/null +++ b/models/subalias/subalias.dat @@ -0,0 +1,21 @@ +e[F1] +0 10 +e[F2] +0 20 +e[F3] +0 30 +f[F1] +0 1 +f[F2] +0 2 +f[F3] +0 3 +FINAL TIME +0 1 +INITIAL TIME +0 0 +SAVEPER +0 1 +1 1 +TIME STEP +0 1 diff --git a/models/subalias/subalias.mdl b/models/subalias/subalias.mdl new file mode 100644 index 00000000..5d19e0b7 --- /dev/null +++ b/models/subalias/subalias.mdl @@ -0,0 +1,11 @@ +{UTF-8} +DimE <-> DimF ~~| +DimF: F1, F2, F3 ~~| + +e[DimE] = 10, 20, 30 ~~~:SUPPLEMENTARY| +f[DimF] = 1, 2, 3 ~~~:SUPPLEMENTARY| + +INITIAL TIME = 0 ~~| +FINAL TIME = 1 ~~| +TIME STEP = 1 ~~| +SAVEPER = TIME STEP ~~| diff --git a/models/subalias/subalias_subs.txt b/models/subalias/subalias_subs.txt new file mode 100644 index 00000000..08bcce13 --- /dev/null +++ b/models/subalias/subalias_subs.txt @@ -0,0 +1,33 @@ +_dime: +{ + modelName: 'DimE', + modelValue: [ 'F1', 'F2', 'F3' ], + modelMappings: [], + name: '_dime', + value: [ '_f1', '_f2', '_f3' ], + size: 3, + family: '_dime', + mappings: {} +} + +_dimf: +{ + modelName: 'DimF', + modelValue: [ 'F1', 'F2', 'F3' ], + modelMappings: [], + name: '_dimf', + value: [ '_f1', '_f2', '_f3' ], + size: 3, + family: '_dime', + mappings: {} +} + +_f1: +{ name: '_f1', value: 0, size: 1, family: '_dime', mappings: {} } + +_f2: +{ name: '_f2', value: 1, size: 1, family: '_dime', mappings: {} } + +_f3: +{ name: '_f3', value: 2, size: 1, family: '_dime', mappings: {} } + diff --git a/models/subalias/subalias_vars.txt b/models/subalias/subalias_vars.txt new file mode 100644 index 00000000..34d4ad02 --- /dev/null +++ b/models/subalias/subalias_vars.txt @@ -0,0 +1,74 @@ +e[DimE]: const (non-apply-to-all) += 10,20,30 +refId(_e[_f1]) +families(_dime) +subscripts(_f1) +separationDims(_dime) +hasInitValue(false) + +e[DimE]: const (non-apply-to-all) += 10,20,30 +refId(_e[_f2]) +families(_dime) +subscripts(_f2) +separationDims(_dime) +hasInitValue(false) + +e[DimE]: const (non-apply-to-all) += 10,20,30 +refId(_e[_f3]) +families(_dime) +subscripts(_f3) +separationDims(_dime) +hasInitValue(false) + +f[DimF]: const (non-apply-to-all) += 1,2,3 +refId(_f[_f1]) +families(_dime) +subscripts(_f1) +separationDims(_dimf) +hasInitValue(false) + +f[DimF]: const (non-apply-to-all) += 1,2,3 +refId(_f[_f2]) +families(_dime) +subscripts(_f2) +separationDims(_dimf) +hasInitValue(false) + +f[DimF]: const (non-apply-to-all) += 1,2,3 +refId(_f[_f3]) +families(_dime) +subscripts(_f3) +separationDims(_dimf) +hasInitValue(false) + +FINAL TIME: const += 1 +refId(_final_time) +hasInitValue(false) + +INITIAL TIME: const += 0 +refId(_initial_time) +hasInitValue(false) + +SAVEPER: aux += TIME STEP +refId(_saveper) +hasInitValue(false) +refs(_time_step) + +Time: const += +refId(_time) +hasInitValue(false) + +TIME STEP: const += 1 +refId(_time_step) +hasInitValue(false) + diff --git a/src/Model.js b/src/Model.js index 057d2d8b..e3b3a67f 100644 --- a/src/Model.js +++ b/src/Model.js @@ -11,6 +11,7 @@ import EquationReader from './EquationReader.js' import Variable from './Variable.js' import { addIndex, + allAliases, allDimensions, indexNamesForSubscript, isDimension, @@ -54,22 +55,34 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies) { let subscriptRangeReader = new SubscriptRangeReader() subscriptRangeReader.visitModel(tree) let allDims = allDimensions() - // Expand dimensions that appeared in subscript range definitions into indices. // Repeat until there are only indices in dimension values. let dimFoundInValue do { dimFoundInValue = false for (let dim of allDims) { - let value = R.flatten(R.map(subscript => (isDimension(subscript) ? sub(subscript).value : subscript), dim.value)) - if (!R.equals(value, dim.value)) { - dimFoundInValue = true - dim.value = value - dim.size = value.length + if (dim.value !== '') { + let value = R.flatten(R.map(subscript => (isDimension(subscript) ? sub(subscript).value : subscript), dim.value)) + if (!R.equals(value, dim.value)) { + dimFoundInValue = true + dim.value = value + dim.size = value.length + } } } } while (dimFoundInValue) + // Fill in subscript aliases from their model families. + for (let dim of allAliases()) { + if (dim.value === '') { + let refDim = sub(dim.family) + dim.value = refDim.value + dim.size = refDim.size + dim.modelValue = refDim.modelValue + allDims.push(dim) + } + } + // Update the families of dimensions. At this point, all dimensions have their family // provisionally set to their own dimension name. let dimComparator = (dim1, dim2) => { @@ -97,7 +110,10 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies) { // first in alpha sort order, by convention. // Take the first index in the dimension. let index = dim.value[0] - let familyDims = R.sort(dimComparator, R.filter(thisDim => R.contains(index, thisDim.value), allDims)) + let familyDims = R.sort( + dimComparator, + R.filter(thisDim => R.contains(index, thisDim.value), allDims) + ) if (familyDims.length > 0) { dim.family = R.last(familyDims).name } else { @@ -140,9 +156,7 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies) { invertedMappingValue[toIndNumber] = fromIndName } else { console.error( - `ERROR: map-to index "${toSubName}" not found when mapping from dimension "${ - fromDim.name - }" index "${fromIndName}"` + `ERROR: map-to index "${toSubName}" not found when mapping from dimension "${fromDim.name}" index "${fromIndName}"` ) } } @@ -467,7 +481,6 @@ function initVars() { return sortInitVars() } function varWithRefId(refId) { - const findVarWithRefId = rid => { // First see if we have a map key where ref id matches the var name let varsForName = variablesByName.get(rid) @@ -687,7 +700,10 @@ function sortVarsOfType(varType) { } // Turn the dependency-sorted var name list into a var list. - let sortedVars = varsOfType(varType, R.map(refId => varWithRefId(refId), deps)) + let sortedVars = varsOfType( + varType, + R.map(refId => varWithRefId(refId), deps) + ) // Add the ref ids to a set for faster lookup in the next step const sortedVarRefIds = new Set() @@ -788,7 +804,10 @@ function sortInitVars() { let sortedVars = R.map(refId => varWithRefId(refId), deps) // Filter out vars with constant values. - sortedVars = R.reject(R.propSatisfies(varType => varType === 'const' || varType === 'lookup', 'varType'), sortedVars) + sortedVars = R.reject( + R.propSatisfies(varType => varType === 'const' || varType === 'lookup', 'varType'), + sortedVars + ) // Add the ref ids to a set for faster lookup in the next step const sortedVarRefIds = new Set() @@ -834,7 +853,10 @@ function printVarList() { } function yamlVarList() { // Print selected properties of all variable objects to a YAML string. - let vars = R.sortBy(R.prop('refId'), R.map(v => filterVar(v), variables)) + let vars = R.sortBy( + R.prop('refId'), + R.map(v => filterVar(v), variables) + ) return yaml.safeDump(vars) } function printVar(v) { diff --git a/src/Subscript.js b/src/Subscript.js index 095b1688..08823726 100644 --- a/src/Subscript.js +++ b/src/Subscript.js @@ -36,6 +36,8 @@ import { canonicalName, asort, vlog } from './Helpers.js' // sets a subscript dimension and its indices // Subscript(modelName, modelValue, modelFamily) with modelValue as number // sets a subscript element and its index value in the subscript family +// Subscript(modelName, '', modelFamily, []) +// sets modelName as an alias for the dimension named as modelFamily // // Call Subscript with an array of subscript indices to establish a dimension. // If there is a mapping to another dimension, give modelMappings in the call. @@ -53,14 +55,19 @@ export function Subscript(modelName, modelValue = null, modelFamily = null, mode // Look up a subscript by its model name. return sub(name) } - // Map the subscript value array into canonical form. let value, size if (Array.isArray(modelValue)) { + // Map the subscript value array into canonical form. value = R.map(x => canonicalName(x), modelValue) size = value.length } else if (typeof modelValue === 'number') { + // The value is an index. value = modelValue size = 1 + } else if (modelValue === '') { + // An empty value string indicates a subscript alias given by the modelFamily. + value = '' + size = 0 } // Convert the model family into canonical form. if (modelFamily === null) { @@ -234,6 +241,10 @@ export function allDimensions() { // Return an array of all dimension subscript objects. return R.filter(subscript => Array.isArray(subscript.value), allSubscripts()) } +export function allAliases() { + // Return an array of all subscript aliases. + return R.filter(subscript => subscript.value === '', allSubscripts()) +} export function allMappings() { // Return an array of all subscript mappings as objects. let mappings = [] diff --git a/src/SubscriptRangeReader.js b/src/SubscriptRangeReader.js index c48880aa..8fc99d40 100644 --- a/src/SubscriptRangeReader.js +++ b/src/SubscriptRangeReader.js @@ -25,16 +25,23 @@ export default class SubscriptRangeReader extends ModelReader { // When entering a new subscript range definition, reset the properties that will be filled in. this.indNames = [] this.modelMappings = [] - // All subscript ranges have a dimension name. - let modelName = ctx.Id().getText() - // Visit children to fill in the subscript range definition. - super.visitSubscriptRange(ctx) - // Create a new subscript range definition from Vensim-format names. - // The family is provisionally set to the dimension name. - // It will be updated to the maximal dimension if this is a subdimension. - // The mapping value contains dimensions and indices in the toDim. - // It will be expanded and inverted to fromDim indices later. - Subscript(modelName, this.indNames, modelName, this.modelMappings) + // A subscript alias has two Ids, while a regular subscript range definition has just one. + if (ctx.Id().length === 1) { + // Subscript range definitions have a dimension name. + let modelName = ctx.Id()[0].getText() + // Visit children to fill in the subscript range definition. + super.visitSubscriptRange(ctx) + // Create a new subscript range definition from Vensim-format names. + // The family is provisionally set to the dimension name. + // It will be updated to the maximal dimension if this is a subdimension. + // The mapping value contains dimensions and indices in the toDim. + // It will be expanded and inverted to fromDim indices later. + Subscript(modelName, this.indNames, modelName, this.modelMappings) + } else { + let modelName = ctx.Id()[0].getText() + let modelFamily = ctx.Id()[1].getText() + Subscript(modelName, '', modelFamily, []) + } } visitSubscriptList(ctx) { // A subscript list can appear in either a subscript range or mapping. diff --git a/src/tests/modeltests b/src/tests/modeltests index 95abfbce..a5527cbd 100755 --- a/src/tests/modeltests +++ b/src/tests/modeltests @@ -2,8 +2,8 @@ set -e # fail on error -# Use the local sde command. -SDE='node ../sde.js' +# Use the local sde command relative to the model directory. +SDE='node ../../src/sde.js' $SDE -v &>/dev/null if [[ $? -ne 0 ]]; then echo 'SDEverywhere is not installed.' @@ -15,6 +15,7 @@ function test { MODEL=$1 echo "Testing the $MODEL model" MODEL_DIR=../../models/$MODEL + pushd $MODEL_DIR >/dev/null # Clean up before $SDE clean --modeldir $MODEL_DIR @@ -39,6 +40,7 @@ function test { # Clean up after $SDE clean --modeldir $MODEL_DIR + popd >/dev/null echo } From e47d9117d7ef0695a73e1f5a6f695a5eba0392b1 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Mon, 12 Jul 2021 12:53:26 -0700 Subject: [PATCH 07/11] rename "tag" to "file" and "sheetName" to "tab" for generality (#81) --- src/EquationGen.js | 10 +++++----- src/EquationReader.js | 8 ++++++-- src/sde-generate.js | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/EquationGen.js b/src/EquationGen.js index 52be92f5..975e548e 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -330,10 +330,10 @@ export default class EquationGen extends ModelReader { // If direct data exists for this variable, copy it from the workbook into one or more lookups. let result = [] if (this.mode === 'init-lookups') { - let { tag, sheetName, timeRowOrCol, startCell } = this.var.directDataArgs - let workbook = this.directData.get(tag) + let { file, tab, timeRowOrCol, startCell } = this.var.directDataArgs + let workbook = this.directData.get(file) if (workbook) { - let sheet = workbook.Sheets[sheetName] + let sheet = workbook.Sheets[tab] if (sheet) { let indexNum = 0 if (!R.isEmpty(this.var.subscripts)) { @@ -344,10 +344,10 @@ export default class EquationGen extends ModelReader { } result.push(this.generateDirectDataLookup(sheet, timeRowOrCol, startCell, indexNum)) } else { - throw new Error(`ERROR: Direct data worksheet ${sheetName} tagged ${tag} not found`) + throw new Error(`ERROR: Direct data worksheet ${tab} tagged ${file} not found`) } } else { - throw new Error(`ERROR: Direct data workbook tagged ${tag} not found`) + throw new Error(`ERROR: Direct data workbook tagged ${file} not found`) } } return result diff --git a/src/EquationReader.js b/src/EquationReader.js index 9c4cf204..b102e835 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -127,13 +127,17 @@ export default class EquationReader extends ModelReader { this.expandDelayFunction(fn, args) } else if (fn === '_GET_DIRECT_DATA') { // Extract string constant arguments into an object used in code generation. + // For Excel files, the file argument names an indirect "?" file tag from the model settings. + // For CSV files, it gives a relative pathname in the model directory. + // For Excel files, the tab argument names an Excel worksheet. + // For CSV files, it gives the delimiter character. let args = R.map( arg => matchRegex(arg, /'(.*)'/), R.map(expr => expr.getText(), ctx.expr()) ) this.var.directDataArgs = { - tag: args[0], - sheetName: args[1], + file: args[0], + tab: args[1], timeRowOrCol: args[2], startCell: args[3] } diff --git a/src/sde-generate.js b/src/sde-generate.js index be96aa73..9f6d8271 100644 --- a/src/sde-generate.js +++ b/src/sde-generate.js @@ -107,12 +107,12 @@ export let generate = async (model, opts) => { extData = new Map([...extData, ...data]) } } - // Attach Excel workbook data to directData entries by tag name. + // Attach Excel workbook data to directData entries by file name. let directData = new Map() if (spec.directData) { - for (let [tag, xlsxFilename] of Object.entries(spec.directData)) { + for (let [file, xlsxFilename] of Object.entries(spec.directData)) { let pathname = path.join(modelDirname, xlsxFilename) - directData.set(tag, readXlsx(pathname)) + directData.set(file, readXlsx(pathname)) } } // Produce a runnable model with the "genc" and "preprocess" options. From 77052e50aad61ee1750873191489de9ef9bae15b Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Mon, 12 Jul 2021 22:06:14 -0700 Subject: [PATCH 08/11] implement GET DIRECT DATA for CSV files (#81) --- models/directdata/directdata.dat | 212 +++++++++++++++++++++++++++++++ models/directdata/directdata.mdl | 14 +- models/directdata/e_data.csv | 6 + models/directdata/g_data.csv | 14 ++ src/EquationGen.js | 71 +++++++---- src/SubscriptRangeReader.js | 12 +- 6 files changed, 296 insertions(+), 33 deletions(-) create mode 100644 models/directdata/e_data.csv create mode 100644 models/directdata/g_data.csv diff --git a/models/directdata/directdata.dat b/models/directdata/directdata.dat index 119edbed..41d78ef5 100644 --- a/models/directdata/directdata.dat +++ b/models/directdata/directdata.dat @@ -184,8 +184,194 @@ d 2048 422 2049 446 2050 470 +f[A1] +1990 6100 +1991 6093.33 +1992 6086.67 +1993 6080 +1994 6073.33 +1995 6066.67 +1996 6060 +1997 6053.33 +1998 6046.67 +1999 6040 +2000 6033.33 +2001 6026.67 +2002 6020 +2003 6013.33 +2004 6006.67 +2005 6000 +2006 5990 +2007 5980 +2008 5970 +2009 5960 +2010 5950 +2011 5940 +2012 5930 +2013 5920 +2014 5910 +2015 5900 +2016 5902 +2017 5904 +2018 5906 +2019 5908 +2020 5910 +2021 5912 +2022 5914 +2023 5916 +2024 5918 +2025 5920 +2026 5922 +2027 5924 +2028 5926 +2029 5928 +2030 5930 +2031 5925 +2032 5920 +2033 5915 +2034 5910 +2035 5905 +2036 5900 +2037 5895 +2038 5890 +2039 5885 +2040 5880 +2041 5875 +2042 5870 +2043 5865 +2044 5860 +2045 5855 +2046 5850 +2047 5845 +2048 5840 +2049 5835 +2050 5830 +f[A2] +1990 2100 +1991 2093.33 +1992 2086.67 +1993 2080 +1994 2073.33 +1995 2066.67 +1996 2060 +1997 2053.33 +1998 2046.67 +1999 2040 +2000 2033.33 +2001 2026.67 +2002 2020 +2003 2013.33 +2004 2006.67 +2005 2000 +2006 1990 +2007 1980 +2008 1970 +2009 1960 +2010 1950 +2011 1940 +2012 1930 +2013 1920 +2014 1910 +2015 1900 +2016 1896.67 +2017 1893.33 +2018 1890 +2019 1886.67 +2020 1883.33 +2021 1880 +2022 1876.67 +2023 1873.33 +2024 1870 +2025 1866.67 +2026 1863.33 +2027 1860 +2028 1856.67 +2029 1853.33 +2030 1850 +2031 1847.5 +2032 1845 +2033 1842.5 +2034 1840 +2035 1837.5 +2036 1835 +2037 1832.5 +2038 1830 +2039 1827.5 +2040 1825 +2041 1822.5 +2042 1820 +2043 1817.5 +2044 1815 +2045 1812.5 +2046 1810 +2047 1807.5 +2048 1805 +2049 1802.5 +2050 1800 FINAL TIME 1990 2050 +h +1990 970 +1991 806 +1992 642 +1993 478 +1994 314 +1995 150 +1996 198 +1997 246 +1998 294 +1999 342 +2000 390 +2001 450 +2002 510 +2003 570 +2004 630 +2005 690 +2006 712 +2007 734 +2008 756 +2009 778 +2010 800 +2011 722 +2012 644 +2013 566 +2014 488 +2015 410 +2016 512 +2017 614 +2018 716 +2019 818 +2020 920 +2021 836 +2022 752 +2023 668 +2024 584 +2025 500 +2026 518 +2027 536 +2028 554 +2029 572 +2030 590 +2031 580 +2032 570 +2033 560 +2034 550 +2035 540 +2036 466 +2037 392 +2038 318 +2039 244 +2040 170 +2041 206 +2042 242 +2043 278 +2044 314 +2045 350 +2046 374 +2047 398 +2048 422 +2049 446 +2050 470 INITIAL TIME 1990 1990 SAVEPER @@ -278,3 +464,29 @@ c 2040 17 2045 35 2050 47 +e[A1] +1990 610 +2005 600 +2015 590 +2030 593 +2050 583 +e[A2] +1990 210 +2005 200 +2015 190 +2030 185 +2050 180 +g +1990 97 +1995 15 +2000 39 +2005 69 +2010 80 +2015 41 +2020 92 +2025 50 +2030 59 +2035 54 +2040 17 +2045 35 +2050 47 diff --git a/models/directdata/directdata.mdl b/models/directdata/directdata.mdl index ba822284..10afb071 100644 --- a/models/directdata/directdata.mdl +++ b/models/directdata/directdata.mdl @@ -1,9 +1,17 @@ {UTF-8} -a[DimA] := GET DIRECT DATA('?data', 'A Data', 'A', 'B2') ~~| DimA: A1, A2 ~~| -b[DimA] = a[DimA] * 10 ~~| + +a[DimA] := GET DIRECT DATA('?data', 'A Data', 'A', 'B2') ~~| +b[DimA] = a[DimA] * 10 ~~~:SUPPLEMENTARY| + c:= GET DIRECT DATA('?data', 'C Data', 'A', 'B2') ~~| -d = c * 10 ~~| +d = c * 10 ~~~:SUPPLEMENTARY| + +e[DimA] := GET DIRECT DATA('e_data.csv', ',', 'A', 'B2') ~~| +f[DimA] = e[DimA] * 10 ~~~:SUPPLEMENTARY| + +g:= GET DIRECT DATA('g_data.csv', ',', 'A', 'B2') ~~| +h = g * 10 ~~~:SUPPLEMENTARY| ******************************************************** .Control diff --git a/models/directdata/e_data.csv b/models/directdata/e_data.csv new file mode 100644 index 00000000..429ec617 --- /dev/null +++ b/models/directdata/e_data.csv @@ -0,0 +1,6 @@ +Year,A1,A2 +1990,610,210 +2005,600,200 +2015,590,190 +2030,593,185 +2050,583,180 \ No newline at end of file diff --git a/models/directdata/g_data.csv b/models/directdata/g_data.csv new file mode 100644 index 00000000..b8072144 --- /dev/null +++ b/models/directdata/g_data.csv @@ -0,0 +1,14 @@ +,g +1990,97 +1995,15 +2000,39 +2005,69 +2010,80 +2015,41 +2020,92 +2025,50 +2030,59 +2035,54 +2040,17 +2045,35 +2050,47 \ No newline at end of file diff --git a/src/EquationGen.js b/src/EquationGen.js index 975e548e..3f0b15cd 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -28,6 +28,7 @@ import { listConcat, newTmpVarName, permutationsOf, + readCsv, strToConst, vlog } from './Helpers.js' @@ -330,24 +331,41 @@ export default class EquationGen extends ModelReader { // If direct data exists for this variable, copy it from the workbook into one or more lookups. let result = [] if (this.mode === 'init-lookups') { + let getCellValue let { file, tab, timeRowOrCol, startCell } = this.var.directDataArgs - let workbook = this.directData.get(file) - if (workbook) { - let sheet = workbook.Sheets[tab] - if (sheet) { - let indexNum = 0 - if (!R.isEmpty(this.var.subscripts)) { - // Generate a lookup for a separated index in the variable's dimension. - // TODO allow the index to be in either position of a 2D subscript - let ind = sub(this.var.subscripts[0]) - indexNum = ind.value + if (file.startsWith('?')) { + // The file is a tag for an Excel file with data in the directData map. + let workbook = this.directData.get(file) + if (workbook) { + let sheet = workbook.Sheets[tab] + if (sheet) { + getCellValue = (c, r) => { + let cell = sheet[XLSX.utils.encode_cell({ c, r })] + return cell != null ? cdbl(cell.v) : null + } + } else { + throw new Error(`ERROR: Direct data worksheet ${tab} tagged ${file} not found`) } - result.push(this.generateDirectDataLookup(sheet, timeRowOrCol, startCell, indexNum)) } else { - throw new Error(`ERROR: Direct data worksheet ${tab} tagged ${file} not found`) + throw new Error(`ERROR: Direct data workbook tagged ${file} not found`) } } else { - throw new Error(`ERROR: Direct data workbook tagged ${file} not found`) + // The file is a CSV pathname. Read it now. + let data = readCsv(file, tab) + if (data) { + getCellValue = (c, r) => (data[r] != null ? cdbl(data[r][c]) : null) + } + } + // If the data was found, convert it to a lookup. + if (getCellValue) { + let indexNum = 0 + if (!R.isEmpty(this.var.subscripts)) { + // Generate a lookup for a separated index in the variable's dimension. + // TODO allow the index to be in either position of a 2D subscript + let ind = sub(this.var.subscripts[0]) + indexNum = ind.value + } + result.push(this.generateDirectDataLookup(getCellValue, timeRowOrCol, startCell, indexNum)) } } return result @@ -368,7 +386,11 @@ export default class EquationGen extends ModelReader { if (mode === 'decl') { // In decl mode, declare a static data array that will be used to create the associated `Lookup` // at init time. See `generateLookup` for more details. - const points = R.reduce((a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true), '', Array.from(data.entries())) + const points = R.reduce( + (a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true), + '', + Array.from(data.entries()) + ) return `double ${dataName}[${data.size * 2}] = { ${points} };` } else if (mode === 'init-lookups') { // In init mode, create the `Lookup`, passing in a pointer to the static data array declared in decl mode. @@ -444,13 +466,12 @@ export default class EquationGen extends ModelReader { } return result } - - generateDirectDataLookup(sheet, timeRowOrCol, startCell, indexNum) { + generateDirectDataLookup(getCellValue, timeRowOrCol, startCell, indexNum) { // Read a row or column of data as (time, value) pairs from the worksheet. - let dataCol, dataRow, dataCell, timeCol, timeRow, timeCell, nextCell + // The cell(c,r) function wraps data access by column and row. + let dataCol, dataRow, dataValue, timeCol, timeRow, timeValue, nextCell let lookupData = '' let lookupSize = 0 - let cell = (c, r) => sheet[XLSX.utils.encode_cell({ c, r })] let dataAddress = XLSX.utils.decode_cell(startCell) dataCol = dataAddress.c dataRow = dataAddress.r @@ -473,14 +494,14 @@ export default class EquationGen extends ModelReader { timeCol++ } } - timeCell = cell(timeCol, timeRow) - dataCell = cell(dataCol, dataRow) - while (timeCell && dataCell) { - lookupData = listConcat(lookupData, `${cdbl(timeCell.v)}, ${cdbl(dataCell.v)}`, true) + timeValue = getCellValue(timeCol, timeRow) + dataValue = getCellValue(dataCol, dataRow) + while (timeValue != null && dataValue != null) { + lookupData = listConcat(lookupData, `${timeValue}, ${dataValue}`, true) lookupSize++ nextCell() - dataCell = cell(dataCol, dataRow) - timeCell = cell(timeCol, timeRow) + dataValue = getCellValue(dataCol, dataRow) + timeValue = getCellValue(timeCol, timeRow) } return [` ${this.lhs} = __new_lookup(${lookupSize}, /*copy=*/true, (double[]){ ${lookupData} });`] } @@ -725,7 +746,7 @@ export default class EquationGen extends ModelReader { // (since Vensim indices are one-based). let s = this.rhsSubscriptGen([varName]) // Remove the brackets around the C subscript expression. - s = s.slice(1, s.length-1) + s = s.slice(1, s.length - 1) this.emit(`(${s} + 1)`) } } else { diff --git a/src/SubscriptRangeReader.js b/src/SubscriptRangeReader.js index 8fc99d40..a8eeefad 100644 --- a/src/SubscriptRangeReader.js +++ b/src/SubscriptRangeReader.js @@ -105,11 +105,13 @@ export default class SubscriptRangeReader extends ModelReader { } // Read subscript names from the CSV file at the given position. let data = readCsv(pathname, delimiter) - let indexName = data[row][col] - while (indexName != null) { - this.indNames.push(indexName) - nextCell() - indexName = data[row] != null ? data[row][col] : null + if (data) { + let indexName = data[row][col] + while (indexName != null) { + this.indNames.push(indexName) + nextCell() + indexName = data[row] != null ? data[row][col] : null + } } super.visitExprList(ctx) } From cf15d97db40c1accedec6a8acf148110c5b1469b Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 23 Jul 2021 16:50:15 -0700 Subject: [PATCH 09/11] build: revert to modeltests script from develop branch --- src/tests/modeltests | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tests/modeltests b/src/tests/modeltests index a5527cbd..95abfbce 100755 --- a/src/tests/modeltests +++ b/src/tests/modeltests @@ -2,8 +2,8 @@ set -e # fail on error -# Use the local sde command relative to the model directory. -SDE='node ../../src/sde.js' +# Use the local sde command. +SDE='node ../sde.js' $SDE -v &>/dev/null if [[ $? -ne 0 ]]; then echo 'SDEverywhere is not installed.' @@ -15,7 +15,6 @@ function test { MODEL=$1 echo "Testing the $MODEL model" MODEL_DIR=../../models/$MODEL - pushd $MODEL_DIR >/dev/null # Clean up before $SDE clean --modeldir $MODEL_DIR @@ -40,7 +39,6 @@ function test { # Clean up after $SDE clean --modeldir $MODEL_DIR - popd >/dev/null echo } From c05f2f905a444e4ff69cfeaa2e7a29d596fcccf3 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 23 Jul 2021 22:54:25 -0700 Subject: [PATCH 10/11] fix: read CSV file from a path relative to the model directory (#81) --- src/CodeGen.js | 2 +- src/EquationGen.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CodeGen.js b/src/CodeGen.js index 786b1fc4..a73ae0b5 100644 --- a/src/CodeGen.js +++ b/src/CodeGen.js @@ -19,7 +19,7 @@ export let codeGenerator = (parseTree, opts) => { outputAllVars = true } // Function to generate a section of the code - let generateSection = R.map(v => new EquationGen(v, extData, directData, mode).generate()) + let generateSection = R.map(v => new EquationGen(v, extData, directData, mode, modelDirname).generate()) let section = R.pipe(generateSection, R.flatten, lines) function generate() { // Read variables and subscript ranges from the model parse tree. diff --git a/src/EquationGen.js b/src/EquationGen.js index 3f0b15cd..d2974a67 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -1,3 +1,4 @@ +import path from 'path' import R from 'ramda' import XLSX from 'xlsx' import { ModelLexer, ModelParser } from 'antlr4-vensim' @@ -34,7 +35,7 @@ import { } from './Helpers.js' export default class EquationGen extends ModelReader { - constructor(variable, extData, directData, mode) { + constructor(variable, extData, directData, mode, modelDirname) { super() // the variable we are generating code for this.var = variable @@ -44,6 +45,8 @@ export default class EquationGen extends ModelReader { this.directData = directData // set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated this.mode = mode + // The model directory is required when reading data files for GET DIRECT DATA. + this.modelDirname = modelDirname // Maps of LHS subscript families to loop index vars for lookup on the RHS this.loopIndexVars = new LoopIndexVars(['i', 'j', 'k']) this.arrayIndexVars = new LoopIndexVars(['v', 'w']) @@ -351,7 +354,8 @@ export default class EquationGen extends ModelReader { } } else { // The file is a CSV pathname. Read it now. - let data = readCsv(file, tab) + let csvPathname = path.resolve(this.modelDirname, file) + let data = readCsv(csvPathname, tab) if (data) { getCellValue = (c, r) => (data[r] != null ? cdbl(data[r][c]) : null) } From 9f9918fea17d28abffec52513411dbbe39972273 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Mon, 26 Jul 2021 15:11:24 -0700 Subject: [PATCH 11/11] fix: allow CSV read exception to propagate instead of catching it (#93) --- src/Helpers.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Helpers.js b/src/Helpers.js index ca229fdd..af45b717 100644 --- a/src/Helpers.js +++ b/src/Helpers.js @@ -325,13 +325,8 @@ export let readCsv = (pathname, delimiter = ',') => { skip_empty_lines: true, skip_lines_with_empty_values: true } - try { - let data = B.read(pathname) - result = parseCsv(data, CSV_PARSE_OPTS) - } catch (error) { - console.error(`ERROR: CSV file ${pathname} not found`) - } - return result + let data = B.read(pathname) + return parseCsv(data, CSV_PARSE_OPTS) } // Convert the var name and subscript names to canonical form separately. export let canonicalVensimName = vname => {