Early implementation of scripting elements

This commit is contained in:
2025-03-05 01:36:58 -05:00
parent 5dd9ee96ad
commit 1b3e2c0887
28 changed files with 1384 additions and 371 deletions

View File

@@ -1,66 +1,66 @@
use std::fmt::{Debug, Display};
use std::{cell::Ref, fmt::{Debug, Display}};
use crate::prelude::*;
pub const KEYWORDS: [&str;14] = [
"if",
"then",
"elif",
"else",
"fi",
"while",
"until",
"for",
"in",
"select",
"do",
"done",
"case",
"esac"
pub const KEYWORDS: [TkRule;14] = [
TkRule::If,
TkRule::Then,
TkRule::Elif,
TkRule::Else,
TkRule::Fi,
TkRule::While,
TkRule::Until,
TkRule::For,
TkRule::In,
TkRule::Select,
TkRule::Do,
TkRule::Done,
TkRule::Case,
TkRule::Esac
];
pub const SEPARATORS: [TkRule; 7] = [
pub const SEPARATORS: [TkRule; 6] = [
TkRule::Sep,
TkRule::AndOp,
TkRule::OrOp,
TkRule::PipeOp,
TkRule::ErrPipeOp,
TkRule::BgOp,
TkRule::Keyword // Keywords don't count as command names
];
pub trait LexRule {
fn try_match(input: &str) -> Option<usize>;
}
pub struct Lexer {
input: Rc<String>,
pub struct Lexer<'a> {
input: String,
tokens: Vec<Token>,
is_command: bool,
shenv: &'a mut ShEnv,
consumed: usize
}
impl Lexer {
pub fn new(input: Rc<String>) -> Self {
Self { input, tokens: vec![], is_command: true, consumed: 0 }
impl<'a> Lexer<'a> {
pub fn new(input: String, shenv: &'a mut ShEnv) -> Self {
Self { input, tokens: vec![], is_command: true, shenv, consumed: 0 }
}
pub fn lex(mut self) -> Vec<Token> {
unsafe {
let mut input = self.input.as_str();
while let Some((mut rule,len)) = TkRule::try_match(input) {
// If we see a keyword in an argument position, it's actually an ident
if !self.is_command && rule == TkRule::Keyword {
if !self.is_command && KEYWORDS.contains(&rule) {
rule = TkRule::Ident
// If we are in a command right now, after this we are in arguments
} else if self.is_command && !matches!(rule,TkRule::Keyword | TkRule::Whitespace) {
} else if self.is_command && rule != TkRule::Whitespace && !KEYWORDS.contains(&rule) {
self.is_command = false;
}
// If we see a separator like && or ;, we are now in a command again
if SEPARATORS.contains(&rule) {
self.is_command = true;
}
let span = Span::new(self.input.clone(),self.consumed,self.consumed + len);
let span = self.shenv.inputman_mut().new_span(self.consumed, self.consumed + len);
let token = Token::new(rule, span);
self.consumed += len;
input = &input[len..];
@@ -77,73 +77,43 @@ impl Lexer {
#[derive(Clone)]
pub struct Token {
rule: TkRule,
span: Span
span: Rc<RefCell<Span>>
}
impl Token {
pub fn new(rule: TkRule, span: Span) -> Self {
pub fn new(rule: TkRule, span: Rc<RefCell<Span>>) -> Self {
Self { rule, span }
}
pub fn span(&self) -> &Span {
&self.span
}
pub fn span_mut(&mut self) -> &mut Span {
&mut self.span
pub fn span(&self) -> Rc<RefCell<Span>> {
self.span.clone()
}
pub fn rule(&self) -> TkRule {
self.rule
}
pub fn as_raw(&self, shenv: &mut ShEnv) -> String {
shenv.input_slice(self.span()).to_string()
}
}
impl Debug for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let info = (self.rule(),self.to_string(),self.span.start,self.span.end);
let info = (self.rule(),self.span.borrow().start,self.span.borrow().end);
write!(f,"{:?}",info)
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let slice = self.span.get_slice();
write!(f,"{}",slice)
}
}
#[derive(Debug,Clone)]
pub struct Span {
input: Rc<String>,
start: usize,
end: usize
}
impl Span {
pub fn new(input: Rc<String>, start: usize, end: usize) -> Self {
Self { input, start, end } }
pub fn get_slice(&self) -> String {
unsafe {
let slice = &self.input[self.start..self.end];
slice.to_string()
}
}
pub fn get_line(&self) -> (usize,usize,String) {
unsafe {
let mut dist = 0;
let mut line_no = 0;
let mut lines = self.input.lines();
while let Some(line) = lines.next() {
line_no += 1;
dist += line.len();
if dist > self.start {
dist -= line.len();
let offset = self.start - dist;
return (offset,line_no,line.to_string())
}
}
}
(0,0,String::new())
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn start(&self) -> usize {
self.start
@@ -151,8 +121,19 @@ impl Span {
pub fn end(&self) -> usize {
self.end
}
pub fn get_input(&self) -> Rc<String> {
self.input.clone()
pub fn clamp_start(&mut self, start: usize) {
if self.start > start {
self.start = start
}
}
pub fn clamp_end(&mut self, end: usize) {
if self.end > end {
self.end = end
}
}
pub fn shift(&mut self, delta: isize) {
self.start = self.start.saturating_add_signed(delta);
self.end = self.end.saturating_add_signed(delta);
}
}
@@ -204,7 +185,20 @@ pub enum TkRule {
CmdSub,
DQuote,
SQuote,
Keyword,
If,
Then,
Elif,
Else,
Fi,
While,
Until,
For,
In,
Select,
Do,
Done,
Case,
Esac,
Assign,
Ident,
Sep,
@@ -232,8 +226,21 @@ impl TkRule {
try_match!(TildeSub,input);
try_match!(Subshell,input);
try_match!(Sep,input);
try_match!(Keyword,input);
try_match!(Assign,input);
try_match!(If,input);
try_match!(Then,input);
try_match!(Elif,input);
try_match!(Else,input);
try_match!(Fi,input);
try_match!(While,input);
try_match!(Until,input);
try_match!(For,input);
try_match!(In,input);
try_match!(Select,input);
try_match!(Do,input);
try_match!(Done,input);
try_match!(Case,input);
try_match!(Esac,input);
try_match!(Ident,input);
None
}
@@ -371,17 +378,159 @@ tkrule_def!(OrOp, |input: &str| {
}
});
tkrule_def!(Keyword, |input: &str| {
for &kw in KEYWORDS.iter() {
if input.starts_with(kw) {
let len = kw.len();
if input.chars().nth(len).map_or(true, |ch| ch.is_whitespace() || ch == ';') {
return Some(len);
}
tkrule_def!(If, |input: &str| {
if input.starts_with("if") {
match input.chars().nth(2) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(2),
Some(_) => None,
None => Some(2), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Then, |input: &str| {
if input.starts_with("then") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Elif, |input: &str| {
if input.starts_with("elif") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Else, |input: &str| {
if input.starts_with("else") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Fi, |input: &str| {
if input.starts_with("fi") {
match input.chars().nth(2) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(2),
Some(_) => None,
None => Some(2), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(While, |input: &str| {
if input.starts_with("while") {
match input.chars().nth(5) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(5),
Some(_) => None,
None => Some(5), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Until, |input: &str| {
if input.starts_with("until") {
match input.chars().nth(5) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(5),
Some(_) => None,
None => Some(5), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(For, |input: &str| {
if input.starts_with("for") {
match input.chars().nth(3) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(3),
Some(_) => None,
None => Some(3), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(In, |input: &str| {
if input.starts_with("in") {
match input.chars().nth(2) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(2),
Some(_) => None,
None => Some(2), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Select, |input: &str| {
if input.starts_with("select") {
match input.chars().nth(6) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(6),
Some(_) => None,
None => Some(6), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Do, |input: &str| {
if input.starts_with("do") {
match input.chars().nth(2) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(2),
Some(_) => None,
None => Some(2), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Done, |input: &str| {
if input.starts_with("done") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Case, |input: &str| {
if input.starts_with("case") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
});
tkrule_def!(Esac, |input: &str| {
if input.starts_with("esac") {
match input.chars().nth(4) {
Some(ch) if ch.is_whitespace() || ch == ';' => Some(4),
Some(_) => None,
None => Some(4), // "if" is the entire input
}
} else {
None
}
None
});
tkrule_def!(Ident, |input: &str| {
@@ -717,6 +866,14 @@ tkrule_def!(BraceGrp, |input: &str| {
});
tkrule_def!(RedirOp, |input: &str| {
if let Some(ch) = input.chars().next() {
match ch {
'>' |
'<' |
'&' => { /* Continue */ }
_ => return None
}
}
// Order matters here
// For instance, if '>' is checked before '>>', '>' will always match first, and '>>' will never be checked
try_match_inner!(RedirCombineAppend,input); // Ex: &>>

View File

@@ -1,9 +1,9 @@
use core::fmt::Display;
use std::str::FromStr;
use std::{cell::Ref, str::FromStr};
use crate::prelude::*;
use super::lex::{TkRule, Span, Token};
use super::lex::{Span, TkRule, Token, KEYWORDS};
bitflags! {
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
@@ -17,12 +17,12 @@ bitflags! {
pub trait ParseRule {
/// Used for cases where a rule is optional
fn try_match(input: &[Token]) -> ShResult<Option<Node>>;
fn try_match(input: &[Token], shenv: &mut ShEnv) -> ShResult<Option<Node>>;
/// Used for cases where a rule is assumed based on context
/// For instance, if the "for" keyword is encountered, then it *must* be a for loop
/// And if it isn't, return a parse error
fn assert_match(input: &[Token]) -> ShResult<Node> {
Self::try_match(input)?.ok_or_else(||
fn assert_match(input: &[Token], shenv: &mut ShEnv) -> ShResult<Node> {
Self::try_match(input,shenv)?.ok_or_else(||
ShErr::simple(ShErrKind::ParseErr, "Parse Error")
)
}
@@ -40,7 +40,7 @@ pub enum CmdGuard {
pub struct Node {
node_rule: NdRule,
tokens: Vec<Token>,
span: Span,
span: Rc<RefCell<Span>>,
flags: NdFlag,
}
@@ -60,9 +60,12 @@ impl Node {
pub fn into_rule(self) -> NdRule {
self.node_rule
}
pub fn span(&self) -> Span {
pub fn span(&self) -> Rc<RefCell<Span>> {
self.span.clone()
}
pub fn as_raw(&self, shenv: &mut ShEnv) -> String {
shenv.input_slice(self.span()).to_string()
}
pub fn flags(&self) -> NdFlag {
self.flags
}
@@ -71,11 +74,10 @@ impl Node {
}
}
impl Display for Node {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let raw = self.span().get_slice();
write!(f, "{}", raw)
}
#[derive(Clone,Debug)]
pub enum LoopKind {
While,
Until
}
#[derive(Clone,Debug)]
@@ -84,6 +86,9 @@ pub enum NdRule {
Command { argv: Vec<Token>, redirs: Vec<Redir> },
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
FuncDef { name: Token, body: Token },
IfThen { cond_blocks: Vec<(Vec<Node>,Vec<Node>)>, else_block: Option<Vec<Node>> },
Loop { kind: LoopKind, cond: Vec<Node>, body: Vec<Node> },
ForLoop { vars: Vec<Token>, arr: Vec<Token>, body: Vec<Node> },
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
Pipeline { cmds: Vec<Node> }
@@ -91,12 +96,12 @@ pub enum NdRule {
/// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule.
macro_rules! ndrule_def {
($name:ident,$try:expr) => {
($name:ident,$shenv:ident,$try:expr) => {
#[derive(Debug)]
pub struct $name;
impl ParseRule for $name {
fn try_match(input: &[Token]) -> ShResult<Option<Node>> {
$try(input)
fn try_match(input: &[Token],shenv: &mut ShEnv) -> ShResult<Option<Node>> {
$try(input,shenv)
}
}
};
@@ -105,9 +110,9 @@ macro_rules! ndrule_def {
/// This macro attempts to match all of the given Rules. It returns upon finding the first match, so the order matters
/// Place the most specialized/specific rules first, and the most general rules last
macro_rules! try_rules {
($tokens:expr, $($name:ident),+) => {
($tokens:expr,$shenv:expr,$($name:ident),+) => {
$(
let result = $name::try_match($tokens)?;
let result = $name::try_match($tokens,$shenv)?;
if let Some(node) = result {
return Ok(Some(node))
}
@@ -125,6 +130,9 @@ impl SynTree {
pub fn new() -> Self {
Self { tree: VecDeque::new() }
}
pub fn from_vec(nodes: Vec<Node>) -> Self {
Self { tree: VecDeque::from(nodes) }
}
pub fn push_node(&mut self, node: Node) {
self.tree.bpush(node)
}
@@ -133,16 +141,17 @@ impl SynTree {
}
}
pub struct Parser {
pub struct Parser<'a> {
token_stream: Vec<Token>,
shenv: &'a mut ShEnv,
ast: SynTree
}
impl Parser {
pub fn new(mut token_stream: Vec<Token>) -> Self {
impl<'a> Parser<'a> {
pub fn new(mut token_stream: Vec<Token>, shenv: &'a mut ShEnv) -> Self {
log!(TRACE, "New parser");
token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment));
Self { token_stream, ast: SynTree::new() }
Self { token_stream, shenv, ast: SynTree::new() }
}
pub fn parse(mut self) -> ShResult<SynTree> {
@@ -150,11 +159,10 @@ impl Parser {
let mut lists = VecDeque::new();
let token_slice = &*self.token_stream;
// Get the Main rule
if let Some(mut node) = Main::try_match(token_slice)? {
if let Some(mut node) = Main::try_match(token_slice,self.shenv)? {
// Extract the inner lists
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
while let Some(node) = cmd_lists.pop() {
log!(DEBUG, node);
lists.bpush(node)
}
}
@@ -167,26 +175,42 @@ impl Parser {
}
}
fn get_span(toks: &Vec<Token>) -> ShResult<Span> {
fn get_span(toks: &Vec<Token>, shenv: &mut ShEnv) -> ShResult<Rc<RefCell<Span>>> {
if toks.is_empty() {
Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list"))
} else {
let start = toks.first().unwrap().span().start();
let end = toks.iter().last().unwrap().span().end();
let input = toks.iter().last().unwrap().span().get_input();
Ok(Span::new(input,start,end))
let start = toks.first().unwrap().span().borrow().start();
let end = toks.iter().last().unwrap().span().borrow().end();
let span = shenv.inputman_mut().new_span(start, end);
Ok(span)
}
}
fn get_lists(mut tokens: &[Token], shenv: &mut ShEnv) -> (usize,Vec<Node>) {
let mut lists = vec![];
let mut tokens_eaten = 0;
while !tokens.is_empty() {
match CmdList::try_match(tokens, shenv) {
Ok(Some(list)) => {
tokens_eaten += list.len();
tokens = &tokens[list.len()..];
lists.push(list);
}
Ok(None) | Err(_) => break
}
}
(tokens_eaten,lists)
}
// TODO: Redirs with FD sources appear to be looping endlessly for some reason
ndrule_def!(Main, |tokens: &[Token]| {
ndrule_def!(Main, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
log!(TRACE, "Parsing main");
let mut cmd_lists = vec![];
let mut node_toks = vec![];
let mut token_slice = &*tokens;
while let Some(node) = CmdList::try_match(token_slice)? {
while let Some(node) = CmdList::try_match(token_slice,shenv)? {
node_toks.extend(node.tokens().clone());
token_slice = &token_slice[node.len()..];
cmd_lists.push(node);
@@ -195,7 +219,7 @@ ndrule_def!(Main, |tokens: &[Token]| {
if cmd_lists.is_empty() {
return Ok(None)
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::Main { cmd_lists },
tokens: node_toks,
@@ -205,14 +229,14 @@ ndrule_def!(Main, |tokens: &[Token]| {
Ok(Some(node))
});
ndrule_def!(CmdList, |tokens: &[Token]| {
ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
log!(TRACE, "Parsing cmdlist");
let mut commands: Vec<(Option<CmdGuard>,Node)> = vec![];
let mut node_toks = vec![];
let mut token_slice = &*tokens;
let mut cmd_guard = None; // Operators like '&&' and '||'
while let Some(mut node) = Expr::try_match(token_slice)? {
while let Some(mut node) = Expr::try_match(token_slice,shenv)? {
// Add sub-node tokens to our tokens
node_toks.extend(node.tokens().clone());
// Reflect changes in the token slice
@@ -221,8 +245,11 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
log!(DEBUG, token_slice);
// Push sub-node
if let NdRule::Command { argv, redirs: _ } = node.rule() {
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
*node.flags_mut() |= NdFlag::BUILTIN;
if let Some(arg) = argv.first() {
let slice = shenv.input_slice(arg.span().clone());
if BUILTINS.contains(&slice) {
*node.flags_mut() |= NdFlag::BUILTIN;
}
}
}
commands.push((cmd_guard.take(),node));
@@ -244,7 +271,7 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
if node_toks.is_empty() {
return Ok(None)
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::CmdList { cmds: commands },
tokens: node_toks,
@@ -254,8 +281,8 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
Ok(Some(node))
});
ndrule_def!(Expr, |tokens: &[Token]| {
try_rules!(tokens,
ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
try_rules!(tokens, shenv,
ShellCmd,
Pipeline,
Subshell,
@@ -264,8 +291,8 @@ ndrule_def!(Expr, |tokens: &[Token]| {
);
});
// Used in pipelines to avoid recursion
ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
try_rules!(tokens,
ndrule_def!(ExprNoPipeline, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
try_rules!(tokens, shenv,
ShellCmd,
Subshell,
Assignment,
@@ -273,13 +300,352 @@ ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
);
});
ndrule_def!(ShellCmd, |tokens: &[Token]| {
try_rules!(tokens,
ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
try_rules!(tokens, shenv,
IfThen,
Loop,
FuncDef
);
});
ndrule_def!(FuncDef, |tokens: &[Token]| {
ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
let err = |msg: &str, span: Rc<RefCell<Span>>, shenv: &mut ShEnv | {
ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span)
};
let mut tokens_iter = tokens.iter().peekable();
let mut node_toks = vec![];
let mut vars = vec![];
let mut arr = vec![];
let body: Vec<Node>;
if let Some(token) = tokens_iter.next() {
if let TkRule::For = token.rule() {
node_toks.push(token.clone());
tokens = &tokens[1..];
} else { return Ok(None) }
} else { return Ok(None) }
while let Some(token) = tokens_iter.next() {
if let TkRule::Ident = token.rule() {
node_toks.push(token.clone());
tokens = &tokens[1..];
if token.as_raw(shenv) == "in" { break }
vars.push(token.clone());
} else {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop vars",span,shenv))
}
}
if vars.is_empty() {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop vars",span,shenv))
}
while let Some(token) = tokens_iter.next() {
if let TkRule::Ident = token.rule() {
node_toks.push(token.clone());
tokens = &tokens[1..];
if token.rule() == TkRule::Sep { break }
arr.push(token.clone());
} else {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop array",span,shenv))
}
}
if arr.is_empty() {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop array",span,shenv))
}
if let Some(token) = tokens_iter.next() {
node_toks.push(token.clone());
tokens = &tokens[1..];
if token.rule() != TkRule::Do {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected `do` after for loop array",span,shenv))
}
} else {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected `do` after for loop array",span,shenv))
}
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
body = lists;
tokens_iter = tokens.iter().peekable();
if let Some(token) = tokens_iter.next() {
node_toks.push(token.clone());
if token.rule() != TkRule::Done {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected `done` after for loop",span,shenv))
}
} else {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected `done` after for loop",span,shenv))
}
let span = get_span(&node_toks, shenv)?;
let node = Node {
node_rule: NdRule::ForLoop { vars, arr, body },
tokens: node_toks,
span,
flags: NdFlag::empty()
};
Ok(Some(node))
});
ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
let err = |msg: &str, span: Rc<RefCell<Span>>, shenv: &mut ShEnv | {
ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span)
};
let mut tokens_iter = tokens.iter().peekable();
let mut node_toks = vec![];
let mut cond_blocks = vec![];
let mut else_block: Option<Vec<Node>> = None;
if let Some(token) = tokens_iter.next() {
if let TkRule::If = token.rule() {
node_toks.push(token.clone());
tokens = &tokens[1..];
} else { return Ok(None) }
} else { return Ok(None) }
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
let cond = lists;
tokens_iter = tokens.iter().peekable();
while let Some(token) = tokens_iter.next() {
match token.rule() {
TkRule::Then => {
node_toks.push(token.clone());
tokens = &tokens[1..];
break
}
TkRule::Sep | TkRule::Whitespace => {
node_toks.push(token.clone());
tokens = &tokens[1..];
}
_ => {
let span = get_span(&node_toks,shenv)?;
return Err(err("Expected `then` after if statement condition",span,shenv))
}
}
}
if tokens_iter.peek().is_none() {
let span = get_span(&node_toks,shenv)?;
return Err(err("Failed to parse this if statement",span,shenv))
}
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
let body = lists;
tokens_iter = tokens.iter().peekable();
cond_blocks.push((cond,body));
let mut closed = false;
while let Some(token) = tokens_iter.next() {
match token.rule() {
TkRule::Elif => {
node_toks.push(token.clone());
tokens = &tokens[1..];
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
let cond = lists;
tokens_iter = tokens.iter().peekable();
while let Some(token) = tokens_iter.next() {
match token.rule() {
TkRule::Then => {
node_toks.push(token.clone());
tokens = &tokens[1..];
break
}
TkRule::Sep | TkRule::Whitespace => {
node_toks.push(token.clone());
tokens = &tokens[1..];
}
_ => {
let span = get_span(&node_toks,shenv)?;
return Err(err("Expected `then` after if statement condition",span,shenv))
}
}
}
if tokens_iter.peek().is_none() {
let span = get_span(&node_toks,shenv)?;
return Err(err("Failed to parse this if statement",span,shenv))
}
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
let body = lists;
tokens_iter = tokens.iter().peekable();
cond_blocks.push((cond,body));
}
TkRule::Else => {
node_toks.push(token.clone());
tokens = &tokens[1..];
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
else_block = Some(lists);
tokens_iter = tokens.iter().peekable();
}
TkRule::Fi => {
closed = true;
node_toks.push(token.clone());
tokens = &tokens[1..];
}
TkRule::Sep | TkRule::Whitespace => {
node_toks.push(token.clone());
tokens = &tokens[1..];
if closed { break }
}
_ => {
let span = get_span(&node_toks, shenv)?;
return Err(err("Unexpected token in if statement",span,shenv))
}
}
}
if !closed {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected `fi` to close if statement",span,shenv))
}
let span = get_span(&node_toks, shenv)?;
let node = Node {
node_rule: NdRule::IfThen { cond_blocks, else_block },
tokens: node_toks,
span,
flags: NdFlag::empty()
};
Ok(Some(node))
});
ndrule_def!(Loop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
let err = |msg: &str, span: Rc<RefCell<Span>>, shenv: &mut ShEnv | {
ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span)
};
let mut tokens_iter = tokens.iter().peekable();
let mut node_toks = vec![];
let kind: LoopKind;
let cond: Vec<Node>;
let body: Vec<Node>;
if let Some(token) = tokens_iter.next() {
node_toks.push(token.clone());
match token.rule() {
TkRule::While => {
kind = LoopKind::While
}
TkRule::Until => {
kind = LoopKind::Until
}
_ => return Ok(None)
}
} else { return Ok(None) }
tokens = &tokens[1..];
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
cond = lists;
tokens_iter = tokens.iter().peekable();
while let Some(token) = tokens_iter.next() {
match token.rule() {
TkRule::Sep | TkRule::Whitespace => {
node_toks.push(token.clone());
tokens = &tokens[1..];
}
TkRule::Do => {
node_toks.push(token.clone());
tokens = &tokens[1..];
break
}
_ => {
let span = get_span(&node_toks,shenv)?;
return Err(err("Expected `do` after loop condition",span,shenv))
}
}
}
if tokens_iter.peek().is_none() {
return Ok(None)
}
let (used,lists) = get_lists(tokens, shenv);
for list in &lists {
node_toks.extend(list.tokens().clone());
}
tokens = &tokens[used..];
body = lists;
tokens_iter = tokens.iter().peekable();
let mut closed = false;
while let Some(token) = tokens_iter.next() {
match token.rule() {
TkRule::Sep | TkRule::Whitespace => {
node_toks.push(token.clone());
tokens = &tokens[1..];
if closed { break }
}
TkRule::Done => {
closed = true;
node_toks.push(token.clone());
tokens = &tokens[1..];
}
_ => {
let span = get_span(&node_toks,shenv)?;
return Err(err("Unexpected token in loop",span,shenv))
}
}
}
if !closed {
let span = get_span(&node_toks,shenv)?;
return Err(err("Expected `done` to close loop",span,shenv))
}
let span = get_span(&node_toks, shenv)?;
let node = Node {
node_rule: NdRule::Loop { kind, cond, body },
tokens: node_toks,
span,
flags: NdFlag::empty()
};
log!(DEBUG, node);
Ok(Some(node))
});
ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
let mut tokens_iter = tokens.iter();
let mut node_toks = vec![];
let name: Token;
@@ -307,7 +673,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| {
return Ok(None)
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::FuncDef { name, body },
tokens: node_toks,
@@ -317,7 +683,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| {
Ok(Some(node))
});
ndrule_def!(Subshell, |tokens: &[Token]| {
ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
let mut tokens_iter = tokens.iter();
let mut node_toks = vec![];
let mut argv = vec![];
@@ -350,27 +716,29 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
TkRule::RedirOp => {
node_toks.push(token.clone());
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
let redir_raw = token.span().get_slice();
let redir_raw = shenv.input_slice(token.span());
let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap();
// If there isn't an FD target, get the next token and use it as the filename
if redir_bldr.tgt().is_none() {
if let Some(filename) = tokens_iter.next() {
// Make sure it's a word and not an operator or something
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) {
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) {
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
err.blame(token.span().clone());
let input = shenv.input_slice(token.span()).to_string();
err.blame(input, token.span());
return Err(err)
}
node_toks.push(filename.clone());
// Construct the Path object
let filename_raw = filename.span().get_slice();
let filename_raw = shenv.input_slice(filename.span()).to_string();
let filename_path = PathBuf::from(filename_raw);
let tgt = RedirTarget::File(filename_path);
// Update the builder
redir_bldr = redir_bldr.with_tgt(tgt);
} else {
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
err.blame(token.span().clone());
let input = shenv.input_slice(token.span()).to_string();
err.blame(input, token.span());
return Err(err)
}
}
@@ -379,7 +747,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
_ => break
}
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::Subshell { body, argv, redirs },
tokens: node_toks,
@@ -394,7 +762,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
Ok(None)
});
ndrule_def!(Pipeline, |mut tokens: &[Token]| {
ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
log!(TRACE, "Parsing pipeline");
let mut tokens_iter = tokens.iter().peekable();
let mut node_toks = vec![];
@@ -411,7 +779,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
}
_ => { /* Keep going */ }
}
if let Some(mut cmd) = ExprNoPipeline::try_match(tokens)? {
if let Some(mut cmd) = ExprNoPipeline::try_match(tokens,shenv)? {
// Add sub-node's tokens to our tokens
node_toks.extend(cmd.tokens().clone());
@@ -422,8 +790,11 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
}
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
*cmd.flags_mut() |= NdFlag::BUILTIN;
if let Some(arg) = argv.first() {
let slice = shenv.input_slice(arg.span().clone());
if BUILTINS.contains(&slice) {
*cmd.flags_mut() |= NdFlag::BUILTIN;
}
}
}
// Push sub-node
@@ -458,7 +829,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
if node_toks.is_empty() {
return Ok(None)
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::Pipeline { cmds },
tokens: node_toks,
@@ -468,7 +839,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
Ok(Some(node))
});
ndrule_def!(Command, |tokens: &[Token]| {
ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
log!(TRACE, "Parsing command");
let mut tokens = tokens.iter().peekable();
let mut node_toks = vec![];
@@ -495,40 +866,49 @@ ndrule_def!(Command, |tokens: &[Token]| {
}
TkRule::RedirOp => {
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
let redir_raw = token.span().get_slice();
let redir_raw = shenv.input_slice(token.span()).to_string();
let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap();
// If there isn't an FD target, get the next token and use it as the filename
if redir_bldr.tgt().is_none() {
if let Some(filename) = tokens.next() {
// Make sure it's a word and not an operator or something
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) {
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) {
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
err.blame(token.span().clone());
let input = shenv.input_slice(token.span()).to_string();
err.blame(input, token.span());
return Err(err)
}
node_toks.push(filename.clone());
// Construct the Path object
let filename_raw = filename.span().get_slice();
let filename_raw = shenv.input_slice(filename.span()).to_string();
let filename_path = PathBuf::from(filename_raw);
let tgt = RedirTarget::File(filename_path);
// Update the builder
redir_bldr = redir_bldr.with_tgt(tgt);
} else {
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
err.blame(token.span().clone());
let input = shenv.input_slice(token.span()).to_string();
err.blame(input, token.span());
return Err(err)
}
}
redirs.push(redir_bldr.build());
}
TkRule::Sep => break,
_ => unreachable!("Found this rule: {:?}", token.rule())
_ => return Err(
ShErr::full(
ShErrKind::ParseErr,
format!("Unexpected token in command rule: {:?}", token.rule()),
shenv.get_input(),
get_span(&node_toks,shenv)?
)
)
}
}
if node_toks.is_empty() {
return Ok(None)
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
if !argv.is_empty() {
let node = Node {
node_rule: NdRule::Command { argv, redirs },
@@ -542,7 +922,7 @@ ndrule_def!(Command, |tokens: &[Token]| {
}
});
ndrule_def!(Assignment, |tokens: &[Token]| {
ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
log!(TRACE, "Parsing assignment");
let mut tokens = tokens.into_iter().peekable();
let mut node_toks = vec![];
@@ -559,11 +939,11 @@ ndrule_def!(Assignment, |tokens: &[Token]| {
if tokens.peek().is_some() {
let tokens_vec: Vec<Token> = tokens.into_iter().map(|token| token.clone()).collect();
let tokens_slice = &tokens_vec;
let cmd = Command::try_match(tokens_slice)?.map(|cmd| Box::new(cmd));
let cmd = Command::try_match(tokens_slice,shenv)?.map(|cmd| Box::new(cmd));
if let Some(ref cmd) = cmd {
node_toks.extend(cmd.tokens().clone());
}
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::Assignment { assignments, cmd },
tokens: node_toks,
@@ -572,7 +952,7 @@ ndrule_def!(Assignment, |tokens: &[Token]| {
};
return Ok(Some(node))
} else {
let span = get_span(&node_toks)?;
let span = get_span(&node_toks,shenv)?;
let node = Node {
node_rule: NdRule::Assignment { assignments, cmd: None },
tokens: node_toks,