@ifc-lite/codegen 1.0.0 → 1.1.0

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.
Files changed (44) hide show
  1. package/dist/cli.js +23 -5
  2. package/dist/cli.js.map +1 -1
  3. package/dist/crc32.d.ts +16 -0
  4. package/dist/crc32.d.ts.map +1 -0
  5. package/dist/crc32.js +69 -0
  6. package/dist/crc32.js.map +1 -0
  7. package/dist/generator.d.ts +20 -4
  8. package/dist/generator.d.ts.map +1 -1
  9. package/dist/generator.js +175 -19
  10. package/dist/generator.js.map +1 -1
  11. package/dist/index.d.ts +13 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +19 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/rust-generator.d.ts +20 -0
  16. package/dist/rust-generator.d.ts.map +1 -0
  17. package/dist/rust-generator.js +640 -0
  18. package/dist/rust-generator.js.map +1 -0
  19. package/dist/serialization-generator.d.ts +11 -0
  20. package/dist/serialization-generator.d.ts.map +1 -0
  21. package/dist/serialization-generator.js +333 -0
  22. package/dist/serialization-generator.js.map +1 -0
  23. package/dist/type-ids-generator.d.ts +11 -0
  24. package/dist/type-ids-generator.d.ts.map +1 -0
  25. package/dist/type-ids-generator.js +153 -0
  26. package/dist/type-ids-generator.js.map +1 -0
  27. package/generated/ifc4x3/entities.ts +9 -13
  28. package/generated/ifc4x3/enums.ts +0 -4
  29. package/generated/ifc4x3/index.ts +4 -2
  30. package/generated/ifc4x3/schema-registry.ts +326 -2718
  31. package/generated/ifc4x3/selects.ts +0 -4
  32. package/generated/ifc4x3/serializers.ts +322 -0
  33. package/generated/ifc4x3/test-compile.ts +49 -0
  34. package/generated/ifc4x3/type-ids.ts +1882 -0
  35. package/generated/ifc4x3/types.ts +0 -4
  36. package/package.json +1 -1
  37. package/src/cli.ts +49 -18
  38. package/src/crc32.ts +77 -0
  39. package/src/generator.ts +213 -21
  40. package/src/index.ts +28 -2
  41. package/src/rust-generator.ts +715 -0
  42. package/src/serialization-generator.ts +343 -0
  43. package/src/type-ids-generator.ts +166 -0
  44. package/tsconfig.json +1 -0
@@ -1,7 +1,3 @@
1
- /* This Source Code Form is subject to the terms of the Mozilla Public
2
- * License, v. 2.0. If a copy of the MPL was not distributed with this
3
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
-
5
1
  /**
6
2
  * IFC Type Aliases
7
3
  * Generated from EXPRESS schema: IFC4X3_DEV_923b0514
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/codegen",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript code generator from IFC EXPRESS schemas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cli.ts CHANGED
@@ -5,37 +5,68 @@
5
5
 
6
6
  /**
7
7
  * CLI for IFC code generation
8
+ *
9
+ * Generates TypeScript and optionally Rust code from IFC EXPRESS schemas.
8
10
  */
9
11
 
10
12
  import { Command } from 'commander';
11
- import { generateFromFile } from './generator.js';
13
+ import { generateFromFile, type GeneratorOptions } from './generator.js';
12
14
 
13
15
  const program = new Command();
14
16
 
15
17
  program
16
18
  .name('ifc-codegen')
17
- .description('Generate TypeScript code from IFC EXPRESS schemas')
18
- .version('0.1.0');
19
+ .description('Generate TypeScript and Rust code from IFC EXPRESS schemas')
20
+ .version('0.2.0');
19
21
 
20
22
  program
21
23
  .argument('<schema>', 'Path to EXPRESS schema file (.exp)')
22
24
  .option('-o, --output <dir>', 'Output directory', './generated')
