swls_lang_trig/ecs/
mod.rs1use 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 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}