@idfkit/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/collection.d.ts +48 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +100 -0
- package/dist/collection.js.map +1 -0
- package/dist/document.d.ts +100 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +0 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +25 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +25 -0
- package/dist/internal.js.map +1 -0
- package/dist/node.d.ts +40 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +70 -0
- package/dist/node.js.map +1 -0
- package/dist/object.d.ts +114 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +224 -0
- package/dist/object.js.map +1 -0
- package/dist/parse/epjson.d.ts +16 -0
- package/dist/parse/epjson.d.ts.map +1 -0
- package/dist/parse/epjson.js +91 -0
- package/dist/parse/epjson.js.map +1 -0
- package/dist/parse/idf.d.ts +44 -0
- package/dist/parse/idf.d.ts.map +1 -0
- package/dist/parse/idf.js +170 -0
- package/dist/parse/idf.js.map +1 -0
- package/dist/parse/lexer.d.ts +37 -0
- package/dist/parse/lexer.d.ts.map +1 -0
- package/dist/parse/lexer.js +105 -0
- package/dist/parse/lexer.js.map +1 -0
- package/dist/references.d.ts +56 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +147 -0
- package/dist/references.js.map +1 -0
- package/dist/shape.d.ts +54 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +114 -0
- package/dist/shape.js.map +1 -0
- package/dist/typemap.d.ts +43 -0
- package/dist/typemap.d.ts.map +1 -0
- package/dist/typemap.js +18 -0
- package/dist/typemap.js.map +1 -0
- package/dist/types/v26-1.d.ts +65896 -0
- package/dist/types/v26-1.d.ts.map +1 -0
- package/dist/types/v26-1.js +5 -0
- package/dist/types/v26-1.js.map +1 -0
- package/dist/types/v9-4.d.ts +61414 -0
- package/dist/types/v9-4.d.ts.map +1 -0
- package/dist/types/v9-4.js +5 -0
- package/dist/types/v9-4.js.map +1 -0
- package/dist/versions.d.ts +17 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +34 -0
- package/dist/versions.js.map +1 -0
- package/dist/write/epjson.d.ts +15 -0
- package/dist/write/epjson.d.ts.map +1 -0
- package/dist/write/epjson.js +10 -0
- package/dist/write/epjson.js.map +1 -0
- package/dist/write/idf.d.ts +47 -0
- package/dist/write/idf.d.ts.map +1 -0
- package/dist/write/idf.js +149 -0
- package/dist/write/idf.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { IDFDocument } from '../document.js';
|
|
2
|
+
import { lex } from './lexer.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse IDF text into a document.
|
|
5
|
+
*
|
|
6
|
+
* Synchronous and pure: text in, document out, no I/O. Everything that touches
|
|
7
|
+
* the filesystem or network lives at the edges (`@idfkit/core/node`), so this
|
|
8
|
+
* function behaves identically in Node, a browser, and a worker. The schema
|
|
9
|
+
* must already be loaded, which is the one genuinely async step.
|
|
10
|
+
*/
|
|
11
|
+
export function parseIdf(text, schema, options = {}) {
|
|
12
|
+
const strict = options.strict ?? true;
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
const report = (diagnostic) => {
|
|
15
|
+
if (strict) {
|
|
16
|
+
throw new IdfParseError(diagnostic);
|
|
17
|
+
}
|
|
18
|
+
diagnostics.push(diagnostic);
|
|
19
|
+
options.onDiagnostic?.(diagnostic);
|
|
20
|
+
};
|
|
21
|
+
const raw = lex(text, { onDiagnostic: report });
|
|
22
|
+
const document = new IDFDocument(schema);
|
|
23
|
+
for (const object of raw) {
|
|
24
|
+
const canonical = schema.resolve(object.typeName);
|
|
25
|
+
if (canonical === undefined) {
|
|
26
|
+
report({
|
|
27
|
+
message: `Unknown object type "${object.typeName}" in EnergyPlus ${schema.version}`,
|
|
28
|
+
line: object.line,
|
|
29
|
+
typeName: object.typeName,
|
|
30
|
+
});
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const definition = schema.require(canonical);
|
|
34
|
+
try {
|
|
35
|
+
const { name, values } = interpret(definition, object);
|
|
36
|
+
document.addRaw(canonical, definition.anon === 1 ? null : name, values);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
report({
|
|
40
|
+
message: error instanceof Error ? error.message : String(error),
|
|
41
|
+
line: object.line,
|
|
42
|
+
typeName: canonical,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { document, diagnostics };
|
|
47
|
+
}
|
|
48
|
+
/** Map positional IDF values onto named schema fields. */
|
|
49
|
+
function interpret(definition, object) {
|
|
50
|
+
const order = definition.f;
|
|
51
|
+
const named = definition.anon !== 1 && order[0] === 'name';
|
|
52
|
+
const values = {};
|
|
53
|
+
let cursor = 0;
|
|
54
|
+
let name = '';
|
|
55
|
+
if (named) {
|
|
56
|
+
name = object.values[0]?.trim() ?? '';
|
|
57
|
+
if (name === '' && definition.nreq === 1) {
|
|
58
|
+
throw new Error(`${object.typeName} requires a name`);
|
|
59
|
+
}
|
|
60
|
+
cursor = 1;
|
|
61
|
+
}
|
|
62
|
+
const fixed = named ? order.slice(1) : order;
|
|
63
|
+
for (const field of fixed) {
|
|
64
|
+
const raw = object.values[cursor++];
|
|
65
|
+
if (raw === undefined)
|
|
66
|
+
break;
|
|
67
|
+
if (raw === '')
|
|
68
|
+
continue;
|
|
69
|
+
const coerced = coerce(definition, field, raw);
|
|
70
|
+
if (coerced !== undefined)
|
|
71
|
+
values[field] = coerced;
|
|
72
|
+
}
|
|
73
|
+
// Everything past the fixed fields belongs to the extensible section, read in
|
|
74
|
+
// repeats of the group width. A trailing partial group is kept rather than
|
|
75
|
+
// dropped: files in the wild are truncated, and losing data on a round-trip
|
|
76
|
+
// is worse than carrying an incomplete group.
|
|
77
|
+
const extensible = definition.x;
|
|
78
|
+
if (extensible !== undefined) {
|
|
79
|
+
const width = extensible.fields.length;
|
|
80
|
+
const groups = [];
|
|
81
|
+
let lastPopulated = -1;
|
|
82
|
+
while (cursor < object.values.length) {
|
|
83
|
+
const group = {};
|
|
84
|
+
let populated = false;
|
|
85
|
+
for (let offset = 0; offset < width; offset += 1) {
|
|
86
|
+
const raw = object.values[cursor++];
|
|
87
|
+
if (raw === undefined || raw === '')
|
|
88
|
+
continue;
|
|
89
|
+
const field = extensible.fields[offset];
|
|
90
|
+
const coerced = coerceExtensible(definition, field, raw);
|
|
91
|
+
if (coerced !== undefined) {
|
|
92
|
+
group[field] = coerced;
|
|
93
|
+
populated = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (populated)
|
|
97
|
+
lastPopulated = groups.length;
|
|
98
|
+
groups.push(group);
|
|
99
|
+
}
|
|
100
|
+
// An all-blank repeat in the middle is kept, because the section is
|
|
101
|
+
// positional: dropping it pulls every later group down a slot, so a surface
|
|
102
|
+
// silently loses a vertex and its `number_of_vertices` stops matching.
|
|
103
|
+
// Trailing blanks are padding rather than data, and are dropped.
|
|
104
|
+
groups.length = lastPopulated + 1;
|
|
105
|
+
if (groups.length > 0)
|
|
106
|
+
values[extensible.key] = groups;
|
|
107
|
+
}
|
|
108
|
+
return { name, values };
|
|
109
|
+
}
|
|
110
|
+
function coerce(definition, field, raw) {
|
|
111
|
+
return coerceValue(definition.p[field]?.t, raw);
|
|
112
|
+
}
|
|
113
|
+
function coerceExtensible(definition, field, raw) {
|
|
114
|
+
const value = coerceValue(definition.x?.p[field]?.t, raw);
|
|
115
|
+
return Array.isArray(value) ? undefined : value;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Convert IDF text to a stored value.
|
|
119
|
+
*
|
|
120
|
+
* Numeric fields that hold `Autosize`, `Autocalculate`, or anything else
|
|
121
|
+
* non-numeric stay as strings. EnergyPlus accepts them and silently coercing
|
|
122
|
+
* to `NaN` would destroy the model on write.
|
|
123
|
+
*/
|
|
124
|
+
function coerceValue(kind, raw) {
|
|
125
|
+
if (kind === 'n' || kind === 'i') {
|
|
126
|
+
const parsed = Number(raw);
|
|
127
|
+
if (!Number.isNaN(parsed) && raw.trim() !== '') {
|
|
128
|
+
const value = kind === 'i' ? Math.trunc(parsed) : parsed;
|
|
129
|
+
// Normalize -0 to 0. IDF files do contain `-0`, and JavaScript keeps the
|
|
130
|
+
// sign, so without this a value round-trips to a different (if equal)
|
|
131
|
+
// number and every strict comparison downstream reports a spurious diff.
|
|
132
|
+
return value === 0 ? 0 : value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return raw;
|
|
136
|
+
}
|
|
137
|
+
export class IdfParseError extends Error {
|
|
138
|
+
line;
|
|
139
|
+
typeName;
|
|
140
|
+
constructor(diagnostic) {
|
|
141
|
+
super(`${diagnostic.message} (line ${diagnostic.line})`);
|
|
142
|
+
this.name = 'IdfParseError';
|
|
143
|
+
this.line = diagnostic.line;
|
|
144
|
+
this.typeName = diagnostic.typeName;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Read the version identifier from IDF text without a schema.
|
|
149
|
+
*
|
|
150
|
+
* Chicken-and-egg: choosing the schema requires knowing the version, and the
|
|
151
|
+
* version lives inside the file. This does the minimum scan needed to break
|
|
152
|
+
* the cycle, and does not validate anything else.
|
|
153
|
+
*/
|
|
154
|
+
export function detectVersion(text) {
|
|
155
|
+
// Strip comments first. Trying to tolerate them inside the pattern means
|
|
156
|
+
// guessing how many comment lines sit between the comma and the value, which
|
|
157
|
+
// is exactly the kind of thing that works on the file you tested and fails on
|
|
158
|
+
// the next one.
|
|
159
|
+
const stripped = text.replace(/!.*$/gm, '');
|
|
160
|
+
// Anchored to a statement boundary so a field value containing the word
|
|
161
|
+
// "Version" cannot be mistaken for the object.
|
|
162
|
+
const match = /(?:^|;)\s*Version\s*,\s*([\d.]+)\s*;/i.exec(stripped);
|
|
163
|
+
const raw = match?.[1];
|
|
164
|
+
if (raw === undefined)
|
|
165
|
+
return undefined;
|
|
166
|
+
const parts = raw.split('.').map((p) => Number(p) || 0);
|
|
167
|
+
// EnergyPlus writes `Version, 26.1;` but schemas are keyed `26.1.0`.
|
|
168
|
+
return `${parts[0] ?? 0}.${parts[1] ?? 0}.${parts[2] ?? 0}`;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=idf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idf.js","sourceRoot":"","sources":["../../src/parse/idf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** A raw object as it appears in the file, before schema interpretation. */
|
|
2
|
+
export interface RawObject {
|
|
3
|
+
/** Type name exactly as written, e.g. `BuildingSurface:Detailed`. */
|
|
4
|
+
typeName: string;
|
|
5
|
+
/** Comma-separated values after the type name, trimmed, comments stripped. */
|
|
6
|
+
values: string[];
|
|
7
|
+
/** 1-based line where the object starts, for diagnostics. */
|
|
8
|
+
line: number;
|
|
9
|
+
}
|
|
10
|
+
export interface LexDiagnostic {
|
|
11
|
+
message: string;
|
|
12
|
+
line: number;
|
|
13
|
+
}
|
|
14
|
+
export interface LexOptions {
|
|
15
|
+
/** Report a problem instead of throwing. */
|
|
16
|
+
onDiagnostic?: (diagnostic: LexDiagnostic) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Split IDF text into raw objects.
|
|
20
|
+
*
|
|
21
|
+
* A hand-written character scan rather than a regex. The Python library matches
|
|
22
|
+
* objects with a `(?:[^;!]*(?:![^\n]*\n)?)*?` inner loop; that is a nested
|
|
23
|
+
* quantifier, so it backtracks badly on malformed input and cannot report where
|
|
24
|
+
* the problem was. A scanner is about the same amount of code, is linear in the
|
|
25
|
+
* input, and always knows its line number.
|
|
26
|
+
*
|
|
27
|
+
* The grammar is small:
|
|
28
|
+
* - `!` starts a comment running to end of line
|
|
29
|
+
* - `,` separates fields
|
|
30
|
+
* - `;` terminates an object
|
|
31
|
+
* - everything else is field text, trimmed
|
|
32
|
+
*
|
|
33
|
+
* There are no string literals and no escape sequences, so a comma cannot occur
|
|
34
|
+
* inside a field value. Real files depend on that.
|
|
35
|
+
*/
|
|
36
|
+
export declare function lex(text: string, options?: LexOptions): RawObject[];
|
|
37
|
+
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,SAAS,EAAE,CAkGvE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split IDF text into raw objects.
|
|
3
|
+
*
|
|
4
|
+
* A hand-written character scan rather than a regex. The Python library matches
|
|
5
|
+
* objects with a `(?:[^;!]*(?:![^\n]*\n)?)*?` inner loop; that is a nested
|
|
6
|
+
* quantifier, so it backtracks badly on malformed input and cannot report where
|
|
7
|
+
* the problem was. A scanner is about the same amount of code, is linear in the
|
|
8
|
+
* input, and always knows its line number.
|
|
9
|
+
*
|
|
10
|
+
* The grammar is small:
|
|
11
|
+
* - `!` starts a comment running to end of line
|
|
12
|
+
* - `,` separates fields
|
|
13
|
+
* - `;` terminates an object
|
|
14
|
+
* - everything else is field text, trimmed
|
|
15
|
+
*
|
|
16
|
+
* There are no string literals and no escape sequences, so a comma cannot occur
|
|
17
|
+
* inside a field value. Real files depend on that.
|
|
18
|
+
*/
|
|
19
|
+
export function lex(text, options = {}) {
|
|
20
|
+
const objects = [];
|
|
21
|
+
const report = options.onDiagnostic;
|
|
22
|
+
const length = text.length;
|
|
23
|
+
/** Field text pieces, split whenever a comment interrupts a field. */
|
|
24
|
+
let chunks = [];
|
|
25
|
+
/** Fields of the object being read; index 0 ends up being the type name. */
|
|
26
|
+
let values = [];
|
|
27
|
+
let index = 0;
|
|
28
|
+
let line = 1;
|
|
29
|
+
let fieldStart = 0;
|
|
30
|
+
let objectLine = 1;
|
|
31
|
+
let objectStarted = false;
|
|
32
|
+
const endField = (end) => {
|
|
33
|
+
chunks.push(text.slice(fieldStart, end));
|
|
34
|
+
const value = chunks.join('').trim();
|
|
35
|
+
chunks = [];
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
while (index < length) {
|
|
39
|
+
const char = text[index];
|
|
40
|
+
if (char === '!') {
|
|
41
|
+
// Preserve any field text seen before the comment, then resume after the
|
|
42
|
+
// newline. This is what lets `Zone1, !- Name` work: the comment is not
|
|
43
|
+
// part of the value, but the value is not finished either.
|
|
44
|
+
chunks.push(text.slice(fieldStart, index));
|
|
45
|
+
const newline = text.indexOf('\n', index);
|
|
46
|
+
if (newline === -1) {
|
|
47
|
+
index = length;
|
|
48
|
+
fieldStart = length;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
index = newline + 1;
|
|
52
|
+
fieldStart = index;
|
|
53
|
+
line += 1;
|
|
54
|
+
if (!objectStarted && chunks.join('').trim() === '') {
|
|
55
|
+
chunks = [];
|
|
56
|
+
objectLine = line;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (char === ',') {
|
|
61
|
+
values.push(endField(index));
|
|
62
|
+
objectStarted = true;
|
|
63
|
+
index += 1;
|
|
64
|
+
fieldStart = index;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (char === ';') {
|
|
68
|
+
values.push(endField(index));
|
|
69
|
+
index += 1;
|
|
70
|
+
fieldStart = index;
|
|
71
|
+
const typeName = values.shift() ?? '';
|
|
72
|
+
if (typeName === '') {
|
|
73
|
+
report?.({ message: 'Object with no type name', line: objectLine });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
objects.push({ typeName, values, line: objectLine });
|
|
77
|
+
}
|
|
78
|
+
values = [];
|
|
79
|
+
objectStarted = false;
|
|
80
|
+
objectLine = line;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (char === '\n') {
|
|
84
|
+
line += 1;
|
|
85
|
+
if (!objectStarted &&
|
|
86
|
+
chunks.join('').trim() === '' &&
|
|
87
|
+
text.slice(fieldStart, index).trim() === '') {
|
|
88
|
+
// Blank line before any object content: keep the start line current.
|
|
89
|
+
chunks = [];
|
|
90
|
+
fieldStart = index + 1;
|
|
91
|
+
objectLine = line;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
index += 1;
|
|
95
|
+
}
|
|
96
|
+
const trailing = (chunks.join('') + text.slice(fieldStart, length)).trim();
|
|
97
|
+
if (trailing !== '' || values.length > 0) {
|
|
98
|
+
report?.({
|
|
99
|
+
message: `Unterminated object near "${trailing.slice(0, 40) || values[0]}" (missing ";")`,
|
|
100
|
+
line: objectLine,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return objects;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=lexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../../src/parse/lexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { IdfObject } from './object.js';
|
|
2
|
+
/** One field of one object pointing at a name. */
|
|
3
|
+
export interface ReferenceEdge {
|
|
4
|
+
readonly from: IdfObject;
|
|
5
|
+
readonly field: string;
|
|
6
|
+
readonly target: string;
|
|
7
|
+
/**
|
|
8
|
+
* Repeat index, when the field lives inside an extensible group. Absent for
|
|
9
|
+
* ordinary positional fields, which is what distinguishes the two.
|
|
10
|
+
*/
|
|
11
|
+
readonly index?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Live index of every name-to-name reference in a document.
|
|
15
|
+
*
|
|
16
|
+
* Kept current by the document as objects are added, removed, renamed, and
|
|
17
|
+
* edited, so `referencing()` is a lookup rather than a scan. EnergyPlus models
|
|
18
|
+
* are dense with references (every surface names a zone and a construction,
|
|
19
|
+
* every construction names materials), and the rename-propagation behaviour
|
|
20
|
+
* that makes the library useful depends on this being exact.
|
|
21
|
+
*
|
|
22
|
+
* Names are matched case-insensitively, because EnergyPlus resolves them that
|
|
23
|
+
* way, but the original casing is preserved for round-tripping.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ReferenceGraph {
|
|
26
|
+
#private;
|
|
27
|
+
/** Record that `obj.field` points at `target`. */
|
|
28
|
+
add(obj: IdfObject, field: string, target: string, index?: number): void;
|
|
29
|
+
/** Index every reference field of an object at once. */
|
|
30
|
+
addObject(obj: IdfObject): void;
|
|
31
|
+
/** Drop every edge originating from an object. */
|
|
32
|
+
removeObject(obj: IdfObject): void;
|
|
33
|
+
/** Update the edge for a single field after its value changed. */
|
|
34
|
+
updateField(obj: IdfObject, field: string, previous: unknown, next: unknown): void;
|
|
35
|
+
/** Edges pointing at a name. */
|
|
36
|
+
referencing(name: string): ReferenceEdge[];
|
|
37
|
+
/** Objects that reference a name, deduplicated. */
|
|
38
|
+
referencingObjects(name: string): IdfObject[];
|
|
39
|
+
/** Names an object points at. */
|
|
40
|
+
referencedBy(obj: IdfObject): string[];
|
|
41
|
+
/** Whether anything points at this name. */
|
|
42
|
+
isReferenced(name: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Rewrite every edge pointing at `previous` to point at `next`.
|
|
45
|
+
*
|
|
46
|
+
* Only updates the index. The document is responsible for writing the new
|
|
47
|
+
* value into the referencing objects' fields, which it does without going
|
|
48
|
+
* back through the setter hook to avoid re-entering this method.
|
|
49
|
+
*/
|
|
50
|
+
retarget(previous: string, next: string): ReferenceEdge[];
|
|
51
|
+
/** Edges whose target does not exist. `valid` holds lowercased names. */
|
|
52
|
+
dangling(valid: ReadonlySet<string>): ReferenceEdge[];
|
|
53
|
+
clear(): void;
|
|
54
|
+
get size(): number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc;;IAMzB,kDAAkD;IAClD,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAoBxE,wDAAwD;IACxD,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI;IAM/B,kDAAkD;IAClD,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI;IAalC,kEAAkE;IAClE,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAqBlF,gCAAgC;IAChC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE;IAK1C,mDAAmD;IACnD,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C,iCAAiC;IACjC,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE;IAKtC,4CAA4C;IAC5C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAInC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE;IAiBzD,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,aAAa,EAAE;IASrD,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAIjB;CACF"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live index of every name-to-name reference in a document.
|
|
3
|
+
*
|
|
4
|
+
* Kept current by the document as objects are added, removed, renamed, and
|
|
5
|
+
* edited, so `referencing()` is a lookup rather than a scan. EnergyPlus models
|
|
6
|
+
* are dense with references (every surface names a zone and a construction,
|
|
7
|
+
* every construction names materials), and the rename-propagation behaviour
|
|
8
|
+
* that makes the library useful depends on this being exact.
|
|
9
|
+
*
|
|
10
|
+
* Names are matched case-insensitively, because EnergyPlus resolves them that
|
|
11
|
+
* way, but the original casing is preserved for round-tripping.
|
|
12
|
+
*/
|
|
13
|
+
export class ReferenceGraph {
|
|
14
|
+
/** lowercased target name -> edges pointing at it. */
|
|
15
|
+
#incoming = new Map();
|
|
16
|
+
/** object -> edges originating from it. */
|
|
17
|
+
#outgoing = new Map();
|
|
18
|
+
/** Record that `obj.field` points at `target`. */
|
|
19
|
+
add(obj, field, target, index) {
|
|
20
|
+
if (target === '')
|
|
21
|
+
return;
|
|
22
|
+
const edge = Object.freeze({ from: obj, field, target, index });
|
|
23
|
+
const key = target.toLowerCase();
|
|
24
|
+
let incoming = this.#incoming.get(key);
|
|
25
|
+
if (incoming === undefined) {
|
|
26
|
+
incoming = new Set();
|
|
27
|
+
this.#incoming.set(key, incoming);
|
|
28
|
+
}
|
|
29
|
+
incoming.add(edge);
|
|
30
|
+
let outgoing = this.#outgoing.get(obj);
|
|
31
|
+
if (outgoing === undefined) {
|
|
32
|
+
outgoing = new Set();
|
|
33
|
+
this.#outgoing.set(obj, outgoing);
|
|
34
|
+
}
|
|
35
|
+
outgoing.add(edge);
|
|
36
|
+
}
|
|
37
|
+
/** Index every reference field of an object at once. */
|
|
38
|
+
addObject(obj) {
|
|
39
|
+
for (const { field, target, index } of obj.outgoingReferences()) {
|
|
40
|
+
this.add(obj, field, target, index);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Drop every edge originating from an object. */
|
|
44
|
+
removeObject(obj) {
|
|
45
|
+
const outgoing = this.#outgoing.get(obj);
|
|
46
|
+
if (outgoing === undefined)
|
|
47
|
+
return;
|
|
48
|
+
for (const edge of outgoing) {
|
|
49
|
+
const key = edge.target.toLowerCase();
|
|
50
|
+
const incoming = this.#incoming.get(key);
|
|
51
|
+
if (incoming === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
incoming.delete(edge);
|
|
54
|
+
if (incoming.size === 0)
|
|
55
|
+
this.#incoming.delete(key);
|
|
56
|
+
}
|
|
57
|
+
this.#outgoing.delete(obj);
|
|
58
|
+
}
|
|
59
|
+
/** Update the edge for a single field after its value changed. */
|
|
60
|
+
updateField(obj, field, previous, next) {
|
|
61
|
+
if (typeof previous === 'string' && previous !== '') {
|
|
62
|
+
const key = previous.toLowerCase();
|
|
63
|
+
const incoming = this.#incoming.get(key);
|
|
64
|
+
const outgoing = this.#outgoing.get(obj);
|
|
65
|
+
if (incoming !== undefined && outgoing !== undefined) {
|
|
66
|
+
for (const edge of outgoing) {
|
|
67
|
+
// `index !== undefined` marks an edge inside an extensible group.
|
|
68
|
+
// Those are re-indexed wholesale, so a positional-field update must
|
|
69
|
+
// not consume one that happens to share the field name.
|
|
70
|
+
if (edge.field !== field || edge.index !== undefined)
|
|
71
|
+
continue;
|
|
72
|
+
outgoing.delete(edge);
|
|
73
|
+
incoming.delete(edge);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (incoming.size === 0)
|
|
77
|
+
this.#incoming.delete(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (typeof next === 'string' && next !== '')
|
|
81
|
+
this.add(obj, field, next);
|
|
82
|
+
}
|
|
83
|
+
/** Edges pointing at a name. */
|
|
84
|
+
referencing(name) {
|
|
85
|
+
const edges = this.#incoming.get(name.toLowerCase());
|
|
86
|
+
return edges === undefined ? [] : [...edges];
|
|
87
|
+
}
|
|
88
|
+
/** Objects that reference a name, deduplicated. */
|
|
89
|
+
referencingObjects(name) {
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (const edge of this.referencing(name))
|
|
92
|
+
seen.add(edge.from);
|
|
93
|
+
return [...seen];
|
|
94
|
+
}
|
|
95
|
+
/** Names an object points at. */
|
|
96
|
+
referencedBy(obj) {
|
|
97
|
+
const edges = this.#outgoing.get(obj);
|
|
98
|
+
return edges === undefined ? [] : [...new Set([...edges].map((e) => e.target))];
|
|
99
|
+
}
|
|
100
|
+
/** Whether anything points at this name. */
|
|
101
|
+
isReferenced(name) {
|
|
102
|
+
return (this.#incoming.get(name.toLowerCase())?.size ?? 0) > 0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Rewrite every edge pointing at `previous` to point at `next`.
|
|
106
|
+
*
|
|
107
|
+
* Only updates the index. The document is responsible for writing the new
|
|
108
|
+
* value into the referencing objects' fields, which it does without going
|
|
109
|
+
* back through the setter hook to avoid re-entering this method.
|
|
110
|
+
*/
|
|
111
|
+
retarget(previous, next) {
|
|
112
|
+
const key = previous.toLowerCase();
|
|
113
|
+
const edges = this.#incoming.get(key);
|
|
114
|
+
if (edges === undefined)
|
|
115
|
+
return [];
|
|
116
|
+
const affected = [...edges];
|
|
117
|
+
this.#incoming.delete(key);
|
|
118
|
+
for (const edge of affected) {
|
|
119
|
+
this.#outgoing.get(edge.from)?.delete(edge);
|
|
120
|
+
}
|
|
121
|
+
for (const edge of affected) {
|
|
122
|
+
this.add(edge.from, edge.field, next, edge.index);
|
|
123
|
+
}
|
|
124
|
+
return affected;
|
|
125
|
+
}
|
|
126
|
+
/** Edges whose target does not exist. `valid` holds lowercased names. */
|
|
127
|
+
dangling(valid) {
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const [key, edges] of this.#incoming) {
|
|
130
|
+
if (valid.has(key))
|
|
131
|
+
continue;
|
|
132
|
+
out.push(...edges);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
clear() {
|
|
137
|
+
this.#incoming.clear();
|
|
138
|
+
this.#outgoing.clear();
|
|
139
|
+
}
|
|
140
|
+
get size() {
|
|
141
|
+
let total = 0;
|
|
142
|
+
for (const edges of this.#incoming.values())
|
|
143
|
+
total += edges.size;
|
|
144
|
+
return total;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=references.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"references.js","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"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"}
|
package/dist/shape.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SlimType } from '@idfkit/schemas';
|
|
2
|
+
import type { IdfObject } from './object.js';
|
|
3
|
+
/**
|
|
4
|
+
* Per-object-type prototype carrying real accessors for every field.
|
|
5
|
+
*
|
|
6
|
+
* The Python library resolves `zone.ceiling_height` through `__getattr__`. The
|
|
7
|
+
* mechanical translation of that is a `Proxy`, which we deliberately do not use:
|
|
8
|
+
* proxies defeat V8's inline caches, and more importantly they are invisible to
|
|
9
|
+
* TypeScript, so nothing would autocomplete. Instead each object type gets one
|
|
10
|
+
* prototype with `Object.defineProperty` accessors, built once and shared by
|
|
11
|
+
* every instance of that type. Property access is then an ordinary monomorphic
|
|
12
|
+
* lookup, and the generated `.d.ts` interfaces describe it statically.
|
|
13
|
+
*
|
|
14
|
+
* Shapes are keyed by the schema definition object rather than by type name.
|
|
15
|
+
* Because the schema bundle is content-addressed, `Zone` in 9.4.0 and `Zone` in
|
|
16
|
+
* 26.1.0 are the same frozen definition, so they share one shape and one
|
|
17
|
+
* prototype. Cross-version documents stay monomorphic for free.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ObjectShape {
|
|
20
|
+
readonly typeName: string;
|
|
21
|
+
readonly type: SlimType;
|
|
22
|
+
readonly proto: object;
|
|
23
|
+
/** Field names in IDF positional order, excluding the name field. */
|
|
24
|
+
readonly fields: readonly string[];
|
|
25
|
+
/** Fields that point into a reference list, i.e. foreign keys. */
|
|
26
|
+
readonly refFields: readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Fields *inside* the extensible group that point into a reference list.
|
|
29
|
+
*
|
|
30
|
+
* Kept separate from `refFields` because these live in `type.x.fields`, not
|
|
31
|
+
* the positional field list, and so need the repeat index to address them.
|
|
32
|
+
* Ignoring them is not cosmetic: `ZoneList`, `Branch`, and the supply/return
|
|
33
|
+
* paths carry all of their references here, so leaving them out of the graph
|
|
34
|
+
* makes `rename()` silently produce a broken model.
|
|
35
|
+
*/
|
|
36
|
+
readonly extensibleRefFields: readonly string[];
|
|
37
|
+
/** Fields whose value declares a name other objects may reference. */
|
|
38
|
+
readonly keyFields: readonly string[];
|
|
39
|
+
/** Extensible array key (`vertices`), if this type has one. */
|
|
40
|
+
readonly extensibleKey: string | undefined;
|
|
41
|
+
/** Whether the object carries a name (most do; `Version` does not). */
|
|
42
|
+
readonly named: boolean;
|
|
43
|
+
constructor(typeName: string, type: SlimType, base: object);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get (or build) the shape for an object type.
|
|
47
|
+
*
|
|
48
|
+
* `base` is `IdfObject.prototype`, threaded in rather than imported to keep
|
|
49
|
+
* this module free of a cycle with `object.ts`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function shapeFor(typeName: string, type: SlimType, base: object): ObjectShape;
|
|
52
|
+
/** Number of distinct shapes built, for tests and diagnostics. */
|
|
53
|
+
export declare function shapeOf(obj: IdfObject): ObjectShape;
|
|
54
|
+
//# sourceMappingURL=shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM;CA4B3D;AA0BD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAYpF;AAED,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,GAAG,WAAW,CAEnD"}
|