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