sysml-v2-parser 0.18.0

SysML v2 textual notation parser for Rust
Documentation
//! Collect ParseError diagnostics from recovery nodes embedded in the AST.

use super::diagnostics::{bare_feature_declaration_in_part_def_diagnostic, trim_ascii_start};
use super::recovery::parse_error_from_recovery_node;
use crate::ast::{
    ActionDefBody, ActionDefBodyElement, ActionUsageBody, ActionUsageBodyElement, CalcDefBody,
    CalcDefBodyElement, ConstraintDefBody, ConstraintDefBodyElement, PackageBody,
    PackageBodyElement, PartDefBody, PartDefBodyElement, PartUsageBody, PartUsageBodyElement,
    RequirementDefBody, RequirementDefBodyElement, RootNamespace, StateDefBody,
    StateDefBodyElement, UseCaseDefBody, UseCaseDefBodyElement, ViewBody, ViewBodyElement,
    ViewDefBody, ViewDefBodyElement,
};
use crate::error::{DiagnosticCategory, DiagnosticSeverity, ParseError};
fn collect_requirement_body_errors(body: &RequirementDefBody, errors: &mut Vec<ParseError>) {
    if let RequirementDefBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                RequirementDefBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                RequirementDefBodyElement::Frame(n) => {
                    collect_requirement_body_errors(&n.value.body, errors)
                }
                _ => {}
            }
        }
    }
}

fn collect_action_def_body_errors(body: &ActionDefBody, errors: &mut Vec<ParseError>) {
    if let ActionDefBody::Brace { elements } = body {
        for element in elements {
            if let ActionDefBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_action_usage_body_errors(body: &ActionUsageBody, errors: &mut Vec<ParseError>) {
    if let ActionUsageBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                ActionUsageBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                ActionUsageBodyElement::ActionUsage(n) => {
                    collect_action_usage_body_errors(&n.value.body, errors)
                }
                _ => {}
            }
        }
    }
}

fn collect_state_body_errors(body: &StateDefBody, errors: &mut Vec<ParseError>) {
    if let StateDefBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                StateDefBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                StateDefBodyElement::Entry(n) => collect_state_body_errors(&n.value.body, errors),
                StateDefBodyElement::RequirementUsage(n) => {
                    collect_requirement_body_errors(&n.value.body, errors)
                }
                StateDefBodyElement::StateUsage(n) => {
                    collect_state_body_errors(&n.value.body, errors)
                }
                _ => {}
            }
        }
    }
}

