1use crate::{lexer::TokenKind, nodes::Code};
2
3use super::Parser;
4
5pub(crate) fn code(p: &mut Parser<'_>) -> Option<Code> {
6 let start_pos = p.pos();
7 let lang = p
8 .advance_or_backtrack(|t| t.kind == TokenKind::Eol)
9 .map(|(_, end)| end)?;
10
11 let Some((start, end)) = p.advance_until(
12 |t| t.kind == TokenKind::Backtick && t.position.column == 0 && t.slice.len() == 3,
13 true,
14 ) else {
15 p.backtrack(start_pos);
16 p.flip_to_literal_at(start_pos);
17 return None;
18 };
19
20 p.next_token();
21 Some(Code::new(
22 p.range_to_string(start_pos + 1..lang),
23 p.range_to_string(start..end - 1),
24 ))
25}
26
27#[cfg(test)]
28mod tests {
29
30 use crate::{
31 lexer::{Position, Token, TokenKind},
32 nodes::Code,
33 parser::Parser,
34 };
35
36 use super::code;
37 use pretty_assertions::assert_eq;
38
39 #[test]
40 fn happy_path() {
41 let mut p = Parser::new("```rust\nprintln!(\"hello\");\n```");
42 assert_eq!(
43 code(&mut p),
44 Some(Code::new("rust", "println!(\"hello\");"))
45 );
46 }
47
48 #[test]
49 fn eol_before_lang() {
50 let mut p = Parser::new("```\nprintln!(\"hello\");\n```");
51 assert_eq!(code(&mut p), Some(Code::new("", "println!(\"hello\");")));
52 }
53
54 #[test]
55 fn terminator_before_lang() {
56 let mut p = Parser::new("```\n\nprintln!(\"hello\");\n```");
57 assert_eq!(code(&mut p), None);
58 assert_eq!(
59 p.peek(),
60 Some((
61 &Token::new(TokenKind::Literal, "```", Position::default()),
62 0
63 ))
64 )
65 }
66
67 #[test]
68 fn do_not_have_closing_token() {
69 let mut p = Parser::new("```\nprintln!(\"hello\");\n``");
70 assert_eq!(code(&mut p), None);
71 assert_eq!(
72 p.peek(),
73 Some((
74 &Token::new(TokenKind::Literal, "```", Position::default()),
75 0
76 ))
77 );
78 }
79
80 #[test]
81 fn terminator_in_the_middle_and_do_not_have_closing_token() {
82 let mut p = Parser::new("```\nprintln!(\"hello\");\n\n\n``");
83 assert_eq!(code(&mut p), None);
84 assert_eq!(
85 p.peek(),
86 Some((
87 &Token::new(TokenKind::Literal, "```", Position::default()),
88 0
89 ))
90 );
91 }
92}