///|
fn Cursor::expect_char(
self : Cursor,
expected_character : Char,
) -> Unit raise ParseError {
self.skip_space()
match self.advance() {
Some(character) if character == expected_character => ()
Some(character) =>
raise ParseError::UnexpectedCharacter(
position=self.position(),
character~,
)
None => raise unexpected_end(self.offset, self.line, self.column)
}
}
///|
fn Cursor::expect_symbol(
self : Cursor,
expected_symbol : String,
) -> Unit raise ParseError {
let actual = self.read_symbol()
if actual != expected_symbol {
raise expected(self.offset, self.line, self.column, expected_symbol)
}
}
///|
fn Cursor::read_step_string(self : Cursor) -> String raise ParseError {
self.skip_space()
match self.advance() {
Some('\'') => ()
Some(character) =>
raise ParseError::UnexpectedCharacter(
position=self.position(),
character~,
)
None => raise unexpected_end(self.offset, self.line, self.column)
}
let output = StringBuilder()
for ;; {
match self.advance() {
None => raise unexpected_end(self.offset, self.line, self.column)
Some('\\') => {
output.write_char('\\')
match self.advance() {
None => raise unexpected_end(self.offset, self.line, self.column)
Some(character) => output.write_char(character)
}
}
Some('\'') =>
match self.peek() {
Some('\'') => {
ignore(self.advance())
output.write_char('\'')
}
_ => return decode_step_string(output.to_string())
}
Some(character) => output.write_char(character)
}
}
}
///|
fn Cursor::read_number_text(self : Cursor) -> String raise ParseError {
self.skip_space()
let start = self.offset
match self.peek() {
Some('-' | '+') => ignore(self.advance())
_ => ()
}
let mut saw_digit = false
for ;; {
match self.peek() {
Some('0'..='9' | '.' | 'e' | 'E' | '+' | '-') => {
let character = self.peek()
if character is Some('0'..='9') {
saw_digit = true
}
ignore(self.advance())
}
_ => break
}
}
if !saw_digit {
raise ParseError::InvalidNumber(
position=self.position(),
text=self.source[start:self.offset].to_owned(),
)
}
self.source[start:self.offset].to_owned()
}
///|
fn Cursor::read_value(self : Cursor) -> Value raise ParseError {
self.skip_space()
match self.peek() {
Some('$') => {
ignore(self.advance())
Null
}
Some('*') => {
ignore(self.advance())
Omitted
}
Some('#') => {
ignore(self.advance())
let position = self.position()
let text = self.read_number_text()
Reference(parse_int64(text, position).to_int())
}
Some('\'') => String(self.read_step_string())
Some('"') => {
ignore(self.advance())
let text = self.read_until('"')
Binary(text)
}
Some('.') => {
ignore(self.advance())
let value = self.read_symbol()
self.expect_char('.')
Enumeration(value)
}
Some('(') => self.read_list()
Some('+' | '-' | '0'..='9') => {
let position = self.position()
let text = self.read_number_text()
if text.contains_char('.') ||
text.contains_char('e') ||
text.contains_char('E') {
Real(parse_double(text, position))
} else {
Integer(parse_int64(text, position))
}
}
Some(_) => {
let name = self.read_symbol()
self.skip_space()
match self.peek() {
Some('(') => {
ignore(self.advance())
let inner = self.read_value()
self.expect_char(')')
Typed(name~, value=inner)
}
_ => Enumeration(name)
}
}
None => raise unexpected_end(self.offset, self.line, self.column)
}
}
///|
fn Cursor::read_list(self : Cursor) -> Value raise ParseError {
self.expect_char('(')
let values = Array::new()
self.skip_space()
if self.peek() is Some(')') {
ignore(self.advance())
return List(values)
}
for ;; {
values.push(self.read_value())
self.skip_space()
match self.advance() {
Some(',') => ()
Some(')') => break
Some(character) =>
raise ParseError::UnexpectedCharacter(
position=self.position(),
character~,
)
None => raise unexpected_end(self.offset, self.line, self.column)
}
}
List(values)
}
///|
fn Cursor::read_statement(
self : Cursor,
) -> (String, Array[Value]) raise ParseError {
let name = self.read_symbol()
self.expect_char('(')
let values = Array::new()
self.skip_space()
if self.peek() is Some(')') {
ignore(self.advance())
} else {
for ;; {
values.push(self.read_value())
self.skip_space()
match self.advance() {
Some(',') => ()
Some(')') => break
Some(character) =>
raise ParseError::UnexpectedCharacter(
position=self.position(),
character~,
)
None => raise unexpected_end(self.offset, self.line, self.column)
}
}
}
self.expect_char(';')
(name, values)
}
///|
fn values_to_strings(value : Value) -> Array[String] {
match value {
List(values) =>
values.fold(init=Array::new(), (result, item) => {
match item.as_string() {
Some(text) => {
result.push(text)
result
}
None => result
}
})
_ => []
}
}
///|
fn value_string(value : Value?) -> String? {
match value {
Some(item) => item.as_string()
None => None
}
}
///|
fn parse_header_section(cursor : Cursor) -> Header raise ParseError {
let mut description : Array[String] = []
let mut file_name : String? = None
let mut time_stamp : String? = None
let mut authors : Array[String] = []
let mut organizations : Array[String] = []
let mut preprocessor_version : String? = None
let mut originating_system : String? = None
let mut authorization : String? = None
let mut schema_identifiers : Array[String] = []
for ;; {
let name = cursor.read_symbol()
if name == "ENDSEC" {
cursor.expect_char(';')
break
}
cursor.expect_char('(')
let values = Array::new()
cursor.skip_space()
if cursor.peek() is Some(')') {
ignore(cursor.advance())
} else {
for ;; {
values.push(cursor.read_value())
cursor.skip_space()
match cursor.advance() {
Some(',') => ()
Some(')') => break
Some(character) =>
raise ParseError::UnexpectedCharacter(
position=cursor.position(),
character~,
)
None =>
raise unexpected_end(cursor.offset, cursor.line, cursor.column)
}
}
}
cursor.expect_char(';')
if name == "FILE_DESCRIPTION" {
description = values.get(0).map(values_to_strings).unwrap_or([])
} else if name == "FILE_NAME" {
file_name = value_string(values.get(0))
time_stamp = value_string(values.get(1))
authors = values.get(2).map(values_to_strings).unwrap_or([])
organizations = values.get(3).map(values_to_strings).unwrap_or([])
preprocessor_version = value_string(values.get(4))
originating_system = value_string(values.get(5))
authorization = value_string(values.get(6))
} else if name == "FILE_SCHEMA" {
schema_identifiers = values.get(0).map(values_to_strings).unwrap_or([])
}
}
Header::{
description,
file_name,
time_stamp,
authors,
organizations,
preprocessor_version,
originating_system,
authorization,
schema_identifiers,
}
}
///|
fn parse_data_section(cursor : Cursor) -> Array[Entity] raise ParseError {
let entities = Array::new()
for ;; {
cursor.skip_space()
if cursor.peek() is Some('E') {
let save_offset = cursor.offset
let save_line = cursor.line
let save_column = cursor.column
let word = cursor.read_symbol()
if word == "ENDSEC" {
cursor.expect_char(';')
break
}
cursor.offset = save_offset
cursor.line = save_line
cursor.column = save_column
}
cursor.expect_char('#')
let position = cursor.position()
let id = parse_int64(cursor.read_number_text(), position).to_int()
if id <= 0 {
raise ParseError::InvalidEntity(position~, text="#" + id.to_string())
}
cursor.expect_char('=')
let (name, arguments) = cursor.read_statement()
entities.push(Entity::{ id, name, arguments })
}
entities
}
///|
/// Parse an IFC2x3 or IFC4 ISO 10303-21 physical file.
pub fn parse(source : String) -> Model raise ParseError {
let cursor = Cursor::new(source)
cursor.expect_symbol("ISO-10303-21")
cursor.expect_char(';')
cursor.expect_symbol("HEADER")
cursor.expect_char(';')
let header = parse_header_section(cursor)
if header.schema_identifiers.length() == 0 {
raise ParseError::InvalidHeader(
position=SourcePosition::{ offset: 0, line: 1, column: 1 },
text="FILE_SCHEMA is missing",
)
}
cursor.expect_symbol("DATA")
cursor.expect_char(';')
let entities = parse_data_section(cursor)
cursor.expect_symbol("END-ISO-10303-21")
cursor.expect_char(';')
cursor.skip_space()
if !cursor.eof() {
raise ParseError::UnsupportedFile(
text="trailing data after END-ISO-10303-21",
)
}
Model::{ header, entities }
}
///|
pub fn parse_many(source : String) -> Array[Model] raise ParseError {
[parse(source)]
}