25
+ .option('-r, --rust', 'Generate Rust code', false)
26
+ .option('--rust-dir <dir>', 'Rust output subdirectory (relative to output)', 'rust')
27
+ .option('--skip-collision-check', 'Skip CRC32 collision check', false)
23
28
  .option('-v, --verbose', 'Verbose output', false)
24
- .action((schemaPath: string, options: { output: string; verbose: boolean }) => {
25
- try {
26
- console.log('šŸš€ IFC TypeScript Code Generator\n');
27
- console.log(`Schema: ${schemaPath}`);
28
- console.log(`Output: ${options.output}\n`);
29
-
30
- const start = Date.now();
31
- generateFromFile(schemaPath, options.output);
32
- const elapsed = Date.now() - start;
33
-
34
- console.log(`\nā±ļø Completed in ${elapsed}ms`);
35
- } catch (error) {
36
- console.error('\nāŒ Error:', error instanceof Error ? error.message : error);
37
- process.exit(1);
29
+ .action(
30
+ (
31
+ schemaPath: string,
32
+ options: {
33
+ output: string;
34
+ rust: boolean;
35
+ rustDir: string;
36
+ skipCollisionCheck: boolean;
37
+ verbose: boolean;
38
+ }
39
+ ) => {
40
+ try {
41
+ console.log('šŸš€ IFC Code Generator\n');
42
+ console.log(`Schema: ${schemaPath}`);
43
+ console.log(`Output: ${options.output}`);
44
+ if (options.rust) {
45
+ // Check if rustDir is absolute or relative for display
46
+ const rustDisplay = options.rustDir.startsWith('/')
47
+ ? options.rustDir
48
+ : `${options.output}/${options.rustDir}`;
49
+ console.log(`Rust: ${rustDisplay}`);
50
+ }
51
+ console.log();
52
+
53
+ const start = Date.now();
54
+
55
+ const genOptions: GeneratorOptions = {
56
+ rust: options.rust,
57
+ rustDir: options.rustDir,
58
+ skipCollisionCheck: options.skipCollisionCheck,
59
+ };
60
+
61
+ generateFromFile(schemaPath, options.output, genOptions);
62
+
63
+ const elapsed = Date.now() - start;
64
+ console.log(`\nā±ļø Completed in ${elapsed}ms`);
65
+ } catch (error) {
66
+ console.error('\nāŒ Error:', error instanceof Error ? error.message : error);
67
+ process.exit(1);
68
+ }
38
69
  }
39
- });
70
+ );
40
71
 
41
72
  program.parse();
package/src/crc32.ts ADDED
@@ -0,0 +1,77 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * CRC32 Type ID Generator
7
+ *
8
+ * Generates consistent CRC32 hashes for IFC type names.
9
+ * Used for fast O(1) type lookup in both TypeScript and Rust.
10
+ *
11
+ * This matches the algorithm used by web-ifc for compatibility.
12
+ */
13
+
14
+ // Pre-computed CRC32 lookup table (IEEE polynomial)
15
+ const CRC32_TABLE = buildCRC32Table();
16
+
17
+ function buildCRC32Table(): Uint32Array {
18
+ const table = new Uint32Array(256);
19
+ for (let i = 0; i < 256; i++) {
20
+ let c = i;
21
+ for (let j = 0; j < 8; j++) {
22
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
23
+ }
24
+ table[i] = c >>> 0;
25
+ }
26
+ return table;
27
+ }
28
+
29
+ /**
30
+ * Calculate CRC32 hash for a string
31
+ * @param str Input string (will be uppercased)
32
+ * @returns 32-bit unsigned integer hash
33
+ */
34
+ export function crc32(str: string): number {
35
+ const upper = str.toUpperCase();
36
+ let crc = 0xffffffff;
37
+ for (let i = 0; i < upper.length; i++) {
38
+ crc = CRC32_TABLE[(crc ^ upper.charCodeAt(i)) & 0xff] ^ (crc >>> 8);
39
+ }
40
+ return (crc ^ 0xffffffff) >>> 0;
41
+ }
42
+
43
+ /**
44
+ * Generate type IDs for all entities in a schema
45
+ */
46
+ export function generateTypeIds(entityNames: string[]): Map<string, number> {
47
+ const ids = new Map<string, number>();
48
+ for (const name of entityNames) {
49
+ ids.set(name, crc32(name));
50
+ }
51
+ return ids;
52
+ }
53
+
54
+ /**
55
+ * Check for CRC32 collisions in a list of names
56
+ * @returns Map of hash -> names[] for any collisions
57
+ */
58
+ export function findCollisions(names: string[]): Map<number, string[]> {
59
+ const hashToNames = new Map<number, string[]>();
60
+
61
+ for (const name of names) {
62
+ const hash = crc32(name);
63
+ const existing = hashToNames.get(hash) || [];
64
+ existing.push(name);
65
+ hashToNames.set(hash, existing);
66
+ }
67
+
68
+ // Filter to only collisions
69
+ const collisions = new Map<number, string[]>();
70
+ for (const [hash, nameList] of hashToNames) {
71
+ if (nameList.length > 1) {
72
+ collisions.set(hash, nameList);
73
+ }
74
+ }
75
+
76
+ return collisions;
77
+ }
package/src/generator.ts CHANGED
@@ -3,29 +3,61 @@
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
4
 
5
5
  /**
6
- * High-level generator functions
6
+ * High-level Code Generator
7
+ *
8
+ * Orchestrates code generation from EXPRESS schemas to:
9
+ * - TypeScript interfaces and types
10
+ * - TypeScript type IDs (CRC32)
11
+ * - TypeScript serialization helpers
12
+ * - Rust types and type IDs
7
13
  */
8
14
 
9
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
- import { dirname } from 'node:path';
11
- import { parseExpressSchema } from './express-parser.js';
15
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
16
+ import { join, isAbsolute } from 'node:path';
17
+ import { parseExpressSchema, type ExpressSchema } from './express-parser.js';
12
18
  import { generateTypeScript, type GeneratedCode } from './typescript-generator.js';
19
+ import { generateTypeIds } from './type-ids-generator.js';
20
+ import { generateSerializers } from './serialization-generator.js';
21
+ import { generateRust, type RustGeneratedCode } from './rust-generator.js';
22
+ import { findCollisions } from './crc32.js';
23
+
24
+ export interface FullGeneratedCode extends GeneratedCode {
25
+ typeIds: string;
26
+ serializers: string;
27
+ }
28
+
29
+ export interface GeneratorOptions {
30
+ /** Generate Rust output */
31
+ rust?: boolean;
32
+ /** Rust output directory (relative to outputDir or absolute) */
33
+ rustDir?: string;
34
+ /** Skip type ID collision check */
35
+ skipCollisionCheck?: boolean;
36
+ }
13
37
 
14
38
  /**
15
- * Generate TypeScript code from an EXPRESS schema file
39
+ * Generate all code from an EXPRESS schema file
16
40
  */
17
- export function generateFromFile(schemaPath: string, outputDir: string): GeneratedCode {
41
+ export function generateFromFile(
42
+ schemaPath: string,
43
+ outputDir: string,
44
+ options: GeneratorOptions = {}
45
+ ): FullGeneratedCode {
18
46
  // Read schema file
19
47
  const content = readFileSync(schemaPath, 'utf-8');
20
48
 
21
49
  // Generate code
22
- return generateFromSchema(content, outputDir);
50
+ return generateFromSchema(content, outputDir, options);
23
51
  }
24
52
 
25
53
  /**
26
- * Generate TypeScript code from EXPRESS schema content
54
+ * Generate all code from EXPRESS schema content
27
55
  */
28
- export function generateFromSchema(schemaContent: string, outputDir: string): GeneratedCode {
56
+ export function generateFromSchema(
57
+ schemaContent: string,
58
+ outputDir: string,
59
+ options: GeneratorOptions = {}
60
+ ): FullGeneratedCode {
29
61
  console.log('šŸ“– Parsing EXPRESS schema...');
30
62
  const schema = parseExpressSchema(schemaContent);
31
63
 
@@ -35,35 +67,62 @@ export function generateFromSchema(schemaContent: string, outputDir: string): Ge
35
67
  console.log(` - ${schema.enums.length} enums`);
36
68
  console.log(` - ${schema.selects.length} selects`);
37
69
 
70
+ // Check for CRC32 collisions
71
+ if (!options.skipCollisionCheck) {
72
+ console.log('\nšŸ” Checking for CRC32 collisions...');
73
+ const entityNames = schema.entities.map((e) => e.name);
74
+ const collisions = findCollisions(entityNames);
75
+ if (collisions.size > 0) {
76
+ console.warn('āš ļø CRC32 collisions detected:');
77
+ for (const [hash, names] of collisions) {
78
+ console.warn(` ${hash}: ${names.join(', ')}`);
79
+ }
80
+ } else {
81
+ console.log(' āœ“ No collisions');
82
+ }
83
+ }
84
+
38
85
  console.log('\nšŸ”Ø Generating TypeScript code...');
39
- const code = generateTypeScript(schema);
86
+ const tsCode = generateTypeScript(schema);
87
+ const typeIds = generateTypeIds(schema);
88
+ const serializers = generateSerializers(schema);
40
89
 
41
- console.log('šŸ’¾ Writing generated files...');
90
+ console.log('šŸ’¾ Writing TypeScript files...');
42
91
 
43
92
  // Create output directory
44
93
  mkdirSync(outputDir, { recursive: true });
45
94
 
46
- // Write files
47
- writeFileSync(`${outputDir}/entities.ts`, code.entities);
95
+ // Write TypeScript files
96
+ writeFileSync(`${outputDir}/entities.ts`, tsCode.entities);
48
97
  console.log(` āœ“ ${outputDir}/entities.ts`);
49
98
 
50
- writeFileSync(`${outputDir}/types.ts`, code.types);
99
+ writeFileSync(`${outputDir}/types.ts`, tsCode.types);
51
100
  console.log(` āœ“ ${outputDir}/types.ts`);
52
101
 
53
- writeFileSync(`${outputDir}/enums.ts`, code.enums);
102
+ writeFileSync(`${outputDir}/enums.ts`, tsCode.enums);
54
103
  console.log(` āœ“ ${outputDir}/enums.ts`);
55
104
 
56
- writeFileSync(`${outputDir}/selects.ts`, code.selects);
105
+ writeFileSync(`${outputDir}/selects.ts`, tsCode.selects);
57
106
  console.log(` āœ“ ${outputDir}/selects.ts`);
58
107
 
59
- writeFileSync(`${outputDir}/schema-registry.ts`, code.schemaRegistry);
108
+ writeFileSync(`${outputDir}/schema-registry.ts`, tsCode.schemaRegistry);
60
109
  console.log(` āœ“ ${outputDir}/schema-registry.ts`);
61
110
 
111
+ writeFileSync(`${outputDir}/type-ids.ts`, typeIds);
112
+ console.log(` āœ“ ${outputDir}/type-ids.ts`);
113
+
114
+ writeFileSync(`${outputDir}/serializers.ts`, serializers);
115
+ console.log(` āœ“ ${outputDir}/serializers.ts`);
116
+
62
117
  // Write index file
63
- const indexContent = `/**
64
- * Generated IFC Schema
118
+ const indexContent = `/* This Source Code Form is subject to the terms of the Mozilla Public
119
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
120
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
121
+
122
+ /**
123
+ * Generated IFC Schema: ${schema.name}
65
124
  *
66
- * DO NOT EDIT - This file is auto-generated
125
+ * DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen
67
126
  */
68
127
 
69
128
  export * from './entities.js';
@@ -71,11 +130,144 @@ export * from './types.js';
71
130
  export * from './enums.js';
72
131
  export * from './selects.js';
73
132
  export * from './schema-registry.js';
133
+ export * from './type-ids.js';
134
+ export * from './serializers.js';
74
135
  `;
75
136
  writeFileSync(`${outputDir}/index.ts`, indexContent);
76
137
  console.log(` āœ“ ${outputDir}/index.ts`);
77
138
 
139
+ // Generate Rust code if requested
140
+ if (options.rust) {
141
+ console.log('\nšŸ¦€ Generating Rust code...');
142
+ const rustCode = generateRust(schema);
143
+ // Use absolute path directly, or join relative path with outputDir
144
+ const rustDir = options.rustDir
145
+ ? isAbsolute(options.rustDir)
146
+ ? options.rustDir
147
+ : join(outputDir, options.rustDir)
148
+ : join(outputDir, 'rust');
149
+
150
+ mkdirSync(rustDir, { recursive: true });
151
+
152
+ writeFileSync(`${rustDir}/type_ids.rs`, rustCode.typeIds);
153
+ console.log(` āœ“ ${rustDir}/type_ids.rs`);
154
+
155
+ writeFileSync(`${rustDir}/schema.rs`, rustCode.schema);
156
+ console.log(` āœ“ ${rustDir}/schema.rs`);
157
+
158
+ writeFileSync(`${rustDir}/geometry_categories.rs`, rustCode.geometryCategories);
159
+ console.log(` āœ“ ${rustDir}/geometry_categories.rs`);
160
+
161
+ // Write mod.rs
162
+ const modContent = `// This Source Code Form is subject to the terms of the Mozilla Public
163
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
164
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
165
+
166
+ //! Auto-generated IFC Schema Types
167
+ //!
168
+ //! Generated from EXPRESS schema: ${schema.name}
169
+ //!
170
+ //! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen
171
+
172
+ mod type_ids;
173
+ mod schema;
174
+ mod geometry_categories;
175
+
176
+ pub use type_ids::*;
177
+ pub use schema::*;
178
+ pub use geometry_categories::*;
179
+ `;
180
+ writeFileSync(`${rustDir}/mod.rs`, modContent);
181
+ console.log(` āœ“ ${rustDir}/mod.rs`);
182
+ }
183
+
184
+ // Write test-compile file
185
+ const testCompileContent = `/* This Source Code Form is subject to the terms of the Mozilla Public
186
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
187
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
188
+
189
+ /**
190
+ * Type-check test file
191
+ * This file is used to verify the generated types compile correctly.
192
+ *
193
+ * DO NOT EDIT - This file is auto-generated
194
+ */
195
+
196
+ import type { IfcWall, IfcProject, IfcExtrudedAreaSolid } from './entities.js';
197
+ import { TYPE_IDS, getTypeId, getTypeName } from './type-ids.js';
198
+ import { SCHEMA_REGISTRY, getEntityMetadata } from './schema-registry.js';
199
+ import { toStepLine, serializeValue, ref, enumVal, type StepEntity } from './serializers.js';
200
+
201
+ // Test type IDs
202
+ const wallId: number = TYPE_IDS.IfcWall;
203
+ const projectId: number = TYPE_IDS.IfcProject;
204
+
205
+ // Test ID lookup
206
+ const wallIdFromName = getTypeId('IfcWall');
207
+ const nameFromId = getTypeName(wallId);
208
+
209
+ // Test schema registry
210
+ const wallMeta = getEntityMetadata('IfcWall');
211
+ const wallAttrs = wallMeta?.allAttributes;
212
+
213
+ // Test serialization
214
+ const testEntity: StepEntity = {
215
+ expressId: 1,
216
+ type: 'IfcProject',
217
+ GlobalId: '0YvctVUKr0kugbFTf53O9L',
218
+ OwnerHistory: ref(2),
219
+ Name: 'Test Project',
220
+ Description: null,
221
+ ObjectType: null,
222
+ LongName: null,
223
+ Phase: null,
224
+ RepresentationContexts: [ref(3)],
225
+ UnitsInContext: ref(4),
226
+ };
227
+
228
+ const stepLine = toStepLine(testEntity);
229
+
230
+ console.log('āœ“ All types compile correctly');
231
+ console.log(' Wall ID:', wallId);
232
+ console.log(' Project ID:', projectId);
233
+ console.log(' STEP line:', stepLine);
234
+ `;
235
+ writeFileSync(`${outputDir}/test-compile.ts`, testCompileContent);
236
+ console.log(` āœ“ ${outputDir}/test-compile.ts`);
237
+
78
238
  console.log('\n✨ Code generation complete!');
79
239
 
80
- return code;
240
+ return {
241
+ ...tsCode,
242
+ typeIds,
243
+ serializers,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Generate code for both IFC4 and IFC4X3 schemas
249
+ */
250
+ export function generateAll(
251
+ schemasDir: string,
252
+ outputBaseDir: string,
253
+ options: GeneratorOptions = {}
254
+ ): void {
255
+ const schemas = [
256
+ { name: 'IFC4', file: 'IFC4_ADD2_TC1.exp', dir: 'ifc4' },
257
+ { name: 'IFC4X3', file: 'IFC4X3_ADD2.exp', dir: 'ifc4x3' },
258
+ ];
259
+
260
+ for (const schema of schemas) {
261
+ const schemaPath = join(schemasDir, schema.file);
262
+ if (existsSync(schemaPath)) {
263
+ console.log(`\n${'='.repeat(60)}`);
264
+ console.log(`Processing ${schema.name}...`);
265
+ console.log(`${'='.repeat(60)}\n`);
266
+
267
+ const outputDir = join(outputBaseDir, schema.dir);
268
+ generateFromFile(schemaPath, outputDir, options);
269
+ } else {
270
+ console.warn(`āš ļø Schema file not found: ${schemaPath}`);
271
+ }
272
+ }
81
273
  }
package/src/index.ts CHANGED
@@ -5,9 +5,17 @@
5
5
  /**
6
6
  * @ifc-lite/codegen
7
7
  *
8
- * TypeScript code generator from IFC EXPRESS schemas
8
+ * IFC Code Generator - TypeScript and Rust from EXPRESS schemas
9
+ *
10
+ * Features:
11
+ * - TypeScript interfaces from EXPRESS entities
12
+ * - CRC32 type IDs for fast O(1) lookup
13
+ * - Serialization support for IFC writing
14
+ * - Rust type generation
15
+ * - Schema metadata registry
9
16
  */
10
17
 
18
+ // Core parser
11
19
  export {
12
20
  parseExpressSchema,
13
21
  getAllAttributes,
@@ -22,10 +30,28 @@ export {
22
30
  type InverseAttribute,
23
31
  } from './express-parser.js';
24
32
 
33
+ // TypeScript generation
25
34
  export {
26
35
  generateTypeScript,
27
36
  writeGeneratedFiles,
28
37
  type GeneratedCode,
29
38
  } from './typescript-generator.js';
30
39
 
31
- export { generateFromFile, generateFromSchema } from './generator.js';
40
+ // Type IDs (CRC32)
41
+ export { generateTypeIds } from './type-ids-generator.js';
42
+ export { crc32, generateTypeIds as generateTypeIdMap, findCollisions } from './crc32.js';
43
+
44
+ // Serialization
45
+ export { generateSerializers } from './serialization-generator.js';
46
+
47
+ // Rust generation
48
+ export { generateRust, type RustGeneratedCode } from './rust-generator.js';
49
+
50
+ // High-level generator
51
+ export {
52
+ generateFromFile,
53
+ generateFromSchema,
54
+ generateAll,
55
+ type FullGeneratedCode,
56
+ type GeneratorOptions,
57
+ } from './generator.js';