From 696ffcb8fbac16412118896e7ab6f7d26e28da32 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 14:35:18 +0900 Subject: [PATCH 01/18] concise --- compiler/parser/src/lexer.rs | 58 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 37b41944f17..1029e728bb8 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -128,16 +128,16 @@ where fn next(&mut self) -> Option { // Collapse \r\n into \n loop { - if self.chr0 == Some('\r') { - if self.chr1 == Some('\n') { - // Transform windows EOL into \n + match (self.chr0, self.chr1) { + (Some('\r'), Some('\n')) => { + // Windows EOL into \n self.shift(); - } else { - // Transform MAC EOL into \n - self.chr0 = Some('\n') } - } else { - break; + (Some('\r'), _) => { + // MAC EOL into \n + self.chr0 = Some('\n'); + } + _ => break, } } @@ -159,8 +159,8 @@ where nesting: 0, indentation_stack: vec![Default::default()], pending: Vec::new(), - chr0: None, location: start, + chr0: None, chr1: None, chr2: None, }; @@ -184,17 +184,13 @@ where let mut saw_f = false; loop { // Detect r"", f"", b"" and u"" - if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('b') | Some('B')) { + if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('b' | 'B')) { saw_b = true; - } else if !(saw_b || saw_r || saw_u || saw_f) - && matches!(self.chr0, Some('u') | Some('U')) - { + } else if !(saw_b || saw_r || saw_u || saw_f) && matches!(self.chr0, Some('u' | 'U')) { saw_u = true; - } else if !(saw_r || saw_u) && (self.chr0 == Some('r') || self.chr0 == Some('R')) { + } else if !(saw_r || saw_u) && matches!(self.chr0, Some('r' | 'R')) { saw_r = true; - } else if !(saw_b || saw_u || saw_f) - && (self.chr0 == Some('f') || self.chr0 == Some('F')) - { + } else if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('f' | 'F')) { saw_f = true; } else { break; @@ -204,7 +200,7 @@ where name.push(self.next_char().unwrap()); // Check if we have a string: - if self.chr0 == Some('"') || self.chr0 == Some('\'') { + if matches!(self.chr0, Some('"' | '\'')) { return self .lex_string(saw_b, saw_r, saw_u, saw_f) .map(|(_, tok, end_pos)| (start_pos, tok, end_pos)); @@ -227,18 +223,18 @@ where fn lex_number(&mut self) -> LexResult { let start_pos = self.get_pos(); if self.chr0 == Some('0') { - if self.chr1 == Some('x') || self.chr1 == Some('X') { - // Hex! + if matches!(self.chr1, Some('x' | 'X')) { + // Hex! (0xdeadbeef) self.next_char(); self.next_char(); self.lex_number_radix(start_pos, 16) - } else if self.chr1 == Some('o') || self.chr1 == Some('O') { - // Octal style! + } else if matches!(self.chr1, Some('o' | 'O')) { + // Octal style! (0o377) self.next_char(); self.next_char(); self.lex_number_radix(start_pos, 8) - } else if self.chr1 == Some('b') || self.chr1 == Some('B') { - // Binary! + } else if matches!(self.chr1, Some('b' | 'B')) { + // Binary! (0b_1110_0101) self.next_char(); self.next_char(); self.lex_number_radix(start_pos, 2) @@ -283,7 +279,7 @@ where } // 1e6 for example: - if self.chr0 == Some('e') || self.chr0 == Some('E') { + if matches!(self.chr0, Some('e' | 'E')) { if self.chr1 == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), @@ -292,7 +288,7 @@ where } value_text.push(self.next_char().unwrap().to_ascii_lowercase()); // Optional +/- - if self.chr0 == Some('-') || self.chr0 == Some('+') { + if matches!(self.chr0, Some('-' | '+')) { if self.chr1 == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), @@ -309,8 +305,9 @@ where error: LexicalErrorType::OtherError("Invalid decimal literal".to_owned()), location: self.get_pos(), })?; + // Parse trailing 'j': - if self.chr0 == Some('j') || self.chr0 == Some('J') { + if matches!(self.chr0, Some('j' | 'J')) { self.next_char(); let end_pos = self.get_pos(); Ok(( @@ -327,7 +324,7 @@ where } } else { // Parse trailing 'j': - if self.chr0 == Some('j') || self.chr0 == Some('J') { + if matches!(self.chr0, Some('j' | 'J')) { self.next_char(); let end_pos = self.get_pos(); let imag = f64::from_str(&value_text).unwrap(); @@ -336,6 +333,7 @@ where let end_pos = self.get_pos(); let value = value_text.parse::().unwrap(); if start_is_zero && !value.is_zero() { + // leading zeros in decimal integer literals are not permitted return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Token".to_owned()), location: self.get_pos(), @@ -389,8 +387,8 @@ where /// Test if we face '[eE][-+]?[0-9]+' fn at_exponent(&self) -> bool { match self.chr0 { - Some('e') | Some('E') => match self.chr1 { - Some('+') | Some('-') => matches!(self.chr2, Some('0'..='9')), + Some('e' | 'E') => match self.chr1 { + Some('+' | '-') => matches!(self.chr2, Some('0'..='9')), Some('0'..='9') => true, _ => false, }, From d63d62964d72177a24674bf07bd78ff80238660b Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 15:33:00 +0900 Subject: [PATCH 02/18] short circuit --- compiler/parser/src/lexer.rs | 78 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 1029e728bb8..f5201eec275 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -727,44 +727,46 @@ where fn handle_indentations(&mut self) -> Result<(), LexicalError> { let indentation_level = self.eat_indentation()?; - if self.nesting == 0 { - // Determine indent or dedent: - let current_indentation = self.indentation_stack.last().unwrap(); - let ordering = indentation_level.compare_strict(current_indentation, self.get_pos())?; - match ordering { - Ordering::Equal => { - // Same same - } - Ordering::Greater => { - // New indentation level: - self.indentation_stack.push(indentation_level); - let tok_pos = self.get_pos(); - self.emit((tok_pos, Tok::Indent, tok_pos)); - } - Ordering::Less => { - // One or more dedentations - // Pop off other levels until col is found: - - loop { - let current_indentation = self.indentation_stack.last().unwrap(); - let ordering = indentation_level - .compare_strict(current_indentation, self.get_pos())?; - match ordering { - Ordering::Less => { - self.indentation_stack.pop(); - let tok_pos = self.get_pos(); - self.emit((tok_pos, Tok::Dedent, tok_pos)); - } - Ordering::Equal => { - // We arrived at proper level of indentation. - break; - } - Ordering::Greater => { - return Err(LexicalError { - error: LexicalErrorType::IndentationError, - location: self.get_pos(), - }); - } + if self.nesting != 0 { + return Ok(()); + } + + // Determine indent or dedent: + let current_indentation = self.indentation_stack.last().unwrap(); + let ordering = indentation_level.compare_strict(current_indentation, self.get_pos())?; + match ordering { + Ordering::Equal => { + // Same same + } + Ordering::Greater => { + // New indentation level: + self.indentation_stack.push(indentation_level); + let tok_pos = self.get_pos(); + self.emit((tok_pos, Tok::Indent, tok_pos)); + } + Ordering::Less => { + // One or more dedentations + // Pop off other levels until col is found: + + loop { + let current_indentation = self.indentation_stack.last().unwrap(); + let ordering = + indentation_level.compare_strict(current_indentation, self.get_pos())?; + match ordering { + Ordering::Less => { + self.indentation_stack.pop(); + let tok_pos = self.get_pos(); + self.emit((tok_pos, Tok::Dedent, tok_pos)); + } + Ordering::Equal => { + // We arrived at proper level of indentation. + break; + } + Ordering::Greater => { + return Err(LexicalError { + error: LexicalErrorType::IndentationError, + location: self.get_pos(), + }); } } } From d02dd7cfd088e2f910a158bd0cb89e91d42be13b Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 16:07:03 +0900 Subject: [PATCH 03/18] abstract --- compiler/parser/src/lexer.rs | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index f5201eec275..40c3f93b9bd 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -56,11 +56,47 @@ impl IndentationLevel { } } +#[derive(Debug)] +struct Indentations { + indent_stack: Vec, +} + +impl Indentations { + pub fn is_empty(&self) -> bool { + self.indent_stack.len() == 1 + } + + pub fn push(&mut self, indent: IndentationLevel) { + self.indent_stack.push(indent); + } + + pub fn pop(&mut self) -> Option { + if self.is_empty() { + return None; + } + self.indent_stack.pop() + } + + pub fn current(&self) -> &IndentationLevel { + self.indent_stack + .last() + .expect("Indetations must have at least one level") + } +} + +impl Default for Indentations { + fn default() -> Self { + Self { + indent_stack: vec![IndentationLevel::default()], + } + } +} + pub struct Lexer> { chars: T, at_begin_of_line: bool, nesting: usize, // Amount of parenthesis - indentation_stack: Vec, + indentations: Indentations, pending: Vec, chr0: Option, chr1: Option, @@ -157,7 +193,7 @@ where chars: input, at_begin_of_line: true, nesting: 0, - indentation_stack: vec![Default::default()], + indentations: Indentations::default(), pending: Vec::new(), location: start, chr0: None, @@ -732,7 +768,7 @@ where } // Determine indent or dedent: - let current_indentation = self.indentation_stack.last().unwrap(); + let current_indentation = self.indentations.current(); let ordering = indentation_level.compare_strict(current_indentation, self.get_pos())?; match ordering { Ordering::Equal => { @@ -740,7 +776,7 @@ where } Ordering::Greater => { // New indentation level: - self.indentation_stack.push(indentation_level); + self.indentations.push(indentation_level); let tok_pos = self.get_pos(); self.emit((tok_pos, Tok::Indent, tok_pos)); } @@ -749,12 +785,12 @@ where // Pop off other levels until col is found: loop { - let current_indentation = self.indentation_stack.last().unwrap(); + let current_indentation = self.indentations.current(); let ordering = indentation_level.compare_strict(current_indentation, self.get_pos())?; match ordering { Ordering::Less => { - self.indentation_stack.pop(); + self.indentations.pop(); let tok_pos = self.get_pos(); self.emit((tok_pos, Tok::Dedent, tok_pos)); } @@ -817,8 +853,8 @@ where } // Next, flush the indentation stack to zero. - while self.indentation_stack.len() > 1 { - self.indentation_stack.pop(); + while !self.indentations.is_empty() { + self.indentations.pop(); self.emit((tok_pos, Tok::Dedent, tok_pos)); } @@ -1267,7 +1303,7 @@ where "Lex token {:?}, nesting={:?}, indent stack: {:?}", token, self.nesting, - self.indentation_stack + self.indentations, ); match token { From 0269f39ff5af860b5a68b0720056d32a001c44d7 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 17:03:47 +0900 Subject: [PATCH 04/18] use charwindow of 3 elements --- compiler/parser/src/lexer.rs | 185 +++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 71 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 40c3f93b9bd..5d6f34dfb9b 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -11,6 +11,8 @@ use num_traits::identities::Zero; use num_traits::Num; use std::char; use std::cmp::Ordering; +use std::ops::{Index, IndexMut}; +use std::slice::SliceIndex; use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unic_ucd_ident::{is_xid_continue, is_xid_start}; @@ -92,15 +94,51 @@ impl Default for Indentations { } } +struct CharWindow(pub [Option; N]); + +impl CharWindow { + fn slide(&mut self, next_char: Option) { + let len = self.0.len(); + for i in 0..(len - 1) { + self[i] = self[i + 1]; + } + self[len - 1] = next_char; + } +} + +impl Default for CharWindow { + fn default() -> Self { + Self([None; N]) + } +} + +impl Index for CharWindow +where + Idx: SliceIndex<[Option], Output = Option>, +{ + type Output = Option; + + fn index(&self, index: Idx) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for CharWindow +where + Idx: SliceIndex<[Option], Output = Option>, +{ + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + self.0.index_mut(index) + } +} + pub struct Lexer> { chars: T, at_begin_of_line: bool, nesting: usize, // Amount of parenthesis indentations: Indentations, pending: Vec, - chr0: Option, - chr1: Option, - chr2: Option, + window: CharWindow<3>, location: Location, } @@ -196,9 +234,7 @@ where indentations: Indentations::default(), pending: Vec::new(), location: start, - chr0: None, - chr1: None, - chr2: None, + window: CharWindow::default(), }; lxr.next_char(); lxr.next_char(); @@ -220,13 +256,15 @@ where let mut saw_f = false; loop { // Detect r"", f"", b"" and u"" - if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('b' | 'B')) { + if !(saw_b || saw_u || saw_f) && matches!(self.window[0], Some('b' | 'B')) { saw_b = true; - } else if !(saw_b || saw_r || saw_u || saw_f) && matches!(self.chr0, Some('u' | 'U')) { + } else if !(saw_b || saw_r || saw_u || saw_f) + && matches!(self.window[0], Some('u' | 'U')) + { saw_u = true; - } else if !(saw_r || saw_u) && matches!(self.chr0, Some('r' | 'R')) { + } else if !(saw_r || saw_u) && matches!(self.window[0], Some('r' | 'R')) { saw_r = true; - } else if !(saw_b || saw_u || saw_f) && matches!(self.chr0, Some('f' | 'F')) { + } else if !(saw_b || saw_u || saw_f) && matches!(self.window[0], Some('f' | 'F')) { saw_f = true; } else { break; @@ -236,7 +274,7 @@ where name.push(self.next_char().unwrap()); // Check if we have a string: - if matches!(self.chr0, Some('"' | '\'')) { + if matches!(self.window[0], Some('"' | '\'')) { return self .lex_string(saw_b, saw_r, saw_u, saw_f) .map(|(_, tok, end_pos)| (start_pos, tok, end_pos)); @@ -258,18 +296,18 @@ where /// Numeric lexing. The feast can start! fn lex_number(&mut self) -> LexResult { let start_pos = self.get_pos(); - if self.chr0 == Some('0') { - if matches!(self.chr1, Some('x' | 'X')) { + if self.window[0] == Some('0') { + if matches!(self.window[1], Some('x' | 'X')) { // Hex! (0xdeadbeef) self.next_char(); self.next_char(); self.lex_number_radix(start_pos, 16) - } else if matches!(self.chr1, Some('o' | 'O')) { + } else if matches!(self.window[1], Some('o' | 'O')) { // Octal style! (0o377) self.next_char(); self.next_char(); self.lex_number_radix(start_pos, 8) - } else if matches!(self.chr1, Some('b' | 'B')) { + } else if matches!(self.window[1], Some('b' | 'B')) { // Binary! (0b_1110_0101) self.next_char(); self.next_char(); @@ -296,15 +334,15 @@ where /// Lex a normal number, that is, no octal, hex or binary number. fn lex_normal_number(&mut self) -> LexResult { let start_pos = self.get_pos(); - let start_is_zero = self.chr0 == Some('0'); + let start_is_zero = self.window[0] == Some('0'); // Normal number: let mut value_text = self.radix_run(10); // If float: - if self.chr0 == Some('.') || self.at_exponent() { + if self.window[0] == Some('.') || self.at_exponent() { // Take '.': - if self.chr0 == Some('.') { - if self.chr1 == Some('_') { + if self.window[0] == Some('.') { + if self.window[1] == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), location: self.get_pos(), @@ -315,8 +353,8 @@ where } // 1e6 for example: - if matches!(self.chr0, Some('e' | 'E')) { - if self.chr1 == Some('_') { + if matches!(self.window[0], Some('e' | 'E')) { + if self.window[1] == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), location: self.get_pos(), @@ -324,8 +362,8 @@ where } value_text.push(self.next_char().unwrap().to_ascii_lowercase()); // Optional +/- - if matches!(self.chr0, Some('-' | '+')) { - if self.chr1 == Some('_') { + if matches!(self.window[0], Some('-' | '+')) { + if self.window[1] == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), location: self.get_pos(), @@ -343,7 +381,7 @@ where })?; // Parse trailing 'j': - if matches!(self.chr0, Some('j' | 'J')) { + if matches!(self.window[0], Some('j' | 'J')) { self.next_char(); let end_pos = self.get_pos(); Ok(( @@ -360,7 +398,7 @@ where } } else { // Parse trailing 'j': - if matches!(self.chr0, Some('j' | 'J')) { + if matches!(self.window[0], Some('j' | 'J')) { self.next_char(); let end_pos = self.get_pos(); let imag = f64::from_str(&value_text).unwrap(); @@ -389,7 +427,9 @@ where loop { if let Some(c) = self.take_number(radix) { value_text.push(c); - } else if self.chr0 == Some('_') && Lexer::::is_digit_of_radix(self.chr1, radix) { + } else if self.window[1] == Some('_') + && Lexer::::is_digit_of_radix(self.window[1], radix) + { self.next_char(); } else { break; @@ -400,7 +440,7 @@ where /// Consume a single character with the given radix. fn take_number(&mut self, radix: u32) -> Option { - let take_char = Lexer::::is_digit_of_radix(self.chr0, radix); + let take_char = Lexer::::is_digit_of_radix(self.window[0], radix); if take_char { Some(self.next_char().unwrap()) @@ -422,9 +462,9 @@ where /// Test if we face '[eE][-+]?[0-9]+' fn at_exponent(&self) -> bool { - match self.chr0 { - Some('e' | 'E') => match self.chr1 { - Some('+' | '-') => matches!(self.chr2, Some('0'..='9')), + match self.window[0] { + Some('e' | 'E') => match self.window[1] { + Some('+' | '-') => matches!(self.window[2], Some('0'..='9')), Some('0'..='9') => true, _ => false, }, @@ -437,7 +477,7 @@ where let start_pos = self.get_pos(); self.next_char(); loop { - match self.chr0 { + match self.window[0] { Some('\n') | None => { let end_pos = self.get_pos(); return Ok((start_pos, Tok::Comment, end_pos)); @@ -473,7 +513,7 @@ where let mut octet_content = String::new(); octet_content.push(first); while octet_content.len() < 3 { - if let Some('0'..='7') = self.chr0 { + if let Some('0'..='7') = self.window[0] { octet_content.push(self.next_char().unwrap()) } else { break; @@ -535,18 +575,19 @@ where // If the next two characters are also the quote character, then we have a triple-quoted // string; consume those two characters and ensure that we require a triple-quote to close - let triple_quoted = if self.chr0 == Some(quote_char) && self.chr1 == Some(quote_char) { - self.next_char(); - self.next_char(); - true - } else { - false - }; + let triple_quoted = + if self.window[0] == Some(quote_char) && self.window[1] == Some(quote_char) { + self.next_char(); + self.next_char(); + true + } else { + false + }; loop { match self.next_char() { Some('\\') => { - if self.chr0 == Some(quote_char) && !is_raw { + if self.window[0] == Some(quote_char) && !is_raw { string_content.push(quote_char); self.next_char(); } else if is_raw { @@ -606,7 +647,9 @@ where // Look ahead at the next two characters; if we have two more // quote_chars, it's the end of the string; consume the remaining // closing quotes and break the loop - if self.chr0 == Some(quote_char) && self.chr1 == Some(quote_char) { + if self.window[0] == Some(quote_char) + && self.window[1] == Some(quote_char) + { self.next_char(); self.next_char(); break; @@ -665,7 +708,7 @@ where } fn is_identifier_continuation(&self) -> bool { - if let Some(c) = self.chr0 { + if let Some(c) = self.window[0] { match c { '_' | '0'..='9' => true, c => is_xid_continue(c), @@ -697,7 +740,7 @@ where let mut spaces: usize = 0; let mut tabs: usize = 0; loop { - match self.chr0 { + match self.window[0] { Some(' ') => { /* if tabs != 0 { @@ -815,7 +858,7 @@ where /// Take a look at the next character, if any, and decide upon the next steps. fn consume_normal(&mut self) -> Result<(), LexicalError> { // Check if we have some character: - if let Some(c) = self.chr0 { + if let Some(c) = self.window[0] { // First check identifier: if self.is_identifier_start(c) { let identifier = self.lex_identifier()?; @@ -882,7 +925,7 @@ where '=' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -897,7 +940,7 @@ where '+' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::PlusEqual, tok_end)); @@ -909,7 +952,7 @@ where '*' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -917,7 +960,7 @@ where } Some('*') => { self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -938,7 +981,7 @@ where '/' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -946,7 +989,7 @@ where } Some('/') => { self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -967,7 +1010,7 @@ where '%' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::PercentEqual, tok_end)); @@ -979,7 +1022,7 @@ where '|' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::VbarEqual, tok_end)); @@ -991,7 +1034,7 @@ where '^' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::CircumflexEqual, tok_end)); @@ -1003,7 +1046,7 @@ where '&' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::AmperEqual, tok_end)); @@ -1015,7 +1058,7 @@ where '-' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -1035,7 +1078,7 @@ where '@' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::AtEqual, tok_end)); @@ -1047,7 +1090,7 @@ where '!' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::NotEqual, tok_end)); @@ -1106,7 +1149,7 @@ where ':' => { let tok_start = self.get_pos(); self.next_char(); - if let Some('=') = self.chr0 { + if let Some('=') = self.window[0] { self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Tok::ColonEqual, tok_end)); @@ -1121,10 +1164,10 @@ where '<' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('<') => { self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -1150,10 +1193,10 @@ where '>' => { let tok_start = self.get_pos(); self.next_char(); - match self.chr0 { + match self.window[0] { Some('>') => { self.next_char(); - match self.chr0 { + match self.window[0] { Some('=') => { self.next_char(); let tok_end = self.get_pos(); @@ -1183,13 +1226,13 @@ where self.emit((tok_start, Tok::Comma, tok_end)); } '.' => { - if let Some('0'..='9') = self.chr1 { + if let Some('0'..='9') = self.window[1] { let number = self.lex_number()?; self.emit(number); } else { let tok_start = self.get_pos(); self.next_char(); - if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { + if let (Some('.'), Some('.')) = (&self.window[0], &self.window[1]) { self.next_char(); self.next_char(); let tok_end = self.get_pos(); @@ -1214,14 +1257,16 @@ where ' ' | '\t' | '\x0C' => { // Skip whitespaces self.next_char(); - while self.chr0 == Some(' ') || self.chr0 == Some('\t') || self.chr0 == Some('\x0C') + while self.window[0] == Some(' ') + || self.window[0] == Some('\t') + || self.window[0] == Some('\x0C') { self.next_char(); } } '\\' => { self.next_char(); - if let Some('\n') = self.chr0 { + if let Some('\n') = self.window[0] { self.next_char(); } else { return Err(LexicalError { @@ -1230,7 +1275,7 @@ where }); } - if self.chr0.is_none() { + if self.window[0].is_none() { return Err(LexicalError { error: LexicalErrorType::Eof, location: self.get_pos(), @@ -1259,11 +1304,9 @@ where /// Helper function to go to the next character coming up. fn next_char(&mut self) -> Option { - let c = self.chr0; + let c = self.window[0]; let nxt = self.chars.next(); - self.chr0 = self.chr1; - self.chr1 = self.chr2; - self.chr2 = nxt; + self.window.slide(nxt); if c == Some('\n') { self.location.newline(); } else { From b687fd09806f05b01a7fc1824691ffa9b9e50e58 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 17:53:55 +0900 Subject: [PATCH 05/18] api to the top --- compiler/parser/src/lexer.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 5d6f34dfb9b..3dfe63caba5 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -17,6 +17,19 @@ use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unic_ucd_ident::{is_xid_continue, is_xid_start}; +#[inline] +pub fn make_tokenizer(source: &str) -> impl Iterator + '_ { + make_tokenizer_located(source, Location::new(0, 0)) +} + +pub fn make_tokenizer_located( + source: &str, + start_location: Location, +) -> impl Iterator + '_ { + let nlh = NewlineHandler::new(source.chars()); + Lexer::new(nlh, start_location) +} + #[derive(Clone, Copy, PartialEq, Debug, Default)] struct IndentationLevel { tabs: usize, @@ -134,11 +147,12 @@ where pub struct Lexer> { chars: T, + window: CharWindow<3>, + at_begin_of_line: bool, nesting: usize, // Amount of parenthesis indentations: Indentations, pending: Vec, - window: CharWindow<3>, location: Location, } @@ -149,19 +163,6 @@ pub static KEYWORDS: phf::Map<&'static str, Tok> = pub type Spanned = (Location, Tok, Location); pub type LexResult = Result; -#[inline] -pub fn make_tokenizer(source: &str) -> impl Iterator + '_ { - make_tokenizer_located(source, Location::new(0, 0)) -} - -pub fn make_tokenizer_located( - source: &str, - start_location: Location, -) -> impl Iterator + '_ { - let nlh = NewlineHandler::new(source.chars()); - Lexer::new(nlh, start_location) -} - // The newline handler is an iterator which collapses different newline // types into \n always. pub struct NewlineHandler> { From fcd29750242f06b4694babea61dea7901c120f5d Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 18:28:35 +0900 Subject: [PATCH 06/18] use charwindow --- compiler/parser/src/lexer.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 3dfe63caba5..6dd6eca00e2 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -147,12 +147,11 @@ where pub struct Lexer> { chars: T, - window: CharWindow<3>, - at_begin_of_line: bool, nesting: usize, // Amount of parenthesis indentations: Indentations, pending: Vec, + window: CharWindow<3>, location: Location, } @@ -167,8 +166,7 @@ pub type LexResult = Result; // types into \n always. pub struct NewlineHandler> { source: T, - chr0: Option, - chr1: Option, + window: CharWindow<2>, } impl NewlineHandler @@ -178,8 +176,7 @@ where pub fn new(source: T) -> Self { let mut nlh = NewlineHandler { source, - chr0: None, - chr1: None, + window: CharWindow::default(), }; nlh.shift(); nlh.shift(); @@ -187,9 +184,8 @@ where } fn shift(&mut self) -> Option { - let result = self.chr0; - self.chr0 = self.chr1; - self.chr1 = self.source.next(); + let result = self.window[0]; + self.window.slide(self.source.next()); result } } @@ -203,14 +199,14 @@ where fn next(&mut self) -> Option { // Collapse \r\n into \n loop { - match (self.chr0, self.chr1) { + match (self.window[0], self.window[1]) { (Some('\r'), Some('\n')) => { // Windows EOL into \n self.shift(); } (Some('\r'), _) => { // MAC EOL into \n - self.chr0 = Some('\n'); + self.window[0] = Some('\n'); } _ => break, } From ae24f830224bd27a132b262ceb0a378093aad20f Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 18:32:19 +0900 Subject: [PATCH 07/18] rearrange --- compiler/parser/src/lexer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 6dd6eca00e2..cca107b026c 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -147,11 +147,13 @@ where pub struct Lexer> { chars: T, + window: CharWindow<3>, + at_begin_of_line: bool, nesting: usize, // Amount of parenthesis indentations: Indentations, + pending: Vec, - window: CharWindow<3>, location: Location, } From 829a98aa5ac8e270eb4f4810b8c29a16bc722e34 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 18:58:42 +0900 Subject: [PATCH 08/18] match --- compiler/parser/src/lexer.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index cca107b026c..f89ea8d4299 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -352,7 +352,7 @@ where } // 1e6 for example: - if matches!(self.window[0], Some('e' | 'E')) { + if let Some('e' | 'E') = self.window[0] { if self.window[1] == Some('_') { return Err(LexicalError { error: LexicalErrorType::OtherError("Invalid Syntax".to_owned()), @@ -1256,10 +1256,7 @@ where ' ' | '\t' | '\x0C' => { // Skip whitespaces self.next_char(); - while self.window[0] == Some(' ') - || self.window[0] == Some('\t') - || self.window[0] == Some('\x0C') - { + while let Some(' ' | '\t' | '\x0C') = self.window[0] { self.next_char(); } } From dc9415040423effd610342950f5396a1988692fd Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Wed, 2 Nov 2022 20:30:18 +0900 Subject: [PATCH 09/18] fix bug with new test --- compiler/parser/src/lexer.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index f89ea8d4299..c85e6876d11 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -426,7 +426,7 @@ where loop { if let Some(c) = self.take_number(radix) { value_text.push(c); - } else if self.window[1] == Some('_') + } else if self.window[0] == Some('_') && Lexer::::is_digit_of_radix(self.window[1], radix) { self.next_char(); @@ -1392,7 +1392,7 @@ mod tests { #[test] fn test_numbers() { - let source = "0x2f 0b1101 0 123 0.2 2j 2.2j"; + let source = "0x2f 0b1101 0 123 123_45_67_890 0.2 2j 2.2j"; let tokens = lex_source(source); assert_eq!( tokens, @@ -1409,6 +1409,9 @@ mod tests { Tok::Int { value: BigInt::from(123), }, + Tok::Int { + value: BigInt::from(1234567890), + }, Tok::Float { value: 0.2 }, Tok::Complex { real: 0.0, From 3a88e1ca4f0446ce289644656b43d3fba46a0dc2 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Thu, 3 Nov 2022 09:13:06 +0900 Subject: [PATCH 10/18] remove unnecessary `pub` --- compiler/parser/src/lexer.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index c85e6876d11..6b563d6551a 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -77,22 +77,22 @@ struct Indentations { } impl Indentations { - pub fn is_empty(&self) -> bool { + fn is_empty(&self) -> bool { self.indent_stack.len() == 1 } - pub fn push(&mut self, indent: IndentationLevel) { + fn push(&mut self, indent: IndentationLevel) { self.indent_stack.push(indent); } - pub fn pop(&mut self) -> Option { + fn pop(&mut self) -> Option { if self.is_empty() { return None; } self.indent_stack.pop() } - pub fn current(&self) -> &IndentationLevel { + fn current(&self) -> &IndentationLevel { self.indent_stack .last() .expect("Indetations must have at least one level") @@ -107,7 +107,7 @@ impl Default for Indentations { } } -struct CharWindow(pub [Option; N]); +struct CharWindow([Option; N]); impl CharWindow { fn slide(&mut self, next_char: Option) { From 6bf71e70b417051a219ef5733aedc74bae1f553c Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 08:42:24 +0900 Subject: [PATCH 11/18] Update compiler/parser/src/lexer.rs Co-authored-by: Jeong YunWon <69878+youknowone@users.noreply.github.com> --- compiler/parser/src/lexer.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 6b563d6551a..fa8693fefb1 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -111,11 +111,8 @@ struct CharWindow([Option; N]); impl CharWindow { fn slide(&mut self, next_char: Option) { - let len = self.0.len(); - for i in 0..(len - 1) { - self[i] = self[i + 1]; - } - self[len - 1] = next_char; + self.0.rotate_left(1); + *self.0.last_mut().expect("never empty") = next_char; } } From e7eae87ec46844741d4e47f99371fcc22bb025e4 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 08:51:50 +0900 Subject: [PATCH 12/18] small diff --- compiler/parser/src/lexer.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index fa8693fefb1..86d0d6f5550 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -17,19 +17,6 @@ use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unic_ucd_ident::{is_xid_continue, is_xid_start}; -#[inline] -pub fn make_tokenizer(source: &str) -> impl Iterator + '_ { - make_tokenizer_located(source, Location::new(0, 0)) -} - -pub fn make_tokenizer_located( - source: &str, - start_location: Location, -) -> impl Iterator + '_ { - let nlh = NewlineHandler::new(source.chars()); - Lexer::new(nlh, start_location) -} - #[derive(Clone, Copy, PartialEq, Debug, Default)] struct IndentationLevel { tabs: usize, @@ -161,6 +148,19 @@ pub static KEYWORDS: phf::Map<&'static str, Tok> = pub type Spanned = (Location, Tok, Location); pub type LexResult = Result; +#[inline] +pub fn make_tokenizer(source: &str) -> impl Iterator + '_ { + make_tokenizer_located(source, Location::new(0, 0)) +} + +pub fn make_tokenizer_located( + source: &str, + start_location: Location, +) -> impl Iterator + '_ { + let nlh = NewlineHandler::new(source.chars()); + Lexer::new(nlh, start_location) +} + // The newline handler is an iterator which collapses different newline // types into \n always. pub struct NewlineHandler> { From 7b26b22f81874d6764343f747dfbdb8f1a26838b Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 08:52:19 +0900 Subject: [PATCH 13/18] remove IndexMut --- compiler/parser/src/lexer.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 86d0d6f5550..48c2f55dafa 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -11,7 +11,7 @@ use num_traits::identities::Zero; use num_traits::Num; use std::char; use std::cmp::Ordering; -use std::ops::{Index, IndexMut}; +use std::ops::Index; use std::slice::SliceIndex; use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; @@ -101,6 +101,10 @@ impl CharWindow { self.0.rotate_left(1); *self.0.last_mut().expect("never empty") = next_char; } + + fn swap(&mut self, ch: char) { + *self.0.first_mut().expect("never empty") = Some(ch); + } } impl Default for CharWindow { @@ -120,15 +124,6 @@ where } } -impl IndexMut for CharWindow -where - Idx: SliceIndex<[Option], Output = Option>, -{ - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - self.0.index_mut(index) - } -} - pub struct Lexer> { chars: T, window: CharWindow<3>, @@ -205,7 +200,7 @@ where } (Some('\r'), _) => { // MAC EOL into \n - self.window[0] = Some('\n'); + self.window.swap('\n'); } _ => break, } From 54869e97ffce18b6ee343f3651b3427ef4f212ba Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 08:54:04 +0900 Subject: [PATCH 14/18] change fn name --- compiler/parser/src/lexer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 48c2f55dafa..0face2bdac8 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -102,7 +102,7 @@ impl CharWindow { *self.0.last_mut().expect("never empty") = next_char; } - fn swap(&mut self, ch: char) { + fn swap_first(&mut self, ch: char) { *self.0.first_mut().expect("never empty") = Some(ch); } } @@ -200,7 +200,7 @@ where } (Some('\r'), _) => { // MAC EOL into \n - self.window.swap('\n'); + self.window.swap_first('\n'); } _ => break, } From b1480b9962b5ca1b1014f016e457c8db5cd8a06d Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 20:54:28 +0900 Subject: [PATCH 15/18] char window owns its source --- compiler/parser/src/lexer.rs | 63 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 0face2bdac8..bc9d2fbfb78 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -94,39 +94,52 @@ impl Default for Indentations { } } -struct CharWindow([Option; N]); +struct CharWindow, const N: usize> { + source: T, + window: [Option; N], +} + +impl CharWindow +where + T: Iterator, +{ + fn new(source: T) -> Self { + Self { + source, + window: [None; N], + } + } -impl CharWindow { - fn slide(&mut self, next_char: Option) { - self.0.rotate_left(1); - *self.0.last_mut().expect("never empty") = next_char; + fn slide(&mut self) { + self.window.rotate_left(1); + *self.window.last_mut().expect("never empty") = self.source.next(); } - fn swap_first(&mut self, ch: char) { - *self.0.first_mut().expect("never empty") = Some(ch); + fn fill(&mut self) { + while self.window[0] == None { + self.slide(); + } } -} -impl Default for CharWindow { - fn default() -> Self { - Self([None; N]) + fn change_first(&mut self, ch: char) { + *self.window.first_mut().expect("never empty") = Some(ch); } } -impl Index for CharWindow +impl Index for CharWindow where + T: Iterator, Idx: SliceIndex<[Option], Output = Option>, { type Output = Option; fn index(&self, index: Idx) -> &Self::Output { - self.0.index(index) + self.window.index(index) } } pub struct Lexer> { - chars: T, - window: CharWindow<3>, + window: CharWindow, at_begin_of_line: bool, nesting: usize, // Amount of parenthesis @@ -159,8 +172,7 @@ pub fn make_tokenizer_located( // The newline handler is an iterator which collapses different newline // types into \n always. pub struct NewlineHandler> { - source: T, - window: CharWindow<2>, + window: CharWindow, } impl NewlineHandler @@ -169,8 +181,7 @@ where { pub fn new(source: T) -> Self { let mut nlh = NewlineHandler { - source, - window: CharWindow::default(), + window: CharWindow::new(source), }; nlh.shift(); nlh.shift(); @@ -179,7 +190,7 @@ where fn shift(&mut self) -> Option { let result = self.window[0]; - self.window.slide(self.source.next()); + self.window.slide(); result } } @@ -200,7 +211,7 @@ where } (Some('\r'), _) => { // MAC EOL into \n - self.window.swap_first('\n'); + self.window.change_first('\n'); } _ => break, } @@ -219,17 +230,14 @@ where { pub fn new(input: T, start: Location) -> Self { let mut lxr = Lexer { - chars: input, at_begin_of_line: true, nesting: 0, indentations: Indentations::default(), pending: Vec::new(), location: start, - window: CharWindow::default(), + window: CharWindow::new(input), }; - lxr.next_char(); - lxr.next_char(); - lxr.next_char(); + lxr.window.fill(); // Start at top row (=1) left column (=1) lxr.location.reset(); lxr @@ -1293,8 +1301,7 @@ where /// Helper function to go to the next character coming up. fn next_char(&mut self) -> Option { let c = self.window[0]; - let nxt = self.chars.next(); - self.window.slide(nxt); + self.window.slide(); if c == Some('\n') { self.location.newline(); } else { From ddbdb6d3c4cffccee599cb5d26793fa9cee87f94 Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 21:16:41 +0900 Subject: [PATCH 16/18] prevent inf loop --- compiler/parser/src/lexer.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index bc9d2fbfb78..85070d7dbf5 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -110,14 +110,18 @@ where } } - fn slide(&mut self) { + fn slide(&mut self) -> Option { self.window.rotate_left(1); - *self.window.last_mut().expect("never empty") = self.source.next(); + let next = self.source.next(); + *self.window.last_mut().expect("never empty") = next; + next } fn fill(&mut self) { while self.window[0] == None { - self.slide(); + if self.slide() == None { + return; + } } } From f903abb66fb687566c1a1f7a0dcc93e6d08657ed Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 21:34:58 +0900 Subject: [PATCH 17/18] clippy --- compiler/parser/src/lexer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 85070d7dbf5..078cbc4e884 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -118,8 +118,8 @@ where } fn fill(&mut self) { - while self.window[0] == None { - if self.slide() == None { + while self.window[0].is_none() { + if self.slide().is_none() { return; } } From daafbd18fd0c8da15008ad076fdc0db668d3b5bd Mon Sep 17 00:00:00 2001 From: Bongjun Jang Date: Mon, 7 Nov 2022 22:21:25 +0900 Subject: [PATCH 18/18] fix --- compiler/parser/src/lexer.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler/parser/src/lexer.rs b/compiler/parser/src/lexer.rs index 078cbc4e884..7bf88851cfc 100644 --- a/compiler/parser/src/lexer.rs +++ b/compiler/parser/src/lexer.rs @@ -117,14 +117,6 @@ where next } - fn fill(&mut self) { - while self.window[0].is_none() { - if self.slide().is_none() { - return; - } - } - } - fn change_first(&mut self, ch: char) { *self.window.first_mut().expect("never empty") = Some(ch); } @@ -241,7 +233,9 @@ where location: start, window: CharWindow::new(input), }; - lxr.window.fill(); + lxr.window.slide(); + lxr.window.slide(); + lxr.window.slide(); // Start at top row (=1) left column (=1) lxr.location.reset(); lxr