Skip to content

Commit

Permalink
feat: ✨ 完成「严格模式」;修复「CIN输入无效」的漏洞
Browse files Browse the repository at this point in the history
✅可配置的「严格模式」,允许「测试失败⇒提前返回⇒上报异常」;🐞连带修复「命令行运行时输入NAVM指令遗漏换行符」的问题
  • Loading branch information
ARCJ137442 committed Apr 3, 2024
1 parent ebace25 commit efdd7dc
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "babel_nar"
version = "0.12.0"
version = "0.13.0"
edition = "2021"

# Cargo文档参考:<https://rustwiki.org/zh-CN/cargo/reference/manifest.html>
Expand Down
11 changes: 11 additions & 0 deletions src/bin/babelnar_cli/launch_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,18 @@ pub struct LaunchConfig {
pub input_mode: InputMode,

/// 自动重启
/// * 🎯程序健壮性:用户的意外输入,不会随意让程序崩溃
/// * 🚩在虚拟机终止(收到「终止」输出)时,自动用配置重启虚拟机
/// * 📜默认为`false`(关闭)
#[serde(default = "bool_false")]
pub auto_restart: bool,

/// 严格模式
/// * 🎯测试敏感性:测试中的「预期失败」可以让程序上报异常
/// * 🚩在虚拟机终止(收到「终止」输出)时,自动用配置重启虚拟机
/// * 📜默认为`false`(关闭)
#[serde(default = "bool_false")]
pub strict_mode: bool,
}

/// 布尔值`true`
Expand Down Expand Up @@ -140,6 +148,8 @@ impl Default for LaunchConfig {
input_mode: InputMode::default(),
// 不自动重启
auto_restart: false,
// 不开启严格模式
strict_mode: false,
}
}
}
Expand Down Expand Up @@ -280,6 +290,7 @@ impl LaunchConfig {
self.user_input = other.user_input;
self.input_mode = other.input_mode;
self.auto_restart = other.auto_restart;
self.strict_mode = other.strict_mode;
// 递归合并所有【含有可选键】的值
LaunchConfigCommand::merge_as_key(&mut self.command, &other.command);
}
Expand Down
17 changes: 12 additions & 5 deletions src/bin/babelnar_cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,25 @@ pub fn main_args(_cwd: IoResult<PathBuf>, args: impl Iterator<Item = String>) ->
// 启动失败⇒打印错误信息,等待并退出
Err(e) => {
println_cli!([Error] "NARS运行时启动错误:{e}");
println_cli!([Info] "程序将在 3 秒后自动退出。。。");
sleep(Duration::from_secs(3));
// 启用用户输入时延时提示
if config.user_input {
println_cli!([Info] "程序将在 3 秒后自动退出。。。");
sleep(Duration::from_secs(3));
}
return Err(e);
}
};
// 运行时交互、管理
let manager = RuntimeManager::new(runtime, config.clone());
let result = loop_manage(manager, &config);

// 最终退出
println_cli!([Info] "程序将在 5 秒后退出");
sleep(Duration::from_secs(5));
// 启用用户输入时延时提示
if config.user_input {
println_cli!([Info] "程序将在 5 秒后自动退出。。。");
sleep(Duration::from_secs(3));
}

// 返回结果
result
}

