///|
fn stream_char(source : String, offset : Int) -> Char? {
source.get_char(offset)
}
///|
fn stream_is_space(character : Char) -> Bool {
character == ' ' ||
character == '\n' ||
character == '\r' ||
character == '\t' ||
character == '\u{000c}'
}
///|
fn stream_is_digit(character : Char) -> Bool {
character is ('0'..='9')
}
///|
fn stream_is_identifier(character : Char) -> Bool {
character is ('A'..='Z') || character is ('a'..='z') || character == '_'
}
///|
fn stream_skip_space(source : String, offset : Int) -> Int {
let mut cursor = offset
for ;; {
match stream_char(source, cursor) {
Some(character) if stream_is_space(character) => cursor += 1
Some('/') if stream_char(source, cursor + 1) == Some('/') => {
cursor += 2
for ;; {
match stream_char(source, cursor) {
Some('\n') | None => break
Some(_) => cursor += 1
}
}
}
Some('-') if stream_char(source, cursor + 1) == Some('-') => {
cursor += 2
for ;; {
match stream_char(source, cursor) {
Some('\n') | None => break
Some(_) => cursor += 1
}
}
}
_ => return cursor
}
}
}
///|
fn stream_read_digits(source : String, offset : Int) -> (Int, Int) {
let mut cursor = offset
for ;; {
match stream_char(source, cursor) {
Some(character) if stream_is_digit(character) => cursor += 1
_ => break
}
}
(offset, cursor)
}
///|
fn stream_parse_int(source : String, start : Int, end : Int) -> Int {
let text = source[start:end].to_owned()
@strconv.from_str(text) catch {
_ => 0
}
}
///|
fn stream_read_name(source : String, offset : Int) -> (String, Int) {
let mut cursor = offset
for ;; {
match stream_char(source, cursor) {
Some(character) if stream_is_identifier(character) ||
stream_is_digit(character) => cursor += 1
_ => break
}
}
(source[offset:cursor].to_owned(), cursor)
}
///|
fn stream_record_end(source : String, start : Int) -> (Int, Int) {
let mut cursor = start
let mut depth = 0
let mut line = 1
let mut in_string = false
for ;; {
match stream_char(source, cursor) {
None => return (source.length(), line)
Some('\\') if in_string => cursor += 2
Some('\'') =>
if stream_char(source, cursor + 1) == Some('\'') && in_string {
cursor += 2
} else {
in_string = !in_string
cursor += 1
}
Some('(') if !in_string => {
depth += 1
cursor += 1
}
Some(')') if !in_string => {
if depth > 0 {
depth -= 1
}
cursor += 1
}
Some(';') if !in_string && depth == 0 => return (cursor + 1, line)
Some('\n') => {
line += 1
cursor += 1
}
Some(_) => cursor += 1
}
}
}
///|
fn stream_header(source : String, start : Int) -> (Int, String)? {
let mut cursor = start
match stream_char(source, cursor) {
Some('#') => cursor += 1
_ => return None
}
let (id_start, id_end) = stream_read_digits(source, cursor)
if id_start == id_end {
return None
}
cursor = id_end
match stream_char(source, cursor) {
Some('=') => cursor += 1
_ => return None
}
cursor = stream_skip_space(source, cursor)
let (name, _) = stream_read_name(source, cursor)
Some((stream_parse_int(source, id_start, id_end), name))
}
///|
fn count_character_outside_string(text : String, wanted : Char) -> Int {
let mut count = 0
let mut cursor = 0
let mut in_string = false
for ;; {
match stream_char(text, cursor) {
None => break
Some('\\') if in_string => cursor += 2
Some('\'') =>
if stream_char(text, cursor + 1) == Some('\'') && in_string {
cursor += 2
} else {
in_string = !in_string
cursor += 1
}
Some(character) => {
if !in_string && character == wanted {
count += 1
}
cursor += 1
}
}
}
count
}
///|
fn stream_find(text : String, needle : String) -> Int? {
if needle.length() > text.length() {
return None
}
let last = text.length() - needle.length()
for index in 0..<=last {
if text[index:].has_prefix(needle) {
return Some(index)
}
}
None
}
///|
fn stream_count_character(text : String, wanted : Char) -> Int {
let mut count = 0
for index in 0.. Array[StepRecord] {
let records : Array[StepRecord] = []
let mut cursor = 0
let mut line = 1
for ;; {
cursor = stream_skip_space(source, cursor)
match stream_char(source, cursor) {
None => break
Some('#') => {
let start = cursor
let (end, relative_lines) = stream_record_end(source, start)
match stream_header(source, start) {
Some((id, name)) => {
let raw = source[start:end].to_owned()
records.push({
id,
name,
start_offset: start,
end_offset: end,
line,
raw,
})
}
None => ()
}
line += relative_lines - 1
cursor = end
}
Some(character) => {
if character == '\n' {
line += 1
}
cursor += 1
}
}
}
records
}
///|
pub fn stream_entities(source : String) -> Array[StepRecord] {
scan_step_records(source)
}
///|
fn stream_schema(source : String) -> String? {
let upper = source.to_upper()
match stream_find(upper, "FILE_SCHEMA") {
None => None
Some(start) => {
let tail = source[start:]
match stream_find(tail.to_owned(), "'") {
Some(first) => {
let remaining = tail[first + 1:]
stream_find(remaining.to_owned(), "'").map(second => {
remaining[:second].to_owned()
})
}
None => None
}
}
}
}
///|
/// Calculate source and record statistics with a single record scan.
pub fn stream_statistics(source : String) -> StreamStats {
let records = scan_step_records(source)
let mut max_record_length = 0
let mut reference_count = 0
for record in records {
if record.raw.length() > max_record_length {
max_record_length = record.raw.length()
}
reference_count += count_character_outside_string(record.raw, '#')
}
{
source_length: source.length(),
record_count: records.length(),
reference_count,
max_record_length,
comment_count: count_character_outside_string(source, '/') / 2,
line_count: stream_count_character(source, '\n') + 1,
schema: stream_schema(source),
}
}
///|
/// Parse one scanned record through the normal lossless parser.
pub fn parse_step_record(record : StepRecord) -> Entity raise ParseError {
let source = "ISO-10303-21;HEADER;FILE_SCHEMA(('IFC4'));ENDSEC;DATA;" +
record.raw +
"ENDSEC;END-ISO-10303-21;"
parse(source).entities[0]
}
///|
pub fn records_named(source : String, name : String) -> Array[StepRecord] {
let wanted = name.to_upper()
scan_step_records(source).filter(record => record.name.to_upper() == wanted)
}
///|
pub fn records_in_range(
source : String,
start_offset : Int,
end_offset : Int,
) -> Array[StepRecord] {
scan_step_records(source).filter(record => {
record.start_offset >= start_offset && record.end_offset <= end_offset
})
}
///|
pub fn StepRecord::length(self : StepRecord) -> Int {
self.end_offset - self.start_offset
}
///|
pub fn StepRecord::is_relationship(self : StepRecord) -> Bool {
self.name.to_upper().has_prefix("IFCREL")
}
///|
pub fn StepRecord::reference_count(self : StepRecord) -> Int {
count_character_outside_string(self.raw, '#')
}
///|
pub fn StreamStats::density(self : StreamStats) -> Double {
if self.source_length == 0 {
0.0
} else {
self.record_count.to_double() / self.source_length.to_double()
}
}
///|
pub fn StreamStats::headline(self : StreamStats) -> String {
"records=" +
self.record_count.to_string() +
" refs=" +
self.reference_count.to_string() +
" bytes=" +
self.source_length.to_string() +
" max_record=" +
self.max_record_length.to_string()
}