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}1920pub fn lex_str_block(lex: &mut Lexer<SyntaxKind>) -> Result<(), StringBlockError> {21 struct Context<'a> {22 source: &'a str,23 index: usize,24 offset: usize,25 }2627 impl<'a> Context<'a> {28 fn rest(&self) -> &'a str {29 &self.source[self.index..]30 }3132 fn next(&mut self) -> Option<char> {33 if self.index == self.source.len() {34 return None;35 }3637 match self.rest().chars().next() {38 None => None,39 Some(c) => {40 self.index += c.len_utf8();41 Some(c)42 }43 }44 }4546 fn peek(&self) -> Option<char> {47 if self.index == self.source.len() {48 return None;49 }5051 self.rest().chars().next()52 }5354 fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {55 if self.index == self.source.len() {56 return 0;57 }5859 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6061 match next_char {62 None => {63 let diff = self.source.len() - self.index;64 self.index = self.source.len();65 diff66 }67 Some((idx, _)) => {68 self.index += idx;69 idx70 }71 }72 }7374 fn skip(&mut self, len: usize) {75 self.index = match self.index + len {76 n if n > self.source.len() => self.source.len(),77 n => n,78 };79 }8081 fn pos(&self) -> Range<usize> {82 if self.index == self.source.len() {83 self.offset + self.index..self.offset + self.index84 } else {85 86 self.offset + self.index..self.offset + self.index + 187 }88 }89 }9091 92 93 94 fn check_whitespace(a: &str, b: &str) -> usize {95 let a = a.as_bytes();96 let b = b.as_bytes();9798 for i in 0..a.len() {99 if a[i] != b' ' && a[i] != b'\t' {100 101 return i;102 }103104 if i >= b.len() {105 106 return 0;107 }108109 if a[i] != b[i] {110 111 return 0;112 }113 }114115 116 a.len()117 }118119 fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {120 let end_index = ctx121 .rest()122 .find("|||")123 .map(|v| v + 3)124 .unwrap_or_else(|| ctx.rest().len());125 lex.bump(ctx.index + end_index);126 }127128 debug_assert_eq!(lex.slice(), "|||");129 let mut ctx = Context {130 source: lex.remainder(),131 index: 0,132 offset: lex.span().end,133 };134135 136 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');137138 139 match ctx.next() {140 Some('\n') => (),141 None => {142 guess_token_end_and_bump(lex, &ctx);143 return Err(UnexpectedEnd);144 }145 146 Some(_) => {147 guess_token_end_and_bump(lex, &ctx);148 return Err(MissingNewLine);149 }150 }151152 153 while let Some('\n') = ctx.peek() {154 ctx.next();155 }156157 let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());158 let str_block_indent = &ctx.rest()[..num_whitespace];159160 if num_whitespace == 0 {161 162 guess_token_end_and_bump(lex, &ctx);163 return Err(MissingIndent);164 }165166 loop {167 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");168 ctx.skip(num_whitespace);169170 loop {171 match ctx.next() {172 None => {173 guess_token_end_and_bump(lex, &ctx);174 return Err(UnexpectedEnd);175 }176 Some('\n') => break,177 Some(_) => (),178 }179 }180181 182 while let Some('\n') = ctx.peek() {183 ctx.next();184 }185186 187 num_whitespace = check_whitespace(str_block_indent, ctx.rest());188 if num_whitespace == 0 {189 190 let mut term_indent = String::with_capacity(num_whitespace);191 while let Some(' ' | '\t') = ctx.peek() {192 term_indent.push(ctx.next().unwrap());193 }194195 if !ctx.rest().starts_with("|||") {196 197 let pos = ctx.pos();198 if pos.is_empty() {199 200 lex.bump(ctx.index);201 return Err(UnexpectedEnd);202 }203204 guess_token_end_and_bump(lex, &ctx);205 return Err(MissingTermination);206 }207208 209 ctx.skip(3);210 break;211 }212 }213214 lex.bump(ctx.index);215 Ok(())216}