From 22deb8bf645b3085e075950c6ff459c86e87a161 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:30:21 +0800 Subject: [PATCH 01/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=8E=9F=E5=9E=8B=E5=BC=80=E5=8F=91=EF=BC=9A=E5=B0=91=E9=87=8F?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/cin_launcher.rs | 7 +++++++ src/lib.rs | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 src/bin/cin_launcher.rs create mode 100644 src/lib.rs diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs new file mode 100644 index 0000000..b0c2415 --- /dev/null +++ b/src/bin/cin_launcher.rs @@ -0,0 +1,7 @@ +//! 一个一站式启动各CIN的启动器 +//! * 📌用于集成原先「BabelNAR」「BabelNAR_Implements」两个库 +//! TODO: 完成代码 + +/// 主函数 +/// TODO: 完成代码 +fn main() {} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a30abec --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +//! 主模块 +//! TODO: 参照**BabelNAR.jl**迁移并完善代码 From 464a5bf134931fd7d27b061944e858041ea7b626 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:03:01 +0800 Subject: [PATCH 02/59] =?UTF-8?q?feat:=20:construction:=20=E5=BA=93?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=9F=BA=E6=9C=AC=E6=90=AD=E5=BB=BA=EF=BC=9B?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E8=AF=BB=E5=86=99=E5=88=9D=E6=AD=A5=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/extensions.json | 4 +- .vscode/launch.json | 64 +++++++++++++++ .vscode/settings.json | 4 + Cargo.lock | 31 ++++++++ Cargo.toml | 31 ++++++++ README.md | 5 +- src/impl_runtime/mod.rs | 2 + src/lib.rs | 21 ++++- src/process_io/mod.rs | 167 ++++++++++++++++++++++++++++++++++++++++ src/runtime/mod.rs | 1 + 10 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 src/impl_runtime/mod.rs create mode 100644 src/process_io/mod.rs create mode 100644 src/runtime/mod.rs diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2808a0b..2af77d0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,8 @@ { "recommendations": [ "swellaby.vscode-rust-test-adapter", - "nyxiative.rust-and-friends" + "nyxiative.rust-and-friends", + "itsyaasir.rust-feature-toggler", + "rust-lang.rust-analyzer" ] } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dd63ce2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,64 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug级单元测试", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=babel_nar" + ], + "filter": { + "name": "babel_nar", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug可执行文件「CIN启动器」", + "cargo": { + "args": [ + "build", + "--bin=cin_launcher", + "--package=babel_nar" + ], + "filter": { + "name": "cin_launcher", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug级单元测试「CIN启动器」", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=cin_launcher", + "--package=babel_nar" + ], + "filter": { + "name": "cin_launcher", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d06b245 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.features": "all", + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e260f4d..dafbd0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,34 @@ version = 3 [[package]] name = "babel_nar" version = "0.1.0" +dependencies = [ + "nar_dev_utils", + "narsese", + "navm", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "nar_dev_utils" +version = "0.12.0" + +[[package]] +name = "narsese" +version = "0.5.0" +dependencies = [ + "lazy_static", + "nar_dev_utils", +] + +[[package]] +name = "navm" +version = "0.1.0" +dependencies = [ + "nar_dev_utils", + "narsese", +] diff --git a/Cargo.toml b/Cargo.toml index 536ef43..12a95c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,34 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +[dependencies.nar_dev_utils] +# 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` +# version = "0.1.0" # ! 本地依赖可以不添加版本 +# *🚩【2024-03-21 09:26:38】启用所有 +path = "../NAR-dev-util" +features = [] + +[dependencies.narsese] +# ! 本地依赖可以不添加版本 +# 载入Narsese API,引入其中所有部分 +path = "../Narsese.rs" +features = ["bundled"] + +[dependencies.navm] +# ! 本地依赖可以不添加版本 +# 载入NAVM API,引入「非公理虚拟机」模型 +path = "../NAVM.rs" +features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 + +# 定义库的特性 +[features] +# 默认启用的特性 +default = [] +# 大杂烩 +bundled = [ + "implements" +] +# 各个独立的特性 # +# 具体接口实现:OpenNARS、ONA、NARS-Python、OpenJunars、PyNARS…… +implements = [] diff --git a/README.md b/README.md index 8c7134b..81dd3ad 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 -基于[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)的CIN(NARS计算机实现)接口 +[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)的**运行时** - 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) -- 旨在方便连接各类CIN,并通过**Websocket**等服务提供**通用统一交互接口**。 +- 🎯为「非公理虚拟机模型」提供程序实现 +- 🎯为各CIN实现**统一输入输出**形式 ## 概念 diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs new file mode 100644 index 0000000..7c8740d --- /dev/null +++ b/src/impl_runtime/mod.rs @@ -0,0 +1,2 @@ +//! 对各CIN实现「非公理虚拟机」模型 +//! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 diff --git a/src/lib.rs b/src/lib.rs index a30abec..76230fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,21 @@ //! 主模块 -//! TODO: 参照**BabelNAR.jl**迁移并完善代码 +//! * ✨进程IO库 +//! * ✨通用运行时 +//! * ✨运行时的各类实现(可选) + +// 实用库别名 +pub extern crate nar_dev_utils as util; + +// 必选模块 +util::pub_mod_and_pub_use! { + // 进程IO + process_io + // 运行时 + runtime +} + +// 可选模块 +util::feature_pub_mod_and_reexport!{ + // 运行时实现 + "implements" => impl_runtime +} \ No newline at end of file diff --git a/src/process_io/mod.rs b/src/process_io/mod.rs new file mode 100644 index 0000000..935f3ac --- /dev/null +++ b/src/process_io/mod.rs @@ -0,0 +1,167 @@ +//! 用于封装抽象「进程通信」逻辑 +//! 示例代码来源:https://www.nikbrendler.com/rust-process-communication/ +//! * 📌基于「通道」的「子进程+专职读写的子线程」通信逻辑 +//! +//! TODO: 封装抽象提取 + +#![allow(unused)] + +use std::ffi::OsStr; +use std::io::{BufRead, BufReader, Write}; +use std::process::{ChildStdin, ChildStdout, Command, Stdio}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Mutex; +use std::thread; +use std::thread::sleep; +use std::time::Duration; + +fn sleep_secs(secs: u64) { + sleep(Duration::from_secs(secs)); +} + +/// 启动子进程 +fn start_process>( + program_path: S, + sender: Sender, + receiver: Receiver, +) { + // 创建一个子进程 + let child = + // 指令+参数 + Command::new(program_path) + .arg("shell") + // 输入输出 + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + // 产生进程 + .spawn() + .expect("Failed to start process"); + + println!("Started process: {}", child.id()); + + let stdin = child.stdin.unwrap(); + let stdout = child.stdout.unwrap(); + /// 生成进程的「读写守护」(线程) + let thread_write_in = spawn_thread_write_in(stdin, receiver); + let thread_read_out = spawn_thread_read_out(stdout, sender); +} + +/// 生成一个子线程,管理子进程的标准输入,接收通道另一端输出 +/// * 📌读输入,写进程 +fn spawn_thread_write_in(stdin: ChildStdin, receiver: Receiver) -> thread::JoinHandle<()> { + thread::spawn(move || { + // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 + let mut stdin = stdin; + for line in receiver { + // 写入输出 + if let Err(e) = stdin.write_all(line.as_bytes()) { + println!("无法向子进程输入:{e:?}"); + } + } + }) +} + +/// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 +/// * 📌写输出 +fn spawn_thread_read_out(stdout: ChildStdout, sender: Sender) -> thread::JoinHandle<()> { + thread::spawn(move || { + // 读取输出 + let mut stdout_reader = BufReader::new(stdout); + // 持续循环 + loop { + // 从子进程「标准输出」读取输入 + let mut buf = String::new(); + match stdout_reader.read_line(&mut buf) { + // 没有任何输入⇒跳过 + Ok(0) => continue, + // 有效输入 + Ok(_) => { + println!("子进程输出: {buf:?}"); + // 向「进程消息接收者」传递消息(实际上是「输出」) + if let Err(e) = sender.send(buf) { + println!("无法接收子进程输出:{e:?}"); + break; + } + continue; + } + Err(e) => { + println!("子进程报错: {:?}", e); + break; + } + } + } + }) +} + +fn start_command_thread(mutex: Mutex>) { + // 生成一个子线程,对上述进程进行读取 + thread::spawn(move || { + let sender = mutex.lock().unwrap(); + // 测试输入输出 + sleep_secs(1); + sender.send(" B>.\n".into()).unwrap(); + sleep_secs(1); + sender.send(" C>.\n".into()).unwrap(); + sleep_secs(1); + sender.send(" C>?\n".into()).unwrap(); + sleep_secs(1); + }); +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + // 定义一系列路径 + const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; + const EXE_PATH_REPL: &str = r"..\..\..\Julia\语言学小工Ju\繁简转换\dist\repl_简化.exe"; + const EXE_PATH_ECHO: &str = r"..\NAVM.rs\target\debug\examples\echo_exe.exe"; + + /// 实验用测试 + #[test] + fn test() { + // 创建通道 + let (child_out, out_sender) = channel(); + let (in_receiver, child_in) = channel(); + + // 启动进程 + start_process(EXE_PATH_ONA, child_out, child_in); + + // tx2.send(("Command 1\n".into())).unwrap(); + let mutex = Mutex::new(in_receiver); + start_command_thread(mutex); + // println!("{in_receiver:?}"); + + // 从外部获取输出(阻塞) + // for line in out_sender { + // println!("Got this back: {}", line); + // } + + // 等待 + sleep_secs(5); + println!("程序结束!"); + } + + /// 标准案例:ONA交互 + /// + /// ## 测试输入 + /// + /// ```plaintext + /// B>. + /// C>. + /// C>? + /// ``` + /// + /// ## 预期输出 + /// + /// ```plaintext + /// Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.810000 + /// ``` + /// + /// TODO: 【2024-03-21 10:02:34】按想要的「目标形式」写测试,然后以此驱动开发整个库(面向用法) + #[test] + fn test_ona() { + // let runtime = Runtime::builder(); + } +} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs new file mode 100644 index 0000000..7886f5d --- /dev/null +++ b/src/runtime/mod.rs @@ -0,0 +1 @@ +//! 用于封装表示「非公理虚拟机」运行时 From 0c7bfcc87b39932e2695e579760289dec852c2ea Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 21 Mar 2024 22:58:21 +0800 Subject: [PATCH 03/59] =?UTF-8?q?feat:=20:construction:=20=E8=B7=9F?= =?UTF-8?q?=E8=BF=9BNAVM=EF=BC=8C=E4=B8=BA=E3=80=8C=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E8=99=9A=E6=8B=9F=E6=9C=BA=E3=80=8D=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=A9=BA=E7=99=BD=E5=AE=9E=E7=8E=B0=EF=BC=88WIP=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/runtime/mod.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 7886f5d..4cdf8e4 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1 +1,53 @@ //! 用于封装表示「非公理虚拟机」运行时 +//! TODO: 给出一个基于「进程通信」实现[`VM`]的结构 + +use navm::{ + cmd::Cmd, + vm::{Output, VmBuilder, VmRuntime}, +}; + +/// 命令行虚拟机(构建者) +/// * 🎯配置化构造[`CommandVmRuntime`] +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandVm { + // TODO: 增加具体字段 +} + +/// 命令行虚拟机运行时 +/// * 🎯封装「进程通信」逻辑 +pub struct CommandVmRuntime { + // TODO: 增加具体字段 +} + +impl VmRuntime for CommandVmRuntime { + fn input_cmd(&mut self, cmd: Cmd) { + todo!() + } + + fn store_output(&mut self, output: Output) { + todo!() + } + + fn fetch_output(&mut self) -> Option { + todo!() + } + + fn add_output_listener(&mut self, listener: Listener) + where + Listener: FnMut(Output) -> Option, + { + todo!() + } + + fn iter_output_listeners<'a>( + &'a self, + ) -> Box Option> + 'a> { + todo!() + } +} + +impl VmBuilder for CommandVm { + fn build(self) -> CommandVmRuntime { + CommandVmRuntime {} + } +} From 0322af92a6f77b2ab98cd1336f04d9ba173a8ff5 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:38:38 +0800 Subject: [PATCH 04/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E3=80=8C=E8=BE=93=E5=85=A5=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=AD=90=E8=BF=9B=E7=A8=8B=E3=80=8D=E7=9A=84=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于「输出侦听器」的模型,封装进程通信逻辑到「IoProcess」 --- src/process_io/io_process.rs | 356 +++++++++++++++++++++++++++++++++++ src/process_io/mod.rs | 164 +--------------- 2 files changed, 359 insertions(+), 161 deletions(-) create mode 100644 src/process_io/io_process.rs diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs new file mode 100644 index 0000000..aa24bb4 --- /dev/null +++ b/src/process_io/io_process.rs @@ -0,0 +1,356 @@ +//! 封装一个简单的「交互式输入输出」 + +use std::ffi::OsStr; +use std::io::{BufRead, BufReader, Result as IoResult, Write}; +use std::process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Mutex; +use std::thread::{self, JoinHandle}; + +use util::ResultTransform; + +/// 统一定义「输出侦听器」的类型 +type OutputListener = dyn FnMut(String) + Send + Sync; + +/// 构建一个「IO进程」 +/// * 📌只是作为一个「构建器」存在 +/// * 作为真正的`IoProcessManager`的launcher/builder +/// +/// ! 因为有「系统指令」与「函数闭包」无法派生任何常规宏 +#[derive()] +pub struct IoProcess { + /// 内部封装的「进程指令」对象 + command: Command, + /// 内部配置的「输出侦听器」 + out_listener: Option>, +} + +impl IoProcess { + /// 构造函数 + pub fn new(program_path: impl AsRef) -> Self { + Self { + command: Command::new(program_path), + out_listener: None, + } + } + + /// 添加命令行参数 + pub fn arg(mut self, arg: impl AsRef) -> Self { + // 添加参数 + self.command.arg(arg); + // 返回自身以便链式调用 + self + } + + /// 添加输出侦听器 + /// * 📌此处因生命周期问题(难以绑定`listener`到`self`)设置`F`的约束为`'static` + pub fn out_listener(mut self, listener: F) -> Self + where + F: FnMut(String) + Send + Sync + 'static, + { + // 字段赋值 + self.out_listener = Some(Box::new(listener)); + // 返回自身以便链式调用 + self + } + + /// 启动 + /// * 🚩通过[`Self::try_launch`]尝试启动,然后直接解包 + /// + /// # Panics + /// * 📌如果子进程创建失败,将直接 panic + pub fn launch(self) -> IoProcessManager { + self + // 尝试启动 + .try_launch() + //解包 + .expect("无法启动子进程") + } + + /// 启动 + /// * 🚩此处只负责创建子进程[`Child`], + /// * ⚠️不负责对子进程的控制(监听、通道)等 + pub fn try_launch(mut self) -> std::io::Result { + // 创建一个子进程 + let child = + // 指令+参数 + self.command + .arg("shell") + // 输入输出 + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + // 产生进程 + .spawn()?; + println!("Started process: {}", child.id()); + + // 获取输出侦听器 + let out_listener = self.out_listener; + + // 创建「子进程管理器」对象 + Ok(IoProcessManager::new(child, out_listener)) + } +} + +/// 子进程管理器 +/// * 🎯负责 +/// * 统一管理子进程 +/// * 封装提供易用的(字符串)输入输出接口 +#[allow(dead_code)] +pub struct IoProcessManager { + /// 正在管理的子进程 + process: Child, + + /// 子进程的「写(到子进程的)输入」守护线程 + thread_write_in: JoinHandle<()>, + /// 子进程的「读(到子进程的)输出」守护线 + /// * 📌【2024-03-22 09:57:39】现在使用「输出侦听器」模式,可能没有 + thread_read_out: Option>, + + // /// 子进程输出的「接收者」 + // /// * 🚩子进程发送给外部侦听器,由外部接收 + // child_out: Mutex>, + // ! 【2024-03-22 09:54:22】↑现在使用「输出侦听器」模式,不再需要此字段 + /// 子进程输入的「发送者」 + /// * 🚩子进程接收来自外部发送的消息,由外部发送 + child_in: Mutex>, + // /// 子进程的「输出监听器」 + // out_listener: Option>, + // ! 【2024-03-22 09:54:22】↑现在使用「输出侦听器」模式,此字段数据存储在`thread_read_out`中 +} + +impl IoProcessManager { + // * 初始化 * // + /// 构造方法 + /// * 🚩从「子进程」与「输出侦听器」构造「进程管理者」 + pub fn new(mut child: Child, out_listener: Option>) -> Self { + // 提取子进程的标准输入输出 + let stdin = child.stdin.take().unwrap(); + let stdout = child.stdout.take().unwrap(); + + // 创建通道 + // * 📌IO流向:从左到右 + // ! 🚩【2024-03-22 09:53:12】现在采用「输出侦听器」的方法,不再需要封装通道 + // let (child_out, out_sender) = channel(); + let (in_receiver, child_in) = channel(); + + // 生成进程的「读写守护」(线程) + let thread_write_in = IoProcessManager::spawn_thread_write_in(stdin, child_in); + // let thread_read_out = IoProcessManager::spawn_thread_read_out(stdout, child_out); + let thread_read_out = + out_listener.map(|listener| IoProcessManager::spawn_thread_read_out(stdout, listener)); + + // 捕获通道的两端 + // let child_out_sender = Mutex::new(out_sender); + let child_in_receiver = Mutex::new(in_receiver); + + // 构造并返回自身 + Self { + process: child, + thread_read_out, + thread_write_in, + // child_out: child_out_sender, + child_in: child_in_receiver, + // out_listener, + // ! 【2024-03-22 09:53:50】↑不再于自身存储「输出侦听器」,而是存储在`thread_read_out`中 + } + } + + /// 生成一个子线程,管理子进程的标准输入,接收通道另一端输出 + /// * 📌读输入,写进程 | stdin >>> child_in_receiver + fn spawn_thread_write_in( + stdin: ChildStdin, + child_in_receiver: Receiver, + ) -> thread::JoinHandle<()> { + thread::spawn(move || { + // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 + let mut stdin = stdin; + for line in child_in_receiver { + // 写入输出 + if let Err(e) = stdin.write_all(line.as_bytes()) { + println!("无法向子进程输入:{e:?}"); + } + } + }) + } + + /// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 + /// // * 📌写输出 | child_out_sender >>> stdout + /// * 🚩【2024-03-22 09:58:54】现在采用「输出侦听器」模式,不再需要通道 + fn spawn_thread_read_out( + stdout: ChildStdout, + // child_out_sender: Sender, + mut listener: Box, + ) -> thread::JoinHandle<()> { + thread::spawn(move || { + // 读取输出 + let mut stdout_reader = BufReader::new(stdout); + // 持续循环 + loop { + // 从子进程「标准输出」读取输入 + let mut buf = String::new(); + match stdout_reader.read_line(&mut buf) { + // 没有任何输入⇒跳过 + Ok(0) => continue, + // 有效输入 + Ok(_) => { + // ! 🚩【2024-03-22 10:00:51】↓使用「输出侦听器」,不再需要 + // // 向「进程消息接收者」传递消息(实际上是「输出」) + // if let Err(e) = child_out_sender.send(buf) { + // println!("无法接收子进程输出:{e:?}"); + // break; + // } + listener(buf.clone()); + continue; + } + Err(e) => { + println!("子进程报错: {:?}", e); + break; + } + } + } + }) + } + + // * 正常运作 * // + /// 向子进程写入数据 + /// * 🚩通过使用自身「子进程输入」的互斥锁,从中输入数据 + /// * ⚠️返回空,或返回字符串形式的错误 + pub fn put(&self, input: impl ToString) -> Result<(), String> { + // 从互斥锁中获取输入 + // * 🚩等待直到锁定互斥锁,最终在作用域结束(MutexGuard析构)时释放(解锁) + let child_in_guard = self.child_in.lock().transform_err(|err| err.to_string())?; + child_in_guard + .send(input.to_string()) + .transform_err(|err| err.to_string()) + } + + /// 等待子进程结束 + /// * 🚩调用[`Child::wait`]方法 + /// * ⚠️对于【不会主动终止】的子进程,此举可能导致调用者死锁 + pub fn wait(&mut self) -> IoResult { + self.process.wait() + } + + /// 强制结束子进程 + /// * 🚩调用[`Child::kill`]方法 + pub fn kill(&mut self) -> IoResult<()> { + self.process.kill() + } + + /// 获取子进程id + /// * 🚩调用[`Child::id`]方法 + pub fn id(&self) -> u32 { + self.process.id() + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + + use super::*; + use std::{ + process::exit, + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, + }; + + /// 测试/睡眠指定时间 + fn sleep_secs(secs: u64) { + sleep(Duration::from_secs(secs)); + } + + // 定义一系列路径 + #[allow(unused)] + const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; + #[allow(unused)] + const EXE_PATH_REPL: &str = r"..\..\..\Julia\语言学小工Ju\繁简转换\dist\repl_简化.exe"; + #[allow(unused)] + const EXE_PATH_ECHO: &str = r"..\NAVM.rs\target\debug\examples\echo_exe.exe"; + + /// 标准案例:ONA交互 + /// + /// ## 测试输入 + /// + /// ```plaintext + /// B>. + /// C>. + /// C>? + /// ``` + /// + /// ## 预期输出 + /// + /// ```plaintext + /// Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.810000 + /// ``` + /// + /// ## 笔记 + /// + /// * 📝[`Arc`]能满足[`Sync`]+[`Send`],但R[`efCell`]不满足 + /// * ❌无法使用`Arc>`组合 + /// * 📝[`Mutex`]能进行进程交互,但无法共享引用 + /// * 🚩最终使用`Arc>`作为进程交互的共享引用 + /// * 📌[`Arc`]允许被拷贝并移动入闭包(共享引用,超越生命周期) + /// * 📌[`Mutex`]允许进程间共享的内部可变性(运行时借用检查) + #[test] + fn test_ona() { + // 接收输出 + let outputs = Arc::new(Mutex::new(vec![])); + let outputs_inner = outputs.clone(); + // 从一个系统指令开始构建并启动子进程 + let mut process = IoProcess::new(EXE_PATH_ONA) + // 添加命令参数 + .arg("shell") + // 添加输出监听器 | 简单回显 + // ! 【2024-03-22 10:06:38】基于「输出侦听器」的情形,若需要与外部交互,则会遇到所有权/生命周期问题 + // * 📄子进程与子进程外部(如此处的主进程)的问题 + // * ✅【2024-03-22 10:16:32】↑已使用`Arc`解决 + .out_listener(move |output: String| { + outputs_inner + .lock() + .expect("无法锁定 outputs_inner") + .push(output.clone()); + println!("[OUT] {}", output); + }) + // 启动子进程 + .launch(); + + // 测试:输入输出 // + let output_must_contains = |s: &str| { + let outputs = outputs.lock().expect("无法锁定 outputs"); + assert!(outputs.iter().any(|x| x.contains(s))) + }; + // 先置入输入 + sleep_secs(1); + dbg!(process.put(" B>.\n").expect("无法放置输入")); + sleep_secs(1); + + // 中途检验 + output_must_contains(" B>."); + + // 继续输入 + dbg!(process.put(" C>.\n").expect("无法放置输入")); + sleep_secs(1); + dbg!(process.put(" C>?\n").expect("无法放置输入")); + sleep_secs(1); + + // 最后检验 + output_must_contains("Answer: C>."); + + // // 等待结束 + // process.wait(); + + // 等待五秒并强制结束 + println!("Waiting for 5 seconds and then killing the process..."); + sleep_secs(5); + dbg!(process.kill().expect("无法杀死进程")); + println!("Process killed."); + + // 读取检验输出 + dbg!(&outputs); + + // 退出 + exit(0); + } +} diff --git a/src/process_io/mod.rs b/src/process_io/mod.rs index 935f3ac..a822416 100644 --- a/src/process_io/mod.rs +++ b/src/process_io/mod.rs @@ -2,166 +2,8 @@ //! 示例代码来源:https://www.nikbrendler.com/rust-process-communication/ //! * 📌基于「通道」的「子进程+专职读写的子线程」通信逻辑 //! -//! TODO: 封装抽象提取 -#![allow(unused)] - -use std::ffi::OsStr; -use std::io::{BufRead, BufReader, Write}; -use std::process::{ChildStdin, ChildStdout, Command, Stdio}; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Mutex; -use std::thread; -use std::thread::sleep; -use std::time::Duration; - -fn sleep_secs(secs: u64) { - sleep(Duration::from_secs(secs)); -} - -/// 启动子进程 -fn start_process>( - program_path: S, - sender: Sender, - receiver: Receiver, -) { - // 创建一个子进程 - let child = - // 指令+参数 - Command::new(program_path) - .arg("shell") - // 输入输出 - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - // 产生进程 - .spawn() - .expect("Failed to start process"); - - println!("Started process: {}", child.id()); - - let stdin = child.stdin.unwrap(); - let stdout = child.stdout.unwrap(); - /// 生成进程的「读写守护」(线程) - let thread_write_in = spawn_thread_write_in(stdin, receiver); - let thread_read_out = spawn_thread_read_out(stdout, sender); -} - -/// 生成一个子线程,管理子进程的标准输入,接收通道另一端输出 -/// * 📌读输入,写进程 -fn spawn_thread_write_in(stdin: ChildStdin, receiver: Receiver) -> thread::JoinHandle<()> { - thread::spawn(move || { - // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 - let mut stdin = stdin; - for line in receiver { - // 写入输出 - if let Err(e) = stdin.write_all(line.as_bytes()) { - println!("无法向子进程输入:{e:?}"); - } - } - }) -} - -/// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 -/// * 📌写输出 -fn spawn_thread_read_out(stdout: ChildStdout, sender: Sender) -> thread::JoinHandle<()> { - thread::spawn(move || { - // 读取输出 - let mut stdout_reader = BufReader::new(stdout); - // 持续循环 - loop { - // 从子进程「标准输出」读取输入 - let mut buf = String::new(); - match stdout_reader.read_line(&mut buf) { - // 没有任何输入⇒跳过 - Ok(0) => continue, - // 有效输入 - Ok(_) => { - println!("子进程输出: {buf:?}"); - // 向「进程消息接收者」传递消息(实际上是「输出」) - if let Err(e) = sender.send(buf) { - println!("无法接收子进程输出:{e:?}"); - break; - } - continue; - } - Err(e) => { - println!("子进程报错: {:?}", e); - break; - } - } - } - }) -} - -fn start_command_thread(mutex: Mutex>) { - // 生成一个子线程,对上述进程进行读取 - thread::spawn(move || { - let sender = mutex.lock().unwrap(); - // 测试输入输出 - sleep_secs(1); - sender.send(" B>.\n".into()).unwrap(); - sleep_secs(1); - sender.send(" C>.\n".into()).unwrap(); - sleep_secs(1); - sender.send(" C>?\n".into()).unwrap(); - sleep_secs(1); - }); -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - - // 定义一系列路径 - const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - const EXE_PATH_REPL: &str = r"..\..\..\Julia\语言学小工Ju\繁简转换\dist\repl_简化.exe"; - const EXE_PATH_ECHO: &str = r"..\NAVM.rs\target\debug\examples\echo_exe.exe"; - - /// 实验用测试 - #[test] - fn test() { - // 创建通道 - let (child_out, out_sender) = channel(); - let (in_receiver, child_in) = channel(); - - // 启动进程 - start_process(EXE_PATH_ONA, child_out, child_in); - - // tx2.send(("Command 1\n".into())).unwrap(); - let mutex = Mutex::new(in_receiver); - start_command_thread(mutex); - // println!("{in_receiver:?}"); - - // 从外部获取输出(阻塞) - // for line in out_sender { - // println!("Got this back: {}", line); - // } - - // 等待 - sleep_secs(5); - println!("程序结束!"); - } - - /// 标准案例:ONA交互 - /// - /// ## 测试输入 - /// - /// ```plaintext - /// B>. - /// C>. - /// C>? - /// ``` - /// - /// ## 预期输出 - /// - /// ```plaintext - /// Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.810000 - /// ``` - /// - /// TODO: 【2024-03-21 10:02:34】按想要的「目标形式」写测试,然后以此驱动开发整个库(面向用法) - #[test] - fn test_ona() { - // let runtime = Runtime::builder(); - } +util::pub_mod_and_pub_use! { + // 输入输出进程 + io_process } From d7b3c7e012ccd9176c414aaa907630eaa8601e41 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:56:50 +0800 Subject: [PATCH 05/59] =?UTF-8?q?refactor:=20:recycle:=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E3=80=8C=E5=91=BD=E4=BB=A4=E8=A1=8C=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E3=80=8D=EF=BC=8C=E4=BC=98=E5=8C=96=E5=8C=85=E6=9E=B6?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/extensions.json | 3 +- Cargo.lock | 2 +- src/lib.rs | 6 ++-- src/runtime/command_vm/builder.rs | 16 +++++++++ src/runtime/command_vm/mod.rs | 16 +++++++++ src/runtime/command_vm/runtime.rs | 56 +++++++++++++++++++++++++++++++ src/runtime/command_vm/traits.rs | 19 +++++++++++ src/runtime/mod.rs | 55 +++--------------------------- 8 files changed, 118 insertions(+), 55 deletions(-) create mode 100644 src/runtime/command_vm/builder.rs create mode 100644 src/runtime/command_vm/mod.rs create mode 100644 src/runtime/command_vm/runtime.rs create mode 100644 src/runtime/command_vm/traits.rs diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2af77d0..1cec836 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ "swellaby.vscode-rust-test-adapter", "nyxiative.rust-and-friends", "itsyaasir.rust-feature-toggler", - "rust-lang.rust-analyzer" + "rust-lang.rust-analyzer", + "aaron-bond.better-comments" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dafbd0a..826d9e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.12.0" [[package]] name = "narsese" -version = "0.5.0" +version = "0.6.0" dependencies = [ "lazy_static", "nar_dev_utils", diff --git a/src/lib.rs b/src/lib.rs index 76230fb..c2fc4b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub extern crate nar_dev_utils as util; // 必选模块 -util::pub_mod_and_pub_use! { +util::mod_and_pub_use! { // 进程IO process_io // 运行时 @@ -15,7 +15,7 @@ util::pub_mod_and_pub_use! { } // 可选模块 -util::feature_pub_mod_and_reexport!{ +util::feature_pub_mod_and_reexport! { // 运行时实现 "implements" => impl_runtime -} \ No newline at end of file +} diff --git a/src/runtime/command_vm/builder.rs b/src/runtime/command_vm/builder.rs new file mode 100644 index 0000000..6dc2316 --- /dev/null +++ b/src/runtime/command_vm/builder.rs @@ -0,0 +1,16 @@ +//! 命令行虚拟机(构建者) + +/// 命令行虚拟机(构建者) +/// * 🎯配置化构造[`CommandVmRuntime`] +/// * 🚩有关「启动」的流程,放在「虚拟机运行时」[`super::runtime`]中 +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandVm { + // TODO: 增加具体字段 +} + +impl CommandVm { + /// 构造函数 + pub fn new() -> Self { + Self {} + } +} diff --git a/src/runtime/command_vm/mod.rs b/src/runtime/command_vm/mod.rs new file mode 100644 index 0000000..e4269f3 --- /dev/null +++ b/src/runtime/command_vm/mod.rs @@ -0,0 +1,16 @@ +//! 基于「进程通信」与「IO转译器」的「命令行运行时」 + +use crate::IoProcessManager; +use navm::{ + cmd::Cmd, + vm::{Output, VmBuilder, VmRuntime}, +}; + +util::pub_mod_and_pub_use! { + // 抽象特征 + traits + // 构建器 + builder + // 运行时 + runtime +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs new file mode 100644 index 0000000..1e1e45c --- /dev/null +++ b/src/runtime/command_vm/runtime.rs @@ -0,0 +1,56 @@ +//! 命令行虚拟机 运行时 + +/// 命令行虚拟机运行时 +/// * 🎯封装「进程通信」逻辑 +pub struct CommandVmRuntime +where + I: InputTranslator, + O: OutputTranslator, +{ + /// 封装的「进程管理者」 + /// * 🚩使用[`IoProcessManager`]封装「进程通信」的逻辑细节 + io_process: IoProcessManager, + + /// [`Cmd`]→进程输入 转译器 + input_translator: I, + + /// 进程输出→[`Output`]转译器 + output_translator: O, +} + +impl VmRuntime for CommandVmRuntime { + fn input_cmd(&mut self, cmd: Cmd) { + todo!() + } + + fn store_output(&mut self, output: Output) { + todo!() + } + + fn fetch_output(&mut self) -> Option { + todo!() + } + + fn add_output_listener(&mut self, listener: Listener) + where + Listener: FnMut(Output) -> Option, + { + todo!() + } + + fn iter_output_listeners<'a>( + &'a self, + ) -> Box Option> + 'a> { + todo!() + } +} + +impl VmBuilder for CommandVm { + fn build(self) -> CommandVmRuntime { + // TODO: 增加启动流程 + todo!() + // CommandVmRuntime { + + // } + } +} diff --git a/src/runtime/command_vm/traits.rs b/src/runtime/command_vm/traits.rs new file mode 100644 index 0000000..fea6827 --- /dev/null +++ b/src/runtime/command_vm/traits.rs @@ -0,0 +1,19 @@ +//! 定义有关「命令行虚拟机」的抽象特征 +//! * ✨核心内容 +//! * ⇄ 基于「进程通信」的消息互转 +//! * 📌核心IO流程: +//! 1. NAVM指令[`Cmd`] >>> 进程输入 >>> 子进程 +//! 2. 子进程 >>> 进程输出 >>> NAVM输出[`Output`] +//! * 🚩实现方式:两处转译器 + +use navm::{cmd::Cmd, vm::Output}; + +/// [`Cmd`]→进程输入 转译器 +pub trait InputTranslator { + fn translate_to_input(cmd: Cmd) -> String; +} + +/// 进程输出→[`Output`]转译器 +pub trait OutputTranslator { + fn translate_from_output(output: String) -> Output; +} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 4cdf8e4..b7db7af 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1,53 +1,8 @@ //! 用于封装表示「非公理虚拟机」运行时 -//! TODO: 给出一个基于「进程通信」实现[`VM`]的结构 +//! * 📌不与特定的CIN相关 +//! * 📄一个「命令行运行时」可同时适用于OpenNARS、ONA、NARS-Python…… -use navm::{ - cmd::Cmd, - vm::{Output, VmBuilder, VmRuntime}, -}; - -/// 命令行虚拟机(构建者) -/// * 🎯配置化构造[`CommandVmRuntime`] -#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] -pub struct CommandVm { - // TODO: 增加具体字段 -} - -/// 命令行虚拟机运行时 -/// * 🎯封装「进程通信」逻辑 -pub struct CommandVmRuntime { - // TODO: 增加具体字段 -} - -impl VmRuntime for CommandVmRuntime { - fn input_cmd(&mut self, cmd: Cmd) { - todo!() - } - - fn store_output(&mut self, output: Output) { - todo!() - } - - fn fetch_output(&mut self) -> Option { - todo!() - } - - fn add_output_listener(&mut self, listener: Listener) - where - Listener: FnMut(Output) -> Option, - { - todo!() - } - - fn iter_output_listeners<'a>( - &'a self, - ) -> Box Option> + 'a> { - todo!() - } -} - -impl VmBuilder for CommandVm { - fn build(self) -> CommandVmRuntime { - CommandVmRuntime {} - } +util::mod_and_pub_use! { + // 命令行运行时 + command_vm } From e4996c1099a6d62eb6d740153a237436a2a54e3c Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:40:54 +0800 Subject: [PATCH 06/59] =?UTF-8?q?feat:=20:sparkles:=20=E6=A0=B9=E6=8D=AENA?= =?UTF-8?q?VM=E8=B0=83=E6=95=B4=E6=9C=89=E5=85=B3=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E7=BB=93=E6=9E=84=EF=BC=9B=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E4=B8=BA=E3=80=8C=E5=91=BD=E4=BB=A4=E8=A1=8C=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E3=80=8D=E6=B7=BB=E5=8A=A0=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据NAVM.rs更新所依赖的内容;现在「输入转译器」「输出转译器」不再使用特征;初步实现「命令行虚拟机」的launch方法 --- Cargo.lock | 4 +-- src/impl_runtime/mod.rs | 1 + src/lib.rs | 14 ++++----- src/process_io/io_process.rs | 22 +++++++------- src/runtime/command_vm/api.rs | 17 +++++++++++ src/runtime/command_vm/builder.rs | 25 +++++++++++++--- src/runtime/command_vm/mod.rs | 11 ++----- src/runtime/command_vm/runtime.rs | 49 ++++++++++++++++++++++--------- src/runtime/command_vm/traits.rs | 19 ------------ 9 files changed, 97 insertions(+), 65 deletions(-) create mode 100644 src/runtime/command_vm/api.rs delete mode 100644 src/runtime/command_vm/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 826d9e1..fa1848b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,11 +19,11 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "nar_dev_utils" -version = "0.12.0" +version = "0.14.0" [[package]] name = "narsese" -version = "0.6.0" +version = "0.6.1" dependencies = [ "lazy_static", "nar_dev_utils", diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs index 7c8740d..cb85f4d 100644 --- a/src/impl_runtime/mod.rs +++ b/src/impl_runtime/mod.rs @@ -1,2 +1,3 @@ //! 对各CIN实现「非公理虚拟机」模型 //! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 +//! TODO: 支持各大CIN diff --git a/src/lib.rs b/src/lib.rs index c2fc4b7..704c3a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,15 +6,13 @@ // 实用库别名 pub extern crate nar_dev_utils as util; -// 必选模块 -util::mod_and_pub_use! { - // 进程IO - process_io - // 运行时 - runtime -} +// 必选模块 // +// 进程IO +pub mod process_io; +// 运行时 +pub mod runtime; -// 可选模块 +// 可选模块 // util::feature_pub_mod_and_reexport! { // 运行时实现 "implements" => impl_runtime diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index aa24bb4..75da147 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -1,13 +1,16 @@ //! 封装一个简单的「交互式输入输出」 -use std::ffi::OsStr; -use std::io::{BufRead, BufReader, Result as IoResult, Write}; -use std::process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::sync::Mutex; -use std::thread::{self, JoinHandle}; - -use util::ResultTransform; +use std::{ + ffi::OsStr, + io::{BufRead, BufReader, Result as IoResult, Write}, + process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Mutex, + }, + thread::{self, JoinHandle}, +}; +use util::*; /// 统一定义「输出侦听器」的类型 type OutputListener = dyn FnMut(String) + Send + Sync; @@ -16,8 +19,7 @@ type OutputListener = dyn FnMut(String) + Send + Sync; /// * 📌只是作为一个「构建器」存在 /// * 作为真正的`IoProcessManager`的launcher/builder /// -/// ! 因为有「系统指令」与「函数闭包」无法派生任何常规宏 -#[derive()] +/// ! 因为有「系统指令」与「函数闭包」,无法派生任何常规宏 pub struct IoProcess { /// 内部封装的「进程指令」对象 command: Command, diff --git a/src/runtime/command_vm/api.rs b/src/runtime/command_vm/api.rs new file mode 100644 index 0000000..9b7ecb5 --- /dev/null +++ b/src/runtime/command_vm/api.rs @@ -0,0 +1,17 @@ +//! 定义有关「命令行虚拟机」的抽象API + +use navm::{cmd::Cmd, output::Output}; + +/// [`Cmd`]→进程输入 转译器 +/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 +/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 +/// * 📌要求线程稳定 +/// * 只有转译功能,没有其它涉及外部的操作(纯函数) +pub type InputTranslator = dyn Fn(Cmd) -> String + Send + Sync; + +/// 进程输出→[`Output`]转译器 +/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 +/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 +/// * 📌要求线程稳定 +/// * 只有转译功能,没有其它涉及外部的操作(纯函数) +pub type OutputTranslator = dyn Fn(String) -> Output + Send + Sync; diff --git a/src/runtime/command_vm/builder.rs b/src/runtime/command_vm/builder.rs index 6dc2316..5d63d02 100644 --- a/src/runtime/command_vm/builder.rs +++ b/src/runtime/command_vm/builder.rs @@ -1,16 +1,33 @@ //! 命令行虚拟机(构建者) +use super::{InputTranslator, OutputTranslator}; +use crate::process_io::IoProcess; +use std::ffi::OsStr; + /// 命令行虚拟机(构建者) /// * 🎯配置化构造[`CommandVmRuntime`] +/// * 封装内部「输入输出进程」的「输出侦听器」逻辑 /// * 🚩有关「启动」的流程,放在「虚拟机运行时」[`super::runtime`]中 -#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct CommandVm { - // TODO: 增加具体字段 + /// 内部存储的「输入输出进程」 + pub(super) io_process: IoProcess, + + /// [`Cmd`]→进程输入 转译器 + pub(super) input_translator: Option>, + + /// 进程输出→[`Output`]转译器 + pub(super) output_translator: Option>, } impl CommandVm { /// 构造函数 - pub fn new() -> Self { - Self {} + pub fn new(program_path: impl AsRef) -> Self { + Self { + // 指令 + io_process: IoProcess::new(program_path), + // 其它暂时置空 + input_translator: None, + output_translator: None, + } } } diff --git a/src/runtime/command_vm/mod.rs b/src/runtime/command_vm/mod.rs index e4269f3..4d9fe58 100644 --- a/src/runtime/command_vm/mod.rs +++ b/src/runtime/command_vm/mod.rs @@ -1,14 +1,9 @@ //! 基于「进程通信」与「IO转译器」的「命令行运行时」 - -use crate::IoProcessManager; -use navm::{ - cmd::Cmd, - vm::{Output, VmBuilder, VmRuntime}, -}; +//! * 🎯基于进程通信与各CIN交互 util::pub_mod_and_pub_use! { - // 抽象特征 - traits + // 抽象API + api // 构建器 builder // 运行时 diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 1e1e45c..0ccf565 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -1,21 +1,32 @@ //! 命令行虚拟机 运行时 +//! * ✨核心内容 +//! * ⇄ 基于「进程通信」的消息互转 +//! * 📌核心IO流程: +//! 1. NAVM指令[`Cmd`] >>> 进程输入 >>> 子进程 +//! 2. 子进程 >>> 进程输出 >>> NAVM输出[`Output`] +//! * 🚩实现方式:两处转译器 + +use super::{CommandVm, InputTranslator, OutputTranslator}; +use crate::process_io::IoProcessManager; +use navm::{ + cmd::Cmd, + output::Output, + vm::{VmBuilder, VmRuntime}, +}; /// 命令行虚拟机运行时 /// * 🎯封装「进程通信」逻辑 -pub struct CommandVmRuntime -where - I: InputTranslator, - O: OutputTranslator, -{ +pub struct CommandVmRuntime { /// 封装的「进程管理者」 /// * 🚩使用[`IoProcessManager`]封装「进程通信」的逻辑细节 - io_process: IoProcessManager, + process: IoProcessManager, /// [`Cmd`]→进程输入 转译器 - input_translator: I, + input_translator: Box, /// 进程输出→[`Output`]转译器 - output_translator: O, + output_translator: Box, + // TODO: 输出侦听系统 } impl VmRuntime for CommandVmRuntime { @@ -46,11 +57,21 @@ impl VmRuntime for CommandVmRuntime { } impl VmBuilder for CommandVm { - fn build(self) -> CommandVmRuntime { - // TODO: 增加启动流程 - todo!() - // CommandVmRuntime { - - // } + fn launch(self) -> CommandVmRuntime { + CommandVmRuntime { + // 启动内部的「进程管理者」 + process: self.io_process.launch(), + // 输入转译器 + input_translator: self + .input_translator + // 默认值:直接调用Cmd的`to_string`方法 | 使用NAVM Cmd语法 + .unwrap_or(Box::new(|cmd| cmd.to_string())), + // 输出转译器 + output_translator: self + .output_translator + // 默认值:直接归入「其它」输出 | 约等于不分类 + .unwrap_or(Box::new(|content| Output::OTHER { content })), + // TODO: 其它 + } } } diff --git a/src/runtime/command_vm/traits.rs b/src/runtime/command_vm/traits.rs deleted file mode 100644 index fea6827..0000000 --- a/src/runtime/command_vm/traits.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! 定义有关「命令行虚拟机」的抽象特征 -//! * ✨核心内容 -//! * ⇄ 基于「进程通信」的消息互转 -//! * 📌核心IO流程: -//! 1. NAVM指令[`Cmd`] >>> 进程输入 >>> 子进程 -//! 2. 子进程 >>> 进程输出 >>> NAVM输出[`Output`] -//! * 🚩实现方式:两处转译器 - -use navm::{cmd::Cmd, vm::Output}; - -/// [`Cmd`]→进程输入 转译器 -pub trait InputTranslator { - fn translate_to_input(cmd: Cmd) -> String; -} - -/// 进程输出→[`Output`]转译器 -pub trait OutputTranslator { - fn translate_from_output(output: String) -> Output; -} From 6f8af05f4851af59f5f6bc0e60df807ac4fee3da Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:53:33 +0800 Subject: [PATCH 07/59] =?UTF-8?q?feat:=20:sparkles:=20=E6=96=B0=E8=BE=85?= =?UTF-8?q?=E5=8A=A9=E7=BB=93=E6=9E=84=E3=80=8C=E6=B5=81=E5=BC=8F=E5=A4=84?= =?UTF-8?q?=E7=90=86=E8=80=85=E5=88=97=E8=A1=A8=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将原先NAVM中的「流式输出处理」提取为独立的、可选的功能 --- Cargo.lock | 2 +- src/lib.rs | 2 + src/tools/flow_handler_list.rs | 209 +++++++++++++++++++++++++++++++++ src/tools/mod.rs | 6 + 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/tools/flow_handler_list.rs create mode 100644 src/tools/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fa1848b..2fd858c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.14.0" [[package]] name = "narsese" -version = "0.6.1" +version = "0.7.2" dependencies = [ "lazy_static", "nar_dev_utils", diff --git a/src/lib.rs b/src/lib.rs index 704c3a9..f5081f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ pub extern crate nar_dev_utils as util; pub mod process_io; // 运行时 pub mod runtime; +// (可选的实用)工具 +pub mod tools; // 可选模块 // util::feature_pub_mod_and_reexport! { diff --git a/src/tools/flow_handler_list.rs b/src/tools/flow_handler_list.rs new file mode 100644 index 0000000..8d7f38b --- /dev/null +++ b/src/tools/flow_handler_list.rs @@ -0,0 +1,209 @@ +//! 模块:流式处理者列表 +//! * 🎯用于流式处理物件,并在这其中灵活控制处理流程 +//! * 📌组合式处理流程:多个处理者在一个处理函数中处理 +//! * 📌截断式消耗过程:处理的物件可能会在中途被处理者消耗 +//! +//! ? 【2024-03-23 14:45:53】是否需要整合进[`nar_dev_utils`]中去 + +use std::marker::PhantomData; + +/// 枚举:处理结果 +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum HandleResult { + /// 物件通过了所有处理者,并最终返回 + Passed(Item), + /// 物件在处理中途被消耗,指示「消耗了物件的处理者」 + Consumed(HandlerIndex), +} + +/// 流式处理者列表 +/// * 🚩处理者的特征约束:`FnMut(Item) -> Option` +/// * 📝不能显式声明「处理者」类型 +/// * ❗若作为泛型参数,则意味着「需要统一所有类型」 +/// * 📌而各个闭包彼此之间类型都是不同的 +pub struct FlowHandlerList { + /// 存储所有的处理者 + /// * 🚩使用[`Box`]以容纳不同类型的闭包 + handlers: Vec Option>>, + + /// 用于对未直接作为字段的`Item`类型的占位符 + /// * 🔗标准库文档: + _marker: PhantomData, +} + +impl FlowHandlerList { + /// 构造函数/从某个[`Box`]迭代器中构造 + /// * ℹ️若需构造一个空列表,可使用[`FlowHandlerList::default`] + /// * 📝【2024-03-23 15:09:58】避免不了装箱:存储的是特征对象,不能不装箱就迭代 + /// * ❌【2024-03-23 15:31:48】停用:对参数`([Box::new(|x| Some(x)),],)`也无法使用 + /// * 🚩已改用快捷构造宏 + pub fn new() -> Self { + Self::from_vec(vec![]) + } + + /// 构造函数/直接从[`Vec`]构造 + /// * 需要自己手动装箱 + /// * ℹ️若需构造一个空列表,可使用[`FlowHandlerList::default`] + pub fn from_vec(vec: Vec Option>>) -> Self { + Self { + handlers: vec, + _marker: PhantomData, + } + } + + // 核心逻辑 // + + /// 【核心】处理 + /// * 🚩主要思路:不断让`Item`值通过各个处理者,直到「全部通过」或「有处理者消耗」 + /// * ⚙️返回值:全部通过后的物件 / 被消耗的处理者索引 + /// * 📝实际上也可不用额外的`let item`,直接使用传入所有权的参数变量 + pub fn handle(&mut self, mut item: Item) -> HandleResult { + // // 预置好物件变量 + // let mut item = item; + // 逐个遍历处理者 + for (index, handler) in self.handlers.iter_mut().enumerate() { + // 调用处理者处理物件,并对返回值做分支 + match handler(item) { + // 有返回值⇒继续 + // ! 这里的返回值有可能已【不是】原来的那个了 + Some(new_item) => item = new_item, + // 没返回值⇒报告处理者所在索引 + None => return HandleResult::Consumed(index), + } + } + // 最终通过 + HandleResult::Passed(item) + } + + // 对「处理者列表」的操作 // + + /// 获取某个位置的处理者(不可变) + pub fn get_handler(&self, index: usize) -> Option<&dyn FnMut(Item) -> Option> { + // 获取指定位置的box,然后将其转为索引 + self.handlers.get(index).map(Box::as_ref) + } + + // ! 【2024-03-23 15:16:08】废稿:可变引用的生命周期类型是【invariant】的 + // * 📝生命周期中`'self : 'handler`不代表`&mut 'self` + // * 🔗参考: + // /// 获取某个位置的处理者(可变) + // /// * ℹ️[`Self::get_handler`]的可变引用版本 + // pub fn get_handler_mut( + // &mut self, + // index: usize, + // ) -> Option<&mut dyn FnMut(Item) -> Option> { + // self.handlers.get_mut(index).map(Box::as_mut) + // } + + /// 添加新的处理者 + /// * ⚠️虽然结构体定义时无需对「处理者」类型约束为`'static`静态周期, + /// * 但此处传入作为参数(的函数指针)是需要的 + pub fn add_handler(&mut self, handler: impl FnMut(Item) -> Option + 'static) { + self.handlers.push(Box::new(handler)) + } +} + +/// 默认构造函数:空数组 +impl Default for FlowHandlerList { + fn default() -> Self { + Self::new() + } +} + +/// 快捷构造宏 +#[macro_export] +macro_rules! flow_handler_list { + [ $($handler:expr),* $(,)? ] => { + // * ❌【2024-03-23 15:34:04】暂时不使用`$crate`:模块路径尚未固定 + FlowHandlerList::from_vec( + vec![$(Box::new($handler)),*] + ) + }; +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use util::*; + use HandleResult::*; + + /// 基础功能测试 + #[test] + fn test_flow_handler_list() { + // * 📝`|x| Some(x)`可以直接使用构造函数调用,写成`Some` + let handler1 = Some; + let handler2 = |x| Some(x + 1); + let handler3 = |x| if x > 1 { Some(x) } else { None }; + + let mut list = FlowHandlerList::new(); + + asserts! { + // 第一个闭包 + list.add_handler(handler1) => (), + list.handle(0) =>Passed(0), + // 第二个闭包 + list.add_handler(handler2) => (), + list.handle(0) => Passed(1), + // 第三个闭包 + list.add_handler(handler3) => (), + list.handle(0) => Consumed(2), // 被消耗,索引在最后一个 + list.handle(1) => Passed(2), // 通过 + } + + let mut list = flow_handler_list![ + Some, + |x: usize| Some(x + 1), + |x| Some(dbg!(x)), + |x: usize| Some(x - 1), + ]; + + asserts! { + list.handle(0) => Passed(0) + } + } + + /// 联动「NAVM输出」测试 + #[test] + fn test_navm_output() { + use narsese::lexical::shortcut::*; + use navm::output::*; + // 构造输出 + let answer = Output::ANSWER { + content_raw: " B>.".into(), + narsese: Some(nse!( B>.)), // * ✨直接使用新版快捷构造宏 + }; + let out = Output::OUT { + content_raw: " C>".into(), + narsese: Some(nse!( C>.)), + }; + // 构造处理者列表 + let mut list = flow_handler_list![ + // 展示 + |out: Output| Some(dbg!(out)), + // 截获回答 + |out| match out { + Output::ANSWER { + content_raw, + narsese, + } => { + println!("截获到回答:{content_raw:?} | {narsese:?}"); + None + } + _ => Some(out), + }, + // 展示 + |out| { + println!("这是其它输出:{out:?}"); + Some(out) + }, + ]; + // 测试处理 + asserts! { + // 回答被截获 + list.handle(answer) => Consumed(1), + // 其它被通过 + list.handle(out.clone()) => Passed(out), + } + } +} diff --git a/src/tools/mod.rs b/src/tools/mod.rs new file mode 100644 index 0000000..0f78be4 --- /dev/null +++ b/src/tools/mod.rs @@ -0,0 +1,6 @@ +//! 存储一些可选的实用工具 +//! * 🎯用于与NAVM运行时结合,形成生产级程序 +//! * 📌或,辅助封装NAVM的「指令-输出 通道」结构 + +// 流式处理者列表 +pub mod flow_handler_list; From 876f8db19c30abb16b7c377abeb18eb3e8d859cb Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 23 Mar 2024 18:11:09 +0800 Subject: [PATCH 08/59] =?UTF-8?q?docs:=20:page=5Ffacing=5Fup:=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0MIT=E3=80=81Apache=E5=8F=8C=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 23 ++++++ 2 files changed, 224 insertions(+) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..96bcd34 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 ARCJ137442 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..468cd79 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file From 1f3399d5a590a5c8c9ee64edd9d87a35f3db4cd0 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:09:25 +0800 Subject: [PATCH 09/59] =?UTF-8?q?feat:=20:sparkles:=20=E8=AE=A9=E3=80=8CIO?= =?UTF-8?q?=E5=AD=90=E8=BF=9B=E7=A8=8B=E3=80=8D=E5=90=8C=E6=97=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E3=80=8C=E4=BE=A6=E5=90=AC=E5=99=A8=E3=80=8D=E4=B8=8E?= =?UTF-8?q?=E3=80=8C=E9=80=9A=E9=81=93=E3=80=8D=E4=B8=A4=E7=A7=8D=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E7=90=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 6 ++ src/process_io/io_process.rs | 166 +++++++++++++++++++++--------- src/runtime/command_vm/runtime.rs | 23 +---- 3 files changed, 128 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12a95c3..67a03cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,18 +12,24 @@ edition = "2021" # version = "0.1.0" # ! 本地依赖可以不添加版本 # *🚩【2024-03-21 09:26:38】启用所有 path = "../NAR-dev-util" +# git = "https://github.com/ARCJ137442/NAR-dev-util" +# ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [] [dependencies.narsese] # ! 本地依赖可以不添加版本 # 载入Narsese API,引入其中所有部分 path = "../Narsese.rs" +# git = "https://github.com/ARCJ137442/Narsese.rs" +# ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = ["bundled"] [dependencies.navm] # ! 本地依赖可以不添加版本 # 载入NAVM API,引入「非公理虚拟机」模型 path = "../NAVM.rs" +# git = "https://github.com/ARCJ137442/NAVM.rs" +# ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 # 定义库的特性 diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 75da147..0687f48 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -3,10 +3,10 @@ use std::{ ffi::OsStr, io::{BufRead, BufReader, Result as IoResult, Write}, - process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, + process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio, Termination}, sync::{ mpsc::{channel, Receiver, Sender}, - Mutex, + Arc, Mutex, }, thread::{self, JoinHandle}, }; @@ -97,6 +97,9 @@ impl IoProcess { /// * 🎯负责 /// * 统一管理子进程 /// * 封装提供易用的(字符串)输入输出接口 +/// * 🚩现在兼容「输出侦听」与「输出通道」两处 +/// * 🎯「输出侦听」用于「需要**响应式**即时处理输出,但又不想阻塞主进程/开新进程」时 +/// * 🎯「输出通道」用于「需要封装『并发异步获取』延迟处理输出,兼容已有异步并发模型」时 #[allow(dead_code)] pub struct IoProcessManager { /// 正在管理的子进程 @@ -104,14 +107,18 @@ pub struct IoProcessManager { /// 子进程的「写(到子进程的)输入」守护线程 thread_write_in: JoinHandle<()>, - /// 子进程的「读(到子进程的)输出」守护线 - /// * 📌【2024-03-22 09:57:39】现在使用「输出侦听器」模式,可能没有 - thread_read_out: Option>, - - // /// 子进程输出的「接收者」 - // /// * 🚩子进程发送给外部侦听器,由外部接收 - // child_out: Mutex>, - // ! 【2024-03-22 09:54:22】↑现在使用「输出侦听器」模式,不再需要此字段 + /// 子进程的「读(到子进程的)输出」守护线程 + /// * 🚩现在兼容「侦听器」「通道」两种模式,重新必要化 + // thread_read_out: Option>, + thread_read_out: JoinHandle<()>, + + /// 子线程的终止信号 + termination_signal: Arc>, + + /// 子进程输出的「接收者」 + /// * 🚩子进程发送给外部侦听器,由外部接收 + child_out: Mutex>, + // ! 【2024-03-23 19:31:56】现在兼容「输出侦听」与「输出通道」二者 /// 子进程输入的「发送者」 /// * 🚩子进程接收来自外部发送的消息,由外部发送 child_in: Mutex>, @@ -132,17 +139,25 @@ impl IoProcessManager { // 创建通道 // * 📌IO流向:从左到右 // ! 🚩【2024-03-22 09:53:12】现在采用「输出侦听器」的方法,不再需要封装通道 - // let (child_out, out_sender) = channel(); + let (child_out, out_sender) = channel(); let (in_receiver, child_in) = channel(); + let termination_signal = Arc::new(Mutex::new(false)); // 生成进程的「读写守护」(线程) - let thread_write_in = IoProcessManager::spawn_thread_write_in(stdin, child_in); - // let thread_read_out = IoProcessManager::spawn_thread_read_out(stdout, child_out); - let thread_read_out = - out_listener.map(|listener| IoProcessManager::spawn_thread_read_out(stdout, listener)); + let thread_write_in = + IoProcessManager::spawn_thread_write_in(stdin, child_in, termination_signal.clone()); + let thread_read_out = IoProcessManager::spawn_thread_read_out( + stdout, + child_out, + out_listener, + termination_signal.clone(), + ); + // let thread_read_out = + // out_listener.map(|listener| IoProcessManager::spawn_thread_read_out(stdout, listener)); + // ! 🚩【2024-03-23 19:33:45】↑现在兼容「侦听器」「通道」二者 // 捕获通道的两端 - // let child_out_sender = Mutex::new(out_sender); + let child_out_sender = Mutex::new(out_sender); let child_in_receiver = Mutex::new(in_receiver); // 构造并返回自身 @@ -150,70 +165,99 @@ impl IoProcessManager { process: child, thread_read_out, thread_write_in, - // child_out: child_out_sender, + child_out: child_out_sender, child_in: child_in_receiver, // out_listener, // ! 【2024-03-22 09:53:50】↑不再于自身存储「输出侦听器」,而是存储在`thread_read_out`中 + termination_signal, } } /// 生成一个子线程,管理子进程的标准输入,接收通道另一端输出 /// * 📌读输入,写进程 | stdin >>> child_in_receiver + #[inline] fn spawn_thread_write_in( stdin: ChildStdin, child_in_receiver: Receiver, + termination_signal: Arc>, ) -> thread::JoinHandle<()> { thread::spawn(move || { // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 let mut stdin = stdin; + // ! 注意:这个`for`循环是阻塞的 for line in child_in_receiver { // 写入输出 if let Err(e) = stdin.write_all(line.as_bytes()) { println!("无法向子进程输入:{e:?}"); } + // 检查终止信号 + if *termination_signal.lock().expect("无法锁定终止信号") { + // println!("子进程收到终止信号"); + break; + } } }) } /// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 - /// // * 📌写输出 | child_out_sender >>> stdout - /// * 🚩【2024-03-22 09:58:54】现在采用「输出侦听器」模式,不再需要通道 + /// * 📌写输出 | child_out_sender >>> stdout + /// * 🚩【2024-03-23 20:46:38】现在「侦听器」与「通道」并行运作 + #[inline] fn spawn_thread_read_out( stdout: ChildStdout, - // child_out_sender: Sender, - mut listener: Box, + child_out_sender: Sender, + out_listener: Option>, + termination_signal: Arc>, ) -> thread::JoinHandle<()> { + // 将Option包装成一个新的函数 + // ! ⚠️【2024-03-23 19:54:43】↓类型注释是必须的:要约束闭包类型一致 + let mut listener_code: Box = match out_listener { + // * 🚩先前有⇒实际执行 | 仅在实际有值时拷贝并传送给侦听器 + Some(mut listener) => Box::new(move |s: &String| listener(s.clone())), + // * 🚩先前无⇒空函数 + None => Box::new(move |_| {}), + }; + // 启动线程 thread::spawn(move || { // 读取输出 let mut stdout_reader = BufReader::new(stdout); + let mut buf = String::new(); // 持续循环 loop { // 从子进程「标准输出」读取输入 - let mut buf = String::new(); match stdout_reader.read_line(&mut buf) { - // 没有任何输入⇒跳过 - Ok(0) => continue, + // 没有任何输入⇒检查终止信号 + // * 📌不能在这里中断,需要检查终止信号 + Ok(0) => { + if dbg!(*termination_signal.lock().expect("无法锁定终止信号")) { + // println!("子进程收到终止信号"); + break; + } + } // 有效输入 Ok(_) => { - // ! 🚩【2024-03-22 10:00:51】↓使用「输出侦听器」,不再需要 - // // 向「进程消息接收者」传递消息(实际上是「输出」) - // if let Err(e) = child_out_sender.send(buf) { - // println!("无法接收子进程输出:{e:?}"); - // break; - // } - listener(buf.clone()); - continue; + // ! 🚩现在兼容「侦听器」「通道」二者 + // 先侦听 | 只传递引用,仅在「实际有侦听器」时拷贝消息 + listener_code(&buf); + // 向「进程消息接收者」传递消息(实际上是「输出」) + if let Err(e) = child_out_sender.send(buf.clone()) { + println!("无法接收子进程输出:{e:?}"); + break; + } } + // 报错⇒显示错误,终止读取 Err(e) => { println!("子进程报错: {:?}", e); break; } } + buf.clear(); } }) } // * 正常运作 * // + /// 向子进程写入数据 /// * 🚩通过使用自身「子进程输入」的互斥锁,从中输入数据 /// * ⚠️返回空,或返回字符串形式的错误 @@ -233,10 +277,27 @@ impl IoProcessManager { self.process.wait() } - /// 强制结束子进程 + /// 杀死自身 + /// * 🚩设置终止信号,通知子线程终止 /// * 🚩调用[`Child::kill`]方法 - pub fn kill(&mut self) -> IoResult<()> { - self.process.kill() + /// * ⚠️将终止自身 + pub fn kill(mut self) -> IoResult<()> { + // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑 + // 终止信号 + { + *self.termination_signal.lock().unwrap() = true; + dbg!(*self.termination_signal.lock().unwrap()); + } + // 杀死子进程 + let kill_result = self.process.kill(); + dbg!(self.put("\n").unwrap()); // ! 解除子线程的阻塞 + + // 等待子线程终止 + dbg!(self.thread_write_in.join().unwrap()); + dbg!(self.thread_read_out.join().unwrap()); + // 返回 + kill_result + // self.process.kill() } /// 获取子进程id @@ -301,7 +362,7 @@ mod tests { let outputs = Arc::new(Mutex::new(vec![])); let outputs_inner = outputs.clone(); // 从一个系统指令开始构建并启动子进程 - let mut process = IoProcess::new(EXE_PATH_ONA) + let process = IoProcess::new(EXE_PATH_ONA) // 添加命令参数 .arg("shell") // 添加输出监听器 | 简单回显 @@ -313,7 +374,7 @@ mod tests { .lock() .expect("无法锁定 outputs_inner") .push(output.clone()); - println!("[OUT] {}", output); + print!("[OUT] {}", output); }) // 启动子进程 .launch(); @@ -321,20 +382,24 @@ mod tests { // 测试:输入输出 // let output_must_contains = |s: &str| { let outputs = outputs.lock().expect("无法锁定 outputs"); - assert!(outputs.iter().any(|x| x.contains(s))) + let line = outputs + .iter() + .find(|line| line.contains(s)) + .expect("没有指定的输出!"); + println!("检验「{s:?}」成功!所在之处:{line:?}"); }; // 先置入输入 sleep_secs(1); - dbg!(process.put(" B>.\n").expect("无法放置输入")); + process.put(" B>.\n").expect("无法放置输入"); sleep_secs(1); // 中途检验 output_must_contains(" B>."); // 继续输入 - dbg!(process.put(" C>.\n").expect("无法放置输入")); + process.put(" C>.\n").expect("无法放置输入"); sleep_secs(1); - dbg!(process.put(" C>?\n").expect("无法放置输入")); + process.put(" C>?\n").expect("无法放置输入"); sleep_secs(1); // 最后检验 @@ -343,14 +408,21 @@ mod tests { // // 等待结束 // process.wait(); - // 等待五秒并强制结束 - println!("Waiting for 5 seconds and then killing the process..."); - sleep_secs(5); - dbg!(process.kill().expect("无法杀死进程")); + // 读取其中缓冲区里边的数据(多了会阻塞!) + let r = process.child_out.lock().unwrap(); + for _ in 0..outputs.lock().unwrap().len() { + let line = r.recv().expect("接收失败!"); + print!("从输出中读取到的一行(多了会阻塞!):{line}"); + } + + // 等待2秒并强制结束 + println!("Waiting for 2 seconds and then killing the process..."); + sleep_secs(2); + // process.kill().expect("无法杀死进程"); println!("Process killed."); - // 读取检验输出 - dbg!(&outputs); + // 读取检验输出 | 杀死进程后还有 + dbg!(&*outputs); // 退出 exit(0); diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 0ccf565..71c6288 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -34,26 +34,9 @@ impl VmRuntime for CommandVmRuntime { todo!() } - fn store_output(&mut self, output: Output) { - todo!() - } - fn fetch_output(&mut self) -> Option { todo!() } - - fn add_output_listener(&mut self, listener: Listener) - where - Listener: FnMut(Output) -> Option, - { - todo!() - } - - fn iter_output_listeners<'a>( - &'a self, - ) -> Box Option> + 'a> { - todo!() - } } impl VmBuilder for CommandVm { @@ -63,9 +46,9 @@ impl VmBuilder for CommandVm { process: self.io_process.launch(), // 输入转译器 input_translator: self - .input_translator - // 默认值:直接调用Cmd的`to_string`方法 | 使用NAVM Cmd语法 - .unwrap_or(Box::new(|cmd| cmd.to_string())), + .input_translator + // 默认值:直接调用Cmd的`to_string`方法 | 使用NAVM Cmd语法 + .unwrap_or(Box::new(|cmd| cmd.to_string())), // 输出转译器 output_translator: self .output_translator From a6f5f148763274034c3b020fc35a6478d9f40524 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:51:44 +0800 Subject: [PATCH 10/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E3=80=8CIoProcess=E3=80=8D=EF=BC=8C=E5=A2=9E=E5=8A=A0=E3=80=8C?= =?UTF-8?q?=E6=8B=89=E5=8F=96=E8=BE=93=E5=87=BA=E3=80=8D=E3=80=8C=E7=BD=AE?= =?UTF-8?q?=E5=85=A5=E4=B8=80=E8=A1=8C=E8=BE=93=E5=85=A5=E3=80=8D=E7=AD=89?= =?UTF-8?q?=E5=AE=9E=E7=94=A8=E5=8A=9F=E8=83=BD=EF=BC=9B=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E3=80=81=E6=95=B4=E7=90=86=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E3=80=81=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=B8=8E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- src/process_io/io_process.rs | 301 ++++++++++++++++++++++++----------- 2 files changed, 209 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fd858c..306b60f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "nar_dev_utils" -version = "0.14.0" +version = "0.15.0" [[package]] name = "narsese" diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 0687f48..4481b40 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -3,7 +3,7 @@ use std::{ ffi::OsStr, io::{BufRead, BufReader, Result as IoResult, Write}, - process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio, Termination}, + process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, sync::{ mpsc::{channel, Receiver, Sender}, Arc, Mutex, @@ -15,6 +15,12 @@ use util::*; /// 统一定义「输出侦听器」的类型 type OutputListener = dyn FnMut(String) + Send + Sync; +/// 简化定义`Arc< Mutex>` +type ArcMutex = Arc>; + +/// 简化定义`Result` +type ResultS = Result; + /// 构建一个「IO进程」 /// * 📌只是作为一个「构建器」存在 /// * 作为真正的`IoProcessManager`的launcher/builder @@ -113,10 +119,12 @@ pub struct IoProcessManager { thread_read_out: JoinHandle<()>, /// 子线程的终止信号 - termination_signal: Arc>, + termination_signal: ArcMutex, /// 子进程输出的「接收者」 - /// * 🚩子进程发送给外部侦听器,由外部接收 + /// * 🚩子进程发送给外部侦听器,同时由外部接收 + /// * 在将输出发送给侦听器时,会在此留下备份 + /// * ⚠️如果直接调用[`Receiver::recv`]方法,可能会导致线程阻塞 child_out: Mutex>, // ! 【2024-03-23 19:31:56】现在兼容「输出侦听」与「输出通道」二者 /// 子进程输入的「发送者」 @@ -125,10 +133,16 @@ pub struct IoProcessManager { // /// 子进程的「输出监听器」 // out_listener: Option>, // ! 【2024-03-22 09:54:22】↑现在使用「输出侦听器」模式,此字段数据存储在`thread_read_out`中 + // /// 输出计数 + // /// * 🎯用于追踪输出数量,以便在不阻塞[`Self::child_out`] + // num_output: ArcMutex, + // ! ✅【2024-03-24 01:20:11】↑现在因[`Receiver::try_recv`],无需使用此计数 + // * 📌【2024-03-24 01:20:38】并且,这个计数在测试中还偶发有不稳定行为(可能遗漏计数) } impl IoProcessManager { // * 初始化 * // + /// 构造方法 /// * 🚩从「子进程」与「输出侦听器」构造「进程管理者」 pub fn new(mut child: Child, out_listener: Option>) -> Self { @@ -142,7 +156,12 @@ impl IoProcessManager { let (child_out, out_sender) = channel(); let (in_receiver, child_in) = channel(); + // 生成「终止信号」共享数据 let termination_signal = Arc::new(Mutex::new(false)); + + // // 生成「输出计数」共享数据 + // let num_output = Arc::new(Mutex::new(0)); + // 生成进程的「读写守护」(线程) let thread_write_in = IoProcessManager::spawn_thread_write_in(stdin, child_in, termination_signal.clone()); @@ -151,25 +170,26 @@ impl IoProcessManager { child_out, out_listener, termination_signal.clone(), + // num_output.clone(), ); // let thread_read_out = // out_listener.map(|listener| IoProcessManager::spawn_thread_read_out(stdout, listener)); // ! 🚩【2024-03-23 19:33:45】↑现在兼容「侦听器」「通道」二者 - // 捕获通道的两端 - let child_out_sender = Mutex::new(out_sender); - let child_in_receiver = Mutex::new(in_receiver); - // 构造并返回自身 Self { process: child, thread_read_out, thread_write_in, - child_out: child_out_sender, - child_in: child_in_receiver, + // 捕获通道的两端 + child_out: Mutex::new(out_sender), + child_in: Mutex::new(in_receiver), // out_listener, // ! 【2024-03-22 09:53:50】↑不再于自身存储「输出侦听器」,而是存储在`thread_read_out`中 + // 共享变量 termination_signal, + // num_output, + // ! 【2024-03-24 01:24:58】↑不再使用「输出计数」:有时会遗漏输出,并且有`try_recv`的更可靠方案 } } @@ -179,22 +199,23 @@ impl IoProcessManager { fn spawn_thread_write_in( stdin: ChildStdin, child_in_receiver: Receiver, - termination_signal: Arc>, + termination_signal: ArcMutex, ) -> thread::JoinHandle<()> { + // 生成一个简单的子线程,从通道中(阻塞性)读取数据,并随时将此计入标准输入 thread::spawn(move || { // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 let mut stdin = stdin; // ! 注意:这个`for`循环是阻塞的 for line in child_in_receiver { - // 写入输出 - if let Err(e) = stdin.write_all(line.as_bytes()) { - println!("无法向子进程输入:{e:?}"); - } - // 检查终止信号 + // 检查终止信号 | ⚠️不要在终止后还发消息 if *termination_signal.lock().expect("无法锁定终止信号") { // println!("子进程收到终止信号"); break; } + // 写入输出 + if let Err(e) = stdin.write_all(line.as_bytes()) { + println!("无法向子进程输入:{e:?}"); + } } }) } @@ -202,12 +223,17 @@ impl IoProcessManager { /// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 /// * 📌写输出 | child_out_sender >>> stdout /// * 🚩【2024-03-23 20:46:38】现在「侦听器」与「通道」并行运作 + /// * 📌核心逻辑 + /// * 通过「缓冲区读取器」[`BufReader`]读取子进程输出 + /// * 不断尝试读取,直到有内容 + /// * 朝通道[`Sender`]发送内容 #[inline] fn spawn_thread_read_out( stdout: ChildStdout, child_out_sender: Sender, out_listener: Option>, - termination_signal: Arc>, + termination_signal: ArcMutex, + // num_output: ArcMutex, ) -> thread::JoinHandle<()> { // 将Option包装成一个新的函数 // ! ⚠️【2024-03-23 19:54:43】↓类型注释是必须的:要约束闭包类型一致 @@ -219,15 +245,22 @@ impl IoProcessManager { }; // 启动线程 thread::spawn(move || { - // 读取输出 + // 创建缓冲区读取器 | ⚠️【2024-03-23 23:42:08】这里的`BufReader`不能简化 + // * 📝`ChildStdout`没有`read_line`功能,但可以通过`BufReader`封装 let mut stdout_reader = BufReader::new(stdout); + + // 创建缓冲区 | 🎯可持续使用 let mut buf = String::new(); + // 持续循环 loop { // 从子进程「标准输出」读取输入 + // * 📌此处非阻塞(会读到空),且`buf`会有换行符 match stdout_reader.read_line(&mut buf) { // 没有任何输入⇒检查终止信号 // * 📌不能在这里中断,需要检查终止信号 + // * 🚩【2024-03-24 01:48:19】目前**允许**在进程终止时获取其输出 + // * 一般侦听器都能侦听到 Ok(0) => { if dbg!(*termination_signal.lock().expect("无法锁定终止信号")) { // println!("子进程收到终止信号"); @@ -244,6 +277,12 @@ impl IoProcessManager { println!("无法接收子进程输出:{e:?}"); break; } + // // 输出计数 + // match num_output.lock() { + // Ok(mut n) => *n += 1, + // Err(e) => println!("无法对子进程输出计数:{e:?}"), + // } + // ! 【2024-03-24 01:42:46】现在取消「输出计数」机制:计数可能不准确,并且被`try_recv`取代 } // 报错⇒显示错误,终止读取 Err(e) => { @@ -258,16 +297,73 @@ impl IoProcessManager { // * 正常运作 * // - /// 向子进程写入数据 + /// 获取子进程id + /// * 🚩调用[`Child::id`]方法 + pub fn id(&self) -> u32 { + self.process.id() + } + + /// (从「输出通道」中)拉取一个输出 + /// * 🎯用于(阻塞式等待)从子进程中收取输出信息 + /// * 🚩以字符串形式报告错误 + /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 + pub fn fetch_output(&mut self) -> ResultS { + // 访问自身「子进程输出」字段 + self.child_out + // 互斥锁锁定 + .lock() + .transform_err_string()? + // 通道接收者接收 + .recv() + .transform_err_string() + } + + /// 尝试(从「输出通道」中)拉取一个输出 + /// * 🎯保证不会发生「线程阻塞」 + /// * 🚩类似[`Self::fetch_output`],但仅在「有输出」时拉取 + /// * 📝[`Receiver`]自带的[`Receiver::try_recv`]就做了这件事 + /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 + pub fn try_fetch_output(&mut self) -> ResultS> { + // 访问自身「子进程输出」字段,但加上`try` + let out = self + .child_out + // 互斥锁锁定 + .lock() + .transform_err_string()? + // 通道接收者接收 + .try_recv() + .ok(); + // ! ↑此处使用`ok`是为了区分「锁定错误」与「通道无输出」 + // 返回 + Ok(out) + } + + /// 向子进程写入数据(字符串) /// * 🚩通过使用自身「子进程输入」的互斥锁,从中输入数据 - /// * ⚠️返回空,或返回字符串形式的错误 - pub fn put(&self, input: impl ToString) -> Result<(), String> { + /// * ⚙️返回空,或返回字符串形式的错误(互斥锁错误) + /// * ⚠️此方法需要【自行尾缀换行符】,否则不被视作有效输入 + /// * 📄要触发输入,需传入" B>.\n"而非" B>." + pub fn put(&self, input_line: impl ToString) -> ResultS<()> { // 从互斥锁中获取输入 // * 🚩等待直到锁定互斥锁,最终在作用域结束(MutexGuard析构)时释放(解锁) - let child_in_guard = self.child_in.lock().transform_err(|err| err.to_string())?; - child_in_guard - .send(input.to_string()) - .transform_err(|err| err.to_string()) + // ! ❌【2024-03-23 23:59:20】此处的闭包无法简化成函数指针 + self.child_in + // 锁定以获取`Sender` + .lock() + .transform_err_string()? + // 发送 + .send(input_line.to_string()) + .transform_err_string() + } + + /// 向子进程写入**一行**数据(字符串) + /// * 🚩功能同[`Self::put`],但会自动加上换行符 + /// * 📌类似[`print`]和[`println`]的关系 + /// * ⚠️此方法在输入后【自动添加换行符】 + /// * 📄传入" B>."将自动转换成" B>.\n" + /// * ✅以此总是触发输入 + pub fn put_line(&self, input: impl ToString) -> ResultS<()> { + self.put(format!("{}\n", input.to_string())) } /// 等待子进程结束 @@ -278,32 +374,32 @@ impl IoProcessManager { } /// 杀死自身 - /// * 🚩设置终止信号,通知子线程终止 - /// * 🚩调用[`Child::kill`]方法 - /// * ⚠️将终止自身 - pub fn kill(mut self) -> IoResult<()> { - // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑 - // 终止信号 - { - *self.termination_signal.lock().unwrap() = true; - dbg!(*self.termination_signal.lock().unwrap()); - } - // 杀死子进程 - let kill_result = self.process.kill(); - dbg!(self.put("\n").unwrap()); // ! 解除子线程的阻塞 - - // 等待子线程终止 - dbg!(self.thread_write_in.join().unwrap()); - dbg!(self.thread_read_out.join().unwrap()); - // 返回 - kill_result - // self.process.kill() - } - - /// 获取子进程id - /// * 🚩调用[`Child::id`]方法 - pub fn id(&self) -> u32 { - self.process.id() + /// * 🚩设置终止信号,通知子线程(以及标准IO)终止 + /// * 🚩调用[`Child::kill`]方法,终止子进程 + /// * ⚠️将借走自身所有权,终止并销毁自身 + pub fn kill(mut self) -> ResultS<()> { + // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑:无法脱开对`self`的借用 + // ! 📌更具体而言:对其中两个线程`thread_write_in`、`thread_read_out`的部分借用 + // 向子线程发送终止信号 // + let mut signal = self.termination_signal.lock().transform_err_string()?; + *signal = true; + drop(signal); // ! 手动释放锁 + // * 📝【2024-03-24 00:15:10】必须手动释放锁,否则会导致后续线程死锁 + + // ! 解除子线程「write_stdin」的阻塞 + self.put("\n").unwrap(); + + // 等待子线程终止 // + self.thread_write_in.join().transform_err_debug()?; + self.thread_read_out.join().transform_err_debug()?; + + // * 📝此时子线程连同「子进程的标准输入输出」一同关闭, + // * 子进程自身可以做输出 + // * 📄如:ONA在最后会打印程序运行报告 + // * ⚠️这意味着「输出侦听器」仍然能对其输出产生响应 + + // 杀死子进程 // + self.process.kill().transform_err_string() } } @@ -315,15 +411,8 @@ mod tests { use std::{ process::exit, sync::{Arc, Mutex}, - thread::sleep, - time::Duration, }; - /// 测试/睡眠指定时间 - fn sleep_secs(secs: u64) { - sleep(Duration::from_secs(secs)); - } - // 定义一系列路径 #[allow(unused)] const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; @@ -332,6 +421,30 @@ mod tests { #[allow(unused)] const EXE_PATH_ECHO: &str = r"..\NAVM.rs\target\debug\examples\echo_exe.exe"; + /// 实用测试工具:启动一个ONA,并附带「输出缓存」 + fn launch_ona() -> (IoProcessManager, ArcMutex>) { + // 输出缓存 + let outputs = Arc::new(Mutex::new(vec![])); + let outputs_inner = outputs.clone(); + // 从一个系统指令开始构建子进程 + let process = IoProcess::new(EXE_PATH_ONA) + // 添加命令参数 + .arg("shell") + // 添加输出监听器 | 简单回显 + // ! 【2024-03-22 10:06:38】基于「输出侦听器」的情形,若需要与外部交互,则会遇到所有权/生命周期问题 + // * 📄子进程与子进程外部(如此处的主进程)的问题 + // * ✅【2024-03-22 10:16:32】↑已使用`Arc`解决 + .out_listener(move |output: String| { + outputs_inner + .lock() + .expect("无法锁定 outputs_inner") + .push(output.clone()); + print!("[OUT] {}", output); + }); + // 启动子进程并返回 + (process.launch(), outputs) + } + /// 标准案例:ONA交互 /// /// ## 测试输入 @@ -353,31 +466,13 @@ mod tests { /// * 📝[`Arc`]能满足[`Sync`]+[`Send`],但R[`efCell`]不满足 /// * ❌无法使用`Arc>`组合 /// * 📝[`Mutex`]能进行进程交互,但无法共享引用 - /// * 🚩最终使用`Arc>`作为进程交互的共享引用 + /// * 🚩最终使用`ArcMutex`作为进程交互的共享引用 /// * 📌[`Arc`]允许被拷贝并移动入闭包(共享引用,超越生命周期) /// * 📌[`Mutex`]允许进程间共享的内部可变性(运行时借用检查) #[test] fn test_ona() { - // 接收输出 - let outputs = Arc::new(Mutex::new(vec![])); - let outputs_inner = outputs.clone(); - // 从一个系统指令开始构建并启动子进程 - let process = IoProcess::new(EXE_PATH_ONA) - // 添加命令参数 - .arg("shell") - // 添加输出监听器 | 简单回显 - // ! 【2024-03-22 10:06:38】基于「输出侦听器」的情形,若需要与外部交互,则会遇到所有权/生命周期问题 - // * 📄子进程与子进程外部(如此处的主进程)的问题 - // * ✅【2024-03-22 10:16:32】↑已使用`Arc`解决 - .out_listener(move |output: String| { - outputs_inner - .lock() - .expect("无法锁定 outputs_inner") - .push(output.clone()); - print!("[OUT] {}", output); - }) - // 启动子进程 - .launch(); + // 创建子进程 + let (mut process, outputs) = launch_ona(); // 测试:输入输出 // let output_must_contains = |s: &str| { @@ -388,37 +483,57 @@ mod tests { .expect("没有指定的输出!"); println!("检验「{s:?}」成功!所在之处:{line:?}"); }; + /// 等待子进程输出,直到输出满足条件 + fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { + loop { + let o = dbg!(process.fetch_output().expect("无法拉取输出")); + println!("fetch到其中一个输入: {o:?}"); + if criterion(o) { + break; + } + } + } + // 先置入输入 - sleep_secs(1); - process.put(" B>.\n").expect("无法放置输入"); - sleep_secs(1); + let input = " B>."; + process.put_line(input).expect("无法放置输入"); + await_fetch_until(&mut process, |s| s.contains(input)); // 中途检验 output_must_contains(" B>."); // 继续输入 process.put(" C>.\n").expect("无法放置输入"); - sleep_secs(1); + await_fetch_until(&mut process, |s| s.contains(" C>.")); + process.put(" C>?\n").expect("无法放置输入"); - sleep_secs(1); + await_fetch_until(&mut process, |s| s.contains(" C>?")); + + // 不断fetch直到有答案 + const EXPECTED_ANSWER: &str = "Answer: C>."; + await_fetch_until(&mut process, |s| s.contains(EXPECTED_ANSWER)); - // 最后检验 - output_must_contains("Answer: C>."); + // 最后检验 | 因为是缓存,所以会成功 + output_must_contains(EXPECTED_ANSWER); // // 等待结束 // process.wait(); // 读取其中缓冲区里边的数据(多了会阻塞!) - let r = process.child_out.lock().unwrap(); - for _ in 0..outputs.lock().unwrap().len() { - let line = r.recv().expect("接收失败!"); - print!("从输出中读取到的一行(多了会阻塞!):{line}"); + { + let r = process.child_out.lock().unwrap(); + for _ in r.try_iter() { + let line = r.recv().expect("接收失败!"); + print!("从输出中读取到的一行(多了会阻塞!):{line}"); + } + // * 此处自动释放锁 } - // 等待2秒并强制结束 - println!("Waiting for 2 seconds and then killing the process..."); - sleep_secs(2); - // process.kill().expect("无法杀死进程"); + // // 等待1秒并强制结束 + // println!("Waiting for 1 seconds and then killing the process..."); + // sleep_secs(1); + // ! 【2024-03-24 01:39:45】现在由于`await_until_output`的存在,已无需手动等待 + process.kill().expect("无法杀死进程"); println!("Process killed."); // 读取检验输出 | 杀死进程后还有 From 0bf912c16af9026af4860ddc4256938445bf093b Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 24 Mar 2024 04:00:02 +0800 Subject: [PATCH 11/59] =?UTF-8?q?feat:=20:sparkles:=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81=E8=87=B3utils=E5=BA=93?= =?UTF-8?q?=EF=BC=9B=E5=9F=BA=E4=BA=8ENAVM=E6=9B=B4=E6=96=B0=E3=80=8C?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=A1=8C=E8=99=9A=E6=8B=9F=E6=9C=BA=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E3=80=8D=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E5=9C=A8PyNARS=E4=B8=8A=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E8=B7=91=E9=80=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 8 +-- Cargo.toml | 2 +- src/process_io/io_process.rs | 31 ++++------ src/runtime/command_vm/api.rs | 6 +- src/runtime/command_vm/builder.rs | 20 +++++++ src/runtime/command_vm/runtime.rs | 94 +++++++++++++++++++++++++++---- src/tools/flow_handler_list.rs | 2 +- 7 files changed, 126 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 306b60f..a162291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "babel_nar" -version = "0.1.0" +version = "0.2.0" dependencies = [ "nar_dev_utils", "narsese", @@ -19,11 +19,11 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "nar_dev_utils" -version = "0.15.0" +version = "0.16.0" [[package]] name = "narsese" -version = "0.7.2" +version = "0.9.0" dependencies = [ "lazy_static", "nar_dev_utils", @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.1.0" +version = "0.1.1" dependencies = [ "nar_dev_utils", "narsese", diff --git a/Cargo.toml b/Cargo.toml index 67a03cf..ee83940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.1.0" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 4481b40..611f8d7 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -18,9 +18,6 @@ type OutputListener = dyn FnMut(String) + Send + Sync; /// 简化定义`Arc< Mutex>` type ArcMutex = Arc>; -/// 简化定义`Result` -type ResultS = Result; - /// 构建一个「IO进程」 /// * 📌只是作为一个「构建器」存在 /// * 作为真正的`IoProcessManager`的launcher/builder @@ -405,7 +402,7 @@ impl IoProcessManager { /// 单元测试 #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use std::{ @@ -414,12 +411,18 @@ mod tests { }; // 定义一系列路径 - #[allow(unused)] const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - #[allow(unused)] - const EXE_PATH_REPL: &str = r"..\..\..\Julia\语言学小工Ju\繁简转换\dist\repl_简化.exe"; - #[allow(unused)] - const EXE_PATH_ECHO: &str = r"..\NAVM.rs\target\debug\examples\echo_exe.exe"; + + /// 测试工具/等待子进程输出,直到输出满足条件 + pub fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { + loop { + let o = dbg!(process.fetch_output().expect("无法拉取输出")); + println!("fetch到其中一个输入: {o:?}"); + if criterion(o) { + break; + } + } + } /// 实用测试工具:启动一个ONA,并附带「输出缓存」 fn launch_ona() -> (IoProcessManager, ArcMutex>) { @@ -483,16 +486,6 @@ mod tests { .expect("没有指定的输出!"); println!("检验「{s:?}」成功!所在之处:{line:?}"); }; - /// 等待子进程输出,直到输出满足条件 - fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { - loop { - let o = dbg!(process.fetch_output().expect("无法拉取输出")); - println!("fetch到其中一个输入: {o:?}"); - if criterion(o) { - break; - } - } - } // 先置入输入 let input = " B>."; diff --git a/src/runtime/command_vm/api.rs b/src/runtime/command_vm/api.rs index 9b7ecb5..b9da8d1 100644 --- a/src/runtime/command_vm/api.rs +++ b/src/runtime/command_vm/api.rs @@ -7,11 +7,13 @@ use navm::{cmd::Cmd, output::Output}; /// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 /// * 📌要求线程稳定 /// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type InputTranslator = dyn Fn(Cmd) -> String + Send + Sync; +/// TODO: 在后续的「NSE指令输入」时,需要通过「自动将『空预算任务』作为语句输入」应对「`$$ A.`→`A.`」的情况 +/// * ⚠️转译有可能失败:此时返回并上报错误信息 +pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; /// 进程输出→[`Output`]转译器 /// * 🚩现在不再使用特征,以便在`Option>`中推断类型 /// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 /// * 📌要求线程稳定 /// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type OutputTranslator = dyn Fn(String) -> Output + Send + Sync; +pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; diff --git a/src/runtime/command_vm/builder.rs b/src/runtime/command_vm/builder.rs index 5d63d02..cdb4235 100644 --- a/src/runtime/command_vm/builder.rs +++ b/src/runtime/command_vm/builder.rs @@ -2,6 +2,7 @@ use super::{InputTranslator, OutputTranslator}; use crate::process_io::IoProcess; +use navm::{cmd::Cmd, output::Output}; use std::ffi::OsStr; /// 命令行虚拟机(构建者) @@ -30,4 +31,23 @@ impl CommandVm { output_translator: None, } } + + /// 配置/输入转换器 + /// * 💭何时Rust能给特征起别名。。 + pub fn input_translator( + mut self, + translator: impl Fn(Cmd) -> Result + Send + Sync + 'static, + ) -> Self { + self.input_translator = Some(Box::new(translator)); + self + } + + /// 配置/输出转换器 + pub fn output_translator( + mut self, + translator: impl Fn(String) -> Result + Send + Sync + 'static, + ) -> Self { + self.output_translator = Some(Box::new(translator)); + self + } } diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 71c6288..864632f 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -11,8 +11,9 @@ use crate::process_io::IoProcessManager; use navm::{ cmd::Cmd, output::Output, - vm::{VmBuilder, VmRuntime}, + vm::{VmLauncher, VmRuntime}, }; +use util::ResultS; /// 命令行虚拟机运行时 /// * 🎯封装「进程通信」逻辑 @@ -25,21 +26,37 @@ pub struct CommandVmRuntime { input_translator: Box, /// 进程输出→[`Output`]转译器 + /// * 🚩【2024-03-24 02:06:27】至于「输出侦听」等后续处理,外置给其它专用「处理者」 output_translator: Box, - // TODO: 输出侦听系统 } impl VmRuntime for CommandVmRuntime { - fn input_cmd(&mut self, cmd: Cmd) { - todo!() + fn input_cmd(&mut self, cmd: Cmd) -> ResultS<()> { + // 尝试转译 + let input = (self.input_translator)(cmd)?; + // 置入转译结果 + self.process.put_line(input) } - fn fetch_output(&mut self) -> Option { - todo!() + fn fetch_output(&mut self) -> ResultS { + let s = self.process.fetch_output()?; + (self.output_translator)(s) + } + + fn try_fetch_output(&mut self) -> ResultS> { + let s = self.process.try_fetch_output()?; + // 匹配分支 + match s { + // 有输出⇒尝试转译并返回 + Some(s) => Ok(Some((self.output_translator)(s)?)), + // 没输出⇒没输出 | ⚠️注意:不能使用`map`,否则`?`穿透不出闭包 + None => Ok(None), + } } } -impl VmBuilder for CommandVm { +/// 构建功能:启动命令行虚拟机 +impl VmLauncher for CommandVm { fn launch(self) -> CommandVmRuntime { CommandVmRuntime { // 启动内部的「进程管理者」 @@ -48,13 +65,70 @@ impl VmBuilder for CommandVm { input_translator: self .input_translator // 默认值:直接调用Cmd的`to_string`方法 | 使用NAVM Cmd语法 - .unwrap_or(Box::new(|cmd| cmd.to_string())), + .unwrap_or(Box::new(|cmd| Ok(cmd.to_string()))), // 输出转译器 output_translator: self .output_translator // 默认值:直接归入「其它」输出 | 约等于不分类 - .unwrap_or(Box::new(|content| Output::OTHER { content })), - // TODO: 其它 + .unwrap_or(Box::new(|content| Ok(Output::OTHER { content }))), + // * 🚩【2024-03-24 02:06:59】目前到此为止:只需处理「转译」问题 + } + } +} + +/// 单元测试 +#[cfg(test)] +mod test { + use super::*; + use crate::process_io::tests::await_fetch_until; + use narsese::conversion::string::impl_lexical::shortcuts::*; + + // 定义一系列路径 + #[allow(dead_code)] + const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; + #[allow(dead_code)] + const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; + #[allow(dead_code)] + const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars.jar"; + + /// 示例测试 | PyNARS + #[test] + fn test_pynars() { + let mut vm = CommandVm::new(EXE_PATH_PYNARS) + // 输入转换器:直接取其尾部 + .input_translator(|cmd| Ok(cmd.tail())) + // 🔥启动 + .launch(); + + // // 睡眠等待 + // // std::thread::sleep(std::time::Duration::from_secs(1)); + // ! ↑现在无需睡眠等待:输入会自动在初始化后写入子进程 + + let mut input_cmd_and_await = |cmd, contains: &str| { + // 构造并输入任务 + vm.input_cmd(cmd).expect("无法输入指令!"); + // ! 目前还是失败 + + // 必要时等待 + if !contains.is_empty() { + await_fetch_until(&mut vm.process, |s| s.contains(contains)); + } + }; + + // 构造并输入任务 | 输入进PyNARS后变成了紧凑版本 + input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), "B>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>.)), "C>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>?)), "C>?"); + input_cmd_and_await(Cmd::CYC(5), ""); // * CYC无需自动等待 + + // 等待回答 + await_fetch_until(&mut vm.process, |s| { + s.contains("ANSWER") && s.contains("C>.") + }); + + // 打印所有输出 + while let Some(output) = vm.try_fetch_output().unwrap() { + println!("{:?}", output); } } } diff --git a/src/tools/flow_handler_list.rs b/src/tools/flow_handler_list.rs index 8d7f38b..498c8d2 100644 --- a/src/tools/flow_handler_list.rs +++ b/src/tools/flow_handler_list.rs @@ -166,7 +166,7 @@ mod tests { /// 联动「NAVM输出」测试 #[test] fn test_navm_output() { - use narsese::lexical::shortcut::*; + use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::output::*; // 构造输出 let answer = Output::ANSWER { From 08e01504f669cd95c6ad86b2e572bf741b152b0e Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:01:20 +0800 Subject: [PATCH 12/59] =?UTF-8?q?refactor:=20:truck:=20=E6=A6=82=E5=BF=B5?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=EF=BC=9A=E3=80=8C=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=99=A8=E3=80=8D=E2=86=92=E3=80=8C=E5=90=AF=E5=8A=A8=E5=99=A8?= =?UTF-8?q?=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/process_io/io_process.rs | 2 +- src/runtime/command_vm/{builder.rs => launcher.rs} | 0 src/runtime/command_vm/mod.rs | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/runtime/command_vm/{builder.rs => launcher.rs} (100%) diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 611f8d7..742dff2 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -19,7 +19,7 @@ type OutputListener = dyn FnMut(String) + Send + Sync; type ArcMutex = Arc>; /// 构建一个「IO进程」 -/// * 📌只是作为一个「构建器」存在 +/// * 📌只是作为一个基于配置的「子进程启动器」存在 /// * 作为真正的`IoProcessManager`的launcher/builder /// /// ! 因为有「系统指令」与「函数闭包」,无法派生任何常规宏 diff --git a/src/runtime/command_vm/builder.rs b/src/runtime/command_vm/launcher.rs similarity index 100% rename from src/runtime/command_vm/builder.rs rename to src/runtime/command_vm/launcher.rs diff --git a/src/runtime/command_vm/mod.rs b/src/runtime/command_vm/mod.rs index 4d9fe58..d0cacbf 100644 --- a/src/runtime/command_vm/mod.rs +++ b/src/runtime/command_vm/mod.rs @@ -4,8 +4,8 @@ util::pub_mod_and_pub_use! { // 抽象API api - // 构建器 - builder + // 启动器 + launcher // 运行时 runtime } From 13b9e9e8f4ae9cf91f096f9c61c3b91516b95680 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:58:27 +0800 Subject: [PATCH 13/59] =?UTF-8?q?feat:=20:sparkles:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E5=90=AF=E5=8A=A8=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E5=96=84=E3=80=8C=E5=91=BD=E4=BB=A4=E8=A1=8C?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=9C=BA=E8=BF=90=E8=A1=8C=E6=97=B6=E3=80=8D?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0OpenNARS=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 6 +- Cargo.lock | 4 +- Cargo.toml | 2 +- src/process_io/io_process.rs | 44 +++++-- src/runtime/command_vm/launcher.rs | 23 +++- src/runtime/command_vm/runtime.rs | 177 ++++++++++++++++++++++++++--- 6 files changed, 228 insertions(+), 28 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d06b245..380b1f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,8 @@ { "rust-analyzer.cargo.features": "all", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "cSpell.words": [ + "runpy", + "traceback" + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a162291..cd615eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "babel_nar" -version = "0.2.0" +version = "0.3.0" dependencies = [ "nar_dev_utils", "narsese", @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.1.1" +version = "0.1.2" dependencies = [ "nar_dev_utils", "narsese", diff --git a/Cargo.toml b/Cargo.toml index ee83940..50b4a97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.2.0" +version = "0.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 742dff2..05112dc 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -32,9 +32,20 @@ pub struct IoProcess { impl IoProcess { /// 构造函数 + /// * 🚩从路径构造实体 + /// * 📌直接生成[`Command`]对象,无需额外配置 pub fn new(program_path: impl AsRef) -> Self { + // 实际上是构建了一个新[`Command`]对象 + let command = Command::new(program_path); + Self::from_command(command) + } + + /// 构造函数/自[`Command`]对象 + /// * 🚩从[`Command`]对象构建实体 + /// * ✅这里的[`Command`]必定是未被启动的:Launch之后会变成[`Child`]类型 + pub fn from_command(command: Command) -> Self { Self { - command: Command::new(program_path), + command, out_listener: None, } } @@ -80,7 +91,6 @@ impl IoProcess { let child = // 指令+参数 self.command - .arg("shell") // 输入输出 .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -96,6 +106,16 @@ impl IoProcess { } } +/// 实现/从[`Command`]对象转换为[`IoProcess`] +impl From for IoProcess { + /// 构造函数 + /// * 🚩从`Command`对象构造实体 + /// * 📌直接生成[`Command`]对象,无需额外配置 + fn from(command: Command) -> Self { + Self::from_command(command) + } +} + /// 子进程管理器 /// * 🎯负责 /// * 统一管理子进程 @@ -252,14 +272,16 @@ impl IoProcessManager { // 持续循环 loop { // 从子进程「标准输出」读取输入 - // * 📌此处非阻塞(会读到空),且`buf`会有换行符 + // * ⚠️会阻塞:`read_line` + // * 📄在ONA处不阻塞,但在OpenNARS时阻塞 + // * 🔗 match stdout_reader.read_line(&mut buf) { // 没有任何输入⇒检查终止信号 // * 📌不能在这里中断,需要检查终止信号 // * 🚩【2024-03-24 01:48:19】目前**允许**在进程终止时获取其输出 // * 一般侦听器都能侦听到 Ok(0) => { - if dbg!(*termination_signal.lock().expect("无法锁定终止信号")) { + if *termination_signal.lock().expect("无法锁定终止信号") { // println!("子进程收到终止信号"); break; } @@ -387,8 +409,12 @@ impl IoProcessManager { self.put("\n").unwrap(); // 等待子线程终止 // - self.thread_write_in.join().transform_err_debug()?; - self.thread_read_out.join().transform_err_debug()?; + // * 🚩【2024-03-24 18:49:31】现在强制销毁持有的两个子线程,不再等待其结束 + // * 📌主要原因:在测试OpenNARS时,发现`thread_read_out`仍然会阻塞(无法等待) + // * 📌并且一时难以修复:难点在`BufReader.read_line`如何非阻塞/可终止化 + // ! ℹ️信息 from Claude3:无法简单以此终止子线程 + self.thread_write_in.join().transform_err_debug()?; // * ✅目前这个是可以终止的 + drop(self.thread_read_out); // * 📝此时子线程连同「子进程的标准输入输出」一同关闭, // * 子进程自身可以做输出 @@ -416,9 +442,9 @@ pub(crate) mod tests { /// 测试工具/等待子进程输出,直到输出满足条件 pub fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { loop { - let o = dbg!(process.fetch_output().expect("无法拉取输出")); - println!("fetch到其中一个输入: {o:?}"); - if criterion(o) { + let out = process.fetch_output().expect("无法拉取输出"); + println!("fetch到其中一个输出: {out:?}"); + if criterion(out) { break; } } diff --git a/src/runtime/command_vm/launcher.rs b/src/runtime/command_vm/launcher.rs index cdb4235..961c3b5 100644 --- a/src/runtime/command_vm/launcher.rs +++ b/src/runtime/command_vm/launcher.rs @@ -22,10 +22,20 @@ pub struct CommandVm { impl CommandVm { /// 构造函数 + /// * 🚩接收一个可执行文件路径 + /// * 📌直接生成[`IoProcess`]对象,无需额外配置 pub fn new(program_path: impl AsRef) -> Self { + let io_process = IoProcess::new(program_path); + Self::from_io_process(io_process) + } + + /// 构造函数/自[`IoProcess`]对象 + /// * 🚩从[`IoProcess`]对象创建 + /// * ✅这里的[`IoProcess`]必定是未被启动的:Launch之后会变成其它类型 + pub fn from_io_process(io_process: IoProcess) -> Self { Self { // 指令 - io_process: IoProcess::new(program_path), + io_process, // 其它暂时置空 input_translator: None, output_translator: None, @@ -51,3 +61,14 @@ impl CommandVm { self } } + +/// 实现/从[`IoProcess`]对象转换为[`CommandVm`]对象 +impl From for CommandVm { + fn from(io_process: IoProcess) -> Self { + Self { + io_process, + input_translator: None, + output_translator: None, + } + } +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 864632f..cba69d6 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -53,6 +53,12 @@ impl VmRuntime for CommandVmRuntime { None => Ok(None), } } + + fn terminate(self) -> ResultS<()> { + // 杀死子进程 + self.process.kill()?; + Ok(()) + } } /// 构建功能:启动命令行虚拟机 @@ -78,10 +84,12 @@ impl VmLauncher for CommandVm { /// 单元测试 #[cfg(test)] -mod test { +pub(crate) mod test { + use std::process::Command; + use super::*; - use crate::process_io::tests::await_fetch_until; use narsese::conversion::string::impl_lexical::shortcuts::*; + use util::first; // 定义一系列路径 #[allow(dead_code)] @@ -89,9 +97,147 @@ mod test { #[allow(dead_code)] const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; #[allow(dead_code)] - const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars.jar"; + const COMMAND_JAVA: &str = "java"; + const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; + #[allow(dead_code)] + const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; + + /// 实用测试工具/等待 + pub fn await_fetch_until( + vm: &mut CommandVmRuntime, + criterion: impl Fn(Output, String) -> bool, + ) { + // 不断拉取输出 + // TODO: 💭【2024-03-24 18:21:28】后续可以结合「流式处理者列表」做集成测试 + loop { + // 拉取输出及其内容 | ⚠️必要时等待(阻塞!) + let output = vm.fetch_output().unwrap(); + let raw_content = output.raw_content(); + // 展示输出 + match &output { + // 特别显示「回答」 + Output::ANSWER { content_raw, .. } => println!("捕获到回答!内容:{content_raw}"), + _ => println!("捕获到其它输出!内容:{output:?}"), + } + // 包含⇒结束 + if criterion(output, raw_content) { + break; + } + } + } + + /// 实用测试工具/输入并等待 + pub fn input_cmd_and_await( + vm: &mut CommandVmRuntime, + cmd: Cmd, + criterion: impl Fn(Output, String) -> bool, + ) { + // 构造并输入任务 + vm.input_cmd(cmd).expect("无法输入指令!"); + // 「contains」非空⇒等待 + await_fetch_until(vm, criterion) + } + + /// 实用测试工具/输入并等待「是否包含」 + /// * 🚩`input_cmd_and_await`的简单封装 + /// * 🎯【2024-03-24 18:38:50】用于「输出转换」尚未成熟时 + #[inline(always)] + pub fn input_cmd_and_await_contains( + vm: &mut CommandVmRuntime, + cmd: Cmd, + expected_contains: &str, + ) { + // 空预期⇒直接输入 + // * 🎯在后边测试中统一使用闭包,并且不会因此「空头拉取输出」 + // * 📄【2024-03-24 18:47:20】有过「之前的CYC把Answer拉走了,导致后边的Answer等不到」的情况 + // * ⚠️不能简化:区别在「是否会拉取输入,即便条件永真」 + match expected_contains.is_empty() { + true => vm.input_cmd(cmd).expect("无法输入NAVM指令!"), + false => input_cmd_and_await(vm, cmd, |_, raw_content| { + raw_content.contains(expected_contains) + }), + } + } + + /// 示例测试 | OpenNARS + /// * 🚩通过Java命令启动 + #[test] + fn test_opennars() { + // 构造指令 + let mut command_java = Command::new(COMMAND_JAVA); + // * 📝这里的`args`、`arg都返回的可变借用。。 + command_java + .args(COMMAND_ARGS_JAVA) + .arg(JAR_PATH_OPENNARS) + // OpenNARS的默认参数 | ["null", "null", "null", "null"] + // * 🔗https://github.com/opennars/opennars/blob/master/src/main/java/org/opennars/main/Shell.java + // * ✅fixed「额外参数」问题:之前「IO进程」的测试代码`.arg("shell")`没删干净 + // .args(["null", "null", "null", "null"]) + ; + // dbg!(&command_java); + + /// 临时构建的「输入转换」函数 + /// * 🎯用于转换`VOL 0`⇒`*volume=0`,避免大量输出造成进程卡顿 + fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + Cmd::CYC(n) => n.to_string(), + // VOL指令:调整音量 + Cmd::VOL(n) => format!("*volume={n}"), + // 其它类型 + _ => return Err(format!("未知指令:{cmd:?}")), + }; + // 转换 + Ok(content) + } + + /// 临时构建的「输出转换」函数 + fn output_translate(content: String) -> ResultS { + // 读取输出 + let output = first! { + // 捕获Answer + content.contains("Answer") => Output::ANSWER { content_raw: content, narsese: None }, + // 捕获OUT + content.contains("OUT") => Output::OUT { content_raw: content, narsese: None }, + // 其它情况 + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) + } + + // 构造并启动虚拟机 + let mut vm = CommandVm::from_io_process(command_java.into()) + // 输入转换器 + .input_translator(input_translate) + // 输出转换器 + .output_translator(output_translate) + // 🔥启动 + .launch(); + + // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 + let mut input_cmd_and_await = + |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); + input_cmd_and_await(Cmd::VOL(0), ""); + input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), " B>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>.)), " C>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>?)), " C>?"); + input_cmd_and_await(Cmd::CYC(5), ""); // * CYC无需自动等待 + + // 等待回答(字符串) + await_fetch_until(&mut vm, |_, s| { + s.contains("Answer") && s.contains(" C>.") + }); + + // 终止虚拟机 + vm.terminate().expect("无法终止虚拟机"); + println!("Virtual machine terminated..."); + } /// 示例测试 | PyNARS + /// * 🚩通过预置的批处理文件启动 #[test] fn test_pynars() { let mut vm = CommandVm::new(EXE_PATH_PYNARS) @@ -104,16 +250,9 @@ mod test { // // std::thread::sleep(std::time::Duration::from_secs(1)); // ! ↑现在无需睡眠等待:输入会自动在初始化后写入子进程 - let mut input_cmd_and_await = |cmd, contains: &str| { - // 构造并输入任务 - vm.input_cmd(cmd).expect("无法输入指令!"); - // ! 目前还是失败 - - // 必要时等待 - if !contains.is_empty() { - await_fetch_until(&mut vm.process, |s| s.contains(contains)); - } - }; + // 专有闭包 + let mut input_cmd_and_await = + |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); // 构造并输入任务 | 输入进PyNARS后变成了紧凑版本 input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), "B>."); @@ -122,7 +261,7 @@ mod test { input_cmd_and_await(Cmd::CYC(5), ""); // * CYC无需自动等待 // 等待回答 - await_fetch_until(&mut vm.process, |s| { + await_fetch_until(&mut vm, |_, s| { s.contains("ANSWER") && s.contains("C>.") }); @@ -130,5 +269,15 @@ mod test { while let Some(output) = vm.try_fetch_output().unwrap() { println!("{:?}", output); } + + // 终止虚拟机 + vm.terminate().expect("无法终止虚拟机"); + println!("Virtual machine terminated..."); + // * 📝在实际测试中会使Python报错「EOFError: EOF when reading a line」 + /* // * ✅但这不影响(不会被「命令行虚拟机」捕获为输出) + traceback (most recent call last): + File "", line 198, in _run_module_as_main + File "", line 88, in _run_code + */ } } From 8efe36b9dcfe163549f73cb83d3a74ef04a54c49 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:43:20 +0800 Subject: [PATCH 14/59] =?UTF-8?q?feat:=20:sparkles:=20=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E4=BB=A3=E7=A0=81=E9=80=9A=E7=94=A8=E5=8C=96=E6=8B=86?= =?UTF-8?q?=E5=88=86=EF=BC=9B=E5=AE=9E=E7=8E=B0OpenNARS=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 6 +-- Cargo.toml | 2 +- src/bin/cin_launcher.rs | 2 + src/impl_runtime/mod.rs | 15 ++++++ src/impl_runtime/opennars/launcher.rs | 61 +++++++++++++++++++++ src/impl_runtime/opennars/mod.rs | 29 ++++++++++ src/impl_runtime/opennars/translators.rs | 69 ++++++++++++++++++++++++ src/runtime/command_vm/runtime.rs | 29 +++++----- 8 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 src/impl_runtime/opennars/launcher.rs create mode 100644 src/impl_runtime/opennars/mod.rs create mode 100644 src/impl_runtime/opennars/translators.rs diff --git a/Cargo.lock b/Cargo.lock index cd615eb..8ffe7c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "babel_nar" -version = "0.3.0" +version = "0.4.0" dependencies = [ "nar_dev_utils", "narsese", @@ -19,7 +19,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "nar_dev_utils" -version = "0.16.0" +version = "0.17.0" [[package]] name = "narsese" @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.1.2" +version = "0.2.0" dependencies = [ "nar_dev_utils", "narsese", diff --git a/Cargo.toml b/Cargo.toml index 50b4a97..f331345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.3.0" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs index b0c2415..be7bf04 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher.rs @@ -1,5 +1,7 @@ //! 一个一站式启动各CIN的启动器 //! * 📌用于集成原先「BabelNAR」「BabelNAR_Implements」两个库 +//! * ✨自动根据可执行文件、配置文件、用户输入猜测CIN类型 +//! * ✨自动启动并管理CIN //! TODO: 完成代码 /// 主函数 diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs index cb85f4d..2b8c992 100644 --- a/src/impl_runtime/mod.rs +++ b/src/impl_runtime/mod.rs @@ -1,3 +1,18 @@ //! 对各CIN实现「非公理虚拟机」模型 //! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 //! TODO: 支持各大CIN + +// OpenNARS +pub mod opennars; + +// ONA +// TODO: 具体实现 + +// NARS-Python +// TODO: 具体实现 + +// PyNARS +// TODO: 具体实现 + +// OpenJunars +// TODO: 具体实现 diff --git a/src/impl_runtime/opennars/launcher.rs b/src/impl_runtime/opennars/launcher.rs new file mode 100644 index 0000000..660f897 --- /dev/null +++ b/src/impl_runtime/opennars/launcher.rs @@ -0,0 +1,61 @@ +//! OpenNARS运行时的启动器 +//! * 🎯允许OpenNARS对原先运行时特别配置功能,同时也支持为OpenNARS定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 + +use super::{input_translate, output_translate}; +use crate::runtime::{CommandVm, CommandVmRuntime}; +use navm::vm::VmLauncher; +use std::{path::PathBuf, process::Command}; + +/// 启动Java运行时的命令 +const COMMAND_JAVA: &str = "java"; + +/// jar文件启动的默认指令参数 +/// * 🎯默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` +const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; + +/// OpenNARS运行时启动器 +/// * 🎯配置OpenNARS专有的东西 +/// * 🎯以Java运行时专有形式启动OpenNARS +/// * 🚩基于jar文件启动OpenNARS Shell +/// * 默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct OpenNARS { + /// jar文件路径 + /// * 📌必须有 + jar_path: PathBuf, + /// OpenNARS Shell + default_volume: Option, +} + +impl OpenNARS { + pub fn new(jar_path: impl Into) -> Self { + Self { + // 转换为路径 + jar_path: jar_path.into(), + // 其它全是`None` + ..Default::default() + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for OpenNARS { + fn launch(self) -> CommandVmRuntime { + // 构造指令 + let mut command_java = Command::new(COMMAND_JAVA); + // * 📝这里的`args`、`arg都返回的可变借用。。 + command_java.args(COMMAND_ARGS_JAVA).arg(self.jar_path); + + // 构造并启动虚拟机 + CommandVm::from_io_process(command_java.into()) + // * 🚩固定的「输入输出转换器」 + .input_translator(input_translate) + .output_translator(output_translate) + // 🔥启动 + .launch() + } +} + +// ! 单元测试见[`super`] diff --git a/src/impl_runtime/opennars/mod.rs b/src/impl_runtime/opennars/mod.rs new file mode 100644 index 0000000..56eb062 --- /dev/null +++ b/src/impl_runtime/opennars/mod.rs @@ -0,0 +1,29 @@ +//! 「非公理虚拟机」的OpenNARS运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::test::{_test_opennars, JAR_PATH_OPENNARS}; + use navm::vm::VmLauncher; + + #[test] + fn test() { + // 从别的地方获取jar路径 + let jar_path = JAR_PATH_OPENNARS; + // 一行代码启动OpenNARS + let vm = OpenNARS::new(jar_path).launch(); + // 直接复用之前对OpenNARS的测试 + _test_opennars(vm) + } +} diff --git a/src/impl_runtime/opennars/translators.rs b/src/impl_runtime/opennars/translators.rs new file mode 100644 index 0000000..8019b0a --- /dev/null +++ b/src/impl_runtime/opennars/translators.rs @@ -0,0 +1,69 @@ +//! OpenNARS在「命令行运行时」的转译器 +//! * 🎯维护与OpenNARS Shell的交互 +//! * https://github.com/ARCJ137442/opennars-304/blob/master/src/main/java/org/opennars/main/Shell.java +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 + +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use util::ResultS; + +/// OpenNARS的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「OpenNARS Shell输入」 +pub fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + // ! OpenNARS Shell是自动步进的 + Cmd::CYC(n) => n.to_string(), + // VOL指令:调整音量 + Cmd::VOL(n) => format!("*volume={n}"), + // 其它类型 + // * 📌【2024-03-24 22:57:18】基本足够支持 + _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + }; + // 转译 + Ok(content) +} + +/// OpenNARS的「输出转译」函数 +/// * 🎯用于将OpenNARS Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content: String) -> ResultS { + // 根据冒号分隔一次,然后得到「头部」 + let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + // 根据「头部」生成输出 + let output = match &*head { + "answer" => Output::ANSWER { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "out" => Output::OUT { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "in" => Output::IN { content }, + "anticipate" => Output::ANTICIPATE { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "exe" => Output::EXE { + content_raw: content, + // TODO: 有待捕获转译 + operation: Operation::new("UNKNOWN", [].into_iter()), + }, + "err" | "error" => Output::ERROR { + description: content, + }, + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index cba69d6..67f6a9b 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -85,22 +85,20 @@ impl VmLauncher for CommandVm { /// 单元测试 #[cfg(test)] pub(crate) mod test { - use std::process::Command; - use super::*; use narsese::conversion::string::impl_lexical::shortcuts::*; + use std::process::Command; use util::first; // 定义一系列路径 #[allow(dead_code)] - const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - #[allow(dead_code)] - const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; + pub const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; #[allow(dead_code)] + pub const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; + pub const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; + const COMMAND_JAVA: &str = "java"; const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; - #[allow(dead_code)] - const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; /// 实用测试工具/等待 pub fn await_fetch_until( @@ -209,14 +207,17 @@ pub(crate) mod test { } // 构造并启动虚拟机 - let mut vm = CommandVm::from_io_process(command_java.into()) - // 输入转换器 + let vm = CommandVm::from_io_process(command_java.into()) + // 输入转译器 .input_translator(input_translate) - // 输出转换器 + // 输出转译器 .output_translator(output_translate) // 🔥启动 .launch(); + _test_opennars(vm); + } + pub fn _test_opennars(mut vm: CommandVmRuntime) { // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 let mut input_cmd_and_await = |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); @@ -240,12 +241,16 @@ pub(crate) mod test { /// * 🚩通过预置的批处理文件启动 #[test] fn test_pynars() { - let mut vm = CommandVm::new(EXE_PATH_PYNARS) - // 输入转换器:直接取其尾部 + let vm = CommandVm::new(EXE_PATH_PYNARS) + // 输入转译器:直接取其尾部 .input_translator(|cmd| Ok(cmd.tail())) // 🔥启动 .launch(); + // 可复用的测试逻辑 + _test_pynars(vm); + } + pub fn _test_pynars(mut vm: CommandVmRuntime) { // // 睡眠等待 // // std::thread::sleep(std::time::Duration::from_secs(1)); // ! ↑现在无需睡眠等待:输入会自动在初始化后写入子进程 From 211bdaabb1838e88e02b7cce89b4d612200ed596 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:49:01 +0800 Subject: [PATCH 15/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=A2=9E=E5=8A=A0ON?= =?UTF-8?q?A=E8=BF=90=E8=A1=8C=E6=97=B6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/cin_launcher.rs | 4 +- src/impl_runtime/mod.rs | 2 +- src/impl_runtime/ona/launcher.rs | 58 ++++++++++++++++++++++++ src/impl_runtime/ona/mod.rs | 29 ++++++++++++ src/impl_runtime/ona/translators.rs | 68 +++++++++++++++++++++++++++++ src/runtime/command_vm/runtime.rs | 46 ++++++++++++++----- 6 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 src/impl_runtime/ona/launcher.rs create mode 100644 src/impl_runtime/ona/mod.rs create mode 100644 src/impl_runtime/ona/translators.rs diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs index be7bf04..420a9d9 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher.rs @@ -1,6 +1,8 @@ //! 一个一站式启动各CIN的启动器 +//! * 🎯方便启动、管理各「作为NAVM运行时的CIN」的聚合终端 //! * 📌用于集成原先「BabelNAR」「BabelNAR_Implements」两个库 -//! * ✨自动根据可执行文件、配置文件、用户输入猜测CIN类型 +//! * ✨自动根据可执行文件、配置文件、用户输入猜测CIN类型(字符串匹配) +//! * ✨自动查找(可能)可用的CIN可执行文件(文件搜索) //! * ✨自动启动并管理CIN //! TODO: 完成代码 diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs index 2b8c992..ad5a807 100644 --- a/src/impl_runtime/mod.rs +++ b/src/impl_runtime/mod.rs @@ -6,7 +6,7 @@ pub mod opennars; // ONA -// TODO: 具体实现 +pub mod ona; // NARS-Python // TODO: 具体实现 diff --git a/src/impl_runtime/ona/launcher.rs b/src/impl_runtime/ona/launcher.rs new file mode 100644 index 0000000..66bf981 --- /dev/null +++ b/src/impl_runtime/ona/launcher.rs @@ -0,0 +1,58 @@ +//! ONA运行时的启动器 +//! * 🎯允许ONA对原先运行时特别配置功能,同时也支持为ONA定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 + +use super::{input_translate, output_translate}; +use crate::runtime::{CommandVm, CommandVmRuntime}; +use navm::vm::VmLauncher; +use std::{path::PathBuf, process::Command}; + +/// ONA Shell启动的默认指令参数 +/// * 🎯默认预置指令:`[.exe文件路径] shell` +const COMMAND_ARGS_ONA: [&str; 1] = ["shell"]; + +/// ONA运行时启动器 +/// * 🎯配置ONA专有的东西 +/// * 🎯以Java运行时专有形式启动ONA +/// * 🚩基于exe文件启动ONA Shell +/// * 默认预置指令:`[.exe文件路径] shell` +/// * 📌【2024-03-25 08:41:16】目前跟随Rust命名规则,仅首字母大写 +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ona { + /// exe文件路径 + exe_path: PathBuf, + /// ONA Shell + default_volume: Option, +} + +impl Ona { + pub fn new(exe_path: impl Into) -> Self { + Self { + // 转换为路径 + exe_path: exe_path.into(), + // 其它全是`None` + ..Default::default() + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for Ona { + fn launch(self) -> CommandVmRuntime { + // 构造指令 + let mut command = Command::new(self.exe_path); + // * 📝这里的`args`、`arg都返回的可变借用。。 + command.args(COMMAND_ARGS_ONA); + + // 构造并启动虚拟机 + CommandVm::from_io_process(command.into()) + // * 🚩固定的「输入输出转换器」 + .input_translator(input_translate) + .output_translator(output_translate) + // 🔥启动 + .launch() + } +} + +// ! 单元测试见[`super`] diff --git a/src/impl_runtime/ona/mod.rs b/src/impl_runtime/ona/mod.rs new file mode 100644 index 0000000..70afe15 --- /dev/null +++ b/src/impl_runtime/ona/mod.rs @@ -0,0 +1,29 @@ +//! 「非公理虚拟机」的ONA运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::test::{_test_ona, EXE_PATH_ONA}; + use navm::vm::VmLauncher; + + #[test] + fn test() { + // 从别的地方获取jar路径 + let jar_path = EXE_PATH_ONA; + // 一行代码启动ONA + let vm = Ona::new(jar_path).launch(); + // 直接复用之前对ONA的测试 + _test_ona(vm) + } +} diff --git a/src/impl_runtime/ona/translators.rs b/src/impl_runtime/ona/translators.rs new file mode 100644 index 0000000..993ef64 --- /dev/null +++ b/src/impl_runtime/ona/translators.rs @@ -0,0 +1,68 @@ +//! ONA在「命令行运行时」的转译器 +//! * 🎯维护与ONA Shell的交互 +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 + +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use util::ResultS; + +/// ONA的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 +pub fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + // ! ONA Shell同样是自动步进的 + Cmd::CYC(n) => n.to_string(), + // VOL指令:调整音量 + Cmd::VOL(n) => format!("*volume={n}"), + // 其它类型 + // * 📌【2024-03-24 22:57:18】基本足够支持 + _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + }; + // 转译 + Ok(content) +} + +/// ONA的「输出转译」函数 +/// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content: String) -> ResultS { + // 根据冒号分隔一次,然后得到「头部」 + let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + // 根据「头部」生成输出 + let output = match &*head { + "answer" => Output::ANSWER { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "derived" => Output::OUT { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "input" => Output::IN { content }, + "anticipate" => Output::ANTICIPATE { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "exe" => Output::EXE { + content_raw: content, + // TODO: 有待捕获转译 + operation: Operation::new("UNKNOWN", [].into_iter()), + }, + "err" | "error" => Output::ERROR { + description: content, + }, + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 67f6a9b..c4bdf91 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -103,8 +103,8 @@ pub(crate) mod test { /// 实用测试工具/等待 pub fn await_fetch_until( vm: &mut CommandVmRuntime, - criterion: impl Fn(Output, String) -> bool, - ) { + criterion: impl Fn(&Output, String) -> bool, + ) -> Output { // 不断拉取输出 // TODO: 💭【2024-03-24 18:21:28】后续可以结合「流式处理者列表」做集成测试 loop { @@ -118,8 +118,8 @@ pub(crate) mod test { _ => println!("捕获到其它输出!内容:{output:?}"), } // 包含⇒结束 - if criterion(output, raw_content) { - break; + if criterion(&output, raw_content) { + break output; } } } @@ -128,8 +128,8 @@ pub(crate) mod test { pub fn input_cmd_and_await( vm: &mut CommandVmRuntime, cmd: Cmd, - criterion: impl Fn(Output, String) -> bool, - ) { + criterion: impl Fn(&Output, String) -> bool, + ) -> Output { // 构造并输入任务 vm.input_cmd(cmd).expect("无法输入指令!"); // 「contains」非空⇒等待 @@ -144,16 +144,19 @@ pub(crate) mod test { vm: &mut CommandVmRuntime, cmd: Cmd, expected_contains: &str, - ) { + ) -> Option { // 空预期⇒直接输入 // * 🎯在后边测试中统一使用闭包,并且不会因此「空头拉取输出」 // * 📄【2024-03-24 18:47:20】有过「之前的CYC把Answer拉走了,导致后边的Answer等不到」的情况 // * ⚠️不能简化:区别在「是否会拉取输入,即便条件永真」 match expected_contains.is_empty() { - true => vm.input_cmd(cmd).expect("无法输入NAVM指令!"), - false => input_cmd_and_await(vm, cmd, |_, raw_content| { + true => { + vm.input_cmd(cmd).expect("无法输入NAVM指令!"); + None + } + false => Some(input_cmd_and_await(vm, cmd, |_, raw_content| { raw_content.contains(expected_contains) - }), + })), } } @@ -217,6 +220,7 @@ pub(crate) mod test { _test_opennars(vm); } + /// 通用测试/OpenNARS pub fn _test_opennars(mut vm: CommandVmRuntime) { // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 let mut input_cmd_and_await = @@ -250,6 +254,28 @@ pub(crate) mod test { _test_pynars(vm); } + /// 通用测试/ONA + pub fn _test_ona(mut vm: CommandVmRuntime) { + // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 + let mut input_cmd_and_await = + |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); + // input_cmd_and_await(Cmd::VOL(0), ""); + input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), " B>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>.)), " C>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>?)), " C>?"); + input_cmd_and_await(Cmd::CYC(5), ""); // * CYC无需自动等待 + + // 等待回答(字符串) + await_fetch_until(&mut vm, |o, raw_content| { + matches!(o, Output::ANSWER { .. }) && raw_content.contains(" C>.") + }); + + // 终止虚拟机 + vm.terminate().expect("无法终止虚拟机"); + println!("Virtual machine terminated..."); + } + + /// 通用测试/PyNARS pub fn _test_pynars(mut vm: CommandVmRuntime) { // // 睡眠等待 // // std::thread::sleep(std::time::Duration::from_secs(1)); From b474933c82124958c7aaa5cebead4fbcf81ac48d Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:00:23 +0800 Subject: [PATCH 16/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81PyNARS=EF=BC=9B=E5=AF=B9OpenNARS=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E5=88=B0=E6=89=80=E6=9C=89Jar=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=99=A8=EF=BC=8C=E5=B9=B6=E6=96=B0=E5=A2=9E=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E7=9A=84=E3=80=8C=E6=9C=80=E5=B0=8F/=E6=9C=80=E5=A4=A7=20?= =?UTF-8?q?=E5=A0=86=E5=A4=A7=E5=B0=8F=E3=80=8D=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/impl_runtime/mod.rs | 2 +- src/impl_runtime/ona/launcher.rs | 37 +++++++++---- src/impl_runtime/ona/mod.rs | 6 +- src/impl_runtime/opennars/launcher.rs | 73 ++++++++++++++++++++---- src/impl_runtime/pynars/launcher.rs | 77 ++++++++++++++++++++++++++ src/impl_runtime/pynars/mod.rs | 30 ++++++++++ src/impl_runtime/pynars/translators.rs | 69 +++++++++++++++++++++++ src/runtime/command_vm/runtime.rs | 6 +- 8 files changed, 272 insertions(+), 28 deletions(-) create mode 100644 src/impl_runtime/pynars/launcher.rs create mode 100644 src/impl_runtime/pynars/mod.rs create mode 100644 src/impl_runtime/pynars/translators.rs diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs index ad5a807..b20f63a 100644 --- a/src/impl_runtime/mod.rs +++ b/src/impl_runtime/mod.rs @@ -12,7 +12,7 @@ pub mod ona; // TODO: 具体实现 // PyNARS -// TODO: 具体实现 +pub mod pynars; // OpenJunars // TODO: 具体实现 diff --git a/src/impl_runtime/ona/launcher.rs b/src/impl_runtime/ona/launcher.rs index 66bf981..b8b4301 100644 --- a/src/impl_runtime/ona/launcher.rs +++ b/src/impl_runtime/ona/launcher.rs @@ -5,7 +5,10 @@ use super::{input_translate, output_translate}; use crate::runtime::{CommandVm, CommandVmRuntime}; -use navm::vm::VmLauncher; +use navm::{ + cmd::Cmd, + vm::{VmLauncher, VmRuntime}, +}; use std::{path::PathBuf, process::Command}; /// ONA Shell启动的默认指令参数 @@ -14,19 +17,25 @@ const COMMAND_ARGS_ONA: [&str; 1] = ["shell"]; /// ONA运行时启动器 /// * 🎯配置ONA专有的东西 -/// * 🎯以Java运行时专有形式启动ONA /// * 🚩基于exe文件启动ONA Shell /// * 默认预置指令:`[.exe文件路径] shell` -/// * 📌【2024-03-25 08:41:16】目前跟随Rust命名规则,仅首字母大写 +/// * 🚩【2024-03-25 08:51:30】目前保留原有缩写的大小写风格,与OpenNARS、PyNARS一致 #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Ona { +pub struct ONA { /// exe文件路径 exe_path: PathBuf, - /// ONA Shell - default_volume: Option, + /// ONA Shell的初始音量 + /// * 🚩可能没有:此时不会输入指令 + initial_volume: Option, } -impl Ona { +// ! 🚩【2024-03-25 09:37:22】目前暂时不提取至「VmExe」:预置的`shell`参数需要被处理 +// /// 兼容性别名 +// #[doc(alias = "VmExe")] +// pub type OpenNARS = VmExe; + +impl ONA { + /// 构造函数 pub fn new(exe_path: impl Into) -> Self { Self { // 转换为路径 @@ -38,7 +47,7 @@ impl Ona { } /// 启动到「命令行运行时」 -impl VmLauncher for Ona { +impl VmLauncher for ONA { fn launch(self) -> CommandVmRuntime { // 构造指令 let mut command = Command::new(self.exe_path); @@ -46,12 +55,20 @@ impl VmLauncher for Ona { command.args(COMMAND_ARGS_ONA); // 构造并启动虚拟机 - CommandVm::from_io_process(command.into()) + let mut vm = CommandVm::from_io_process(command.into()) // * 🚩固定的「输入输出转换器」 .input_translator(input_translate) .output_translator(output_translate) // 🔥启动 - .launch() + .launch(); + // 选择性设置初始音量 + if let Some(volume) = self.initial_volume { + // 输入指令,并在执行错误时打印信息 + if let Err(e) = vm.input_cmd(Cmd::VOL(volume)) { + println!("无法设置初始音量「{volume}」:{e}"); + } + }; + vm } } diff --git a/src/impl_runtime/ona/mod.rs b/src/impl_runtime/ona/mod.rs index 70afe15..51bfa18 100644 --- a/src/impl_runtime/ona/mod.rs +++ b/src/impl_runtime/ona/mod.rs @@ -19,10 +19,10 @@ mod tests { #[test] fn test() { - // 从别的地方获取jar路径 - let jar_path = EXE_PATH_ONA; + // 从别的地方获取exe路径 + let exe_path = EXE_PATH_ONA; // 一行代码启动ONA - let vm = Ona::new(jar_path).launch(); + let vm = ONA::new(exe_path).launch(); // 直接复用之前对ONA的测试 _test_ona(vm) } diff --git a/src/impl_runtime/opennars/launcher.rs b/src/impl_runtime/opennars/launcher.rs index 660f897..febc28f 100644 --- a/src/impl_runtime/opennars/launcher.rs +++ b/src/impl_runtime/opennars/launcher.rs @@ -1,11 +1,16 @@ -//! OpenNARS运行时的启动器 +//! Java jar启动器 +//! * 📌OpenNARS运行时的启动器 //! * 🎯允许OpenNARS对原先运行时特别配置功能,同时也支持为OpenNARS定制配置 +//! * 🚩从jar文件启动NARS //! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 //! * ✨不同启动器可以启动到相同运行时 use super::{input_translate, output_translate}; use crate::runtime::{CommandVm, CommandVmRuntime}; -use navm::vm::VmLauncher; +use navm::{ + cmd::Cmd, + vm::{VmLauncher, VmRuntime}, +}; use std::{path::PathBuf, process::Command}; /// 启动Java运行时的命令 @@ -13,23 +18,50 @@ const COMMAND_JAVA: &str = "java"; /// jar文件启动的默认指令参数 /// * 🎯默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` -const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; +/// * 🚩实际上"-Xmx1024m"非必要 +const COMMAND_ARGS_JAVA: [&str; 1] = ["-jar"]; -/// OpenNARS运行时启动器 +/// Java运行时启动配置参数:初始堆大小/最小堆大小 +#[inline(always)] +fn command_arg_xms(size: usize) -> String { + format!("-Xms{size}m") +} + +/// Java运行时启动配置参数:最大堆大小 +#[inline(always)] +fn command_arg_xmx(size: usize) -> String { + format!("-Xmx{size}m") +} + +/// Java jar启动器 /// * 🎯配置OpenNARS专有的东西 /// * 🎯以Java运行时专有形式启动OpenNARS /// * 🚩基于jar文件启动OpenNARS Shell -/// * 默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` +/// * 默认预置指令:`java -jar [.jar文件路径]` #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct OpenNARS { +pub struct VmJava { /// jar文件路径 /// * 📌必须有 jar_path: PathBuf, - /// OpenNARS Shell - default_volume: Option, + /// NARS的初始音量 + /// * 🚩可能没有:此时不会输入指令 + initial_volume: Option, + /// Java运行时的初始堆大小/最小堆大小 + /// * 📄在Java指令中的参数:`-Xms[数值]m` + /// * 🚩可能没有:此时不会附加参数 + min_heap_size: Option, + /// Java运行时的最大堆大小 + /// * 📄在Java指令中的参数:`-Xmx[数值]m` + /// * 🚩可能没有:此时不会附加参数 + max_heap_size: Option, } -impl OpenNARS { +/// 兼容性别名 +#[doc(alias = "VmJava")] +pub type OpenNARS = VmJava; + +impl VmJava { + /// 构造函数 pub fn new(jar_path: impl Into) -> Self { Self { // 转换为路径 @@ -41,20 +73,37 @@ impl OpenNARS { } /// 启动到「命令行运行时」 -impl VmLauncher for OpenNARS { +impl VmLauncher for VmJava { fn launch(self) -> CommandVmRuntime { // 构造指令 let mut command_java = Command::new(COMMAND_JAVA); // * 📝这里的`args`、`arg都返回的可变借用。。 command_java.args(COMMAND_ARGS_JAVA).arg(self.jar_path); + // 选择性添加参数 |设置初始音量 + if let Some(size) = self.min_heap_size { + command_java.arg(command_arg_xms(size)); + } + if let Some(size) = self.max_heap_size { + command_java.arg(command_arg_xmx(size)); + } + // 构造并启动虚拟机 - CommandVm::from_io_process(command_java.into()) + let mut vm = CommandVm::from_io_process(command_java.into()) // * 🚩固定的「输入输出转换器」 .input_translator(input_translate) .output_translator(output_translate) // 🔥启动 - .launch() + .launch(); + // 设置初始音量 + self.initial_volume.inspect(|volume| { + // 输入指令,并在执行错误时打印信息 + if let Err(e) = vm.input_cmd(Cmd::VOL(*volume)) { + println!("无法设置初始音量「{volume}」:{e}"); + } + }); + // 返回 + vm } } diff --git a/src/impl_runtime/pynars/launcher.rs b/src/impl_runtime/pynars/launcher.rs new file mode 100644 index 0000000..7de99d3 --- /dev/null +++ b/src/impl_runtime/pynars/launcher.rs @@ -0,0 +1,77 @@ +//! Python模块 启动器 +//! * 📌PyNARS运行时的启动器 +//! * 🎯允许PyNARS对原先运行时特别配置功能,同时也支持为PyNARS定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 + +use super::{input_translate, output_translate}; +use crate::runtime::{CommandVm, CommandVmRuntime}; +use navm::vm::VmLauncher; +use std::{path::PathBuf, process::Command}; + +/// 启动Python运行时的命令 +const COMMAND_PYTHON: &str = "python"; + +/// jar文件启动的默认指令参数 +/// * 🎯默认预置指令:`python -m -jar [.jar文件路径]` +const COMMAND_ARGS_PYTHON: [&str; 1] = ["-m"]; + +/// PyNARS运行时启动器 +/// * 🎯配置PyNARS专有的东西 +/// * 🎯以Python模块形式启动PyNARS +/// * 📌没有内置的「音量」配置 +/// * ⚠️该配置参考的是PyNARS的`ConsolePlus`模块 +/// * 🚩【2024-03-25 08:55:07】基于Python模块文件启动PyNARS Shell +/// * 默认预置指令:`python -m [Python模块根目录] [Python模块路径]` +/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct VmPython { + /// 根目录 + /// * 📄`root/home/dev/pynars` + root_path: PathBuf, + + /// 模块路径 + /// * 📌相对根目录而言 + /// * 📄`pynars.Console` + /// * 📄`root_path` + `pynars.Console` => `root_path/pynars/Console` + module_path: String, +} + +/// 兼容性别名 +#[doc(alias = "VmPython")] +pub type PyNARS = VmPython; + +impl VmPython { + pub fn new(root_path: impl Into, module_path: &str) -> Self { + Self { + // 转换为路径 + root_path: root_path.into(), + // 转换为字符串 + module_path: module_path.to_string(), + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for VmPython { + fn launch(self) -> CommandVmRuntime { + // 构造指令 + let mut command = Command::new(COMMAND_PYTHON); + command + // * 🚩设置指令工作目录 + // * 📝`python -m`无法自行指定所执行的工作目录,必须在`Command`中设置 + .current_dir(self.root_path) // 以此设置当前工作目录 + .args(COMMAND_ARGS_PYTHON) + .arg(self.module_path); + + // 构造并启动虚拟机 + CommandVm::from_io_process(command.into()) + // * 🚩固定的「输入输出转换器」 + .input_translator(input_translate) + .output_translator(output_translate) + // 🔥启动 + .launch() + } +} + +// ! 单元测试见[`super`] diff --git a/src/impl_runtime/pynars/mod.rs b/src/impl_runtime/pynars/mod.rs new file mode 100644 index 0000000..8fe325c --- /dev/null +++ b/src/impl_runtime/pynars/mod.rs @@ -0,0 +1,30 @@ +//! 「非公理虚拟机」的PyNARS运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::test::{_test_pynars, MODULE_PATH_PYNARS, MODULE_ROOT_PYNARS}; + use navm::vm::VmLauncher; + + #[test] + fn test() { + // 从别的地方获取Python模块根目录、模块自身路径 + let root_path = MODULE_ROOT_PYNARS; + let module_path = MODULE_PATH_PYNARS; + // 一行代码启动PyNARS | python -m pynars.Console @ "..\..\PyNARS-dev" + let vm = PyNARS::new(root_path, module_path).launch(); + // 直接复用之前对PyNARS的测试 + _test_pynars(vm) + } +} diff --git a/src/impl_runtime/pynars/translators.rs b/src/impl_runtime/pynars/translators.rs new file mode 100644 index 0000000..def5b0f --- /dev/null +++ b/src/impl_runtime/pynars/translators.rs @@ -0,0 +1,69 @@ +//! ONA在「命令行运行时」的转译器 +//! * 🎯维护与ONA Shell的交互 +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 + +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use util::ResultS; + +/// ONA的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 +pub fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + // * 📌PyNARS需要手动指定步进数 + Cmd::CYC(n) => n.to_string(), + // VOL指令:调整音量 + // ! ⚠️该指令仅适用于`ConsolePlus` + Cmd::VOL(n) => format!("/volume {n}"), + // 其它类型 + // * 📌【2024-03-24 22:57:18】基本足够支持 + _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + }; + // 转译 + Ok(content) +} + +/// ONA的「输出转译」函数 +/// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content: String) -> ResultS { + // 根据冒号分隔一次,然后得到「头部」 + let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + // 根据「头部」生成输出 + let output = match &*head { + "answer" => Output::ANSWER { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "derived" => Output::OUT { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "input" => Output::IN { content }, + "anticipate" => Output::ANTICIPATE { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "exe" => Output::EXE { + content_raw: content, + // TODO: 有待捕获转译 + operation: Operation::new("UNKNOWN", [].into_iter()), + }, + "err" | "error" => Output::ERROR { + description: content, + }, + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index c4bdf91..abe9baa 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -91,11 +91,13 @@ pub(crate) mod test { use util::first; // 定义一系列路径 - #[allow(dead_code)] + // * 📌【2024-03-25 09:28:36】本地调试:都从根目录`BabelNAR.rs`开始 + // * 📄退一级到开发目录,再退一级到各NARS下载目录 pub const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - #[allow(dead_code)] pub const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; pub const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; + pub const MODULE_ROOT_PYNARS: &str = r"..\..\PyNARS-dev"; + pub const MODULE_PATH_PYNARS: &str = r"pynars.ConsolePlus"; const COMMAND_JAVA: &str = "java"; const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; From 453105eff91c915e5b28cdaa81f11837f8540802 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:14:12 +0800 Subject: [PATCH 17/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81NARS-Python=EF=BC=88=E7=9B=B4=E6=8E=A5exe?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=EF=BC=89=E4=B8=8EOpenJunars=EF=BC=88?= =?UTF-8?q?=E9=80=9A=E8=BF=87Julia=E8=BF=90=E8=A1=8C=E6=97=B6=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E9=87=8D=E5=91=BD=E5=90=8D=E3=80=8CCIN=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E3=80=8D=E7=9B=AE=E5=BD=95=EF=BC=9B=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ✅初步完成对现有常见NARS实现的启动器 2. 📌目前仍然只有OpenNARS、ONA、PyNARS能真正投入实际使用 - 其它都多少存在「IO困难」或「进程残留」问题 - 📄已在自述文档中说明 --- .vscode/settings.json | 1 + Cargo.lock | 2 +- Cargo.toml | 13 ++-- README.md | 25 ++++++- src/cin_implements/mod.rs | 34 ++++++++++ src/cin_implements/nars_python/launcher.rs | 48 ++++++++++++++ src/cin_implements/nars_python/mod.rs | 40 +++++++++++ src/cin_implements/nars_python/translators.rs | 63 ++++++++++++++++++ .../ona/launcher.rs | 0 .../ona/mod.rs | 0 .../ona/translators.rs | 5 -- src/cin_implements/open_junars/launcher.rs | 61 +++++++++++++++++ src/cin_implements/open_junars/mod.rs | 55 ++++++++++++++++ src/cin_implements/open_junars/translators.rs | 66 +++++++++++++++++++ .../opennars/launcher.rs | 6 +- .../opennars/mod.rs | 0 .../opennars/translators.rs | 0 .../pynars/launcher.rs | 4 +- .../pynars/mod.rs | 6 +- .../pynars/translators.rs | 0 src/impl_runtime/mod.rs | 18 ----- src/lib.rs | 2 +- src/process_io/io_process.rs | 30 ++++++++- src/runtime/command_vm/runtime.rs | 6 +- 24 files changed, 444 insertions(+), 41 deletions(-) create mode 100644 src/cin_implements/mod.rs create mode 100644 src/cin_implements/nars_python/launcher.rs create mode 100644 src/cin_implements/nars_python/mod.rs create mode 100644 src/cin_implements/nars_python/translators.rs rename src/{impl_runtime => cin_implements}/ona/launcher.rs (100%) rename src/{impl_runtime => cin_implements}/ona/mod.rs (100%) rename src/{impl_runtime => cin_implements}/ona/translators.rs (93%) create mode 100644 src/cin_implements/open_junars/launcher.rs create mode 100644 src/cin_implements/open_junars/mod.rs create mode 100644 src/cin_implements/open_junars/translators.rs rename src/{impl_runtime => cin_implements}/opennars/launcher.rs (96%) rename src/{impl_runtime => cin_implements}/opennars/mod.rs (100%) rename src/{impl_runtime => cin_implements}/opennars/translators.rs (100%) rename src/{impl_runtime => cin_implements}/pynars/launcher.rs (95%) rename src/{impl_runtime => cin_implements}/pynars/mod.rs (64%) rename src/{impl_runtime => cin_implements}/pynars/translators.rs (100%) delete mode 100644 src/impl_runtime/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 380b1f0..2262744 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "rust-analyzer.cargo.features": "all", "editor.formatOnSave": true, "cSpell.words": [ + "Errno", "runpy", "traceback" ] diff --git a/Cargo.lock b/Cargo.lock index 8ffe7c1..9e6fa75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "babel_nar" -version = "0.4.0" +version = "0.5.0" dependencies = [ "nar_dev_utils", "narsese", diff --git a/Cargo.toml b/Cargo.toml index f331345..ebe83a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.4.0" +version = "0.5.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -38,8 +38,13 @@ features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 default = [] # 大杂烩 bundled = [ - "implements" + "cin_implements" ] # 各个独立的特性 # -# 具体接口实现:OpenNARS、ONA、NARS-Python、OpenJunars、PyNARS…… -implements = [] +# 具体接口实现: +# ✅OpenNARS +# ✅ONA +# ✅PyNARS +# ✅NARS-Python(不稳定) +# ✅OpenJunars(不稳定) +cin_implements = [] diff --git a/README.md b/README.md index 81dd3ad..92e1a06 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 -[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)的**运行时** +[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)的**运行时**及[CIN](#cin-computer-implement-of-nars)启动器 - 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) -- 🎯为「非公理虚拟机模型」提供程序实现 -- 🎯为各CIN实现**统一输入输出**形式 +- ✨为「非公理虚拟机模型」提供程序实现 +- ✨统一各[CIN](#cin-computer-implement-of-nars)的**输入输出**形式,聚合使用各大NARS实现 ## 概念 @@ -22,7 +22,26 @@ 🔗参考[**NAVM.jl**的对应部分](https://github.com/ARCJ137442/navm.jl?tab=readme-ov-file#commonnarsese) +## 各CIN对接情况 + +🕒最后更新时间:【2024-03-25 14:10:36】 + +| CIN | 实现方法 | 进程安全 | 输入转译 | 输出转译 | +| :---------- | :---------: | :--: | :--: | :--: | +| OpenNARS | `java -jar` | ✅ | ✅ | 🚧 | +| ONA | 直接启动exe | ✅ | ✅ | 🚧 | +| PyNARS | `python -m` | ✅ | 🚧 | 🚧 | +| NARS-Python | 直接启动exe | ❓ | 🚧 | 🚧 | +| OpenJunars | `julia` | ✅ | ❌ | ❌ | + +注: + +- 🚧输入输出转译功能仍然在从[BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl)迁移 +- ❓NARS-Python的exe界面可能会在终止后延时关闭 +- ❌基于`julia`启动OpenJunars脚本`launch.jl`时,对「输出捕获」尚未有成功记录 + ## 参考 - [BabelNAR](https://github.com/ARCJ137442/BabelNAR.jl) +- [BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl) - [NAVM.rs](https://github.com/ARCJ137442/NAVM.rs) diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs new file mode 100644 index 0000000..4a929ee --- /dev/null +++ b/src/cin_implements/mod.rs @@ -0,0 +1,34 @@ +//! 对各CIN实现「非公理虚拟机」模型 +//! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 +//! +//! ! ⚠️关键问题:进程残留 +//! * ✅单元测试中利用`taskkill`初步解决 +//! * ❌【2024-03-25 13:36:30】集成测试`cargo t --all-features`中未能解决 +//! * ❗// ! ↑【少用乃至不用这条命令】 +//! +//! ? 【2024-03-25 12:48:08】如何兼顾「复用」「性能」与「简洁」 +//! * 📌复用:将OpenNARS、ONA抽象成「基于jar的启动逻辑」「基于exe的启动逻辑」等方式,以便后续重复使用 +//! * 📄case:目前ONA、NARS-Python都是基于exe的启动方式 +//! * 📌性能:避免过多的封装、粗暴复合导致的空间浪费 +//! * 📄case:「启动器套启动器」在尝试抽象出「exe启动器」时,因为「没法预先指定转译器」在「复用『设置转译器』函数」时 +//! * ❌不希望在「exe启动器」「jar启动器」中重复套【包含一长串函数闭包】 +//! * 📌简洁:代码简明易懂,方便调用方使用 +//! * 📄case:期望能有形如`ONA::new(path).launch()`的语法 +//! * 💭不希望出现「强行模拟」的情况,如`mod ONA {pub fn new(..) {..}}` +//! * ❌不希望因此再全小写/封装命名空间,如`impls::ona::new` +//! * ❓目前的问题:在Rust基于「特征」的组合式设计哲学下,如何进行兼顾三者的优秀设计 + +// OpenNARS +pub mod opennars; + +// ONA +pub mod ona; + +// NARS-Python +pub mod nars_python; + +// PyNARS +pub mod pynars; + +// OpenJunars +pub mod open_junars; diff --git a/src/cin_implements/nars_python/launcher.rs b/src/cin_implements/nars_python/launcher.rs new file mode 100644 index 0000000..0487ef4 --- /dev/null +++ b/src/cin_implements/nars_python/launcher.rs @@ -0,0 +1,48 @@ +//! NARS-Python运行时的启动器 +//! * 🎯允许NARS-Python对原先运行时特别配置功能,同时也支持为NARS-Python定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 + +use super::{input_translate, output_translate}; +use crate::runtime::{CommandVm, CommandVmRuntime}; +use navm::vm::VmLauncher; +use std::path::PathBuf; + +// ! NARS-Python作为一个独立的`main.exe`,没有默认的启动参数 + +/// NARS-Python运行时启动器 +/// * 🎯配置NARS-Python专有的东西 +/// * 🚩基于exe文件启动NARS-Python exe +/// * 🚩【2024-03-25 08:51:30】目前保留原有缩写的大小写风格,与OpenNARS、PyNARS一致 +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct NARSPython { + /// exe文件路径 + exe_path: PathBuf, +} + +// ! 🚩【2024-03-25 09:37:22】目前暂时不提取至「VmExe」:参考`impl_runtime`根目录说明 + +impl NARSPython { + /// 构造函数 + pub fn new(exe_path: impl Into) -> Self { + Self { + // 转换为路径 + exe_path: exe_path.into(), + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for NARSPython { + fn launch(self) -> CommandVmRuntime { + // 构造指令,并启动虚拟机 + CommandVm::new(self.exe_path) + // * 🚩固定的「输入输出转换器」 + .input_translator(input_translate) + .output_translator(output_translate) + // 🔥启动 + .launch() + } +} + +// ! 单元测试见[`super`] diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs new file mode 100644 index 0000000..9eee0cd --- /dev/null +++ b/src/cin_implements/nars_python/mod.rs @@ -0,0 +1,40 @@ +//! 「非公理虚拟机」的NARS-Python运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::{test::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; + use navm::vm::{VmLauncher, VmRuntime}; + + #[test] + fn test() { + // 从别的地方获取exe路径 + let exe_path = EXE_PATH_NARS_PYTHON; + // 一行代码启动NARS-Python + let vm = NARSPython::new(exe_path).launch(); + // 运行专有测试 + _test_nars_python(vm) + } + + /// 测试/NARS-Python + pub(crate) fn _test_nars_python(vm: CommandVmRuntime) { + // TODO: 实际的测试代码 + + // 等待四秒钟,让exe的界面显示出来 + std::thread::sleep(std::time::Duration::from_secs(4)); + + // 终止虚拟机运行时 + vm.terminate().expect("无法终止虚拟机"); + } +} diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs new file mode 100644 index 0000000..fc475d8 --- /dev/null +++ b/src/cin_implements/nars_python/translators.rs @@ -0,0 +1,63 @@ +//! NARS-Python在「命令行运行时」的转译器 +//! * 🎯维护与NARS-Python exe的交互 +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 + +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use util::ResultS; + +/// NARS-Python的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「NARS-Python输入」 +/// +/// TODO: ⚠️其有一种不同的语法,需要细致解析 +pub fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + // ! NARS-Python Shell同样是自动步进的 + Cmd::CYC(n) => n.to_string(), + // 其它类型 + _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + }; + // 转译 + Ok(content) +} + +/// NARS-Python的「输出转译」函数 +/// * 🎯用于将NARS-Python Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content: String) -> ResultS { + // 根据冒号分隔一次,然后得到「头部」 + let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + // 根据「头部」生成输出 + let output = match &*head { + // TODO: 有待适配 + "answer" => Output::ANSWER { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "derived" => Output::OUT { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "input" => Output::IN { content }, + "exe" => Output::EXE { + content_raw: content, + // TODO: 有待捕获转译 + operation: Operation::new("UNKNOWN", [].into_iter()), + }, + "err" | "error" => Output::ERROR { + description: content, + }, + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) +} diff --git a/src/impl_runtime/ona/launcher.rs b/src/cin_implements/ona/launcher.rs similarity index 100% rename from src/impl_runtime/ona/launcher.rs rename to src/cin_implements/ona/launcher.rs diff --git a/src/impl_runtime/ona/mod.rs b/src/cin_implements/ona/mod.rs similarity index 100% rename from src/impl_runtime/ona/mod.rs rename to src/cin_implements/ona/mod.rs diff --git a/src/impl_runtime/ona/translators.rs b/src/cin_implements/ona/translators.rs similarity index 93% rename from src/impl_runtime/ona/translators.rs rename to src/cin_implements/ona/translators.rs index 993ef64..bcb81e6 100644 --- a/src/impl_runtime/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -48,11 +48,6 @@ pub fn output_translate(content: String) -> ResultS { narsese: None, }, "input" => Output::IN { content }, - "anticipate" => Output::ANTICIPATE { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, - }, "exe" => Output::EXE { content_raw: content, // TODO: 有待捕获转译 diff --git a/src/cin_implements/open_junars/launcher.rs b/src/cin_implements/open_junars/launcher.rs new file mode 100644 index 0000000..92a0630 --- /dev/null +++ b/src/cin_implements/open_junars/launcher.rs @@ -0,0 +1,61 @@ +//! Julia模块 启动器 +//! * 📌OpenJunars运行时的启动器 +//! * 🎯允许OpenJunars对原先运行时特别配置功能,同时也支持为OpenJunars定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 +//! * 🚩通过`julia`运行`.jl`脚本启动 + +use super::{input_translate, output_translate}; +use crate::runtime::{CommandVm, CommandVmRuntime}; +use navm::vm::VmLauncher; +use std::{path::PathBuf, process::Command}; + +/// 启动Julia运行时的命令 +const COMMAND_JULIA: &str = "julia"; + +/// ! Julia启动脚本无需附加参数 + +/// OpenJunars运行时启动器 +/// * 🎯配置OpenJunars专有的东西 +/// * 🎯以Julia模块形式启动OpenJunars +/// * 📌没有内置的「音量」配置 +/// * 🚩【2024-03-25 08:55:07】基于Julia模块文件启动OpenJunars +/// * 默认预置指令:``julia [`.jl`脚本文件路径]`` +/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct VmJulia { + /// Julia脚本文件路径 + jl_path: PathBuf, +} + +/// 兼容性别名 +#[doc(alias = "VmJulia")] +pub type OpenJunars = VmJulia; + +impl VmJulia { + pub fn new(jl_path: impl Into) -> Self { + Self { + // 转换为路径 + jl_path: jl_path.into(), + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for VmJulia { + fn launch(self) -> CommandVmRuntime { + // 构造指令 + let mut command = Command::new(COMMAND_JULIA); + command.arg(self.jl_path); + + // 构造并启动虚拟机 + CommandVm::from_io_process(command.into()) + // * 🚩固定的「输入输出转换器」 + .input_translator(input_translate) + .output_translator(output_translate) + // 🔥启动 + .launch() + } +} + +// ! 单元测试见[`super`] diff --git a/src/cin_implements/open_junars/mod.rs b/src/cin_implements/open_junars/mod.rs new file mode 100644 index 0000000..3f0899c --- /dev/null +++ b/src/cin_implements/open_junars/mod.rs @@ -0,0 +1,55 @@ +//! 「非公理虚拟机」的OpenJunars运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + #![allow(unused)] + + use super::*; + use crate::runtime::{test::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; + use narsese::conversion::string::impl_lexical::shortcuts::*; + use navm::{ + cmd::Cmd, + vm::{VmLauncher, VmRuntime}, + }; + + #[test] + fn test() { + // 从别的地方获取jl路径 + let jl_path = JL_PATH_OPEN_JUNARS; + // 一行代码启动OpenJunars + let vm = OpenJunars::new(jl_path).launch(); + // 运行专有测试 + // ! ❌【2024-03-25 13:56:21】目前无法截取到Julia运行时输出,弃用 + // _test_opennars(vm) + _test_open_junars(vm) + } + + /// 测试/OpenJunars + pub(crate) fn _test_open_junars(mut vm: CommandVmRuntime) { + // ! ❌【2024-03-25 13:55:57】无效:似乎无法截取到Julia运行时输出 + + // vm.input_cmd(Cmd::NSE(nse_task!( B>.))) + // .expect("无法输入指令"); + + // // 等待四秒钟,让Junars启动 + // std::thread::sleep(std::time::Duration::from_secs(1)); + + // vm.input_cmd(Cmd::NSE(nse_task!( B>.))) + // .expect("无法输入指令"); + // std::thread::sleep(std::time::Duration::from_secs(6)); + + // 终止虚拟机运行时 + vm.terminate().expect("无法终止虚拟机"); + } +} diff --git a/src/cin_implements/open_junars/translators.rs b/src/cin_implements/open_junars/translators.rs new file mode 100644 index 0000000..4aeffc0 --- /dev/null +++ b/src/cin_implements/open_junars/translators.rs @@ -0,0 +1,66 @@ +//! OpenJunars在「命令行运行时」的转译器 +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 +//! +//! TODO: 🚧自OpenNARS复制而来,一些地方需要特别适配 + +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use util::ResultS; + +/// OpenJunars的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「OpenJunars Shell输入」 +pub fn input_translate(cmd: Cmd) -> ResultS { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + Cmd::CYC(n) => format!(":c {n}"), + // 其它类型 + // * 📌【2024-03-24 22:57:18】基本足够支持 + _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + }; + // 转译 + Ok(content) +} + +/// OpenJunars的「输出转译」函数 +/// * 🎯用于将OpenJunars Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content: String) -> ResultS { + // 根据冒号分隔一次,然后得到「头部」 + let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + // 根据「头部」生成输出 + let output = match &*head { + "answer" => Output::ANSWER { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "out" => Output::OUT { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "in" => Output::IN { content }, + "anticipate" => Output::ANTICIPATE { + content_raw: content, + // TODO: 有待捕获转译 + narsese: None, + }, + "exe" => Output::EXE { + content_raw: content, + // TODO: 有待捕获转译 + operation: Operation::new("UNKNOWN", [].into_iter()), + }, + "err" | "error" => Output::ERROR { + description: content, + }, + _ => Output::OTHER { content }, + }; + // 返回 + Ok(output) +} diff --git a/src/impl_runtime/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs similarity index 96% rename from src/impl_runtime/opennars/launcher.rs rename to src/cin_implements/opennars/launcher.rs index febc28f..b91c4b9 100644 --- a/src/impl_runtime/opennars/launcher.rs +++ b/src/cin_implements/opennars/launcher.rs @@ -96,12 +96,12 @@ impl VmLauncher for VmJava { // 🔥启动 .launch(); // 设置初始音量 - self.initial_volume.inspect(|volume| { + if let Some(volume) = self.initial_volume { // 输入指令,并在执行错误时打印信息 - if let Err(e) = vm.input_cmd(Cmd::VOL(*volume)) { + if let Err(e) = vm.input_cmd(Cmd::VOL(volume)) { println!("无法设置初始音量「{volume}」:{e}"); } - }); + }; // 返回 vm } diff --git a/src/impl_runtime/opennars/mod.rs b/src/cin_implements/opennars/mod.rs similarity index 100% rename from src/impl_runtime/opennars/mod.rs rename to src/cin_implements/opennars/mod.rs diff --git a/src/impl_runtime/opennars/translators.rs b/src/cin_implements/opennars/translators.rs similarity index 100% rename from src/impl_runtime/opennars/translators.rs rename to src/cin_implements/opennars/translators.rs diff --git a/src/impl_runtime/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs similarity index 95% rename from src/impl_runtime/pynars/launcher.rs rename to src/cin_implements/pynars/launcher.rs index 7de99d3..648c825 100644 --- a/src/impl_runtime/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -12,8 +12,8 @@ use std::{path::PathBuf, process::Command}; /// 启动Python运行时的命令 const COMMAND_PYTHON: &str = "python"; -/// jar文件启动的默认指令参数 -/// * 🎯默认预置指令:`python -m -jar [.jar文件路径]` +/// 启动Python模块的默认指令参数 +/// * 🎯默认预置指令:`python -m [当前工作目录下的Python模块]` const COMMAND_ARGS_PYTHON: [&str; 1] = ["-m"]; /// PyNARS运行时启动器 diff --git a/src/impl_runtime/pynars/mod.rs b/src/cin_implements/pynars/mod.rs similarity index 64% rename from src/impl_runtime/pynars/mod.rs rename to src/cin_implements/pynars/mod.rs index 8fe325c..6bacfed 100644 --- a/src/impl_runtime/pynars/mod.rs +++ b/src/cin_implements/pynars/mod.rs @@ -1,6 +1,10 @@ //! 「非公理虚拟机」的PyNARS运行时 //! * 🚩只提供「一行启动」的功能封装 //! * 🎯无需自行配置「输入输出转译器」 +//! +//! * ❌【2024-03-25 13:00:14】目前无法在Rust侧解决「杀死子进程后,Python继续输出无关信息」的问题 +//! * 📄主要形式:子进程结束后打印错误堆栈,输出`OSError: [Errno 22] Invalid argument` +//! * ❗无法被Rust捕获,可能是Python运行时的问题(输出未链接到管道) // 转译器 util::mod_and_pub_use! { @@ -22,7 +26,7 @@ mod tests { // 从别的地方获取Python模块根目录、模块自身路径 let root_path = MODULE_ROOT_PYNARS; let module_path = MODULE_PATH_PYNARS; - // 一行代码启动PyNARS | python -m pynars.Console @ "..\..\PyNARS-dev" + // 一行代码启动PyNARS | `python -m pynars.Console` @ "..\..\PyNARS-dev" let vm = PyNARS::new(root_path, module_path).launch(); // 直接复用之前对PyNARS的测试 _test_pynars(vm) diff --git a/src/impl_runtime/pynars/translators.rs b/src/cin_implements/pynars/translators.rs similarity index 100% rename from src/impl_runtime/pynars/translators.rs rename to src/cin_implements/pynars/translators.rs diff --git a/src/impl_runtime/mod.rs b/src/impl_runtime/mod.rs deleted file mode 100644 index b20f63a..0000000 --- a/src/impl_runtime/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! 对各CIN实现「非公理虚拟机」模型 -//! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 -//! TODO: 支持各大CIN - -// OpenNARS -pub mod opennars; - -// ONA -pub mod ona; - -// NARS-Python -// TODO: 具体实现 - -// PyNARS -pub mod pynars; - -// OpenJunars -// TODO: 具体实现 diff --git a/src/lib.rs b/src/lib.rs index f5081f8..58ad3ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,5 +17,5 @@ pub mod tools; // 可选模块 // util::feature_pub_mod_and_reexport! { // 运行时实现 - "implements" => impl_runtime + "cin_implements" => cin_implements } diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 05112dc..ca419aa 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -1,4 +1,15 @@ -//! 封装一个简单的「交互式输入输出」 +//! 一个简单的「IO子进程」类型 +//! +//! ## 功能 +//! +//! * ✅封装「标准IO读写」「进程通信」「线程阻塞」等逻辑 +//! * ✨支持「输出侦听」与「输出通道」两种输出处理方式 +//! +//! ## 疑难问题 +//! +//! * ❗进程残留:可能在调用`kill`方法后,子进程并未真正被杀死 +//! * 🚩【2024-03-25 13:29:14】目前解决方案:调用系统`taskkill`指令,利用进程id强制终止 +//! * ⚠️【2024-03-25 13:32:50】 use std::{ ffi::OsStr, @@ -396,6 +407,8 @@ impl IoProcessManager { /// * 🚩设置终止信号,通知子线程(以及标准IO)终止 /// * 🚩调用[`Child::kill`]方法,终止子进程 /// * ⚠️将借走自身所有权,终止并销毁自身 + /// + /// * ❓不稳定:有时会导致「野进程」的情况 pub fn kill(mut self) -> ResultS<()> { // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑:无法脱开对`self`的借用 // ! 📌更具体而言:对其中两个线程`thread_write_in`、`thread_read_out`的部分借用 @@ -422,6 +435,21 @@ impl IoProcessManager { // * ⚠️这意味着「输出侦听器」仍然能对其输出产生响应 // 杀死子进程 // + // * 【2024-03-25 13:22:12】尝试使用`taskkill`强制杀死子进程(不会影响后边的kill) + // * 📌启动失败也不影响:主要目的是在系统层面防止「进程残留」 + // * 📄【2024-03-25 13:23:41】目前对OpenNARS有效(Java进程得到了有效终止) + // * ❗可能是系统特定的 + if let Ok(child) = Command::new("taskkill") + // 强制终止id为子进程id的进程 + .args(["-F", "-PID", &self.process.id().to_string()]) + .spawn() + { + // 等待taskkill杀死子进程 + if let Err(err) = child.wait_with_output() { + println!("指令执行失败!{err:?}"); + } + } + // * 🚩通用:调用`Child`对象的`kill`方法 self.process.kill().transform_err_string() } } diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index abe9baa..aca5810 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -93,11 +93,13 @@ pub(crate) mod test { // 定义一系列路径 // * 📌【2024-03-25 09:28:36】本地调试:都从根目录`BabelNAR.rs`开始 // * 📄退一级到开发目录,再退一级到各NARS下载目录 + pub const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; pub const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; pub const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; - pub const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; pub const MODULE_ROOT_PYNARS: &str = r"..\..\PyNARS-dev"; pub const MODULE_PATH_PYNARS: &str = r"pynars.ConsolePlus"; + pub const EXE_PATH_NARS_PYTHON: &str = r"..\..\NARS-executables\main.exe"; + pub const JL_PATH_OPEN_JUNARS: &str = r"..\..\OpenJunars\launch.jl"; const COMMAND_JAVA: &str = "java"; const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; @@ -227,7 +229,7 @@ pub(crate) mod test { // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 let mut input_cmd_and_await = |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); - input_cmd_and_await(Cmd::VOL(0), ""); + // ! ✅【2024-03-25 13:54:36】现在内置进OpenNARS启动器,不再需要执行此操作 input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), " B>."); input_cmd_and_await(Cmd::NSE(nse_task!( C>.)), " C>."); input_cmd_and_await(Cmd::NSE(nse_task!( C>?)), " C>?"); From a24c12be1fee48c1f0c1bf8ece1ab42d071ace8c Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 26 Mar 2024 02:07:39 +0800 Subject: [PATCH 18/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=A4=A7=E5=B9=85?= =?UTF-8?q?=E6=8E=A8=E8=BF=9BCIN=E8=BD=AC=E8=AF=91=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=EF=BC=8C=E9=83=A8=E5=88=86=E8=A7=A3=E5=86=B3=E3=80=8C=E6=96=B9?= =?UTF-8?q?=E8=A8=80=E9=97=AE=E9=A2=98=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🏗️OpenNARS:基本实现除「特定方言语法」外的Narsese解析 - 顺带一并更新实用库,新增并应用「管道宏」`pipe!` 🏗️ONA:同OpenNARS ✅NARS-Python:基本实现输入转译,但未有成功截获到输出 - 为此引入可选依赖`lazy_static` --- .vscode/settings.json | 4 +- Cargo.lock | 52 +++++- Cargo.toml | 13 +- README.md | 5 +- src/cin_implements/nars_python/dialect.rs | 36 ++++ src/cin_implements/nars_python/mod.rs | 55 +++++- src/cin_implements/nars_python/translators.rs | 17 +- src/cin_implements/ona/translators.rs | 164 ++++++++++++++++-- src/cin_implements/opennars/translators.rs | 121 ++++++++++--- src/runtime/command_vm/runtime.rs | 2 +- 10 files changed, 411 insertions(+), 58 deletions(-) create mode 100644 src/cin_implements/nars_python/dialect.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 2262744..9cc6258 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,9 @@ "editor.formatOnSave": true, "cSpell.words": [ "Errno", + "rfind", "runpy", - "traceback" + "traceback", + "tstate" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9e6fa75..d64b2b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "babel_nar" -version = "0.5.0" +version = "0.6.0" dependencies = [ + "lazy_static", "nar_dev_utils", "narsese", "navm", + "regex", ] [[package]] @@ -17,9 +28,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + [[package]] name = "nar_dev_utils" -version = "0.17.0" +version = "0.19.0" [[package]] name = "narsese" @@ -31,8 +48,37 @@ dependencies = [ [[package]] name = "navm" -version = "0.2.0" +version = "0.3.0" dependencies = [ "nar_dev_utils", "narsese", ] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" diff --git a/Cargo.toml b/Cargo.toml index ebe83a1..1dcc00e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,19 @@ [package] name = "babel_nar" -version = "0.5.0" +version = "0.6.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +regex = "1.10.4" + +# 用于实现「静态含闭包常量」 +# * 🎯初次引入:NARS-Python 方言格式 +# * 🔗:https://stackoverflow.com/questions/73260997/rust-boxed-closure-in-global-variable +[dependencies.lazy_static] +version = "1.4.0" +optional = true [dependencies.nar_dev_utils] # 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` @@ -38,7 +46,8 @@ features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 default = [] # 大杂烩 bundled = [ - "cin_implements" + "cin_implements", + "lazy_static" # 这个「词法Narsese」也在用 ] # 各个独立的特性 # # 具体接口实现: diff --git a/README.md b/README.md index 92e1a06..8e8850d 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,14 @@ ## 各CIN对接情况 -🕒最后更新时间:【2024-03-25 14:10:36】 +🕒最后更新时间:【2024-03-26 01:43:28】 | CIN | 实现方法 | 进程安全 | 输入转译 | 输出转译 | | :---------- | :---------: | :--: | :--: | :--: | | OpenNARS | `java -jar` | ✅ | ✅ | 🚧 | | ONA | 直接启动exe | ✅ | ✅ | 🚧 | | PyNARS | `python -m` | ✅ | 🚧 | 🚧 | -| NARS-Python | 直接启动exe | ❓ | 🚧 | 🚧 | +| NARS-Python | 直接启动exe | ❓ | ✅ | ❌ | | OpenJunars | `julia` | ✅ | ❌ | ❌ | 注: @@ -39,6 +39,7 @@ - 🚧输入输出转译功能仍然在从[BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl)迁移 - ❓NARS-Python的exe界面可能会在终止后延时关闭 - ❌基于`julia`启动OpenJunars脚本`launch.jl`时,对「输出捕获」尚未有成功记录 +- ❌目前对NARS-Python的「输出捕获」尚未有成功记录 ## 参考 diff --git a/src/cin_implements/nars_python/dialect.rs b/src/cin_implements/nars_python/dialect.rs new file mode 100644 index 0000000..7150131 --- /dev/null +++ b/src/cin_implements/nars_python/dialect.rs @@ -0,0 +1,36 @@ +//! 用于存储NARS-Python的方言格式 +//! * 🚩【2024-03-26 01:31:44】本质上就是陈述括弧改变了而已 + +use narsese::conversion::string::impl_lexical::{ + format_instances::create_format_ascii, NarseseFormat, +}; +use narsese::lexical::Narsese; + +#[cfg(feature = "lazy_static")] +lazy_static::lazy_static! { + /// NARS-Python的方言格式 + /// * 🚩仅在`lazy_static`启用时开启 + pub static ref FORMAT: NarseseFormat = create_format_nars_python(); +} + +pub fn create_format_nars_python() -> NarseseFormat { + let mut f = create_format_ascii(); + f.statement.brackets = ("(".into(), ")".into()); + f +} + +/// 获取NARS-Python的方言格式 +/// * 🚩使用`lazy_static`定义的静态常量,无需重复初始化 +/// * 🚩否则总是创建一个新的「Narsese格式」 +#[cfg(feature = "lazy_static")] +pub fn format_in_nars_python(narsese: &Narsese) -> String { + FORMAT.format_narsese(narsese) +} + +/// 获取NARS-Python的方言格式 +/// * 🚩否则总是创建一个新的「Narsese格式」 +#[cfg(not(feature = "lazy_static"))] +pub fn format_in_nars_python(narsese: &Narsese) -> String { + // 创建格式,并立即格式化Narsese + create_format_nars_python().format_narsese(narsese) +} diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index 9eee0cd..b572e96 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -4,6 +4,8 @@ // 转译器 util::mod_and_pub_use! { + // 方言(Narsese格式) + dialect // 转译器 translators // 启动器 @@ -15,7 +17,11 @@ util::mod_and_pub_use! { mod tests { use super::*; use crate::runtime::{test::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; - use navm::vm::{VmLauncher, VmRuntime}; + use narsese::conversion::string::impl_lexical::shortcuts::*; + use navm::{ + cmd::Cmd, + vm::{VmLauncher, VmRuntime}, + }; #[test] fn test() { @@ -28,13 +34,54 @@ mod tests { } /// 测试/NARS-Python - pub(crate) fn _test_nars_python(vm: CommandVmRuntime) { - // TODO: 实际的测试代码 + /// * ❌【2024-03-26 01:42:14】目前还没法真正截取到输出 + pub(crate) fn _test_nars_python(mut vm: CommandVmRuntime) { + // 等待几秒钟,让exe的界面显示出来 + std::thread::sleep(std::time::Duration::from_secs(2)); + + vm.input_cmd(Cmd::NSE(nse_task!( B>.))) + .expect("无法输入NAVM指令"); + vm.input_cmd(Cmd::NSE(nse_task!( C>.))) + .expect("无法输入NAVM指令"); + vm.input_cmd(Cmd::NSE(nse_task!( C>?))) + .expect("无法输入NAVM指令"); - // 等待四秒钟,让exe的界面显示出来 std::thread::sleep(std::time::Duration::from_secs(4)); // 终止虚拟机运行时 vm.terminate().expect("无法终止虚拟机"); } + + /* // ! 【2024-03-26 01:44:27】NARS-Python输出崩溃的内容: + running 1 test + Started process: 65784 + Traceback (most recent call last): + File "main.py", line 122, in + File "main.py", line 118, in main + File "NARS.py", line 54, in run + File "NARS.py", line 63, in do_working_cycle + File "InputChannel.py", line 74, in process_pending_sentence + File "InputChannel.py", line 87, in process_sentence + File "NARS.py", line 247, in process_task + File "NARS.py", line 323, in process_question_task + File "NARS.py", line 491, in process_sentence_semantic_inference + File "NARSInferenceEngine.py", line 73, in do_semantic_inference_two_premise + AttributeError: 'NoneType' object has no attribute 'frequency' + [38676] Failed to execute script 'main' due to unhandled exception! + Fatal Python error: could not acquire lock for <_io.BufferedReader name=''> at interpreter shutdown, possibly due to daemon threads + Python runtime state: finalizing (tstate=00000213FB525D60) + + Thread 0x00017e0c (most recent call first): + File "InputChannel.py", line 25 in get_user_input + File "threading.py", line 870 in run + File "threading.py", line 932 in _bootstrap_inner + File "threading.py", line 890 in _bootstrap + + Current thread 0x00013918 (most recent call first): + + 成功: 已终止 PID 为 65784 的进程。 + test cin_implements::nars_python::tests::test ... ok + + test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 6.56s + */ } diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs index fc475d8..ed1065c 100644 --- a/src/cin_implements/nars_python/translators.rs +++ b/src/cin_implements/nars_python/translators.rs @@ -3,21 +3,30 @@ //! * 📌基于命令行输入输出的字符串读写 //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 +//! +//! ## 输出样例 +//! +//! * `EXE: ^left based on desirability: 0.9` +//! * `PROCESSED GOAL: SentenceID:2081:ID ({SELF} --> [SAFE])! :|: %1.00;0.03%from SentenceID:2079:ID ({SELF} --> [SAFE])! :|: %1.00;0.00%,SentenceID:2080:ID ({SELF} --> [SAFE])! :|: %1.00;0.02%,` +//! * `PREMISE IS TRUE: ((*,{SELF}) --> ^right)` +//! * `PREMISE IS SIMPLIFIED ({SELF} --> [SAFE]) FROM (&|,({SELF} --> [SAFE]),((*,{SELF}) --> ^right))` +use narsese::lexical::Narsese; use navm::{ cmd::Cmd, output::{Operation, Output}, }; use util::ResultS; +use super::format_in_nars_python; + /// NARS-Python的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「NARS-Python输入」 -/// -/// TODO: ⚠️其有一种不同的语法,需要细致解析 pub fn input_translate(cmd: Cmd) -> ResultS { let content = match cmd { - // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) - Cmd::NSE(..) => cmd.tail(), + // 使用「末尾」将自动格式化任务(可兼容「空预算」的形式) + // * ✅【2024-03-26 01:44:49】目前采用特定的「方言格式」解决格式化问题 + Cmd::NSE(narsese) => format_in_nars_python(&Narsese::Task(narsese)), // CYC指令:运行指定周期数 // ! NARS-Python Shell同样是自动步进的 Cmd::CYC(n) => n.to_string(), diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index bcb81e6..f222f49 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -3,12 +3,28 @@ //! * 📌基于命令行输入输出的字符串读写 //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 +//! +//! ## 输出样例 +//! +//! * `Input: <<(* x) --> ^left> ==> A>. Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000` +//! * `Derived: <<(* x) --> ^left> ==> good>>. Priority=0.245189 Truth: frequency=1.000000, confidence=0.810000` +//! * `Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.447514` +//! * `Answer: None.` +//! * `^deactivate executed with args` +//! * `^left executed with args (* {SELF})` +//! * `^left executed with args ({SELF} * x)` +//! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` +use narsese::{ + conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, + lexical::Narsese, +}; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::ResultS; +use regex::Regex; +use util::{pipe, ResultBoost, ResultS}; /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 @@ -32,32 +48,146 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// ONA的「输出转译」函数 /// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content: String) -> ResultS { +pub fn output_translate(content_raw: String) -> ResultS { // 根据冒号分隔一次,然后得到「头部」 - let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + let (head, tail) = content_raw.split_once(':').unwrap_or(("", "")); // 根据「头部」生成输出 - let output = match &*head { + let output = match head.to_lowercase().as_str() { "answer" => Output::ANSWER { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, + // 先提取其中的Narsese | ⚠️借用了`content_raw` + // * 🚩ONA会输出带有误导性的`Answer: None.` + // * 看起来是回答,实际上不是 + narsese: match content_raw.contains("Answer: None.") { + true => None, + false => try_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + }, + // 然后传入整个内容 + content_raw, }, "derived" => Output::OUT { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: try_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + // 然后传入整个内容 + content_raw, }, - "input" => Output::IN { content }, - "exe" => Output::EXE { - content_raw: content, - // TODO: 有待捕获转译 - operation: Operation::new("UNKNOWN", [].into_iter()), + "input" => Output::IN { + content: content_raw, }, "err" | "error" => Output::ERROR { - description: content, + description: content_raw, + }, + // * 🚩对于「操作」的特殊语法 + _ if content_raw.contains("executed") => Output::EXE { + operation: parse_operation_ona(&content_raw), + content_raw, + }, + // 若是连续的「头部」⇒识别为「未归类」类型 + _ if !content_raw.contains(char::is_whitespace) => Output::UNCLASSIFIED { + r#type: head.into(), + content: content_raw, + }, + // 其它 + _ => Output::OTHER { + content: content_raw, }, - _ => Output::OTHER { content }, }; // 返回 Ok(output) } + +/// (ONA)从原始输出中解析操作 +pub fn parse_operation_ona(content_raw: &str) -> Operation { + println!("截获到操作:{content_raw:?}"); + Operation { + // TODO: 有待分析 + head: "UNKNOWN".into(), + params: vec![content_raw.into()], + } +} + +/// (尝试)从输出中解析出Narsese +pub fn try_parse_narsese(tail: &str) -> ResultS { + // 提取并解析Narsese字符串 + pipe! { + tail + // 重整 + => #{&} + => reform_output_to_narsese + // 解析 + => #{&} + => parse_narsese_ona + // 转换错误 | 解析失败⇒返回错误信息 | 返回None + => .transform_err(|err| format!("输出「OUT」解析失败:{err}")) + } +} + +/// 重整ONA输出到合法Narsese +/// * 🎯通过「重整→正确解析」的方式,实现初步输出解析兼容 +/// * 🚩【2024-03-25 21:38:39】目前使用正则表达式[`regex`]库 +/// * 🚩【2024-03-25 21:38:52】目前仅基于正则表达式做文本替换 +/// * 📌参数`tail`不附带`Answer:`等部分 +fn reform_output_to_narsese(out: &str) -> String { + // 构造正则表达式(实现中只会编译一次) // + // 匹配ONA输出中的「真值」 + let re_truth = Regex::new(r"Truth:\s*frequency=([0-9.]+),\s*confidence=([0-9.]+)").unwrap(); + // 匹配ONA输出的「创建时间」 + let re_creation_t = Regex::new(r"creationTime=([0-9.]+)\s+").unwrap(); + + // 两次替换 // + pipe! { + out + // 重建真值表达式 + => [re_truth.replace_all](_, |caps: ®ex::Captures<'_>| { + // * 第`0`个是正则表达式匹配的整个内容 + let f = &caps[1]; + let c = &caps[2]; + // 重建CommonNarsese合法的真值 + format!("%{f};{c}%") + }) + => #{&} + // 删去非必要的「创建时间」 + => [re_creation_t.replace_all](_, |_: ®ex::Captures<'_>| "") + // 返回字符串 // + => .into() + } +} + +/// 以OpenNARS的语法解析出Narsese +/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 +/// +/// TODO: 兼容ONA的方言语法 +/// * 📌重点在「用空格分隔乘积词项/中缀情形」的语法 +/// * 📄`(* {SELF})` +/// * 📄`({SELF} * x)` +fn parse_narsese_ona(target: &str) -> ParseResult { + FORMAT_ASCII.parse(target) +} + +/// 单元测试 +#[cfg(test)] +mod test { + use super::*; + use util::asserts; + + /// 测试/正则重整 + #[test] + fn test_regex_reform() { + let inp = " C>. creationTime=2 Truth: frequency=1.000000, confidence=0.447514"; + let s = pipe! { + inp + => reform_output_to_narsese + => .chars() + => .into_iter() + => .filter(|c|!c.is_whitespace()) + // => .collect::() // ! ❌暂时不支持「完全限定语法」 + } + .collect::(); + + // 断言 + asserts! { + s => "C>.%1.000000;0.447514%", + } + } +} diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 8019b0a..2231c1d 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -4,12 +4,27 @@ //! * 📌基于命令行输入输出的字符串读写 //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 +//! +//! ## 输出样例 +//! +//! * `IN: B>. %1.00;0.90% {-1 : (-7995324758518856376,0)}` +//! * `OUT: B>. %1.00;0.90% {-1 : (-7995324758518856376,0)}` +//! * `Answer: C>. %1.00;0.81% {1584885193 : (-7995324758518856376,0);(-7995324758518856376,1)}` +//! * `EXE: $1.00;0.99;1.00$ ^left([{SELF}])=null` +//! * `ANTICIPATE: <{SELF} --> [SAFE]>` +//! * `CONFIRM: <{SELF} --> [SAFE]><{SELF} --> [SAFE]>` +//! * `DISAPPOINT: <{SELF} --> [SAFE]>` +//! * `Executed based on: $0.2904;0.1184;0.7653$ <(&/,<{SELF} --> [right_blocked]>,+7,(^left,{SELF}),+55) =/> <{SELF} --> [SAFE]>>. %1.00;0.53%` +use narsese::{ + conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, + lexical::Narsese, +}; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::ResultS; +use util::{ResultBoost, ResultS}; /// OpenNARS的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「OpenNARS Shell输入」 @@ -33,37 +48,95 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// OpenNARS的「输出转译」函数 /// * 🎯用于将OpenNARS Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content: String) -> ResultS { +pub fn output_translate(content_raw: String) -> ResultS { // 根据冒号分隔一次,然后得到「头部」 - let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + let (head, tail) = content_raw.split_once(':').unwrap_or(("", &content_raw)); // 根据「头部」生成输出 - let output = match &*head { - "answer" => Output::ANSWER { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, + let output = match &*head.to_uppercase() { + "IN" => Output::IN { + content: content_raw, + }, + "OUT" => { + // 返回 + Output::OUT { + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: strip_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + // 然后传入整个内容 + content_raw, + } + } + "ANSWER" => Output::ANSWER { + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: strip_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + // 然后传入整个内容 + content_raw, + }, + "EXE" => Output::EXE { + operation: parse_operation_opennars(&content_raw), + content_raw, }, - "out" => Output::OUT { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, + "ANTICIPATE" => Output::ANTICIPATE { + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: strip_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + // 然后传入整个内容 + content_raw, }, - "in" => Output::IN { content }, - "anticipate" => Output::ANTICIPATE { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, + "ERR" | "ERROR" => Output::ERROR { + description: content_raw, }, - "exe" => Output::EXE { - content_raw: content, - // TODO: 有待捕获转译 - operation: Operation::new("UNKNOWN", [].into_iter()), + // * 🚩利用OpenNARS常见输出「全大写」的特征,兼容「confirm」与「disappoint」 + upper if head == upper => Output::UNCLASSIFIED { + r#type: head.to_string(), + content: content_raw, }, - "err" | "error" => Output::ERROR { - description: content, + // 其它 + _ => Output::OTHER { + content: content_raw, }, - _ => Output::OTHER { content }, }; // 返回 Ok(output) } + +/// 在OpenNARS输出中解析出「NARS操作」 +/// +/// TODO: 结合正则表达式进行解析 +pub fn parse_operation_opennars(content_raw: &str) -> Operation { + // use regex::Regex; + Operation { + // TODO: 有待捕获转译 + head: "UNKNOWN".into(), + params: vec![content_raw.into()], + } +} + +/// 切分尾部字符串,并(尝试)从中解析出Narsese +fn strip_parse_narsese(tail: &str) -> ResultS { + // 提取并解析Narsese字符串 + let narsese = tail + // 去尾 + .rfind('{') + // 截取 & 解析 + .map(|right_index| parse_narsese_opennars(&tail[..right_index])); + // 提取解析结果 + match narsese { + // 解析成功⇒提取 & 返回 + Some(Ok(narsese)) => Ok(narsese), + // 解析失败⇒打印错误日志 | 返回None + Some(Err(err)) => Err(format!("输出「OUT」解析失败:{err}")), + // 未找到括号的情况 + None => Err("输出「OUT」解析失败:未找到「{」".into()), + } +} + +/// 以OpenNARS的语法解析出Narsese +/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 +/// +/// TODO: 兼容OpenNARS特有之语法 +/// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` +fn parse_narsese_opennars(input: &str) -> ParseResult { + FORMAT_ASCII.parse(input) +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index aca5810..79bc4a8 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -118,7 +118,7 @@ pub(crate) mod test { // 展示输出 match &output { // 特别显示「回答」 - Output::ANSWER { content_raw, .. } => println!("捕获到回答!内容:{content_raw}"), + Output::ANSWER { .. } => println!("捕获到回答!内容:{output:?}"), _ => println!("捕获到其它输出!内容:{output:?}"), } // 包含⇒结束 From 62b5a1a4d366aab565dba7875568eb9cbd9feda1 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 26 Mar 2024 02:28:16 +0800 Subject: [PATCH 19/59] =?UTF-8?q?feat:=20:sparkles:=20PyNARS=E8=BE=93?= =?UTF-8?q?=E5=87=BA=20=E5=88=9D=E6=AD=A5=E8=BD=AC=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅初步实现PyNARS输出的类型识别 🚧内部Narsese的识别尚在进行中 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cin_implements/ona/translators.rs | 2 +- src/cin_implements/pynars/translators.rs | 49 ++++++++++++++++++++++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d64b2b9..6d88d9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "babel_nar" -version = "0.6.0" +version = "0.7.0" dependencies = [ "lazy_static", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index 1dcc00e..b18d28d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.6.0" +version = "0.7.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index f222f49..73ff541 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -148,7 +148,7 @@ fn reform_output_to_narsese(out: &str) -> String { }) => #{&} // 删去非必要的「创建时间」 - => [re_creation_t.replace_all](_, |_: ®ex::Captures<'_>| "") + => [re_creation_t.replace_all](_, "") // 返回字符串 // => .into() } diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index def5b0f..ffd4356 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -8,7 +8,7 @@ use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::ResultS; +use util::{pipe, ResultS}; /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 @@ -30,14 +30,57 @@ pub fn input_translate(cmd: Cmd) -> ResultS { Ok(content) } +/// 尝试获取输出类型(「头」文本) +fn try_get_output_type(inp: &str) -> Option { + use regex::Regex; + // ! `\e` => `\u{1b}` + let re = Regex::new(r"\u{1b}\[[0-9;]*m").unwrap(); + // let inp = "\u{1b}[48;2;110;10;10m 0.78 \u{1b}[49m\u{1b}[48;2;10;41;10m 0.25 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[33mOUT :\u{1b}[39mC>. %1.000;0.810%\r\n"; + // 三个预算+一个头 + let re2 = Regex::new(r"([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+(\w+)\s*:").unwrap(); + let replaced = pipe! { + inp + => [re.replace_all](_, "") + => .to_string() + }; + let _ = " 0.78 0.25 0.90 OUT :C>. %1.000;0.810%\r\n"; + dbg!(&replaced); + let captured = dbg!(pipe! { + replaced + => #{&} + => [re2.captures](_) + }); + captured.map(|c| c[4].to_string()) +} + /// ONA的「输出转译」函数 /// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 +/// # * 去除其中的ANSI转义序列,如:`\e[39m` # 并去除前后多余空格 +/// local actual_line::String = strip(replace(line, r"\e\[[0-9;]*m" => "")) +/// #= 去除后样例: +/// * `0.70 0.25 0.60 OUT :<(*, x)-->^left>>. %1.000;0.200%` +/// * INFO : Loading RuleMap ... +/// * EXE :<(*, x)-->^left> = $0.016;0.225;0.562$ <(*, x)-->^left>! %1.000;0.125% {None: 3, 1, 2} +/// * EXE :<(*, 1, 2, 3)-->^left> = $0.000;0.225;0.905$ <(*, 1, 2, 3)-->^left>! %1.000;0.287% {None: 2, 1, 0} +/// * EXE :<(*, {SELF}, [good])-->^f> = $0.026;0.450;0.905$ <(*, {SELF}, [good])-->^f>! %1.000;0.810% {None: 2, 1} +/// =# +/// +/// # * 特殊处理「信息」"INFO":匹配「INFO」开头的行 样例:`INFO : Loading RuleMap ...` pub fn output_translate(content: String) -> ResultS { // 根据冒号分隔一次,然后得到「头部」 - let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); + let head = pipe! { + &content + => try_get_output_type + => .map(|s|s.to_lowercase()) + }; + // 取切片 | ❌不能使用闭包,因为闭包无法返回引用 + let head = match &head { + Some(s) => s, + None => "", + }; // 根据「头部」生成输出 - let output = match &*head { + let output = match head { "answer" => Output::ANSWER { content_raw: content, // TODO: 有待捕获转译 From 176b45201e12d8963ff6211b0de44d102ce73775 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:10:55 +0800 Subject: [PATCH 20/59] =?UTF-8?q?refactor:=20:recycle:=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=9A=E5=BC=95=E5=85=A5=E3=80=8C=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E8=BD=AC=E6=8D=A2=E5=99=A8=E7=BB=84=E3=80=8D?= =?UTF-8?q?=E3=80=8C=E5=91=BD=E4=BB=A4=E7=94=9F=E6=88=90=E5=99=A8=E3=80=8D?= =?UTF-8?q?=E7=9A=84=E6=A6=82=E5=BF=B5=EF=BC=8C=E6=8F=90=E5=8F=96=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E5=90=AF=E5=8A=A8=E5=99=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚧尝试将ONA和NARS-Python提取成「带参数exe启动命令生成器」;尝试分别从OpenNARS、PyNARS、OpenJunars提取出Java、Python、Julia启动器 --- .vscode/settings.json | 1 + src/cin_implements/common/java.rs | 79 ++++++++++++++++ src/cin_implements/common/julia.rs | 48 ++++++++++ src/cin_implements/common/mod.rs | 5 ++ src/cin_implements/common/python.rs | 59 ++++++++++++ src/cin_implements/mod.rs | 5 +- src/cin_implements/ona/launcher.rs | 33 +++---- .../{open_junars => openjunars}/launcher.rs | 36 +++----- .../{open_junars => openjunars}/mod.rs | 0 .../translators.rs | 0 src/cin_implements/opennars/launcher.rs | 83 +++++------------ src/cin_implements/pynars/launcher.rs | 43 +++------ src/cin_implements/utils/mod.rs | 33 +++++++ src/process_io/io_process.rs | 24 ++--- src/runtime/command_vm/api.rs | 19 ---- .../command_vm/api/command_generator.rs | 11 +++ src/runtime/command_vm/api/mod.rs | 10 +++ src/runtime/command_vm/api/translators.rs | 89 +++++++++++++++++++ src/runtime/command_vm/launcher.rs | 41 +++++---- src/runtime/command_vm/runtime.rs | 2 +- 20 files changed, 436 insertions(+), 185 deletions(-) create mode 100644 src/cin_implements/common/java.rs create mode 100644 src/cin_implements/common/julia.rs create mode 100644 src/cin_implements/common/mod.rs create mode 100644 src/cin_implements/common/python.rs rename src/cin_implements/{open_junars => openjunars}/launcher.rs (65%) rename src/cin_implements/{open_junars => openjunars}/mod.rs (100%) rename src/cin_implements/{open_junars => openjunars}/translators.rs (100%) create mode 100644 src/cin_implements/utils/mod.rs delete mode 100644 src/runtime/command_vm/api.rs create mode 100644 src/runtime/command_vm/api/command_generator.rs create mode 100644 src/runtime/command_vm/api/mod.rs create mode 100644 src/runtime/command_vm/api/translators.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 9cc6258..bdc3091 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "editor.formatOnSave": true, "cSpell.words": [ "Errno", + "openjunars", "rfind", "runpy", "traceback", diff --git a/src/cin_implements/common/java.rs b/src/cin_implements/common/java.rs new file mode 100644 index 0000000..f6c016e --- /dev/null +++ b/src/cin_implements/common/java.rs @@ -0,0 +1,79 @@ +//! Java jar启动器 +//! * 🎯通用于任何基于jar文件的CIN,不仅仅是OpenNARS +//! * 🎯封装「NAVM运行时启动过程」中有关「Java启动环境配置」的部分 +//! * 🚩从jar文件启动NARS +//! * 🚩【2024-03-27 15:31:02】取消「初始音量」的特化配置,将其变成一个「命令行参数生成器」而非独立的「启动器」 + +use crate::runtime::CommandGenerator; +use std::{path::PathBuf, process::Command}; + +/// 启动Java运行时的命令 +const COMMAND_JAVA: &str = "java"; + +/// jar文件启动的默认指令参数 +/// * 🎯默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` +/// * 🚩实际上"-Xmx1024m"非必要 +const COMMAND_ARGS_JAVA: [&str; 1] = ["-jar"]; + +/// Java运行时启动配置参数:初始堆大小/最小堆大小 +#[inline(always)] +fn command_arg_xms(size: usize) -> String { + format!("-Xms{size}m") +} + +/// Java运行时启动配置参数:最大堆大小 +#[inline(always)] +fn command_arg_xmx(size: usize) -> String { + format!("-Xmx{size}m") +} + +/// Java jar启动器 +/// * 🎯以Java运行时专有形式启动虚拟机运行时 +/// * 📄基于jar文件启动OpenNARS Shell +/// * 默认预置指令:`java -jar [.jar文件路径] [..其它jar启动参数]` +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandGeneratorJava { + /// jar文件路径 + /// * 📌必须有 + jar_path: PathBuf, + /// Java运行时的初始堆大小/最小堆大小 + /// * 📄在Java指令中的参数:`-Xms[数值]m` + /// * 🚩可能没有:此时不会附加参数 + min_heap_size: Option, + /// Java运行时的最大堆大小 + /// * 📄在Java指令中的参数:`-Xmx[数值]m` + /// * 🚩可能没有:此时不会附加参数 + max_heap_size: Option, +} + +impl CommandGeneratorJava { + /// 构造函数 + pub fn new(jar_path: impl Into) -> Self { + Self { + // 转换为路径 + jar_path: jar_path.into(), + // 其它全是`None` + ..Default::default() + } + } +} + +/// 根据自身生成命令 +impl CommandGenerator for CommandGeneratorJava { + fn generate_command(&self) -> Command { + // 构造指令 + let mut command_java = Command::new(COMMAND_JAVA); + // * 📝这里的`args`、`arg都返回的可变借用。。 + command_java.args(COMMAND_ARGS_JAVA).arg(&self.jar_path); + + // 选择性添加参数 + if let Some(size) = self.min_heap_size { + command_java.arg(command_arg_xms(size)); + } + if let Some(size) = self.max_heap_size { + command_java.arg(command_arg_xmx(size)); + } + + command_java + } +} diff --git a/src/cin_implements/common/julia.rs b/src/cin_implements/common/julia.rs new file mode 100644 index 0000000..971587a --- /dev/null +++ b/src/cin_implements/common/julia.rs @@ -0,0 +1,48 @@ +//! Julia 模块启动器 +//! * 🎯通用于任何基于Julia源码的CIN,不仅仅是OpenJunars +//! * 🎯封装「NAVM运行时启动过程」中有关「Julia启动环境配置」的部分 +//! * 🚩从Julia脚本(`.jl`)启动NARS + +use crate::runtime::CommandGenerator; +use std::{path::PathBuf, process::Command}; + +/// 启动Julia运行时的命令 +const COMMAND_JULIA: &str = "julia"; + +/// ! Julia启动脚本无需附加参数 + +/// OpenJunars运行时启动器 +/// * 🎯配置OpenJunars专有的东西 +/// * 🎯以Julia模块形式启动OpenJunars +/// * 📌没有内置的「音量」配置 +/// * 🚩【2024-03-25 08:55:07】基于Julia模块文件启动OpenJunars +/// * 默认预置指令:``julia [`.jl`脚本文件路径]`` +/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandGeneratorJulia { + /// Julia脚本文件路径 + jl_path: PathBuf, +} + +impl CommandGeneratorJulia { + pub fn new(jl_path: impl Into) -> Self { + Self { + // 转换为路径 + jl_path: jl_path.into(), + } + } +} + +/// 转换为Julia启动命令 +impl CommandGenerator for CommandGeneratorJulia { + fn generate_command(&self) -> Command { + // 构造指令 + let mut command_julia = Command::new(COMMAND_JULIA); + + // 填入路径参数 + command_julia.arg(&self.jl_path); + + // 返回 + command_julia + } +} diff --git a/src/cin_implements/common/mod.rs b/src/cin_implements/common/mod.rs new file mode 100644 index 0000000..3322bcc --- /dev/null +++ b/src/cin_implements/common/mod.rs @@ -0,0 +1,5 @@ +util::pub_mod_and_pub_use! { + java + python + julia +} diff --git a/src/cin_implements/common/python.rs b/src/cin_implements/common/python.rs new file mode 100644 index 0000000..2b58b94 --- /dev/null +++ b/src/cin_implements/common/python.rs @@ -0,0 +1,59 @@ +//! Python模块 启动器 +//! * 🎯通用于任何基于Python源码的CIN,不仅仅是PyNARS +//! * 🎯封装「NAVM运行时启动过程」中有关「Python启动环境配置」的部分 +//! * 🚩从Python模块(`.py`脚本)启动NARS + +use crate::runtime::CommandGenerator; +use std::{path::PathBuf, process::Command}; + +/// 启动Python运行时的命令 +const COMMAND_PYTHON: &str = "python"; + +/// 启动Python模块的默认指令参数 +/// * 🎯默认预置指令:`python -m [当前工作目录下的Python模块]` +const COMMAND_ARGS_PYTHON: [&str; 1] = ["-m"]; + +/// Python启动命令生成器 +/// * 🎯以Python模块形式生成启动命令 +/// * 🚩【2024-03-25 08:55:07】基于Python模块文件启动NARS +/// * 默认预置指令:`python -m [Python模块根目录] [Python模块路径]` +/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandGeneratorPython { + /// 根目录 + /// * 📄`root/home/dev/pynars` + root_path: PathBuf, + + /// 模块路径 + /// * 📌相对根目录而言 + /// * 📄`pynars.Console` + /// * 📄`root_path` + `pynars.Console` => `root_path/pynars/Console` + module_path: String, +} + +impl CommandGeneratorPython { + pub fn new(root_path: impl Into, module_path: &str) -> Self { + Self { + // 转换为路径 + root_path: root_path.into(), + // 转换为字符串 + module_path: module_path.to_string(), + } + } +} + +/// 启动到「命令行运行时」 +impl CommandGenerator for CommandGeneratorPython { + fn generate_command(&self) -> Command { + // 构造指令 + let mut command = Command::new(COMMAND_PYTHON); + command + // * 🚩设置指令工作目录 + // * 📝`python -m`无法自行指定所执行的工作目录,必须在`Command`中设置 + .current_dir(&self.root_path) // 以此设置当前工作目录 + .args(COMMAND_ARGS_PYTHON) + .arg(&self.module_path); + + command + } +} diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs index 4a929ee..527caa6 100644 --- a/src/cin_implements/mod.rs +++ b/src/cin_implements/mod.rs @@ -18,6 +18,9 @@ //! * ❌不希望因此再全小写/封装命名空间,如`impls::ona::new` //! * ❓目前的问题:在Rust基于「特征」的组合式设计哲学下,如何进行兼顾三者的优秀设计 +mod common; +mod utils; + // OpenNARS pub mod opennars; @@ -31,4 +34,4 @@ pub mod nars_python; pub mod pynars; // OpenJunars -pub mod open_junars; +pub mod openjunars; diff --git a/src/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs index b8b4301..3501984 100644 --- a/src/cin_implements/ona/launcher.rs +++ b/src/cin_implements/ona/launcher.rs @@ -4,12 +4,16 @@ //! * ✨不同启动器可以启动到相同运行时 use super::{input_translate, output_translate}; -use crate::runtime::{CommandVm, CommandVmRuntime}; +use crate::{ + cin_implements::utils::{generate_command, generate_command_vm}, + runtime::CommandVmRuntime, +}; use navm::{ cmd::Cmd, vm::{VmLauncher, VmRuntime}, }; -use std::{path::PathBuf, process::Command}; +use std::path::PathBuf; +use util::pipe; /// ONA Shell启动的默认指令参数 /// * 🎯默认预置指令:`[.exe文件路径] shell` @@ -30,9 +34,7 @@ pub struct ONA { } // ! 🚩【2024-03-25 09:37:22】目前暂时不提取至「VmExe」:预置的`shell`参数需要被处理 -// /// 兼容性别名 -// #[doc(alias = "VmExe")] -// pub type OpenNARS = VmExe; +// * ✅【2024-03-27 16:07:48】现在通过作为工具的`generate_command`部分实现了代码复用 impl ONA { /// 构造函数 @@ -49,26 +51,25 @@ impl ONA { /// 启动到「命令行运行时」 impl VmLauncher for ONA { fn launch(self) -> CommandVmRuntime { - // 构造指令 - let mut command = Command::new(self.exe_path); - // * 📝这里的`args`、`arg都返回的可变借用。。 - command.args(COMMAND_ARGS_ONA); - // 构造并启动虚拟机 - let mut vm = CommandVm::from_io_process(command.into()) + let mut runtime = pipe! { + self.exe_path + // 构造指令 | 预置的指令参数 + => generate_command(_, None::, &COMMAND_ARGS_ONA) // * 🚩固定的「输入输出转换器」 - .input_translator(input_translate) - .output_translator(output_translate) + => generate_command_vm(_, (input_translate, output_translate)) // 🔥启动 - .launch(); + => .launch() + }; + // 选择性设置初始音量 if let Some(volume) = self.initial_volume { // 输入指令,并在执行错误时打印信息 - if let Err(e) = vm.input_cmd(Cmd::VOL(volume)) { + if let Err(e) = runtime.input_cmd(Cmd::VOL(volume)) { println!("无法设置初始音量「{volume}」:{e}"); } }; - vm + runtime } } diff --git a/src/cin_implements/open_junars/launcher.rs b/src/cin_implements/openjunars/launcher.rs similarity index 65% rename from src/cin_implements/open_junars/launcher.rs rename to src/cin_implements/openjunars/launcher.rs index 92a0630..3ae6fcd 100644 --- a/src/cin_implements/open_junars/launcher.rs +++ b/src/cin_implements/openjunars/launcher.rs @@ -1,19 +1,16 @@ -//! Julia模块 启动器 -//! * 📌OpenJunars运行时的启动器 +//! OpenJunars 启动器 //! * 🎯允许OpenJunars对原先运行时特别配置功能,同时也支持为OpenJunars定制配置 //! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 //! * ✨不同启动器可以启动到相同运行时 -//! * 🚩通过`julia`运行`.jl`脚本启动 +//! * 🚩通过[`CommandGeneratorJulia`]管理启动参数 use super::{input_translate, output_translate}; -use crate::runtime::{CommandVm, CommandVmRuntime}; +use crate::{ + cin_implements::common::CommandGeneratorJulia, + runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, +}; use navm::vm::VmLauncher; -use std::{path::PathBuf, process::Command}; - -/// 启动Julia运行时的命令 -const COMMAND_JULIA: &str = "julia"; - -/// ! Julia启动脚本无需附加参数 +use std::path::PathBuf; /// OpenJunars运行时启动器 /// * 🎯配置OpenJunars专有的东西 @@ -23,33 +20,28 @@ const COMMAND_JULIA: &str = "julia"; /// * 默认预置指令:``julia [`.jl`脚本文件路径]`` /// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct VmJulia { +pub struct OpenJunars { /// Julia脚本文件路径 - jl_path: PathBuf, + command_generator: CommandGeneratorJulia, } -/// 兼容性别名 -#[doc(alias = "VmJulia")] -pub type OpenJunars = VmJulia; - -impl VmJulia { +impl OpenJunars { pub fn new(jl_path: impl Into) -> Self { Self { // 转换为路径 - jl_path: jl_path.into(), + command_generator: CommandGeneratorJulia::new(jl_path), } } } /// 启动到「命令行运行时」 -impl VmLauncher for VmJulia { +impl VmLauncher for OpenJunars { fn launch(self) -> CommandVmRuntime { // 构造指令 - let mut command = Command::new(COMMAND_JULIA); - command.arg(self.jl_path); + let command = self.command_generator.generate_command(); // 构造并启动虚拟机 - CommandVm::from_io_process(command.into()) + CommandVm::from(command) // * 🚩固定的「输入输出转换器」 .input_translator(input_translate) .output_translator(output_translate) diff --git a/src/cin_implements/open_junars/mod.rs b/src/cin_implements/openjunars/mod.rs similarity index 100% rename from src/cin_implements/open_junars/mod.rs rename to src/cin_implements/openjunars/mod.rs diff --git a/src/cin_implements/open_junars/translators.rs b/src/cin_implements/openjunars/translators.rs similarity index 100% rename from src/cin_implements/open_junars/translators.rs rename to src/cin_implements/openjunars/translators.rs diff --git a/src/cin_implements/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs index b91c4b9..d0681c7 100644 --- a/src/cin_implements/opennars/launcher.rs +++ b/src/cin_implements/opennars/launcher.rs @@ -1,100 +1,60 @@ -//! Java jar启动器 -//! * 📌OpenNARS运行时的启动器 +//! OpenNARS 启动器 //! * 🎯允许OpenNARS对原先运行时特别配置功能,同时也支持为OpenNARS定制配置 -//! * 🚩从jar文件启动NARS //! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 //! * ✨不同启动器可以启动到相同运行时 +//! * 🚩通过[`CommandGeneratorJava`]管理启动参数 use super::{input_translate, output_translate}; -use crate::runtime::{CommandVm, CommandVmRuntime}; +use crate::{ + cin_implements::common::CommandGeneratorJava, + runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, +}; use navm::{ cmd::Cmd, vm::{VmLauncher, VmRuntime}, }; -use std::{path::PathBuf, process::Command}; - -/// 启动Java运行时的命令 -const COMMAND_JAVA: &str = "java"; - -/// jar文件启动的默认指令参数 -/// * 🎯默认预置指令:`java -Xmx1024m -jar [.jar文件路径]` -/// * 🚩实际上"-Xmx1024m"非必要 -const COMMAND_ARGS_JAVA: [&str; 1] = ["-jar"]; - -/// Java运行时启动配置参数:初始堆大小/最小堆大小 -#[inline(always)] -fn command_arg_xms(size: usize) -> String { - format!("-Xms{size}m") -} - -/// Java运行时启动配置参数:最大堆大小 -#[inline(always)] -fn command_arg_xmx(size: usize) -> String { - format!("-Xmx{size}m") -} +use std::path::PathBuf; -/// Java jar启动器 +/// OpenNARS Shell启动器 /// * 🎯配置OpenNARS专有的东西 -/// * 🎯以Java运行时专有形式启动OpenNARS /// * 🚩基于jar文件启动OpenNARS Shell -/// * 默认预置指令:`java -jar [.jar文件路径]` #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct VmJava { - /// jar文件路径 - /// * 📌必须有 - jar_path: PathBuf, +pub struct OpenNARS { + /// Java [`Command`]生成器 + /// * 📌必须有(包含jar文件路径) + command_generator: CommandGeneratorJava, /// NARS的初始音量 /// * 🚩可能没有:此时不会输入指令 initial_volume: Option, - /// Java运行时的初始堆大小/最小堆大小 - /// * 📄在Java指令中的参数:`-Xms[数值]m` - /// * 🚩可能没有:此时不会附加参数 - min_heap_size: Option, - /// Java运行时的最大堆大小 - /// * 📄在Java指令中的参数:`-Xmx[数值]m` - /// * 🚩可能没有:此时不会附加参数 - max_heap_size: Option, } -/// 兼容性别名 -#[doc(alias = "VmJava")] -pub type OpenNARS = VmJava; - -impl VmJava { +impl OpenNARS { /// 构造函数 pub fn new(jar_path: impl Into) -> Self { Self { - // 转换为路径 - jar_path: jar_path.into(), - // 其它全是`None` + // 传入路径 + command_generator: CommandGeneratorJava::new(jar_path), + // 其它沿用默认配置 ..Default::default() } } } /// 启动到「命令行运行时」 -impl VmLauncher for VmJava { +impl VmLauncher for OpenNARS { fn launch(self) -> CommandVmRuntime { // 构造指令 - let mut command_java = Command::new(COMMAND_JAVA); - // * 📝这里的`args`、`arg都返回的可变借用。。 - command_java.args(COMMAND_ARGS_JAVA).arg(self.jar_path); - - // 选择性添加参数 |设置初始音量 - if let Some(size) = self.min_heap_size { - command_java.arg(command_arg_xms(size)); - } - if let Some(size) = self.max_heap_size { - command_java.arg(command_arg_xmx(size)); - } + // * 🚩细致的Java参数配置,都外包给[`CommandGeneratorJava`] + let command_java = self.command_generator.generate_command(); // 构造并启动虚拟机 - let mut vm = CommandVm::from_io_process(command_java.into()) + let mut vm = CommandVm::from(command_java) // * 🚩固定的「输入输出转换器」 .input_translator(input_translate) .output_translator(output_translate) // 🔥启动 .launch(); + // 设置初始音量 if let Some(volume) = self.initial_volume { // 输入指令,并在执行错误时打印信息 @@ -102,6 +62,7 @@ impl VmLauncher for VmJava { println!("无法设置初始音量「{volume}」:{e}"); } }; + // 返回 vm } diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs index 648c825..b31cfc4 100644 --- a/src/cin_implements/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -3,18 +3,15 @@ //! * 🎯允许PyNARS对原先运行时特别配置功能,同时也支持为PyNARS定制配置 //! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 //! * ✨不同启动器可以启动到相同运行时 +//! * 🚩通过[`CommandGeneratorPython`]管理启动参数 use super::{input_translate, output_translate}; -use crate::runtime::{CommandVm, CommandVmRuntime}; +use crate::{ + cin_implements::common::CommandGeneratorPython, + runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, +}; use navm::vm::VmLauncher; -use std::{path::PathBuf, process::Command}; - -/// 启动Python运行时的命令 -const COMMAND_PYTHON: &str = "python"; - -/// 启动Python模块的默认指令参数 -/// * 🎯默认预置指令:`python -m [当前工作目录下的Python模块]` -const COMMAND_ARGS_PYTHON: [&str; 1] = ["-m"]; +use std::path::PathBuf; /// PyNARS运行时启动器 /// * 🎯配置PyNARS专有的东西 @@ -22,19 +19,10 @@ const COMMAND_ARGS_PYTHON: [&str; 1] = ["-m"]; /// * 📌没有内置的「音量」配置 /// * ⚠️该配置参考的是PyNARS的`ConsolePlus`模块 /// * 🚩【2024-03-25 08:55:07】基于Python模块文件启动PyNARS Shell -/// * 默认预置指令:`python -m [Python模块根目录] [Python模块路径]` -/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct VmPython { - /// 根目录 - /// * 📄`root/home/dev/pynars` - root_path: PathBuf, - - /// 模块路径 - /// * 📌相对根目录而言 - /// * 📄`pynars.Console` - /// * 📄`root_path` + `pynars.Console` => `root_path/pynars/Console` - module_path: String, + /// 命令生成器 + command_generator: CommandGeneratorPython, } /// 兼容性别名 @@ -44,10 +32,7 @@ pub type PyNARS = VmPython; impl VmPython { pub fn new(root_path: impl Into, module_path: &str) -> Self { Self { - // 转换为路径 - root_path: root_path.into(), - // 转换为字符串 - module_path: module_path.to_string(), + command_generator: CommandGeneratorPython::new(root_path, module_path), } } } @@ -56,16 +41,10 @@ impl VmPython { impl VmLauncher for VmPython { fn launch(self) -> CommandVmRuntime { // 构造指令 - let mut command = Command::new(COMMAND_PYTHON); - command - // * 🚩设置指令工作目录 - // * 📝`python -m`无法自行指定所执行的工作目录,必须在`Command`中设置 - .current_dir(self.root_path) // 以此设置当前工作目录 - .args(COMMAND_ARGS_PYTHON) - .arg(self.module_path); + let command = self.command_generator.generate_command(); // 构造并启动虚拟机 - CommandVm::from_io_process(command.into()) + CommandVm::from(command) // * 🚩固定的「输入输出转换器」 .input_translator(input_translate) .output_translator(output_translate) diff --git a/src/cin_implements/utils/mod.rs b/src/cin_implements/utils/mod.rs new file mode 100644 index 0000000..7f65e60 --- /dev/null +++ b/src/cin_implements/utils/mod.rs @@ -0,0 +1,33 @@ +use std::{ffi::OsStr, path::Path, process::Command}; + +use crate::runtime::{CommandVm, IoTranslators}; + +/// 根据配置统一生成[`Command`]对象 +/// * 📌「配置」的定义 +/// * exe路径(可能不直接是可执行文件的路径) +/// * 当前文件夹(设置命令启动时的工作目录) +/// * 命令行参数(可以为空) +pub fn generate_command( + exe_path: impl AsRef, + current_dir: Option>, + args: &[&str], +) -> Command { + // 构造指令 + let mut command = Command::new(exe_path); + + // 设置路径 + if let Some(current_dir) = current_dir { + command.current_dir(current_dir); + } + + // 设置附加参数 + // * 📝这里的`args`、`arg都返回的可变借用。。 + command.args(args); + + command +} + +/// 根据「输入输出转译器」构建[`CommandVm`]对象 +pub fn generate_command_vm(command: Command, translators: impl Into) -> CommandVm { + CommandVm::from(command).translators(translators) +} diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index ca419aa..fbdf8e1 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -48,17 +48,7 @@ impl IoProcess { pub fn new(program_path: impl AsRef) -> Self { // 实际上是构建了一个新[`Command`]对象 let command = Command::new(program_path); - Self::from_command(command) - } - - /// 构造函数/自[`Command`]对象 - /// * 🚩从[`Command`]对象构建实体 - /// * ✅这里的[`Command`]必定是未被启动的:Launch之后会变成[`Child`]类型 - pub fn from_command(command: Command) -> Self { - Self { - command, - out_listener: None, - } + Self::from(command) } /// 添加命令行参数 @@ -118,12 +108,16 @@ impl IoProcess { } /// 实现/从[`Command`]对象转换为[`IoProcess`] +/// * ✅这里的[`Command`]必定是未被启动的:Launch之后会变成[`Child`]类型 +/// * 📝即便一些地方没法使用`command.into()`,也可使用`IoProcess::from(command)` impl From for IoProcess { - /// 构造函数 - /// * 🚩从`Command`对象构造实体 - /// * 📌直接生成[`Command`]对象,无需额外配置 fn from(command: Command) -> Self { - Self::from_command(command) + Self { + // 置入命令 + command, + // 侦听器空置 + out_listener: None, + } } } diff --git a/src/runtime/command_vm/api.rs b/src/runtime/command_vm/api.rs deleted file mode 100644 index b9da8d1..0000000 --- a/src/runtime/command_vm/api.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! 定义有关「命令行虚拟机」的抽象API - -use navm::{cmd::Cmd, output::Output}; - -/// [`Cmd`]→进程输入 转译器 -/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 -/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 -/// * 📌要求线程稳定 -/// * 只有转译功能,没有其它涉及外部的操作(纯函数) -/// TODO: 在后续的「NSE指令输入」时,需要通过「自动将『空预算任务』作为语句输入」应对「`$$ A.`→`A.`」的情况 -/// * ⚠️转译有可能失败:此时返回并上报错误信息 -pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; - -/// 进程输出→[`Output`]转译器 -/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 -/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 -/// * 📌要求线程稳定 -/// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; diff --git a/src/runtime/command_vm/api/command_generator.rs b/src/runtime/command_vm/api/command_generator.rs new file mode 100644 index 0000000..fa88fd6 --- /dev/null +++ b/src/runtime/command_vm/api/command_generator.rs @@ -0,0 +1,11 @@ +//! 定义一个抽象特征,用于「命令行虚拟机」的命令参数/执行环境 自动生成 + +use std::process::Command; + +/// 命令生成器 +/// * 🎯主管[`Command`]对象的**模板化生成** +/// * 📄OpenNARS使用`java`命令生成器,允许在指定转译器的同时自定义Java启动参数 +pub trait CommandGenerator { + /// 通过自身内部参数,生成指令参数 + fn generate_command(&self) -> Command; +} diff --git a/src/runtime/command_vm/api/mod.rs b/src/runtime/command_vm/api/mod.rs new file mode 100644 index 0000000..c11066f --- /dev/null +++ b/src/runtime/command_vm/api/mod.rs @@ -0,0 +1,10 @@ +//! 定义有关「命令行虚拟机」的抽象API +//! * 【2024-03-26 22:33:19】总体想法:💡一个「转译器集成包」+「命令行参数生成器」⇒统一复用的「IO进程启动器」 +//! * 📌转译器集成包:用于 + +util::pub_mod_and_pub_use! { + // 转译器 + translators + // 命令行参数生成器 + command_generator +} diff --git a/src/runtime/command_vm/api/translators.rs b/src/runtime/command_vm/api/translators.rs new file mode 100644 index 0000000..459120e --- /dev/null +++ b/src/runtime/command_vm/api/translators.rs @@ -0,0 +1,89 @@ +use navm::{cmd::Cmd, output::Output}; +use util::ResultS; + +/// [`Cmd`]→进程输入 转译器 +/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 +/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 +/// * 📌要求线程稳定 +/// * 只有转译功能,没有其它涉及外部的操作(纯函数) +/// TODO: 在后续的「NSE指令输入」时,需要通过「自动将『空预算任务』作为语句输入」应对「`$$ A.`→`A.`」的情况 +/// * ⚠️转译有可能失败:此时返回并上报错误信息 +pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; + +/// 进程输出→[`Output`]转译器 +/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 +/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 +/// * 📌要求线程稳定 +/// * 只有转译功能,没有其它涉及外部的操作(纯函数) +pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; + +/// IO转换器配置 +/// * 🎯封装并简化其它地方的`translator: impl Fn(...) -> ... + ...`逻辑 +/// * 📝【2024-03-27 10:38:41】无论何时都不推荐直接用`impl Fn`作为字段类型 +/// * ⚠️直接使用会意味着「需要编译前确定类型」 +/// * ❌这会【非必要地】要求一些【不直接传入闭包】的「默认初始化」方法具有类型标注 +pub struct IoTranslators { + pub input_translator: Box, + pub output_translator: Box, +} + +impl IoTranslators { + /// 构造函数 + /// * 🎯基于位置参数构造结构体 + /// * 🎯无需在调用方引入`Box::new` + /// * 📌需要直接传入闭包(要求全局周期`'static`) + pub fn new(i: I, o: O) -> Self + where + I: Fn(Cmd) -> ResultS + Send + Sync + 'static, + O: Fn(String) -> ResultS + Send + Sync + 'static, + { + Self { + input_translator: Box::new(i), + output_translator: Box::new(o), + } + } +} + +impl Default for IoTranslators { + /// 构造一个默认的「转译器组合」 + /// * 🎯默认生成的转译器 + /// * 输入:直接将NAVM指令转换为字符串 + /// * 输出:直接把字符串纳入「其它」输出 + /// * 📝【2024-03-27 10:34:02】下方`IoTranslators`无法换成`Self` + /// * `Self`意味着其带有类型约束 + /// * 📝【2024-03-27 10:37:37】不能直接使用裸露的闭包对象 + /// * 每个闭包都有不同类型⇒必须强迫使用泛型 + /// * 使用泛型⇒难以定义通用的[`Self::default`]方法 + fn default() -> IoTranslators { + IoTranslators { + input_translator: Box::new(|cmd| Ok(cmd.to_string())), + output_translator: Box::new(|content| Ok(Output::OTHER { content })), + } + } +} + +/// 从二元组转换 +/// * 🎯用于后续参数传入[`IoTranslators`]时,可以用`impl Into`,并且仍允许类似位置参数的效果 +/// * case: `fn set_translators(translators: impl Into)` +/// * call: `set_translators((in_translator, out_translator))` +/// * 📄[`super::super::CommandVm::translators`] +impl From<(I, O)> for IoTranslators +where + I: Fn(Cmd) -> ResultS + Send + Sync + 'static, + O: Fn(String) -> ResultS + Send + Sync + 'static, +{ + fn from(value: (I, O)) -> Self { + Self::new(value.0, value.1) + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let _t1 = IoTranslators::default(); + } +} diff --git a/src/runtime/command_vm/launcher.rs b/src/runtime/command_vm/launcher.rs index 961c3b5..8e4e5f9 100644 --- a/src/runtime/command_vm/launcher.rs +++ b/src/runtime/command_vm/launcher.rs @@ -1,9 +1,9 @@ //! 命令行虚拟机(构建者) -use super::{InputTranslator, OutputTranslator}; +use super::{InputTranslator, IoTranslators, OutputTranslator}; use crate::process_io::IoProcess; use navm::{cmd::Cmd, output::Output}; -use std::ffi::OsStr; +use std::{ffi::OsStr, process::Command}; /// 命令行虚拟机(构建者) /// * 🎯配置化构造[`CommandVmRuntime`] @@ -26,23 +26,10 @@ impl CommandVm { /// * 📌直接生成[`IoProcess`]对象,无需额外配置 pub fn new(program_path: impl AsRef) -> Self { let io_process = IoProcess::new(program_path); - Self::from_io_process(io_process) + Self::from(io_process) } - /// 构造函数/自[`IoProcess`]对象 - /// * 🚩从[`IoProcess`]对象创建 - /// * ✅这里的[`IoProcess`]必定是未被启动的:Launch之后会变成其它类型 - pub fn from_io_process(io_process: IoProcess) -> Self { - Self { - // 指令 - io_process, - // 其它暂时置空 - input_translator: None, - output_translator: None, - } - } - - /// 配置/输入转换器 + /// 配置/输入转译器 /// * 💭何时Rust能给特征起别名。。 pub fn input_translator( mut self, @@ -52,7 +39,7 @@ impl CommandVm { self } - /// 配置/输出转换器 + /// 配置/输出转译器 pub fn output_translator( mut self, translator: impl Fn(String) -> Result + Send + Sync + 'static, @@ -60,15 +47,33 @@ impl CommandVm { self.output_translator = Some(Box::new(translator)); self } + + /// 配置/输入输出转译器组 + pub fn translators(self, translators: impl Into) -> Self { + // 一次实现俩 + let translators = translators.into(); + self.input_translator(translators.input_translator) + .output_translator(translators.output_translator) + } } /// 实现/从[`IoProcess`]对象转换为[`CommandVm`]对象 +/// * ✅这里的[`IoProcess`]必定是未被启动的:Launch之后会变成其它类型 impl From for CommandVm { fn from(io_process: IoProcess) -> Self { Self { + // IO进程 io_process, + // 其它所有置空 input_translator: None, output_translator: None, } } } + +/// 实现/从[`Command`]对象转换为[`CommandVm`]对象 +impl From for CommandVm { + fn from(command: Command) -> Self { + Self::from(IoProcess::from(command)) + } +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 79bc4a8..37f00ae 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -214,7 +214,7 @@ pub(crate) mod test { } // 构造并启动虚拟机 - let vm = CommandVm::from_io_process(command_java.into()) + let vm = CommandVm::from(command_java) // 输入转译器 .input_translator(input_translate) // 输出转译器 From 40f84324239c8af71151f4db28b47d4effaadd56 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:21:42 +0800 Subject: [PATCH 21/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=BC=95=E5=85=A5`a?= =?UTF-8?q?nyhow`=EF=BC=8C=E9=A6=96=E4=B8=AA=E5=91=BD=E4=BB=A4=E8=A1=8CCIN?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=99=A8=E7=A4=BA=E4=BE=8B=EF=BC=8C=E6=8B=9F?= =?UTF-8?q?=E5=BC=95=E7=94=A8`pest`=E5=81=9A=E6=96=B9=E8=A8=80=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 引入`anyhow`包,规范错误类型并弃用`util::ResultS` 2. 测试用「CIN启动器」:首个交互式终端跑通(🚧后续添加「CIN选择」与「自动CIN路径查找」) 3. 为各个CIN实现设置独立的feature,并对OpenNARS、ONA引入`pest`库(🚧后续做方言解析) --- .vscode/settings.json | 7 +- Cargo.lock | 83 +++++++++++++- Cargo.toml | 51 ++++++++- src/bin/cin_launcher.rs | 68 +++++++++++- src/cin_implements/common/mod.rs | 3 + src/cin_implements/nars_python/mod.rs | 11 +- src/cin_implements/nars_python/translators.rs | 13 +-- src/cin_implements/ona/dialect.rs | 24 +++++ src/cin_implements/ona/dialect_ona.pest | 17 +++ src/cin_implements/ona/mod.rs | 2 + src/cin_implements/ona/translators.rs | 47 ++++---- src/cin_implements/openjunars/translators.rs | 16 ++- src/cin_implements/opennars/dialect.rs | 22 ++++ src/cin_implements/opennars/mod.rs | 2 + src/cin_implements/opennars/translators.rs | 102 +++++++++++++----- src/cin_implements/pynars/translators.rs | 18 ++-- src/process_io/io_process.rs | 47 +++++--- src/runtime/command_vm/api/translators.rs | 69 ++++++++++-- src/runtime/command_vm/launcher.rs | 12 ++- src/runtime/command_vm/runtime.rs | 18 ++-- 20 files changed, 511 insertions(+), 121 deletions(-) create mode 100644 src/cin_implements/ona/dialect.rs create mode 100644 src/cin_implements/ona/dialect_ona.pest create mode 100644 src/cin_implements/opennars/dialect.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index bdc3091..4660e73 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,12 @@ "openjunars", "rfind", "runpy", + "thiserror", "traceback", "tstate" - ] + ], + "rust-analyzer.linkedProjects": [ + ".\\Cargo.toml", + // ".\\Cargo.toml" + ], } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d88d9a..3685a2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,14 +11,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + [[package]] name = "babel_nar" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "anyhow", "lazy_static", "nar_dev_utils", "narsese", "navm", + "pest", "regex", ] @@ -50,10 +58,40 @@ dependencies = [ name = "navm" version = "0.3.0" dependencies = [ + "anyhow", "nar_dev_utils", "narsese", ] +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "regex" version = "1.10.4" @@ -82,3 +120,46 @@ name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "syn" +version = "2.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index b18d28d..7be632e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,28 @@ [package] name = "babel_nar" -version = "0.7.0" +version = "0.8.0" edition = "2021" +[dependencies] + +[dependencies.anyhow] +version = "1.0.81" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -regex = "1.10.4" +# Rust版本的正则表达式 +# * 🎯用于解析提取NARS输出 +# * 📄OpenNARS、ONA、PyNARS +[dependencies.regex] +version = "1.10.4" +optional = true + +# Rust版本的PEG解析器 +# * 🎯用于对接一些NARS方言的解析 +# * 📄OpenNARS(操作语法)、ONA(中缀语法) +[dependencies.pest] +version = "2.7.8" +optional = true # 用于实现「静态含闭包常量」 # * 🎯初次引入:NARS-Python 方言格式 @@ -47,7 +63,7 @@ default = [] # 大杂烩 bundled = [ "cin_implements", - "lazy_static" # 这个「词法Narsese」也在用 + "lazy_static", # 这个「词法Narsese」也在用 ] # 各个独立的特性 # # 具体接口实现: @@ -56,4 +72,29 @@ bundled = [ # ✅PyNARS # ✅NARS-Python(不稳定) # ✅OpenJunars(不稳定) -cin_implements = [] +cin_implements = [ + "opennars", + "ona", + "pynars", + "nars_python", + "openjunars", +] +# ✅OpenNARS接口 +opennars = [ + "regex", + "pest", +] +# ✅ONA接口 +ona = [ + "regex", + "pest", +] +# ✅PyNARS接口 +pynars = [ + "regex", + # "pest", # ! 【2024-03-27 20:52:17】无需特别解析方言:其输出即为CommonNarsese +] +# ✅NARS-Python接口(不稳定) +nars_python = [] +# ✅OpenJunars接口(不稳定) +openjunars = [] diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs index 420a9d9..f662136 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher.rs @@ -5,7 +5,73 @@ //! * ✨自动查找(可能)可用的CIN可执行文件(文件搜索) //! * ✨自动启动并管理CIN //! TODO: 完成代码 +#![allow(unused)] + +use babel_nar::{ona::ONA, opennars::OpenNARS, runtime::CommandVmRuntime}; +use navm::{ + cmd::Cmd, + output::Output, + vm::{VmLauncher, VmRuntime}, +}; +use std::{fmt::Debug, io::stdin}; + +const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; +const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; + +/// 启动NARS +/// * 🚩【2024-03-27 18:55:07】目前就返回一个测试用的运行时 +fn get_nars() -> impl VmLauncher { + OpenNARS::new(TEST_PATH_OPENNARS) + // ONA::new(TEST_PATH_ONA) +} /// 主函数 /// TODO: 完成代码 -fn main() {} +fn main() { + // 不断开始🔥 + loop { + start(); + } +} + +/// 开始 +fn start() { + let nars = get_nars().launch(); + shell(nars); +} + +/// 打印错误 +fn println_error(e: &impl Debug) { + println!("{e:?}"); +} + +/// 交互式命令行 +fn shell(mut nars: CommandVmRuntime) { + let stdin = stdin(); + let mut input = String::new(); + let mut line; + 'main: while stdin.read_line(&mut input).is_ok() { + // 一行 + line = input.as_str(); + + // 非空⇒解析出NAVM指令,作为输入执行 + if !line.trim().is_empty() { + if let Ok(cmd) = Cmd::parse(line).inspect_err(println_error) { + let _ = nars.input_cmd(cmd).inspect_err(println_error); + } + } + + // 尝试拉取所有NAVM运行时输出 + while let Ok(Some(output)) = nars.try_fetch_output().inspect_err(println_error) { + println!("{output:?}"); + if let Output::TERMINATED { .. } = output { + println!("NAVM已终止运行,正在重启。。。"); + nars.terminate(); + break 'main; // ! 这个告诉Rust编译器,循环必将在此结束 + } + } + + // 清空缓冲区 + input.clear(); + } +} diff --git a/src/cin_implements/common/mod.rs b/src/cin_implements/common/mod.rs index 3322bcc..d16113b 100644 --- a/src/cin_implements/common/mod.rs +++ b/src/cin_implements/common/mod.rs @@ -1,3 +1,6 @@ +//! 所有对接CIN实现的共用模块 + +// 平铺导出元素 util::pub_mod_and_pub_use! { java python diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index b572e96..57b06e9 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -34,7 +34,16 @@ mod tests { } /// 测试/NARS-Python - /// * ❌【2024-03-26 01:42:14】目前还没法真正截取到输出 + /// 【2024-03-27 18:29:42】最近一次输出(NARS-Python控制台): + /// + /// ```text + /// IN: SentenceID:0:ID (A --> B). %1.00;0.90% + /// IN: SentenceID:1:ID (B --> C). %1.00;0.90% + /// IN: SentenceID:2:ID (A --> C)? + /// OUT: SentenceID:3:ID (A --> C). %1.00;0.81% + /// ``` + /// + /// ! ❌仍然无法截获其输出 pub(crate) fn _test_nars_python(mut vm: CommandVmRuntime) { // 等待几秒钟,让exe的界面显示出来 std::thread::sleep(std::time::Duration::from_secs(2)); diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs index ed1065c..7884275 100644 --- a/src/cin_implements/nars_python/translators.rs +++ b/src/cin_implements/nars_python/translators.rs @@ -11,18 +11,18 @@ //! * `PREMISE IS TRUE: ((*,{SELF}) --> ^right)` //! * `PREMISE IS SIMPLIFIED ({SELF} --> [SAFE]) FROM (&|,({SELF} --> [SAFE]),((*,{SELF}) --> ^right))` +use super::format_in_nars_python; +use crate::runtime::TranslateError; +use anyhow::Result; use narsese::lexical::Narsese; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::ResultS; - -use super::format_in_nars_python; /// NARS-Python的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「NARS-Python输入」 -pub fn input_translate(cmd: Cmd) -> ResultS { +pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 使用「末尾」将自动格式化任务(可兼容「空预算」的形式) // * ✅【2024-03-26 01:44:49】目前采用特定的「方言格式」解决格式化问题 @@ -31,7 +31,8 @@ pub fn input_translate(cmd: Cmd) -> ResultS { // ! NARS-Python Shell同样是自动步进的 Cmd::CYC(n) => n.to_string(), // 其它类型 - _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), }; // 转译 Ok(content) @@ -40,7 +41,7 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// NARS-Python的「输出转译」函数 /// * 🎯用于将NARS-Python Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content: String) -> ResultS { +pub fn output_translate(content: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); // 根据「头部」生成输出 diff --git a/src/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs new file mode 100644 index 0000000..8f29153 --- /dev/null +++ b/src/cin_implements/ona/dialect.rs @@ -0,0 +1,24 @@ +//! ONA方言 +//! * 🎯解析ONA输出,如 +//! * 📄以空格分隔的词项:`(* {SELF})` +//! * 📄`({SELF} * x)` +//! +//! TODO: 完成语法解析 + +use narsese::conversion::string::impl_lexical::{ + format_instances::FORMAT_ASCII, structs::ParseResult, +}; + +/// 使用[`pest`]将输入的「ONA方言」转换为「词法Narsese」 +/// 以ONA的语法解析出Narsese +/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 +/// +/// TODO: 兼容ONA的方言语法 +/// * 📌重点在「用空格分隔乘积词项/中缀情形」的语法 +/// * 📄`(* {SELF})` +/// * 📄`({SELF} * x)` +pub fn parse(input: &str) -> ParseResult { + FORMAT_ASCII.parse(input) + // #![allow(unused)] + // todo!("ONA方言!") +} diff --git a/src/cin_implements/ona/dialect_ona.pest b/src/cin_implements/ona/dialect_ona.pest new file mode 100644 index 0000000..ee8df5a --- /dev/null +++ b/src/cin_implements/ona/dialect_ona.pest @@ -0,0 +1,17 @@ +// TODO: 有待替换 +num = @{ int ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ int)? } +int = { ("+" | "-")? ~ ASCII_DIGIT+ } + +operation = _{ add | subtract | multiply | divide | power } +add = { "+" } +subtract = { "-" } +multiply = { "*" } +divide = { "/" } +power = { "^" } + +expr = { term ~ (operation ~ term)* } +term = _{ num | "(" ~ expr ~ ")" } + +calculation = _{ SOI ~ expr ~ EOI } + +WHITESPACE = _{ " " | "\t" } diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs index 51bfa18..571325b 100644 --- a/src/cin_implements/ona/mod.rs +++ b/src/cin_implements/ona/mod.rs @@ -8,6 +8,8 @@ util::mod_and_pub_use! { translators // 启动器 launcher + // 方言 | 【2024-03-27 18:42:50】使用`pest`库解析特殊语法 + dialect } /// 单元测试 diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 73ff541..ee4d36a 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -15,20 +15,19 @@ //! * `^left executed with args ({SELF} * x)` //! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` -use narsese::{ - conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, - lexical::Narsese, -}; +use super::dialect::parse as parse_narsese_ona; +use anyhow::Result; +use narsese::conversion::string::impl_lexical::structs::ParseResult; use navm::{ cmd::Cmd, output::{Operation, Output}, }; use regex::Regex; -use util::{pipe, ResultBoost, ResultS}; +use util::{if_return, pipe, ResultBoost}; /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 -pub fn input_translate(cmd: Cmd) -> ResultS { +pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) Cmd::NSE(..) => cmd.tail(), @@ -39,7 +38,13 @@ pub fn input_translate(cmd: Cmd) -> ResultS { Cmd::VOL(n) => format!("*volume={n}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("该指令类型暂不支持:{cmd:?}"), + ) + .into()) + } }; // 转译 Ok(content) @@ -48,7 +53,11 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// ONA的「输出转译」函数 /// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content_raw: String) -> ResultS { +pub fn output_translate(content_raw: String) -> Result { + // 特别处理:终止信号 + if_return! { + content_raw.contains("Test failed.") => Ok(Output::TERMINATED { description: content_raw }) + } // 根据冒号分隔一次,然后得到「头部」 let (head, tail) = content_raw.split_once(':').unwrap_or(("", "")); // 根据「头部」生成输出 @@ -87,6 +96,8 @@ pub fn output_translate(content_raw: String) -> ResultS { _ if !content_raw.contains(char::is_whitespace) => Output::UNCLASSIFIED { r#type: head.into(), content: content_raw, + // 不尝试捕获Narsese | 💭后续或许可以自动捕获? + narsese: None, }, // 其它 _ => Output::OTHER { @@ -102,13 +113,14 @@ pub fn parse_operation_ona(content_raw: &str) -> Operation { println!("截获到操作:{content_raw:?}"); Operation { // TODO: 有待分析 - head: "UNKNOWN".into(), - params: vec![content_raw.into()], + operator_name: "UNKNOWN".into(), + params: vec![], } } /// (尝试)从输出中解析出Narsese -pub fn try_parse_narsese(tail: &str) -> ResultS { +/// * ❌【2024-03-27 22:01:18】目前引入[`anyhow::Error`]会出问题:不匹配/未满足的特征 +pub fn try_parse_narsese(tail: &str) -> ParseResult { // 提取并解析Narsese字符串 pipe! { tail @@ -119,7 +131,7 @@ pub fn try_parse_narsese(tail: &str) -> ResultS { => #{&} => parse_narsese_ona // 转换错误 | 解析失败⇒返回错误信息 | 返回None - => .transform_err(|err| format!("输出「OUT」解析失败:{err}")) + // => .transform_err(|err| format!("输出「OUT」解析失败:{err}")) } } @@ -154,17 +166,6 @@ fn reform_output_to_narsese(out: &str) -> String { } } -/// 以OpenNARS的语法解析出Narsese -/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 -/// -/// TODO: 兼容ONA的方言语法 -/// * 📌重点在「用空格分隔乘积词项/中缀情形」的语法 -/// * 📄`(* {SELF})` -/// * 📄`({SELF} * x)` -fn parse_narsese_ona(target: &str) -> ParseResult { - FORMAT_ASCII.parse(target) -} - /// 单元测试 #[cfg(test)] mod test { diff --git a/src/cin_implements/openjunars/translators.rs b/src/cin_implements/openjunars/translators.rs index 4aeffc0..64c9624 100644 --- a/src/cin_implements/openjunars/translators.rs +++ b/src/cin_implements/openjunars/translators.rs @@ -5,15 +5,17 @@ //! //! TODO: 🚧自OpenNARS复制而来,一些地方需要特别适配 +use anyhow::Result; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::ResultS; + +use crate::runtime::TranslateError; /// OpenJunars的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「OpenJunars Shell输入」 -pub fn input_translate(cmd: Cmd) -> ResultS { +pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) Cmd::NSE(..) => cmd.tail(), @@ -21,7 +23,8 @@ pub fn input_translate(cmd: Cmd) -> ResultS { Cmd::CYC(n) => format!(":c {n}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), }; // 转译 Ok(content) @@ -30,7 +33,7 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// OpenJunars的「输出转译」函数 /// * 🎯用于将OpenJunars Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content: String) -> ResultS { +pub fn output_translate(content: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); // 根据「头部」生成输出 @@ -46,11 +49,6 @@ pub fn output_translate(content: String) -> ResultS { narsese: None, }, "in" => Output::IN { content }, - "anticipate" => Output::ANTICIPATE { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, - }, "exe" => Output::EXE { content_raw: content, // TODO: 有待捕获转译 diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs new file mode 100644 index 0000000..4fe244e --- /dev/null +++ b/src/cin_implements/opennars/dialect.rs @@ -0,0 +1,22 @@ +//! OpenNARS方言 +//! * 🎯解析OpenNARS输出,如 +//! * 📄以空格分隔的词项:`(* {SELF})` +//! * 📄`({SELF} * x)` +//! +//! TODO: 完成语法解析 + +use narsese::conversion::string::impl_lexical::{ + format_instances::FORMAT_ASCII, structs::ParseResult, +}; + +/// 以OpenNARS的语法解析出Narsese +/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 +/// +/// TODO: 兼容OpenNARS特有之语法 +/// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` +/// +/// TODO: 使用[`pest`]将输入的「OpenNARS方言」转换为「词法Narsese」 +pub fn parse(input: &str) -> ParseResult { + FORMAT_ASCII.parse(input) + // todo!("OpenNARS方言!") +} diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs index 56eb062..3e49713 100644 --- a/src/cin_implements/opennars/mod.rs +++ b/src/cin_implements/opennars/mod.rs @@ -8,6 +8,8 @@ util::mod_and_pub_use! { translators // 启动器 launcher + // 方言 + dialect } /// 单元测试 diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 2231c1d..cdb69cf 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -15,20 +15,22 @@ //! * `CONFIRM: <{SELF} --> [SAFE]><{SELF} --> [SAFE]>` //! * `DISAPPOINT: <{SELF} --> [SAFE]>` //! * `Executed based on: $0.2904;0.1184;0.7653$ <(&/,<{SELF} --> [right_blocked]>,+7,(^left,{SELF}),+55) =/> <{SELF} --> [SAFE]>>. %1.00;0.53%` +//! * `EXE: $0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null` -use narsese::{ - conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, - lexical::Narsese, -}; +use super::dialect::parse as parse_narsese_opennars; +use crate::runtime::TranslateError; +use anyhow::Result; +use narsese::lexical::{Narsese, Term}; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::{ResultBoost, ResultS}; +use regex::Regex; +use util::ResultBoost; /// OpenNARS的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「OpenNARS Shell输入」 -pub fn input_translate(cmd: Cmd) -> ResultS { +pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) Cmd::NSE(..) => cmd.tail(), @@ -39,7 +41,8 @@ pub fn input_translate(cmd: Cmd) -> ResultS { Cmd::VOL(n) => format!("*volume={n}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), }; // 转译 Ok(content) @@ -48,7 +51,7 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// OpenNARS的「输出转译」函数 /// * 🎯用于将OpenNARS Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content_raw: String) -> ResultS { +pub fn output_translate(content_raw: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let (head, tail) = content_raw.split_once(':').unwrap_or(("", &content_raw)); // 根据「头部」生成输出 @@ -74,15 +77,18 @@ pub fn output_translate(content_raw: String) -> ResultS { content_raw, }, "EXE" => Output::EXE { - operation: parse_operation_opennars(&content_raw), + operation: parse_operation_opennars(tail.trim_start()), content_raw, }, - "ANTICIPATE" => Output::ANTICIPATE { + // ! 🚩【2024-03-27 19:40:37】现在将ANTICIPATE降级到`UNCLASSIFIED` + "ANTICIPATE" => Output::UNCLASSIFIED { + // 指定的头部 + r#type: "ANTICIPATE".to_string(), // 先提取其中的Narsese | ⚠️借用了`content_raw` narsese: strip_parse_narsese(tail) .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), // 然后传入整个内容 - content_raw, + content: content_raw, }, "ERR" | "ERROR" => Output::ERROR { description: content_raw, @@ -91,6 +97,8 @@ pub fn output_translate(content_raw: String) -> ResultS { upper if head == upper => Output::UNCLASSIFIED { r#type: head.to_string(), content: content_raw, + // 默认不捕获Narsese + narsese: None, }, // 其它 _ => Output::OTHER { @@ -101,20 +109,67 @@ pub fn output_translate(content_raw: String) -> ResultS { Ok(output) } +#[test] +fn t() { + dbg!(parse_operation_opennars( + "$0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null" + )); +} + /// 在OpenNARS输出中解析出「NARS操作」 +/// * 📄`$0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null` +/// * 📌目前能提取出其中的预算值,但实际上还是需要 /// /// TODO: 结合正则表达式进行解析 -pub fn parse_operation_opennars(content_raw: &str) -> Operation { - // use regex::Regex; +/// TODO: 后续使用[`pest`]进行解析 +pub fn parse_operation_opennars(tail: &str) -> Operation { + // * 构建正则表达式(仅一次编译) + let r = Regex::new(r"(\$[0-9.;]+\$)\s*\^(\w+)\(\[(.*)\]\)=").unwrap(); + + // 构建返回值(参数) + let mut params = vec![]; + + // 提取输出中的字符串 + let c = r.captures(dbg!(tail)); + // let budget; + let operator_name; + let params_str; + if let Some(c) = c { + // 提取 + // budget = &c[1]; + operator_name = c[2].to_string(); + params_str = &c[3]; + // 尝试解析 + for param in params_str.split(", ") { + match parse_term_from_operation(param) { + Ok(term) => params.push(term), + // ? 【2024-03-27 22:29:43】↓是否要将其整合到一个日志系统中去 + Err(e) => println!("【ERR/EXE】在解析Narsese时出现错误:{e}"), + } + } + } else { + operator_name = String::new(); + } + + // 返回 Operation { - // TODO: 有待捕获转译 - head: "UNKNOWN".into(), - params: vec![content_raw.into()], + operator_name, + params, } } +/// 从操作参数中解析出Narsese词项 +fn parse_term_from_operation(term_str: &str) -> Result { + // 首先尝试解析出Narsese + let parsed = parse_narsese_opennars(term_str)?; + // 其次尝试将其转换成Narsese词项 + parsed + .try_into_term() + .transform_err(TranslateError::error_anyhow) +} + /// 切分尾部字符串,并(尝试)从中解析出Narsese -fn strip_parse_narsese(tail: &str) -> ResultS { +fn strip_parse_narsese(tail: &str) -> Result { // 提取并解析Narsese字符串 let narsese = tail // 去尾 @@ -126,17 +181,8 @@ fn strip_parse_narsese(tail: &str) -> ResultS { // 解析成功⇒提取 & 返回 Some(Ok(narsese)) => Ok(narsese), // 解析失败⇒打印错误日志 | 返回None - Some(Err(err)) => Err(format!("输出「OUT」解析失败:{err}")), + Some(Err(err)) => Err(TranslateError(format!("输出「OUT」解析失败:{err}")).into()), // 未找到括号的情况 - None => Err("输出「OUT」解析失败:未找到「{」".into()), + None => Err(TranslateError::from("输出「OUT」解析失败:未找到「{」").into()), } } - -/// 以OpenNARS的语法解析出Narsese -/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 -/// -/// TODO: 兼容OpenNARS特有之语法 -/// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` -fn parse_narsese_opennars(input: &str) -> ParseResult { - FORMAT_ASCII.parse(input) -} diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index ffd4356..3381612 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -4,15 +4,18 @@ //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 +use crate::runtime::TranslateError; +use anyhow::Result; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use util::{pipe, ResultS}; +use regex::Regex; +use util::pipe; /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 -pub fn input_translate(cmd: Cmd) -> ResultS { +pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) Cmd::NSE(..) => cmd.tail(), @@ -24,7 +27,8 @@ pub fn input_translate(cmd: Cmd) -> ResultS { Cmd::VOL(n) => format!("/volume {n}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(format!("该指令类型暂不支持:{cmd:?}")), + // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), }; // 转译 Ok(content) @@ -32,7 +36,6 @@ pub fn input_translate(cmd: Cmd) -> ResultS { /// 尝试获取输出类型(「头」文本) fn try_get_output_type(inp: &str) -> Option { - use regex::Regex; // ! `\e` => `\u{1b}` let re = Regex::new(r"\u{1b}\[[0-9;]*m").unwrap(); // let inp = "\u{1b}[48;2;110;10;10m 0.78 \u{1b}[49m\u{1b}[48;2;10;41;10m 0.25 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[33mOUT :\u{1b}[39mC>. %1.000;0.810%\r\n"; @@ -67,7 +70,7 @@ fn try_get_output_type(inp: &str) -> Option { /// =# /// /// # * 特殊处理「信息」"INFO":匹配「INFO」开头的行 样例:`INFO : Loading RuleMap ...` -pub fn output_translate(content: String) -> ResultS { +pub fn output_translate(content: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let head = pipe! { &content @@ -92,11 +95,6 @@ pub fn output_translate(content: String) -> ResultS { narsese: None, }, "input" => Output::IN { content }, - "anticipate" => Output::ANTICIPATE { - content_raw: content, - // TODO: 有待捕获转译 - narsese: None, - }, "exe" => Output::EXE { content_raw: content, // TODO: 有待捕获转译 diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index fbdf8e1..11a0d9f 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -12,7 +12,9 @@ //! * ⚠️【2024-03-25 13:32:50】 use std::{ + error::Error, ffi::OsStr, + fmt::{self, Debug, Display, Formatter}, io::{BufRead, BufReader, Result as IoResult, Write}, process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, sync::{ @@ -21,7 +23,22 @@ use std::{ }, thread::{self, JoinHandle}, }; -use util::*; +// use util::*; +use anyhow::Result; +use util::ResultBoost; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IoProcessError(String); +impl Display for IoProcessError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl Error for IoProcessError {} + +fn err(e: impl Debug) -> anyhow::Error { + IoProcessError(format!("{e:?}")).into() +} /// 统一定义「输出侦听器」的类型 type OutputListener = dyn FnMut(String) + Send + Sync; @@ -331,15 +348,17 @@ impl IoProcessManager { /// * 🎯用于(阻塞式等待)从子进程中收取输出信息 /// * 🚩以字符串形式报告错误 /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 - pub fn fetch_output(&mut self) -> ResultS { + /// * ❌[`std::sync::PoisonError`]未实现[`Send`],无法被[`anyhow::Error`]直接捕获 + /// * ❌[`std::sync::mpsc::RecvError`]未实现[`From`],无法转换为[`anyhow::Error`] + pub fn fetch_output(&mut self) -> Result { // 访问自身「子进程输出」字段 self.child_out // 互斥锁锁定 .lock() - .transform_err_string()? + .transform_err(err)? // 通道接收者接收 .recv() - .transform_err_string() + .transform_err(err) } /// 尝试(从「输出通道」中)拉取一个输出 @@ -347,13 +366,13 @@ impl IoProcessManager { /// * 🚩类似[`Self::fetch_output`],但仅在「有输出」时拉取 /// * 📝[`Receiver`]自带的[`Receiver::try_recv`]就做了这件事 /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 - pub fn try_fetch_output(&mut self) -> ResultS> { + pub fn try_fetch_output(&mut self) -> Result> { // 访问自身「子进程输出」字段,但加上`try` let out = self .child_out // 互斥锁锁定 .lock() - .transform_err_string()? + .transform_err(err)? // 通道接收者接收 .try_recv() .ok(); @@ -367,17 +386,17 @@ impl IoProcessManager { /// * ⚙️返回空,或返回字符串形式的错误(互斥锁错误) /// * ⚠️此方法需要【自行尾缀换行符】,否则不被视作有效输入 /// * 📄要触发输入,需传入" B>.\n"而非" B>." - pub fn put(&self, input_line: impl ToString) -> ResultS<()> { + pub fn put(&self, input_line: impl ToString) -> Result<()> { // 从互斥锁中获取输入 // * 🚩等待直到锁定互斥锁,最终在作用域结束(MutexGuard析构)时释放(解锁) // ! ❌【2024-03-23 23:59:20】此处的闭包无法简化成函数指针 self.child_in // 锁定以获取`Sender` .lock() - .transform_err_string()? + .transform_err(err)? // 发送 .send(input_line.to_string()) - .transform_err_string() + .transform_err(err) } /// 向子进程写入**一行**数据(字符串) @@ -386,7 +405,7 @@ impl IoProcessManager { /// * ⚠️此方法在输入后【自动添加换行符】 /// * 📄传入" B>."将自动转换成" B>.\n" /// * ✅以此总是触发输入 - pub fn put_line(&self, input: impl ToString) -> ResultS<()> { + pub fn put_line(&self, input: impl ToString) -> Result<()> { self.put(format!("{}\n", input.to_string())) } @@ -403,11 +422,11 @@ impl IoProcessManager { /// * ⚠️将借走自身所有权,终止并销毁自身 /// /// * ❓不稳定:有时会导致「野进程」的情况 - pub fn kill(mut self) -> ResultS<()> { + pub fn kill(mut self) -> Result<()> { // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑:无法脱开对`self`的借用 // ! 📌更具体而言:对其中两个线程`thread_write_in`、`thread_read_out`的部分借用 // 向子线程发送终止信号 // - let mut signal = self.termination_signal.lock().transform_err_string()?; + let mut signal = self.termination_signal.lock().transform_err(err)?; *signal = true; drop(signal); // ! 手动释放锁 // * 📝【2024-03-24 00:15:10】必须手动释放锁,否则会导致后续线程死锁 @@ -420,7 +439,7 @@ impl IoProcessManager { // * 📌主要原因:在测试OpenNARS时,发现`thread_read_out`仍然会阻塞(无法等待) // * 📌并且一时难以修复:难点在`BufReader.read_line`如何非阻塞/可终止化 // ! ℹ️信息 from Claude3:无法简单以此终止子线程 - self.thread_write_in.join().transform_err_debug()?; // * ✅目前这个是可以终止的 + self.thread_write_in.join().transform_err(err)?; // * ✅目前这个是可以终止的 drop(self.thread_read_out); // * 📝此时子线程连同「子进程的标准输入输出」一同关闭, @@ -444,7 +463,7 @@ impl IoProcessManager { } } // * 🚩通用:调用`Child`对象的`kill`方法 - self.process.kill().transform_err_string() + self.process.kill().transform_err(err) } } diff --git a/src/runtime/command_vm/api/translators.rs b/src/runtime/command_vm/api/translators.rs index 459120e..cfc7976 100644 --- a/src/runtime/command_vm/api/translators.rs +++ b/src/runtime/command_vm/api/translators.rs @@ -1,21 +1,20 @@ +use anyhow::Result; use navm::{cmd::Cmd, output::Output}; -use util::ResultS; +use std::{error::Error, fmt::Display}; /// [`Cmd`]→进程输入 转译器 /// * 🚩现在不再使用特征,以便在`Option>`中推断类型 /// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 /// * 📌要求线程稳定 /// * 只有转译功能,没有其它涉及外部的操作(纯函数) -/// TODO: 在后续的「NSE指令输入」时,需要通过「自动将『空预算任务』作为语句输入」应对「`$$ A.`→`A.`」的情况 -/// * ⚠️转译有可能失败:此时返回并上报错误信息 -pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; +pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; /// 进程输出→[`Output`]转译器 /// * 🚩现在不再使用特征,以便在`Option>`中推断类型 /// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 /// * 📌要求线程稳定 /// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; +pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; /// IO转换器配置 /// * 🎯封装并简化其它地方的`translator: impl Fn(...) -> ... + ...`逻辑 @@ -34,8 +33,8 @@ impl IoTranslators { /// * 📌需要直接传入闭包(要求全局周期`'static`) pub fn new(i: I, o: O) -> Self where - I: Fn(Cmd) -> ResultS + Send + Sync + 'static, - O: Fn(String) -> ResultS + Send + Sync + 'static, + I: Fn(Cmd) -> Result + Send + Sync + 'static, + O: Fn(String) -> Result + Send + Sync + 'static, { Self { input_translator: Box::new(i), @@ -69,14 +68,65 @@ impl Default for IoTranslators { /// * 📄[`super::super::CommandVm::translators`] impl From<(I, O)> for IoTranslators where - I: Fn(Cmd) -> ResultS + Send + Sync + 'static, - O: Fn(String) -> ResultS + Send + Sync + 'static, + I: Fn(Cmd) -> Result + Send + Sync + 'static, + O: Fn(String) -> Result + Send + Sync + 'static, { fn from(value: (I, O)) -> Self { Self::new(value.0, value.1) } } +/// 统一封装「转译错误」 +/// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 +/// * 🎯用于可识别的错误,并在打印时直接展示原因 +/// * ⚠️若直接使用[`anyhow::anyhow`],会打印一大堆错误堆栈 +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct TranslateError(pub String); + +// ! ❌【2024-03-27 22:40:22】无法正常使用:不能导出带`format!`的宏 +// * error: macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths +// #[macro_export] +// macro_rules! translate_error { +// ($($t:tt)*) => { +// TranslateError(format!($($t)*)) +// }; +// } + +/// 灵活地从字符串转换为[`TranslateError`] +impl> From for TranslateError { + fn from(value: S) -> Self { + Self(value.as_ref().to_string()) + } +} + +/// 灵活地从[`Error`]转换为[`TranslateError`] +impl TranslateError { + /// 从[`Error`]转换为[`TranslateError`] + pub fn from_error(value: impl Error) -> Self { + Self(value.to_string()) + } + /// 从[`Error`]转换为[`anyhow::Error`] + pub fn error_anyhow(value: impl Error) -> anyhow::Error { + Self::from_error(value).into() + } + /// 从[`Self::from`]转换到[`anyhow::Error`] + /// * 🚩封装为自身类型 + /// * ❗实际上`.into()`比`::anyhow`短 + /// * 📌尽可能用前者 + pub fn anyhow(value: impl Into) -> anyhow::Error { + // ! ❌【2024-03-27 22:59:51】不能使用`Self::from(value).into`:`AsRef`不一定实现`Into` + anyhow::Error::from(value.into()) + } +} +/// 展示错误 +impl Display for TranslateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TranslateError: {}", self.0) + } +} +/// 实现[`Error`]特征 +impl Error for TranslateError {} + /// 单元测试 #[cfg(test)] mod tests { @@ -84,6 +134,7 @@ mod tests { #[test] fn test() { + // TODO: 【2024-03-27 22:56:26】有待完善 let _t1 = IoTranslators::default(); } } diff --git a/src/runtime/command_vm/launcher.rs b/src/runtime/command_vm/launcher.rs index 8e4e5f9..9e74969 100644 --- a/src/runtime/command_vm/launcher.rs +++ b/src/runtime/command_vm/launcher.rs @@ -2,6 +2,7 @@ use super::{InputTranslator, IoTranslators, OutputTranslator}; use crate::process_io::IoProcess; +use anyhow::Result; use navm::{cmd::Cmd, output::Output}; use std::{ffi::OsStr, process::Command}; @@ -33,7 +34,7 @@ impl CommandVm { /// * 💭何时Rust能给特征起别名。。 pub fn input_translator( mut self, - translator: impl Fn(Cmd) -> Result + Send + Sync + 'static, + translator: impl Fn(Cmd) -> Result + Send + Sync + 'static, ) -> Self { self.input_translator = Some(Box::new(translator)); self @@ -42,18 +43,19 @@ impl CommandVm { /// 配置/输出转译器 pub fn output_translator( mut self, - translator: impl Fn(String) -> Result + Send + Sync + 'static, + translator: impl Fn(String) -> Result + Send + Sync + 'static, ) -> Self { self.output_translator = Some(Box::new(translator)); self } /// 配置/输入输出转译器组 - pub fn translators(self, translators: impl Into) -> Self { + pub fn translators(mut self, translators: impl Into) -> Self { // 一次实现俩 let translators = translators.into(); - self.input_translator(translators.input_translator) - .output_translator(translators.output_translator) + self.input_translator = Some(translators.input_translator); + self.output_translator = Some(translators.output_translator); + self } } diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 37f00ae..771fa9d 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -8,12 +8,12 @@ use super::{CommandVm, InputTranslator, OutputTranslator}; use crate::process_io::IoProcessManager; +use anyhow::Result; use navm::{ cmd::Cmd, output::Output, vm::{VmLauncher, VmRuntime}, }; -use util::ResultS; /// 命令行虚拟机运行时 /// * 🎯封装「进程通信」逻辑 @@ -31,19 +31,19 @@ pub struct CommandVmRuntime { } impl VmRuntime for CommandVmRuntime { - fn input_cmd(&mut self, cmd: Cmd) -> ResultS<()> { + fn input_cmd(&mut self, cmd: Cmd) -> Result<()> { // 尝试转译 let input = (self.input_translator)(cmd)?; // 置入转译结果 self.process.put_line(input) } - fn fetch_output(&mut self) -> ResultS { + fn fetch_output(&mut self) -> Result { let s = self.process.fetch_output()?; (self.output_translator)(s) } - fn try_fetch_output(&mut self) -> ResultS> { + fn try_fetch_output(&mut self) -> Result> { let s = self.process.try_fetch_output()?; // 匹配分支 match s { @@ -54,7 +54,7 @@ impl VmRuntime for CommandVmRuntime { } } - fn terminate(self) -> ResultS<()> { + fn terminate(self) -> Result<()> { // 杀死子进程 self.process.kill()?; Ok(()) @@ -85,6 +85,8 @@ impl VmLauncher for CommandVm { /// 单元测试 #[cfg(test)] pub(crate) mod test { + use crate::runtime::TranslateError; + use super::*; use narsese::conversion::string::impl_lexical::shortcuts::*; use std::process::Command; @@ -183,7 +185,7 @@ pub(crate) mod test { /// 临时构建的「输入转换」函数 /// * 🎯用于转换`VOL 0`⇒`*volume=0`,避免大量输出造成进程卡顿 - fn input_translate(cmd: Cmd) -> ResultS { + fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) Cmd::NSE(..) => cmd.tail(), @@ -192,14 +194,14 @@ pub(crate) mod test { // VOL指令:调整音量 Cmd::VOL(n) => format!("*volume={n}"), // 其它类型 - _ => return Err(format!("未知指令:{cmd:?}")), + _ => return Err(TranslateError(format!("未知指令:{cmd:?}")).into()), }; // 转换 Ok(content) } /// 临时构建的「输出转换」函数 - fn output_translate(content: String) -> ResultS { + fn output_translate(content: String) -> Result { // 读取输出 let output = first! { // 捕获Answer From 28fa56b0d4db45ce6bc54cb2b2101f2f187b7b2c Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:03:14 +0800 Subject: [PATCH 22/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=9F=BA=E4=BA=8ENo?= =?UTF-8?q?de.js=E7=9A=84=E8=BF=90=E8=A1=8C=E6=97=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=9B=E7=B2=BE=E7=AE=80=E7=BB=86=E5=8C=96=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持CXinNARS.js;运行时实现中utils并入common;exe启动代码归入common::exe子模块 --- .vscode/settings.json | 1 + .../{utils/mod.rs => common/exe.rs} | 9 +- src/cin_implements/common/mod.rs | 8 + src/cin_implements/common/node_js.rs | 61 +++++++ src/cin_implements/cxin_js/launcher.rs | 58 +++++++ src/cin_implements/cxin_js/mod.rs | 62 +++++++ src/cin_implements/cxin_js/translators.rs | 155 ++++++++++++++++++ src/cin_implements/mod.rs | 5 +- src/cin_implements/ona/launcher.rs | 2 +- src/cin_implements/ona/translators.rs | 9 +- src/runtime/command_vm/runtime.rs | 2 +- 11 files changed, 361 insertions(+), 11 deletions(-) rename src/cin_implements/{utils/mod.rs => common/exe.rs} (77%) create mode 100644 src/cin_implements/common/node_js.rs create mode 100644 src/cin_implements/cxin_js/launcher.rs create mode 100644 src/cin_implements/cxin_js/mod.rs create mode 100644 src/cin_implements/cxin_js/translators.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 4660e73..2e85d01 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "rust-analyzer.cargo.features": "all", "editor.formatOnSave": true, "cSpell.words": [ + "cxin", "Errno", "openjunars", "rfind", diff --git a/src/cin_implements/utils/mod.rs b/src/cin_implements/common/exe.rs similarity index 77% rename from src/cin_implements/utils/mod.rs rename to src/cin_implements/common/exe.rs index 7f65e60..8d0a460 100644 --- a/src/cin_implements/utils/mod.rs +++ b/src/cin_implements/common/exe.rs @@ -1,6 +1,12 @@ -use std::{ffi::OsStr, path::Path, process::Command}; +//! 可执行文件(exe)启动器 +//! * 🎯适用于任何直接从可执行文件(可能带参数)启动的CIN +//! * 📄ONA +//! * 📄NARS-Python +//! * 🚩【2024-03-28 10:00:00】暂且只需提供[`Command`]生成函数 +//! * ❗没必要使用新的数据结构 use crate::runtime::{CommandVm, IoTranslators}; +use std::{ffi::OsStr, path::Path, process::Command}; /// 根据配置统一生成[`Command`]对象 /// * 📌「配置」的定义 @@ -24,6 +30,7 @@ pub fn generate_command( // * 📝这里的`args`、`arg都返回的可变借用。。 command.args(args); + // 返回 command } diff --git a/src/cin_implements/common/mod.rs b/src/cin_implements/common/mod.rs index d16113b..7e95a91 100644 --- a/src/cin_implements/common/mod.rs +++ b/src/cin_implements/common/mod.rs @@ -1,8 +1,16 @@ //! 所有对接CIN实现的共用模块 +//! * 🎯共通的exe、Java、Python、Julia 和 Node.js // 平铺导出元素 util::pub_mod_and_pub_use! { + // exe + exe + // Java java + // Python python + // Julia julia + // Node.js + node_js } diff --git a/src/cin_implements/common/node_js.rs b/src/cin_implements/common/node_js.rs new file mode 100644 index 0000000..3607747 --- /dev/null +++ b/src/cin_implements/common/node_js.rs @@ -0,0 +1,61 @@ +//! Node.js 模块启动器 +//! * 🎯通用于任何基于Node.js脚本的CIN +//! * 🎯封装「NAVM运行时启动过程」中有关「Node.js启动环境配置」的部分 +//! * 🚩从Node.js脚本(`.js`)启动NARS + +use crate::runtime::CommandGenerator; +use std::{path::PathBuf, process::Command}; + +/// 启动Node.js运行时的命令 +const COMMAND_NODE: &str = "node"; + +/// ! Node.js启动脚本无需附加参数 + +/// CXinNARS运行时启动器 +/// * 🎯配置CXinNARS专有的东西 +/// * 🎯以Node.js模块形式启动CXinNARS +/// * 📌没有内置的「音量」配置 +/// * 🚩【2024-03-25 08:55:07】基于Node.js模块文件启动CXinNARS +/// * 默认预置指令:``node [`.js`脚本文件路径]`` +/// * 🚩【2024-03-25 09:15:07】删去[`Default`]派生:因为可能导致无效的路径 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CommandGeneratorNodeJS { + /// Node.js脚本文件路径 + js_path: PathBuf, + /// 附加的命令行参数 + /// * 📄CXinNARS中用到了`shell`参数 + extra_args: Vec, +} + +impl CommandGeneratorNodeJS { + pub fn new( + js_path: impl Into, + extra_args: impl IntoIterator>, + ) -> Self { + Self { + // 转换为路径 + js_path: js_path.into(), + extra_args: extra_args + .into_iter() + .map(|s| s.as_ref().to_string()) + .collect::>(), + } + } +} + +/// 转换为Node.js启动命令 +impl CommandGenerator for CommandGeneratorNodeJS { + fn generate_command(&self) -> Command { + // 构造指令 + let mut command_nodejs = Command::new(COMMAND_NODE); + + // 填入路径参数 + command_nodejs.arg(&self.js_path); + + // 填入其它参数 + command_nodejs.args(&self.extra_args); + + // 返回 + command_nodejs + } +} diff --git a/src/cin_implements/cxin_js/launcher.rs b/src/cin_implements/cxin_js/launcher.rs new file mode 100644 index 0000000..4095d92 --- /dev/null +++ b/src/cin_implements/cxin_js/launcher.rs @@ -0,0 +1,58 @@ +//! CXinNARS.js运行时的启动器 +//! * 🎯允许CXinNARS.js对原先运行时特别配置功能,同时也支持为CXinNARS.js定制配置 +//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 +//! * ✨不同启动器可以启动到相同运行时 + +use super::{input_translate, output_translate}; +use crate::{ + cin_implements::common::{generate_command_vm, CommandGeneratorNodeJS}, + runtime::{CommandGenerator, CommandVmRuntime}, +}; +use navm::vm::VmLauncher; +use std::path::PathBuf; +use util::pipe; + +/// CXinNARS.js Shell启动的默认指令参数 +/// * 🎯默认预置指令:`[.js文件路径] shell` +const COMMAND_ARGS_CXIN_NARS: [&str; 1] = ["shell"]; + +/// CXinNARS.js运行时启动器 +/// * 🎯配置CXinNARS.js专有的东西 +/// * 🚩基于js文件启动CXinNARS.js Shell +/// * 默认预置指令:`[.js文件路径] shell` +/// * 🚩【2024-03-25 08:51:30】目前保留原有缩写的大小写风格,与OpenNARS、PyNARS一致 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CXinJS { + /// Node.js命令生成器 + command_generator: CommandGeneratorNodeJS, +} + +// ! 🚩【2024-03-25 09:37:22】目前暂时不提取至「VmExe」:预置的`shell`参数需要被处理 +impl CXinJS { + /// 构造函数 + pub fn new(js_path: impl Into) -> Self { + Self { + command_generator: CommandGeneratorNodeJS::new(js_path, COMMAND_ARGS_CXIN_NARS), + } + } +} + +/// 启动到「命令行运行时」 +impl VmLauncher for CXinJS { + fn launch(self) -> CommandVmRuntime { + // 构造并启动虚拟机 + let runtime = pipe! { + self.command_generator + // 构造指令 | 预置的指令参数 + => .generate_command() + // * 🚩固定的「输入输出转换器」 + => generate_command_vm(_, (input_translate, output_translate)) + // 🔥启动 + => .launch() + }; + + runtime + } +} + +// ! 单元测试见[`super`] diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs new file mode 100644 index 0000000..8deedd5 --- /dev/null +++ b/src/cin_implements/cxin_js/mod.rs @@ -0,0 +1,62 @@ +//! 「非公理虚拟机」的ONA运行时 +//! * 🚩只提供「一行启动」的功能封装 +//! * 🎯无需自行配置「输入输出转译器」 + +// 转译器 +util::mod_and_pub_use! { + // 转译器 + translators + // 启动器 + launcher +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::{ + test::{await_fetch_until, input_cmd_and_await_contains}, + CommandVmRuntime, + }; + use narsese::lexical_nse_task as nse_task; + use navm::{ + cmd::Cmd, + vm::{VmLauncher, VmRuntime}, + }; + + /// 测试用路径 + const CXIN_NARS_JS_PATH: &str = r"..\cxin-nars-py-to-ts\src\cxin-nars-shell.js"; + + #[test] + fn test() { + // 从别的地方获取exe路径 + let js_path = CXIN_NARS_JS_PATH; + // 一行代码启动CxinNARS + let vm = CXinJS::new(js_path).launch(); + // 进入专用测试 + _test_cxin_js(vm) + } + + /// 专用测试/CXinNARS.js + pub fn _test_cxin_js(mut vm: CommandVmRuntime) { + // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 + let mut input_cmd_and_await = + |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); + // input_cmd_and_await(Cmd::VOL(0), ""); + input_cmd_and_await(Cmd::NSE(nse_task!( B>.)), "B>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>.)), "C>."); + input_cmd_and_await(Cmd::NSE(nse_task!( C>?)), "C>?"); + input_cmd_and_await(Cmd::CYC(20), ""); // * CYC无需自动等待 + + // 等待回答(字符串) + await_fetch_until(&mut vm, |_o, raw_content| { + // ! ❌【2024-03-28 09:51:48】目前CXinNARS能输出导出结论,但无法输出ANSWER + /* matches!(_o, Output::ANSWER { .. }) && */ + raw_content.contains("C>.") + }); + + // 终止虚拟机 + vm.terminate().expect("无法终止虚拟机"); + println!("Virtual machine terminated..."); + } +} diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs new file mode 100644 index 0000000..41ca70c --- /dev/null +++ b/src/cin_implements/cxin_js/translators.rs @@ -0,0 +1,155 @@ +//! CXinNARS.js在「命令行运行时」的转译器 +//! * 🎯维护与CXinNARS.js Shell的交互 +//! * 📌基于命令行输入输出的字符串读写 +//! * ✨NAVM指令→字符串 +//! * ✨字符串→NAVM输出 +//! +//! ## 输出样例 +//! +//! * `Input: <<(* x) --> ^left> ==> A>. Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000` +//! * `Derived: <<(* x) --> ^left> ==> good>>. Priority=0.245189 Truth: frequency=1.000000, confidence=0.810000` +//! * `Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.447514` +//! * `Answer: None.` +//! * `^deactivate executed with args` +//! * `^left executed with args (* {SELF})` +//! * `^left executed with args ({SELF} * x)` +//! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` + +use crate::runtime::TranslateError; +use anyhow::Result; +use narsese::{ + conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, + lexical::Narsese, +}; +use navm::{ + cmd::Cmd, + output::{Operation, Output}, +}; +use regex::Regex; +use util::{if_return, pipe}; + +/// CXinNARS.js的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「CXinNARS.js Shell输入」 +/// * 📝[`IoProcess`]会自动将输入追加上换行符 +pub fn input_translate(cmd: Cmd) -> Result { + let content = match cmd { + // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) + Cmd::NSE(..) => cmd.tail(), + // CYC指令:运行指定周期数 + Cmd::CYC(n) => n.to_string(), + // 其它类型 + // * 📌【2024-03-24 22:57:18】基本足够支持 + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + }; + // 转译 + Ok(content) +} + +/// CXinNARS.js的「输出转译」函数 +/// * 🎯用于将CXinNARS.js Shell的输出(字符串)转译为「NAVM输出」 +/// * 🚩直接根据选取的「头部」进行匹配 +pub fn output_translate(content_raw: String) -> Result { + // 特别处理:终止信号 + // * 📄"node:internal/modules/cjs/loader:1080\n throw err" + // * ❌【2024-03-28 09:00:23】似乎不可行:打开时的错误无法被捕捉 + if_return! { + // 模块未找到 + content_raw.contains("Error: Cannot find module") => Ok(Output::TERMINATED { description: content_raw }) + } + // 匹配「输出类型」的正则表达式 + // * ✅此处的「尾部」不会有前导空格(若识别出了「头部」) + let line_r = Regex::new(r"\[(\w+)\]\s*(.*)").unwrap(); + let head; + let tail; + if let Some(captures) = line_r.captures(&content_raw) { + head = captures[1].to_lowercase(); + tail = captures[2].to_owned(); + } else { + head = String::new(); + tail = content_raw.clone(); + } + // 根据「头部」生成输出 + let output = match head.as_str() { + "answer" => Output::ANSWER { + // 先提取其中的Narsese + narsese: segment_narsese(&head, &tail), + // 然后传入整个内容 + content_raw, + }, + "in" => Output::IN { content: tail }, + "out" => Output::OUT { + // 先提取其中的Narsese + narsese: segment_narsese(&head, &tail), + // 然后传入整个内容 + content_raw: tail, + }, + "comment" => Output::COMMENT { content: tail }, + "err" | "error" => Output::ERROR { description: tail }, + "exe" => Output::EXE { + operation: parse_operation(&tail), + content_raw: tail, + }, + // 若是连续的「头部」⇒识别为「未归类」类型 + _ if !content_raw.contains(char::is_whitespace) => Output::UNCLASSIFIED { + r#type: head, + // 尝试自动捕获Narsese + narsese: match try_segment_narsese(&tail) { + Some(Ok(narsese)) => Some(narsese), + _ => None, + }, + content: tail, + }, + // 其它 + _ => Output::OTHER { + content: content_raw, + }, + }; + // 返回 + Ok(output) +} + +/// (CXinNARS.js)从原始输出中解析操作 +pub fn parse_operation(content_raw: &str) -> Operation { + #![allow(unused_variables)] + todo!("CXinNARS.js暂不支持NAL-8") +} + +fn segment_narsese(head: &str, tail: &str) -> Option { + match try_segment_narsese(tail) { + Some(Ok(narsese)) => Some(narsese), + Some(Err(e)) => { + println!("【ERR/{head}】在解析Narsese时出现错误:{e}"); + None + } + None => { + println!("【ERR/{head}】未匹配到输出中的Narsese块"); + None + } + } +} + +/// 分割 & 解析Narsese +/// * 🎯提供解析CXinNARS中Narsese的方法 +/// * ❗不包含任何副作用(如打印) +/// * 🚩先通过正则表达式从模式`Narsese{{ 【Narsese内容】 }}【Narsese类型】`中分解出Narsese +/// * 🚩再通过标准ASCII解析器解析 +pub fn try_segment_narsese(input: &str) -> Option { + let re_narsese = Regex::new(r"Narsese\{\{ (.+) \}\}").unwrap(); + pipe!( + // 尝试从模式中提取Narsese + re_narsese.captures(input) + // 提取Narsese + => .map( + // 尝试解析Narsese + |captures| try_parse_narsese(&captures[1]) + ) + ) +} + +/// (尝试)从输出中解析出Narsese +/// * ❌【2024-03-27 22:01:18】目前引入[`anyhow::Error`]会出问题:不匹配/未满足的特征 +pub fn try_parse_narsese(narsese: &str) -> ParseResult { + // 提取并解析Narsese字符串 + println!("通过正则表达式解析字符串"); + FORMAT_ASCII.parse(narsese) +} diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs index 527caa6..1c8c71c 100644 --- a/src/cin_implements/mod.rs +++ b/src/cin_implements/mod.rs @@ -18,8 +18,8 @@ //! * ❌不希望因此再全小写/封装命名空间,如`impls::ona::new` //! * ❓目前的问题:在Rust基于「特征」的组合式设计哲学下,如何进行兼顾三者的优秀设计 +// 共用代码 mod common; -mod utils; // OpenNARS pub mod opennars; @@ -35,3 +35,6 @@ pub mod pynars; // OpenJunars pub mod openjunars; + +// CXinNARS.js +pub mod cxin_js; diff --git a/src/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs index 3501984..45b7f94 100644 --- a/src/cin_implements/ona/launcher.rs +++ b/src/cin_implements/ona/launcher.rs @@ -5,7 +5,7 @@ use super::{input_translate, output_translate}; use crate::{ - cin_implements::utils::{generate_command, generate_command_vm}, + cin_implements::common::{generate_command, generate_command_vm}, runtime::CommandVmRuntime, }; use navm::{ diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index ee4d36a..ef8802d 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -16,6 +16,7 @@ //! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` use super::dialect::parse as parse_narsese_ona; +use crate::runtime::TranslateError; use anyhow::Result; use narsese::conversion::string::impl_lexical::structs::ParseResult; use navm::{ @@ -38,13 +39,7 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::VOL(n) => format!("*volume={n}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("该指令类型暂不支持:{cmd:?}"), - ) - .into()) - } + _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), }; // 转译 Ok(content) diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 771fa9d..39b2e01 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -85,9 +85,9 @@ impl VmLauncher for CommandVm { /// 单元测试 #[cfg(test)] pub(crate) mod test { - use crate::runtime::TranslateError; use super::*; + use crate::runtime::TranslateError; use narsese::conversion::string::impl_lexical::shortcuts::*; use std::process::Command; use util::first; From 5913b09dc98701f6722419a05dc01086b19c712c Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:24:45 +0800 Subject: [PATCH 23/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=9F=BA=E4=BA=8EPE?= =?UTF-8?q?G=E5=BA=93`pest`=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0OpenNARS?= =?UTF-8?q?=E3=80=81ONA=E6=96=B9=E8=A8=80=E8=A7=A3=E6=9E=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 128 +++++++++++++++++- Cargo.toml | 1 + src/bin/cin_launcher.rs | 61 +++++++++ src/cin_implements/cxin_js/translators.rs | 1 - src/cin_implements/ona/dialect.rs | 20 +++ src/cin_implements/ona/dialect_ona.pest | 122 +++++++++++++++-- .../opennars/dialect_opennars.pest | 86 ++++++++++++ src/cin_implements/pynars/launcher.rs | 10 +- src/process_io/io_process.rs | 15 +- 9 files changed, 417 insertions(+), 27 deletions(-) create mode 100644 src/cin_implements/opennars/dialect_opennars.pest diff --git a/Cargo.lock b/Cargo.lock index 3685a2f..cd6713f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,15 +27,76 @@ dependencies = [ "narsese", "navm", "pest", + "pest_derive", "regex", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "memchr" version = "2.7.1" @@ -48,7 +109,7 @@ version = "0.19.0" [[package]] name = "narsese" -version = "0.9.0" +version = "0.9.1" dependencies = [ "lazy_static", "nar_dev_utils", @@ -56,13 +117,19 @@ dependencies = [ [[package]] name = "navm" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "nar_dev_utils", "narsese", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "pest" version = "2.7.8" @@ -74,6 +141,40 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -121,6 +222,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "syn" version = "2.0.55" @@ -152,6 +264,12 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "ucd-trie" version = "0.1.6" @@ -163,3 +281,9 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/Cargo.toml b/Cargo.toml index 7be632e..27e56d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.8.0" edition = "2021" [dependencies] +pest_derive = "2.7.8" [dependencies.anyhow] version = "1.0.81" diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs index f662136..02b1904 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher.rs @@ -75,3 +75,64 @@ fn shell(mut nars: CommandVmRuntime) { input.clear(); } } + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use babel_nar::cxin_js::CXinJS; + use babel_nar::pynars::PyNARS; + use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; + use navm::cmd::Cmd; + use navm::vm::VmLauncher; + + #[test] + fn test_20240328() { + // let (test1, test2) = generate_test_cmds(); + // // let nars = CXinJS::new(r"..\cxin-nars-py-to-ts\src\cxin-nars-shell.js"); + // // let nars = OpenNARS::new(r"..\..\NARS-executables\opennars-304-T-modified.jar"); + // let nars = ONA::new("..\\..\\NARS-executables\\NAR.exe"); + // // let nars = PyNARS::new("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); + // std::thread::sleep(std::time::Duration::from_secs(1)); + // test_set(nars.launch(), test1); + } + + fn test_set(mut nars: impl VmRuntime, test_set: Vec) { + for cmd in test_set { + nars.input_cmd(cmd); + } + std::thread::sleep(std::time::Duration::from_secs(5)); + while let Ok(Some(o)) = nars.try_fetch_output() { + println!("{}", format_navm_output(o)); + } + } + + fn format_navm_output(o: Output) -> String { + // 以「有无Narsese」作区分 + match o.get_narsese() { + // * 🚩有Narsese⇒包含Narsese + Some(nse) => format!( + "[{}] (( {} )) {}", + o.type_name(), + FORMAT_ASCII.format_narsese(nse), + o.raw_content() + ), + // * 🚩无⇒仅包含内容 + None => format!("[{}] {}", o.type_name(), o.raw_content()), + } + } + + fn parse_cmd_lines(narsese: impl AsRef) -> Vec { + let narsese = narsese.as_ref(); + let mut result = vec![]; + + for line in narsese.split('\n').map(str::trim).filter(|s| !s.is_empty()) { + match Cmd::parse(line) { + Ok(cmd) => result.push(cmd), + Err(e) => println!("{e}"), + } + } + + result + } +} diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs index 41ca70c..4092dba 100644 --- a/src/cin_implements/cxin_js/translators.rs +++ b/src/cin_implements/cxin_js/translators.rs @@ -150,6 +150,5 @@ pub fn try_segment_narsese(input: &str) -> Option { /// * ❌【2024-03-27 22:01:18】目前引入[`anyhow::Error`]会出问题:不匹配/未满足的特征 pub fn try_parse_narsese(narsese: &str) -> ParseResult { // 提取并解析Narsese字符串 - println!("通过正则表达式解析字符串"); FORMAT_ASCII.parse(narsese) } diff --git a/src/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs index 8f29153..16a9f8f 100644 --- a/src/cin_implements/ona/dialect.rs +++ b/src/cin_implements/ona/dialect.rs @@ -8,6 +8,12 @@ use narsese::conversion::string::impl_lexical::{ format_instances::FORMAT_ASCII, structs::ParseResult, }; +use pest::Parser; +use pest_derive::Parser; + +#[derive(Parser)] // ! ↓ 必须从项目根目录开始 +#[grammar = "src/cin_implements/ona/dialect_ona.pest"] +pub struct DialectParser; /// 使用[`pest`]将输入的「ONA方言」转换为「词法Narsese」 /// 以ONA的语法解析出Narsese @@ -18,7 +24,21 @@ use narsese::conversion::string::impl_lexical::{ /// * 📄`(* {SELF})` /// * 📄`({SELF} * x)` pub fn parse(input: &str) -> ParseResult { + let s = dbg!(DialectParser::parse(Rule::narsese, input)); FORMAT_ASCII.parse(input) // #![allow(unused)] // todo!("ONA方言!") } + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + /// 测试/方言解析器 🚧 + #[test] + fn test_dialect_parser() { + let x = parse(" B>."); + let _ = dbg!(x); + } +} diff --git a/src/cin_implements/ona/dialect_ona.pest b/src/cin_implements/ona/dialect_ona.pest index ee8df5a..9fc4ab1 100644 --- a/src/cin_implements/ona/dialect_ona.pest +++ b/src/cin_implements/ona/dialect_ona.pest @@ -1,17 +1,113 @@ -// TODO: 有待替换 -num = @{ int ~ ("." ~ ASCII_DIGIT*)? ~ (^"e" ~ int)? } -int = { ("+" | "-")? ~ ASCII_DIGIT+ } +narsese = _{ + task + | sentence + | term +} -operation = _{ add | subtract | multiply | divide | power } -add = { "+" } -subtract = { "-" } -multiply = { "*" } -divide = { "/" } -power = { "^" } +term = { + compound + | set + | statement + | atom +} -expr = { term ~ (operation ~ term)* } -term = _{ num | "(" ~ expr ~ ")" } +// 原子词项 +// * 📝其中的`@`会在「中间有空白符」时报错 +atom = { + placeholder // 占位符 | 可能会占满 -calculation = _{ SOI ~ expr ~ EOI } + | atom_prefix ~ atom_content +} +placeholder = { ("_"+) } +atom_prefix = @{ + (PUNCTUATION | SYMBOL) // 其它带前缀原子词项 -WHITESPACE = _{ " " | "\t" } + | "" // 词语 +} +atom_content = @{ word_content } + +// 复合词项 + +compound = ${ + // * 📝使用前缀「$」允许在此中判断空格 + ("(" ~ #multi = connecter ~ optional_separator ~ term_list ~ ")") + | ("(" ~ term ~ #binary = connecter ~ term ~ ")") +} + +optional_separator = _{ (("," | WHITESPACE) ~ WHITESPACE*) } + +// 连接符 | 📝使用` ~ (!"," ~ punct_sym)*`的模式,在每次「尝试延伸」时都匹配 +connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } + +// 集合词项 +set = { + #ext_set = ("{" ~ term_list ~ "}") // 外延集 + + | #int_set = ("[" ~ term_list ~ "]") // 内涵集 +} + +term_list = { term ~ ((("," | WHITESPACE) ~ WHITESPACE*) ~ term)* ~ (",")? } + +// 陈述 +statement = { + "<" ~ term ~ copula ~ term ~ ">" +} + +punct_sym = _{ (PUNCTUATION | SYMBOL) } + +copula = @{ + // (PUNCTUATION | SYMBOL)+ + (punct_sym ~ "-" ~ punct_sym) // 继承/相似/实例/属性/实例属性 + + | (punct_sym ~ "=" ~ punct_sym) // 蕴含/等价 + + | (punct_sym ~ punct_sym ~ ">") // 时序性蕴含/时序性等价 +} + +// 语句 + +sentence = { term ~ punctuation ~ stamp ~ truth } + +punctuation = @{ PUNCTUATION } + +stamp = @{ + (":" ~ stamp_content ~ ":") // 有内容的时间戳 + + | "" // 空时间戳 +} +stamp_content = @{ + "|" + | "\\" + | "/" + | (("+" | "-") ~ ASCII_DIGIT+) +} + +truth = { + ("%" ~ truth_budget_terms ~ "%") // 有值的真值 + + | "" // 空真值 +} +truth_budget_term = @{ + truth_budget_content+ +} +truth_budget_terms = { + (truth_budget_term ~ (";" ~ truth_budget_term)*) + | "" +} + +// 任务 +task = { + budget ~ sentence +} +budget = { + "$" ~ truth_budget_terms ~ "$" +} + +// 基础设施 // +// * 📝用于「防止误吃系词」的方法:字符 ~ (要避免的 ~ 字符)* +word_content = { word_content_char ~ (!copula ~ word_content_char)* } +word_content_char = { LETTER | NUMBER | "_" | "-" } +truth_budget_content = { ASCII_DIGIT | "." } + +// 忽略空白符 +WHITESPACE = _{ WHITE_SPACE } diff --git a/src/cin_implements/opennars/dialect_opennars.pest b/src/cin_implements/opennars/dialect_opennars.pest new file mode 100644 index 0000000..637cfd0 --- /dev/null +++ b/src/cin_implements/opennars/dialect_opennars.pest @@ -0,0 +1,86 @@ +// 空白符 | 所有Unicode空白符 +WHITESPACE = { WHITE_SPACE } + +// 总入口:词法Narsese +narsese = { + task + | sentence + | term +} + +// 任务:有预算的语句 +task = { + budget ~ sentence +} + +// 预算 | 不包括「空字串」隐含的「空预算」 +budget = { + "$" ~ budget_content ~ "$" +} +budget_content = { + (truth_budget_term ~ (";" ~ truth_budget_term)) + | "" // 空预算 +} +truth_budget_term = @{ (ASCII_DIGIT | ".")+ } + +// 语句 = 词项 标点 时间戳 真值 +sentence = { + term ~ punctuation ~ stamp ~ truth +} + +// 词项 = 陈述 | 复合 | 原子 +term = { + statement + | compound + | atom +} + +// 陈述 = <词项 系词 词项> +statement = { + "<" ~ term ~ copula ~ term ~ ">" +} + +copula = @{ + (punct_sym ~ "-" ~ punct_sym) // 继承/相似/实例/属性/实例属性 + + | (punct_sym ~ "=" ~ punct_sym) // 蕴含/等价 + + | (punct_sym ~ punct_sym ~ ">") // 时序性蕴含/时序性等价 +} + +punct_sym = { (PUNCTUATION | SYMBOL) } + +// 复合 = (连接词, 词项...) +compound = { + "(" ~ connecter ~ "," ~ term ~ ("," ~ term)* ~ ")" +} + +connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } + +// 原子 = 前缀(可选) 内容 +atom = { + "_"+ // 占位符 + + | (atom_prefix ~ atom_content) // 变量/间隔/操作 + + | atom_content // 词语 +} +atom_prefix = @{ punct_sym+ } +atom_content = @{ atom_char ~ (!copula ~ atom_char)* } +atom_char = { LETTER | NUMBER } + +// 标点 +punctuation = { (PUNCTUATION | SYMBOL) } + +// 时间戳 +stamp = { + (":" ~ (!":" ~ ANY)+ ~ ":") // 有内容的时间戳 + + | "" // 空时间戳 +} + +// 真值 +truth = { + ("%" ~ (truth_budget_term ~ (";" ~ truth_budget_term)) ~ "%") + | "" // 空真值 +} diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs index b31cfc4..7e5628a 100644 --- a/src/cin_implements/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -20,16 +20,12 @@ use std::path::PathBuf; /// * ⚠️该配置参考的是PyNARS的`ConsolePlus`模块 /// * 🚩【2024-03-25 08:55:07】基于Python模块文件启动PyNARS Shell #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct VmPython { +pub struct PyNARS { /// 命令生成器 command_generator: CommandGeneratorPython, } -/// 兼容性别名 -#[doc(alias = "VmPython")] -pub type PyNARS = VmPython; - -impl VmPython { +impl PyNARS { pub fn new(root_path: impl Into, module_path: &str) -> Self { Self { command_generator: CommandGeneratorPython::new(root_path, module_path), @@ -38,7 +34,7 @@ impl VmPython { } /// 启动到「命令行运行时」 -impl VmLauncher for VmPython { +impl VmLauncher for PyNARS { fn launch(self) -> CommandVmRuntime { // 构造指令 let command = self.command_generator.generate_command(); diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 11a0d9f..5497f11 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -315,7 +315,7 @@ impl IoProcessManager { listener_code(&buf); // 向「进程消息接收者」传递消息(实际上是「输出」) if let Err(e) = child_out_sender.send(buf.clone()) { - println!("无法接收子进程输出:{e:?}"); + println!("无法向主进程发送消息:{e:?}"); break; } // // 输出计数 @@ -325,12 +325,19 @@ impl IoProcessManager { // } // ! 【2024-03-24 01:42:46】现在取消「输出计数」机制:计数可能不准确,并且被`try_recv`取代 } - // 报错⇒显示错误,终止读取 + // 报错⇒处理错误 Err(e) => { - println!("子进程报错: {:?}", e); - break; + // 只是「不包含字符」(过早读取)⇒跳过 + let message = e.to_string(); + if message.contains("stream did not contain") { + // 什么都不做 + } else { + println!("无法接收子进程输出:{e:?} in「{buf}」"); + break; + } } } + // 清空缓冲区 buf.clear(); } }) From 32b7145f85c62a2ddd6d5c0167834eb6e64f6284 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:10:19 +0800 Subject: [PATCH 24/59] =?UTF-8?q?feat:=20:sparkles:=20OpenNARS=E6=96=B9?= =?UTF-8?q?=E8=A8=80=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完成OpenNARS的方言解析器 --- Cargo.toml | 7 +- src/cin_implements/ona/dialect.rs | 2 +- src/cin_implements/opennars/dialect.rs | 291 +++++++++++++++++- .../opennars/dialect_opennars.pest | 96 ++++-- src/cin_implements/pynars/translators.rs | 12 +- src/runtime/command_vm/api/translators.rs | 8 + 6 files changed, 364 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27e56d8..894b4f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,12 @@ features = [] path = "../Narsese.rs" # git = "https://github.com/ARCJ137442/Narsese.rs" # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 -features = ["bundled"] +features = [ + # * 🚩【2024-03-29 09:52:56】在「方言词法折叠」中,需要使用其中的常量 + "enum_narsese", + # * 📌承继NAVM + "lexical_narsese", +] [dependencies.navm] # ! 本地依赖可以不添加版本 diff --git a/src/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs index 16a9f8f..0a5a3a3 100644 --- a/src/cin_implements/ona/dialect.rs +++ b/src/cin_implements/ona/dialect.rs @@ -24,7 +24,7 @@ pub struct DialectParser; /// * 📄`(* {SELF})` /// * 📄`({SELF} * x)` pub fn parse(input: &str) -> ParseResult { - let s = dbg!(DialectParser::parse(Rule::narsese, input)); + let _ = dbg!(DialectParser::parse(Rule::narsese, input)); FORMAT_ASCII.parse(input) // #![allow(unused)] // todo!("ONA方言!") diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs index 4fe244e..20ecc43 100644 --- a/src/cin_implements/opennars/dialect.rs +++ b/src/cin_implements/opennars/dialect.rs @@ -1,22 +1,287 @@ //! OpenNARS方言 //! * 🎯解析OpenNARS输出,如 -//! * 📄以空格分隔的词项:`(* {SELF})` -//! * 📄`({SELF} * x)` -//! -//! TODO: 完成语法解析 +//! * 📄特有的「操作」语法:`(^left, {SELF})` => `<(*, {SELF}) --> ^left>` -use narsese::conversion::string::impl_lexical::{ - format_instances::FORMAT_ASCII, structs::ParseResult, +use crate::runtime::TranslateError; +use anyhow::{Ok, Result}; +use narsese::{ + conversion::string::{ + impl_enum::format_instances::FORMAT_ASCII, impl_lexical::structs::MidParseResult, + }, + lexical::{Budget, Narsese, Term, Truth}, }; +use pest::{iterators::Pair, Parser}; +use pest_derive::Parser; +#[derive(Parser)] // ! ↓ 必须从项目根目录开始 +#[grammar = "src/cin_implements/opennars/dialect_opennars.pest"] +pub struct DialectParser; + +/// 使用[`pest`]将输入的「OpenNARS方言」转换为「词法Narsese」 /// 以OpenNARS的语法解析出Narsese /// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 -/// -/// TODO: 兼容OpenNARS特有之语法 /// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` -/// -/// TODO: 使用[`pest`]将输入的「OpenNARS方言」转换为「词法Narsese」 -pub fn parse(input: &str) -> ParseResult { - FORMAT_ASCII.parse(input) - // todo!("OpenNARS方言!") +pub fn parse(input: &str) -> Result { + // let _ = dbg!(FORMAT_ASCII.parse(input).transform_err(anyhow::Error::from)); + // 语法解析 + let pair = DialectParser::parse(Rule::narsese, input)?.next().unwrap(); + + // 语法折叠 + let folded = dbg!(fold_pest(pair))?; + + // 返回 + Ok(folded) +} + +/// 将[`pest`]解析出的[`Pair`]辅助折叠到「词法Narsese」中 +fn fold_pest(pest_parsed: Pair) -> Result { + let mut mid_result = MidParseResult { + budget: None, + term: None, + punctuation: None, + stamp: None, + truth: None, + }; + fold_pest_procedural(pest_parsed, &mut mid_result)?; + match mid_result.fold() { + Some(narsese) => Ok(narsese), + None => TranslateError::err_anyhow("无效的中间结果"), + } +} + +/// 过程式折叠[`pest`]词法值 +/// * 🎯向「中间解析结果」填充元素,而无需考虑元素的顺序与返回值类型 +fn fold_pest_procedural(pair: Pair, result: &mut MidParseResult) -> Result<()> { + match pair.as_rule() { + // 不会被匹配的`_{..}`元素 + Rule::WHITESPACE | Rule::narsese | Rule::budget_content | Rule::term => { + unreachable!("规则{:?}不会被匹配到!{pair:?}", pair.as_rule()) + } + // Narsese:转发 | 📝语法文件中前缀`_`的,若为纯内容则自动忽略,若内部有元素则自动提取 + // Rule::narsese => fold_pest_procedural(pair.into_inner().next().unwrap(), result), + // 任务⇒所有内部元素递归 | 安装「预算值」「语句」 + Rule::task => { + for pair in pair.into_inner() { + fold_pest_procedural(pair, result)?; + } + } + // 预算⇒尝试解析并填充预算 + Rule::budget => result.budget = Some(fold_pest_budget(pair)?), + // 语句⇒所有内部元素递归 | 安装「词项」「标点」「时间戳」「真值」 + Rule::sentence => { + for pair in pair.into_inner() { + fold_pest_procedural(pair, result)?; + } + } + // 词项⇒提取其中的元素 | 安装 原子 / 复合 / 陈述 | ✅pest自动解包 + // Rule::term => fold_pest_procedural(pair.into_inner().next().unwrap(), result), + Rule::statement => result.term = Some(fold_pest_statement(pair)?), + Rule::compound => result.term = Some(fold_pest_compound(pair)?), + Rule::atom => result.term = Some(fold_pest_atom(pair)?), + // 时间戳 / 标点 ⇒ 直接插入 + Rule::punctuation => result.punctuation = Some(pair.as_str().into()), + Rule::stamp => result.stamp = Some(pair.as_str().into()), + // 真值 ⇒ 解析 ~ 插入 + Rule::truth => result.truth = Some(fold_pest_truth(pair)?), + // 仅出现在内部解析中的不可达规则 + _ => unreachable!("仅出现在内部解析的不可达规则!{:?}{pair}", pair.as_rule()), + } + Ok(()) +} + +/// 折叠[`pest`]真值 +fn fold_pest_truth(pair: Pair) -> Result { + let mut v = Truth::new(); + for pair_value_str in pair.into_inner() { + v.push(pair_value_str.as_str().to_string()); + } + Ok(dbg!(v)) +} + +/// 折叠[`pest`]预算值 +fn fold_pest_budget(pair: Pair) -> Result { + let mut v = Budget::new(); + for pair_value_str in pair.into_inner() { + v.push(pair_value_str.as_str().to_string()); + } + Ok(v) +} + +/// 折叠[`pest`]词项 +/// * 🎯用于「复合词项/陈述」内部词项的解析 +/// * 📌原子、复合、陈述均可 +fn fold_pest_term(pair: Pair) -> Result { + // 根据规则分派 + match pair.as_rule() { + Rule::atom => fold_pest_atom(pair), + Rule::compound => fold_pest_compound(pair), + Rule::statement => fold_pest_statement(pair), + _ => unreachable!("词项只有可能是原子、复合与陈述 | {pair}"), + } +} + +/// 折叠[`pest`]原子词项 +fn fold_pest_atom(pair: Pair) -> Result { + let mut prefix = String::new(); + let mut name = String::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::atom_prefix => prefix.push_str(pair.as_str()), + Rule::atom_content => name.push_str(pair.as_str()), + _ => unreachable!("原子词项只可能有「前缀」与「名称(内容)」两种 | {pair}"), + } + } + Ok(Term::Atom { prefix, name }) +} + +/// 折叠[`pest`]复合词项 +/// * 🚩【2024-03-29 09:42:36】因「需要通过规则识别『外延集/内涵集』」通过「进一步向下分发」细化被折叠对象 +fn fold_pest_compound(pair: Pair) -> Result { + // compound(0, 7, [connecter(1, 2), atom(3, 4, [atom_content(3, 4)]), atom(5, 6, [atom_content(5, 6)])]) + // compound(0, 6, [atom(1, 2, [atom_content(1, 2)]), atom(4, 5, [atom_content(4, 5)])]) + let pair = pair.into_inner().next().unwrap(); + match pair.as_rule() { + Rule::compound_common => { + // * 🚩通用复合词项:连接词 词项... + let mut pairs = pair.into_inner(); + let connecter = pairs.next().unwrap().as_str().into(); + let mut terms = vec![]; + // 遍历剩下的元素 + for pair in pairs { + terms.push(fold_pest_term(pair)?); + } + Ok(Term::Compound { connecter, terms }) + } + Rule::compound_operation => { + // * 🚩通用复合词项:连接词 词项... + let mut pairs = pair.into_inner(); + // 第一个词项应该是谓词 + let predicate = fold_pest_term(pairs.next().unwrap())?; + // 解析主词的组分 + let mut subject_terms = vec![]; + // 遍历剩下的元素 + for pair in pairs { + subject_terms.push(fold_pest_term(pair)?); + } + // 构造 & 返回 + // * 🚩【2024-03-29 09:51:46】使用「枚举Narsese」的语法内容,避免硬编码 + Ok(Term::Statement { + copula: FORMAT_ASCII.statement.copula_inheritance.into(), + subject: Box::new(Term::Compound { + connecter: FORMAT_ASCII.compound.connecter_product.into(), + terms: subject_terms, + }), + predicate: Box::new(predicate), + }) + } + Rule::ext_set => { + let mut terms = vec![]; + for pair in pair.into_inner() { + terms.push(fold_pest_term(pair)?); + } + // 构造 & 返回 + // * 🚩【2024-03-29 09:51:46】使用「枚举Narsese」的语法内容,避免硬编码 + Ok(Term::Set { + left_bracket: FORMAT_ASCII.compound.brackets_set_extension.0.into(), + terms, + right_bracket: FORMAT_ASCII.compound.brackets_set_extension.1.into(), + }) + } + Rule::int_set => { + let mut terms = vec![]; + for pair in pair.into_inner() { + terms.push(fold_pest_term(pair)?); + } + // 构造 & 返回 + // * 🚩【2024-03-29 09:51:46】使用「枚举Narsese」的语法内容,避免硬编码 + Ok(Term::Set { + left_bracket: FORMAT_ASCII.compound.brackets_set_intension.0.into(), + terms, + right_bracket: FORMAT_ASCII.compound.brackets_set_intension.1.into(), + }) + } + _ => unreachable!("复合词项只可能是「通用」「操作」「外延集」「内涵集」四种 | {pair}"), + } +} + +/// 折叠[`pest`]陈述 +fn fold_pest_statement(pair: Pair) -> Result { + // ! 陈述结构保证:主词+系词+谓词 + let mut pairs = pair.into_inner(); + // 🚩顺序折叠 + let subject = fold_pest_term(pairs.next().unwrap())?; + let copula = pairs.next().unwrap().as_str(); + let predicate = fold_pest_term(pairs.next().unwrap())?; + // 创建 + Ok(Term::new_statement(copula, subject, predicate)) +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + /// 测试/方言解析器 🚧 + #[test] + fn test_dialect_parser() { + // 📄部分源自`long_term_stability.nal` + let narseses = " + <(&|,(^want,{SELF},$1,FALSE),(^anticipate,{SELF},$1)) =|> <(*,{SELF},$1) --> afraid_of>>. + B>. + {A, B} + <{tim} --> (/,livingIn,_,{graz})>. %0% + <<(*,$1,sunglasses) --> own> ==> <$1 --> [aggressive]>>. + <(*,{tom},sunglasses) --> own>. + <<$1 --> [aggressive]> ==> <$1 --> murder>>. + <<$1 --> (/,livingIn,_,{graz})> ==> <$1 --> murder>>. + <{?who} --> murder>? + <{tim} --> (/,livingIn,_,{graz})>. + <{tim} --> (/,livingIn,_,{graz})>. %0% + <<(*,$1,sunglasses) --> own> ==> <$1 --> [aggressive]>>. + <(*,{tom},(&,[black],glasses)) --> own>. + <<$1 --> [aggressive]> ==> <$1 --> murder>>. + <<$1 --> (/,livingIn,_,{graz})> ==> <$1 --> murder>>. + (&,[black],glasses)>. + <{?who} --> murder>? + <(*,toothbrush,plastic) --> made_of>. + <(&/,<(*,$1,plastic) --> made_of>,(^lighter,{SELF},$1)) =/> <$1 --> [heated]>>. + <<$1 --> [heated]> =/> <$1 --> [melted]>>. + <<$1 --> [melted]> <|> <$1 --> [pliable]>>. + <(&/,<$1 --> [pliable]>,(^reshape,{SELF},$1)) =/> <$1 --> [hardened]>>. + <<$1 --> [hardened]> =|> <$1 --> [unscrewing]>>. + object>. + (&&,<#1 --> object>,<#1 --> [unscrewing]>)! + <{SELF} --> [hurt]>! %0% + <{SELF} --> [hurt]>. :|: %0% + <(&/,<(*,{SELF},wolf) --> close_to>,+1000) =/> <{SELF} --> [hurt]>>. + <(*,{SELF},wolf) --> close_to>. :|: + <(&|,(^want,{SELF},$1,FALSE),(^anticipate,{SELF},$1)) =|> <(*,{SELF},$1) --> afraid_of>>. + <(*,{SELF},?what) --> afraid_of>? + A>. :|: %1.00;0.90% + B>. :|: %1.00;0.90% + C>. :|: %1.00;0.90% + A>. :|: %1.00;0.90% + B>. :|: %1.00;0.90% + C>>? + <(*,cup,plastic) --> made_of>. + object>. + [bendable]>. + [bendable]>. + object>. + <(&/,<(*,$1,plastic) --> made_of>,(^lighter,{SELF},$1)) =/> <$1 --> [heated]>>. + <<$1 --> [heated]> =/> <$1 --> [melted]>>. + <<$1 --> [melted]> <|> <$1 --> [pliable]>>. + <(&/,<$1 --> [pliable]>,(^reshape,{SELF},$1)) =/> <$1 --> [hardened]>>. + <<$1 --> [hardened]> =|> <$1 --> [unscrewing]>>. + (&&,<#1 --> object>,<#1 --> [unscrewing]>)! + "; + let narseses = narseses + .split('\n') + .map(str::trim) + .filter(|l| !l.is_empty()); + for narsese in narseses { + let parsed = parse(narsese).expect("pest解析失败!"); + dbg!(parsed); + } + } } diff --git a/src/cin_implements/opennars/dialect_opennars.pest b/src/cin_implements/opennars/dialect_opennars.pest index 637cfd0..ed56597 100644 --- a/src/cin_implements/opennars/dialect_opennars.pest +++ b/src/cin_implements/opennars/dialect_opennars.pest @@ -1,86 +1,120 @@ -// 空白符 | 所有Unicode空白符 -WHITESPACE = { WHITE_SPACE } +//! OpenNARS方言语法 +//! * 🎯从OpenNARS输出中解析Narsese +//! * 📌「NARS操作」的简写`(^op, param, arg)` -// 总入口:词法Narsese -narsese = { +/// 空白符 | 所有Unicode空白符,解析前忽略 +WHITESPACE = _{ WHITE_SPACE } + +/// 总入口:词法Narsese | 优先级:任务 > 语句 > 词项 +narsese = _{ task | sentence | term } -// 任务:有预算的语句 +/// 任务:有预算的语句 task = { budget ~ sentence } -// 预算 | 不包括「空字串」隐含的「空预算」 -budget = { +/// 预算值 | 不包括「空字串」隐含的「空预算」 +budget = { "$" ~ budget_content ~ "$" } -budget_content = { - (truth_budget_term ~ (";" ~ truth_budget_term)) - | "" // 空预算 + +/// 预算值内容 +budget_content = _{ + (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) + | "" // 空预算(但带括号) } + +/// 通用于真值、预算值的项 | 用作内部数值,不约束取值范围 truth_budget_term = @{ (ASCII_DIGIT | ".")+ } -// 语句 = 词项 标点 时间戳 真值 +/// 语句 = 词项 标点 时间戳? 真值? sentence = { - term ~ punctuation ~ stamp ~ truth + term ~ punctuation ~ stamp? ~ truth? } -// 词项 = 陈述 | 复合 | 原子 -term = { +/// 词项 = 陈述 | 复合 | 原子 +term = _{ statement | compound | atom } -// 陈述 = <词项 系词 词项> +/// 陈述 = <词项 系词 词项> statement = { "<" ~ term ~ copula ~ term ~ ">" } +/// 陈述系词 copula = @{ (punct_sym ~ "-" ~ punct_sym) // 继承/相似/实例/属性/实例属性 | (punct_sym ~ "=" ~ punct_sym) // 蕴含/等价 - | (punct_sym ~ punct_sym ~ ">") // 时序性蕴含/时序性等价 + | ("=" ~ punct_sym ~ ">") // 时序性蕴含 + + | ("<" ~ punct_sym ~ ">") // 时序性等价 } +/// 标点符号 | 用于「原子词项前缀」「复合词项连接词」和「陈述系词」 punct_sym = { (PUNCTUATION | SYMBOL) } -// 复合 = (连接词, 词项...) +/// 复合 = (连接词, 词项...) | {外延集...} | [内涵集...] +/// * 🆕对OpenNARS兼容形如`(^op, {SELF}, LEFT)`的输出语法 +/// * 🚩此处不进行「静默内联」:便于在「折叠函数」中向下分派 compound = { - "(" ~ connecter ~ "," ~ term ~ ("," ~ term)* ~ ")" + compound_common + | compound_operation + | ext_set + | int_set } +/// 通用的复合词项 +compound_common = { ("(" ~ connecter ~ "," ~ term ~ ("," ~ term)* ~ ")") } + +/// 🆕OpenNARS特定的「操作简写」输出 +/// * 🚩【2024-03-29 09:40:38】目前通用成`(A, B, C)` => `<(*, B, C) --> A>`的转换方式 +compound_operation = { "(" ~ term ~ ("," ~ term)* ~ ")" } + +/// 外延集 | 📌【2024-03-29 09:39:39】pest代码折叠中会丢掉所有「不被规则捕获的字符串信息」 +ext_set = { "{" ~ term ~ ("," ~ term)* ~ "}" } + +/// 内涵集 +int_set = { "[" ~ term ~ ("," ~ term)* ~ "]" } + +/// 复合词项连接词 connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } -// 原子 = 前缀(可选) 内容 -atom = { +/// 原子 = 前缀(可选) 内容 +atom = { "_"+ // 占位符 - | (atom_prefix ~ atom_content) // 变量/间隔/操作 + | (atom_prefix ~ atom_content) // 变量/间隔/操作…… | atom_content // 词语 } -atom_prefix = @{ punct_sym+ } + +/// 原子词项前缀 +atom_prefix = @{ punct_sym+ } + +/// 原子词项内容 | 已避免与「复合词项系词」相冲突 atom_content = @{ atom_char ~ (!copula ~ atom_char)* } -atom_char = { LETTER | NUMBER } -// 标点 +/// 能作为「原子词项内容」的字符 +atom_char = { LETTER | NUMBER | "_" | "-" } + +/// 标点 punctuation = { (PUNCTUATION | SYMBOL) } -// 时间戳 +/// 时间戳 | 空时间戳会直接在「语句」中缺省 stamp = { - (":" ~ (!":" ~ ANY)+ ~ ":") // 有内容的时间戳 - - | "" // 空时间戳 + ":" ~ (!":" ~ ANY)+ ~ ":" } -// 真值 +/// 真值 | 空真值会直接在「语句」中缺省 truth = { - ("%" ~ (truth_budget_term ~ (";" ~ truth_budget_term)) ~ "%") - | "" // 空真值 + "%" ~ (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) ~ "%" } diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index 3381612..7e1f847 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -1,5 +1,5 @@ -//! ONA在「命令行运行时」的转译器 -//! * 🎯维护与ONA Shell的交互 +//! PyNARS在「命令行运行时」的转译器 +//! * 🎯维护与PyNARS的交互 //! * 📌基于命令行输入输出的字符串读写 //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 @@ -13,8 +13,8 @@ use navm::{ use regex::Regex; use util::pipe; -/// ONA的「输入转译」函数 -/// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 +/// PyNARS的「输入转译」函数 +/// * 🎯用于将统一的「NAVM指令」转译为「PyNARS输入」 pub fn input_translate(cmd: Cmd) -> Result { let content = match cmd { // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) @@ -56,8 +56,8 @@ fn try_get_output_type(inp: &str) -> Option { captured.map(|c| c[4].to_string()) } -/// ONA的「输出转译」函数 -/// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 +/// PyNARS的「输出转译」函数 +/// * 🎯用于将PyNARS的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 /// # * 去除其中的ANSI转义序列,如:`\e[39m` # 并去除前后多余空格 /// local actual_line::String = strip(replace(line, r"\e\[[0-9;]*m" => "")) diff --git a/src/runtime/command_vm/api/translators.rs b/src/runtime/command_vm/api/translators.rs index cfc7976..499f578 100644 --- a/src/runtime/command_vm/api/translators.rs +++ b/src/runtime/command_vm/api/translators.rs @@ -109,6 +109,14 @@ impl TranslateError { pub fn error_anyhow(value: impl Error) -> anyhow::Error { Self::from_error(value).into() } + + /// 从「一切可以转换为其自身的值」构建[`anyhow::Result`] + pub fn err_anyhow(from: S) -> anyhow::Result + where + Self: From, + { + Err(Self::from(from).into()) + } /// 从[`Self::from`]转换到[`anyhow::Error`] /// * 🚩封装为自身类型 /// * ❗实际上`.into()`比`::anyhow`短 From a864c7d8a82c422f97e1df6e0dc09c6d35d23382 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 29 Mar 2024 14:21:10 +0800 Subject: [PATCH 25/59] =?UTF-8?q?fix:=20:bug:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90OpenNARS=E6=96=B9=E8=A8=80=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=8D=A0=E4=BD=8D=E7=AC=A6=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cin_implements/opennars/dialect.rs | 63 +++++++++++++++---- .../opennars/dialect_opennars.pest | 5 +- src/cin_implements/opennars/translators.rs | 2 +- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs index 20ecc43..fd15ede 100644 --- a/src/cin_implements/opennars/dialect.rs +++ b/src/cin_implements/opennars/dialect.rs @@ -27,7 +27,7 @@ pub fn parse(input: &str) -> Result { let pair = DialectParser::parse(Rule::narsese, input)?.next().unwrap(); // 语法折叠 - let folded = dbg!(fold_pest(pair))?; + let folded = fold_pest(pair)?; // 返回 Ok(folded) @@ -95,7 +95,7 @@ fn fold_pest_truth(pair: Pair) -> Result { for pair_value_str in pair.into_inner() { v.push(pair_value_str.as_str().to_string()); } - Ok(dbg!(v)) + Ok(v) } /// 折叠[`pest`]预算值 @@ -125,10 +125,18 @@ fn fold_pest_atom(pair: Pair) -> Result { let mut prefix = String::new(); let mut name = String::new(); for pair in pair.into_inner() { + let pair_str = pair.as_str(); match pair.as_rule() { - Rule::atom_prefix => prefix.push_str(pair.as_str()), - Rule::atom_content => name.push_str(pair.as_str()), - _ => unreachable!("原子词项只可能有「前缀」与「名称(内容)」两种 | {pair}"), + Rule::atom_prefix => prefix.push_str(pair_str), + Rule::atom_content => name.push_str(pair_str), + // 占位符 + Rule::placeholder => { + prefix.push('_'); + if pair_str.len() > 1 { + name.push_str(&pair_str[1..]); + } + } + _ => unreachable!("原子词项只可能有「占位符」或「前缀+名称(内容)」两种 | {pair}"), } } Ok(Term::Atom { prefix, name }) @@ -219,13 +227,25 @@ fn fold_pest_statement(pair: Pair) -> Result { /// 单元测试 #[cfg(test)] mod tests { + use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; + use util::first; + use super::*; /// 测试/方言解析器 🚧 #[test] fn test_dialect_parser() { + // 统计用 + let mut 直接相等的个数: usize = 0; + let mut 删去空格后相等的个数: usize = 0; + let mut 形式有变的 = vec![]; + // 📄部分源自`long_term_stability.nal` let narseses = " + _ + __ + ___ + <(&|,(^want,{SELF},$1,FALSE),(^anticipate,{SELF},$1)) =|> <(*,{SELF},$1) --> afraid_of>>. B>. {A, B} @@ -274,14 +294,35 @@ mod tests { <(&/,<$1 --> [pliable]>,(^reshape,{SELF},$1)) =/> <$1 --> [hardened]>>. <<$1 --> [hardened]> =|> <$1 --> [unscrewing]>>. (&&,<#1 --> object>,<#1 --> [unscrewing]>)! - "; - let narseses = narseses - .split('\n') - .map(str::trim) - .filter(|l| !l.is_empty()); + " + // 初步数据处理 + .split('\n') + .map(str::trim) + .filter(|l| !l.is_empty()); + + // 开始测试解析 + let 去掉空格 = |s: &str| s.chars().filter(|c| !c.is_whitespace()).collect::(); for narsese in narseses { let parsed = parse(narsese).expect("pest解析失败!"); - dbg!(parsed); + let parsed_str = FORMAT_ASCII.format_narsese(&parsed); + // 对齐并展示 + println!(" {narsese:?}\n => {:?}", parsed_str); + + first! { + narsese == parsed_str => 直接相等的个数 += 1, + 去掉空格(narsese) == 去掉空格(&parsed_str) => 删去空格后相等的个数 += 1, + _ => 形式有变的.push((去掉空格(narsese), 去掉空格(&parsed_str))), + } + } + + // 报告 + println!("✅直接相等的个数:{直接相等的个数}"); + println!("✅删去空格后相等的个数:{删去空格后相等的个数}"); + println!("⚠️形式有变的个数:{}", 形式有变的.len()); + for (n, (narsese, parsed_str)) in 形式有变的.iter().enumerate() { + // 报告形式有变的 + println!(" {n}:\n\t{narsese:?}\n =?>\t{:?}", parsed_str); } + println!("测试完毕!"); } } diff --git a/src/cin_implements/opennars/dialect_opennars.pest b/src/cin_implements/opennars/dialect_opennars.pest index ed56597..6ee55db 100644 --- a/src/cin_implements/opennars/dialect_opennars.pest +++ b/src/cin_implements/opennars/dialect_opennars.pest @@ -90,13 +90,16 @@ connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } /// 原子 = 前缀(可选) 内容 atom = { - "_"+ // 占位符 + placeholder // 占位符 | (atom_prefix ~ atom_content) // 变量/间隔/操作…… | atom_content // 词语 } +/// 占位符 = 纯下划线字符串 +placeholder = @{ "_"+ } + /// 原子词项前缀 atom_prefix = @{ punct_sym+ } diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index cdb69cf..23820b4 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -175,7 +175,7 @@ fn strip_parse_narsese(tail: &str) -> Result { // 去尾 .rfind('{') // 截取 & 解析 - .map(|right_index| parse_narsese_opennars(&tail[..right_index])); + .map(|right_index| parse_narsese_opennars(tail[..right_index].trim())); // 提取解析结果 match narsese { // 解析成功⇒提取 & 返回 From 83c48196f6d2921dbc039fd89fb96fef7961552d Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:28:07 +0800 Subject: [PATCH 26/59] =?UTF-8?q?feat:=20:sparkles:=20ONA=E6=96=B9?= =?UTF-8?q?=E8=A8=80=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初步完成ONA方言解析,并在此过程中完善OpenNARS的方言解析 --- .vscode/settings.json | 6 +- src/cin_implements/ona/dialect.rs | 371 +++++++++++++- src/cin_implements/ona/dialect_ona.pest | 174 ++++--- src/cin_implements/ona/translators.rs | 475 +++++++++++++++++- src/cin_implements/opennars/dialect.rs | 9 +- .../opennars/dialect_opennars.pest | 11 +- src/runtime/command_vm/runtime.rs | 14 +- 7 files changed, 932 insertions(+), 128 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2e85d01..923fc47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,12 +4,16 @@ "cSpell.words": [ "cxin", "Errno", + "hasher", + "Nalifier", "openjunars", "rfind", "runpy", + "setopname", "thiserror", "traceback", - "tstate" + "tstate", + "whatwarmer" ], "rust-analyzer.linkedProjects": [ ".\\Cargo.toml", diff --git a/src/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs index 0a5a3a3..8f66147 100644 --- a/src/cin_implements/ona/dialect.rs +++ b/src/cin_implements/ona/dialect.rs @@ -2,13 +2,16 @@ //! * 🎯解析ONA输出,如 //! * 📄以空格分隔的词项:`(* {SELF})` //! * 📄`({SELF} * x)` -//! -//! TODO: 完成语法解析 -use narsese::conversion::string::impl_lexical::{ - format_instances::FORMAT_ASCII, structs::ParseResult, +use crate::runtime::TranslateError; +use anyhow::{Ok, Result}; +use narsese::{ + conversion::string::{ + impl_enum::format_instances::FORMAT_ASCII, impl_lexical::structs::MidParseResult, + }, + lexical::{Budget, Narsese, Term, Truth}, }; -use pest::Parser; +use pest::{iterators::Pair, Parser}; use pest_derive::Parser; #[derive(Parser)] // ! ↓ 必须从项目根目录开始 @@ -17,28 +20,366 @@ pub struct DialectParser; /// 使用[`pest`]将输入的「ONA方言」转换为「词法Narsese」 /// 以ONA的语法解析出Narsese -/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 -/// -/// TODO: 兼容ONA的方言语法 /// * 📌重点在「用空格分隔乘积词项/中缀情形」的语法 /// * 📄`(* {SELF})` /// * 📄`({SELF} * x)` -pub fn parse(input: &str) -> ParseResult { - let _ = dbg!(DialectParser::parse(Rule::narsese, input)); - FORMAT_ASCII.parse(input) - // #![allow(unused)] - // todo!("ONA方言!") +pub fn parse(input: &str) -> Result { + // 语法解析 + let pair = DialectParser::parse(Rule::narsese, input)?.next().unwrap(); + + // 语法折叠 + let folded = fold_pest(pair)?; + + // 返回 + Ok(folded) +} + +/// 将[`pest`]解析出的[`Pair`]辅助折叠到「词法Narsese」中 +fn fold_pest(pest_parsed: Pair) -> Result { + let mut mid_result = MidParseResult { + budget: None, + term: None, + punctuation: None, + stamp: None, + truth: None, + }; + fold_pest_procedural(pest_parsed, &mut mid_result)?; + match mid_result.fold() { + Some(narsese) => Ok(narsese), + None => TranslateError::err_anyhow("无效的中间结果"), + } +} + +/// 过程式折叠[`pest`]词法值 +/// * 🎯向「中间解析结果」填充元素,而无需考虑元素的顺序与返回值类型 +pub(super) fn fold_pest_procedural(pair: Pair, result: &mut MidParseResult) -> Result<()> { + match pair.as_rule() { + // 不会被匹配的`_{..}`元素 + Rule::WHITESPACE | Rule::narsese | Rule::budget_content | Rule::term => { + unreachable!("规则{:?}不会被匹配到!{pair:?}", pair.as_rule()) + } + // Narsese:转发 | 📝语法文件中前缀`_`的,若为纯内容则自动忽略,若内部有元素则自动提取 + // Rule::narsese => fold_pest_procedural(pair.into_inner().next().unwrap(), result), + // 任务⇒所有内部元素递归 | 安装「预算值」「语句」 + Rule::task => { + for pair in pair.into_inner() { + fold_pest_procedural(pair, result)?; + } + } + // 预算⇒尝试解析并填充预算 + Rule::budget => result.budget = Some(fold_pest_budget(pair)?), + // 语句⇒所有内部元素递归 | 安装「词项」「标点」「时间戳」「真值」 + Rule::sentence => { + for pair in pair.into_inner() { + fold_pest_procedural(pair, result)?; + } + } + // 词项⇒提取其中的元素 | 安装 原子 / 复合 / 陈述 | ✅pest自动解包 + // Rule::term => fold_pest_procedural(pair.into_inner().next().unwrap(), result), + Rule::statement => result.term = Some(fold_pest_statement(pair)?), + Rule::compound => result.term = Some(fold_pest_compound(pair)?), + Rule::atom => result.term = Some(fold_pest_atom(pair)?), + // 时间戳 / 标点 ⇒ 直接插入 + Rule::punctuation => result.punctuation = Some(pair.as_str().into()), + Rule::stamp => result.stamp = Some(pair.as_str().into()), + // 真值 ⇒ 解析 ~ 插入 + Rule::truth => result.truth = Some(fold_pest_truth(pair)?), + // 仅出现在内部解析中的不可达规则 + _ => unreachable!("仅出现在内部解析的不可达规则!{:?}{pair}", pair.as_rule()), + } + Ok(()) +} + +/// 折叠[`pest`]真值 +pub(super) fn fold_pest_truth(pair: Pair) -> Result { + let mut v = Truth::new(); + for pair_value_str in pair.into_inner() { + v.push(pair_value_str.as_str().to_string()); + } + Ok(v) +} + +/// 折叠[`pest`]预算值 +pub(super) fn fold_pest_budget(pair: Pair) -> Result { + let mut v = Budget::new(); + for pair_value_str in pair.into_inner() { + v.push(pair_value_str.as_str().to_string()); + } + Ok(v) +} + +/// 折叠[`pest`]词项 +/// * 🎯用于「复合词项/陈述」内部词项的解析 +/// * 📌原子、复合、陈述均可 +pub(super) fn fold_pest_term(pair: Pair) -> Result { + // 根据规则分派 + match pair.as_rule() { + Rule::atom => fold_pest_atom(pair), + Rule::compound => fold_pest_compound(pair), + Rule::statement => fold_pest_statement(pair), + _ => unreachable!("词项只有可能是原子、复合与陈述 | {pair}"), + } +} + +/// 折叠[`pest`]原子词项 +pub(super) fn fold_pest_atom(pair: Pair) -> Result { + let mut prefix = String::new(); + let mut name = String::new(); + for pair in pair.into_inner() { + let pair_str = pair.as_str(); + match pair.as_rule() { + Rule::atom_prefix => prefix.push_str(pair_str), + Rule::atom_content => name.push_str(pair_str), + // 占位符 + Rule::placeholder => { + prefix.push('_'); + if pair_str.len() > 1 { + name.push_str(&pair_str[1..]); + } + } + _ => unreachable!("原子词项只可能有「占位符」或「前缀+名称(内容)」两种 | {pair}"), + } + } + Ok(Term::Atom { prefix, name }) +} + +/// 折叠[`pest`]复合词项 +/// * 🚩【2024-03-29 09:42:36】因「需要通过规则识别『外延集/内涵集』」通过「进一步向下分发」细化被折叠对象 +pub(super) fn fold_pest_compound(pair: Pair) -> Result { + let pair = pair.into_inner().next().unwrap(); + match pair.as_rule() { + Rule::compound_common => { + // * 🚩通用复合词项:连接词 词项... + let mut pairs = pair.into_inner(); + let connecter = pairs.next().unwrap().as_str().into(); + let mut terms = vec![]; + // 遍历剩下的元素 + for pair in pairs { + terms.push(fold_pest_term(pair)?); + } + Ok(Term::Compound { connecter, terms }) + } + Rule::compound_binary => { + // * 🆕ONA特有的「二元复合词项」 + let mut pairs = pair.into_inner(); + // 第一个是左边的词项 + let left = fold_pest_term(pairs.next().unwrap())?; + // 连接词 + let connecter = pairs.next().unwrap().as_str().to_string(); + // 第二个是右边的词项 + let right = fold_pest_term(pairs.next().unwrap())?; + // 构造 & 返回 + Ok(Term::Compound { + connecter, + terms: vec![left, right], + }) + } + Rule::ext_set => { + let mut terms = vec![]; + for pair in pair.into_inner() { + terms.push(fold_pest_term(pair)?); + } + // 构造 & 返回 + // * 🚩【2024-03-29 09:51:46】使用「枚举Narsese」的语法内容,避免硬编码 + Ok(Term::Set { + left_bracket: FORMAT_ASCII.compound.brackets_set_extension.0.into(), + terms, + right_bracket: FORMAT_ASCII.compound.brackets_set_extension.1.into(), + }) + } + Rule::int_set => { + let mut terms = vec![]; + for pair in pair.into_inner() { + terms.push(fold_pest_term(pair)?); + } + // 构造 & 返回 + // * 🚩【2024-03-29 09:51:46】使用「枚举Narsese」的语法内容,避免硬编码 + Ok(Term::Set { + left_bracket: FORMAT_ASCII.compound.brackets_set_intension.0.into(), + terms, + right_bracket: FORMAT_ASCII.compound.brackets_set_intension.1.into(), + }) + } + _ => unreachable!("复合词项只可能是「通用」「操作」「外延集」「内涵集」四种 | {pair}"), + } +} + +/// 折叠[`pest`]陈述 +pub(super) fn fold_pest_statement(pair: Pair) -> Result { + // ! 陈述结构保证:主词+系词+谓词 + let mut pairs = pair.into_inner(); + // 🚩顺序折叠 + let subject = fold_pest_term(pairs.next().unwrap())?; + let copula = pairs.next().unwrap().as_str(); + let predicate = fold_pest_term(pairs.next().unwrap())?; + // 创建 + Ok(Term::new_statement(copula, subject, predicate)) } /// 单元测试 #[cfg(test)] mod tests { + use std::collections::HashSet; + use super::*; + use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; + use util::first; /// 测试/方言解析器 🚧 #[test] fn test_dialect_parser() { - let x = parse(" B>."); - let _ = dbg!(x); + // 统计用 + let mut 直接相等的个数: usize = 0; + let mut 删去空格与分隔符后相等的个数: usize = 0; + let mut 去掉空格与分隔符_字符重排后相等的个数: usize = 0; // * 🚩【2024-03-29 15:08:24】用于辨别「中缀运算符⇒重排」的情况 + let mut 形式有变的 = vec![]; + + // 📄部分改自OpenNARS`long_term_stability.nal` + // 📄部分源自ONA`Nalifier_ex1.nal`、`NAR_Nalifier_ex1.nal`、 + let narseses = " + (* {SELF}) + ({SELF} * x) + + <(&|,<(*,{SELF},$1,FALSE)-->^want>,<(*,{SELF},$1)-->^anticipate>) =|> <(*,{SELF},$1) --> afraid_of>>. + B>. + {A, B} + <{tim} --> (/,livingIn,_,{graz})>. %0% + <<(*,$1,sunglasses) --> own> ==> <$1 --> [aggressive]>>. + <(*,{tom},sunglasses) --> own>. + <<$1 --> [aggressive]> ==> <$1 --> murder>>. + <<$1 --> (/,livingIn,_,{graz})> ==> <$1 --> murder>>. + <{?who} --> murder>? + <{tim} --> (/,livingIn,_,{graz})>. + <{tim} --> (/,livingIn,_,{graz})>. %0% + <<(*,$1,sunglasses) --> own> ==> <$1 --> [aggressive]>>. + <(*,{tom},(&,[black],glasses)) --> own>. + <<$1 --> [aggressive]> ==> <$1 --> murder>>. + <<$1 --> (/,livingIn,_,{graz})> ==> <$1 --> murder>>. + (&,[black],glasses)>. + <{?who} --> murder>? + <(*,toothbrush,plastic) --> made_of>. + <(&/,<(*,$1,plastic) --> made_of>,<(*,{SELF},$1)-->^lighter>) =/> <$1 --> [heated]>>. + <<$1 --> [heated]> =/> <$1 --> [melted]>>. + <<$1 --> [melted]> <|> <$1 --> [pliable]>>. + <(&/,<$1 --> [pliable]>,<(*,{SELF},$1)-->^reshape>) =/> <$1 --> [hardened]>>. + <<$1 --> [hardened]> =|> <$1 --> [unscrewing]>>. + object>. + (&&,<#1 --> object>,<#1 --> [unscrewing]>)! + <{SELF} --> [hurt]>! %0% + <{SELF} --> [hurt]>. :|: %0% + <(&/,<(*,{SELF},wolf) --> close_to>,+1000) =/> <{SELF} --> [hurt]>>. + <(*,{SELF},wolf) --> close_to>. :|: + <(&|,<(*,{SELF},$1,FALSE)-->^want>,<(*,{SELF},$1)-->^anticipate>) =|> <(*,{SELF},$1) --> afraid_of>>. + <(*,{SELF},?what) --> afraid_of>? + A>. :|: %1.00;0.90% + B>. :|: %1.00;0.90% + C>. :|: %1.00;0.90% + A>. :|: %1.00;0.90% + B>. :|: %1.00;0.90% + C>>? + <(*,cup,plastic) --> made_of>. + object>. + [bendable]>. + [bendable]>. + object>. + <(&/,<(*,$1,plastic) --> made_of>,<(*,{SELF},$1)-->^lighter>) =/> <$1 --> [heated]>>. + <<$1 --> [heated]> =/> <$1 --> [melted]>>. + <<$1 --> [melted]> <|> <$1 --> [pliable]>>. + <(&/,<$1 --> [pliable]>,<(*,{SELF},$1)-->^reshape>) =/> <$1 --> [hardened]>>. + <<$1 --> [hardened]> =|> <$1 --> [unscrewing]>>. + (&&,<#1 --> object>,<#1 --> [unscrewing]>)! + + <{redInst} |-> [red]>. :|: %1.0% + <{redInst} |-> [green]>. :|: %0.0% + <{redInst} |-> [blue]>. :|: %0.0% + <{greenInst} |-> [red]>. :|: %0.0% + <{greenInst} |-> [green]>. :|: %1.0% + <{greenInst} |-> [blue]>. :|: %0.0% + <{blueInst} |-> [red]>. :|: %0.0% + <{blueInst} |-> [green]>. :|: %0.0% + <{blueInst} |-> [blue]>. :|: %1.0% + <{newColor} |-> [red]>. :|: %0.0% + <{newColor} |-> [green]>. :|: %0.0% + <{newColor} |-> [blue]>. :|: %0.1% + <{?what} <-> {newColor}>? :|: + <{blueInst} <-> {newColor}>. :|: %1.000000;0.810000% + <({blueInst} * {newColor}) --> (+ blue)>? :|: + <({blueInst} * {newColor}) --> (+ blue)>. :|: %1.000000;0.810000% + + <{cat} --> [meowing]>. :|: %0.6% + <{cat} --> [barking]>. :|: %0.0% + <(<({#1} ~ {#2}) --> [meowing]> &/ <({SELF} * #1) --> ^say>) =/> G>. + G! :|: + <{dog} --> [barking]>. :|: %1.0% + <{dog} --> [meowing]>. :|: %0.3% + <({cat} ~ {dog}) --> [meowing]>? :|: + <({cat} ~ {dog}) --> [meowing]>. :|: %1.000000;0.810000% + G! :|: + ({SELF} * cat) + + <( [left]> &/ ^right) =/> [free]>>. + <( [right]> &/ ^left) =/> [free]>>. + <( [front]> &/ ^left) =/> [free]>>. + <(( [open]> &/ [free]>) &/ ^forward) =/> G>. + <( [hold]> &/ <({SELF} * $obj) --> ^goto>) =/> <$obj --> [left]>>. + <( [hold]> &/ <({SELF} * $obj) --> ^goto>) =/> <$obj --> [front]>>. + <( [hold]> &/ <({SELF} * $obj) --> ^goto>) =/> <$obj --> [right]>>. + <(( [open]> &/ [left]>) &/ <({SELF} * bottle) --> ^pick>) =/> G>. + <(( [open]> &/ [front]>) &/ <({SELF} * bottle) --> ^pick>) =/> G>. + <(( [open]> &/ [right]>) &/ <({SELF} * bottle) --> ^pick>) =/> G>. + <(( [hold]> &/ [left]>) &/ ^drop) =/> G>. + <(( [hold]> &/ [front]>) &/ ^drop) =/> G>. + <(( [hold]> &/ [right]>) &/ ^drop) =/> G>. + + " + // 初步数据处理 + .split('\n') + .map(str::trim) + .filter(|l| !l.is_empty()); + + // 开始测试解析 + let 去掉空格与分隔符 = |s: &str| { + s.chars() + .filter(|c| !c.is_whitespace() && *c != ',') + .collect::() + }; + let 去掉空格与分隔符_字符集合 = |s: &str| { + s.chars() + .filter(|c| !c.is_whitespace() && *c != ',') + .collect::>() + }; + for narsese in narseses { + let parsed = parse(narsese).expect("pest解析失败!"); + let parsed_str = FORMAT_ASCII.format_narsese(&parsed); + // 对齐并展示 + println!(" {narsese:?}\n => {:?}", parsed_str); + + first! { + narsese == parsed_str => 直接相等的个数 += 1, + 去掉空格与分隔符(narsese) == 去掉空格与分隔符(&parsed_str) => 删去空格与分隔符后相等的个数 += 1, + 去掉空格与分隔符_字符集合(narsese) == 去掉空格与分隔符_字符集合(&parsed_str) => 去掉空格与分隔符_字符重排后相等的个数 += 1, + _ => 形式有变的.push((去掉空格与分隔符(narsese), 去掉空格与分隔符(&parsed_str), parsed)), + } + } + + // 报告 + println!("✅直接相等的个数:{直接相等的个数}"); + println!("✅删去空格与分隔符后相等的个数:{删去空格与分隔符后相等的个数}"); + println!( + "✅去掉空格与分隔符_字符重排后相等的个数:{去掉空格与分隔符_字符重排后相等的个数}" + ); + println!("⚠️形式有变的个数:{}", 形式有变的.len()); + for (n, (narsese, parsed_str, parsed)) in 形式有变的.iter().enumerate() { + // 报告形式有变的 + println!(" {n}:\n\t{narsese:?}\n =?>\t{:?}", parsed_str); + // 报告长度明显变化的 + let len_diff = parsed_str.len().abs_diff(narsese.len()); + if len_diff as f64 / narsese.len() as f64 > 0.5 { + println!("❗长度有较大变化( 变化量={len_diff} ):{parsed:#?}"); + } + } + // ! 🚩【2024-03-29 15:12:43】现在假设「去掉空格与分隔符、字符重排后必定相等」 + assert!(形式有变的.is_empty(), "❌出现形式有变的解析结果!"); + println!("测试完毕!"); } } diff --git a/src/cin_implements/ona/dialect_ona.pest b/src/cin_implements/ona/dialect_ona.pest index 9fc4ab1..fe8cfda 100644 --- a/src/cin_implements/ona/dialect_ona.pest +++ b/src/cin_implements/ona/dialect_ona.pest @@ -1,113 +1,135 @@ +//! ONA方言语法 +//! * 🎯从ONA输出中解析Narsese +//! * 📌复合词项的「中缀形式」「空格分隔」形式 +//! * 📄以空格分隔的词项:`(* {SELF})` +//! * 📄`({SELF} * x)` + +/// 空白符 | 所有Unicode空白符,解析前忽略 +WHITESPACE = _{ WHITE_SPACE } + +/// 总入口:词法Narsese | 优先级:任务 > 语句 > 词项 narsese = _{ task | sentence | term } -term = { - compound - | set - | statement - | atom +/// 任务:有预算的语句 +task = { + budget ~ sentence } -// 原子词项 -// * 📝其中的`@`会在「中间有空白符」时报错 -atom = { - placeholder // 占位符 | 可能会占满 - - | atom_prefix ~ atom_content +/// 预算值 | 不包括「空字串」隐含的「空预算」 +budget = { + "$" ~ budget_content ~ "$" } -placeholder = { ("_"+) } -atom_prefix = @{ - (PUNCTUATION | SYMBOL) // 其它带前缀原子词项 - | "" // 词语 +/// 预算值内容 +budget_content = _{ + (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) + | "" // 空预算(但带括号) } -atom_content = @{ word_content } -// 复合词项 +/// 通用于真值、预算值的项 | 用作内部数值,不约束取值范围 +truth_budget_term = @{ (ASCII_DIGIT | ".")+ } -compound = ${ - // * 📝使用前缀「$」允许在此中判断空格 - ("(" ~ #multi = connecter ~ optional_separator ~ term_list ~ ")") - | ("(" ~ term ~ #binary = connecter ~ term ~ ")") +/// 语句 = 词项 标点 时间戳? 真值? +sentence = { + term ~ punctuation ~ stamp? ~ truth? } -optional_separator = _{ (("," | WHITESPACE) ~ WHITESPACE*) } - -// 连接符 | 📝使用` ~ (!"," ~ punct_sym)*`的模式,在每次「尝试延伸」时都匹配 -connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } - -// 集合词项 -set = { - #ext_set = ("{" ~ term_list ~ "}") // 外延集 - - | #int_set = ("[" ~ term_list ~ "]") // 内涵集 +/// 词项 = 陈述 | 复合 | 原子 +term = _{ + statement + | compound + | atom } -term_list = { term ~ ((("," | WHITESPACE) ~ WHITESPACE*) ~ term)* ~ (",")? } - -// 陈述 -statement = { +/// 陈述 = <词项 系词 词项> | 非**原子规则**,强制其内表达式忽略空格 +statement = !{ "<" ~ term ~ copula ~ term ~ ">" } -punct_sym = _{ (PUNCTUATION | SYMBOL) } - +/// 陈述系词 copula = @{ - // (PUNCTUATION | SYMBOL)+ (punct_sym ~ "-" ~ punct_sym) // 继承/相似/实例/属性/实例属性 | (punct_sym ~ "=" ~ punct_sym) // 蕴含/等价 - | (punct_sym ~ punct_sym ~ ">") // 时序性蕴含/时序性等价 -} + | ("=" ~ punct_sym ~ ">") // 时序性蕴含 -// 语句 + | ("<" ~ punct_sym ~ ">") // 时序性等价 +} -sentence = { term ~ punctuation ~ stamp ~ truth } +/// 标点符号 | 用于「原子词项前缀」「复合词项连接词」和「陈述系词」 +punct_sym = { (PUNCTUATION | SYMBOL) } + +/// 复合 = (连接词, 词项...) | {外延集...} | [内涵集...] +/// * 🆕对ONA兼容形如`(^op, {SELF}, LEFT)`的输出语法 +/// * 🚩此处不进行「静默内联」:便于在「折叠函数」中向下分派 +/// * 📝使用前缀「$」停止忽略空格,以使用「可选分隔符」 +compound = !{ + compound_common + | compound_binary + | ext_set + | int_set +} -punctuation = @{ PUNCTUATION } +/// 通用的复合词项 +compound_common = ${ "(" ~ connecter ~ optional_separator ~ term_list ~ ")" } -stamp = @{ - (":" ~ stamp_content ~ ":") // 有内容的时间戳 +/// 通用的「词项列表」 | 静默展开 +term_list = _{ term ~ (optional_separator ~ term)* } - | "" // 空时间戳 -} -stamp_content = @{ - "|" - | "\\" - | "/" - | (("+" | "-") ~ ASCII_DIGIT+) +/// 可选的分隔符 +optional_separator = _{ + ("," ~ WHITESPACE*) + | WHITESPACE+ } -truth = { - ("%" ~ truth_budget_terms ~ "%") // 有值的真值 +/// 🆕ONA特定的「二元中缀表达法」 +/// * 🚩【2024-03-29 09:40:38】目前通用成`(A, B, C)` => `<(*, B, C) --> A>`的转换方式 +compound_binary = { "(" ~ term ~ connecter ~ term ~ ")" } - | "" // 空真值 -} -truth_budget_term = @{ - truth_budget_content+ -} -truth_budget_terms = { - (truth_budget_term ~ (";" ~ truth_budget_term)*) - | "" -} +/// 外延集 | 📌【2024-03-29 09:39:39】pest代码折叠中会丢掉所有「不被规则捕获的字符串信息」 +ext_set = { "{" ~ term_list ~ "}" } -// 任务 -task = { - budget ~ sentence -} -budget = { - "$" ~ truth_budget_terms ~ "$" +/// 内涵集 +int_set = { "[" ~ term_list ~ "]" } + +/// 复合词项连接词 | ⚠️不包括「逗号」与「圆括号」 +connecter = @{ (!"," ~ !"(" ~ !")" ~ punct_sym)* } + +/// 原子 = 前缀(可选) 内容 +atom = { + placeholder // 占位符 + + | (atom_prefix ~ atom_content) // 变量/间隔/操作…… + + | atom_content // 词语 } -// 基础设施 // -// * 📝用于「防止误吃系词」的方法:字符 ~ (要避免的 ~ 字符)* -word_content = { word_content_char ~ (!copula ~ word_content_char)* } -word_content_char = { LETTER | NUMBER | "_" | "-" } -truth_budget_content = { ASCII_DIGIT | "." } +/// 占位符 = 纯下划线字符串 +placeholder = @{ "_"+ } -// 忽略空白符 -WHITESPACE = _{ WHITE_SPACE } +/// 原子词项前缀 +atom_prefix = @{ punct_sym+ } + +/// 原子词项内容 | 已避免与「复合词项系词」相冲突 +atom_content = @{ atom_char ~ (!copula ~ atom_char)* } + +/// 能作为「原子词项内容」的字符 +atom_char = { LETTER | NUMBER | "_" | "-" } + +/// 标点 +punctuation = { (PUNCTUATION | SYMBOL) } + +/// 时间戳 | 空时间戳会直接在「语句」中缺省 +stamp = { + ":" ~ (!":" ~ ANY)+ ~ ":" +} + +/// 真值 | 空真值会直接在「语句」中缺省 +truth = { + "%" ~ (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) ~ "%" +} diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index ef8802d..db365eb 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -14,17 +14,30 @@ //! * `^left executed with args (* {SELF})` //! * `^left executed with args ({SELF} * x)` //! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` +//! +//! ## 其它杂项 +//! +//! 💭【2024-03-29 16:58:01】ONA中「注册操作」可以被翻译成`*setopname 操作ID ^操作符名`的形式 +//! * ⚠️但需要自行保证「操作ID」不重复 +//! * 📄`*setopname 1 ^left` +//! * 🔗参见 -use super::dialect::parse as parse_narsese_ona; -use crate::runtime::TranslateError; +use super::dialect::parse as parse_dialect_ona; +use crate::{ + ona::{fold_pest_compound, DialectParser, Rule}, + runtime::TranslateError, +}; use anyhow::Result; -use narsese::conversion::string::impl_lexical::structs::ParseResult; +use narsese::lexical::{Narsese, Term}; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use regex::Regex; -use util::{if_return, pipe, ResultBoost}; +use pest::Parser; +use regex::{Captures, Regex}; +#[cfg(not(test))] +use util::OptionBoost; +use util::{if_return, pipe}; /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 @@ -37,6 +50,8 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::CYC(n) => n.to_string(), // VOL指令:调整音量 Cmd::VOL(n) => format!("*volume={n}"), + // REG指令:注册操作 + Cmd::REG { name } => format!("*setopname {} ^{name}", hash_operator_id(&name)), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), @@ -45,13 +60,64 @@ pub fn input_translate(cmd: Cmd) -> Result { Ok(content) } +/// 🔗参见 +/// ```c +/// //Maximum amount of operations which can be registered +/// #define OPERATIONS_MAX 10 +/// ``` +static mut NEXT_OPERATOR_ID: usize = 0; +const OPERATIONS_MAX: usize = 10; + +/// 从「操作名」到「唯一操作数值ID」 +/// * 🎯用于保证操作ID不重复 +/// * 📌尽可能保证一一映射:操作名(字符串) ↔ 操作ID(无符号整数) +/// +/// * 🚩现在因ONA的「操作符数量限制」不推荐直接用散列函数 +/// * 📄取余后的已知散列冲突:`^op = ^op2` +/// * 🚩【2024-03-29 17:13:41】目前使用「循环取余」尽可能避免「索引越界」 +/// * ⚠️仍然避免不了「操作重复」 +/// * 🚩【2024-03-29 17:19:43】目前采用「及早失败」策略,"let it crash" +/// +/// * 📌ONA中「操作ID」的范围:1..OPERATIONS_MAX +fn hash_operator_id(_: &str) -> usize { + // ! 静态可变量是不安全方法:无法避免数据竞争 + // SAFETY: 实际使用时只需保证 + unsafe { + NEXT_OPERATOR_ID += 1; + NEXT_OPERATOR_ID %= OPERATIONS_MAX; + NEXT_OPERATOR_ID + 1 + } + // ! 🚩【2024-03-29 17:12:28】弃用 + // use std::hash::{DefaultHasher, Hash, Hasher}; + // let mut hasher = DefaultHasher::new(); + // op_name.hash(&mut hasher); + // (hasher.finish() % 10) as usize +} +#[test] +fn t() { + dbg!([ + hash_operator_id("left"), + hash_operator_id("left"), + hash_operator_id("right"), + hash_operator_id("op"), + hash_operator_id("op2"), + hash_operator_id("oq"), + ]); +} + /// ONA的「输出转译」函数 /// * 🎯用于将ONA Shell的输出(字符串)转译为「NAVM输出」 /// * 🚩直接根据选取的「头部」进行匹配 +/// 超参数:严格模式 +/// * 🚩测试环境下「输出Narsese解析失败」会上报错误 pub fn output_translate(content_raw: String) -> Result { - // 特别处理:终止信号 + // 特别处理 if_return! { + // 终止信号 content_raw.contains("Test failed.") => Ok(Output::TERMINATED { description: content_raw }) + // 操作索引越界 + // * 📄`Operator index out of bounds, it can only be between 1 and OPERATIONS_MAX!` + content_raw.contains("Operator index out of bounds") => Ok(Output::ERROR { description: content_raw }) } // 根据冒号分隔一次,然后得到「头部」 let (head, tail) = content_raw.split_once(':').unwrap_or(("", "")); @@ -63,16 +129,14 @@ pub fn output_translate(content_raw: String) -> Result { // * 看起来是回答,实际上不是 narsese: match content_raw.contains("Answer: None.") { true => None, - false => try_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + false => parse_narsese_ona(head, tail)?, }, // 然后传入整个内容 content_raw, }, "derived" => Output::OUT { // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: try_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + narsese: parse_narsese_ona(head, tail)?, // 然后传入整个内容 content_raw, }, @@ -84,7 +148,7 @@ pub fn output_translate(content_raw: String) -> Result { }, // * 🚩对于「操作」的特殊语法 _ if content_raw.contains("executed") => Output::EXE { - operation: parse_operation_ona(&content_raw), + operation: parse_operation_ona(&content_raw)?, content_raw, }, // 若是连续的「头部」⇒识别为「未归类」类型 @@ -104,29 +168,138 @@ pub fn output_translate(content_raw: String) -> Result { } /// (ONA)从原始输出中解析操作 -pub fn parse_operation_ona(content_raw: &str) -> Operation { - println!("截获到操作:{content_raw:?}"); - Operation { - // TODO: 有待分析 - operator_name: "UNKNOWN".into(), - params: vec![], +/// * 📄`^deactivate executed with args` +/// * 📄`^left executed with args (* {SELF})` +/// * 📄`^left executed with args ({SELF} * x)` +pub fn parse_operation_ona(content_raw: &str) -> Result { + // 匹配ONA输出中的「操作」⇒转换 | 操作名 | 操作参数(Narsese复合词项⇒提取组分,变成字符串) + let re_operation = Regex::new(r"\^([^\s]+)\s*executed with args\s*(.*)").unwrap(); + let captures = re_capture(&re_operation, content_raw)?; + // ! 即便是测试环境下,也有可能是[`None`](但只在测试环境下返回[`Err`]并报错) + match captures { + Some(captures) => { + // 操作名称 + let operator_name = captures[1].into(); + // 操作参数 + let params = match captures[2].trim() { + // 空字串⇒空参数组 + "" => vec![], + // 否则⇒作为复合词项解析 + term_str => pipe! { + // 获取操作参数字符串 + term_str + // 基于[`pest`]的词法解析 + => DialectParser::parse(Rule::narsese, _) + => {?}# // 后缀语法:抛出错误/解包 + => .next() + => .unwrap() + // 折叠到「词法Narsese」 + => fold_pest_compound + => {?}# // 后缀语法:抛出错误/解包 + // 提取出词项 + => extract_params + }, + }; + // 返回 + Ok(Operation { + operator_name, + params, + }) + } + // 「未知操作」的占位符 | 仅在生产环境中返回 + None => Ok(Operation { + operator_name: "UNKNOWN".into(), + params: vec![], + }), + } +} + +/// 操作参数提取 +/// * 🎯从一个解析出来的词项中提取出「操作参数列表」 +/// * 🚩测试环境中仅允许「复合词项」被解包 +#[cfg(test)] +fn extract_params(params: Term) -> Vec { + match params { + Term::Compound { terms, .. } => terms, + _ => unreachable!("ONA的「操作参数」只能由「复合词项」承载"), + } +} + +/// 操作参数提取 +/// * 🎯从一个解析出来的词项中提取出「操作参数列表」 +/// * 🚩测试环境中仅允许「复合词项」被解包 +/// * 🚩生产环境中允许多种词项形式(原子词项⇒仅含其自身的参数列表) +#[cfg(not(test))] +fn extract_params(params: Term) -> Vec { + match params { + Term::Compound { terms, .. } => terms, + Term::Set { terms, .. } => terms, + Term::Statement { + subject, predicate, .. + } => vec![*subject, *predicate], + Term::Atom { .. } => vec![params], + } +} + +/// 正则捕获 +/// * 🎯用于在测试环境中启用「严格模式」(无法匹配⇒报错) +/// * 🚩测试环境中会上抛错误 +/// * 🚩生产环境中仅打印错误消息 +#[cfg(not(test))] +fn re_capture<'a>(re: &'a Regex, haystack: &'a str) -> Result>> { + Ok(re + .captures(haystack) + .inspect_none(|| println!("【ERR】使用正则表达式「{re}」无法捕获「{haystack}」"))) +} + +/// 正则捕获 +/// * 🎯用于在测试环境中启用「严格模式」(无法匹配⇒报错) +/// * 🚩测试环境中会上抛错误 +/// * 🚩生产环境中仅打印错误消息 +#[cfg(test)] +fn re_capture<'a>(re: &'a Regex, haystack: &'a str) -> Result>> { + use anyhow::anyhow; + match re.captures(haystack) { + // * 🚩↓因为这里要包一层[`Some`],所以无法使用[`Option::ok_or`] + Some(captures) => Ok(Some(captures)), + None => Err(anyhow!( + "【ERR】无法使用正则表达式「{re}」捕获「{haystack}」" + )), } } +/// (ONA)从原始输出中解析Narsese +/// * 🎯用于结合`#[cfg]`控制「严格模式」 +/// * 🚩生产环境下「Narsese解析出错」仅打印错误信息 +#[cfg(not(test))] +pub fn parse_narsese_ona(head: &str, tail: &str) -> Result> { + use util::ResultBoost; + // ! ↓下方会转换为None + Ok(try_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}"))) +} + +/// (ONA)从原始输出中解析Narsese +/// * 🎯用于结合`#[cfg]`控制「严格模式」 +/// * 🚩测试环境下「Narsese解析出错」会上抛错误 +#[cfg(test)] +pub fn parse_narsese_ona(_: &str, tail: &str) -> Result> { + // ! ↓下方会上抛错误 + Ok(Some(try_parse_narsese(tail)?)) +} + /// (尝试)从输出中解析出Narsese /// * ❌【2024-03-27 22:01:18】目前引入[`anyhow::Error`]会出问题:不匹配/未满足的特征 -pub fn try_parse_narsese(tail: &str) -> ParseResult { +pub fn try_parse_narsese(tail: &str) -> Result { // 提取并解析Narsese字符串 pipe! { tail // 重整 => #{&} => reform_output_to_narsese - // 解析 + // 解析方言 => #{&} - => parse_narsese_ona - // 转换错误 | 解析失败⇒返回错误信息 | 返回None - // => .transform_err(|err| format!("输出「OUT」解析失败:{err}")) + => parse_dialect_ona } } @@ -137,10 +310,14 @@ pub fn try_parse_narsese(tail: &str) -> ParseResult { /// * 📌参数`tail`不附带`Answer:`等部分 fn reform_output_to_narsese(out: &str) -> String { // 构造正则表达式(实现中只会编译一次) // - // 匹配ONA输出中的「真值」 + // 匹配ONA输出中的「真值」⇒转换 let re_truth = Regex::new(r"Truth:\s*frequency=([0-9.]+),\s*confidence=([0-9.]+)").unwrap(); - // 匹配ONA输出的「创建时间」 + // 匹配ONA输出的「创建时间」⇒删去 let re_creation_t = Regex::new(r"creationTime=([0-9.]+)\s+").unwrap(); + // 匹配ONA输出的「时间递进」⇒删去 + let re_dt = Regex::new(r"dt=([0-9.]+)\s+").unwrap(); + // 匹配ONA输出的「优先级」⇒删去 + let re_priority = Regex::new(r"Priority=([0-9.]+)\s+").unwrap(); // 两次替换 // pipe! { @@ -156,6 +333,14 @@ fn reform_output_to_narsese(out: &str) -> String { => #{&} // 删去非必要的「创建时间」 => [re_creation_t.replace_all](_, "") + => #{&} // 必须借用 + // 删去非必要的「递进时间」 + => [re_dt.replace_all](_, "") + => #{&} // 必须借用 + // 删去非必要的「优先级」 + => [re_priority.replace_all](_, "") + // 剪切前后空白符 + => .trim() // 返回字符串 // => .into() } @@ -165,6 +350,7 @@ fn reform_output_to_narsese(out: &str) -> String { #[cfg(test)] mod test { use super::*; + use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; use util::asserts; /// 测试/正则重整 @@ -186,4 +372,245 @@ mod test { s => "C>.%1.000000;0.447514%", } } + + /// 测试/输出解析 + #[test] + fn test_output_parse() { + // 📄输出源自ONA测试文件`whatwarmer.nal`与ONA的命令行交互 + let outputs = " + [warm]>. :|: %0.8% + Input: [warm]>. :|: occurrenceTime=1 Priority=1.000000 Truth: frequency=0.800000, confidence=0.900000 + [warm]>. :|: %0.8% + Input: [warm]>. :|: occurrenceTime=2 Priority=1.000000 Truth: frequency=0.800000, confidence=0.900000 + [warm]>. :|: %0.8% + Input: [warm]>. :|: occurrenceTime=3 Priority=1.000000 Truth: frequency=0.800000, confidence=0.900000 + [warm]>. :|: %0.3% + Input: [warm]>. :|: occurrenceTime=4 Priority=1.000000 Truth: frequency=0.300000, confidence=0.900000 + Derived: dt=1.000000 < [$1]> =/> [$1]>>. Priority=0.120425 Truth: frequency=0.300000, confidence=0.254517 + Derived: dt=1.000000 < [warm]> =/> [warm]>>. Priority=0.120425 Truth: frequency=0.300000, confidence=0.254517 + Derived: b>. :|: occurrenceTime=4 Priority=0.246973 Truth: frequency=0.800000, confidence=0.162760 + Derived: a>. :|: occurrenceTime=4 Priority=0.194273 Truth: frequency=0.300000, confidence=0.341412 + Derived: b>. :|: occurrenceTime=4 Priority=0.189423 Truth: frequency=0.279070, confidence=0.357855 + Derived: a>. :|: occurrenceTime=4 Priority=0.189423 Truth: frequency=0.279070, confidence=0.357855 + Derived: <(b | a) --> [warm]>. :|: occurrenceTime=4 Priority=0.099456 Truth: frequency=0.240000, confidence=0.648000 + Derived: <(a | b) --> [warm]>. :|: occurrenceTime=4 Priority=0.099456 Truth: frequency=0.240000, confidence=0.648000 + Derived: <(b & a) --> [warm]>. :|: occurrenceTime=4 Priority=0.219984 Truth: frequency=0.860000, confidence=0.648000 + Derived: <(a & b) --> [warm]>. :|: occurrenceTime=4 Priority=0.219984 Truth: frequency=0.860000, confidence=0.648000 + Derived: <(b ~ a) --> [warm]>. :|: occurrenceTime=4 Priority=0.064464 Truth: frequency=0.060000, confidence=0.648000 + Derived: <(a ~ b) --> [warm]>. :|: occurrenceTime=4 Priority=0.161664 Truth: frequency=0.560000, confidence=0.648000 + Derived: <(a * b) --> (+ warm)>. :|: occurrenceTime=4 Priority=0.247200 Truth: frequency=1.000000, confidence=0.648000 + Derived: < [$1]> ==> [$1]>>. :|: occurrenceTime=4 Priority=0.108382 Truth: frequency=0.300000, confidence=0.341412 + Derived: < [$1]> ==> [$1]>>. :|: occurrenceTime=4 Priority=0.137782 Truth: frequency=0.800000, confidence=0.162760 + Derived: < [$1]> <=> [$1]>>. :|: occurrenceTime=4 Priority=0.105676 Truth: frequency=0.279070, confidence=0.357855 + Derived: < [$1]> <=> [$1]>>. :|: occurrenceTime=4 Priority=0.105676 Truth: frequency=0.279070, confidence=0.357855 + Derived: ( [#1]> && [#1]>). :|: occurrenceTime=4 Priority=0.083228 Truth: frequency=0.240000, confidence=0.648000 + Derived: ( [#1]> && [#1]>). :|: occurrenceTime=4 Priority=0.083228 Truth: frequency=0.240000, confidence=0.648000 + <(?1 ~ ?2) --> [warm]>? :|: + Input: <(?1 ~ ?2) --> [warm]>? :|: + Answer: <(a ~ b) --> [warm]>. :|: occurrenceTime=4 creationTime=4 Truth: frequency=0.560000, confidence=0.648000 + ^pick. :|: + Input: ^pick. :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G. :|: + Input: G. :|: occurrenceTime=6 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 <( [warm]> &/ ^pick) =/> G>. Priority=0.185124 Truth: frequency=1.000000, confidence=0.186952 + Derived: dt=1.000000 <(<(a | b) --> [warm]> &/ ^pick) =/> G>. Priority=0.149877 Truth: frequency=1.000000, confidence=0.069427 + Derived: dt=1.000000 <( b> &/ ^pick) =/> G>. Priority=0.177205 Truth: frequency=1.000000, confidence=0.059471 + Derived: dt=1.000000 <( a> &/ ^pick) =/> G>. Priority=0.175070 Truth: frequency=1.000000, confidence=0.047999 + Derived: dt=1.000000 <( b> &/ ^pick) =/> G>. Priority=0.174870 Truth: frequency=1.000000, confidence=0.046913 + Derived: dt=1.000000 <( a> &/ ^pick) =/> G>. Priority=0.174870 Truth: frequency=1.000000, confidence=0.046913 + Derived: dt=1.000000 <(<(b | a) --> [warm]> &/ ^pick) =/> G>. Priority=0.149877 Truth: frequency=1.000000, confidence=0.069427 + Derived: dt=1.000000 <( [warm]> &/ ^pick) =/> G>. Priority=0.168996 Truth: frequency=1.000000, confidence=0.109355 + Derived: dt=1.000000 <(<(a & b) --> [warm]> &/ ^pick) =/> G>. Priority=0.170733 Truth: frequency=1.000000, confidence=0.183101 + Derived: dt=1.000000 <(<(b ~ a) --> [warm]> &/ ^pick) =/> G>. Priority=0.142227 Truth: frequency=1.000000, confidence=0.019374 + Derived: dt=1.000000 <(<(a ~ b) --> [warm]> &/ ^pick) =/> G>. Priority=0.161554 Truth: frequency=1.000000, confidence=0.136690 + Derived: dt=1.000000 <(<(a * b) --> (+ warm)> &/ ^pick) =/> G>. Priority=0.174542 Truth: frequency=1.000000, confidence=0.200929 + Derived: dt=1.000000 <(( [#1]> && [#1]>) &/ ^pick) =/> G>. Priority=0.134326 Truth: frequency=1.000000, confidence=0.069427 + Derived: dt=1.000000 <(( [#1]> && [#1]>) &/ ^pick) =/> G>. Priority=0.134326 Truth: frequency=1.000000, confidence=0.069427 + Derived: dt=1.000000 <(( [warm]> &/ [warm]>) &/ ^pick) =/> G>. Priority=0.134326 Truth: frequency=1.000000, confidence=0.069427 + Derived: dt=1.000000 <(<(b & a) --> [warm]> &/ ^pick) =/> G>. Priority=0.170733 Truth: frequency=1.000000, confidence=0.183101 + Derived: dt=3.000000 < [warm]> =/> G>. Priority=0.208187 Truth: frequency=1.000000, confidence=0.199438 + Derived: dt=2.000000 <<(a | b) --> [warm]> =/> G>. Priority=0.162890 Truth: frequency=1.000000, confidence=0.075969 + Derived: dt=2.000000 < b> =/> G>. Priority=0.206921 Truth: frequency=1.000000, confidence=0.065217 + Derived: dt=2.000000 < a> =/> G>. Priority=0.204202 Truth: frequency=1.000000, confidence=0.052770 + Derived: dt=2.000000 < b> =/> G>. Priority=0.203948 Truth: frequency=1.000000, confidence=0.051588 + Derived: dt=2.000000 < a> =/> G>. Priority=0.203948 Truth: frequency=1.000000, confidence=0.051588 + Derived: dt=2.000000 <<(b | a) --> [warm]> =/> G>. Priority=0.162890 Truth: frequency=1.000000, confidence=0.075969 + Derived: dt=2.000000 <<(a * b) --> (+ warm)> =/> G>. Priority=0.191425 Truth: frequency=1.000000, confidence=0.213712 + Derived: dt=2.000000 <( [#1]> && [#1]>) =/> G>. Priority=0.142122 Truth: frequency=1.000000, confidence=0.075969 + Derived: dt=2.000000 <( [#1]> && [#1]>) =/> G>. Priority=0.142122 Truth: frequency=1.000000, confidence=0.075969 + Derived: dt=2.000000 <( [warm]> &/ [warm]>) =/> G>. Priority=0.142122 Truth: frequency=1.000000, confidence=0.075969 + Derived: dt=2.000000 <<(b & a) --> [warm]> =/> G>. Priority=0.187089 Truth: frequency=1.000000, confidence=0.195491 + Derived: dt=2.000000 < [warm]> =/> G>. Priority=0.189098 Truth: frequency=1.000000, confidence=0.118623 + Derived: dt=2.000000 <<(a & b) --> [warm]> =/> G>. Priority=0.187089 Truth: frequency=1.000000, confidence=0.195491 + Derived: dt=2.000000 <<(b ~ a) --> [warm]> =/> G>. Priority=0.153812 Truth: frequency=1.000000, confidence=0.021435 + Derived: dt=2.000000 <<(a ~ b) --> [warm]> =/> G>. Priority=0.176536 Truth: frequency=1.000000, confidence=0.147400 + <(<(a ~ b) --> [warm]> &/ ^pick) =/> G>? + Input: <(<(a ~ b) --> [warm]> &/ ^pick) =/> G>? + Answer: <(<(a ~ b) --> [warm]> &/ ^pick) =/> G>. creationTime=6 Truth: frequency=1.000000, confidence=0.136690 + + a. :|: + Input: a. :|: occurrenceTime=1 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + ^left. :|: + Input: ^left. :|: occurrenceTime=2 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + g. :|: + Input: g. :|: occurrenceTime=3 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 <(a &/ ^left) =/> g>. Priority=0.254962 Truth: frequency=1.000000, confidence=0.241351 + Derived: dt=2.000000 g>. Priority=0.335353 Truth: frequency=1.000000, confidence=0.254517 + a. :|: + Input: a. :|: occurrenceTime=4 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 a>. Priority=0.348301 Truth: frequency=1.000000, confidence=0.282230 + Derived: dt=1.000000 <(a &/ g) =/> a>. Priority=0.246000 Truth: frequency=1.000000, confidence=0.213712 + g! :|: + Input: g! :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + decision expectation=0.578198 implication: <(a &/ ^left) =/> g>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: a. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=4 + ^left executed with args + Input: ^left. :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + A. :|: + Input: A. :|: occurrenceTime=7 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=2.000000 <((g &/ a) &/ ^left) =/> A>. Priority=0.201969 Truth: frequency=1.000000, confidence=0.174792 + Derived: dt=2.000000 <(a &/ ^left) =/> A>. Priority=0.246000 Truth: frequency=1.000000, confidence=0.213712 + Derived: dt=2.000000 <((a &/ g) &/ ^left) =/> A>. Priority=0.191125 Truth: frequency=1.000000, confidence=0.127972 + Derived: dt=2.000000 <(g &/ ^left) =/> A>. Priority=0.237903 Truth: frequency=1.000000, confidence=0.186952 + Derived: dt=3.000000 <(g &/ a) =/> A>. Priority=0.237903 Truth: frequency=1.000000, confidence=0.186952 + Derived: dt=3.000000 A>. Priority=0.323287 Truth: frequency=1.000000, confidence=0.226692 + Derived: dt=4.000000 <(a &/ g) =/> A>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=4.000000 A>. Priority=0.312281 Truth: frequency=1.000000, confidence=0.199438 + <(*, {SELF}) --> ^left>. :|: + Input: <(* {SELF}) --> ^left>. :|: occurrenceTime=8 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: (* {SELF}). :|: occurrenceTime=8 Priority=0.182344 Truth: frequency=1.000000, confidence=0.293146 + G. :|: + Input: G. :|: occurrenceTime=9 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 <(((g &/ A) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.134179 Truth: frequency=1.000000, confidence=0.068411 + Derived: dt=1.000000 <((a &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.144347 Truth: frequency=1.000000, confidence=0.090215 + Derived: dt=1.000000 <(((g &/ a) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.134179 Truth: frequency=1.000000, confidence=0.068411 + Derived: dt=1.000000 <((g &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.141953 Truth: frequency=1.000000, confidence=0.074873 + Derived: dt=1.000000 <(((a &/ A) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.136267 Truth: frequency=1.000000, confidence=0.082685 + Derived: dt=1.000000 <(((a &/ g) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.131034 Truth: frequency=1.000000, confidence=0.046051 + Derived: dt=1.000000 <((A &/ ^left) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.154562 Truth: frequency=1.000000, confidence=0.150345 + Derived: dt=4.000000 <(a &/ ^left) =/> G>. Priority=0.230723 Truth: frequency=1.000000, confidence=0.161649 + Derived: dt=4.000000 <((g &/ a) &/ ^left) =/> G>. Priority=0.191125 Truth: frequency=1.000000, confidence=0.127972 + Derived: dt=4.000000 <(g &/ ^left) =/> G>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=4.000000 <((a &/ g) &/ ^left) =/> G>. Priority=0.183193 Truth: frequency=1.000000, confidence=0.090215 + Derived: dt=1.000000 <((g &/ A) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.150597 Truth: frequency=1.000000, confidence=0.127972 + Derived: dt=1.000000 <(a &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.166364 Truth: frequency=1.000000, confidence=0.161649 + Derived: dt=1.000000 <((g &/ a) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.150597 Truth: frequency=1.000000, confidence=0.127972 + Derived: dt=1.000000 <(g &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.161849 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=1.000000 <((a &/ A) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.154562 Truth: frequency=1.000000, confidence=0.150345 + Derived: dt=1.000000 <((a &/ g) &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.144347 Truth: frequency=1.000000, confidence=0.090215 + Derived: dt=1.000000 <(A &/ <(* {SELF}) --> ^left>) =/> G>. Priority=0.183842 Truth: frequency=1.000000, confidence=0.241351 + Derived: dt=2.000000 <(g &/ A) =/> G>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=5.000000 G>. Priority=0.302437 Truth: frequency=1.000000, confidence=0.173382 + Derived: dt=5.000000 <(g &/ a) =/> G>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=6.000000 G>. Priority=0.293787 Truth: frequency=1.000000, confidence=0.149042 + Derived: dt=2.000000 <(a &/ A) =/> G>. Priority=0.230723 Truth: frequency=1.000000, confidence=0.161649 + Derived: dt=1.000000 <(* {SELF}) =/> G>. Priority=0.195713 Truth: frequency=1.000000, confidence=0.148415 + Derived: dt=6.000000 <(a &/ g) =/> G>. Priority=0.214505 Truth: frequency=1.000000, confidence=0.098268 + Derived: dt=2.000000 G>. Priority=0.335353 Truth: frequency=1.000000, confidence=0.254517 + A. :|: + Input: A. :|: occurrenceTime=10 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=2.000000 <((a &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.141953 Truth: frequency=1.000000, confidence=0.074873 + Derived: dt=2.000000 <(((g &/ a) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.132453 Truth: frequency=1.000000, confidence=0.056268 + Derived: dt=2.000000 <(((g &/ A) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.132453 Truth: frequency=1.000000, confidence=0.056268 + Derived: dt=2.000000 <(((a &/ g) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.129874 Truth: frequency=1.000000, confidence=0.037532 + Derived: dt=2.000000 <((g &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.139967 Truth: frequency=1.000000, confidence=0.061748 + Derived: dt=2.000000 <(((a &/ A) &/ ^left) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.134179 Truth: frequency=1.000000, confidence=0.068411 + Derived: dt=2.000000 <(a &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.161849 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=2.000000 <((g &/ a) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.147209 Truth: frequency=1.000000, confidence=0.107901 + Derived: dt=2.000000 <((g &/ A) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.147209 Truth: frequency=1.000000, confidence=0.107901 + Derived: dt=2.000000 <((a &/ g) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.141953 Truth: frequency=1.000000, confidence=0.074873 + Derived: dt=2.000000 <(g &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.157967 Truth: frequency=1.000000, confidence=0.117083 + Derived: dt=2.000000 <((a &/ A) &/ <(* {SELF}) --> ^left>) =/> A>. Priority=0.150597 Truth: frequency=1.000000, confidence=0.127972 + Derived: dt=5.000000 <(a &/ ^left) =/> A>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Revised: dt=3.113558 <(a &/ ^left) =/> A>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.301794 + Derived: dt=5.000000 <((g &/ a) &/ ^left) =/> A>. Priority=0.186825 Truth: frequency=1.000000, confidence=0.107901 + Revised: dt=3.090418 <((g &/ a) &/ ^left) =/> A>. Priority=0.186825 Truth: frequency=1.000000, confidence=0.249682 + Derived: dt=5.000000 <((a &/ g) &/ ^left) =/> A>. Priority=0.180156 Truth: frequency=1.000000, confidence=0.074873 + Revised: dt=3.066382 <((a &/ g) &/ ^left) =/> A>. Priority=0.180156 Truth: frequency=1.000000, confidence=0.185459 + Derived: dt=5.000000 <(g &/ ^left) =/> A>. Priority=0.219076 Truth: frequency=1.000000, confidence=0.117083 + Revised: dt=3.097308 <(g &/ ^left) =/> A>. Priority=0.219076 Truth: frequency=1.000000, confidence=0.266081 + Derived: dt=6.000000 A>. Priority=0.293787 Truth: frequency=1.000000, confidence=0.149042 + Revised: dt=4.100474 A>. Priority=0.293787 Truth: frequency=0.980787, confidence=0.323166 + Derived: dt=1.000000 A>. Priority=0.348301 Truth: frequency=1.000000, confidence=0.282230 + Derived: dt=2.000000 <(* {SELF}) =/> A>. Priority=0.190743 Truth: frequency=1.000000, confidence=0.126225 + Derived: dt=1.000000 <(A &/ G) =/> A>. Priority=0.246000 Truth: frequency=1.000000, confidence=0.213712 + Derived: dt=1.000000 <(g &/ G) =/> A>. Priority=0.219076 Truth: frequency=1.000000, confidence=0.117083 + Derived: dt=1.000000 <((* {SELF}) &/ G) =/> A>. Priority=0.170371 Truth: frequency=1.000000, confidence=0.116545 + Derived: dt=7.000000 <(a &/ g) =/> A>. Priority=0.210665 Truth: frequency=1.000000, confidence=0.081831 + Revised: dt=5.053462 <(a &/ g) =/> A>. Priority=0.210665 Truth: frequency=0.983303, confidence=0.202427 + Derived: dt=7.000000 A>. Priority=0.286301 Truth: frequency=1.000000, confidence=0.126793 + Revised: dt=5.084493 A>. Priority=0.286301 Truth: frequency=0.981712, confidence=0.286567 + Derived: dt=1.000000 <(a &/ G) =/> A>. Priority=0.224460 Truth: frequency=1.000000, confidence=0.138259 + Derived: dt=6.000000 <(g &/ a) =/> A>. Priority=0.219076 Truth: frequency=1.000000, confidence=0.117083 + Revised: dt=4.077649 <(g &/ a) =/> A>. Priority=0.219076 Truth: frequency=0.982085, confidence=0.269626 + G! :|: + Input: G! :|: occurrenceTime=11 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=4.000000 (* {SELF})>. Priority=0.182921 Truth: frequency=1.000000, confidence=0.088860 + Derived: dt=4.000000 <(g &/ a) =/> (* {SELF})>. Priority=0.161381 Truth: frequency=1.000000, confidence=0.067330 + Derived: dt=5.000000 <(a &/ g) =/> (* {SELF})>. Priority=0.157655 Truth: frequency=1.000000, confidence=0.045286 + Derived: dt=5.000000 (* {SELF})>. Priority=0.179929 Truth: frequency=1.000000, confidence=0.073708 + decision expectation=0.578198 implication: <(A &/ <(* {SELF}) --> ^left>) =/> G>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: A. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=10 + ^left executed with args (* {SELF}) + Input: <(* {SELF}) --> ^left>. :|: occurrenceTime=11 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: (* {SELF}). :|: occurrenceTime=11 Priority=0.120799 Truth: frequency=1.000000, confidence=0.175147 + + A. :|: + Input: A. :|: occurrenceTime=1 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + <(*, {SELF}) --> ^left>. :|: + Input: <(* {SELF}) --> ^left>. :|: occurrenceTime=2 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G. :|: + Input: G. :|: occurrenceTime=3 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + A. :|: + Input: A. :|: occurrenceTime=4 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G! :|: + Input: G! :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + decision expectation=0.578198 implication: <(A &/ <(* {SELF}) --> ^left>) =/> G>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: A. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=4 + ^left executed with args (* {SELF}) + Input: <(* {SELF}) --> ^left>. :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + + A2. :|: + Input: A2. :|: occurrenceTime=8 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + <(*, {SELF}, P) --> ^left>. :|: + Input: <({SELF} * P) --> ^left>. :|: occurrenceTime=9 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G2. :|: + Input: G2. :|: occurrenceTime=10 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + A2. :|: + Input: A2. :|: occurrenceTime=11 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G2! :|: + Input: G2! :|: occurrenceTime=12 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + decision expectation=0.578198 implication: <(A2 &/ <({SELF} * P) --> ^left>) =/> G2>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: A2. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=11 + ^left executed with args ({SELF} * P) + Input: <({SELF} * P) --> ^left>. :|: occurrenceTime=12 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + + A. :|: + Input: A. :|: occurrenceTime=1 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + <(*, {SELF}) --> ^op>. :|: + Input: <(* {SELF}) --> ^op>. :|: occurrenceTime=2 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + G. :|: + Input: G. :|: occurrenceTime=3 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 <(A &/ <(* {SELF}) --> ^op>) =/> G>. Priority=0.183842 Truth: frequency=1.000000, confidence=0.241351 + Derived: dt=2.000000 G>. Priority=0.335353 Truth: frequency=1.000000, confidence=0.254517 + A. :|: + Input: A. :|: occurrenceTime=4 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + Derived: dt=1.000000 A>. Priority=0.348301 Truth: frequency=1.000000, confidence=0.282230 + Derived: dt=1.000000 <(A &/ G) =/> A>. Priority=0.246000 Truth: frequency=1.000000, confidence=0.213712 + G! :|: + Input: G! :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + decision expectation=0.578198 implication: <(A &/ <(* {SELF}) --> ^op>) =/> G>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: A. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=4 + ^op executed with args (* {SELF}) + Input: <(* {SELF}) --> ^op>. :|: occurrenceTime=5 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000 + " // 【2024-03-29 16:58:32】省略的「操作注册」语法:`*setopname 1 ^op` + // 初步数据处理 + .split('\n') + .map(str::trim) + .filter(|l| !l.is_empty()); + + // 开始测试解析 + for output in outputs { + // ! 测试环境下[`parse_narsese_ona`]会强制要求「Narsese内容解析成功」 + let o = output_translate(output.into()).expect("输出解析失败"); + if let Some(narsese) = o.get_narsese() { + println!("{}", FORMAT_ASCII.format_narsese(narsese)) + } + } + } } diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs index fd15ede..b9351aa 100644 --- a/src/cin_implements/opennars/dialect.rs +++ b/src/cin_implements/opennars/dialect.rs @@ -19,10 +19,8 @@ pub struct DialectParser; /// 使用[`pest`]将输入的「OpenNARS方言」转换为「词法Narsese」 /// 以OpenNARS的语法解析出Narsese -/// * 🚩【2024-03-25 21:08:34】目前是直接调用ASCII解析器 /// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` pub fn parse(input: &str) -> Result { - // let _ = dbg!(FORMAT_ASCII.parse(input).transform_err(anyhow::Error::from)); // 语法解析 let pair = DialectParser::parse(Rule::narsese, input)?.next().unwrap(); @@ -145,8 +143,6 @@ fn fold_pest_atom(pair: Pair) -> Result { /// 折叠[`pest`]复合词项 /// * 🚩【2024-03-29 09:42:36】因「需要通过规则识别『外延集/内涵集』」通过「进一步向下分发」细化被折叠对象 fn fold_pest_compound(pair: Pair) -> Result { - // compound(0, 7, [connecter(1, 2), atom(3, 4, [atom_content(3, 4)]), atom(5, 6, [atom_content(5, 6)])]) - // compound(0, 6, [atom(1, 2, [atom_content(1, 2)]), atom(4, 5, [atom_content(4, 5)])]) let pair = pair.into_inner().next().unwrap(); match pair.as_rule() { Rule::compound_common => { @@ -161,7 +157,7 @@ fn fold_pest_compound(pair: Pair) -> Result { Ok(Term::Compound { connecter, terms }) } Rule::compound_operation => { - // * 🚩通用复合词项:连接词 词项... + // * 🆕OpenNARS特有的「操作」词项简写... let mut pairs = pair.into_inner(); // 第一个词项应该是谓词 let predicate = fold_pest_term(pairs.next().unwrap())?; @@ -227,11 +223,10 @@ fn fold_pest_statement(pair: Pair) -> Result { /// 单元测试 #[cfg(test)] mod tests { + use super::*; use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; use util::first; - use super::*; - /// 测试/方言解析器 🚧 #[test] fn test_dialect_parser() { diff --git a/src/cin_implements/opennars/dialect_opennars.pest b/src/cin_implements/opennars/dialect_opennars.pest index 6ee55db..1680e2f 100644 --- a/src/cin_implements/opennars/dialect_opennars.pest +++ b/src/cin_implements/opennars/dialect_opennars.pest @@ -73,17 +73,20 @@ compound = { } /// 通用的复合词项 -compound_common = { ("(" ~ connecter ~ "," ~ term ~ ("," ~ term)* ~ ")") } +compound_common = { ("(" ~ connecter ~ "," ~ term_list ~ ")") } + +/// 通用的「词项列表」 | 静默展开 +term_list = _{ term ~ ("," ~ term)* } /// 🆕OpenNARS特定的「操作简写」输出 /// * 🚩【2024-03-29 09:40:38】目前通用成`(A, B, C)` => `<(*, B, C) --> A>`的转换方式 -compound_operation = { "(" ~ term ~ ("," ~ term)* ~ ")" } +compound_operation = { "(" ~ term_list ~ ")" } /// 外延集 | 📌【2024-03-29 09:39:39】pest代码折叠中会丢掉所有「不被规则捕获的字符串信息」 -ext_set = { "{" ~ term ~ ("," ~ term)* ~ "}" } +ext_set = { "{" ~ term_list ~ "}" } /// 内涵集 -int_set = { "[" ~ term ~ ("," ~ term)* ~ "]" } +int_set = { "[" ~ term_list ~ "]" } /// 复合词项连接词 connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 39b2e01..32fbe38 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -88,7 +88,7 @@ pub(crate) mod test { use super::*; use crate::runtime::TranslateError; - use narsese::conversion::string::impl_lexical::shortcuts::*; + use narsese::conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, shortcuts::*}; use std::process::Command; use util::first; @@ -121,6 +121,18 @@ pub(crate) mod test { match &output { // 特别显示「回答」 Output::ANSWER { .. } => println!("捕获到回答!内容:{output:?}"), + // 特别显示「操作」 + Output::EXE { operation, .. } => { + println!( + "捕获到操作!操作名称:{:?},内容:{:?}", + operation.operator_name, + operation + .params + .iter() + .map(|param| FORMAT_ASCII.format_term(param)) + .collect::>() + ) + } _ => println!("捕获到其它输出!内容:{output:?}"), } // 包含⇒结束 From 4d9010ec7ca771dcfd4b12f1c464c73d2783dd8c Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:26:31 +0800 Subject: [PATCH 27/59] =?UTF-8?q?feat:=20:sparkles:=20=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E3=80=8CIN=E3=80=8DNarsese=E8=BD=AC=E8=AF=91=EF=BC=9BPyNARS?= =?UTF-8?q?=E5=AE=8C=E6=95=B4IO=E8=BD=AC=E8=AF=91=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=9B=E7=BB=86=E5=BE=AE=E9=87=8D=E6=9E=84OpenNARS=E3=80=8C?= =?UTF-8?q?=E6=96=B9=E8=A8=80=E8=A7=A3=E6=9E=90=E3=80=8D=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=9B=E9=80=9A=E7=94=A8=E3=80=8C=E7=AE=80=E5=8D=95=E6=BC=94?= =?UTF-8?q?=E7=BB=8E=E4=B8=89=E6=AE=B5=E8=AE=BA=E3=80=8D=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 跟随NAVM支持「IN」的Narsese转译 2. 完整支持PyNARS的IO转译 3. 细微重构OpenNARS的「IO转译」,并标记「已完成」 4. 通用的「简单演绎三段论」测试搭建;首批通过的CIN:✅OpenNARS、ONA、PyNARS --- Cargo.lock | 6 +- src/bin/cin_launcher.rs | 6 +- src/cin_implements/cxin_js/mod.rs | 2 +- src/cin_implements/cxin_js/translators.rs | 7 +- src/cin_implements/nars_python/mod.rs | 2 +- src/cin_implements/nars_python/translators.rs | 16 +- src/cin_implements/ona/mod.rs | 26 +- src/cin_implements/ona/translators.rs | 2 + src/cin_implements/openjunars/mod.rs | 2 +- src/cin_implements/openjunars/translators.rs | 10 +- src/cin_implements/opennars/mod.rs | 26 +- src/cin_implements/opennars/translators.rs | 47 ++-- src/cin_implements/pynars/mod.rs | 27 +- src/cin_implements/pynars/translators.rs | 248 ++++++++++++++++-- src/runtime/command_vm/runtime.rs | 182 ++++++++++++- 15 files changed, 532 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd6713f..489dec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,11 +105,11 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "nar_dev_utils" -version = "0.19.0" +version = "0.20.0" [[package]] name = "narsese" -version = "0.9.1" +version = "0.12.1" dependencies = [ "lazy_static", "nar_dev_utils", @@ -117,7 +117,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher.rs index 02b1904..f9760ce 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher.rs @@ -7,7 +7,7 @@ //! TODO: 完成代码 #![allow(unused)] -use babel_nar::{ona::ONA, opennars::OpenNARS, runtime::CommandVmRuntime}; +use babel_nar::{ona::ONA, opennars::OpenNARS, pynars::PyNARS, runtime::CommandVmRuntime}; use navm::{ cmd::Cmd, output::Output, @@ -17,11 +17,13 @@ use std::{fmt::Debug, io::stdin}; const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; +const TEST_PATH_PYNARS: (&str, &str) = ("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); /// 启动NARS /// * 🚩【2024-03-27 18:55:07】目前就返回一个测试用的运行时 fn get_nars() -> impl VmLauncher { - OpenNARS::new(TEST_PATH_OPENNARS) + // OpenNARS::new(TEST_PATH_OPENNARS) + PyNARS::new(TEST_PATH_PYNARS.0, TEST_PATH_PYNARS.1) // ONA::new(TEST_PATH_ONA) } diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs index 8deedd5..3b819c1 100644 --- a/src/cin_implements/cxin_js/mod.rs +++ b/src/cin_implements/cxin_js/mod.rs @@ -15,7 +15,7 @@ util::mod_and_pub_use! { mod tests { use super::*; use crate::runtime::{ - test::{await_fetch_until, input_cmd_and_await_contains}, + tests::{await_fetch_until, input_cmd_and_await_contains}, CommandVmRuntime, }; use narsese::lexical_nse_task as nse_task; diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs index 4092dba..e40b93d 100644 --- a/src/cin_implements/cxin_js/translators.rs +++ b/src/cin_implements/cxin_js/translators.rs @@ -76,7 +76,12 @@ pub fn output_translate(content_raw: String) -> Result { // 然后传入整个内容 content_raw, }, - "in" => Output::IN { content: tail }, + "in" => Output::IN { + // 先提取其中的Narsese + narsese: segment_narsese(&head, &tail), + // 然后传入整个内容 + content: tail, + }, "out" => Output::OUT { // 先提取其中的Narsese narsese: segment_narsese(&head, &tail), diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index 57b06e9..b5bf0e5 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -16,7 +16,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{test::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; + use crate::runtime::{tests::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs index 7884275..79b376c 100644 --- a/src/cin_implements/nars_python/translators.rs +++ b/src/cin_implements/nars_python/translators.rs @@ -28,7 +28,7 @@ pub fn input_translate(cmd: Cmd) -> Result { // * ✅【2024-03-26 01:44:49】目前采用特定的「方言格式」解决格式化问题 Cmd::NSE(narsese) => format_in_nars_python(&Narsese::Task(narsese)), // CYC指令:运行指定周期数 - // ! NARS-Python Shell同样是自动步进的 + // ! NARS-Python同样是自动步进的 Cmd::CYC(n) => n.to_string(), // 其它类型 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 @@ -39,8 +39,8 @@ pub fn input_translate(cmd: Cmd) -> Result { } /// NARS-Python的「输出转译」函数 -/// * 🎯用于将NARS-Python Shell的输出(字符串)转译为「NAVM输出」 -/// * 🚩直接根据选取的「头部」进行匹配 +/// * 🎯用于将NARS-Python的输出(字符串)转译为「NAVM输出」 +/// * ❌【2024-03-29 19:45:41】目前尚未能从NARS-Python有效获得输出 pub fn output_translate(content: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); @@ -48,20 +48,24 @@ pub fn output_translate(content: String) -> Result { let output = match &*head { // TODO: 有待适配 "answer" => Output::ANSWER { - content_raw: content, // TODO: 有待捕获转译 narsese: None, + content_raw: content, }, "derived" => Output::OUT { + // TODO: 有待捕获转译 + narsese: None, content_raw: content, + }, + "input" => Output::IN { // TODO: 有待捕获转译 narsese: None, + content, }, - "input" => Output::IN { content }, "exe" => Output::EXE { - content_raw: content, // TODO: 有待捕获转译 operation: Operation::new("UNKNOWN", [].into_iter()), + content_raw: content, }, "err" | "error" => Output::ERROR { description: content, diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs index 571325b..edf5f47 100644 --- a/src/cin_implements/ona/mod.rs +++ b/src/cin_implements/ona/mod.rs @@ -16,16 +16,34 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::test::{_test_ona, EXE_PATH_ONA}; + use crate::runtime::{ + tests::{_test_ona, test_simple_answer, EXE_PATH_ONA}, + CommandVmRuntime, + }; use navm::vm::VmLauncher; - #[test] - fn test() { + /// 工具/启动ONA,获得虚拟机运行时 + fn launch_vm() -> CommandVmRuntime { // 从别的地方获取exe路径 let exe_path = EXE_PATH_ONA; // 一行代码启动ONA - let vm = ONA::new(exe_path).launch(); + ONA::new(exe_path).launch() + } + + #[test] + fn test() { + // 启动ONA虚拟机 + let vm = launch_vm(); // 直接复用之前对ONA的测试 _test_ona(vm) } + + /// 测试/通用 | 基于Narsese + #[test] + fn test_universal() { + // 启动ONA虚拟机 + let vm = launch_vm(); + // 使用通用测试逻辑 + test_simple_answer(vm) + } } diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index db365eb..b873790 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -141,6 +141,8 @@ pub fn output_translate(content_raw: String) -> Result { content_raw, }, "input" => Output::IN { + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: parse_narsese_ona(head, tail)?, content: content_raw, }, "err" | "error" => Output::ERROR { diff --git a/src/cin_implements/openjunars/mod.rs b/src/cin_implements/openjunars/mod.rs index 3f0899c..364c522 100644 --- a/src/cin_implements/openjunars/mod.rs +++ b/src/cin_implements/openjunars/mod.rs @@ -16,7 +16,7 @@ mod tests { #![allow(unused)] use super::*; - use crate::runtime::{test::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; + use crate::runtime::{tests::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/openjunars/translators.rs b/src/cin_implements/openjunars/translators.rs index 64c9624..37d1ca5 100644 --- a/src/cin_implements/openjunars/translators.rs +++ b/src/cin_implements/openjunars/translators.rs @@ -39,20 +39,24 @@ pub fn output_translate(content: String) -> Result { // 根据「头部」生成输出 let output = match &*head { "answer" => Output::ANSWER { - content_raw: content, // TODO: 有待捕获转译 narsese: None, + content_raw: content, }, "out" => Output::OUT { + // TODO: 有待捕获转译 + narsese: None, content_raw: content, + }, + "in" => Output::IN { // TODO: 有待捕获转译 narsese: None, + content, }, - "in" => Output::IN { content }, "exe" => Output::EXE { - content_raw: content, // TODO: 有待捕获转译 operation: Operation::new("UNKNOWN", [].into_iter()), + content_raw: content, }, "err" | "error" => Output::ERROR { description: content, diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs index 3e49713..32112f6 100644 --- a/src/cin_implements/opennars/mod.rs +++ b/src/cin_implements/opennars/mod.rs @@ -16,16 +16,34 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::test::{_test_opennars, JAR_PATH_OPENNARS}; + use crate::runtime::{ + tests::{_test_opennars, test_simple_answer, JAR_PATH_OPENNARS}, + CommandVmRuntime, + }; use navm::vm::VmLauncher; - #[test] - fn test() { + /// 工具/启动OpenNARS,获得虚拟机运行时 + fn launch_vm() -> CommandVmRuntime { // 从别的地方获取jar路径 let jar_path = JAR_PATH_OPENNARS; // 一行代码启动OpenNARS - let vm = OpenNARS::new(jar_path).launch(); + OpenNARS::new(jar_path).launch() + } + + #[test] + fn test() { + // 启动OpenNARS虚拟机 + let vm = launch_vm(); // 直接复用之前对OpenNARS的测试 _test_opennars(vm) } + + /// 测试/通用 | 基于Narsese + #[test] + fn test_universal() { + // 启动OpenNARS虚拟机 + let vm = launch_vm(); + // 使用通用测试逻辑 + test_simple_answer(vm) + } } diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 23820b4..4f19970 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -17,7 +17,7 @@ //! * `Executed based on: $0.2904;0.1184;0.7653$ <(&/,<{SELF} --> [right_blocked]>,+7,(^left,{SELF}),+55) =/> <{SELF} --> [SAFE]>>. %1.00;0.53%` //! * `EXE: $0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null` -use super::dialect::parse as parse_narsese_opennars; +use super::dialect::parse as parse_dialect_opennars; use crate::runtime::TranslateError; use anyhow::Result; use narsese::lexical::{Narsese, Term}; @@ -57,22 +57,23 @@ pub fn output_translate(content_raw: String) -> Result { // 根据「头部」生成输出 let output = match &*head.to_uppercase() { "IN" => Output::IN { + // 先提取其中的Narsese | ⚠️借用了`content_raw` + narsese: parse_narsese_opennars(head, tail)?, + // 然后传入整个内容 content: content_raw, }, "OUT" => { // 返回 Output::OUT { // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: strip_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + narsese: parse_narsese_opennars(head, tail)?, // 然后传入整个内容 content_raw, } } "ANSWER" => Output::ANSWER { // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: strip_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + narsese: parse_narsese_opennars(head, tail)?, // 然后传入整个内容 content_raw, }, @@ -85,7 +86,7 @@ pub fn output_translate(content_raw: String) -> Result { // 指定的头部 r#type: "ANTICIPATE".to_string(), // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: strip_parse_narsese(tail) + narsese: try_parse_narsese(tail) .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), // 然后传入整个内容 content: content_raw, @@ -109,19 +110,29 @@ pub fn output_translate(content_raw: String) -> Result { Ok(output) } -#[test] -fn t() { - dbg!(parse_operation_opennars( - "$0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null" - )); +/// (ONA)从原始输出中解析Narsese +/// * 🎯用于结合`#[cfg]`控制「严格模式」 +/// * 🚩生产环境下「Narsese解析出错」仅打印错误信息 +#[cfg(not(test))] +pub fn parse_narsese_opennars(head: &str, tail: &str) -> Result> { + use util::ResultBoost; + // ! ↓下方会转换为None + Ok(try_parse_narsese(tail) + .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}"))) +} + +/// (ONA)从原始输出中解析Narsese +/// * 🎯用于结合`#[cfg]`控制「严格模式」 +/// * 🚩测试环境下「Narsese解析出错」会上抛错误 +#[cfg(test)] +pub fn parse_narsese_opennars(_: &str, tail: &str) -> Result> { + // ! ↓下方会上抛错误 + Ok(Some(try_parse_narsese(tail)?)) } /// 在OpenNARS输出中解析出「NARS操作」 /// * 📄`$0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null` -/// * 📌目前能提取出其中的预算值,但实际上还是需要 -/// -/// TODO: 结合正则表达式进行解析 -/// TODO: 后续使用[`pest`]进行解析 +/// * 🚩【2024-03-29 22:45:11】目前能提取出其中的预算值,但实际上暂且不需要 pub fn parse_operation_opennars(tail: &str) -> Operation { // * 构建正则表达式(仅一次编译) let r = Regex::new(r"(\$[0-9.;]+\$)\s*\^(\w+)\(\[(.*)\]\)=").unwrap(); @@ -161,7 +172,7 @@ pub fn parse_operation_opennars(tail: &str) -> Operation { /// 从操作参数中解析出Narsese词项 fn parse_term_from_operation(term_str: &str) -> Result { // 首先尝试解析出Narsese - let parsed = parse_narsese_opennars(term_str)?; + let parsed = parse_dialect_opennars(term_str)?; // 其次尝试将其转换成Narsese词项 parsed .try_into_term() @@ -169,13 +180,13 @@ fn parse_term_from_operation(term_str: &str) -> Result { } /// 切分尾部字符串,并(尝试)从中解析出Narsese -fn strip_parse_narsese(tail: &str) -> Result { +fn try_parse_narsese(tail: &str) -> Result { // 提取并解析Narsese字符串 let narsese = tail // 去尾 .rfind('{') // 截取 & 解析 - .map(|right_index| parse_narsese_opennars(tail[..right_index].trim())); + .map(|right_index| parse_dialect_opennars(tail[..right_index].trim())); // 提取解析结果 match narsese { // 解析成功⇒提取 & 返回 diff --git a/src/cin_implements/pynars/mod.rs b/src/cin_implements/pynars/mod.rs index 6bacfed..45f8f38 100644 --- a/src/cin_implements/pynars/mod.rs +++ b/src/cin_implements/pynars/mod.rs @@ -18,17 +18,36 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::test::{_test_pynars, MODULE_PATH_PYNARS, MODULE_ROOT_PYNARS}; + use crate::runtime::{ + tests::{_test_pynars, test_simple_answer, MODULE_PATH_PYNARS, MODULE_ROOT_PYNARS}, + CommandVmRuntime, + }; use navm::vm::VmLauncher; - #[test] - fn test() { + /// 工具/启动PyNARS,获得虚拟机运行时 + fn launch_vm() -> CommandVmRuntime { // 从别的地方获取Python模块根目录、模块自身路径 let root_path = MODULE_ROOT_PYNARS; let module_path = MODULE_PATH_PYNARS; // 一行代码启动PyNARS | `python -m pynars.Console` @ "..\..\PyNARS-dev" - let vm = PyNARS::new(root_path, module_path).launch(); + PyNARS::new(root_path, module_path).launch() + } + + /// 测试/先前PyNARS测试 + #[test] + fn test() { + // 启动PyNARS虚拟机 + let vm = launch_vm(); // 直接复用之前对PyNARS的测试 _test_pynars(vm) } + + /// 测试/通用 | 基于Narsese + #[test] + fn test_universal() { + // 启动PyNARS虚拟机 + let vm = launch_vm(); + // 使用通用测试逻辑 + test_simple_answer(vm) + } } diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index 7e1f847..525ef39 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -3,15 +3,31 @@ //! * 📌基于命令行输入输出的字符串读写 //! * ✨NAVM指令→字符串 //! * ✨字符串→NAVM输出 +//! +//! ## 输出样例 +//! +//! * 📄`\u{1b}[90mInput: \u{1b}[39m\u{1b}[48;2;124;10;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;124;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;10;137m 1.00 \u{1b}[49m\u{1b}[36mIN :\u{1b}[39mC>?\r\n` +//! * 📄`\u{1b}[90mInput: \u{1b}[39m \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[34mINFO :\u{1b}[39m\u{1b}[38;5;249mRun 5 cycles.\u{1b}[39m\r\n` +//! * 📄`\u{1b}[48;2;106;10;10m 0.75 \u{1b}[49m\u{1b}[48;2;10;41;10m 0.25 \u{1b}[49m\u{1b}[48;2;10;10;102m 0.72 \u{1b}[49m\u{1b}[33mOUT :\u{1b}[39mA>. %1.000;0.448%\r\n` +//! * 📄`\u{1b}[48;2;134;10;10m 0.98 \u{1b}[49m\u{1b}[48;2;10;124;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[32mANSWER:\u{1b}[39mC>. %1.000;0.810%\r\n` +//! * 📄` \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0)-->^op> = $0.022;0.232;0.926$ <(*, 0)-->^op>! :\\: %1.000;0.853% {7: 2, 0, 1}\r\n` use crate::runtime::TranslateError; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use narsese::{ + api::ExtractTerms, + conversion::string::{ + impl_enum::format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, + impl_lexical::format_instances::FORMAT_ASCII, + }, + lexical::{Narsese, Term}, +}; use navm::{ cmd::Cmd, output::{Operation, Output}, }; -use regex::Regex; -use util::pipe; +use regex::{Captures, Regex}; +use util::{pipe, JoinTo}; /// PyNARS的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「PyNARS输入」 @@ -25,6 +41,10 @@ pub fn input_translate(cmd: Cmd) -> Result { // VOL指令:调整音量 // ! ⚠️该指令仅适用于`ConsolePlus` Cmd::VOL(n) => format!("/volume {n}"), + // REG指令:注册操作符 + // * 📄Input: /register name + // * `Operator ^name was successfully registered without code` + Cmd::REG { name, .. } => format!("/register {name}"), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 @@ -34,26 +54,155 @@ pub fn input_translate(cmd: Cmd) -> Result { Ok(content) } -/// 尝试获取输出类型(「头」文本) -fn try_get_output_type(inp: &str) -> Option { +/// 预处理 +/// * 🎯去掉输出字串中语义无关的杂项 +/// * 📄ANSI转义序列 +pub fn preprocess(s: &str) -> String { // ! `\e` => `\u{1b}` let re = Regex::new(r"\u{1b}\[[0-9;]*m").unwrap(); - // let inp = "\u{1b}[48;2;110;10;10m 0.78 \u{1b}[49m\u{1b}[48;2;10;41;10m 0.25 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[33mOUT :\u{1b}[39mC>. %1.000;0.810%\r\n"; - // 三个预算+一个头 - let re2 = Regex::new(r"([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+(\w+)\s*:").unwrap(); - let replaced = pipe! { - inp + pipe! { + s + // 去掉ANSI转义序列 => [re.replace_all](_, "") + // 去掉前后缀空白符 + => .trim() + // 转换为字符串 => .to_string() - }; - let _ = " 0.78 0.25 0.90 OUT :C>. %1.000;0.810%\r\n"; - dbg!(&replaced); - let captured = dbg!(pipe! { - replaced - => #{&} + } +} + +/// 尝试获取输出类型(「头」文本) +/// * 🚩输入:[`preprocess`]预处理后的文本 +/// * 🎯尝试获取「类型」字符串,若无则返回[`None`] +fn try_get_output_type(preprocessed: &str) -> Option { + // 截获输出类型,忽略前边的预算值 + let re2 = Regex::new(r"[0-9\s|]*(\w+)\s*:").unwrap(); + pipe! { + preprocessed + // 捕获 => [re2.captures](_) - }); - captured.map(|c| c[4].to_string()) + // 转换为字符串 + => .map(|captures|captures[1].into()) + } +} + +/// 尝试获取输出中的Narsese +/// * 🚩输入:[`preprocess`]预处理后的文本 +/// * 🎯尝试获取「Narsese」值 +fn try_get_narsese(preprocessed: &str) -> Result { + // 删去无用内容,并替换成预算值 | 三个预算+一个头 + // * 🚩【2024-03-30 00:15:24】开头必须是`[^0-9.]*`,以避免吃掉预算值「`0.98`⇒`8`」💥 + let re_trim_and_budget = + Regex::new(r"^[^0-9.]*([0-9.]+)[\s|]+([0-9.]+)[\s|]+([0-9.]+)[\s|]+\w+\s*:\s*").unwrap(); + let trimmed = re_trim_and_budget + // 删去其中无用的内容,并重整其中的预算值 // + .replace(preprocessed, |s: &Captures| { + // 创建「预算值」字串 + let mut budget = FORMAT_ASCII_ENUM.task.budget_brackets.0.to_string(); + + // 构造迭代器 + let mut s = s.iter(); + s.next(); // 消耗掉第一个「被匹配到的字符串」 + + // 遍历所有匹配到的「预算内容」 + s.flatten() + // 全部转换成「字串切片」 + .map(|c| c.as_str()) + // 拼接到已预置好「预算起始括弧」的字符串中 + .join_to(&mut budget, FORMAT_ASCII_ENUM.task.budget_separator); + + // 最后加入并返回 + budget + FORMAT_ASCII_ENUM.task.budget_brackets.1 + }) + .to_string(); + let parsed_narsese = FORMAT_ASCII.parse(&trimmed)?; + Ok(parsed_narsese) +} + +/// 获取输出中的Narsese +/// * 🎯根据「测试环境」与「生产环境」启用不同的模式 +/// * 🚩测试环境中「解析失败」会报错(成功了总返回[`Some`]) +/// * 🚩生产环境中「解析失败」仅提示(然后返回[`None`]) +#[cfg(not(test))] +fn get_narsese(preprocessed: &str) -> Result> { + use util::ResultBoost; + // * 🚩解析失败⇒提示⇒返回[`None`] + Ok(try_get_narsese(preprocessed).ok_or_run(|e| println!("尝试解析Narsese错误:{e}"))) +} + +/// 获取输出中的Narsese +/// * 🎯根据「测试环境」与「生产环境」启用不同的模式 +/// * 🚩测试环境中「解析失败」会报错(成功了总返回[`Some`]) +/// * 🚩生产环境中「解析失败」仅提示(然后返回[`None`]) +#[cfg(test)] +fn get_narsese(preprocessed: &str) -> Result> { + // * 🚩解析失败会上抛,成功了总是返回[`Some`] + Ok(Some(try_get_narsese(preprocessed)?)) +} + +/// 尝试获取输出中的「Narsese操作」 +/// * 🎯截获PyNARS中的「EXE」部分 +/// * 📄` \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0)-->^op> = $0.022;0.232;0.926$ <(*, 0)-->^op>! :\\: %1.000;0.853% {7: 2, 0, 1}\r\n` +/// * 📄"executed: arguments=, task=$0.000;0.339;0.950$ <(*, 0, 1, 2, 3)-->^op>! %1.000;0.853% {None: 7, 4, 5}, memory=. the \"task\" will be returned\r\n" +/// * 📄` \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0, 1, 2, 3)-->^op> = $0.000;0.339;0.950$ <(*, 0, 1, 2, 3)-->^op>! %1.000;0.853% {None: 7, 4, 5}\r\n` +/// * 📄"executed: arguments=, task=$0.220;0.232;0.926$ <(*, 0)-->^op>! :\\: %1.000;0.853% {7: 2, 0, 1}, memory=. the \"task\" will be returned\r\n" +fn try_get_operation(preprocessed: &str) -> Result { + let re_operation = Regex::new(r"EXE\s*:\s*(.+) = ").unwrap(); + let op = re_operation + .captures(preprocessed) + .unwrap() + .get(1) + .unwrap() + .as_str(); + let op = FORMAT_ASCII.parse(op).unwrap().try_into_term().unwrap(); + match op { + // * 📄`<(*, 0)-->^op>` + Term::Statement { + subject, predicate, .. + } => { + // 从主词提取操作参数 + let params = subject.extract_terms_to_vec(); + // 从谓词提取操作名 + let operator_name = match *predicate { + Term::Atom { name, .. } => name, + _ => return Err(anyhow!("陈述谓词不是原子词项")), + }; + Ok(Operation { + operator_name, + params, + }) + } + _ => Err(anyhow::anyhow!("无效的「操作表示」词项:{op:?}")), + } +} + +/// 获取输出中的「Narsese操作」 +/// * 🎯获取名称及其参数 +/// * 🎯根据「测试环境」与「生产环境」启用不同的模式 +/// * 🚩测试环境中「解析失败」会报错(成功了总返回[`Some`]) +/// * 🚩生产环境中「解析失败」仅提示(然后返回[`None`]) +#[cfg(not(test))] +fn get_operation(preprocessed: &str) -> Operation { + // * 🚩解析失败仅提示,然后返回「空操作」 + try_get_operation(preprocessed).unwrap_or_else(|e| { + println!("尝试从「{preprocessed}」解析Narsese操作错误:{e}"); + // 空操作 + Operation { + operator_name: "".into(), + params: vec![], + } + }) +} + +/// 获取输出中的Narsese +/// * 🎯根据「测试环境」与「生产环境」启用不同的模式 +/// * 🚩测试环境中「解析失败」会报错(成功了总返回[`Some`]) +/// * 🚩生产环境中「解析失败」仅提示(然后返回[`None`]) +#[cfg(test)] +fn get_operation(preprocessed: &str) -> Operation { + // * 🚩解析失败会直接报错 + try_get_operation(preprocessed) + .unwrap_or_else(|e| panic!("无法从「{preprocessed}」解析出Narsese操作:{e}")) } /// PyNARS的「输出转译」函数 @@ -71,10 +220,14 @@ fn try_get_output_type(inp: &str) -> Option { /// /// # * 特殊处理「信息」"INFO":匹配「INFO」开头的行 样例:`INFO : Loading RuleMap ...` pub fn output_translate(content: String) -> Result { + // 预处理 | 利用变量遮蔽,在输出中屏蔽ANSI转义序列 + let content = preprocess(&content); // 根据冒号分隔一次,然后得到「头部」 let head = pipe! { &content + // 获取输出类型 => try_get_output_type + // 统一转成小写 | ✅无需`trim`:在`try_get_output_type`中使用正则表达式保证 => .map(|s|s.to_lowercase()) }; // 取切片 | ❌不能使用闭包,因为闭包无法返回引用 @@ -85,20 +238,25 @@ pub fn output_translate(content: String) -> Result { // 根据「头部」生成输出 let output = match head { "answer" => Output::ANSWER { + narsese: get_narsese(&content)?, + content_raw: content, + }, + "achieved" => Output::ACHIEVED { + narsese: get_narsese(&content)?, content_raw: content, - // TODO: 有待捕获转译 - narsese: None, }, - "derived" => Output::OUT { + "out" => Output::OUT { + narsese: get_narsese(&content)?, content_raw: content, - // TODO: 有待捕获转译 - narsese: None, }, - "input" => Output::IN { content }, + "input" | "in" => Output::IN { + narsese: get_narsese(&content)?, + content, + }, + "info" => Output::INFO { message: content }, "exe" => Output::EXE { + operation: get_operation(&content), content_raw: content, - // TODO: 有待捕获转译 - operation: Operation::new("UNKNOWN", [].into_iter()), }, "err" | "error" => Output::ERROR { description: content, @@ -108,3 +266,41 @@ pub fn output_translate(content: String) -> Result { // 返回 Ok(output) } + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + /// 测试/尝试获取输出 + #[test] + fn test_try_get_output() { + test("\u{1b}[48;2;110;10;10m 0.78 \u{1b}[49m\u{1b}[48;2;10;41;10m 0.25 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[33mOUT :\u{1b}[39mC>. %1.000;0.810%\r\n"); + test("|0.80|0.50|0.95| IN : A. %1.000;0.900%"); + test("\u{1b}[90mInput: \u{1b}[39m\u{1b}[48;2;124;10;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;124;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;10;137m 1.00 \u{1b}[49m\u{1b}[36mIN :\u{1b}[39mC>?\r\n"); + test("0.98 0.90 0.90 ANSWER:C>. %1.000;0.810%"); + + fn test(inp: &str) { + let preprocessed = preprocess(inp); + let _ = " 0.78 0.25 0.90 OUT :C>. %1.000;0.810%\r\n"; + dbg!(&preprocessed); + let t = try_get_output_type(&preprocessed); + dbg!(&t); + + // 删去无用内容,并替换成预算值 | 三个预算+一个头 + dbg!(try_get_narsese(&preprocessed).expect("Narsese解析失败!")); + } + } + + /// 测试/尝试获取操作 + #[test] + fn test_try_get_operation() { + test(" \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0)-->^op> = $0.022;0.232;0.926$ <(*, 0)-->^op>! :\\: %1.000;0.853% {7: 2, 0, 1}\r\n"); + test(" \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0, 1, 2, 3)-->^op> = $0.000;0.339;0.950$ <(*, 0, 1, 2, 3)-->^op>! %1.000;0.853% {None: 7, 4, 5}\r\n"); + fn test(inp: &str) { + let inp = preprocess(inp); + let op = try_get_operation(&inp).unwrap(); + dbg!(op); + } + } +} diff --git a/src/runtime/command_vm/runtime.rs b/src/runtime/command_vm/runtime.rs index 32fbe38..3833808 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtime/command_vm/runtime.rs @@ -83,12 +83,27 @@ impl VmLauncher for CommandVm { } /// 单元测试 +/// * 🎯作为任何NAVM运行时的共用测试包 +/// * 🚩【2024-03-29 23:23:12】进一步开放:仍然只限定在「测试」环境中使用 #[cfg(test)] -pub(crate) mod test { - +pub mod tests { use super::*; use crate::runtime::TranslateError; - use narsese::conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, shortcuts::*}; + use narsese::{ + api::{GetBudget, GetPunctuation, GetStamp, GetTerm, GetTruth}, + conversion::{ + inter_type::lexical_fold::TryFoldInto, + string::{ + impl_enum::format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, + impl_lexical::{format_instances::FORMAT_ASCII, shortcuts::*}, + }, + }, + enum_narsese::{ + Budget as EnumBudget, Narsese as EnumNarsese, Sentence as EnumSentence, + Task as EnumTask, Truth as EnumTruth, + }, + lexical::Narsese, + }; use std::process::Command; use util::first; @@ -178,6 +193,134 @@ pub(crate) mod test { } } + /// 实用测试工具/输入并等待「Narsese回显」 + /// * 🚩`input_cmd_and_await`的简单封装 + /// * ✅【2024-03-29 22:55:11】现在「输出转换」已经成熟(可以提取出Narsese) + /// * 🚩通过「转换为『枚举Narsese』」以实现判等逻辑(主要为「语义相等」) + #[inline(always)] + pub fn input_cmd_and_await_narsese( + vm: &mut CommandVmRuntime, + cmd: Cmd, + expected: Narsese, + ) -> Output { + // 预先构建预期 + let expected = expected + .clone() + .try_fold_into(&FORMAT_ASCII_ENUM) + .expect("作为预期的词法Narsese无法折叠!"); + // 输入 & 等待 + input_cmd_and_await(vm, cmd, |out, _| { + // 有Narsese + out.get_narsese().is_some_and(|out| { + // 且与预期一致 + out.clone() // 必须复制:折叠消耗自身 + .try_fold_into(&FORMAT_ASCII_ENUM) + .is_ok_and(|out| is_expected_narsese(&expected, &out)) + }) + }) + } + + /// 判断「输出是否(在Narsese语义层面)符合预期」 + /// * 🎯词法Narsese⇒枚举Narsese,以便从语义上判断 + pub fn is_expected_narsese_lexical(expected: &Narsese, out: &Narsese) -> bool { + // 临时折叠预期 + let expected = (expected.clone().try_fold_into(&FORMAT_ASCII_ENUM)) + .expect("作为预期的词法Narsese无法折叠!"); + // 与预期一致 + (out.clone() // 必须复制:折叠消耗自身 + .try_fold_into(&FORMAT_ASCII_ENUM)) + .is_ok_and(|out| is_expected_narsese(&expected, &out)) + } + + /// 判断「输出是否(在Narsese层面)符合预期」 + /// * 🎯预期词项⇒只比较词项,语句⇒只比较语句,…… + pub fn is_expected_narsese(expected: &EnumNarsese, out: &EnumNarsese) -> bool { + match ((expected), (out)) { + // 词项⇒只比较词项 | 直接判等 + (EnumNarsese::Term(term), ..) => term == out.get_term(), + // 语句⇒只比较语句 + // ! 仍然不能直接判等:真值/预算值 + ( + EnumNarsese::Sentence(s_exp), + EnumNarsese::Sentence(s_out) | EnumNarsese::Task(EnumTask(s_out, ..)), + ) => is_expected_sentence(s_exp, s_out), + // 任务⇒直接判断 + // ! 仍然不能直接判等:真值/预算值 + (EnumNarsese::Task(t_exp), EnumNarsese::Task(t_out)) => is_expected_task(t_exp, t_out), + // 所有其它情况⇒都是假 + (..) => false, + } + } + + /// 判断输出的任务是否与预期任务相同 + /// * 🎯用于细粒度判断「预算值」「语句」的预期 + pub fn is_expected_task(expected: &EnumTask, out: &EnumTask) -> bool { + // 预算 + is_expected_budget(expected.get_budget(), out.get_budget()) + // 语句 + && is_expected_sentence(expected.get_sentence(), out.get_sentence()) + } + + /// 判断输出的语句是否与预期语句相同 + /// * 🎯用于细粒度判断「真值」的预期 + pub fn is_expected_sentence(expected: &EnumSentence, out: &EnumSentence) -> bool { + // 词项判等 + ((expected.get_term())==(out.get_term())) + // 标点相等 + && expected.get_punctuation() == out.get_punctuation() + // 时间戳相等 + && expected.get_stamp()== out.get_stamp() + // 真值兼容 | 需要考虑「没有真值可判断」的情况 + && match (expected.get_truth(),out.get_truth()) { + // 都有⇒判断「真值是否符合预期」 + (Some(t_e), Some(t_o)) => is_expected_truth(t_e, t_o), + // 都没⇒肯定真 + (None, None) => true, + // 有一个没有⇒肯定假 + _ => false, + } + } + + /// 判断「输出是否在真值层面符合预期」 + /// * 🎯空真值的语句,应该符合「固定真值的语句」的预期——相当于「通配符」 + pub fn is_expected_truth(expected: &EnumTruth, out: &EnumTruth) -> bool { + match (expected, out) { + // 预期空真值⇒通配 + (EnumTruth::Empty, ..) => true, + // 预期单真值 + (EnumTruth::Single(f_e), EnumTruth::Single(f_o) | EnumTruth::Double(f_o, ..)) => { + f_e == f_o + } + // 预期双真值 + (EnumTruth::Double(..), EnumTruth::Double(..)) => expected == out, + // 其它情况 + _ => false, + } + } + + /// 判断「输出是否在预算值层面符合预期」 + /// * 🎯空预算的语句,应该符合「固定预算值的语句」的预期——相当于「通配符」 + pub fn is_expected_budget(expected: &EnumBudget, out: &EnumBudget) -> bool { + match (expected, out) { + // 预期空预算⇒通配 + (EnumBudget::Empty, ..) => true, + // 预期单预算 + ( + EnumBudget::Single(p_e), + EnumBudget::Single(p_o) | EnumBudget::Double(p_o, ..) | EnumBudget::Triple(p_o, ..), + ) => p_e == p_o, + // 预期双预算 + ( + EnumBudget::Double(p_e, d_e), + EnumBudget::Double(p_o, d_o) | EnumBudget::Triple(p_o, d_o, ..), + ) => p_e == p_o && d_e == d_o, + // 预期三预算 + (EnumBudget::Triple(..), EnumBudget::Triple(..)) => expected == out, + // 其它情况 + _ => false, + } + } + /// 示例测试 | OpenNARS /// * 🚩通过Java命令启动 #[test] @@ -329,4 +472,37 @@ pub(crate) mod test { File "", line 88, in _run_code */ } + + /// 通用测试/简单回答 | 基于Narsese + /// * 📌考察NARS最基础的「继承演绎推理」 + pub fn test_simple_answer(mut vm: CommandVmRuntime) { + // 构造并输入任务 | 输入进PyNARS后变成了紧凑版本 + let _ = vm.input_cmd(Cmd::VOL(0)); // * 尝试静音 + input_cmd_and_await_narsese(&mut vm, Cmd::NSE(nse_task!( B>.)), nse!( B>.)); + input_cmd_and_await_narsese(&mut vm, Cmd::NSE(nse_task!( C>.)), nse!( C>.)); + input_cmd_and_await_narsese(&mut vm, Cmd::NSE(nse_task!( C>?)), nse!( C>?)); + vm.input_cmd(Cmd::CYC(5)).expect("无法输入CYC指令"); // * CYC无需自动等待 + + // 等待回答 + let expected_answer = nse!( C>.); + await_fetch_until(&mut vm, |output, _| match output { + Output::ANSWER { narsese: out, .. } => { + is_expected_narsese_lexical( + &expected_answer, + // ! 不允许回答内容为空 | 必须拷贝再比对 + &out.clone().expect("预期的回答内容为空!"), + ) + } + _ => false, + }); + + // 打印所有输出 + while let Some(output) = vm.try_fetch_output().unwrap() { + println!("{:?}", output); + } + + // 终止虚拟机 + vm.terminate().expect("无法终止虚拟机"); + println!("Virtual machine terminated..."); + } } From 159aa91bf09cabbde96f445c33cb9b920b8b846a Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 30 Mar 2024 01:24:54 +0800 Subject: [PATCH 28/59] =?UTF-8?q?test:=20:test=5Ftube:=20=E4=B8=BACXinNARS?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E3=80=8C=E7=AE=80=E5=8D=95=E6=BC=94=E7=BB=8E?= =?UTF-8?q?=E4=B8=89=E6=AE=B5=E8=AE=BA=E3=80=8D=E6=B5=8B=E8=AF=95=EF=BC=88?= =?UTF-8?q?failing=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cin_implements/cxin_js/mod.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs index 3b819c1..a3cca31 100644 --- a/src/cin_implements/cxin_js/mod.rs +++ b/src/cin_implements/cxin_js/mod.rs @@ -13,9 +13,10 @@ util::mod_and_pub_use! { /// 单元测试 #[cfg(test)] mod tests { + #![allow(unused)] use super::*; use crate::runtime::{ - tests::{await_fetch_until, input_cmd_and_await_contains}, + tests::{await_fetch_until, input_cmd_and_await_contains, test_simple_answer}, CommandVmRuntime, }; use narsese::lexical_nse_task as nse_task; @@ -27,12 +28,18 @@ mod tests { /// 测试用路径 const CXIN_NARS_JS_PATH: &str = r"..\cxin-nars-py-to-ts\src\cxin-nars-shell.js"; - #[test] - fn test() { - // 从别的地方获取exe路径 + /// 通用/启动VM + fn launch_vm() -> CommandVmRuntime { + // 从别的地方获取js路径 let js_path = CXIN_NARS_JS_PATH; // 一行代码启动CxinNARS - let vm = CXinJS::new(js_path).launch(); + CXinJS::new(js_path).launch() + } + + /// 测试/专用 + #[test] + fn test() { + let vm = launch_vm(); // 进入专用测试 _test_cxin_js(vm) } @@ -59,4 +66,13 @@ mod tests { vm.terminate().expect("无法终止虚拟机"); println!("Virtual machine terminated..."); } + + /// 测试/通用 | 基于Narsese + #[test] + fn test_universal() { + // // 启动OpenNARS虚拟机 + // let vm = launch_vm(); + // // 使用通用测试逻辑 + // test_simple_answer(vm) + } } From e44b0090ae96146ab779614177ec855cc23c4747 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 31 Mar 2024 01:31:37 +0800 Subject: [PATCH 29/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E3=80=8C=E8=B7=AF=E5=BE=84=E9=81=8D=E5=8E=86?= =?UTF-8?q?/=E8=B7=AF=E5=BE=84=E6=9E=84=E5=BB=BA=E3=80=8D=E7=9A=84?= =?UTF-8?q?=E3=80=8C=E8=87=AA=E5=8A=A8CIN=E6=90=9C=E7=B4=A2=E3=80=8D?= =?UTF-8?q?=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ✅基于「路径遍历器」与「路径构建器」的通用搜索特征 2. ✅基本实用的「名称匹配」逻辑,及其「路径遍历」应用测试(📄遇见名称与「nars」有关的就深入) 3. ✅适用于OpenNARS、ONA的「路径构建器」 4. 🚧TODO:PyNARS的「路径构建器」 5. 🚧TODO:不同类型「CIN搜索」的统一适配(通过用户输入选取,在选择后进行匹配,尽可能数据驱动) --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/bin/cin_launcher/cin_search/anyhow_vm.rs | 125 +++++++++ src/bin/cin_launcher/cin_search/mod.rs | 19 ++ src/bin/cin_launcher/cin_search/name_match.rs | 79 ++++++ .../cin_launcher/cin_search/path_builder.rs | 66 +++++ .../cin_launcher/cin_search/path_walker.rs | 240 ++++++++++++++++++ .../cin_launcher/impls_path_builder/mod.rs | 75 ++++++ .../impls_path_builder/path_builder_ona.rs | 86 +++++++ .../path_builder_opennars.rs | 83 ++++++ src/bin/cin_launcher/io/mod.rs | 6 + .../{cin_launcher.rs => cin_launcher/main.rs} | 12 + 12 files changed, 794 insertions(+), 3 deletions(-) create mode 100644 src/bin/cin_launcher/cin_search/anyhow_vm.rs create mode 100644 src/bin/cin_launcher/cin_search/mod.rs create mode 100644 src/bin/cin_launcher/cin_search/name_match.rs create mode 100644 src/bin/cin_launcher/cin_search/path_builder.rs create mode 100644 src/bin/cin_launcher/cin_search/path_walker.rs create mode 100644 src/bin/cin_launcher/impls_path_builder/mod.rs create mode 100644 src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs create mode 100644 src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs create mode 100644 src/bin/cin_launcher/io/mod.rs rename src/bin/{cin_launcher.rs => cin_launcher/main.rs} (94%) diff --git a/Cargo.lock b/Cargo.lock index 489dec8..4130358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "babel_nar" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "lazy_static", @@ -105,7 +105,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "nar_dev_utils" -version = "0.20.0" +version = "0.22.0" [[package]] name = "narsese" diff --git a/Cargo.toml b/Cargo.toml index 894b4f1..f5dcbbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.8.0" +version = "0.9.0" edition = "2021" [dependencies] diff --git a/src/bin/cin_launcher/cin_search/anyhow_vm.rs b/src/bin/cin_launcher/cin_search/anyhow_vm.rs new file mode 100644 index 0000000..1150271 --- /dev/null +++ b/src/bin/cin_launcher/cin_search/anyhow_vm.rs @@ -0,0 +1,125 @@ +//! 🚩【2024-03-30 23:36:48】曾经的尝试: +//! * 所有「路径构建器」都返回一个动态的「虚拟机启动器」类型 +//! * 启动时只需在一个「Anyhow虚拟机启动器」列表中选择 + +trait Turned { + fn say(&self); +} +trait Unturned { + type Target: Turned; + fn turn(self) -> Self::Target; + fn turn_box(self: Box) -> Box; + fn turn_box_sized(self: Box) -> Box + where + Self: Sized, + { + Box::new(self.turn()) + } +} +struct U(usize); +struct T(usize); +impl Turned for T { + fn say(&self) { + print!("I'm T({})", self.0) + } +} +impl Unturned for U { + type Target = T; + fn turn(self) -> T { + T(self.0) + } + fn turn_box(self: Box) -> Box { + self.turn_box_sized() + } +} +struct AnyhowUnturned { + inner: Box>, +} +struct AnyhowTurned { + inner: Box, +} +impl Turned for AnyhowTurned { + fn say(&self) { + self.inner.say() + } +} +impl Unturned for AnyhowUnturned { + type Target = AnyhowTurned; + fn turn(self) -> AnyhowTurned { + AnyhowTurned { + inner: self.inner.turn_box(), + } + } + + fn turn_box(self: Box) -> Box { + self.turn_box_sized() + } +} +impl> From for AnyhowUnturned { + fn from(value: U) -> Self { + Self { + inner: Box::new(value), + } + } +} +struct AnyhowUnturned2 { + inner: AnyhowTurned, +} + +fn main() { + let unturned: AnyhowUnturned<_> = U(1).into(); +} + +// pub struct AnyhowLauncher<'a, Runtime: VmRuntime + 'a> { +// pub launcher: Box + 'a>, +// } + +// impl<'a, Runtime: VmRuntime + 'a> AnyhowLauncher<'a, Runtime> { +// pub fn new(launcher: impl VmLauncher + 'a) -> Self +// where +// Launcher: VmLauncher + 'a, +// { +// Self { +// launcher: Box::new(launcher), +// } +// } +// } + +// /// ! Box不能充当`VmLauncher`的参数:未实现`VmRuntime` +// impl<'a, Runtime: VmRuntime + 'a> VmLauncher> for AnyhowLauncher<'a, Runtime> { +// fn launch(self) -> AnyhowRuntime<'a> { +// AnyhowRuntime { +// inner: Box::new(self.launcher.launch()), +// } +// } +// } + +// struct AnyhowRuntime<'a> { +// inner: Box, +// } + +// impl AnyhowRuntime<'_> { +// fn new(inner: impl VmRuntime) -> Self { +// Self { +// inner: Box::new(inner), +// } +// } +// } + +// impl VmRuntime for AnyhowRuntime<'_> { +// fn input_cmd(&mut self, cmd: navm::cmd::Cmd) -> anyhow::Result<()> { +// self.inner.input_cmd(cmd) +// } + +// fn fetch_output(&mut self) -> anyhow::Result { +// self.inner.fetch_output() +// } + +// fn try_fetch_output(&mut self) -> anyhow::Result> { +// self.inner.try_fetch_output() +// } + +// fn terminate(self) -> anyhow::Result<()> { +// self.inner.terminate() +// } +// } diff --git a/src/bin/cin_launcher/cin_search/mod.rs b/src/bin/cin_launcher/cin_search/mod.rs new file mode 100644 index 0000000..c27aee7 --- /dev/null +++ b/src/bin/cin_launcher/cin_search/mod.rs @@ -0,0 +1,19 @@ +//! CIN启动器中有关「CIN路径构建(搜索)」的逻辑 +//! * ✨根据「CIN路径构建器」搜索(判别)系统中已存在的CIN实现(并自动构建) +//! * 🚩输入:搜索起点(一般是编译后exe所在文件夹) +//! * 🚩输出:NAVM启动器列表 +//! * ❓【2024-03-30 19:12:29】是否要考虑返回更细化的「CIN实例位置」而非「CIN启动器」,以避免额外的性能开销? + +use nar_dev_utils::mods; + +/// 导出模块 +mods! { + // 路径遍历器 + use pub path_walker; + // 路径构造器 + use pub path_builder; + // anyhow | 弃用 + // anyhow_vm; + // 名称匹配 + use pub name_match; +} diff --git a/src/bin/cin_launcher/cin_search/name_match.rs b/src/bin/cin_launcher/cin_search/name_match.rs new file mode 100644 index 0000000..e6a0341 --- /dev/null +++ b/src/bin/cin_launcher/cin_search/name_match.rs @@ -0,0 +1,79 @@ +//! 封装「名称匹配」逻辑 +//! * 🎯用于「语义化」「模糊化」的字符串匹配 +//! * ✨无视大小写匹配 +//! * 📄"opennars"匹配"OpenNARS" +//! * ✨「含于」与「包含」匹配 +//! * 📄"opennars"匹配"OpenNARS 3.0.4"(含于)与"nars"(包含) +//! * ✨返回一个「匹配度」的数值 +//! * `0`统一表示「未匹配」 +//! * 剩余值可用于排序 + +use nar_dev_utils::{first, if_return}; + +/// 名称匹配 +/// * 🎯用于「语义化」「模糊化」的字符串匹配 +/// * ✨无视大小写匹配 +/// * 📄"opennars"匹配"OpenNARS" +/// * ✨「含于」与「包含」匹配 +/// * 📄"opennars"匹配"OpenNARS 3.0.4"(含于)与"nars"(包含) +/// * ⚙️返回一个「匹配度」的数值 +/// * `0`统一表示「未匹配」 +/// * 剩余值可用于排序 +pub fn name_match(name: &str, target: &str) -> usize { + // 完全相等⇒最高级 + if_return! { + // 完全相等⇒高 + name == target => 6 + // 包含于⇒中 + target.contains(name) => 4 + // 包含⇒低 + name.contains(target) => 2 + } + + // 忽略大小写的情况 | 忽略大小写,降一个匹配度 + let name = name.to_lowercase(); + let target = target.to_lowercase(); + + first! { + // 完全相等⇒高 + name == target => 5, + // 包含于⇒中 + target.contains(&name) => 3, + // 包含⇒低 + name.contains(&target) => 1, + // 否则⇒不匹配 + _ => 0, + } +} + +/// 名称匹配/仅「含于」 +/// * 🚩与[`name_match`]类似,但仅「含于」而不适配「包含」 +/// * 🎯用于「长串名称作为内部关键词」的匹配 +pub fn name_match_only_contains(name: &str, target: &str) -> usize { + // 完全相等⇒最高级 + if_return! { + // 完全相等⇒高 + name == target => 4 + // 含于⇒低 + target.contains(name) => 2 + } + + // 忽略大小写的情况 | 忽略大小写,降一个匹配度 + let name = name.to_lowercase(); + let target = target.to_lowercase(); + + first! { + // 完全相等⇒高 + name == target => 3, + // 含于⇒低 + target.contains(&name) => 1, + // 否则⇒不匹配 + _ => 0, + } +} + +/// 判断「是否匹配」,不管「匹配度」多少 +/// * 🚩直接复用逻辑,以牺牲一定性能为代价 +pub fn is_name_match(name: &str, target: &str) -> bool { + name_match(name, target) > 0 +} diff --git a/src/bin/cin_launcher/cin_search/path_builder.rs b/src/bin/cin_launcher/cin_search/path_builder.rs new file mode 100644 index 0000000..004805f --- /dev/null +++ b/src/bin/cin_launcher/cin_search/path_builder.rs @@ -0,0 +1,66 @@ +//! 统一的「路径构建器」逻辑 + +use navm::vm::{VmLauncher, VmRuntime}; +use std::path::Path; + +/// CIN路径构建器 +/// * 🚩本身不承担「遍历路径」的任务,只负责 +/// * 📌判断是否「可以用于构建NAVM运行时」 +/// * 📌从某路径构建「NAVM启动器」 +/// * ❌【2024-03-30 19:05:48】放弃「通用启动器/通用运行时」的适配尝试 +/// * 📝目前[`CinSearch::Launcher`]总是要带上[`CinSearch::Runtime`]作类型参数 +/// * 📌堵点:难以定义一个使用`Box>`封装的`AnyhowVmLauncher`类型 +/// * 📍问题领域:特征对象及其转换 +/// * ❓一个可能的参考:[`anyhow`]对「错误类型」的统一 +/// * ❌【2024-03-30 21:24:10】尝试仍然失败:有关`Box`的所有权转换问题 +/// * 🔗技术参考1: +pub trait CinPathBuilder { + /// 搜索结果的启动器类型 + /// * 📌启动后变为[`CinSearch::Runtime`]运行时类型 + type Launcher: VmLauncher; + + /// 搜索结果的运行时类型 + type Runtime: VmRuntime; + + /// 路径匹配 + /// * 🎯匹配某路径(可能是文件夹,也可能是文件)是否可用于「构建NAVM启动器」 + /// * ⚠️与**该路径是否存在**有关 + /// * 📌需要访问本地文件系统 + /// * 📄一些CIN可能要求判断其子目录的文件(附属文件) + /// * ⚙️返回「匹配度」 + /// * 📌`0`⇒不匹配,其它⇒不同程度的匹配 + /// * 🎯对接「名称匹配」中的「匹配度」 + /// * ✨可用于后续排序 + fn match_path(&self, path: &Path) -> usize; + + /// 用于检查路径是否匹配 + /// * 🔗参见[`match_path`] + fn is_path_matched(&self, path: &Path) -> bool { + self.match_path(path) > 0 + } + + /// 路径构建 + /// * 🎯从某个路径构建出一个NAVM启动器 + /// * ✅除路径以外,其它参数可作默认 + /// * 📄OpenNARS的「Java最大堆大小」 + /// + /// # Panics + /// + /// ⚠️需要保证[`is_path_matched`]为真 + /// * 为假时可能`panic` + fn construct_from_path(&self, path: &Path) -> Self::Launcher; + + /// 尝试路径构建 + /// * 🚩返回一个[`Option`] + /// * 能构建⇒返回构建后的结果 `Some((启动器, 匹配度))` + /// * 无法构建⇒返回[`None`] + #[inline] + fn try_construct_from_path(&self, path: &Path) -> Option<(Self::Launcher, usize)> { + match self.match_path(path) { + // 不匹配⇒无 + 0 => None, + // 匹配⇒元组 + n => Some((self.construct_from_path(path), n)), + } + } +} diff --git a/src/bin/cin_launcher/cin_search/path_walker.rs b/src/bin/cin_launcher/cin_search/path_walker.rs new file mode 100644 index 0000000..7c6ec50 --- /dev/null +++ b/src/bin/cin_launcher/cin_search/path_walker.rs @@ -0,0 +1,240 @@ +//! 路径遍历器 +//! * 🎯用于分离「路径查找」与「CIN识别」两功能 +//! * 📌「路径遍历器」负责「提供路径,并有选择地 深入/跳出 路径」 + +use anyhow::{Error, Result}; +use std::path::{Path, PathBuf}; + +/// 抽象的「路径遍历」特征 +/// * ✨允许「迭代出下一个路径」 +/// * 🏗️后续可能会添加更多特性,如「根据结果调整遍历策略」等 +pub trait PathWalker { + /// ✨返回「下一个路径」 + /// * 可能为空,也可能返回错误 + fn next_path(&mut self) -> Result>; + + /// 类似迭代器的`next`方法 + /// * 🎯对标`Iterator>` + /// * 🚩【2024-03-31 01:03:04】是「没法为`impl PathWalker`自动实现`Iterator`」的补偿 + fn iter_next_path(&mut self) -> Option> { + match self.next_path() { + // 正常情况 + Ok(Some(path)) => Some(Ok(path)), + // 中途报错⇒返回错误 + Err(e) => Some(Err(e)), + // 终止⇒真正终止 + Ok(None) => None, + } + } + + /// 利用[`std::iter::from_fn`]将自身转换为迭代器,而无需实现[`Iterator`]特征 + /// * 🎯便于在`impl PathWalker`中使用 + #[inline] + fn to_iter_fn<'a>(mut self) -> impl Iterator> + 'a + where + Self: Sized + 'a, + { + std::iter::from_fn(move || self.iter_next_path()) + } +} + +/// 初代路径遍历器 +/// * ✨使用「渐近回退性扫描」机制,总体为「深度优先」 +/// * 📌「起始目录」一般为exe所在目录 +/// * 🚩从「起始目录」开始,扫描其下子目录 +/// * 递归深入、迭代出文件夹与文件 +/// * 🚩若「起始目录」已扫描完毕,向上「条件扫描」父目录 +/// * 遍历其【直接包含】的文件/文件夹 +/// * 若有满足特定「可深入条件」的文件夹,则深入扫描该文件夹(仍然是「条件扫描」) +/// * 🚩父目录扫描完毕后,继续扫描父目录 +pub struct PathWalkerV1<'a> { + // 父目录堆栈 + ancestors_stack: Vec, + + /// 待遍历目录的堆栈 + to_visit_stack: Vec, + + /// 可深入条件 + deep_criterion: Box bool + Send + Sync + 'a>, + + /// 当前在遍历目录的迭代器 + current_dir_iter: Box>>, +} + +impl<'a> PathWalkerV1<'a> { + pub fn new( + start: &Path, + deep_criterion: impl Fn(&Path) -> bool + Send + Sync + 'a, + ) -> Result { + // 计算根目录 + // * 🚩不是文件夹⇒向上寻找根目录 + let mut root = start; + while !root.is_dir() { + root = root.parent().unwrap(); + } + // 构造路径堆栈 + let mut ancestors_stack = root.ancestors().map(Path::to_owned).collect::>(); + ancestors_stack.reverse(); // 从「当前→根」转为「根→当前」,先遍历当前,再遍历根 + // 拿出目录 + let root = match ancestors_stack.pop() { + Some(path) => path, + None => return Err(Error::msg("起始目录无效")), + }; + let deep_criterion = Box::new(deep_criterion); + let current_dir_iter = Box::new(Self::new_path_iter(&root)?); + Ok(Self { + ancestors_stack, + to_visit_stack: vec![], // 空栈初始化 + deep_criterion, + current_dir_iter, + }) + } + + /// ✨构造路径迭代器 + /// * 🎯尽可能让异常变得可处理:避免`unwrap` + fn new_path_iter(path: &Path) -> Result>> { + Ok(std::fs::read_dir(path)?.map(|e| match e { + Ok(entry) => Ok(entry.path()), + Err(e) => Err(e.into()), + })) + } + + /// 可能返回[`None`]的[`Self::next`] + /// * 🎯应对「切换到父目录的迭代器后,首个迭代结果还是[`None`]」的情况 + /// * 🚩解决方案:再次[`Self::poll_path`] + fn poll_path(&mut self) -> PathPollResult { + // ! ❌【2024-03-30 22:34:04】目前没法稳定地使用`?` + match self.current_dir_iter.next() { + // 正常情况 + Some(Ok(path)) => { + // 如果「值得深入」⇒预备在后续深入 + if path.is_dir() && (self.deep_criterion)(&path) { + self.to_visit_stack.push(path.clone()) + } + // 返回 + PathPollResult::Some(path) + } + // 中途报错情况 + Some(Err(e)) => PathPollResult::Err(e), + // 没有⇒尝试切换路径 + None => self.try_switch_current_path(), + } + } + + /// 尝试切换路径 + /// * 切换到一个新的路径 + fn try_switch_current_path(&mut self) -> PathPollResult { + match self.to_visit_stack.pop() { + // 「待检查路径」有⇒尝试pop一个,构造并切换到新的迭代器 + Some(path) => match self.change_current_path(&path) { + Ok(()) => PathPollResult::None, // 构造了就收手,无需立马查看里边有无路径 + Err(e) => PathPollResult::Err(e), + }, + // 「待检查路径」没有⇒尝试从「祖先路径」中尝试pop一个 + None => match self.ancestors_stack.pop() { + // 「祖先路径」有⇒尝试pop一个,构造并切换到新的迭代器 + Some(path) => match self.change_current_path(&path) { + Ok(()) => PathPollResult::None, // 构造了就收手,无需立马查看里边有无路径 + Err(e) => PathPollResult::Err(e), + }, // 「祖先路径」没有⇒终止 + None => PathPollResult::Ended, + }, + } + } + + /// 尝试更改到某个目录(的迭代器) + fn change_current_path(&mut self, path: &Path) -> Result<()> { + let iter = Self::new_path_iter(path)?; + self.current_dir_iter = Box::new(iter); + Ok(()) + } +} + +/// 枚举「路径遍历」结果 +/// * 🎯用于「路径遍历器」的返回值 +pub enum PathPollResult { + /// 拿到了一个路径 + Some(PathBuf), + /// 尝试拿,但没拿到路径 + None, + /// 尝试拿,但发生错误 + Err(Error), + /// 结束了 + Ended, +} + +impl From> for PathPollResult { + fn from(value: Option) -> Self { + match value { + Some(path) => Self::Some(path), + None => Self::None, + } + } +} + +impl From> for PathPollResult { + fn from(value: Result) -> Self { + match value { + Ok(path) => Self::Some(path), + Err(e) => Self::Err(e), + } + } +} + +impl PathWalker for PathWalkerV1<'_> { + fn next_path(&mut self) -> Result> { + // 持续不断poll自身,压缩掉其中的`None`项 + loop { + match self.poll_path() { + // 正常返回路径 + PathPollResult::Some(path) => break Ok(Some(path)), + // 没有⇒继续循环(压缩掉) + PathPollResult::None => continue, + // 报错⇒返回错误 + PathPollResult::Err(e) => break Err(e), + // 终止⇒返回终止信号 + PathPollResult::Ended => break Ok(None), + } + } + } +} + +/// 实现迭代器,返回所有「搜索结果」 +impl Iterator for PathWalkerV1<'_> { + type Item = Result; + fn next(&mut self) -> Option> { + self.iter_next_path() + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::name_match::is_name_match; + use std::env::current_dir; + + fn _test_path_walker_v1(start: impl Into) { + // 起始目录 + let start = &start.into(); + // 深入条件 + fn deep_criterion(path: &Path) -> bool { + path.file_name() + .is_some_and(|name| name.to_str().is_some_and(|s| is_name_match("nars", s))) + }; + // 构建遍历者,加上条件 + let walker = PathWalkerV1::new(start, deep_criterion).unwrap(); + // 打印遍历者的「祖先列表」 + println!("{:?}", walker.ancestors_stack); + // 遍历 + for path in walker { + println!("{path:?}"); + } + } + + #[test] + fn test_path_walker_v1() { + // 测试当前路径 + _test_path_walker_v1(current_dir().unwrap()); + } +} diff --git a/src/bin/cin_launcher/impls_path_builder/mod.rs b/src/bin/cin_launcher/impls_path_builder/mod.rs new file mode 100644 index 0000000..d6837fa --- /dev/null +++ b/src/bin/cin_launcher/impls_path_builder/mod.rs @@ -0,0 +1,75 @@ +//! 存储各CIN的「路径构建器」 +//! * ✅OpenNARS +//! * ✅ONA +//! TODO: PyNARS +//! TODO: CXinNARS +//! * 🚩【2024-03-31 01:27:09】其它接口完成度不高的CIN,暂时弃了 + +use crate::{name_match::is_name_match, path_builder::CinPathBuilder, path_walker::PathWalker}; +use nar_dev_utils::{list, mods}; +use navm::vm::{VmLauncher, VmRuntime}; +use std::path::Path; + +mods! { + // OpenNARS + use pub path_builder_opennars; + // ONA + use pub path_builder_ona; +} + +// 深入条件 +pub fn file_name_matches(path: &Path, name: &str) -> bool { + path.file_name().is_some_and(|name_os| { + name_os + .to_str() + .is_some_and(|name_str| is_name_match(name, name_str)) + }) +} + +/// 从遍历者中找到匹配的所有启动器 +/// * 🎯仅搜索出「可能有效,故构建好」的启动器 +pub fn launchers_from_walker>( + path_walker: impl PathWalker, + path_builder: impl CinPathBuilder, +) -> Vec<(L, usize)> { + path_walker + .to_iter_fn() + .filter_map(Result::ok) + .filter_map(|p| path_builder.try_construct_from_path(&p)) + .collect::>() +} + +/// 类似[`launchers_from_walker`],但根据返回的「匹配度」从高到底排序 +pub fn launchers_from_walker_sorted>( + path_walker: impl PathWalker, + path_builder: impl CinPathBuilder, +) -> Vec { + // 获取 & 排序 + let mut launchers = launchers_from_walker(path_walker, path_builder); + launchers.sort_by(|(_, a), (_, b)| b.cmp(a)); // ←此处是倒序 + // 提取左侧元素 + launchers.into_iter().map(|(l, _)| l).collect::>() +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use crate::cin_search::path_builder::CinPathBuilder; + use crate::path_walker::PathWalkerV1; + use nar_dev_utils::list; + use std::env::current_dir; + + #[test] + fn test() { + let path_walker = PathWalkerV1::new(¤t_dir().unwrap(), |path| { + file_name_matches(path, "nars") + }) + .unwrap(); + + dbg!(launchers_from_walker_sorted( + path_walker, + PathBuilderOpenNARS + )); + } +} diff --git a/src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs b/src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs new file mode 100644 index 0000000..8dd3c1a --- /dev/null +++ b/src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs @@ -0,0 +1,86 @@ +//! 用于ONA的路径构建器 + +use crate::{ + name_match::{is_name_match, name_match, name_match_only_contains}, + path_builder::CinPathBuilder, +}; +use babel_nar::{ona::ONA, runtime::CommandVmRuntime}; +use nar_dev_utils::{if_return, list, OptionBoost}; +use navm::vm::{VmLauncher, VmRuntime}; +use std::path::{Path, PathBuf}; + +/// ONA路径构建器 +/// * 🎯判别路径并构建ONA启动器 +pub struct PathBuilderONA; + +impl PathBuilderONA { + // 匹配文件名 + #[inline(always)] + fn match_name(name: &str) -> usize { + // 常用的`NAR.exe` + (if name == "NAR.exe" { 10 } else { 0 }) + // 综合,只需「均不满足⇒0」即可 + + name_match("ona", name) + + name_match_only_contains("opennars-for-application", name) + + name_match_only_contains("opennars_for_application", name) + } + + /// 检查文件匹配度 + fn valid_exe(path: &Path) -> usize { + // ! 不一定是本地存在的文件 + if_return! { !path.extension().is_some_and(|ex| ex == "exe") => 0} + // 名称匹配`ona` + path.file_name().map_unwrap_or( + |name_os| name_os.to_str().map_unwrap_or(Self::match_name, 0), + 0, + ) + } +} + +impl CinPathBuilder for PathBuilderONA { + type Runtime = CommandVmRuntime; + type Launcher = ONA; + + fn match_path(&self, path: &Path) -> usize { + // ! 与本地文件系统有关 + // 不是本地的文件⇒0 + if_return! { !path.is_file() => 0 } + // 否则⇒查看exe匹配度 + Self::valid_exe(path) + } + + fn construct_from_path(&self, path: &Path) -> Self::Launcher { + ONA::new(path) + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use nar_dev_utils::{f_parallel, fail_tests}; + use std::path::{self, Path}; + + /// 工具/测试单个路径 + fn test_matched(path: &str) { + let path = Path::new(path); + assert!(dbg!(PathBuilderONA::valid_exe(path)) > 0); + } + + /// 测试/名称匹配 + #[test] + fn test_match() { + f_parallel![ + test_matched; + "../NAR.exe"; + "../opennars-for-applications.exe"; + "../ona.exe"; + "ona_old.exe"; + ]; + } + + fail_tests! { + 无效扩展名 test_matched("../opennars.exe"); + 无效名称 test_matched("../NARust.exe"); + } +} diff --git a/src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs b/src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs new file mode 100644 index 0000000..1ed6a24 --- /dev/null +++ b/src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs @@ -0,0 +1,83 @@ +//! 用于OpenNARS的路径构建器 + +use crate::{ + name_match::{is_name_match, name_match}, + path_builder::CinPathBuilder, +}; +use babel_nar::{opennars::OpenNARS, runtime::CommandVmRuntime}; +use nar_dev_utils::{if_return, list, OptionBoost}; +use navm::vm::{VmLauncher, VmRuntime}; +use std::path::{Path, PathBuf}; + +/// OpenNARS路径构建器 +/// * 🎯判别路径并构建OpenNARS启动器 +pub struct PathBuilderOpenNARS; + +impl PathBuilderOpenNARS { + // 匹配文件名 + #[inline(always)] + fn match_name(name: &str) -> usize { + // 二者综合,只需「二者均不满足⇒0」即可 + name_match("opennars", name) + name_match("open_nars", name) + } + + /// 检查文件匹配度 + fn valid_jar(path: &Path) -> usize { + // ! 不一定是本地存在的文件 + if_return! { !path.extension().is_some_and(|ex| ex == "jar") => 0} + // 名称匹配`opennars` + path.file_name().map_unwrap_or( + |name_os| name_os.to_str().map_unwrap_or(Self::match_name, 0), + 0, + ) + } +} + +impl CinPathBuilder for PathBuilderOpenNARS { + type Runtime = CommandVmRuntime; + type Launcher = OpenNARS; + + fn match_path(&self, path: &Path) -> usize { + // ! 与本地文件系统有关 + // 不是本地的文件⇒0 + if_return! { !path.is_file() => 0 } + // 否则⇒查看jar匹配度 + Self::valid_jar(path) + } + + fn construct_from_path(&self, path: &Path) -> Self::Launcher { + OpenNARS::new(path) + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use nar_dev_utils::{f_parallel, fail_tests}; + use std::path::{self, Path}; + + /// 工具/测试单个路径 + fn test_matched(path: &str) { + let path = Path::new(path); + assert!(dbg!(PathBuilderOpenNARS::valid_jar(path)) > 0); + } + + /// 测试/名称匹配 + #[test] + fn test_match() { + f_parallel![ + test_matched; + "../opennars-304-T-modified.jar"; + "../OpenNARS-3.0.4-Snapshot.jar"; + "../opennars.jar"; + "open_nars.jar"; + "opennars-3.0.4-SNAPSHOT.jar"; + ]; + } + + fail_tests! { + 无效扩展名 test_matched("../opennars-304-T-modified.jar.exe"); + 无效名称 test_matched("../ona-T-modified.jar"); + } +} diff --git a/src/bin/cin_launcher/io/mod.rs b/src/bin/cin_launcher/io/mod.rs new file mode 100644 index 0000000..a5e3786 --- /dev/null +++ b/src/bin/cin_launcher/io/mod.rs @@ -0,0 +1,6 @@ +//! 用于管理启动器的输入输出 +//! * ✨交互式终端 函数支持 +//! * 📌输入输出「通道」绑定 +//! * ✨终端美化相关 +//! +//! TODO: 添加功能 diff --git a/src/bin/cin_launcher.rs b/src/bin/cin_launcher/main.rs similarity index 94% rename from src/bin/cin_launcher.rs rename to src/bin/cin_launcher/main.rs index f9760ce..14f5aa1 100644 --- a/src/bin/cin_launcher.rs +++ b/src/bin/cin_launcher/main.rs @@ -3,11 +3,14 @@ //! * 📌用于集成原先「BabelNAR」「BabelNAR_Implements」两个库 //! * ✨自动根据可执行文件、配置文件、用户输入猜测CIN类型(字符串匹配) //! * ✨自动查找(可能)可用的CIN可执行文件(文件搜索) +//! * 📌可根据「匹配度」排名 //! * ✨自动启动并管理CIN +//! * 📌可保存/加载「常用CIN」配置 //! TODO: 完成代码 #![allow(unused)] use babel_nar::{ona::ONA, opennars::OpenNARS, pynars::PyNARS, runtime::CommandVmRuntime}; +use nar_dev_utils::*; use navm::{ cmd::Cmd, output::Output, @@ -15,6 +18,15 @@ use navm::{ }; use std::{fmt::Debug, io::stdin}; +mods! { + // CIN搜索 + use pub cin_search; + // 输入输出 + use pub io; + // 路径构建器的各CIN实现 + use pub impls_path_builder; +} + const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; const TEST_PATH_PYNARS: (&str, &str) = ("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); From 09510be5174527085e3239326d252c42a977ca07 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:42:28 +0800 Subject: [PATCH 30/59] =?UTF-8?q?refactor:=20:recycle:=20=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E3=80=8C=E7=89=B9=E6=80=A7=E3=80=8D=E7=9A=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 全库定位重调整:+NAVM的应用程序实现 2. 将原先归属二进制子crate的「cin_launcher」归入主crate特性「命令行支持」下 --- Cargo.lock | 79 +++++++++++++++- Cargo.toml | 90 ++++++++++++------- README.md | 2 +- src/bin/cin_launcher/main.rs | 18 ++-- src/cin_implements/ona/translators.rs | 2 +- .../cin_search/anyhow_vm.rs | 0 .../cin_search}/impls_path_builder/mod.rs | 11 ++- .../impls_path_builder/path_builder_ona.rs | 13 ++- .../path_builder_opennars.rs | 13 ++- .../cin_search/mod.rs | 21 +++-- .../cin_search/name_match.rs | 0 .../cin_search/path_builder.rs | 0 .../cin_search/path_walker.rs | 4 +- .../io/mod.rs | 0 src/cmdline_support/mod.rs | 10 +++ src/lib.rs | 8 +- 16 files changed, 190 insertions(+), 81 deletions(-) rename src/{bin/cin_launcher => cmdline_support}/cin_search/anyhow_vm.rs (100%) rename src/{bin/cin_launcher => cmdline_support/cin_search}/impls_path_builder/mod.rs (88%) rename src/{bin/cin_launcher => cmdline_support/cin_search}/impls_path_builder/path_builder_ona.rs (87%) rename src/{bin/cin_launcher => cmdline_support/cin_search}/impls_path_builder/path_builder_opennars.rs (87%) rename src/{bin/cin_launcher => cmdline_support}/cin_search/mod.rs (72%) rename src/{bin/cin_launcher => cmdline_support}/cin_search/name_match.rs (100%) rename src/{bin/cin_launcher => cmdline_support}/cin_search/path_builder.rs (100%) rename src/{bin/cin_launcher => cmdline_support}/cin_search/path_walker.rs (99%) rename src/{bin/cin_launcher => cmdline_support}/io/mod.rs (100%) create mode 100644 src/cmdline_support/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4130358..83d301f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "babel_nar" version = "0.9.0" dependencies = [ "anyhow", + "colored", "lazy_static", "nar_dev_utils", "narsese", @@ -46,6 +47,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -105,7 +116,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "nar_dev_utils" -version = "0.22.0" +version = "0.23.1" [[package]] name = "narsese" @@ -287,3 +298,69 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index f5dcbbd..f90316b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,35 +3,16 @@ name = "babel_nar" version = "0.9.0" edition = "2021" +# Cargo文档参考: + +## 必要的依赖 ## + [dependencies] -pest_derive = "2.7.8" +# 用于错误处理 [dependencies.anyhow] version = "1.0.81" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -# Rust版本的正则表达式 -# * 🎯用于解析提取NARS输出 -# * 📄OpenNARS、ONA、PyNARS -[dependencies.regex] -version = "1.10.4" -optional = true - -# Rust版本的PEG解析器 -# * 🎯用于对接一些NARS方言的解析 -# * 📄OpenNARS(操作语法)、ONA(中缀语法) -[dependencies.pest] -version = "2.7.8" -optional = true - -# 用于实现「静态含闭包常量」 -# * 🎯初次引入:NARS-Python 方言格式 -# * 🔗:https://stackoverflow.com/questions/73260997/rust-boxed-closure-in-global-variable -[dependencies.lazy_static] -version = "1.4.0" -optional = true - [dependencies.nar_dev_utils] # 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` # version = "0.1.0" # ! 本地依赖可以不添加版本 @@ -39,7 +20,7 @@ optional = true path = "../NAR-dev-util" # git = "https://github.com/ARCJ137442/NAR-dev-util" # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 -features = [] +features = [ "bundled" ] # 启用所有特性 [dependencies.narsese] # ! 本地依赖可以不添加版本 @@ -62,17 +43,53 @@ path = "../NAVM.rs" # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 -# 定义库的特性 +## 依赖特性的可选依赖 ## + +# Rust版本的正则表达式 +# * 🎯用于解析提取NARS输出 +# * 📄OpenNARS、ONA、PyNARS +[dependencies.regex] +version = "1.10.4" +optional = true + +# 用于实现「静态含闭包常量」 +# * 🎯初次引入:NARS-Python 方言格式 +# * 🔗:https://stackoverflow.com/questions/73260997/rust-boxed-closure-in-global-variable +[dependencies.lazy_static] +version = "1.4.0" +optional = true + +# Rust版本的PEG解析器 +# * 🎯用于对接一些NARS方言的解析 +# * 📄OpenNARS(操作语法)、ONA(中缀语法) +[dependencies.pest] +version = "2.7.8" +optional = true + +# Rust版本的PEG解析器(派生宏) +[dependencies.pest_derive] +version = "2.7.8" +optional = true + +# 命令行支持/彩色终端 +[dependencies.colored] +version = "2.1.0" +optional = true + +### 定义库的特性 ### [features] -# 默认启用的特性 + +## 默认启用的特性 ## default = [] -# 大杂烩 +## 大杂烩 ## bundled = [ - "cin_implements", - "lazy_static", # 这个「词法Narsese」也在用 + "cin_implements", # 各大CIN的NAVM实现 + "cmdline_support" # 命令行支持 ] -# 各个独立的特性 # -# 具体接口实现: + +## 各个独立的特性 ## + +# 具体接口实现(虚拟机启动器) # # ✅OpenNARS # ✅ONA # ✅PyNARS @@ -101,6 +118,13 @@ pynars = [ # "pest", # ! 【2024-03-27 20:52:17】无需特别解析方言:其输出即为CommonNarsese ] # ✅NARS-Python接口(不稳定) -nars_python = [] +nars_python = [ + "lazy_static", # 这个「词法Narsese」也在用 +] # ✅OpenJunars接口(不稳定) openjunars = [] + +# 命令行支持 # +cmdline_support = [ + "colored" +] diff --git a/README.md b/README.md index 8e8850d..e4348f9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 -[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)的**运行时**及[CIN](#cin-computer-implement-of-nars)启动器 +[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)对[CIN](#cin-computer-implement-of-nars)的**启动器**、**运行时**及应用程序实现 - 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) - ✨为「非公理虚拟机模型」提供程序实现 diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 14f5aa1..368a390 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -9,7 +9,10 @@ //! TODO: 完成代码 #![allow(unused)] -use babel_nar::{ona::ONA, opennars::OpenNARS, pynars::PyNARS, runtime::CommandVmRuntime}; +use babel_nar::{ + cin_implements::{ona::ONA, opennars::OpenNARS, pynars::PyNARS}, + runtime::CommandVmRuntime, +}; use nar_dev_utils::*; use navm::{ cmd::Cmd, @@ -18,15 +21,6 @@ use navm::{ }; use std::{fmt::Debug, io::stdin}; -mods! { - // CIN搜索 - use pub cin_search; - // 输入输出 - use pub io; - // 路径构建器的各CIN实现 - use pub impls_path_builder; -} - const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; const TEST_PATH_PYNARS: (&str, &str) = ("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); @@ -94,8 +88,8 @@ fn shell(mut nars: CommandVmRuntime) { #[cfg(test)] mod tests { use super::*; - use babel_nar::cxin_js::CXinJS; - use babel_nar::pynars::PyNARS; + use babel_nar::cin_implements::cxin_js::CXinJS; + use babel_nar::cin_implements::pynars::PyNARS; use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; use navm::cmd::Cmd; use navm::vm::VmLauncher; diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index b873790..5765276 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -24,7 +24,7 @@ use super::dialect::parse as parse_dialect_ona; use crate::{ - ona::{fold_pest_compound, DialectParser, Rule}, + cin_implements::ona::{fold_pest_compound, DialectParser, Rule}, runtime::TranslateError, }; use anyhow::Result; diff --git a/src/bin/cin_launcher/cin_search/anyhow_vm.rs b/src/cmdline_support/cin_search/anyhow_vm.rs similarity index 100% rename from src/bin/cin_launcher/cin_search/anyhow_vm.rs rename to src/cmdline_support/cin_search/anyhow_vm.rs diff --git a/src/bin/cin_launcher/impls_path_builder/mod.rs b/src/cmdline_support/cin_search/impls_path_builder/mod.rs similarity index 88% rename from src/bin/cin_launcher/impls_path_builder/mod.rs rename to src/cmdline_support/cin_search/impls_path_builder/mod.rs index d6837fa..08bc854 100644 --- a/src/bin/cin_launcher/impls_path_builder/mod.rs +++ b/src/cmdline_support/cin_search/impls_path_builder/mod.rs @@ -5,12 +5,13 @@ //! TODO: CXinNARS //! * 🚩【2024-03-31 01:27:09】其它接口完成度不高的CIN,暂时弃了 -use crate::{name_match::is_name_match, path_builder::CinPathBuilder, path_walker::PathWalker}; -use nar_dev_utils::{list, mods}; +use crate::cmdline_support::cin_search::{ + name_match::is_name_match, path_builder::CinPathBuilder, path_walker::PathWalker, +}; use navm::vm::{VmLauncher, VmRuntime}; use std::path::Path; -mods! { +util::mods! { // OpenNARS use pub path_builder_opennars; // ONA @@ -55,9 +56,7 @@ pub fn launchers_from_walker_sorted>( #[cfg(test)] mod tests { use super::*; - use crate::cin_search::path_builder::CinPathBuilder; - use crate::path_walker::PathWalkerV1; - use nar_dev_utils::list; + use crate::cmdline_support::cin_search::path_walker::PathWalkerV1; use std::env::current_dir; #[test] diff --git a/src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs b/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs similarity index 87% rename from src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs rename to src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs index 8dd3c1a..de205aa 100644 --- a/src/bin/cin_launcher/impls_path_builder/path_builder_ona.rs +++ b/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs @@ -1,13 +1,12 @@ //! 用于ONA的路径构建器 -use crate::{ - name_match::{is_name_match, name_match, name_match_only_contains}, +use crate::cmdline_support::cin_search::{ + name_match::{name_match, name_match_only_contains}, path_builder::CinPathBuilder, }; -use babel_nar::{ona::ONA, runtime::CommandVmRuntime}; -use nar_dev_utils::{if_return, list, OptionBoost}; -use navm::vm::{VmLauncher, VmRuntime}; -use std::path::{Path, PathBuf}; +use crate::{cin_implements::ona::ONA, runtime::CommandVmRuntime}; +use nar_dev_utils::{if_return, OptionBoost}; +use std::path::Path; /// ONA路径构建器 /// * 🎯判别路径并构建ONA启动器 @@ -59,7 +58,7 @@ impl CinPathBuilder for PathBuilderONA { mod tests { use super::*; use nar_dev_utils::{f_parallel, fail_tests}; - use std::path::{self, Path}; + use std::path::Path; /// 工具/测试单个路径 fn test_matched(path: &str) { diff --git a/src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs b/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs similarity index 87% rename from src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs rename to src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs index 1ed6a24..5c950f4 100644 --- a/src/bin/cin_launcher/impls_path_builder/path_builder_opennars.rs +++ b/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs @@ -1,13 +1,12 @@ //! 用于OpenNARS的路径构建器 use crate::{ - name_match::{is_name_match, name_match}, - path_builder::CinPathBuilder, + cin_implements::opennars::OpenNARS, + cmdline_support::cin_search::{name_match::name_match, path_builder::CinPathBuilder}, + runtime::CommandVmRuntime, }; -use babel_nar::{opennars::OpenNARS, runtime::CommandVmRuntime}; -use nar_dev_utils::{if_return, list, OptionBoost}; -use navm::vm::{VmLauncher, VmRuntime}; -use std::path::{Path, PathBuf}; +use nar_dev_utils::{if_return, OptionBoost}; +use std::path::Path; /// OpenNARS路径构建器 /// * 🎯判别路径并构建OpenNARS启动器 @@ -55,7 +54,7 @@ impl CinPathBuilder for PathBuilderOpenNARS { mod tests { use super::*; use nar_dev_utils::{f_parallel, fail_tests}; - use std::path::{self, Path}; + use std::path::Path; /// 工具/测试单个路径 fn test_matched(path: &str) { diff --git a/src/bin/cin_launcher/cin_search/mod.rs b/src/cmdline_support/cin_search/mod.rs similarity index 72% rename from src/bin/cin_launcher/cin_search/mod.rs rename to src/cmdline_support/cin_search/mod.rs index c27aee7..feb8598 100644 --- a/src/bin/cin_launcher/cin_search/mod.rs +++ b/src/cmdline_support/cin_search/mod.rs @@ -4,16 +4,19 @@ //! * 🚩输出:NAVM启动器列表 //! * ❓【2024-03-30 19:12:29】是否要考虑返回更细化的「CIN实例位置」而非「CIN启动器」,以避免额外的性能开销? -use nar_dev_utils::mods; - -/// 导出模块 -mods! { - // 路径遍历器 - use pub path_walker; - // 路径构造器 - use pub path_builder; +// 导出模块 +util::mods! { // anyhow | 弃用 // anyhow_vm; // 名称匹配 - use pub name_match; + pub name_match; + + // 路径遍历器 + pub path_walker; + + // 路径构建器 + pub path_builder; + + // 路径构建器的各CIN实现 + "cin_implements" => pub impls_path_builder; } diff --git a/src/bin/cin_launcher/cin_search/name_match.rs b/src/cmdline_support/cin_search/name_match.rs similarity index 100% rename from src/bin/cin_launcher/cin_search/name_match.rs rename to src/cmdline_support/cin_search/name_match.rs diff --git a/src/bin/cin_launcher/cin_search/path_builder.rs b/src/cmdline_support/cin_search/path_builder.rs similarity index 100% rename from src/bin/cin_launcher/cin_search/path_builder.rs rename to src/cmdline_support/cin_search/path_builder.rs diff --git a/src/bin/cin_launcher/cin_search/path_walker.rs b/src/cmdline_support/cin_search/path_walker.rs similarity index 99% rename from src/bin/cin_launcher/cin_search/path_walker.rs rename to src/cmdline_support/cin_search/path_walker.rs index 7c6ec50..f01115c 100644 --- a/src/bin/cin_launcher/cin_search/path_walker.rs +++ b/src/cmdline_support/cin_search/path_walker.rs @@ -211,7 +211,7 @@ impl Iterator for PathWalkerV1<'_> { #[cfg(test)] mod tests { use super::*; - use crate::name_match::is_name_match; + use crate::cmdline_support::cin_search::name_match::is_name_match; use std::env::current_dir; fn _test_path_walker_v1(start: impl Into) { @@ -221,7 +221,7 @@ mod tests { fn deep_criterion(path: &Path) -> bool { path.file_name() .is_some_and(|name| name.to_str().is_some_and(|s| is_name_match("nars", s))) - }; + } // 构建遍历者,加上条件 let walker = PathWalkerV1::new(start, deep_criterion).unwrap(); // 打印遍历者的「祖先列表」 diff --git a/src/bin/cin_launcher/io/mod.rs b/src/cmdline_support/io/mod.rs similarity index 100% rename from src/bin/cin_launcher/io/mod.rs rename to src/cmdline_support/io/mod.rs diff --git a/src/cmdline_support/mod.rs b/src/cmdline_support/mod.rs new file mode 100644 index 0000000..810a0c9 --- /dev/null +++ b/src/cmdline_support/mod.rs @@ -0,0 +1,10 @@ +//! 命令行支持 +//! * 🎯通用、可选地复用「CIN启动器」等「命令行工具」的内容 +//! * 🎯亦可为后续基于UI的应用提供支持 + +util::mods! { + // CIN搜索 + pub cin_search; + // 输入输出 + pub io; +} diff --git a/src/lib.rs b/src/lib.rs index 58ad3ab..50017a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,17 @@ pub extern crate nar_dev_utils as util; // 必选模块 // // 进程IO pub mod process_io; + // 运行时 pub mod runtime; + // (可选的实用)工具 pub mod tools; // 可选模块 // -util::feature_pub_mod_and_reexport! { +util::mods! { // 运行时实现 - "cin_implements" => cin_implements + "cin_implements" => pub cin_implements; + // 命令行支持 + "cmdline_support" => pub cmdline_support; } From 035f21ae046d89619c401d8b569fb663571a53b3 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:56:19 +0800 Subject: [PATCH 31/59] =?UTF-8?q?refactor:=20:recycle:=20=E7=BB=A7?= =?UTF-8?q?=E7=BB=AD=E8=B0=83=E6=95=B4=E5=90=84=E7=9B=AE=E5=BD=95=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=98=8E=E7=A1=AE=E6=A8=A1=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重命名「runtime→runtimes」以明确「一切有关运行时的实现」;提取「tools→output_handler」以明确模块功能定位 --- src/bin/cin_launcher/main.rs | 2 +- src/cin_implements/common/exe.rs | 2 +- src/cin_implements/common/java.rs | 2 +- src/cin_implements/common/julia.rs | 2 +- src/cin_implements/common/node_js.rs | 2 +- src/cin_implements/common/python.rs | 2 +- src/cin_implements/cxin_js/launcher.rs | 2 +- src/cin_implements/cxin_js/mod.rs | 2 +- src/cin_implements/cxin_js/translators.rs | 2 +- src/cin_implements/nars_python/launcher.rs | 2 +- src/cin_implements/nars_python/mod.rs | 2 +- src/cin_implements/nars_python/translators.rs | 2 +- src/cin_implements/ona/dialect.rs | 2 +- src/cin_implements/ona/launcher.rs | 2 +- src/cin_implements/ona/mod.rs | 2 +- src/cin_implements/ona/translators.rs | 2 +- src/cin_implements/openjunars/launcher.rs | 2 +- src/cin_implements/openjunars/mod.rs | 2 +- src/cin_implements/openjunars/translators.rs | 2 +- src/cin_implements/opennars/dialect.rs | 2 +- src/cin_implements/opennars/launcher.rs | 2 +- src/cin_implements/opennars/mod.rs | 2 +- src/cin_implements/opennars/translators.rs | 2 +- src/cin_implements/pynars/launcher.rs | 2 +- src/cin_implements/pynars/mod.rs | 2 +- src/cin_implements/pynars/translators.rs | 2 +- .../impls_path_builder/path_builder_ona.rs | 2 +- .../path_builder_opennars.rs | 2 +- src/lib.rs | 23 +++++++++++-------- .../flow_handler_list.rs | 0 src/output_handler/mod.rs | 5 ++++ .../command_vm/api/command_generator.rs | 0 .../command_vm/api/mod.rs | 0 .../command_vm/api/translators.rs | 0 .../command_vm/launcher.rs | 0 src/{runtime => runtimes}/command_vm/mod.rs | 0 .../command_vm/runtime.rs | 2 +- src/{runtime => runtimes}/mod.rs | 2 +- src/tools/mod.rs | 6 ----- 39 files changed, 48 insertions(+), 46 deletions(-) rename src/{tools => output_handler}/flow_handler_list.rs (100%) create mode 100644 src/output_handler/mod.rs rename src/{runtime => runtimes}/command_vm/api/command_generator.rs (100%) rename src/{runtime => runtimes}/command_vm/api/mod.rs (100%) rename src/{runtime => runtimes}/command_vm/api/translators.rs (100%) rename src/{runtime => runtimes}/command_vm/launcher.rs (100%) rename src/{runtime => runtimes}/command_vm/mod.rs (100%) rename src/{runtime => runtimes}/command_vm/runtime.rs (99%) rename src/{runtime => runtimes}/mod.rs (73%) delete mode 100644 src/tools/mod.rs diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 368a390..0a2dd3b 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -11,7 +11,7 @@ use babel_nar::{ cin_implements::{ona::ONA, opennars::OpenNARS, pynars::PyNARS}, - runtime::CommandVmRuntime, + runtimes::CommandVmRuntime, }; use nar_dev_utils::*; use navm::{ diff --git a/src/cin_implements/common/exe.rs b/src/cin_implements/common/exe.rs index 8d0a460..c941880 100644 --- a/src/cin_implements/common/exe.rs +++ b/src/cin_implements/common/exe.rs @@ -5,7 +5,7 @@ //! * 🚩【2024-03-28 10:00:00】暂且只需提供[`Command`]生成函数 //! * ❗没必要使用新的数据结构 -use crate::runtime::{CommandVm, IoTranslators}; +use crate::runtimes::{CommandVm, IoTranslators}; use std::{ffi::OsStr, path::Path, process::Command}; /// 根据配置统一生成[`Command`]对象 diff --git a/src/cin_implements/common/java.rs b/src/cin_implements/common/java.rs index f6c016e..b8a1ff4 100644 --- a/src/cin_implements/common/java.rs +++ b/src/cin_implements/common/java.rs @@ -4,7 +4,7 @@ //! * 🚩从jar文件启动NARS //! * 🚩【2024-03-27 15:31:02】取消「初始音量」的特化配置,将其变成一个「命令行参数生成器」而非独立的「启动器」 -use crate::runtime::CommandGenerator; +use crate::runtimes::CommandGenerator; use std::{path::PathBuf, process::Command}; /// 启动Java运行时的命令 diff --git a/src/cin_implements/common/julia.rs b/src/cin_implements/common/julia.rs index 971587a..c2e6d7a 100644 --- a/src/cin_implements/common/julia.rs +++ b/src/cin_implements/common/julia.rs @@ -3,7 +3,7 @@ //! * 🎯封装「NAVM运行时启动过程」中有关「Julia启动环境配置」的部分 //! * 🚩从Julia脚本(`.jl`)启动NARS -use crate::runtime::CommandGenerator; +use crate::runtimes::CommandGenerator; use std::{path::PathBuf, process::Command}; /// 启动Julia运行时的命令 diff --git a/src/cin_implements/common/node_js.rs b/src/cin_implements/common/node_js.rs index 3607747..3f1a7df 100644 --- a/src/cin_implements/common/node_js.rs +++ b/src/cin_implements/common/node_js.rs @@ -3,7 +3,7 @@ //! * 🎯封装「NAVM运行时启动过程」中有关「Node.js启动环境配置」的部分 //! * 🚩从Node.js脚本(`.js`)启动NARS -use crate::runtime::CommandGenerator; +use crate::runtimes::CommandGenerator; use std::{path::PathBuf, process::Command}; /// 启动Node.js运行时的命令 diff --git a/src/cin_implements/common/python.rs b/src/cin_implements/common/python.rs index 2b58b94..647ea4d 100644 --- a/src/cin_implements/common/python.rs +++ b/src/cin_implements/common/python.rs @@ -3,7 +3,7 @@ //! * 🎯封装「NAVM运行时启动过程」中有关「Python启动环境配置」的部分 //! * 🚩从Python模块(`.py`脚本)启动NARS -use crate::runtime::CommandGenerator; +use crate::runtimes::CommandGenerator; use std::{path::PathBuf, process::Command}; /// 启动Python运行时的命令 diff --git a/src/cin_implements/cxin_js/launcher.rs b/src/cin_implements/cxin_js/launcher.rs index 4095d92..8aa895b 100644 --- a/src/cin_implements/cxin_js/launcher.rs +++ b/src/cin_implements/cxin_js/launcher.rs @@ -6,7 +6,7 @@ use super::{input_translate, output_translate}; use crate::{ cin_implements::common::{generate_command_vm, CommandGeneratorNodeJS}, - runtime::{CommandGenerator, CommandVmRuntime}, + runtimes::{CommandGenerator, CommandVmRuntime}, }; use navm::vm::VmLauncher; use std::path::PathBuf; diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs index a3cca31..b65cda8 100644 --- a/src/cin_implements/cxin_js/mod.rs +++ b/src/cin_implements/cxin_js/mod.rs @@ -15,7 +15,7 @@ util::mod_and_pub_use! { mod tests { #![allow(unused)] use super::*; - use crate::runtime::{ + use crate::runtimes::{ tests::{await_fetch_until, input_cmd_and_await_contains, test_simple_answer}, CommandVmRuntime, }; diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs index e40b93d..4a394be 100644 --- a/src/cin_implements/cxin_js/translators.rs +++ b/src/cin_implements/cxin_js/translators.rs @@ -15,7 +15,7 @@ //! * `^left executed with args ({SELF} * x)` //! * `decision expectation=0.616961 implication: <((<{SELF} --> [left_blocked]> &/ ^say) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [SAFE]>>. Truth: frequency=0.978072 confidence=0.394669 dt=1.000000 precondition: <{SELF} --> [left_blocked]>. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=50` -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::Result; use narsese::{ conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, structs::ParseResult}, diff --git a/src/cin_implements/nars_python/launcher.rs b/src/cin_implements/nars_python/launcher.rs index 0487ef4..41d9f6a 100644 --- a/src/cin_implements/nars_python/launcher.rs +++ b/src/cin_implements/nars_python/launcher.rs @@ -4,7 +4,7 @@ //! * ✨不同启动器可以启动到相同运行时 use super::{input_translate, output_translate}; -use crate::runtime::{CommandVm, CommandVmRuntime}; +use crate::runtimes::{CommandVm, CommandVmRuntime}; use navm::vm::VmLauncher; use std::path::PathBuf; diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index b5bf0e5..7780aaa 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -16,7 +16,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{tests::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; + use crate::runtimes::{tests::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs index 79b376c..e8ff8fe 100644 --- a/src/cin_implements/nars_python/translators.rs +++ b/src/cin_implements/nars_python/translators.rs @@ -12,7 +12,7 @@ //! * `PREMISE IS SIMPLIFIED ({SELF} --> [SAFE]) FROM (&|,({SELF} --> [SAFE]),((*,{SELF}) --> ^right))` use super::format_in_nars_python; -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::Result; use narsese::lexical::Narsese; use navm::{ diff --git a/src/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs index 8f66147..74c560d 100644 --- a/src/cin_implements/ona/dialect.rs +++ b/src/cin_implements/ona/dialect.rs @@ -3,7 +3,7 @@ //! * 📄以空格分隔的词项:`(* {SELF})` //! * 📄`({SELF} * x)` -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::{Ok, Result}; use narsese::{ conversion::string::{ diff --git a/src/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs index 45b7f94..7d3f543 100644 --- a/src/cin_implements/ona/launcher.rs +++ b/src/cin_implements/ona/launcher.rs @@ -6,7 +6,7 @@ use super::{input_translate, output_translate}; use crate::{ cin_implements::common::{generate_command, generate_command_vm}, - runtime::CommandVmRuntime, + runtimes::CommandVmRuntime, }; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs index edf5f47..464bd9f 100644 --- a/src/cin_implements/ona/mod.rs +++ b/src/cin_implements/ona/mod.rs @@ -16,7 +16,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{ + use crate::runtimes::{ tests::{_test_ona, test_simple_answer, EXE_PATH_ONA}, CommandVmRuntime, }; diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 5765276..9b913a4 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -25,7 +25,7 @@ use super::dialect::parse as parse_dialect_ona; use crate::{ cin_implements::ona::{fold_pest_compound, DialectParser, Rule}, - runtime::TranslateError, + runtimes::TranslateError, }; use anyhow::Result; use narsese::lexical::{Narsese, Term}; diff --git a/src/cin_implements/openjunars/launcher.rs b/src/cin_implements/openjunars/launcher.rs index 3ae6fcd..4757c8a 100644 --- a/src/cin_implements/openjunars/launcher.rs +++ b/src/cin_implements/openjunars/launcher.rs @@ -7,7 +7,7 @@ use super::{input_translate, output_translate}; use crate::{ cin_implements::common::CommandGeneratorJulia, - runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, + runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use navm::vm::VmLauncher; use std::path::PathBuf; diff --git a/src/cin_implements/openjunars/mod.rs b/src/cin_implements/openjunars/mod.rs index 364c522..7dc6d19 100644 --- a/src/cin_implements/openjunars/mod.rs +++ b/src/cin_implements/openjunars/mod.rs @@ -16,7 +16,7 @@ mod tests { #![allow(unused)] use super::*; - use crate::runtime::{tests::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; + use crate::runtimes::{tests::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/openjunars/translators.rs b/src/cin_implements/openjunars/translators.rs index 37d1ca5..52028d6 100644 --- a/src/cin_implements/openjunars/translators.rs +++ b/src/cin_implements/openjunars/translators.rs @@ -11,7 +11,7 @@ use navm::{ output::{Operation, Output}, }; -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; /// OpenJunars的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「OpenJunars Shell输入」 diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs index b9351aa..f1d6bab 100644 --- a/src/cin_implements/opennars/dialect.rs +++ b/src/cin_implements/opennars/dialect.rs @@ -2,7 +2,7 @@ //! * 🎯解析OpenNARS输出,如 //! * 📄特有的「操作」语法:`(^left, {SELF})` => `<(*, {SELF}) --> ^left>` -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::{Ok, Result}; use narsese::{ conversion::string::{ diff --git a/src/cin_implements/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs index d0681c7..8d3e46d 100644 --- a/src/cin_implements/opennars/launcher.rs +++ b/src/cin_implements/opennars/launcher.rs @@ -7,7 +7,7 @@ use super::{input_translate, output_translate}; use crate::{ cin_implements::common::CommandGeneratorJava, - runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, + runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use navm::{ cmd::Cmd, diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs index 32112f6..b94f7cb 100644 --- a/src/cin_implements/opennars/mod.rs +++ b/src/cin_implements/opennars/mod.rs @@ -16,7 +16,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{ + use crate::runtimes::{ tests::{_test_opennars, test_simple_answer, JAR_PATH_OPENNARS}, CommandVmRuntime, }; diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 4f19970..cd2b516 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -18,7 +18,7 @@ //! * `EXE: $0.11;0.33;0.57$ ^left([{SELF}, a, b, (/,^left,a,b,_)])=null` use super::dialect::parse as parse_dialect_opennars; -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::Result; use narsese::lexical::{Narsese, Term}; use navm::{ diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs index 7e5628a..6004390 100644 --- a/src/cin_implements/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -8,7 +8,7 @@ use super::{input_translate, output_translate}; use crate::{ cin_implements::common::CommandGeneratorPython, - runtime::{CommandGenerator, CommandVm, CommandVmRuntime}, + runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use navm::vm::VmLauncher; use std::path::PathBuf; diff --git a/src/cin_implements/pynars/mod.rs b/src/cin_implements/pynars/mod.rs index 45f8f38..db56f39 100644 --- a/src/cin_implements/pynars/mod.rs +++ b/src/cin_implements/pynars/mod.rs @@ -18,7 +18,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{ + use crate::runtimes::{ tests::{_test_pynars, test_simple_answer, MODULE_PATH_PYNARS, MODULE_ROOT_PYNARS}, CommandVmRuntime, }; diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index 525ef39..85ae4b0 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -12,7 +12,7 @@ //! * 📄`\u{1b}[48;2;134;10;10m 0.98 \u{1b}[49m\u{1b}[48;2;10;124;10m 0.90 \u{1b}[49m\u{1b}[48;2;10;10;125m 0.90 \u{1b}[49m\u{1b}[32mANSWER:\u{1b}[39mC>. %1.000;0.810%\r\n` //! * 📄` \u{1b}[49m \u{1b}[49m \u{1b}[49m\u{1b}[32mEXE :\u{1b}[39m<(*, 0)-->^op> = $0.022;0.232;0.926$ <(*, 0)-->^op>! :\\: %1.000;0.853% {7: 2, 0, 1}\r\n` -use crate::runtime::TranslateError; +use crate::runtimes::TranslateError; use anyhow::{anyhow, Result}; use narsese::{ api::ExtractTerms, diff --git a/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs b/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs index de205aa..093ad04 100644 --- a/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs +++ b/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs @@ -4,7 +4,7 @@ use crate::cmdline_support::cin_search::{ name_match::{name_match, name_match_only_contains}, path_builder::CinPathBuilder, }; -use crate::{cin_implements::ona::ONA, runtime::CommandVmRuntime}; +use crate::{cin_implements::ona::ONA, runtimes::CommandVmRuntime}; use nar_dev_utils::{if_return, OptionBoost}; use std::path::Path; diff --git a/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs b/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs index 5c950f4..ea6d678 100644 --- a/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs +++ b/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs @@ -3,7 +3,7 @@ use crate::{ cin_implements::opennars::OpenNARS, cmdline_support::cin_search::{name_match::name_match, path_builder::CinPathBuilder}, - runtime::CommandVmRuntime, + runtimes::CommandVmRuntime, }; use nar_dev_utils::{if_return, OptionBoost}; use std::path::Path; diff --git a/src/lib.rs b/src/lib.rs index 50017a7..f0e7e18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,20 +6,23 @@ // 实用库别名 pub extern crate nar_dev_utils as util; -// 必选模块 // -// 进程IO -pub mod process_io; +util::mods! { + // 必选模块 // -// 运行时 -pub mod runtime; + // 进程IO + pub process_io; -// (可选的实用)工具 -pub mod tools; + // NAVM运行时 + pub runtimes; -// 可选模块 // -util::mods! { - // 运行时实现 + // 输出处理者 + pub output_handler; + + // 可选模块 // + + // 各CIN的启动器、运行时实现 "cin_implements" => pub cin_implements; + // 命令行支持 "cmdline_support" => pub cmdline_support; } diff --git a/src/tools/flow_handler_list.rs b/src/output_handler/flow_handler_list.rs similarity index 100% rename from src/tools/flow_handler_list.rs rename to src/output_handler/flow_handler_list.rs diff --git a/src/output_handler/mod.rs b/src/output_handler/mod.rs new file mode 100644 index 0000000..902a8fc --- /dev/null +++ b/src/output_handler/mod.rs @@ -0,0 +1,5 @@ +//! 存储有关「输出捕获」的工具 +//! * 🎯辅助封装NAVM的「指令-输出 通道」结构 + +// 流式处理者列表 +pub mod flow_handler_list; diff --git a/src/runtime/command_vm/api/command_generator.rs b/src/runtimes/command_vm/api/command_generator.rs similarity index 100% rename from src/runtime/command_vm/api/command_generator.rs rename to src/runtimes/command_vm/api/command_generator.rs diff --git a/src/runtime/command_vm/api/mod.rs b/src/runtimes/command_vm/api/mod.rs similarity index 100% rename from src/runtime/command_vm/api/mod.rs rename to src/runtimes/command_vm/api/mod.rs diff --git a/src/runtime/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs similarity index 100% rename from src/runtime/command_vm/api/translators.rs rename to src/runtimes/command_vm/api/translators.rs diff --git a/src/runtime/command_vm/launcher.rs b/src/runtimes/command_vm/launcher.rs similarity index 100% rename from src/runtime/command_vm/launcher.rs rename to src/runtimes/command_vm/launcher.rs diff --git a/src/runtime/command_vm/mod.rs b/src/runtimes/command_vm/mod.rs similarity index 100% rename from src/runtime/command_vm/mod.rs rename to src/runtimes/command_vm/mod.rs diff --git a/src/runtime/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs similarity index 99% rename from src/runtime/command_vm/runtime.rs rename to src/runtimes/command_vm/runtime.rs index 3833808..12f960f 100644 --- a/src/runtime/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -88,7 +88,7 @@ impl VmLauncher for CommandVm { #[cfg(test)] pub mod tests { use super::*; - use crate::runtime::TranslateError; + use crate::runtimes::TranslateError; use narsese::{ api::{GetBudget, GetPunctuation, GetStamp, GetTerm, GetTruth}, conversion::{ diff --git a/src/runtime/mod.rs b/src/runtimes/mod.rs similarity index 73% rename from src/runtime/mod.rs rename to src/runtimes/mod.rs index b7db7af..cc5e081 100644 --- a/src/runtime/mod.rs +++ b/src/runtimes/mod.rs @@ -1,4 +1,4 @@ -//! 用于封装表示「非公理虚拟机」运行时 +//! 用于封装表示「非公理虚拟机」的通用运行时支持 //! * 📌不与特定的CIN相关 //! * 📄一个「命令行运行时」可同时适用于OpenNARS、ONA、NARS-Python…… diff --git a/src/tools/mod.rs b/src/tools/mod.rs deleted file mode 100644 index 0f78be4..0000000 --- a/src/tools/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! 存储一些可选的实用工具 -//! * 🎯用于与NAVM运行时结合,形成生产级程序 -//! * 📌或,辅助封装NAVM的「指令-输出 通道」结构 - -// 流式处理者列表 -pub mod flow_handler_list; From 10839cabb99af5a67835e3fbcbcd93f2b911f137 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Mon, 1 Apr 2024 01:45:04 +0800 Subject: [PATCH 32/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E3=80=8C=E6=B5=8B=E8=AF=95=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E3=80=8D=E6=A8=A1=E5=9D=97=E7=9A=84=E6=95=B4=E4=BD=93=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=EF=BC=9A=E5=85=BC=E5=AE=B9=E6=97=A7`.nal`=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=9E=84=E5=BB=BA=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E3=80=81=E9=80=9A=E7=94=A8=E7=9A=84=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=EF=BC=88NAL=EF=BC=89=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + Cargo.lock | 6 +- Cargo.toml | 15 +- src/cmdline_support/io/mod.rs | 2 +- src/lib.rs | 3 + src/runtimes/command_vm/api/mod.rs | 2 +- src/runtimes/command_vm/api/translators.rs | 106 +++++---- src/runtimes/command_vm/runtime.rs | 4 +- src/test_tools/mod.rs | 15 ++ src/test_tools/nal_format/mod.rs | 265 +++++++++++++++++++++ src/test_tools/nal_format/nal_grammar.pest | 205 ++++++++++++++++ src/test_tools/structs.rs | 58 +++++ src/test_tools/vm_interact.rs | 70 ++++++ src/tests/nal/test_operation.nal | 31 +++ src/tests/nal/test_simple_deduction.nal | 21 ++ 15 files changed, 743 insertions(+), 61 deletions(-) create mode 100644 src/test_tools/mod.rs create mode 100644 src/test_tools/nal_format/mod.rs create mode 100644 src/test_tools/nal_format/nal_grammar.pest create mode 100644 src/test_tools/structs.rs create mode 100644 src/test_tools/vm_interact.rs create mode 100644 src/tests/nal/test_operation.nal create mode 100644 src/tests/nal/test_simple_deduction.nal diff --git a/.vscode/settings.json b/.vscode/settings.json index 923fc47..b7a27fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "Errno", "hasher", "Nalifier", + "nanos", "openjunars", "rfind", "runpy", diff --git a/Cargo.lock b/Cargo.lock index 83d301f..2e21e06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "babel_nar" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "colored", @@ -116,7 +116,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "nar_dev_utils" -version = "0.23.1" +version = "0.24.0" [[package]] name = "narsese" @@ -128,7 +128,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.5.0" +version = "0.6.1" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index f90316b..155e3a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.9.0" +version = "0.10.0" edition = "2021" # Cargo文档参考: @@ -84,7 +84,8 @@ default = [] ## 大杂烩 ## bundled = [ "cin_implements", # 各大CIN的NAVM实现 - "cmdline_support" # 命令行支持 + "cmdline_support", # 命令行支持 + "test_tools", # 测试工具集 ] ## 各个独立的特性 ## @@ -105,12 +106,12 @@ cin_implements = [ # ✅OpenNARS接口 opennars = [ "regex", - "pest", + "pest", "pest_derive", ] # ✅ONA接口 ona = [ "regex", - "pest", + "pest", "pest_derive", ] # ✅PyNARS接口 pynars = [ @@ -128,3 +129,9 @@ openjunars = [] cmdline_support = [ "colored" ] + +# 测试工具集 # +test_tools = [ + # 统一`.nal`格式 + "pest", "pest_derive", +] diff --git a/src/cmdline_support/io/mod.rs b/src/cmdline_support/io/mod.rs index a5e3786..d7347ca 100644 --- a/src/cmdline_support/io/mod.rs +++ b/src/cmdline_support/io/mod.rs @@ -2,5 +2,5 @@ //! * ✨交互式终端 函数支持 //! * 📌输入输出「通道」绑定 //! * ✨终端美化相关 -//! +//! //! TODO: 添加功能 diff --git a/src/lib.rs b/src/lib.rs index f0e7e18..f1c9984 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,4 +25,7 @@ util::mods! { // 命令行支持 "cmdline_support" => pub cmdline_support; + + // 测试工具集 + "test_tools" => pub test_tools; } diff --git a/src/runtimes/command_vm/api/mod.rs b/src/runtimes/command_vm/api/mod.rs index c11066f..48f0cf1 100644 --- a/src/runtimes/command_vm/api/mod.rs +++ b/src/runtimes/command_vm/api/mod.rs @@ -1,6 +1,6 @@ //! 定义有关「命令行虚拟机」的抽象API //! * 【2024-03-26 22:33:19】总体想法:💡一个「转译器集成包」+「命令行参数生成器」⇒统一复用的「IO进程启动器」 -//! * 📌转译器集成包:用于 +//! * 📌转译器集成包:用于将「输入转译器」与「输出转译器」打包成一个统一类型的值以传入 util::pub_mod_and_pub_use! { // 转译器 diff --git a/src/runtimes/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs index 499f578..300a3d4 100644 --- a/src/runtimes/command_vm/api/translators.rs +++ b/src/runtimes/command_vm/api/translators.rs @@ -76,64 +76,70 @@ where } } -/// 统一封装「转译错误」 -/// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 -/// * 🎯用于可识别的错误,并在打印时直接展示原因 -/// * ⚠️若直接使用[`anyhow::anyhow`],会打印一大堆错误堆栈 -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct TranslateError(pub String); +/// 错误类型 +mod translate_error { + use super::*; -// ! ❌【2024-03-27 22:40:22】无法正常使用:不能导出带`format!`的宏 -// * error: macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths -// #[macro_export] -// macro_rules! translate_error { -// ($($t:tt)*) => { -// TranslateError(format!($($t)*)) -// }; -// } + /// 统一封装「转译错误」 + /// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 + /// * 🎯用于可识别的错误,并在打印时直接展示原因 + /// * ⚠️若直接使用[`anyhow::anyhow`],会打印一大堆错误堆栈 + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] + pub struct TranslateError(pub String); -/// 灵活地从字符串转换为[`TranslateError`] -impl> From for TranslateError { - fn from(value: S) -> Self { - Self(value.as_ref().to_string()) - } -} + // ! ❌【2024-03-27 22:40:22】无法正常使用:不能导出带`format!`的宏 + // * error: macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths + // #[macro_export] + // macro_rules! translate_error { + // ($($t:tt)*) => { + // TranslateError(format!($($t)*)) + // }; + // } -/// 灵活地从[`Error`]转换为[`TranslateError`] -impl TranslateError { - /// 从[`Error`]转换为[`TranslateError`] - pub fn from_error(value: impl Error) -> Self { - Self(value.to_string()) - } - /// 从[`Error`]转换为[`anyhow::Error`] - pub fn error_anyhow(value: impl Error) -> anyhow::Error { - Self::from_error(value).into() + /// 灵活地从字符串转换为[`TranslateError`] + impl> From for TranslateError { + fn from(value: S) -> Self { + Self(value.as_ref().to_string()) + } } - /// 从「一切可以转换为其自身的值」构建[`anyhow::Result`] - pub fn err_anyhow(from: S) -> anyhow::Result - where - Self: From, - { - Err(Self::from(from).into()) - } - /// 从[`Self::from`]转换到[`anyhow::Error`] - /// * 🚩封装为自身类型 - /// * ❗实际上`.into()`比`::anyhow`短 - /// * 📌尽可能用前者 - pub fn anyhow(value: impl Into) -> anyhow::Error { - // ! ❌【2024-03-27 22:59:51】不能使用`Self::from(value).into`:`AsRef`不一定实现`Into` - anyhow::Error::from(value.into()) + /// 灵活地从[`Error`]转换为[`TranslateError`] + impl TranslateError { + /// 从[`Error`]转换为[`TranslateError`] + pub fn from_error(value: impl Error) -> Self { + Self(value.to_string()) + } + /// 从[`Error`]转换为[`anyhow::Error`] + pub fn error_anyhow(value: impl Error) -> anyhow::Error { + Self::from_error(value).into() + } + + /// 从「一切可以转换为其自身的值」构建[`anyhow::Result`] + pub fn err_anyhow(from: S) -> anyhow::Result + where + Self: From, + { + Err(Self::from(from).into()) + } + /// 从[`Self::from`]转换到[`anyhow::Error`] + /// * 🚩封装为自身类型 + /// * ❗实际上`.into()`比`::anyhow`短 + /// * 📌尽可能用前者 + pub fn anyhow(value: impl Into) -> anyhow::Error { + // ! ❌【2024-03-27 22:59:51】不能使用`Self::from(value).into`:`AsRef`不一定实现`Into` + anyhow::Error::from(value.into()) + } } -} -/// 展示错误 -impl Display for TranslateError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "TranslateError: {}", self.0) + /// 展示错误 + impl Display for TranslateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TranslateError: {}", self.0) + } } + /// 实现[`Error`]特征 + impl Error for TranslateError {} } -/// 实现[`Error`]特征 -impl Error for TranslateError {} +pub use translate_error::*; /// 单元测试 #[cfg(test)] diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 12f960f..4a7352a 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -124,7 +124,7 @@ pub mod tests { /// 实用测试工具/等待 pub fn await_fetch_until( vm: &mut CommandVmRuntime, - criterion: impl Fn(&Output, String) -> bool, + criterion: impl Fn(&Output, &str) -> bool, ) -> Output { // 不断拉取输出 // TODO: 💭【2024-03-24 18:21:28】后续可以结合「流式处理者列表」做集成测试 @@ -161,7 +161,7 @@ pub mod tests { pub fn input_cmd_and_await( vm: &mut CommandVmRuntime, cmd: Cmd, - criterion: impl Fn(&Output, String) -> bool, + criterion: impl Fn(&Output, &str) -> bool, ) -> Output { // 构造并输入任务 vm.input_cmd(cmd).expect("无法输入指令!"); diff --git a/src/test_tools/mod.rs b/src/test_tools/mod.rs new file mode 100644 index 0000000..1f4b2ab --- /dev/null +++ b/src/test_tools/mod.rs @@ -0,0 +1,15 @@ +//! 有关NAVM的**测试工具集**支持 +//! * 🎯提供可复用的测试用代码 +//! * 🎯提供自动化测试工具 +//! * 🎯提供一种通用的`.nal`测试方法 +//! * ✅存量支持:兼容大部分OpenNARS、ONA的`.nal`文件 +//! * ✨增量特性:基于NAVM提供新的测试语法 + +util::mods! { + // 结构定义 + pub pub structs; + // NAL格式支持 + pub nal_format; + // NAVM交互 + pub pub vm_interact; +} diff --git a/src/test_tools/nal_format/mod.rs b/src/test_tools/nal_format/mod.rs new file mode 100644 index 0000000..25fdee9 --- /dev/null +++ b/src/test_tools/nal_format/mod.rs @@ -0,0 +1,265 @@ +//! 基于NAVM的「统一`.nal`格式」支持 +//! * ✨语法(解析)支持 +//! * 🎯提供一种(部分)兼容现有`.nal`格式文件的语法 +//! * ⚠️对其中所有Narsese部分使用CommonNarsese「通用纳思语」:不兼容方言 + +use std::time::Duration; + +use super::structs::*; +use anyhow::{Ok, Result}; +use narsese::{ + conversion::string::impl_lexical::format_instances::FORMAT_ASCII, + lexical::{Narsese, Sentence, Task}, +}; +use navm::{cmd::Cmd, output::Operation}; +use pest::{iterators::Pair, Parser}; +use pest_derive::Parser; +use util::{first, pipe}; + +#[derive(Parser)] // ! ↓ 必须从项目根目录开始 +#[grammar = "src/test_tools/nal_format/nal_grammar.pest"] +pub struct NALParser; + +/// 使用[`pest`]将整个`.nal`文件内容转换为[`NALInput`]结果序列 +/// * ✨也可只输入一行,用以解析单个[`NALInput`] +/// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` +pub fn parse(input: &str) -> Vec> { + input + // 切分并过滤空行 + .split('\n') + .filter(|line| !line.trim().is_empty()) + // 逐行解析 + .map(parse_single) + // 收集所有结果 + .collect::>() +} + +pub fn parse_single(line: &str) -> Result { + // 解析一行 + pipe! { + line + // 从一行输入解析到[`pest`]的一个[`Pairs`] + => NALParser::parse(Rule::nal_input, _) + // 发现错误即上抛 + => {?}# + // 🚩只对应[`Rule::nal_input`]规则,因此只会有一个[`Pair`],不会有其它情形 + => .next() + => .unwrap() + // 折叠,返回结果 + => fold_pest + } +} + +/// 将[`pest`]解析出的[`Pair`]辅助折叠到「词法Narsese」中 +/// * 🚩只需处理单行输入:行与行之间分开解析,避免上下文污染 +/// * 📌只会存在如下主要情况 +/// * `cyc_uint`:`CYC`语法糖,亦兼容原`.nal`格式 +/// * `narsese`:`NSE`语法糖,亦兼容原`.nal`格式 +/// * `comment`:各类或「魔法」或「非魔法」的注释 +fn fold_pest(pair: Pair) -> Result { + match pair.as_rule() { + // 一行的无符号整数 // + Rule::cyc_uint => { + // 仅取数字部分 + let n: usize = pair.as_str().parse()?; + // * 🚩作为`CYC`语法糖 + let input = NALInput::Put(Cmd::CYC(n)); + Ok(input) + } + // 一行的Narsese // + Rule::narsese => { + // 作为CommonNarsese,直接取字符串,然后调用CommonNarsese ASCII解析器 + // * 🚩【2024-03-31 16:37:32】虽可能有失灵活性,但代码上更显通用 + let narsese = pair.as_str(); + let narsese = FORMAT_ASCII.parse(narsese)?.try_into_task_compatible()?; + // * 🚩作为`NSE`语法糖 + let input = NALInput::Put(Cmd::NSE(narsese)); + Ok(input) + } + // 各种魔法注释 // + // 单纯的行注释:`REM`语法糖 + Rule::comment_raw => { + // 仅取注释部分 + // ! 不能用`to_string`:后者只会显示其总体信息,而非捕获相应字符串切片 + let comment = pair.as_str().into(); + // * 🚩作为`REM`语法糖 + let input = NALInput::Put(Cmd::REM { comment }); + Ok(input) + } + // 魔法注释/置入指令 + Rule::comment_navm_cmd => { + // 取其中第一个`comment_raw`元素 | 一定只有唯一一个`comment_raw` + let comment_raw = pair.into_inner().next().unwrap(); + // 仅取注释部分 + let line = comment_raw.as_str(); + // * 🚩作为所有NAVM指令的入口 + let input = NALInput::Put(Cmd::parse(line)?); + Ok(input) + } + // 魔法注释/睡眠等待 + Rule::comment_sleep => { + // 取其中第一个`comment_raw`元素 | 一定只有唯一一个`comment_raw` + let duration_raw = pair.into_inner().next().unwrap().as_str(); + // 尝试解析时间 + let duration = first! { + // 毫秒→微秒→纳秒→秒 | 对于「秒」分「整数」「浮点」两种 + duration_raw.ends_with("ms") => Duration::from_millis(duration_raw.strip_suffix("ms").unwrap().parse()?), + duration_raw.ends_with("μs") => Duration::from_micros(duration_raw.strip_suffix("μs").unwrap().parse()?), + duration_raw.ends_with("ns") => Duration::from_nanos(duration_raw.strip_suffix("ns").unwrap().parse()?), + duration_raw.ends_with('s') && duration_raw.contains('.') => Duration::try_from_secs_f64(duration_raw.strip_suffix('s').unwrap().parse()?)?, + duration_raw.ends_with('s') => Duration::from_secs(duration_raw.strip_suffix('s').unwrap().parse()?), + // 否则报错 + _ => return Err(anyhow::anyhow!("未知的睡眠时间参数 {duration_raw:?}")) + }; + // * 封装 + let input = NALInput::Sleep(duration); + Ok(input) + } + // 魔法注释/等待 + Rule::comment_await => { + // 取其中唯一一个「输出预期」 + let output_expectation = pair.into_inner().next().unwrap(); + let output_expectation = fold_pest_output_expectation(output_expectation)?; + Ok(NALInput::Await(output_expectation)) + } + // 魔法注释/输出包含 + Rule::comment_expect_contains => { + // 取其中唯一一个「输出预期」 + let output_expectation = pair.into_inner().next().unwrap(); + let output_expectation = fold_pest_output_expectation(output_expectation)?; + Ok(NALInput::ExpectContains(output_expectation)) + } + // 其它情况 + _ => unreachable!("不该被匹配到的规则\tpair = {pair:?}"), + } +} + +/// 解析其中的「输出预期」[`Pair`] +/// * 🚩在「遍历内部元素」时消耗[`Pair`]对象 +#[inline] +fn fold_pest_output_expectation(pair: Pair) -> Result { + // 构造一个(全空的)输出预期对象 + let mut result = OutputExpectation::default(); + // 开始遍历其中的元素 + for inner in pair.into_inner() { + // 逐个匹配规则类型 + // * 🚩【2024-04-01 00:18:23】目前只可能有三个 + // * ✨输出类型 + // * ✨Narsese + // * ✨NAVM操作 + match inner.as_rule() { + // 输出类型 + Rule::output_type => { + // 取其中唯一一个`output_type_name` + // ! 不能用`to_string`:后者只会显示其总体信息,而非捕获相应字符串切片 + let output_type = inner.as_str().into(); + // 添加到结果中 + result.output_type = Some(output_type); + } + // Narsese + Rule::narsese => { + // 取其中唯一一个`narsese` + let narsese = inner.as_str(); + // 解析Narsese + let narsese = FORMAT_ASCII.parse(narsese)?; + // 添加到结果中 + result.narsese = Some(narsese); + } + // NAVM操作 + Rule::output_operation => result.operation = Some(fold_pest_output_operation(inner)?), + // 其它情况 + _ => unreachable!("不该被匹配到的规则\tpair = {inner:?}"), + } + } + + // 返回 + Ok(result) +} + +/// 解析其中的「NAVM操作」[`Pair`] +/// * 其中[`Pair`]的`rule`属性必是`output_operation` +#[inline] +fn fold_pest_output_operation(pair: Pair) -> Result { + // 生成迭代器 + let mut pairs = pair.into_inner(); + // 取第一个子Pair当操作名 | 语法上保证一定有 + let operator_name = pairs.next().unwrap().to_string(); + // 操作参数 + let mut params = vec![]; + // 消耗剩下的,填充参数 + for inner in pairs { + // 尝试作为Narsese词项解析 | 无法使用 *narsese.get_term()强制转换成词项 + let term = match FORMAT_ASCII.parse(inner.as_str())? { + Narsese::Term(term) + | Narsese::Sentence(Sentence { term, .. }) + | Narsese::Task(Task { + sentence: Sentence { term, .. }, + .. + }) => term, + }; + // 添加到参数中 + params.push(term); + } + // 返回 + Ok(Operation { + operator_name, + params, + }) +} + +/// 单元测试 +#[cfg(test)] +pub mod tests { + use super::*; + use util::{for_in_ifs, list}; + + pub const TESTSET: &str = "\ +' 用于测试CIN的「简单演绎推理」 +' * 📝利用现有`Narsese`语法 +' +' 输出预期 +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` + +'/VOL 0 + B>. + C>. + C>? +5 +''sleep: 1s +''expect-contains: ANSWER C>. + +A3. :|: +<(*, {SELF}, (*, P1, P2)) --> ^left>. :|: +G3. :|: +A3. :|: +G3! :|: +''sleep: 500ms +10 + +''expect-contains: EXE (^left, {SELF}, (*, P1, P2))"; + + #[test] + fn test_parse() { + _test_parse(" B>."); + _test_parse("5"); + _test_parse("'这是一个注释"); + _test_parse("'/VOL 0"); + _test_parse("''await: OUT B>."); + _test_parse("''sleep: 500ms"); + _test_parse(TESTSET); + } + + fn _test_parse(input: &str) { + let results = parse(input); + let results = list![ + (r.expect("解析失败!")) + for r in (results) + ]; + for_in_ifs! { + {println!("{:?}", r);} + for r in (results) + } + } +} diff --git a/src/test_tools/nal_format/nal_grammar.pest b/src/test_tools/nal_format/nal_grammar.pest new file mode 100644 index 0000000..886a998 --- /dev/null +++ b/src/test_tools/nal_format/nal_grammar.pest @@ -0,0 +1,205 @@ +//! 「统一`.nal`格式」语法 +//! * 🎯从常见的`.nal`文件中解析出「NAVM测试语句」 +//! * 🎯在「ASCII CommonNarsese」之下,附加注释与「测试预期」语法 +//! * 📝原则:凡是无法对应到「解析结果」(此处是NALInput结构)的,都给「静默」unwrap + +/// 空白符 | 所有非换行的Unicode空白符,解析前忽略 +/// * 🚩【2024-03-31 23:40:41】现在将「行分割」交给Rust预处理 +/// * 📌此处对「换行符」的特殊处理,基本不会用到 +WHITESPACE = _{ !"\n" ~ WHITE_SPACE } + +/// 多个`.nal`输入 +/// * 🚩【2024-03-31 23:41:33】目前不启用,将「行分割」交给Rust预处理 +nal_inputs = !{ + "\n"* ~ nal_input ~ ("\n"+ ~ nal_input)* +} + +/// 入口/单个`.nal`输入(静默展开) +/// * 🚩以数字代替`CYC`指令,并兼容原`.nal`语法 +/// * 🚩以直接的Narsese代替`NSE`指令,并兼容原`.nal`语法 +/// * 🚩在注释中扩展新语法 +nal_input = _{ + (cyc_uint | comment | narsese) +} + +/// 直接对应CYC指令的用法 +/// * ✅`CYC`的语法糖 +cyc_uint = { ASCII_DIGIT+ } + +/// 注释(静默) +/// * 🚩包括「输出预期」等「魔法注释」 +comment = _{ + comment_head ~ (comment_navm_cmd | comment_sleep | comment_await | comment_expect_contains | comment_raw) +} + +/// 注释的头部字符(静默) +comment_head = _{ "'" } + +/// 有关「置入命令」的「魔法注释」 +/// * ✨允许构建并向NAVM置入指令 +/// * 📄用`'/VOL 0`代替非通用的`*volume=0` +comment_navm_cmd = !{ + // 特殊前缀`/` + "/" ~ WHITESPACE* ~ comment_raw +} + +/// 有关「睡眠等待」的「魔法注释」 +/// * ✨允许构建并向NAVM置入指令 +/// * 📄用`'/VOL 0`代替非通用的`*volume=0` +/// * 具体的「时间格式」留给Rust侧 +comment_sleep = !{ + // 额外的前缀 + "'sleep:" ~ WHITESPACE* ~ comment_raw +} + +/// 有关「输出等待」的「魔法注释」 +/// ✨阻塞主线程,等待NAVM的某个输出再继续 +comment_await = { + // 额外的前缀 + "'await:" ~ output_expectation +} + +/// 有关「输出预期(包含)」的「魔法注释」 +/// ✨检查NAVM的所有输出,返回「是否有符合预期的输出」的[`Result`] +comment_expect_contains = { + // 额外的前缀 + "'expect-contains:" ~ output_expectation +} + +/// 原始注释语法:纯粹的行注释 +/// * ✅`REM`的语法糖 +comment_raw = @{ (!"\n" ~ ANY)* } + +/// 输出预期 +/// * 📌只描述「预期的内容」,与「具体的使用方式」无关 +/// * 🚩【2024-03-31 17:10:03】目前不包含对「原始内容」的预期:并非跨CIN通用 +output_expectation = { + output_type? ~ narsese? ~ output_operation? +} + +/// NAVM输出的「类型」 +/// * 🚩直接使用内容 +/// * 📝原子操作配合空格识别 +output_type = @{ (!WHITE_SPACE ~ ANY)* } + +/// NAVM输出中「操作」的一种表征形式 +/// * 🚩刻意与CommonNarsese语法不一致,以便省去「XX=」前缀进行识别 +output_operation = { + "(" ~ "^" ~ atom_content ~ "," ~ term_list? ~ ")" +} + +/// Narsese | 优先级:任务 > 语句 > 词项 +/// * 🚩不使用「静默规则」,让剩下的语法树作为「Narsese边界匹配」用 +/// * 🚩不能使用「原子规则」匹配整个字符串:会导致匹配失败 +/// * ✅`NSE`的语法糖 +narsese = { + task + | sentence + | term +} + +/// 任务:有预算的语句 +task = { + budget ~ sentence +} + +/// 预算值 | 不包括「空字串」隐含的「空预算」 +budget = { + "$" ~ budget_content ~ "$" +} + +/// 预算值内容 +budget_content = _{ + (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) + | "" // 空预算(但带括号) +} + +/// 通用于真值、预算值的项 | 用作内部数值,不约束取值范围 +truth_budget_term = @{ (ASCII_DIGIT | ".")+ } + +/// 语句 = 词项 标点 时间戳? 真值? +sentence = { + term ~ punctuation ~ stamp? ~ truth? +} + +/// 词项 = 陈述 | 复合 | 原子 +term = _{ + statement + | compound + | atom +} + +/// 陈述 = <词项 系词 词项> +statement = { + "<" ~ term ~ copula ~ term ~ ">" +} + +/// 陈述系词 +copula = @{ + (punct_sym ~ "-" ~ punct_sym) // 继承/相似/实例/属性/实例属性 + + | (punct_sym ~ "=" ~ punct_sym) // 蕴含/等价 + + | ("=" ~ punct_sym ~ ">") // 时序性蕴含 + + | ("<" ~ punct_sym ~ ">") // 时序性等价 +} + +/// 标点符号 | 用于「原子词项前缀」「复合词项连接词」和「陈述系词」 +punct_sym = { (PUNCTUATION | SYMBOL) } + +/// 复合 = (连接词, 词项...) | {外延集...} | [内涵集...] +compound = { + compound_common + | ext_set + | int_set +} + +/// 通用的复合词项 +compound_common = { ("(" ~ connecter ~ "," ~ term_list ~ ")") } + +/// 通用的「词项列表」 | 静默展开 +term_list = _{ term ~ ("," ~ term)* } + +/// 外延集 | 📌【2024-03-29 09:39:39】pest代码折叠中会丢掉所有「不被规则捕获的字符串信息」 +ext_set = { "{" ~ term_list ~ "}" } + +/// 内涵集 +int_set = { "[" ~ term_list ~ "]" } + +/// 复合词项连接词 +connecter = @{ punct_sym ~ (!"," ~ punct_sym)* } + +/// 原子 = 前缀(可选) 内容 +atom = { + placeholder // 占位符 + + | (atom_prefix ~ atom_content) // 变量/间隔/操作…… + + | atom_content // 词语 +} + +/// 占位符 = 纯下划线字符串 +placeholder = @{ "_"+ } + +/// 原子词项前缀 +atom_prefix = @{ (!"<" ~ !"(" ~ !"[" ~ !"{" ~ punct_sym)+ } + +/// 原子词项内容 | 已避免与「复合词项系词」相冲突 +atom_content = @{ atom_char ~ (!copula ~ atom_char)* } + +/// 能作为「原子词项内容」的字符 +atom_char = { LETTER | NUMBER | "_" | "-" } + +/// 标点 +punctuation = { (PUNCTUATION | SYMBOL) } + +/// 时间戳 | 空时间戳会直接在「语句」中缺省 +stamp = { + ":" ~ (!":" ~ ANY)+ ~ ":" +} + +/// 真值 | 空真值会直接在「语句」中缺省 +truth = { + "%" ~ (truth_budget_term ~ (";" ~ truth_budget_term)* ~ ";"*) ~ "%" +} diff --git a/src/test_tools/structs.rs b/src/test_tools/structs.rs new file mode 100644 index 0000000..aa069ba --- /dev/null +++ b/src/test_tools/structs.rs @@ -0,0 +1,58 @@ +//! 有关「NAVM测试工具」的数据结构支持 +//! * 🎯构造在「NAVM指令」之上的超集,支持与测试有关的数据结构存储 +//! * ✨[`NALInput`]:在「直接对应CIN输入输出」的「NAVM指令」之上,引入「等待」「预期」等机制 +//! * ✨[`OutputExpectation`]:面向NAL测试,具体实现「预期」机制 + +use narsese::lexical::Narsese; +use navm::{cmd::Cmd, output::Operation}; +use std::time::Duration; + +/// NAVM测试中的「NAL输入」 +/// * 📌`.nal`文件中一行的超集 +/// * 🎯在原有NAVM指令下,扩展与测试有关的功能 +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NALInput { + /// 置入 + /// * 🎯向CIN置入NAVM指令 + Put(Cmd), + + /// 睡眠 + /// * 📌调用[`thread::sleep`]单纯等待一段时间(单位:[`Duration`]) + /// * 🚩语法中常用的是秒数,但这里不直接存储 + Sleep(Duration), + + /// 输出等待 + /// * 📌在CIN输出与指定[`Output`]符合后,再继续运行 + /// * 🎯用于结合`IN`等待CIN「回显」 + Await(OutputExpectation), + + /// 对「输出含有」的预期 + /// * 🎯用于「在现有的输出中检查是否任一和指定的[`Output`]符合」 + /// * 📄对应OpenNARS中常有的`''outputMustContain('')` + ExpectContains(OutputExpectation), + // 🏗️后续还能有更多 +} + +/// 输出预期 +/// * 📌对应语法中的`output_expectation`结构 +/// * 🎯用于统一表示对「NAVM输出」的预期 +/// * 🚩除了「原始内容」外,与[`Output`]类型一致 +/// * ✨可进行有关「检查范围」「严格性」等更细致的配置,而非仅仅是「文本包含」 +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct OutputExpectation { + /// 预期的「输出类型」 + /// * 🚩可能没有:此时是「通配」情形 + /// * 对任何可能的输入都适用 + pub output_type: Option, + + /// 预期的「Narsese」字段 + /// * 🚩可能没有:此时是「通配」情形 + /// * 对任何可能的输入都适用 + /// * 🚩对内部[`Narsese`]会进行**递归匹配** + pub narsese: Option, + + /// 预期的「NAVM操作」字段 + /// * 🚩可能没有:此时是「通配」情形 + /// * 对任何可能的输入都适用 + pub operation: Option, +} diff --git a/src/test_tools/vm_interact.rs b/src/test_tools/vm_interact.rs new file mode 100644 index 0000000..1da77fd --- /dev/null +++ b/src/test_tools/vm_interact.rs @@ -0,0 +1,70 @@ +//! 与NAVM虚拟机的交互逻辑 + +use super::{NALInput, OutputExpectation}; +use anyhow::Result; +use navm::{output::Output, vm::VmRuntime}; + +impl OutputExpectation { + /// 判断一个「NAVM输出」是否与自身相符合 + /// * 🏗️TODO: 迁移功能 + pub fn matches(&self, output: &Output) -> bool { + todo!() + } +} + +/// 向虚拟机置入[`NALInput`] +/// * 🎯除了「输入指令」之外,还附带其它逻辑 +/// * 🚩通过「输出缓存」参数,解决「缓存输出」问题 +/// * ❓需要迁移「符合预期」的逻辑 +pub fn put_nal( + mut vm: impl VmRuntime, + input: NALInput, + output_cache: &mut Vec, +) -> Result<()> { + match input { + // 置入NAVM指令 + NALInput::Put(cmd) => vm.input_cmd(cmd), + // 睡眠 + NALInput::Sleep(duration) => { + // 睡眠指定时间 + std::thread::sleep(duration); + // 返回`ok` + Ok(()) + } + // 等待一个符合预期的NAVM输出 + NALInput::Await(expectation) => loop { + let output = match vm.fetch_output() { + Ok(output) => { + // 加入缓存 + output_cache.push(output); + // 返回引用 + output_cache.last().unwrap() + } + Err(e) => { + println!("尝试拉取输出出错:{e}"); + continue; + } + }; + // 只有匹配了才返回 + if expectation.matches(output) { + break Ok(()); + } + }, + // 检查是否有NAVM输出符合预期 + NALInput::ExpectContains(expectation) => { + // 先尝试拉取所有输出到「输出缓存」 + while let Ok(Some(output)) = vm.try_fetch_output() { + output_cache.push(output); + } + // 然后逐个读取输出缓存 + for output in output_cache.iter() { + // 只有匹配了才返回Ok + if expectation.matches(output) { + return Ok(()); + } + } + // 否则返回Err + Err(anyhow::anyhow!("没有找到符合要求「{expectation:?}」的输出")) + } + } +} diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal new file mode 100644 index 0000000..541d5f7 --- /dev/null +++ b/src/tests/nal/test_operation.nal @@ -0,0 +1,31 @@ +' 用于测试CIN对NAL-8的支持 +' ! ⚠️【2024-03-29 16:52:57】ONA不支持「有两个以上组分的乘积词项」,故使用(*, P1, P2)替代 +' * ONA特有「操作注册」语法:`*setopname 11 ^left` +' 输出预期 +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` + +A. :|: +<(*, {SELF}) --> ^left>. :|: +G. :|: +A. :|: +G! :|: +10 +''expect-contains: EXE (^left, {SELF}) + +A2. :|: +<(*, {SELF}, P) --> ^left>. :|: +G2. :|: +A2. :|: +G2! :|: +10 +''expect-contains: EXE (^left, {SELF}, P) + +A3. :|: +<(*, {SELF}, (*, P1, P2)) --> ^left>. :|: +G3. :|: +A3. :|: +G3! :|: +10 +''expect-contains: EXE (^left, {SELF}, (*, P1, P2)) diff --git a/src/tests/nal/test_simple_deduction.nal b/src/tests/nal/test_simple_deduction.nal new file mode 100644 index 0000000..f1a101c --- /dev/null +++ b/src/tests/nal/test_simple_deduction.nal @@ -0,0 +1,21 @@ +' 用于测试CIN的「简单演绎推理」 +' * 📝利用现有`Narsese`语法 +' +' 输出等待 `expect-contains` +' * 📝统一的NAL等待语法:`''await: 【输入类别】 【其它内容】` +' * ⚠️可能会阻塞测试,慎用 +' * 🚩以下await已被注释失效,仅作语法演示 +' +' 输出预期 `expect-contains` +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` + + B>. +' ''await: IN B>. + C>. +' ''await: IN C>. + C>? +5 + +''expect-contains: ANSWER C>. From c529eb70525b4cdc12025bc19df58114435cb68b Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Mon, 1 Apr 2024 14:35:18 +0800 Subject: [PATCH 33/59] =?UTF-8?q?feat:=20:sparkles:=20CLI=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E9=85=8D=E7=BD=AE=EF=BC=9B.nal=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 引入有关CLI的库,支持「启动配置」与「参数解析」 - 📦命令行参数解析`clap` - 📦JSON解析`serde_json` - ✅基本稳定的「启动配置」数据类型 - ✅基本稳定的「启动配置」加载方案 - 🏗️TODO:尚未与CIN运行时对接 2. 扩展.nal语法中有关「直接输入NAVM指令」的部分 - ✨三个单引号前缀直接对应NAVM指令 - 📄`'''VOL 0` --- .vscode/settings.json | 1 + BabelNAR.launch.json | 19 + Cargo.lock | 664 +++++++++++++++++- Cargo.toml | 30 +- src/bin/babelnar_cli/arg_parse.rs | 263 +++++++ src/bin/babelnar_cli/launch_config.rs | 279 ++++++++ src/bin/babelnar_cli/main.rs | 43 ++ .../cin_search/anyhow_vm.rs | 0 .../cin_search/impls_path_builder/mod.rs | 4 +- .../impls_path_builder/path_builder_ona.rs | 2 +- .../path_builder_opennars.rs | 2 +- .../cin_search/mod.rs | 0 .../cin_search/name_match.rs | 0 .../cin_search/path_builder.rs | 0 .../cin_search/path_walker.rs | 2 +- .../io/mod.rs | 0 src/{cmdline_support => cli_support}/mod.rs | 0 src/lib.rs | 2 +- src/test_tools/nal_format/mod.rs | 3 + src/test_tools/nal_format/nal_grammar.pest | 5 +- src/tests/cli/config_opennars.json | 12 + src/tests/cli/config_websocket.json | 6 + 22 files changed, 1312 insertions(+), 25 deletions(-) create mode 100644 BabelNAR.launch.json create mode 100644 src/bin/babelnar_cli/arg_parse.rs create mode 100644 src/bin/babelnar_cli/launch_config.rs create mode 100644 src/bin/babelnar_cli/main.rs rename src/{cmdline_support => cli_support}/cin_search/anyhow_vm.rs (100%) rename src/{cmdline_support => cli_support}/cin_search/impls_path_builder/mod.rs (94%) rename src/{cmdline_support => cli_support}/cin_search/impls_path_builder/path_builder_ona.rs (98%) rename src/{cmdline_support => cli_support}/cin_search/impls_path_builder/path_builder_opennars.rs (96%) rename src/{cmdline_support => cli_support}/cin_search/mod.rs (100%) rename src/{cmdline_support => cli_support}/cin_search/name_match.rs (100%) rename src/{cmdline_support => cli_support}/cin_search/path_builder.rs (100%) rename src/{cmdline_support => cli_support}/cin_search/path_walker.rs (99%) rename src/{cmdline_support => cli_support}/io/mod.rs (100%) rename src/{cmdline_support => cli_support}/mod.rs (100%) create mode 100644 src/tests/cli/config_opennars.json create mode 100644 src/tests/cli/config_websocket.json diff --git a/.vscode/settings.json b/.vscode/settings.json index b7a27fb..b8f743d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "rust-analyzer.cargo.features": "all", "editor.formatOnSave": true, "cSpell.words": [ + "confy", "cxin", "Errno", "hasher", diff --git a/BabelNAR.launch.json b/BabelNAR.launch.json new file mode 100644 index 0000000..cecdb86 --- /dev/null +++ b/BabelNAR.launch.json @@ -0,0 +1,19 @@ +{ + "translators": "opennars", + "command": { + "cmd": "java", + "cmd_args": [ + "-Xmx1024m", + "-jar", + "nars.jar" + ], + "current_dir": "root/nars/test" + }, + "websocket": { + "host": "localhost", + "port": 8080 + }, + "prelude_nal": { + "text": "'/VOL 0" + } +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2e21e06..56dd464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,17 +11,72 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + [[package]] name = "babel_nar" version = "0.10.0" dependencies = [ "anyhow", + "clap", "colored", "lazy_static", "nar_dev_utils", @@ -30,6 +85,27 @@ dependencies = [ "pest", "pest_derive", "regex", + "serde", + "serde_json", + "ws", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", ] [[package]] @@ -38,15 +114,98 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" version = "2.1.0" @@ -54,7 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -72,20 +231,69 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -96,24 +304,137 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + [[package]] name = "nar_dev_utils" version = "0.24.0" @@ -135,12 +456,35 @@ dependencies = [ "narsese", ] +[[package]] +name = "net2" +version = "0.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.8" @@ -186,6 +530,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -204,6 +554,47 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "regex" version = "1.10.4" @@ -233,17 +624,81 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.10.7", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "syn" version = "2.0.55" @@ -275,6 +730,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typenum" version = "1.17.0" @@ -287,25 +757,106 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -314,13 +865,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -329,38 +895,108 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "ws" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fe90c75f236a0a00247d5900226aea4f2d7b05ccc34da9e7a8880ff59b5848" +dependencies = [ + "byteorder", + "bytes", + "httparse", + "log", + "mio", + "mio-extras", + "rand", + "sha-1", + "slab", + "url", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml index 155e3a1..1767092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,27 @@ optional = true version = "2.1.0" optional = true +# 命令行支持/JSON配置解析 +[dependencies.serde] +version = "1.0.197" +optional = true +features = ["derive"] + +[dependencies.serde_json] +version = "1.0.115" +optional = true + +# 命令行支持/Websocket服务 +[dependencies.ws] +version = "0.9.2" +optional = true + +# 命令行支持/命令行参数解析 +[dependencies.clap] +version = "4.5.4" +features = ["derive"] +optional = true + ### 定义库的特性 ### [features] @@ -84,7 +105,7 @@ default = [] ## 大杂烩 ## bundled = [ "cin_implements", # 各大CIN的NAVM实现 - "cmdline_support", # 命令行支持 + "cli_support", # 命令行支持 "test_tools", # 测试工具集 ] @@ -126,8 +147,11 @@ nars_python = [ openjunars = [] # 命令行支持 # -cmdline_support = [ - "colored" +cli_support = [ + "colored", # 命令行io 彩色打印 + "serde", "serde_json", # 配置文件解析 + "ws", # 命令行io Websocket服务 + "clap" # 命令行参数解析 ] # 测试工具集 # diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs new file mode 100644 index 0000000..ab257c6 --- /dev/null +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -0,0 +1,263 @@ +//! BabelNAR CLI的命令行(参数 & 配置)解析支持 +//! * ⚠️【2024-04-01 14:31:09】特定于二进制crate,目前不要并入[`babel_nar`] + +use crate::launch_config::LaunchConfig; +use anyhow::Result; +use clap::Parser; +use nar_dev_utils::{pipe, ResultBoost}; +use std::{fs::read_to_string, path::PathBuf}; + +/// 默认的「外部JSON」路径 +pub const DEFAULT_CONFIG_PATH: &str = "BabelNAR.launch.json"; + +/// 基于[`clap`]的命令行参数数据 +// 配置命令行解析器 +#[derive(Parser)] +#[command(name = "BabelNAR CLI")] +#[command(about = "BabelNAR's Cmdline Interface", long_about = None)] +#[command(version, about, long_about = None)] +// 其它 +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct CliArgs { + // 配置文件路径 + // * ✨可支持加载多个配置 + // * ⚠️需要重复使用`-c` + // * ✅会以「重复使用`-c`」的顺序被载入 + // * 🚩【2024-04-01 13:07:18】具有最高加载优先级 + // * 📌剩余的是和exe同目录的`json`文件 + // ! 📝此处的文档字符串会被用作`-h`的说明 + /// Configuration file path in JSON (multiple supported by call it multiple times) + #[arg(short, long, value_name = "FILE")] + pub config: Vec, + + // 禁用默认配置 + // * 禁用与exe同目录的配置文件 + // * 📜默认为`false` + // * 📌行为 + // * 没有 ⇒ `false` + // * 有  ⇒ `true` + /// Disable the default configuration file in the same directory as exe + #[arg(short, long)] + pub disable_default: bool, +} + +/// 加载配置 +/// * 🚩按照一定优先级顺序进行覆盖(从先到后) +/// * 命令行参数中指定的配置文件 +/// * 默认的JSON文件 | 可以在`disable_default = true`的情况下传入任意字串作占位符 +pub fn load_config(args: &CliArgs, default_config_path: impl Into) -> LaunchConfig { + // 构建返回值 | 全`None` + let mut result = LaunchConfig::new(); + // 尝试从命令行参数中读取再合并配置 | 仅提取出其中`Some`的项 + args.config + // 尝试加载配置文件,对错误采取「警告并抛掉」的策略 + .iter() + .filter_map(load_config_extern) + // 逐个从「命令行参数指定的配置文件」中合并 + .for_each(|config| result.merge_from(&config)); + // 若未禁用,尝试读取再合并默认启动配置 + if !args.disable_default { + // * 🚩读取失败⇒警告&无动作 | 避免多次空合并 + load_config_extern(&default_config_path.into()) + .inspect(|config_extern| result.merge_from(config_extern)); + } + // 返回 + result +} + +/// 从外部JSON文件中加载启动配置 +/// * 🎯错误处理 & 错误⇒空置 +/// * 🚩在遇到错误时会发出警告 +pub fn load_config_extern(path: &PathBuf) -> Option { + // Ok⇒Some,Err⇒警告+None + read_config_extern(path).ok_or_run(|e| { + // 根据错误类型进行分派 + if let Some(e) = e.downcast_ref::() { + match e.kind() { + std::io::ErrorKind::NotFound => { + println!("[WARN] 未找到外部配置,使用空配置……"); + } + _ => println!("[WARN] 读取外部配置时出现预期之外的错误: {}", e), + } + } else if let Some(e) = e.downcast_ref::() { + match e.classify() { + serde_json::error::Category::Syntax => { + println!("[WARN] 外部配置文件格式错误,使用空配置……"); + } + _ => println!("[WARN] 解析外部配置时出现预期之外的错误: {}", e), + } + } else { + println!("[WARN] 加载外部配置时出现预期之外的错误: {}", e) + } + // 空置 + }) +} + +/// 从外部JSON文件中读取启动配置 +/// * 🎯仅涉及具体读取逻辑,不涉及错误处理 +pub fn read_config_extern(path: &PathBuf) -> Result { + // 尝试读取外部启动配置,并尝试解析 + pipe! { + path + // 尝试读取文件内容 + => read_to_string + => {?}# + // 尝试解析JSON配置 + => #{&} + => LaunchConfig::from_json_str + => {?}# + // 返回Ok(转换为`anyhow::Result`) + => Ok + } + // ! 若需使用`confy`,必须封装 + // * 🚩目前无需使用`confy`:可以自动创建配置文件,但个人希望其路径与exe同目录 + // Ok(confy::load_path(path)?) // ! 必须封装 +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use nar_dev_utils::fail_tests; + + /// 测试/参数解析 + mod arg_parse { + use super::*; + + fn _test_arg_parse(args: &[&str], expected: &CliArgs) { + // ! 📝此处必须前缀一个「自身程序名」 + let args = CliArgs::parse_from([&["test.exe"], args].concat()); + assert_eq!(dbg!(args), *expected) + } + + // 快捷测试宏 + macro_rules! test_arg_parse { + // 成功测试 + { + $( $args:expr => $expected:expr $(;)? )* + } => { + $( + _test_arg_parse(&$args, &$expected); + )* + }; + // 失败测试 + { + $args:expr + } => { + // 直接使用默认构造,解析成功了大概率报错 + _test_arg_parse(&$args, &CliArgs::default()) + }; + } + + /// 测试/打印帮助 + #[test] + fn test_arg_parse_help() { + _test_arg_parse(&["--help"], &CliArgs::default()); + } + + /// 测试/成功的解析 + #[test] + fn test_arg_parse() { + test_arg_parse! { + ["-c", "./src/tests/cli/config_opennars.json"] + => CliArgs { + config: vec!["./src/tests/cli/config_opennars.json".into()], + ..Default::default() + }; + // 多个配置:重复使用`-c`/`--config`,按使用顺序填充 + ["-c", "1.json", "--config", "2.json"] + => CliArgs { + config: vec!["1.json".into(), "2.json".into()], + ..Default::default() + }; + // 禁用默认配置:使用`-d`/`--disable-default` + ["-d"] + => CliArgs { + disable_default: true, + ..Default::default() + }; + }; + } + + // 失败解析 + fail_tests! { + fail_缺少参数 test_arg_parse!(["-c"]); + fail_参数名不对 test_arg_parse!(["--c"]); + fail_缺少参数2 test_arg_parse!(["--config"]); + 多个参数没各自前缀 test_arg_parse!(["-c", "1.json", "2.json"]); + } + } + + /// 测试/加载配置 + mod read_config { + use crate::LaunchConfigWebsocket; + + use super::*; + + /// 测试/加载配置 + fn load(args: &[&str]) -> LaunchConfig { + // 读取配置 | 自动填充第一个命令行参数作为「当前程序路径」 + let args = CliArgs::parse_from([&["test.exe"], args].concat()); + let config = load_config(&args, DEFAULT_CONFIG_PATH); + dbg!(config) + } + + /// 实用测试宏 + macro_rules! test { + // 成功测试 + { $( [ $($arg:expr $(,)? )* ] => $expected:expr $(;)? )* } => { + $( assert_eq!(load(&[ $($arg ),* ]), $expected); )* + }; + // 失败测试 | 总是返回默认值 + { $( $args:expr $(;)? )* } => { + $( assert_eq!(load(&$args), LaunchConfig::default()); )* + }; + } + + /// 测试 + #[test] + fn test() { + // 成功测试 + test! { + // 单个配置文件 + ["-c" "src/tests/cli/config_opennars.json" "-d"] => LaunchConfig { + translators: Some( + crate::LaunchConfigTranslators::Same( + "opennars".into(), + ), + ), + command: None, + websocket: None, + prelude_nal: None, + }; + ["-c" "src/tests/cli/config_websocket.json" "-d"] => LaunchConfig { + translators: None, + command: None, + websocket: Some(LaunchConfigWebsocket { + host: "localhost".into(), + port: 8080, + }), + prelude_nal: None, + }; + // 两个配置文件合并 + [ + "-d" + "-c" "src/tests/cli/config_opennars.json" + "-c" "src/tests/cli/config_websocket.json" + ] => LaunchConfig { + translators: Some( + crate::LaunchConfigTranslators::Same( + "opennars".into(), + ), + ), + command: None, + websocket: Some(LaunchConfigWebsocket { + host: "localhost".into(), + port: 8080, + }), + prelude_nal: None, + } + } + } + } +} diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs new file mode 100644 index 0000000..19fcd23 --- /dev/null +++ b/src/bin/babelnar_cli/launch_config.rs @@ -0,0 +1,279 @@ +//! BabelNAR CLI的启动配置 +//! * ✨格式支持 +//! * ✅JSON +//! * 🎯用于配置表示,❗不用于命令行解析 +//! * ⚠️【2024-04-01 14:31:09】特定于二进制crate,目前不要并入[`babel_nar`] +//! +//! ## ⚙️内容 +//! +//! Rust结构: +//! +//! * 📌转译器组合? +//! * (互斥)单个值?(输入输出相同) `opennars` / `ona` / `nars-python` / `pynars` / `openjunars` / `cxin-js` +//! * (互斥)输入输出单独配置? +//! * 输入 `opennars` / `ona` / `nars-python` / `pynars` / `openjunars` / `cxin-js` +//! * 输出 `opennars` / `ona` / `nars-python` / `pynars` / `openjunars` / `cxin-js` +//! * 📌启动命令? +//! * 命令 `XXX.exe` / `python` / `java` / `node` / ... +//! * 命令参数? `["-m", 【Python模块】]` / `["-jar", 【Jar路径】]` +//! * 工作目录? `root/path/to/current_dir` | 🎯用于Python模块 +//! * 📌预置NAL? +//! * (互斥)文件路径? `root/path/to/file` | 与下边「纯文本」互斥 +//! * (互斥)纯文本? `"'/VOL 0"` +//! * 📌Websocket参数? | ✅支持ipv6 +//! * 主机地址 `localhost` `192.168.1.1` `fe80::abcd:fade:dad1` +//! * 连接端口 `3040` +//! +//! TypeScript声明: +//! +//! ```ts +//! type LaunchConfig = { +//! translators?: LaunchConfigTranslators; +//! command?: LaunchConfigCommand; +//! websocket?: LaunchConfigWebsocket; +//! prelude_nal?: LaunchConfigPreludeNAL; +//! } +//! +//! type LaunchConfigTranslators = string | { +//! // ↓虽然`in`是JavaScript/TypeScript/Rust的关键字,但仍可在此直接使用 +//! in: string; +//! out: string; +//! }; +//! +//! type LaunchConfigCommand = { +//! cmd: string; +//! cmd_args?: string[]; +//! current_dir?: string; +//! } +//! type LaunchConfigWebsocket = { +//! host: string; +//! port: number; +//! } +//! // ↓ 文件、纯文本 二选一 +//! type LaunchConfigPreludeNAL = { +//! file?: string; +//! text?: string; +//! } +//! ``` + +use nar_dev_utils::OptionBoost; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct LaunchConfig { + /// 转译器组合(可选) + /// * 🚩使用字符串模糊匹配 + pub translators: Option, + + /// 启动命令(可选) + pub command: Option, + + /// Websocket参数(可选) + pub websocket: Option, + + /// 预置NAL(可选) + pub prelude_nal: Option, +} + +/// 转译器组合 +/// * 🚩【2024-04-01 11:20:36】目前使用「字符串+内置模糊匹配」进行有限的「转译器支持」 +/// * 🚧尚不支持自定义转译器 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] // 🔗参考: +pub enum LaunchConfigTranslators { + /// 🚩单个字符串⇒输入输出使用同一个转译配置 + Same(String), + + /// 🚩一个对象⇒输入和输出分别使用不同的转译配置 + Separated { + #[serde(rename = "in")] + input: String, + #[serde(rename = "out")] + output: String, + }, +} + +/// 启动命令 +/// * ❓后续可能支持「自动搜索」 +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct LaunchConfigCommand { + /// 命令 + /// * 直接对应[`std::process::Command`] + /// * 🚩[`Default`]中默认对应空字串 + pub cmd: String, + + /// 命令的参数(可选) + pub cmd_args: Option>, + + /// 工作目录(可选) + /// * 🎯可用于Python模块 + pub current_dir: Option, +} + +/// Websocket参数 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct LaunchConfigWebsocket { + /// 主机地址 + /// * 📄`localhost` + /// * 📄`192.168.0.0` + /// * 📄`fe80::abcd:fade:dad1` + pub host: String, + + /// 连接端口 + /// * 🚩采用十六位无符号整数 + /// * 📄范围:0 ~ 65535 + /// * 🔗参考: + pub port: u16, +} + +/// 预置NAL +/// * 🚩在CLI启动后自动执行 +/// * 📝[`serde`]允许对枚举支持序列化/反序列化 +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum LaunchConfigPreludeNAL { + /// 从文件路径导入 + /// * 📌键名:`file` + #[serde(rename = "file")] + File(String), + /// 从文本解析 + /// * 📌键名:`text` + #[serde(rename = "text")] + Text(String), +} + +/// 启动配置 +impl LaunchConfig { + /// 零参构造函数 + /// * 🚩使用[`Default`]提供默认空数据 + pub fn new() -> Self { + Self::default() + } + + /// (尝试)从JSON字符串构造 + pub fn from_json_str(json: &str) -> serde_json::Result { + serde_json::from_str(json) + } + + /// 从另一个配置中并入配置 + /// * 🚩合并逻辑:`Some(..)` => `None` + /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 + /// * ✨对【内部含有可选键】的值,会**递归深入** + pub fn merge_from(&mut self, other: &Self) { + // 合并所有【不含可选键】的值 + self.translators.coalesce_clone(&other.translators); + self.prelude_nal.coalesce_clone(&other.prelude_nal); + self.websocket.coalesce_clone(&other.websocket); + // 递归合并所有【含有可选键】的值 + LaunchConfigCommand::merge_as_key(&mut self.command, &other.command); + } +} + +impl LaunchConfigCommand { + /// 从另一个配置中并入配置 + /// * 🚩`Some(..)` => `None` + pub fn merge_from(&mut self, other: &Self) { + self.cmd_args.coalesce_clone(&other.cmd_args); + self.current_dir.coalesce_clone(&other.current_dir); + } + + /// 作为一个键,从另一个配置中并入配置 + /// * 🚩`Some(..)` => `None` + /// * 适用于自身为[`Option`]的情况 + pub fn merge_as_key(option: &mut Option, other: &Option) { + // 双重`inspect` + if let (Some(config_self), Some(config_other)) = (option, other) { + config_self.merge_from(config_other); + } + } +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Result; + + macro_rules! test { + { $( $data:expr => $expected:expr )* } => { + $( + _test(&$data, &$expected).expect("测试失败"); + )* + }; + } + + fn _test(data: &str, expected: &LaunchConfig) -> Result<()> { + // Some JSON input data as a &str. Maybe this comes from the user. + let parsed = LaunchConfig::from_json_str(data)?; + + dbg!(&parsed); + assert_eq!(parsed, *expected); + + Ok(()) + } + + #[test] + fn main() { + test! { + // 平凡情况/空 + "{}" => LaunchConfig::new() + "{}" => LaunchConfig::default() + // 完整情况 + r#" + { + "translators": "opennars", + "command": { + "cmd": "java", + "cmd_args": ["-Xmx1024m", "-jar", "nars.jar"], + "current_dir": "root/nars/test" + }, + "websocket": { + "host": "localhost", + "port": 8080 + }, + "prelude_nal": { + "text": "'/VOL 0" + } + }"# => LaunchConfig { + translators: Some(LaunchConfigTranslators::Same("opennars".into())), + command: Some(LaunchConfigCommand { + cmd: "java".into(), + cmd_args: Some(vec!["-Xmx1024m".into(), "-jar".into(), "nars.jar".into()]), + current_dir: Some("root/nars/test".into()) + }), + websocket: Some(LaunchConfigWebsocket{ + host: "localhost".into(), + port: 8080 + }), + prelude_nal: Some(LaunchConfigPreludeNAL::Text("'/VOL 0".into())) + } + // 测试`translators`、`prelude_nal`的其它枚举 + r#" + { + "translators": { + "in": "opennars", + "out": "ona" + }, + "command": { + "cmd": "root/nars/open_ona.exe" + }, + "prelude_nal": { + "file": "root/nars/prelude.nal" + } + }"# => LaunchConfig { + translators: Some(LaunchConfigTranslators::Separated { + input: "opennars".into(), + output: "ona".into() + }), + command: Some(LaunchConfigCommand { + cmd: "root/nars/open_ona.exe".into(), + ..Default::default() + }), + prelude_nal: Some(LaunchConfigPreludeNAL::File("root/nars/prelude.nal".into())), + ..Default::default() + } + } + /* + "file": "root/path/to/file" + */ + } +} diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs new file mode 100644 index 0000000..e375bfb --- /dev/null +++ b/src/bin/babelnar_cli/main.rs @@ -0,0 +1,43 @@ +//! BabelNAR 命令行接口 +//! * ✨提供对BabelNAR的命令行支持 +//! +//! ## 命令行参数语法 +//! +//! ``` +//! usage: BabelNAR [OPTIONS] +//! ``` +//! +//! TODO: 配置读取、预加载 + +use clap::Parser; +use std::io::Result as IoResult; +use std::{env, path::PathBuf}; + +nar_dev_utils::mods! { + // 启动参数 + use launch_config; + // 命令行解析 + use arg_parse; +} + +pub fn main() { + // 以默认参数启动 + main_args(env::current_dir(), env::args()) +} + +/// 以特定参数开始命令行主程序 +/// * 🚩此处只应该有自[`env`]传入的参数 +/// * 🚩【2024-04-01 14:25:38】暂时用不到「当前工作路径」 +pub fn main_args(_cwd: IoResult, args: impl Iterator) { + let args = CliArgs::parse_from(args); + dbg!(&args); + // 读取配置 | with 默认配置文件 + let config = load_config(&args, DEFAULT_CONFIG_PATH); + dbg!(config); +} + +/// 单元测试 +#[cfg(test)] +mod tests { + // use super::*; +} diff --git a/src/cmdline_support/cin_search/anyhow_vm.rs b/src/cli_support/cin_search/anyhow_vm.rs similarity index 100% rename from src/cmdline_support/cin_search/anyhow_vm.rs rename to src/cli_support/cin_search/anyhow_vm.rs diff --git a/src/cmdline_support/cin_search/impls_path_builder/mod.rs b/src/cli_support/cin_search/impls_path_builder/mod.rs similarity index 94% rename from src/cmdline_support/cin_search/impls_path_builder/mod.rs rename to src/cli_support/cin_search/impls_path_builder/mod.rs index 08bc854..2d47af7 100644 --- a/src/cmdline_support/cin_search/impls_path_builder/mod.rs +++ b/src/cli_support/cin_search/impls_path_builder/mod.rs @@ -5,7 +5,7 @@ //! TODO: CXinNARS //! * 🚩【2024-03-31 01:27:09】其它接口完成度不高的CIN,暂时弃了 -use crate::cmdline_support::cin_search::{ +use crate::cli_support::cin_search::{ name_match::is_name_match, path_builder::CinPathBuilder, path_walker::PathWalker, }; use navm::vm::{VmLauncher, VmRuntime}; @@ -56,7 +56,7 @@ pub fn launchers_from_walker_sorted>( #[cfg(test)] mod tests { use super::*; - use crate::cmdline_support::cin_search::path_walker::PathWalkerV1; + use crate::cli_support::cin_search::path_walker::PathWalkerV1; use std::env::current_dir; #[test] diff --git a/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs b/src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs similarity index 98% rename from src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs rename to src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs index 093ad04..07d05fd 100644 --- a/src/cmdline_support/cin_search/impls_path_builder/path_builder_ona.rs +++ b/src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs @@ -1,6 +1,6 @@ //! 用于ONA的路径构建器 -use crate::cmdline_support::cin_search::{ +use crate::cli_support::cin_search::{ name_match::{name_match, name_match_only_contains}, path_builder::CinPathBuilder, }; diff --git a/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs b/src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs similarity index 96% rename from src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs rename to src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs index ea6d678..ad6350d 100644 --- a/src/cmdline_support/cin_search/impls_path_builder/path_builder_opennars.rs +++ b/src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs @@ -2,7 +2,7 @@ use crate::{ cin_implements::opennars::OpenNARS, - cmdline_support::cin_search::{name_match::name_match, path_builder::CinPathBuilder}, + cli_support::cin_search::{name_match::name_match, path_builder::CinPathBuilder}, runtimes::CommandVmRuntime, }; use nar_dev_utils::{if_return, OptionBoost}; diff --git a/src/cmdline_support/cin_search/mod.rs b/src/cli_support/cin_search/mod.rs similarity index 100% rename from src/cmdline_support/cin_search/mod.rs rename to src/cli_support/cin_search/mod.rs diff --git a/src/cmdline_support/cin_search/name_match.rs b/src/cli_support/cin_search/name_match.rs similarity index 100% rename from src/cmdline_support/cin_search/name_match.rs rename to src/cli_support/cin_search/name_match.rs diff --git a/src/cmdline_support/cin_search/path_builder.rs b/src/cli_support/cin_search/path_builder.rs similarity index 100% rename from src/cmdline_support/cin_search/path_builder.rs rename to src/cli_support/cin_search/path_builder.rs diff --git a/src/cmdline_support/cin_search/path_walker.rs b/src/cli_support/cin_search/path_walker.rs similarity index 99% rename from src/cmdline_support/cin_search/path_walker.rs rename to src/cli_support/cin_search/path_walker.rs index f01115c..5b2e250 100644 --- a/src/cmdline_support/cin_search/path_walker.rs +++ b/src/cli_support/cin_search/path_walker.rs @@ -211,7 +211,7 @@ impl Iterator for PathWalkerV1<'_> { #[cfg(test)] mod tests { use super::*; - use crate::cmdline_support::cin_search::name_match::is_name_match; + use crate::cli_support::cin_search::name_match::is_name_match; use std::env::current_dir; fn _test_path_walker_v1(start: impl Into) { diff --git a/src/cmdline_support/io/mod.rs b/src/cli_support/io/mod.rs similarity index 100% rename from src/cmdline_support/io/mod.rs rename to src/cli_support/io/mod.rs diff --git a/src/cmdline_support/mod.rs b/src/cli_support/mod.rs similarity index 100% rename from src/cmdline_support/mod.rs rename to src/cli_support/mod.rs diff --git a/src/lib.rs b/src/lib.rs index f1c9984..400c41c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ util::mods! { "cin_implements" => pub cin_implements; // 命令行支持 - "cmdline_support" => pub cmdline_support; + "cli_support" => pub cli_support; // 测试工具集 "test_tools" => pub test_tools; diff --git a/src/test_tools/nal_format/mod.rs b/src/test_tools/nal_format/mod.rs index 25fdee9..ab4b86c 100644 --- a/src/test_tools/nal_format/mod.rs +++ b/src/test_tools/nal_format/mod.rs @@ -246,8 +246,11 @@ G3! :|: _test_parse("5"); _test_parse("'这是一个注释"); _test_parse("'/VOL 0"); + _test_parse("'''VOL 0"); _test_parse("''await: OUT B>."); _test_parse("''sleep: 500ms"); + _test_parse("''sleep: 5000μs"); + _test_parse("''sleep: 600ns"); _test_parse(TESTSET); } diff --git a/src/test_tools/nal_format/nal_grammar.pest b/src/test_tools/nal_format/nal_grammar.pest index 886a998..eb7579b 100644 --- a/src/test_tools/nal_format/nal_grammar.pest +++ b/src/test_tools/nal_format/nal_grammar.pest @@ -38,9 +38,10 @@ comment_head = _{ "'" } /// 有关「置入命令」的「魔法注释」 /// * ✨允许构建并向NAVM置入指令 /// * 📄用`'/VOL 0`代替非通用的`*volume=0` +/// * 📄亦可直接用三个`'`:`'''VOL 0` comment_navm_cmd = !{ - // 特殊前缀`/` - "/" ~ WHITESPACE* ~ comment_raw + // 特殊前缀`/`或`''` + ("/" | "''") ~ WHITESPACE* ~ comment_raw } /// 有关「睡眠等待」的「魔法注释」 diff --git a/src/tests/cli/config_opennars.json b/src/tests/cli/config_opennars.json new file mode 100644 index 0000000..620cdd3 --- /dev/null +++ b/src/tests/cli/config_opennars.json @@ -0,0 +1,12 @@ +{ + "translators": "opennars", + "command": { + "cmd": "java", + "cmd_args": [ + "-Xmx1024m", + "-jar", + "nars.jar" + ], + "current_dir": "root/nars/test" + } +} \ No newline at end of file diff --git a/src/tests/cli/config_websocket.json b/src/tests/cli/config_websocket.json new file mode 100644 index 0000000..612abf0 --- /dev/null +++ b/src/tests/cli/config_websocket.json @@ -0,0 +1,6 @@ +{ + "websocket": { + "host": "localhost", + "port": 8080 + } +} \ No newline at end of file From 2e2e5c1eeddf93b4cf2bff0291b0183ecf9be847 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 2 Apr 2024 04:38:15 +0800 Subject: [PATCH 34/59] =?UTF-8?q?feat:=20:construction:=20=E5=A4=A7?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=9A=E5=88=9D=E6=AD=A5=E8=B7=91=E9=80=9A?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=9A=84CIN=E5=8A=A0=E8=BD=BD=E3=80=81?= =?UTF-8?q?=E4=BB=8E=E9=85=8D=E7=BD=AE=E5=90=AF=E5=8A=A8=E3=80=81=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A1=8C=E4=BA=A4=E4=BA=92=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 从应用再回到NAVM.rs,优化完善其API 2. 基本打通CLI整个启动流程(依靠anyhow、serde、clap等库) --- Cargo.lock | 6 +- Cargo.toml | 2 +- src/bin/babelnar_cli/arg_parse.rs | 54 ++++--- src/bin/babelnar_cli/config_launcher.rs | 176 +++++++++++++++++++++ src/bin/babelnar_cli/launch_config.rs | 14 ++ src/bin/babelnar_cli/main.rs | 53 ++++++- src/bin/babelnar_cli/runtime_manage.rs | 76 +++++++++ src/bin/cin_launcher/main.rs | 2 +- src/cin_implements/common/exe.rs | 13 +- src/cin_implements/cxin_js/launcher.rs | 9 +- src/cin_implements/cxin_js/mod.rs | 3 +- src/cin_implements/mod.rs | 31 ++-- src/cin_implements/nars_python/launcher.rs | 3 +- src/cin_implements/nars_python/mod.rs | 2 +- src/cin_implements/ona/launcher.rs | 9 +- src/cin_implements/ona/mod.rs | 2 +- src/cin_implements/openjunars/launcher.rs | 3 +- src/cin_implements/openjunars/mod.rs | 2 +- src/cin_implements/opennars/launcher.rs | 7 +- src/cin_implements/opennars/mod.rs | 2 +- src/cin_implements/pynars/launcher.rs | 3 +- src/cin_implements/pynars/mod.rs | 4 +- src/process_io/io_process.rs | 16 +- src/runtimes/command_vm/runtime.rs | 14 +- src/tests/cli/config_test_ona.json | 9 ++ 25 files changed, 430 insertions(+), 85 deletions(-) create mode 100644 src/bin/babelnar_cli/config_launcher.rs create mode 100644 src/bin/babelnar_cli/runtime_manage.rs create mode 100644 src/tests/cli/config_test_ona.json diff --git a/Cargo.lock b/Cargo.lock index 56dd464..5e236af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.10.0" +version = "0.11.0" dependencies = [ "anyhow", "clap", @@ -437,7 +437,7 @@ dependencies = [ [[package]] name = "nar_dev_utils" -version = "0.24.0" +version = "0.26.1" [[package]] name = "narsese" @@ -449,7 +449,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index 1767092..0a434e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.10.0" +version = "0.11.0" edition = "2021" # Cargo文档参考: diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index ab257c6..6778b35 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -39,6 +39,16 @@ pub struct CliArgs { /// Disable the default configuration file in the same directory as exe #[arg(short, long)] pub disable_default: bool, + + // 禁用用户输入 + // * 禁用用户对程序的交互式输入 + // * 📜默认为`false` + // * 📌行为 + // * 没有 ⇒ `false` + // * 有  ⇒ `true` + /// Disable the user's ability to interact with the program + #[arg(short, long)] + pub no_user_input: bool, } /// 加载配置 @@ -61,6 +71,11 @@ pub fn load_config(args: &CliArgs, default_config_path: impl Into) -> L load_config_extern(&default_config_path.into()) .inspect(|config_extern| result.merge_from(config_extern)); } + // 展示加载的配置 | 以便调试(以防其它地方意外插入别的配置) + match serde_json::to_string(&result) { + Ok(json) => println!("[INFO] 加载的配置: {json}",), + Err(e) => println!("[WARN] 展示加载的配置时出现预期之外的错误: {e}"), + } // 返回 result } @@ -132,28 +147,32 @@ mod tests { // 快捷测试宏 macro_rules! test_arg_parse { - // 成功测试 - { - $( $args:expr => $expected:expr $(;)? )* - } => { - $( - _test_arg_parse(&$args, &$expected); - )* - }; - // 失败测试 - { - $args:expr - } => { - // 直接使用默认构造,解析成功了大概率报错 - _test_arg_parse(&$args, &CliArgs::default()) - }; - } + // 成功测试 + { + $( $args:expr => $expected:expr $(;)? )* + } => { + $( + _test_arg_parse(&$args, &$expected); + )* + }; + // 失败测试 + { + $args:expr + } => { + // 直接使用默认构造,解析成功了大概率报错 + _test_arg_parse(&$args, &CliArgs::default()) + }; + } /// 测试/打印帮助 #[test] fn test_arg_parse_help() { _test_arg_parse(&["--help"], &CliArgs::default()); } + #[test] + fn test_arg_parse_help2() { + _test_arg_parse(&["-h"], &CliArgs::default()); + } /// 测试/成功的解析 #[test] @@ -190,9 +209,8 @@ mod tests { /// 测试/加载配置 mod read_config { - use crate::LaunchConfigWebsocket; - use super::*; + use crate::LaunchConfigWebsocket; /// 测试/加载配置 fn load(args: &[&str]) -> LaunchConfig { diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs new file mode 100644 index 0000000..67ed294 --- /dev/null +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -0,0 +1,176 @@ +//! 用于从「启动参数」启动NAVM运行时 + +use crate::{LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators}; +use anyhow::{anyhow, Ok, Result}; +use babel_nar::{ + cin_implements::{ + common::generate_command, cxin_js, nars_python, ona, openjunars, opennars, pynars, + }, + cli_support::cin_search::name_match::name_match, + runtimes::{ + api::{InputTranslator, IoTranslators}, + CommandVm, OutputTranslator, + }, +}; +use navm::{ + cmd::Cmd, + output::Output, + vm::{VmLauncher, VmRuntime}, +}; + +/// (若缺省)要求用户手动填充配置项 +pub fn polyfill_config_from_user(config: &mut LaunchConfig) { + if config.need_polyfill() { + // TODO: 在有缺省的情况下 手动要求用户输入填补缺省项 + } +} + +/// 从「启动参数」中启动 +/// * 🚩默认所有参数都经过确认 +pub fn launch_by_config(config: LaunchConfig) -> Result { + // 生成虚拟机 + let config_command = config.command.ok_or_else(|| anyhow!("缺少启动命令"))?; + let mut vm = load_command_vm(config_command)?; + + // 配置虚拟机 + if let Some(translators) = config.translators { + // 因为配置函数的设计,此处要暂时借用所有权 + vm = config_launcher_translators(vm, &translators)?; + } + + // 启动虚拟机 + let runtime = vm.launch()?; + Ok(runtime) +} + +/// 从「启动参数/启动命令」启动「命令行虚拟机」 +/// * ❓需要用到「具体启动器实现」吗 +pub fn load_command_vm(config: LaunchConfigCommand) -> Result { + let command = generate_command( + config.cmd, + config.current_dir, + // ↓此处`unwrap_or_default`默认使用一个空数组作为迭代器 + config.cmd_args.unwrap_or_default().into_iter().by_ref(), + ); + let vm = command.into(); + Ok(vm) +} + +/// 从「启动参数/输入输出转译器」配置「命令行虚拟机」 +/// * 🚩【2024-04-02 01:03:54】此处暂时需要**硬编码**现有的CIN实现 +/// * 🏗️后续可能支持定义自定义转译器(long-term) +/// * ⚠️可能会有「转译器没找到/转译器加载失败」等 +/// * 📌【2024-04-02 01:49:46】此处需要暂时借用所有权 +pub fn config_launcher_translators( + vm: CommandVm, + config: &LaunchConfigTranslators, +) -> Result { + let translators = get_translator_by_name(config)?; + Ok(vm.translators(translators)) +} + +/// 从「转译器名」检索「输入输出转译器」 +/// * 🚩继续分派到「输入转译器检索」与「输出转译器检索」 +pub fn get_translator_by_name(config: &LaunchConfigTranslators) -> Result { + let name_i = match config { + LaunchConfigTranslators::Same(input) | LaunchConfigTranslators::Separated { input, .. } => { + input + } + }; + let name_o = match config { + LaunchConfigTranslators::Same(output) + | LaunchConfigTranslators::Separated { output, .. } => output, + }; + Ok(IoTranslators { + input_translator: get_input_translator_by_name(name_i.as_str())?, + output_translator: get_output_translator_by_name(name_o.as_str())?, + }) +} + +/// 输入转译器的索引字典类型 +/// * 📌结构:`[(转译器名, 输入转译器, 输出转译器)]` +pub type TranslatorDict<'a> = &'a [( + &'a str, + fn(Cmd) -> Result, + fn(String) -> Result, +)]; +/// 输入转译器的索引字典 +/// * 🚩静态存储映射,后续遍历可有序可无序 +pub const TRANSLATOR_DICT: TranslatorDict = &[ + ( + "OpenNARS", + opennars::input_translate, + opennars::output_translate, + ), + ("ONA", ona::input_translate, ona::output_translate), + ( + "NARS-Python", + nars_python::input_translate, + nars_python::output_translate, + ), + ( + "NARSPython", + nars_python::input_translate, + nars_python::output_translate, + ), + ("PyNARS", pynars::input_translate, pynars::output_translate), + ( + "OpenJunars", + openjunars::input_translate, + openjunars::output_translate, + ), + ( + "CXinJS", + cxin_js::input_translate, + cxin_js::output_translate, + ), +]; + +pub fn get_input_translator_by_name(cin_name: &str) -> Result> { + // 根据「匹配度」的最大值选取 + let translator = TRANSLATOR_DICT + .iter() + .max_by_key(|(name, _, _)| name_match(name, cin_name)) + .ok_or_else(|| anyhow!("未找到输入转译器"))? + .1; // 输入转译器 + Ok(Box::new(translator)) +} + +pub fn get_output_translator_by_name(cin_name: &str) -> Result> { + // 根据「匹配度」的最大值选取 + let translator = TRANSLATOR_DICT + .iter() + .max_by_key(|(name, _, _)| name_match(name, cin_name)) + .ok_or_else(|| anyhow!("未找到输出转译器"))? + .2; // 输出转译器 + Ok(Box::new(translator)) +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use nar_dev_utils::{asserts, f_parallel}; + + #[test] + fn t() { + dbg!(format!("{:p}", opennars::input_translate as fn(_) -> _)); + } + + /// 测试 + /// * 🚩仅能测试「是否查找成功」,无法具体地比较函数是否相同 + /// * 📝函数在被装进[`Box`]后,对原先结构的完整引用就丧失了 + #[test] + fn test() { + fn t(name: &str) { + asserts! { + get_input_translator_by_name(name).is_ok() + get_output_translator_by_name(name).is_ok() + } + } + f_parallel![ + t; + "opennars"; "ona"; "nars-python"; "narsPython"; "pynars"; "openjunars"; "cxinJS" + ]; + } +} diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs index 19fcd23..e46693c 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/launch_config.rs @@ -154,6 +154,18 @@ impl LaunchConfig { serde_json::from_str(json) } + /// 判断其自身是否需要用户填充 + /// * 🎯用于在「启动NAVM运行时」时避免「参数无效」情况 + /// * 🚩判断「启动时必要项」是否为空 + pub fn need_polyfill(&self) -> bool { + // 启动命令非空 + self.command.is_none() || + // 输入输出转译器非空 + self.translators.is_none() + // ! Websocket为空⇒不启动Websocket服务器 + // ! 预加载NAL为空⇒不预加载NAL + } + /// 从另一个配置中并入配置 /// * 🚩合并逻辑:`Some(..)` => `None` /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 @@ -180,6 +192,8 @@ impl LaunchConfigCommand { /// * 🚩`Some(..)` => `None` /// * 适用于自身为[`Option`]的情况 pub fn merge_as_key(option: &mut Option, other: &Option) { + // 先处理「自身为`None`」的情况 + option.coalesce_clone(other); // 双重`inspect` if let (Some(config_self), Some(config_other)) = (option, other) { config_self.merge_from(config_other); diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index e375bfb..3f1dc16 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -6,11 +6,11 @@ //! ``` //! usage: BabelNAR [OPTIONS] //! ``` -//! -//! TODO: 配置读取、预加载 use clap::Parser; use std::io::Result as IoResult; +use std::thread::sleep; +use std::time::Duration; use std::{env, path::PathBuf}; nar_dev_utils::mods! { @@ -18,8 +18,13 @@ nar_dev_utils::mods! { use launch_config; // 命令行解析 use arg_parse; + // 从参数启动 + use config_launcher; + // 运行时交互、管理 + use runtime_manage; } +/// 主入口 pub fn main() { // 以默认参数启动 main_args(env::current_dir(), env::args()) @@ -30,14 +35,50 @@ pub fn main() { /// * 🚩【2024-04-01 14:25:38】暂时用不到「当前工作路径」 pub fn main_args(_cwd: IoResult, args: impl Iterator) { let args = CliArgs::parse_from(args); - dbg!(&args); // 读取配置 | with 默认配置文件 - let config = load_config(&args, DEFAULT_CONFIG_PATH); - dbg!(config); + let mut config = load_config(&args, DEFAULT_CONFIG_PATH); + // 用户填充配置项 + polyfill_config_from_user(&mut config); + // 从配置项启动 + let runtime = match launch_by_config(config) { + // 启动成功⇒返回 + Ok(runtime) => runtime, + // 启动失败⇒打印错误信息,等待并退出 + Err(e) => { + println!("NARS运行时启动错误:{e}"); + println!("程序将在 3 秒后自动退出。。。"); + sleep(Duration::from_secs(3)); + return; + } + }; + // 运行时交互、管理 + if let Err(e) = manage(runtime, &args) { + println!("运行时发生错误:{e}"); + } + // 最终退出 + println!("程序将在 5 秒后退出"); + sleep(Duration::from_secs(5)); } /// 单元测试 #[cfg(test)] mod tests { - // use super::*; + use super::*; + + /// 测试入口 + #[test] + pub fn main_ona() { + // 以默认参数启动 + main_args( + env::current_dir(), + [ + "test.exe", + "-d", + "-c", + "./src/tests/cli/config_test_ona.json", + ] + .into_iter() + .map(str::to_string), + ) + } } diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs new file mode 100644 index 0000000..afd8c96 --- /dev/null +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -0,0 +1,76 @@ +//! 启动后运行时的(交互与)管理 + +use crate::CliArgs; +use anyhow::Result; +use nar_dev_utils::if_return; +use navm::{cmd::Cmd, output::Output, vm::VmRuntime}; +use std::{fmt::Debug, io::Result as IoResult}; + +/// 读取行迭代器 +/// * 🚩每迭代一次,请求用户输入一行 +/// * ✨自动清空缓冲区 +/// * ❌无法在【不复制字符串】的情况下实现「迭代出所输入内容」的功能 +/// * ❌【2024-04-02 03:49:56】无论如何都无法实现:迭代器物件中引入就必须碰生命周期 +/// * 🚩最终仍需复制字符串:调用处方便使用 +/// * ❓是否需要支持提示词 +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ReadlineIter { + pub buffer: String, +} + +/// 实现迭代器 +impl Iterator for ReadlineIter { + type Item = IoResult; + + fn next(&mut self) -> Option { + // 清空缓冲区 + self.buffer.clear(); + // 读取一行 + // * 📝此处的`stdin`是懒加载的 + if let Err(e) = std::io::stdin().read_line(&mut self.buffer) { + return Some(Err(e)); + } + // 返回 + Some(IoResult::Ok(self.buffer.clone())) + } +} + +/// 打印错误 +fn println_error(e: &impl Debug) { + println!("{e:?}"); +} + +/// 在运行时启动后,对其进行管理 +/// * 🚩`.nal`脚本预加载逻辑 +/// * 🚩用户的运行时交互逻辑 +/// * 🚩Websocket服务器逻辑 +pub fn manage(mut nars: impl VmRuntime, args: &CliArgs) -> Result<()> { + // TODO: 优化并行逻辑 + // TODO: 结合test_tools + if_return! { args.no_user_input => Ok(()) } + + // 用户输入主循环 + 'main: for io_result in ReadlineIter::default() { + // 读取一行 + let line = io_result?; + + // 非空⇒解析出NAVM指令,作为输入执行 + if !line.trim().is_empty() { + if let Ok(cmd) = Cmd::parse(&line).inspect_err(println_error) { + let _ = nars.input_cmd(cmd).inspect_err(println_error); + } + } + + // 尝试拉取所有NAVM运行时输出 + while let Ok(Some(output)) = nars.try_fetch_output().inspect_err(println_error) { + println!("{output:?}"); + if let Output::TERMINATED { .. } = output { + println!("NAVM已终止运行,正在重启。。。"); + nars.terminate()?; + break 'main; // ! 这个告诉Rust编译器,循环必将在此结束 + } + } + } + // 正常运行结束 + Ok(()) +} diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 0a2dd3b..9b46a8e 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -44,7 +44,7 @@ fn main() { /// 开始 fn start() { - let nars = get_nars().launch(); + let nars = get_nars().launch().expect("无法启动虚拟机"); shell(nars); } diff --git a/src/cin_implements/common/exe.rs b/src/cin_implements/common/exe.rs index c941880..0cccd31 100644 --- a/src/cin_implements/common/exe.rs +++ b/src/cin_implements/common/exe.rs @@ -13,13 +13,16 @@ use std::{ffi::OsStr, path::Path, process::Command}; /// * exe路径(可能不直接是可执行文件的路径) /// * 当前文件夹(设置命令启动时的工作目录) /// * 命令行参数(可以为空) -pub fn generate_command( - exe_path: impl AsRef, +pub fn generate_command( + exe_path: impl AsRef, current_dir: Option>, - args: &[&str], -) -> Command { + args: impl Iterator, +) -> Command +where + S: AsRef, +{ // 构造指令 - let mut command = Command::new(exe_path); + let mut command = Command::new(exe_path.as_ref()); // 设置路径 if let Some(current_dir) = current_dir { diff --git a/src/cin_implements/cxin_js/launcher.rs b/src/cin_implements/cxin_js/launcher.rs index 8aa895b..d28286d 100644 --- a/src/cin_implements/cxin_js/launcher.rs +++ b/src/cin_implements/cxin_js/launcher.rs @@ -8,6 +8,7 @@ use crate::{ cin_implements::common::{generate_command_vm, CommandGeneratorNodeJS}, runtimes::{CommandGenerator, CommandVmRuntime}, }; +use anyhow::Result; use navm::vm::VmLauncher; use std::path::PathBuf; use util::pipe; @@ -39,9 +40,9 @@ impl CXinJS { /// 启动到「命令行运行时」 impl VmLauncher for CXinJS { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造并启动虚拟机 - let runtime = pipe! { + pipe! { self.command_generator // 构造指令 | 预置的指令参数 => .generate_command() @@ -49,9 +50,7 @@ impl VmLauncher for CXinJS { => generate_command_vm(_, (input_translate, output_translate)) // 🔥启动 => .launch() - }; - - runtime + } } } diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs index b65cda8..6274245 100644 --- a/src/cin_implements/cxin_js/mod.rs +++ b/src/cin_implements/cxin_js/mod.rs @@ -29,11 +29,12 @@ mod tests { const CXIN_NARS_JS_PATH: &str = r"..\cxin-nars-py-to-ts\src\cxin-nars-shell.js"; /// 通用/启动VM + /// * 🚩【2024-04-02 04:16:04】测试用代码无需返回[`Result`] fn launch_vm() -> CommandVmRuntime { // 从别的地方获取js路径 let js_path = CXIN_NARS_JS_PATH; // 一行代码启动CxinNARS - CXinJS::new(js_path).launch() + CXinJS::new(js_path).launch().expect("无法启动虚拟机") } /// 测试/专用 diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs index 1c8c71c..5ab3cfd 100644 --- a/src/cin_implements/mod.rs +++ b/src/cin_implements/mod.rs @@ -18,23 +18,26 @@ //! * ❌不希望因此再全小写/封装命名空间,如`impls::ona::new` //! * ❓目前的问题:在Rust基于「特征」的组合式设计哲学下,如何进行兼顾三者的优秀设计 -// 共用代码 -mod common; +util::mods! { + // 共用代码 + pub common; -// OpenNARS -pub mod opennars; + // OpenNARS + pub opennars; -// ONA -pub mod ona; + // ONA + pub ona; -// NARS-Python -pub mod nars_python; + // NARS-Python + pub nars_python; -// PyNARS -pub mod pynars; + // PyNARS + pub pynars; -// OpenJunars -pub mod openjunars; + // OpenJunars + pub openjunars; -// CXinNARS.js -pub mod cxin_js; + // CXinNARS.js + pub cxin_js; + +} diff --git a/src/cin_implements/nars_python/launcher.rs b/src/cin_implements/nars_python/launcher.rs index 41d9f6a..e85a175 100644 --- a/src/cin_implements/nars_python/launcher.rs +++ b/src/cin_implements/nars_python/launcher.rs @@ -5,6 +5,7 @@ use super::{input_translate, output_translate}; use crate::runtimes::{CommandVm, CommandVmRuntime}; +use anyhow::Result; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -34,7 +35,7 @@ impl NARSPython { /// 启动到「命令行运行时」 impl VmLauncher for NARSPython { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造指令,并启动虚拟机 CommandVm::new(self.exe_path) // * 🚩固定的「输入输出转换器」 diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index 7780aaa..bf8e33f 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -28,7 +28,7 @@ mod tests { // 从别的地方获取exe路径 let exe_path = EXE_PATH_NARS_PYTHON; // 一行代码启动NARS-Python - let vm = NARSPython::new(exe_path).launch(); + let vm = NARSPython::new(exe_path).launch().expect("无法启动虚拟机"); // 运行专有测试 _test_nars_python(vm) } diff --git a/src/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs index 7d3f543..6bb399c 100644 --- a/src/cin_implements/ona/launcher.rs +++ b/src/cin_implements/ona/launcher.rs @@ -8,6 +8,7 @@ use crate::{ cin_implements::common::{generate_command, generate_command_vm}, runtimes::CommandVmRuntime, }; +use anyhow::Result; use navm::{ cmd::Cmd, vm::{VmLauncher, VmRuntime}, @@ -50,17 +51,17 @@ impl ONA { /// 启动到「命令行运行时」 impl VmLauncher for ONA { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造并启动虚拟机 let mut runtime = pipe! { self.exe_path // 构造指令 | 预置的指令参数 - => generate_command(_, None::, &COMMAND_ARGS_ONA) + => generate_command(_, None::, COMMAND_ARGS_ONA.into_iter().by_ref()) // * 🚩固定的「输入输出转换器」 => generate_command_vm(_, (input_translate, output_translate)) // 🔥启动 => .launch() - }; + }?; // 选择性设置初始音量 if let Some(volume) = self.initial_volume { @@ -69,7 +70,7 @@ impl VmLauncher for ONA { println!("无法设置初始音量「{volume}」:{e}"); } }; - runtime + Ok(runtime) } } diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs index 464bd9f..b86f0f3 100644 --- a/src/cin_implements/ona/mod.rs +++ b/src/cin_implements/ona/mod.rs @@ -27,7 +27,7 @@ mod tests { // 从别的地方获取exe路径 let exe_path = EXE_PATH_ONA; // 一行代码启动ONA - ONA::new(exe_path).launch() + ONA::new(exe_path).launch().expect("无法启动虚拟机") } #[test] diff --git a/src/cin_implements/openjunars/launcher.rs b/src/cin_implements/openjunars/launcher.rs index 4757c8a..81fb6cb 100644 --- a/src/cin_implements/openjunars/launcher.rs +++ b/src/cin_implements/openjunars/launcher.rs @@ -9,6 +9,7 @@ use crate::{ cin_implements::common::CommandGeneratorJulia, runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; +use anyhow::Result; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -36,7 +37,7 @@ impl OpenJunars { /// 启动到「命令行运行时」 impl VmLauncher for OpenJunars { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造指令 let command = self.command_generator.generate_command(); diff --git a/src/cin_implements/openjunars/mod.rs b/src/cin_implements/openjunars/mod.rs index 7dc6d19..aed3e5c 100644 --- a/src/cin_implements/openjunars/mod.rs +++ b/src/cin_implements/openjunars/mod.rs @@ -28,7 +28,7 @@ mod tests { // 从别的地方获取jl路径 let jl_path = JL_PATH_OPEN_JUNARS; // 一行代码启动OpenJunars - let vm = OpenJunars::new(jl_path).launch(); + let vm = OpenJunars::new(jl_path).launch().expect("无法启动虚拟机"); // 运行专有测试 // ! ❌【2024-03-25 13:56:21】目前无法截取到Julia运行时输出,弃用 // _test_opennars(vm) diff --git a/src/cin_implements/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs index 8d3e46d..ad3a265 100644 --- a/src/cin_implements/opennars/launcher.rs +++ b/src/cin_implements/opennars/launcher.rs @@ -9,6 +9,7 @@ use crate::{ cin_implements::common::CommandGeneratorJava, runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; +use anyhow::Result; use navm::{ cmd::Cmd, vm::{VmLauncher, VmRuntime}, @@ -42,7 +43,7 @@ impl OpenNARS { /// 启动到「命令行运行时」 impl VmLauncher for OpenNARS { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造指令 // * 🚩细致的Java参数配置,都外包给[`CommandGeneratorJava`] let command_java = self.command_generator.generate_command(); @@ -53,7 +54,7 @@ impl VmLauncher for OpenNARS { .input_translator(input_translate) .output_translator(output_translate) // 🔥启动 - .launch(); + .launch()?; // 设置初始音量 if let Some(volume) = self.initial_volume { @@ -64,7 +65,7 @@ impl VmLauncher for OpenNARS { }; // 返回 - vm + Ok(vm) } } diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs index b94f7cb..47e3b74 100644 --- a/src/cin_implements/opennars/mod.rs +++ b/src/cin_implements/opennars/mod.rs @@ -27,7 +27,7 @@ mod tests { // 从别的地方获取jar路径 let jar_path = JAR_PATH_OPENNARS; // 一行代码启动OpenNARS - OpenNARS::new(jar_path).launch() + OpenNARS::new(jar_path).launch().expect("无法启动虚拟机") } #[test] diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs index 6004390..d153764 100644 --- a/src/cin_implements/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -10,6 +10,7 @@ use crate::{ cin_implements::common::CommandGeneratorPython, runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; +use anyhow::Result; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -35,7 +36,7 @@ impl PyNARS { /// 启动到「命令行运行时」 impl VmLauncher for PyNARS { - fn launch(self) -> CommandVmRuntime { + fn launch(self) -> Result { // 构造指令 let command = self.command_generator.generate_command(); diff --git a/src/cin_implements/pynars/mod.rs b/src/cin_implements/pynars/mod.rs index db56f39..a58d252 100644 --- a/src/cin_implements/pynars/mod.rs +++ b/src/cin_implements/pynars/mod.rs @@ -30,7 +30,9 @@ mod tests { let root_path = MODULE_ROOT_PYNARS; let module_path = MODULE_PATH_PYNARS; // 一行代码启动PyNARS | `python -m pynars.Console` @ "..\..\PyNARS-dev" - PyNARS::new(root_path, module_path).launch() + PyNARS::new(root_path, module_path) + .launch() + .expect("无法启动虚拟机") } /// 测试/先前PyNARS测试 diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 5497f11..57da15e 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -90,15 +90,11 @@ impl IoProcess { /// 启动 /// * 🚩通过[`Self::try_launch`]尝试启动,然后直接解包 - /// - /// # Panics - /// * 📌如果子进程创建失败,将直接 panic - pub fn launch(self) -> IoProcessManager { - self - // 尝试启动 - .try_launch() - //解包 - .expect("无法启动子进程") + /// * 🚩【2024-04-02 04:11:27】现在为方便反馈处理错误,重新变为[`Result`]类型 + /// * 📄路径问题:启动路径不合法 等 + pub fn launch(self) -> Result { + // 尝试启动 + Ok(self.try_launch()?) } /// 启动 @@ -519,7 +515,7 @@ pub(crate) mod tests { print!("[OUT] {}", output); }); // 启动子进程并返回 - (process.launch(), outputs) + (process.launch().expect("ONA启动失败"), outputs) } /// 标准案例:ONA交互 diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 4a7352a..98d19fe 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -63,10 +63,10 @@ impl VmRuntime for CommandVmRuntime { /// 构建功能:启动命令行虚拟机 impl VmLauncher for CommandVm { - fn launch(self) -> CommandVmRuntime { - CommandVmRuntime { + fn launch(self) -> Result { + Ok(CommandVmRuntime { // 启动内部的「进程管理者」 - process: self.io_process.launch(), + process: self.io_process.launch()?, // 输入转译器 input_translator: self .input_translator @@ -78,7 +78,7 @@ impl VmLauncher for CommandVm { // 默认值:直接归入「其它」输出 | 约等于不分类 .unwrap_or(Box::new(|content| Ok(Output::OTHER { content }))), // * 🚩【2024-03-24 02:06:59】目前到此为止:只需处理「转译」问题 - } + }) } } @@ -377,7 +377,8 @@ pub mod tests { // 输出转译器 .output_translator(output_translate) // 🔥启动 - .launch(); + .launch() + .expect("无法启动虚拟机"); _test_opennars(vm); } @@ -410,7 +411,8 @@ pub mod tests { // 输入转译器:直接取其尾部 .input_translator(|cmd| Ok(cmd.tail())) // 🔥启动 - .launch(); + .launch() + .expect("无法启动虚拟机"); // 可复用的测试逻辑 _test_pynars(vm); } diff --git a/src/tests/cli/config_test_ona.json b/src/tests/cli/config_test_ona.json new file mode 100644 index 0000000..99160d4 --- /dev/null +++ b/src/tests/cli/config_test_ona.json @@ -0,0 +1,9 @@ +{ + "translators": "ona", + "command": { + "cmd": "H:/A137442/Develop/AGI/NARS/NARS-executables/NAR.exe", + "cmd_args": [ + "shell" + ] + } +} \ No newline at end of file From fe960a1ed2efef1be97d625de595732fae5f3739 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:09:48 +0800 Subject: [PATCH 35/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=A4=A7=E6=9B=B4?= =?UTF-8?q?=E6=96=B02=EF=BC=9ACLI=E8=BF=9B=E4=B8=80=E6=AD=A5=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ✅较为完善的输出系统与格式化彩色CLI输出 2. ✅基本完整的「虚拟机实例管理者」模型,在此之上加入「用户输入」「输入模式」「自动重启」等附加功能 3. 📍预期将旧项目「CIN Launcher」修缮为一个Rust API Playground,用于敏捷开发 4. ♻️规范错误类型与输出逻辑,尽可能减少直接的println!与anyhow!调用 5. 🐞修复「ONA输出转译器误认『操作名 executed by NAR』为『EXE』输出」的漏洞 6. ✅通过「置字段类型为Option」解决「IO进程terminate调用必须消耗自身所有权」的问题,与此同时为NAVM API松绑(不再需要传入所有权,而是像Drop那样只需传入可变引用) 7. 🐞修复「NAL交互输入时末尾换行符(\r)未截取,导致NAL输入解析失败」的漏洞 - 🚧NAL输入有关「输出预期」的逻辑 - 🚧NAVM实例管理有关「Websocket 」的逻辑 --- .gitignore | 3 + BabelNAR.launch.json | 23 +- Cargo.lock | 4 +- Cargo.toml | 2 +- src/bin/babelnar_cli/arg_parse.rs | 41 +-- src/bin/babelnar_cli/launch_config.rs | 172 +++++++-- src/bin/babelnar_cli/main.rs | 23 +- src/bin/babelnar_cli/runtime_manage.rs | 341 ++++++++++++++++-- src/bin/cin_launcher/main.rs | 30 +- src/cin_implements/cxin_js/translators.rs | 4 +- src/cin_implements/ona/translators.rs | 15 +- src/cin_implements/opennars/translators.rs | 7 +- src/cli_support/error_handling_boost.rs | 36 ++ src/cli_support/io/mod.rs | 8 +- src/cli_support/io/output_print.rs | 181 ++++++++++ src/cli_support/mod.rs | 3 + src/process_io/io_process.rs | 50 ++- src/runtimes/command_vm/runtime.rs | 2 +- src/test_tools/nal_format/mod.rs | 15 +- src/test_tools/vm_interact.rs | 2 +- src/tests/cli/config/matriangle_server.json | 8 + .../opennars.json} | 4 +- src/tests/cli/config/test_ona.json | 11 + .../websocket.json} | 1 + src/tests/cli/config_test_ona.json | 9 - 25 files changed, 834 insertions(+), 161 deletions(-) create mode 100644 src/cli_support/error_handling_boost.rs create mode 100644 src/cli_support/io/output_print.rs create mode 100644 src/tests/cli/config/matriangle_server.json rename src/tests/cli/{config_opennars.json => config/opennars.json} (71%) create mode 100644 src/tests/cli/config/test_ona.json rename src/tests/cli/{config_websocket.json => config/websocket.json} (54%) delete mode 100644 src/tests/cli/config_test_ona.json diff --git a/.gitignore b/.gitignore index b716794..1f6dc99 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ # 可执行文件 executables/ + +# 临时文件 +*tempCodeRunnerFile* \ No newline at end of file diff --git a/BabelNAR.launch.json b/BabelNAR.launch.json index cecdb86..fe4428c 100644 --- a/BabelNAR.launch.json +++ b/BabelNAR.launch.json @@ -1,19 +1,20 @@ { - "translators": "opennars", + "description": "测试「默认加载」逻辑", + "translators": "默认", "command": { - "cmd": "java", - "cmd_args": [ - "-Xmx1024m", - "-jar", - "nars.jar" + "cmd": "默认", + "cmdArgs": [ + "-默认", + "-默认", + "默认" ], - "current_dir": "root/nars/test" + "currentDir": "默认" }, "websocket": { - "host": "localhost", - "port": 8080 + "host": "默认", + "port": 8848 }, - "prelude_nal": { - "text": "'/VOL 0" + "preludeNAL": { + "text": "'默认" } } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5e236af..8aad07b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "clap", @@ -449,7 +449,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index 0a434e4..1445d3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.11.0" +version = "0.12.0" edition = "2021" # Cargo文档参考: diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index 6778b35..841847d 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -3,6 +3,7 @@ use crate::launch_config::LaunchConfig; use anyhow::Result; +use babel_nar::println_cli; use clap::Parser; use nar_dev_utils::{pipe, ResultBoost}; use std::{fs::read_to_string, path::PathBuf}; @@ -39,16 +40,7 @@ pub struct CliArgs { /// Disable the default configuration file in the same directory as exe #[arg(short, long)] pub disable_default: bool, - - // 禁用用户输入 - // * 禁用用户对程序的交互式输入 - // * 📜默认为`false` - // * 📌行为 - // * 没有 ⇒ `false` - // * 有  ⇒ `true` - /// Disable the user's ability to interact with the program - #[arg(short, long)] - pub no_user_input: bool, + // ! 🚩【2024-04-02 11:36:18】目前除了「配置加载」外,莫将任何「NAVM实现特定,可以内置到『虚拟机配置』的字段放这儿」 } /// 加载配置 @@ -73,8 +65,8 @@ pub fn load_config(args: &CliArgs, default_config_path: impl Into) -> L } // 展示加载的配置 | 以便调试(以防其它地方意外插入别的配置) match serde_json::to_string(&result) { - Ok(json) => println!("[INFO] 加载的配置: {json}",), - Err(e) => println!("[WARN] 展示加载的配置时出现预期之外的错误: {e}"), + Ok(json) => println_cli!([Log] "外部配置已加载:{json}",), + Err(e) => println_cli!([Warn] "展示加载的配置时出现预期之外的错误: {e}"), } // 返回 result @@ -90,19 +82,19 @@ pub fn load_config_extern(path: &PathBuf) -> Option { if let Some(e) = e.downcast_ref::() { match e.kind() { std::io::ErrorKind::NotFound => { - println!("[WARN] 未找到外部配置,使用空配置……"); + println_cli!([Warn] "未找到外部配置,使用空配置……"); } - _ => println!("[WARN] 读取外部配置时出现预期之外的错误: {}", e), + _ => println_cli!([Warn] "读取外部配置时出现预期之外的错误: {}", e), } } else if let Some(e) = e.downcast_ref::() { match e.classify() { serde_json::error::Category::Syntax => { - println!("[WARN] 外部配置文件格式错误,使用空配置……"); + println_cli!([Warn] "外部配置文件格式错误,使用空配置……"); } - _ => println!("[WARN] 解析外部配置时出现预期之外的错误: {}", e), + _ => println_cli!([Warn] "解析外部配置时出现预期之外的错误: {}", e), } } else { - println!("[WARN] 加载外部配置时出现预期之外的错误: {}", e) + println_cli!([Warn] "加载外部配置时出现预期之外的错误: {}", e) } // 空置 }) @@ -178,9 +170,9 @@ mod tests { #[test] fn test_arg_parse() { test_arg_parse! { - ["-c", "./src/tests/cli/config_opennars.json"] + ["-c", "./src/tests/cli/config/opennars.json"] => CliArgs { - config: vec!["./src/tests/cli/config_opennars.json".into()], + config: vec!["./src/tests/cli/config/opennars.json".into()], ..Default::default() }; // 多个配置:重复使用`-c`/`--config`,按使用顺序填充 @@ -238,7 +230,7 @@ mod tests { // 成功测试 test! { // 单个配置文件 - ["-c" "src/tests/cli/config_opennars.json" "-d"] => LaunchConfig { + ["-c" "src/tests/cli/config/opennars.json" "-d"] => LaunchConfig { translators: Some( crate::LaunchConfigTranslators::Same( "opennars".into(), @@ -247,8 +239,9 @@ mod tests { command: None, websocket: None, prelude_nal: None, + ..Default::default() }; - ["-c" "src/tests/cli/config_websocket.json" "-d"] => LaunchConfig { + ["-c" "src/tests/cli/config/websocket.json" "-d"] => LaunchConfig { translators: None, command: None, websocket: Some(LaunchConfigWebsocket { @@ -256,12 +249,13 @@ mod tests { port: 8080, }), prelude_nal: None, + ..Default::default() }; // 两个配置文件合并 [ "-d" - "-c" "src/tests/cli/config_opennars.json" - "-c" "src/tests/cli/config_websocket.json" + "-c" "src/tests/cli/config/opennars.json" + "-c" "src/tests/cli/config/websocket.json" ] => LaunchConfig { translators: Some( crate::LaunchConfigTranslators::Same( @@ -274,6 +268,7 @@ mod tests { port: 8080, }), prelude_nal: None, + ..Default::default() } } } diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs index e46693c..6072b8d 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/launch_config.rs @@ -28,38 +28,51 @@ //! //! ```ts //! type LaunchConfig = { -//! translators?: LaunchConfigTranslators; -//! command?: LaunchConfigCommand; -//! websocket?: LaunchConfigWebsocket; -//! prelude_nal?: LaunchConfigPreludeNAL; +//! translators?: LaunchConfigTranslators, +//! command?: LaunchConfigCommand, +//! websocket?: LaunchConfigWebsocket, +//! preludeNAL?: LaunchConfigPreludeNAL, +//! userInput?: boolean +//! inputMode?: InputMode +//! autoRestart?: boolean //! } //! +//! type InputMode = 'cmd' | 'nal' +//! //! type LaunchConfigTranslators = string | { //! // ↓虽然`in`是JavaScript/TypeScript/Rust的关键字,但仍可在此直接使用 -//! in: string; -//! out: string; -//! }; +//! in: string, +//! out: string, +//! } //! //! type LaunchConfigCommand = { -//! cmd: string; -//! cmd_args?: string[]; -//! current_dir?: string; +//! cmd: string, +//! cmdArgs?: string[], +//! currentDir?: string, //! } //! type LaunchConfigWebsocket = { -//! host: string; -//! port: number; +//! host: string, +//! port: number, // Uint16 //! } //! // ↓ 文件、纯文本 二选一 //! type LaunchConfigPreludeNAL = { -//! file?: string; -//! text?: string; +//! file?: string, +//! text?: string, //! } //! ``` +use std::path::PathBuf; + use nar_dev_utils::OptionBoost; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +/// NAVM虚拟机(运行时)启动配置 +/// * 🎯启动完整的NAVM实例,并附带相关运行时配置 +/// * ✨启动时数据提供 +/// * ✨运行时数据提供 +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, PartialEq, Eq)] pub struct LaunchConfig { /// 转译器组合(可选) /// * 🚩使用字符串模糊匹配 @@ -72,14 +85,92 @@ pub struct LaunchConfig { pub websocket: Option, /// 预置NAL(可选) + #[serde(rename = "preludeNAL")] // * 📝serde配置中,`rename`优先于`rename_all` pub prelude_nal: Option, + + /// 启用用户输入(可选) + /// * 🎯控制该实例是否需要(来自用户的)交互式输入 + /// * 📜默认值:`true` + /// * 📝serde中,若不使用`bool::default`(false),需要指定一个函数来初始化 + /// * ⚠️即便在[`LaunchConfig`]中定义了[`default`],也会使用[`bool::default`] + #[serde(default = "bool_true")] + pub user_input: bool, + + /// 输入模式 + /// * 🚩对输入(不论交互还是Websocket)采用的解析模式 + /// * 📄用于纯NAVM指令(可选)的解析 + /// * 🎯用于兼容旧`BabelNAR.jl`服务端 + /// * 📜默认为`"nal"` + /// Disable the user's ability to interact with the program + #[serde(default)] + pub input_mode: InputMode, + + /// 自动重启 + /// * 🚩在虚拟机终止(收到「终止」输出)时,自动用配置重启虚拟机 + /// * 📜默认为`false`(关闭) + #[serde(default = "bool_false")] + pub auto_restart: bool, +} + +/// 布尔值`true` +/// * 🎯配置解析中「默认为`true`」的默认值指定 +/// * 📝serde中,`#[serde(default)]`使用的是[`bool::default`]而非容器的`default` +/// * 因此需要指定一个函数来初始化 +#[inline(always)] +const fn bool_true() -> bool { + true +} + +#[inline(always)] +const fn bool_false() -> bool { + false +} + +impl Default for LaunchConfig { + fn default() -> Self { + Self { + // [`Option`]全部为[`None`] + translators: None, + command: None, + websocket: None, + prelude_nal: None, + // 默认启用用户输入 + user_input: true, + // 输入模式传递默认值 + input_mode: InputMode::default(), + // 不自动重启 + auto_restart: false, + } + } +} + +/// NAVM实例的输入类型 +/// * 🎯处理用户输入、Websocket输入的解析方式 +/// * 📜默认值:`nal` +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +// #[serde(untagged)] // ! 🚩【2024-04-02 18:14:16】不启用方通过:本质上是几个字符串里选一个 +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum InputMode { + /// (NAVM)指令 + /// * 📄类型:[`navm::cmd::Cmd`] + #[serde(rename = "cmd")] + Cmd, + /// `.nal`输入 + /// * 📜默认值 + /// * 📄类型:[`babel_nar::test_tools::NALInput`] + #[serde(rename = "nal")] + #[default] + Nal, } /// 转译器组合 /// * 🚩【2024-04-01 11:20:36】目前使用「字符串+内置模糊匹配」进行有限的「转译器支持」 /// * 🚧尚不支持自定义转译器 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] #[serde(untagged)] // 🔗参考: +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, PartialEq, Eq)] pub enum LaunchConfigTranslators { /// 🚩单个字符串⇒输入输出使用同一个转译配置 Same(String), @@ -95,7 +186,9 @@ pub enum LaunchConfigTranslators { /// 启动命令 /// * ❓后续可能支持「自动搜索」 -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LaunchConfigCommand { /// 命令 /// * 直接对应[`std::process::Command`] @@ -111,7 +204,9 @@ pub struct LaunchConfigCommand { } /// Websocket参数 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LaunchConfigWebsocket { /// 主机地址 /// * 📄`localhost` @@ -129,14 +224,19 @@ pub struct LaunchConfigWebsocket { /// 预置NAL /// * 🚩在CLI启动后自动执行 /// * 📝[`serde`]允许对枚举支持序列化/反序列化 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, PartialEq, Eq)] pub enum LaunchConfigPreludeNAL { /// 从文件路径导入 /// * 📌键名:`file` + /// * 📌类型:路径 #[serde(rename = "file")] - File(String), + File(PathBuf), + /// 从文本解析 /// * 📌键名:`text` + /// * 📌类型:纯文本(允许换行等) #[serde(rename = "text")] Text(String), } @@ -167,6 +267,7 @@ impl LaunchConfig { } /// 从另一个配置中并入配置 + /// * 📌优先级:`other` > `self` /// * 🚩合并逻辑:`Some(..)` => `None` /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 /// * ✨对【内部含有可选键】的值,会**递归深入** @@ -175,6 +276,10 @@ impl LaunchConfig { self.translators.coalesce_clone(&other.translators); self.prelude_nal.coalesce_clone(&other.prelude_nal); self.websocket.coalesce_clone(&other.websocket); + // ! 覆盖所有【必定有】的值 | 如:布尔值 + self.user_input = other.user_input; + self.input_mode = other.input_mode; + self.auto_restart = other.auto_restart; // 递归合并所有【含有可选键】的值 LaunchConfigCommand::merge_as_key(&mut self.command, &other.command); } @@ -225,6 +330,7 @@ mod tests { Ok(()) } + /// 主测试 #[test] fn main() { test! { @@ -237,14 +343,14 @@ mod tests { "translators": "opennars", "command": { "cmd": "java", - "cmd_args": ["-Xmx1024m", "-jar", "nars.jar"], - "current_dir": "root/nars/test" + "cmdArgs": ["-Xmx1024m", "-jar", "nars.jar"], + "currentDir": "root/nars/test" }, "websocket": { "host": "localhost", "port": 8080 }, - "prelude_nal": { + "preludeNAL": { "text": "'/VOL 0" } }"# => LaunchConfig { @@ -258,7 +364,8 @@ mod tests { host: "localhost".into(), port: 8080 }), - prelude_nal: Some(LaunchConfigPreludeNAL::Text("'/VOL 0".into())) + prelude_nal: Some(LaunchConfigPreludeNAL::Text("'/VOL 0".into())), + ..Default::default() } // 测试`translators`、`prelude_nal`的其它枚举 r#" @@ -270,7 +377,7 @@ mod tests { "command": { "cmd": "root/nars/open_ona.exe" }, - "prelude_nal": { + "preludeNAL": { "file": "root/nars/prelude.nal" } }"# => LaunchConfig { @@ -285,6 +392,21 @@ mod tests { prelude_nal: Some(LaunchConfigPreludeNAL::File("root/nars/prelude.nal".into())), ..Default::default() } + r#" + { + "inputMode": "cmd" + }"# => LaunchConfig { + input_mode: InputMode::Cmd, + ..Default::default() + } + r#"{ + "autoRestart": true, + "userInput": false + }"# => LaunchConfig { + auto_restart: true, + user_input: false, + ..Default::default() + } } /* "file": "root/path/to/file" diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 3f1dc16..dc52faa 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -7,6 +7,7 @@ //! usage: BabelNAR [OPTIONS] //! ``` +use babel_nar::println_cli; use clap::Parser; use std::io::Result as IoResult; use std::thread::sleep; @@ -39,24 +40,24 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) { let mut config = load_config(&args, DEFAULT_CONFIG_PATH); // 用户填充配置项 polyfill_config_from_user(&mut config); - // 从配置项启动 - let runtime = match launch_by_config(config) { + // 从配置项启动 | 复制一个新配置,不会附带任何非基础类型开销 + let runtime = match launch_by_config(config.clone()) { // 启动成功⇒返回 Ok(runtime) => runtime, // 启动失败⇒打印错误信息,等待并退出 Err(e) => { - println!("NARS运行时启动错误:{e}"); - println!("程序将在 3 秒后自动退出。。。"); + println_cli!([Error] "NARS运行时启动错误:{e}"); + println_cli!([Info] "程序将在 3 秒后自动退出。。。"); sleep(Duration::from_secs(3)); return; } }; // 运行时交互、管理 - if let Err(e) = manage(runtime, &args) { - println!("运行时发生错误:{e}"); - } + let manager = RuntimeManager::new(runtime, config.clone()); + loop_manage(manager, &config); + // 最终退出 - println!("程序将在 5 秒后退出"); + println_cli!([Info] "程序将在 5 秒后退出"); sleep(Duration::from_secs(5)); } @@ -64,10 +65,14 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) { #[cfg(test)] mod tests { use super::*; + use nar_dev_utils::if_return; /// 测试入口 #[test] pub fn main_ona() { + // ! 此处需要测试用路径 + const PATH_ONA_EXE: &str = "../../NARS-executables/NAR.exe"; + if_return! { !PathBuf::from(PATH_ONA_EXE).exists() } // 以默认参数启动 main_args( env::current_dir(), @@ -75,7 +80,7 @@ mod tests { "test.exe", "-d", "-c", - "./src/tests/cli/config_test_ona.json", + "./src/tests/cli/config/test_ona.json", ] .into_iter() .map(str::to_string), diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index afd8c96..6f1853e 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -1,10 +1,21 @@ //! 启动后运行时的(交互与)管理 -use crate::CliArgs; -use anyhow::Result; -use nar_dev_utils::if_return; +use crate::{launch_by_config, InputMode, LaunchConfig, LaunchConfigPreludeNAL}; +use anyhow::{anyhow, Result}; +use babel_nar::{ + cli_support::error_handling_boost::error_anyhow, + eprintln_cli, println_cli, + test_tools::{nal_format::parse, put_nal}, +}; +use nar_dev_utils::ResultBoost; use navm::{cmd::Cmd, output::Output, vm::VmRuntime}; -use std::{fmt::Debug, io::Result as IoResult}; +use std::{ + fmt::Debug, + io::Result as IoResult, + sync::{Arc, Mutex}, + thread::{self, sleep, JoinHandle}, + time::Duration, +}; /// 读取行迭代器 /// * 🚩每迭代一次,请求用户输入一行 @@ -35,42 +46,306 @@ impl Iterator for ReadlineIter { } } -/// 打印错误 -fn println_error(e: &impl Debug) { - println!("{e:?}"); +/// 运行时管理器 +/// * 🎯在一个数据结构中封装「虚拟机运行时」与「配置信息」 +/// * 📌只负责**单个运行时**的运行管理 +/// * 🚩不负责「终止、重启运行时」等过程 +#[derive(Debug, Clone)] +pub struct RuntimeManager +where + // ! 🚩【2024-04-02 14:51:23】需要`Send + Sync`进行多线程操作,需要`'static`保证生命周期 + R: VmRuntime + Send + Sync + 'static, +{ + /// 内部封装的虚拟机运行时 + /// * 🏗️后续可能会支持「同时运行多个虚拟机」 + /// * 🚩多线程共享:输入/输出 + runtime: Arc>, + + /// 内部封装的「命令行参数」 + /// * 🎯用于从命令行中加载配置 + /// * 🚩只读 + config: Arc, + + /// 内部缓存的「NAVM输出」 + /// * 🎯用于NAL测试 + /// * 🚩多线程共享 + output_cache: Arc>>, } -/// 在运行时启动后,对其进行管理 -/// * 🚩`.nal`脚本预加载逻辑 -/// * 🚩用户的运行时交互逻辑 -/// * 🚩Websocket服务器逻辑 -pub fn manage(mut nars: impl VmRuntime, args: &CliArgs) -> Result<()> { - // TODO: 优化并行逻辑 - // TODO: 结合test_tools - if_return! { args.no_user_input => Ok(()) } - - // 用户输入主循环 - 'main: for io_result in ReadlineIter::default() { - // 读取一行 - let line = io_result?; +impl RuntimeManager +where + R: VmRuntime + Send + Sync + 'static, +{ + /// 构造函数 + pub fn new(runtime: R, config: LaunchConfig) -> Self { + Self { + runtime: Arc::new(Mutex::new(runtime)), + config: Arc::new(config), + output_cache: Arc::new(Mutex::new(vec![])), + } + } + + /// 在运行时启动后,对其进行管理 + /// * 🎯健壮性:更多「警告/重来」而非`panic` + /// * 🎯用户友好:尽可能隐藏底层内容 + /// * 如错误堆栈 + /// * 🚩`.nal`脚本预加载逻辑 + /// * 🚩用户的运行时交互逻辑 + /// * 🚩Websocket服务器逻辑 + pub fn manage(&mut self) -> Result<()> { + // 预置输入 | 不会阻塞后续运行 + self.prelude_nal() + .unwrap_or_else(|e| println_cli!([Error] "预置NAL输入发生错误:{e}")); + + // 生成「读取输出」子线程 + let thread_read = self.spawn_read_output()?; + + // 生成「Websocket服务」子线程 + let thread_ws = self.spawn_ws_server()?; + + // 生成「用户输入」子线程 + let mut thread_input = None; + if self.config.user_input { + thread_input = Some(self.spawn_user_input()?); + } + + // ! 🚩不要在主线程开始用户输入 + + // 等待子线程结束,并抛出其抛出的错误 + // ! 🚩【2024-04-02 15:09:32】错误处理交给外界 + thread_read.join().transform_err(error_anyhow)??; + thread_ws.join().transform_err(error_anyhow)??; + if let Some(thread_input) = thread_input { + thread_input.join().transform_err(error_anyhow)??; + } + + // 正常运行结束 + Ok(()) + } + + /// 预置NAL + /// * 🎯用于自动化调取`.nal`文件进行测试 + pub fn prelude_nal(&mut self) -> Result<()> { + let config = &*self.config; + // 仅在有预置NAL时开始 + if let Some(prelude_nal) = &config.prelude_nal { + // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 + let runtime = &mut *self.runtime.lock().transform_err(error_anyhow)?; + // 尝试获取输出缓冲区引用 | 仅有其它地方panic了才会停止 + let output_cache = &mut *self.output_cache.lock().transform_err(error_anyhow)?; + + // 开始预置 + let nal = match prelude_nal { + // 文件⇒尝试读取文件内容 | ⚠️此处创建了一个新值,所以要统一成`String` + LaunchConfigPreludeNAL::File(path) => std::fs::read_to_string(path)?, + // 纯文本⇒直接引入 + LaunchConfigPreludeNAL::Text(nal) => nal.to_string(), + }; + // 输入NAL + Self::input_nal_to_vm(runtime, &nal, output_cache) + } + Ok(()) + } + + /// 生成「读取输出」子线程 + pub fn spawn_read_output(&mut self) -> Result>> { + // 准备引用 + let runtime_arc = self.runtime.clone(); + + // 启动线程 + let thread = thread::spawn(move || { + loop { + // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 + let mut runtime = runtime_arc.lock().transform_err(error_anyhow)?; + // 尝试拉取所有NAVM运行时输出 + if let Ok(Some(output)) = runtime + .try_fetch_output() + .inspect_err(|e| eprintln_cli!([Error] "尝试拉取NAVM运行时输出时发生错误:{e}")) + { + // 格式化输出 + println_cli!(&output); + // 检测终止信号 + // * 🚩【2024-04-02 14:58:19】目前视作「异常退出」 + if let Output::TERMINATED { description } = output { + // println_cli!([Info] "NAVM已终止运行:{description}"); + // 线程退出 + break Err(anyhow!("NAVM意外终止运行:{description}")); + } + } + } + }); + + // 返回启动的线程 + Ok(thread) + } + + /// 生成「Websocket服务」子线程 + pub fn spawn_ws_server(&mut self) -> Result>> { + // 准备引用 + let runtime_arc = self.runtime.clone(); - // 非空⇒解析出NAVM指令,作为输入执行 - if !line.trim().is_empty() { - if let Ok(cmd) = Cmd::parse(&line).inspect_err(println_error) { - let _ = nars.input_cmd(cmd).inspect_err(println_error); + // 启动线程 + let thread = thread::spawn(move || { + loop { + // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 + let mut runtime = runtime_arc.lock().transform_err(error_anyhow)?; + // TODO: Websocket服务端逻辑 } + }); + + // 返回启动的线程 + Ok(thread) + } + + /// 生成「用户输入」子线程 + pub fn spawn_user_input(&mut self) -> Result>> { + // 准备引用 + // ! 📝不能在此外置「可复用引用」变量:borrowed data escapes outside of method + let runtime = self.runtime.clone(); + let config = self.config.clone(); + let output_cache = self.output_cache.clone(); + + // 启动线程 + let thread = thread::spawn(move || { + // 主循环 + // ! 📝不能在此中出现裸露的`MutexGuard`对象:其并非线程安全 + // * ✅可使用`&(mut) *`重引用语法,从`MutexGuard`转换为线程安全的引用 + // * ✅对`Arc`使用`&*`同理:可以解包成引用,以便后续统一传递值的引用 + for io_result in ReadlineIter::default() { + // 从迭代器中读取一行 + let line = io_result?; + + // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 + // ! 📝PoisonError无法在线程中传递 + let runtime = &mut *runtime + .lock() + .transform_err(|e| anyhow!("获取运行时引用时发生错误:{e:?}"))?; + + // 尝试获取输出缓冲区引用 | 仅有其它地方panic了才会停止 + // ! 🚩【2024-04-02 19:27:01】及早报错:即便无关紧要,也停止 + let output_cache = &mut *output_cache + .lock() + .transform_err(|e| anyhow!("获取NAVM输出缓存时发生错误:{e}"))?; + + // 非空⇒解析输入并执行 + if !line.trim().is_empty() { + if let Err(e) = Self::input_line_to_vm(runtime, &line, &config, output_cache) { + println_cli!([Error] "输入过程中发生错误:{e}") + } + } + } + + // 返回 + Ok(()) + }); + + // 返回启动的线程 + Ok(thread) + } + + /// 置入一行输入 + pub fn input_line_to_vm( + runtime: &mut R, + line: &str, + config: &LaunchConfig, + output_cache: &mut Vec, + ) -> Result<()> { + // 向运行时输入 + match config.input_mode { + // NAVM指令 + InputMode::Cmd => Self::input_cmd_to_vm(runtime, line), + // NAL输入 + InputMode::Nal => Self::input_nal_to_vm(runtime, line, output_cache), } - // 尝试拉取所有NAVM运行时输出 - while let Ok(Some(output)) = nars.try_fetch_output().inspect_err(println_error) { - println!("{output:?}"); - if let Output::TERMINATED { .. } = output { - println!("NAVM已终止运行,正在重启。。。"); - nars.terminate()?; - break 'main; // ! 这个告诉Rust编译器,循环必将在此结束 + // 输入完成 + 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}")); + } + } + + /// 像NAVM实例输入NAL(输入) + /// * 🎯预置、用户输入、Websocket输入 + /// * ⚠️可能有多行 + fn input_nal_to_vm(runtime: &mut R, input: &str, output_cache: &mut Vec) { + // 解析输入,并遍历解析出的每个NAL输入 + for input in parse(input) { + // 尝试解析NAL输入 + match input { + Ok(nal) => { + // 尝试置入NAL输入 | 为了错误消息,必须克隆 + put_nal(runtime, nal.clone(), output_cache).unwrap_or_else( + |e| eprintln_cli!([Error] "置入NAL输入「{nal:?}」时发生错误:{e}"), + ); + } + // 错误⇒报错 + Err(e) => eprintln_cli!( + [Error] "解析NAL输入时发生错误:{e}" + ), } } } - // 正常运行结束 - Ok(()) +} + +/// 重启虚拟机 +/// * 🚩消耗原先的虚拟机管理者,返回一个新的管理者 +/// * 🚩【2024-04-02 20:25:21】目前对「终止先前虚拟机」持放松态度 +/// * 📝从`Arc>`中拿取值的所有权:[`Arc::try_unwrap`] + [`Mutex::into_inner`] +/// * 🔗参考: +pub fn restart_manager( + manager: RuntimeManager, +) -> Result> { + // 尝试终止先前的虚拟机 + // ! ❌[`Arc::try_unwrap`]的返回值包括`VmRuntime`,所以连[`Debug`]都不支持 + // ! ❌【2024-04-02 20:33:01】目前测试中`Arc::into_inner`基本总是失败(线程里还有引用) + // * 🚩【2024-04-02 20:33:18】现在通过修改NAVM API,不再需要获取运行时所有权了(销毁交给) + // let old_runtime_mutex = + // Arc::into_inner(manager.runtime).ok_or(anyhow!("runtime Arc解包失败"))?; + // let mut old_runtime = old_runtime_mutex.into_inner()?; + let old_runtime = &mut *manager + .runtime + .lock() + .transform_err(|e| anyhow!("runtime Mutex解锁失败:{e:?}"))?; + old_runtime.terminate()?; + + // 启动新的虚拟机 + let config_ref = &*manager.config; + let new_runtime = launch_by_config(config_ref.clone())?; + let new_manager = RuntimeManager::new(new_runtime, config_ref.clone()); + + // 返回 + Ok(new_manager) +} + +/// 根据配置(的「是否重启」选项)管理(一系列)虚拟机实例 +pub fn loop_manage( + mut manager: RuntimeManager, + config: &LaunchConfig, +) { + if let Err(e) = manager.manage() { + println_cli!([Error] "运行时发生错误:{e}"); + // 尝试重启 + if config.auto_restart { + println_cli!([Info] "程序将在 2 秒后自动重启。。。"); + sleep(Duration::from_secs(2)); + let new_manager = match restart_manager(manager) { + Ok(manager) => manager, + Err(e) => { + println_cli!([Error] "重启失败:{e}"); + return; + } + }; + // 重启之后继续循环 + loop_manage(new_manager, config); + } + } } diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 9b46a8e..12eba19 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -6,11 +6,15 @@ //! * 📌可根据「匹配度」排名 //! * ✨自动启动并管理CIN //! * 📌可保存/加载「常用CIN」配置 +//! +//! * 🚩目前用于敏捷原型开发 //! TODO: 完成代码 #![allow(unused)] +use anyhow::Result; use babel_nar::{ cin_implements::{ona::ONA, opennars::OpenNARS, pynars::PyNARS}, + eprintln_cli, println_cli, runtimes::CommandVmRuntime, }; use nar_dev_utils::*; @@ -25,16 +29,21 @@ const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modifie const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; const TEST_PATH_PYNARS: (&str, &str) = ("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); -/// 启动NARS +/// 启动并获取NARS /// * 🚩【2024-03-27 18:55:07】目前就返回一个测试用的运行时 +/// * 🎯敏捷开发用 fn get_nars() -> impl VmLauncher { // OpenNARS::new(TEST_PATH_OPENNARS) PyNARS::new(TEST_PATH_PYNARS.0, TEST_PATH_PYNARS.1) // ONA::new(TEST_PATH_ONA) } +fn put_cmd_to_nars(nars: &mut impl VmRuntime, cmd: Cmd) -> Result<()> { + nars.input_cmd(cmd) +} + /// 主函数 -/// TODO: 完成代码 +/// * 🚩【2024-04-02 20:58:07】现在更完整的支持交给BabelNAR CLI,此文件用于敏捷开发 fn main() { // 不断开始🔥 loop { @@ -58,22 +67,29 @@ fn shell(mut nars: CommandVmRuntime) { let stdin = stdin(); let mut input = String::new(); let mut line; + 'main: while stdin.read_line(&mut input).is_ok() { // 一行 line = input.as_str(); // 非空⇒解析出NAVM指令,作为输入执行 if !line.trim().is_empty() { - if let Ok(cmd) = Cmd::parse(line).inspect_err(println_error) { - let _ = nars.input_cmd(cmd).inspect_err(println_error); + if let Ok(cmd) = Cmd::parse(line) + .inspect_err(|e| eprintln_cli!([Error] "解析NAVM指令时发生错误:{e}")) + { + let _ = put_cmd_to_nars(&mut nars, cmd) + .inspect_err(|e| eprintln_cli!([Error] "执行NAVM指令时发生错误:{e}")); } } // 尝试拉取所有NAVM运行时输出 - while let Ok(Some(output)) = nars.try_fetch_output().inspect_err(println_error) { + while let Ok(Some(output)) = nars + .try_fetch_output() + .inspect_err(|e| eprintln_cli!([Error] "拉取NAVM运行时输出时发生错误:{e}")) + { println!("{output:?}"); - if let Output::TERMINATED { .. } = output { - println!("NAVM已终止运行,正在重启。。。"); + if let Output::TERMINATED { description } = output { + println_cli!([Info] "NAVM已终止运行:{description}"); nars.terminate(); break 'main; // ! 这个告诉Rust编译器,循环必将在此结束 } diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs index 4a394be..8fd1aca 100644 --- a/src/cin_implements/cxin_js/translators.rs +++ b/src/cin_implements/cxin_js/translators.rs @@ -123,11 +123,11 @@ fn segment_narsese(head: &str, tail: &str) -> Option { match try_segment_narsese(tail) { Some(Ok(narsese)) => Some(narsese), Some(Err(e)) => { - println!("【ERR/{head}】在解析Narsese时出现错误:{e}"); + println!("【{head}】在解析Narsese时出现错误:{e}"); None } None => { - println!("【ERR/{head}】未匹配到输出中的Narsese块"); + println!("【{head}】未匹配到输出中的Narsese块"); None } } diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 9b913a4..131f12c 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -149,7 +149,8 @@ pub fn output_translate(content_raw: String) -> Result { description: content_raw, }, // * 🚩对于「操作」的特殊语法 - _ if content_raw.contains("executed") => Output::EXE { + // * 🚩【2024-04-02 18:45:17】仅截取`executed with args`,不截取`executed by NAR` + _ if content_raw.contains("executed with args") => Output::EXE { operation: parse_operation_ona(&content_raw)?, content_raw, }, @@ -173,9 +174,10 @@ pub fn output_translate(content_raw: String) -> Result { /// * 📄`^deactivate executed with args` /// * 📄`^left executed with args (* {SELF})` /// * 📄`^left executed with args ({SELF} * x)` +/// * ❌`right executed by NAR` pub fn parse_operation_ona(content_raw: &str) -> Result { // 匹配ONA输出中的「操作」⇒转换 | 操作名 | 操作参数(Narsese复合词项⇒提取组分,变成字符串) - let re_operation = Regex::new(r"\^([^\s]+)\s*executed with args\s*(.*)").unwrap(); + let re_operation = Regex::new(r"\^([^\s]+)\s*executed with args\s*(.*)$").unwrap(); let captures = re_capture(&re_operation, content_raw)?; // ! 即便是测试环境下,也有可能是[`None`](但只在测试环境下返回[`Err`]并报错) match captures { @@ -251,7 +253,7 @@ fn extract_params(params: Term) -> Vec { fn re_capture<'a>(re: &'a Regex, haystack: &'a str) -> Result>> { Ok(re .captures(haystack) - .inspect_none(|| println!("【ERR】使用正则表达式「{re}」无法捕获「{haystack}」"))) + .inspect_none(|| println!("使用正则表达式「{re}」无法捕获「{haystack}」"))) } /// 正则捕获 @@ -264,9 +266,7 @@ fn re_capture<'a>(re: &'a Regex, haystack: &'a str) -> Result Ok(Some(captures)), - None => Err(anyhow!( - "【ERR】无法使用正则表达式「{re}」捕获「{haystack}」" - )), + None => Err(anyhow!("无法使用正则表达式「{re}」捕获「{haystack}」")), } } @@ -277,8 +277,7 @@ fn re_capture<'a>(re: &'a Regex, haystack: &'a str) -> Result Result> { use util::ResultBoost; // ! ↓下方会转换为None - Ok(try_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}"))) + Ok(try_parse_narsese(tail).ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}"))) } /// (ONA)从原始输出中解析Narsese diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index cd2b516..74b37b2 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -87,7 +87,7 @@ pub fn output_translate(content_raw: String) -> Result { r#type: "ANTICIPATE".to_string(), // 先提取其中的Narsese | ⚠️借用了`content_raw` narsese: try_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}")), + .ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}")), // 然后传入整个内容 content: content_raw, }, @@ -117,8 +117,7 @@ pub fn output_translate(content_raw: String) -> Result { pub fn parse_narsese_opennars(head: &str, tail: &str) -> Result> { use util::ResultBoost; // ! ↓下方会转换为None - Ok(try_parse_narsese(tail) - .ok_or_run(|e| println!("【ERR/{head}】在解析Narsese时出现错误:{e}"))) + Ok(try_parse_narsese(tail).ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}"))) } /// (ONA)从原始输出中解析Narsese @@ -155,7 +154,7 @@ pub fn parse_operation_opennars(tail: &str) -> Operation { match parse_term_from_operation(param) { Ok(term) => params.push(term), // ? 【2024-03-27 22:29:43】↓是否要将其整合到一个日志系统中去 - Err(e) => println!("【ERR/EXE】在解析Narsese时出现错误:{e}"), + Err(e) => println!("【EXE】在解析Narsese时出现错误:{e}"), } } } else { diff --git a/src/cli_support/error_handling_boost.rs b/src/cli_support/error_handling_boost.rs new file mode 100644 index 0000000..6b6e282 --- /dev/null +++ b/src/cli_support/error_handling_boost.rs @@ -0,0 +1,36 @@ +//! 增强的快捷错误处理 +//! * 🎯用于(在命令行)快速处理、输出各种错误 + +use crate::cli_support::io::output_print::OutputType; +use anyhow::{anyhow, Error}; +use std::fmt::Debug; + +/// 打印错误 +/// * 🚩在标准错误中打印基于[`Debug`]的信息 +/// * 🎯快速表示「报错而非panic」 +/// * 🚩【2024-04-02 18:59:19】不建议使用:不应向用户打印大量错误堆栈信息 +/// * ✨替代用法可参考[`crate::eprintln_cli`] +#[deprecated = "不建议使用:不应向用户打印大量错误堆栈信息"] +pub fn println_error(e: &impl Debug) { + // ! 无法在此直接使用:macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths + // * 🚩【2024-04-02 16:33:47】目前处理办法:直接展开 + println!("{}", OutputType::Error.format_line(&format!("{e:?}"))); +} + +/// 打印错误 +/// * 🚩在标准错误中打印基于[`Debug`]的信息 +/// * 🎯快速表示「报错而非panic」 +/// * 🎯用于「传入所有权而非不可变引用」的[`Result::unwrap_or_else`] +/// * 🚩【2024-04-02 18:59:19】不建议使用:不应向用户打印大量错误堆栈信息 +/// * ✨替代用法可参考[`crate::eprintln_cli`] +#[deprecated = "不建议使用:不应向用户打印大量错误堆栈信息"] +pub fn println_error_owned(e: impl Debug) { + println!("{}", OutputType::Error.format_line(&format!("{e:?}"))); +} + +/// 将错误转换为[`anyhow::Error`] +/// * 🚩将错误转换为[`Debug`]信息,装入[`anyhow::Error`]中 +/// * 🎯在线程通信中安全抛出未实现[`Send`]的[`std::sync::PoisonError`] +pub fn error_anyhow(e: impl Debug) -> Error { + anyhow!("{e:?}") +} diff --git a/src/cli_support/io/mod.rs b/src/cli_support/io/mod.rs index d7347ca..1eb7e90 100644 --- a/src/cli_support/io/mod.rs +++ b/src/cli_support/io/mod.rs @@ -1,6 +1,8 @@ //! 用于管理启动器的输入输出 -//! * ✨交互式终端 函数支持 -//! * 📌输入输出「通道」绑定 //! * ✨终端美化相关 //! -//! TODO: 添加功能 + +util::mods! { + // 输出打印 + pub output_print; +} diff --git a/src/cli_support/io/output_print.rs b/src/cli_support/io/output_print.rs new file mode 100644 index 0000000..f3d9384 --- /dev/null +++ b/src/cli_support/io/output_print.rs @@ -0,0 +1,181 @@ +//! 输出打印 +//! * 🎯用于规范化、统一、美化CLI输出 +//! * 📌不仅仅是NAVM的输出 +//! +//! ## 输出美化参考 +//! +//! 输出美化逻辑参考了如下Julia代码: +//! +//! ```julia +//! """ +//! 用于高亮「输出颜色」的字典 +//! """ +//! const output_color_dict = Dict([ +//! NARSOutputType.IN => :light_white +//! NARSOutputType.OUT => :light_white +//! NARSOutputType.EXE => :light_cyan +//! NARSOutputType.ANTICIPATE => :light_yellow +//! NARSOutputType.ANSWER => :light_green +//! NARSOutputType.ACHIEVED => :light_green +//! NARSOutputType.INFO => :white +//! NARSOutputType.COMMENT => :white +//! NARSOutputType.ERROR => :light_red +//! NARSOutputType.OTHER => :light_black # * 未识别的信息 +//! # ! ↓这俩是OpenNARS附加的 +//! "CONFIRM" => :light_blue +//! "DISAPPOINT" => :light_magenta +//! ]) +//! +//! """ +//! 用于分派「颜色反转」的集合 +//! """ +//! const output_reverse_color_dict = Set([ +//! NARSOutputType.EXE +//! NARSOutputType.ANSWER +//! NARSOutputType.ACHIEVED +//! ]) +//! ``` +//! +//! * 最后更新:【2024-04-02 15:54:23】 +//! * 参考链接: + +use colored::Colorize; +use navm::output::Output; +use std::fmt::Display; + +/// 统一的「CLI输出类型」 +#[derive(Debug, Clone, Copy)] +pub enum OutputType<'a> { + /// NAVM输出 + /// * 🚩【2024-04-02 15:42:44】目前因NAVM的[`Output`]仅有`enum`结构而无「类型」标签, + /// * 无法复用NAVM的枚举 + Vm(&'a str), + /// CLI错误 + Error, + /// CLI警告 + Warn, + /// CLI信息 + Info, + /// CLI日志 + Log, + /// CLI debug + Debug, +} + +impl OutputType<'_> { + /// 自身的字符串形式 + /// * 🎯作为输出的「头部」 + pub fn as_str(&self) -> &str { + match self { + OutputType::Vm(s) => s, + OutputType::Error => "ERROR", + OutputType::Warn => "WARN", + OutputType::Info => "INFO", + OutputType::Debug => "DEBUG", + OutputType::Log => "LOG", + } + } + + /// 格式化CLI输出 + /// * 🎯封装标准输出形式:`[类型] 内容` + /// * 🎯封装命令行美化逻辑 + #[inline(always)] + pub fn format_line(&self, msg: &str) -> impl Display { + self.to_colored_str(format!("[{}] {}", self.as_str(), msg)) + } + + /// 从NAVM输出格式化 + /// * 🎯封装「从NAVM输出打印」 + #[inline(always)] + pub fn format_from_navm_output(out: &Output) -> impl Display { + OutputType::from(out).format_line(out.raw_content().trim_end()) + } + + /// 基于[`colored`]的输出美化 + /// * 🎯用于CLI的彩色输出 + /// * 🔗参考Julia版本 + pub fn to_colored_str(&self, message: String) -> impl Display { + match self.as_str() { + // CLI独有 + "DEBUG" => message.bright_blue(), + "WARN" => message.bright_yellow(), + "LOG" => message.white(), + // NAVM输出 + "IN" | "OUT" => message.bright_white(), + "EXE" => message.bright_cyan().reversed(), + "ANSWER" | "ACHIEVED" => message.bright_green().reversed(), + "INFO" | "COMMENT" => message.white(), + "ERROR" => message.red(), + "TERMINATED" => message.bright_white().reversed().blink(), + // ↓OpenNARS附加 + "ANTICIPATE" => message.bright_yellow(), + "CONFIRM" => message.bright_blue(), + "DISAPPOINT" => message.bright_magenta(), + // 默认 / 其它 + "OTHER" => message.bright_black(), + _ => message.bright_white(), + } + // 参考Julia,始终加粗 + .bold() + } + + /// ✨格式化打印CLI输出 + /// * 🎯BabelNAR CLI + #[inline] + pub fn print_line(&self, message: &str) { + println!("{}", self.format_line(message)); + } + + /// ✨格式化打印NAVM输出 + /// * 🎯BabelNAR CLI + #[inline] + pub fn print_from_navm_output(out: &Output) { + println!("{}", Self::format_from_navm_output(out)); + } + + /// ✨格式化打印CLI输出(标准错误) + /// * 🎯BabelNAR CLI + #[inline] + pub fn eprint_line(&self, message: &str) { + eprintln!("{}", self.format_line(message)); + } + + /// ✨格式化打印NAVM输出(标准错误) + /// * 🎯BabelNAR CLI + #[inline] + pub fn eprint_from_navm_output(out: &Output) { + eprintln!("{}", Self::format_from_navm_output(out)); + } +} + +/// 快捷打印宏 +#[macro_export] +macro_rules! println_cli { + ([$enum_type_name:ident] $($tail:tt)*) => { + // 调用内部函数 + $crate::cli_support::io::output_print::OutputType::$enum_type_name.print_line(&format!($($tail)*)); + }; + ($navm_output:expr) => { + // 调用内部函数 + $crate::cli_support::io::output_print::OutputType::print_from_navm_output($navm_output); + }; +} + +/// 快捷打印宏/标准错误 +#[macro_export] +macro_rules! eprintln_cli { + ([$enum_type_name:ident] $($tail:tt)*) => { + // 调用内部函数 + $crate::cli_support::io::output_print::OutputType::$enum_type_name.eprint_line(&format!($($tail)*)); + }; + ($navm_output:expr) => { + // 调用内部函数 + $crate::cli_support::io::output_print::OutputType::eprint_from_navm_output($navm_output); + }; +} + +impl<'a> From<&'a Output> for OutputType<'a> { + fn from(out: &'a Output) -> Self { + OutputType::Vm(out.type_name()) + } +} diff --git a/src/cli_support/mod.rs b/src/cli_support/mod.rs index 810a0c9..6db9768 100644 --- a/src/cli_support/mod.rs +++ b/src/cli_support/mod.rs @@ -8,3 +8,6 @@ util::mods! { // 输入输出 pub io; } + +// 错误处理增强 +pub mod error_handling_boost; diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 57da15e..65b596f 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -15,7 +15,7 @@ use std::{ error::Error, ffi::OsStr, fmt::{self, Debug, Display, Formatter}, - io::{BufRead, BufReader, Result as IoResult, Write}, + io::{BufRead, BufReader, ErrorKind, Result as IoResult, Write}, process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, sync::{ mpsc::{channel, Receiver, Sender}, @@ -141,18 +141,20 @@ impl From for IoProcess { /// * 🚩现在兼容「输出侦听」与「输出通道」两处 /// * 🎯「输出侦听」用于「需要**响应式**即时处理输出,但又不想阻塞主进程/开新进程」时 /// * 🎯「输出通道」用于「需要封装『并发异步获取』延迟处理输出,兼容已有异步并发模型」时 +/// * 📝【2024-04-02 20:40:35】使用[`Option`]应对「可能会移动所有权」的情形 +/// * 📄在「线程消耗」的场景中,有时需要「消耗线程,重启新线程」,此时就需要[`Option`]确保销毁 #[allow(dead_code)] pub struct IoProcessManager { /// 正在管理的子进程 process: Child, /// 子进程的「写(到子进程的)输入」守护线程 - thread_write_in: JoinHandle<()>, + thread_write_in: Option>, + /// 子进程的「读(到子进程的)输出」守护线程 /// * 🚩现在兼容「侦听器」「通道」两种模式,重新必要化 - // thread_read_out: Option>, - thread_read_out: JoinHandle<()>, - + thread_read_out: Option>, + // thread_read_out: JoinHandle<()>, /// 子线程的终止信号 termination_signal: ArcMutex, @@ -198,15 +200,18 @@ impl IoProcessManager { // let num_output = Arc::new(Mutex::new(0)); // 生成进程的「读写守护」(线程) - let thread_write_in = - IoProcessManager::spawn_thread_write_in(stdin, child_in, termination_signal.clone()); - let thread_read_out = IoProcessManager::spawn_thread_read_out( + let thread_write_in = Some(IoProcessManager::spawn_thread_write_in( + stdin, + child_in, + termination_signal.clone(), + )); + let thread_read_out = Some(IoProcessManager::spawn_thread_read_out( stdout, child_out, out_listener, termination_signal.clone(), // num_output.clone(), - ); + )); // let thread_read_out = // out_listener.map(|listener| IoProcessManager::spawn_thread_read_out(stdout, listener)); // ! 🚩【2024-03-23 19:33:45】↑现在兼容「侦听器」「通道」二者 @@ -249,7 +254,16 @@ impl IoProcessManager { } // 写入输出 if let Err(e) = stdin.write_all(line.as_bytes()) { - println!("无法向子进程输入:{e:?}"); + match e.kind() { + // * 🚩进程已关闭⇒退出 + // TODO: 🏗️外包「错误处理」逻辑 + ErrorKind::BrokenPipe => { + println!("子进程已关闭"); + break; + } + // 其它 + _ => println!("子进程写入错误:{e}"), + } } } }) @@ -422,10 +436,13 @@ impl IoProcessManager { /// 杀死自身 /// * 🚩设置终止信号,通知子线程(以及标准IO)终止 /// * 🚩调用[`Child::kill`]方法,终止子进程 - /// * ⚠️将借走自身所有权,终止并销毁自身 + /// * ~~⚠️将借走自身所有权,终止并销毁自身~~ + /// * 🚩【2024-04-02 20:37:28】如今不再消耗自身所有权 + /// * ✅【2024-04-02 20:36:40】现在通过「将字段类型变为[`Option`]」安全借走子线程所有权 + /// * 📌销毁自身的逻辑,交给调用方处理 /// /// * ❓不稳定:有时会导致「野进程」的情况 - pub fn kill(mut self) -> Result<()> { + pub fn kill(&mut self) -> Result<()> { // ! ❌【2024-03-23 21:08:56】暂不独立其中的逻辑:无法脱开对`self`的借用 // ! 📌更具体而言:对其中两个线程`thread_write_in`、`thread_read_out`的部分借用 // 向子线程发送终止信号 // @@ -442,8 +459,13 @@ impl IoProcessManager { // * 📌主要原因:在测试OpenNARS时,发现`thread_read_out`仍然会阻塞(无法等待) // * 📌并且一时难以修复:难点在`BufReader.read_line`如何非阻塞/可终止化 // ! ℹ️信息 from Claude3:无法简单以此终止子线程 - self.thread_write_in.join().transform_err(err)?; // * ✅目前这个是可以终止的 - drop(self.thread_read_out); + // * 🚩【2024-04-02 20:31:24】现在通过「字段类型转为[`Option`]」的方法,安全拿取所有权并销毁 + drop( + self.thread_write_in + .take() + .map(|t| t.join().transform_err(err)), + ); // * ✅目前这个是可以终止的 + drop(self.thread_read_out.take()); // * 📝此时子线程连同「子进程的标准输入输出」一同关闭, // * 子进程自身可以做输出 diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 98d19fe..0f90a78 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -54,7 +54,7 @@ impl VmRuntime for CommandVmRuntime { } } - fn terminate(self) -> Result<()> { + fn terminate(&mut self) -> Result<()> { // 杀死子进程 self.process.kill()?; Ok(()) diff --git a/src/test_tools/nal_format/mod.rs b/src/test_tools/nal_format/mod.rs index ab4b86c..3113115 100644 --- a/src/test_tools/nal_format/mod.rs +++ b/src/test_tools/nal_format/mod.rs @@ -27,7 +27,8 @@ pub fn parse(input: &str) -> Vec> { input // 切分并过滤空行 .split('\n') - .filter(|line| !line.trim().is_empty()) + .map(str::trim) + .filter(|line| !line.is_empty()) // 逐行解析 .map(parse_single) // 收集所有结果 @@ -57,11 +58,13 @@ pub fn parse_single(line: &str) -> Result { /// * `narsese`:`NSE`语法糖,亦兼容原`.nal`格式 /// * `comment`:各类或「魔法」或「非魔法」的注释 fn fold_pest(pair: Pair) -> Result { + // * 🚩【2024-04-02 18:33:05】此处不用再`trim`了:入口`parse`已经做过 + let pair_str = pair.as_str(); match pair.as_rule() { // 一行的无符号整数 // Rule::cyc_uint => { // 仅取数字部分 - let n: usize = pair.as_str().parse()?; + let n: usize = pair_str.parse()?; // * 🚩作为`CYC`语法糖 let input = NALInput::Put(Cmd::CYC(n)); Ok(input) @@ -70,7 +73,7 @@ fn fold_pest(pair: Pair) -> Result { Rule::narsese => { // 作为CommonNarsese,直接取字符串,然后调用CommonNarsese ASCII解析器 // * 🚩【2024-03-31 16:37:32】虽可能有失灵活性,但代码上更显通用 - let narsese = pair.as_str(); + let narsese = pair_str; let narsese = FORMAT_ASCII.parse(narsese)?.try_into_task_compatible()?; // * 🚩作为`NSE`语法糖 let input = NALInput::Put(Cmd::NSE(narsese)); @@ -81,7 +84,7 @@ fn fold_pest(pair: Pair) -> Result { Rule::comment_raw => { // 仅取注释部分 // ! 不能用`to_string`:后者只会显示其总体信息,而非捕获相应字符串切片 - let comment = pair.as_str().into(); + let comment = pair_str.into(); // * 🚩作为`REM`语法糖 let input = NALInput::Put(Cmd::REM { comment }); Ok(input) @@ -91,7 +94,7 @@ fn fold_pest(pair: Pair) -> Result { // 取其中第一个`comment_raw`元素 | 一定只有唯一一个`comment_raw` let comment_raw = pair.into_inner().next().unwrap(); // 仅取注释部分 - let line = comment_raw.as_str(); + let line = comment_raw.as_str().trim(); // * 🚩作为所有NAVM指令的入口 let input = NALInput::Put(Cmd::parse(line)?); Ok(input) @@ -99,7 +102,7 @@ fn fold_pest(pair: Pair) -> Result { // 魔法注释/睡眠等待 Rule::comment_sleep => { // 取其中第一个`comment_raw`元素 | 一定只有唯一一个`comment_raw` - let duration_raw = pair.into_inner().next().unwrap().as_str(); + let duration_raw = pair.into_inner().next().unwrap().as_str().trim(); // 尝试解析时间 let duration = first! { // 毫秒→微秒→纳秒→秒 | 对于「秒」分「整数」「浮点」两种 diff --git a/src/test_tools/vm_interact.rs b/src/test_tools/vm_interact.rs index 1da77fd..74d1818 100644 --- a/src/test_tools/vm_interact.rs +++ b/src/test_tools/vm_interact.rs @@ -17,7 +17,7 @@ impl OutputExpectation { /// * 🚩通过「输出缓存」参数,解决「缓存输出」问题 /// * ❓需要迁移「符合预期」的逻辑 pub fn put_nal( - mut vm: impl VmRuntime, + vm: &mut impl VmRuntime, input: NALInput, output_cache: &mut Vec, ) -> Result<()> { diff --git a/src/tests/cli/config/matriangle_server.json b/src/tests/cli/config/matriangle_server.json new file mode 100644 index 0000000..716035f --- /dev/null +++ b/src/tests/cli/config/matriangle_server.json @@ -0,0 +1,8 @@ +{ + "description": "对接Matriangle服务器,兼容旧BabelNAR与Matriangle的Websocket交互逻辑", + "websocket": { + "host": "localhost", + "port": 8765 + }, + "input_mode": "cmd" +} \ No newline at end of file diff --git a/src/tests/cli/config_opennars.json b/src/tests/cli/config/opennars.json similarity index 71% rename from src/tests/cli/config_opennars.json rename to src/tests/cli/config/opennars.json index 620cdd3..b67eb88 100644 --- a/src/tests/cli/config_opennars.json +++ b/src/tests/cli/config/opennars.json @@ -2,11 +2,11 @@ "translators": "opennars", "command": { "cmd": "java", - "cmd_args": [ + "cmdArgs": [ "-Xmx1024m", "-jar", "nars.jar" ], - "current_dir": "root/nars/test" + "currentDir": "root/nars/test" } } \ No newline at end of file diff --git a/src/tests/cli/config/test_ona.json b/src/tests/cli/config/test_ona.json new file mode 100644 index 0000000..f5c0fb9 --- /dev/null +++ b/src/tests/cli/config/test_ona.json @@ -0,0 +1,11 @@ +{ + "description": "用于测试ONA,使用了与项目无关的本地路径(ONA依赖cygwin,故即便内置也容易出错)", + "translators": "ona", + "command": { + "cmd": "../../NARS-executables/NAR.exe", + "cmdArgs": [ + "shell" + ] + }, + "autoRestart": true +} \ No newline at end of file diff --git a/src/tests/cli/config_websocket.json b/src/tests/cli/config/websocket.json similarity index 54% rename from src/tests/cli/config_websocket.json rename to src/tests/cli/config/websocket.json index 612abf0..e6de238 100644 --- a/src/tests/cli/config_websocket.json +++ b/src/tests/cli/config/websocket.json @@ -1,4 +1,5 @@ { + "description": "用于测试可作补丁的Websocket配置", "websocket": { "host": "localhost", "port": 8080 diff --git a/src/tests/cli/config_test_ona.json b/src/tests/cli/config_test_ona.json deleted file mode 100644 index 99160d4..0000000 --- a/src/tests/cli/config_test_ona.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "translators": "ona", - "command": { - "cmd": "H:/A137442/Develop/AGI/NARS/NARS-executables/NAR.exe", - "cmd_args": [ - "shell" - ] - } -} \ No newline at end of file From ebace25128e3a84a184a1c9528a85ac87dfe8027 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 02:26:49 +0800 Subject: [PATCH 36/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=A4=A7=E6=9B=B4?= =?UTF-8?q?=E6=96=B03=EF=BC=9A=E5=9F=BA=E6=9C=AC=E7=9A=84=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 📌统一的「输出缓存」系统,完整的「终止信号控制」 2. ✅预加载NAL测试通过:测试时输出、预期系统(非严格)、测试后自动结束并返回结果(尚未对接「预期系统」) 3. ✅为所有的「REM」指令添加「输入转译」方式:空字串(相当于自动忽略) 4. 🏗️基于「NAVM状态」完善「终止处理」「自动重启」「自动测试」逻辑 5. ✅为`.nal`新增「''terminate」魔法注释,用于自动化测试 --- .gitignore | 5 +- Cargo.lock | 3 +- Cargo.toml | 5 +- src/bin/babelnar_cli/main.rs | 56 +++- src/bin/babelnar_cli/runtime_manage.rs | 227 ++++++++++++---- src/bin/cin_launcher/main.rs | 2 +- src/cin_implements/cxin_js/translators.rs | 4 +- src/cin_implements/nars_python/translators.rs | 4 +- src/cin_implements/ona/translators.rs | 8 +- src/cin_implements/openjunars/translators.rs | 4 +- src/cin_implements/opennars/translators.rs | 6 +- src/cin_implements/pynars/translators.rs | 4 +- src/runtimes/command_vm/api/translators.rs | 70 +++-- src/runtimes/command_vm/runtime.rs | 43 ++- src/runtimes/mod.rs | 4 +- src/test_tools/nal_format/mod.rs | 42 ++- src/test_tools/nal_format/nal_grammar.pest | 26 +- src/test_tools/structs.rs | 58 +++- src/test_tools/vm_interact.rs | 247 ++++++++++++++++-- .../cli/config/test_prelude_operation.json | 8 + .../config/test_prelude_simple_deduction.json | 8 + src/tests/nal/test_operation.nal | 7 + src/tests/nal/test_simple_deduction.nal | 11 +- 23 files changed, 716 insertions(+), 136 deletions(-) create mode 100644 src/tests/cli/config/test_prelude_operation.json create mode 100644 src/tests/cli/config/test_prelude_simple_deduction.json diff --git a/.gitignore b/.gitignore index 1f6dc99..90602f6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ executables/ # 临时文件 -*tempCodeRunnerFile* \ No newline at end of file +*tempCodeRunnerFile* + +# 测试用文件 +\[test\]* diff --git a/Cargo.lock b/Cargo.lock index 8aad07b..7eea3c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,7 @@ dependencies = [ "regex", "serde", "serde_json", + "thiserror", "ws", ] @@ -449,7 +450,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index 1445d3b..bc83c8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,9 @@ edition = "2021" ## 必要的依赖 ## [dependencies] - # 用于错误处理 -[dependencies.anyhow] -version = "1.0.81" +thiserror = "1.0.58" +anyhow = "1.0.81" [dependencies.nar_dev_utils] # 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index dc52faa..0bcdc07 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -7,6 +7,7 @@ //! usage: BabelNAR [OPTIONS] //! ``` +use anyhow::Result; use babel_nar::println_cli; use clap::Parser; use std::io::Result as IoResult; @@ -26,7 +27,7 @@ nar_dev_utils::mods! { } /// 主入口 -pub fn main() { +pub fn main() -> Result<()> { // 以默认参数启动 main_args(env::current_dir(), env::args()) } @@ -34,7 +35,7 @@ pub fn main() { /// 以特定参数开始命令行主程序 /// * 🚩此处只应该有自[`env`]传入的参数 /// * 🚩【2024-04-01 14:25:38】暂时用不到「当前工作路径」 -pub fn main_args(_cwd: IoResult, args: impl Iterator) { +pub fn main_args(_cwd: IoResult, args: impl Iterator) -> Result<()> { let args = CliArgs::parse_from(args); // 读取配置 | with 默认配置文件 let mut config = load_config(&args, DEFAULT_CONFIG_PATH); @@ -49,30 +50,30 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) { println_cli!([Error] "NARS运行时启动错误:{e}"); println_cli!([Info] "程序将在 3 秒后自动退出。。。"); sleep(Duration::from_secs(3)); - return; + return Err(e); } }; // 运行时交互、管理 let manager = RuntimeManager::new(runtime, config.clone()); - loop_manage(manager, &config); + let result = loop_manage(manager, &config); // 最终退出 println_cli!([Info] "程序将在 5 秒后退出"); sleep(Duration::from_secs(5)); + result } /// 单元测试 #[cfg(test)] mod tests { use super::*; - use nar_dev_utils::if_return; - /// 测试入口 + /// 测试入口/ONA/交互shell + /// * 🎯正常BabelNAR CLI shell启动 + /// * 🎯正常用户命令行交互体验 + /// * ⚠️使用与项目无关的路径,以定位启动CIN #[test] - pub fn main_ona() { - // ! 此处需要测试用路径 - const PATH_ONA_EXE: &str = "../../NARS-executables/NAR.exe"; - if_return! { !PathBuf::from(PATH_ONA_EXE).exists() } + pub fn main_ona_shell() -> Result<()> { // 以默认参数启动 main_args( env::current_dir(), @@ -86,4 +87,39 @@ mod tests { .map(str::to_string), ) } + + /// 测试入口/ONA/预加载NAL + /// * 🎯多「虚拟机启动配置」合并 + /// * 🎯预引入NAL + /// * ⚠️使用与项目无关的路径,以定位启动CIN + pub fn main_ona_prelude(prelude_config_path: &str) -> Result<()> { + // 以默认参数启动 + main_args( + env::current_dir(), + [ + "test.exe", + "-d", + // 第一个文件,指示ONA + "-c", + "./src/tests/cli/config/test_ona.json", + // 第二个文件,指示预加载 + "-c", + prelude_config_path, + ] + .into_iter() + .map(str::to_string), + ) + } + + #[test] + pub fn test_ona_prelude_de() -> Result<()> { + main_ona_prelude("./src/tests/cli/config/test_prelude_simple_deduction.json") + } + + #[test] + pub fn test_ona_prelude_op() -> Result<()> { + main_ona_prelude("./src/tests/cli/config/test_prelude_operation.json") + } } + +// mod test_ws; diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 6f1853e..71cabb4 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -5,14 +5,19 @@ use anyhow::{anyhow, Result}; use babel_nar::{ cli_support::error_handling_boost::error_anyhow, eprintln_cli, println_cli, - test_tools::{nal_format::parse, put_nal}, + test_tools::{nal_format::parse, put_nal, VmOutputCache}, }; use nar_dev_utils::ResultBoost; -use navm::{cmd::Cmd, output::Output, vm::VmRuntime}; +use navm::{ + cmd::Cmd, + output::Output, + vm::{VmRuntime, VmStatus}, +}; use std::{ fmt::Debug, io::Result as IoResult, - sync::{Arc, Mutex}, + ops::ControlFlow, + sync::{Arc, Mutex, MutexGuard}, thread::{self, sleep, JoinHandle}, time::Duration, }; @@ -46,6 +51,77 @@ impl Iterator for ReadlineIter { } } +/// 线程间可变引用计数的别名 +type ArcMutex = Arc>; + +/// 输出缓存 +/// * 🎯统一「加入输出⇒打印输出」的逻辑 +/// * 🚩仅封装一个[`Vec`],而不对其附加任何[`Arc`]、[`Mutex`]的限定 +/// * ❌【2024-04-03 01:43:13】[`Arc`]必须留给[`RuntimeManager`]:需要对其中键的值进行引用 +#[derive(Debug)] +pub struct OutputCache { + /// 内部封装的输出数组 + /// * 🚩【2024-04-03 01:43:41】不附带任何包装类型,仅包装其自身 + inner: Vec, +} + +/// 功能实现 +impl OutputCache { + /// 构造函数 + pub fn new(inner: Vec) -> Self { + Self { inner } + } + + /// 默认[`Arc`]<[`Mutex`]> + pub fn default_arc_mutex() -> ArcMutex { + Arc::new(Mutex::new(Self::default())) + } + + /// 从[`Arc`]<[`Mutex`]>中解锁 + pub fn unlock_arc_mutex(arc_mutex: &mut ArcMutex) -> Result> { + arc_mutex.lock().transform_err(error_anyhow) + } +} + +/// 默认构造:空数组 +impl Default for OutputCache { + fn default() -> Self { + Self::new(vec![]) + } +} + +/// 实现「输出缓存」 +/// * 不再涉及任何[`Arc`]或[`Mutex`] +impl VmOutputCache for OutputCache { + /// 存入输出 + /// * 🎯统一的「打印输出」逻辑 + /// * 🚩【2024-04-03 01:07:55】不打算封装了 + fn put(&mut self, output: Output) -> Result<()> { + // 尝试打印输出 + println_cli!(&output); + + // 加入输出 + self.inner.push(output); + Ok(()) + } + + /// 遍历输出 + /// * 🚩不是返回迭代器,而是用闭包开始计算 + fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result> { + // 遍历 + for output in self.inner.iter() { + // 基于控制流的运行 + match f(output) { + ControlFlow::Break(value) => return Ok(Some(value)), + ControlFlow::Continue(()) => {} + } + } + + // 返回 + Ok(None) + } +} + /// 运行时管理器 /// * 🎯在一个数据结构中封装「虚拟机运行时」与「配置信息」 /// * 📌只负责**单个运行时**的运行管理 @@ -69,7 +145,7 @@ where /// 内部缓存的「NAVM输出」 /// * 🎯用于NAL测试 /// * 🚩多线程共享 - output_cache: Arc>>, + output_cache: Arc>, } impl RuntimeManager @@ -81,7 +157,7 @@ where Self { runtime: Arc::new(Mutex::new(runtime)), config: Arc::new(config), - output_cache: Arc::new(Mutex::new(vec![])), + output_cache: OutputCache::default_arc_mutex(), } } @@ -89,17 +165,31 @@ where /// * 🎯健壮性:更多「警告/重来」而非`panic` /// * 🎯用户友好:尽可能隐藏底层内容 /// * 如错误堆栈 - /// * 🚩`.nal`脚本预加载逻辑 - /// * 🚩用户的运行时交互逻辑 - /// * 🚩Websocket服务器逻辑 - pub fn manage(&mut self) -> Result<()> { - // 预置输入 | 不会阻塞后续运行 - self.prelude_nal() - .unwrap_or_else(|e| println_cli!([Error] "预置NAL输入发生错误:{e}")); - - // 生成「读取输出」子线程 + /// * 📌主要逻辑 + /// * `.nal`脚本预加载 + /// * 用户的运行时交互 + /// * Websocket服务端 + /// * 🚩【2024-04-03 00:33:41】返回的[`Result`]作为程序的终止码 + /// * `Ok(Ok(..))` ⇒ 程序正常退出 + /// * `Ok(Err(..))` ⇒ 程序异常退出 + pub fn manage(&mut self) -> Result> { + // 生成「读取输出」子线程 | 📌必须最先 let thread_read = self.spawn_read_output()?; + // 预置输入 | ⚠️阻塞 + if let Err(e) = self.prelude_nal() { + println_cli!([Error] "预置NAL输入发生错误:{e}") + } + + // 虚拟机被终止 & 无用户输入 ⇒ 程序退出 + if let VmStatus::Terminated(..) = self.runtime.lock().transform_err(error_anyhow)?.status() + { + if !self.config.user_input { + // 直接返回,使程序退出 + return Ok(Ok(())); + } + } + // 生成「Websocket服务」子线程 let thread_ws = self.spawn_ws_server()?; @@ -120,56 +210,72 @@ where } // 正常运行结束 - Ok(()) + Ok(Ok(())) } /// 预置NAL /// * 🎯用于自动化调取`.nal`文件进行测试 pub fn prelude_nal(&mut self) -> Result<()> { let config = &*self.config; + + // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 + let runtime = &mut *self.runtime.lock().transform_err(error_anyhow)?; + // 仅在有预置NAL时开始 if let Some(prelude_nal) = &config.prelude_nal { - // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 - let runtime = &mut *self.runtime.lock().transform_err(error_anyhow)?; // 尝试获取输出缓冲区引用 | 仅有其它地方panic了才会停止 - let output_cache = &mut *self.output_cache.lock().transform_err(error_anyhow)?; + let output_cache = &mut *OutputCache::unlock_arc_mutex(&mut self.output_cache)?; - // 开始预置 + // 读取内容 let nal = match prelude_nal { // 文件⇒尝试读取文件内容 | ⚠️此处创建了一个新值,所以要统一成`String` LaunchConfigPreludeNAL::File(path) => std::fs::read_to_string(path)?, // 纯文本⇒直接引入 LaunchConfigPreludeNAL::Text(nal) => nal.to_string(), }; + // 输入NAL - Self::input_nal_to_vm(runtime, &nal, output_cache) + Self::input_nal_to_vm(runtime, &nal, output_cache, config) } + + // 返回 Ok(()) } /// 生成「读取输出」子线程 pub fn spawn_read_output(&mut self) -> Result>> { // 准备引用 - let runtime_arc = self.runtime.clone(); + let runtime = self.runtime.clone(); + let output_cache = self.output_cache.clone(); // 启动线程 let thread = thread::spawn(move || { loop { // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 - let mut runtime = runtime_arc.lock().transform_err(error_anyhow)?; + let mut runtime = runtime.lock().transform_err(error_anyhow)?; + + // 若运行时已终止,返回终止信号 + if let VmStatus::Terminated(result) = runtime.status() { + // * 🚩【2024-04-02 21:48:07】↓下面没法简化:[`anyhow::Result`]拷贝之后还是引用 + match result { + Ok(..) => break Ok(()), + Err(e) => break Err(anyhow!("NAVM运行时已终止:{e}")), + } + } + // 尝试拉取所有NAVM运行时输出 - if let Ok(Some(output)) = runtime + while let Ok(Some(output)) = runtime .try_fetch_output() .inspect_err(|e| eprintln_cli!([Error] "尝试拉取NAVM运行时输出时发生错误:{e}")) { // 格式化输出 + // * 🚩可能还要交给Websocket println_cli!(&output); - // 检测终止信号 - // * 🚩【2024-04-02 14:58:19】目前视作「异常退出」 - if let Output::TERMINATED { description } = output { - // println_cli!([Info] "NAVM已终止运行:{description}"); - // 线程退出 - break Err(anyhow!("NAVM意外终止运行:{description}")); + + // 缓存输出 + match output_cache.lock() { + Ok(mut output_cache) => output_cache.put(output)?, + Err(e) => eprintln_cli!([Error] "缓存NAVM运行时输出时发生错误:{e}"), } } } @@ -221,6 +327,15 @@ where .lock() .transform_err(|e| anyhow!("获取运行时引用时发生错误:{e:?}"))?; + // 若运行时已终止,返回终止信号 + if let VmStatus::Terminated(result) = runtime.status() { + // * 🚩【2024-04-02 21:48:07】↓下面没法简化:[`anyhow::Result`]拷贝之后还是引用 + match result { + Ok(..) => return Ok(()), + Err(e) => return Err(anyhow!("NAVM运行时已终止:{e}")), + } + } + // 尝试获取输出缓冲区引用 | 仅有其它地方panic了才会停止 // ! 🚩【2024-04-02 19:27:01】及早报错:即便无关紧要,也停止 let output_cache = &mut *output_cache @@ -248,14 +363,14 @@ where runtime: &mut R, line: &str, config: &LaunchConfig, - output_cache: &mut Vec, + output_cache: &mut OutputCache, ) -> Result<()> { // 向运行时输入 match config.input_mode { // NAVM指令 InputMode::Cmd => Self::input_cmd_to_vm(runtime, line), // NAL输入 - InputMode::Nal => Self::input_nal_to_vm(runtime, line, output_cache), + InputMode::Nal => Self::input_nal_to_vm(runtime, line, output_cache, config), } // 输入完成 @@ -276,14 +391,20 @@ where /// 像NAVM实例输入NAL(输入) /// * 🎯预置、用户输入、Websocket输入 /// * ⚠️可能有多行 - fn input_nal_to_vm(runtime: &mut R, input: &str, output_cache: &mut Vec) { + fn input_nal_to_vm( + runtime: &mut R, + input: &str, + output_cache: &mut OutputCache, + config: &LaunchConfig, + ) { // 解析输入,并遍历解析出的每个NAL输入 for input in parse(input) { // 尝试解析NAL输入 match input { Ok(nal) => { // 尝试置入NAL输入 | 为了错误消息,必须克隆 - put_nal(runtime, nal.clone(), output_cache).unwrap_or_else( + put_nal(runtime, nal.clone(), output_cache, config.user_input).unwrap_or_else( + // TODO: 严格模式:预期失败时上报错误,乃至使整个程序运行失败 |e| eprintln_cli!([Error] "置入NAL输入「{nal:?}」时发生错误:{e}"), ); } @@ -330,22 +451,30 @@ pub fn restart_manager( pub fn loop_manage( mut manager: RuntimeManager, config: &LaunchConfig, -) { - if let Err(e) = manager.manage() { - println_cli!([Error] "运行时发生错误:{e}"); - // 尝试重启 - if config.auto_restart { - println_cli!([Info] "程序将在 2 秒后自动重启。。。"); - sleep(Duration::from_secs(2)); - let new_manager = match restart_manager(manager) { - Ok(manager) => manager, - Err(e) => { - println_cli!([Error] "重启失败:{e}"); - return; - } - }; - // 重启之后继续循环 - loop_manage(new_manager, config); +) -> Result<()> { + match manager.manage() { + // 返回了「结果」⇒解包并传递结果 + Ok(result) => result, + // 发生错误⇒尝试处理 + Err(e) => { + // 打印错误信息 + println_cli!([Error] "运行时发生错误:{e}"); + // 尝试重启 + if config.auto_restart { + println_cli!([Info] "程序将在 2 秒后自动重启。。。"); + sleep(Duration::from_secs(2)); + let new_manager = match restart_manager(manager) { + Ok(manager) => manager, + Err(e) => { + println_cli!([Error] "重启失败:{e}"); + return Err(anyhow!("NAVM运行时发生错误,且重启失败:{e}")); + } + }; + // 重启之后继续循环 + return loop_manage(new_manager, config); + } + // 正常返回 + Ok(()) } } } diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 12eba19..4d586a2 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -6,7 +6,7 @@ //! * 📌可根据「匹配度」排名 //! * ✨自动启动并管理CIN //! * 📌可保存/加载「常用CIN」配置 -//! +//! //! * 🚩目前用于敏捷原型开发 //! TODO: 完成代码 #![allow(unused)] diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs index 8fd1aca..3185a70 100644 --- a/src/cin_implements/cxin_js/translators.rs +++ b/src/cin_implements/cxin_js/translators.rs @@ -37,9 +37,11 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::NSE(..) => cmd.tail(), // CYC指令:运行指定周期数 Cmd::CYC(n) => n.to_string(), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) diff --git a/src/cin_implements/nars_python/translators.rs b/src/cin_implements/nars_python/translators.rs index e8ff8fe..9e396a2 100644 --- a/src/cin_implements/nars_python/translators.rs +++ b/src/cin_implements/nars_python/translators.rs @@ -30,9 +30,11 @@ pub fn input_translate(cmd: Cmd) -> Result { // CYC指令:运行指定周期数 // ! NARS-Python同样是自动步进的 Cmd::CYC(n) => n.to_string(), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 131f12c..00d07e6 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -52,9 +52,11 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::VOL(n) => format!("*volume={n}"), // REG指令:注册操作 Cmd::REG { name } => format!("*setopname {} ^{name}", hash_operator_id(&name)), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) @@ -177,8 +179,8 @@ pub fn output_translate(content_raw: String) -> Result { /// * ❌`right executed by NAR` pub fn parse_operation_ona(content_raw: &str) -> Result { // 匹配ONA输出中的「操作」⇒转换 | 操作名 | 操作参数(Narsese复合词项⇒提取组分,变成字符串) - let re_operation = Regex::new(r"\^([^\s]+)\s*executed with args\s*(.*)$").unwrap(); - let captures = re_capture(&re_operation, content_raw)?; + let re_operation = Regex::new(r"\^([^\s]+)\s*executed with args\s*(.*)").unwrap(); + let captures = re_capture(&re_operation, content_raw.trim())?; // ! 即便是测试环境下,也有可能是[`None`](但只在测试环境下返回[`Err`]并报错) match captures { Some(captures) => { diff --git a/src/cin_implements/openjunars/translators.rs b/src/cin_implements/openjunars/translators.rs index 52028d6..1a25206 100644 --- a/src/cin_implements/openjunars/translators.rs +++ b/src/cin_implements/openjunars/translators.rs @@ -21,10 +21,12 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::NSE(..) => cmd.tail(), // CYC指令:运行指定周期数 Cmd::CYC(n) => format!(":c {n}"), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 74b37b2..36341ec 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -39,10 +39,12 @@ pub fn input_translate(cmd: Cmd) -> Result { Cmd::CYC(n) => n.to_string(), // VOL指令:调整音量 Cmd::VOL(n) => format!("*volume={n}"), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) @@ -191,7 +193,7 @@ fn try_parse_narsese(tail: &str) -> Result { // 解析成功⇒提取 & 返回 Some(Ok(narsese)) => Ok(narsese), // 解析失败⇒打印错误日志 | 返回None - Some(Err(err)) => Err(TranslateError(format!("输出「OUT」解析失败:{err}")).into()), + Some(Err(err)) => Err(TranslateError::from(err).into()), // 未找到括号的情况 None => Err(TranslateError::from("输出「OUT」解析失败:未找到「{」").into()), } diff --git a/src/cin_implements/pynars/translators.rs b/src/cin_implements/pynars/translators.rs index 85ae4b0..8243dd7 100644 --- a/src/cin_implements/pynars/translators.rs +++ b/src/cin_implements/pynars/translators.rs @@ -45,10 +45,12 @@ pub fn input_translate(cmd: Cmd) -> Result { // * 📄Input: /register name // * `Operator ^name was successfully registered without code` Cmd::REG { name, .. } => format!("/register {name}"), + // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) + Cmd::REM { .. } => String::new(), // 其它类型 // * 📌【2024-03-24 22:57:18】基本足够支持 // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 - _ => return Err(TranslateError(format!("该指令类型暂不支持:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转译 Ok(content) diff --git a/src/runtimes/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs index 300a3d4..8e67139 100644 --- a/src/runtimes/command_vm/api/translators.rs +++ b/src/runtimes/command_vm/api/translators.rs @@ -1,6 +1,12 @@ +//! 定义有关「输入输出转译器」的API +//! * ✨类型别名 +//! * ✨特制结构 +//! * ✨特有错误类型 + use anyhow::Result; use navm::{cmd::Cmd, output::Output}; -use std::{error::Error, fmt::Display}; +use std::error::Error; +use thiserror::Error; /// [`Cmd`]→进程输入 转译器 /// * 🚩现在不再使用特征,以便在`Option>`中推断类型 @@ -78,40 +84,60 @@ where /// 错误类型 mod translate_error { + use anyhow::anyhow; + use super::*; /// 统一封装「转译错误」 /// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 /// * 🎯用于可识别的错误,并在打印时直接展示原因 /// * ⚠️若直接使用[`anyhow::anyhow`],会打印一大堆错误堆栈 - #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] - pub struct TranslateError(pub String); - - // ! ❌【2024-03-27 22:40:22】无法正常使用:不能导出带`format!`的宏 - // * error: macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths - // #[macro_export] - // macro_rules! translate_error { - // ($($t:tt)*) => { - // TranslateError(format!($($t)*)) - // }; - // } + /// * 🚩【2024-04-02 22:05:30】现在展开成枚举 + /// * 🎯以便捕获方识别为「警告」 + #[derive(Debug, Error)] + pub enum TranslateError { + /// 不支持的NAVM指令 + /// * 📌一般处理方法:警告+静默置空 + /// * 📌用「调用者的处理场合」判断是「输入转译不支持」还是「输出转译不支持」 + #[error("不支持的NAVM指令:\"{0}\"")] + UnsupportedInput(Cmd), + /// 解析错误 + /// * 🎯表示原先的「转译错误」 + #[error("NAVM转译错误:「{0}」")] + ParseError(#[from] anyhow::Error), + } + // ! ❌弃用:为一个泛型参数实现转换,会导致其它「泛型实现」无法使用 + // /// 灵活地从字符串转换为[`TranslateError`] + // impl> From for TranslateError { + // fn from(value: S) -> Self { + // Self::ParseError(value.as_ref().to_string()) + // } + // } /// 灵活地从字符串转换为[`TranslateError`] - impl> From for TranslateError { - fn from(value: S) -> Self { - Self(value.as_ref().to_string()) + impl From<&'_ str> for TranslateError { + fn from(value: &'_ str) -> Self { + Self::ParseError(anyhow!("{value}")) + } + } + impl From<&'_ String> for TranslateError { + fn from(value: &'_ String) -> Self { + Self::ParseError(anyhow!("{value}")) } } /// 灵活地从[`Error`]转换为[`TranslateError`] impl TranslateError { /// 从[`Error`]转换为[`TranslateError`] + /// * 🚩目前还是调用 pub fn from_error(value: impl Error) -> Self { - Self(value.to_string()) + Self::from(&value.to_string()) } + /// 从[`Error`]转换为[`anyhow::Error`] - pub fn error_anyhow(value: impl Error) -> anyhow::Error { - Self::from_error(value).into() + /// * 🚩【2024-04-02 22:39:47】此处「转换为[`anyhow::Error`]的需求」就是`Error + Send + Sync + 'static` + pub fn error_anyhow(value: impl Error + Send + Sync + 'static) -> anyhow::Error { + Self::ParseError(value.into()).into() } /// 从「一切可以转换为其自身的值」构建[`anyhow::Result`] @@ -130,14 +156,6 @@ mod translate_error { anyhow::Error::from(value.into()) } } - /// 展示错误 - impl Display for TranslateError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "TranslateError: {}", self.0) - } - } - /// 实现[`Error`]特征 - impl Error for TranslateError {} } pub use translate_error::*; diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 0f90a78..c4e6cb8 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -8,11 +8,12 @@ use super::{CommandVm, InputTranslator, OutputTranslator}; use crate::process_io::IoProcessManager; -use anyhow::Result; +use anyhow::{anyhow, Result}; +use nar_dev_utils::if_return; use navm::{ cmd::Cmd, output::Output, - vm::{VmLauncher, VmRuntime}, + vm::{VmLauncher, VmRuntime, VmStatus}, }; /// 命令行虚拟机运行时 @@ -28,14 +29,22 @@ pub struct CommandVmRuntime { /// 进程输出→[`Output`]转译器 /// * 🚩【2024-03-24 02:06:27】至于「输出侦听」等后续处理,外置给其它专用「处理者」 output_translator: Box, + + /// 用于指示的「状态」变量 + status: VmStatus, } impl VmRuntime for CommandVmRuntime { fn input_cmd(&mut self, cmd: Cmd) -> Result<()> { // 尝试转译 let input = (self.input_translator)(cmd)?; - // 置入转译结果 - self.process.put_line(input) + // 当输入非空时,置入转译结果 + // * 🚩【2024-04-03 02:20:48】目前用「空字串」作为「空输入」的情形 + // TODO: 后续或将让「转译器」返回`Option` + // 空⇒提前返回 + if_return! { input.is_empty() => Ok(()) } + // 置入 + self.process.put(input) } fn fetch_output(&mut self) -> Result { @@ -48,15 +57,35 @@ impl VmRuntime for CommandVmRuntime { // 匹配分支 match s { // 有输出⇒尝试转译并返回 - Some(s) => Ok(Some((self.output_translator)(s)?)), + Some(s) => Ok(Some({ + // 转译输出 + let output = (self.output_translator)(s)?; + // * 当输出为「TERMINATED」时,将自身终止状态置为「TERMINATED」 + if let Output::TERMINATED { description } = &output { + // ! 🚩【2024-04-02 21:39:56】目前将所有「终止」视作「意外终止」⇒返回`Err` + self.status = VmStatus::Terminated(Err(anyhow!(description.clone()))); + } + // 传出输出 + output + })), // 没输出⇒没输出 | ⚠️注意:不能使用`map`,否则`?`穿透不出闭包 None => Ok(None), } } + fn status(&self) -> &VmStatus { + &self.status + } + fn terminate(&mut self) -> Result<()> { // 杀死子进程 self.process.kill()?; + + // (杀死后)设置状态 + // * 🚩【2024-04-02 21:42:30】目前直接覆盖状态 + self.status = VmStatus::Terminated(Ok(())); + + // 返回「终止完成」 Ok(()) } } @@ -65,6 +94,8 @@ impl VmRuntime for CommandVmRuntime { impl VmLauncher for CommandVm { fn launch(self) -> Result { Ok(CommandVmRuntime { + // 状态:正在运行 + status: VmStatus::Running, // 启动内部的「进程管理者」 process: self.io_process.launch()?, // 输入转译器 @@ -349,7 +380,7 @@ pub mod tests { // VOL指令:调整音量 Cmd::VOL(n) => format!("*volume={n}"), // 其它类型 - _ => return Err(TranslateError(format!("未知指令:{cmd:?}")).into()), + _ => return Err(TranslateError::UnsupportedInput(cmd).into()), }; // 转换 Ok(content) diff --git a/src/runtimes/mod.rs b/src/runtimes/mod.rs index cc5e081..bf5d808 100644 --- a/src/runtimes/mod.rs +++ b/src/runtimes/mod.rs @@ -2,7 +2,7 @@ //! * 📌不与特定的CIN相关 //! * 📄一个「命令行运行时」可同时适用于OpenNARS、ONA、NARS-Python…… -util::mod_and_pub_use! { +util::mods! { // 命令行运行时 - command_vm + pub pub command_vm; } diff --git a/src/test_tools/nal_format/mod.rs b/src/test_tools/nal_format/mod.rs index 3113115..331b1f6 100644 --- a/src/test_tools/nal_format/mod.rs +++ b/src/test_tools/nal_format/mod.rs @@ -3,7 +3,7 @@ //! * 🎯提供一种(部分)兼容现有`.nal`格式文件的语法 //! * ⚠️对其中所有Narsese部分使用CommonNarsese「通用纳思语」:不兼容方言 -use std::time::Duration; +use std::{result::Result::Err as StdErr, result::Result::Ok as StdOk, time::Duration}; use super::structs::*; use anyhow::{Ok, Result}; @@ -132,6 +132,40 @@ fn fold_pest(pair: Pair) -> Result { let output_expectation = fold_pest_output_expectation(output_expectation)?; Ok(NALInput::ExpectContains(output_expectation)) } + // 魔法注释/终止 + Rule::comment_terminate => { + // 预置默认值 + let mut if_not_user = false; + let mut result = StdOk(()); + + // 遍历其中的Pair + for inner in pair.into_inner() { + // 逐个匹配规则类型 + // * ✨comment_terminate_option: `if-not-user` + // * ✨comment_raw: Err(`message`) + match inner.as_rule() { + // 可选规则 + Rule::comment_terminate_option => { + if inner.as_str() == "if-no-user" { + if_not_user = true; + } + } + // 错误消息 + Rule::comment_raw => { + // 构造错误 | 仅取注释部分 + result = StdErr(inner.as_str().trim().into()) + } + // 其它 + _ => unreachable!("不该被匹配到的规则\tpair = {inner:?}"), + } + } + + // 构造&返回 + Ok(NALInput::Terminate { + if_not_user, + result, + }) + } // 其它情况 _ => unreachable!("不该被匹配到的规则\tpair = {pair:?}"), } @@ -186,7 +220,7 @@ fn fold_pest_output_operation(pair: Pair) -> Result { // 生成迭代器 let mut pairs = pair.into_inner(); // 取第一个子Pair当操作名 | 语法上保证一定有 - let operator_name = pairs.next().unwrap().to_string(); + let operator_name = pairs.next().unwrap().as_str().to_owned(); // 操作参数 let mut params = vec![]; // 消耗剩下的,填充参数 @@ -241,7 +275,8 @@ G3! :|: ''sleep: 500ms 10 -''expect-contains: EXE (^left, {SELF}, (*, P1, P2))"; +''expect-contains: EXE (^left, {SELF}, (*, P1, P2)) +''terminate(if-no-user)"; #[test] fn test_parse() { @@ -254,6 +289,7 @@ G3! :|: _test_parse("''sleep: 500ms"); _test_parse("''sleep: 5000μs"); _test_parse("''sleep: 600ns"); + _test_parse("''terminate(if-no-user): 异常的退出消息!"); _test_parse(TESTSET); } diff --git a/src/test_tools/nal_format/nal_grammar.pest b/src/test_tools/nal_format/nal_grammar.pest index eb7579b..f446038 100644 --- a/src/test_tools/nal_format/nal_grammar.pest +++ b/src/test_tools/nal_format/nal_grammar.pest @@ -29,7 +29,7 @@ cyc_uint = { ASCII_DIGIT+ } /// 注释(静默) /// * 🚩包括「输出预期」等「魔法注释」 comment = _{ - comment_head ~ (comment_navm_cmd | comment_sleep | comment_await | comment_expect_contains | comment_raw) + comment_head ~ (comment_navm_cmd | comment_sleep | comment_await | comment_expect_contains | comment_terminate | comment_raw) } /// 注释的头部字符(静默) @@ -39,9 +39,11 @@ comment_head = _{ "'" } /// * ✨允许构建并向NAVM置入指令 /// * 📄用`'/VOL 0`代替非通用的`*volume=0` /// * 📄亦可直接用三个`'`:`'''VOL 0` -comment_navm_cmd = !{ +/// * 🚩【2024-04-02 23:44:06】现在使用`$`要求「特殊前缀」紧挨内容,避免误认「被注释掉的注释」为指令 +/// * ✅使用正谓词`&LETTER`要求必须是`/文字`而非其它(如`//`) +comment_navm_cmd = ${ // 特殊前缀`/`或`''` - ("/" | "''") ~ WHITESPACE* ~ comment_raw + ("/" | "''") ~ &LETTER ~ comment_raw } /// 有关「睡眠等待」的「魔法注释」 @@ -67,6 +69,24 @@ comment_expect_contains = { "'expect-contains:" ~ output_expectation } +/// 有关「终止」的「魔法注释」 +/// ✨终止NAVM虚拟机 +/// * 📄参数:选项、理由 +/// * 选项:决定执行的条件 +/// * 📌无 ⇒ 无条件强制退出 +/// * 📌有 ⇒ 有条件,或其它副作用 +/// * 理由:决定返回是否「正常」 +/// * 📌无理由 ⇒ 虚拟机返回 `Ok` +/// * 📌有理由 ⇒ 虚拟机返回 `Err(终止理由)` +comment_terminate = { + // 额外的前缀 | 可选的「错误」参数 + "'terminate" ~ ("(" ~ comment_terminate_option ~ ")")? ~ (":" ~ comment_raw)? +} + +/// 虚拟机终止指令的选项 +/// * 🎯控制终止的前提条件:可以在「终止」后交由用户输入 +comment_terminate_option = @{ "if-no-user" } + /// 原始注释语法:纯粹的行注释 /// * ✅`REM`的语法糖 comment_raw = @{ (!"\n" ~ ANY)* } diff --git a/src/test_tools/structs.rs b/src/test_tools/structs.rs index aa069ba..7b4c081 100644 --- a/src/test_tools/structs.rs +++ b/src/test_tools/structs.rs @@ -3,9 +3,10 @@ //! * ✨[`NALInput`]:在「直接对应CIN输入输出」的「NAVM指令」之上,引入「等待」「预期」等机制 //! * ✨[`OutputExpectation`]:面向NAL测试,具体实现「预期」机制 -use narsese::lexical::Narsese; +use narsese::{conversion::string::impl_lexical::format_instances::FORMAT_ASCII, lexical::Narsese}; use navm::{cmd::Cmd, output::Operation}; -use std::time::Duration; +use std::{fmt::Display, time::Duration}; +use thiserror::Error; /// NAVM测试中的「NAL输入」 /// * 📌`.nal`文件中一行的超集 @@ -17,20 +18,41 @@ pub enum NALInput { Put(Cmd), /// 睡眠 + /// * 📄语法示例:`''sleep 1s` /// * 📌调用[`thread::sleep`]单纯等待一段时间(单位:[`Duration`]) /// * 🚩语法中常用的是秒数,但这里不直接存储 Sleep(Duration), /// 输出等待 + /// * 📄语法示例:`''await: IN B>.` /// * 📌在CIN输出与指定[`Output`]符合后,再继续运行 /// * 🎯用于结合`IN`等待CIN「回显」 Await(OutputExpectation), /// 对「输出含有」的预期 + /// * 📄语法示例:`''expect-contains: ANSWER C>.` /// * 🎯用于「在现有的输出中检查是否任一和指定的[`Output`]符合」 /// * 📄对应OpenNARS中常有的`''outputMustContain('')` ExpectContains(OutputExpectation), - // 🏗️后续还能有更多 + + /// 终止虚拟机 + /// * 🎯用于「预加载NAL『测试』结束后,程序自动退出/交给用户输入」 + /// * 📄语法示例: + /// * `''terminate` + /// * `''terminate(if-no-user): 异常的退出消息!` + /// * 🔧可选的「子参数」 + /// * `if-no-user`:仅在「用户无法输入」时退出 + Terminate { + /// 仅在「用户无法输入」时退出 + /// * 🎯用于「测试完毕后交给用户输入」的测试 + if_not_user: bool, + + /// 退出的返回值 + /// * 🎯用于「测试完毕后向外部传递结果」的测试 + /// * 💭始终注意这只是个线性执行的指令,不要做得太复杂 + /// * 🚩【2024-04-02 23:56:34】目前不在此装载[`anyhow::Error`]类型:避免复杂 + result: std::result::Result<(), String>, + }, } /// 输出预期 @@ -56,3 +78,33 @@ pub struct OutputExpectation { /// * 对任何可能的输入都适用 pub operation: Option, } + +impl Display for OutputExpectation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "OutputExpectation {{ {} {} {} }}", + self.output_type.as_deref().unwrap_or("*"), + match &self.narsese { + Some(narsese) => FORMAT_ASCII.format_narsese(narsese), + None => "*".to_string(), + }, + self.operation + .as_ref() + .map(|op| op.to_string()) + .unwrap_or("*".to_string()), + ) + } +} + +/// 预期错误 +/// * 🎯用于定义可被识别的「NAL预期失败/脱离预期」错误 +/// * 🚩使用[`thiserror`]快捷定义 +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum OutputExpectationError { + /// 输出未包含预期 + /// * 🎯对应[`NALInput::ExpectContains`] + /// * 📝此处`{0:?}`参照 + #[error("输出内容中不存在符合预期的输出:{0}")] + ExpectedNotExists(OutputExpectation), +} diff --git a/src/test_tools/vm_interact.rs b/src/test_tools/vm_interact.rs index 74d1818..7f6d45c 100644 --- a/src/test_tools/vm_interact.rs +++ b/src/test_tools/vm_interact.rs @@ -1,17 +1,206 @@ //! 与NAVM虚拟机的交互逻辑 -use super::{NALInput, OutputExpectation}; +use std::ops::ControlFlow; + +use crate::cli_support::error_handling_boost::error_anyhow; + +use super::{NALInput, OutputExpectation, OutputExpectationError}; use anyhow::Result; +use nar_dev_utils::{if_return, ResultBoost}; use navm::{output::Output, vm::VmRuntime}; +/// * 🎯统一存放与「Narsese预期识别」有关的代码 +/// * 🚩【2024-04-02 22:49:12】从[`crate::runtimes::command_vm::runtime::tests`]中迁移而来 +mod narsese_expectation { + use nar_dev_utils::if_return; + use narsese::{ + api::{GetBudget, GetPunctuation, GetStamp, GetTerm, GetTruth}, + conversion::{ + inter_type::lexical_fold::TryFoldInto, + string::impl_enum::format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, + }, + enum_narsese::{ + Budget as EnumBudget, Narsese as EnumNarsese, Sentence as EnumSentence, + Task as EnumTask, Term as EnumTerm, Truth as EnumTruth, + }, + lexical::Narsese, + }; + use navm::output::Operation; + + /// 判断「输出是否(在Narsese语义层面)符合预期」 + /// * 🎯词法Narsese⇒枚举Narsese,以便从语义上判断 + pub fn is_expected_narsese_lexical(expected: &Narsese, out: &Narsese) -> bool { + // 临时折叠预期 + let expected = (expected.clone().try_fold_into(&FORMAT_ASCII_ENUM)) + .expect("作为预期的词法Narsese无法折叠!"); + // 与预期一致 + (out.clone() // 必须复制:折叠消耗自身 + .try_fold_into(&FORMAT_ASCII_ENUM)) + .is_ok_and(|out| is_expected_narsese(&expected, &out)) + } + + /// 判断「输出是否(在Narsese层面)符合预期」 + /// * 🎯预期词项⇒只比较词项,语句⇒只比较语句,…… + pub fn is_expected_narsese(expected: &EnumNarsese, out: &EnumNarsese) -> bool { + match ((expected), (out)) { + // 词项⇒只比较词项 | 直接判等 + (EnumNarsese::Term(term), ..) => is_expected_term(term, out.get_term()), + // 语句⇒只比较语句 + // ! 仍然不能直接判等:真值/预算值 + ( + EnumNarsese::Sentence(s_exp), + EnumNarsese::Sentence(s_out) | EnumNarsese::Task(EnumTask(s_out, ..)), + ) => is_expected_sentence(s_exp, s_out), + // 任务⇒直接判断 + // ! 仍然不能直接判等:真值/预算值 + (EnumNarsese::Task(t_exp), EnumNarsese::Task(t_out)) => is_expected_task(t_exp, t_out), + // 所有其它情况⇒都是假 + (..) => false, + } + } + + /// 判断输出的任务是否与预期任务相同 + /// * 🎯用于细粒度判断「预算值」「语句」的预期 + pub fn is_expected_task(expected: &EnumTask, out: &EnumTask) -> bool { + // 预算 + is_expected_budget(expected.get_budget(), out.get_budget()) + // 语句 + && is_expected_sentence(expected.get_sentence(), out.get_sentence()) + } + + /// 判断输出的语句是否与预期语句相同 + /// * 🎯用于细粒度判断「真值」的预期 + pub fn is_expected_sentence(expected: &EnumSentence, out: &EnumSentence) -> bool { + // 词项 + (is_expected_term(expected.get_term(),out.get_term())) + // 标点相等 + && expected.get_punctuation() == out.get_punctuation() + // 时间戳相等 + && expected.get_stamp()== out.get_stamp() + // 真值兼容 | 需要考虑「没有真值可判断」的情况 + && match (expected.get_truth(),out.get_truth()) { + // 都有⇒判断「真值是否符合预期」 + (Some(t_e), Some(t_o)) => is_expected_truth(t_e, t_o), + // 都没⇒肯定真 + (None, None) => true, + // 有一个没有⇒肯定假 + _ => false, + } + } + + /// 判断输出的词项是否与预期词项相同 + /// * 🎯用于独立出「词项预期」功能 + /// * 🚩【2024-04-02 22:55:13】目前直接判等 + pub fn is_expected_term(expected: &EnumTerm, out: &EnumTerm) -> bool { + expected == out + } + + /// 判断「输出是否在真值层面符合预期」 + /// * 🎯空真值的语句,应该符合「固定真值的语句」的预期——相当于「通配符」 + pub fn is_expected_truth(expected: &EnumTruth, out: &EnumTruth) -> bool { + match (expected, out) { + // 预期空真值⇒通配 + (EnumTruth::Empty, ..) => true, + // 预期单真值 + (EnumTruth::Single(f_e), EnumTruth::Single(f_o) | EnumTruth::Double(f_o, ..)) => { + f_e == f_o + } + // 预期双真值 + (EnumTruth::Double(..), EnumTruth::Double(..)) => expected == out, + // 其它情况 + _ => false, + } + } + + /// 判断「输出是否在预算值层面符合预期」 + /// * 🎯空预算的语句,应该符合「固定预算值的语句」的预期——相当于「通配符」 + pub fn is_expected_budget(expected: &EnumBudget, out: &EnumBudget) -> bool { + match (expected, out) { + // 预期空预算⇒通配 + (EnumBudget::Empty, ..) => true, + // 预期单预算 + ( + EnumBudget::Single(p_e), + EnumBudget::Single(p_o) | EnumBudget::Double(p_o, ..) | EnumBudget::Triple(p_o, ..), + ) => p_e == p_o, + // 预期双预算 + ( + EnumBudget::Double(p_e, d_e), + EnumBudget::Double(p_o, d_o) | EnumBudget::Triple(p_o, d_o, ..), + ) => p_e == p_o && d_e == d_o, + // 预期三预算 + (EnumBudget::Triple(..), EnumBudget::Triple(..)) => expected == out, + // 其它情况 + _ => false, + } + } + + /// 判断「输出是否在操作层面符合预期」 + /// * 🎯仅有「操作符」的「NARS操作」应该能通配所有「NARS操作」 + pub fn is_expected_operation(expected: &Operation, out: &Operation) -> bool { + // 操作符名不同⇒直接pass + if_return! { expected.operator_name != out.operator_name => false } + + // 比对操作参数:先判空 + match (expected.no_params(), out.no_params()) { + // 预期无⇒通配 + (true, ..) => true, + // 预期有,输出无⇒直接pass + (false, true) => false, + // 预期有,输出有⇒判断参数是否相同 + (false, false) => expected.params == out.params, + } + } +} +pub use narsese_expectation::*; + +/// 实现/预期匹配功能 impl OutputExpectation { /// 判断一个「NAVM输出」是否与自身相符合 - /// * 🏗️TODO: 迁移功能 pub fn matches(&self, output: &Output) -> bool { - todo!() + // 输出类型 + if let Some(expected) = &self.output_type { + if_return! { expected != output.type_name() => false } + } + + // Narsese + match (&self.narsese, output.get_narsese()) { + // 预期有,输出无⇒直接pass + (Some(..), None) => return false, + // 预期输出都有⇒判断Narsese是否相同 + (Some(expected), Some(out)) => { + if_return! { !is_expected_narsese_lexical(expected, out) => false } + } + _ => (), + } + + // 操作 | 最后返回 + match (&self.operation, output.get_operation()) { + // 预期无⇒通配 + (None, ..) => true, + // 预期有,输出无⇒直接pass + (Some(_), None) => false, + // 预期有,输出有⇒判断操作是否相同 + (Some(expected), Some(out)) => is_expected_operation(expected, out), + } } } +/// 输出缓存 +/// * 🎯为「使用『推送』功能,而不引入具体数据类型」设置 +/// * 📌基础功能:推送输出、遍历输出 +pub trait VmOutputCache { + /// 存入输出 + /// * 🎯统一的「打印输出」逻辑 + fn put(&mut self, output: Output) -> Result<()>; + + /// 遍历输出 + /// * 🚩不是返回迭代器,而是用闭包开始计算 + /// * 📝使用最新的「控制流」数据结构 + /// * 使用[`None`]代表「一路下来没`break`」 + fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result>; +} + /// 向虚拟机置入[`NALInput`] /// * 🎯除了「输入指令」之外,还附带其它逻辑 /// * 🚩通过「输出缓存」参数,解决「缓存输出」问题 @@ -19,7 +208,9 @@ impl OutputExpectation { pub fn put_nal( vm: &mut impl VmRuntime, input: NALInput, - output_cache: &mut Vec, + output_cache: &mut impl VmOutputCache, + // 不能传入「启动配置」,就要传入「是否启用用户输入」状态变量 + enabled_user_input: bool, ) -> Result<()> { match input { // 置入NAVM指令 @@ -36,9 +227,10 @@ pub fn put_nal( let output = match vm.fetch_output() { Ok(output) => { // 加入缓存 - output_cache.push(output); - // 返回引用 - output_cache.last().unwrap() + output_cache.put(output.clone())?; + // ! ❌【2024-04-03 01:19:06】无法再返回引用:不再能直接操作数组,MutexGuard也不允许返回引用 + // output_cache.last().unwrap() + output } Err(e) => { println!("尝试拉取输出出错:{e}"); @@ -46,7 +238,7 @@ pub fn put_nal( } }; // 只有匹配了才返回 - if expectation.matches(output) { + if expectation.matches(&output) { break Ok(()); } }, @@ -54,17 +246,38 @@ pub fn put_nal( NALInput::ExpectContains(expectation) => { // 先尝试拉取所有输出到「输出缓存」 while let Ok(Some(output)) = vm.try_fetch_output() { - output_cache.push(output); + output_cache.put(output)?; } - // 然后逐个读取输出缓存 - for output in output_cache.iter() { - // 只有匹配了才返回Ok - if expectation.matches(output) { - return Ok(()); - } + // 然后读取并匹配缓存 + let result = output_cache.for_each(|output| match expectation.matches(output) { + true => ControlFlow::Break(true), + false => ControlFlow::Continue(()), + })?; + match result { + // 只有匹配到了一个,才返回Ok + Some(true) => Ok(()), + // 否则返回Err + _ => Err(OutputExpectationError::ExpectedNotExists(expectation).into()), } - // 否则返回Err - Err(anyhow::anyhow!("没有找到符合要求「{expectation:?}」的输出")) + // for output in output_cache.for_each() { + // // 只有匹配了才返回Ok + // if expectation.matches(output) { + // } + // } + } + // 终止虚拟机 + NALInput::Terminate { + if_not_user, + result, + } => { + // 检查前提条件 | 仅「非用户输入」&启用了用户输入 ⇒ 放弃终止 + if_return! { if_not_user && enabled_user_input => Ok(()) } + + // 终止虚拟机 + vm.terminate()?; + + // 返回 + result.transform_err(error_anyhow) } } } diff --git a/src/tests/cli/config/test_prelude_operation.json b/src/tests/cli/config/test_prelude_operation.json new file mode 100644 index 0000000..dd295d2 --- /dev/null +++ b/src/tests/cli/config/test_prelude_operation.json @@ -0,0 +1,8 @@ +{ + "description": "用于测试「预加载NAL输入」,加载「操作测试」", + "autoRestart": true, + "userInput": false, + "preludeNAL": { + "file": "./src/tests/nal/test_operation.nal" + } +} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_simple_deduction.json b/src/tests/cli/config/test_prelude_simple_deduction.json new file mode 100644 index 0000000..d89e037 --- /dev/null +++ b/src/tests/cli/config/test_prelude_simple_deduction.json @@ -0,0 +1,8 @@ +{ + "description": "用于测试「预加载NAL输入」,加载「简单演绎推理」", + "autoRestart": true, + "userInput": false, + "preludeNAL": { + "file": "./src/tests/nal/test_simple_deduction.nal" + } +} \ No newline at end of file diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal index 541d5f7..03534c5 100644 --- a/src/tests/nal/test_operation.nal +++ b/src/tests/nal/test_operation.nal @@ -5,6 +5,7 @@ ' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` ' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` ' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` +' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出 A. :|: <(*, {SELF}) --> ^left>. :|: @@ -12,6 +13,7 @@ G. :|: A. :|: G! :|: 10 +''sleep: 1s ''expect-contains: EXE (^left, {SELF}) A2. :|: @@ -20,6 +22,7 @@ G2. :|: A2. :|: G2! :|: 10 +''sleep: 1s ''expect-contains: EXE (^left, {SELF}, P) A3. :|: @@ -28,4 +31,8 @@ G3. :|: A3. :|: G3! :|: 10 +''sleep: 1s ''expect-contains: EXE (^left, {SELF}, (*, P1, P2)) + +''sleep: 500ms +''terminate(if-no-user) diff --git a/src/tests/nal/test_simple_deduction.nal b/src/tests/nal/test_simple_deduction.nal index f1a101c..85c58f6 100644 --- a/src/tests/nal/test_simple_deduction.nal +++ b/src/tests/nal/test_simple_deduction.nal @@ -12,10 +12,17 @@ ' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` B>. -' ''await: IN B>. +' // ''await: IN B>. C>. -' ''await: IN C>. +' // ''await: IN C>. C>? 5 +' 使用睡眠延时,给足输出呈现时间 +''sleep: 1s + +' 检验输出 ''expect-contains: ANSWER C>. + +' 用户无法输入时退出(正常退出) +''terminate(if-no-user) \ No newline at end of file From efdd7dc3e2a10abcc0aede36de4cbcbe6f075a77 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:53:30 +0800 Subject: [PATCH 37/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E3=80=8C=E4=B8=A5=E6=A0=BC=E6=A8=A1=E5=BC=8F=E3=80=8D=EF=BC=9B?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E3=80=8CCIN=E8=BE=93=E5=85=A5=E6=97=A0?= =?UTF-8?q?=E6=95=88=E3=80=8D=E7=9A=84=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅可配置的「严格模式」,允许「测试失败⇒提前返回⇒上报异常」;🐞连带修复「命令行运行时输入NAVM指令遗漏换行符」的问题 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/launch_config.rs | 11 ++ src/bin/babelnar_cli/main.rs | 17 ++- src/bin/babelnar_cli/runtime_manage.rs | 121 ++++++++++++------ src/cin_implements/ona/dialect_ona.pest | 3 + src/runtimes/command_vm/runtime.rs | 3 +- src/test_tools/vm_interact.rs | 2 +- .../cli/config/test_prelude_operation.json | 7 +- .../config/test_prelude_simple_deduction.json | 7 +- src/tests/nal/test_operation.nal | 4 + 11 files changed, 124 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eea3c7..f3b8480 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.12.0" +version = "0.13.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index bc83c8b..83be4c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.12.0" +version = "0.13.0" edition = "2021" # Cargo文档参考: diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs index 6072b8d..1d69c61 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/launch_config.rs @@ -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` @@ -140,6 +148,8 @@ impl Default for LaunchConfig { input_mode: InputMode::default(), // 不自动重启 auto_restart: false, + // 不开启严格模式 + strict_mode: false, } } } @@ -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); } diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 0bcdc07..468aae1 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -48,8 +48,11 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> // 启动失败⇒打印错误信息,等待并退出 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); } }; @@ -57,9 +60,13 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> 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 } diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 71cabb4..6afcc37 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -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, @@ -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, @@ -161,7 +161,7 @@ where } } - /// 在运行时启动后,对其进行管理 + /// 【主函数】在运行时启动后,对其进行管理 /// * 🎯健壮性:更多「警告/重来」而非`panic` /// * 🎯用户友好:尽可能隐藏底层内容 /// * 如错误堆栈 @@ -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> { // 生成「读取输出」子线程 | 📌必须最先 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(..)) => (), } // 虚拟机被终止 & 无用户输入 ⇒ 程序退出 @@ -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<()>> { 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(()) } /// 生成「读取输出」子线程 @@ -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}"), @@ -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(()) } } diff --git a/src/cin_implements/ona/dialect_ona.pest b/src/cin_implements/ona/dialect_ona.pest index fe8cfda..d716c00 100644 --- a/src/cin_implements/ona/dialect_ona.pest +++ b/src/cin_implements/ona/dialect_ona.pest @@ -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:

(^left /2 {SELF})>. :|` compound = !{ compound_common | compound_binary diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index c4e6cb8..9dd54af 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -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 { diff --git a/src/test_tools/vm_interact.rs b/src/test_tools/vm_interact.rs index 7f6d45c..36e8b8f 100644 --- a/src/test_tools/vm_interact.rs +++ b/src/test_tools/vm_interact.rs @@ -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)?; } // 然后读取并匹配缓存 diff --git a/src/tests/cli/config/test_prelude_operation.json b/src/tests/cli/config/test_prelude_operation.json index dd295d2..e16bcbe 100644 --- a/src/tests/cli/config/test_prelude_operation.json +++ b/src/tests/cli/config/test_prelude_operation.json @@ -1,8 +1,9 @@ { "description": "用于测试「预加载NAL输入」,加载「操作测试」", - "autoRestart": true, - "userInput": false, "preludeNAL": { "file": "./src/tests/nal/test_operation.nal" - } + }, + "userInput": false, + "autoRestart": false, + "strictMode": true } \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_simple_deduction.json b/src/tests/cli/config/test_prelude_simple_deduction.json index d89e037..f7f6e6f 100644 --- a/src/tests/cli/config/test_prelude_simple_deduction.json +++ b/src/tests/cli/config/test_prelude_simple_deduction.json @@ -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 } \ No newline at end of file diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal index 03534c5..6f2cefa 100644 --- a/src/tests/nal/test_operation.nal +++ b/src/tests/nal/test_operation.nal @@ -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. :|: From 34f2a5e12d3687f02ee5632a1b48b7417664658d Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:39:25 +0800 Subject: [PATCH 38/59] =?UTF-8?q?refactor:=20:recycle:=20=E5=B0=86?= =?UTF-8?q?=E3=80=8C=E8=AF=BB=E5=8F=96=E8=A1=8C=E8=BF=AD=E4=BB=A3=E5=99=A8?= =?UTF-8?q?=E3=80=8D=E4=B8=8E=E3=80=8CNAVM=E8=BE=93=E5=87=BA=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E3=80=8D=E5=86=85=E7=BD=AE=E8=BF=9B=E3=80=8C=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A1=8C=E6=94=AF=E6=8C=81/=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E3=80=8D=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/runtime_manage.rs | 109 ++---------------------- src/cli_support/io/mod.rs | 7 ++ src/cli_support/io/navm_output_cache.rs | 94 ++++++++++++++++++++ src/cli_support/io/readline_iter.rs | 52 +++++++++++ 6 files changed, 160 insertions(+), 106 deletions(-) create mode 100644 src/cli_support/io/navm_output_cache.rs create mode 100644 src/cli_support/io/readline_iter.rs diff --git a/Cargo.lock b/Cargo.lock index f3b8480..dde25af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.13.0" +version = "0.13.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 83be4c7..4c35312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.13.0" +version = "0.13.1" edition = "2021" # Cargo文档参考: diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 6afcc37..dd376d6 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -3,125 +3,26 @@ use crate::{launch_by_config, InputMode, LaunchConfig, LaunchConfigPreludeNAL}; use anyhow::{anyhow, Result}; use babel_nar::{ - cli_support::error_handling_boost::error_anyhow, + cli_support::{ + error_handling_boost::error_anyhow, + io::{navm_output_cache::OutputCache, readline_iter::ReadlineIter}, + }, eprintln_cli, println_cli, test_tools::{nal_format::parse, put_nal, VmOutputCache}, }; use nar_dev_utils::{if_return, ResultBoost}; use navm::{ cmd::Cmd, - output::Output, vm::{VmRuntime, VmStatus}, }; use std::{ fmt::Debug, - io::Result as IoResult, ops::{ControlFlow, ControlFlow::Break, ControlFlow::Continue}, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, thread::{self, sleep, JoinHandle}, time::Duration, }; -/// 读取行迭代器 -/// * 🚩每迭代一次,请求用户输入一行 -/// * ✨自动清空缓冲区 -/// * ❌无法在【不复制字符串】的情况下实现「迭代出所输入内容」的功能 -/// * ❌【2024-04-02 03:49:56】无论如何都无法实现:迭代器物件中引入就必须碰生命周期 -/// * 🚩最终仍需复制字符串:调用处方便使用 -/// * ❓是否需要支持提示词 -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct ReadlineIter { - pub buffer: String, -} - -/// 实现迭代器 -impl Iterator for ReadlineIter { - type Item = IoResult; - - fn next(&mut self) -> Option { - // 清空缓冲区 - self.buffer.clear(); - // 读取一行 - // * 📝此处的`stdin`是懒加载的 - if let Err(e) = std::io::stdin().read_line(&mut self.buffer) { - return Some(Err(e)); - } - // 返回 - Some(IoResult::Ok(self.buffer.clone())) - } -} - -/// 线程间可变引用计数的别名 -type ArcMutex = Arc>; - -/// 输出缓存 -/// * 🎯统一「加入输出⇒打印输出」的逻辑 -/// * 🚩仅封装一个[`Vec`],而不对其附加任何[`Arc`]、[`Mutex`]的限定 -/// * ❌【2024-04-03 01:43:13】[`Arc`]必须留给[`RuntimeManager`]:需要对其中键的值进行引用 -#[derive(Debug)] -pub struct OutputCache { - /// 内部封装的输出数组 - /// * 🚩【2024-04-03 01:43:41】不附带任何包装类型,仅包装其自身 - inner: Vec, -} - -/// 功能实现 -impl OutputCache { - /// 构造函数 - pub fn new(inner: Vec) -> Self { - Self { inner } - } - - /// 默认[`Arc`]<[`Mutex`]> - pub fn default_arc_mutex() -> ArcMutex { - Arc::new(Mutex::new(Self::default())) - } - - /// 从[`Arc`]<[`Mutex`]>中解锁 - pub fn unlock_arc_mutex(arc_mutex: &mut ArcMutex) -> Result> { - arc_mutex.lock().transform_err(error_anyhow) - } -} - -/// 默认构造:空数组 -impl Default for OutputCache { - fn default() -> Self { - Self::new(vec![]) - } -} - -/// 实现「输出缓存」 -/// * 不再涉及任何[`Arc`]或[`Mutex`] -impl VmOutputCache for OutputCache { - /// 存入输出 - /// * 🎯统一的「打印输出」逻辑 - /// * 🚩【2024-04-03 01:07:55】不打算封装了 - fn put(&mut self, output: Output) -> Result<()> { - // 尝试打印输出 - println_cli!(&output); - - // 加入输出 - self.inner.push(output); - Ok(()) - } - - /// 遍历输出 - /// * 🚩不是返回迭代器,而是用闭包开始计算 - fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result> { - // 遍历 - for output in self.inner.iter() { - // 基于控制流的运行 - match f(output) { - ControlFlow::Break(value) => return Ok(Some(value)), - ControlFlow::Continue(()) => {} - } - } - - // 返回 - Ok(None) - } -} - /// 运行时管理器 /// * 🎯在一个数据结构中封装「虚拟机运行时」与「配置信息」 /// * 📌只负责**单个运行时**的运行管理 diff --git a/src/cli_support/io/mod.rs b/src/cli_support/io/mod.rs index 1eb7e90..7a8d0d7 100644 --- a/src/cli_support/io/mod.rs +++ b/src/cli_support/io/mod.rs @@ -2,7 +2,14 @@ //! * ✨终端美化相关 //! + util::mods! { // 输出打印 pub output_print; + + // 读取行迭代器 + pub readline_iter; + + // NAVM输出缓存 + pub navm_output_cache; } diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs new file mode 100644 index 0000000..fd47f36 --- /dev/null +++ b/src/cli_support/io/navm_output_cache.rs @@ -0,0 +1,94 @@ +//! NAVM输出缓存 +//! * 🎯一站式存储、展示与管理NAVM的输出 +//! * 🎯可被其它二进制库所复用 + +use crate::{cli_support::error_handling_boost::error_anyhow, test_tools::VmOutputCache}; +use anyhow::Result; +use nar_dev_utils::ResultBoost; +use navm::output::Output; +use std::{ + ops::ControlFlow, + sync::{Arc, Mutex, MutexGuard}, +}; + +use super::output_print::OutputType; + +/// 线程间可变引用计数的别名 +type ArcMutex = Arc>; + +/// 输出缓存 +/// * 🎯统一「加入输出⇒打印输出」的逻辑 +/// * 🚩仅封装一个[`Vec`],而不对其附加任何[`Arc`]、[`Mutex`]的限定 +/// * ❌【2024-04-03 01:43:13】[`Arc`]必须留给[`RuntimeManager`]:需要对其中键的值进行引用 +#[derive(Debug)] +pub struct OutputCache { + /// 内部封装的输出数组 + /// * 🚩【2024-04-03 01:43:41】不附带任何包装类型,仅包装其自身 + inner: Vec, +} + +/// 功能实现 +impl OutputCache { + /// 构造函数 + pub fn new(inner: Vec) -> Self { + Self { inner } + } + + /// 默认[`Arc`]<[`Mutex`]> + pub fn default_arc_mutex() -> ArcMutex { + Arc::new(Mutex::new(Self::default())) + } + + /// 从[`Arc`]<[`Mutex`]>中解锁 + pub fn unlock_arc_mutex(arc_mutex: &mut ArcMutex) -> Result> { + arc_mutex.lock().transform_err(error_anyhow) + } + + /// 静默存入输出 + /// * 🎯内部可用的「静默存入输出」逻辑 + /// * 🚩【2024-04-03 01:07:55】不打算封装了 + pub fn put_silent(&mut self, output: Output) -> Result<()> { + // 加入输出 + self.inner.push(output); + Ok(()) + } +} + +/// 默认构造:空数组 +impl Default for OutputCache { + fn default() -> Self { + Self::new(vec![]) + } +} + +/// 实现「输出缓存」 +/// * 🚩【2024-04-03 14:33:50】不再涉及任何[`Arc`]或[`Mutex`] +impl VmOutputCache for OutputCache { + /// 存入输出 + /// * 🎯统一的「打印输出」逻辑 + /// * 🚩【2024-04-03 01:07:55】不打算封装了 + fn put(&mut self, output: Output) -> Result<()> { + // 打印输出 + // * 🚩现在内置入「命令行支持」,不再能直接使用`println_cli` + OutputType::print_from_navm_output(&output); + + // 静默加入输出 + self.put_silent(output) + } + + /// 遍历输出 + /// * 🚩不是返回迭代器,而是用闭包开始计算 + fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result> { + // 遍历 + for output in self.inner.iter() { + // 基于控制流的运行 + match f(output) { + ControlFlow::Break(value) => return Ok(Some(value)), + ControlFlow::Continue(()) => {} + } + } + + // 返回 + Ok(None) + } +} diff --git a/src/cli_support/io/readline_iter.rs b/src/cli_support/io/readline_iter.rs new file mode 100644 index 0000000..16c54d9 --- /dev/null +++ b/src/cli_support/io/readline_iter.rs @@ -0,0 +1,52 @@ +//! 读取行迭代器 +//! * 🎯以迭代器的语法获取、处理用户输入 +//! * ❌【2024-04-03 14:28:02】放弃「泛型化改造」:[`Stdin`]能`read_line`,但却没实现[`std::io::BufRead`] + +use std::io::{stdin, Result as IoResult, Stdin}; + +/// 读取行迭代器 +/// * 🚩每迭代一次,请求用户输入一行 +/// * ✨自动清空缓冲区 +/// * ❌无法在【不复制字符串】的情况下实现「迭代出所输入内容」的功能 +/// * ❌【2024-04-02 03:49:56】无论如何都无法实现:迭代器物件中引入就必须碰生命周期 +/// * 🚩最终仍需复制字符串:调用处方便使用 +/// * ❓是否需要支持提示词 +#[derive(Debug)] +pub struct ReadlineIter { + /// 内置的「输入内容缓冲区」 + buffer: String, + /// 内置的「标准输入」 + stdin: Stdin, +} + +impl ReadlineIter { + pub fn new() -> Self { + Self { + buffer: String::new(), + stdin: stdin(), + } + } +} + +impl Default for ReadlineIter { + fn default() -> Self { + Self::new() + } +} + +/// 实现迭代器 +impl Iterator for ReadlineIter { + type Item = IoResult; + + fn next(&mut self) -> Option { + // 清空缓冲区 + self.buffer.clear(); + // 读取一行 + // * 📝`stdin()`是懒加载的,只会获取一次,随后返回的都是引用对象 + if let Err(e) = self.stdin.read_line(&mut self.buffer) { + return Some(Err(e)); + } + // 返回 + Some(IoResult::Ok(self.buffer.clone())) + } +} From 98b6283a7d0dcc9c49ff04a34f803fa9a460ee2e Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:22:36 +0800 Subject: [PATCH 39/59] =?UTF-8?q?docs:=20:memo:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自述文件的English版本(WIP);完善Cargo.toml的库信息 --- Cargo.toml | 10 ++++++++++ README.md | 47 ++++------------------------------------------ README.zh-cn.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 README.zh-cn.md diff --git a/Cargo.toml b/Cargo.toml index 4c35312..9d6c216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,16 @@ name = "babel_nar" version = "0.13.1" edition = "2021" +description = """ +Implementation and application supports of the NAVM model +""" + +readme = "README.md" +keywords = ["NARS", "NAVM"] + +license = "MIT OR Apache-2.0" +categories = [] # TODO +repository = "https://github.com/ARCJ137442/BabelNAR.rs" # Cargo文档参考: diff --git a/README.md b/README.md index e4348f9..e589b58 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,9 @@ # BabelNAR.rs -[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) - -该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 - -[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)对[CIN](#cin-computer-implement-of-nars)的**启动器**、**运行时**及应用程序实现 - -- 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) -- ✨为「非公理虚拟机模型」提供程序实现 -- ✨统一各[CIN](#cin-computer-implement-of-nars)的**输入输出**形式,聚合使用各大NARS实现 - -## 概念 - -### CIN (Computer Implement of NARS) - -- 「NARS计算机实现」之英文缩写 -- 指代所有**实现NARS**的计算机软件系统 - - 不要求完整实现NAL 1~9 +English | [简体中文](README.zh-cn.md) -### ***CommonNarsese*** - -🔗参考[**NAVM.jl**的对应部分](https://github.com/ARCJ137442/navm.jl?tab=readme-ov-file#commonnarsese) - -## 各CIN对接情况 - -🕒最后更新时间:【2024-03-26 01:43:28】 - -| CIN | 实现方法 | 进程安全 | 输入转译 | 输出转译 | -| :---------- | :---------: | :--: | :--: | :--: | -| OpenNARS | `java -jar` | ✅ | ✅ | 🚧 | -| ONA | 直接启动exe | ✅ | ✅ | 🚧 | -| PyNARS | `python -m` | ✅ | 🚧 | 🚧 | -| NARS-Python | 直接启动exe | ❓ | ✅ | ❌ | -| OpenJunars | `julia` | ✅ | ❌ | ❌ | - -注: - -- 🚧输入输出转译功能仍然在从[BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl)迁移 -- ❓NARS-Python的exe界面可能会在终止后延时关闭 -- ❌基于`julia`启动OpenJunars脚本`launch.jl`时,对「输出捕获」尚未有成功记录 -- ❌目前对NARS-Python的「输出捕获」尚未有成功记录 +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) -## 参考 +Implementation and application supports of the NAVM model -- [BabelNAR](https://github.com/ARCJ137442/BabelNAR.jl) -- [BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl) -- [NAVM.rs](https://github.com/ARCJ137442/NAVM.rs) +⚠️【2024-04-03 15:12:55】**This documentation is still in progress. For full and latest content, please refer to [the Simplified Chinese version](README.zh-cn.md).** diff --git a/README.zh-cn.md b/README.zh-cn.md new file mode 100644 index 0000000..bc2846f --- /dev/null +++ b/README.zh-cn.md @@ -0,0 +1,50 @@ +# BabelNAR.rs + +[English](README.md) | 简体中文 + +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) + +该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 + +[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)对[CIN](#cin-computer-implement-of-nars)的**启动器**、**运行时**及应用程序实现 + +- 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) +- ✨为「非公理虚拟机模型」提供程序实现 +- ✨统一各[CIN](#cin-computer-implement-of-nars)的**输入输出**形式,聚合使用各大NARS实现 + +## 概念 + +### CIN (Computer Implement of NARS) + +- 「NARS计算机实现」之英文缩写 +- 指代所有**实现NARS**的计算机软件系统 + - 不要求完整实现NAL 1~9 + +### ***CommonNarsese*** + +🔗参考[**NAVM.jl**的对应部分](https://github.com/ARCJ137442/navm.jl?tab=readme-ov-file#commonnarsese) + +## 各CIN对接情况 + +🕒最后更新时间:【2024-03-26 01:43:28】 + +| CIN | 实现方法 | 进程安全 | 输入转译 | 输出转译 | +| :---------- | :---------: | :--: | :--: | :--: | +| OpenNARS | `java -jar` | ✅ | ✅ | 🚧 | +| ONA | 直接启动exe | ✅ | ✅ | 🚧 | +| PyNARS | `python -m` | ✅ | 🚧 | 🚧 | +| NARS-Python | 直接启动exe | ❓ | ✅ | ❌ | +| OpenJunars | `julia` | ✅ | ❌ | ❌ | + +注: + +- 🚧输入输出转译功能仍然在从[BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl)迁移 +- ❓NARS-Python的exe界面可能会在终止后延时关闭 +- ❌基于`julia`启动OpenJunars脚本`launch.jl`时,对「输出捕获」尚未有成功记录 +- ❌目前对NARS-Python的「输出捕获」尚未有成功记录 + +## 参考 + +- [BabelNAR](https://github.com/ARCJ137442/BabelNAR.jl) +- [BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl) +- [NAVM.rs](https://github.com/ARCJ137442/NAVM.rs) From 90a469db4f4598f93c5f44384a8ead4ae84242d1 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 16:03:35 +0800 Subject: [PATCH 40/59] =?UTF-8?q?feat:=20:construction:=20=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E7=9A=84Websocket=E5=BA=93=E4=BB=A3=E7=A0=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/launch_config.rs | 2 + src/cli_support/io/mod.rs | 4 +- src/cli_support/io/websocket.rs | 192 ++++++++++++++++++++++++++ src/cli_support/mod.rs | 1 + 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/cli_support/io/websocket.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index b8f743d..929db87 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "rust-analyzer.cargo.features": "all", "editor.formatOnSave": true, "cSpell.words": [ + "Addrs", "confy", "cxin", "Errno", diff --git a/Cargo.lock b/Cargo.lock index dde25af..e076ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.13.1" +version = "0.14.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 9d6c216..5890cb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.13.1" +version = "0.14.0" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs index 1d69c61..c79befe 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/launch_config.rs @@ -281,6 +281,8 @@ impl LaunchConfig { /// * 🚩合并逻辑:`Some(..)` => `None` /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 /// * ✨对【内部含有可选键】的值,会**递归深入** + /// + /// TODO: ❓启动时与运行时需要分开:不推荐直接覆盖布尔值 pub fn merge_from(&mut self, other: &Self) { // 合并所有【不含可选键】的值 self.translators.coalesce_clone(&other.translators); diff --git a/src/cli_support/io/mod.rs b/src/cli_support/io/mod.rs index 7a8d0d7..f29c48f 100644 --- a/src/cli_support/io/mod.rs +++ b/src/cli_support/io/mod.rs @@ -2,7 +2,6 @@ //! * ✨终端美化相关 //! - util::mods! { // 输出打印 pub output_print; @@ -12,4 +11,7 @@ util::mods! { // NAVM输出缓存 pub navm_output_cache; + + // Websocket支持 + pub websocket; } diff --git a/src/cli_support/io/websocket.rs b/src/cli_support/io/websocket.rs new file mode 100644 index 0000000..3e67fea --- /dev/null +++ b/src/cli_support/io/websocket.rs @@ -0,0 +1,192 @@ +//! 基于[`ws`]为CLI提供Websocket IO支持 +//! * 📝【2024-04-03 16:01:37】可以启动IPv6服务端,但尚且没有测试方法 +//! * 📌语法:`[主机地址]:连接端口` +//! * 📄示例:`[::]:3012` +//! * 🔗参考: + +use anyhow::Result; +use std::{ + fmt, + net::ToSocketAddrs, + thread::{self, JoinHandle}, +}; +use ws::{Factory, Handler, Sender, WebSocket}; + +/// 生成一个Websocket监听线程 +/// * 🎯简单生成一个Websocket监听线程 +/// * ⚠️线程在生成后立即开始运行 +#[inline] +pub fn spawn_on(addr: A, message_listener: F) -> JoinHandle> +where + A: ToSocketAddrs + fmt::Debug + Send + Sync + 'static, + F: FnMut(Sender) -> H + Send + Sync + 'static, + H: Handler, +{ + spawn_server(addr, message_listener) +} + +/// 生成一个Websocket监听线程,使用特定的服务端 +/// * 🎯使用自定义的Websocket「工厂」[`Factory`]生成服务端与连接处理者(侦听器) +/// * 📄地址格式:`127.0.0.1:8080`、`localhost:8080` +/// * ⚠️线程在生成后立即开始运行 +/// * 📝与[`ws::listen`]不同的是:允许在[`factory`]处自定义各种连接、侦听逻辑 +/// * 🔗参考: +#[inline] +pub fn spawn_server(address: A, factory: F) -> JoinHandle> +where + A: ToSocketAddrs + fmt::Debug + Send + Sync + 'static, + F: Factory + Send + Sync + 'static, + H: Handler, +{ + thread::spawn(move || { + let server = WebSocket::new(factory)?; + server.listen(address)?; + Ok(()) + }) +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn main() { + let t = spawn_on("127.0.0.1:3012", |sender| { + println!("Websocket启动成功"); + move |msg| { + println!("Received: {}", msg); + sender.send(msg) + } + }); + t.join().expect("Websocket失败!").expect("Websocket出错!"); + } + + /// 📄简单的回传连接处理者 + /// * 🎯处理单个Websocket连接 + #[derive(Debug)] + struct EchoHandler { + sender: Sender, + } + + impl Handler for EchoHandler { + fn on_shutdown(&mut self) { + println!("Handler received WebSocket shutdown request."); + } + + fn on_open(&mut self, shake: ws::Handshake) -> ws::Result<()> { + if let Some(addr) = shake.remote_addr()? { + println!("Connection with {} now open", addr); + } + Ok(()) + } + + fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { + println!("Received message {:?}", msg); + self.sender.send(msg)?; + Ok(()) + } + + fn on_close(&mut self, code: ws::CloseCode, reason: &str) { + println!("Connection closing due to ({:?}) {}", code, reason); + } + + fn on_error(&mut self, err: ws::Error) { + // Ignore connection reset errors by default, but allow library clients to see them by + // overriding this method if they want + if let ws::ErrorKind::Io(ref err) = err.kind { + if let Some(104) = err.raw_os_error() { + return; + } + } + + eprintln!("{:?}", err); + } + + fn on_request(&mut self, req: &ws::Request) -> ws::Result { + println!("Handler received request:\n{}", req); + ws::Response::from_request(req) + } + + fn on_response(&mut self, res: &ws::Response) -> ws::Result<()> { + println!("Handler received response:\n{}", res); + Ok(()) + } + + fn on_timeout(&mut self, event: ws::util::Token) -> ws::Result<()> { + println!("Handler received timeout token: {:?}", event); + Ok(()) + } + + fn on_new_timeout(&mut self, _: ws::util::Token, _: ws::util::Timeout) -> ws::Result<()> { + // default implementation discards the timeout handle + Ok(()) + } + + fn on_frame(&mut self, frame: ws::Frame) -> ws::Result> { + println!("Handler received: {}", frame); + // default implementation doesn't allow for reserved bits to be set + if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() { + Err(ws::Error::new( + ws::ErrorKind::Protocol, + "Encountered frame with reserved bits set.", + )) + } else { + Ok(Some(frame)) + } + } + + fn on_send_frame(&mut self, frame: ws::Frame) -> ws::Result> { + println!("Handler will send: {}", frame); + // default implementation doesn't allow for reserved bits to be set + if frame.has_rsv1() || frame.has_rsv2() || frame.has_rsv3() { + Err(ws::Error::new( + ws::ErrorKind::Protocol, + "Encountered frame with reserved bits set.", + )) + } else { + Ok(Some(frame)) + } + } + } + + struct EchoFactory; + + impl Factory for EchoFactory { + type Handler = EchoHandler; + + fn connection_made(&mut self, sender: Sender) -> EchoHandler { + dbg!(EchoHandler { sender }) + } + + fn on_shutdown(&mut self) { + println!("Factory received WebSocket shutdown request."); + } + + fn client_connected(&mut self, ws: Sender) -> Self::Handler { + self.connection_made(ws) + } + + fn server_connected(&mut self, ws: Sender) -> Self::Handler { + self.connection_made(ws) + } + + fn connection_lost(&mut self, handler: Self::Handler) { + println!("Connection lost of {handler:?}"); + } + } + + #[test] + fn test_echo_localhost() -> Result<()> { + let ws = WebSocket::new(EchoFactory)?; + ws.listen("localhost:3012")?; + Ok(()) + } + + #[test] + fn test_echo_ipv6() -> Result<()> { + let ws = WebSocket::new(EchoFactory)?; + ws.listen("[::]:3012")?; + Ok(()) + } +} diff --git a/src/cli_support/mod.rs b/src/cli_support/mod.rs index 6db9768..026385e 100644 --- a/src/cli_support/mod.rs +++ b/src/cli_support/mod.rs @@ -5,6 +5,7 @@ util::mods! { // CIN搜索 pub cin_search; + // 输入输出 pub io; } From 2c3ea1ae767abebb4a35e5349dece71397bb8d39 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:36:54 +0800 Subject: [PATCH 41/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0Websocket=E6=9C=8D=E5=8A=A1=E6=90=AD=E5=BB=BA?= =?UTF-8?q?=EF=BC=88=E4=BB=85=E8=BE=93=E5=85=A5=EF=BC=8C=E5=9B=9E=E4=BC=A0?= =?UTF-8?q?=E5=B0=9A=E6=9C=AA=E5=81=9A=E5=A5=BD=EF=BC=89=EF=BC=9B=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dexe=E9=A2=9C=E8=89=B2=E6=98=BE=E7=A4=BAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/bin/babelnar_cli/config_launcher.rs | 21 ++- src/bin/babelnar_cli/launch_config.rs | 3 +- src/bin/babelnar_cli/main.rs | 31 +++- src/bin/babelnar_cli/runtime_manage.rs | 49 +++--- src/bin/babelnar_cli/websocket_server.rs | 208 +++++++++++++++++++++++ src/cli_support/io/navm_output_cache.rs | 12 +- src/cli_support/io/output_print.rs | 5 +- src/cli_support/io/readline_iter.rs | 15 +- src/cli_support/io/websocket.rs | 22 +++ src/tests/nal/test_operation.nal | 2 + 11 files changed, 333 insertions(+), 37 deletions(-) create mode 100644 src/bin/babelnar_cli/websocket_server.rs diff --git a/Cargo.toml b/Cargo.toml index 5890cb5..92d4916 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ optional = true [features] ## 默认启用的特性 ## -default = [] +default = [ "bundled" ] # * 默认启用所有(可选禁用) ## 大杂烩 ## bundled = [ "cin_implements", # 各大CIN的NAVM实现 diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 67ed294..88189af 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -6,7 +6,8 @@ use babel_nar::{ cin_implements::{ common::generate_command, cxin_js, nars_python, ona, openjunars, opennars, pynars, }, - cli_support::cin_search::name_match::name_match, + cli_support::{cin_search::name_match::name_match, io::readline_iter::ReadlineIter}, + eprintln_cli, runtimes::{ api::{InputTranslator, IoTranslators}, CommandVm, OutputTranslator, @@ -17,11 +18,27 @@ use navm::{ output::Output, vm::{VmLauncher, VmRuntime}, }; +use std::path::PathBuf; /// (若缺省)要求用户手动填充配置项 pub fn polyfill_config_from_user(config: &mut LaunchConfig) { if config.need_polyfill() { - // TODO: 在有缺省的情况下 手动要求用户输入填补缺省项 + // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件路径 + print!("> 请输入配置文件路径(如`C:/nars/BabelNAR.launch.json`): "); + for input in ReadlineIter::new("") { + if let Err(e) = input { + eprintln_cli!([Error] "输入无效:{e}"); + continue; + } + let path = PathBuf::from(input.unwrap()); + if !path.is_file() { + eprintln_cli!([Error] "文件「{path:?}」不存在"); + continue; + } + + // 新的input + print!("> 请输入配置文件地址(如`BabelNAR.launch.json`): "); + } } } diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/launch_config.rs index c79befe..7bc2ae7 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/launch_config.rs @@ -82,6 +82,7 @@ pub struct LaunchConfig { pub command: Option, /// Websocket参数(可选) + /// * 🚩【2024-04-03 18:21:00】目前对客户端输出JSON pub websocket: Option, /// 预置NAL(可选) @@ -281,7 +282,7 @@ impl LaunchConfig { /// * 🚩合并逻辑:`Some(..)` => `None` /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 /// * ✨对【内部含有可选键】的值,会**递归深入** - /// + /// /// TODO: ❓启动时与运行时需要分开:不推荐直接覆盖布尔值 pub fn merge_from(&mut self, other: &Self) { // 合并所有【不含可选键】的值 diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 468aae1..a48a0f1 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -8,7 +8,7 @@ //! ``` use anyhow::Result; -use babel_nar::println_cli; +use babel_nar::{eprintln_cli, println_cli}; use clap::Parser; use std::io::Result as IoResult; use std::thread::sleep; @@ -24,6 +24,8 @@ nar_dev_utils::mods! { use config_launcher; // 运行时交互、管理 use runtime_manage; + // Websocket服务端 + use websocket_server; } /// 主入口 @@ -36,6 +38,10 @@ pub fn main() -> Result<()> { /// * 🚩此处只应该有自[`env`]传入的参数 /// * 🚩【2024-04-01 14:25:38】暂时用不到「当前工作路径」 pub fn main_args(_cwd: IoResult, args: impl Iterator) -> Result<()> { + // (Windows下)启用终端颜色 + let _ = colored::control::set_virtual_terminal(true) + .inspect_err(|_| eprintln_cli!([Error] "无法启动终端彩色显示。。")); + // 解析命令行参数 let args = CliArgs::parse_from(args); // 读取配置 | with 默认配置文件 let mut config = load_config(&args, DEFAULT_CONFIG_PATH); @@ -127,6 +133,25 @@ mod tests { pub fn test_ona_prelude_op() -> Result<()> { main_ona_prelude("./src/tests/cli/config/test_prelude_operation.json") } + /// 测试入口/ONA/交互shell + /// * 🎯正常BabelNAR CLI shell启动 + /// * 🎯正常用户命令行交互体验 + /// * ⚠️使用与项目无关的路径,以定位启动CIN + #[test] + pub fn main_ona_websocket() -> Result<()> { + // 以默认参数启动 + main_args( + env::current_dir(), + [ + "test.exe", + "-d", + "-c", + "./src/tests/cli/config/test_ona.json", + "-c", + "./src/tests/cli/config/websocket.json", + ] + .into_iter() + .map(str::to_string), + ) + } } - -// mod test_ws; diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index dd376d6..c0aa592 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -1,11 +1,15 @@ //! 启动后运行时的(交互与)管理 +use super::websocket_server::*; use crate::{launch_by_config, InputMode, LaunchConfig, LaunchConfigPreludeNAL}; use anyhow::{anyhow, Result}; use babel_nar::{ cli_support::{ error_handling_boost::error_anyhow, - io::{navm_output_cache::OutputCache, readline_iter::ReadlineIter}, + io::{ + navm_output_cache::{ArcMutex, OutputCache}, + readline_iter::ReadlineIter, + }, }, eprintln_cli, println_cli, test_tools::{nal_format::parse, put_nal, VmOutputCache}, @@ -36,17 +40,17 @@ where /// 内部封装的虚拟机运行时 /// * 🏗️后续可能会支持「同时运行多个虚拟机」 /// * 🚩多线程共享:输入/输出 - runtime: Arc>, + pub(crate) runtime: ArcMutex, /// 内部封装的「命令行参数」 /// * 🎯用于从命令行中加载配置 /// * 🚩只读 - config: Arc, + pub(crate) config: Arc, /// 内部缓存的「NAVM输出」 /// * 🎯用于NAL测试 /// * 🚩多线程共享 - output_cache: Arc>, + pub(crate) output_cache: ArcMutex, } impl RuntimeManager @@ -54,6 +58,7 @@ where R: VmRuntime + Send + Sync + 'static, { /// 构造函数 + /// * 🎯由此接管虚拟机实例、配置的所有权 pub fn new(runtime: R, config: LaunchConfig) -> Self { Self { runtime: Arc::new(Mutex::new(runtime)), @@ -97,8 +102,8 @@ where } } - // 生成「Websocket服务」子线程 - let thread_ws = self.spawn_ws_server()?; + // 生成「Websocket服务」子线程(若有连接) + let thread_ws = self.try_spawn_ws_server()?; // 生成「用户输入」子线程 let mut thread_input = None; @@ -111,7 +116,9 @@ where // 等待子线程结束,并抛出其抛出的错误 // ! 🚩【2024-04-02 15:09:32】错误处理交给外界 thread_read.join().transform_err(error_anyhow)??; - thread_ws.join().transform_err(error_anyhow)??; + if let Some(thread_ws) = thread_ws { + thread_ws.join().transform_err(error_anyhow)?? + } if let Some(thread_input) = thread_input { thread_input.join().transform_err(error_anyhow)??; } @@ -221,21 +228,15 @@ where } /// 生成「Websocket服务」子线程 - pub fn spawn_ws_server(&mut self) -> Result>> { - // 准备引用 - let runtime_arc = self.runtime.clone(); - - // 启动线程 - let thread = thread::spawn(move || { - loop { - // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 - let mut runtime = runtime_arc.lock().transform_err(error_anyhow)?; - // TODO: Websocket服务端逻辑 - } - }); + pub fn try_spawn_ws_server(&mut self) -> Result>>> { + // 若有⇒启动 + if let Some(config) = &self.config.websocket { + let thread = spawn_ws_server(self, &config.host, config.port); + return Ok(Some(thread)); + } - // 返回启动的线程 - Ok(thread) + // 完成,即便没有启动 + Ok(None) } /// 生成「用户输入」子线程 @@ -252,7 +253,7 @@ where // ! 📝不能在此中出现裸露的`MutexGuard`对象:其并非线程安全 // * ✅可使用`&(mut) *`重引用语法,从`MutexGuard`转换为线程安全的引用 // * ✅对`Arc`使用`&*`同理:可以解包成引用,以便后续统一传递值的引用 - for io_result in ReadlineIter::default() { + for io_result in ReadlineIter::new("BabelNAR> ") { // 从迭代器中读取一行 let line = io_result?; @@ -318,7 +319,7 @@ where .inspect_err(|e| eprintln_cli!([Error] "NAVM指令执行错误:{e}")) } - /// 像NAVM实例输入NAL(输入) + /// 向NAVM实例输入NAL(输入) /// * 🎯预置、用户输入、Websocket输入 /// * 🎯严格模式 /// * 📌要么是「有失败 + 非严格模式 ⇒ 仅报告错误」 @@ -362,7 +363,7 @@ where /// 重启虚拟机 /// * 🚩消耗原先的虚拟机管理者,返回一个新的管理者 /// * 🚩【2024-04-02 20:25:21】目前对「终止先前虚拟机」持放松态度 -/// * 📝从`Arc>`中拿取值的所有权:[`Arc::try_unwrap`] + [`Mutex::into_inner`] +/// * 📝从`ArcMutex>`中拿取值的所有权:[`Arc::try_unwrap`] + [`Mutex::into_inner] /// * 🔗参考: pub fn restart_manager( manager: RuntimeManager, diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs new file mode 100644 index 0000000..0d2ebac --- /dev/null +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -0,0 +1,208 @@ +//! BabelNAR CLI的Websocket交互逻辑 +//! * 🎯为BabelNAR CLI实现Websocket IO +//! * 🎯实现专有的Websocket服务端逻辑 + +use crate::{LaunchConfig, RuntimeManager}; +use anyhow::Result; +use babel_nar::{ + cli_support::io::{ + navm_output_cache::{ArcMutex, OutputCache}, + websocket::{spawn_server, to_address}, + }, + eprintln_cli, println_cli, +}; +use navm::vm::VmRuntime; +use std::{sync::Arc, thread::JoinHandle}; +use ws::{Factory, Handler, Sender}; + +/// 工具宏:尝试执行,如果失败则上抛错误 +/// * 🎯在「无法使用[`anyhow::Result`]上抛错误」的情况下适用 +macro_rules! try_or_return_err { + ($value:expr; $e_id:ident => $($error_msg:tt)*) => { + match $value { + Ok(value) => value, + Err($e_id) => { + // 生成并输出错误信息 + let error_msg = format!($($error_msg)*); + println_cli!([Error] "{error_msg}"); + // 转换错误 | 使用Websocket的「内部错误」以指示是CLI的错误 + let error = ws::Error::new(ws::ErrorKind::Internal, error_msg); + return Err(error); + } + } + }; +} + +/// 入口代码 +/// * 🎯生成一个Websocket服务端线程 +/// * 🚩不管参数`config`中的地址:可能没有 +pub fn spawn_ws_server( + manager: &RuntimeManager, + host: &str, + port: u16, +) -> JoinHandle> +where + R: VmRuntime + Send + Sync, +{ + // 合并地址 + let address = to_address(host, port); + + // 获取服务端「处理者工厂」 + // * 🚩拷贝[`Arc`] + let factory = WSServer { + runtime: manager.runtime.clone(), + output_cache: manager.output_cache.clone(), + config: manager.config.clone(), + }; + + // 根据专有服务端逻辑,生成子线程并返回 + let server = spawn_server(address.clone(), factory); + println_cli!([Info] "Websocket服务器已在 {:?} 启动", address); + server +} + +/// 一个Websocket连接 +/// * 🎯处理单个Websocket连接 +#[derive(Debug)] +pub struct Connection +where + R: VmRuntime + Send + Sync, +{ + /// 所涉及的运行时 + pub(crate) runtime: ArcMutex, + + /// 所涉及的运行时配置 + pub(crate) config: Arc, + + /// 所涉及的运行时 + pub(crate) output_cache: ArcMutex, + + /// 连接(服务端这方的)发送者 + pub(crate) sender: Sender, +} + +impl Handler for Connection +where + R: VmRuntime + Send + Sync + 'static, +{ + fn on_shutdown(&mut self) { + println_cli!([Info] "Websocket连接已关停") + } + + fn on_open(&mut self, shake: ws::Handshake) -> ws::Result<()> { + if let Some(addr) = shake.remote_addr()? { + println_cli!([Info] "Websocket连接已打开:{addr}") + } + Ok(()) + } + + fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { + println_cli!([Debug] "Websocket收到消息:{msg}"); + // 获取所需的参数信息 | 在此时独占锁 + let runtime = &mut *try_or_return_err!(self.runtime.lock(); poison => "在Websocket连接中获取运行时失败:{poison}"); + let config = &self.config; + let output_cache = &mut *try_or_return_err!(self.output_cache.lock(); err => "在Websocket连接中获取输出缓存失败:{err}"); + + // 输入信息,并监控缓存的新输出 + let old_len_cache = output_cache.borrow_inner().len(); + try_or_return_err!(RuntimeManager::input_line_to_vm( + runtime, + &msg.to_string(), + config, + output_cache + ); err => "在Websocket连接中输入「{msg}」时发生错误:{err}"); + // TODO: 此处是异步的,所以输出放在了别的地方 + let new_len_cache = dbg!(output_cache.borrow_inner()).len(); + + // 若有输出,则获取并回传JSON信息 + if new_len_cache > old_len_cache { + let mut output; + let mut json_text; + // 逐个获取 + for i in (old_len_cache - 1)..new_len_cache { + output = &output_cache.borrow_inner()[i]; + json_text = output.to_json_string(); + // 回传,若出错仅输出错误 + if let Err(e) = self.sender.send(json_text.clone()) { + eprintln_cli!([Error] "尝试回传消息「{json_text}」时发生错误:{e}"); + } + } + } + + Ok(()) + } + + fn on_close(&mut self, code: ws::CloseCode, reason: &str) { + println_cli!([Info] "Websocket连接关闭(退出码:{code:?};原因:「{reason}」)") + } + + fn on_error(&mut self, err: ws::Error) { + // Ignore connection reset errors by default, but allow library clients to see them by + // overriding this method if they want + if let ws::ErrorKind::Io(ref err) = err.kind { + if let Some(104) = err.raw_os_error() { + return; + } + } + + println_cli!([Error] "连接发生错误:{err:?}"); + } + + fn on_timeout(&mut self, event: ws::util::Token) -> ws::Result<()> { + println_cli!([Warn] "连接超时:{:?}", event); + Ok(()) + } + + fn on_new_timeout(&mut self, _: ws::util::Token, _: ws::util::Timeout) -> ws::Result<()> { + // default implementation discards the timeout handle + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct WSServer +where + R: VmRuntime, +{ + /// 所涉及的虚拟机运行时 + pub(crate) runtime: ArcMutex, + + /// 所涉及的虚拟机配置 + pub(crate) config: Arc, + + /// 所涉及的输出缓存 + pub(crate) output_cache: ArcMutex, +} + +impl Factory for WSServer +where + R: VmRuntime + Send + Sync + 'static, +{ + type Handler = Connection; + + fn connection_made(&mut self, sender: Sender) -> Connection { + println_cli!([Info] "Websocket连接已建立"); + Connection { + sender, + runtime: self.runtime.clone(), + config: self.config.clone(), + output_cache: self.output_cache.clone(), + } + } + + fn on_shutdown(&mut self) { + println_cli!([Info] "Websocket服务器已关停") + } + + fn client_connected(&mut self, ws: Sender) -> Self::Handler { + self.connection_made(ws) + } + + fn server_connected(&mut self, ws: Sender) -> Self::Handler { + self.connection_made(ws) + } + + fn connection_lost(&mut self, handler: Self::Handler) { + eprintln_cli!([Error] "与id为{}", handler.sender.connection_id()); + } +} diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs index fd47f36..d8971cf 100644 --- a/src/cli_support/io/navm_output_cache.rs +++ b/src/cli_support/io/navm_output_cache.rs @@ -14,7 +14,7 @@ use std::{ use super::output_print::OutputType; /// 线程间可变引用计数的别名 -type ArcMutex = Arc>; +pub type ArcMutex = Arc>; /// 输出缓存 /// * 🎯统一「加入输出⇒打印输出」的逻辑 @@ -34,6 +34,16 @@ impl OutputCache { Self { inner } } + /// 不可变借用内部 + pub fn borrow_inner(&self) -> &Vec { + &self.inner + } + + /// 可变借用内部 + pub fn borrow_inner_mut(&mut self) -> &mut Vec { + &mut self.inner + } + /// 默认[`Arc`]<[`Mutex`]> pub fn default_arc_mutex() -> ArcMutex { Arc::new(Mutex::new(Self::default())) diff --git a/src/cli_support/io/output_print.rs b/src/cli_support/io/output_print.rs index f3d9384..46f7798 100644 --- a/src/cli_support/io/output_print.rs +++ b/src/cli_support/io/output_print.rs @@ -99,12 +99,13 @@ impl OutputType<'_> { // CLI独有 "DEBUG" => message.bright_blue(), "WARN" => message.bright_yellow(), - "LOG" => message.white(), + "LOG" => message.bright_black(), // NAVM输出 "IN" | "OUT" => message.bright_white(), "EXE" => message.bright_cyan().reversed(), "ANSWER" | "ACHIEVED" => message.bright_green().reversed(), - "INFO" | "COMMENT" => message.white(), + "INFO" => message.cyan(), + "COMMENT" => message.white(), "ERROR" => message.red(), "TERMINATED" => message.bright_white().reversed().blink(), // ↓OpenNARS附加 diff --git a/src/cli_support/io/readline_iter.rs b/src/cli_support/io/readline_iter.rs index 16c54d9..bfae0d0 100644 --- a/src/cli_support/io/readline_iter.rs +++ b/src/cli_support/io/readline_iter.rs @@ -2,7 +2,8 @@ //! * 🎯以迭代器的语法获取、处理用户输入 //! * ❌【2024-04-03 14:28:02】放弃「泛型化改造」:[`Stdin`]能`read_line`,但却没实现[`std::io::BufRead`] -use std::io::{stdin, Result as IoResult, Stdin}; +use crate::cli_support::io::output_print::OutputType; +use std::io::{stdin, stdout, Result as IoResult, Stdin, Write}; /// 读取行迭代器 /// * 🚩每迭代一次,请求用户输入一行 @@ -17,20 +18,23 @@ pub struct ReadlineIter { buffer: String, /// 内置的「标准输入」 stdin: Stdin, + /// 输入提示词 + prompt: String, } impl ReadlineIter { - pub fn new() -> Self { + pub fn new(prompt: impl Into) -> Self { Self { buffer: String::new(), stdin: stdin(), + prompt: prompt.into(), } } } impl Default for ReadlineIter { fn default() -> Self { - Self::new() + Self::new("") } } @@ -41,6 +45,11 @@ impl Iterator for ReadlineIter { fn next(&mut self) -> Option { // 清空缓冲区 self.buffer.clear(); + // 打印提示词 + print!("{}", self.prompt); + if let Err(e) = stdout().flush() { + OutputType::Warn.print_line(&format!("无法冲洗输出: {e}")); + } // 读取一行 // * 📝`stdin()`是懒加载的,只会获取一次,随后返回的都是引用对象 if let Err(e) = self.stdin.read_line(&mut self.buffer) { diff --git a/src/cli_support/io/websocket.rs b/src/cli_support/io/websocket.rs index 3e67fea..7d57e9d 100644 --- a/src/cli_support/io/websocket.rs +++ b/src/cli_support/io/websocket.rs @@ -1,4 +1,6 @@ //! 基于[`ws`]为CLI提供Websocket IO支持 +//! * ✨简单的「地址生成」「服务端启动」等逻辑 +//! * ⚠️不涉及具体业务代码 //! * 📝【2024-04-03 16:01:37】可以启动IPv6服务端,但尚且没有测试方法 //! * 📌语法:`[主机地址]:连接端口` //! * 📄示例:`[::]:3012` @@ -12,6 +14,26 @@ use std::{ }; use ws::{Factory, Handler, Sender, WebSocket}; +/// 从「主机地址」与「连接端口」格式化到「完整地址」 +/// * ✨兼容IPv4 和 IPv6 +/// * 📌端口号为十六位无符号整数(0~65535,含两端) +pub fn to_address(host: &str, port: u16) -> String { + match is_ipv6_host(host) { + // IPv6 + true => format!("[{host}]:{port}"), + // IPv4 + false => format!("{host}:{port}"), + } +} + +/// 判断**主机地址**是否为IPv6地址 +/// * 🚩【2024-04-03 17:20:59】目前判断标准:地址中是否包含冒号 +/// * 📄`::1` +/// * 📄`fe80::abcd:fade:dad1` +pub fn is_ipv6_host(host: &str) -> bool { + host.contains(':') +} + /// 生成一个Websocket监听线程 /// * 🎯简单生成一个Websocket监听线程 /// * ⚠️线程在生成后立即开始运行 diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal index 6f2cefa..2dab3f7 100644 --- a/src/tests/nal/test_operation.nal +++ b/src/tests/nal/test_operation.nal @@ -11,6 +11,8 @@ ' * 📄【2024-04-03 11:51:12】目前输出过多会造成CLI轻微卡顿 '/VOL 75 +''sleep: 0.5s + A. :|: <(*, {SELF}) --> ^left>. :|: G. :|: From 78749b847d4793a460799665b50a4df577936a6b Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:04:19 +0800 Subject: [PATCH 42/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90Websocket=E7=9A=84=E3=80=8C=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E3=80=8D=E5=8A=9F=E8=83=BD=EF=BC=88=E4=B8=8D?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/babelnar_cli/config_launcher.rs | 7 +- src/bin/babelnar_cli/websocket_server.rs | 76 +++++++++++---------- src/cli_support/io/navm_output_cache.rs | 23 +++++-- src/tests/cli/config/matriangle_server.json | 2 +- 4 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 88189af..576c406 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -24,8 +24,8 @@ use std::path::PathBuf; pub fn polyfill_config_from_user(config: &mut LaunchConfig) { if config.need_polyfill() { // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件路径 - print!("> 请输入配置文件路径(如`C:/nars/BabelNAR.launch.json`): "); - for input in ReadlineIter::new("") { + for input in ReadlineIter::new("请输入配置文件地址(如`BabelNAR.launch.json`): ") + { if let Err(e) = input { eprintln_cli!([Error] "输入无效:{e}"); continue; @@ -35,9 +35,6 @@ pub fn polyfill_config_from_user(config: &mut LaunchConfig) { eprintln_cli!([Error] "文件「{path:?}」不存在"); continue; } - - // 新的input - print!("> 请输入配置文件地址(如`BabelNAR.launch.json`): "); } } } diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs index 0d2ebac..f0be734 100644 --- a/src/bin/babelnar_cli/websocket_server.rs +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -76,9 +76,11 @@ where /// 所涉及的运行时 pub(crate) output_cache: ArcMutex, - - /// 连接(服务端这方的)发送者 - pub(crate) sender: Sender, + // /// 连接(服务端这方的)发送者 + // /// * 🚩【2024-04-03 19:44:58】现在不再需要 + // pub(crate) sender: Sender, + /// 连接id + pub(crate) id: u32, } impl Handler for Connection @@ -104,31 +106,27 @@ where let output_cache = &mut *try_or_return_err!(self.output_cache.lock(); err => "在Websocket连接中获取输出缓存失败:{err}"); // 输入信息,并监控缓存的新输出 - let old_len_cache = output_cache.borrow_inner().len(); - try_or_return_err!(RuntimeManager::input_line_to_vm( - runtime, - &msg.to_string(), - config, - output_cache - ); err => "在Websocket连接中输入「{msg}」时发生错误:{err}"); - // TODO: 此处是异步的,所以输出放在了别的地方 - let new_len_cache = dbg!(output_cache.borrow_inner()).len(); - - // 若有输出,则获取并回传JSON信息 - if new_len_cache > old_len_cache { - let mut output; - let mut json_text; - // 逐个获取 - for i in (old_len_cache - 1)..new_len_cache { - output = &output_cache.borrow_inner()[i]; - json_text = output.to_json_string(); - // 回传,若出错仅输出错误 - if let Err(e) = self.sender.send(json_text.clone()) { - eprintln_cli!([Error] "尝试回传消息「{json_text}」时发生错误:{e}"); - } - } + if let Err(err) = + RuntimeManager::input_line_to_vm(runtime, &msg.to_string(), config, output_cache) + { + eprintln_cli!([Error] "在Websocket连接中输入「{msg}」时发生错误:{err}") } + // ! 🚩此处无法回传输出:输出捕捉在缓存中处理的地方 + // if new_len_cache > old_len_cache { + // let mut output; + // let mut json_text; + // // 逐个获取 + // for i in (old_len_cache - 1)..new_len_cache { + // output = &output_cache.borrow_inner()[i]; + // json_text = output.to_json_string(); + // // 回传,若出错仅输出错误 + // if let Err(e) = self.sender.send(json_text.clone()) { + // eprintln_cli!([Error] "尝试回传消息「{json_text}」时发生错误:{e}"); + // } + // } + // } + Ok(()) } @@ -182,11 +180,25 @@ where fn connection_made(&mut self, sender: Sender) -> Connection { println_cli!([Info] "Websocket连接已建立"); + let id = sender.connection_id(); + // 尝试添加「发送者」 + match self.output_cache.lock() { + Ok(mut output_cache) => { + let output_cache = &mut *output_cache; + // 添加「发送者」 + output_cache.websocket_senders.push(sender); + } + Err(err) => { + // 输出错误 + println_cli!([Error] "Websocket输出侦听器添加失败:{err}"); + } + } + // 返回连接 Connection { - sender, runtime: self.runtime.clone(), config: self.config.clone(), output_cache: self.output_cache.clone(), + id, } } @@ -194,15 +206,7 @@ where println_cli!([Info] "Websocket服务器已关停") } - fn client_connected(&mut self, ws: Sender) -> Self::Handler { - self.connection_made(ws) - } - - fn server_connected(&mut self, ws: Sender) -> Self::Handler { - self.connection_made(ws) - } - fn connection_lost(&mut self, handler: Self::Handler) { - eprintln_cli!([Error] "与id为{}", handler.sender.connection_id()); + eprintln_cli!([Error] "与id为 {} 的客户端断开连接!", handler.id); } } diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs index d8971cf..9bdbb5c 100644 --- a/src/cli_support/io/navm_output_cache.rs +++ b/src/cli_support/io/navm_output_cache.rs @@ -2,6 +2,7 @@ //! * 🎯一站式存储、展示与管理NAVM的输出 //! * 🎯可被其它二进制库所复用 +use super::output_print::OutputType; use crate::{cli_support::error_handling_boost::error_anyhow, test_tools::VmOutputCache}; use anyhow::Result; use nar_dev_utils::ResultBoost; @@ -11,8 +12,6 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; -use super::output_print::OutputType; - /// 线程间可变引用计数的别名 pub type ArcMutex = Arc>; @@ -24,14 +23,22 @@ pub type ArcMutex = Arc>; pub struct OutputCache { /// 内部封装的输出数组 /// * 🚩【2024-04-03 01:43:41】不附带任何包装类型,仅包装其自身 - inner: Vec, + pub(crate) inner: Vec, + + /// 内部封装的「发送者」列表 + /// * 🎯Websocket输出回传(JSON) + /// TODO: 🏗️后续优化 + pub websocket_senders: Vec, } /// 功能实现 impl OutputCache { /// 构造函数 pub fn new(inner: Vec) -> Self { - Self { inner } + Self { + inner, + websocket_senders: Vec::new(), + } } /// 不可变借用内部 @@ -82,6 +89,14 @@ impl VmOutputCache for OutputCache { // * 🚩现在内置入「命令行支持」,不再能直接使用`println_cli` OutputType::print_from_navm_output(&output); + // 回传JSON + // TODO: 待优化 + for sender in &self.websocket_senders { + if let Err(e) = sender.send(output.to_json_string()) { + OutputType::Error.print_line(&format!("Websocket💬回传失败:{e}")); + } + } + // 静默加入输出 self.put_silent(output) } diff --git a/src/tests/cli/config/matriangle_server.json b/src/tests/cli/config/matriangle_server.json index 716035f..5921362 100644 --- a/src/tests/cli/config/matriangle_server.json +++ b/src/tests/cli/config/matriangle_server.json @@ -4,5 +4,5 @@ "host": "localhost", "port": 8765 }, - "input_mode": "cmd" + "inputMode": "cmd" } \ No newline at end of file From c4778aec5407b4bebb556777d55dbb7506cad842 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 4 Apr 2024 00:41:22 +0800 Subject: [PATCH 43/59] =?UTF-8?q?fix:=20:bug:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=AF=BB=E5=8F=96=E7=9A=84?= =?UTF-8?q?bug=EF=BC=9A=E8=A1=A5=E5=AE=8C=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8D=A2=E8=A1=8C=E7=AC=A6=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/babelnar_cli/config_launcher.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 576c406..538285b 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -1,7 +1,7 @@ //! 用于从「启动参数」启动NAVM运行时 -use crate::{LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators}; -use anyhow::{anyhow, Ok, Result}; +use crate::{read_config_extern, LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators}; +use anyhow::{anyhow, Result}; use babel_nar::{ cin_implements::{ common::generate_command, cxin_js, nars_python, ona, openjunars, opennars, pynars, @@ -24,17 +24,30 @@ use std::path::PathBuf; pub fn polyfill_config_from_user(config: &mut LaunchConfig) { if config.need_polyfill() { // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件路径 - for input in ReadlineIter::new("请输入配置文件地址(如`BabelNAR.launch.json`): ") + for line in ReadlineIter::new("请输入配置文件地址(如`BabelNAR.launch.json`): ") { - if let Err(e) = input { + // 检验输入 + if let Err(e) = line { eprintln_cli!([Error] "输入无效:{e}"); continue; } - let path = PathBuf::from(input.unwrap()); + // 检验路径 + let path = PathBuf::from(line.unwrap().trim()); if !path.is_file() { eprintln_cli!([Error] "文件「{path:?}」不存在"); continue; } + // 读取配置文件 + let content = match read_config_extern(&path) { + Ok(config) => config, + Err(e) => { + eprintln_cli!([Error] "配置文件「{path:?}」读取失败:{e}"); + continue; + } + }; + // 读取成功⇒覆盖,返回 + *config = content; + break; } } } From 7989a8802b6fd98758c2c67d9aca766bc1165284 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 4 Apr 2024 03:00:40 +0800 Subject: [PATCH 44/59] =?UTF-8?q?refactor:=20:recycle:=20=E3=80=8C?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E8=A1=8C=E8=BF=90=E8=A1=8C=E6=97=B6=E3=80=8D?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E9=80=BB=E8=BE=91=E4=BF=AE=E6=94=B9=EF=BC=9A?= =?UTF-8?q?=E6=94=BE=E7=BD=AE=E8=BD=AC=E8=AF=91=E5=99=A8=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=80=9F=E8=B5=B0=E6=89=80=E6=9C=89=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现在学习标准库Command的做法,践行「能不拿所有权就不拿所有权」的API设计原则 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/config_launcher.rs | 14 ++++--- src/cin_implements/cxin_js/launcher.rs | 2 +- src/cin_implements/nars_python/launcher.rs | 15 +++++--- src/cin_implements/ona/launcher.rs | 2 +- src/cin_implements/openjunars/launcher.rs | 15 +++++--- src/cin_implements/opennars/launcher.rs | 15 +++++--- src/cin_implements/pynars/launcher.rs | 15 +++++--- src/runtimes/command_vm/api/translators.rs | 35 +++++++++++++++-- src/runtimes/command_vm/launcher.rs | 15 +++++--- src/runtimes/command_vm/runtime.rs | 44 ++++++++++++++-------- 12 files changed, 117 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e076ac7..42f41d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.14.0" +version = "0.14.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 92d4916..f34d077 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.14.0" +version = "0.14.1" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 538285b..eb53dd4 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -24,7 +24,7 @@ use std::path::PathBuf; pub fn polyfill_config_from_user(config: &mut LaunchConfig) { if config.need_polyfill() { // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件路径 - for line in ReadlineIter::new("请输入配置文件地址(如`BabelNAR.launch.json`): ") + for line in ReadlineIter::new("请输入配置文件路径(如`BabelNAR.launch.json`): ") { // 检验输入 if let Err(e) = line { @@ -62,7 +62,7 @@ pub fn launch_by_config(config: LaunchConfig) -> Result { // 配置虚拟机 if let Some(translators) = config.translators { // 因为配置函数的设计,此处要暂时借用所有权 - vm = config_launcher_translators(vm, &translators)?; + config_launcher_translators(&mut vm, &translators)?; } // 启动虚拟机 @@ -89,11 +89,15 @@ pub fn load_command_vm(config: LaunchConfigCommand) -> Result { /// * ⚠️可能会有「转译器没找到/转译器加载失败」等 /// * 📌【2024-04-02 01:49:46】此处需要暂时借用所有权 pub fn config_launcher_translators( - vm: CommandVm, + vm: &mut CommandVm, config: &LaunchConfigTranslators, -) -> Result { +) -> Result<()> { + // 获取转译器 let translators = get_translator_by_name(config)?; - Ok(vm.translators(translators)) + // 设置转译器 + vm.translators(translators); + // 返回成功 + Ok(()) } /// 从「转译器名」检索「输入输出转译器」 diff --git a/src/cin_implements/cxin_js/launcher.rs b/src/cin_implements/cxin_js/launcher.rs index d28286d..2957f49 100644 --- a/src/cin_implements/cxin_js/launcher.rs +++ b/src/cin_implements/cxin_js/launcher.rs @@ -46,7 +46,7 @@ impl VmLauncher for CXinJS { self.command_generator // 构造指令 | 预置的指令参数 => .generate_command() - // * 🚩固定的「输入输出转换器」 + // * 🚩固定的「输入输出转译器」 => generate_command_vm(_, (input_translate, output_translate)) // 🔥启动 => .launch() diff --git a/src/cin_implements/nars_python/launcher.rs b/src/cin_implements/nars_python/launcher.rs index e85a175..a0d9370 100644 --- a/src/cin_implements/nars_python/launcher.rs +++ b/src/cin_implements/nars_python/launcher.rs @@ -6,6 +6,7 @@ use super::{input_translate, output_translate}; use crate::runtimes::{CommandVm, CommandVmRuntime}; use anyhow::Result; +use nar_dev_utils::manipulate; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -37,12 +38,14 @@ impl NARSPython { impl VmLauncher for NARSPython { fn launch(self) -> Result { // 构造指令,并启动虚拟机 - CommandVm::new(self.exe_path) - // * 🚩固定的「输入输出转换器」 - .input_translator(input_translate) - .output_translator(output_translate) - // 🔥启动 - .launch() + manipulate!( + CommandVm::new(self.exe_path) + // * 🚩固定的「输入输出转译器」 + => .input_translator(input_translate) + => .output_translator(output_translate) + ) + // 🔥启动 + .launch() } } diff --git a/src/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs index 6bb399c..dd6d87d 100644 --- a/src/cin_implements/ona/launcher.rs +++ b/src/cin_implements/ona/launcher.rs @@ -57,7 +57,7 @@ impl VmLauncher for ONA { self.exe_path // 构造指令 | 预置的指令参数 => generate_command(_, None::, COMMAND_ARGS_ONA.into_iter().by_ref()) - // * 🚩固定的「输入输出转换器」 + // * 🚩固定的「输入输出转译器」 => generate_command_vm(_, (input_translate, output_translate)) // 🔥启动 => .launch() diff --git a/src/cin_implements/openjunars/launcher.rs b/src/cin_implements/openjunars/launcher.rs index 81fb6cb..3123b4d 100644 --- a/src/cin_implements/openjunars/launcher.rs +++ b/src/cin_implements/openjunars/launcher.rs @@ -10,6 +10,7 @@ use crate::{ runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use anyhow::Result; +use nar_dev_utils::manipulate; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -42,12 +43,14 @@ impl VmLauncher for OpenJunars { let command = self.command_generator.generate_command(); // 构造并启动虚拟机 - CommandVm::from(command) - // * 🚩固定的「输入输出转换器」 - .input_translator(input_translate) - .output_translator(output_translate) - // 🔥启动 - .launch() + manipulate!( + CommandVm::from(command) + // * 🚩固定的「输入输出转译器」 + => .input_translator(input_translate) + => .output_translator(output_translate) + ) + // 🔥启动 + .launch() } } diff --git a/src/cin_implements/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs index ad3a265..181af44 100644 --- a/src/cin_implements/opennars/launcher.rs +++ b/src/cin_implements/opennars/launcher.rs @@ -10,6 +10,7 @@ use crate::{ runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use anyhow::Result; +use nar_dev_utils::manipulate; use navm::{ cmd::Cmd, vm::{VmLauncher, VmRuntime}, @@ -49,12 +50,14 @@ impl VmLauncher for OpenNARS { let command_java = self.command_generator.generate_command(); // 构造并启动虚拟机 - let mut vm = CommandVm::from(command_java) - // * 🚩固定的「输入输出转换器」 - .input_translator(input_translate) - .output_translator(output_translate) - // 🔥启动 - .launch()?; + let mut vm = manipulate!( + CommandVm::from(command_java) + // * 🚩固定的「输入输出转译器」 + => .input_translator(input_translate) + => .output_translator(output_translate) + ) + // 🔥启动 + .launch()?; // 设置初始音量 if let Some(volume) = self.initial_volume { diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs index d153764..ed04dfc 100644 --- a/src/cin_implements/pynars/launcher.rs +++ b/src/cin_implements/pynars/launcher.rs @@ -11,6 +11,7 @@ use crate::{ runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, }; use anyhow::Result; +use nar_dev_utils::manipulate; use navm::vm::VmLauncher; use std::path::PathBuf; @@ -41,12 +42,14 @@ impl VmLauncher for PyNARS { let command = self.command_generator.generate_command(); // 构造并启动虚拟机 - CommandVm::from(command) - // * 🚩固定的「输入输出转换器」 - .input_translator(input_translate) - .output_translator(output_translate) - // 🔥启动 - .launch() + manipulate!( + CommandVm::from(command) + // * 🚩固定的「输入输出转译器」 + => .input_translator(input_translate) + => .output_translator(output_translate) + ) + // 🔥启动 + .launch() } } diff --git a/src/runtimes/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs index 8e67139..24d9d02 100644 --- a/src/runtimes/command_vm/api/translators.rs +++ b/src/runtimes/command_vm/api/translators.rs @@ -3,7 +3,7 @@ //! * ✨特制结构 //! * ✨特有错误类型 -use anyhow::Result; +use anyhow::{Ok, Result}; use navm::{cmd::Cmd, output::Output}; use std::error::Error; use thiserror::Error; @@ -22,7 +22,35 @@ pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; /// * 只有转译功能,没有其它涉及外部的操作(纯函数) pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; -/// IO转换器配置 +/// 默认输入转译器 +/// * 🎯给「输入输出转译器」提供「默认选项」 +/// * 🚩按照NAVM指令原样输入:调用[`Cmd::to_string`]原样转换成字符串 +pub fn default_input_translate(cmd: Cmd) -> Result { + Ok(cmd.to_string()) +} + +/// 默认输出转译器 +/// * 🎯给「输入输出转译器」提供「默认选项」 +/// * 🚩不含任何实质转译逻辑,原样保留在「其它」输出中 +pub fn default_output_translate(content: String) -> Result { + Ok(Output::OTHER { content }) +} + +/// 获取「默认输入转译器」 +/// * 🎯统一提供默认值 +/// * 🚩使用函数指针,以优化先前「创建闭包」产生的性能开销 +pub fn default_input_translator() -> Box { + Box::new(default_input_translate) +} + +/// 获取「默认输出转译器」 +/// * 🎯统一提供默认值 +/// * 🚩使用函数指针,以优化先前「创建闭包」产生的性能开销 +pub fn default_output_translator() -> Box { + Box::new(default_output_translate) +} + +/// IO转译器配置 /// * 🎯封装并简化其它地方的`translator: impl Fn(...) -> ... + ...`逻辑 /// * 📝【2024-03-27 10:38:41】无论何时都不推荐直接用`impl Fn`作为字段类型 /// * ⚠️直接使用会意味着「需要编译前确定类型」 @@ -84,9 +112,8 @@ where /// 错误类型 mod translate_error { - use anyhow::anyhow; - use super::*; + use anyhow::anyhow; /// 统一封装「转译错误」 /// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 diff --git a/src/runtimes/command_vm/launcher.rs b/src/runtimes/command_vm/launcher.rs index 9e74969..83cbd91 100644 --- a/src/runtimes/command_vm/launcher.rs +++ b/src/runtimes/command_vm/launcher.rs @@ -32,27 +32,30 @@ impl CommandVm { /// 配置/输入转译器 /// * 💭何时Rust能给特征起别名。。 + /// * 🚩【2024-04-04 02:06:57】不再需要借走所有权 + /// * ✅链式操作现在可以使用[`util::manipulate`]简化 pub fn input_translator( - mut self, + &mut self, translator: impl Fn(Cmd) -> Result + Send + Sync + 'static, - ) -> Self { + ) { self.input_translator = Some(Box::new(translator)); - self } /// 配置/输出转译器 + /// * 🚩【2024-04-04 02:06:57】不再需要借走所有权 + /// * ✅链式操作现在可以使用[`util::manipulate`]简化 pub fn output_translator( - mut self, + &mut self, translator: impl Fn(String) -> Result + Send + Sync + 'static, - ) -> Self { + ) { self.output_translator = Some(Box::new(translator)); - self } /// 配置/输入输出转译器组 pub fn translators(mut self, translators: impl Into) -> Self { // 一次实现俩 let translators = translators.into(); + // 直接赋值 self.input_translator = Some(translators.input_translator); self.output_translator = Some(translators.output_translator); self diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 9dd54af..125033d 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -6,7 +6,10 @@ //! 2. 子进程 >>> 进程输出 >>> NAVM输出[`Output`] //! * 🚩实现方式:两处转译器 -use super::{CommandVm, InputTranslator, OutputTranslator}; +use super::{ + default_input_translator, default_output_translator, CommandVm, InputTranslator, + OutputTranslator, +}; use crate::process_io::IoProcessManager; use anyhow::{anyhow, Result}; use nar_dev_utils::if_return; @@ -102,13 +105,15 @@ impl VmLauncher for CommandVm { // 输入转译器 input_translator: self .input_translator - // 默认值:直接调用Cmd的`to_string`方法 | 使用NAVM Cmd语法 - .unwrap_or(Box::new(|cmd| Ok(cmd.to_string()))), + // 解包or使用默认值 + // * 🚩【2024-04-04 02:02:53】似乎不应有如此默认行为:后续若配置载入失败,将难以识别问题 + .unwrap_or(default_input_translator()), // 输出转译器 output_translator: self .output_translator - // 默认值:直接归入「其它」输出 | 约等于不分类 - .unwrap_or(Box::new(|content| Ok(Output::OTHER { content }))), + // 解包or使用默认值 + // * 🚩【2024-04-04 02:02:53】似乎不应有如此默认行为:后续若配置载入失败,将难以识别问题 + .unwrap_or(default_output_translator()), // * 🚩【2024-03-24 02:06:59】目前到此为止:只需处理「转译」问题 }) } @@ -121,6 +126,7 @@ impl VmLauncher for CommandVm { pub mod tests { use super::*; use crate::runtimes::TranslateError; + use nar_dev_utils::manipulate; use narsese::{ api::{GetBudget, GetPunctuation, GetStamp, GetTerm, GetTruth}, conversion::{ @@ -403,14 +409,16 @@ pub mod tests { } // 构造并启动虚拟机 - let vm = CommandVm::from(command_java) + let vm = manipulate!( + CommandVm::from(command_java) // 输入转译器 - .input_translator(input_translate) + => .input_translator(input_translate) // 输出转译器 - .output_translator(output_translate) - // 🔥启动 - .launch() - .expect("无法启动虚拟机"); + => .output_translator(output_translate) + ) + // 🔥启动 + .launch() + .expect("无法启动虚拟机"); _test_opennars(vm); } @@ -439,12 +447,16 @@ pub mod tests { /// * 🚩通过预置的批处理文件启动 #[test] fn test_pynars() { - let vm = CommandVm::new(EXE_PATH_PYNARS) + let vm = manipulate!( + CommandVm::new(EXE_PATH_PYNARS) // 输入转译器:直接取其尾部 - .input_translator(|cmd| Ok(cmd.tail())) - // 🔥启动 - .launch() - .expect("无法启动虚拟机"); + => .input_translator(|cmd| Ok(cmd.tail())) + // 暂无输出转译器 + // => .output_translator(output_translate) + ) + // 🔥启动 + .launch() + .expect("无法启动虚拟机"); // 可复用的测试逻辑 _test_pynars(vm); } From d097c2ac304388f67f8dcbe4a059d383c24548a7 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 4 Apr 2024 05:44:38 +0800 Subject: [PATCH 45/59] =?UTF-8?q?feat:=20:sparkles:=20=20=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E7=9A=84=E8=BF=90=E8=A1=8C=E6=97=B6=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=9Bhjson=EF=BC=9B=E6=89=A9=E5=B1=95=E5=90=8D=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 📌区分「启动配置」与「运行时配置」,对基础类型实现「完全可自由合并」 2. 🆕引入并兼容hjson格式 3. ✨新的「扩展名自动补全」功能,可对无扩展名路径自动补全为hjson或json --- .vscode/extensions.json | 5 +- .vscode/settings.json | 2 + BabelNAR.launch.json => BabelNAR-launch.json | 0 Cargo.lock | 12 +- Cargo.toml | 8 +- src/bin/babelnar_cli/arg_parse.rs | 236 ++++++++------- src/bin/babelnar_cli/config_launcher.rs | 65 ++-- src/bin/babelnar_cli/main.rs | 31 +- src/bin/babelnar_cli/runtime_manage.rs | 17 +- .../{launch_config.rs => vm_config.rs} | 286 ++++++++++++++---- src/bin/babelnar_cli/websocket_server.rs | 6 +- src/cin_implements/common/exe.rs | 6 +- src/runtimes/command_vm/api/translators.rs | 2 +- src/runtimes/command_vm/launcher.rs | 3 +- src/tests/cli/config/matriangle_server.hjson | 12 + src/tests/cli/config/matriangle_server.json | 8 - src/tests/cli/config/opennars.hjson | 21 ++ src/tests/cli/config/opennars.json | 12 - src/tests/cli/config/pynars.hjson | 11 + src/tests/cli/config/test_ona.hjson | 18 ++ src/tests/cli/config/test_ona.json | 11 - .../cli/config/test_prelude_operation.hjson | 16 + .../cli/config/test_prelude_operation.json | 9 - .../test_prelude_simple_deduction.hjson | 16 + .../config/test_prelude_simple_deduction.json | 9 - src/tests/cli/config/websocket.hjson | 8 + src/tests/cli/config/websocket.json | 7 - 27 files changed, 564 insertions(+), 273 deletions(-) rename BabelNAR.launch.json => BabelNAR-launch.json (100%) rename src/bin/babelnar_cli/{launch_config.rs => vm_config.rs} (57%) create mode 100644 src/tests/cli/config/matriangle_server.hjson delete mode 100644 src/tests/cli/config/matriangle_server.json create mode 100644 src/tests/cli/config/opennars.hjson delete mode 100644 src/tests/cli/config/opennars.json create mode 100644 src/tests/cli/config/pynars.hjson create mode 100644 src/tests/cli/config/test_ona.hjson delete mode 100644 src/tests/cli/config/test_ona.json create mode 100644 src/tests/cli/config/test_prelude_operation.hjson delete mode 100644 src/tests/cli/config/test_prelude_operation.json create mode 100644 src/tests/cli/config/test_prelude_simple_deduction.hjson delete mode 100644 src/tests/cli/config/test_prelude_simple_deduction.json create mode 100644 src/tests/cli/config/websocket.hjson delete mode 100644 src/tests/cli/config/websocket.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1cec836..50d6283 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,9 @@ "nyxiative.rust-and-friends", "itsyaasir.rust-feature-toggler", "rust-lang.rust-analyzer", - "aaron-bond.better-comments" + "aaron-bond.better-comments", + "laktak.hjson", + "tanh.hjson-formatter", + "pkief.material-icon-theme" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 929db87..0f55514 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,10 @@ "Addrs", "confy", "cxin", + "deser", "Errno", "hasher", + "hjson", "Nalifier", "nanos", "openjunars", diff --git a/BabelNAR.launch.json b/BabelNAR-launch.json similarity index 100% rename from BabelNAR.launch.json rename to BabelNAR-launch.json diff --git a/Cargo.lock b/Cargo.lock index 42f41d1..8e2bae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,11 +73,12 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.14.1" +version = "0.15.0" dependencies = [ "anyhow", "clap", "colored", + "deser-hjson", "lazy_static", "nar_dev_utils", "narsese", @@ -236,6 +237,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deser-hjson" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94aac4095c08ded7e4b9ba7fc2b2929f11b94bb96897ca188b0f64e01688e1" +dependencies = [ + "serde", +] + [[package]] name = "digest" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index f34d077..39902e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.14.1" +version = "0.15.0" edition = "2021" description = """ Implementation and application supports of the NAVM model @@ -85,7 +85,7 @@ optional = true version = "2.1.0" optional = true -# 命令行支持/JSON配置解析 +# 命令行支持/(H)JSON配置解析 [dependencies.serde] version = "1.0.197" optional = true @@ -95,6 +95,10 @@ features = ["derive"] version = "1.0.115" optional = true +[dependencies.deser-hjson] +version = "2.2.4" +optional = true + # 命令行支持/Websocket服务 [dependencies.ws] version = "0.9.2" diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index 841847d..42cca30 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -1,15 +1,14 @@ //! BabelNAR CLI的命令行(参数 & 配置)解析支持 //! * ⚠️【2024-04-01 14:31:09】特定于二进制crate,目前不要并入[`babel_nar`] +//! * 🚩【2024-04-04 03:03:58】现在移出所有与「启动配置」相关的逻辑到[`super::vm_config`] -use crate::launch_config::LaunchConfig; -use anyhow::Result; +use crate::{load_config_extern, read_config_extern, LaunchConfig}; use babel_nar::println_cli; use clap::Parser; -use nar_dev_utils::{pipe, ResultBoost}; -use std::{fs::read_to_string, path::PathBuf}; - -/// 默认的「外部JSON」路径 -pub const DEFAULT_CONFIG_PATH: &str = "BabelNAR.launch.json"; +use std::{ + env::{current_dir, current_exe}, + path::PathBuf, +}; /// 基于[`clap`]的命令行参数数据 // 配置命令行解析器 @@ -43,25 +42,59 @@ pub struct CliArgs { // ! 🚩【2024-04-02 11:36:18】目前除了「配置加载」外,莫将任何「NAVM实现特定,可以内置到『虚拟机配置』的字段放这儿」 } +/// 默认的「启动配置」关键词 +/// * 🎯在「自动追加扩展名」的机制下,可以进行自动补全 +/// * 🚩【2024-04-04 05:28:45】目前仍然难以直接在[`PathBuf`]中直接追加字符串 +/// * 多词如`BabelNAR-launch`需要使用`-`而非`.`:后者会被识别为「`.launch`扩展名」,导致无法进行「自动补全」 +pub const DEFAULT_CONFIG_KEYWORD: &str = "BabelNAR-launch"; + +/// 获取「默认启动配置」文件 +/// * 🎯更灵活地寻找可用的配置文件 +/// * exe当前目录下 | 工作目录下 +/// * `BabelNAR.launch.(h)json` +pub fn try_load_default_config() -> Option { + // 检查一个目录 + #[inline(always)] + fn in_one_root(root: PathBuf) -> Option { + // 计算路径:同目录下 + let path = match root.is_dir() { + true => root.join(DEFAULT_CONFIG_KEYWORD), + false => root.with_file_name(DEFAULT_CONFIG_KEYWORD), + }; + // 尝试读取,静默失败 + read_config_extern(&path).ok() + } + // 寻找第一个可用的配置文件 + [current_dir(), current_exe()] + // 转换为迭代器 + .into_iter() + // 筛去转换失败的 + .flatten() + // 尝试获取其中的一个有效配置,然后(惰性)返回「有效配置」 + .filter_map(in_one_root) + // 只取第一个(最先遍历的根路径优先) + .next() +} + /// 加载配置 /// * 🚩按照一定优先级顺序进行覆盖(从先到后) /// * 命令行参数中指定的配置文件 -/// * 默认的JSON文件 | 可以在`disable_default = true`的情况下传入任意字串作占位符 -pub fn load_config(args: &CliArgs, default_config_path: impl Into) -> LaunchConfig { +/// * 默认配置文件路径 | 可以在`disable_default = true`的情况下传入任意字串作占位符 +pub fn load_config(args: &CliArgs) -> LaunchConfig { // 构建返回值 | 全`None` let mut result = LaunchConfig::new(); // 尝试从命令行参数中读取再合并配置 | 仅提取出其中`Some`的项 args.config // 尝试加载配置文件,对错误采取「警告并抛掉」的策略 .iter() + .map(PathBuf::as_ref) .filter_map(load_config_extern) // 逐个从「命令行参数指定的配置文件」中合并 .for_each(|config| result.merge_from(&config)); // 若未禁用,尝试读取再合并默认启动配置 if !args.disable_default { // * 🚩读取失败⇒警告&无动作 | 避免多次空合并 - load_config_extern(&default_config_path.into()) - .inspect(|config_extern| result.merge_from(config_extern)); + try_load_default_config().inspect(|config_extern| result.merge_from(config_extern)); } // 展示加载的配置 | 以便调试(以防其它地方意外插入别的配置) match serde_json::to_string(&result) { @@ -72,55 +105,6 @@ pub fn load_config(args: &CliArgs, default_config_path: impl Into) -> L result } -/// 从外部JSON文件中加载启动配置 -/// * 🎯错误处理 & 错误⇒空置 -/// * 🚩在遇到错误时会发出警告 -pub fn load_config_extern(path: &PathBuf) -> Option { - // Ok⇒Some,Err⇒警告+None - read_config_extern(path).ok_or_run(|e| { - // 根据错误类型进行分派 - if let Some(e) = e.downcast_ref::() { - match e.kind() { - std::io::ErrorKind::NotFound => { - println_cli!([Warn] "未找到外部配置,使用空配置……"); - } - _ => println_cli!([Warn] "读取外部配置时出现预期之外的错误: {}", e), - } - } else if let Some(e) = e.downcast_ref::() { - match e.classify() { - serde_json::error::Category::Syntax => { - println_cli!([Warn] "外部配置文件格式错误,使用空配置……"); - } - _ => println_cli!([Warn] "解析外部配置时出现预期之外的错误: {}", e), - } - } else { - println_cli!([Warn] "加载外部配置时出现预期之外的错误: {}", e) - } - // 空置 - }) -} - -/// 从外部JSON文件中读取启动配置 -/// * 🎯仅涉及具体读取逻辑,不涉及错误处理 -pub fn read_config_extern(path: &PathBuf) -> Result { - // 尝试读取外部启动配置,并尝试解析 - pipe! { - path - // 尝试读取文件内容 - => read_to_string - => {?}# - // 尝试解析JSON配置 - => #{&} - => LaunchConfig::from_json_str - => {?}# - // 返回Ok(转换为`anyhow::Result`) - => Ok - } - // ! 若需使用`confy`,必须封装 - // * 🚩目前无需使用`confy`:可以自动创建配置文件,但个人希望其路径与exe同目录 - // Ok(confy::load_path(path)?) // ! 必须封装 -} - /// 单元测试 #[cfg(test)] mod tests { @@ -170,15 +154,15 @@ mod tests { #[test] fn test_arg_parse() { test_arg_parse! { - ["-c", "./src/tests/cli/config/opennars.json"] + ["-c", "./src/tests/cli/config/opennars"] => CliArgs { - config: vec!["./src/tests/cli/config/opennars.json".into()], + config: vec!["./src/tests/cli/config/opennars".into()], ..Default::default() }; // 多个配置:重复使用`-c`/`--config`,按使用顺序填充 - ["-c", "1.json", "--config", "2.json"] + ["-c", "1", "--config", "2"] => CliArgs { - config: vec!["1.json".into(), "2.json".into()], + config: vec!["1".into(), "2".into()], ..Default::default() }; // 禁用默认配置:使用`-d`/`--disable-default` @@ -195,20 +179,21 @@ mod tests { fail_缺少参数 test_arg_parse!(["-c"]); fail_参数名不对 test_arg_parse!(["--c"]); fail_缺少参数2 test_arg_parse!(["--config"]); - 多个参数没各自前缀 test_arg_parse!(["-c", "1.json", "2.json"]); + 多个参数没各自前缀 test_arg_parse!(["-c", "1", "2"]); } } /// 测试/加载配置 mod read_config { use super::*; + use crate::vm_config::*; use crate::LaunchConfigWebsocket; /// 测试/加载配置 fn load(args: &[&str]) -> LaunchConfig { // 读取配置 | 自动填充第一个命令行参数作为「当前程序路径」 let args = CliArgs::parse_from([&["test.exe"], args].concat()); - let config = load_config(&args, DEFAULT_CONFIG_PATH); + let config = load_config(&args); dbg!(config) } @@ -229,47 +214,88 @@ mod tests { fn test() { // 成功测试 test! { - // 单个配置文件 - ["-c" "src/tests/cli/config/opennars.json" "-d"] => LaunchConfig { - translators: Some( - crate::LaunchConfigTranslators::Same( - "opennars".into(), + // 单个配置文件 + ["-c" "src/tests/cli/config/opennars" "-d"] => LaunchConfig { + translators: Some( + LaunchConfigTranslators::Same( + "opennars".into(), + ), ), - ), - command: None, - websocket: None, - prelude_nal: None, - ..Default::default() - }; - ["-c" "src/tests/cli/config/websocket.json" "-d"] => LaunchConfig { - translators: None, - command: None, - websocket: Some(LaunchConfigWebsocket { - host: "localhost".into(), - port: 8080, - }), - prelude_nal: None, - ..Default::default() - }; - // 两个配置文件合并 - [ - "-d" - "-c" "src/tests/cli/config/opennars.json" - "-c" "src/tests/cli/config/websocket.json" - ] => LaunchConfig { - translators: Some( - crate::LaunchConfigTranslators::Same( - "opennars".into(), + command: Some(LaunchConfigCommand { + cmd: "java".into(), + cmd_args: Some(vec![ + "-Xmx1024m".into(), + "-jar".into(), + "nars.jar".into() + ]), + current_dir: Some("root/nars/test".into()), + }), + ..Default::default() + }; + ["-c" "src/tests/cli/config/websocket" "-d"] => LaunchConfig { + websocket: Some(LaunchConfigWebsocket { + host: "localhost".into(), + port: 8080, + }), + ..Default::default() + }; + // 两个配置文件合并 + [ + "-d" + "-c" "src/tests/cli/config/opennars" + "-c" "src/tests/cli/config/websocket" + ] => LaunchConfig { + translators: Some( + LaunchConfigTranslators::Same( + "opennars".into(), + ), ), - ), - command: None, - websocket: Some(LaunchConfigWebsocket { - host: "localhost".into(), - port: 8080, - }), - prelude_nal: None, - ..Default::default() - } + command: Some(LaunchConfigCommand { + cmd: "java".into(), + cmd_args: Some(vec![ + "-Xmx1024m".into(), + "-jar".into(), + "nars.jar".into() + ]), + current_dir: Some("root/nars/test".into()), + }), + websocket: Some(LaunchConfigWebsocket { + host: "localhost".into(), + port: 8080, + }), + ..Default::default() + }; + // 三个配置文件合并 + [ + "-d" + "-c" "src/tests/cli/config/opennars" + "-c" "src/tests/cli/config/websocket" + "-c" "src/tests/cli/config/test_prelude_simple_deduction" + ] => LaunchConfig { + translators: Some( + LaunchConfigTranslators::Same( + "opennars".into(), + ), + ), + command: Some(LaunchConfigCommand { + cmd: "java".into(), + cmd_args: Some(vec![ + "-Xmx1024m".into(), + "-jar".into(), + "nars.jar".into() + ]), + current_dir: Some("root/nars/test".into()), + }), + websocket: Some(LaunchConfigWebsocket { + host: "localhost".into(), + port: 8080, + }), + prelude_nal: Some(LaunchConfigPreludeNAL::File("./src/tests/nal/test_simple_deduction.nal".into())), + user_input: Some(false), + auto_restart: Some(false), + strict_mode: Some(true), + ..Default::default() + } } } } diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index eb53dd4..9d2f65a 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -1,6 +1,8 @@ //! 用于从「启动参数」启动NAVM运行时 -use crate::{read_config_extern, LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators}; +use crate::{ + read_config_extern, LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators, RuntimeConfig, +}; use anyhow::{anyhow, Result}; use babel_nar::{ cin_implements::{ @@ -13,6 +15,7 @@ use babel_nar::{ CommandVm, OutputTranslator, }, }; +use nar_dev_utils::pipe; use navm::{ cmd::Cmd, output::Output, @@ -53,17 +56,33 @@ pub fn polyfill_config_from_user(config: &mut LaunchConfig) { } /// 从「启动参数」中启动 -/// * 🚩默认所有参数都经过确认 -pub fn launch_by_config(config: LaunchConfig) -> Result { +/// * 🚩在转换中确认参数 +/// * ⚙️返回(启动后的运行时, 转换后的『运行时配置』) +/// * ❌无法使用`impl TryInto`统一「启动参数」与「运行参数」 +/// * 📌即便:对于「运行时参数」,[`TryInto::try_into`]始终返回自身 +/// * 📝然而:对自身的[`TryInto`]错误类型总是[`std::convert::Infallible`] +/// * ❗错误类型不一致,无法统一返回 +pub fn launch_by_config( + config: impl TryInto, +) -> Result<(impl VmRuntime, RuntimeConfig)> { + // 转换启动配置 + let config: RuntimeConfig = config.try_into()?; + + // 生成虚拟机 + let runtime = launch_by_runtime_config(&config)?; + + // 返回 + Ok((runtime, config)) +} + +pub fn launch_by_runtime_config(config: &RuntimeConfig) -> Result { // 生成虚拟机 - let config_command = config.command.ok_or_else(|| anyhow!("缺少启动命令"))?; + let config_command = &config.command; let mut vm = load_command_vm(config_command)?; // 配置虚拟机 - if let Some(translators) = config.translators { - // 因为配置函数的设计,此处要暂时借用所有权 - config_launcher_translators(&mut vm, &translators)?; - } + // * 🚩【2024-04-04 03:17:43】现在「转译器」成了必选项,所以必定会有配置 + config_launcher_translators(&mut vm, &config.translators)?; // 启动虚拟机 let runtime = vm.launch()?; @@ -72,14 +91,21 @@ pub fn launch_by_config(config: LaunchConfig) -> Result { /// 从「启动参数/启动命令」启动「命令行虚拟机」 /// * ❓需要用到「具体启动器实现」吗 -pub fn load_command_vm(config: LaunchConfigCommand) -> Result { +pub fn load_command_vm(config: &LaunchConfigCommand) -> Result { + // 构造指令 let command = generate_command( - config.cmd, - config.current_dir, - // ↓此处`unwrap_or_default`默认使用一个空数组作为迭代器 - config.cmd_args.unwrap_or_default().into_iter().by_ref(), + &config.cmd, + config.current_dir.as_ref(), + // 🚩获取其内部数组的引用,或使用一个空数组作迭代器(无法简化成[`unwrap_or`]) + match &config.cmd_args { + Some(v) => v.iter(), + // ↓此处`unwrap_or_default`默认使用一个空数组作为迭代器 + None => [].iter(), + }, ); + // 构造虚拟机 let vm = command.into(); + // 返回 Ok(vm) } @@ -92,12 +118,13 @@ pub fn config_launcher_translators( vm: &mut CommandVm, config: &LaunchConfigTranslators, ) -> Result<()> { - // 获取转译器 - let translators = get_translator_by_name(config)?; - // 设置转译器 - vm.translators(translators); - // 返回成功 - Ok(()) + Ok(pipe! { + // 获取转译器 + get_translator_by_name(config) => {?}# + // 设置转译器 + => [vm.translators](_) + // 返回成功 + }) } /// 从「转译器名」检索「输入输出转译器」 diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index a48a0f1..7b3b6a2 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -17,7 +17,7 @@ use std::{env, path::PathBuf}; nar_dev_utils::mods! { // 启动参数 - use launch_config; + use vm_config; // 命令行解析 use arg_parse; // 从参数启动 @@ -44,18 +44,18 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> // 解析命令行参数 let args = CliArgs::parse_from(args); // 读取配置 | with 默认配置文件 - let mut config = load_config(&args, DEFAULT_CONFIG_PATH); + let mut config = load_config(&args); // 用户填充配置项 polyfill_config_from_user(&mut config); // 从配置项启动 | 复制一个新配置,不会附带任何非基础类型开销 - let runtime = match launch_by_config(config.clone()) { + let (runtime, config) = match launch_by_config(config.clone()) { // 启动成功⇒返回 - Ok(runtime) => runtime, + Ok((r, c)) => (r, c), // 启动失败⇒打印错误信息,等待并退出 Err(e) => { println_cli!([Error] "NARS运行时启动错误:{e}"); // 启用用户输入时延时提示 - if config.user_input { + if let Some(true) = config.user_input { println_cli!([Info] "程序将在 3 秒后自动退出。。。"); sleep(Duration::from_secs(3)); } @@ -90,14 +90,9 @@ mod tests { // 以默认参数启动 main_args( env::current_dir(), - [ - "test.exe", - "-d", - "-c", - "./src/tests/cli/config/test_ona.json", - ] - .into_iter() - .map(str::to_string), + ["test.exe", "-d", "-c", "./src/tests/cli/config/test_ona"] + .into_iter() + .map(str::to_string), ) } @@ -114,7 +109,7 @@ mod tests { "-d", // 第一个文件,指示ONA "-c", - "./src/tests/cli/config/test_ona.json", + "./src/tests/cli/config/test_ona", // 第二个文件,指示预加载 "-c", prelude_config_path, @@ -126,12 +121,12 @@ mod tests { #[test] pub fn test_ona_prelude_de() -> Result<()> { - main_ona_prelude("./src/tests/cli/config/test_prelude_simple_deduction.json") + main_ona_prelude("./src/tests/cli/config/test_prelude_simple_deduction") } #[test] pub fn test_ona_prelude_op() -> Result<()> { - main_ona_prelude("./src/tests/cli/config/test_prelude_operation.json") + main_ona_prelude("./src/tests/cli/config/test_prelude_operation") } /// 测试入口/ONA/交互shell /// * 🎯正常BabelNAR CLI shell启动 @@ -146,9 +141,9 @@ mod tests { "test.exe", "-d", "-c", - "./src/tests/cli/config/test_ona.json", + "./src/tests/cli/config/test_ona", "-c", - "./src/tests/cli/config/websocket.json", + "./src/tests/cli/config/websocket", ] .into_iter() .map(str::to_string), diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index c0aa592..ef1116b 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -1,7 +1,7 @@ //! 启动后运行时的(交互与)管理 use super::websocket_server::*; -use crate::{launch_by_config, InputMode, LaunchConfig, LaunchConfigPreludeNAL}; +use crate::{launch_by_runtime_config, InputMode, LaunchConfigPreludeNAL, RuntimeConfig}; use anyhow::{anyhow, Result}; use babel_nar::{ cli_support::{ @@ -45,7 +45,7 @@ where /// 内部封装的「命令行参数」 /// * 🎯用于从命令行中加载配置 /// * 🚩只读 - pub(crate) config: Arc, + pub(crate) config: Arc, /// 内部缓存的「NAVM输出」 /// * 🎯用于NAL测试 @@ -59,7 +59,7 @@ where { /// 构造函数 /// * 🎯由此接管虚拟机实例、配置的所有权 - pub fn new(runtime: R, config: LaunchConfig) -> Self { + pub fn new(runtime: R, config: RuntimeConfig) -> Self { Self { runtime: Arc::new(Mutex::new(runtime)), config: Arc::new(config), @@ -253,7 +253,8 @@ where // ! 📝不能在此中出现裸露的`MutexGuard`对象:其并非线程安全 // * ✅可使用`&(mut) *`重引用语法,从`MutexGuard`转换为线程安全的引用 // * ✅对`Arc`使用`&*`同理:可以解包成引用,以便后续统一传递值的引用 - for io_result in ReadlineIter::new("BabelNAR> ") { + // ! 不建议在此启用提示词:会被异步的输出所打断 + for io_result in ReadlineIter::default() { // 从迭代器中读取一行 let line = io_result?; @@ -298,7 +299,7 @@ where pub fn input_line_to_vm( runtime: &mut R, line: &str, - config: &LaunchConfig, + config: &RuntimeConfig, output_cache: &mut OutputCache, ) -> Result<()> { // 向运行时输入 @@ -329,7 +330,7 @@ where runtime: &mut R, input: &str, output_cache: &mut OutputCache, - config: &LaunchConfig, + config: &RuntimeConfig, ) -> Result<()> { // 解析输入,并遍历解析出的每个NAL输入 for input in parse(input) { @@ -383,7 +384,7 @@ pub fn restart_manager( // 启动新的虚拟机 let config_ref = &*manager.config; - let new_runtime = launch_by_config(config_ref.clone())?; + let new_runtime = launch_by_runtime_config(config_ref)?; let new_manager = RuntimeManager::new(new_runtime, config_ref.clone()); // 返回 @@ -393,7 +394,7 @@ pub fn restart_manager( /// 根据配置(的「是否重启」选项)管理(一系列)虚拟机实例 pub fn loop_manage( mut manager: RuntimeManager, - config: &LaunchConfig, + config: &RuntimeConfig, ) -> Result<()> { match manager.manage() { // 返回了「结果」⇒解包并传递结果 diff --git a/src/bin/babelnar_cli/launch_config.rs b/src/bin/babelnar_cli/vm_config.rs similarity index 57% rename from src/bin/babelnar_cli/launch_config.rs rename to src/bin/babelnar_cli/vm_config.rs index 7bc2ae7..e75b7ac 100644 --- a/src/bin/babelnar_cli/launch_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -61,62 +61,132 @@ //! } //! ``` -use std::path::PathBuf; - -use nar_dev_utils::OptionBoost; +use anyhow::{anyhow, Result}; +use babel_nar::println_cli; +use nar_dev_utils::{if_return, pipe, OptionBoost, ResultBoost}; use serde::{Deserialize, Serialize}; +use std::{ + fs::read_to_string, + path::{Path, PathBuf}, +}; + +/// 工具宏/批量拷贝性合并 +/// * 🎯简化重复的`对象.方法`调用 +/// * 📄参考[`Option::coalesce_clone`] +macro_rules! coalesce_clones { + { + // 合并的方向 + $other:ident => $this:ident; + // 要合并的键 + $($field:ident)* + } => { $( $this.$field.coalesce_clone(&$other.$field); )* }; +} /// NAVM虚拟机(运行时)启动配置 /// * 🎯启动完整的NAVM实例,并附带相关运行时配置 /// * ✨启动时数据提供 /// * ✨运行时数据提供 +/// * 📍【2024-04-04 02:17:10】现在所有都是**可选**的 +/// * 🎯用于无损合并从键值对中加载而来的配置 +/// * 📄`true`可以在识别到`null`时替换`null`,而无需管其是否为默认值 +/// * 🚩在启动时会转换为「运行时配置」,并在此时检查完整性 +/// * 📌这意味着其总是能派生[`Default`] #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] // 🔗参考: -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LaunchConfig { - /// 转译器组合(可选) + /// 转译器组合 /// * 🚩使用字符串模糊匹配 pub translators: Option, - /// 启动命令(可选) + /// 启动命令 pub command: Option, - /// Websocket参数(可选) + /// Websocket参数 /// * 🚩【2024-04-03 18:21:00】目前对客户端输出JSON pub websocket: Option, - /// 预置NAL(可选) + /// 预置NAL #[serde(rename = "preludeNAL")] // * 📝serde配置中,`rename`优先于`rename_all` pub prelude_nal: Option, - /// 启用用户输入(可选) + /// 启用用户输入 /// * 🎯控制该实例是否需要(来自用户的)交互式输入 - /// * 📜默认值:`true` - /// * 📝serde中,若不使用`bool::default`(false),需要指定一个函数来初始化 - /// * ⚠️即便在[`LaunchConfig`]中定义了[`default`],也会使用[`bool::default`] - #[serde(default = "bool_true")] - pub user_input: bool, + /// * 🚩【2024-04-04 02:19:36】默认值由「运行时转换」决定 + /// * 🎯兼容「多启动配置合并」 + pub user_input: Option, /// 输入模式 /// * 🚩对输入(不论交互还是Websocket)采用的解析模式 - /// * 📄用于纯NAVM指令(可选)的解析 - /// * 🎯用于兼容旧`BabelNAR.jl`服务端 - /// * 📜默认为`"nal"` - /// Disable the user's ability to interact with the program + /// * 📄纯NAVM指令的解析 + /// * 🎯兼容旧`BabelNAR.jl`服务端 + /// * 🚩【2024-04-04 02:19:36】默认值由「运行时转换」决定 + /// * 🎯兼容「多启动配置合并」 #[serde(default)] - pub input_mode: InputMode, + pub input_mode: Option, /// 自动重启 /// * 🎯程序健壮性:用户的意外输入,不会随意让程序崩溃 /// * 🚩在虚拟机终止(收到「终止」输出)时,自动用配置重启虚拟机 - /// * 📜默认为`false`(关闭) + /// * 🚩【2024-04-04 02:19:36】默认值由「运行时转换」决定 + /// * 🎯兼容「多启动配置合并」 + pub auto_restart: Option, + + /// 严格模式 + /// * 🎯测试敏感性:测试中的「预期失败」可以让程序上报异常 + /// * 🚩在「预引入NAL」等场景中,若出现「预期失败」则程序直接异常退出 + /// * 🚩【2024-04-04 02:19:36】默认值由「运行时转换」决定 + /// * 🎯兼容「多启动配置合并」 + pub strict_mode: Option, +} + +/// NAVM虚拟机(运行时)运行时配置 +/// * 🎯没有任何非必要的空值 +/// * 🚩自[`LaunchConfig`]加载而来 +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] // 🔗参考: +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimeConfig { + /// 转译器组合 + /// * 🚩运行时必须提供转译器 + /// * 📌【2024-04-04 02:11:44】即便是所谓「默认」转译器,使用「及早报错」避免非预期运行 + pub translators: LaunchConfigTranslators, + + /// 启动命令 + /// * 🚩运行时必须有一个启动命令 + /// * 🚩内部可缺省 + pub command: LaunchConfigCommand, + + /// Websocket参数(可选) + /// * 🚩允许无:不启动Websocket服务器 + pub websocket: Option, + + /// 预置NAL + /// * 🚩允许无:不预置NAL测试文件 + #[serde(rename = "preludeNAL")] // * 📝serde配置中,`rename`优先于`rename_all` + pub prelude_nal: Option, + + /// 启用用户输入 + /// * 🚩必选:[`None`]将视为默认值 + /// * 📜默认值:`true`(启用) + #[serde(default = "bool_true")] + pub user_input: bool, + + /// 输入模式 + /// * 🚩必选:[`None`]将视为默认值 + /// * 📜默认值:`"nal"` + #[serde(default)] + pub input_mode: InputMode, + + /// 自动重启 + /// * 🚩必选:[`None`]将视为默认值 + /// * 📜默认值:`false`(关闭) #[serde(default = "bool_false")] pub auto_restart: bool, /// 严格模式 - /// * 🎯测试敏感性:测试中的「预期失败」可以让程序上报异常 - /// * 🚩在虚拟机终止(收到「终止」输出)时,自动用配置重启虚拟机 - /// * 📜默认为`false`(关闭) + /// * 🚩必选:[`None`]将视为默认值 + /// * 📜默认值:`false`(关闭) #[serde(default = "bool_false")] pub strict_mode: bool, } @@ -135,23 +205,32 @@ const fn bool_false() -> bool { false } -impl Default for LaunchConfig { - fn default() -> Self { - Self { - // [`Option`]全部为[`None`] - translators: None, - command: None, - websocket: None, - prelude_nal: None, +/// 尝试将启动时配置[`LaunchConfig`]转换成运行时配置[`RuntimeConfig`] +/// * 📌默认项:存在默认值,如「启用用户输入」「不自动重启」 +/// * 📌必选项:要求必填值,如「转译器组」「启动命令」 +/// * ⚠️正是此处可能报错 +/// * 📌可选项:仅为可选值,如「Websocket」「预引入NAL」 +impl TryFrom for RuntimeConfig { + type Error = anyhow::Error; + + fn try_from(config: LaunchConfig) -> Result { + Ok(Self { + // * 🚩必选项统一用`ok_or(..)?` + translators: config.translators.ok_or(anyhow!("启动配置缺少转译器"))?, + command: config.command.ok_or(anyhow!("启动配置缺少启动命令"))?, + // * 🚩可选项直接置入 + websocket: config.websocket, + prelude_nal: config.prelude_nal, + // * 🚩默认项统一用`unwrap_or` // 默认启用用户输入 - user_input: true, + user_input: config.user_input.unwrap_or(true), // 输入模式传递默认值 - input_mode: InputMode::default(), + input_mode: config.input_mode.unwrap_or_default(), // 不自动重启 - auto_restart: false, + auto_restart: config.auto_restart.unwrap_or(false), // 不开启严格模式 - strict_mode: false, - } + strict_mode: config.strict_mode.unwrap_or(false), + }) } } @@ -260,9 +339,11 @@ impl LaunchConfig { Self::default() } - /// (尝试)从JSON字符串构造 - pub fn from_json_str(json: &str) -> serde_json::Result { - serde_json::from_str(json) + /// (尝试)从(H)JSON字符串构造 + /// * 🚩【2024-04-04 03:43:01】现在使用[`deser_hjson`]兼容`json`且一并兼容`hjson` + /// * 🔗有关`hjson`格式: + pub fn from_json_str(json: &str) -> Result { + Ok(deser_hjson::from_str(json)?) } /// 判断其自身是否需要用户填充 @@ -282,18 +363,19 @@ impl LaunchConfig { /// * 🚩合并逻辑:`Some(..)` => `None` /// * 当并入者为`Some`,自身为`None`时,合并`Some`中的值 /// * ✨对【内部含有可选键】的值,会**递归深入** - /// - /// TODO: ❓启动时与运行时需要分开:不推荐直接覆盖布尔值 pub fn merge_from(&mut self, other: &Self) { - // 合并所有【不含可选键】的值 - self.translators.coalesce_clone(&other.translators); - self.prelude_nal.coalesce_clone(&other.prelude_nal); - self.websocket.coalesce_clone(&other.websocket); - // ! 覆盖所有【必定有】的值 | 如:布尔值 - self.user_input = other.user_input; - self.input_mode = other.input_mode; - self.auto_restart = other.auto_restart; - self.strict_mode = other.strict_mode; + // 合并所有内部Option | 使用工具宏简化语法 + coalesce_clones! { + other => self; + translators + // command // ! 此键需递归处理 + websocket + prelude_nal + user_input + input_mode + auto_restart + strict_mode + } // 递归合并所有【含有可选键】的值 LaunchConfigCommand::merge_as_key(&mut self.command, &other.command); } @@ -303,8 +385,11 @@ impl LaunchConfigCommand { /// 从另一个配置中并入配置 /// * 🚩`Some(..)` => `None` pub fn merge_from(&mut self, other: &Self) { - self.cmd_args.coalesce_clone(&other.cmd_args); - self.current_dir.coalesce_clone(&other.current_dir); + coalesce_clones! { + other => self; + cmd_args + current_dir + } } /// 作为一个键,从另一个配置中并入配置 @@ -320,12 +405,101 @@ impl LaunchConfigCommand { } } +/// 从外部JSON文件中加载启动配置 +/// * 🎯错误处理 & 错误⇒空置 +/// * 🚩在遇到错误时会发出警告 +/// * ⚠️若无需打印警告,请使用[`read_config_extern`] +pub fn load_config_extern(path: &Path) -> Option { + // Ok⇒Some,Err⇒警告+None + read_config_extern(path).ok_or_run(|e| { + // 根据错误类型进行分派 // + // 文件读写错误 + if let Some(e) = e.downcast_ref::() { + match e.kind() { + std::io::ErrorKind::NotFound => { + println_cli!([Warn] "未找到外部配置,使用空配置……"); + } + _ => println_cli!([Warn] "读取外部配置时出现预期之外的错误: {}", e), + } + } + // 配置解析错误/serde + else if let Some(e) = e.downcast_ref::() { + match e.classify() { + serde_json::error::Category::Syntax => { + println_cli!([Warn] "外部配置文件格式错误,使用空配置……"); + } + _ => println_cli!([Warn] "解析外部配置时出现预期之外的错误: {}", e), + } + } + // 配置解析错误/hjson + else if let Some(e) = e.downcast_ref::() { + match e { + deser_hjson::Error::Syntax { .. } => { + println_cli!([Warn] "外部配置文件格式错误,使用空配置……"); + } + deser_hjson::Error::Io { .. } => { + println_cli!([Warn] "外部配置文件读取错误,使用空配置……"); + } + _ => println_cli!([Warn] "解析外部配置时出现预期之外的错误: {}", e), + } + } + // 其它 + else { + println_cli!([Warn] "加载外部配置时出现预期之外的错误: {}", e) + } + // 空置 + }) +} + +/// 从外部JSON文件中读取启动配置 +/// * 🎯仅涉及具体读取逻辑,不涉及错误处理 +pub fn read_config_extern(path: &Path) -> Result { + // 尝试读取外部启动配置,并尝试解析 + pipe! { + path + // 尝试补全路径 + => try_complete_path + // 尝试读取文件内容 + => read_to_string + => {?}# + // 尝试解析JSON配置 + => #{&} + => LaunchConfig::from_json_str + => {?}# + // 返回Ok(转换为`anyhow::Result`) + => Ok + } + // ! 若需使用`confy`,必须封装 + // * 🚩目前无需使用`confy`:可以自动创建配置文件,但个人希望其路径与exe同目录 + // Ok(confy::load_path(path)?) // ! 必须封装 +} + +/// 尝试对无扩展名的路径添加扩展名 +/// * 🎯用于自动匹配`.json`与`.hjson` +/// * ❌不能用于「多扩展名」的情况,如`BabelNAR.launch` +/// * 此处会认定是「有扩展名」而不会补全 +pub fn try_complete_path(path: &Path) -> PathBuf { + // 创建路径缓冲区 + let path = path.to_path_buf(); + // 当扩展名为空时补全 + if path.extension().is_none() { + // 尝试补全为`.hjson` | 无扩展名⇒追加,有扩展名⇒替换 + let path_ = path.with_extension("hjson"); + if_return! { path_.exists() => path_ } + // 尝试补全为`.json` | 无扩展名⇒追加,有扩展名⇒替换 + let path_ = path.with_extension("json"); + if_return! { path_.exists() => path_ } + } + path +} + /// 单元测试 #[cfg(test)] mod tests { use super::*; - use serde_json::Result; + use anyhow::Result; + /// 实用测试宏 macro_rules! test { { $( $data:expr => $expected:expr )* } => { $( @@ -410,15 +584,15 @@ mod tests { { "inputMode": "cmd" }"# => LaunchConfig { - input_mode: InputMode::Cmd, + input_mode: Some(InputMode::Cmd), ..Default::default() } r#"{ "autoRestart": true, "userInput": false }"# => LaunchConfig { - auto_restart: true, - user_input: false, + auto_restart: Some(true), + user_input: Some(false), ..Default::default() } } diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs index f0be734..cdfd809 100644 --- a/src/bin/babelnar_cli/websocket_server.rs +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -2,7 +2,7 @@ //! * 🎯为BabelNAR CLI实现Websocket IO //! * 🎯实现专有的Websocket服务端逻辑 -use crate::{LaunchConfig, RuntimeManager}; +use crate::{RuntimeConfig, RuntimeManager}; use anyhow::Result; use babel_nar::{ cli_support::io::{ @@ -72,7 +72,7 @@ where pub(crate) runtime: ArcMutex, /// 所涉及的运行时配置 - pub(crate) config: Arc, + pub(crate) config: Arc, /// 所涉及的运行时 pub(crate) output_cache: ArcMutex, @@ -166,7 +166,7 @@ where pub(crate) runtime: ArcMutex, /// 所涉及的虚拟机配置 - pub(crate) config: Arc, + pub(crate) config: Arc, /// 所涉及的输出缓存 pub(crate) output_cache: ArcMutex, diff --git a/src/cin_implements/common/exe.rs b/src/cin_implements/common/exe.rs index 0cccd31..1c73b4d 100644 --- a/src/cin_implements/common/exe.rs +++ b/src/cin_implements/common/exe.rs @@ -6,6 +6,7 @@ //! * ❗没必要使用新的数据结构 use crate::runtimes::{CommandVm, IoTranslators}; +use nar_dev_utils::manipulate; use std::{ffi::OsStr, path::Path, process::Command}; /// 根据配置统一生成[`Command`]对象 @@ -39,5 +40,8 @@ where /// 根据「输入输出转译器」构建[`CommandVm`]对象 pub fn generate_command_vm(command: Command, translators: impl Into) -> CommandVm { - CommandVm::from(command).translators(translators) + manipulate!( + CommandVm::from(command) + => .translators(translators) + ) } diff --git a/src/runtimes/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs index 24d9d02..a953ed6 100644 --- a/src/runtimes/command_vm/api/translators.rs +++ b/src/runtimes/command_vm/api/translators.rs @@ -3,7 +3,7 @@ //! * ✨特制结构 //! * ✨特有错误类型 -use anyhow::{Ok, Result}; +use anyhow::Result; use navm::{cmd::Cmd, output::Output}; use std::error::Error; use thiserror::Error; diff --git a/src/runtimes/command_vm/launcher.rs b/src/runtimes/command_vm/launcher.rs index 83cbd91..9628a37 100644 --- a/src/runtimes/command_vm/launcher.rs +++ b/src/runtimes/command_vm/launcher.rs @@ -52,13 +52,12 @@ impl CommandVm { } /// 配置/输入输出转译器组 - pub fn translators(mut self, translators: impl Into) -> Self { + pub fn translators(&mut self, translators: impl Into) { // 一次实现俩 let translators = translators.into(); // 直接赋值 self.input_translator = Some(translators.input_translator); self.output_translator = Some(translators.output_translator); - self } } diff --git a/src/tests/cli/config/matriangle_server.hjson b/src/tests/cli/config/matriangle_server.hjson new file mode 100644 index 0000000..ee33625 --- /dev/null +++ b/src/tests/cli/config/matriangle_server.hjson @@ -0,0 +1,12 @@ +#hjson +// * 🎯对接Matriangle服务器 +// * ✨兼容旧BabelNAR与Matriangle的Websocket交互逻辑 +{ + // Websocket服务端地址 + websocket: { + host: localhost + port: 8765 + } + // 【2024-04-04 04:49:32】Matriangle环境目前以「NAVM指令」的形式传入 + inputMode: cmd +} \ No newline at end of file diff --git a/src/tests/cli/config/matriangle_server.json b/src/tests/cli/config/matriangle_server.json deleted file mode 100644 index 5921362..0000000 --- a/src/tests/cli/config/matriangle_server.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "对接Matriangle服务器,兼容旧BabelNAR与Matriangle的Websocket交互逻辑", - "websocket": { - "host": "localhost", - "port": 8765 - }, - "inputMode": "cmd" -} \ No newline at end of file diff --git a/src/tests/cli/config/opennars.hjson b/src/tests/cli/config/opennars.hjson new file mode 100644 index 0000000..c7fe159 --- /dev/null +++ b/src/tests/cli/config/opennars.hjson @@ -0,0 +1,21 @@ +#hjson +// * 用于测试「预加载NAL输入」,加载「简单演绎推理」 +{ + // 转译器 + translators: "opennars" + // 启动命令 + command: { + // 命令:启动java运行时 + cmd: "java" + // 传入的命令参数 + cmdArgs: [ + // 设置最大堆内存为1024M + "-Xmx1024m" + // 启动jar包 + -jar + nars.jar + ] + // 启动时的工作目录 + currentDir: root/nars/test + } +} \ No newline at end of file diff --git a/src/tests/cli/config/opennars.json b/src/tests/cli/config/opennars.json deleted file mode 100644 index b67eb88..0000000 --- a/src/tests/cli/config/opennars.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "translators": "opennars", - "command": { - "cmd": "java", - "cmdArgs": [ - "-Xmx1024m", - "-jar", - "nars.jar" - ], - "currentDir": "root/nars/test" - } -} \ No newline at end of file diff --git a/src/tests/cli/config/pynars.hjson b/src/tests/cli/config/pynars.hjson new file mode 100644 index 0000000..3406e20 --- /dev/null +++ b/src/tests/cli/config/pynars.hjson @@ -0,0 +1,11 @@ +// TODO: 待完成 +{ + translators: pynars + command: { + cmd: ../../NARS-executables/NAR.exe + cmdArgs: [ + shell + ] + } + autoRestart: true +} \ No newline at end of file diff --git a/src/tests/cli/config/test_ona.hjson b/src/tests/cli/config/test_ona.hjson new file mode 100644 index 0000000..e50032b --- /dev/null +++ b/src/tests/cli/config/test_ona.hjson @@ -0,0 +1,18 @@ +#hjson +// * 🎯用于测试ONA +// ! ⚠️使用了与项目无关的本地路径 +// * 📝ONA依赖cygwin,故即便内置也容易出错 +{ + // 转译器支持单独指定「输入转译器」和「输出转译器」 + translators: { + in: ona + out: ona + } + command: { + cmd: ../../NARS-executables/NAR.exe + cmdArgs: [ + shell + ] + } + autoRestart: true +} \ No newline at end of file diff --git a/src/tests/cli/config/test_ona.json b/src/tests/cli/config/test_ona.json deleted file mode 100644 index f5c0fb9..0000000 --- a/src/tests/cli/config/test_ona.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "用于测试ONA,使用了与项目无关的本地路径(ONA依赖cygwin,故即便内置也容易出错)", - "translators": "ona", - "command": { - "cmd": "../../NARS-executables/NAR.exe", - "cmdArgs": [ - "shell" - ] - }, - "autoRestart": true -} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_operation.hjson b/src/tests/cli/config/test_prelude_operation.hjson new file mode 100644 index 0000000..9a5a598 --- /dev/null +++ b/src/tests/cli/config/test_prelude_operation.hjson @@ -0,0 +1,16 @@ +#hjson +// * 用于测试「预加载NAL输入」,加载「操作测试」 +{ + preludeNAL: { + // 预置的NAL测试文件 + file: ./src/tests/nal/test_operation.nal + } + // 禁止用户输入 + userInput: false + // 不自动重启 + autoRestart: false + // 开启严格模式 + // * 🎯用于自动化测试中捕获错误 + // * ✨可由此被外部脚本调用 + strictMode: true +} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_operation.json b/src/tests/cli/config/test_prelude_operation.json deleted file mode 100644 index e16bcbe..0000000 --- a/src/tests/cli/config/test_prelude_operation.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "用于测试「预加载NAL输入」,加载「操作测试」", - "preludeNAL": { - "file": "./src/tests/nal/test_operation.nal" - }, - "userInput": false, - "autoRestart": false, - "strictMode": true -} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_simple_deduction.hjson b/src/tests/cli/config/test_prelude_simple_deduction.hjson new file mode 100644 index 0000000..77579be --- /dev/null +++ b/src/tests/cli/config/test_prelude_simple_deduction.hjson @@ -0,0 +1,16 @@ +#hjson +// * 用于测试「预加载NAL输入」,加载「简单演绎推理」 +{ + preludeNAL: { + // 预置的NAL测试文件 + file: ./src/tests/nal/test_simple_deduction.nal + } + // 禁止用户输入 + userInput: false + // 不自动重启 + autoRestart: false + // 开启严格模式 + // * 🎯用于自动化测试中捕获错误 + // * ✨可由此被外部脚本调用 + strictMode: true +} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_simple_deduction.json b/src/tests/cli/config/test_prelude_simple_deduction.json deleted file mode 100644 index f7f6e6f..0000000 --- a/src/tests/cli/config/test_prelude_simple_deduction.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "用于测试「预加载NAL输入」,加载「简单演绎推理」", - "preludeNAL": { - "file": "./src/tests/nal/test_simple_deduction.nal" - }, - "userInput": false, - "autoRestart": false, - "strictMode": true -} \ No newline at end of file diff --git a/src/tests/cli/config/websocket.hjson b/src/tests/cli/config/websocket.hjson new file mode 100644 index 0000000..1b09197 --- /dev/null +++ b/src/tests/cli/config/websocket.hjson @@ -0,0 +1,8 @@ +#hjson +// 用于测试可作补丁的Websocket配置 +{ + websocket: { + host: localhost + port: 8080 + } +} \ No newline at end of file diff --git a/src/tests/cli/config/websocket.json b/src/tests/cli/config/websocket.json deleted file mode 100644 index e6de238..0000000 --- a/src/tests/cli/config/websocket.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "description": "用于测试可作补丁的Websocket配置", - "websocket": { - "host": "localhost", - "port": 8080 - } -} \ No newline at end of file From 60c985e2f58134bbefaf3ef571b3cc2ab14700f2 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 5 Apr 2024 03:48:31 +0800 Subject: [PATCH 46/59] =?UTF-8?q?chore:=20:arrow=5Fup:=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BE=9D=E8=B5=96=E6=9D=A5=E6=BA=90=EF=BC=8C=E6=B6=A6?= =?UTF-8?q?=E8=89=B2Cargo=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 10 +++++++--- Cargo.toml | 17 +++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e2bae5..4622449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,11 +448,15 @@ dependencies = [ [[package]] name = "nar_dev_utils" -version = "0.26.1" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4355b0bb0c70d4682624cf9405c3594fffcf655629ebfb7ac7631b9981835b" [[package]] name = "narsese" -version = "0.12.1" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbb48e37fc65a9d76f2ac9e097fd5b7391ef31e2591f6b1f6ad6c5ba9e68b14" dependencies = [ "lazy_static", "nar_dev_utils", @@ -460,7 +464,7 @@ dependencies = [ [[package]] name = "navm" -version = "0.9.0" +version = "0.9.1" dependencies = [ "anyhow", "nar_dev_utils", diff --git a/Cargo.toml b/Cargo.toml index 39902e9..915d7db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,14 @@ Implementation and application supports of the NAVM model """ readme = "README.md" -keywords = ["NARS", "NAVM"] +keywords = ["NARS", "Non-Axiomatic Reasoning", "NAVM"] license = "MIT OR Apache-2.0" -categories = [] # TODO +categories = [ + "parser-implementations", # 解析器实现 | 各CIN方言 + "development-tools", # 开发工具 + "command-line-utilities", # CLI应用 +] # 🔗 repository = "https://github.com/ARCJ137442/BabelNAR.rs" # Cargo文档参考: @@ -24,9 +28,9 @@ anyhow = "1.0.81" [dependencies.nar_dev_utils] # 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` -# version = "0.1.0" # ! 本地依赖可以不添加版本 +version = "0" # * ✅现已发布至`crates.io` # *🚩【2024-03-21 09:26:38】启用所有 -path = "../NAR-dev-util" +# path = "../NAR-dev-util" # git = "https://github.com/ARCJ137442/NAR-dev-util" # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [ "bundled" ] # 启用所有特性 @@ -34,7 +38,8 @@ features = [ "bundled" ] # 启用所有特性 [dependencies.narsese] # ! 本地依赖可以不添加版本 # 载入Narsese API,引入其中所有部分 -path = "../Narsese.rs" +version = "0" # * ✅现已发布至`crates.io` +# path = "../Narsese.rs" # git = "https://github.com/ARCJ137442/Narsese.rs" # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [ @@ -162,7 +167,7 @@ openjunars = [] # 命令行支持 # cli_support = [ "colored", # 命令行io 彩色打印 - "serde", "serde_json", # 配置文件解析 + "serde", "serde_json", "deser-hjson", # 配置文件解析 "ws", # 命令行io Websocket服务 "clap" # 命令行参数解析 ] From 96dfc022c5a66bd8f632c252fe292708ae9db887 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Fri, 5 Apr 2024 03:49:27 +0800 Subject: [PATCH 47/59] =?UTF-8?q?refactor:=20:recycle:=20=E6=B6=A6?= =?UTF-8?q?=E8=89=B2OpenNARS=E8=BE=93=E5=87=BA=E8=BD=AC=E8=AF=91=E5=99=A8?= =?UTF-8?q?=EF=BC=9A=E7=8E=B0=E5=9C=A8=E4=B8=8D=E4=BC=9A=E5=B0=86=E7=A9=BA?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=BD=93=E4=BD=9C=E3=80=8CUNCLASSIF?= =?UTF-8?q?IED=E3=80=8D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cin_implements/opennars/translators.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4622449..4ba9394 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.15.0" +version = "0.15.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 915d7db..2d376cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.15.0" +version = "0.15.1" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index 36341ec..fd1972d 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -97,7 +97,7 @@ pub fn output_translate(content_raw: String) -> Result { description: content_raw, }, // * 🚩利用OpenNARS常见输出「全大写」的特征,兼容「confirm」与「disappoint」 - upper if head == upper => Output::UNCLASSIFIED { + upper if !head.is_empty() && head == upper => Output::UNCLASSIFIED { r#type: head.to_string(), content: content_raw, // 默认不捕获Narsese @@ -112,7 +112,7 @@ pub fn output_translate(content_raw: String) -> Result { Ok(output) } -/// (ONA)从原始输出中解析Narsese +/// (OpenNARS)从原始输出中解析Narsese /// * 🎯用于结合`#[cfg]`控制「严格模式」 /// * 🚩生产环境下「Narsese解析出错」仅打印错误信息 #[cfg(not(test))] @@ -122,7 +122,7 @@ pub fn parse_narsese_opennars(head: &str, tail: &str) -> Result> Ok(try_parse_narsese(tail).ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}"))) } -/// (ONA)从原始输出中解析Narsese +/// (OpenNARS)从原始输出中解析Narsese /// * 🎯用于结合`#[cfg]`控制「严格模式」 /// * 🚩测试环境下「Narsese解析出错」会上抛错误 #[cfg(test)] @@ -142,7 +142,7 @@ pub fn parse_operation_opennars(tail: &str) -> Operation { let mut params = vec![]; // 提取输出中的字符串 - let c = r.captures(dbg!(tail)); + let c = r.captures(tail); // let budget; let operator_name; let params_str; From 248df9e8e9e633350c47f0bd7d680326280eda40 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sat, 6 Apr 2024 03:20:09 +0800 Subject: [PATCH 48/59] =?UTF-8?q?refactor:=20:recycle:=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC=EF=BC=9B=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E9=85=8D=E7=BD=AE=E5=88=A4=E7=A9=BA=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=BA=94=E7=94=A8=E5=9C=A8CLI=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +++++++++++++++---------------- Cargo.toml | 4 +-- src/bin/babelnar_cli/arg_parse.rs | 10 +++++--- src/bin/babelnar_cli/vm_config.rs | 22 ++++++++++++++++ src/bin/cin_launcher/main.rs | 1 - 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ba9394..e91e84c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.15.1" +version = "0.15.2" dependencies = [ "anyhow", "clap", @@ -399,9 +399,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mio" @@ -448,15 +448,15 @@ dependencies = [ [[package]] name = "nar_dev_utils" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4355b0bb0c70d4682624cf9405c3594fffcf655629ebfb7ac7631b9981835b" +checksum = "1662c34eaa2853e1342dc1bbedf0373dc4315d1b0b7637e2c25cf424c9642355" [[package]] name = "narsese" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cbb48e37fc65a9d76f2ac9e097fd5b7391ef31e2591f6b1f6ad6c5ba9e68b14" +checksum = "dd2b29328c5c47d1b94893a9a85a58bee0165fde6c87490b0d01a3c00c41ac2b" dependencies = [ "lazy_static", "nar_dev_utils", @@ -502,9 +502,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ "memchr", "thiserror", @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", @@ -536,9 +536,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.8" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ryu" @@ -710,15 +710,15 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.55" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2d376cc..2829df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "babel_nar" -version = "0.15.1" +version = "0.15.2" edition = "2021" description = """ Implementation and application supports of the NAVM model """ readme = "README.md" -keywords = ["NARS", "Non-Axiomatic Reasoning", "NAVM"] +keywords = ["NARS", "Non-Axiomatic-Reasoning", "NAVM"] license = "MIT OR Apache-2.0" categories = [ diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index 42cca30..d52be07 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -97,9 +97,13 @@ pub fn load_config(args: &CliArgs) -> LaunchConfig { try_load_default_config().inspect(|config_extern| result.merge_from(config_extern)); } // 展示加载的配置 | 以便调试(以防其它地方意外插入别的配置) - match serde_json::to_string(&result) { - Ok(json) => println_cli!([Log] "外部配置已加载:{json}",), - Err(e) => println_cli!([Warn] "展示加载的配置时出现预期之外的错误: {e}"), + if result.is_empty() { + println_cli!([Log] "未加载任何外部配置"); + } else { + match serde_json::to_string(&result) { + Ok(json) => println_cli!([Log] "外部配置已加载:{json}",), + Err(e) => println_cli!([Warn] "展示加载的配置时出现预期之外的错误: {e}"), + } } // 返回 result diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index e75b7ac..1f89490 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -140,6 +140,21 @@ pub struct LaunchConfig { pub strict_mode: Option, } +/// 使用`const`常量存储「空启动配置」 +/// * 🎯用于启动配置的「判空」逻辑 +/// * ✅与此同时,实现了「有提醒的后期维护」 +/// * 📌后续若新增字段,此处会因「缺字段」立即报错 +const EMPTY_LAUNCH_CONFIG: LaunchConfig = LaunchConfig { + translators: None, + command: None, + websocket: None, + prelude_nal: None, + user_input: None, + input_mode: None, + auto_restart: None, + strict_mode: None, +}; + /// NAVM虚拟机(运行时)运行时配置 /// * 🎯没有任何非必要的空值 /// * 🚩自[`LaunchConfig`]加载而来 @@ -339,6 +354,13 @@ impl LaunchConfig { Self::default() } + /// 判断配置是否为空 + /// * 📌本质:判断字段是否全为[`None`] + /// * 🚩直接与「空配置」相匹配 + pub fn is_empty(&self) -> bool { + self == &EMPTY_LAUNCH_CONFIG + } + /// (尝试)从(H)JSON字符串构造 /// * 🚩【2024-04-04 03:43:01】现在使用[`deser_hjson`]兼容`json`且一并兼容`hjson` /// * 🔗有关`hjson`格式: diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 4d586a2..4f1db43 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -8,7 +8,6 @@ //! * 📌可保存/加载「常用CIN」配置 //! //! * 🚩目前用于敏捷原型开发 -//! TODO: 完成代码 #![allow(unused)] use anyhow::Result; From 5a041a61342250f15152c033b60dc89628363b46 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 00:51:16 +0800 Subject: [PATCH 49/59] =?UTF-8?q?refactor:=20:recycle:=20=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E5=AE=8C=E6=88=90=E3=80=8C=E9=85=8D=E7=BD=AE=E4=B8=AD?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E7=9B=B8=E5=AF=B9=E5=8C=96=E3=80=8D=E6=96=B9?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅基于「配置中路径自动变基根路径到配置文件自身路径」的方式,实现「『配置文件中出现的相对路径』以『配置文件自身所在目录』为起点目录」的功能 📌后续内部实现可能还要大改: - 因为需要「生成启动命令时Cmd::current_dir(配置文件所在目录)」以实现完全的「配置文件相对路径的脱exe化」 - 最终仍需将「配置文件所在目录」记录在「启动配置」和「运行时配置」的数据结构中 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/arg_parse.rs | 23 ++-- src/bin/babelnar_cli/main.rs | 27 +++-- src/bin/babelnar_cli/runtime_manage.rs | 1 + src/bin/babelnar_cli/vm_config.rs | 105 ++++++++++++++++-- src/tests/cli/config/opennars.hjson | 4 +- src/tests/cli/config/pynars.hjson | 11 +- .../cli/config/test_prelude_operation.hjson | 4 +- .../test_prelude_simple_deduction.hjson | 4 +- 10 files changed, 140 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e91e84c..ae7ec2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.15.2" +version = "0.15.3" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2829df7..e02884f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.15.2" +version = "0.15.3" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index d52be07..24591cf 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -117,6 +117,8 @@ mod tests { /// 测试/参数解析 mod arg_parse { + use crate::vm_config::tests::test_config_paths::TEST_OPENNARS; + use super::*; fn _test_arg_parse(args: &[&str], expected: &CliArgs) { @@ -158,9 +160,9 @@ mod tests { #[test] fn test_arg_parse() { test_arg_parse! { - ["-c", "./src/tests/cli/config/opennars"] + ["-c", TEST_OPENNARS] => CliArgs { - config: vec!["./src/tests/cli/config/opennars".into()], + config: vec![TEST_OPENNARS.into()], ..Default::default() }; // 多个配置:重复使用`-c`/`--config`,按使用顺序填充 @@ -190,6 +192,9 @@ mod tests { /// 测试/加载配置 mod read_config { use super::*; + use crate::vm_config::tests::test_config_paths::TEST_OPENNARS; + use crate::vm_config::tests::test_config_paths::TEST_PRELUDE_SIMPLE_DEDUCTION; + use crate::vm_config::tests::test_config_paths::TEST_WEBSOCKET; use crate::vm_config::*; use crate::LaunchConfigWebsocket; @@ -219,7 +224,7 @@ mod tests { // 成功测试 test! { // 单个配置文件 - ["-c" "src/tests/cli/config/opennars" "-d"] => LaunchConfig { + ["-c" TEST_OPENNARS "-d"] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( "opennars".into(), @@ -236,7 +241,7 @@ mod tests { }), ..Default::default() }; - ["-c" "src/tests/cli/config/websocket" "-d"] => LaunchConfig { + ["-c" TEST_WEBSOCKET "-d"] => LaunchConfig { websocket: Some(LaunchConfigWebsocket { host: "localhost".into(), port: 8080, @@ -246,8 +251,8 @@ mod tests { // 两个配置文件合并 [ "-d" - "-c" "src/tests/cli/config/opennars" - "-c" "src/tests/cli/config/websocket" + "-c" TEST_OPENNARS + "-c" TEST_WEBSOCKET ] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( @@ -272,9 +277,9 @@ mod tests { // 三个配置文件合并 [ "-d" - "-c" "src/tests/cli/config/opennars" - "-c" "src/tests/cli/config/websocket" - "-c" "src/tests/cli/config/test_prelude_simple_deduction" + "-c" TEST_OPENNARS + "-c" TEST_WEBSOCKET + "-c" TEST_PRELUDE_SIMPLE_DEDUCTION ] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 7b3b6a2..b4d990c 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -79,6 +79,9 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> /// 单元测试 #[cfg(test)] mod tests { + use self::vm_config::tests::test_config_paths::{ + TEST_ONA, TEST_PRELUDE_OPERATION, TEST_PRELUDE_SIMPLE_DEDUCTION, TEST_WEBSOCKET, + }; use super::*; /// 测试入口/ONA/交互shell @@ -90,7 +93,7 @@ mod tests { // 以默认参数启动 main_args( env::current_dir(), - ["test.exe", "-d", "-c", "./src/tests/cli/config/test_ona"] + ["test.exe", "-d", "-c", TEST_ONA] .into_iter() .map(str::to_string), ) @@ -109,7 +112,7 @@ mod tests { "-d", // 第一个文件,指示ONA "-c", - "./src/tests/cli/config/test_ona", + TEST_ONA, // 第二个文件,指示预加载 "-c", prelude_config_path, @@ -119,15 +122,18 @@ mod tests { ) } + /// 测试/预加载NAL:简单演绎推理 #[test] pub fn test_ona_prelude_de() -> Result<()> { - main_ona_prelude("./src/tests/cli/config/test_prelude_simple_deduction") + main_ona_prelude(TEST_PRELUDE_SIMPLE_DEDUCTION) } + /// 测试/预加载NAL:操作 #[test] pub fn test_ona_prelude_op() -> Result<()> { - main_ona_prelude("./src/tests/cli/config/test_prelude_operation") + main_ona_prelude(TEST_PRELUDE_OPERATION) } + /// 测试入口/ONA/交互shell /// * 🎯正常BabelNAR CLI shell启动 /// * 🎯正常用户命令行交互体验 @@ -137,16 +143,9 @@ mod tests { // 以默认参数启动 main_args( env::current_dir(), - [ - "test.exe", - "-d", - "-c", - "./src/tests/cli/config/test_ona", - "-c", - "./src/tests/cli/config/websocket", - ] - .into_iter() - .map(str::to_string), + ["test.exe", "-d", "-c", TEST_ONA, "-c", TEST_WEBSOCKET] + .into_iter() + .map(str::to_string), ) } } diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index ef1116b..1a58482 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -164,6 +164,7 @@ where &mut *try_break!(OutputCache::unlock_arc_mutex(&mut self.output_cache)); // 读取内容 + // TODO: 从「配置文件所在路径」开始 let nal = match prelude_nal { // 文件⇒尝试读取文件内容 | ⚠️此处创建了一个新值,所以要统一成`String` LaunchConfigPreludeNAL::File(path) => try_break!(std::fs::read_to_string(path)), diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index 1f89490..306e0d1 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -63,7 +63,7 @@ use anyhow::{anyhow, Result}; use babel_nar::println_cli; -use nar_dev_utils::{if_return, pipe, OptionBoost, ResultBoost}; +use nar_dev_utils::{if_return, manipulate, pipe, OptionBoost, ResultBoost}; use serde::{Deserialize, Serialize}; use std::{ fs::read_to_string, @@ -91,6 +91,8 @@ macro_rules! coalesce_clones { /// * 📄`true`可以在识别到`null`时替换`null`,而无需管其是否为默认值 /// * 🚩在启动时会转换为「运行时配置」,并在此时检查完整性 /// * 📌这意味着其总是能派生[`Default`] +/// * ⚠️其中的所有**相对路径**,在[`read_config_extern`]中都基于**配置文件自身** +/// * 🎯不论CLI自身所处何处,均保证配置读取稳定 #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] // 🔗参考: #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -305,7 +307,7 @@ pub struct LaunchConfigCommand { /// 工作目录(可选) /// * 🎯可用于Python模块 - pub current_dir: Option, + pub current_dir: Option, } /// Websocket参数 @@ -370,6 +372,7 @@ impl LaunchConfig { /// 判断其自身是否需要用户填充 /// * 🎯用于在「启动NAVM运行时」时避免「参数无效」情况 + /// * 📌原则:必填参数不能为空 /// * 🚩判断「启动时必要项」是否为空 pub fn need_polyfill(&self) -> bool { // 启动命令非空 @@ -380,6 +383,38 @@ impl LaunchConfig { // ! 预加载NAL为空⇒不预加载NAL } + /// 变基配置中所含的路径,从其它地方变为 + /// * 🎯解决「配置中的**相对路径**仅相对于exe而非配置文件本身」的问题 + /// * 🎯将配置中相对路径的**根目录**从「exe」变更到配置文件本身 + /// * 🚩将`config_path`的路径追加到自身[`Path::is_relative`]的路径中,使之以`config_path`为根路径 + pub fn rebase_path_from(&mut self, config_path: &Path) { + /// 变基一个相对路径 + /// * 🚩是相对路径⇒尝试变基 + #[inline(always)] + fn rebase_one(config_path: &Path, relative_path: &mut PathBuf) { + if relative_path.is_relative() { + *relative_path = config_path.join(&relative_path); + } + } + // 预加载NAL + if let Some(LaunchConfigPreludeNAL::File(ref mut path)) = &mut self.prelude_nal { + rebase_one(config_path, path); + } + // 启动命令 + if let Some(LaunchConfigCommand { + current_dir: Some(ref mut path), + .. + }) = &mut self.command + { + rebase_one(config_path, path); + } + } + + /// 变基路径,但基于所有权[`Self`]→[`Self`] + pub fn rebase_path_from_owned(self, config_path: &Path) -> Self { + manipulate!( self => .rebase_path_from(config_path) ) + } + /// 从另一个配置中并入配置 /// * 📌优先级:`other` > `self` /// * 🚩合并逻辑:`Some(..)` => `None` @@ -430,7 +465,9 @@ impl LaunchConfigCommand { /// 从外部JSON文件中加载启动配置 /// * 🎯错误处理 & 错误⇒空置 /// * 🚩在遇到错误时会发出警告 -/// * ⚠️若无需打印警告,请使用[`read_config_extern`] +/// * ⚠️若无需打印警告(并手动处理错误),请使用[`read_config_extern`] +/// * ⚠️其中的所有**相对路径**,在[`read_config_extern`]中都基于**配置文件自身** +/// * 🎯不论CLI自身所处何处,均保证配置读取稳定 pub fn load_config_extern(path: &Path) -> Option { // Ok⇒Some,Err⇒警告+None read_config_extern(path).ok_or_run(|e| { @@ -439,7 +476,7 @@ pub fn load_config_extern(path: &Path) -> Option { if let Some(e) = e.downcast_ref::() { match e.kind() { std::io::ErrorKind::NotFound => { - println_cli!([Warn] "未找到外部配置,使用空配置……"); + println_cli!([Warn] "未在路径 {path:?} 找到外部配置,返回空配置……"); } _ => println_cli!([Warn] "读取外部配置时出现预期之外的错误: {}", e), } @@ -448,7 +485,7 @@ pub fn load_config_extern(path: &Path) -> Option { else if let Some(e) = e.downcast_ref::() { match e.classify() { serde_json::error::Category::Syntax => { - println_cli!([Warn] "外部配置文件格式错误,使用空配置……"); + println_cli!([Warn] "外部配置文件格式错误,返回空配置……"); } _ => println_cli!([Warn] "解析外部配置时出现预期之外的错误: {}", e), } @@ -475,6 +512,8 @@ pub fn load_config_extern(path: &Path) -> Option { /// 从外部JSON文件中读取启动配置 /// * 🎯仅涉及具体读取逻辑,不涉及错误处理 +/// * ⚠️其中的所有**相对路径**,在[`read_config_extern`]中都基于**配置文件自身** +/// * 🎯不论CLI自身所处何处,均保证配置读取稳定 pub fn read_config_extern(path: &Path) -> Result { // 尝试读取外部启动配置,并尝试解析 pipe! { @@ -488,6 +527,8 @@ pub fn read_config_extern(path: &Path) -> Result { => #{&} => LaunchConfig::from_json_str => {?}# + // 变基相对路径,从「基于CLI自身」到「基于配置文件自身」 + => .rebase_path_from_owned(path.parent().ok_or(anyhow!("无效的根路径!"))?) // 返回Ok(转换为`anyhow::Result`) => Ok } @@ -517,12 +558,39 @@ pub fn try_complete_path(path: &Path) -> PathBuf { /// 单元测试 #[cfg(test)] -mod tests { +pub mod tests { use super::*; use anyhow::Result; + /// 测试用文件路径 + /// * 🎯后续其它地方统一使用该处路径 + /// * 📌相对路径の根目录:项目根目录(`Cargo.toml`所在目录) + /// * ⚠️只与配置文件路径有关,不与CIN位置有关 + /// * 💭后续若在不同工作环境中,需要调整配置文件中有关「CIN位置」的信息 + pub mod test_config_paths { + #![allow(unused)] + /// 测试OpenNARS + pub const TEST_OPENNARS: &str = "./src/tests/cli/config/opennars.hjson"; + /// 测试ONA + pub const TEST_ONA: &str = "./src/tests/cli/config/test_ona.hjson"; + /// PyNARS + pub const PYNARS: &str = "./src/tests/cli/config/pynars.hjson"; + /// 预引入/简单演绎推理 + pub const TEST_PRELUDE_SIMPLE_DEDUCTION: &str = + "./src/tests/cli/config/test_prelude_simple_deduction.hjson"; + /// 预引入/操作 + pub const TEST_PRELUDE_OPERATION: &str = + "./src/tests/cli/config/test_prelude_operation.hjson"; + /// 预引入/Websocket + pub const TEST_WEBSOCKET: &str = "./src/tests/cli/config/websocket.hjson"; + /// 预引入/Matriangle服务器 + pub const TEST_MATRIANGLE_SERVER: &str = "./src/tests/cli/config/matriangle_server.hjson"; + } + use nar_dev_utils::asserts; + use test_config_paths::*; + /// 实用测试宏 - macro_rules! test { + macro_rules! test_parse { { $( $data:expr => $expected:expr )* } => { $( _test(&$data, &$expected).expect("测试失败"); @@ -540,13 +608,15 @@ mod tests { Ok(()) } - /// 主测试 + /// 测试/解析 + /// * 🎯JSON/HJSON的解析逻辑 #[test] - fn main() { - test! { + fn test_parse() { + test_parse! { // 平凡情况/空 "{}" => LaunchConfig::new() "{}" => LaunchConfig::default() + "{}" => EMPTY_LAUNCH_CONFIG // 完整情况 r#" { @@ -622,4 +692,19 @@ mod tests { "file": "root/path/to/file" */ } + + /// 测试/读取 + /// * 🎯相对**配置文件**的路径表示 + #[test] + fn test_read() { + // 使用OpenNARS配置文件的路径作测试 + let path: PathBuf = TEST_OPENNARS.into(); + let launch_config = read_config_extern(&path).expect("路径读取失败"); + let expected_path = "./src/tests/cli/config/root/nars/test".into(); + asserts! { + // * 🎯启动命令中的「当前目录」应该被追加到配置自身的路径上 + // * ✅即便拼接后路径是`"./src/tests/cli/config\\root/nars/test"`,也和上边的路径相等 + launch_config.command.unwrap().current_dir => Some(expected_path) + } + } } diff --git a/src/tests/cli/config/opennars.hjson b/src/tests/cli/config/opennars.hjson index c7fe159..ddb6c20 100644 --- a/src/tests/cli/config/opennars.hjson +++ b/src/tests/cli/config/opennars.hjson @@ -1,5 +1,7 @@ #hjson -// * 用于测试「预加载NAL输入」,加载「简单演绎推理」 +// * 📌包含OpenNARS转译器 及其jar启动的命令配置 +// * ⚠️不包括具体运行时所在路径 +// * 🎯用于测试「预加载NAL输入」,加载「简单演绎推理」 { // 转译器 translators: "opennars" diff --git a/src/tests/cli/config/pynars.hjson b/src/tests/cli/config/pynars.hjson index 3406e20..c717228 100644 --- a/src/tests/cli/config/pynars.hjson +++ b/src/tests/cli/config/pynars.hjson @@ -1,11 +1,16 @@ -// TODO: 待完成 +// PyNARS的启动配置 +// * ⚠️【2024-04-06 16:47:55】目前仍然使用了相对路径 { translators: pynars command: { - cmd: ../../NARS-executables/NAR.exe + cmd: python cmdArgs: [ - shell + -m + pynars.Console ] + // ! ⚠️此处为本机路径,在其它系统测试时需要修改到正确的PyNARS目录内 + // * 文件结构:参考GitHub https://github.com/bowen-xu/PyNARS + currentDir: ../../PyNARS-dev } autoRestart: true } \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_operation.hjson b/src/tests/cli/config/test_prelude_operation.hjson index 9a5a598..2d81c82 100644 --- a/src/tests/cli/config/test_prelude_operation.hjson +++ b/src/tests/cli/config/test_prelude_operation.hjson @@ -2,8 +2,8 @@ // * 用于测试「预加载NAL输入」,加载「操作测试」 { preludeNAL: { - // 预置的NAL测试文件 - file: ./src/tests/nal/test_operation.nal + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_operation.nal } // 禁止用户输入 userInput: false diff --git a/src/tests/cli/config/test_prelude_simple_deduction.hjson b/src/tests/cli/config/test_prelude_simple_deduction.hjson index 77579be..708afcc 100644 --- a/src/tests/cli/config/test_prelude_simple_deduction.hjson +++ b/src/tests/cli/config/test_prelude_simple_deduction.hjson @@ -2,8 +2,8 @@ // * 用于测试「预加载NAL输入」,加载「简单演绎推理」 { preludeNAL: { - // 预置的NAL测试文件 - file: ./src/tests/nal/test_simple_deduction.nal + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_simple_deduction.nal } // 禁止用户输入 userInput: false From e7aa9ccdcd360e33dbec92e401f6fafe2704c6ca Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:19:30 +0800 Subject: [PATCH 50/59] =?UTF-8?q?refactor:=20:recycle:=20=E5=86=85?= =?UTF-8?q?=E7=BD=AECIN=E8=BF=90=E8=A1=8C=E6=97=B6=EF=BC=8C=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=B7=AF=E5=BE=84=E7=B3=BB=E7=BB=9F=E4=B8=8E=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E5=B9=B6=E5=AE=8C=E5=96=84NAL=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 🏗️将OpenNARS、ONA、PyNARS、NARS-Python、OpenJunars与CXinNARS运行时内置到与`src`同级的`executables`中,但在`.gitignore`中忽略 2. 📌CLI的「启动配置」中的相对路径,现在在读取时就基于「配置文件自身的路径」被「绝对化」——鼓励将CIN可执行文件、配置文件、预引入NAL放在一起(或使用绝对路径) 3. ♻️重命名大多数配置文件,并提取「预置NAL测试」的逻辑 4. ✅编写并运行多个示例用NAL测试;新增如下NAL测试:高阶演绎(NAL-5)、自变量消除(NAL-6)、时间归纳(NAL-7)、简单操作(NAL-8) --- .gitignore | 1 + .vscode/settings.json | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/arg_parse.rs | 41 +-- src/bin/babelnar_cli/config_launcher.rs | 10 +- src/bin/babelnar_cli/main.rs | 236 +++++++++++++++--- src/bin/babelnar_cli/runtime_manage.rs | 22 +- src/bin/babelnar_cli/vm_config.rs | 125 ++++++---- src/bin/babelnar_cli/websocket_server.rs | 2 + src/bin/cin_launcher/main.rs | 18 +- src/cin_implements/common/exe.rs | 2 +- src/cin_implements/nars_python/mod.rs | 4 +- src/cin_implements/ona/mod.rs | 9 +- src/cin_implements/openjunars/mod.rs | 19 +- src/cin_implements/opennars/mod.rs | 10 +- src/cin_implements/pynars/mod.rs | 13 +- src/lib.rs | 99 ++++++++ src/process_io/io_process.rs | 7 +- src/runtimes/command_vm/runtime.rs | 23 +- .../cli/config/_arg_parse_test.opennars.hjson | 23 ++ src/tests/cli/config/cxin_js.hjson | 20 ++ .../cli/config/nal_higher_deduction.hjson | 10 + .../cli/config/nal_i_var_elimination.hjson | 10 + src/tests/cli/config/nal_operation.hjson | 10 + .../cli/config/nal_simple_deduction.hjson | 10 + .../cli/config/nal_simple_operation.hjson | 10 + .../cli/config/nal_temporal_induction.hjson | 10 + src/tests/cli/config/ona.hjson | 21 ++ src/tests/cli/config/opennars.hjson | 7 +- ...ude_operation.hjson => prelude_test.hjson} | 8 +- src/tests/cli/config/pynars.hjson | 11 +- src/tests/cli/config/test_ona.hjson | 18 -- .../test_prelude_simple_deduction.hjson | 16 -- src/tests/nal/test_higher_deduction.nal | 35 +++ src/tests/nal/test_i_var_elimination.nal | 28 +++ src/tests/nal/test_operation.nal | 19 +- src/tests/nal/test_simple_deduction.nal | 11 +- src/tests/nal/test_simple_operation.nal | 37 +++ src/tests/nal/test_temporal_induction.nal | 40 +++ 40 files changed, 781 insertions(+), 219 deletions(-) create mode 100644 src/tests/cli/config/_arg_parse_test.opennars.hjson create mode 100644 src/tests/cli/config/cxin_js.hjson create mode 100644 src/tests/cli/config/nal_higher_deduction.hjson create mode 100644 src/tests/cli/config/nal_i_var_elimination.hjson create mode 100644 src/tests/cli/config/nal_operation.hjson create mode 100644 src/tests/cli/config/nal_simple_deduction.hjson create mode 100644 src/tests/cli/config/nal_simple_operation.hjson create mode 100644 src/tests/cli/config/nal_temporal_induction.hjson create mode 100644 src/tests/cli/config/ona.hjson rename src/tests/cli/config/{test_prelude_operation.hjson => prelude_test.hjson} (54%) delete mode 100644 src/tests/cli/config/test_ona.hjson delete mode 100644 src/tests/cli/config/test_prelude_simple_deduction.hjson create mode 100644 src/tests/nal/test_higher_deduction.nal create mode 100644 src/tests/nal/test_i_var_elimination.nal create mode 100644 src/tests/nal/test_simple_operation.nal create mode 100644 src/tests/nal/test_temporal_induction.nal diff --git a/.gitignore b/.gitignore index 90602f6..d0d12c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target # 可执行文件 +# * 🚩【2024-04-07 08:42:13】目前内置被忽略的CIN可执行文件,用于稳定的测试代码编写 executables/ # 临时文件 diff --git a/.vscode/settings.json b/.vscode/settings.json index 0f55514..f531c97 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "editor.formatOnSave": true, "cSpell.words": [ "Addrs", + "canonicalize", "confy", "cxin", "deser", diff --git a/Cargo.lock b/Cargo.lock index ae7ec2a..8a80b94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.15.3" +version = "0.16.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index e02884f..4332dca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.15.3" +version = "0.16.0" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/bin/babelnar_cli/arg_parse.rs index 24591cf..2270b28 100644 --- a/src/bin/babelnar_cli/arg_parse.rs +++ b/src/bin/babelnar_cli/arg_parse.rs @@ -113,13 +113,13 @@ pub fn load_config(args: &CliArgs) -> LaunchConfig { #[cfg(test)] mod tests { use super::*; + use babel_nar::tests::*; use nar_dev_utils::fail_tests; /// 测试/参数解析 mod arg_parse { - use crate::vm_config::tests::test_config_paths::TEST_OPENNARS; - use super::*; + use config_paths::*; fn _test_arg_parse(args: &[&str], expected: &CliArgs) { // ! 📝此处必须前缀一个「自身程序名」 @@ -160,9 +160,9 @@ mod tests { #[test] fn test_arg_parse() { test_arg_parse! { - ["-c", TEST_OPENNARS] + ["-c", ARG_PARSE_TEST] => CliArgs { - config: vec![TEST_OPENNARS.into()], + config: vec![ARG_PARSE_TEST.into()], ..Default::default() }; // 多个配置:重复使用`-c`/`--config`,按使用顺序填充 @@ -192,11 +192,10 @@ mod tests { /// 测试/加载配置 mod read_config { use super::*; - use crate::vm_config::tests::test_config_paths::TEST_OPENNARS; - use crate::vm_config::tests::test_config_paths::TEST_PRELUDE_SIMPLE_DEDUCTION; - use crate::vm_config::tests::test_config_paths::TEST_WEBSOCKET; use crate::vm_config::*; use crate::LaunchConfigWebsocket; + use config_paths::*; + use nar_dev_utils::manipulate; /// 测试/加载配置 fn load(args: &[&str]) -> LaunchConfig { @@ -221,10 +220,17 @@ mod tests { /// 测试 #[test] fn test() { + let expected_current_dir = manipulate!( + current_dir().unwrap() + => .push("src") + => .push("tests") + => .push("cli") + => .push("executables") + ); // 成功测试 test! { // 单个配置文件 - ["-c" TEST_OPENNARS "-d"] => LaunchConfig { + ["-c" ARG_PARSE_TEST "-d"] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( "opennars".into(), @@ -237,11 +243,11 @@ mod tests { "-jar".into(), "nars.jar".into() ]), - current_dir: Some("root/nars/test".into()), + current_dir: Some(expected_current_dir.clone()), }), ..Default::default() }; - ["-c" TEST_WEBSOCKET "-d"] => LaunchConfig { + ["-c" WEBSOCKET "-d"] => LaunchConfig { websocket: Some(LaunchConfigWebsocket { host: "localhost".into(), port: 8080, @@ -251,8 +257,8 @@ mod tests { // 两个配置文件合并 [ "-d" - "-c" TEST_OPENNARS - "-c" TEST_WEBSOCKET + "-c" ARG_PARSE_TEST + "-c" WEBSOCKET ] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( @@ -266,7 +272,7 @@ mod tests { "-jar".into(), "nars.jar".into() ]), - current_dir: Some("root/nars/test".into()), + current_dir: Some(expected_current_dir.clone()), }), websocket: Some(LaunchConfigWebsocket { host: "localhost".into(), @@ -277,9 +283,9 @@ mod tests { // 三个配置文件合并 [ "-d" - "-c" TEST_OPENNARS - "-c" TEST_WEBSOCKET - "-c" TEST_PRELUDE_SIMPLE_DEDUCTION + "-c" ARG_PARSE_TEST + "-c" WEBSOCKET + "-c" PRELUDE_TEST ] => LaunchConfig { translators: Some( LaunchConfigTranslators::Same( @@ -293,13 +299,12 @@ mod tests { "-jar".into(), "nars.jar".into() ]), - current_dir: Some("root/nars/test".into()), + current_dir: Some(expected_current_dir.clone()), }), websocket: Some(LaunchConfigWebsocket { host: "localhost".into(), port: 8080, }), - prelude_nal: Some(LaunchConfigPreludeNAL::File("./src/tests/nal/test_simple_deduction.nal".into())), user_input: Some(false), auto_restart: Some(false), strict_mode: Some(true), diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 9d2f65a..5b0a53a 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -68,6 +68,11 @@ pub fn launch_by_config( // 转换启动配置 let config: RuntimeConfig = config.try_into()?; + // * 🚩【2024-04-07 10:13:51】目前通过「设置exe工作路径」切换到启动环境中 + if let Some(path) = &config.command.current_dir { + std::env::set_current_dir(path)?; + } + // 生成虚拟机 let runtime = launch_by_runtime_config(&config)?; @@ -95,7 +100,10 @@ pub fn load_command_vm(config: &LaunchConfigCommand) -> Result { // 构造指令 let command = generate_command( &config.cmd, - config.current_dir.as_ref(), + // ! 🚩【2024-04-07 12:35:41】不能再设置工作目录:已在[`launch_by_config`]处设置 + // * 否则会导致「目录名称无效」 + // config.current_dir.as_ref(), + None::<&str>, // 🚩获取其内部数组的引用,或使用一个空数组作迭代器(无法简化成[`unwrap_or`]) match &config.cmd_args { Some(v) => v.iter(), diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index b4d990c..c663ae6 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -79,71 +79,227 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> /// 单元测试 #[cfg(test)] mod tests { - use self::vm_config::tests::test_config_paths::{ - TEST_ONA, TEST_PRELUDE_OPERATION, TEST_PRELUDE_SIMPLE_DEDUCTION, TEST_WEBSOCKET, - }; use super::*; + use babel_nar::tests::config_paths::*; + use nar_dev_utils::list; - /// 测试入口/ONA/交互shell - /// * 🎯正常BabelNAR CLI shell启动 - /// * 🎯正常用户命令行交互体验 - /// * ⚠️使用与项目无关的路径,以定位启动CIN - #[test] - pub fn main_ona_shell() -> Result<()> { + /// 测试入口/ONA + /// * 🎯通用、可复用的启动代码 + /// * 🎯跨不同CIN通用 + /// * 🎯跨同CIN不同测试通用 + pub fn main(cin_config_path: &str, other_args: &[&str]) -> Result<()> { + babel_nar::exists_or_exit!("./executables"); // 以默认参数启动 main_args( env::current_dir(), - ["test.exe", "-d", "-c", TEST_ONA] - .into_iter() - .map(str::to_string), + [ + &["BabelNAR-cli.exe", "-d", "-c", cin_config_path], + other_args, + ] + .concat() + .into_iter() + .map(str::to_string), ) } - /// 测试入口/ONA/预加载NAL + /// 测试入口/多配置加载 /// * 🎯多「虚拟机启动配置」合并 /// * 🎯预引入NAL - /// * ⚠️使用与项目无关的路径,以定位启动CIN - pub fn main_ona_prelude(prelude_config_path: &str) -> Result<()> { - // 以默认参数启动 - main_args( - env::current_dir(), + pub fn main_configs(cin_config_path: &str, other_config_paths: &[&str]) -> Result<()> { + let args = list![ [ - "test.exe", - "-d", - // 第一个文件,指示ONA + // 第二个文件,搭建测试环境 "-c", - TEST_ONA, - // 第二个文件,指示预加载 + config_path, + // 第三个文件,指示预加载 "-c", - prelude_config_path, + config_path, ] - .into_iter() - .map(str::to_string), - ) + for config_path in (other_config_paths) + ] + .concat(); + main(cin_config_path, &args) } - /// 测试/预加载NAL:简单演绎推理 - #[test] - pub fn test_ona_prelude_de() -> Result<()> { - main_ona_prelude(TEST_PRELUDE_SIMPLE_DEDUCTION) + /// 批量生成「预引入NAL」 + macro_rules! cin_tests { + ( + $cin_path:expr; + $( + $(#[$attr:meta])* + $name:ident => $config_path:expr $(;)? + )* + ) => { + /// 主Shell + /// * 🎯正常BabelNAR CLI shell启动 + /// * 🎯正常用户命令行交互体验 + #[test] + pub fn main_shell() -> Result<()> { + main($cin_path, &[]) + } + + $( + $(#[$attr])* + #[test] + pub fn $name() -> Result<()> { + main_configs($cin_path, &[PRELUDE_TEST, $config_path]) + } + )* + }; } - /// 测试/预加载NAL:操作 - #[test] - pub fn test_ona_prelude_op() -> Result<()> { - main_ona_prelude(TEST_PRELUDE_OPERATION) + /// 测试/ONA + mod ona { + use super::*; + + cin_tests! { + ONA; + + /// 简单演绎 + /// * 📝✅【2024-04-07 14:56:04】成功 + nal_de => NAL_SIMPLE_DEDUCTION + + /// 高阶演绎 + /// * 📝✅【2024-04-07 14:56:04】成功 + nal_hi => NAL_HIGHER_DEDUCTION + + /// 自变量消除 + /// * 📝✅【2024-04-07 16:03:47】成功 + nal_ie => NAL_I_VAR_ELIMINATION + + /// 时间归纳 + /// * 📝✅【2024-04-07 15:22:28】成功 + nal_te => NAL_TEMPORAL_INDUCTION + + /// 简单操作 + /// * 📝❌【2024-04-07 16:15:53】失败:推理不出任何内容 + nal_so => NAL_SIMPLE_OPERATION + + /// 操作 + /// * 📝✅【2024-04-07 14:57:50】成功,但少许问题 + /// * 📝【2024-04-07 14:17:21】目前ONA面对其中的「经验问句」没有回答 + /// * ⚠️在启用`REG left`注册操作后,反而从成功变为失败 + nal_op => NAL_OPERATION + } + } + + /// 测试/OpenNARS + mod opennars { + use super::*; + + cin_tests! { + OPENNARS; + + /// 简单演绎 + /// * 📝✅【2024-04-07 14:59:37】成功 + nal_de => NAL_SIMPLE_DEDUCTION + + /// 高阶演绎 + /// * 📝✅【2024-04-07 14:59:44】成功 + nal_hi => NAL_HIGHER_DEDUCTION + + /// 自变量消除 + /// * 📝✅【2024-04-07 16:01:15】成功 + nal_ie => NAL_I_VAR_ELIMINATION + + /// 时间归纳 + /// * 📝✅【2024-04-07 15:22:28】成功 + nal_te => NAL_TEMPORAL_INDUCTION + + /// 简单操作 + /// * 📝✅【2024-04-07 16:13:39】成功 + nal_so => NAL_SIMPLE_OPERATION + + /// 操作 + /// * 📝✅【2024-04-07 14:59:53】成功 + nal_op => NAL_OPERATION + } } - /// 测试入口/ONA/交互shell + /// 测试/PyNARS + mod pynars { + use super::*; + + cin_tests! { + PYNARS; + + /// 简单演绎 + nal_de => NAL_SIMPLE_DEDUCTION + + /// 高阶演绎 + nal_hi => NAL_HIGHER_DEDUCTION + + /// 自变量消除 + /// * 📝❌【2024-04-07 16:01:15】失败:啥推理都没有 + nal_ie => NAL_I_VAR_ELIMINATION + + /// 时间归纳 + /// * 📝❌【2024-04-07 16:13:52】失败:只会回答`D>. :\: %1.000;0.900%` + nal_te => NAL_TEMPORAL_INDUCTION + + /// 简单操作 + /// * 📝❌【2024-04-07 16:13:42】失败:没有任何回答 + nal_so => NAL_SIMPLE_OPERATION + + /// 操作 + /// * 📝❌【2024-04-07 14:39:49】目前仍测试失败 + /// * 📌PyNARS自身对NAL-7、NAL-8支持尚不完善 + /// * 📌PyNARS中操作`left`并非默认已注册 + /// * ❌【2024-04-07 14:41:54】补充:追加了也不行 + nal_op => NAL_OPERATION + } + } + + /// 测试/CXinJS + mod cxin_js { + use super::*; + + cin_tests! { + CXIN_JS; + + /// 简单演绎 + /// * 📝❌【2024-04-07 14:37:49】失败:导出了结论,但没法回答 + nal_de => NAL_SIMPLE_DEDUCTION + + /// 高阶演绎 + /// * 📝❌【2024-04-07 14:37:49】失败:只能导出到`B>?` + /// * 📌即便是五百步,也推不出来 + nal_hi => NAL_HIGHER_DEDUCTION + + /// 自变量消除 + /// * 📝❌【2024-04-07 16:01:15】失败:仅推理到`C>?`,并且遇到「XXX is not a function」错误 + nal_ie => NAL_I_VAR_ELIMINATION + + /// 时间归纳 + /// * 📝❌失败:解析即报错——不支持`=/>` + nal_te => NAL_TEMPORAL_INDUCTION + + /// 简单操作 + /// * 📝❌【2024-04-07 16:16:24】失败:推理不出任何内容 + /// * 💭还会把「目标」解析成「判断」…… + nal_so => NAL_SIMPLE_OPERATION + + /// 操作 + /// * 📝❌目前仍测试失败 + /// * 📌PyNARS自身对NAL-7、NAL-8支持尚不完善 + /// * 📌PyNARS中操作`left`并非默认已注册 + /// * 📝❌【2024-04-07 14:37:49】失败:自身就不支持 + nal_op => NAL_OPERATION + } + } + + // ! ❌【2024-04-07 14:39:20】接口完成度不高的NARS-Python、OpenJunars暂不进行测试 + + /// 测试入口/带Websocket Shell /// * 🎯正常BabelNAR CLI shell启动 - /// * 🎯正常用户命令行交互体验 - /// * ⚠️使用与项目无关的路径,以定位启动CIN + /// * 🎯用户命令行交互体验(并存) + /// * 🎯Websocket通信 #[test] - pub fn main_ona_websocket() -> Result<()> { + pub fn main_websocket() -> Result<()> { // 以默认参数启动 main_args( env::current_dir(), - ["test.exe", "-d", "-c", TEST_ONA, "-c", TEST_WEBSOCKET] + ["test.exe", "-d", "-c", ONA, "-c", WEBSOCKET] .into_iter() .map(str::to_string), ) diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 1a58482..6bc981b 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -12,6 +12,7 @@ use babel_nar::{ }, }, eprintln_cli, println_cli, + runtimes::TranslateError, test_tools::{nal_format::parse, put_nal, VmOutputCache}, }; use nar_dev_utils::{if_return, ResultBoost}; @@ -164,10 +165,15 @@ where &mut *try_break!(OutputCache::unlock_arc_mutex(&mut self.output_cache)); // 读取内容 - // TODO: 从「配置文件所在路径」开始 let nal = match prelude_nal { // 文件⇒尝试读取文件内容 | ⚠️此处创建了一个新值,所以要统一成`String` - LaunchConfigPreludeNAL::File(path) => try_break!(std::fs::read_to_string(path)), + LaunchConfigPreludeNAL::File(path) => { + try_break!(std::fs::read_to_string(path) => e { + println_cli!([Error] "读取预置NAL文件 {path:?} 发生错误:{e}"); + // 继续(用户输入/Websocket服务端) + e.into() + }) + } // 纯文本⇒直接引入 LaunchConfigPreludeNAL::Text(nal) => nal.to_string(), }; @@ -351,8 +357,16 @@ where if let Err(e) = put_result { // 无论是否严格模式,都报告错误 eprintln_cli!([Error] "置入NAL输入「{nal:?}」时发生错误:{e}"); - // 严格模式下提前返回 - if_return! { config.strict_mode => Err(e) } + // 严格模式下考虑上报错误 + if config.strict_mode { + match e.downcast_ref::() { + // * 🚩在「不支持的指令」时仅警告 + // * 🎯**兼容尽可能多的CIN版本** + Some(TranslateError::UnsupportedInput(..)) => {} + // * 🚩在「其他错误」时直接返回 + _ => return Err(e), + } + } } } } diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index 306e0d1..79a25eb 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -63,11 +63,11 @@ use anyhow::{anyhow, Result}; use babel_nar::println_cli; -use nar_dev_utils::{if_return, manipulate, pipe, OptionBoost, ResultBoost}; +use nar_dev_utils::{if_return, pipe, OptionBoost, ResultBoost}; use serde::{Deserialize, Serialize}; use std::{ fs::read_to_string, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, }; /// 工具宏/批量拷贝性合并 @@ -307,6 +307,8 @@ pub struct LaunchConfigCommand { /// 工作目录(可选) /// * 🎯可用于Python模块 + /// * 🚩【2024-04-07 10:13:59】现在用于「基于配置文件的相对路径」 + /// * 📌被主程序在启动时用于「设置自身工作目录」 pub current_dir: Option, } @@ -383,22 +385,73 @@ impl LaunchConfig { // ! 预加载NAL为空⇒不预加载NAL } + /// 变基一个相对路径 + /// * 🚩将`config_path`的路径作为自身[`Path::is_relative`]的根路径 + /// * 📌引入[`Path::canonicalize`]解决「`path/test/../a` => `path/a`」的问题 + /// * 📌总是将相对路径(按照以`config_path`为根路径)展开成绝对路径 + #[inline(always)] + pub fn rebase_relative_path(config_path: &Path, relative_path: &mut PathBuf) -> Result<()> { + // 若`relative_path`非相对路径,直接返回 + if_return! { relative_path.is_absolute() => Ok(()) } + // 先绝对化「配置根路径」 + let mut new_path = config_path.canonicalize()?; + // 遍历「相对路径」的组分,追加/上溯路径 + for component in relative_path.components() { + match component { + // 当前文件夹⇒跳过 + Component::CurDir => continue, + // 上一级文件夹⇒上溯 + Component::ParentDir => { + new_path.pop(); + } + // 其它⇒增加组分 + _ => new_path.push(component), + } + } + + // * ❌无法通过真正治本的「前缀替换」行事:[`PrefixComponent`]全为私有字段,无法构建⇒无法构建`Component` + // let new_path = new_path + // .components() + // .map(|com| match com { + // Component::Prefix(prefix) => { + // let prefix = match prefix.kind() { + // Prefix::VerbatimUNC(a, b) => Prefix::UNC(a, b), + // Prefix::VerbatimDisk(name) => Prefix::Disk(name), + // kind => kind, + // }; + // Component::from(prefix) + // } + // _ => com, + // }) + // .collect::(); + + // 转换回字符串,然后删除`canonicalize`产生的多余前缀 + // * ⚠️【2024-04-07 13:51:16】删除原因:JVM、Python等启动命令不能处理带`\\?\【盘符】:`、`\\.\【盘符】:`前缀的路径 + // * 📌即便其实际上为「Verbatim UNC prefixes」 + // * 🔗参考: + // * 🔗参考: + // 先转换为字符串 + if let Some(path) = new_path.to_str() { + new_path = path + // 删去无用前缀 + .trim_start_matches(r"\\?\") + .trim_start_matches(r"\\.\") + // 转换回路径 + .into(); + } + // 赋值 + *relative_path = new_path; + Ok(()) + } + /// 变基配置中所含的路径,从其它地方变为 /// * 🎯解决「配置中的**相对路径**仅相对于exe而非配置文件本身」的问题 /// * 🎯将配置中相对路径的**根目录**从「exe」变更到配置文件本身 - /// * 🚩将`config_path`的路径追加到自身[`Path::is_relative`]的路径中,使之以`config_path`为根路径 - pub fn rebase_path_from(&mut self, config_path: &Path) { - /// 变基一个相对路径 - /// * 🚩是相对路径⇒尝试变基 - #[inline(always)] - fn rebase_one(config_path: &Path, relative_path: &mut PathBuf) { - if relative_path.is_relative() { - *relative_path = config_path.join(&relative_path); - } - } + /// * 📌原则:由此消灭所有相对路径,均以「配置文件自身路径」为根,转换为绝对路径 + pub fn rebase_relative_path_from(&mut self, config_path: &Path) -> Result<()> { // 预加载NAL if let Some(LaunchConfigPreludeNAL::File(ref mut path)) = &mut self.prelude_nal { - rebase_one(config_path, path); + Self::rebase_relative_path(config_path, path)?; } // 启动命令 if let Some(LaunchConfigCommand { @@ -406,13 +459,18 @@ impl LaunchConfig { .. }) = &mut self.command { - rebase_one(config_path, path); + Self::rebase_relative_path(config_path, path)?; } + // 返回成功 + Ok(()) } - /// 变基路径,但基于所有权[`Self`]→[`Self`] - pub fn rebase_path_from_owned(self, config_path: &Path) -> Self { - manipulate!( self => .rebase_path_from(config_path) ) + /// 变基路径,但基于所有权 + /// * 📌总体逻辑:[`Self`]→[`Self`] + /// * ⚠️有可能会出错(引入[`Path::canonicalize`]) + pub fn rebase_path_from_owned(mut self, config_path: &Path) -> Result { + self.rebase_relative_path_from(config_path)?; + Ok(self) } /// 从另一个配置中并入配置 @@ -529,6 +587,7 @@ pub fn read_config_extern(path: &Path) -> Result { => {?}# // 变基相对路径,从「基于CLI自身」到「基于配置文件自身」 => .rebase_path_from_owned(path.parent().ok_or(anyhow!("无效的根路径!"))?) + => {?}# // 返回Ok(转换为`anyhow::Result`) => Ok } @@ -561,33 +620,8 @@ pub fn try_complete_path(path: &Path) -> PathBuf { pub mod tests { use super::*; use anyhow::Result; - - /// 测试用文件路径 - /// * 🎯后续其它地方统一使用该处路径 - /// * 📌相对路径の根目录:项目根目录(`Cargo.toml`所在目录) - /// * ⚠️只与配置文件路径有关,不与CIN位置有关 - /// * 💭后续若在不同工作环境中,需要调整配置文件中有关「CIN位置」的信息 - pub mod test_config_paths { - #![allow(unused)] - /// 测试OpenNARS - pub const TEST_OPENNARS: &str = "./src/tests/cli/config/opennars.hjson"; - /// 测试ONA - pub const TEST_ONA: &str = "./src/tests/cli/config/test_ona.hjson"; - /// PyNARS - pub const PYNARS: &str = "./src/tests/cli/config/pynars.hjson"; - /// 预引入/简单演绎推理 - pub const TEST_PRELUDE_SIMPLE_DEDUCTION: &str = - "./src/tests/cli/config/test_prelude_simple_deduction.hjson"; - /// 预引入/操作 - pub const TEST_PRELUDE_OPERATION: &str = - "./src/tests/cli/config/test_prelude_operation.hjson"; - /// 预引入/Websocket - pub const TEST_WEBSOCKET: &str = "./src/tests/cli/config/websocket.hjson"; - /// 预引入/Matriangle服务器 - pub const TEST_MATRIANGLE_SERVER: &str = "./src/tests/cli/config/matriangle_server.hjson"; - } + use babel_nar::tests::*; use nar_dev_utils::asserts; - use test_config_paths::*; /// 实用测试宏 macro_rules! test_parse { @@ -695,12 +729,13 @@ pub mod tests { /// 测试/读取 /// * 🎯相对**配置文件**的路径表示 + /// * 🎯被重定向到`./executables`,以便启动其下的`.jar`文件 #[test] fn test_read() { // 使用OpenNARS配置文件的路径作测试 - let path: PathBuf = TEST_OPENNARS.into(); + let path: PathBuf = config_paths::OPENNARS.into(); let launch_config = read_config_extern(&path).expect("路径读取失败"); - let expected_path = "./src/tests/cli/config/root/nars/test".into(); + let expected_path = "./executables".into(); asserts! { // * 🎯启动命令中的「当前目录」应该被追加到配置自身的路径上 // * ✅即便拼接后路径是`"./src/tests/cli/config\\root/nars/test"`,也和上边的路径相等 diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs index cdfd809..5e60760 100644 --- a/src/bin/babelnar_cli/websocket_server.rs +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -210,3 +210,5 @@ where eprintln_cli!([Error] "与id为 {} 的客户端断开连接!", handler.id); } } + +// TODO: ❓【2024-04-07 12:42:51】单元测试不好做:网络连接难以被模拟 diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs index 4f1db43..d45516c 100644 --- a/src/bin/cin_launcher/main.rs +++ b/src/bin/cin_launcher/main.rs @@ -15,6 +15,7 @@ use babel_nar::{ cin_implements::{ona::ONA, opennars::OpenNARS, pynars::PyNARS}, eprintln_cli, println_cli, runtimes::CommandVmRuntime, + tests::cin_paths::{ONA, OPENNARS, PYNARS_ROOT}, }; use nar_dev_utils::*; use navm::{ @@ -24,9 +25,9 @@ use navm::{ }; use std::{fmt::Debug, io::stdin}; -const TEST_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; -const TEST_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; -const TEST_PATH_PYNARS: (&str, &str) = ("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); +const TEST_PATH_OPENNARS: &str = OPENNARS; +const TEST_PATH_ONA: &str = ONA; +const TEST_PATH_PYNARS: (&str, &str) = (PYNARS_ROOT, "pynars.ConsolePlus"); /// 启动并获取NARS /// * 🚩【2024-03-27 18:55:07】目前就返回一个测试用的运行时 @@ -109,17 +110,6 @@ mod tests { use navm::cmd::Cmd; use navm::vm::VmLauncher; - #[test] - fn test_20240328() { - // let (test1, test2) = generate_test_cmds(); - // // let nars = CXinJS::new(r"..\cxin-nars-py-to-ts\src\cxin-nars-shell.js"); - // // let nars = OpenNARS::new(r"..\..\NARS-executables\opennars-304-T-modified.jar"); - // let nars = ONA::new("..\\..\\NARS-executables\\NAR.exe"); - // // let nars = PyNARS::new("..\\..\\PyNARS-dev", "pynars.ConsolePlus"); - // std::thread::sleep(std::time::Duration::from_secs(1)); - // test_set(nars.launch(), test1); - } - fn test_set(mut nars: impl VmRuntime, test_set: Vec) { for cmd in test_set { nars.input_cmd(cmd); diff --git a/src/cin_implements/common/exe.rs b/src/cin_implements/common/exe.rs index 1c73b4d..9cbb427 100644 --- a/src/cin_implements/common/exe.rs +++ b/src/cin_implements/common/exe.rs @@ -17,7 +17,7 @@ use std::{ffi::OsStr, path::Path, process::Command}; pub fn generate_command( exe_path: impl AsRef, current_dir: Option>, - args: impl Iterator, + args: impl IntoIterator, ) -> Command where S: AsRef, diff --git a/src/cin_implements/nars_python/mod.rs b/src/cin_implements/nars_python/mod.rs index bf8e33f..469211c 100644 --- a/src/cin_implements/nars_python/mod.rs +++ b/src/cin_implements/nars_python/mod.rs @@ -16,7 +16,7 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtimes::{tests::EXE_PATH_NARS_PYTHON, CommandVmRuntime}; + use crate::{runtimes::CommandVmRuntime, tests::cin_paths::NARS_PYTHON}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, @@ -26,7 +26,7 @@ mod tests { #[test] fn test() { // 从别的地方获取exe路径 - let exe_path = EXE_PATH_NARS_PYTHON; + let exe_path = NARS_PYTHON; // 一行代码启动NARS-Python let vm = NARSPython::new(exe_path).launch().expect("无法启动虚拟机"); // 运行专有测试 diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs index b86f0f3..67259d5 100644 --- a/src/cin_implements/ona/mod.rs +++ b/src/cin_implements/ona/mod.rs @@ -16,9 +16,12 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtimes::{ - tests::{_test_ona, test_simple_answer, EXE_PATH_ONA}, - CommandVmRuntime, + use crate::{ + runtimes::{ + tests::{_test_ona, test_simple_answer}, + CommandVmRuntime, + }, + tests::cin_paths::ONA as EXE_PATH_ONA, }; use navm::vm::VmLauncher; diff --git a/src/cin_implements/openjunars/mod.rs b/src/cin_implements/openjunars/mod.rs index aed3e5c..f9fad07 100644 --- a/src/cin_implements/openjunars/mod.rs +++ b/src/cin_implements/openjunars/mod.rs @@ -16,7 +16,7 @@ mod tests { #![allow(unused)] use super::*; - use crate::runtimes::{tests::JL_PATH_OPEN_JUNARS, CommandVmRuntime}; + use crate::{runtimes::CommandVmRuntime, tests::cin_paths::OPENJUNARS}; use narsese::conversion::string::impl_lexical::shortcuts::*; use navm::{ cmd::Cmd, @@ -26,12 +26,11 @@ mod tests { #[test] fn test() { // 从别的地方获取jl路径 - let jl_path = JL_PATH_OPEN_JUNARS; + let jl_path = OPENJUNARS; // 一行代码启动OpenJunars let vm = OpenJunars::new(jl_path).launch().expect("无法启动虚拟机"); // 运行专有测试 // ! ❌【2024-03-25 13:56:21】目前无法截取到Julia运行时输出,弃用 - // _test_opennars(vm) _test_open_junars(vm) } @@ -39,15 +38,15 @@ mod tests { pub(crate) fn _test_open_junars(mut vm: CommandVmRuntime) { // ! ❌【2024-03-25 13:55:57】无效:似乎无法截取到Julia运行时输出 - // vm.input_cmd(Cmd::NSE(nse_task!( B>.))) - // .expect("无法输入指令"); + vm.input_cmd(Cmd::NSE(nse_task!( B>.))) + .expect("无法输入指令"); - // // 等待四秒钟,让Junars启动 - // std::thread::sleep(std::time::Duration::from_secs(1)); + // 等待四秒钟,让Junars启动 + std::thread::sleep(std::time::Duration::from_secs(1)); - // vm.input_cmd(Cmd::NSE(nse_task!( B>.))) - // .expect("无法输入指令"); - // std::thread::sleep(std::time::Duration::from_secs(6)); + vm.input_cmd(Cmd::NSE(nse_task!( B>.))) + .expect("无法输入指令"); + std::thread::sleep(std::time::Duration::from_secs(6)); // 终止虚拟机运行时 vm.terminate().expect("无法终止虚拟机"); diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs index 47e3b74..dd2d9b1 100644 --- a/src/cin_implements/opennars/mod.rs +++ b/src/cin_implements/opennars/mod.rs @@ -16,9 +16,12 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtimes::{ - tests::{_test_opennars, test_simple_answer, JAR_PATH_OPENNARS}, - CommandVmRuntime, + use crate::{ + runtimes::{ + tests::{_test_opennars, test_simple_answer}, + CommandVmRuntime, + }, + tests::cin_paths::OPENNARS as JAR_PATH_OPENNARS, }; use navm::vm::VmLauncher; @@ -30,6 +33,7 @@ mod tests { OpenNARS::new(jar_path).launch().expect("无法启动虚拟机") } + /// 测试 #[test] fn test() { // 启动OpenNARS虚拟机 diff --git a/src/cin_implements/pynars/mod.rs b/src/cin_implements/pynars/mod.rs index a58d252..e51cac7 100644 --- a/src/cin_implements/pynars/mod.rs +++ b/src/cin_implements/pynars/mod.rs @@ -18,17 +18,20 @@ util::mod_and_pub_use! { #[cfg(test)] mod tests { use super::*; - use crate::runtimes::{ - tests::{_test_pynars, test_simple_answer, MODULE_PATH_PYNARS, MODULE_ROOT_PYNARS}, - CommandVmRuntime, + use crate::{ + runtimes::{ + tests::{_test_pynars, test_simple_answer}, + CommandVmRuntime, + }, + tests::cin_paths::{PYNARS_MODULE, PYNARS_ROOT}, }; use navm::vm::VmLauncher; /// 工具/启动PyNARS,获得虚拟机运行时 fn launch_vm() -> CommandVmRuntime { // 从别的地方获取Python模块根目录、模块自身路径 - let root_path = MODULE_ROOT_PYNARS; - let module_path = MODULE_PATH_PYNARS; + let root_path = PYNARS_ROOT; + let module_path = PYNARS_MODULE; // 一行代码启动PyNARS | `python -m pynars.Console` @ "..\..\PyNARS-dev" PyNARS::new(root_path, module_path) .launch() diff --git a/src/lib.rs b/src/lib.rs index 400c41c..cd0736e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,3 +29,102 @@ util::mods! { // 测试工具集 "test_tools" => pub test_tools; } + +/// 单元测试 +/// * 🎯为下属单元测试提供测试支持 +/// * 📄测试用配置文件的名称及路径 +/// * 📄各测试用CIN的内部路径(`executables`) +/// * ❌【2024-04-07 08:50:07】已知问题:不同crate的`[cfg(test)]`代码无法互通 +/// * 🚩【2024-04-07 08:52:36】当下解决方案:禁用`#[cfg(test)]` +/// * 📌以**十数个常量**的编译成本,换得**更方便的测试可维护性**(无需复制代码) +// #[cfg(test)] +pub mod tests { + #![allow(unused_variables)] + + /// 实用宏/简化字符串常量 + macro_rules! str_const { + ($( + $(#[$m:meta])* + $name:ident = $value:literal $(;)? + )*) => {$( + $(#[$m])* + pub const $name: &str = $value; + )*}; + } + + /// 测试用配置文件路径 + /// * 🎯后续其它地方统一使用该处路径 + /// * 📌相对路径の根目录:项目根目录(`Cargo.toml`所在目录) + /// * ⚠️只与配置文件路径有关,不与CIN位置有关 + /// * 💭后续若在不同工作环境中,需要调整配置文件中有关「CIN位置」的信息 + /// * ⚠️此处所涉及的CIN不附带于源码中,而是**另行发布** + /// * ❗部分CIN涉及c + pub mod config_paths { + str_const! { + + /// 用于「启动参数解析」的测试环境 + ARG_PARSE_TEST = + "./src/tests/cli/config/_arg_parse_test.opennars.hjson" + + /// OpenNARS + OPENNARS = "./src/tests/cli/config/opennars.hjson" + /// ONA + ONA = "./src/tests/cli/config/ona.hjson" + /// PyNARS + PYNARS = "./src/tests/cli/config/pynars.hjson" + /// CXinJS + CXIN_JS = "./src/tests/cli/config/cxin_js.hjson" + + /// 预引入/NAL测试环境 + PRELUDE_TEST = "./src/tests/cli/config/prelude_test.hjson" + /// NAL/简单演绎 + NAL_SIMPLE_DEDUCTION = "./src/tests/cli/config/nal_simple_deduction.hjson" + /// NAL/高阶演绎 + NAL_HIGHER_DEDUCTION = "./src/tests/cli/config/nal_higher_deduction.hjson" + /// NAL/自变量消除 + NAL_I_VAR_ELIMINATION = "./src/tests/cli/config/nal_i_var_elimination.hjson" + /// NAL/时间归纳 + NAL_TEMPORAL_INDUCTION = "./src/tests/cli/config/nal_temporal_induction.hjson" + /// NAL/操作 + NAL_OPERATION = "./src/tests/cli/config/nal_operation.hjson" + /// NAL/简单操作 + NAL_SIMPLE_OPERATION = "./src/tests/cli/config/nal_simple_operation.hjson" + + /// Websocket + WEBSOCKET = "./src/tests/cli/config/websocket.hjson" + /// Matriangle服务器 + MATRIANGLE_SERVER = "./src/tests/cli/config/matriangle_server.hjson" + } + } + + /// 测试用CIN路径 + /// * 🎯后续其它地方统一使用该处路径 + /// * 🎯存储测试用的本地CIN + /// * ⚠️该处CIN被自动忽略,不附带于源码中,需要另外的运行时包以启动 + /// * 📌相对路径の根目录:项目根目录(`Cargo.toml`所在目录) + pub mod cin_paths { + str_const! { + OPENNARS = "./executables/opennars-304-T-modified.jar" + ONA = "./executables/ONA.exe" + PYNARS_ROOT = "./executables/PyNARS" + PYNARS_MODULE = "pynars.ConsolePlus" + NARS_PYTHON = "./executables/nars-python-main.exe" + CXIN_JS = "./executables/cxin-nars-shell.js" + OPENJUNARS = "./executables/OpenJunars/run.jl" + } + } + + /// 测试用宏/找不到路径即退出 + /// * 🚩输入一个`&str`,构建`&Path`并在其不存在时退出程序,或返回该`&Path`对象 + #[macro_export] + macro_rules! exists_or_exit { + ($path:expr) => {{ + let path = std::path::Path::new($path); + if !path.exists() { + println!("所需路径 {path:?} 不存在,已自动退出"); + std::process::exit(0) + } + path + }}; + } +} diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 65b596f..15c9c3a 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -495,16 +495,13 @@ impl IoProcessManager { /// 单元测试 #[cfg(test)] pub(crate) mod tests { - use super::*; + use crate::tests::cin_paths::ONA as PATH_ONA; use std::{ process::exit, sync::{Arc, Mutex}, }; - // 定义一系列路径 - const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - /// 测试工具/等待子进程输出,直到输出满足条件 pub fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { loop { @@ -522,7 +519,7 @@ pub(crate) mod tests { let outputs = Arc::new(Mutex::new(vec![])); let outputs_inner = outputs.clone(); // 从一个系统指令开始构建子进程 - let process = IoProcess::new(EXE_PATH_ONA) + let process = IoProcess::new(PATH_ONA) // 添加命令参数 .arg("shell") // 添加输出监听器 | 简单回显 diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index 125033d..dd7f509 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -125,7 +125,11 @@ impl VmLauncher for CommandVm { #[cfg(test)] pub mod tests { use super::*; - use crate::runtimes::TranslateError; + use crate::{ + cin_implements::common::generate_command, + runtimes::TranslateError, + tests::cin_paths::{OPENNARS, PYNARS_MODULE, PYNARS_ROOT}, + }; use nar_dev_utils::manipulate; use narsese::{ api::{GetBudget, GetPunctuation, GetStamp, GetTerm, GetTruth}, @@ -145,16 +149,7 @@ pub mod tests { use std::process::Command; use util::first; - // 定义一系列路径 - // * 📌【2024-03-25 09:28:36】本地调试:都从根目录`BabelNAR.rs`开始 - // * 📄退一级到开发目录,再退一级到各NARS下载目录 - pub const JAR_PATH_OPENNARS: &str = r"..\..\NARS-executables\opennars-304-T-modified.jar"; - pub const EXE_PATH_ONA: &str = r"..\..\NARS-executables\NAR.exe"; - pub const EXE_PATH_PYNARS: &str = r"..\..\NARS-executables\launch-pynars-console-plus.cmd"; - pub const MODULE_ROOT_PYNARS: &str = r"..\..\PyNARS-dev"; - pub const MODULE_PATH_PYNARS: &str = r"pynars.ConsolePlus"; - pub const EXE_PATH_NARS_PYTHON: &str = r"..\..\NARS-executables\main.exe"; - pub const JL_PATH_OPEN_JUNARS: &str = r"..\..\OpenJunars\launch.jl"; + // ! 🚩【2024-04-07 12:09:44】现在路径统一迁移到`lib.rs`的`tests`模块下 const COMMAND_JAVA: &str = "java"; const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; @@ -368,7 +363,7 @@ pub mod tests { // * 📝这里的`args`、`arg都返回的可变借用。。 command_java .args(COMMAND_ARGS_JAVA) - .arg(JAR_PATH_OPENNARS) + .arg(OPENNARS) // OpenNARS的默认参数 | ["null", "null", "null", "null"] // * 🔗https://github.com/opennars/opennars/blob/master/src/main/java/org/opennars/main/Shell.java // * ✅fixed「额外参数」问题:之前「IO进程」的测试代码`.arg("shell")`没删干净 @@ -444,11 +439,11 @@ pub mod tests { } /// 示例测试 | PyNARS - /// * 🚩通过预置的批处理文件启动 + /// * 🚩通过Python命令从**内置文件**启动 #[test] fn test_pynars() { let vm = manipulate!( - CommandVm::new(EXE_PATH_PYNARS) + CommandVm::from(generate_command("python", Some(PYNARS_ROOT), ["-m", PYNARS_MODULE])) // 输入转译器:直接取其尾部 => .input_translator(|cmd| Ok(cmd.tail())) // 暂无输出转译器 diff --git a/src/tests/cli/config/_arg_parse_test.opennars.hjson b/src/tests/cli/config/_arg_parse_test.opennars.hjson new file mode 100644 index 0000000..b24ffc1 --- /dev/null +++ b/src/tests/cli/config/_arg_parse_test.opennars.hjson @@ -0,0 +1,23 @@ +#hjson +// * ⚠️仅作「读取配置」测试用 +// * 📌包含OpenNARS转译器 及其jar启动的命令配置 +// * 🎯用于测试「预加载NAL输入」,加载「简单演绎推理」 +{ + // 转译器 + translators: "opennars" + // 启动命令 + command: { + // 命令:启动java运行时 + cmd: "java" + // 传入的命令参数 + cmdArgs: [ + // 设置最大堆内存为1024M + "-Xmx1024m" + // 启动jar包 + -jar + nars.jar + ] + // 启动时的工作目录 | 仅测试「以配置自身为根」 + currentDir: ./../executables + } +} \ No newline at end of file diff --git a/src/tests/cli/config/cxin_js.hjson b/src/tests/cli/config/cxin_js.hjson new file mode 100644 index 0000000..49a1bf4 --- /dev/null +++ b/src/tests/cli/config/cxin_js.hjson @@ -0,0 +1,20 @@ +#hjson +// * 🎯用于测试ONA +// ! ⚠️使用了与项目无关的本地路径 +// * 📝ONA依赖cygwin,故即便内置也容易出错 +{ + // 转译器支持单独指定「输入转译器」和「输出转译器」 + translators: cxin_js + command: { + // * ⚠️必须前缀`./`以指定是「启动当前工作目录下的exe文件」 + cmd: node + cmdArgs: [ + cxin-nars-shell.js + shell + ] + // * 🚩现在基于「固定位置的CIN程序包」运行测试 + // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables + currentDir: ./../../../../executables + } + autoRestart: true +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_higher_deduction.hjson b/src/tests/cli/config/nal_higher_deduction.hjson new file mode 100644 index 0000000..453b251 --- /dev/null +++ b/src/tests/cli/config/nal_higher_deduction.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「高阶演绎推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_higher_deduction.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_i_var_elimination.hjson b/src/tests/cli/config/nal_i_var_elimination.hjson new file mode 100644 index 0000000..3df33eb --- /dev/null +++ b/src/tests/cli/config/nal_i_var_elimination.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「自变量消去推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_i_var_elimination.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_operation.hjson b/src/tests/cli/config/nal_operation.hjson new file mode 100644 index 0000000..2144a80 --- /dev/null +++ b/src/tests/cli/config/nal_operation.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「操作推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_operation.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_simple_deduction.hjson b/src/tests/cli/config/nal_simple_deduction.hjson new file mode 100644 index 0000000..d533c72 --- /dev/null +++ b/src/tests/cli/config/nal_simple_deduction.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「简单演绎推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_simple_deduction.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_simple_operation.hjson b/src/tests/cli/config/nal_simple_operation.hjson new file mode 100644 index 0000000..8b3f9c7 --- /dev/null +++ b/src/tests/cli/config/nal_simple_operation.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「简单操作推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_simple_operation.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/nal_temporal_induction.hjson b/src/tests/cli/config/nal_temporal_induction.hjson new file mode 100644 index 0000000..7b54610 --- /dev/null +++ b/src/tests/cli/config/nal_temporal_induction.hjson @@ -0,0 +1,10 @@ +#hjson +// * 🎯测试「时间归纳推理」 +// * ℹ️测试环境交由`prelude_test.hjson`加载 +// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 +{ + preludeNAL: { + // 预置的NAL测试文件(相对配置文件自身) + file: ./../../nal/test_temporal_induction.nal + } +} \ No newline at end of file diff --git a/src/tests/cli/config/ona.hjson b/src/tests/cli/config/ona.hjson new file mode 100644 index 0000000..7a78bf1 --- /dev/null +++ b/src/tests/cli/config/ona.hjson @@ -0,0 +1,21 @@ +#hjson +// * 🎯用于测试ONA +// * 📝ONA依赖cygwin,故即便内置也容易出错 +{ + // 转译器支持单独指定「输入转译器」和「输出转译器」 + translators: { + in: ona + out: ona + } + command: { + // * ⚠️必须前缀`./`以指定是「启动当前工作目录下的exe文件」 + cmd: ./ONA.exe + cmdArgs: [ + shell + ] + // * 🚩现在基于「固定位置的CIN程序包」运行测试 + // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables + currentDir: ./../../../../executables + } + autoRestart: true +} \ No newline at end of file diff --git a/src/tests/cli/config/opennars.hjson b/src/tests/cli/config/opennars.hjson index ddb6c20..cad8431 100644 --- a/src/tests/cli/config/opennars.hjson +++ b/src/tests/cli/config/opennars.hjson @@ -1,6 +1,5 @@ #hjson // * 📌包含OpenNARS转译器 及其jar启动的命令配置 -// * ⚠️不包括具体运行时所在路径 // * 🎯用于测试「预加载NAL输入」,加载「简单演绎推理」 { // 转译器 @@ -15,9 +14,11 @@ "-Xmx1024m" // 启动jar包 -jar - nars.jar + ./opennars-304-T-modified.jar ] // 启动时的工作目录 - currentDir: root/nars/test + // * 🚩现在基于「固定位置的CIN程序包」运行测试 + // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables + currentDir: ./../../../../executables } } \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_operation.hjson b/src/tests/cli/config/prelude_test.hjson similarity index 54% rename from src/tests/cli/config/test_prelude_operation.hjson rename to src/tests/cli/config/prelude_test.hjson index 2d81c82..f075a3c 100644 --- a/src/tests/cli/config/test_prelude_operation.hjson +++ b/src/tests/cli/config/prelude_test.hjson @@ -1,10 +1,8 @@ #hjson -// * 用于测试「预加载NAL输入」,加载「操作测试」 +// * 🎯测试「预加载NAL输入」,并统一设置「测试环境」 +// * ⚠️不包括具体的「预引入NAL」文件定义 { - preludeNAL: { - // 预置的NAL测试文件(相对配置文件自身) - file: ./../../nal/test_operation.nal - } + // preludeNAL: "" // 禁止用户输入 userInput: false // 不自动重启 diff --git a/src/tests/cli/config/pynars.hjson b/src/tests/cli/config/pynars.hjson index c717228..a11d001 100644 --- a/src/tests/cli/config/pynars.hjson +++ b/src/tests/cli/config/pynars.hjson @@ -1,16 +1,15 @@ // PyNARS的启动配置 -// * ⚠️【2024-04-06 16:47:55】目前仍然使用了相对路径 { translators: pynars command: { cmd: python cmdArgs: [ - -m - pynars.Console + "-m" + // * 🚩【2024-04-07 14:41:20】使用扩展了「附加指令」的「高级控制台」 + pynars.ConsolePlus ] - // ! ⚠️此处为本机路径,在其它系统测试时需要修改到正确的PyNARS目录内 - // * 文件结构:参考GitHub https://github.com/bowen-xu/PyNARS - currentDir: ../../PyNARS-dev + // * 🚩【2024-04-07 14:26:30】现在「CIN测试用运行时」路径已固定 + currentDir: ./../../../../executables/PyNARS } autoRestart: true } \ No newline at end of file diff --git a/src/tests/cli/config/test_ona.hjson b/src/tests/cli/config/test_ona.hjson deleted file mode 100644 index e50032b..0000000 --- a/src/tests/cli/config/test_ona.hjson +++ /dev/null @@ -1,18 +0,0 @@ -#hjson -// * 🎯用于测试ONA -// ! ⚠️使用了与项目无关的本地路径 -// * 📝ONA依赖cygwin,故即便内置也容易出错 -{ - // 转译器支持单独指定「输入转译器」和「输出转译器」 - translators: { - in: ona - out: ona - } - command: { - cmd: ../../NARS-executables/NAR.exe - cmdArgs: [ - shell - ] - } - autoRestart: true -} \ No newline at end of file diff --git a/src/tests/cli/config/test_prelude_simple_deduction.hjson b/src/tests/cli/config/test_prelude_simple_deduction.hjson deleted file mode 100644 index 708afcc..0000000 --- a/src/tests/cli/config/test_prelude_simple_deduction.hjson +++ /dev/null @@ -1,16 +0,0 @@ -#hjson -// * 用于测试「预加载NAL输入」,加载「简单演绎推理」 -{ - preludeNAL: { - // 预置的NAL测试文件(相对配置文件自身) - file: ./../../nal/test_simple_deduction.nal - } - // 禁止用户输入 - userInput: false - // 不自动重启 - autoRestart: false - // 开启严格模式 - // * 🎯用于自动化测试中捕获错误 - // * ✨可由此被外部脚本调用 - strictMode: true -} \ No newline at end of file diff --git a/src/tests/nal/test_higher_deduction.nal b/src/tests/nal/test_higher_deduction.nal new file mode 100644 index 0000000..a73a549 --- /dev/null +++ b/src/tests/nal/test_higher_deduction.nal @@ -0,0 +1,35 @@ +' 用于测试CIN的「高阶演绎推理」 +' * 📍所涉及NAL层级:NAL-5 +' * 📝在「文件表示」上利用现有`Narsese`语法 +' +' 输出等待 `expect-contains` +' * 📝统一的NAL等待语法:`''await: 【输入类别】 【其它内容】` +' * ⚠️可能会阻塞测试,慎用 +' * 🚩以下await已被注释失效,仅作语法演示 +' +' 输出预期 `expect-contains` +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` + +' 🚩降低音量,减少无关输出 +'/VOL 0 + +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 +''sleep: 0.5s + +< B> ==> D>>. +' // ''await: IN < B> ==> D>>. + B>. +' // ''await: IN B>. + D>? +5 + +' 使用睡眠延时,给足输出呈现时间 +''sleep: 1s + +' 检验输出 +''expect-contains: ANSWER D>. + +' 用户无法输入时退出(正常退出) +''terminate(if-no-user) diff --git a/src/tests/nal/test_i_var_elimination.nal b/src/tests/nal/test_i_var_elimination.nal new file mode 100644 index 0000000..bfa138c --- /dev/null +++ b/src/tests/nal/test_i_var_elimination.nal @@ -0,0 +1,28 @@ +' 用于测试「自变量消除」 +' * 📍所涉及NAL层级:NAL-6 +' +' 输出预期 +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` +' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出 + +' 🚩降低音量,减少无关输出 +'/VOL 0 + +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 +''sleep: 0.5s + + B>. +< $1> ==> <$1 --> C>>. + C>? +100 + +' 使用睡眠延时,给足输出呈现时间 +''sleep: 1s + +' 检验输出 +''expect-contains: ANSWER C>. + +' 用户无法输入时退出(正常退出) +''terminate(if-no-user) diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal index 2dab3f7..576a5ba 100644 --- a/src/tests/nal/test_operation.nal +++ b/src/tests/nal/test_operation.nal @@ -1,4 +1,5 @@ -' 用于测试CIN对NAL-8的支持 +' 用于测试CIN对「操作」的支持 +' * 📍所涉及NAL层级:NAL-7、NAL-8 ' ! ⚠️【2024-03-29 16:52:57】ONA不支持「有两个以上组分的乘积词项」,故使用(*, P1, P2)替代 ' * ONA特有「操作注册」语法:`*setopname 11 ^left` ' 输出预期 @@ -9,13 +10,21 @@ ' 降低音量,减少无关输出 ' * 📄【2024-04-03 11:51:12】目前输出过多会造成CLI轻微卡顿 -'/VOL 75 +' * 📝【2024-04-07 14:18:43】观察到输出过多会导致「未能截取操作」的事情发生 +'/VOL 0 +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 ''sleep: 0.5s +' ⚠️【2024-04-07 14:58:12】对ONA使用会造成失败 +' '/REG left + A. :|: <(*, {SELF}) --> ^left>. :|: G. :|: +' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 +' ! ⚠️不加下边这条问句,OpenNARS将测试失败 +<(&/, A, <(*, {SELF}) --> ^left>) ==> G>? A. :|: G! :|: 10 @@ -25,6 +34,9 @@ G! :|: A2. :|: <(*, {SELF}, P) --> ^left>. :|: G2. :|: +' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 +' ! ⚠️不加下边这条问句,OpenNARS将测试失败 +<(&/, A2, <(*, {SELF}, P) --> ^left>) ==> G2>? A2. :|: G2! :|: 10 @@ -34,6 +46,9 @@ G2! :|: A3. :|: <(*, {SELF}, (*, P1, P2)) --> ^left>. :|: G3. :|: +' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 +' ! ⚠️不加下边这条问句,OpenNARS将测试失败 +<(&/, A3, <(*, {SELF}, (*, P1, P2)) --> ^left>) ==> G3>? A3. :|: G3! :|: 10 diff --git a/src/tests/nal/test_simple_deduction.nal b/src/tests/nal/test_simple_deduction.nal index 85c58f6..878ae4a 100644 --- a/src/tests/nal/test_simple_deduction.nal +++ b/src/tests/nal/test_simple_deduction.nal @@ -1,5 +1,6 @@ ' 用于测试CIN的「简单演绎推理」 -' * 📝利用现有`Narsese`语法 +' * 📍所涉及NAL层级:NAL-1 +' * 📝在「文件表示」上利用现有`Narsese`语法 ' ' 输出等待 `expect-contains` ' * 📝统一的NAL等待语法:`''await: 【输入类别】 【其它内容】` @@ -11,6 +12,12 @@ ' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` ' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` +' 🚩降低音量,减少无关输出 +'/VOL 0 + +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 +''sleep: 0.5s + B>. ' // ''await: IN B>. C>. @@ -25,4 +32,4 @@ ''expect-contains: ANSWER C>. ' 用户无法输入时退出(正常退出) -''terminate(if-no-user) \ No newline at end of file +''terminate(if-no-user) diff --git a/src/tests/nal/test_simple_operation.nal b/src/tests/nal/test_simple_operation.nal new file mode 100644 index 0000000..2999a2b --- /dev/null +++ b/src/tests/nal/test_simple_operation.nal @@ -0,0 +1,37 @@ +' 用于测试CIN对「简单操作」的支持 +' * 📍所涉及NAL层级:NAL-8 +' * 📝原理:可直接执行的操作 as 目标 ⇒ 直接执行 +' 输出预期 +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` +' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出 + +' 降低音量,减少无关输出 +' * 📄【2024-04-03 11:51:12】目前输出过多会造成CLI轻微卡顿 +' * 📝【2024-04-07 14:18:43】观察到输出过多会导致「未能截取操作」的事情发生 +'/VOL 0 + +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 +''sleep: 0.5s + +' ⚠️【2024-04-07 14:58:12】对ONA使用会造成失败 +' '/REG left + +<(*, {SELF}) --> ^left>! :|: +10 +''sleep: 1s +''expect-contains: EXE (^left, {SELF}) + +<(*, {SELF}, P) --> ^left>! :|: +10 +''sleep: 1s +''expect-contains: EXE (^left, {SELF}, P) + +<(*, {SELF}, (*, P1, P2)) --> ^left>! :|: +10 +''sleep: 1s +''expect-contains: EXE (^left, {SELF}, (*, P1, P2)) + +''sleep: 500ms +''terminate(if-no-user) diff --git a/src/tests/nal/test_temporal_induction.nal b/src/tests/nal/test_temporal_induction.nal new file mode 100644 index 0000000..cdec7ee --- /dev/null +++ b/src/tests/nal/test_temporal_induction.nal @@ -0,0 +1,40 @@ +' 用于测试CIN的「时间归纳推理」 +' * 📍所涉及NAL层级:NAL-7 +' * 📝在「文件表示」上利用现有`Narsese`语法 +' +' 输出等待 `expect-contains` +' * 📝统一的NAL等待语法:`''await: 【输入类别】 【其它内容】` +' * ⚠️可能会阻塞测试,慎用 +' * 🚩以下await已被注释失效,仅作语法演示 +' +' 输出预期 `expect-contains` +' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` +' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` +' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` + +' 🚩降低音量,减少无关输出 +'/VOL 0 + +' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 +''sleep: 0.5s + + B>. :|: +' // ''await: IN B>. + +5 + + D>. :|: +' // ''await: IN C>. + +< B> =/> D>>? + +5 + +' 使用睡眠延时,给足输出呈现时间 +''sleep: 1s + +' 检验输出 +''expect-contains: ANSWER < B> =/> D>>. + +' 用户无法输入时退出(正常退出) +''terminate(if-no-user) From ae1c83d06811d9f1f4a46e733e6b1ecdc9ac95e9 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:47:34 +0800 Subject: [PATCH 51/59] =?UTF-8?q?refactor:=20:recycle:=20=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8DCIN=E5=90=AF=E5=8A=A8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0Windows=20CMD=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=8C=E6=96=87=E6=A1=A3=E6=B6=A6=E8=89=B2?= =?UTF-8?q?&=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.zh-cn.md | 15 +++++++++++++++ scripts/windows/release-launch-cxin_js.cmd | 2 ++ scripts/windows/release-launch-ona.cmd | 2 ++ scripts/windows/release-launch-opennars.cmd | 2 ++ scripts/windows/release-launch-pynars.cmd | 2 ++ scripts/windows/release-launch.cmd | 2 ++ src/bin/babelnar_cli/main.rs | 2 ++ src/lib.rs | 8 ++++---- .../config/{cxin_js.hjson => cin_cxin_js.hjson} | 0 src/tests/cli/config/{ona.hjson => cin_ona.hjson} | 0 .../config/{opennars.hjson => cin_opennars.hjson} | 0 .../cli/config/{pynars.hjson => cin_pynars.hjson} | 0 src/tests/nal/test_i_var_elimination.nal | 2 +- 13 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 scripts/windows/release-launch-cxin_js.cmd create mode 100644 scripts/windows/release-launch-ona.cmd create mode 100644 scripts/windows/release-launch-opennars.cmd create mode 100644 scripts/windows/release-launch-pynars.cmd create mode 100644 scripts/windows/release-launch.cmd rename src/tests/cli/config/{cxin_js.hjson => cin_cxin_js.hjson} (100%) rename src/tests/cli/config/{ona.hjson => cin_ona.hjson} (100%) rename src/tests/cli/config/{opennars.hjson => cin_opennars.hjson} (100%) rename src/tests/cli/config/{pynars.hjson => cin_pynars.hjson} (100%) diff --git a/README.zh-cn.md b/README.zh-cn.md index bc2846f..1e1d5c3 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -43,6 +43,21 @@ - ❌基于`julia`启动OpenJunars脚本`launch.jl`时,对「输出捕获」尚未有成功记录 - ❌目前对NARS-Python的「输出捕获」尚未有成功记录 +## CLI测试:各CIN完成度评估 + +🕒最后更新时间:【2024-04-07 16:52:29】 + +| | 简单演绎 | 高阶演绎 | 自变量消除 | 时间归纳 | 简单操作 | 时序操作 | +| :--- | :--: | :--: | :--: | :--: | :--: | :--: | +| 原理 | 继承关系的传递性 | 蕴含关系的蕴含保真 | 代入消元 | 前后事件的联系 | 直接要求「做某事」 | 在「发生某事,做某事,目标达成」中学会「若发生某事,就做某事」 | +| 对应NAL内容 | NAL-1 | NAL-5 | NAL-5 + NAL-6 | NAL-7 | NAL-8 | NAL-7 + NAL-8 | +| 语句输入 | ` B>.` + ` C>.` | `< B> ==> D>>.` + ` B>.` | `< $1> ==> <$1 --> C>>.` + ` B>.` | ` B>. :\|:` + ` D>. :\|:` | `<(*, ...) --> ^left>! :\|:` | `A. :\|:` + `<(*, {SELF}) --> ^left>. :\|:` + `G. :\|:` + `<(&/, A, <(*, ...) --> ^left>) ==> G>?` + `G! :\|:` | +| 预期输出 | ` C>.` | ` D>.` | ` C>.` | `< B> =/> D>>.` | EXE `<(*, ...) --> ^left> :\|:` | EXE `<(&/, A, <(*, ...) --> ^left>) ==> G>` | +| OpenNARS(3.0.4) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| ONA | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| PyNARS | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| CXinNARS | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | + ## 参考 - [BabelNAR](https://github.com/ARCJ137442/BabelNAR.jl) diff --git a/scripts/windows/release-launch-cxin_js.cmd b/scripts/windows/release-launch-cxin_js.cmd new file mode 100644 index 0000000..efde7ce --- /dev/null +++ b/scripts/windows/release-launch-cxin_js.cmd @@ -0,0 +1,2 @@ +@echo off +".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_cxin_js.hjson" diff --git a/scripts/windows/release-launch-ona.cmd b/scripts/windows/release-launch-ona.cmd new file mode 100644 index 0000000..9cd064b --- /dev/null +++ b/scripts/windows/release-launch-ona.cmd @@ -0,0 +1,2 @@ +@echo off +".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_ona.hjson" diff --git a/scripts/windows/release-launch-opennars.cmd b/scripts/windows/release-launch-opennars.cmd new file mode 100644 index 0000000..65180b7 --- /dev/null +++ b/scripts/windows/release-launch-opennars.cmd @@ -0,0 +1,2 @@ +@echo off +".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_opennars.hjson" diff --git a/scripts/windows/release-launch-pynars.cmd b/scripts/windows/release-launch-pynars.cmd new file mode 100644 index 0000000..9aa05ce --- /dev/null +++ b/scripts/windows/release-launch-pynars.cmd @@ -0,0 +1,2 @@ +@echo off +".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_pynars.hjson" diff --git a/scripts/windows/release-launch.cmd b/scripts/windows/release-launch.cmd new file mode 100644 index 0000000..5a64442 --- /dev/null +++ b/scripts/windows/release-launch.cmd @@ -0,0 +1,2 @@ +@echo off +".\..\..\target\release\babelnar_cli.exe" diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index c663ae6..ed62873 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -224,9 +224,11 @@ mod tests { PYNARS; /// 简单演绎 + /// * 📝✅【2024-04-07 17:11:22】成功 nal_de => NAL_SIMPLE_DEDUCTION /// 高阶演绎 + /// * 📝✅【2024-04-07 17:11:36】成功 nal_hi => NAL_HIGHER_DEDUCTION /// 自变量消除 diff --git a/src/lib.rs b/src/lib.rs index cd0736e..e9589a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,13 +67,13 @@ pub mod tests { "./src/tests/cli/config/_arg_parse_test.opennars.hjson" /// OpenNARS - OPENNARS = "./src/tests/cli/config/opennars.hjson" + OPENNARS = "./src/tests/cli/config/cin_opennars.hjson" /// ONA - ONA = "./src/tests/cli/config/ona.hjson" + ONA = "./src/tests/cli/config/cin_ona.hjson" /// PyNARS - PYNARS = "./src/tests/cli/config/pynars.hjson" + PYNARS = "./src/tests/cli/config/cin_pynars.hjson" /// CXinJS - CXIN_JS = "./src/tests/cli/config/cxin_js.hjson" + CXIN_JS = "./src/tests/cli/config/cin_cxin_js.hjson" /// 预引入/NAL测试环境 PRELUDE_TEST = "./src/tests/cli/config/prelude_test.hjson" diff --git a/src/tests/cli/config/cxin_js.hjson b/src/tests/cli/config/cin_cxin_js.hjson similarity index 100% rename from src/tests/cli/config/cxin_js.hjson rename to src/tests/cli/config/cin_cxin_js.hjson diff --git a/src/tests/cli/config/ona.hjson b/src/tests/cli/config/cin_ona.hjson similarity index 100% rename from src/tests/cli/config/ona.hjson rename to src/tests/cli/config/cin_ona.hjson diff --git a/src/tests/cli/config/opennars.hjson b/src/tests/cli/config/cin_opennars.hjson similarity index 100% rename from src/tests/cli/config/opennars.hjson rename to src/tests/cli/config/cin_opennars.hjson diff --git a/src/tests/cli/config/pynars.hjson b/src/tests/cli/config/cin_pynars.hjson similarity index 100% rename from src/tests/cli/config/pynars.hjson rename to src/tests/cli/config/cin_pynars.hjson diff --git a/src/tests/nal/test_i_var_elimination.nal b/src/tests/nal/test_i_var_elimination.nal index bfa138c..9552984 100644 --- a/src/tests/nal/test_i_var_elimination.nal +++ b/src/tests/nal/test_i_var_elimination.nal @@ -1,5 +1,5 @@ ' 用于测试「自变量消除」 -' * 📍所涉及NAL层级:NAL-6 +' * 📍所涉及NAL层级:NAL-5、NAL-6 ' ' 输出预期 ' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` From 8c7b464ca99eb2fdf25380d1fea75f675212f8a5 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:04:58 +0800 Subject: [PATCH 52/59] =?UTF-8?q?feat:=20:sparkles:=20=E7=AE=80=E5=8D=95?= =?UTF-8?q?=E7=9A=84=E3=80=8C=E9=85=8D=E7=BD=AE=E8=87=AA=E5=8A=A8=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E8=A1=A5=E5=85=A8=E7=B3=BB=E7=BB=9F=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现在将在请求用户输入「配置文件位置」之前,自动搜索附近可能的配置文件(当前目录+逐级上溯+只看JSON/HJSON)并提供给用户选择 --- BabelNAR-launch.json | 20 ------ Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/config_launcher.rs | 56 ++++++++++++--- src/bin/babelnar_cli/config_search.rs | 92 +++++++++++++++++++++++++ src/bin/babelnar_cli/main.rs | 14 ++-- src/bin/babelnar_cli/vm_config.rs | 18 +++-- 7 files changed, 162 insertions(+), 42 deletions(-) delete mode 100644 BabelNAR-launch.json create mode 100644 src/bin/babelnar_cli/config_search.rs diff --git a/BabelNAR-launch.json b/BabelNAR-launch.json deleted file mode 100644 index fe4428c..0000000 --- a/BabelNAR-launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "description": "测试「默认加载」逻辑", - "translators": "默认", - "command": { - "cmd": "默认", - "cmdArgs": [ - "-默认", - "-默认", - "默认" - ], - "currentDir": "默认" - }, - "websocket": { - "host": "默认", - "port": 8848 - }, - "preludeNAL": { - "text": "'默认" - } -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8a80b94..0f9543a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.16.0" +version = "0.17.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 4332dca..f982e88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.16.0" +version = "0.17.0" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 5b0a53a..365fb65 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -1,7 +1,8 @@ //! 用于从「启动参数」启动NAVM运行时 use crate::{ - read_config_extern, LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators, RuntimeConfig, + read_config_extern, search_configs, LaunchConfig, LaunchConfigCommand, LaunchConfigTranslators, + RuntimeConfig, SUPPORTED_CONFIG_EXTENSIONS, }; use anyhow::{anyhow, Result}; use babel_nar::{ @@ -9,7 +10,7 @@ use babel_nar::{ common::generate_command, cxin_js, nars_python, ona, openjunars, opennars, pynars, }, cli_support::{cin_search::name_match::name_match, io::readline_iter::ReadlineIter}, - eprintln_cli, + eprintln_cli, println_cli, runtimes::{ api::{InputTranslator, IoTranslators}, CommandVm, OutputTranslator, @@ -21,21 +22,56 @@ use navm::{ output::Output, vm::{VmLauncher, VmRuntime}, }; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; /// (若缺省)要求用户手动填充配置项 -pub fn polyfill_config_from_user(config: &mut LaunchConfig) { +pub fn polyfill_config_from_user(config: &mut LaunchConfig, cwd: Option>) { if config.need_polyfill() { - // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件路径 - for line in ReadlineIter::new("请输入配置文件路径(如`BabelNAR.launch.json`): ") - { + // * 先搜索已有的文件 | 不开启 + let search = |verbose| { + // 执行搜索 + let searched_configs = cwd + .as_ref() + .map(|p| search_configs(p.as_ref(), SUPPORTED_CONFIG_EXTENSIONS, verbose)); + // 转换为数组并返回 + match searched_configs { + Some(Ok(v)) => v.into_iter().collect(), + _ => vec![], + } + }; + // 第一次搜索 + let mut searched_configs = search(false); + // * 🚩【2024-04-03 19:33:20】目前是要求输入配置文件位置 + const HINT: &str = "现在需要输入配置文件位置。\n 示例:「BabelNAR.launch.json」\n 若搜索到已有配置文件,可输入其在方括号内的索引,如「0」\n 可直接按下回车,以查看详细搜索过程"; + const PROMPT: &str = "配置文件位置: "; + // 提示(不会频繁打印) + println_cli!([Info] "{}", HINT); + for line in ReadlineIter::new(PROMPT) { // 检验输入 - if let Err(e) = line { - eprintln_cli!([Error] "输入无效:{e}"); + let line = match line { + Err(e) => { + eprintln_cli!([Error] "输入无效:{e}"); + continue; + } + Ok(l) => l, + }; // ! 不能直接加`.trim()`,临时变量会被抛掉 + let line = line.trim(); + if let Ok(i) = line.parse::() { + if i < searched_configs.len() { + println_cli!([Info] "已选择搜索到的第「{i}」个配置:{:?}", searched_configs[i]) + } + // 返回结果 + *config = searched_configs[i].clone(); + break; + } + // 输入为空⇒详细搜索配置⇒重新回到循环 + if line.is_empty() { + searched_configs = search(true); + println_cli!([Info] "{}", HINT); continue; } // 检验路径 - let path = PathBuf::from(line.unwrap().trim()); + let path = PathBuf::from(line); if !path.is_file() { eprintln_cli!([Error] "文件「{path:?}」不存在"); continue; diff --git a/src/bin/babelnar_cli/config_search.rs b/src/bin/babelnar_cli/config_search.rs new file mode 100644 index 0000000..b37cb1d --- /dev/null +++ b/src/bin/babelnar_cli/config_search.rs @@ -0,0 +1,92 @@ +//! CIN自动搜索 + +use crate::{read_config_extern, LaunchConfig}; +use anyhow::Result; +use babel_nar::{ + cli_support::cin_search::{name_match::is_name_match, path_walker::PathWalkerV1}, + println_cli, +}; +use std::path::{Path, PathBuf}; + +pub fn search_configs>( + start: &Path, + allowed_extension_names: impl IntoIterator, + verbose: bool, +) -> Result> { + // 允许的扩展名 + let extension_names = allowed_extension_names.into_iter().collect::>(); + // 深入条件 + fn deep_criterion(path: &Path) -> bool { + path.file_name() + .is_some_and(|name| name.to_str().is_some_and(|s| is_name_match("nars", s))) + } + + // 构建遍历者,加上条件 + let walker = PathWalkerV1::new(start, deep_criterion).unwrap(); + + let is_extension_match = |path: &PathBuf| { + path.extension().is_some_and(|ext| { + ext.to_str().is_some_and(|ext_str| { + extension_names + .iter() + .any(|name| is_name_match(name.as_ref(), ext_str)) + }) + }) + }; + + // 遍历(成功的) + let mut c = 0; + let mut c_valid = 0; + let mut valid_non_empty_configs = vec![]; + for path in walker.flatten().filter(is_extension_match) { + if verbose { + println_cli!([Log] "正在搜索 {path:?}"); + } + if let Ok(config) = read_config_extern(&path) { + c_valid += 1; + if !config.is_empty() { + if verbose { + println_cli!([Info] "搜索到配置文件:{config:?}"); + } + valid_non_empty_configs.push(config); + } + } + c += 1; + } + + // 输出搜索结果 + println_cli!( + [Info] + "一共搜索了{c}个文件,其中 {c_valid} 个文件符合条件,{} 个非空", + &valid_non_empty_configs.len() + ); + match valid_non_empty_configs.is_empty() { + true => println_cli!([Info] "未搜索到任何有效配置。"), + false => { + println_cli!([Info] "已搜索到以下有效配置:"); + for (i, config) in valid_non_empty_configs.iter().enumerate() { + // TODO: 后续或许在其中添加描述信息? + println_cli!([Info] "【{i}】 {config:?}"); + } + } + } + + // 返回 + Ok(valid_non_empty_configs) +} + +/// 单元测试 +#[cfg(test)] +mod tests { + use super::*; + use babel_nar::tests::config_paths::ARG_PARSE_TEST; + // use std::env::current_dir; + + #[test] + fn test_path_walker_v1() { + // 测试`config`目录下的文件 + let start = ARG_PARSE_TEST; + // * 📌起始目录即项目根目录 + search_configs(&PathBuf::from(start), ["json", "hjson"], true).expect("搜索出错"); + } +} diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index ed62873..237ebd7 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -20,7 +20,9 @@ nar_dev_utils::mods! { use vm_config; // 命令行解析 use arg_parse; - // 从参数启动 + // 配置(自动)搜索 + use config_search; + // 从配置启动 use config_launcher; // 运行时交互、管理 use runtime_manage; @@ -37,7 +39,11 @@ pub fn main() -> Result<()> { /// 以特定参数开始命令行主程序 /// * 🚩此处只应该有自[`env`]传入的参数 /// * 🚩【2024-04-01 14:25:38】暂时用不到「当前工作路径」 -pub fn main_args(_cwd: IoResult, args: impl Iterator) -> Result<()> { +pub fn main_args(cwd: IoResult, args: impl Iterator) -> Result<()> { + // 解包当前工作目录 + let cwd = cwd + .inspect_err(|e| println_cli!([Warn] "无法获取当前工作目录:{e}")) + .ok(); // (Windows下)启用终端颜色 let _ = colored::control::set_virtual_terminal(true) .inspect_err(|_| eprintln_cli!([Error] "无法启动终端彩色显示。。")); @@ -45,8 +51,8 @@ pub fn main_args(_cwd: IoResult, args: impl Iterator) -> let args = CliArgs::parse_from(args); // 读取配置 | with 默认配置文件 let mut config = load_config(&args); - // 用户填充配置项 - polyfill_config_from_user(&mut config); + // 用户填充配置项 | 需要用户输入、工作路径(🎯自动搜索) + polyfill_config_from_user(&mut config, cwd); // 从配置项启动 | 复制一个新配置,不会附带任何非基础类型开销 let (runtime, config) = match launch_by_config(config.clone()) { // 启动成功⇒返回 diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index 79a25eb..3f32214 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -70,6 +70,12 @@ use std::{ path::{Component, Path, PathBuf}, }; +/// 允许的配置文件扩展名 +/// * 🚩【2024-04-07 18:30:24】目前支持JSON与HJSON +/// * 📌其顺序决定了在「扩展名优先补充」中的遍历顺序 +/// * 📄当`a.hjson`与`a.json`存在时,`a`优先补全为`a.hjson` +pub const SUPPORTED_CONFIG_EXTENSIONS: &[&str] = &["hjson", "json"]; + /// 工具宏/批量拷贝性合并 /// * 🎯简化重复的`对象.方法`调用 /// * 📄参考[`Option::coalesce_clone`] @@ -605,12 +611,12 @@ pub fn try_complete_path(path: &Path) -> PathBuf { let path = path.to_path_buf(); // 当扩展名为空时补全 if path.extension().is_none() { - // 尝试补全为`.hjson` | 无扩展名⇒追加,有扩展名⇒替换 - let path_ = path.with_extension("hjson"); - if_return! { path_.exists() => path_ } - // 尝试补全为`.json` | 无扩展名⇒追加,有扩展名⇒替换 - let path_ = path.with_extension("json"); - if_return! { path_.exists() => path_ } + // 尝试用已有的扩展名填充文件名 + for extension in SUPPORTED_CONFIG_EXTENSIONS { + // 尝试补全为指定扩展名 | 无扩展名⇒追加,有扩展名⇒替换 + let path_ = path.with_extension(extension); + if_return! { path_.exists() => path_ } + } } path } From 048241b797d74f899ba10aa11c55e995cf47a275 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:12:34 +0800 Subject: [PATCH 53/59] =?UTF-8?q?fix:=20:bug:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E3=80=8C=E7=94=A8=E6=88=B7=E6=89=80=E5=A1=AB=E5=85=85=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=E7=A9=BA=E3=80=81NARS=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=B6=EF=BC=8C=E7=A8=8B=E5=BA=8F=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E9=97=AA=E9=80=80=E3=80=8D=E7=9A=84=E4=BD=93=E9=AA=8C?= =?UTF-8?q?=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/main.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f9543a..524fcff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.17.0" +version = "0.17.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index f982e88..25cc6eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.17.0" +version = "0.17.1" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 237ebd7..97f25bc 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -60,8 +60,8 @@ pub fn main_args(cwd: IoResult, args: impl Iterator) -> // 启动失败⇒打印错误信息,等待并退出 Err(e) => { println_cli!([Error] "NARS运行时启动错误:{e}"); - // 启用用户输入时延时提示 - if let Some(true) = config.user_input { + // 空配置/启用用户输入⇒延时提示 + if config.user_input.is_none() || config.user_input.unwrap() { println_cli!([Info] "程序将在 3 秒后自动退出。。。"); sleep(Duration::from_secs(3)); } From 73e0b66b99dde468b571050e5ba35af15aaf19f1 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:46:14 +0800 Subject: [PATCH 54/59] =?UTF-8?q?feat:=20:sparkles:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=95=8C=E9=9D=A2=EF=BC=8C=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E9=A6=96=E4=B8=AApre=5Falpha=E9=A2=84=E8=A7=88=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现在配置可用描述文本替代自身源码;优化CLI的用户呈现体验 --- .vscode/settings.json | 1 + Cargo.lock | 257 ++++++++++++++++++++++++- Cargo.toml | 3 +- src/bin/babelnar_cli/config_search.rs | 4 +- src/bin/babelnar_cli/main.rs | 18 +- src/bin/babelnar_cli/vm_config.rs | 8 + src/tests/cli/config/cin_cxin_js.hjson | 5 +- src/tests/cli/config/cin_ona.hjson | 3 +- 8 files changed, 283 insertions(+), 16 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f531c97..242f0ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "cSpell.words": [ "Addrs", "canonicalize", + "clearscreen", "confy", "cxin", "deser", diff --git a/Cargo.lock b/Cargo.lock index 524fcff..1e5e910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,10 +73,11 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.17.1" +version = "0.18.0" dependencies = [ "anyhow", "clap", + "clearscreen", "colored", "deser-hjson", "lazy_static", @@ -98,6 +99,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "block-buffer" version = "0.7.3" @@ -202,6 +209,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clearscreen" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" +dependencies = [ + "nix", + "terminfo", + "thiserror", + "which", + "winapi 0.3.9", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -265,12 +285,54 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -286,7 +348,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -323,7 +385,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -332,6 +405,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "httparse" version = "1.8.0" @@ -391,6 +473,22 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "log" version = "0.4.21" @@ -403,6 +501,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.6.23" @@ -482,6 +586,27 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -545,6 +670,44 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -575,13 +738,22 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", "rand_chacha", - "rand_core", + "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -589,7 +761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -598,16 +770,33 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom 0.2.13", + "libredox", + "thiserror", ] [[package]] @@ -639,6 +828,19 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.17" @@ -699,6 +901,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -725,6 +933,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "terminfo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" +dependencies = [ + "dirs", + "fnv", + "nom", + "phf", + "phf_codegen", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -822,6 +1043,24 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.2.8" @@ -1000,7 +1239,7 @@ dependencies = [ "log", "mio", "mio-extras", - "rand", + "rand 0.7.3", "sha-1", "slab", "url", diff --git a/Cargo.toml b/Cargo.toml index 25cc6eb..75c2435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.17.1" +version = "0.18.0" edition = "2021" description = """ Implementation and application supports of the NAVM model @@ -25,6 +25,7 @@ repository = "https://github.com/ARCJ137442/BabelNAR.rs" # 用于错误处理 thiserror = "1.0.58" anyhow = "1.0.81" +clearscreen = "2.0.1" [dependencies.nar_dev_utils] # 【2024-03-13 21:17:55】实用库现在独立为`nar_dev_utils` diff --git a/src/bin/babelnar_cli/config_search.rs b/src/bin/babelnar_cli/config_search.rs index b37cb1d..077f0fa 100644 --- a/src/bin/babelnar_cli/config_search.rs +++ b/src/bin/babelnar_cli/config_search.rs @@ -6,6 +6,7 @@ use babel_nar::{ cli_support::cin_search::{name_match::is_name_match, path_walker::PathWalkerV1}, println_cli, }; +use nar_dev_utils::ToDebug; use std::path::{Path, PathBuf}; pub fn search_configs>( @@ -66,7 +67,8 @@ pub fn search_configs>( println_cli!([Info] "已搜索到以下有效配置:"); for (i, config) in valid_non_empty_configs.iter().enumerate() { // TODO: 后续或许在其中添加描述信息? - println_cli!([Info] "【{i}】 {config:?}"); + let information = config.description.clone().unwrap_or(config.to_debug()); + println_cli!([Info] "【{i}】 {information}"); } } } diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 97f25bc..9f75e8d 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -44,15 +44,30 @@ pub fn main_args(cwd: IoResult, args: impl Iterator) -> let cwd = cwd .inspect_err(|e| println_cli!([Warn] "无法获取当前工作目录:{e}")) .ok(); + // (Windows下)启用终端颜色 let _ = colored::control::set_virtual_terminal(true) .inspect_err(|_| eprintln_cli!([Error] "无法启动终端彩色显示。。")); + // 解析命令行参数 let args = CliArgs::parse_from(args); + // 读取配置 | with 默认配置文件 let mut config = load_config(&args); + + // 是否向用户展示「详细信息」 | 用于等待、提示等 + let user_verbose = config.user_input.is_none() || config.user_input.unwrap(); + // 用户填充配置项 | 需要用户输入、工作路径(🎯自动搜索) polyfill_config_from_user(&mut config, cwd); + + // 清屏,预备启动 + if user_verbose { + println_cli!([Info] "配置加载完毕!程序将在1s后启动。。。"); + sleep(Duration::from_secs(1)); + } + let _ = clearscreen::clear().inspect_err(|e| eprintln_cli!([Warn] "清屏失败:{e}")); + // 从配置项启动 | 复制一个新配置,不会附带任何非基础类型开销 let (runtime, config) = match launch_by_config(config.clone()) { // 启动成功⇒返回 @@ -61,13 +76,14 @@ pub fn main_args(cwd: IoResult, args: impl Iterator) -> Err(e) => { println_cli!([Error] "NARS运行时启动错误:{e}"); // 空配置/启用用户输入⇒延时提示 - if config.user_input.is_none() || config.user_input.unwrap() { + if user_verbose { 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); diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index 3f32214..432cf92 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -103,6 +103,13 @@ macro_rules! coalesce_clones { #[serde(rename_all = "camelCase")] // 🔗参考: #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LaunchConfig { + /// 启动配置的文本描述 + /// * 🎯在自动搜索时呈现给用户 + /// * 📌一般是单行文本 + /// + /// * ❓I18n 国际化 + pub description: Option, + /// 转译器组合 /// * 🚩使用字符串模糊匹配 pub translators: Option, @@ -153,6 +160,7 @@ pub struct LaunchConfig { /// * ✅与此同时,实现了「有提醒的后期维护」 /// * 📌后续若新增字段,此处会因「缺字段」立即报错 const EMPTY_LAUNCH_CONFIG: LaunchConfig = LaunchConfig { + description: None, translators: None, command: None, websocket: None, diff --git a/src/tests/cli/config/cin_cxin_js.hjson b/src/tests/cli/config/cin_cxin_js.hjson index 49a1bf4..3999356 100644 --- a/src/tests/cli/config/cin_cxin_js.hjson +++ b/src/tests/cli/config/cin_cxin_js.hjson @@ -1,7 +1,6 @@ #hjson -// * 🎯用于测试ONA -// ! ⚠️使用了与项目无关的本地路径 -// * 📝ONA依赖cygwin,故即便内置也容易出错 +// * 🎯用于测试CXinNARS(JS版本) +// * 📌使用Node.js启动 { // 转译器支持单独指定「输入转译器」和「输出转译器」 translators: cxin_js diff --git a/src/tests/cli/config/cin_ona.hjson b/src/tests/cli/config/cin_ona.hjson index 7a78bf1..d8ce203 100644 --- a/src/tests/cli/config/cin_ona.hjson +++ b/src/tests/cli/config/cin_ona.hjson @@ -1,6 +1,7 @@ #hjson // * 🎯用于测试ONA -// * 📝ONA依赖cygwin,故即便内置也容易出错 +// * ⚠️启动需要cygwin +// * 🔗中文官网: http://www.cygwin.cn/ { // 转译器支持单独指定「输入转译器」和「输出转译器」 translators: { From 3632527b5469c1dfd4c82fe30c3cfa1f72661434 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 9 Apr 2024 01:33:18 +0800 Subject: [PATCH 55/59] =?UTF-8?q?fix:=20:bug:=20=E8=BE=93=E5=87=BA&Websock?= =?UTF-8?q?et&=E8=BD=AC=E8=AF=91=20=E5=A4=A7=E4=BF=AE=E5=A4=8D=EF=BC=9A?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E3=80=8CWebsocket=E4=BB=A3=E7=A0=81=E8=80=A6?= =?UTF-8?q?=E5=90=88=E3=80=8D=E3=80=8C=E6=B6=88=E6=81=AF=E5=9B=9E=E4=BC=A0?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E9=94=81=E6=AD=BB=E9=97=AE=E9=A2=98=E3=80=8D?= =?UTF-8?q?=E7=AD=89bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 通过在「NAVM输出缓存」中启用「流式处理者列表」实现「输出打印、输出Websocket回传、输出缓存」解耦 2. 通过深入`ws`库,探索使用`ws::Settings`与`WebSocket::broadcaster` 1. 简化「广播到所有连接」的「消息回传」问题 2. 缓解「一时间回传过多消息,Websocket回传线程阻塞导致整个程序阻塞」的问题:输出容量从500提升到24576 3. 结合ONA源码,对ONA shell重新启用「操作注册」,但对「预置操作符名」自动忽略 4. 更改逻辑以修复OpenNARS对「ANTICIPATE」的输出转译失败问题 5. 优化并正式实装Matriangle Websocket服务器,目前可正常对接实验 --- .gitignore | 3 + .vscode/settings.json | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/main.rs | 9 + src/bin/babelnar_cli/runtime_manage.rs | 48 ++++- src/bin/babelnar_cli/websocket_server.rs | 181 +++++++++++++------ src/bin/ws_server_test/main.rs | 72 ++++++++ src/cin_implements/ona/translators.rs | 27 ++- src/cin_implements/opennars/translators.rs | 25 +-- src/cli_support/io/navm_output_cache.rs | 37 ++-- src/cli_support/io/output_print.rs | 10 + src/output_handler/flow_handler_list.rs | 25 ++- src/process_io/io_process.rs | 9 +- src/runtimes/command_vm/runtime.rs | 1 + src/tests/cli/config/matriangle_server.hjson | 8 +- 16 files changed, 356 insertions(+), 104 deletions(-) create mode 100644 src/bin/ws_server_test/main.rs diff --git a/.gitignore b/.gitignore index d0d12c9..8a3cde7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,8 @@ executables/ # 临时文件 *tempCodeRunnerFile* +# 日志文件 +*.log + # 测试用文件 \[test\]* diff --git a/.vscode/settings.json b/.vscode/settings.json index 242f0ba..1428978 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "editor.formatOnSave": true, "cSpell.words": [ "Addrs", + "boardcaster", "canonicalize", "clearscreen", "confy", diff --git a/Cargo.lock b/Cargo.lock index 1e5e910..90c63c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.18.0" +version = "0.18.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 75c2435..0fc3d69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.18.0" +version = "0.18.1" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 9f75e8d..1c2c303 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -160,6 +160,15 @@ mod tests { main($cin_path, &[]) } + + /// Matriangle服务器 + /// * 🎯复现先前基于Matriangle环境的NARS实验 + #[test] + pub fn main_matriangle_server() -> Result<()> { + // 以默认参数启动 + main_configs($cin_path, &[MATRIANGLE_SERVER]) + } + $( $(#[$attr])* #[test] diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 6bc981b..8a044d0 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -11,11 +11,11 @@ use babel_nar::{ readline_iter::ReadlineIter, }, }, - eprintln_cli, println_cli, + eprintln_cli, if_let_err_eprintln_cli, println_cli, runtimes::TranslateError, test_tools::{nal_format::parse, put_nal, VmOutputCache}, }; -use nar_dev_utils::{if_return, ResultBoost}; +use nar_dev_utils::{if_return, manipulate, pipe, ResultBoost}; use navm::{ cmd::Cmd, vm::{VmRuntime, VmStatus}, @@ -64,10 +64,40 @@ where Self { runtime: Arc::new(Mutex::new(runtime)), config: Arc::new(config), - output_cache: OutputCache::default_arc_mutex(), + // 创建的同时增加侦听器 + output_cache: Self::new_output_cache(), } } + /// 新建一个「输出缓存」 + /// * 🚩创建缓存⇒增加侦听器⇒装入[`ArcMutex`] + /// * 🎯避免 + fn new_output_cache() -> ArcMutex { + pipe! { + manipulate!( + // 产生一个新的「输出缓存」 + OutputCache::default() + // 添加侦听器 + => Self::add_output_listener + ) + // 装入ArcMutex + => Mutex::new => Arc::new + } + } + + /// 增加「打印输出」侦听器 + /// * 🎯(与Websocket一同)分离「输出侦听」逻辑 + /// * 🎯统一给管理者添加功能 + /// * ❓后续可配置 + fn add_output_listener(output_cache: &mut OutputCache) { + output_cache.output_handlers.add_handler(|output| { + // 打印输出 + println_cli!(&output); + // 继续返回 + Some(output) + }); + } + /// 【主函数】在运行时启动后,对其进行管理 /// * 🎯健壮性:更多「警告/重来」而非`panic` /// * 🎯用户友好:尽可能隐藏底层内容 @@ -222,6 +252,7 @@ where { // 缓存输出 // * 🚩在缓存时格式化输出 + // TODO: 【2024-04-08 19:15:30】现在必须不再能直接`put`输出了:要兼容Websocket情形 match output_cache.lock() { Ok(mut output_cache) => output_cache.put(output)?, Err(e) => eprintln_cli!([Error] "缓存NAVM运行时输出时发生错误:{e}"), @@ -237,8 +268,8 @@ where /// 生成「Websocket服务」子线程 pub fn try_spawn_ws_server(&mut self) -> Result>>> { // 若有⇒启动 - if let Some(config) = &self.config.websocket { - let thread = spawn_ws_server(self, &config.host, config.port); + if self.config.websocket.is_some() { + let thread = spawn_ws_server(self)?; return Ok(Some(thread)); } @@ -288,9 +319,10 @@ where // 非空⇒解析输入并执行 if !line.trim().is_empty() { - if let Err(e) = Self::input_line_to_vm(runtime, &line, &config, output_cache) { - println_cli!([Error] "输入过程中发生错误:{e}") - } + if_let_err_eprintln_cli!( + Self::input_line_to_vm(runtime, &line, &config, output_cache) + => e => [Error] "输入过程中发生错误:{e}" + ); } } diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs index 5e60760..fae8dd4 100644 --- a/src/bin/babelnar_cli/websocket_server.rs +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -2,17 +2,23 @@ //! * 🎯为BabelNAR CLI实现Websocket IO //! * 🎯实现专有的Websocket服务端逻辑 -use crate::{RuntimeConfig, RuntimeManager}; +use crate::{LaunchConfigWebsocket, RuntimeConfig, RuntimeManager}; use anyhow::Result; use babel_nar::{ - cli_support::io::{ - navm_output_cache::{ArcMutex, OutputCache}, - websocket::{spawn_server, to_address}, + cli_support::{ + error_handling_boost::error_anyhow, + io::{ + navm_output_cache::{ArcMutex, OutputCache}, + websocket::to_address, + }, }, - eprintln_cli, println_cli, + eprintln_cli, if_let_err_eprintln_cli, println_cli, +}; +use navm::{output::Output, vm::VmRuntime}; +use std::{ + sync::Arc, + thread::{self, JoinHandle}, }; -use navm::vm::VmRuntime; -use std::{sync::Arc, thread::JoinHandle}; use ws::{Factory, Handler, Sender}; /// 工具宏:尝试执行,如果失败则上抛错误 @@ -33,32 +39,82 @@ macro_rules! try_or_return_err { }; } +/// 通信用代码 +/// * 🎯统一有关「通信消息格式」的内容 +/// * 📌形式:JSON**对象数组** +/// * ⚠️【2024-04-08 19:08:15】即便一次只回传一条消息,也需包装上方括号`[{...}]` +#[inline] +pub fn format_output_message(output: &Output) -> String { + // 包装成「对象数组」 + format!("[{}]", output.to_json_string()) +} + /// 入口代码 /// * 🎯生成一个Websocket服务端线程 -/// * 🚩不管参数`config`中的地址:可能没有 -pub fn spawn_ws_server( - manager: &RuntimeManager, - host: &str, - port: u16, -) -> JoinHandle> +/// * ⚠️此处要求**manager.config.websocket**必须非空,否则会直接panic +/// * 🚩此处手动生成Websocket服务端并启动:提升其「待发消息缓冲区」容量到24576 +/// * ❗【2024-04-09 01:20:57】问题缘起:服务端在「突然收到大量消息需要重发」时,可能会直接阻塞线程 +/// * 📌【2024-04-09 01:21:37】现在通过配置「最大连接数」与「队列大小」以**暂时缓解**此问题 +/// * 🔗参考: +/// * 🔗GitHub issue: +pub fn spawn_ws_server(manager: &mut RuntimeManager) -> Result>> where R: VmRuntime + Send + Sync, { - // 合并地址 - let address = to_address(host, port); + // 提取并合并地址 + let LaunchConfigWebsocket { host, port } = manager + .config + .websocket + .as_ref() + .expect("尝试在无配置时启动Websocket服务器"); + let address = to_address(host, *port); // 获取服务端「处理者工厂」 // * 🚩拷贝[`Arc`] - let factory = WSServer { + let server = WSServer { runtime: manager.runtime.clone(), output_cache: manager.output_cache.clone(), config: manager.config.clone(), }; - // 根据专有服务端逻辑,生成子线程并返回 - let server = spawn_server(address.clone(), factory); + // 生成定制版的Websocket服务端 + // * 🎯获取生成的[`WebSocket`](服务端)对象,调用[`WebSocket::boardcaster`]方法快速广播 + // * ❌【2024-04-08 23:23:08】无法独立为单独的函数:此中NAVM运行时「R」的生命周期问题(难以参与推导) + let (handle, sender) = { + let factory = server; + let address = address.clone(); + let ws_setting = ws::Settings { + // * 📝使用`ws::Builder`结合`ws::Settings`生成配置 + // * ✅在配置中调节「队列大小」以扩宽「连续消息接收限制」 + // * 默认:100(最大连接)×5(最长队列)→500条后阻塞 + // * 🚩【2024-04-09 01:03:52】现在调整成「最多32个连接,每个连接最多768条消息」 + // * ⚠️仍然会在24576条消息后产生阻塞——但相比原先500条,情况少很多 + max_connections: 0x20, + queue_size: 0x300, + ..Default::default() + }; + let server = ws::Builder::new() + .with_settings(ws_setting) + .build(factory)?; + let sender = server.broadcaster(); + let handle = thread::spawn(move || { + server.listen(address)?; + // ! ❌此处不能缩并:必须转换为`anyhow::Error` + Ok(()) + }); + (handle, sender) + }; println_cli!([Info] "Websocket服务器已在 {:?} 启动", address); - server + + // 向(服务端自身)「输出缓存」添加侦听器 + if_let_err_eprintln_cli! { + // ! 此处需要可变的`manager` + register_listener(&mut manager.output_cache, sender) + => e => [Error] "无法为服务端注册侦听器:{e}" + } + + // 返回线程句柄 + Ok(handle) } /// 一个Websocket连接 @@ -106,32 +162,22 @@ where let output_cache = &mut *try_or_return_err!(self.output_cache.lock(); err => "在Websocket连接中获取输出缓存失败:{err}"); // 输入信息,并监控缓存的新输出 - if let Err(err) = - RuntimeManager::input_line_to_vm(runtime, &msg.to_string(), config, output_cache) - { - eprintln_cli!([Error] "在Websocket连接中输入「{msg}」时发生错误:{err}") + // * 📝【2024-04-08 22:10:17】现在查明「Websocket线程阻塞」问题在Websocket「回传发送者」的`send`调用中 + if_let_err_eprintln_cli! { + RuntimeManager::input_line_to_vm( + runtime, + &msg.to_string(), + config, + output_cache, + ) + => err => [Error] "在Websocket连接中输入「{msg}」时发生错误:{err}" } - // ! 🚩此处无法回传输出:输出捕捉在缓存中处理的地方 - // if new_len_cache > old_len_cache { - // let mut output; - // let mut json_text; - // // 逐个获取 - // for i in (old_len_cache - 1)..new_len_cache { - // output = &output_cache.borrow_inner()[i]; - // json_text = output.to_json_string(); - // // 回传,若出错仅输出错误 - // if let Err(e) = self.sender.send(json_text.clone()) { - // eprintln_cli!([Error] "尝试回传消息「{json_text}」时发生错误:{e}"); - // } - // } - // } - Ok(()) } fn on_close(&mut self, code: ws::CloseCode, reason: &str) { - println_cli!([Info] "Websocket连接关闭(退出码:{code:?};原因:「{reason}」)") + println_cli!([Info] "Websocket连接关闭(退出码:{code:?};原因:「{reason}」)"); } fn on_error(&mut self, err: ws::Error) { @@ -172,6 +218,48 @@ where pub(crate) output_cache: ArcMutex, } +/// 向所有「回传发送者」广播NAVM输出 +/// * 🎯回传所侦听到的NAVM输出 +pub(crate) fn broadcast_to_senders( + // senders: &mut ArcMutex, + broadcaster: &mut Sender, + output: &Output, +) -> Result<()> { + let output_str = format_output_message(output); + + // println_cli!([Debug] "🏗️正在向接收者回传消息:\n{output_str}"); + // * 通过一个`broadcaster`直接向所有连接广播消息 + if_let_err_eprintln_cli! { + broadcaster.send(output_str.to_string()) + => e => [Error] "广播消息失败:{e}" + }; + + // println_cli!([Debug] "✅向接收者回传消息完成:\n{output_str}"); + + Ok(()) +} + +/// 向「输出缓存」注册侦听器 +/// * 🎯绑定侦听器到输出缓存中,以便在「侦听器有输出」时广播 +/// * 🎯现在只有「输出缓存」会留存:因为`WebSocket.broadcaster`只在服务器启动后创建 +pub(crate) fn register_listener( + output_cache: &mut ArcMutex, + mut broadcaster: Sender, +) -> Result<()> { + // 尝试解包「输出缓存」 + let output_cache = &mut *output_cache.lock().map_err(error_anyhow)?; + output_cache.output_handlers.add_handler(move |output| { + // 广播 + if_let_err_eprintln_cli! { + broadcast_to_senders(&mut broadcaster, &output) + => e => [Error] "Websocket回传广播到发送者时出现错误:{:?}", e + } + // 返回 + Some(output) + }); + Ok(()) +} + impl Factory for WSServer where R: VmRuntime + Send + Sync + 'static, @@ -179,20 +267,8 @@ where type Handler = Connection; fn connection_made(&mut self, sender: Sender) -> Connection { - println_cli!([Info] "Websocket连接已建立"); let id = sender.connection_id(); - // 尝试添加「发送者」 - match self.output_cache.lock() { - Ok(mut output_cache) => { - let output_cache = &mut *output_cache; - // 添加「发送者」 - output_cache.websocket_senders.push(sender); - } - Err(err) => { - // 输出错误 - println_cli!([Error] "Websocket输出侦听器添加失败:{err}"); - } - } + println_cli!([Info] "Websocket连接已在id {id} 处建立"); // 返回连接 Connection { runtime: self.runtime.clone(), @@ -203,6 +279,7 @@ where } fn on_shutdown(&mut self) { + // 打印消息 println_cli!([Info] "Websocket服务器已关停") } diff --git a/src/bin/ws_server_test/main.rs b/src/bin/ws_server_test/main.rs new file mode 100644 index 0000000..d522de0 --- /dev/null +++ b/src/bin/ws_server_test/main.rs @@ -0,0 +1,72 @@ +use std::{ + thread::{self, sleep}, + time::Duration, +}; +extern crate ws; + +fn main() { + loop { + let _ = ws::connect("ws://127.0.0.1:8765", |sender| { + // 生成一个不断发送消息的线程 + thread::spawn(move || loop { + let _ = sender.send("NSE A.".to_string()); + let _ = sender.send("NSE B.".to_string()); + let _ = sender.send("NSE A?".to_string()); + }); + + // handle received message + move |msg| { + println!("Got message: {}", msg); + // out.close(CloseCode::Normal) + Ok(()) + } + }); + sleep(Duration::from_secs(1)); + } +} + +/// 压力测试 +/// * 🔗GitHub issue: +#[test] +fn main_server() { + // A client that sends tons of messages to the server + thread::spawn(move || { + let _ = ws::connect("ws://127.0.0.1:3012", |sender| { + let mut num_send = 0_usize; + // Generate a thread that constantly sends messages for testing + thread::spawn(move || loop { + num_send += 1; + // The content is just for example, the actual situation has more variety + let _ = sender.send(format!("overwhelming message #{num_send}!")); + }); + + // Handle nothing + move |_| Ok(()) + }); + }); + + // A server that echoes messages back to the client + ws::Builder::new() + .with_settings(ws::Settings { + max_connections: 0x40, + // * ↓Change this setting to `usize::MAX` actually can't be allowed: It might run out of memory + queue_size: 0x300, + // ! ↓Even enabled it, it still can't stop the blocking + panic_on_queue: true, + ..Default::default() + }) + .build(|sender: ws::Sender| { + // handle received message + move |msg| { + println!("Got message: {}", msg); + println!("from {sender:?}"); + // ! It will block on ↓this line when the `SyncSender` is full + let _ = sender.send(msg); + // * ↑If uncomment this line of code, the server will not be blocked + Ok(()) + } + }) + .unwrap() + .listen("127.0.0.1:3012") + .unwrap(); +} diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 00d07e6..5d57868 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -39,6 +39,24 @@ use regex::{Captures, Regex}; use util::OptionBoost; use util::{if_return, pipe}; +/// ONA已内置的操作列表 +/// * 🎯避免「重复操作注册」 +/// * 🎯【2024-04-07 23:12:56】兼容PyNARS的同时,不将自身搞崩 +/// * 📄首次出现场景:Matriangle Websocket服务器链接 +/// * 🔗参考: +pub const OPERATOR_NAME_LIST: &[&str] = &[ + "left", + "right", + "up", + "down", + "say", + "pick", + "drop", + "go", + "activate", + "deactivate", +]; + /// ONA的「输入转译」函数 /// * 🎯用于将统一的「NAVM指令」转译为「ONA Shell输入」 pub fn input_translate(cmd: Cmd) -> Result { @@ -51,7 +69,10 @@ pub fn input_translate(cmd: Cmd) -> Result { // VOL指令:调整音量 Cmd::VOL(n) => format!("*volume={n}"), // REG指令:注册操作 - Cmd::REG { name } => format!("*setopname {} ^{name}", hash_operator_id(&name)), + Cmd::REG { name } => match OPERATOR_NAME_LIST.contains(&name.as_str()) { + true => String::new(), + false => format!("*setopname {} ^{name}", hash_operator_id(&name)), + }, // 注释 ⇒ 忽略 | ❓【2024-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) Cmd::REM { .. } => String::new(), // 其它类型 @@ -95,8 +116,10 @@ fn hash_operator_id(_: &str) -> usize { // op_name.hash(&mut hasher); // (hasher.finish() % 10) as usize } + +/// 测试/获取注册的操作符id #[test] -fn t() { +fn test_hash_operator_id() { dbg!([ hash_operator_id("left"), hash_operator_id("left"), diff --git a/src/cin_implements/opennars/translators.rs b/src/cin_implements/opennars/translators.rs index fd1972d..e579a03 100644 --- a/src/cin_implements/opennars/translators.rs +++ b/src/cin_implements/opennars/translators.rs @@ -56,6 +56,7 @@ pub fn input_translate(cmd: Cmd) -> Result { pub fn output_translate(content_raw: String) -> Result { // 根据冒号分隔一次,然后得到「头部」 let (head, tail) = content_raw.split_once(':').unwrap_or(("", &content_raw)); + let tail = tail.trim(); // 根据「头部」生成输出 let output = match &*head.to_uppercase() { "IN" => Output::IN { @@ -89,7 +90,7 @@ pub fn output_translate(content_raw: String) -> Result { r#type: "ANTICIPATE".to_string(), // 先提取其中的Narsese | ⚠️借用了`content_raw` narsese: try_parse_narsese(tail) - .ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}")), + .ok_or_run(|e| println!("【{head}】在解析Narsese「{tail}」时出现错误:{e}")), // 然后传入整个内容 content: content_raw, }, @@ -119,7 +120,8 @@ pub fn output_translate(content_raw: String) -> Result { pub fn parse_narsese_opennars(head: &str, tail: &str) -> Result> { use util::ResultBoost; // ! ↓下方会转换为None - Ok(try_parse_narsese(tail).ok_or_run(|e| println!("【{head}】在解析Narsese时出现错误:{e}"))) + Ok(try_parse_narsese(tail) + .ok_or_run(|e| println!("【{head}】在解析Narsese「{tail}」时出现错误:{e}"))) } /// (OpenNARS)从原始输出中解析Narsese @@ -181,20 +183,19 @@ fn parse_term_from_operation(term_str: &str) -> Result { } /// 切分尾部字符串,并(尝试)从中解析出Narsese +/// * 🎯对OpenNARS中的「时间戳/证据基」做切分 +/// * 📄`<{SELF} --> [satisfied]>! :|: %1.00;0.90% {1269408|1269408 : (-8058943780727144183,628)}` +/// * 🚩现在无需考虑:[`pest`]会自动忽略无关前缀 +/// * ❌在「无证据基case」如`ANTICIPATE: <{powerup_bad_x} --> [seen]>`中报错:把`{`截掉了 +/// * 📌此中`tail`已做好行切分 fn try_parse_narsese(tail: &str) -> Result { - // 提取并解析Narsese字符串 - let narsese = tail - // 去尾 - .rfind('{') - // 截取 & 解析 - .map(|right_index| parse_dialect_opennars(tail[..right_index].trim())); + // 提取并解析Narsese字符 // 提取解析结果 + let narsese = parse_dialect_opennars(tail); match narsese { // 解析成功⇒提取 & 返回 - Some(Ok(narsese)) => Ok(narsese), + Ok(narsese) => Ok(narsese), // 解析失败⇒打印错误日志 | 返回None - Some(Err(err)) => Err(TranslateError::from(err).into()), - // 未找到括号的情况 - None => Err(TranslateError::from("输出「OUT」解析失败:未找到「{」").into()), + Err(err) => Err(TranslateError::from(err).into()), } } diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs index 9bdbb5c..06f93b4 100644 --- a/src/cli_support/io/navm_output_cache.rs +++ b/src/cli_support/io/navm_output_cache.rs @@ -2,8 +2,11 @@ //! * 🎯一站式存储、展示与管理NAVM的输出 //! * 🎯可被其它二进制库所复用 -use super::output_print::OutputType; -use crate::{cli_support::error_handling_boost::error_anyhow, test_tools::VmOutputCache}; +use crate::{ + cli_support::error_handling_boost::error_anyhow, + output_handler::flow_handler_list::{FlowHandlerList, HandleResult}, + test_tools::VmOutputCache, +}; use anyhow::Result; use nar_dev_utils::ResultBoost; use navm::output::Output; @@ -25,10 +28,12 @@ pub struct OutputCache { /// * 🚩【2024-04-03 01:43:41】不附带任何包装类型,仅包装其自身 pub(crate) inner: Vec, - /// 内部封装的「发送者」列表 + /// 流式侦听器列表 + /// * 🎯用于功能解耦、易分派的「NAVM输出处理」 + /// * 📌可在此过程中对输出进行拦截、转换等操作 + /// * 🎯CLI输出打印 /// * 🎯Websocket输出回传(JSON) - /// TODO: 🏗️后续优化 - pub websocket_senders: Vec, + pub output_handlers: FlowHandlerList, } /// 功能实现 @@ -37,7 +42,7 @@ impl OutputCache { pub fn new(inner: Vec) -> Self { Self { inner, - websocket_senders: Vec::new(), + output_handlers: FlowHandlerList::new(), } } @@ -85,20 +90,14 @@ impl VmOutputCache for OutputCache { /// * 🎯统一的「打印输出」逻辑 /// * 🚩【2024-04-03 01:07:55】不打算封装了 fn put(&mut self, output: Output) -> Result<()> { - // 打印输出 - // * 🚩现在内置入「命令行支持」,不再能直接使用`println_cli` - OutputType::print_from_navm_output(&output); - - // 回传JSON - // TODO: 待优化 - for sender in &self.websocket_senders { - if let Err(e) = sender.send(output.to_json_string()) { - OutputType::Error.print_line(&format!("Websocket💬回传失败:{e}")); - } + // 交给处理者处理 + let r = self.output_handlers.handle(output); + match r { + // 通过⇒静默加入输出 + HandleResult::Passed(output) => self.put_silent(output), + // 被消耗⇒提示 + HandleResult::Consumed(index) => Ok(println!("NAVM输出在[{index}]位置被拦截。")), } - - // 静默加入输出 - self.put_silent(output) } /// 遍历输出 diff --git a/src/cli_support/io/output_print.rs b/src/cli_support/io/output_print.rs index 46f7798..884919f 100644 --- a/src/cli_support/io/output_print.rs +++ b/src/cli_support/io/output_print.rs @@ -175,6 +175,16 @@ macro_rules! eprintln_cli { }; } +/// 快捷打印宏/当输出为`Err`时打印,当Ok时为值 +#[macro_export] +macro_rules! if_let_err_eprintln_cli { + { $value:expr => $e:ident => $($tail:tt)* } => { + if let Err($e) = $value { + eprintln_cli!($($tail)*); + } + }; +} + impl<'a> From<&'a Output> for OutputType<'a> { fn from(out: &'a Output) -> Self { OutputType::Vm(out.type_name()) diff --git a/src/output_handler/flow_handler_list.rs b/src/output_handler/flow_handler_list.rs index 498c8d2..0bfcbb0 100644 --- a/src/output_handler/flow_handler_list.rs +++ b/src/output_handler/flow_handler_list.rs @@ -16,6 +16,11 @@ pub enum HandleResult { Consumed(HandlerIndex), } +/// 统一表示「输出处理者」 +/// * 🎯简化类型表示 +/// * 🚩【2024-04-08 21:04:47】因需进行线程共享,此闭包必须附带`Send`和`Sync` +pub type DynOutputHandler = dyn FnMut(Item) -> Option + Send + Sync; + /// 流式处理者列表 /// * 🚩处理者的特征约束:`FnMut(Item) -> Option` /// * 📝不能显式声明「处理者」类型 @@ -24,13 +29,20 @@ pub enum HandleResult { pub struct FlowHandlerList { /// 存储所有的处理者 /// * 🚩使用[`Box`]以容纳不同类型的闭包 - handlers: Vec Option>>, + handlers: Vec>>, /// 用于对未直接作为字段的`Item`类型的占位符 /// * 🔗标准库文档: _marker: PhantomData, } +/// 实现调试呈现 +impl std::fmt::Debug for FlowHandlerList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FlowHandlerList(num={})", self.handlers.len()) + } +} + impl FlowHandlerList { /// 构造函数/从某个[`Box`]迭代器中构造 /// * ℹ️若需构造一个空列表,可使用[`FlowHandlerList::default`] @@ -44,7 +56,7 @@ impl FlowHandlerList { /// 构造函数/直接从[`Vec`]构造 /// * 需要自己手动装箱 /// * ℹ️若需构造一个空列表,可使用[`FlowHandlerList::default`] - pub fn from_vec(vec: Vec Option>>) -> Self { + pub fn from_vec(vec: Vec>>) -> Self { Self { handlers: vec, _marker: PhantomData, @@ -78,7 +90,7 @@ impl FlowHandlerList { // 对「处理者列表」的操作 // /// 获取某个位置的处理者(不可变) - pub fn get_handler(&self, index: usize) -> Option<&dyn FnMut(Item) -> Option> { + pub fn get_handler(&self, index: usize) -> Option<&DynOutputHandler> { // 获取指定位置的box,然后将其转为索引 self.handlers.get(index).map(Box::as_ref) } @@ -91,14 +103,17 @@ impl FlowHandlerList { // pub fn get_handler_mut( // &mut self, // index: usize, - // ) -> Option<&mut dyn FnMut(Item) -> Option> { + // ) -> Option<&mut DynOutputHandler> { // self.handlers.get_mut(index).map(Box::as_mut) // } /// 添加新的处理者 /// * ⚠️虽然结构体定义时无需对「处理者」类型约束为`'static`静态周期, /// * 但此处传入作为参数(的函数指针)是需要的 - pub fn add_handler(&mut self, handler: impl FnMut(Item) -> Option + 'static) { + pub fn add_handler( + &mut self, + handler: impl FnMut(Item) -> Option + Send + Sync + 'static, + ) { self.handlers.push(Box::new(handler)) } } diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs index 15c9c3a..95a407f 100644 --- a/src/process_io/io_process.rs +++ b/src/process_io/io_process.rs @@ -241,7 +241,6 @@ impl IoProcessManager { child_in_receiver: Receiver, termination_signal: ArcMutex, ) -> thread::JoinHandle<()> { - // 生成一个简单的子线程,从通道中(阻塞性)读取数据,并随时将此计入标准输入 thread::spawn(move || { // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 let mut stdin = stdin; @@ -411,9 +410,10 @@ impl IoProcessManager { // 锁定以获取`Sender` .lock() .transform_err(err)? - // 发送 + // 发送 | 📄文档说此处不会阻塞: .send(input_line.to_string()) .transform_err(err) + // * ✅【2024-04-08 22:46:04】有关「线程死锁」的问题已定位:`ws`库中的`Sender.send`方法使用`std::mpsc::SyncSender`导致阻塞 } /// 向子进程写入**一行**数据(字符串) @@ -452,7 +452,10 @@ impl IoProcessManager { // * 📝【2024-03-24 00:15:10】必须手动释放锁,否则会导致后续线程死锁 // ! 解除子线程「write_stdin」的阻塞 - self.put("\n").unwrap(); + // * 有可能在程序崩溃后还发信息,此时是`SendError` + let _ = self + .put("\n") + .inspect_err(|e| println!("向「进程读取」子线程发送消息失败!{e}")); // 等待子线程终止 // // * 🚩【2024-03-24 18:49:31】现在强制销毁持有的两个子线程,不再等待其结束 diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs index dd7f509..3a5cbf9 100644 --- a/src/runtimes/command_vm/runtime.rs +++ b/src/runtimes/command_vm/runtime.rs @@ -48,6 +48,7 @@ impl VmRuntime for CommandVmRuntime { if_return! { input.is_empty() => Ok(()) } // 置入 // * 🚩没有换行符 + // * 📌【2024-04-07 23:43:59】追踪「Websocket进程阻塞」漏洞:问题不在此,在`ws::Sender::send`处 self.process.put_line(input) } diff --git a/src/tests/cli/config/matriangle_server.hjson b/src/tests/cli/config/matriangle_server.hjson index ee33625..bac6e35 100644 --- a/src/tests/cli/config/matriangle_server.hjson +++ b/src/tests/cli/config/matriangle_server.hjson @@ -4,9 +4,15 @@ { // Websocket服务端地址 websocket: { - host: localhost + // * ❌【2024-04-07 23:05:21】不能是`localhost`,需要是`127.0.0.1`(Matriangle端要求) + host: 127.0.0.1 port: 8765 } // 【2024-04-04 04:49:32】Matriangle环境目前以「NAVM指令」的形式传入 inputMode: cmd + preludeNAL: { + // 预置的NAL指令 + // * 🔬【2024-04-08 15:43:27】控制程序输出:当产生大量输出时,将会发生线程死锁 + text: "'''VOL 0" + } } \ No newline at end of file From 3c3f3f71381d0511ff174bad0f94fb7caf9d8416 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 9 Apr 2024 04:01:38 +0800 Subject: [PATCH 56/59] =?UTF-8?q?test:=20:white=5Fcheck=5Fmark:=20?= =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E5=85=88=E5=89=8D=E7=9A=84=E3=80=8CWebsocket?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=B5=8B=E8=AF=95=E3=80=8D=E6=8E=A2?= =?UTF-8?q?=E7=A9=B6=E3=80=8C=E6=97=A0Matriangle=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E3=80=8D=E4=BB=A5=E5=8F=8A=E3=80=8CNARS?= =?UTF-8?q?=E6=8E=A8=E7=90=86=E9=80=82=E5=BA=94=E6=80=A7=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于「知识不时颠倒,需要反复修正」的「动态环境」测试NARS的适应性:ONA最强,OpenNARS容易死板 --- src/bin/babelnar_cli/runtime_manage.rs | 5 +- src/bin/ws_server_test/main.rs | 95 ++++++++++++++++++++++++++ src/cin_implements/ona/translators.rs | 1 + 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 8a044d0..0e3ace5 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -295,6 +295,7 @@ where for io_result in ReadlineIter::default() { // 从迭代器中读取一行 let line = io_result?; + let line = line.trim(); // ! 这两句无法合并:临时变量的引用问题 // 尝试获取运行时引用 | 仅有其它地方panic了才会停止 // ! 📝PoisonError无法在线程中传递 @@ -318,9 +319,9 @@ where .transform_err(|e| anyhow!("获取NAVM输出缓存时发生错误:{e}"))?; // 非空⇒解析输入并执行 - if !line.trim().is_empty() { + if !line.is_empty() { if_let_err_eprintln_cli!( - Self::input_line_to_vm(runtime, &line, &config, output_cache) + Self::input_line_to_vm(runtime, line, &config, output_cache) => e => [Error] "输入过程中发生错误:{e}" ); } diff --git a/src/bin/ws_server_test/main.rs b/src/bin/ws_server_test/main.rs index d522de0..52c0ccd 100644 --- a/src/bin/ws_server_test/main.rs +++ b/src/bin/ws_server_test/main.rs @@ -1,10 +1,105 @@ use std::{ + cell::RefCell, thread::{self, sleep}, time::Duration, }; extern crate ws; fn main() { + /// 单次训练过程 + fn train(sender: ws::Sender) -> impl Fn(ws::Message) -> Result<(), ws::Error> { + // 尝试注册操作 + let _ = sender.send("REG left".to_string()); + let _ = sender.send("REG right".to_string()); + + // 预先经验 + for _ in 0..5 { + // 背景事件 + let _ = sender.send("NSE b>. :|:".to_string()); + // 自身操作 + let _ = sender.send("NSE <(*, {SELF}) --> ^left>. :|:".to_string()); + let _ = sender.send("NSE <(*, {SELF}) --> ^right>. :|:".to_string()); + // 一定间隔 + let _ = sender.send("CYC 10".to_string()); + // 自身状态 + let _ = sender.send("NSE <{SELF} --> [good]>. :|:".to_string()); + } + // 再间隔一段时间,开始训练 + let _ = sender.send("CYC 100".to_string()); + + let sender2 = sender.clone(); + // 生成一个不断发送消息的线程 + thread::spawn(move || loop { + let _ = sender2.send("NSE b>. :|:".to_string()); + let _ = sender2.send("CYC 10".to_string()); + let _ = sender2.send("NSE <{SELF} --> [good]>! :|:".to_string()); + // let _ = sender2.send("NSE <{SELF} --> [good]>>? :|:".to_string()); + thread::sleep(Duration::from_secs_f64(0.03)); + }); + + // * 📝Websocket Handler不能可变,就用RefCell实现内部可变性 + let right_side = RefCell::new(false); + let num_good = RefCell::new(0_usize); + let output_steps = RefCell::new(0_usize); + let minimum_fitness_period = RefCell::new(usize::MAX); + const MAX_GOOD: usize = 20; + move |msg: ws::Message| { + // println!("Got message: {}", msg); + let msg = msg.to_string(); + // 记录步数 + let output_steps = &mut *output_steps.borrow_mut(); + *output_steps += 1; + // 操作 + if msg.contains("EXE") { + // 左右操作状态 + let left = msg.contains(r#"["left","{SELF}"]"#); + let right = msg.contains(r#"["right","{SELF}"]"#); + if !left && !right { + return Ok(()); + } + let minimum_fitness_period = &mut *minimum_fitness_period.borrow_mut(); + // * 🔬可以尝试「左右颠倒」以观察NARS的适应能力 + let num_good = &mut *num_good.borrow_mut(); + let right_side = &mut *right_side.borrow_mut(); + let lr = if *right_side { "right" } else { "left" }; + // 奖励 + if left && !*right_side || right && *right_side { + let _ = sender.send("NSE <{SELF} --> [good]>. :|: %1.0; 0.5%".to_string()); + println!("good\t{lr}\tfor {num_good}!\t{minimum_fitness_period}"); + *num_good += 1; + // 改变模式 + if *num_good > MAX_GOOD { + let b = *right_side; + *right_side = !b; + *num_good = 0; + // 一个轮回⇒以「轮回数」记录「适应性」 + if b { + *minimum_fitness_period = *minimum_fitness_period.min(output_steps); + *output_steps = 0; + } + } + } + // 惩罚 + else { + let _ = sender.send("NSE <{SELF} --> [good]>. :|: %0.0; 0.5%".to_string()); + println!("bad\t{lr}\tfor {num_good}!\t{minimum_fitness_period}"); + } + } + // out.close(CloseCode::Normal) + Ok(()) + } + } + + // 循环 + loop { + let _ = ws::connect("ws://127.0.0.1:8765", train); + // 连接失败则延迟等待 + sleep(Duration::from_secs(1)); + } +} + +#[test] +fn test_overwhelming_nse() { loop { let _ = ws::connect("ws://127.0.0.1:8765", |sender| { // 生成一个不断发送消息的线程 diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 5d57868..051334f 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -135,6 +135,7 @@ fn test_hash_operator_id() { /// * 🚩直接根据选取的「头部」进行匹配 /// 超参数:严格模式 /// * 🚩测试环境下「输出Narsese解析失败」会上报错误 +/// TODO: 解决`Input: <(* {SELF}) --> ^left>. :|: occurrenceTime=119 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000` pub fn output_translate(content_raw: String) -> Result { // 特别处理 if_return! { From 527dd89e103b582be04c66df78137b2cbb05e449 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:16:00 +0800 Subject: [PATCH 57/59] =?UTF-8?q?feat:=20:sparkles:=20=E3=80=8C=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E3=80=8D=E8=BD=AC=E8=AF=91=E5=99=A8=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E3=80=8C=E5=8E=9F=E7=94=9FIL-1=E3=80=8D=E7=A4=BA?= =?UTF-8?q?=E4=BE=8BCIN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加【以NAVM指令作为输入,输出JSON式NAVM输出】的「原生」CIN转译器支持;基于「原生」转译器,示例性支持NAVM所开发的「原生IL-1」运行时(仅能跑通「简单演绎推理」) --- Cargo.lock | 6 ++-- Cargo.toml | 2 +- src/bin/babelnar_cli/config_launcher.rs | 3 +- src/bin/babelnar_cli/main.rs | 33 ++++++++++++++++++++++ src/cin_implements/mod.rs | 5 +++- src/cin_implements/native/mod.rs | 9 ++++++ src/cin_implements/native/translators.rs | 27 ++++++++++++++++++ src/lib.rs | 2 ++ src/tests/cli/config/cin_native_il_1.hjson | 16 +++++++++++ 9 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/cin_implements/native/mod.rs create mode 100644 src/cin_implements/native/translators.rs create mode 100644 src/tests/cli/config/cin_native_il_1.hjson diff --git a/Cargo.lock b/Cargo.lock index 90c63c3..eed7d3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.18.1" +version = "0.19.0" dependencies = [ "anyhow", "clap", @@ -568,11 +568,13 @@ dependencies = [ [[package]] name = "navm" -version = "0.9.1" +version = "0.11.0" dependencies = [ "anyhow", "nar_dev_utils", "narsese", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0fc3d69..952ec23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.18.1" +version = "0.19.0" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/bin/babelnar_cli/config_launcher.rs index 365fb65..a754a9c 100644 --- a/src/bin/babelnar_cli/config_launcher.rs +++ b/src/bin/babelnar_cli/config_launcher.rs @@ -7,7 +7,7 @@ use crate::{ use anyhow::{anyhow, Result}; use babel_nar::{ cin_implements::{ - common::generate_command, cxin_js, nars_python, ona, openjunars, opennars, pynars, + common::generate_command, cxin_js, nars_python, native, ona, openjunars, opennars, pynars, }, cli_support::{cin_search::name_match::name_match, io::readline_iter::ReadlineIter}, eprintln_cli, println_cli, @@ -199,6 +199,7 @@ pub type TranslatorDict<'a> = &'a [( /// 输入转译器的索引字典 /// * 🚩静态存储映射,后续遍历可有序可无序 pub const TRANSLATOR_DICT: TranslatorDict = &[ + ("Native", native::input_translate, native::output_translate), ( "OpenNARS", opennars::input_translate, diff --git a/src/bin/babelnar_cli/main.rs b/src/bin/babelnar_cli/main.rs index 1c2c303..bfb6c5f 100644 --- a/src/bin/babelnar_cli/main.rs +++ b/src/bin/babelnar_cli/main.rs @@ -321,6 +321,39 @@ mod tests { } } + /// 测试/原生IL-1 + mod native_il_1 { + use super::*; + + cin_tests! { + NATIVE_IL_1; + + /// 简单演绎 + /// * 📝✅【2024-04-09 21:12:10】成功 + nal_de => NAL_SIMPLE_DEDUCTION + + /// 高阶演绎 + /// * 📝❌【2024-04-09 21:12:32】失败:尚不支持 + nal_hi => NAL_HIGHER_DEDUCTION + + /// 自变量消除 + /// * 📝❌【2024-04-09 21:12:32】失败:尚不支持 + nal_ie => NAL_I_VAR_ELIMINATION + + /// 时间归纳 + /// * 📝❌【2024-04-09 21:12:32】失败:尚不支持 + nal_te => NAL_TEMPORAL_INDUCTION + + /// 简单操作 + /// * 📝❌【2024-04-09 21:12:32】失败:尚不支持 + nal_so => NAL_SIMPLE_OPERATION + + /// 操作 + /// * 📝❌【2024-04-09 21:12:32】失败:尚不支持 + nal_op => NAL_OPERATION + } + } + // ! ❌【2024-04-07 14:39:20】接口完成度不高的NARS-Python、OpenJunars暂不进行测试 /// 测试入口/带Websocket Shell diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs index 5ab3cfd..29b9f6c 100644 --- a/src/cin_implements/mod.rs +++ b/src/cin_implements/mod.rs @@ -6,7 +6,7 @@ //! * ❌【2024-03-25 13:36:30】集成测试`cargo t --all-features`中未能解决 //! * ❗// ! ↑【少用乃至不用这条命令】 //! -//! ? 【2024-03-25 12:48:08】如何兼顾「复用」「性能」与「简洁」 +//! * 🚩【2024-04-09 20:47:04】基本完成对「复用」「性能」与「简洁」的兼顾 //! * 📌复用:将OpenNARS、ONA抽象成「基于jar的启动逻辑」「基于exe的启动逻辑」等方式,以便后续重复使用 //! * 📄case:目前ONA、NARS-Python都是基于exe的启动方式 //! * 📌性能:避免过多的封装、粗暴复合导致的空间浪费 @@ -22,6 +22,9 @@ util::mods! { // 共用代码 pub common; + // 原生 + pub native; + // OpenNARS pub opennars; diff --git a/src/cin_implements/native/mod.rs b/src/cin_implements/native/mod.rs new file mode 100644 index 0000000..f9889ff --- /dev/null +++ b/src/cin_implements/native/mod.rs @@ -0,0 +1,9 @@ +//! NAVM原生的虚拟机(转译器) +//! * ✨Cmd输入转译:直接将[`Cmd`]转换为字符串形式 +//! * ✨NAVM_JSON输出转译:基于[`serde_json`]直接从JSON字符串读取[`Output`] +//! * 📌没有固定的启动器:仅通过「命令行启动器」即可启动 + +util::mods! { + // 输入输出转译 + pub pub translators; +} diff --git a/src/cin_implements/native/translators.rs b/src/cin_implements/native/translators.rs new file mode 100644 index 0000000..bfd42da --- /dev/null +++ b/src/cin_implements/native/translators.rs @@ -0,0 +1,27 @@ +//! 输入输出转译 +//! * ✨Cmd输入转译:直接将[`Cmd`]转换为字符串形式 +//! * ✨NAVM_JSON输出转译:基于[`serde_json`]直接从JSON字符串读取[`Output`] + +use anyhow::Result; +use navm::{cmd::Cmd, output::Output}; +extern crate serde_json; + +/// Cmd输入转译 +/// * 🚩直接将[`Cmd`]转换为字符串形式 +/// * 📌总是成功 +pub fn input_translate(cmd: Cmd) -> Result { + Ok(cmd.to_string()) +} + +/// NAVM_JSON输出转译 +/// * 🚩基于[`serde_json`]直接从JSON字符串读取[`Output`] +pub fn output_translate(content_raw: String) -> Result { + match serde_json::from_str(&content_raw) { + // 解析成功⇒返回 + Ok(output) => Ok(output), + // 解析失败⇒转为`OTHER` + Err(..) => Ok(Output::OTHER { + content: content_raw, + }), + } +} diff --git a/src/lib.rs b/src/lib.rs index e9589a2..6f24266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,8 @@ pub mod tests { PYNARS = "./src/tests/cli/config/cin_pynars.hjson" /// CXinJS CXIN_JS = "./src/tests/cli/config/cin_cxin_js.hjson" + /// 原生IL-1 + NATIVE_IL_1 = "./src/tests/cli/config/cin_native_il_1.hjson" /// 预引入/NAL测试环境 PRELUDE_TEST = "./src/tests/cli/config/prelude_test.hjson" diff --git a/src/tests/cli/config/cin_native_il_1.hjson b/src/tests/cli/config/cin_native_il_1.hjson new file mode 100644 index 0000000..35597d4 --- /dev/null +++ b/src/tests/cli/config/cin_native_il_1.hjson @@ -0,0 +1,16 @@ +#hjson +// * 🎯用于测试原生「IL-1」运行时 +// * ✨基于NAVM,纯Rust编写 +{ + // 使用「原生」输入输出转译器 + translators: native + command: { + // * ⚠️必须前缀`./`以指定是「启动当前工作目录下的exe文件」 + cmd: ./native-IL-1.exe + cmdArgs: [] + // * 🚩现在基于「固定位置的CIN程序包」运行测试 + // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables + currentDir: ./../../../../executables + } + autoRestart: true +} \ No newline at end of file From db604e8abd48654e66d58b17baf8a1c0eb402b91 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:59:21 +0800 Subject: [PATCH 58/59] =?UTF-8?q?feat:=20:sparkles:=20=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E3=80=8C=E4=BF=9D=E5=AD=98=E8=BE=93=E5=87=BA=E3=80=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9B=E5=9C=A8=E3=80=8CCmd=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=A8=A1=E5=BC=8F=E3=80=8D=E4=B8=AD=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=80=8CNAL=E8=BE=93=E5=85=A5=E3=80=8D=E7=9A=84?= =?UTF-8?q?=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 现在可以在NAL格式中使用「魔法注释」`''save-outputs: 【文件相对路径】`保存「当前NAVM运行时」的所有输出 - 📌格式:JSON对象数组 2. 现在可用前缀「/」在「NAVM指令输入模式」中使用「NAL输入」 - 📄如在Matriangle服务器中使用`/''save-outputs: out.log.json`保存当前NAVM实例输出 --- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/runtime_manage.rs | 43 +++++++++++++++++++--- src/bin/babelnar_cli/vm_config.rs | 30 ++++++++++++++- src/bin/babelnar_cli/websocket_server.rs | 1 + src/cli_support/io/navm_output_cache.rs | 2 +- src/test_tools/nal_format/mod.rs | 6 +++ src/test_tools/nal_format/nal_grammar.pest | 9 ++++- src/test_tools/structs.rs | 5 +++ src/test_tools/vm_interact.rs | 33 ++++++++++++++++- src/tests/nal/test_operation.nal | 8 ++++ 12 files changed, 129 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 8a3cde7..1720687 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ executables/ # 日志文件 *.log +*.log.* # 测试用文件 \[test\]* diff --git a/Cargo.lock b/Cargo.lock index eed7d3c..b4bf744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.19.0" +version = "0.20.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 952ec23..93e281f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.19.0" +version = "0.20.0" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 0e3ace5..0002a60 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -22,7 +22,8 @@ use navm::{ }; use std::{ fmt::Debug, - ops::{ControlFlow, ControlFlow::Break, ControlFlow::Continue}, + ops::ControlFlow::{self, Break, Continue}, + path::Path, sync::{Arc, Mutex}, thread::{self, sleep, JoinHandle}, time::Duration, @@ -208,10 +209,22 @@ where LaunchConfigPreludeNAL::Text(nal) => nal.to_string(), }; + // 获取「NAL执行路径」 + // * 🎯在「预置NAL」中执行「保存文件」时,决定以哪个路径为「相对路径起点」 + let nal_file_path = match prelude_nal { + // 文件⇒基于文件路径 + LaunchConfigPreludeNAL::File(path) => { + path.parent().unwrap_or(&self.config.config_path) + } + // 纯文本⇒直接引入 + LaunchConfigPreludeNAL::Text(..) => &self.config.config_path, + }; + // 输入NAL并处理 // * 🚩【2024-04-03 11:10:44】遇到错误,统一上报 // * 根据「严格模式」判断要「继续」还是「终止」 - let put_result = Self::input_nal_to_vm(runtime, &nal, output_cache, config); + let put_result = + Self::input_nal_to_vm(runtime, &nal, output_cache, config, nal_file_path); match self.config.strict_mode { false => Continue(put_result), true => Break(put_result), @@ -321,7 +334,8 @@ where // 非空⇒解析输入并执行 if !line.is_empty() { if_let_err_eprintln_cli!( - Self::input_line_to_vm(runtime, line, &config, output_cache) + // * 🚩【2024-04-09 22:11:41】置入时以「配置文件所在目录」为NAL工作目录 + Self::input_line_to_vm(runtime, line, &config, output_cache, &config.config_path) => e => [Error] "输入过程中发生错误:{e}" ); } @@ -336,18 +350,28 @@ where } /// 置入一行输入 + /// * 📄`nal_root_path`:从NAL文件加载⇒NAL文件所在路径;用户输入⇒配置文件所在路径 pub fn input_line_to_vm( runtime: &mut R, line: &str, config: &RuntimeConfig, output_cache: &mut OutputCache, + nal_root_path: &Path, ) -> Result<()> { // 向运行时输入 match config.input_mode { // NAVM指令 - InputMode::Cmd => Self::input_cmd_to_vm(runtime, line), + // * ✨【2024-04-09 22:48:01】转义输入:使用(NAVM指令不可能用的)前缀「/」以重新启用「NAL输入」 + InputMode::Cmd => match line.starts_with('/') { + true => { + Self::input_nal_to_vm(runtime, &line[1..], output_cache, config, nal_root_path) + } + false => Self::input_cmd_to_vm(runtime, line), + }, // NAL输入 - InputMode::Nal => Self::input_nal_to_vm(runtime, line, output_cache, config), + InputMode::Nal => { + Self::input_nal_to_vm(runtime, line, output_cache, config, nal_root_path) + } } } @@ -371,6 +395,7 @@ where input: &str, output_cache: &mut OutputCache, config: &RuntimeConfig, + nal_root_path: &Path, // 📄从NAL文件加载⇒NAL文件所在路径;用户输入⇒配置文件所在路径 ) -> Result<()> { // 解析输入,并遍历解析出的每个NAL输入 for input in parse(input) { @@ -385,7 +410,13 @@ where } Ok(nal) => { // 尝试置入NAL输入 | 为了错误消息,必须克隆 - let put_result = put_nal(runtime, nal.clone(), output_cache, config.user_input); + let put_result = put_nal( + runtime, + nal.clone(), + output_cache, + config.user_input, + nal_root_path, + ); // 处理错误 if let Err(e) = put_result { // 无论是否严格模式,都报告错误 diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/bin/babelnar_cli/vm_config.rs index 432cf92..3beeae0 100644 --- a/src/bin/babelnar_cli/vm_config.rs +++ b/src/bin/babelnar_cli/vm_config.rs @@ -103,6 +103,15 @@ macro_rules! coalesce_clones { #[serde(rename_all = "camelCase")] // 🔗参考: #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LaunchConfig { + /// 配置的加载路径 + /// * 🎯用于记录「基于配置自身的配置路径」 + /// * 🚩从文件中加载⇒`Some(配置文件所在目录)` + /// * 🚩从其它加载⇒[`None`] + /// * 📌不在反序列化(解析)时解析 + #[serde(skip)] + #[serde(default)] + pub config_path: Option, + /// 启动配置的文本描述 /// * 🎯在自动搜索时呈现给用户 /// * 📌一般是单行文本 @@ -160,6 +169,7 @@ pub struct LaunchConfig { /// * ✅与此同时,实现了「有提醒的后期维护」 /// * 📌后续若新增字段,此处会因「缺字段」立即报错 const EMPTY_LAUNCH_CONFIG: LaunchConfig = LaunchConfig { + config_path: None, description: None, translators: None, command: None, @@ -178,6 +188,13 @@ const EMPTY_LAUNCH_CONFIG: LaunchConfig = LaunchConfig { #[serde(rename_all = "camelCase")] // 🔗参考: #[derive(Debug, Clone, PartialEq, Eq)] pub struct RuntimeConfig { + /// 配置的加载路径 + /// * 🎯用于记录「基于配置自身的配置路径」 + /// * 🚩从文件中加载⇒配置文件所在目录 + /// * 🚩从其它加载⇒[`PathBuf::new`] + #[serde(skip)] + pub config_path: PathBuf, + /// 转译器组合 /// * 🚩运行时必须提供转译器 /// * 📌【2024-04-04 02:11:44】即便是所谓「默认」转译器,使用「及早报错」避免非预期运行 @@ -231,6 +248,10 @@ const fn bool_true() -> bool { true } +/// 布尔值`false` +/// * 🎯配置解析中「默认为`false`」的默认值指定 +/// * 📝serde中,`#[serde(default)]`使用的是[`bool::default`]而非容器的`default` +/// * 因此需要指定一个函数来初始化(false特别标识) #[inline(always)] const fn bool_false() -> bool { false @@ -246,6 +267,8 @@ impl TryFrom for RuntimeConfig { fn try_from(config: LaunchConfig) -> Result { Ok(Self { + // * 路径承袭:空值自动补默认值(空白) + config_path: config.config_path.unwrap_or_default(), // * 🚩必选项统一用`ok_or(..)?` translators: config.translators.ok_or(anyhow!("启动配置缺少转译器"))?, command: config.command.ok_or(anyhow!("启动配置缺少启动命令"))?, @@ -458,11 +481,16 @@ impl LaunchConfig { Ok(()) } - /// 变基配置中所含的路径,从其它地方变为 + /// 变基配置中所含的路径,从其它地方变为「以配置文件自身为根」的绝对路径 /// * 🎯解决「配置中的**相对路径**仅相对于exe而非配置文件本身」的问题 /// * 🎯将配置中相对路径的**根目录**从「exe」变更到配置文件本身 /// * 📌原则:由此消灭所有相对路径,均以「配置文件自身路径」为根,转换为绝对路径 + /// * 一同决定的还有其中的[`Self::config_path`]字段 pub fn rebase_relative_path_from(&mut self, config_path: &Path) -> Result<()> { + // 配置所在目录 + if let Some(root) = config_path.parent() { + self.config_path = Some(root.to_path_buf()); + } // 预加载NAL if let Some(LaunchConfigPreludeNAL::File(ref mut path)) = &mut self.prelude_nal { Self::rebase_relative_path(config_path, path)?; diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/bin/babelnar_cli/websocket_server.rs index fae8dd4..70ade4a 100644 --- a/src/bin/babelnar_cli/websocket_server.rs +++ b/src/bin/babelnar_cli/websocket_server.rs @@ -169,6 +169,7 @@ where &msg.to_string(), config, output_cache, + &config.config_path ) => err => [Error] "在Websocket连接中输入「{msg}」时发生错误:{err}" } diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs index 06f93b4..ed6e862 100644 --- a/src/cli_support/io/navm_output_cache.rs +++ b/src/cli_support/io/navm_output_cache.rs @@ -102,7 +102,7 @@ impl VmOutputCache for OutputCache { /// 遍历输出 /// * 🚩不是返回迭代器,而是用闭包开始计算 - fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result> { + fn for_each(&self, mut f: impl FnMut(&Output) -> ControlFlow) -> Result> { // 遍历 for output in self.inner.iter() { // 基于控制流的运行 diff --git a/src/test_tools/nal_format/mod.rs b/src/test_tools/nal_format/mod.rs index 331b1f6..f9698fb 100644 --- a/src/test_tools/nal_format/mod.rs +++ b/src/test_tools/nal_format/mod.rs @@ -132,6 +132,12 @@ fn fold_pest(pair: Pair) -> Result { let output_expectation = fold_pest_output_expectation(output_expectation)?; Ok(NALInput::ExpectContains(output_expectation)) } + // 魔法注释/保存输出 + Rule::comment_save_outputs => { + // 取其中唯一一个「输出预期」 + let file_path = pair.into_inner().next().unwrap().as_str().into(); + Ok(NALInput::SaveOutputs(file_path)) + } // 魔法注释/终止 Rule::comment_terminate => { // 预置默认值 diff --git a/src/test_tools/nal_format/nal_grammar.pest b/src/test_tools/nal_format/nal_grammar.pest index f446038..fcfe616 100644 --- a/src/test_tools/nal_format/nal_grammar.pest +++ b/src/test_tools/nal_format/nal_grammar.pest @@ -29,7 +29,7 @@ cyc_uint = { ASCII_DIGIT+ } /// 注释(静默) /// * 🚩包括「输出预期」等「魔法注释」 comment = _{ - comment_head ~ (comment_navm_cmd | comment_sleep | comment_await | comment_expect_contains | comment_terminate | comment_raw) + comment_head ~ (comment_navm_cmd | comment_sleep | comment_await | comment_expect_contains | comment_save_outputs | comment_terminate | comment_raw) } /// 注释的头部字符(静默) @@ -69,6 +69,13 @@ comment_expect_contains = { "'expect-contains:" ~ output_expectation } +/// 有关「保存输出」的「魔法注释」 +/// ✨存储缓存的所有输出到指定路径下的文件(阻塞主线程) +comment_save_outputs = { + // 额外的前缀 + "'save-outputs:" ~ output_expectation +} + /// 有关「终止」的「魔法注释」 /// ✨终止NAVM虚拟机 /// * 📄参数:选项、理由 diff --git a/src/test_tools/structs.rs b/src/test_tools/structs.rs index 7b4c081..1243232 100644 --- a/src/test_tools/structs.rs +++ b/src/test_tools/structs.rs @@ -35,6 +35,11 @@ pub enum NALInput { /// * 📄对应OpenNARS中常有的`''outputMustContain('')` ExpectContains(OutputExpectation), + /// 保存「输出缓存」到指定文件 + /// * 📄语法示例:`''save-outputs: outputs.log` + /// * 🎯用于「将现有所有输出以『NAVM输出的JSON格式』存档至指定文件中」 + SaveOutputs(String), + /// 终止虚拟机 /// * 🎯用于「预加载NAL『测试』结束后,程序自动退出/交给用户输入」 /// * 📄语法示例: diff --git a/src/test_tools/vm_interact.rs b/src/test_tools/vm_interact.rs index 36e8b8f..561f6ca 100644 --- a/src/test_tools/vm_interact.rs +++ b/src/test_tools/vm_interact.rs @@ -1,6 +1,6 @@ //! 与NAVM虚拟机的交互逻辑 -use std::ops::ControlFlow; +use std::{ops::ControlFlow, path::Path}; use crate::cli_support::error_handling_boost::error_anyhow; @@ -198,7 +198,7 @@ pub trait VmOutputCache { /// * 🚩不是返回迭代器,而是用闭包开始计算 /// * 📝使用最新的「控制流」数据结构 /// * 使用[`None`]代表「一路下来没`break`」 - fn for_each(&self, f: impl Fn(&Output) -> ControlFlow) -> Result>; + fn for_each(&self, f: impl FnMut(&Output) -> ControlFlow) -> Result>; } /// 向虚拟机置入[`NALInput`] @@ -211,6 +211,7 @@ pub fn put_nal( output_cache: &mut impl VmOutputCache, // 不能传入「启动配置」,就要传入「是否启用用户输入」状态变量 enabled_user_input: bool, + nal_root_path: &Path, ) -> Result<()> { match input { // 置入NAVM指令 @@ -265,6 +266,34 @@ pub fn put_nal( // } // } } + // 保存(所有)输出 + // * 🚩输出到一个文本文件中 + // * ✨复合JSON「对象数组」格式 + NALInput::SaveOutputs(path_str) => { + // 先收集所有输出的字符串 + let mut file_str = "[".to_string(); + output_cache.for_each(|output| { + // 换行制表 + file_str += "\n\t"; + // 统一追加到字符串中 + file_str += &output.to_json_string(); + // 逗号 + file_str.push(','); + // 继续 + ControlFlow::<()>::Continue(()) + })?; + // 删去尾后逗号 + file_str.pop(); + // 换行,终止符 + file_str += "\n]"; + // 保存到文件中 | 使用基于`nal_root_path`的相对路径 + let path = nal_root_path.join(path_str.trim()); + std::fs::write(path, file_str)?; + // 提示 | ❌【2024-04-09 22:22:04】执行「NAL输入」时,应始终静默 + // println_cli!([Info] "已将所有NAVM输出保存到文件{path:?}"); + // 返回 + Ok(()) + } // 终止虚拟机 NALInput::Terminate { if_not_user, diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal index 576a5ba..dfb2cd5 100644 --- a/src/tests/nal/test_operation.nal +++ b/src/tests/nal/test_operation.nal @@ -7,6 +7,9 @@ ' * 📄预期「回答」:`''expect-contains: ANSWER 【CommonNarsese】` ' * 📄预期「操作」:`''expect-contains: EXE (^【操作名】, 【操作参数(CommonNarsese词项)】)` ' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出 +' 日志存储 +' * ✨存储所有「NAVM输出」到指定文件(JSON格式):`''save-outputs: 【文件相对路径】` +' * 📌以`.nal`文件自身所在目录为根目录 ' 降低音量,减少无关输出 ' * 📄【2024-04-03 11:51:12】目前输出过多会造成CLI轻微卡顿 @@ -56,4 +59,9 @@ G3! :|: ''expect-contains: EXE (^left, {SELF}, (*, P1, P2)) ''sleep: 500ms + +' * 保存输出 +''save-outputs: nal_operation_outputs.log.json + +' * 终止CIN ''terminate(if-no-user) From 9e28776439aa75112e77fbf9140db51a660b5a08 Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 9 Apr 2024 23:26:12 +0800 Subject: [PATCH 59/59] =?UTF-8?q?feat:=20:sparkles:=20ONA=E8=BD=AC?= =?UTF-8?q?=E8=AF=91=E5=99=A8=E3=80=8CANTICIPATE=E3=80=8D=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E8=BD=AC=E8=AF=91=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于正则匹配+Narsese方言解析,对ONA的「decision expectation」支持转译成「ANTICIPATE」类型输出 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bin/babelnar_cli/runtime_manage.rs | 1 - src/cin_implements/ona/translators.rs | 35 ++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4bf744..b7bfb4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "babel_nar" -version = "0.20.0" +version = "0.20.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 93e281f..505979b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babel_nar" -version = "0.20.0" +version = "0.20.1" edition = "2021" description = """ Implementation and application supports of the NAVM model diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/bin/babelnar_cli/runtime_manage.rs index 0002a60..652e6ba 100644 --- a/src/bin/babelnar_cli/runtime_manage.rs +++ b/src/bin/babelnar_cli/runtime_manage.rs @@ -265,7 +265,6 @@ where { // 缓存输出 // * 🚩在缓存时格式化输出 - // TODO: 【2024-04-08 19:15:30】现在必须不再能直接`put`输出了:要兼容Websocket情形 match output_cache.lock() { Ok(mut output_cache) => output_cache.put(output)?, Err(e) => eprintln_cli!([Error] "缓存NAVM运行时输出时发生错误:{e}"), diff --git a/src/cin_implements/ona/translators.rs b/src/cin_implements/ona/translators.rs index 051334f..0721261 100644 --- a/src/cin_implements/ona/translators.rs +++ b/src/cin_implements/ona/translators.rs @@ -25,6 +25,7 @@ use super::dialect::parse as parse_dialect_ona; use crate::{ cin_implements::ona::{fold_pest_compound, DialectParser, Rule}, + cli_support::io::output_print::OutputType, runtimes::TranslateError, }; use anyhow::Result; @@ -180,6 +181,13 @@ pub fn output_translate(content_raw: String) -> Result { operation: parse_operation_ona(&content_raw)?, content_raw, }, + // * 🚩对于「决策预期→ANTICIPATE」的特殊语法 + // * 🚩【2024-04-02 18:45:17】仅截取`executed with args`,不截取`executed by NAR` + _ if content_raw.contains("decision expectation=") => Output::UNCLASSIFIED { + r#type: "ANTICIPATE".into(), + narsese: parse_anticipate_ona(&content_raw)?, + content: content_raw, + }, // 若是连续的「头部」⇒识别为「未归类」类型 _ if !content_raw.contains(char::is_whitespace) => Output::UNCLASSIFIED { r#type: head.into(), @@ -244,6 +252,33 @@ pub fn parse_operation_ona(content_raw: &str) -> Result { } } +/// (ONA)从原始输出中解析「ANTICIPATE」预期 +/// * 🚩通过「前缀正则截取」分割并解析随后Narsese获得 +/// * 📄`"decision expectation=0.502326 implication: <((<{SELF} --> [good]> &/ b>) &/ <(* {SELF}) --> ^left>) =/> <{SELF} --> [good]>>. Truth: frequency=0.872512 confidence=0.294720 dt=12.000000 precondition: (<{SELF} --> [good]> &/ b>). :|: Truth: frequency=1.000000 confidence=0.360000 occurrenceTime=35124\n"` +/// * 📄`"decision expectation=0.578198 implication: <(a &/ ^left) =/> g>. Truth: frequency=1.000000 confidence=0.241351 dt=1.000000 precondition: a. :|: Truth: frequency=1.000000 confidence=0.900000 occurrenceTime=4\n"` +pub fn parse_anticipate_ona(content_raw: &str) -> Result> { + // 正则捕获 + let re_operation = Regex::new(r"implication:\s*(.*)\s*dt=").unwrap(); + let captures = re_capture(&re_operation, content_raw.trim())?; + match captures { + Some(captures) => { + // 获取内容 + let narsese_content = captures[1].to_string(); + // 解析 + let parse_result = + parse_narsese_ona("ANTICIPATE", narsese_content.trim()).inspect_err(|e| { + OutputType::Error.eprint_line(&format!("ONA「预期」解析失败:{e}")); + }); + // 返回 + parse_result + } + // 截取失败的情形 + None => { + OutputType::Error.eprint_line(&format!("ONA「预期」正则捕获失败:{content_raw:?}")); + Ok(None) + } + } +} /// 操作参数提取 /// * 🎯从一个解析出来的词项中提取出「操作参数列表」 /// * 🚩测试环境中仅允许「复合词项」被解包