cdoc_parser/code_ast/
mod.rs1pub 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#[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 padded.push('\n');
104 let mut p = TaskParser::parse(Rule::doc, &padded)?;
105 let p = p.next().expect("no top level").into_inner();
106 let mut meta = LinkedHashMap::new();
107 let blocks = p
108 .into_iter()
109 .filter_map(|v| parse_value(&content, v, &mut meta).transpose())
110 .collect::<anyhow::Result<Vec<CodeElem>, Box<pest::error::Error<Rule>>>>()?;
111 let mut hasher = DefaultHasher::new();
112 content.hash(&mut hasher);
113 Ok(CodeContent {
114 blocks,
115 meta,
116 hash: hasher.finish(),
117 })
118}
119
120pub(crate) fn human_errors(error: pest::error::Error<Rule>) -> Box<pest::error::Error<Rule>> {
121 Box::new(error.renamed_rules(|rule| match *rule {
122 Rule::source_code_block => "code".to_owned(),
123 Rule::code_block => "placeholder/solution block".to_owned(),
124
125 Rule::source_comment => "code comment".to_owned(),
126 Rule::source_comment_block => "code comment".to_owned(),
127
128 _ => "Unknown".to_owned(),
129 }))
130}
131
132pub fn format_pest_err(error: pest::error::Error<Rule>) -> String {
133 let error = human_errors(error);
134 format!("{}", error)
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_parse() {
150 let str = include_str!("../../resources/sample.py");
151 let _doc = parse_code_string(str.into()).unwrap();
152 }
153
154 #[test]
164 fn test_serialize() {
165 let str = include_str!("../../resources/sample.rs");
166 let doc = parse_code_string(str.into()).unwrap();
167
168 let _res = serde_json::to_string(&doc).unwrap();
169 }
170}