git.delta.rocks / jrsonnet / refs/commits / fc45c6ca2bef

difftreelog

source

crates/jrsonnet-rowan-parser/src/string_block.rs4.6 KiBsourcehistory
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				// TODO: char size88				self.offset + self.index..self.offset + self.index + 189			}90		}91	}9293	// Check that b has at least the same whitespace prefix as a and returns the94	// amount of this whitespace, otherwise returns 0.  If a has no whitespace95	// prefix than return 0.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				// a has run out of whitespace and b matched up to this point. Return result.103				return i;104			}105106			if i >= b.len() {107				// We ran off the edge of b while a still has whitespace. Return 0 as failure.108				return 0;109			}110111			if a[i] != b[i] {112				// a has whitespace but b does not. Return 0 as failure.113				return 0;114			}115		}116117		// We ran off the end of a and b kept up118		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	// Skip whitespaces137	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');138139	// Skip \n140	match ctx.next() {141		Some('\n') => (),142		None => {143			guess_token_end_and_bump(lex, &ctx);144			return Err(UnexpectedEnd);145		}146		// Text block requires new line after |||.147		Some(_) => {148			guess_token_end_and_bump(lex, &ctx);149			return Err(MissingNewLine);150		}151	}152153	// Process leading blank lines before calculating string block indent154	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		// Text block's first line must start with whitespace163		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		// Skip any blank lines183		while ctx.peek() == Some('\n') {184			ctx.next();185		}186187		// Look at the next line188		num_whitespace = check_whitespace(str_block_indent, ctx.rest());189		if num_whitespace == 0 {190			// End of the text block191			// let mut term_indent = String::with_capacity(num_whitespace);192			while let Some(' ' | '\t') = ctx.peek() {193				// term_indent.push(194				ctx.next().unwrap();195				// );196			}197198			if !ctx.rest().starts_with("|||") {199				// Text block not terminated with |||200				let pos = ctx.pos();201				if pos.is_empty() {202					// eof203					lex.bump(ctx.index);204					return Err(UnexpectedEnd);205				}206207				guess_token_end_and_bump(lex, &ctx);208				return Err(MissingTermination);209			}210211			// Skip '|||'212			ctx.skip(3);213			break;214		}215	}216217	lex.bump(ctx.index);218	Ok(())219}