-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: remove duplicate implementations of canonical[Var]Name functions (…
- Loading branch information
1 parent
c04e0ca
commit e215d7d
Showing
24 changed files
with
275 additions
and
280 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) 2023 Climate Interactive / New Venture Fund | ||
|
||
// Detect '!' at the end of a marked dimension when preceded by whitespace | ||
const reTrailingMark = new RegExp('\\s+!$', 'g') | ||
|
||
// Detect one or more consecutive whitespace or underscore characters | ||
const reWhitespace = new RegExp('(\\s|_)+', 'g') | ||
|
||
// Detect special punctuation characters | ||
// TODO: We do not currently include '!' characters in this set; we should only replace these | ||
// when they don't appear at the end of a (marked) dimension | ||
const reSpecialChars = new RegExp(`['"\\.,\\-\\$&%\\/\\|]`, 'g') | ||
|
||
/** | ||
* Format a model variable or subscript/dimension name into a valid C identifier (with | ||
* special characters converted to underscore). | ||
* | ||
* Note that this should only be called with an individual variable base name (e.g., | ||
* 'Variable name') or a subscript/dimension name (e.g., 'DimA'). In the case where | ||
* you have a full variable name that includes subscripts/dimensions (e.g., | ||
* 'Variable name[DimA,B2]'), use `canonicalVarId` to convert the base variable name | ||
* and subscript/dimension parts to canonical form indepdendently. | ||
* | ||
* @param {string} name The name of the variable in the source model, e.g., "Variable name". | ||
* @returns {string} The C identifier for the given name, e.g., "_variable_name". | ||
*/ | ||
export function canonicalId(name) { | ||
return ( | ||
'_' + | ||
name | ||
// Ignore any leading or trailing whitespace | ||
.trim() | ||
// When a '!' character appears at the end of a marked dimension, preserve the mark | ||
// but remove any preceding whitespace | ||
.replace(reTrailingMark, '!') | ||
// Replace one or more consecutive whitespace or underscore characters with a single | ||
// underscore character; this matches the behavior of Vensim documented here: | ||
// https://www.vensim.com/documentation/ref_variable_names.html | ||
.replace(reWhitespace, '_') | ||
// Replace each special punctuation character with an underscore | ||
.replace(reSpecialChars, '_') | ||
// Convert to lower case | ||
.toLowerCase() | ||
) | ||
} | ||
|
||
/** | ||
* Format a (subscripted or non-subscripted) model variable name into a canonical identifier, | ||
* (with special characters converted to underscore, and subscript/dimension parts separated | ||
* by commas). | ||
* | ||
* @param {string} name The name of the variable in the source model, e.g., "Variable name[DimA, B2]". | ||
* @returns {string} The canonical identifier for the given name, e.g., "_variable_name[_dima,_b2]". | ||
*/ | ||
export function canonicalVarId(name) { | ||
const m = name.match(/([^[]+)(?:\[([^\]]+)\])?/) | ||
if (!m) { | ||
throw new Error(`Invalid variable name: ${name}`) | ||
} | ||
|
||
let id = canonicalId(m[1]) | ||
if (m[2]) { | ||
const subscripts = m[2].split(',').map(x => canonicalId(x)) | ||
id += `[${subscripts.join(',')}]` | ||
} | ||
|
||
return id | ||
} | ||
|
||
/** | ||
* Format a model function name into a valid C identifier (with special characters | ||
* converted to underscore, and the ID converted to uppercase). | ||
* | ||
* @param {string} name The name of the variable in the source model, e.g., "FUNCTION name". | ||
* @returns {string} The C identifier for the given name, e.g., "_FUNCTION_NAME". | ||
*/ | ||
export function canonicalFunctionId(name) { | ||
return canonicalId(name).toUpperCase() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright (c) 2024 Climate Interactive / New Venture Fund | ||
|
||
import { describe, expect, it } from 'vitest' | ||
|
||
import { canonicalFunctionId, canonicalId, canonicalVarId } from './canonical-id' | ||
|
||
describe('canonicalId', () => { | ||
it('should collapse multiple consecutive whitespace or underscore characters to a single underscore', () => { | ||
// The following examples are taken from the Vensim documentation under "Rules for Variable Names": | ||
// https://www.vensim.com/documentation/ref_variable_names.html | ||
expect(canonicalId('Hello There')).toBe('_hello_there') | ||
expect(canonicalId('Hello_There')).toBe('_hello_there') | ||
expect(canonicalId('Hello __ ___ There')).toBe('_hello_there') | ||
}) | ||
|
||
it('should replace each special character with a single underscore', () => { | ||
let input = '"Special' | ||
let expected = '__special' | ||
function add(name: string, char: string) { | ||
input += ` ${name}${char}` | ||
expected += `_${name}_` | ||
} | ||
add('period', '.') | ||
add('comma', ',') | ||
add('dash', '-') | ||
add('dollar', '$') | ||
add('amp', '&') | ||
add('pct', '%') | ||
add('slash', '/') | ||
// TODO: Handle backslashes | ||
// add('bslash', '\\') | ||
// TODO: Handle parentheses | ||
// add('lparen', '(') | ||
// add('rparen', ')') | ||
input += ' characters"' | ||
expected += '_characters_' | ||
expect(canonicalId(input)).toBe(expected) | ||
|
||
// The following examples are taken from the Vensim documentation under "Rules for Variable Names": | ||
// https://www.vensim.com/documentation/ref_variable_names.html | ||
expect(canonicalId('"HiRes TV/Web Sets"')).toBe('__hires_tv_web_sets_') | ||
// TODO: Handle backslashes | ||
// expect(canonicalId('"The \\"Final\\" Frontier"')).toBe('') | ||
expect(canonicalId("érosion d'action")).toBe('_érosion_d_action') | ||
}) | ||
|
||
it('should preserve mark when preceded by whitespace', () => { | ||
expect(canonicalVarId(`DimA !`)).toBe('_dima!') | ||
}) | ||
|
||
it('should preserve mark when split over multiple lines', () => { | ||
const name = `DimA | ||
! | ||
` | ||
expect(canonicalVarId(name)).toBe('_dima!') | ||
}) | ||
}) | ||
|
||
describe('canonicalVarId', () => { | ||
it('should work for non-subscripted variable', () => { | ||
expect(canonicalVarId('Hello There')).toBe('_hello_there') | ||
}) | ||
|
||
it('should work for variable with 1 subscript', () => { | ||
expect(canonicalVarId('Variable name[A1]')).toBe('_variable_name[_a1]') | ||
}) | ||
|
||
it('should work for variable with 2 subscripts', () => { | ||
expect(canonicalVarId('Variable name[A1, DimB]')).toBe('_variable_name[_a1,_dimb]') | ||
}) | ||
|
||
it('should work for variable with 3 subscripts', () => { | ||
expect(canonicalVarId('Variable name[A1, DimB,C2]')).toBe('_variable_name[_a1,_dimb,_c2]') | ||
}) | ||
}) | ||
|
||
describe('canonicalFunctionId', () => { | ||
it('should work for uppercase function name', () => { | ||
expect(canonicalFunctionId('FUNCTION NAME')).toBe('_FUNCTION_NAME') | ||
}) | ||
|
||
it('should work for mixed case function name', () => { | ||
expect(canonicalFunctionId('function name')).toBe('_FUNCTION_NAME') | ||
}) | ||
}) |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.