satysfi_parser/
types.rs

1use std::fmt::Display;
2
3use itertools::Itertools;
4
5use crate::Mode;
6
7use self::rule::Rule;
8
9pub mod rule;
10pub mod structure;
11
12pub trait Vectorize<T> {
13    /// 色んなものを強引に vector にしてしまう恐ろしいメソッド。
14    /// 構文解析ではオプショナルな構文要素があったり、配列状の構文要素があったりするため
15    /// それらを楽にまとめて1つの Vec に格納してしまうための処置。
16    fn vectorize(self) -> Vec<T>;
17}
18
19impl<T> Vectorize<T> for Vec<T> {
20    /// vec![t] のときはそのまま
21    fn vectorize(self) -> Vec<T> {
22        self
23    }
24}
25
26impl<T> Vectorize<T> for T {
27    /// t のときは vec![t] に変換される
28    fn vectorize(self) -> Vec<T> {
29        vec![self]
30    }
31}
32
33impl<T> Vectorize<T> for Vec<Vec<T>> {
34    /// T の可変長配列の concat が一発でできる
35    fn vectorize(self) -> Vec<T> {
36        let mut vec = vec![];
37        for v in self {
38            vec.extend(v);
39        }
40        vec
41    }
42}
43
44impl<T> Vectorize<T> for Option<T> {
45    /// Some(t) は vec![t] に、 None は vec![] に変換される
46    fn vectorize(self) -> Vec<T> {
47        self.into_iter().collect()
48    }
49}
50
51impl<T> Vectorize<T> for Vec<Option<T>> {
52    /// vec![ Some(a), None, Some(c), Some(d), None ] は vec![a, c, d] になる
53    fn vectorize(self) -> Vec<T> {
54        self.into_iter().filter_map(|e| e).collect()
55    }
56}
57
58impl<T> Vectorize<T> for Option<Vec<T>> {
59    fn vectorize(self) -> Vec<T> {
60        self.unwrap_or_default()
61    }
62}
63
64/// コードの範囲を表すもの。 usize 2 個ぶんなので Copyable.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct Span {
67    pub start: usize,
68    pub end: usize,
69}
70
71impl Span {
72    /// span が特定の位置を含んでいるか。
73    pub fn includes(&self, pos: usize) -> bool {
74        self.start <= pos && pos < self.end
75    }
76
77    /// span が特定のspanを完全に内部に含んでいるか。
78    pub fn contains(&self, other: &Span) -> bool {
79        self.start <= other.start && other.end <= self.end
80    }
81}
82
83/// コードを line & column 形式 (0-indexed) で指定したもの。
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub struct LineCol {
86    pub line: usize,
87    pub column: usize,
88}
89
90/// CST にテキストの情報を付加したもの。
91// TODO: 自己参照構造体にする。
92#[derive(Debug, PartialEq, Eq, Hash)]
93pub struct CstText {
94    pub text: String,
95    pub lines: Vec<usize>,
96    pub cst: Cst,
97}
98
99impl CstText {
100    /// 与えられたパーサに基づき、与えられたテキストをパースする。
101    pub fn parse<F>(
102        text: &str,
103        parser: F,
104    ) -> std::result::Result<Self, (LineCol, Vec<&'static str>)>
105    where
106        F: Fn(&str) -> std::result::Result<Cst, peg::error::ParseError<peg::str::LineCol>>,
107    {
108        let mut lines = vec![0usize];
109        lines.extend(text.match_indices('\n').map(|(p, _)| p + 1));
110        match parser(text) {
111            Ok(cst) => Ok(CstText {
112                text: text.to_owned(),
113                lines,
114                cst,
115            }),
116            Err(e) => {
117                let peg::str::LineCol { line, column, .. } = e.location;
118                let lc = LineCol {
119                    line: line - 1,
120                    column: column - 1,
121                }; // 0-indexed に変換
122                let expected: Vec<_> = e.expected.tokens().collect();
123                Err((lc, expected))
124            }
125        }
126    }
127
128    /// self.cst の子要素である Cst について、その要素に相当する text を取得する。
129    pub fn get_text_from_span(&self, span: Span) -> &str {
130        let text = self.text.as_str();
131        let Span { start, end } = span;
132        &text[start..end]
133    }
134
135    /// self.cst の子要素である Cst について、その要素に相当する text を取得する。
136    pub fn get_text(&self, cst: &Cst) -> &str {
137        let text = self.text.as_str();
138        let Span { start, end } = cst.span;
139        &text[start..end]
140    }
141
142    /// 与えられた position の line 及び col を出力する。
143    pub fn get_line_col(&self, pos: usize) -> Option<LineCol> {
144        if pos > self.text.len() {
145            return None;
146        }
147        let line = match self.lines.binary_search(&pos) {
148            Ok(i) => i,
149            Err(i) => i - 1,
150        };
151        let column = pos - self.lines[line];
152        Some(LineCol { line, column })
153    }
154
155    /// 与えられた line 及び col の position を出力する。
156    pub fn from_line_col(&self, line: usize, column: usize) -> Option<usize> {
157        let idxline = self.lines.get(line)?;
158        let max_idx = match self.lines.get(line + 1) {
159            Some(idx) => *idx,
160            None => self.text.len(),
161        };
162        if (*idxline + column) < max_idx {
163            Some(*idxline + column)
164        } else {
165            None
166        }
167    }
168
169    /// CST の構造を string にして出力する。
170    pub fn pritty_cst_recursive(&self, cst: &Cst) -> String {
171        fn print_cst(csttext: &CstText, cst: &Cst, indent: usize) -> String {
172            let mut s = String::new();
173
174            s.push_str(&"  ".repeat(indent));
175            s.push_str(&csttext.pritty_cst(cst));
176            s.push('\n');
177
178            for child in &cst.inner {
179                s.push_str(&print_cst(csttext, child, indent + 1))
180            }
181            s
182        }
183
184        print_cst(&self, cst, 0)
185    }
186
187    /// Cst を pritty 表示。
188    pub fn pritty_cst(&self, cst: &Cst) -> String {
189        let mut s = String::new();
190        s.push_str(&format!("[{:?}]", cst.rule));
191
192        // 長すぎないものだけテキストを表示
193        let Span { start, end } = cst.span;
194        let slice = &self.text[start..end];
195        if !slice.contains('\n') && slice.len() < 80 {
196            s.push_str(&format!(": \"{}\"", slice));
197        } else {
198            let LineCol {
199                line: start_line,
200                column: start_column,
201            } = self.get_line_col(start).unwrap();
202            let LineCol {
203                line: end_line,
204                column: end_column,
205            } = self.get_line_col(end).unwrap();
206            s.push_str(&format!(
207                " (L{}-C{} .. L{}-C{})",
208                start_line + 1,
209                start_column + 1,
210                end_line + 1,
211                end_column + 1
212            ));
213        }
214        s
215    }
216
217    /// 与えられた場所がコメント内かどうか判定する。
218    pub fn is_comment(&self, pos: usize) -> bool {
219        let dig = self.cst.dig(pos);
220        let cst = dig.get(0);
221        if cst.is_none() {
222            return false;
223        }
224        let cst = cst.unwrap();
225        // cst: その pos を含む最小の cst
226        // span: pos を含む終端要素
227        let span = cst
228            .get_terminal_spans()
229            .into_iter()
230            .find(|span| span.includes(pos));
231        if span.is_none() {
232            // span が見つからないことは無いと思うんだけど
233            return false;
234        }
235        let span = span.unwrap();
236
237        // TODO: まあまあアドホックなのでなんとかしたい
238        let text = self.get_text_from_span(span);
239        let char_indices = text.char_indices().map(|(idx, _)| idx).collect_vec();
240        let pos_char = char_indices
241            .binary_search(&(pos - span.start))
242            .unwrap_or_else(|x| x);
243        for c in text.chars().take(pos_char).collect_vec().into_iter().rev() {
244            match c {
245                // 改行が見つかったらそこで探索打ち切り。コメントでないこと確定
246                '\n' => return false,
247                // コメント文字が見つかったらコメント確定。
248                '%' => return true,
249                _ => continue,
250            }
251        }
252        // 改行もコメント文字も何も見つからなかったらコメントでないこと確定。
253        false
254    }
255}
256
257#[test]
258fn test_is_comment() {
259    use crate::grammar;
260
261    let csttext = CstText::parse("let x = 1 in% foo \n  2", grammar::program).unwrap();
262    assert_eq!(csttext.is_comment(11), false); // let x = 1 i"n" foo
263    assert_eq!(csttext.is_comment(12), false); // let x = 1 in"%" foo
264    assert_eq!(csttext.is_comment(13), true); // let x = 1 in%" "foo
265    assert_eq!(csttext.is_comment(14), true); // let x = 1 in% "f"oo
266    assert_eq!(csttext.is_comment(17), true); // let x = 1 in% foo" "\n
267    assert_eq!(csttext.is_comment(18), true); // let x = 1 in% foo"\n"
268    assert_eq!(csttext.is_comment(19), false); // let x = 1 in% foo \n" "
269}
270
271impl Display for CstText {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        let text = self.pritty_cst_recursive(&self.cst);
274        write!(f, "{}", text)
275    }
276}
277
278/// Concrete syntax tree.
279/// 1つの CST は構文規則、テキストの範囲、子要素からなり、全体として木構造をなす。
280#[derive(Debug, PartialEq, Eq, Clone, Hash)]
281pub struct Cst {
282    /// 構文規則。
283    pub rule: Rule,
284    pub span: Span,
285    pub inner: Vec<Cst>,
286}
287
288impl Cst {
289    /// 新たな CST を作成する。
290    pub fn new(rule: Rule, range: (usize, usize), inner: Vec<Cst>) -> Self {
291        let span = Span {
292            start: range.0,
293            end: range.1,
294        };
295        Self { rule, span, inner }
296    }
297
298    pub fn as_str<'a>(&'a self, text: &'a str) -> &'a str {
299        let Span { start, end } = self.span;
300        &text[start..end]
301    }
302
303    /// 与えられたルールの Cst を再帰的に抽出する。
304    pub fn pickup(&self, rule: Rule) -> Vec<&Cst> {
305        let mut vec = vec![];
306        for cst in &self.inner {
307            if cst.rule == rule {
308                vec.push(cst)
309            }
310            let v = cst.pickup(rule);
311            vec.extend(v);
312        }
313        vec
314    }
315
316    /// 自分の子のうち、与えられた pos を含むものを返す。
317    pub fn choose(&self, pos: usize) -> Option<&Cst> {
318        for cst in &self.inner {
319            if cst.span.includes(pos) {
320                return Some(cst);
321            }
322        }
323        None
324    }
325
326    /// 与えられた pos を含む Pair を再帰的に探索する。
327    /// 範囲が小さいものから順に返す。
328    pub fn dig(&self, pos: usize) -> Vec<&Cst> {
329        let child = self.choose(pos);
330        let mut v = if let Some(child) = child {
331            let mut v = child.dig(pos);
332            v.push(child);
333            v
334        } else {
335            vec![]
336        };
337        if self.span.includes(pos) {
338            v.push(self);
339        }
340        v
341    }
342
343    /// 自分の Cst の内部で、 child の親となる Cst
344    /// (child の scope を内包する最小の Cst)を探し、あればそれを返す。
345    pub fn get_parent(&self, child: &Cst) -> Option<&Cst> {
346        let child_pos = child.span.start;
347        self.dig(child_pos)
348            .into_iter()
349            .find(|&cst| cst.span.contains(&child.span) && cst.span != child.span)
350    }
351
352    pub fn mode(&self, pos: usize) -> Mode {
353        let csts = self.dig(pos);
354        let rules = csts.iter().map(|cst| cst.rule);
355
356        for rule in rules {
357            if let Some(mode) = rule.mode() {
358                return mode;
359            }
360        }
361        Mode::Program
362    }
363
364    /// 自身及び子要素の Cst を羅列する。
365    pub fn listup(&self) -> Vec<&Cst> {
366        let mut v = vec![];
367        v.push(self);
368        for cst in &self.inner {
369            let inner = cst.listup();
370            v.extend(inner);
371        }
372        v
373    }
374
375    /// 終端要素の Span を返す。ここでいう終端要素とは、
376    /// 自身の Cst の span には含まれているが、子の span には含まれていない範囲。
377    pub fn get_terminal_spans(&self) -> Vec<Span> {
378        let mut v = vec![];
379        let mut i = self.span.start;
380        for inner in &self.inner {
381            if i != inner.span.start {
382                v.push(Span {
383                    start: i,
384                    end: inner.span.start,
385                })
386            }
387            i = inner.span.end;
388        }
389        if i != self.span.end {
390            v.push(Span {
391                start: i,
392                end: self.span.end,
393            })
394        }
395        v
396    }
397}
398
399/// CST を楽に構成するためのマクロ。
400#[macro_export]
401macro_rules! cst {
402
403    ($rule:ident ($s:expr, $e:expr) []) => {
404        Cst {
405            rule: Rule::$rule,
406            span: Span {start: $s, end: $e},
407            inner: vec![]
408        }
409    };
410
411    ($rule:ident ($s:expr, $e:expr) [$($inner:expr),*]) => {
412        Cst {
413            rule: Rule::$rule,
414            span: Span {start: $s, end: $e},
415            inner: vec![$($inner.vectorize()),*].vectorize()
416        }
417    };
418
419}