Skip to content
3,933 changes: 1,975 additions & 1,958 deletions CXF-Core.jsonld

Large diffs are not rendered by default.

50 changes: 42 additions & 8 deletions lib/cxfExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,38 @@ function parsePropagateAnnotations (propagateObjects, instanceNode, graph, s231N
}
}

/**
* Node to use as the object of `S231:isOfDataType` for a declared type.
*
* The four Modelica built-ins map to the S231 data types of the same name. An
* enumeration maps to the node of its enumeration class, which lives in the
* S231 namespace for CDL types and in the example namespace otherwise — the
* same prefix rule the `enumeration_class` branch applies when it emits that
* class. Any other type (e.g. a `Modelica.Units.SI` alias) has no S231 data
* type and yields null, leaving the triple unwritten.
*
* @param typeSpecifier type name as written in the declaration
* @param instanceDict the component's entry in the objects JSON
* @param s231Ns S231 namespace function
* @param cxfPrefix example namespace function
* @returns {Object|null} the node to point `S231:isOfDataType` at, or null
*/
function getDataTypeNode (typeSpecifier, instanceDict, s231Ns, cxfPrefix) {
if (typeSpecifier === undefined || typeSpecifier === null) {
return null
}
if (['Real', 'Integer', 'Boolean', 'String'].includes(typeSpecifier)) {
return s231Ns(typeSpecifier)
}
const enumerationType = ut.resolveEnumerationType(typeSpecifier, instanceDict.within, instanceDict.fullMoFilePath)
if (enumerationType === null) {
return null
}
return ut.checkIfCdlElementaryBlockOrPackage(enumerationType.within, false)
? s231Ns(enumerationType.name)
: cxfPrefix(enumerationType.name)
}

