-
Notifications
You must be signed in to change notification settings - Fork 1
express parser
Maarten Vroegindeweij edited this page Feb 18, 2026
·
1 revision
Layer 0 — Zero dependencies. Parses EXPRESS schema (.exp) files into an Abstract Syntax Tree (AST).
EXPRESS is a data modeling language defined in ISO 10303-11. It is used to define the IFC schema. An EXPRESS file (.exp) describes:
- Entities with attributes, inheritance, and constraints
- Types (enumerations, selects, aliases, aggregations)
- Functions and Rules for data validation
Example EXPRESS snippet:
SCHEMA IFC4X3;
TYPE IfcLabel = STRING(255);
END_TYPE;
TYPE IfcWallTypeEnum = ENUMERATION OF
(MOVABLE, PARAPET, PARTITIONING, PLUMBINGWALL, SHEAR,
SOLIDWALL, STANDARD, POLYGONAL, ELEMENTEDWALL,
RETAININGWALL, WAVEWALL, USERDEFINED, NOTDEFINED);
END_TYPE;
ENTITY IfcRoot
ABSTRACT SUPERTYPE OF (ONEOF(IfcObjectDefinition, IfcPropertyDefinition, IfcRelationship));
GlobalId : IfcGloballyUniqueId;
OwnerHistory : OPTIONAL IfcOwnerHistory;
Name : OPTIONAL IfcLabel;
Description : OPTIONAL IfcText;
UNIQUE
UR1 : GlobalId;
END_ENTITY;
ENTITY IfcWall
SUBTYPE OF (IfcBuildingElement);
PredefinedType : OPTIONAL IfcWallTypeEnum;
WHERE
CorrectPredefinedType : NOT(EXISTS(PredefinedType)) OR
(PredefinedType <> IfcWallTypeEnum.USERDEFINED) OR
((PredefinedType = IfcWallTypeEnum.USERDEFINED) AND EXISTS(SELF\IfcObject.ObjectType));
END_ENTITY;
END_SCHEMA;
pnpm add @ifc-factory/express-parserimport { parseExpress } from '@ifc-factory/express-parser';
import { readFileSync } from 'node:fs';
const source = readFileSync('IFC4X3_ADD2.exp', 'utf-8');
const schema = parseExpress(source);
console.log(`Schema: ${schema.name}`);
console.log(`Declarations: ${schema.declarations.length}`);for (const decl of schema.declarations) {
if (decl.kind === 'entity') {
console.log(`Entity: ${decl.name}`);
console.log(` Abstract: ${decl.abstract}`);
console.log(` Extends: ${decl.subtypeOf ?? '(none)'}`);
console.log(` Attributes: ${decl.attributes.length}`);
for (const attr of decl.attributes) {
const opt = attr.optional ? 'OPTIONAL ' : '';
console.log(` ${attr.name}: ${opt}${formatType(attr.type)}`);
}
if (decl.inverseAttributes.length > 0) {
console.log(` Inverse attributes: ${decl.inverseAttributes.length}`);
}
if (decl.whereRules.length > 0) {
console.log(` WHERE rules: ${decl.whereRules.length}`);
}
}
}for (const decl of schema.declarations) {
if (decl.kind !== 'type') continue;
const ut = decl.underlyingType;
switch (ut.kind) {
case 'enumeration':
console.log(`Enum ${decl.name}: ${ut.values.join(', ')}`);
break;
case 'select':
console.log(`Select ${decl.name}: ${ut.types.join(' | ')}`);
break;
case 'named':
console.log(`Alias ${decl.name} = ${ut.name}`);
break;
case 'aggregation':
console.log(`${decl.name} = ${ut.aggregationType} OF ${formatType(ut.elementType)}`);
break;
case 'simple':
console.log(`${decl.name} = ${ut.type}`);
break;
}
}import { tokenize } from '@ifc-factory/express-parser';
const tokens = tokenize(source);
for (const token of tokens) {
console.log(`${token.type}: ${token.value} (line ${token.line})`);
}Parses a complete EXPRESS schema and returns the AST. Throws ParseError on syntax errors.
Low-level tokenizer. Returns an array of tokens. Useful for debugging or building custom parsers.
interface SchemaNode {
name: string; // Schema name (e.g., "IFC4X3_DEV_923b0514")
declarations: Declaration[]; // All declarations in order
}type Declaration =
| TypeDeclaration
| EntityDeclaration
| FunctionDeclaration
| RuleDeclaration;interface TypeDeclaration {
kind: 'type';
name: string; // e.g., "IfcLabel"
underlyingType: UnderlyingType; // What this type resolves to
whereRules: WhereRule[]; // Optional constraints
}type UnderlyingType =
| { kind: 'enumeration'; values: string[] }
| { kind: 'select'; types: string[] }
| { kind: 'aggregation'; aggregationType: 'SET' | 'LIST' | 'BAG' | 'ARRAY'; elementType: AttributeType; bounds?: { lower: number; upper: number } }
| { kind: 'named'; name: string } // Reference to another type
| { kind: 'simple'; type: string }; // INTEGER, REAL, STRING, etc.interface EntityDeclaration {
kind: 'entity';
name: string; // e.g., "IfcWall"
abstract: boolean; // Is this entity abstract?
supertypeConstraint?: SupertypeConstraint; // SUPERTYPE OF (ONEOF(...))
subtypeOf?: string; // Parent entity name
attributes: ExplicitAttribute[]; // Own explicit attributes
deriveAttributes: DeriveAttribute[]; // DERIVE attributes
inverseAttributes: InverseAttribute[]; // INVERSE attributes
whereRules: WhereRule[]; // WHERE rules
uniqueRules: UniqueRule[]; // UNIQUE rules
}interface ExplicitAttribute {
name: string; // e.g., "GlobalId"
optional: boolean; // OPTIONAL keyword present?
type: AttributeType; // The attribute's type
}interface InverseAttribute {
name: string; // e.g., "IsDecomposedBy"
type: AttributeType; // e.g., SET OF IfcRelAggregates
forEntity: string; // The related entity type
forAttribute: string; // The related attribute name
}interface DeriveAttribute {
name: string; // e.g., "Dim"
type: AttributeType;
expression: string; // Raw EXPRESS expression string
}interface FunctionDeclaration {
kind: 'function';
name: string;
body: string; // Raw body text (not deeply parsed)
}
interface RuleDeclaration {
kind: 'rule';
name: string;
body: string; // Raw body text (not deeply parsed)
}The lexer (tokenizer) handles:
- 90+ EXPRESS keywords (SCHEMA, ENTITY, TYPE, END_ENTITY, etc.) — case-insensitive
- Identifiers — alphanumeric + underscore
-
String literals — single-quoted (
'text') -
Integers —
123,-42 -
Real numbers —
3.14,1.0E-6 -
Operators —
:=,<>,<=,>=,<,>,=,:,;,,,(,),.,\,|,[,],{,} -
Nested comments —
(* ... *)(can be nested) -
Line comments —
-- ...
The parser is a recursive descent parser:
- No external parser generator (no PEG, ANTLR, etc.)
- FUNCTION and RULE bodies are captured as raw strings (not deeply parsed) — this is sufficient for code generation
- Error handling includes source location (line, column) for debugging
- Handles EXPRESS-specific constructs like
ABSTRACT SUPERTYPE OF (ONEOF(...))andSUBTYPE OF (...)
import { parseExpress, ParseError } from '@ifc-factory/express-parser';
try {
const schema = parseExpress(source);
} catch (error) {
if (error instanceof ParseError) {
console.log(`Parse error at line ${error.location.line}, column ${error.location.column}`);
console.log(error.message);
}
}- codegen — Uses the EXPRESS AST to generate TypeScript
- schema — The output of code generation
- IFC Concepts — Background on IFC and EXPRESS
Ifc-Factory
Getting Started
Concepts
Packages
API & Reference
Development