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 jobctl;
|
||||
pub mod alias;
|
||||
pub mod flowctl;
|
||||
|
||||
pub const BUILTINS: [&str;10] = [
|
||||
pub const BUILTINS: [&str;14] = [
|
||||
"echo",
|
||||
"cd",
|
||||
"export",
|
||||
@@ -21,7 +22,11 @@ pub const BUILTINS: [&str;10] = [
|
||||
"jobs",
|
||||
"fg",
|
||||
"bg",
|
||||
"alias"
|
||||
"alias",
|
||||
"return",
|
||||
"break",
|
||||
"continue",
|
||||
"exit"
|
||||
];
|
||||
|
||||
/// 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;
|
||||
|
||||
@@ -460,7 +460,9 @@ impl Job {
|
||||
for child in self.children.iter_mut() {
|
||||
if child.pid == Pid::this() {
|
||||
// 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
|
||||
}
|
||||
let result = child.wait(Some(WtFlag::WSTOPPED));
|
||||
@@ -619,7 +621,9 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||
attach_tty(job.pgid())?;
|
||||
disable_reaping()?;
|
||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||
flog!(DEBUG,statuses);
|
||||
for status in statuses {
|
||||
flog!(DEBUG,status);
|
||||
match status {
|
||||
WtStat::Exited(_, exit_code) => {
|
||||
code = exit_code;
|
||||
|
||||
@@ -217,10 +217,10 @@ pub enum ShErrKind {
|
||||
Errno,
|
||||
FileNotFound(String),
|
||||
CmdNotFound(String),
|
||||
CleanExit,
|
||||
FuncReturn,
|
||||
LoopContinue,
|
||||
LoopBreak,
|
||||
CleanExit(i32),
|
||||
FuncReturn(i32),
|
||||
LoopContinue(i32),
|
||||
LoopBreak(i32),
|
||||
Null
|
||||
}
|
||||
|
||||
@@ -237,10 +237,10 @@ impl Display for ShErrKind {
|
||||
ShErrKind::Errno => "ERRNO",
|
||||
ShErrKind::FileNotFound(file) => &format!("File not found: {file}"),
|
||||
ShErrKind::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||
ShErrKind::CleanExit => "",
|
||||
ShErrKind::FuncReturn => "",
|
||||
ShErrKind::LoopContinue => "",
|
||||
ShErrKind::LoopBreak => "",
|
||||
ShErrKind::CleanExit(_) => "",
|
||||
ShErrKind::FuncReturn(_) => "",
|
||||
ShErrKind::LoopContinue(_) => "",
|
||||
ShErrKind::LoopBreak(_) => "",
|
||||
ShErrKind::Null => "",
|
||||
};
|
||||
write!(f,"{output}")
|
||||
|
||||
@@ -1,7 +1,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 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};
|
||||
|
||||
@@ -151,7 +151,14 @@ impl Dispatcher {
|
||||
|
||||
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
||||
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
|
||||
@@ -233,7 +240,7 @@ impl Dispatcher {
|
||||
body_frame.extend(out_redirs);
|
||||
|
||||
let CondNode { cond, body } = cond_node;
|
||||
loop {
|
||||
'outer: loop {
|
||||
self.io_stack.push(cond_frame.clone());
|
||||
|
||||
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
||||
@@ -247,8 +254,14 @@ impl Dispatcher {
|
||||
for node in &body {
|
||||
if let Err(e) = self.dispatch_node(node.clone()) {
|
||||
match e.kind() {
|
||||
ShErrKind::LoopBreak => break,
|
||||
ShErrKind::LoopContinue => continue,
|
||||
ShErrKind::LoopBreak(code) => {
|
||||
state::set_status(*code);
|
||||
break 'outer
|
||||
}
|
||||
ShErrKind::LoopContinue(code) => {
|
||||
state::set_status(*code);
|
||||
continue 'outer
|
||||
}
|
||||
_ => return Err(e.into())
|
||||
}
|
||||
}
|
||||
@@ -346,6 +359,10 @@ impl Dispatcher {
|
||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
||||
"jobs" => jobs(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())
|
||||
};
|
||||
|
||||
|
||||
@@ -615,6 +615,7 @@ impl ParseStream {
|
||||
|
||||
if self.check_keyword("esac") {
|
||||
node_tks.push(self.next_tk().unwrap());
|
||||
self.assert_separator(&mut node_tks)?;
|
||||
break
|
||||
}
|
||||
|
||||
@@ -623,6 +624,7 @@ impl ParseStream {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let node = Node {
|
||||
class: NdRule::CaseNode { pattern, case_blocks },
|
||||
flags: NdFlags::empty(),
|
||||
|
||||
@@ -66,6 +66,7 @@ impl Highlighter for FernReadline {
|
||||
|
||||
impl Validator for FernReadline {
|
||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
return Ok(ValidationResult::Valid(None));
|
||||
let mut tokens = vec![];
|
||||
let tk_stream = LexStream::new(Rc::new(ctx.input().to_string()), LexFlags::empty());
|
||||
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 {
|
||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
||||
}
|
||||
#[track_caller]
|
||||
pub fn set_status(code: i32) {
|
||||
flog!(DEBUG,std::panic::Location::caller());
|
||||
flog!(DEBUG,code);
|
||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||
}
|
||||
|
||||
|
||||
@@ -184,3 +184,15 @@ esac";
|
||||
|
||||
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