function getCxfGraph (instances, requiredReferences, blockName, generateElementary, generateCxfCore) {
let instancesList = []

Expand Down Expand Up @@ -263,9 +295,8 @@ function getCxfGraph (instances, requiredReferences, blockName, generateElementa
}

if ((isElementaryBlock && (generateElementary || generateCxfCore)) || (!isElementaryBlock)) {
graph.add(instanceNode, rdfNs('type'), s231Ns('EnumerationType'))
graph.add(instanceNode, rdfNs('type'), s231Ns('EnumerationDatatype'))
graph.add(instanceNode, s231Ns('label'), instance)
graph.add(instanceNode, s231Ns('value'), instance)
if ('description' in instanceDict) {
if ('description_string' in instanceDict.description) {
const descriptionString = instanceDict.description.description_string
Expand Down Expand Up @@ -350,18 +381,21 @@ function getCxfGraph (instances, requiredReferences, blockName, generateElementa
graph.add(instanceNode, s231Ns('label'), instance)
}

// An enumeration-typed parameter records its type here like any other;
// without it the type survives only when a fully-qualified default
// literal happens to be present in S231:value, and not at all when the
// declaration has no default.
const dataTypeNode = getDataTypeNode(typeSpecifier, instanceDict, s231Ns, cxfPrefix)
if (instanceDict.type_prefix === 'parameter') {
graph.add(blockNode, s231Ns('hasParameter'), instanceNode)
// TODO: check if enumeration should be here
if (typeSpecifier !== undefined && ['Real', 'Integer', 'Boolean', 'String'].includes(typeSpecifier)) {
graph.add(instanceNode, s231Ns('isOfDataType'), s231Ns(typeSpecifier))
if (dataTypeNode !== null) {
graph.add(instanceNode, s231Ns('isOfDataType'), dataTypeNode)
graph.add(instanceNode, rdfNs('type'), s231Ns('Parameter'))
}
} else if (instanceDict.type_prefix === 'constant') {
graph.add(blockNode, s231Ns('hasConstant'), instanceNode)
// TODO: check if enumeration should be here
if (typeSpecifier !== undefined && ['Real', 'Integer', 'Boolean', 'String'].includes(typeSpecifier)) {
graph.add(instanceNode, s231Ns('isOfDataType'), s231Ns(typeSpecifier))
if (dataTypeNode !== null) {
graph.add(instanceNode, s231Ns('isOfDataType'), dataTypeNode)
}
graph.add(instanceNode, rdfNs('type'), s231Ns('Constant'))
} else if (instanceDict.type_prefix === '' || instanceDict.type_prefix === undefined) {
Expand Down
12 changes: 6 additions & 6 deletions lib/s231ClassesProperties.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ S231:DataType
S231:Boolean a S231:BooleanDataType,
S231:DataType
.
S231:Real a S231:Datatype,
S231:Real a S231:DataType,
S231:RealDatatype
.
S231:Integer a S231:DataType,
Expand All @@ -489,15 +489,15 @@ S231:Integer a S231:DataType,
S231:Analog a S231:AnalogDataType,
S231:DataType
.
S231:String a S231:Datatype,
S231:String a S231:DataType,
S231:StringDatatype ;
rdfs:comment "A data type to represent text";
.
S231:BooleanDataType a rdfs:Class ;
rdfs:subClassOf S231:DataType
.
S231:RealDatatype a rdfs:Class ;
rdfs:subClassOf S231:Datatype
rdfs:subClassOf S231:DataType
.
S231:IntegerDataType a rdfs:Class ;
rdfs:subClassOf S231:DataType
Expand All @@ -506,13 +506,13 @@ S231:AnalogDataType a rdfs:Class ;
rdfs:subClassOf S231:DataType
.
S231:StringDatatype a rdfs:Class ;
rdfs:subClassOf S231:Datatype
rdfs:subClassOf S231:DataType
.
S231:EnumerationType a S231:Datatype,
S231:EnumerationType a S231:DataType,
S231:EnumerationDatatype
.
S231:EnumerationDatatype a rdfs:Class ;
rdfs:subClassOf S231:Datatype
rdfs:subClassOf S231:DataType
.
S231:AnalogInput a rdfs:Class ;
rdfs:comment "An input connector for real data type";
Expand Down
149 changes: 149 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,154 @@ function resolveTypeSpecifier (typeSpecifier, within, sourceMoFile) {
return resolved
}

// Cache enumeration type resolutions to avoid repeated file lookups.
const enumerationTypeCache = {}

/**
* Blank out string literals and comments, preserving offsets and line breaks.
*
* A Modelica class carries its documentation in `info="<html>…</html>"`
* strings, and reference material quotes declaration syntax verbatim — e.g.
* `ModelicaReference/package.mo` contains `type E = enumeration(...)` inside an
* info string. Matching a declaration against raw source would treat that as
* real.
*
* @param content Modelica source
* @returns {string} the source with literals and comments replaced by spaces
*/
function blankLiteralsAndComments (content) {
return content.replace(/"(?:[^"\\]|\\[\s\S])*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//g,
match => match.replace(/[^\n]/g, ' '))
}

/**
* Check whether Modelica source declares `className` as an enumeration type.
*
* @param content Modelica source, with literals and comments already blanked
* @param className name of the class to look for
* @returns {boolean} true if it declares `type <className> = enumeration(`
*/
function isEnumerationDeclaration (content, className) {
if (!/^[A-Za-z_]\w*$/.test(className)) {
return false
}
return new RegExp(`(^|\\n)\\s*type\\s+${className}\\s*=\\s*enumeration\\s*\\(`).test(content)
}

/**
* First file in `moFiles` that declares the enumeration `typeSpecifier` names,
* as its qualified name and within scope.
*
* A candidate is accepted only if it declares an enumeration of the right name
* *and* the qualified name rebuilt from it ends with the specifier as written.
* The rebuild assumes the enumeration sits directly in the class the file
* declares, which does not hold for a monolithic package.mo holding nested
* packages: `Modelica.Media.Interfaces.Choices.ReferenceEnthalpy` resolves to
* `Modelica/Media/package.mo` and would come back as the wrong name
* `Modelica.Media.ReferenceEnthalpy`. Rejecting those keeps a wrong IRI out of
* the output, and lets the caller try a different lookup instead.
*
* @param moFiles candidate .mo paths, best first
* @param typeSpecifier type name as written in the declaration
* @returns {{name: string, within: string}|null}
*/
function firstEnumerationType (moFiles, typeSpecifier) {
if (moFiles === undefined || moFiles === null) {
return null
}
const enumerationName = typeSpecifier.split('.').slice(-1)[0]
for (let i = 0; i < moFiles.length; i++) {
const moFile = moFiles[i]
const classWithin = readWithin(moFile)
if (classWithin === null || classWithin.length === 0) {
continue
}
let content = null
try {
content = blankLiteralsAndComments(fs.readFileSync(moFile, 'utf-8'))
} catch (e) {
continue
}
if (!isEnumerationDeclaration(content, enumerationName)) {
continue
}
// The class the file itself declares: its name for a single-class file,
// the enclosing directory for a package.mo.
const fileClass = path.basename(moFile) === 'package.mo'
? path.basename(path.dirname(moFile))
: path.basename(moFile, '.mo')
const name = fileClass === enumerationName
? `${classWithin}.${enumerationName}`
: `${classWithin}.${fileClass}.${enumerationName}`
if (name === typeSpecifier || name.endsWith(`.${typeSpecifier}`)) {
return { name, within: name.slice(0, name.lastIndexOf('.')) }
}
}
return null
}

/**
* Resolve the type specifier of a component declaration to the enumeration
* class it names, if it names one.
*
* A declaration carries its type exactly as the source writes it, which may be
* fully qualified (`Buildings.Controls.OBC.CDL.Types.SimpleController`),
* partially qualified (`CDL.Types.SimpleController`) or relative to an
* enclosing package (`Types.VentilationStandard`). A CXF node is keyed by the
* fully-qualified name, so the declaration alone does not identify the type.
* The class file located through `searchPath` answers both questions: whether
* the type is an enumeration, and — via its own `within` clause — its qualified
* name.
*
* `resolveTypeSpecifier` cannot be used here because it returns early for any
* name containing a '.', so it never resolves the partially-qualified forms.
*
* The located file may declare the enumeration as its own class, or as one of
* several classes inside a package (`Types.mo`, `package.mo`), so the
* enumeration is looked up by the last segment of the type specifier rather
* than by the file name.
*
* @param typeSpecifier type name as written in the declaration
* @param within within scope of the declaring file
* @param sourceMoFile absolute path of the declaring .mo file
* @returns {{name: string, within: string}|null} the enumeration's qualified
* name and within scope, or null if the type is not an enumeration or
* cannot be located
*/
function resolveEnumerationType (typeSpecifier, within, sourceMoFile) {
if (typeSpecifier === undefined || typeSpecifier === null || typeSpecifier.length === 0) {
return null
}
// MODELICAPATH is part of the key: a caller may parse successive models
// against different library sets in one process, and a lookup that failed
// under one set must not be reused under another.
const cacheKey = `${process.env.MODELICAPATH}|${within}|${typeSpecifier}`
if (cacheKey in enumerationTypeCache) {
return enumerationTypeCache[cacheKey]
}
// searchPath walks down from each MODELICAPATH entry, so it identifies the
// class only when MODELICAPATH holds the directory *containing* the library.
// Pointed at the library directory itself (`…/modelica-buildings/Buildings`)
// it finds nothing for a qualified name, and for a name written relative to
// the enclosing package it can find the wrong file — `Types.VentilationStandard`
// lands on `Buildings/Types/package.mo`, an unrelated package that happens to
// sit one level below. getMoFiles matches by path suffix instead and resolves
// both. Try it whenever searchPath yields no file that checks out, not only
// when it yields nothing at all.
let resolved = firstEnumerationType(searchPath([typeSpecifier], within, sourceMoFile), typeSpecifier)
if (resolved === null) {
try {
resolved = firstEnumerationType(
getMoFiles(typeSpecifier).filter((f) => f.endsWith('.mo') && fs.existsSync(f)),
typeSpecifier)
} catch (e) {
resolved = null
}
}
enumerationTypeCache[cacheKey] = resolved
return resolved
}

// Get array of potential paths
function joinedPathes (eleArr) {
const pathes = []
Expand Down Expand Up @@ -743,6 +891,7 @@ module.exports.removeDir = removeDir
module.exports.searchPath = searchPath
module.exports.readWithin = readWithin
module.exports.resolveTypeSpecifier = resolveTypeSpecifier
module.exports.resolveEnumerationType = resolveEnumerationType
module.exports.writeFile = writeFile
module.exports.isEmptyObject = isEmptyObject
module.exports.copyFolderSync = copyFolderSync
Expand Down
10 changes: 10 additions & 0 deletions test/FromModelica/ParameterWithEnumeration.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
within FromModelica;
block ParameterWithEnumeration "Block with enumeration-typed parameters"
parameter Enumeration1 eNoDefault
"Enumeration parameter without a default";
parameter Enumeration1 eWithDefault=FromModelica.Enumeration1.e2
"Enumeration parameter with a default";
parameter Buildings.Controls.OBC.CDL.Types.SimpleController conTyp=
Buildings.Controls.OBC.CDL.Types.SimpleController.PI
"Enumeration parameter of a CDL type";
end ParameterWithEnumeration;
Loading