Skip to content

Commit

Permalink
#21 supporting richer suffixes and correcting the behaviour of regex …
Browse files Browse the repository at this point in the history
…ranges in exec
  • Loading branch information
sminez committed Oct 14, 2024
1 parent 0cf4625 commit 1a6a8a3
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 93 deletions.
1 change: 1 addition & 0 deletions src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ where
// the cause of the panic is visible before we exit isn't _too_ bad.
std::thread::sleep(std::time::Duration::from_millis(300));
eprintln!("Fatal error:\n{panic_info}");
_ = std::fs::write("/tmp/ad.panic", &format!("{panic_info}"));
}));

enable_mouse_support(&mut self.stdout);
Expand Down
136 changes: 68 additions & 68 deletions src/exec/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ impl Addr {
Some(',') => {
it.next();
let start = start.unwrap_or(AddrBase::Bof.into());
let end = match AddrBase::parse(it) {
let end = match SimpleAddr::parse(it) {
Ok(exp) => exp,
Err(ParseError::NotAnAddress) => AddrBase::Eof,
Err(ParseError::NotAnAddress) => AddrBase::Eof.into(),
Err(e) => return Err(e),
};

Ok(Addr::Compound(start, end.into()))
Ok(Addr::Compound(start, end))
}

_ => Err(ParseError::NotAnAddress),
Expand All @@ -86,35 +86,28 @@ impl Addr {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SimpleAddr {
base: AddrBase,
to_eol: bool,
to_bol: bool,
suffixes: Vec<AddrBase>, // restricted to variants that return true for is_valid_suffix
}

impl SimpleAddr {
fn parse(it: &mut Peekable<Chars<'_>>) -> Result<Self, ParseError> {
let base = AddrBase::parse(it)?;
let mut suffix = String::with_capacity(2);

for _ in 0..2 {
if let Some(c @ '-' | c @ '+') = it.peek() {
suffix.push(*c);
it.next();
let mut suffixes = Vec::new();

loop {
match it.peek() {
Some('-' | '+') => {
let a = AddrBase::parse(it)?;
if !a.is_valid_suffix() {
return Err(ParseError::InvalidSuffix);
}
suffixes.push(a);
}
_ => break,
}
}

let (to_eol, to_bol) = match suffix.as_str() {
"++" | "--" => return Err(ParseError::InvalidSuffix),
"-+" | "+-" => (true, true),
"-" => (false, true),
"+" => (true, false),
_ => (false, false),
};

Ok(Self {
base,
to_eol,
to_bol,
})
Ok(Self { base, suffixes })
}
}

Expand Down Expand Up @@ -147,8 +140,7 @@ impl From<AddrBase> for SimpleAddr {
fn from(base: AddrBase) -> Self {
Self {
base,
to_eol: false,
to_bol: false,
suffixes: Vec::new(),
}
}
}
Expand All @@ -159,6 +151,14 @@ enum Dir {
}

impl AddrBase {
fn is_valid_suffix(&self) -> bool {
use AddrBase::*;
matches!(
self,
RelativeLine(_) | RelativeChar(_) | Regex(_) | RegexBack(_)
)
}

pub(crate) fn parse(it: &mut Peekable<Chars<'_>>) -> Result<Self, ParseError> {
let dir = match it.peek() {
Some('-') => {
Expand Down Expand Up @@ -297,10 +297,10 @@ pub trait Address: IterBoundedChars {
Some(Dot::from_char_indices(from, to))
}

fn map_simple_addr(&self, addr: &mut SimpleAddr, cur_dot: Dot) -> Option<Dot> {
fn map_addr_base(&self, addr_base: &mut AddrBase, cur_dot: Dot) -> Option<Dot> {
use AddrBase::*;

let mut dot = match &mut addr.base {
let dot = match addr_base {
Current => cur_dot,
Bof => Cur { idx: 0 }.into(),
Eof => Cur::new(self.max_iter()).into(),
Expand Down Expand Up @@ -329,33 +329,25 @@ pub trait Address: IterBoundedChars {
let to = self.max_iter();
let m = re.match_iter(&mut self.iter_between(from, to), from)?;
let (from, to) = m.loc();
Dot::from_char_indices(from, to)
Dot::from_char_indices(from, to.saturating_sub(1))
}

RegexBack(re) => {
let from = cur_dot.first_cur().idx;
let m = re.match_iter(&mut self.rev_iter_between(from, 0), from)?;
let (from, to) = m.loc();
Dot::from_char_indices(from, to)
Dot::from_char_indices(from, to.saturating_sub(1))
}
};

if addr.to_eol || addr.to_bol {
let Range { start, end, .. } = dot.as_range();

let from = if addr.to_bol {
self.char_to_line_start(start.idx)?
} else {
start.idx
};
Some(dot)
}

let to = if addr.to_eol {
self.char_to_line_end(end.idx)?
} else {
end.idx
};
fn map_simple_addr(&self, addr: &mut SimpleAddr, cur_dot: Dot) -> Option<Dot> {
let mut dot = self.map_addr_base(&mut addr.base, cur_dot)?;

dot = Dot::from_char_indices(from, to);
for suffix in addr.suffixes.iter_mut() {
dot = self.map_addr_base(suffix, dot)?;
}

Some(dot)
Expand All @@ -364,7 +356,7 @@ pub trait Address: IterBoundedChars {
fn map_compound_addr(&self, from: &mut SimpleAddr, to: &mut SimpleAddr) -> Option<Dot> {
let d = self.map_simple_addr(from, self.current_dot())?;
let c1 = d.first_cur();
let c2 = self.map_simple_addr(to, d)?.last_cur();
let c2 = self.map_simple_addr(to, self.current_dot())?.last_cur();

Some(Range::from_cursors(c1, c2, false).into())
}
Expand Down Expand Up @@ -443,6 +435,11 @@ mod tests {
Regex::compile(s).unwrap()
}

fn re_rev(s: &str) -> Regex {
Regex::compile_reverse(s).unwrap()
}


// Simple
#[test_case(".", Simple(Current.into()); "current dot")]
#[test_case("0", Simple(Bof.into()); "begining of file")]
Expand All @@ -459,24 +456,14 @@ mod tests {
#[test_case("-/bar/", Simple(RegexBack(Regex::compile_reverse("bar").unwrap()).into()); "regex back")]
// Simple with suffix
#[test_case(
"#3+",
Simple(SimpleAddr { base: Char(3), to_eol: true, to_bol: false });
"char to eol"
"5+#3",
Simple(SimpleAddr { base: Line(4), suffixes: vec![RelativeChar(3)] });
"line plus char"
)]
#[test_case(
"#3-",
Simple(SimpleAddr { base: Char(3), to_eol: false, to_bol: true });
"char to bol"
)]
#[test_case(
"#3-+",
Simple(SimpleAddr { base: Char(3), to_eol: true, to_bol: true });
"char full line minus plus"
)]
#[test_case(
"#3+-",
Simple(SimpleAddr { base: Char(3), to_eol: true, to_bol: true });
"char full line plus minus"
"5-#3",
Simple(SimpleAddr { base: Line(4), suffixes: vec![RelativeChar(-3)] });
"line minus char"
)]
// Compound
#[test_case(",", Compound(Bof.into(), Eof.into()); "full")]
Expand All @@ -485,26 +472,39 @@ mod tests {
#[test_case("5,9", Compound(Line(4).into(), Line(8).into()); "from n to m")]
#[test_case("25,90", Compound(Line(24).into(), Line(89).into()); "from n to m multi digit")]
#[test_case("/foo/,/bar/", Compound(Regex(re("foo")).into(), Regex(re("bar")).into()); "regex range")]
// Compound with suffix
#[test_case(
"-/\\s/+#1,/\\s/-#1",
Compound(
SimpleAddr { base: RegexBack(re_rev("\\s")), suffixes: vec![RelativeChar(1)] },
SimpleAddr { base: Regex(re("\\s")), suffixes: vec![RelativeChar(-1)] },
);
"regex range with suffixes"
)]
#[test]
fn parse_works(s: &str, expected: Addr) {
let addr = Addr::parse(&mut s.chars().peekable()).expect("valid input");
assert_eq!(addr, expected);
}

#[test_case("0", Dot::default(); "bof")]
#[test_case("2", Dot::from_char_indices(15, 26); "line 2")]
#[test_case("-1", Dot::from_char_indices(0, 14); "line 1 relative to 2")]
#[test_case("/something/", Dot::from_char_indices(33, 42); "regex forward")]
#[test_case("-/line/", Dot::from_char_indices(10, 14); "regex back")]
#[test_case("-/his/", Dot::from_char_indices(1, 4); "regex back 2")]
#[test_case("0", Dot::default(), "t"; "bof")]
#[test_case("2", Dot::from_char_indices(15, 26), "and another\n"; "line 2")]
#[test_case("-1", Dot::from_char_indices(0, 14), "this is a line\n"; "line 1 relative to 2")]
#[test_case("/something/", Dot::from_char_indices(33, 41), "something"; "regex forward")]
#[test_case("-/line/", Dot::from_char_indices(10, 13), "line"; "regex back")]
#[test_case("-/his/", Dot::from_char_indices(1, 3), "his"; "regex back 2")]
#[test_case("-/a/,/a/", Dot::from_char_indices(15, 19), "and a"; "regex range")]
#[test_case("-/\\s/+#1,/\\s/-#1", Dot::from_char_indices(15, 17), "and"; "regex range boundaries")]
#[test]
fn map_addr_works(s: &str, expected: Dot) {
fn map_addr_works(s: &str, expected: Dot, expected_contents: &str) {
let mut b = Buffer::new_unnamed(0, "this is a line\nand another\n- [ ] something to do\n");
b.dot = Cur::new(16).into();

let mut addr = Addr::parse(&mut s.chars().peekable()).expect("valid addr");
b.dot = b.map_addr(&mut addr);

assert_eq!(b.dot, expected, ">{}<", b.dot_contents());
assert_eq!(b.dot_contents(), expected_contents);
}
}

49 changes: 25 additions & 24 deletions src/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl Program {
}

let (from, to) = initial_dot.as_char_indices();
let initial = &Match::synthetic(from, to);
let initial = &Match::synthetic(from, to + 1);

ed.begin_edit_transaction();
let (from, to) = self.step(ed, initial, 0, fname, out)?.as_char_indices();
Expand Down Expand Up @@ -527,32 +527,33 @@ mod tests {
assert_eq!(dot_content, "that");
}

#[test_case("/oo.fo/ d", "fo│foo"; "regex dot delete")]
#[test_case("/oo/,/oo/ d", "f│foo"; "regex dot range delete")]
#[test_case(", x/foo/ p/$0/", "foo│foo│foo"; "x print")]
#[test_case(", x/foo/ i/X/", "Xfoo│Xfoo│Xfoo"; "x insert")]
#[test_case(", x/foo/ a/X/", "fooX│fooX│fooX"; "x append")]
#[test_case(", x/foo/ c/X/", "X│X│X"; "x change")]
#[test_case(", x/foo/ c/XX/", "XX│XX│XX"; "x change 2")]
#[test_case(", x/foo/ d", "││"; "x delete")]
#[test_case(", x/foo/ s/o/X/", "fXo│fXo│fXo"; "x substitute")]
#[test_case(", y/foo/ p/>$0</", "foo│foo│foo"; "y print")]
#[test_case(", y/foo/ i/X/", "fooX│fooX│foo"; "y insert")]
#[test_case(", y/foo/ a/X/", "foo│Xfoo│Xfoo"; "y append")]
#[test_case(", y/foo/ c/X/", "fooXfooXfoo"; "y change")]
#[test_case(", y/foo/ d", "foofoofoo"; "y delete")]
#[test_case(", y/│/ d", "││"; "y delete 2")]
#[test_case(", s/oo/X/", "fX│foo│foo"; "sub single")]
#[test_case(", s/\\w+/X/", "X│foo│foo"; "sub word single")]
#[test_case(", s/oo/X/g", "fX│fX│fX"; "sub all")]
#[test_case(", s/.*/X/g", "X"; "sub all dot star")]
#[test_case(", x/\\b\\w+\\b/ c/X/", "X│X│X"; "change each word")]
#[test_case(", x/foo/ s/o/X/g", "fXX│fXX│fXX"; "nested loop x substitute all")]
#[test_case(", x/oo/ s/.*/X/g", "fX│fX│fX"; "nested loop x sub all dot star")]
#[test_case(0, "/oo.fo/ d", "fo│foo"; "regex dot delete")]
#[test_case(2, "-/f/,/f/ d", "oo│foo"; "regex dot range delete")]
#[test_case(0, ", x/foo/ p/$0/", "foo│foo│foo"; "x print")]
#[test_case(0, ", x/foo/ i/X/", "Xfoo│Xfoo│Xfoo"; "x insert")]
#[test_case(0, ", x/foo/ a/X/", "fooX│fooX│fooX"; "x append")]
#[test_case(0, ", x/foo/ c/X/", "X│X│X"; "x change")]
#[test_case(0, ", x/foo/ c/XX/", "XX│XX│XX"; "x change 2")]
#[test_case(0, ", x/foo/ d", "││"; "x delete")]
#[test_case(0, ", x/foo/ s/o/X/", "fXo│fXo│fXo"; "x substitute")]
#[test_case(0, ", y/foo/ p/>$0</", "foo│foo│foo"; "y print")]
#[test_case(0, ", y/foo/ i/X/", "fooX│fooX│fooX"; "y insert")]
#[test_case(0, ", y/foo/ a/X/", "foo│Xfoo│XfooX"; "y append")]
#[test_case(0, ", y/foo/ c/X/", "fooXfooXfooX"; "y change")]
#[test_case(0, ", y/foo/ d", "foofoofoo"; "y delete")]
#[test_case(0, ", y/│/ d", "││"; "y delete 2")]
#[test_case(0, ", s/oo/X/", "fX│foo│foo"; "sub single")]
#[test_case(0, ", s/\\w+/X/", "X│foo│foo"; "sub word single")]
#[test_case(0, ", s/oo/X/g", "fX│fX│fX"; "sub all")]
#[test_case(0, ", s/.*/X/g", "X"; "sub all dot star")]
#[test_case(0, ", x/\\b\\w+\\b/ c/X/", "X│X│X"; "change each word")]
#[test_case(0, ", x/foo/ s/o/X/g", "fXX│fXX│fXX"; "nested loop x substitute all")]
#[test_case(0, ", x/oo/ s/.*/X/g", "fX│fX│fX"; "nested loop x sub all dot star")]
#[test]
fn execute_produces_the_correct_string(s: &str, expected: &str) {
fn execute_produces_the_correct_string(idx: usize, s: &str, expected: &str) {
let mut prog = Program::try_parse(s).unwrap();
let mut b = Buffer::new_unnamed(0, "foo│foo│foo");
b.dot = Cur::new(idx).into();
prog.execute(&mut b, "test", &mut vec![]).unwrap();

assert_eq!(&b.txt.to_string(), expected, "buffer");
Expand Down
10 changes: 9 additions & 1 deletion src/regex/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub(super) const N_SLOTS: usize = 30;
/// optimisations and runs reasonably quickly. It is not at all designed to
/// be robust against mallicious input and it does not attempt to support
/// full PCRE syntax or functionality.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub struct Regex {
/// The compiled instructions for running the VM
prog: Prog,
Expand All @@ -51,6 +51,14 @@ pub struct Regex {
next: Option<char>,
}

impl std::fmt::Debug for Regex {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.debug_tuple("Regex")
.field(&self.prog)
.finish()
}
}

impl Regex {
/// Attempt to compile the given regular expression into its optimised VM opcode form.
///
Expand Down

0 comments on commit 1a6a8a3

Please sign in to comment.