1use std::{
2 io::{self, Cursor, Write},
3 ops::Range,
4};
5
6use swls_core::text::LineIndex;
7use swls_core::{
8 lsp_types::FormattingOptions,
9 prelude::{spanned, Spanned},
10};
11use tracing::{trace, warn};
12
13use swls_lang_rdf_base::traits::{Base, BlankNode, Term, Triple, Turtle, TurtlePrefix, PO};
14
15type Buf = Cursor<Vec<u8>>;
16#[derive(Default, Clone, Copy)]
17pub enum PrefixStyle {
18 #[default]
19 OldSchool,
20 Sparql,
21}
22struct FormatState<'a> {
23 indent_level: usize,
24 indent: String,
25 buf: Buf,
26 line_start: u64,
27 comments: &'a [Spanned<String>],
28 comments_idx: usize,
29 tail: Spanned<String>,
30 line_count: usize,
31 prefix_style: PrefixStyle,
32}
33
34impl<'a> FormatState<'a> {
35 fn new(
36 options: FormattingOptions,
37 buf: Buf,
38 comments: &'a [Spanned<String>],
39 source: &'a LineIndex,
40 prefix_style: PrefixStyle,
41 ) -> Self {
42 let mut indent = String::new();
43 for _ in 0..options.tab_size {
44 indent.push(' ');
45 }
46
47 let tail = spanned(
50 String::new(),
51 source.len_bytes() + 1..source.len_bytes() + 1,
52 );
53
54 Self {
55 tail,
56 line_start: 0,
57 indent_level: 0,
58 indent,
59 buf,
60 comments,
61 comments_idx: 0,
62 line_count: 0,
63 prefix_style,
64 }
65 }
66
67 fn check_comments(&mut self, span: &Range<usize>) -> io::Result<bool> {
68 trace!("checking comments with span {:?}", span);
69 let mut first = true;
70 loop {
71 let current = self.comments.get(self.comments_idx).unwrap_or(&self.tail);
72
73 if current.1.start > span.start {
74 break;
75 }
76
77 first = false;
78 write!(self.buf, "{}", current.0)?;
79 self.new_line()?;
80 self.comments_idx += 1;
81 }
82 Ok(!first)
83 }
84 fn current_line_length(&self) -> u64 {
85 self.buf.position() - self.line_start
86 }
87 fn new_line(&mut self) -> io::Result<()> {
88 self.line_count += 1;
89 write!(self.buf, "\n")?;
90 self.line_start = self.buf.position();
91 for _ in 0..self.indent_level {
92 write!(self.buf, "{}", &self.indent)?;
93 }
94 Ok(())
95 }
96 fn inc(&mut self) {
97 self.indent_level += 1;
98 }
99 fn decr(&mut self) {
100 self.indent_level -= 1;
101 }
102}
103
104impl FormatState<'_> {
105 fn write_turtle(&mut self, turtle: &Turtle) -> io::Result<()> {
106 if let Some(ref b) = turtle.base {
107 self.check_comments(&b.1)?;
108 self.write_base(b)?;
109 self.new_line()?;
110 }
111 for p in &turtle.prefixes {
112 self.check_comments(&p.1)?;
113 self.write_prefix(p)?;
114 self.new_line()?;
115 }
116
117 let mut prev_line = 0;
118
119 for t in &turtle.triples {
120 if prev_line + 1 < self.line_count {
121 self.new_line()?;
122 }
123 prev_line = self.line_count;
124 self.check_comments(&t.1)?;
125 self.write_triple(&t)?;
126 self.new_line()?;
127 }
129 self.new_line()?;
130
131 for i in self.comments_idx..self.comments.len() {
132 write!(self.buf, "{}", self.comments[i].0)?;
133 self.new_line()?;
134 }
135
136 Ok(())
137 }
138
139 fn write_prefix(&mut self, prefix: &TurtlePrefix) -> io::Result<()> {
140 match self.prefix_style {
141 PrefixStyle::OldSchool => {
142 write!(self.buf, "@prefix {}: {}.", prefix.prefix.0, prefix.value.0)
143 }
144 PrefixStyle::Sparql => {
145 write!(self.buf, "PREFIX {}: {}", prefix.prefix.0, prefix.value.0)
146 }
147 }
148 }
149
150 fn write_base(&mut self, base: &Base) -> io::Result<()> {
151 match self.prefix_style {
152 PrefixStyle::OldSchool => {
153 write!(self.buf, "@base {}.", base.1 .0)
154 }
155 PrefixStyle::Sparql => {
156 write!(self.buf, "BASE {}", base.1 .0)
157 }
158 }
159 }
160
161 fn write_bnode(&mut self, bnode: &BlankNode) -> io::Result<()> {
162 match bnode {
163 BlankNode::Named(x, _) => write!(self.buf, "_:{}", x)?,
164 BlankNode::Unnamed(pos, _, _) => {
165 if pos.len() == 0 {
166 return write!(self.buf, "[ ]");
167 }
168 if pos.len() == 1 {
169 write!(self.buf, "[ ")?;
170 self.write_po(&pos[0])?;
171 return write!(self.buf, " ]");
172 }
173 let is_first_of_line = self.current_line_length() == 0;
174 self.inc();
175 write!(self.buf, "[")?;
176 let should_skip = if is_first_of_line {
177 write!(self.buf, " ")?;
178 self.write_po(&pos[0])?;
179 write!(self.buf, ";")?;
180 1
181 } else {
182 0
183 };
184 for po in pos.iter().skip(should_skip) {
185 self.new_line()?;
186 self.check_comments(&po.1)?;
187 self.write_po(&po)?;
188 write!(self.buf, ";")?;
189 }
190 self.decr();
191 self.new_line()?;
192 write!(self.buf, "]")?;
193 }
194 BlankNode::Invalid => return Err(io::Error::new(io::ErrorKind::Other, "")),
195 }
196 Ok(())
197 }
198
199 fn write_collection(&mut self, coll: &Vec<Spanned<Term>>) -> io::Result<()> {
200 if coll.is_empty() {
201 return write!(self.buf, "( )");
202 }
203
204 let mut should_indent = false;
205 let start = self.buf.position();
206 let current_line = self.line_count;
207
208 write!(self.buf, "( ")?;
209
210 self.check_comments(&coll[0].1)?;
211 self.write_term(&coll[0])?;
212
213 for po in coll.iter().skip(1) {
214 self.check_comments(&po.1)?;
215 write!(self.buf, " ")?;
216 self.write_term(&po)?;
217 if self.current_line_length() > 80 {
218 should_indent = true;
219 break;
220 }
221 }
222 write!(self.buf, " )")?;
223
224 if should_indent {
225 self.buf.set_position(start);
226 self.line_count = current_line;
227 write!(self.buf, "(")?;
228 self.inc();
229 for po in coll.iter() {
230 self.new_line()?;
231 self.check_comments(&po.1)?;
232 self.write_term(&po)?;
233 }
234 self.decr();
235 self.new_line()?;
236 write!(self.buf, ")")?;
237 }
238
239 Ok(())
240 }
241
242 fn write_term(&mut self, term: &Term) -> io::Result<()> {
243 match term {
244 Term::Literal(s) => write!(self.buf, "{}", s)?,
245 Term::BlankNode(b) => self.write_bnode(b)?,
246 Term::NamedNode(n) => write!(self.buf, "{}", n)?,
247 Term::Collection(ts) => self.write_collection(ts)?,
248 Term::Invalid => {
249 return Err(io::Error::new(
250 io::ErrorKind::Other,
251 "cannot format turtle with invalid terms",
252 ))
253 }
254 Term::Variable(_) => {
255 return Err(io::Error::new(
256 io::ErrorKind::Other,
257 "cannot format turtle with variables",
258 ))
259 }
260 }
261 Ok(())
262 }
263
264 fn write_po(&mut self, po: &PO) -> io::Result<()> {
265 write!(self.buf, "{} ", po.predicate.0)?;
266 self.write_term(&po.object[0])?;
267 let mut should_indent = false;
268
269 let start = self.buf.position();
270 let current_line = self.line_count;
271 for i in 1..po.object.len() {
272 write!(self.buf, ", ")?;
273 self.write_term(&po.object[i])?;
274
275 if self.current_line_length() > 80 {
276 should_indent = true;
277 break;
278 }
279 }
280
281 if should_indent {
282 self.buf.set_position(start);
283 self.line_count = current_line;
284 self.inc();
285 for i in 1..po.object.len() {
286 write!(self.buf, ",")?;
287 self.new_line()?;
288 self.check_comments(&po.object[i].1)?;
289 self.write_term(&po.object[i])?;
290 }
291 self.decr();
292 }
293
294 Ok(())
295 }
296
297 fn write_triple(&mut self, triple: &Triple) -> io::Result<()> {
298 match &triple.subject.0 {
299 Term::BlankNode(bn) => self.write_bnode(bn)?,
300 Term::NamedNode(n) => write!(self.buf, "{}", n)?,
301 _ => write!(self.buf, "invalid")?,
302 }
303 if triple.po.is_empty() {
304 write!(self.buf, ".")?;
305 return Ok(());
306 }
307 write!(self.buf, " ")?;
308 self.write_po(&triple.po[0])?;
309 if triple.po.len() == 1 {
310 write!(self.buf, ".")?;
311 return Ok(());
312 }
313 write!(self.buf, ";")?;
314 self.inc();
315
316 self.new_line()?;
317 self.check_comments(&triple.po[1].1)?;
318 self.write_po(&triple.po[1])?;
319
320 if triple.po.len() == 2 {
321 self.decr();
322 write!(self.buf, ".")?;
323 return Ok(());
324 }
325
326 for i in 2..triple.po.len() {
327 write!(self.buf, ";")?;
328 self.new_line()?;
329 self.check_comments(&triple.po[i].1)?;
330 self.write_po(&triple.po[i])?;
331 }
332
333 write!(self.buf, ".")?;
334 self.decr();
335 Ok(())
336 }
337}
338
339pub fn format_turtle(
340 turtle: &Turtle,
341 config: FormattingOptions,
342 comments: &[Spanned<String>],
343 source: &LineIndex,
344 prefix_style: impl Into<PrefixStyle>,
345) -> Option<String> {
346 let buf: Buf = Cursor::new(Vec::new());
347 let mut state = FormatState::new(config, buf, comments, source, prefix_style.into());
348 match state.write_turtle(turtle) {
349 Ok(_) => tracing::debug!("Format successful"),
350 Err(e) => {
351 warn!("Format unsuccessful {:?}", e);
352 return None;
353 }
354 }
355 String::from_utf8(state.buf.into_inner()).ok()
356}
357
358#[cfg(test)]
359mod tests {
360
361 use std::str::FromStr;
362
363 use rdf_parsers::turtle::SyntaxKind;
364 use rowan::NodeOrToken;
365 use swls_core::prelude::Spanned;
366 use swls_core::text::LineIndex;
367
368 use crate::lang::{
369 formatter::{format_turtle, PrefixStyle},
370 parser::parse_new,
371 Turtle,
372 };
373
374 fn parse_turtle(inp: &str, url: &swls_core::lsp_types::Url) -> (Turtle, Vec<Spanned<String>>) {
375 let (turtle, errs, _, syntax, _) = parse_new(inp, url.as_str(), None);
376 for e in &errs {
377 println!("Parse error: {:?}", e);
378 }
379
380 let mut comments: Vec<Spanned<String>> = Vec::new();
382 for node_or_token in syntax.descendants_with_tokens() {
383 if let NodeOrToken::Token(t) = node_or_token {
384 if t.kind() == SyntaxKind::Comment {
385 let range = t.text_range();
386 let span = usize::from(range.start())..usize::from(range.end());
387 let text = t.text().to_string();
388 comments.push(swls_core::prelude::spanned(text, span));
389 }
390 }
391 }
392 comments.sort_by_key(|x| x.1.start);
393
394 (turtle, comments)
395 }
396
397 #[test]
398 fn easy_format() {
399 let txt = r#"
400@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
401@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
402@base <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
403
404[] a foaf:Name;
405 foaf:knows <abc>;.
406"#;
407
408 let expected = r#"@base <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
409@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
410@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
411
412[ ] a foaf:Name;
413 foaf:knows <abc>.
414
415"#;
416
417 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
418 let (output, comments) = parse_turtle(txt, &url);
419 let formatted = format_turtle(
420 &output,
421 swls_core::lsp_types::FormattingOptions {
422 tab_size: 2,
423 ..Default::default()
424 },
425 &comments,
426 &LineIndex::new(txt),
427 PrefixStyle::OldSchool,
428 )
429 .expect("formatting");
430 assert_eq!(formatted, expected);
431 }
432
433 #[test]
434 fn harder_format_pos() {
435 let txt = r#"
436[] a foaf:Name;
437 foaf:knows <abc>; foaf:knows2 <abc>.
438
439"#;
440
441 let expected = r#"[ ] a foaf:Name;
442 foaf:knows <abc>;
443 foaf:knows2 <abc>.
444
445"#;
446
447 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
448 let (output, comments) = parse_turtle(txt, &url);
449 let formatted = format_turtle(
450 &output,
451 swls_core::lsp_types::FormattingOptions {
452 tab_size: 2,
453 ..Default::default()
454 },
455 &comments,
456 &LineIndex::new(txt),
457 PrefixStyle::OldSchool,
458 )
459 .expect("formatting");
460 assert_eq!(formatted, expected);
461 }
462
463 #[test]
464 fn format_blanknodes() {
465 let txt = r#"
466 [ <a> foaf:Person; foaf:knows <abc>; foaf:knows <def> ] foaf:knows [
467 a foaf:Person;
468 foaf:knows <abc>;
469 foaf:knows <def>;
470 ] .
471
472"#;
473
474 let expected = r#"[ <a> foaf:Person;
475 foaf:knows <abc>;
476 foaf:knows <def>;
477] foaf:knows [
478 a foaf:Person;
479 foaf:knows <abc>;
480 foaf:knows <def>;
481].
482
483"#;
484
485 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
486 let (output, comments) = parse_turtle(txt, &url);
487 let formatted = format_turtle(
488 &output,
489 swls_core::lsp_types::FormattingOptions {
490 tab_size: 2,
491 ..Default::default()
492 },
493 &comments,
494 &LineIndex::new(txt),
495 PrefixStyle::OldSchool,
496 )
497 .expect("formatting");
498 assert_eq!(formatted, expected);
499 }
500
501 #[test]
502 fn long_objectlist() {
503 let txt = r#"
504 <abc> a <something-long>, <something-longer-still>, <something-longer>, <something-tes>, <soemthing-eeeellssee>.
505"#;
506
507 let expected = r#"<abc> a <something-long>,
508 <something-longer-still>,
509 <something-longer>,
510 <something-tes>,
511 <soemthing-eeeellssee>.
512
513"#;
514
515 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
516 let (output, comments) = parse_turtle(txt, &url);
517 let formatted = format_turtle(
518 &output,
519 swls_core::lsp_types::FormattingOptions {
520 tab_size: 2,
521 ..Default::default()
522 },
523 &comments,
524 &LineIndex::new(txt),
525 PrefixStyle::OldSchool,
526 )
527 .expect("formatting");
528 assert_eq!(formatted, expected);
529 }
530
531 #[test]
532 fn short_collection() {
533 let txt = r#"
534 <abc> a (), (<abc> <def>).
535"#;
536
537 let expected = r#"<abc> a ( ), ( <abc> <def> ).
538
539"#;
540
541 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
542 let (output, comments) = parse_turtle(txt, &url);
543 let formatted = format_turtle(
544 &output,
545 swls_core::lsp_types::FormattingOptions {
546 tab_size: 2,
547 ..Default::default()
548 },
549 &comments,
550 &LineIndex::new(txt),
551 PrefixStyle::OldSchool,
552 )
553 .expect("formatting");
554 assert_eq!(formatted, expected);
555 }
556
557 #[test]
558 fn long_collection() {
559 let txt = r#"
560 <abc> a (), (<somevery-very-very-long-item> <and-othersss> <and-ottteeehs> <wheeeeeeeeeeeee>).
561"#;
562
563 let expected = r#"<abc> a ( ), (
564 <somevery-very-very-long-item>
565 <and-othersss>
566 <and-ottteeehs>
567 <wheeeeeeeeeeeee>
568).
569
570"#;
571
572 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
573 let (output, comments) = parse_turtle(txt, &url);
574 let formatted = format_turtle(
575 &output,
576 swls_core::lsp_types::FormattingOptions {
577 tab_size: 2,
578 ..Default::default()
579 },
580 &comments,
581 &LineIndex::new(txt),
582 PrefixStyle::OldSchool,
583 )
584 .expect("formatting");
585 assert_eq!(formatted, expected);
586 }
587
588 #[test]
589 fn easy_comments() {
590 let txt = r#"
591# Test this is a cool test or something!
592 # Another comment!
593
594[] a foaf:Name;
595 foaf:knows <abc>; foaf:knows2 <abc>.
596
597"#;
598
599 let expected = r#"# Test this is a cool test or something!
600# Another comment!
601[ ] a foaf:Name;
602 foaf:knows <abc>;
603 foaf:knows2 <abc>.
604
605"#;
606
607 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
608 let (output, comments) = parse_turtle(txt, &url);
609 println!("OUtput {:?}", output);
610 let formatted = format_turtle(
611 &output,
612 swls_core::lsp_types::FormattingOptions {
613 tab_size: 2,
614 ..Default::default()
615 },
616 &comments,
617 &LineIndex::new(txt),
618 PrefixStyle::OldSchool,
619 )
620 .expect("formatting");
621 assert_eq!(formatted, expected);
622 }
623
624 #[test]
625 fn hard_comments() {
626 let txt = r#"
627
628[] a foaf:Name; # Nested comment
629 foaf:knows <abc>; # Another comment!
630 foaf:knows2 <abc>.
631
632 #trailing comments
633"#;
634
635 let expected = r#"[ ] a foaf:Name;
636 # Nested comment
637 foaf:knows <abc>;
638 # Another comment!
639 foaf:knows2 <abc>.
640
641#trailing comments
642"#;
643 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
644 let (output, comments) = parse_turtle(txt, &url);
645 let formatted = format_turtle(
646 &output,
647 swls_core::lsp_types::FormattingOptions {
648 tab_size: 2,
649 ..Default::default()
650 },
651 &comments,
652 &LineIndex::new(txt),
653 PrefixStyle::OldSchool,
654 )
655 .expect("formatting");
656 assert_eq!(formatted, expected);
657 }
658
659 #[test]
660 fn bug_1() {
661 let txt = r#"
662[] a sh:NodeShape;
663 sh:targetClass js:Echo;
664 sh:property [
665 sh:class :ReaderChannel;
666 sh:path js:input;
667 sh:name "Input Channel"
668 ], [
669 sh:class :WriterChannel;
670 sh:path js:output;
671 sh:name "Output Channel"
672 ].
673
674"#;
675
676 let expected = r#"[ ] a sh:NodeShape;
677 sh:targetClass js:Echo;
678 sh:property [
679 sh:class :ReaderChannel;
680 sh:path js:input;
681 sh:name "Input Channel";
682 ], [
683 sh:class :WriterChannel;
684 sh:path js:output;
685 sh:name "Output Channel";
686 ].
687
688"#;
689
690 let url = swls_core::lsp_types::Url::from_str("http://example.com/ns#").unwrap();
691 let (output, comments) = parse_turtle(txt, &url);
692 let formatted = format_turtle(
693 &output,
694 swls_core::lsp_types::FormattingOptions {
695 tab_size: 2,
696 ..Default::default()
697 },
698 &comments,
699 &LineIndex::new(txt),
700 PrefixStyle::OldSchool,
701 )
702 .expect("formatting");
703 assert_eq!(formatted, expected);
704 }
705}