added another test for the parser
This commit is contained in:
39
src/builtin/flowctl.rs
Normal file
39
src/builtin/flowctl.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use crate::{builtin::setup_builtin, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::IoStack, state};
|
||||||
|
|
||||||
|
pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
||||||
|
use ShErrKind::*;
|
||||||
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut code = 0;
|
||||||
|
|
||||||
|
let mut argv = prepare_argv(argv);
|
||||||
|
let cmd = argv.remove(0).0;
|
||||||
|
|
||||||
|
if !argv.is_empty() {
|
||||||
|
let (arg,span) = argv
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let Ok(status) = arg.parse::<i32>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::full(ShErrKind::SyntaxErr, format!("{cmd}: Expected a number"), span)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
code = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
flog!(DEBUG,code);
|
||||||
|
|
||||||
|
let kind = match kind {
|
||||||
|
LoopContinue(_) => LoopContinue(code),
|
||||||
|
LoopBreak(_) => LoopBreak(code),
|
||||||
|
FuncReturn(_) => FuncReturn(code),
|
||||||
|
CleanExit(_) => CleanExit(code),
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(ShErr::simple(kind, ""))
|
||||||
|
}
|
||||||
@@ -10,8 +10,9 @@ pub mod source;
|
|||||||
pub mod shift;
|
pub mod shift;
|
||||||
pub mod jobctl;
|
pub mod jobctl;
|
||||||
pub mod alias;
|
pub mod alias;
|
||||||
|
pub mod flowctl;
|
||||||
|
|
||||||
pub const BUILTINS: [&str;10] = [
|
pub const BUILTINS: [&str;14] = [
|
||||||
"echo",
|
"echo",
|
||||||
"cd",
|
"cd",
|
||||||
"export",
|
"export",
|
||||||
@@ -21,7 +22,11 @@ pub const BUILTINS: [&str;10] = [
|
|||||||
"jobs",
|
"jobs",
|
||||||
"fg",
|
"fg",
|
||||||
"bg",
|
"bg",
|
||||||
"alias"
|
"alias",
|
||||||
|
"return",
|
||||||
|
"break",
|
||||||
|
"continue",
|
||||||
|
"exit"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd, state::{set_status, write_jobs}};
|
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd, state::{self, set_status, write_jobs}};
|
||||||
|
|
||||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||||
|
|
||||||
@@ -460,7 +460,9 @@ impl Job {
|
|||||||
for child in self.children.iter_mut() {
|
for child in self.children.iter_mut() {
|
||||||
if child.pid == Pid::this() {
|
if child.pid == Pid::this() {
|
||||||
// TODO: figure out some way to get the exit code of builtins
|
// TODO: figure out some way to get the exit code of builtins
|
||||||
stats.push(WtStat::Exited(child.pid, 0));
|
let code = state::get_status();
|
||||||
|
flog!(DEBUG,code);
|
||||||
|
stats.push(WtStat::Exited(child.pid, code));
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let result = child.wait(Some(WtFlag::WSTOPPED));
|
let result = child.wait(Some(WtFlag::WSTOPPED));
|
||||||
@@ -619,7 +621,9 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
disable_reaping()?;
|
disable_reaping()?;
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
|
flog!(DEBUG,statuses);
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
|
flog!(DEBUG,status);
|
||||||
match status {
|
match status {
|
||||||
WtStat::Exited(_, exit_code) => {
|
WtStat::Exited(_, exit_code) => {
|
||||||
code = exit_code;
|
code = exit_code;
|
||||||
|
|||||||
@@ -217,10 +217,10 @@ pub enum ShErrKind {
|
|||||||
Errno,
|
Errno,
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
CmdNotFound(String),
|
CmdNotFound(String),
|
||||||
CleanExit,
|
CleanExit(i32),
|
||||||
FuncReturn,
|
FuncReturn(i32),
|
||||||
LoopContinue,
|
LoopContinue(i32),
|
||||||
LoopBreak,
|
LoopBreak(i32),
|
||||||
Null
|
Null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,10 +237,10 @@ impl Display for ShErrKind {
|
|||||||
ShErrKind::Errno => "ERRNO",
|
ShErrKind::Errno => "ERRNO",
|
||||||
ShErrKind::FileNotFound(file) => &format!("File not found: {file}"),
|
ShErrKind::FileNotFound(file) => &format!("File not found: {file}"),
|
||||||
ShErrKind::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
ShErrKind::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||||
ShErrKind::CleanExit => "",
|
ShErrKind::CleanExit(_) => "",
|
||||||
ShErrKind::FuncReturn => "",
|
ShErrKind::FuncReturn(_) => "",
|
||||||
ShErrKind::LoopContinue => "",
|
ShErrKind::LoopContinue(_) => "",
|
||||||
ShErrKind::LoopBreak => "",
|
ShErrKind::LoopBreak(_) => "",
|
||||||
ShErrKind::Null => "",
|
ShErrKind::Null => "",
|
||||||
};
|
};
|
||||||
write!(f,"{output}")
|
write!(f,"{output}")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::VecDeque;
|
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 crate::{builtin::{alias::alias, cd::cd, echo::echo, export::export, flowctl::flowctl, 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, CaseNode, 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};
|
||||||
|
|
||||||
@@ -151,7 +151,14 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
||||||
write_vars(|v| **v = scope_snapshot);
|
write_vars(|v| **v = scope_snapshot);
|
||||||
return Err(e.into())
|
match e.kind() {
|
||||||
|
ShErrKind::FuncReturn(code) => {
|
||||||
|
state::set_status(*code);
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
_ => return Err(e.into())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return to the outer scope
|
// Return to the outer scope
|
||||||
@@ -233,7 +240,7 @@ impl Dispatcher {
|
|||||||
body_frame.extend(out_redirs);
|
body_frame.extend(out_redirs);
|
||||||
|
|
||||||
let CondNode { cond, body } = cond_node;
|
let CondNode { cond, body } = cond_node;
|
||||||
loop {
|
'outer: loop {
|
||||||
self.io_stack.push(cond_frame.clone());
|
self.io_stack.push(cond_frame.clone());
|
||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
||||||
@@ -247,8 +254,14 @@ impl Dispatcher {
|
|||||||
for node in &body {
|
for node in &body {
|
||||||
if let Err(e) = self.dispatch_node(node.clone()) {
|
if let Err(e) = self.dispatch_node(node.clone()) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::LoopBreak => break,
|
ShErrKind::LoopBreak(code) => {
|
||||||
ShErrKind::LoopContinue => continue,
|
state::set_status(*code);
|
||||||
|
break 'outer
|
||||||
|
}
|
||||||
|
ShErrKind::LoopContinue(code) => {
|
||||||
|
state::set_status(*code);
|
||||||
|
continue 'outer
|
||||||
|
}
|
||||||
_ => return Err(e.into())
|
_ => return Err(e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,6 +359,10 @@ impl Dispatcher {
|
|||||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
||||||
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
||||||
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"return" => flowctl(cmd, ShErrKind::FuncReturn(0)),
|
||||||
|
"break" => flowctl(cmd, ShErrKind::LoopBreak(0)),
|
||||||
|
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
||||||
|
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -615,6 +615,7 @@ impl ParseStream {
|
|||||||
|
|
||||||
if self.check_keyword("esac") {
|
if self.check_keyword("esac") {
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
self.assert_separator(&mut node_tks)?;
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +624,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::CaseNode { pattern, case_blocks },
|
class: NdRule::CaseNode { pattern, case_blocks },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ impl Highlighter for FernReadline {
|
|||||||
|
|
||||||
impl Validator for FernReadline {
|
impl Validator for FernReadline {
|
||||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||||
|
return Ok(ValidationResult::Valid(None));
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
let tk_stream = LexStream::new(Rc::new(ctx.input().to_string()), LexFlags::empty());
|
let tk_stream = LexStream::new(Rc::new(ctx.input().to_string()), LexFlags::empty());
|
||||||
for tk in tk_stream {
|
for tk in tk_stream {
|
||||||
|
|||||||
@@ -315,7 +315,10 @@ pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T
|
|||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
||||||
}
|
}
|
||||||
|
#[track_caller]
|
||||||
pub fn set_status(code: i32) {
|
pub fn set_status(code: i32) {
|
||||||
|
flog!(DEBUG,std::panic::Location::caller());
|
||||||
|
flog!(DEBUG,code);
|
||||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,3 +184,15 @@ esac";
|
|||||||
|
|
||||||
insta::assert_debug_snapshot!(nodes)
|
insta::assert_debug_snapshot!(nodes)
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn parse_cursed() {
|
||||||
|
let input = "if if if if case foo in foo) if true; then true; fi;; esac; then case foo in foo) until true; do true; done;; esac; fi; then until if case foo in foo) true;; esac; then if true; then true; fi; fi; do until until true; do true; done; do case foo in foo) true;; esac; done; done; fi; then until until case foo in foo) true;; esac; do if true; then true; fi; done; do until true; do true; done; done; fi; then until case foo in foo) case foo in foo) true;; esac;; esac; do if if true; then true; fi; then until true; do true; done; fi; done; elif until until case foo in foo) true;; esac; do if true; then true; fi; done; do case foo in foo) until true; do true; done;; esac; done; then case foo in foo) if case foo in foo) true;; esac; then if true; then true; fi; fi;; esac; else case foo in foo) until until true; do true; done; do case foo in foo) true;; esac; done;; esac; fi";
|
||||||
|
|
||||||
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
|
.map(|tk| tk.unwrap())
|
||||||
|
.collect();
|
||||||
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|
||||||
|
// 15,000 line snapshot file btw
|
||||||
|
insta::assert_debug_snapshot!(nodes)
|
||||||
|
}
|
||||||
|
|||||||
15563
src/tests/snapshots/fern__tests__parser__parse_cursed.snap
Normal file
15563
src/tests/snapshots/fern__tests__parser__parse_cursed.snap
Normal file
File diff suppressed because it is too large
Load Diff
4516
src/tests/snapshots/fern__tests__parser__parse_final_boss.snap
Normal file
4516
src/tests/snapshots/fern__tests__parser__parse_final_boss.snap
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user