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_while(&mut self, f: impl Fn(char) -> bool) -> usize {56 if self.index == self.source.len() {57 return 0;58 }5960 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6162 match next_char {63 None => {64 let diff = self.source.len() - self.index;65 self.index = self.source.len();66 diff67 }68 Some((idx, _)) => {69 self.index += idx;70 idx71 }72 }73 }7475 fn skip(&mut self, len: usize) {76 self.index = match self.index + len {77 n if n > self.source.len() => self.source.len(),78 n => n,79 };80 }8182 #[allow(clippy::range_plus_one)]83 fn pos(&self) -> Range<usize> {84 if self.index == self.source.len() {85 self.offset + self.index..self.offset + self.index86 } else {87 88 self.offset + self.index..self.offset + self.index + 189 }90 }91 }9293 94 95 96 fn check_whitespace(a: &str, b: &str) -> usize {97 let a = a.as_bytes();98 let b = b.as_bytes();99100 for i in 0..a.len() {101 if a[i] != b' ' && a[i] != b'\t' {102 103 return i;104 }105106 if i >= b.len() {107 108 return 0;109 }110111 if a[i] != b[i] {112 113 return 0;114 }115 }116117 118 a.len()119 }120121 fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {122 let end_index = ctx123 .rest()124 .find("|||")125 .map_or_else(|| ctx.rest().len(), |v| v + 3);126 lex.bump(ctx.index + end_index);127 }128129 debug_assert_eq!(lex.slice(), "|||");130 let mut ctx = Context {131 source: lex.remainder(),132 index: 0,133 offset: lex.span().end,134 };135136 137 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');138139 140 match ctx.next() {141 Some('\n') => (),142 None => {143 guess_token_end_and_bump(lex, &ctx);144 return Err(UnexpectedEnd);145 }146 147 Some(_) => {148 guess_token_end_and_bump(lex, &ctx);149 return Err(MissingNewLine);150 }151 }152153 154 while ctx.peek() == Some('\n') {155 ctx.next();156 }157158 let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());159 let str_block_indent = &ctx.rest()[..num_whitespace];160161 if num_whitespace == 0 {162 163 guess_token_end_and_bump(lex, &ctx);164 return Err(MissingIndent);165 }166167 loop {168 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");169 ctx.skip(num_whitespace);170171 loop {172 match ctx.next() {173 None => {174 guess_token_end_and_bump(lex, &ctx);175 return Err(UnexpectedEnd);176 }177 Some('\n') => break,178 Some(_) => (),179 }180 }181182 183 while ctx.peek() == Some('\n') {184 ctx.next();185 }186187 188 num_whitespace = check_whitespace(str_block_indent, ctx.rest());189 if num_whitespace == 0 {190 191 192 while let Some(' ' | '\t') = ctx.peek() {193 194 ctx.next().unwrap();195 196 }197198 if !ctx.rest().starts_with("|||") {199 200 let pos = ctx.pos();201 if pos.is_empty() {202 203 lex.bump(ctx.index);204 return Err(UnexpectedEnd);205 }206207 guess_token_end_and_bump(lex, &ctx);208 return Err(MissingTermination);209 }210211 212 ctx.skip(3);213 break;214 }215 }216217 lex.bump(ctx.index);218 Ok(())219}