fn collect_use_case_body_errors(body: &UseCaseDefBody, errors: &mut Vec<ParseError>) {
    if let UseCaseDefBody::Brace { elements } = body {
        for element in elements {
            if let UseCaseDefBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_constraint_body_errors(body: &ConstraintDefBody, errors: &mut Vec<ParseError>) {
    if let ConstraintDefBody::Brace { elements } = body {
        for element in elements {
            if let ConstraintDefBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_calc_body_errors(body: &CalcDefBody, errors: &mut Vec<ParseError>) {
    if let CalcDefBody::Brace { elements } = body {
        for element in elements {
            if let CalcDefBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_view_def_body_errors(body: &ViewDefBody, errors: &mut Vec<ParseError>) {
    if let ViewDefBody::Brace { elements } = body {
        for element in elements {
            if let ViewDefBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_view_body_errors(body: &ViewBody, errors: &mut Vec<ParseError>) {
    if let ViewBody::Brace { elements } = body {
        for element in elements {
            if let ViewBodyElement::Error(n) = &element.value {
                errors.push(parse_error_from_recovery_node(&element.span, &n.value));
            }
        }
    }
}

fn collect_part_def_body_errors(body: &PartDefBody, errors: &mut Vec<ParseError>) {
    if let PartDefBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                PartDefBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                PartDefBodyElement::PartUsage(n) => {
                    collect_part_usage_body_errors(&n.value.body, errors)
                }
                PartDefBodyElement::Perform(n) => {
                    collect_perform_body_errors(&n.value.body, errors)
                }
                _ => {}
            }
        }
    }
}

fn collect_perform_body_errors(body: &crate::ast::PerformBody, _errors: &mut Vec<ParseError>) {
    match body {
        crate::ast::PerformBody::Semicolon => {}
        crate::ast::PerformBody::Brace { .. } => {}
    }
}

fn collect_part_usage_body_errors(body: &PartUsageBody, errors: &mut Vec<ParseError>) {
    if let PartUsageBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                PartUsageBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                PartUsageBodyElement::PartUsage(n) => {
                    collect_part_usage_body_errors(&n.value.body, errors)
                }
                PartUsageBodyElement::Perform(n) => {
                    collect_perform_body_errors(&n.value.body, errors)
                }
                PartUsageBodyElement::StateUsage(n) => {
                    collect_state_body_errors(&n.value.body, errors)
                }
                _ => {}
            }
        }
    }
}

fn collect_package_body_errors(body: &PackageBody, errors: &mut Vec<ParseError>) {
    if let PackageBody::Brace { elements } = body {
        for element in elements {
            match &element.value {
                PackageBodyElement::Error(n) => {
                    errors.push(parse_error_from_recovery_node(&element.span, &n.value));
                }
                PackageBodyElement::Package(n) => {
                    collect_package_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::LibraryPackage(n) => {
                    collect_package_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::PartDef(n) => {
                    collect_part_def_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::PartUsage(n) => {
                    collect_part_usage_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::ActionDef(n) => {
                    collect_action_def_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::ActionUsage(n) => {
                    collect_action_usage_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::RequirementDef(n) => {
                    collect_requirement_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::RequirementUsage(n) => {
                    collect_requirement_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::UseCaseDef(n) => {
                    collect_use_case_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::UseCaseUsage(n) => {
                    collect_use_case_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::ConcernUsage(n) => {
                    collect_requirement_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::StateDef(n) => collect_state_body_errors(&n.value.body, errors),
                PackageBodyElement::StateUsage(n) => {
                    collect_state_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::ConstraintDef(n) => {
                    collect_constraint_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::CalcDef(n) => collect_calc_body_errors(&n.value.body, errors),
                PackageBodyElement::ViewDef(n) => {
                    collect_view_def_body_errors(&n.value.body, errors)
                }
                PackageBodyElement::ViewUsage(n) => collect_view_body_errors(&n.value.body, errors),
                _ => {}
            }
        }
    }
}

pub(crate) fn collect_implicit_attribute_in_part_def_warnings(bytes: &[u8]) -> Vec<ParseError> {
    let text = String::from_utf8_lossy(bytes);
    let mut errors = Vec::new();
    let mut in_part_def_body = false;
    let mut brace_depth = 0i32;
    let mut offset = 0usize;
    for (line_idx, line) in text.lines().enumerate() {
        let trimmed = line.trim();
        if trimmed.starts_with("part def") {
            in_part_def_body = false;
            brace_depth = 0;
        }
        if trimmed.contains('{') {
            if in_part_def_body || trimmed.starts_with("part def") {
                in_part_def_body = true;
            }
            brace_depth += trimmed.chars().filter(|&c| c == '{').count() as i32;
        }
        if trimmed.contains('}') {
            brace_depth -= trimmed.chars().filter(|&c| c == '}').count() as i32;
            if brace_depth <= 0 {
                in_part_def_body = false;
            }
        }
        if in_part_def_body && brace_depth > 0 {
            let skip = trimmed.starts_with("attribute")
                || trimmed.starts_with("part ")
                || trimmed.starts_with("port ")
                || trimmed.starts_with("interface")
                || trimmed.starts_with("connect")
                || trimmed.contains(":>")
                || trimmed.contains("::>")
                || trimmed.is_empty()
                || trimmed.starts_with("//")
                || trimmed.starts_with("/*")
                || trimmed.starts_with("doc ");
            if !skip {
                if let Some((code, message, expected, suggestion)) =
                    bare_feature_declaration_in_part_def_diagnostic(trimmed.as_bytes())
                {
                    let line_no = (line_idx + 1) as u32;
                    let column = line.find(trimmed).unwrap_or(0) + 1;
                    let line_offset = offset + line.find(trimmed).unwrap_or(0);
                    errors.push(
                        ParseError::new(message)
                            .with_location(line_offset, line_no, column)
                            .with_length(trimmed.len().max(1))
                            .with_code(code)
                            .with_expected(expected)
                            .with_suggestion(suggestion)
                            .with_severity(DiagnosticSeverity::Warning)
                            .with_category(DiagnosticCategory::ParseError),
                    );
                }
            }
        }
        offset += line.len() + 1;
    }
    errors
}

pub(crate) fn collect_requirement_id_dialect_diagnostics(bytes: &[u8]) -> Vec<ParseError> {
    let pattern = b"requirement def id ";
    let mut errors = Vec::new();
    let mut search_from = 0usize;
    while search_from < bytes.len() {
        let Some(rel) = bytes[search_from..]
            .windows(pattern.len())
            .position(|window| window == pattern)
        else {
            break;
        };
        let offset = search_from + rel;
        let after = trim_ascii_start(&bytes[offset + pattern.len()..]);
        if after.first() != Some(&b'\'') && after.first() != Some(&b'"') {
            search_from = offset + 1;
            continue;
        }
        let quote = after[0];
        let Some(close) = after[1..].iter().position(|&b| b == quote) else {
            search_from = offset + 1;
            continue;
        };
        let req_id = String::from_utf8_lossy(&after[1..1 + close]);
        let (line, column) = offset_to_line_column(bytes, offset);
        errors.push(
            ParseError::new(format!(
                "requirement definition uses non-standard `id '{req_id}'` syntax; use a short name in angle brackets"
            ))
            .with_location(offset, line, column)
            .with_length(pattern.len().max(1))
            .with_code("invalid_requirement_short_name_syntax")
            .with_expected("short name in angle brackets after `requirement def`".to_string())
            .with_suggestion(format!(
                "Use `requirement def <'{req_id}'> ...` instead of `requirement def id '{req_id}' ...`."
            ))
            .with_category(DiagnosticCategory::ParseError),
        );
        search_from = offset + pattern.len();
    }
    errors
}

fn offset_to_line_column(bytes: &[u8], offset: usize) -> (u32, usize) {
    let mut line = 1u32;
    let mut column = 1usize;
    for (idx, &b) in bytes.iter().enumerate() {
        if idx >= offset {
            break;
        }
        if b == b'\n' {
            line += 1;
            column = 1;
        } else {
            column += 1;
        }
    }
    (line, column)
}

pub(crate) fn collect_recovery_errors(root: &RootNamespace) -> Vec<ParseError> {
    let mut errors = Vec::new();
    for element in &root.elements {
        match &element.value {
            crate::ast::RootElement::Package(n) => {
                collect_package_body_errors(&n.value.body, &mut errors)
            }
            crate::ast::RootElement::LibraryPackage(n) => {
                collect_package_body_errors(&n.value.body, &mut errors)
            }
            crate::ast::RootElement::Namespace(n) => {
                collect_package_body_errors(&n.value.body, &mut errors)
            }
            crate::ast::RootElement::Import(_) => {}
        }
    }
    errors
}