Skip to main content

cdoc_parser/code_ast/
mod.rs

1pub mod types;
2
3use crate::code_ast::types::{CodeContent, CodeElem, Solution};
4
5use linked_hash_map::LinkedHashMap;
6use pest::iterators::Pair;
7use pest::Parser;
8use pest_derive::Parser;
9use std::collections::hash_map::DefaultHasher;
10
11use cowstr::CowStr;
12use std::hash::{Hash, Hasher};
13
14/// The parser for exercise placeholders/solutions.
15#[derive(Parser)]
16#[grammar = "grammars/exercises.pest"]
17pub struct TaskParser;
18
19pub(crate) fn parse_code_placeholder_block(
20    content: &CowStr,
21    pair: Pair<Rule>,
22) -> Result<CowStr, Box<pest::error::Error<Rule>>> {
23    match pair.as_rule() {
24        Rule::source_comment_block => Ok(pair
25            .into_inner()
26            .map(|p| parse_source_comment(content, p))
27            .collect::<anyhow::Result<CowStr, Box<pest::error::Error<Rule>>>>()?),
28        _ => unreachable!(),
29    }
30}
31
32fn parse_source_comment(
33    content: &CowStr,
34    pair: Pair<Rule>,
35) -> Result<CowStr, Box<pest::error::Error<Rule>>> {
36    Ok(match pair.as_rule() {
37        Rule::source_comment => {
38            let mut inner = pair.into_inner();
39            let spacing = inner.next().expect("Unexpected end of iterator");
40            let spacing = cowstr_from_span(content, spacing.as_span());
41            let str = inner.next().expect("Unexpected end of iterator");
42            let str = cowstr_from_span(content, str.as_span());
43
44            cowstr::format!("{spacing}{str}")
45        }
46        _ => unreachable!(),
47    })
48}
49
50pub(crate) fn parse_value(
51    content: &CowStr,
52    pair: Pair<Rule>,
53    meta: &mut LinkedHashMap<CowStr, CowStr>,
54) -> Result<Option<CodeElem>, Box<pest::error::Error<Rule>>> {
55    Ok(match pair.as_rule() {
56        Rule::source_code_block => Some(CodeElem::Src(pair.as_str().to_string())),
57
58        Rule::code_block => {
59            let mut block_segments = pair.into_inner();
60            let solution_pair = block_segments.next().expect("Unexpected end of iterator");
61            let solution = CowStr::from(solution_pair.into_inner().as_str());
62
63            let placeholder: Option<CowStr> = if let Some(placeholder_pair) = block_segments.next()
64            {
65                Some(
66                    placeholder_pair
67                        .into_inner()
68                        .map(|p| parse_code_placeholder_block(content, p))
69                        .collect::<anyhow::Result<CowStr, Box<pest::error::Error<Rule>>>>()?,
70                )
71            } else {
72                None
73            };
74
75            Some(CodeElem::Solution(Solution {
76                placeholder,
77                solution,
78            }))
79        }
80
81        Rule::meta => {
82            let mut outer = pair.into_inner();
83            let tp = outer.next().expect("Missing meta type");
84            let mut ct = tp.into_inner();
85
86            let ident = cowstr_from_span(content, ct.next().unwrap().as_span());
87            let value = cowstr_from_span(content, ct.next().unwrap().as_span());
88
89            meta.insert(ident, value);
90            None
91        }
92
93        _ => unreachable!(),
94    })
95}
96
97fn cowstr_from_span(base: &CowStr, span: pest::Span) -> CowStr {
98    CowStr::from(&base[span.start()..span.end()])
99}
100
101pub fn parse_code_string(content: CowStr) -> Result<CodeContent, Box<pest::error::Error<Rule>>> {
102    let mut padded = content.to_string();
103    let src = CowStr::from(padded.trim());
104    padded.push('\n');
105    let mut p = TaskParser::parse(Rule::doc, &padded)?;
106    let p = p.next().expect("no top level").into_inner();
107    let mut meta = LinkedHashMap::new();
108    let blocks = p
109        .into_iter()
110        .filter_map(|v| parse_value(&content, v, &mut meta).transpose())
111        .collect::<anyhow::Result<Vec<CodeElem>, Box<pest::error::Error<Rule>>>>()?;
112    let mut hasher = DefaultHasher::new();
113    content.hash(&mut hasher);
114    Ok(CodeContent::Parsed {
115        blocks,
116        meta,
117        hash: hasher.finish(),
118    })
119}
120
121pub(crate) fn human_errors(error: pest::error::Error<Rule>) -> Box<pest::error::Error<Rule>> {
122    Box::new(error.renamed_rules(|rule| match *rule {
123        Rule::source_code_block => "code".to_owned(),
124        Rule::code_block => "placeholder/solution block".to_owned(),
125
126        Rule::source_comment => "code comment".to_owned(),
127        Rule::source_comment_block => "code comment".to_owned(),
128
129        _ => "Unknown".to_owned(),
130    }))
131}
132
133pub fn format_pest_err(error: pest::error::Error<Rule>) -> String {
134    let error = human_errors(error);
135    // format!(
136    //     r#"
137    // line: {:?}, col: {:?},
138    // details: {}
139    // "#,
140    //     error.location, error.line_col, error.variant
141    // )
142    format!("{}", error)
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_parse() {
151        let str = include_str!("../../resources/sample.py");
152        let _doc = parse_code_string(str.into()).unwrap();
153    }
154
155    // #[test]
156    // fn test_output() {
157    //     let str = include_str!("../../../resources/test/sample.rs");
158    //     let doc = parse_code_string(str).unwrap();
159    //
160    //     let _output_solution = doc.write_string(true);
161    //     let _output_placeholder = doc.write_string(false);
162    // }
163
164    #[test]
165    fn test_serialize() {
166        let str = include_str!("../../resources/sample.rs");
167        let doc = parse_code_string(str.into()).unwrap();
168
169        let _res = serde_json::to_string(&doc).unwrap();
170    }
171}