diff --git a/packages/parse/src/ast/ast-builders.ts b/packages/parse/src/ast/ast-builders.ts index 6a072d48..c8de4b03 100644 --- a/packages/parse/src/ast/ast-builders.ts +++ b/packages/parse/src/ast/ast-builders.ts @@ -61,7 +61,8 @@ export function dimDef( familyName: DimName, dimOrSubNames: SubName[], subscriptMappings: SubscriptMapping[] = [], - comment = '' + comment = '', + group?: string ): DimensionDef { return { dimName, @@ -70,7 +71,8 @@ export function dimDef( familyId: canonicalName(familyName), subscriptRefs: dimOrSubNames.map(subRef), subscriptMappings, - comment + comment, + ...(group ? { group } : {}) } } @@ -176,7 +178,7 @@ export function varDef( } } -export function exprEqn(varDef: VariableDef, expr: Expr, units = '', comment = ''): Equation { +export function exprEqn(varDef: VariableDef, expr: Expr, units = '', comment = '', group?: string): Equation { return { lhs: { varDef @@ -186,7 +188,8 @@ export function exprEqn(varDef: VariableDef, expr: Expr, units = '', comment = ' expr }, units, - comment + comment, + ...(group ? { group } : {}) } } diff --git a/packages/parse/src/ast/ast-types.ts b/packages/parse/src/ast/ast-types.ts index e3815f5b..87332d90 100644 --- a/packages/parse/src/ast/ast-types.ts +++ b/packages/parse/src/ast/ast-types.ts @@ -114,6 +114,10 @@ export interface DimensionDef { * The optional comment text that accompanies the dimension definition in the model. */ comment?: string + /** + * The optional group name, if this dimension definition is contained within a group. + */ + group?: string } // @@ -375,6 +379,10 @@ export interface Equation { * The optional comment text that accompanies the equation definition in the model. */ comment?: string + /** + * The optional group name, if this equation definition is contained within a group. + */ + group?: string } // diff --git a/packages/parse/src/vensim/parse-vensim-model.spec.ts b/packages/parse/src/vensim/parse-vensim-model.spec.ts index 598cb5b3..b5b91011 100644 --- a/packages/parse/src/vensim/parse-vensim-model.spec.ts +++ b/packages/parse/src/vensim/parse-vensim-model.spec.ts @@ -115,4 +115,45 @@ x = 1 ) ) }) + + it('should preserve group names', () => { + const mdl = `\ +{UTF-8} + +DimA: A1, A2 ~~| +x = 1 ~~| + +******************************************************** + .Group name 1 +********************************************************~ + Group comment here. + | + +y[DimA] = 1 ~~| + +******************************************************** + .Group name 2 +********************************************************~ + Group comment here. + | + +DimB: B1, B2 ~~| + +z[DimB] = 1 ~~| + +\\\\\\---/// Sketch information - do not modify anything except names +V301 Do not put anything below this section - it will be ignored +*XYZ +` + expect(parseVensimModel(mdl)).toEqual( + model( + [dimDef('DimA', 'DimA', ['A1', 'A2']), dimDef('DimB', 'DimB', ['B1', 'B2'], [], '', 'Group name 2')], + [ + exprEqn(varDef('x'), num(1)), + exprEqn(varDef('y', ['DimA']), num(1), '', '', 'Group name 1'), + exprEqn(varDef('z', ['DimB']), num(1), '', '', 'Group name 2') + ] + ) + ) + }) }) diff --git a/packages/parse/src/vensim/parse-vensim-model.ts b/packages/parse/src/vensim/parse-vensim-model.ts index ad4a9c41..3e3600e6 100644 --- a/packages/parse/src/vensim/parse-vensim-model.ts +++ b/packages/parse/src/vensim/parse-vensim-model.ts @@ -55,19 +55,23 @@ export function parseVensimModel(input: string, context?: VensimParseContext, so } for (const dimensionDef of parsedModel.dimensions) { - // Fold in the comment string that was extracted during preprocessing + // Fold in the other strings that were extracted during preprocessing + const group = def.group dimensions.push({ ...dimensionDef, - comment: def.comment + comment: def.comment, + ...(group ? { group } : {}) }) } for (const equation of parsedModel.equations) { - // Fold in the units and comment strings that were extracted during preprocessing + // Fold in the other strings that were extracted during preprocessing + const group = def.group equations.push({ ...equation, units: def.units, - comment: def.comment + comment: def.comment, + ...(group ? { group } : {}) }) } } diff --git a/packages/parse/src/vensim/preprocess-vensim.spec.ts b/packages/parse/src/vensim/preprocess-vensim.spec.ts index 1ddd8ed7..a271addc 100644 --- a/packages/parse/src/vensim/preprocess-vensim.spec.ts +++ b/packages/parse/src/vensim/preprocess-vensim.spec.ts @@ -79,7 +79,8 @@ $192-192-192,0,Arial|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,5,0 def: 'W[A,B] :EXCEPT: [A1,B1] = 1 ~~|', line: 23, units: '', - comment: '' + comment: '', + group: 'Group name' } ]) }) diff --git a/packages/parse/src/vensim/preprocess-vensim.ts b/packages/parse/src/vensim/preprocess-vensim.ts index b808077c..98791fbe 100644 --- a/packages/parse/src/vensim/preprocess-vensim.ts +++ b/packages/parse/src/vensim/preprocess-vensim.ts @@ -30,6 +30,10 @@ export interface VensimDef { * The comment text. */ comment: string + /** + * The optional group name, if the definition is contained within a group. + */ + group?: string } /** @@ -46,6 +50,11 @@ interface RawDef { * The (1-based) line number where the definition begins. */ line: number + /** + * The group in which the definition is contained (can be undefined if + * the definition is not inside a group). + */ + group?: string } /** @@ -96,6 +105,7 @@ function splitDefs(input: string): RawDef[] { // number of line breaks in that definition const rawDefs = [] let lineNum = 1 + let currentGroup: string for (let defText of defTexts) { if (lineNum === 1) { // Strip the encoding (included in first def) @@ -115,11 +125,24 @@ function splitDefs(input: string): RawDef[] { const leadingLineBreaks = parts[1]?.match(/\r\n|\n|\r/gm) lineNum += leadingLineBreaks?.length || 0 - // Add the def (if it's not a group) - if (!defText.includes('********************************************************')) { + // See if this is a group header + if (defText.includes('********************************************************')) { + // This is a group; save the group name + const groupLines = splitLines(defText).filter(s => s.trim().length > 0) + currentGroup = undefined + if (groupLines.length > 1) { + const groupNameLine = groupLines[1] + const groupNameParts = groupNameLine.match(/^\s*\.(.*)$/) + if (groupNameParts) { + currentGroup = groupNameParts[1] + } + } + } else { + // This is a regular definition; add it rawDefs.push({ text: defText, - line: lineNum + line: lineNum, + group: currentGroup }) } @@ -304,11 +327,15 @@ function processDef(rawDef: RawDef): VensimDef | undefined { // TODO: Extract the `:SUPPLEMENTARY:` flags? + // Preserve the group name, if defined + const group = rawDef.group + return { key, def, line: rawDef.line, units, - comment + comment, + ...(group ? { group } : {}) } }