implemented 'type' and 'wait' builtins

fixed some tcsetpgrp() misbehavior

fixed not being able to redirect stderr from builtins
This commit is contained in:
2026-03-01 17:14:48 -05:00
parent 84aed128d6
commit 2ea44c55e9
38 changed files with 922 additions and 635 deletions

View File

@@ -100,6 +100,9 @@ impl Span {
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
let source = SpanSource { name: "<stdin>".into(), content: source };
Span { range, source }
}
pub fn from_span_source(range: Range<usize>, source: SpanSource) -> Self {
Span { range, source }
}
pub fn rename(&mut self, name: String) {
self.source.name = name;
@@ -108,6 +111,12 @@ impl Span {
self.source.name = name;
self
}
pub fn line_and_col(&self) -> (usize,usize) {
let content = self.source.content();
let source = ariadne::Source::from(content.as_str());
let (_, line, col) = source.get_byte_line(self.range.start).unwrap();
(line, col)
}
/// Slice the source string at the wrapped range
pub fn as_str(&self) -> &str {
&self.source.content[self.range().start..self.range().end]
@@ -234,7 +243,9 @@ bitflags! {
pub struct LexStream {
source: Arc<String>,
pub cursor: usize,
pub name: String,
quote_state: QuoteState,
brc_grp_depth: usize,
brc_grp_start: Option<usize>,
flags: LexFlags,
}
@@ -243,23 +254,21 @@ bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct LexFlags: u32 {
/// The lexer is operating in interactive mode
const INTERACTIVE = 0b000000001;
const INTERACTIVE = 0b0000000001;
/// Allow unfinished input
const LEX_UNFINISHED = 0b000000010;
const LEX_UNFINISHED = 0b0000000010;
/// The next string-type token is a command name
const NEXT_IS_CMD = 0b000000100;
const NEXT_IS_CMD = 0b0000000100;
/// We are in a quotation, so quoting rules apply
const IN_QUOTE = 0b000001000;
const IN_QUOTE = 0b0000001000;
/// Only lex strings; used in expansions
const RAW = 0b000010000;
const RAW = 0b0000010000;
/// The lexer has not produced any tokens yet
const FRESH = 0b000010000;
const FRESH = 0b0000100000;
/// The lexer has no more tokens to produce
const STALE = 0b000100000;
/// The lexer's cursor is in a brace group
const IN_BRC_GRP = 0b001000000;
const EXPECTING_IN = 0b010000000;
const IN_CASE = 0b100000000;
const STALE = 0b0001000000;
const EXPECTING_IN = 0b0010000000;
const IN_CASE = 0b0100000000;
}
}
@@ -269,8 +278,10 @@ impl LexStream {
Self {
flags,
source,
name: "<stdin>".into(),
cursor: 0,
quote_state: QuoteState::default(),
brc_grp_depth: 0,
brc_grp_start: None,
}
}
@@ -296,18 +307,25 @@ impl LexStream {
};
self.source.get(start..end)
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn slice_from_cursor(&self) -> Option<&str> {
self.slice(self.cursor..)
}
pub fn in_brc_grp(&self) -> bool {
self.flags.contains(LexFlags::IN_BRC_GRP)
self.brc_grp_depth > 0
}
pub fn set_in_brc_grp(&mut self, is: bool) {
if is {
self.flags |= LexFlags::IN_BRC_GRP;
pub fn enter_brc_grp(&mut self) {
if self.brc_grp_depth == 0 {
self.brc_grp_start = Some(self.cursor);
} else {
self.flags &= !LexFlags::IN_BRC_GRP;
}
self.brc_grp_depth += 1;
}
pub fn leave_brc_grp(&mut self) {
self.brc_grp_depth -= 1;
if self.brc_grp_depth == 0 {
self.brc_grp_start = None;
}
}
@@ -627,7 +645,7 @@ impl LexStream {
pos += 1;
let mut tk = self.get_token(self.cursor..pos, TkRule::BraceGrpStart);
tk.flags |= TkFlags::IS_CMD;
self.set_in_brc_grp(true);
self.enter_brc_grp();
self.set_next_is_cmd(true);
self.cursor = pos;
@@ -636,7 +654,7 @@ impl LexStream {
'}' if pos == self.cursor && self.in_brc_grp() => {
pos += 1;
let tk = self.get_token(self.cursor..pos, TkRule::BraceGrpEnd);
self.set_in_brc_grp(false);
self.leave_brc_grp();
self.set_next_is_cmd(true);
self.cursor = pos;
return Ok(tk);
@@ -731,7 +749,8 @@ impl LexStream {
Ok(new_tk)
}
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
let span = Span::new(range, self.source.clone());
let mut span = Span::new(range, self.source.clone());
span.rename(self.name.clone());
Tk::new(class, span)
}
}