diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2ef86..d853ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 2.1.0 (January 2, 2022) + +### Release 2.1.0 +* Patched: + - change(&mut self, input: &char`, pos: usize`) in `Extras` +* New Feature: + - *.next_to_left()* + - *.next_to_right()* + * *If `next` or `jump` can effect the `Extras`.* + - *.noeffects()* + - *.noeffects_mut()* + - *.noeffects_on()* + - *.noefeects_off()* + * *Bump until meets `fn` = `true`.* + - *.next_to_until(`fn`)* + * *Bump while `fn` = `true`.* + - *.next_to_while(`fn`)* + * *Cloning `saved().extras` to `self.extras()`.* + - *.to_range_extras()* +--- + ## 2.0.0 (December 29, 2021) ### Release 2.0.0 diff --git a/Cargo.toml b/Cargo.toml index ae901ec..7f42209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cursor" -version = "2.0.0" +version = "2.1.0" authors = ["just-do-halee "] homepage = "https://github.com/just-do-halee/cursor" repository = "https://github.com/just-do-halee/cursor" @@ -20,4 +20,7 @@ edition = "2021" default = [ "std" ] std = [] +[dev-dependencies] +derive-new = "0.5" + [dependencies] diff --git a/cursor_diagram.svg b/cursor_diagram.svg index f1787a0..1e35774 100644 --- a/cursor_diagram.svg +++ b/cursor_diagram.svg @@ -1 +1 @@ -
CURSOR
CURSOR
next()
next()
jump()
jump()
next_to_pos()
next_to_pos()
next_to_offset()
next_to_offset()
next_to_first()
next_to_first()
next_to_last()
next_to_last()
next_to_load()
next_to_load()
next_cycle()
next_cycle()
jump_to_offset()
jump_to_offset()
jump_to_first()
jump_to_first()
jump_to_last()
jump_to_last()
jump_to_load()
jump_to_load()
jump_cycle()
jump_cycle()
next_to_offset_cycle()
next_to_offset_cycle()
next_to_offset_cycle()
next_to_offset_cycle()
Arithmetical
Arithmetical
cursor += i;
cursor -= i;
&mut cursor - i;
&mut cursor + i;
cursor.as_mut() + i;
cursor.as_mut() - i;
cursor += i;...
cursor += 1;
cursor -= 1;
&mut cursor - 1;
&mut cursor + 1;
cursor.as_mut() + 1;
cursor.as_mut() - 1;
cursor += 1;...
1
1
i
i
>1
>1
=
=
item_size()
range()
len()
is_empty()
is_init()
backwards()
turnaround()
head_to_left()
head_to_right()
head_to_pos()
pos()
extras()
to_extras()
reset()
save()
saved()
load()
as_slice()
as_slice_loaded()
as_left_side_slice()
as_right_side_slice()
as_preserved_slice()
as_remaining_slice()
current()
first_to_last()
item_size()...
pos() starts at -1
pos() starts at...
pos() starts at 0
pos() starts at 0
will change
the head direction
(= backwards())
will change...
will not change
the head direction
will not change...
Viewer does not support full SVG 1.1
\ No newline at end of file +
next_to_while()
next_to_while()
next_cycle()
next_cycle()
next_to_until()
next_to_until()
next_to_while()
next_to_while()
next_to_offset_cycle()
next_to_offset_cycle()
Arithmetical
Arithmetical
cursor += i;
cursor -= i;
&mut cursor - i;
&mut cursor + i;
cursor.as_mut() + i;
cursor.as_mut() - i;
cursor += i;...
>1
>1
CURSOR
CURSOR
next()
next()
jump()
jump()
next_to_pos()
next_to_pos()
next_to_offset()
next_to_offset()
next_to_left()
next_to_left()
next_to_right()
next_to_right()
jump_to_offset()
jump_to_offset()
jump_to_first()
jump_to_first()
jump_to_last()
jump_to_last()
jump_to_load()
jump_to_load()
cursor += 1;
cursor -= 1;
&mut cursor - 1;
&mut cursor + 1;
cursor.as_mut() + 1;
cursor.as_mut() - 1;
cursor += 1;...
1
1
=
=
item_size()
range()
len()
is_empty()
is_init()
noeffects()
noeffects_on()
noeffects_off()
backwards()
turnaround()
head_to_left()
head_to_right()
head_to_pos()
pos()
extras()
to_extras()
to_range_extras()
reset()
save()
saved()
load()
as_slice()
as_slice_loaded()
as_left_side_slice()
as_right_side_slice()
as_preserved_slice()
as_remaining_slice()
current()
first_to_last()
clone()
item_size()...
pos() starts at -1
pos() starts at...
pos() starts at 0
pos() starts at 0
will change
the head direction
(= backwards())
will change...
will not change
the head direction
will not change...
next_to_first()
next_to_first()
next_to_last()
next_to_last()
jump_cycle()
jump_cycle()
next_to_offset_cycle()
next_to_offset_cycle()
i
i
next_to_load()
next_to_load()
next_to_until()
next_to_until()
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/examples/advanced.rs b/examples/advanced.rs index 53b3757..2b77126 100644 --- a/examples/advanced.rs +++ b/examples/advanced.rs @@ -30,7 +30,7 @@ impl Extras for Counter { fn reset(&mut self) { self._reset() } - fn change(&mut self, input: &u8) { + fn change(&mut self, input: &u8, _pos: usize) { if input % 2 == 0 { self.0 += 1; } @@ -47,7 +47,7 @@ impl Extras for Counter { fn reset(&mut self) { self._reset() } - fn change(&mut self, input: &char) { + fn change(&mut self, input: &char, _pos: usize) { if *input == ' ' { self.0 += 1; } diff --git a/examples/lexer.rs b/examples/lexer.rs new file mode 100644 index 0000000..da5d532 --- /dev/null +++ b/examples/lexer.rs @@ -0,0 +1,389 @@ +#![allow(dead_code)] + +use cursor::*; + +use derive_new::*; +use std::str::FromStr; + +derive_debug_partials! { + + #[derive(Default, PartialOrd, Ord, Clone, Copy, new)] + struct Offset { + pub pos: usize, + pub line: usize, + pub column: usize, + } + + #[derive(Default, Clone, Copy, new)] + struct Span { + pub start: Offset, + pub end: Offset, + } + + #[derive(Clone, new)] + struct SourceChunk<'s> { + pub source: &'s str, // whole mass + span: Span, + } + + #[derive(Clone, Copy)] + enum TokenKind { + // Single-character tokens. + LeftParen, + RightParen, + LeftBrace, + RightBrace, + Comma, + Dot, + Minus, + Plus, + Semicolon, + Slash, + Star, + Ampersand, + VerticalBar, + Circumflex, + + // One or two character tokens. + Bang, + BangEqual, + Equal, + EqualEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + + // Literals. + Identifier, + String, + Number, + + // Keywords. + And, + Class, + Else, + False, + Fun, + For, + If, + Nil, + Or, + Print, + Return, + Super, + This, + True, + Var, + While, + + Eof, + } + + #[derive(Clone)] + enum Object { + Identifier(String), + String(String), + Number(i32), + Boolean(bool), + Nil, + None, + } + + +} + +#[derive(PartialEq, Eq, Clone, new)] +struct Token<'s> { + pub kind: TokenKind, + pub lexeme: SourceChunk<'s>, + pub literal: Object, +} + +type Tokens<'s> = Vec>; + +impl<'s> fmt::Debug for Token<'s> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Token") + .field("kind", &self.kind) + .field("lexeme", &self.lexeme.as_str()) + .field("literal", &self.literal) + .finish() + } +} + +impl<'s> SourceChunk<'s> { + pub fn as_str(&self) -> &'s str { + let Span { start, end } = self.span; + &self.source[start.pos..end.pos.saturating_add(1)] + } +} + +impl From> for Span { + fn from(v: Range) -> Self { + Span { + start: v.start.offset, + end: v.end.offset, + } + } +} + +impl<'s> From<&StrCursor<'s, LexerExtras>> for SourceChunk<'s> { + fn from(cursor: &StrCursor<'s, LexerExtras>) -> Self { + SourceChunk { + source: cursor.as_str(), + span: Span::from(cursor.to_range_extras()), + } + } +} + +#[derive(Default, Debug)] +struct LexerExtras { + prev_offset: Offset, + offset: Offset, +} + +impl Extras for LexerExtras { + fn new() -> Self { + LexerExtras::default() + } + fn clone(&self) -> Self { + LexerExtras { + prev_offset: self.prev_offset, + offset: self.offset, + } + } + fn reset(&mut self) { + self.prev_offset = Offset::default(); + self.offset = Offset::default(); + } + fn change(&mut self, input: &char, pos: usize) { + if self.offset.pos > pos { + self.offset = self.prev_offset; + } else { + self.prev_offset = self.offset; + self.offset.pos = pos; + match *input { + '\n' => { + self.offset.line += 1; + self.offset.column = 0; + } + _ => self.offset.column += 1, + } + } + } +} + +fn main() { + example1(); + println!(); +} + +#[inline] +fn example1() { + let mut cursor = StrCursor::new_with_extras::( + r#" + print 2 + 1; + print "one"; + print true; + + if () {} + /* U(Uj#$*()@#)(@!#&%#%NM) */ + + if (1 != 2) { + print "yes"; + }"#, + ); + let mut tokens = Tokens::new(); + let undo = |cursor: &mut StrCursor| { + cursor.next_to_left(); + cursor.head_to_right(); + }; + + while let Some(c) = cursor.next() { + match c { + '+' => { + cursor.save(); + tokens.push(Token::new( + TokenKind::Plus, + SourceChunk::from(&cursor), + Object::None, + )) + } + '"' => { + // strings + cursor.save(); + cursor.next_to_until(|c| c == '"'); + let s = cursor.as_str_loaded(); + let literal = (&s[1..s.len().saturating_sub(1)]).to_string(); + tokens.push(Token::new( + TokenKind::String, + SourceChunk::from(&cursor), + Object::String(literal), + )); + } + '0'..='9' => { + // numbers + cursor.save(); + cursor.next_to_while(|c| c.is_digit(10)); + undo(&mut cursor); // Undo + let literal = i32::from_str(cursor.as_str_loaded()).unwrap(); + tokens.push(Token::new( + TokenKind::Number, + SourceChunk::from(&cursor), + Object::Number(literal), + )); + } + 'a'..='z' | 'A'..='Z' | '_' => { + // ident + cursor.save(); + cursor.next_to_while(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')); + undo(&mut cursor); // Undo + let literal = cursor.as_str_loaded().to_string(); + tokens.push(Token::new( + TokenKind::Identifier, + SourceChunk::from(&cursor), + Object::Identifier(literal), + )); + } + _ => {} + } + } + let s = format!("{:#?}", tokens); + assert_eq!( + s, + r#"[ + Token { + kind: Identifier, + lexeme: "print", + literal: Identifier( + "print", + ), + }, + Token { + kind: Number, + lexeme: "2", + literal: Number( + 2, + ), + }, + Token { + kind: Plus, + lexeme: "+", + literal: None, + }, + Token { + kind: Number, + lexeme: "1", + literal: Number( + 1, + ), + }, + Token { + kind: Identifier, + lexeme: "print", + literal: Identifier( + "print", + ), + }, + Token { + kind: String, + lexeme: "\"one\"", + literal: String( + "one", + ), + }, + Token { + kind: Identifier, + lexeme: "print", + literal: Identifier( + "print", + ), + }, + Token { + kind: Identifier, + lexeme: "true", + literal: Identifier( + "true", + ), + }, + Token { + kind: Identifier, + lexeme: "if", + literal: Identifier( + "if", + ), + }, + Token { + kind: Identifier, + lexeme: "U", + literal: Identifier( + "U", + ), + }, + Token { + kind: Identifier, + lexeme: "Uj", + literal: Identifier( + "Uj", + ), + }, + Token { + kind: Identifier, + lexeme: "NM", + literal: Identifier( + "NM", + ), + }, + Token { + kind: Identifier, + lexeme: "if", + literal: Identifier( + "if", + ), + }, + Token { + kind: Number, + lexeme: "1", + literal: Number( + 1, + ), + }, + Token { + kind: Number, + lexeme: "2", + literal: Number( + 2, + ), + }, + Token { + kind: Identifier, + lexeme: "print", + literal: Identifier( + "print", + ), + }, + Token { + kind: String, + lexeme: "\"yes\"", + literal: String( + "yes", + ), + }, +]"# + ) +} + +#[macro_export] +macro_rules! derive_debug_partials { + ( + $( + $i:item + )* + ) => { + $( + #[derive(Debug, PartialEq, Eq)] + $i + )* + }; +} diff --git a/src/cmn/extras.rs b/src/cmn/extras.rs index 6d77bba..9ea0d70 100644 --- a/src/cmn/extras.rs +++ b/src/cmn/extras.rs @@ -14,7 +14,7 @@ impl Extras for NoneExtras { NoneExtras::new() } #[inline] - fn change(&mut self, _: &T) {} + fn change(&mut self, _: &T, _: usize) {} #[inline] fn reset(&mut self) {} } diff --git a/src/cmn/info.rs b/src/cmn/info.rs index cff5db5..579dfb1 100644 --- a/src/cmn/info.rs +++ b/src/cmn/info.rs @@ -8,6 +8,7 @@ pub struct CursorInfo = NoneExtras> { pub backwards: bool, pub pos: usize, pub extras: E, + pub noeffects: bool, _marker: PhantomData, } impl> Default for CursorInfo { @@ -18,6 +19,7 @@ impl> Default for CursorInfo { backwards: false, pos: 0, extras: Extras::new(), + noeffects: false, _marker: PhantomData, } } @@ -30,6 +32,7 @@ impl> Clone for CursorInfo { backwards: self.backwards, pos: self.pos, extras: self.extras.clone(), + noeffects: false, _marker: PhantomData, } } @@ -46,6 +49,7 @@ impl> CursorInfo { self.backwards = false; self.pos = 0; self.extras.reset(); + self.noeffects = false; } } @@ -58,6 +62,7 @@ pub struct StrCursorInfo = NoneExtras> { pub extras: E, pub char_start_pos: usize, pub current: char, + pub noeffects: bool, } impl> Default for StrCursorInfo { #[inline] @@ -68,6 +73,7 @@ impl> Default for StrCursorInfo { extras: Extras::new(), char_start_pos: 0, current: EOF_CHAR, + noeffects: false, } } } @@ -80,6 +86,7 @@ impl> Clone for StrCursorInfo { extras: self.extras.clone(), char_start_pos: self.char_start_pos, current: self.current, + noeffects: self.noeffects, } } } @@ -94,6 +101,8 @@ impl> StrCursorInfo { self.inner.reset(); self.pos = 0; self.extras.reset(); + self.char_start_pos = 0; self.current = EOF_CHAR; + self.noeffects = false; } } diff --git a/src/cursors/extensions/string.rs b/src/cursors/extensions/string.rs index 114ce02..940691f 100644 --- a/src/cursors/extensions/string.rs +++ b/src/cursors/extensions/string.rs @@ -120,7 +120,9 @@ impl<'s, E: Extras> StrCursor<'s, E> { } #[inline] fn blush_extras(&mut self) { - self.info.extras.change(&self.current()); + if !self.noeffects() { + self.info.extras.change(&self.current(), self.pos()); + } } #[inline] @@ -174,6 +176,15 @@ impl<'s, E: Extras> StrCursorTrait<'s, E> for StrCursor<'s, E> { fn is_init(&self) -> bool { self.current() != EOF_CHAR } + /// if `next` or `jump` can effect the [`Extras`](Extras). + #[inline] + fn noeffects(&self) -> bool { + self.info.noeffects + } + #[inline] + fn noeffects_mut(&mut self) -> &mut bool { + &mut self.info.noeffects + } #[inline] fn backwards(&self) -> bool { self.cursor.backwards() diff --git a/src/cursors/mod.rs b/src/cursors/mod.rs index 72e1dee..f56cb2c 100644 --- a/src/cursors/mod.rs +++ b/src/cursors/mod.rs @@ -102,7 +102,9 @@ impl<'s, T: 's, E: Extras> Cursor<'s, T, E> { } #[inline] fn blush_extras(&mut self) { - self.info.extras.change(self.current()); + if !self.noeffects() { + self.info.extras.change(self.current(), self.pos()); + } } #[inline] @@ -144,6 +146,15 @@ impl<'s, T: 's, E: Extras> CursorTrait<'s, T, E> for Cursor<'s, T, E> { fn is_init(&self) -> bool { self.info.init } + /// if `next` or `jump` can effect the [`Extras`](Extras). + #[inline] + fn noeffects(&self) -> bool { + self.info.noeffects + } + #[inline] + fn noeffects_mut(&mut self) -> &mut bool { + &mut self.info.noeffects + } #[inline] fn backwards(&self) -> bool { self.info.backwards diff --git a/src/traits.rs b/src/traits.rs index 3c42378..d281ace 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,7 +5,7 @@ use super::*; pub trait Extras { fn new() -> Self; fn clone(&self) -> Self; - fn change(&mut self, input: &Input); + fn change(&mut self, input: &Input, pos: usize); fn reset(&mut self); } @@ -53,6 +53,18 @@ where self.as_slice().is_empty() } fn is_init(&self) -> bool; + + fn noeffects(&self) -> bool; + fn noeffects_mut(&mut self) -> &mut bool; + #[inline] + fn noeffects_on(&mut self) { + *self.noeffects_mut() = true; + } + #[inline] + fn noeffects_off(&mut self) { + *self.noeffects_mut() = false; + } + fn backwards(&self) -> bool; fn backwards_mut(&mut self) -> &mut bool; @@ -84,6 +96,12 @@ where fn pos(&self) -> usize; fn extras(&self) -> &E; + /// cloning `saved().extras` to `self.extras()`. + #[inline] + fn to_range_extras(&self) -> Range { + self.saved().extras.clone()..self.extras().clone() + } + fn reset(&mut self); fn save(&mut self); fn saved(&self) -> &CursorInfo; @@ -316,6 +334,38 @@ where while self.next().is_some() {} self.current() } + #[inline] + fn next_to_left(&mut self) -> Option<&'s T> { + self.head_to_left(); + self.next() + } + #[inline] + fn next_to_right(&mut self) -> Option<&'s T> { + self.head_to_right(); + self.next() + } + /// bump until meets f() = `true`. + #[inline] + fn next_to_until(&mut self, f: fn(&T) -> bool) -> &'s T { + #[allow(clippy::while_let_on_iterator)] + while let Some(item) = self.next() { + if f(item) { + break; + } + } + self.current() + } + /// bump while f() = `true`. + #[inline] + fn next_to_while(&mut self, f: fn(&T) -> bool) -> &'s T { + #[allow(clippy::while_let_on_iterator)] + while let Some(item) = self.next() { + if !f(item) { + break; + } + } + self.current() + } /// bump until meets saved pos. #[inline] fn next_to_load(&mut self) -> &'s T { @@ -384,6 +434,18 @@ where } fn is_init(&self) -> bool; + + fn noeffects(&self) -> bool; + fn noeffects_mut(&mut self) -> &mut bool; + #[inline] + fn noeffects_on(&mut self) { + *self.noeffects_mut() = true; + } + #[inline] + fn noeffects_off(&mut self) { + *self.noeffects_mut() = false; + } + fn backwards(&self) -> bool; fn turnaround(&mut self); @@ -415,6 +477,12 @@ where fn extras(&self) -> &E; + /// cloning `saved().extras` to `self.extras()`. + #[inline] + fn to_range_extras(&self) -> Range { + self.saved().extras.clone()..self.extras().clone() + } + fn reset(&mut self); fn save(&mut self); fn saved(&self) -> &StrCursorInfo; @@ -541,7 +609,11 @@ where }; utf::from_utf8_unchecked(&self.as_bytes()[saved_pos..curr_pos + 1]) } - Ordering::Equal => "", + Ordering::Equal => utf::from_utf8_unchecked(if curr_backwards { + &self.as_bytes()[curr_pos..curr_char_start_pos + 1] + } else { + &self.as_bytes()[curr_char_start_pos..curr_pos + 1] + }), Ordering::Less => { let saved_pos = if saved_backwards { saved_char_start_pos @@ -752,6 +824,38 @@ where while self.next().is_some() {} self.current() } + #[inline] + fn next_to_left(&mut self) -> Option { + self.head_to_left(); + self.next() + } + #[inline] + fn next_to_right(&mut self) -> Option { + self.head_to_right(); + self.next() + } + /// bump until meets f() = `true`. + #[inline] + fn next_to_until(&mut self, f: fn(char) -> bool) -> char { + #[allow(clippy::while_let_on_iterator)] + while let Some(ch) = self.next() { + if f(ch) { + break; + } + } + self.current() + } + /// bump while f() = `true`. + #[inline] + fn next_to_while(&mut self, f: fn(char) -> bool) -> char { + #[allow(clippy::while_let_on_iterator)] + while let Some(ch) = self.next() { + if !f(ch) { + break; + } + } + self.current() + } /// bump until meets saved pos. #[inline] fn next_to_load(&mut self) -> char { diff --git a/tests/cursor_test.rs b/tests/cursor_test.rs index aa0dfd9..b5591f0 100644 --- a/tests/cursor_test.rs +++ b/tests/cursor_test.rs @@ -17,7 +17,7 @@ impl Extras for EvenCounter { fn reset(&mut self) { self.0 = 0; } - fn change(&mut self, input: &u8) { + fn change(&mut self, input: &u8, _pos: usize) { if input % 2 == 0 { self.0 += 1; } diff --git a/tests/strcursor_test.rs b/tests/strcursor_test.rs index e9f04dc..5cb20b7 100644 --- a/tests/strcursor_test.rs +++ b/tests/strcursor_test.rs @@ -4,17 +4,6 @@ use cursor::*; const STRING: &str = "this is test. 안녕하세요. 이것은 #&*@( 테스트입니다. ^^ thanks."; -// #[test] -// fn str_works() { -// let mut cursor = StrCursor::new(STRING); -// assert_eq!((cursor.as_mut() + 3).unwrap(), 's'); -// cursor -= 2; -// assert_eq!(cursor.current(), 'h'); -// cursor += 8; -// assert_eq!(cursor.current(), 'e'); -// assert_eq!((cursor - 8).unwrap(), 'h'); -// } - #[derive(Debug, Default)] struct SpaceCounter(pub usize); @@ -28,7 +17,7 @@ impl Extras for SpaceCounter { fn reset(&mut self) { self.0 = 0; } - fn change(&mut self, input: &char) { + fn change(&mut self, input: &char, _pos: usize) { if *input == ' ' { self.0 += 1; }