Skip to main content

swls_lang_trig/ecs/
mod.rs

1use std::collections::HashMap;
2
3use bevy_ecs::prelude::*;
4use rdf_parsers::{IncrementalBias, PrevParseInfo};
5use rowan::{GreenNode, NodeOrToken};
6use swls_core::prelude::*;
7use swls_lang_turtle::{ecs::parse::derive_triples_system, lang::parser::TurtleParseError};
8use tracing::instrument;
9
10use crate::TriGLang;
11
12pub fn setup_parsing(world: &mut World) {
13    use swls_core::feature::parse::*;
14    world.schedule_scope(ParseLabel, |_, schedule| {
15        schedule.add_systems((
16            parse_trig_system,
17            swls_lang_rdf_base::parse::derive_prefixes_system::<TriGLang>
18                .after(parse_trig_system)
19                .before(prefixes),
20            derive_triples_system::<TriGLang>
21                .after(parse_trig_system)
22                .before(triples),
23        ));
24    });
25}
26
27
28fn extract_trig_cst_tokens(
29    node: &rowan::SyntaxNode<rdf_parsers::trig::parser::Lang>,
30) -> Vec<Spanned<rowan::SyntaxKind>> {
31    use rdf_parsers::trig::parser::SyntaxKind;
32    let mut tokens = Vec::new();
33    for node_or_token in node.descendants_with_tokens() {
34        if let NodeOrToken::Token(t) = node_or_token {
35            let kind = t.kind();
36            if kind == SyntaxKind::WhiteSpace {
37                continue;
38            }
39            let range = t.text_range();
40            let span = usize::from(range.start())..usize::from(range.end());
41            tokens.push(spanned(rowan::SyntaxKind(kind as u16), span));
42        }
43    }
44    tokens
45}
46
47fn collect_errors(
48    node: &rowan::SyntaxNode<rdf_parsers::trig::parser::Lang>,
49) -> Vec<TurtleParseError> {
50    use rdf_parsers::trig::parser::SyntaxKind;
51    let mut errors = Vec::new();
52    let mut stack = vec![node.clone()];
53    while let Some(current) = stack.pop() {
54        for child in current.children_with_tokens() {
55            match child {
56                NodeOrToken::Node(n) => {
57                    if n.kind() == SyntaxKind::Error {
58                        let range = rdf_parsers::effective_error_span::<
59                            rdf_parsers::trig::parser::Lang,
60                        >(&n);
61                        let msg = n
62                            .parent()
63                            .map(|p| format!("Expected: {:?}", p.kind()))
64                            .unwrap_or_else(|| format!("Unexpected: {}", n.text()));
65                        errors.push(TurtleParseError { range, msg });
66                    } else {
67                        stack.push(n);
68                    }
69                }
70                NodeOrToken::Token(t) => {
71                    if t.kind() == SyntaxKind::Error {
72                        let r = t.text_range();
73                        errors.push(TurtleParseError {
74                            range: r.start().into()..r.end().into(),
75                            msg: format!("Unexpected: {}", t.text()),
76                        });
77                    }
78                }
79            }
80        }
81    }
82    errors
83}
84
85#[instrument(skip(query, commands, prev_infos, config))]
86fn parse_trig_system(
87    query: Query<(Entity, &Source, &Label), (Changed<Source>, With<TriGLang>)>,
88    mut commands: Commands,
89    mut prev_infos: Local<HashMap<String, PrevParseInfo>>,
90    config: Res<ServerConfig>,
91) {
92    if !config.config.trig.unwrap_or(true) {
93        return;
94    }
95    for (entity, source, label) in &query {
96        use rdf_parsers::trig::{
97            convert::convert,
98            parser::{Lang, Rule, SyntaxKind},
99        };
100
101        let prev = prev_infos.get(label.as_str());
102        let (parse, new_prev) = rdf_parsers::parse_incremental(
103            Rule::new(SyntaxKind::TrigDoc),
104            source.0.as_str(),
105            prev,
106            IncrementalBias::default(),
107        );
108
109        let gn = parse.green_node.clone();
110        prev_infos.insert(label.to_string(), new_prev);
111
112        let syntax = parse.syntax::<Lang>();
113        let errors = collect_errors(&syntax);
114        let mut trig_model = convert(&syntax);
115        trig_model.set_base = Some(label.to_string());
116
117        let cst_tokens = extract_trig_cst_tokens(&syntax);
118
119        tracing::debug!(
120            "{} triples ({} parse errors)",
121            trig_model.triples.len(),
122            errors.len()
123        );
124
125        let span = 0..source.0.len();
126        let element = Element(spanned(trig_model, span));
127
128        if errors.is_empty() {
129            commands
130                .entity(entity)
131                .insert((element, Errors(errors), CstTokens(cst_tokens), Wrapped(gn)))
132                .remove::<Dirty>();
133        } else {
134            commands.entity(entity).insert((
135                element,
136                Errors(errors),
137                CstTokens(cst_tokens),
138                Dirty,
139                Wrapped(gn),
140            ));
141        }
142    }
143}
144
145pub(crate) fn format_trig_system(
146    mut query: Query<(&RopeC, &Wrapped<GreenNode>, &mut FormatRequest), With<TriGLang>>,
147    config: Res<ServerConfig>,
148) {
149    use swls_core::lsp_types::{Position, Range};
150    if !config.config.format.trig() {
151        tracing::debug!("TriG formatting disabled by config");
152        return;
153    }
154    for (source, node, mut request) in &mut query {
155        if request.0.is_some() {
156            tracing::debug!("Didn't format with the trig format system, already formatted");
157            continue;
158        }
159        tracing::debug!("Formatting with trig format system");
160
161        let root = rowan::SyntaxNode::new_root(node.0.clone());
162
163        let formatted = rdf_parsers::trig::format::format(&root, 80);
164
165        request.0 = Some(vec![swls_core::lsp_types::TextEdit::new(
166            Range::new(
167                Position::new(0, 0),
168                Position::new(source.0.len_lines() as u32 + 1, 0),
169            ),
170            formatted,
171        )]);
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use swls_core::prelude::*;
178    use swls_test_utils::{create_file, setup_world, TestClient};
179    use test_log::test;
180
181    fn format_with(trig_enabled: Option<bool>) -> Option<String> {
182        let (mut world, _rx) = setup_world(TestClient::new(), crate::setup_world::<TestClient>);
183
184        let mut cfg = ServerConfig::default();
185        cfg.config.format.trig = trig_enabled;
186        world.insert_resource(cfg);
187
188        let entity = create_file(
189            &mut world,
190            "<a> <b> <c>;<d> <e>.",
191            "http://example.com/ns#",
192            "trig",
193            Open,
194        );
195        // ParseLabel populates the green node the formatter consumes.
196        world.run_schedule(ParseLabel);
197
198        world.entity_mut(entity).insert(FormatRequest(None));
199        world.run_schedule(FormatLabel);
200
201        world
202            .entity_mut(entity)
203            .take::<FormatRequest>()
204            .and_then(|r| r.0)
205            .map(|edits| edits[0].new_text.clone())
206    }
207
208    #[test]
209    fn trig_formatting_off_by_default() {
210        assert!(
211            format_with(None).is_none(),
212            "TriG formatting should be disabled unless explicitly enabled"
213        );
214    }
215
216    #[test]
217    fn trig_formatting_can_be_enabled() {
218        let formatted = format_with(Some(true));
219        assert!(
220            formatted.is_some(),
221            "TriG formatting should run when enabled via config"
222        );
223        assert!(formatted.unwrap().contains("<a> <b> <c>;"));
224    }
225}