Expand Down
121 changes: 81 additions & 40 deletions src/bin/babelnar_cli/runtime_manage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use babel_nar::{
eprintln_cli, println_cli,
test_tools::{nal_format::parse, put_nal, VmOutputCache},
};
use nar_dev_utils::ResultBoost;
use nar_dev_utils::{if_return, ResultBoost};
use navm::{
cmd::Cmd,
output::Output,
Expand All @@ -16,7 +16,7 @@ use navm::{
use std::{
fmt::Debug,
io::Result as IoResult,
ops::ControlFlow,
ops::{ControlFlow, ControlFlow::Break, ControlFlow::Continue},
sync::{Arc, Mutex, MutexGuard},
thread::{self, sleep, JoinHandle},
time::Duration,
Expand Down Expand Up @@ -161,7 +161,7 @@ where
}
}

/// 在运行时启动后,对其进行管理
/// 【主函数】在运行时启动后,对其进行管理
/// * 🎯健壮性:更多「警告/重来」而非`panic`
/// * 🎯用户友好:尽可能隐藏底层内容
/// * 如错误堆栈
Expand All @@ -170,15 +170,21 @@ where
/// * 用户的运行时交互
/// * Websocket服务端
/// * 🚩【2024-04-03 00:33:41】返回的[`Result`]作为程序的终止码
/// * `Ok(Ok(..))` ⇒ 程序正常退出
/// * `Ok(Err(..))` ⇒ 程序异常退出
/// * `Ok(Ok(..))` ⇒ 程序正常终止
/// * `Ok(Err(..))` ⇒ 程序异常终止
/// * `Err(..)` ⇒ 程序异常中断
pub fn manage(&mut self) -> Result<Result<()>> {
// 生成「读取输出」子线程 | 📌必须最先
let thread_read = self.spawn_read_output()?;

// 预置输入 | ⚠️阻塞
if let Err(e) = self.prelude_nal() {
println_cli!([Error] "预置NAL输入发生错误:{e}")
let prelude_result = self.prelude_nal();
match prelude_result {
// 预置输入要求终止⇒终止
Break(result) => return Ok(result),
// 预置输入发生错误⇒展示 & 继续
Continue(Err(e)) => println_cli!([Error] "预置NAL输入发生错误:{e}"),
Continue(Ok(..)) => (),
}

// 虚拟机被终止 & 无用户输入 ⇒ 程序退出
Expand Down Expand Up @@ -215,31 +221,62 @@ where

/// 预置NAL
/// * 🎯用于自动化调取`.nal`文件进行测试
pub fn prelude_nal(&mut self) -> Result<()> {
/// * 🚩【2024-04-03 10:28:18】使用[`ControlFlow`]对象以控制「是否提前返回」和「返回的结果」
/// * 📌[`Continue`] => 使用「警告&忽略」的方式处理[`Result`] => 继续(用户输入/Websocket服务端)
/// * 📌[`Break`] => 告知调用者「需要提前结束」
/// * 📌[`Break`]([`Ok`]) => 正常退出
/// * 📌[`Break`]([`Err`]) => 异常退出(报错)
pub fn prelude_nal(&mut self) -> ControlFlow<Result<()>, Result<()>> {
let config = &*self.config;

/// 尝试获取结果并返回
/// * 🎯对错误返回`Break(Err(错误))`而非`Err(错误)`
macro_rules! try_break {
// 统一逻辑
($v:expr => $e_id:ident $e:expr) => {
match $v {
// 获取成功⇒返回并继续
Ok(v) => v,
// 获取失败⇒ 告知「异常结束」
Err($e_id) => return Break(Err($e)),
}
};
// 两种错误分派方法
($v:expr) => { try_break!($v => e e.into()) };
(anyhow $v:expr) => { try_break!($v => e error_anyhow(e)) }; // * 🎯针对`PoisonError`
}

// 尝试获取运行时引用 | 仅有其它地方panic了才会停止
let runtime = &mut *self.runtime.lock().transform_err(error_anyhow)?;
let runtime = &mut *try_break!(anyhow self.runtime.lock());

// 仅在有预置NAL时开始
if let Some(prelude_nal) = &config.prelude_nal {
// 尝试获取输出缓冲区引用 | 仅有其它地方panic了才会停止
let output_cache = &mut *OutputCache::unlock_arc_mutex(&mut self.output_cache)?;
let output_cache =
&mut *try_break!(OutputCache::unlock_arc_mutex(&mut self.output_cache));

// 读取内容
let nal = match prelude_nal {
// 文件⇒尝试读取文件内容 | ⚠️此处创建了一个新值,所以要统一成`String`
LaunchConfigPreludeNAL::File(path) => std::fs::read_to_string(path)?,
LaunchConfigPreludeNAL::File(path) => try_break!(std::fs::read_to_string(path)),
// 纯文本⇒直接引入
LaunchConfigPreludeNAL::Text(nal) => nal.to_string(),
};

// 输入NAL
Self::input_nal_to_vm(runtime, &nal, output_cache, config)
// 输入NAL并处理
// * 🚩【2024-04-03 11:10:44】遇到错误,统一上报
// * 根据「严格模式」判断要「继续」还是「终止」
let put_result = Self::input_nal_to_vm(runtime, &nal, output_cache, config);
match self.config.strict_mode {
false => Continue(put_result),
true => Break(put_result),
}
}
// 否则自动返回「正常」
else {
// 返回 | 正常继续
Continue(Ok(()))
}

// 返回
Ok(())
}

/// 生成「读取输出」子线程
Expand Down Expand Up @@ -268,11 +305,8 @@ where
.try_fetch_output()
.inspect_err(|e| eprintln_cli!([Error] "尝试拉取NAVM运行时输出时发生错误:{e}"))
{
// 格式化输出
// * 🚩可能还要交给Websocket
println_cli!(&output);

// 缓存输出
// * 🚩在缓存时格式化输出
match output_cache.lock() {
Ok(mut output_cache) => output_cache.put(output)?,
Err(e) => eprintln_cli!([Error] "缓存NAVM运行时输出时发生错误:{e}"),
Expand Down Expand Up @@ -372,48 +406,55 @@ where
// NAL输入
InputMode::Nal => Self::input_nal_to_vm(runtime, line, output_cache, config),
}

// 输入完成
Ok(())
}

/// 像NAVM实例输入NAVM指令
fn input_cmd_to_vm(runtime: &mut R, line: &str) {
if let Ok(cmd) =
Cmd::parse(line).inspect_err(|e| eprintln_cli!([Error] "NAVM指令解析错误:{e}"))
{
let _ = runtime
.input_cmd(cmd)
.inspect_err(|e| eprintln_cli!([Error] "NAVM指令执行错误:{e}"));
}
fn input_cmd_to_vm(runtime: &mut R, line: &str) -> Result<()> {
let cmd =
Cmd::parse(line).inspect_err(|e| eprintln_cli!([Error] "NAVM指令解析错误:{e}"))?;
runtime
.input_cmd(cmd)
.inspect_err(|e| eprintln_cli!([Error] "NAVM指令执行错误:{e}"))
}

/// 像NAVM实例输入NAL(输入)
/// * 🎯预置、用户输入、Websocket输入
/// * 🎯严格模式
/// * 📌要么是「有失败 + 非严格模式 ⇒ 仅报告错误」
/// * 📌要么是「有一个失败 + 严格模式 ⇒ 返回错误」
/// * ⚠️可能有多行
fn input_nal_to_vm(
runtime: &mut R,
input: &str,
output_cache: &mut OutputCache,
config: &LaunchConfig,
) {
) -> Result<()> {
// 解析输入,并遍历解析出的每个NAL输入
for input in parse(input) {
// 尝试解析NAL输入
match input {
// 错误⇒根据严格模式处理
Err(e) => {
// 无论是否严格模式,都报告错误
eprintln_cli!([Error] "解析NAL输入时发生错误:{e}");
// 严格模式下提前返回
if_return! { config.strict_mode => Err(e) }
}
Ok(nal) => {
// 尝试置入NAL输入 | 为了错误消息,必须克隆
put_nal(runtime, nal.clone(), output_cache, config.user_input).unwrap_or_else(
// TODO: 严格模式:预期失败时上报错误,乃至使整个程序运行失败
|e| eprintln_cli!([Error] "置入NAL输入「{nal:?}」时发生错误:{e}"),
);
let put_result = put_nal(runtime, nal.clone(), output_cache, config.user_input);
// 处理错误
if let Err(e) = put_result {
// 无论是否严格模式,都报告错误
eprintln_cli!([Error] "置入NAL输入「{nal:?}」时发生错误:{e}");
// 严格模式下提前返回
if_return! { config.strict_mode => Err(e) }
}
}
// 错误⇒报错
Err(e) => eprintln_cli!(
[Error] "解析NAL输入时发生错误:{e}"
),
}
}
// 正常返回
Ok(())
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/cin_implements/ona/dialect_ona.pest
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ punct_sym = { (PUNCTUATION | SYMBOL) }
/// * 🆕对ONA兼容形如`(^op, {SELF}, LEFT)`的输出语法
/// * 🚩此处不进行「静默内联」:便于在「折叠函数」中向下分派
/// * 📝使用前缀「$」停止忽略空格,以使用「可选分隔符」
/// TODO: 【2024-04-03 11:50:06】对「像」可能需要特别的语法
/// * 📄`[OUT] Derived: <{SELF} --> (^left /1 P)>. :|: occurrenceTime=21 Priority=0.301667 Truth: frequency=1.000000, confidence=0.810000`
/// * 📄`[OUT] Derived: <P --> (^left /2 {SELF})>. :|`
compound = !{
compound_common
| compound_binary
Expand Down
3 changes: 2 additions & 1 deletion src/runtimes/command_vm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ impl VmRuntime for CommandVmRuntime {
// 空⇒提前返回
if_return! { input.is_empty() => Ok(()) }
// 置入
self.process.put(input)
// * 🚩没有换行符
self.process.put_line(input)
}

fn fetch_output(&mut self) -> Result<Output> {
Expand Down
2 changes: 1 addition & 1 deletion src/test_tools/vm_interact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ pub fn put_nal(
// 检查是否有NAVM输出符合预期
NALInput::ExpectContains(expectation) => {
// 先尝试拉取所有输出到「输出缓存」
while let Ok(Some(output)) = vm.try_fetch_output() {
while let Some(output) = vm.try_fetch_output()? {
output_cache.put(output)?;
}
// 然后读取并匹配缓存
Expand Down
7 changes: 4 additions & 3 deletions src/tests/cli/config/test_prelude_operation.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"description": "用于测试「预加载NAL输入」,加载「操作测试」",
"autoRestart": true,
"userInput": false,
"preludeNAL": {
"file": "./src/tests/nal/test_operation.nal"
}
},
"userInput": false,
"autoRestart": false,
"strictMode": true
}
7 changes: 4 additions & 3 deletions src/tests/cli/config/test_prelude_simple_deduction.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"description": "用于测试「预加载NAL输入」,加载「简单演绎推理」",
"autoRestart": true,
"userInput": false,
"preludeNAL": {
"file": "./src/tests/nal/test_simple_deduction.nal"
}
},
"userInput": false,
"autoRestart": false,
"strictMode": true
}
4 changes: 4 additions & 0 deletions src/tests/nal/test_operation.nal
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)`
' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出

' 降低音量,减少无关输出
' * 📄【2024-04-03 11:51:12】目前输出过多会造成CLI轻微卡顿
'/VOL 75

A. :|:
<(*, {SELF}) --> ^left>. :|:
G. :|:
Expand Down

0 comments on commit efdd7dc

Please sign in to comment.