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

difftreelog

source

crates/jrsonnet-rowan-parser/src/string_block.rs4.5 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}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				// TODO: char size86				self.offset + self.index..self.offset + self.index + 187			}88		}89	}9091	// Check that b has at least the same whitespace prefix as a and returns the92	// amount of this whitespace, otherwise returns 0.  If a has no whitespace93	// prefix than return 0.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				// a has run out of whitespace and b matched up to this point. Return result.101				return i;102			}103104			if i >= b.len() {105				// We ran off the edge of b while a still has whitespace. Return 0 as failure.106				return 0;107			}108109			if a[i] != b[i] {110				// a has whitespace but b does not. Return 0 as failure.111				return 0;112			}113		}114115		// We ran off the end of a and b kept up116		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	// Skip whitespaces136	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');137138	// Skip \n139	match ctx.next() {140		Some('\n') => (),141		None => {142			guess_token_end_and_bump(lex, &ctx);143			return Err(UnexpectedEnd);144		}145		// Text block requires new line after |||.146		Some(_) => {147			guess_token_end_and_bump(lex, &ctx);148			return Err(MissingNewLine);149		}150	}151152	// Process leading blank lines before calculating string block indent153	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		// Text block's first line must start with whitespace162		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		// Skip any blank lines182		while let Some('\n') = ctx.peek() {183			ctx.next();184		}185186		// Look at the next line187		num_whitespace = check_whitespace(str_block_indent, ctx.rest());188		if num_whitespace == 0 {189			// End of the text block190			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				// Text block not terminated with |||197				let pos = ctx.pos();198				if pos.is_empty() {199					// eof200					lex.bump(ctx.index);201					return Err(UnexpectedEnd);202				}203204				guess_token_end_and_bump(lex, &ctx);205				return Err(MissingTermination);206			}207208			// Skip '|||'209			ctx.skip(3);210			break;211		}212	}213214	lex.bump(ctx.index);215	Ok(())216}