Implemented case statements

This commit is contained in:
2025-03-19 02:16:53 -04:00
parent 19dee4bcd6
commit aa2684546e
12 changed files with 2809 additions and 100 deletions

View File

@@ -3,7 +3,7 @@ use std::collections::VecDeque;
use crate::{builtin::{alias::alias, cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}};
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags, KEYWORDS}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, ParsedSrc, Redir, RedirType};
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags, KEYWORDS}, AssignKind, CaseNode, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, ParsedSrc, Redir, RedirType};
pub enum AssignBehavior {
Export,
@@ -62,6 +62,7 @@ impl Dispatcher {
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
NdRule::IfNode {..} => self.exec_if(node)?,
NdRule::LoopNode {..} => self.exec_loop(node)?,
NdRule::CaseNode {..} => self.exec_case(node)?,
NdRule::BraceGrp {..} => self.exec_brc_grp(node)?,
NdRule::FuncDef {..} => self.exec_func_def(node)?,
NdRule::Command {..} => self.dispatch_cmd(node)?,
@@ -181,6 +182,39 @@ impl Dispatcher {
Ok(())
}
pub fn exec_case(&mut self, case_stmt: Node) -> ShResult<()> {
let NdRule::CaseNode { pattern, case_blocks } = case_stmt.class else {
unreachable!()
};
self.io_stack.append_to_frame(case_stmt.redirs);
flog!(DEBUG,pattern.span.as_str());
let exp_pattern = pattern.clone().expand(pattern.span.clone(), pattern.flags.clone());
let pattern_raw = exp_pattern
.get_words()
.first()
.map(|s| s.to_string())
.unwrap_or_default();
flog!(DEBUG,exp_pattern);
for block in case_blocks {
let CaseNode { pattern, body } = block;
let block_pattern_raw = pattern.span.as_str().trim_end_matches(')').trim();
// Split at '|' to allow for multiple patterns like `foo|bar)`
let block_patterns = block_pattern_raw.split('|');
for pattern in block_patterns {
if pattern_raw == pattern || pattern == "*" {
for node in &body {
self.dispatch_node(node.clone())?;
}
}
}
}
Ok(())
}
pub fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
unreachable!();

View File

@@ -1,4 +1,4 @@
use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}, str::Chars};
use bitflags::bitflags;
@@ -76,6 +76,7 @@ pub enum TkRule {
Bg,
Sep,
Redir,
CasePattern,
BraceGrpStart,
BraceGrpEnd,
Expanded { exp: Vec<String> },
@@ -110,6 +111,13 @@ impl Tk {
pub fn source(&self) -> Rc<String> {
self.span.source.clone()
}
/// Used to see if a separator is ';;' for case statements
pub fn has_double_semi(&self) -> bool {
let TkRule::Sep = self.class else {
return false;
};
self.span.as_str().trim() == ";;"
}
}
impl Display for Tk {
@@ -143,6 +151,7 @@ pub struct LexStream {
}
bitflags! {
#[derive(Debug)]
pub struct LexFlags: u32 {
/// Return comment tokens
const LEX_COMMENTS = 0b00000001;
@@ -158,7 +167,9 @@ bitflags! {
const FRESH = 0b00010000;
/// The lexer has no more tokens to produce
const STALE = 0b00100000;
/// The lexer's cursor is in a brace group
const IN_BRC_GRP = 0b01000000;
const EXPECTING_IN = 0b10000000;
}
}
@@ -300,6 +311,15 @@ impl LexStream {
let mut pos = self.cursor;
let mut chars = slice.chars();
let mut quote_pos = None;
if let Some(count) = case_pat_lookahead(chars.clone()) {
pos += count;
let casepat_tk = self.get_token(self.cursor..pos, TkRule::CasePattern);
self.cursor = pos;
self.set_next_is_cmd(true);
return Ok(casepat_tk)
}
while let Some(ch) = chars.next() {
match ch {
_ if self.flags.contains(LexFlags::RAW) => {
@@ -373,22 +393,39 @@ impl LexStream {
)
);
}
// TODO: clean up this mess
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
flog!(DEBUG,&new_tk.span.as_str());
if is_keyword(&new_tk.span.as_str()) {
flog!(DEBUG,"is keyword");
new_tk.flags |= TkFlags::KEYWORD;
if matches!(new_tk.span.as_str(), "case" | "select" | "for") {
self.flags |= LexFlags::EXPECTING_IN;
new_tk.flags |= TkFlags::KEYWORD;
self.set_next_is_cmd(false);
} else {
new_tk.flags |= TkFlags::KEYWORD;
}
} else if is_assignment(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::ASSIGN;
} else {
new_tk.flags |= TkFlags::IS_CMD;
flog!(TRACE, new_tk.span.as_str());
if self.flags.contains(LexFlags::EXPECTING_IN) {
if new_tk.span.as_str() != "in" {
new_tk.flags |= TkFlags::IS_CMD;
} else {
new_tk.flags |= TkFlags::KEYWORD;
self.flags &= !LexFlags::EXPECTING_IN;
}
} else {
new_tk.flags |= TkFlags::IS_CMD;
}
if BUILTINS.contains(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::BUILTIN;
}
flog!(TRACE, new_tk.flags);
self.set_next_is_cmd(false);
}
} else if self.flags.contains(LexFlags::EXPECTING_IN) {
if new_tk.span.as_str() == "in" {
new_tk.flags |= TkFlags::KEYWORD;
self.flags &= !LexFlags::EXPECTING_IN;
}
}
self.cursor = pos;
Ok(new_tk)
@@ -557,3 +594,18 @@ pub fn is_keyword(slice: &str) -> bool {
KEYWORDS.contains(&slice) ||
(slice.ends_with("()") && !slice.ends_with("\\()"))
}
pub fn case_pat_lookahead(mut chars: Chars) -> Option<usize> {
let mut pos = 0;
while let Some(ch) = chars.next() {
pos += 1;
match ch {
_ if is_hard_sep(ch) => return None,
'\\' => { chars.next(); }
')' => return Some(pos),
'(' => return None,
_ => { /* continue */ }
}
}
None
}

View File

@@ -385,9 +385,16 @@ impl ParseStream {
fn next_tk_is_some(&self) -> bool {
self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI)
}
fn check_case_pattern(&self) -> bool {
self.tokens.first().is_some_and(|tk| tk.class == TkRule::CasePattern)
}
fn check_keyword(&self, kw: &str) -> bool {
self.tokens.first().is_some_and(|tk| {
tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw
if kw == "in" {
tk.span.as_str() == "in"
} else {
tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw
}
})
}
fn check_redir(&self) -> bool {
@@ -441,6 +448,7 @@ impl ParseStream {
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
try_match!(self.parse_func_def()?);
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
try_match!(self.parse_case()?);
try_match!(self.parse_loop()?);
try_match!(self.parse_if()?);
if check_pipelines {
@@ -556,6 +564,73 @@ impl ParseStream {
};
Ok(Some(node))
}
fn parse_case(&mut self) -> ShResult<Option<Node>> {
// Needs a pattern token
// Followed by any number of CaseNodes
let mut node_tks: Vec<Tk> = vec![];
let pattern: Tk;
let mut case_blocks: Vec<CaseNode> = vec![];
let mut redirs: Vec<Redir> = vec![];
if !self.check_keyword("case") || !self.next_tk_is_some() {
return Ok(None)
}
node_tks.push(self.next_tk().unwrap());
let Some(pat_tk) = self.next_tk() else {
return Err(parse_err_full("Expected a pattern after 'case'", &node_tks.get_span().unwrap()));
};
pattern = pat_tk;
node_tks.push(pattern.clone());
if !self.check_keyword("in") || !self.next_tk_is_some() {
return Err(parse_err_full("Expected 'in' after case variable name", &node_tks.get_span().unwrap()));
}
node_tks.push(self.next_tk().unwrap());
self.catch_separator(&mut node_tks);
loop {
if !self.check_case_pattern() || !self.next_tk_is_some() {
return Err(parse_err_full("Expected a case pattern here", &node_tks.get_span().unwrap()));
}
let case_pat_tk = self.next_tk().unwrap();
node_tks.push(case_pat_tk.clone());
let mut nodes = vec![];
while let Some(node) = self.parse_block(true /* check_pipelines */)? {
node_tks.extend(node.tokens.clone());
let sep = node.tokens.last().unwrap();
if sep.has_double_semi() {
nodes.push(node);
break
} else {
nodes.push(node);
}
}
let case_node = CaseNode { pattern: case_pat_tk, body: nodes };
case_blocks.push(case_node);
if self.check_keyword("esac") {
node_tks.push(self.next_tk().unwrap());
break
}
if !self.next_tk_is_some() {
return Err(parse_err_full("Expected 'esac' after case block", &node_tks.get_span().unwrap()));
}
}
let node = Node {
class: NdRule::CaseNode { pattern, case_blocks },
flags: NdFlags::empty(),
redirs,
tokens: node_tks
};
Ok(Some(node))
}
fn parse_if(&mut self) -> ShResult<Option<Node>> {
// Needs at last one 'if-then',
// Any number of 'elif-then',
@@ -563,7 +638,7 @@ impl ParseStream {
let mut node_tks: Vec<Tk> = vec![];
let mut cond_nodes: Vec<CondNode> = vec![];
let mut else_block: Vec<Node> = vec![];
let mut redirs = vec![];
let mut redirs: Vec<Redir> = vec![];
if !self.check_keyword("if") || !self.next_tk_is_some() {
return Ok(None)