1#[derive(Clone, Copy, Debug, PartialEq, Eq)]2pub enum StringBlockError {3 UnexpectedEnd,4 MissingNewLine,5 MissingTermination,6 MissingIndent,7}89use std::ops::Range;1011use logos::Lexer;12use StringBlockError::*;1314use crate::SyntaxKind;1516pub fn lex_str_block_test(lex: &mut Lexer<SyntaxKind>) {17 let _ = lex_str_block(lex);18}1920#[allow(clippy::too_many_lines)]21pub fn lex_str_block(lex: &mut Lexer<SyntaxKind>) -> Result<(), StringBlockError> {22 struct Context<'a> {23 source: &'a str,24 index: usize,25 offset: usize,26 }2728 impl<'a> Context<'a> {29 fn rest(&self) -> &'a str {30 &self.source[self.index..]31 }3233 fn next(&mut self) -> Option<char> {34 if self.index == self.source.len() {35 return None;36 }3738 match self.rest().chars().next() {39 None => None,40 Some(c) => {41 self.index += c.len_utf8();42 Some(c)43 }44 }45 }4647 fn peek(&self) -> Option<char> {48 if self.index == self.source.len() {49 return None;50 }5152 self.rest().chars().next()53 }5455 fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {56 if self.peek().map(f).unwrap_or(false) {57 self.index += 1;58 return 1;59 }60 061 }6263 fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {64 if self.index == self.source.len() {65 return 0;66 }6768 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6970 match next_char {71 None => {72 let diff = self.source.len() - self.index;73 self.index = self.source.len();74 diff75 }76 Some((idx, _)) => {77 self.index += idx;78 idx79 }80 }81 }8283 fn skip(&mut self, len: usize) {84 self.index = match self.index + len {85 n if n > self.source.len() => self.source.len(),86 n => n,87 };88 }8990 #[allow(clippy::range_plus_one)]91 fn pos(&self) -> Range<usize> {92 if self.index == self.source.len() {93 self.offset + self.index..self.offset + self.index94 } else {95 96 self.offset + self.index..self.offset + self.index + 197 }98 }99 }100101 102 103 104 fn check_whitespace(a: &str, b: &str) -> usize {105 let a = a.as_bytes();106 let b = b.as_bytes();107108 for i in 0..a.len() {109 if a[i] != b' ' && a[i] != b'\t' {110 111 return i;112 }113114 if i >= b.len() {115 116 return 0;117 }118119 if a[i] != b[i] {120 121 return 0;122 }123 }124125 126 a.len()127 }128129 fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {130 let end_index = ctx131 .rest()132 .find("|||")133 .map_or_else(|| ctx.rest().len(), |v| v + 3);134 lex.bump(ctx.index + end_index);135 }136137 debug_assert_eq!(lex.slice(), "|||");138 let mut ctx = Context {139 source: lex.remainder(),140 index: 0,141 offset: lex.span().end,142 };143144 ctx.eat_if(|v| v == '-');145146 147 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');148149 150 match ctx.next() {151 Some('\n') => (),152 None => {153 guess_token_end_and_bump(lex, &ctx);154 return Err(UnexpectedEnd);155 }156 157 Some(_) => {158 guess_token_end_and_bump(lex, &ctx);159 return Err(MissingNewLine);160 }161 }162163 164 while ctx.peek() == Some('\n') {165 ctx.next();166 }167168 let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());169 let str_block_indent = &ctx.rest()[..num_whitespace];170171 if num_whitespace == 0 {172 173 guess_token_end_and_bump(lex, &ctx);174 return Err(MissingIndent);175 }176177 loop {178 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");179 ctx.skip(num_whitespace);180181 loop {182 match ctx.next() {183 None => {184 guess_token_end_and_bump(lex, &ctx);185 return Err(UnexpectedEnd);186 }187 Some('\n') => break,188 Some(_) => (),189 }190 }191192 193 while ctx.peek() == Some('\n') {194 ctx.next();195 }196197 198 num_whitespace = check_whitespace(str_block_indent, ctx.rest());199 if num_whitespace == 0 {200 201 202 while let Some(' ' | '\t') = ctx.peek() {203 204 ctx.next().unwrap();205 206 }207208 if !ctx.rest().starts_with("|||") {209 210 let pos = ctx.pos();211 if pos.is_empty() {212 213 lex.bump(ctx.index);214 return Err(UnexpectedEnd);215 }216217 guess_token_end_and_bump(lex, &ctx);218 return Err(MissingTermination);219 }220221 222 ctx.skip(3);223 break;224 }225 }226227 lex.bump(ctx.index);228 Ok(())229}