Skip to main content

swls_lang_turtle/lang/
formatter.rs

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        // Sentinel span past the end of the document, so the trailing comment
48        // bucket sorts after every real span.
49        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            // request_newline = t.0.po.len() > 1 || t.0.po[0].0.object.len() > 1;
128        }
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        // Extract comments from the CST
381        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}