Skip to main content

swls_lang_turtle/ecs/
parse.rs

1use std::collections::HashMap;
2
3use bevy_ecs::prelude::*;
4use rdf_parsers::{turtle::SyntaxKind, PrevParseInfo};
5use rowan::NodeOrToken;
6use swls_core::prelude::*;
7use swls_lang_rdf_base::traits::TurtleExt;
8use tracing::instrument;
9
10use crate::{
11    lang::parser::{parse_new, TurtleNode},
12    TurtleLang,
13};
14
15/// Extract CST leaf tokens (skipping whitespace) and comments from the syntax tree.
16fn extract_cst_tokens(
17    syntax: &TurtleNode,
18    source: &str,
19) -> (Vec<Spanned<rowan::SyntaxKind>>, Vec<Spanned<String>>) {
20    let mut tokens = Vec::new();
21    let mut comments = Vec::new();
22
23    for node_or_token in syntax.descendants_with_tokens() {
24        if let NodeOrToken::Token(t) = node_or_token {
25            let kind = t.kind();
26            if kind == SyntaxKind::WhiteSpace {
27                continue;
28            }
29            let range = t.text_range();
30            let span = usize::from(range.start())..usize::from(range.end());
31            let rowan_kind = rowan::SyntaxKind(kind as u16);
32
33            if kind == SyntaxKind::Comment {
34                let text = t.text().to_string();
35                comments.push(spanned(text, span.clone()));
36            }
37
38            tokens.push(spanned(rowan_kind, span));
39        }
40    }
41
42    // Validate spans against source length
43    let src_len = source.len();
44    tokens.retain(|t| t.span().start <= src_len && t.span().end <= src_len);
45    comments.retain(|c| c.span().start <= src_len && c.span().end <= src_len);
46
47    (tokens, comments)
48}
49
50#[instrument(skip(query, commands, prev_infos, config), name = "parse_turtle")]
51pub fn parse_turtle_system(
52    query: Query<(Entity, &Source, &Label, Option<&Open>), (Changed<Source>, With<TurtleLang>)>,
53    mut commands: Commands,
54    mut prev_infos: Local<HashMap<String, PrevParseInfo>>,
55    config: Res<ServerConfig>,
56) {
57    if !config.config.turtle.unwrap_or(true) {
58        return;
59    }
60    for (entity, source, label, open) in &query {
61        let span = tracing::info_span!("parse_turtle", label = label.to_string());
62        let _enter = span.enter();
63
64        // For open (user-edited) documents, don't reuse incremental state.
65        let prev = if open.is_none() {
66            prev_infos.get(label.as_str())
67        } else {
68            None
69        };
70        let (turtle, errors, new_prev, syntax, node) =
71            parse_new(source.0.as_str(), label.as_str(), prev);
72
73        tracing::debug!(
74            "{} triples ({} parse errors)",
75            turtle.triples.len(),
76            errors.len()
77        );
78        if open.is_some() {
79            for e in &errors {
80                tracing::debug!("Parse error {:?}: {}", e.range, e.msg);
81            }
82        }
83
84        prev_infos.insert(label.to_string(), new_prev);
85
86        let (cst_tokens, cst_comments) = extract_cst_tokens(&syntax, source.0.as_str());
87
88        let span = 0..source.0.len();
89        let element = Element(swls_core::prelude::spanned(turtle, span));
90        if errors.is_empty() {
91            commands
92                .entity(entity)
93                .insert((
94                    element,
95                    Errors(errors),
96                    CstTokens(cst_tokens),
97                    Comments(cst_comments),
98                ))
99                .insert(Wrapped(node))
100                .remove::<Dirty>();
101        } else {
102            commands.entity(entity).insert((
103                element,
104                Errors(errors),
105                CstTokens(cst_tokens),
106                Comments(cst_comments),
107                Dirty,
108            ));
109        }
110    }
111}
112
113/// Generic ECS system that extracts RDF triples from any language whose element
114/// implements [`TurtleExt`] (i.e. uses `rdf_parsers::model::Turtle` as its parsed model).
115///
116/// Register this in your language crate's `setup_parsing()` after the parse system,
117/// using `.before(swls_core::feature::parse::triples)`.
118pub fn derive_triples_system<L: Component>(
119    query: Query<(Entity, &Label, &Element), (Changed<Element>, With<L>)>,
120    mut commands: Commands,
121) {
122    use std::sync::Arc;
123
124    for (entity, label, element) in &query {
125        match element.0.value().get_simple_triples() {
126            Ok(tripl) => {
127                let triples: Vec<_> = tripl.iter().map(|x| x.to_owned()).collect();
128                commands.entity(entity).insert(Triples(Arc::new(triples)));
129            }
130            Err(e) => {
131                tracing::warn!("derive_triples: error for {}: {:?}", label.as_str(), e);
132            }
133        }
134    }
135}