use crate::{BString, bstr}; #[cfg(test)] mod test; pub trait Stage: PartialEq { type Str: std::fmt::Debug + Clone + PartialEq; } pub trait CmdDisplay { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()>; } #[derive(Debug, Clone, PartialEq)] pub struct PreExpansion; #[derive(Debug, Clone, PartialEq)] pub struct PostExpansion; impl Stage for PreExpansion { type Str = ExpString; } impl Stage for PostExpansion { type Str = BString; } type Res = std::result::Result; pub trait Expander { type Error; fn expand_var(&mut self, v: BString, default: Option) -> Res; fn expand_cmd(&mut self, c: Ast) -> Res; type AliasAge; fn expand_alias( &mut self, cmd: &bstr, age: Option, ) -> Res)>, Self::Error> { let _ = cmd; let _ = age; Ok(None) } } #[derive(Debug, Clone, PartialEq)] pub struct Block { pub commands: Vec>, } #[derive(Debug, Clone, PartialEq)] pub struct Script { pub stmts: Vec>, } impl Parse for Script { fn parse(b: &mut Cursor<'_>) -> Result { let mut stmts = Vec::new(); loop { b.spaces(); if b.is_empty() { break; } match Ast::parse(b) { Ok(s) => stmts.push(s), Err(ParseError::Eof) => break, Err(e) => Err(e)?, } } Ok(Script { stmts }) } } #[derive(Debug, Clone, PartialEq)] pub enum Ast { FunDecl(FunDecl), VarAssign(VarAssign), Pipes(Pipes), } pub fn decl(name: ExpString, body: Block) -> Ast { Ast::FunDecl(FunDecl { name, body }) } pub fn assign(var: ExpString, val: ExpString) -> Ast { Ast::VarAssign(VarAssign { var, val }) } pub fn pipes(cmds: [Command; N]) -> Ast { Ast::Pipes(Pipes { cmds: cmds.to_vec(), }) } pub fn estr(x: &[u8]) -> ExpString { ExpString { parts: vec![StringPart::Boring(x.to_vec())], delim: StringDelimiter::None, } } pub fn str(parts: [StringPart; N]) -> ExpString { ExpString { parts: parts.to_vec(), delim: StringDelimiter::None, } } pub fn plain(x: &[u8]) -> StringPart { StringPart::Boring(x.to_vec()) } pub fn var(x: &[u8]) -> StringPart { StringPart::Var(Var { name: VarName { name: x.to_vec() }, default: None, already_complete: true, }) } pub fn var_default(x: &[u8], default: ExpString) -> StringPart { StringPart::Var(Var { name: VarName { name: x.to_vec() }, default: Some(default), already_complete: true, }) } pub fn cmdp(x: Ast) -> StringPart { StringPart::Cmd(CmdInterp { cmd: x, already_complete: true, }) } pub fn cmd(x: [ExpString; N]) -> Command { Command { cmd: x[0].clone(), args: x[1..].to_vec(), } } pub fn block(x: [Ast; N]) -> Block { Block { commands: x.to_vec(), } } impl CmdDisplay for Block { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { write!(w, "block([")?; let mut prev = false; for cmd in self.commands.iter() { if prev { write!(w, ",")?; } prev = true; cmd.cdisplay(w)?; } write!(w, "])") } } impl CmdDisplay for Ast { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { match self { Ast::FunDecl(fun_decl) => { write!(w, "decl(")?; fun_decl.name.cdisplay(w)?; write!(w, ", ")?; fun_decl.body.cdisplay(w)?; write!(w, ")")?; } Ast::VarAssign(var_assign) => { write!(w, "assign(")?; var_assign.var.cdisplay(w)?; write!(w, ", ")?; var_assign.val.cdisplay(w)?; write!(w, ")")?; } Ast::Pipes(pipes) => { write!(w, "pipes([")?; for cmd in pipes.cmds.iter() { cmd.cdisplay(w)?; write!(w, ",")?; } write!(w, "])")?; } } Ok(()) } } impl CmdDisplay for ExpString { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { if self.parts.len() == 1 && self.parts[0].is_boring() { write!( w, "estr(b\"{}\")", self.parts[0].clone().unwrap_boring().escape_ascii() ) } else { write!(w, "str([")?; let mut first = true; for part in self.parts.iter() { if !first { write!(w, ",")?; } first = false; part.cdisplay(w)?; } write!(w, "])") } } } impl CmdDisplay for StringPart { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { match self { StringPart::Boring(items) => { write!(w, "plain(")?; items.as_slice().cdisplay(w)?; write!(w, ")") } StringPart::Var(var) => { if let Some(default) = &var.default { write!(w, "var_default(")?; var.name.name.as_slice().cdisplay(w)?; write!(w, ",")?; default.cdisplay(w)?; write!(w, ")") } else { write!(w, "var(")?; var.name.name.as_slice().cdisplay(w)?; write!(w, ")") } } StringPart::Cmd(ast) => { write!(w, "cmdp(")?; ast.cmd.cdisplay(w)?; write!(w, ")") } } } } impl CmdDisplay for Command { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { write!(w, "cmd([")?; self.cmd.cdisplay(w)?; for arg in self.args.iter() { write!(w, ", ")?; arg.cdisplay(w)?; } write!(w, "])") } } impl CmdDisplay for &[u8] { fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { write!(w, "b\"")?; write!(w, "{}", self.escape_ascii())?; write!(w, "\"") } } impl Ast { pub fn expand(self, e: &mut E) -> Res, E::Error> { match self { Ast::VarAssign(va) => Ok(Ast::VarAssign(va.expand(e)?)), Ast::Pipes(pipes) => Ok(Ast::Pipes(pipes.expand(e)?)), Ast::FunDecl(fd) => Ok(Ast::FunDecl(fd.expand(e)?)), } } } #[derive(Debug, Clone, PartialEq)] pub struct FunBody { pub body: Box>, } impl Parse for FunBody { fn parse(b: &mut Cursor<'_>) -> Result { b.spaces(); if b.is_empty() { return Err(ParseError::Eof); } if b.peek() != b'{' { return Err(ParseError::Expected('{')); } b.adv(); let body = Box::new(Ast::parse(b)?); b.spaces(); if b.is_empty() { if b.is_completion() { Ok(Self { body }) } else { Err(ParseError::Eof) } } else if b.peek() == b'}' { Ok(Self { body }) } else { Err(ParseError::Expected('}')) } } } #[derive(Debug, Clone, PartialEq)] pub struct FunDecl { pub name: S::Str, pub body: Block, } impl Parse for Block { fn parse(b: &mut Cursor<'_>) -> Result { let mut commands = Vec::new(); b.spaces(); if b.has() && b.peek() == b'{' { b.adv(); } else { return Err(ParseError::NotABlock); } loop { while { b.spaces(); b.has() && b.peek() == b';' } { b.adv(); } if b.has() && b"})".contains(&b.peek()) { break; } let cmd = Ast::parse(b)?; commands.push(cmd); } if b.has() { if b.peek() != b'}' { return Err(ParseError::Expected('}')); } } else if !b.is_completion() { return Err(ParseError::Expected('}')); } Ok(Self { commands }) } } impl Parse for FunDecl { fn parse(b: &mut Cursor<'_>) -> Result { if !b.buf.starts_with(b"fun ") && !b.buf.starts_with(b"fun\t") { return Err(ParseError::NotAFunDecl); } b.advance(4); b.spaces(); let name = ExpString::parse(b)?; let body = Block::parse(b)?; Ok(Self { name, body }) } } impl FunDecl { fn expand(self, e: &mut E) -> Res, E::Error> { Ok(FunDecl { name: self.name.expand(e)?, body: self.body, }) } } #[derive(Debug, Clone, PartialEq)] pub struct VarAssign { pub var: S::Str, pub val: S::Str, } impl Parse for VarAssign { fn parse(b: &mut Cursor<'_>) -> Result { if !b.buf.starts_with(b"set ") && !b.buf.starts_with(b"set\t") { return Err(ParseError::NotAVarAssign); } b.advance(4); b.spaces(); let var = ExpString::parse(b)?; b.spaces(); if b.is_empty() { return Err(ParseError::Eof); } let eq = b.adv(); if eq != b'=' { return Err(ParseError::Expected('=')); } let val = ExpString::parse(b)?; Ok(Self { var, val }) } } impl VarAssign { fn expand(self, e: &mut E) -> Res, E::Error> { Ok(VarAssign { var: self.var.expand(e)?, val: self.val.expand(e)?, }) } } #[derive(Debug, Clone, PartialEq)] pub struct Pipes { pub cmds: Vec>, } impl Pipes { fn expand(self, e: &mut E) -> Res, E::Error> { let mut cmds = Vec::with_capacity(self.cmds.len()); for cmd in self.cmds.into_iter() { cmds.push(cmd.expand(e)?); } Ok(Pipes { cmds }) } } #[derive(Debug, Clone, PartialEq)] pub enum StringPart { Boring(BString), Var(Var), Cmd(CmdInterp), } #[derive(Debug, Clone, PartialEq)] pub struct CmdInterp { pub cmd: Ast, pub already_complete: bool, } #[derive(Debug, Clone, PartialEq)] pub struct Var { name: VarName, default: Option, /// if pressing tab right after the parsed variable should not try to complete the variable /// /// i.e. `${HOM}` -> true, `$HOM` -> false, `${HOM` -> false already_complete: bool, } impl Var { pub fn new(name: VarName) -> Self { Self { name, default: None, already_complete: false, } } } impl StringPart { pub fn is_boring(&self) -> bool { matches!(self, StringPart::Boring(..)) } pub fn unwrap_boring(self) -> BString { match self { StringPart::Boring(items) => items, _ => panic!("unwrap on non-boring value"), } } } #[derive(Debug, Clone, PartialEq)] /// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]` pub struct ExpString { parts: Vec, delim: StringDelimiter, } impl ExpString { fn expand(self, e: &mut E) -> Res { let mut out = BString::new(); for part in self.parts.into_iter() { let mut x = match part { StringPart::Boring(items) => items, StringPart::Var(v) => { let default = match v.default { Some(default) => Some(default.expand(e)?), None => None, }; e.expand_var(v.name.name, default)? } StringPart::Cmd(ast) => { let exp = ast.cmd.expand(e)?; e.expand_cmd(exp)? } }; out.append(&mut x); } Ok(out) } } fn is_symbol(x: u8) -> bool { matches!( x, b';' | b'|' | b'{' | b'}' | b'$' | b'(' | b')' | b'\'' | b'"' ) } fn is_var_begin(x: u8) -> bool { x.is_ascii_alphanumeric() } fn is_var_name(x: u8) -> bool { x.is_ascii_alphanumeric() || x == b'_' } #[derive(Debug, Clone, PartialEq)] pub struct VarName { name: BString, } impl Parse for VarName { fn parse(b: &mut Cursor<'_>) -> Result { if b.is_empty() { return Err(ParseError::Eof); } let mut name = BString::new(); if b.peek().is_ascii_digit() { while b.has() && b.peek().is_ascii_digit() { name.push(b.adv()); } return Ok(Self { name }); } if !is_var_begin(b.peek()) { return Err(ParseError::ExpectedAlphabetic); } while b.has() { let x = b.peek(); if is_var_name(x) { b.adv(); name.push(x) } else { break; } } Ok(Self { name }) } } #[derive(Clone, Debug, PartialEq)] pub enum StringDelimiter { /// no delimiter, i.e. when parsing a simple command like `echo foo` None, /// double quotes, allows interpolation, `echo "foo $var"` Interp, /// single quotes, does not allow interpolation `echo 'foo $vardoesnotexpand'` Strict, /// triple quotes with custom prefix/suffix ``` /// echo DELIM""" /// basically /// a /// heredoc /// with /// $variables /// """DELIM /// ``` InterpCustom(BString), /// triple single quotes with custom prefix/suffix ``` /// echo FOO''' /// basically /// a /// heredoc /// without /// variables /// '''FOO /// ``` StrictCustom(BString), } trait PushAll { fn push_all(&mut self, other: &bstr); } impl PushAll for BString { fn push_all(&mut self, other: &bstr) { for &c in other { self.push(c); } } } /// gets the largest ident this slice starts with, might be empty fn peek_ident(b: &[u8]) -> &[u8] { if b.is_empty() || !b[0].is_ascii_alphabetic() { return &[]; } let mut out = &b[..1]; for i in 1..b.len() { if b[i].is_ascii_alphanumeric() { out = &b[..=i]; } else { break; } } out } impl StringDelimiter { fn try_begin(b: &mut Cursor<'_>) -> Option { if !b.has() { return None; } let ident = peek_ident(&b.buf); if b.buf[ident.len()..].starts_with(b"\"\"\"") { b.advance(ident.len() + 3); if b.has() && b.peek() == b'\n' { b.adv(); } return Some(Self::InterpCustom(ident.to_vec())); } if b.buf[ident.len()..].starts_with(b"'''") { b.advance(ident.len() + 3); if b.has() && b.peek() == b'\n' { b.adv(); } return Some(Self::StrictCustom(ident.to_vec())); } // at this point we know it's not a custom identifier with triple quotes let x = b.peek(); if !x.is_ascii_whitespace() && (!is_symbol(x) || x == b'$') { return Some(Self::None); } if x == b'"' { b.adv(); return Some(Self::Interp); } if x == b'\'' { b.adv(); return Some(Self::Strict); } None } /// if the current string ends right at the cursor, consumes the string closing tokens and returns true /// otherwise, consumes no tokens and returns false fn try_end(&self, b: &mut Cursor<'_>) -> bool { if !b.has() { return matches!(self, Self::None); } let x = b.peek(); let buf = &mut b.buf; match self { StringDelimiter::None if x.is_ascii_whitespace() || is_symbol(x) && x != b'$' => true, StringDelimiter::Interp if x == b'"' => { b.adv(); true } StringDelimiter::Strict if x == b'\'' => { b.adv(); true } StringDelimiter::InterpCustom(delim) if buf.len() >= 3 && &buf[..3] == b"\"\"\"" && buf[3..].starts_with(&delim) => { b.advance(3 + delim.len()); true } StringDelimiter::StrictCustom(delim) if buf.len() >= 3 && &buf[..3] == b"'''" && buf[3..].starts_with(&delim) => { b.advance(3 + delim.len()); true } _ => false, } } fn is_strict(&self) -> bool { matches!(self, Self::Strict | Self::StrictCustom(_)) } fn is_none(&self) -> bool { matches!(self, Self::None) } /// assuming that `s` will be placed in the middle of some specifically delimited string pub fn escape(&self, mut s: &bstr, out: &mut BString) { while !s.is_empty() { let first = s[0]; match self { StringDelimiter::None => { if matches!(first, b' ' | b'$' | b'\\' | b'\'' | b'"') { out.push(b'\\'); } } StringDelimiter::Interp | StringDelimiter::InterpCustom(_) => { if matches!(first, b'$' | b'\\' | b'"') { out.push(b'\\'); } } StringDelimiter::Strict => { if first == b'\'' { out.push_all(b"'\\'"); } } StringDelimiter::StrictCustom(delim) => { if s.starts_with(b"'''") && s[3..].starts_with(delim) { out.push_all(b"'''"); out.push_all(delim); out.push_all(b"\\'\\'\\'"); out.push_all(delim); out.push_all(b"''"); out.push_all(delim); out.push_all(b"'''"); s = &s[3 + delim.len()..]; continue; } } } out.push(first); s = &s[1..]; } } pub fn write_closing_delimiter(&self, out: &mut BString) { match self { StringDelimiter::None => (), StringDelimiter::Interp => out.push(b'"'), StringDelimiter::Strict => out.push(b'\''), StringDelimiter::InterpCustom(delim) => { out.push_all(b"\"\"\""); out.push_all(&delim); } StringDelimiter::StrictCustom(delim) => { out.push_all(b"'''"); out.push_all(&delim); } } } } fn parse_escape_code(b: &mut Cursor<'_>) -> Result> { if !b.has() { return Err(ParseError::Eof); } let x = b.adv(); let y = match x { b'\n' => return Ok(None), b'n' => b'\n', b'r' => b'\r', b't' => b'\t', b'e' => 0x1b, // escape b'x' => { // parse two hex digits if b.buf.len() < 2 { Err(ParseError::Eof)?; } let x1 = b.adv(); let x2 = b.adv(); if !x1.is_ascii_hexdigit() || !x2.is_ascii_hexdigit() { Err(ParseError::NotHexDigit)?; } let x1 = (x1 as char).to_digit(16).unwrap_or(0); let x2 = (x2 as char).to_digit(16).unwrap_or(0); ((x1 << 4) | x2) as u8 } _ => x, }; Ok(Some(y)) } impl Parse for ExpString { fn parse(b: &mut Cursor<'_>) -> Result { b.spaces(); if b.is_empty() { return Err(ParseError::NotAString); } let mut parts = Vec::new(); let p = &mut parts; let add_char = |p: &mut Vec, x: u8| match p.last_mut() { Some(StringPart::Boring(v)) => v.push(x), _ => p.push(StringPart::Boring(vec![x])), }; let mut already_parsed = false; let mut last_delim = StringDelimiter::None; 'outer: loop { let Some(delim) = StringDelimiter::try_begin(b) else { break; }; last_delim = delim.clone(); already_parsed = true; while !delim.try_end(b) { if !b.has() { if b.is_completion() { break 'outer; } else { return Err(ParseError::Eof); } } let x = b.adv(); if x == b'\\' { if let Some(x) = parse_escape_code(b)? { add_char(p, x); } } else if x == b'$' && !delim.is_strict() { if !b.has() { add_char(p, b'$'); continue; } let x = b.peek(); if is_var_begin(x) { let v = VarName::parse(b)?; p.push(StringPart::Var(Var::new(v))); continue; } b.adv(); match x { b'?' | b'!' => p.push(StringPart::Var(Var::new(VarName { name: vec![x] }))), b'{' => { let v = VarName::parse(b)?; let mut default = None; if !b.has() { if !b.is_completion() { return Err(ParseError::Eof); } } else if b.peek() == b':' { b.adv(); if !b.has() { return Err(ParseError::Eof); } if b.peek() == b'-' { b.adv(); default = Some(ExpString::parse(b)?); } else { todo!(": in var expansion") } } if !b.has() { if !b.is_completion() { return Err(ParseError::Eof); } } else if b.peek() != b'}' { return Err(ParseError::Expected('}')); } let already_complete = b.has(); if already_complete { b.adv(); } p.push(StringPart::Var(Var { name: v, default, already_complete, })); } b'(' => { let cmd = Ast::parse(b)?; b.spaces(); if b.is_empty() && !b.is_completion() { return Err(ParseError::Expected(')')); } if b.has() && b.peek() == b')' { b.adv(); p.push(StringPart::Cmd(CmdInterp { cmd, already_complete: true, })); } else if b.is_completion() { p.push(StringPart::Cmd(CmdInterp { cmd, already_complete: false, })) } else { return Err(ParseError::Expected(')')); } } x => { add_char(p, b'$'); add_char(p, x); } } } else if delim.is_none() && x == b'~' && p.is_empty() && (!b.has() || b.peek().is_ascii_whitespace() || b.peek() == b'/') { p.push(StringPart::Var(Var { name: VarName { name: b"HOME".to_vec(), }, default: None, already_complete: true, })); } else { add_char(p, x); } } } if already_parsed { Ok(Self { parts, delim: last_delim, }) } else { Err(ParseError::NotAString) } } } impl Parse for Vec { fn parse(b: &mut Cursor<'_>) -> Result { let mut strings = Vec::new(); loop { match ExpString::parse(b) { Ok(s) => strings.push(s), Err(ParseError::Eof) => break, Err(ParseError::NotAString) => break, Err(e) => Err(e)?, } } Ok(strings) } } #[derive(Debug, Clone, PartialEq)] pub struct Command { pub cmd: T::Str, pub args: Vec, } impl Command { fn full_alias_expansion(&mut self, e: &mut E) -> Res<(), E::Error> { self.args.reverse(); let mut age = None; while self.cmd.parts.len() == 1 && let StringPart::Boring(s) = &self.cmd.parts[0] { if let Some((new_age, exp)) = e.expand_alias(&s, age.take())? { age = Some(new_age); self.cmd = exp.first().unwrap().clone(); for e in exp.into_iter().skip(1).rev() { self.args.push(e); } } else { break; } } self.args.reverse(); Ok(()) } fn expand(mut self, e: &mut E) -> Res, E::Error> { self.full_alias_expansion(e)?; let cmd = self.cmd.expand(e)?; let mut args = Vec::with_capacity(self.args.len()); for arg in self.args.into_iter() { args.push(arg.expand(e)?); } Ok(Command { cmd, args }) } } #[allow(unused)] #[derive(Debug, PartialEq)] pub enum ParseError { /// "clean" EOF, i.e. not in the middle of something Eof, /// "unclean" EOF, i.e. EOF after beginning a quoted string Incomplete, ExpectedAlphabetic, Unknown(u8), Expected(char), NotAString, NotAFunDecl, NotAVarAssign, NotHexDigit, NotABlock, } type Result = std::result::Result; pub fn do_parse(x: &[u8]) -> Res, (ParseError, &[u8])> { let mut c = Cursor::new(x, ParseMode::Command); match Ast::parse(&mut c) { Ok(ast) => Ok(ast), Err(e) => Err((e, c.buf)), } } #[derive(Debug, PartialEq, Clone)] pub enum CompletionKind { Command, PathCommand, Argument, Variable, None, } pub struct CompletionContext { pub kind: CompletionKind, pub partial: BString, pub delim: StringDelimiter, } impl CompletionContext { pub fn none() -> Self { Self { kind: CompletionKind::None, partial: BString::new(), delim: StringDelimiter::None, } } } impl Block { fn completion(&self, e: &mut E) -> CompletionContext { if let Some(cmd) = self.commands.last() { cmd.completion(e) } else { CompletionContext::none() } } } impl Ast { fn completion(&self, e: &mut E) -> CompletionContext { match self { Ast::FunDecl(fd) => fd.body.completion(e), Ast::VarAssign(va) => va.val.completion(e, CompletionKind::Argument), Ast::Pipes(p) => p.completion(e), } } } impl ExpString { fn completion(&self, e: &mut E, mut kind: CompletionKind) -> CompletionContext { if let Some(StringPart::Var(var)) = self.parts.last() && !var.already_complete { CompletionContext { kind: CompletionKind::Variable, partial: var.name.name.clone(), delim: self.delim.clone(), } } else if let Some(StringPart::Cmd(cmd)) = self.parts.last() && !cmd.already_complete { cmd.cmd.completion(e) } else if let Ok(s) = self.clone().expand(e) { if s.contains(&b'/') && kind == CompletionKind::Command { kind = CompletionKind::PathCommand; } CompletionContext { kind, partial: s, delim: self.delim.clone(), } } else { CompletionContext::none() } } } impl Pipes { fn completion(&self, e: &mut E) -> CompletionContext { let Some(cmd) = self.cmds.last() else { return CompletionContext::none(); }; if let Some(arg) = cmd.args.last() { arg.completion(e, CompletionKind::Argument) } else { cmd.cmd.completion(e, CompletionKind::Command) } } } pub fn completion_context(x: &[u8], e: &mut E) -> CompletionContext { let mut cursor = Cursor::new(x, ParseMode::Completion); let ast = Ast::parse(&mut cursor); let Ok(ast) = ast else { return CompletionContext::none(); }; if cursor.spaced { return CompletionContext::none(); } ast.completion(e) } pub trait Parse: Sized { fn parse(b: &mut Cursor<'_>) -> Result; fn parse_from_bytes(x: &[u8]) -> Result { let mut c = Cursor::new(x, ParseMode::Command); let parsed = Self::parse(&mut c)?; if c.has() { return Err(ParseError::Unknown(c.buf[0])); } Ok(parsed) } } enum ParseMode { Command, Completion, } pub struct Cursor<'a> { buf: &'a [u8], mode: ParseMode, /// if the last byte that was consumed was whitespace or part of a word spaced: bool, backtrace: bool, } #[derive(Default)] struct SpaceStats { space: u32, tab: u32, lf: u32, cr: u32, } impl<'a> Cursor<'a> { fn new(buf: &'a [u8], mode: ParseMode) -> Self { Self { buf, mode, spaced: false, backtrace: false, } } // non empty fn has(&self) -> bool { !self.buf.is_empty() } fn is_empty(&self) -> bool { self.buf.is_empty() } fn bt(&self, word: &str) { if self.backtrace { let bt = std::backtrace::Backtrace::capture(); let bt = format!("{bt}"); println!("{word} {}\r", self.buf[0] as char); for l in bt.lines().skip(4).take(2) { println!("{l}\r"); } println!("\r"); } } fn peek(&self) -> u8 { self.bt("peek"); self.buf[0] } fn adv(&mut self) -> u8 { self.bt("adv"); let out = self.buf[0]; self.buf = &self.buf[1..]; self.spaced = false; out } fn advance(&mut self, amt: usize) -> &[u8] { self.bt(&format!("adv({amt})")); let out = &self.buf[..amt]; self.buf = &self.buf[amt..]; self.spaced = false; out } fn peek_space(&self) -> bool { if self.buf.is_empty() { return false; } matches!(self.buf[0], b' ' | b'\t' | b'\n' | b'\r') } fn spaces(&mut self) { while self.peek_space() { self.adv(); self.spaced = true; } } fn spaces_stats(&mut self) -> SpaceStats { let mut stats = SpaceStats::default(); while self.has() && b" \t\n\r".contains(&self.buf[0]) { match self.buf[0] { b' ' => stats.space += 1, b'\t' => stats.tab += 1, b'\n' => stats.lf += 1, b'\r' => stats.cr += 1, _ => unreachable!(), } self.adv(); } stats } /// returns true if the next thing in the buffer is whitespace (including at least one newline) /// /// does not modify the buffer fn whitespace_newline(&mut self) -> bool { let x = self.buf; let s = self.spaces_stats(); self.buf = x; s.lf > 0 } fn is_completion(&self) -> bool { matches!(self.mode, ParseMode::Completion) } fn parse(&mut self) -> Result { T::parse(self) } } impl Parse for Ast { fn parse(b: &mut Cursor<'_>) -> Result { b.spaces(); let orig_len = b.buf.len(); let x = VarAssign::parse(b); if let Ok(va) = x { return Ok(Self::VarAssign(va)); } else if b.buf.len() != orig_len { x?; } let orig_len = b.buf.len(); let x = FunDecl::parse(b); if let Ok(fd) = x { return Ok(Self::FunDecl(fd)); } else if b.buf.len() != orig_len { x?; } Ok(Self::Pipes(b.parse()?)) } } impl Parse for Command { fn parse(b: &mut Cursor<'_>) -> Result { let path: ExpString = b.parse()?; let mut args = Vec::new(); while !b.whitespace_newline() { match ExpString::parse(b) { Ok(arg) => args.push(arg), Err(ParseError::NotAString) => break, Err(e) => Err(e)?, } } Ok(Self { cmd: path, args }) } } impl Parse for Pipes { fn parse(b: &mut Cursor<'_>) -> Result { let mut cmds: Vec> = vec![b.parse()?]; loop { let space_stats = b.spaces_stats(); if b.is_empty() { return Ok(Pipes { cmds }); } let c = b.peek(); if c == b'|' { b.adv(); cmds.push(b.parse()?); } else if space_stats.lf > 0 || is_symbol(c) { return Ok(Pipes { cmds }); } else { Err(ParseError::Unknown(c))?; } } } }