From 2357f6f75bd23dc647d0a7b0da3dec24036804ca Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:50:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20:truck:=20=E5=88=9D=E6=AD=A5CLI?= =?UTF-8?q?=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从BabelNAR.rs中拆分出BabelNAR-CLI.rs,以分离程序依赖 --- Cargo.toml | 106 +-- README.md | 52 +- build.bat | 4 +- 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 => }/arg_parse.rs | 0 src/bin/cin_launcher/main.rs | 151 ---- src/bin/ws_server_test/main.rs | 169 ----- src/cin_implements/common/exe.rs | 47 -- src/cin_implements/common/java.rs | 79 -- src/cin_implements/common/julia.rs | 48 -- src/cin_implements/common/mod.rs | 16 - src/cin_implements/common/node_js.rs | 61 -- src/cin_implements/common/python.rs | 59 -- src/cin_implements/cxin_js/launcher.rs | 58 -- src/cin_implements/cxin_js/mod.rs | 79 -- src/cin_implements/cxin_js/translators.rs | 163 ---- src/cin_implements/mod.rs | 46 -- src/cin_implements/nars_python/dialect.rs | 36 - src/cin_implements/nars_python/launcher.rs | 53 -- src/cin_implements/nars_python/mod.rs | 97 --- src/cin_implements/nars_python/translators.rs | 79 -- src/cin_implements/native/mod.rs | 9 - src/cin_implements/native/translators.rs | 27 - src/cin_implements/ona/dialect.rs | 386 ---------- src/cin_implements/ona/dialect_ona.pest | 138 ---- src/cin_implements/ona/launcher.rs | 78 -- src/cin_implements/ona/mod.rs | 52 -- src/cin_implements/ona/translators.rs | 696 ------------------ src/cin_implements/openjunars/launcher.rs | 58 -- src/cin_implements/openjunars/mod.rs | 78 -- src/cin_implements/openjunars/translators.rs | 69 -- src/cin_implements/opennars/dialect.rs | 324 -------- .../opennars/dialect_opennars.pest | 126 ---- src/cin_implements/opennars/launcher.rs | 76 -- src/cin_implements/opennars/mod.rs | 55 -- src/cin_implements/opennars/translators.rs | 207 ------ src/cin_implements/pynars/launcher.rs | 57 -- src/cin_implements/pynars/mod.rs | 58 -- src/cin_implements/pynars/translators.rs | 308 -------- src/cli_support/cin_search/anyhow_vm.rs | 125 ---- .../cin_search/impls_path_builder/mod.rs | 74 -- .../impls_path_builder/path_builder_ona.rs | 85 --- .../path_builder_opennars.rs | 82 --- src/cli_support/cin_search/mod.rs | 22 - src/cli_support/cin_search/name_match.rs | 79 -- src/cli_support/cin_search/path_builder.rs | 66 -- src/cli_support/cin_search/path_walker.rs | 240 ------ src/cli_support/error_handling_boost.rs | 36 - src/cli_support/io/mod.rs | 17 - src/cli_support/io/navm_output_cache.rs | 118 --- src/cli_support/io/output_print.rs | 293 -------- src/cli_support/io/readline_iter.rs | 61 -- src/cli_support/io/websocket.rs | 214 ------ src/cli_support/mod.rs | 14 - src/{bin/babelnar_cli => }/config_launcher.rs | 0 src/{bin/babelnar_cli => }/config_search.rs | 0 src/lib.rs | 136 ---- src/{bin/babelnar_cli => }/main.rs | 0 src/output_handler/flow_handler_list.rs | 224 ------ src/output_handler/mod.rs | 5 - src/process_io/io_process.rs | 630 ---------------- src/process_io/mod.rs | 9 - src/{bin/babelnar_cli => }/runtime_manage.rs | 0 .../command_vm/api/command_generator.rs | 11 - src/runtimes/command_vm/api/mod.rs | 10 - src/runtimes/command_vm/api/translators.rs | 199 ----- src/runtimes/command_vm/launcher.rs | 83 --- src/runtimes/command_vm/mod.rs | 11 - src/runtimes/command_vm/runtime.rs | 558 -------------- src/runtimes/mod.rs | 8 - src/test_tools/mod.rs | 15 - src/test_tools/nal_format/mod.rs | 358 --------- src/test_tools/nal_format/nal_grammar.pest | 247 ------- src/test_tools/structs.rs | 124 ---- src/test_tools/vm_interact/mod.rs | 264 ------- .../vm_interact/narsese_expectation.rs | 445 ----------- src/test_tools/vm_interact/term_equal.rs | 526 ------------- .../cli/config/_arg_parse_test.opennars.hjson | 23 - src/tests/cli/config/cin_cxin_js.hjson | 19 - src/tests/cli/config/cin_native_il_1.hjson | 16 - src/tests/cli/config/cin_ona.hjson | 22 - src/tests/cli/config/cin_opennars.hjson | 23 - src/tests/cli/config/cin_opennars_158.hjson | 23 - src/tests/cli/config/cin_pynars.hjson | 15 - src/tests/cli/config/matriangle_server.hjson | 18 - .../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/nal_truth_wildcard.hjson | 10 - src/tests/cli/config/prelude_test.hjson | 14 - src/tests/cli/config/websocket.hjson | 8 - src/tests/nal/test_higher_deduction.nal | 33 - src/tests/nal/test_i_var_elimination.nal | 24 - src/tests/nal/test_operation.nal | 67 -- src/tests/nal/test_simple_deduction.nal | 34 - src/tests/nal/test_simple_operation.nal | 34 - src/tests/nal/test_temporal_induction.nal | 36 - src/tests/nal/test_truth_wildcard.nal | 22 - src/{bin/babelnar_cli => }/vm_config.rs | 0 .../babelnar_cli => }/websocket_server.rs | 0 107 files changed, 29 insertions(+), 10076 deletions(-) rename src/{bin/babelnar_cli => }/arg_parse.rs (100%) delete mode 100644 src/bin/cin_launcher/main.rs delete mode 100644 src/bin/ws_server_test/main.rs delete mode 100644 src/cin_implements/common/exe.rs delete mode 100644 src/cin_implements/common/java.rs delete mode 100644 src/cin_implements/common/julia.rs delete mode 100644 src/cin_implements/common/mod.rs delete mode 100644 src/cin_implements/common/node_js.rs delete mode 100644 src/cin_implements/common/python.rs delete mode 100644 src/cin_implements/cxin_js/launcher.rs delete mode 100644 src/cin_implements/cxin_js/mod.rs delete mode 100644 src/cin_implements/cxin_js/translators.rs delete mode 100644 src/cin_implements/mod.rs delete mode 100644 src/cin_implements/nars_python/dialect.rs delete mode 100644 src/cin_implements/nars_python/launcher.rs delete mode 100644 src/cin_implements/nars_python/mod.rs delete mode 100644 src/cin_implements/nars_python/translators.rs delete mode 100644 src/cin_implements/native/mod.rs delete mode 100644 src/cin_implements/native/translators.rs delete mode 100644 src/cin_implements/ona/dialect.rs delete mode 100644 src/cin_implements/ona/dialect_ona.pest delete mode 100644 src/cin_implements/ona/launcher.rs delete mode 100644 src/cin_implements/ona/mod.rs delete mode 100644 src/cin_implements/ona/translators.rs delete mode 100644 src/cin_implements/openjunars/launcher.rs delete mode 100644 src/cin_implements/openjunars/mod.rs delete mode 100644 src/cin_implements/openjunars/translators.rs delete mode 100644 src/cin_implements/opennars/dialect.rs delete mode 100644 src/cin_implements/opennars/dialect_opennars.pest delete mode 100644 src/cin_implements/opennars/launcher.rs delete mode 100644 src/cin_implements/opennars/mod.rs delete mode 100644 src/cin_implements/opennars/translators.rs delete mode 100644 src/cin_implements/pynars/launcher.rs delete mode 100644 src/cin_implements/pynars/mod.rs delete mode 100644 src/cin_implements/pynars/translators.rs delete mode 100644 src/cli_support/cin_search/anyhow_vm.rs delete mode 100644 src/cli_support/cin_search/impls_path_builder/mod.rs delete mode 100644 src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs delete mode 100644 src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs delete mode 100644 src/cli_support/cin_search/mod.rs delete mode 100644 src/cli_support/cin_search/name_match.rs delete mode 100644 src/cli_support/cin_search/path_builder.rs delete mode 100644 src/cli_support/cin_search/path_walker.rs delete mode 100644 src/cli_support/error_handling_boost.rs delete mode 100644 src/cli_support/io/mod.rs delete mode 100644 src/cli_support/io/navm_output_cache.rs delete mode 100644 src/cli_support/io/output_print.rs delete mode 100644 src/cli_support/io/readline_iter.rs delete mode 100644 src/cli_support/io/websocket.rs delete mode 100644 src/cli_support/mod.rs rename src/{bin/babelnar_cli => }/config_launcher.rs (100%) rename src/{bin/babelnar_cli => }/config_search.rs (100%) delete mode 100644 src/lib.rs rename src/{bin/babelnar_cli => }/main.rs (100%) delete mode 100644 src/output_handler/flow_handler_list.rs delete mode 100644 src/output_handler/mod.rs delete mode 100644 src/process_io/io_process.rs delete mode 100644 src/process_io/mod.rs rename src/{bin/babelnar_cli => }/runtime_manage.rs (100%) delete mode 100644 src/runtimes/command_vm/api/command_generator.rs delete mode 100644 src/runtimes/command_vm/api/mod.rs delete mode 100644 src/runtimes/command_vm/api/translators.rs delete mode 100644 src/runtimes/command_vm/launcher.rs delete mode 100644 src/runtimes/command_vm/mod.rs delete mode 100644 src/runtimes/command_vm/runtime.rs delete mode 100644 src/runtimes/mod.rs delete mode 100644 src/test_tools/mod.rs delete mode 100644 src/test_tools/nal_format/mod.rs delete mode 100644 src/test_tools/nal_format/nal_grammar.pest delete mode 100644 src/test_tools/structs.rs delete mode 100644 src/test_tools/vm_interact/mod.rs delete mode 100644 src/test_tools/vm_interact/narsese_expectation.rs delete mode 100644 src/test_tools/vm_interact/term_equal.rs delete mode 100644 src/tests/cli/config/_arg_parse_test.opennars.hjson delete mode 100644 src/tests/cli/config/cin_cxin_js.hjson delete mode 100644 src/tests/cli/config/cin_native_il_1.hjson delete mode 100644 src/tests/cli/config/cin_ona.hjson delete mode 100644 src/tests/cli/config/cin_opennars.hjson delete mode 100644 src/tests/cli/config/cin_opennars_158.hjson delete mode 100644 src/tests/cli/config/cin_pynars.hjson delete mode 100644 src/tests/cli/config/matriangle_server.hjson delete mode 100644 src/tests/cli/config/nal_higher_deduction.hjson delete mode 100644 src/tests/cli/config/nal_i_var_elimination.hjson delete mode 100644 src/tests/cli/config/nal_operation.hjson delete mode 100644 src/tests/cli/config/nal_simple_deduction.hjson delete mode 100644 src/tests/cli/config/nal_simple_operation.hjson delete mode 100644 src/tests/cli/config/nal_temporal_induction.hjson delete mode 100644 src/tests/cli/config/nal_truth_wildcard.hjson delete mode 100644 src/tests/cli/config/prelude_test.hjson delete mode 100644 src/tests/cli/config/websocket.hjson delete mode 100644 src/tests/nal/test_higher_deduction.nal delete mode 100644 src/tests/nal/test_i_var_elimination.nal delete mode 100644 src/tests/nal/test_operation.nal delete mode 100644 src/tests/nal/test_simple_deduction.nal delete mode 100644 src/tests/nal/test_simple_operation.nal delete mode 100644 src/tests/nal/test_temporal_induction.nal delete mode 100644 src/tests/nal/test_truth_wildcard.nal rename src/{bin/babelnar_cli => }/vm_config.rs (100%) rename src/{bin/babelnar_cli => }/websocket_server.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 5d02a92..2a4982b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,19 @@ [package] -name = "babel_nar" -version = "0.25.3" +name = "babel_nar_cli" +version = "0.1.0" edition = "2021" description = """ -Implementation and application supports of the NAVM model +Command Line Interface (CLI) of BabelNAR.rs """ readme = "README.md" # ! ⚠️↓crates.io的每个关键词长度不能超过20字符 -keywords = ["NARS", "Non-Axiomatic-Logic", "NAVM"] +keywords = ["NARS", "NAVM", "BabelNAR"] license = "MIT OR Apache-2.0" categories = [ - "parser-implementations", # 解析器实现 | 各CIN方言 - "development-tools", # 开发工具 "command-line-utilities", # CLI应用 ] # 🔗 -repository = "https://github.com/ARCJ137442/BabelNAR.rs" +repository = "https://github.com/ARCJ137442/BabelNAR-CLI" # Cargo文档参考: @@ -59,6 +57,10 @@ version = "0.17" # 现已发布于`crates.io` # ! 【2024-03-23 19:19:01】似乎Rust-Analyzer无法获取私有仓库数据 features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 +[dependencies.babel_nar] +version = "0.25" +features = ["bundled"] + ## 依赖特性的可选依赖 ## # Rust版本的正则表达式 @@ -66,116 +68,36 @@ features = [] # ! 【2024-03-21 09:24:51】暂时没有特性 # * 📄OpenNARS、ONA、PyNARS [dependencies.regex] version = "1.10.6" -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.12" -optional = true # 命令行支持/彩色终端 [dependencies.colored] version = "2.1.0" -optional = true # 命令行支持/(H)JSON配置解析 [dependencies.serde] version = "1.0.210" -optional = true features = ["derive"] [dependencies.serde_json] version = "1.0.128" -optional = true [dependencies.deser-hjson] version = "2.2.4" -optional = true # 命令行支持/Websocket服务 [dependencies.ws] version = "0.9.2" -optional = true # 命令行支持/命令行参数解析 [dependencies.clap] version = "4.5.17" features = ["derive"] -optional = true ### 定义库的特性 ### [features] -## 默认启用的特性 ## -default = [ "bundled" ] # * 默认启用所有(可选禁用) -## 大杂烩 ## -bundled = [ - "cin_implements", # 各大CIN的NAVM实现 - "cli_support", # 命令行支持 - "test_tools", # 测试工具集 -] - -## 各个独立的特性 ## - -# 具体接口实现(虚拟机启动器) # -# ✅OpenNARS -# ✅ONA -# ✅PyNARS -# ✅NARS-Python(不稳定) -# ✅OpenJunars(不稳定) -cin_implements = [ - "opennars", - "ona", - "pynars", - "nars_python", - "openjunars", -] -# ✅OpenNARS接口 -opennars = [ - "regex", - "pest", "pest_derive", -] -# ✅ONA接口 -ona = [ - "regex", - "pest", "pest_derive", -] -# ✅PyNARS接口 -pynars = [ - "regex", - # "pest", # ! 【2024-03-27 20:52:17】无需特别解析方言:其输出即为CommonNarsese -] -# ✅NARS-Python接口(不稳定) -nars_python = [ - "lazy_static", # 这个「词法Narsese」也在用 -] -# ✅OpenJunars接口(不稳定) -openjunars = [] - -# 命令行支持 # -cli_support = [ - "colored", # 命令行io 彩色打印 - "serde", "serde_json", "deser-hjson", # 配置文件解析 - "ws", # 命令行io Websocket服务 - "clap" # 命令行参数解析 -] - -# 测试工具集 # -test_tools = [ - # 统一`.nal`格式 - "pest", "pest_derive", -] +# * 🚩【2024-09-12 16:27:50】暂时没有需要启用的特性 +# ## 默认启用的特性 ## +# default = [ "bundled" ] # * 默认启用所有(可选禁用) +# ## 大杂烩 ## +# bundled = [] diff --git a/README.md b/README.md index fa5a44f..9748c1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BabelNAR.rs +# BabelNAR-CLI.rs 简体中文 | [English](./README.en-us.md) @@ -8,12 +8,9 @@ 该项目使用[语义化版本 2.0.0](https://semver.org/)进行版本号管理。 -[**NAVM.rs**](https://github.com/ARCJ137442/NAVM.rs)对[CIN](#cin-computer-implement-of-nars)的**启动器**、**运行时**及应用程序实现 +[**BabelNAR.rs**](https://github.com/ARCJ137442/NAVM.rs)的命令行接口(CLI) -- 前身为[**BabelNAR.jl**](https://github.com/ARCJ137442/BabelNAR.jl) -- ✨为「非公理虚拟机模型」提供程序实现 -- ✨统一各[CIN](#cin-computer-implement-of-nars)的**输入输出**形式,聚合使用各大NARS实现 -- ✨提供一个方便使用的CLI工具,以便从配置中快速启动、测试各类NARS实现 +- ✨从配置中快速启动、测试各类NARS实现 - 🎯一站式NARS**启动器** - 🎯NARS**交叉测试**工具 @@ -23,45 +20,14 @@ ## 使用 -### CLI - -- 使用 JSON/HJSON 配置文件 (`.json`/`.hjson`) - - 格式可参考项目测试代码 +- 依照使用 JSON/HJSON 配置文件 (`.json`/`.hjson`) + - 格式可参考 + - `config_public` 中的配置文件 + - 项目测试代码 - Rust调用侧:可参考项目测试代码 🏗️TODO(接受贡献) -## 概念 - -### 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的「输出捕获」尚未有成功记录 - ## CLI测试:各CIN完成度评估 🕒最后更新时间:【2024-04-07 16:52:29】 @@ -79,6 +45,4 @@ ## 参考 -- [BabelNAR](https://github.com/ARCJ137442/BabelNAR.jl) -- [BabelNAR_Implements](https://github.com/ARCJ137442/BabelNAR_Implements.jl) -- [NAVM.rs](https://github.com/ARCJ137442/NAVM.rs) +- [BabelNAR.rs](https://github.com/ARCJ137442/BabelNAR.rs) diff --git a/build.bat b/build.bat index 0e4ef4d..657981c 100644 --- a/build.bat +++ b/build.bat @@ -4,7 +4,7 @@ @echo off @REM 构建BabelNAR CLI(基于cargo) -cargo b -r --bin babelnar_cli +cargo b -r @REM 重置dist文件夹 rm -rf dist @@ -16,7 +16,7 @@ mkdir .\dist xcopy /E /I .\config_public .\dist\nars_config @REM 拷贝BabelNAR CLI -copy .\target\release\babelnar_cli.exe .\dist +copy .\target\release\babel_nar_cli.exe .\dist\babelnar_cli.exe @REM 拷贝指定的可执行文件 xcopy /E /I .\executables\PyNARS .\dist\executables\PyNARS diff --git a/scripts/windows/release-launch-cxin_js.cmd b/scripts/windows/release-launch-cxin_js.cmd index efde7ce..937761e 100644 --- a/scripts/windows/release-launch-cxin_js.cmd +++ b/scripts/windows/release-launch-cxin_js.cmd @@ -1,2 +1,2 @@ @echo off -".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_cxin_js.hjson" +".\..\..\target\release\babel_nar_cli.exe" -c ".\..\..\config_public\cin_cxin_js.hjson" diff --git a/scripts/windows/release-launch-ona.cmd b/scripts/windows/release-launch-ona.cmd index 9cd064b..5156c07 100644 --- a/scripts/windows/release-launch-ona.cmd +++ b/scripts/windows/release-launch-ona.cmd @@ -1,2 +1,2 @@ @echo off -".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_ona.hjson" +".\..\..\target\release\babel_nar_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 index 65180b7..5e99eec 100644 --- a/scripts/windows/release-launch-opennars.cmd +++ b/scripts/windows/release-launch-opennars.cmd @@ -1,2 +1,2 @@ @echo off -".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_opennars.hjson" +".\..\..\target\release\babel_nar_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 index 9aa05ce..62c1722 100644 --- a/scripts/windows/release-launch-pynars.cmd +++ b/scripts/windows/release-launch-pynars.cmd @@ -1,2 +1,2 @@ @echo off -".\..\..\target\release\babelnar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_pynars.hjson" +".\..\..\target\release\babel_nar_cli.exe" -c ".\..\..\src\tests\cli\config\cin_pynars.hjson" diff --git a/scripts/windows/release-launch.cmd b/scripts/windows/release-launch.cmd index 5a64442..8e75764 100644 --- a/scripts/windows/release-launch.cmd +++ b/scripts/windows/release-launch.cmd @@ -1,2 +1,2 @@ @echo off -".\..\..\target\release\babelnar_cli.exe" +".\..\..\target\release\babel_nar_cli.exe" diff --git a/src/bin/babelnar_cli/arg_parse.rs b/src/arg_parse.rs similarity index 100% rename from src/bin/babelnar_cli/arg_parse.rs rename to src/arg_parse.rs diff --git a/src/bin/cin_launcher/main.rs b/src/bin/cin_launcher/main.rs deleted file mode 100644 index b4f74fe..0000000 --- a/src/bin/cin_launcher/main.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! 一个一站式启动各CIN的启动器 -//! * 🎯方便启动、管理各「作为NAVM运行时的CIN」的聚合终端 -//! * 📌用于集成原先「BabelNAR」「BabelNAR_Implements」两个库 -//! * ✨自动根据可执行文件、配置文件、用户输入猜测CIN类型(字符串匹配) -//! * ✨自动查找(可能)可用的CIN可执行文件(文件搜索) -//! * 📌可根据「匹配度」排名 -//! * ✨自动启动并管理CIN -//! * 📌可保存/加载「常用CIN」配置 -//! -//! * 🚩目前用于敏捷原型开发 -#![allow(unused)] - -use anyhow::Result; -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::{ - cmd::Cmd, - output::Output, - vm::{VmLauncher, VmRuntime}, -}; -use std::{fmt::Debug, io::stdin}; - -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】目前就返回一个测试用的运行时 -/// * 🎯敏捷开发用 -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) -} - -/// 主函数 -/// * 🚩【2024-04-02 20:58:07】现在更完整的支持交给BabelNAR CLI,此文件用于敏捷开发 -fn main() { - // 不断开始🔥 - loop { - start(); - } -} - -/// 开始 -fn start() { - let nars = get_nars().launch().expect("无法启动虚拟机"); - 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(|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(|e| eprintln_cli!([Error] "拉取NAVM运行时输出时发生错误:{e}")) - { - println!("{output:?}"); - if let Output::TERMINATED { description } = output { - println_cli!([Info] "NAVM已终止运行:{description}"); - nars.terminate(); - break 'main; // ! 这个告诉Rust编译器,循环必将在此结束 - } - } - - // 清空缓冲区 - input.clear(); - } -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - 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; - - 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/bin/ws_server_test/main.rs b/src/bin/ws_server_test/main.rs deleted file mode 100644 index faa0381..0000000 --- a/src/bin/ws_server_test/main.rs +++ /dev/null @@ -1,169 +0,0 @@ -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] -#[ignore = "【2024-04-14 20:38:45】仅作为其它Websocket服务端的压力测试,不应在库测试中出现"] -fn test_overwhelming_nse() { - 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] -#[ignore = "【2024-04-14 20:38:45】仅作为其它Websocket服务端的压力测试,不应在库测试中出现"] -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/common/exe.rs b/src/cin_implements/common/exe.rs deleted file mode 100644 index 9cbb427..0000000 --- a/src/cin_implements/common/exe.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! 可执行文件(exe)启动器 -//! * 🎯适用于任何直接从可执行文件(可能带参数)启动的CIN -//! * 📄ONA -//! * 📄NARS-Python -//! * 🚩【2024-03-28 10:00:00】暂且只需提供[`Command`]生成函数 -//! * ❗没必要使用新的数据结构 - -use crate::runtimes::{CommandVm, IoTranslators}; -use nar_dev_utils::manipulate; -use std::{ffi::OsStr, path::Path, process::Command}; - -/// 根据配置统一生成[`Command`]对象 -/// * 📌「配置」的定义 -/// * exe路径(可能不直接是可执行文件的路径) -/// * 当前文件夹(设置命令启动时的工作目录) -/// * 命令行参数(可以为空) -pub fn generate_command( - exe_path: impl AsRef, - current_dir: Option>, - args: impl IntoIterator, -) -> Command -where - S: AsRef, -{ - // 构造指令 - let mut command = Command::new(exe_path.as_ref()); - - // 设置路径 - 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 { - manipulate!( - CommandVm::from(command) - => .translators(translators) - ) -} diff --git a/src/cin_implements/common/java.rs b/src/cin_implements/common/java.rs deleted file mode 100644 index b8a1ff4..0000000 --- a/src/cin_implements/common/java.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Java jar启动器 -//! * 🎯通用于任何基于jar文件的CIN,不仅仅是OpenNARS -//! * 🎯封装「NAVM运行时启动过程」中有关「Java启动环境配置」的部分 -//! * 🚩从jar文件启动NARS -//! * 🚩【2024-03-27 15:31:02】取消「初始音量」的特化配置,将其变成一个「命令行参数生成器」而非独立的「启动器」 - -use crate::runtimes::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 deleted file mode 100644 index c2e6d7a..0000000 --- a/src/cin_implements/common/julia.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Julia 模块启动器 -//! * 🎯通用于任何基于Julia源码的CIN,不仅仅是OpenJunars -//! * 🎯封装「NAVM运行时启动过程」中有关「Julia启动环境配置」的部分 -//! * 🚩从Julia脚本(`.jl`)启动NARS - -use crate::runtimes::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 deleted file mode 100644 index 7e95a91..0000000 --- a/src/cin_implements/common/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! 所有对接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 deleted file mode 100644 index 3f1a7df..0000000 --- a/src/cin_implements/common/node_js.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Node.js 模块启动器 -//! * 🎯通用于任何基于Node.js脚本的CIN -//! * 🎯封装「NAVM运行时启动过程」中有关「Node.js启动环境配置」的部分 -//! * 🚩从Node.js脚本(`.js`)启动NARS - -use crate::runtimes::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/common/python.rs b/src/cin_implements/common/python.rs deleted file mode 100644 index 647ea4d..0000000 --- a/src/cin_implements/common/python.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Python模块 启动器 -//! * 🎯通用于任何基于Python源码的CIN,不仅仅是PyNARS -//! * 🎯封装「NAVM运行时启动过程」中有关「Python启动环境配置」的部分 -//! * 🚩从Python模块(`.py`脚本)启动NARS - -use crate::runtimes::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/cxin_js/launcher.rs b/src/cin_implements/cxin_js/launcher.rs deleted file mode 100644 index fe86628..0000000 --- a/src/cin_implements/cxin_js/launcher.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! CXinNARS.js运行时的启动器 -//! * 🎯允许CXinNARS.js对原先运行时特别配置功能,同时也支持为CXinNARS.js定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 - -use super::{input_translate, output_translate}; -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; - -/// 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 { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造并启动虚拟机 - pipe! { - self.command_generator - // 构造指令 | 预置的指令参数 - => .generate_command() - // * 🚩固定的「输入输出转译器」 - => generate_command_vm(_, (input_translate, output_translate)) - // 🔥启动 - => .launch() - } - } -} - -// ! 单元测试见[`super`] diff --git a/src/cin_implements/cxin_js/mod.rs b/src/cin_implements/cxin_js/mod.rs deleted file mode 100644 index 6274245..0000000 --- a/src/cin_implements/cxin_js/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! 「非公理虚拟机」的ONA运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 - -// 转译器 -util::mod_and_pub_use! { - // 转译器 - translators - // 启动器 - launcher -} - -/// 单元测试 -#[cfg(test)] -mod tests { - #![allow(unused)] - use super::*; - use crate::runtimes::{ - tests::{await_fetch_until, input_cmd_and_await_contains, test_simple_answer}, - 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"; - - /// 通用/启动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().expect("无法启动虚拟机") - } - - /// 测试/专用 - #[test] - fn test() { - let vm = launch_vm(); - // 进入专用测试 - _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..."); - } - - /// 测试/通用 | 基于Narsese - #[test] - fn test_universal() { - // // 启动OpenNARS虚拟机 - // let vm = launch_vm(); - // // 使用通用测试逻辑 - // test_simple_answer(vm) - } -} diff --git a/src/cin_implements/cxin_js/translators.rs b/src/cin_implements/cxin_js/translators.rs deleted file mode 100644 index 355a6cf..0000000 --- a/src/cin_implements/cxin_js/translators.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! 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::runtimes::TranslateError; -use anyhow::Result; -use narsese::{ - conversion::string::impl_lexical::{format_instances::FORMAT_ASCII, 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-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) - Cmd::REM { .. } => String::new(), - // 退出 ⇒ 特殊命令 | // * 🚩【2024-06-13 00:16:38】最新版本行为 - Cmd::EXI { .. } => "/q".into(), - // 其它类型 - // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(TranslateError::UnsupportedInput(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 { - // 先提取其中的Narsese - narsese: segment_narsese(&head, &tail), - // 然后传入整个内容 - 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!("【{head}】在解析Narsese时出现错误:{e}"); - None - } - None => { - println!("【{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字符串 - FORMAT_ASCII.parse(narsese) -} diff --git a/src/cin_implements/mod.rs b/src/cin_implements/mod.rs deleted file mode 100644 index 29b9f6c..0000000 --- a/src/cin_implements/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! 对各CIN实现「非公理虚拟机」模型 -//! * 🎯基于「NAVM指令/NAVM输出↔字符串」的转换 -//! -//! ! ⚠️关键问题:进程残留 -//! * ✅单元测试中利用`taskkill`初步解决 -//! * ❌【2024-03-25 13:36:30】集成测试`cargo t --all-features`中未能解决 -//! * ❗// ! ↑【少用乃至不用这条命令】 -//! -//! * 🚩【2024-04-09 20:47:04】基本完成对「复用」「性能」与「简洁」的兼顾 -//! * 📌复用:将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基于「特征」的组合式设计哲学下,如何进行兼顾三者的优秀设计 - -util::mods! { - // 共用代码 - pub common; - - // 原生 - pub native; - - // OpenNARS - pub opennars; - - // ONA - pub ona; - - // NARS-Python - pub nars_python; - - // PyNARS - pub pynars; - - // OpenJunars - pub openjunars; - - // CXinNARS.js - pub cxin_js; - -} diff --git a/src/cin_implements/nars_python/dialect.rs b/src/cin_implements/nars_python/dialect.rs deleted file mode 100644 index 7150131..0000000 --- a/src/cin_implements/nars_python/dialect.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! 用于存储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/launcher.rs b/src/cin_implements/nars_python/launcher.rs deleted file mode 100644 index 0a1d9eb..0000000 --- a/src/cin_implements/nars_python/launcher.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! NARS-Python运行时的启动器 -//! * 🎯允许NARS-Python对原先运行时特别配置功能,同时也支持为NARS-Python定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 - -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; - -// ! 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 { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造指令,并启动虚拟机 - manipulate!( - 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 deleted file mode 100644 index 0184be9..0000000 --- a/src/cin_implements/nars_python/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! 「非公理虚拟机」的NARS-Python运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 - -// 转译器 -util::mod_and_pub_use! { - // 方言(Narsese格式) - dialect - // 转译器 - translators - // 启动器 - launcher -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - use crate::{runtimes::CommandVmRuntime, tests::cin_paths::NARS_PYTHON}; - use narsese::conversion::string::impl_lexical::shortcuts::*; - use navm::{ - cmd::Cmd, - vm::{VmLauncher, VmRuntime}, - }; - - #[test] - #[ignore = "【2024-04-14 20:24:52】会导致残留子进程"] - fn test() { - // 从别的地方获取exe路径 - let exe_path = NARS_PYTHON; - // 一行代码启动NARS-Python - let vm = NARSPython::new(exe_path).launch().expect("无法启动虚拟机"); - // 运行专有测试 - _test_nars_python(vm) - } - - /// 测试/NARS-Python - /// 【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)); - - 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指令"); - - 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 deleted file mode 100644 index 98b3df5..0000000 --- a/src/cin_implements/nars_python/translators.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! NARS-Python在「命令行运行时」的转译器 -//! * 🎯维护与NARS-Python exe的交互 -//! * 📌基于命令行输入输出的字符串读写 -//! * ✨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 super::format_in_nars_python; -use crate::runtimes::TranslateError; -use anyhow::Result; -use narsese::lexical::Narsese; -use navm::{ - cmd::Cmd, - output::{Operation, Output}, -}; - -/// NARS-Python的「输入转译」函数 -/// * 🎯用于将统一的「NAVM指令」转译为「NARS-Python输入」 -pub fn input_translate(cmd: Cmd) -> Result { - let content = match cmd { - // 使用「末尾」将自动格式化任务(可兼容「空预算」的形式) - // * ✅【2024-03-26 01:44:49】目前采用特定的「方言格式」解决格式化问题 - Cmd::NSE(narsese) => format_in_nars_python(&Narsese::Task(narsese)), - // 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::UnsupportedInput(cmd).into()), - }; - // 转译 - Ok(content) -} - -/// NARS-Python的「输出转译」函数 -/// * 🎯用于将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(); - // 根据「头部」生成输出 - let output = match &*head { - // TODO: 有待适配 - "answer" => Output::ANSWER { - // TODO: 有待捕获转译 - narsese: None, - content_raw: content, - }, - "derived" => Output::OUT { - // TODO: 有待捕获转译 - narsese: None, - content_raw: content, - }, - "input" => Output::IN { - // TODO: 有待捕获转译 - narsese: None, - content, - }, - "exe" => Output::EXE { - // TODO: 有待捕获转译 - operation: Operation::new("UNKNOWN", []), - content_raw: content, - }, - "err" | "error" => Output::ERROR { - description: content, - }, - _ => Output::OTHER { content }, - }; - // 返回 - Ok(output) -} diff --git a/src/cin_implements/native/mod.rs b/src/cin_implements/native/mod.rs deleted file mode 100644 index f9889ff..0000000 --- a/src/cin_implements/native/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! 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 deleted file mode 100644 index bfd42da..0000000 --- a/src/cin_implements/native/translators.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! 输入输出转译 -//! * ✨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/cin_implements/ona/dialect.rs b/src/cin_implements/ona/dialect.rs deleted file mode 100644 index af641a2..0000000 --- a/src/cin_implements/ona/dialect.rs +++ /dev/null @@ -1,386 +0,0 @@ -//! ONA方言 -//! * 🎯解析ONA输出,如 -//! * 📄以空格分隔的词项:`(* {SELF})` -//! * 📄`({SELF} * x)` - -use crate::runtimes::TranslateError; -use anyhow::{Ok, Result}; -use narsese::{ - api::NarseseOptions, - conversion::string::impl_enum::format_instances::FORMAT_ASCII, - lexical::{Budget, Narsese, Punctuation, Stamp, Term, Truth}, -}; -use pest::{iterators::Pair, Parser}; -use pest_derive::Parser; - -type MidParseResult = NarseseOptions; - -#[derive(Parser)] // ! ↓ 必须从项目根目录开始 -#[grammar = "src/cin_implements/ona/dialect_ona.pest"] -pub struct DialectParser; - -/// 使用[`pest`]将输入的「ONA方言」转换为「词法Narsese」 -/// 以ONA的语法解析出Narsese -/// * 📌重点在「用空格分隔乘积词项/中缀情形」的语法 -/// * 📄`(* {SELF})` -/// * 📄`({SELF} * x)` -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 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 deleted file mode 100644 index d716c00..0000000 --- a/src/cin_implements/ona/dialect_ona.pest +++ /dev/null @@ -1,138 +0,0 @@ -//! ONA方言语法 -//! * 🎯从ONA输出中解析Narsese -//! * 📌复合词项的「中缀形式」「空格分隔」形式 -//! * 📄以空格分隔的词项:`(* {SELF})` -//! * 📄`({SELF} * x)` - -/// 空白符 | 所有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) } - -/// 复合 = (连接词, 词项...) | {外延集...} | [内涵集...] -/// * 🆕对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 - | ext_set - | int_set -} - -/// 通用的复合词项 -compound_common = ${ "(" ~ connecter ~ optional_separator ~ term_list ~ ")" } - -/// 通用的「词项列表」 | 静默展开 -term_list = _{ term ~ (optional_separator ~ term)* } - -/// 可选的分隔符 -optional_separator = _{ - ("," ~ WHITESPACE*) - | WHITESPACE+ -} - -/// 🆕ONA特定的「二元中缀表达法」 -/// * 🚩【2024-03-29 09:40:38】目前通用成`(A, B, C)` => `<(*, B, C) --> A>`的转换方式 -compound_binary = { "(" ~ term ~ connecter ~ term ~ ")" } - -/// 外延集 | 📌【2024-03-29 09:39:39】pest代码折叠中会丢掉所有「不被规则捕获的字符串信息」 -ext_set = { "{" ~ term_list ~ "}" } - -/// 内涵集 -int_set = { "[" ~ term_list ~ "]" } - -/// 复合词项连接词 | ⚠️不包括「逗号」与「圆括号」 -connecter = @{ (!"," ~ !"(" ~ !")" ~ 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/cin_implements/ona/launcher.rs b/src/cin_implements/ona/launcher.rs deleted file mode 100644 index f1d500f..0000000 --- a/src/cin_implements/ona/launcher.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! ONA运行时的启动器 -//! * 🎯允许ONA对原先运行时特别配置功能,同时也支持为ONA定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 - -use super::{input_translate, output_translate}; -use crate::{ - cin_implements::common::{generate_command, generate_command_vm}, - runtimes::CommandVmRuntime, -}; -use anyhow::Result; -use navm::{ - cmd::Cmd, - vm::{VmLauncher, VmRuntime}, -}; -use std::path::PathBuf; -use util::pipe; - -/// ONA Shell启动的默认指令参数 -/// * 🎯默认预置指令:`[.exe文件路径] shell` -const COMMAND_ARGS_ONA: [&str; 1] = ["shell"]; - -/// ONA运行时启动器 -/// * 🎯配置ONA专有的东西 -/// * 🚩基于exe文件启动ONA Shell -/// * 默认预置指令:`[.exe文件路径] shell` -/// * 🚩【2024-03-25 08:51:30】目前保留原有缩写的大小写风格,与OpenNARS、PyNARS一致 -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct ONA { - /// exe文件路径 - exe_path: PathBuf, - /// ONA Shell的初始音量 - /// * 🚩可能没有:此时不会输入指令 - initial_volume: Option, -} - -// ! 🚩【2024-03-25 09:37:22】目前暂时不提取至「VmExe」:预置的`shell`参数需要被处理 -// * ✅【2024-03-27 16:07:48】现在通过作为工具的`generate_command`部分实现了代码复用 - -impl ONA { - /// 构造函数 - pub fn new(exe_path: impl Into) -> Self { - Self { - // 转换为路径 - exe_path: exe_path.into(), - // 其它全是`None` - ..Default::default() - } - } -} - -/// 启动到「命令行运行时」 -impl VmLauncher for ONA { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造并启动虚拟机 - let mut runtime = pipe! { - self.exe_path - // 构造指令 | 预置的指令参数 - => 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 { - // 输入指令,并在执行错误时打印信息 - if let Err(e) = runtime.input_cmd(Cmd::VOL(volume)) { - println!("无法设置初始音量「{volume}」:{e}"); - } - }; - Ok(runtime) - } -} - -// ! 单元测试见[`super`] diff --git a/src/cin_implements/ona/mod.rs b/src/cin_implements/ona/mod.rs deleted file mode 100644 index 67259d5..0000000 --- a/src/cin_implements/ona/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! 「非公理虚拟机」的ONA运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 - -// 转译器 -util::mod_and_pub_use! { - // 转译器 - translators - // 启动器 - launcher - // 方言 | 【2024-03-27 18:42:50】使用`pest`库解析特殊语法 - dialect -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - runtimes::{ - tests::{_test_ona, test_simple_answer}, - CommandVmRuntime, - }, - tests::cin_paths::ONA as EXE_PATH_ONA, - }; - use navm::vm::VmLauncher; - - /// 工具/启动ONA,获得虚拟机运行时 - fn launch_vm() -> CommandVmRuntime { - // 从别的地方获取exe路径 - let exe_path = EXE_PATH_ONA; - // 一行代码启动ONA - ONA::new(exe_path).launch().expect("无法启动虚拟机") - } - - #[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 deleted file mode 100644 index 8343ee4..0000000 --- a/src/cin_implements/ona/translators.rs +++ /dev/null @@ -1,696 +0,0 @@ -//! ONA在「命令行运行时」的转译器 -//! * 🎯维护与ONA 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` -//! -//! ## 其它杂项 -//! -//! 💭【2024-03-29 16:58:01】ONA中「注册操作」可以被翻译成`*setopname 操作ID ^操作符名`的形式 -//! * ⚠️但需要自行保证「操作ID」不重复 -//! * 📄`*setopname 1 ^left` -//! * 🔗参见 - -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; -use narsese::lexical::{Narsese, Term}; -use navm::{ - cmd::Cmd, - output::{type_names::ANTICIPATE, Operation, Output}, -}; -use pest::Parser; -use regex::{Captures, Regex}; -#[cfg(not(test))] -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 { - let content = match cmd { - // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) - Cmd::NSE(..) => cmd.tail(), - // CYC指令:运行指定周期数 - // ! ONA Shell同样是自动步进的 - Cmd::CYC(n) => n.to_string(), - // VOL指令:调整音量 - Cmd::VOL(n) => format!("*volume={n}"), - // REG指令:注册操作 - 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(), - // 退出 ⇒ 无效输入 | // ! 🚩故意使用ONA中会「报错退出」的输入,强制ONA shell退出(其后不会再接收输入) - Cmd::EXI { .. } => "*quit".into(), - // 其它类型 - // * 📌【2024-03-24 22:57:18】基本足够支持 - _ => return Err(TranslateError::UnsupportedInput(cmd).into()), - }; - // 转译 - 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 -} - -/// 测试/获取注册的操作符id -#[test] -fn test_hash_operator_id() { - 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解析失败」会上报错误 -/// 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! { - // 终止信号 - 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(("", "")); - // 根据「头部」生成输出 - // * 🚩此处不直接使用NAVM输出中的「头部字串常量」主要考虑是「此为ONA特有」 - let output = match head.to_lowercase().as_str() { - // 回答,但排除「似是而非」的`Answer: None.` - // * 🚩ONA会输出带有误导性的`Answer: None.` - // * 看起来是回答,实际上不是 - // * 🚩【2024-04-11 23:01:50】现在将`Answer: None.`开除出「回答」的输出格式 - "answer" if !content_raw.contains("Answer: None.") => Output::ANSWER { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_ona(head, tail)?, - // 然后传入整个内容 - content_raw, - }, - "derived" => Output::OUT { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_ona(head, tail)?, - // 然后传入整个内容 - content_raw, - }, - "input" => Output::IN { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_ona(head, tail)?, - content: content_raw, - }, - "err" | "error" => Output::ERROR { - description: content_raw, - }, - // * 🚩对于「操作」的特殊语法 - // * 🚩【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, - }, - // * 🚩对于「决策预期→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(), - content: content_raw, - // 不尝试捕获Narsese | 💭后续或许可以自动捕获? - narsese: None, - }, - // 其它 - _ => Output::OTHER { - content: content_raw, - }, - }; - // 返回 - Ok(output) -} - -/// (ONA)从原始输出中解析操作 -/// * 📄`^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 captures = re_capture(&re_operation, content_raw.trim())?; - // ! 即便是测试环境下,也有可能是[`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![], - }), - } -} - -/// (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) - } - } -} -/// 操作参数提取 -/// * 🎯从一个解析出来的词项中提取出「操作参数列表」 -/// * 🚩测试环境中仅允许「复合词项」被解包 -#[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!("使用正则表达式「{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!("无法使用正则表达式「{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!("【{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) -> Result { - // 提取并解析Narsese字符串 - pipe! { - tail - // 重整 - => #{&} - => reform_output_to_narsese - // 解析方言 - => #{&} - => parse_dialect_ona - } -} - -/// 重整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(); - // 匹配ONA输出的「发生时间」⇒删去 - let re_occurrence_t = Regex::new(r"occurrenceTime=([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! { - 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](_, "") - => #{&} // 必须借用 - // 删去非必要的「发生时间」 - => [re_occurrence_t.replace_all](_, "") - => #{&} // 必须借用 - // 删去非必要的「递进时间」 - => [re_dt.replace_all](_, "") - => #{&} // 必须借用 - // 删去非必要的「优先级」 - => [re_priority.replace_all](_, "") - // 剪切前后空白符 - => .trim() - // 返回字符串 // - => .into() - } -} - -/// 单元测试 -#[cfg(test)] -mod test { - use super::*; - use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; - use navm::output::type_names::ANSWER; - 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%", - } - } - - /// 测试/输出解析 - #[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 - - A. - B? - Answer: None. - " // 【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("输出解析失败"); - // * 📌测试不能放过`Answer: None.`这个「不是回答的『回答』」 - // * 🚩「是回答」与「内容为`Answer: None.`」不能共存 - assert!(!(o.is_type(ANSWER) && o.raw_content().contains("None."))); - // 正常解析并展示Narsese - if let Some(narsese) = o.get_narsese() { - println!("{}", FORMAT_ASCII.format_narsese(narsese)) - } else { - println!("[{}] {}", o.type_name(), o.raw_content()) - } - } - } -} diff --git a/src/cin_implements/openjunars/launcher.rs b/src/cin_implements/openjunars/launcher.rs deleted file mode 100644 index 6d9595f..0000000 --- a/src/cin_implements/openjunars/launcher.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! OpenJunars 启动器 -//! * 🎯允许OpenJunars对原先运行时特别配置功能,同时也支持为OpenJunars定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 -//! * 🚩通过[`CommandGeneratorJulia`]管理启动参数 - -use super::{input_translate, output_translate}; -use crate::{ - cin_implements::common::CommandGeneratorJulia, - runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, -}; -use anyhow::Result; -use nar_dev_utils::manipulate; -use navm::vm::VmLauncher; -use std::path::PathBuf; - -/// 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 OpenJunars { - /// Julia脚本文件路径 - command_generator: CommandGeneratorJulia, -} - -impl OpenJunars { - pub fn new(jl_path: impl Into) -> Self { - Self { - // 转换为路径 - command_generator: CommandGeneratorJulia::new(jl_path), - } - } -} - -/// 启动到「命令行运行时」 -impl VmLauncher for OpenJunars { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造指令 - let command = self.command_generator.generate_command(); - - // 构造并启动虚拟机 - manipulate!( - CommandVm::from(command) - // * 🚩固定的「输入输出转译器」 - => .input_translator(input_translate) - => .output_translator(output_translate) - ) - // 🔥启动 - .launch() - } -} - -// ! 单元测试见[`super`] diff --git a/src/cin_implements/openjunars/mod.rs b/src/cin_implements/openjunars/mod.rs deleted file mode 100644 index 9dc4383..0000000 --- a/src/cin_implements/openjunars/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! 「非公理虚拟机」的OpenJunars运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 - -// 转译器 -util::mod_and_pub_use! { - // 转译器 - translators - // 启动器 - launcher -} - -/// 单元测试 -#[cfg(test)] -mod tests { - #![allow(unused)] - - use super::*; - use crate::{runtimes::CommandVmRuntime, tests::cin_paths::OPENJUNARS}; - use narsese::conversion::string::impl_lexical::shortcuts::*; - use navm::{ - cmd::Cmd, - vm::{VmLauncher, VmRuntime}, - }; - - #[test] - #[ignore = "【2024-04-14 20:24:52】会导致残留子进程"] - fn test() { - // 从别的地方获取jl路径 - let jl_path = OPENJUNARS; - // 一行代码启动OpenJunars - let vm = OpenJunars::new(jl_path).launch().expect("无法启动虚拟机"); - // 运行专有测试 - // ! ❌【2024-03-25 13:56:21】目前无法截取到Julia运行时输出,弃用 - _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(4)); - - vm.input_cmd(Cmd::NSE(nse_task!( B>.))) - .expect("无法输入指令"); - - std::thread::sleep(std::time::Duration::from_secs(1)); - - vm.input_cmd(Cmd::CYC(1)).expect("无法输入指令"); - - 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(1)); - - vm.input_cmd(Cmd::CYC(1)).expect("无法输入指令"); - - std::thread::sleep(std::time::Duration::from_secs(3)); - - // 尝试截获其所有输出 - // * 🚩【2024-04-13 16:10:27】目前经由Julia侧`flush(stdout)`,仍然无法捕获 - // * 有输出`[ Info: Answer: B>. %1.0;0.9%`,但无法被程序捕获为文本 - while let Ok(Some(output)) = vm.try_fetch_output() { - dbg!(output); - } - - std::thread::sleep(std::time::Duration::from_secs(2)); - - // 终止虚拟机运行时 - vm.terminate().expect("无法终止虚拟机"); - } -} diff --git a/src/cin_implements/openjunars/translators.rs b/src/cin_implements/openjunars/translators.rs deleted file mode 100644 index 87054db..0000000 --- a/src/cin_implements/openjunars/translators.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! OpenJunars在「命令行运行时」的转译器 -//! * 📌基于命令行输入输出的字符串读写 -//! * ✨NAVM指令→字符串 -//! * ✨字符串→NAVM输出 -//! -//! TODO: 🚧自OpenNARS复制而来,一些地方需要特别适配 - -use crate::runtimes::TranslateError; -use anyhow::Result; -use navm::{ - cmd::Cmd, - output::{Operation, Output}, -}; - -/// OpenJunars的「输入转译」函数 -/// * 🎯用于将统一的「NAVM指令」转译为「OpenJunars Shell输入」 -pub fn input_translate(cmd: Cmd) -> Result { - let content = match cmd { - // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) - 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::UnsupportedInput(cmd).into()), - }; - // 转译 - Ok(content) -} - -/// OpenJunars的「输出转译」函数 -/// * 🎯用于将OpenJunars Shell的输出(字符串)转译为「NAVM输出」 -/// * 🚩直接根据选取的「头部」进行匹配 -pub fn output_translate(content: String) -> Result { - // 根据冒号分隔一次,然后得到「头部」 - let head = content.split_once(':').unwrap_or(("", "")).0.to_lowercase(); - // 根据「头部」生成输出 - let output = match &*head { - "answer" => Output::ANSWER { - // TODO: 有待捕获转译 - narsese: None, - content_raw: content, - }, - "out" => Output::OUT { - // TODO: 有待捕获转译 - narsese: None, - content_raw: content, - }, - "in" => Output::IN { - // TODO: 有待捕获转译 - narsese: None, - content, - }, - "exe" => Output::EXE { - // TODO: 有待捕获转译 - operation: Operation::new("UNKNOWN", []), - content_raw: content, - }, - "err" | "error" => Output::ERROR { - description: content, - }, - _ => Output::OTHER { content }, - }; - // 返回 - Ok(output) -} diff --git a/src/cin_implements/opennars/dialect.rs b/src/cin_implements/opennars/dialect.rs deleted file mode 100644 index fbe7b62..0000000 --- a/src/cin_implements/opennars/dialect.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! OpenNARS方言 -//! * 🎯解析OpenNARS输出,如 -//! * 📄特有的「操作」语法:`(^left, {SELF})` => `<(*, {SELF}) --> ^left>` - -use crate::runtimes::TranslateError; -use anyhow::{Ok, Result}; -use narsese::{ - api::NarseseOptions, - conversion::string::impl_enum::format_instances::FORMAT_ASCII, - lexical::{Budget, Narsese, Punctuation, Stamp, Term, Truth}, -}; -use pest::{iterators::Pair, Parser}; -use pest_derive::Parser; - -pub(super) type MidParseResult = NarseseOptions; - -#[derive(Parser)] // ! ↓ 必须从项目根目录开始 -#[grammar = "src/cin_implements/opennars/dialect_opennars.pest"] -pub struct DialectParser; - -/// 使用[`pest`]将输入的「OpenNARS方言」转换为「词法Narsese」 -/// 以OpenNARS的语法解析出Narsese -/// * 📌重点在其简写的「操作」语法`(^left, {SELF}, x)` => `<(*, {SELF}, x) --> ^left>` -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`]词法值 -/// * 🎯向「中间解析结果」填充元素,而无需考虑元素的顺序与返回值类型 -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(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() { - 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】因「需要通过规则识别『外延集/内涵集』」通过「进一步向下分发」细化被折叠对象 -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_operation => { - // * 🆕OpenNARS特有的「操作」词项简写... - 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::*; - use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; - use util::first; - - /// 测试/方言解析器 🚧 - #[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} - <{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]>)! - " - // 初步数据处理 - .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解析失败!"); - 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 deleted file mode 100644 index 1680e2f..0000000 --- a/src/cin_implements/opennars/dialect_opennars.pest +++ /dev/null @@ -1,126 +0,0 @@ -//! OpenNARS方言语法 -//! * 🎯从OpenNARS输出中解析Narsese -//! * 📌「NARS操作」的简写`(^op, param, arg)` - -/// 空白符 | 所有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) } - -/// 复合 = (连接词, 词项...) | {外延集...} | [内涵集...] -/// * 🆕对OpenNARS兼容形如`(^op, {SELF}, LEFT)`的输出语法 -/// * 🚩此处不进行「静默内联」:便于在「折叠函数」中向下分派 -compound = { - compound_common - | compound_operation - | ext_set - | int_set -} - -/// 通用的复合词项 -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_list ~ ")" } - -/// 外延集 | 📌【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/cin_implements/opennars/launcher.rs b/src/cin_implements/opennars/launcher.rs deleted file mode 100644 index 649dbc4..0000000 --- a/src/cin_implements/opennars/launcher.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! OpenNARS 启动器 -//! * 🎯允许OpenNARS对原先运行时特别配置功能,同时也支持为OpenNARS定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 -//! * 🚩通过[`CommandGeneratorJava`]管理启动参数 - -use super::{input_translate, output_translate}; -use crate::{ - cin_implements::common::CommandGeneratorJava, - runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, -}; -use anyhow::Result; -use nar_dev_utils::manipulate; -use navm::{ - cmd::Cmd, - vm::{VmLauncher, VmRuntime}, -}; -use std::path::PathBuf; - -/// OpenNARS Shell启动器 -/// * 🎯配置OpenNARS专有的东西 -/// * 🚩基于jar文件启动OpenNARS Shell -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct OpenNARS { - /// Java [`Command`]生成器 - /// * 📌必须有(包含jar文件路径) - command_generator: CommandGeneratorJava, - /// NARS的初始音量 - /// * 🚩可能没有:此时不会输入指令 - initial_volume: Option, -} - -impl OpenNARS { - /// 构造函数 - pub fn new(jar_path: impl Into) -> Self { - Self { - // 传入路径 - command_generator: CommandGeneratorJava::new(jar_path), - // 其它沿用默认配置 - ..Default::default() - } - } -} - -/// 启动到「命令行运行时」 -impl VmLauncher for OpenNARS { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造指令 - // * 🚩细致的Java参数配置,都外包给[`CommandGeneratorJava`] - let command_java = self.command_generator.generate_command(); - - // 构造并启动虚拟机 - let mut vm = manipulate!( - CommandVm::from(command_java) - // * 🚩固定的「输入输出转译器」 - => .input_translator(input_translate) - => .output_translator(output_translate) - ) - // 🔥启动 - .launch()?; - - // 设置初始音量 - if let Some(volume) = self.initial_volume { - // 输入指令,并在执行错误时打印信息 - if let Err(e) = vm.input_cmd(Cmd::VOL(volume)) { - println!("无法设置初始音量「{volume}」:{e}"); - } - }; - - // 返回 - Ok(vm) - } -} - -// ! 单元测试见[`super`] diff --git a/src/cin_implements/opennars/mod.rs b/src/cin_implements/opennars/mod.rs deleted file mode 100644 index 2f4aaf6..0000000 --- a/src/cin_implements/opennars/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! 「非公理虚拟机」的OpenNARS运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 - -// 转译器 -util::mod_and_pub_use! { - // 转译器 - translators - // 启动器 - launcher - // 方言 - dialect -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - runtimes::{ - tests::{_test_opennars, test_simple_answer}, - CommandVmRuntime, - }, - tests::cin_paths::OPENNARS as JAR_PATH_OPENNARS, - }; - use navm::vm::VmLauncher; - - /// 工具/启动OpenNARS,获得虚拟机运行时 - fn launch_vm() -> CommandVmRuntime { - // 从别的地方获取jar路径 - let jar_path = JAR_PATH_OPENNARS; - // 一行代码启动OpenNARS - OpenNARS::new(jar_path).launch().expect("无法启动虚拟机") - } - - /// 测试 - #[test] - #[ignore = "【2024-04-14 20:24:52】会导致残留子进程"] - fn test() { - // 启动OpenNARS虚拟机 - let vm = launch_vm(); - // 直接复用之前对OpenNARS的测试 - _test_opennars(vm) - } - - /// 测试/通用 | 基于Narsese - #[test] - #[ignore = "【2024-04-14 20:24:52】会导致残留子进程"] - 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 deleted file mode 100644 index f6cb737..0000000 --- a/src/cin_implements/opennars/translators.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! OpenNARS在「命令行运行时」的转译器 -//! * 🎯维护与OpenNARS Shell的交互 -//! * https://github.com/ARCJ137442/opennars-304/blob/master/src/main/java/org/opennars/main/Shell.java -//! * 📌基于命令行输入输出的字符串读写 -//! * ✨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%` -//! * `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::runtimes::TranslateError; -use anyhow::Result; -use narsese::lexical::{Narsese, Term}; -use navm::{ - cmd::Cmd, - output::{Operation, Output}, -}; -use regex::Regex; -use util::ResultBoost; - -/// OpenNARS的「输入转译」函数 -/// * 🎯用于将统一的「NAVM指令」转译为「OpenNARS Shell输入」 -pub fn input_translate(cmd: Cmd) -> Result { - 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-04-02 22:43:05】可能需要打印,但这样却没法统一IO(到处print的习惯不好) - Cmd::REM { .. } => String::new(), - // 退出码 - Cmd::EXI { .. } => "*exit".into(), - // 其它类型 - // * 📌【2024-03-24 22:57:18】基本足够支持 - // ! 🚩【2024-03-27 22:42:56】不使用[`anyhow!`]:打印时会带上一大堆调用堆栈 - _ => return Err(TranslateError::UnsupportedInput(cmd).into()), - }; - // 转译 - Ok(content) -} - -/// OpenNARS的「输出转译」函数 -/// * 🎯用于将OpenNARS Shell的输出(字符串)转译为「NAVM输出」 -/// * 🚩直接根据选取的「头部」进行匹配 -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 { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_opennars(head, tail)?, - // 然后传入整个内容 - content: content_raw, - }, - "OUT" => { - // 返回 - Output::OUT { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_opennars(head, tail)?, - // 然后传入整个内容 - content_raw, - } - } - "ANSWER" => Output::ANSWER { - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: parse_narsese_opennars(head, tail)?, - // 然后传入整个内容 - content_raw, - }, - "EXE" => Output::EXE { - operation: parse_operation_opennars(tail.trim_start()), - content_raw, - }, - // ! 🚩【2024-03-27 19:40:37】现在将ANTICIPATE降级到`UNCLASSIFIED` - "ANTICIPATE" => Output::UNCLASSIFIED { - // 指定的头部 - r#type: "ANTICIPATE".to_string(), - // 先提取其中的Narsese | ⚠️借用了`content_raw` - narsese: try_parse_narsese(tail) - .ok_or_run(|e| println!("【{head}】在解析Narsese「{tail}」时出现错误:{e}")), - // 然后传入整个内容 - content: content_raw, - }, - "ERR" | "ERROR" => Output::ERROR { - description: content_raw, - }, - // * 🚩【2024-05-09 14:41:11】目前为OpenNARS 1.5.8(定制版)专用 - "TERMINATED" | "EXITED" | "QUITTED" => Output::TERMINATED { - description: content_raw, - }, - // * 🚩利用OpenNARS常见输出「全大写」的特征,兼容「confirm」与「disappoint」 - upper if !head.is_empty() && head == upper => Output::UNCLASSIFIED { - r#type: head.to_string(), - content: content_raw, - // 默认不捕获Narsese - narsese: None, - }, - // 其它 - _ => Output::OTHER { - content: content_raw, - }, - }; - // 返回 - Ok(output) -} - -/// (OpenNARS)从原始输出中解析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!("【{head}】在解析Narsese「{tail}」时出现错误:{e}"))) -} - -/// (OpenNARS)从原始输出中解析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` -/// * 🚩【2024-03-29 22:45:11】目前能提取出其中的预算值,但实际上暂且不需要 -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(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!("【EXE】在解析Narsese时出现错误:{e}"), - } - } - } else { - operator_name = String::new(); - } - - // 返回 - Operation { - operator_name, - params, - } -} - -/// 从操作参数中解析出Narsese词项 -fn parse_term_from_operation(term_str: &str) -> Result { - // 首先尝试解析出Narsese - let parsed = parse_dialect_opennars(term_str)?; - // 其次尝试将其转换成Narsese词项 - parsed - .try_into_term() - .transform_err(TranslateError::error_anyhow) -} - -/// 切分尾部字符串,并(尝试)从中解析出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 = parse_dialect_opennars(tail); - match narsese { - // 解析成功⇒提取 & 返回 - Ok(narsese) => Ok(narsese), - // 解析失败⇒打印错误日志 | 返回None - Err(err) => Err(TranslateError::from(err).into()), - } -} diff --git a/src/cin_implements/pynars/launcher.rs b/src/cin_implements/pynars/launcher.rs deleted file mode 100644 index e585422..0000000 --- a/src/cin_implements/pynars/launcher.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Python模块 启动器 -//! * 📌PyNARS运行时的启动器 -//! * 🎯允许PyNARS对原先运行时特别配置功能,同时也支持为PyNARS定制配置 -//! * 🚩只憎加「启动器」类型,而不增加「运行时」类型 -//! * ✨不同启动器可以启动到相同运行时 -//! * 🚩通过[`CommandGeneratorPython`]管理启动参数 - -use super::{input_translate, output_translate}; -use crate::{ - cin_implements::common::CommandGeneratorPython, - runtimes::{CommandGenerator, CommandVm, CommandVmRuntime}, -}; -use anyhow::Result; -use nar_dev_utils::manipulate; -use navm::vm::VmLauncher; -use std::path::PathBuf; - -/// PyNARS运行时启动器 -/// * 🎯配置PyNARS专有的东西 -/// * 🎯以Python模块形式启动PyNARS -/// * 📌没有内置的「音量」配置 -/// * ⚠️该配置参考的是PyNARS的`ConsolePlus`模块 -/// * 🚩【2024-03-25 08:55:07】基于Python模块文件启动PyNARS Shell -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct PyNARS { - /// 命令生成器 - command_generator: CommandGeneratorPython, -} - -impl PyNARS { - pub fn new(root_path: impl Into, module_path: &str) -> Self { - Self { - command_generator: CommandGeneratorPython::new(root_path, module_path), - } - } -} - -/// 启动到「命令行运行时」 -impl VmLauncher for PyNARS { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - // 构造指令 - let command = self.command_generator.generate_command(); - - // 构造并启动虚拟机 - manipulate!( - CommandVm::from(command) - // * 🚩固定的「输入输出转译器」 - => .input_translator(input_translate) - => .output_translator(output_translate) - ) - // 🔥启动 - .launch() - } -} - -// ! 单元测试见[`super`] diff --git a/src/cin_implements/pynars/mod.rs b/src/cin_implements/pynars/mod.rs deleted file mode 100644 index e51cac7..0000000 --- a/src/cin_implements/pynars/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! 「非公理虚拟机」的PyNARS运行时 -//! * 🚩只提供「一行启动」的功能封装 -//! * 🎯无需自行配置「输入输出转译器」 -//! -//! * ❌【2024-03-25 13:00:14】目前无法在Rust侧解决「杀死子进程后,Python继续输出无关信息」的问题 -//! * 📄主要形式:子进程结束后打印错误堆栈,输出`OSError: [Errno 22] Invalid argument` -//! * ❗无法被Rust捕获,可能是Python运行时的问题(输出未链接到管道) - -// 转译器 -util::mod_and_pub_use! { - // 转译器 - translators - // 启动器 - launcher -} - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - 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 = PYNARS_ROOT; - let module_path = PYNARS_MODULE; - // 一行代码启动PyNARS | `python -m pynars.Console` @ "..\..\PyNARS-dev" - PyNARS::new(root_path, module_path) - .launch() - .expect("无法启动虚拟机") - } - - /// 测试/先前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 deleted file mode 100644 index 8243dd7..0000000 --- a/src/cin_implements/pynars/translators.rs +++ /dev/null @@ -1,308 +0,0 @@ -//! PyNARS在「命令行运行时」的转译器 -//! * 🎯维护与PyNARS的交互 -//! * 📌基于命令行输入输出的字符串读写 -//! * ✨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::runtimes::TranslateError; -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::{Captures, Regex}; -use util::{pipe, JoinTo}; - -/// PyNARS的「输入转译」函数 -/// * 🎯用于将统一的「NAVM指令」转译为「PyNARS输入」 -pub fn input_translate(cmd: Cmd) -> Result { - let content = match cmd { - // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) - Cmd::NSE(..) => cmd.tail(), - // CYC指令:运行指定周期数 - // * 📌PyNARS需要手动指定步进数 - Cmd::CYC(n) => n.to_string(), - // 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-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::UnsupportedInput(cmd).into()), - }; - // 转译 - Ok(content) -} - -/// 预处理 -/// * 🎯去掉输出字串中语义无关的杂项 -/// * 📄ANSI转义序列 -pub fn preprocess(s: &str) -> String { - // ! `\e` => `\u{1b}` - let re = Regex::new(r"\u{1b}\[[0-9;]*m").unwrap(); - pipe! { - s - // 去掉ANSI转义序列 - => [re.replace_all](_, "") - // 去掉前后缀空白符 - => .trim() - // 转换为字符串 - => .to_string() - } -} - -/// 尝试获取输出类型(「头」文本) -/// * 🚩输入:[`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](_) - // 转换为字符串 - => .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的「输出转译」函数 -/// * 🎯用于将PyNARS的输出(字符串)转译为「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) -> Result { - // 预处理 | 利用变量遮蔽,在输出中屏蔽ANSI转义序列 - let content = preprocess(&content); - // 根据冒号分隔一次,然后得到「头部」 - let head = pipe! { - &content - // 获取输出类型 - => try_get_output_type - // 统一转成小写 | ✅无需`trim`:在`try_get_output_type`中使用正则表达式保证 - => .map(|s|s.to_lowercase()) - }; - // 取切片 | ❌不能使用闭包,因为闭包无法返回引用 - let head = match &head { - Some(s) => s, - None => "", - }; - // 根据「头部」生成输出 - let output = match head { - "answer" => Output::ANSWER { - narsese: get_narsese(&content)?, - content_raw: content, - }, - "achieved" => Output::ACHIEVED { - narsese: get_narsese(&content)?, - content_raw: content, - }, - "out" => Output::OUT { - narsese: get_narsese(&content)?, - content_raw: 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, - }, - "err" | "error" => Output::ERROR { - description: content, - }, - _ => Output::OTHER { content }, - }; - // 返回 - 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/cli_support/cin_search/anyhow_vm.rs b/src/cli_support/cin_search/anyhow_vm.rs deleted file mode 100644 index 1150271..0000000 --- a/src/cli_support/cin_search/anyhow_vm.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! 🚩【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/cli_support/cin_search/impls_path_builder/mod.rs b/src/cli_support/cin_search/impls_path_builder/mod.rs deleted file mode 100644 index 6acc588..0000000 --- a/src/cli_support/cin_search/impls_path_builder/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! 存储各CIN的「路径构建器」 -//! * ✅OpenNARS -//! * ✅ONA -//! TODO: PyNARS -//! TODO: CXinNARS -//! * 🚩【2024-03-31 01:27:09】其它接口完成度不高的CIN,暂时弃了 - -use crate::cli_support::cin_search::{ - name_match::is_name_match, path_builder::CinPathBuilder, path_walker::PathWalker, -}; -use navm::vm::{VmLauncher, VmRuntime}; -use std::path::Path; - -util::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::cli_support::cin_search::path_walker::PathWalkerV1; - 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/cli_support/cin_search/impls_path_builder/path_builder_ona.rs b/src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs deleted file mode 100644 index 07d05fd..0000000 --- a/src/cli_support/cin_search/impls_path_builder/path_builder_ona.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! 用于ONA的路径构建器 - -use crate::cli_support::cin_search::{ - name_match::{name_match, name_match_only_contains}, - path_builder::CinPathBuilder, -}; -use crate::{cin_implements::ona::ONA, runtimes::CommandVmRuntime}; -use nar_dev_utils::{if_return, OptionBoost}; -use std::path::Path; - -/// 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::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/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs b/src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs deleted file mode 100644 index ad6350d..0000000 --- a/src/cli_support/cin_search/impls_path_builder/path_builder_opennars.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! 用于OpenNARS的路径构建器 - -use crate::{ - cin_implements::opennars::OpenNARS, - cli_support::cin_search::{name_match::name_match, path_builder::CinPathBuilder}, - runtimes::CommandVmRuntime, -}; -use nar_dev_utils::{if_return, OptionBoost}; -use std::path::Path; - -/// 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::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/cli_support/cin_search/mod.rs b/src/cli_support/cin_search/mod.rs deleted file mode 100644 index feb8598..0000000 --- a/src/cli_support/cin_search/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! CIN启动器中有关「CIN路径构建(搜索)」的逻辑 -//! * ✨根据「CIN路径构建器」搜索(判别)系统中已存在的CIN实现(并自动构建) -//! * 🚩输入:搜索起点(一般是编译后exe所在文件夹) -//! * 🚩输出:NAVM启动器列表 -//! * ❓【2024-03-30 19:12:29】是否要考虑返回更细化的「CIN实例位置」而非「CIN启动器」,以避免额外的性能开销? - -// 导出模块 -util::mods! { - // anyhow | 弃用 - // anyhow_vm; - // 名称匹配 - pub name_match; - - // 路径遍历器 - pub path_walker; - - // 路径构建器 - pub path_builder; - - // 路径构建器的各CIN实现 - "cin_implements" => pub impls_path_builder; -} diff --git a/src/cli_support/cin_search/name_match.rs b/src/cli_support/cin_search/name_match.rs deleted file mode 100644 index e6a0341..0000000 --- a/src/cli_support/cin_search/name_match.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! 封装「名称匹配」逻辑 -//! * 🎯用于「语义化」「模糊化」的字符串匹配 -//! * ✨无视大小写匹配 -//! * 📄"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/cli_support/cin_search/path_builder.rs b/src/cli_support/cin_search/path_builder.rs deleted file mode 100644 index c32f5b9..0000000 --- a/src/cli_support/cin_search/path_builder.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! 统一的「路径构建器」逻辑 - -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/cli_support/cin_search/path_walker.rs b/src/cli_support/cin_search/path_walker.rs deleted file mode 100644 index 5b2e250..0000000 --- a/src/cli_support/cin_search/path_walker.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! 路径遍历器 -//! * 🎯用于分离「路径查找」与「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::cli_support::cin_search::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/cli_support/error_handling_boost.rs b/src/cli_support/error_handling_boost.rs deleted file mode 100644 index 6b6e282..0000000 --- a/src/cli_support/error_handling_boost.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! 增强的快捷错误处理 -//! * 🎯用于(在命令行)快速处理、输出各种错误 - -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 deleted file mode 100644 index f29c48f..0000000 --- a/src/cli_support/io/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! 用于管理启动器的输入输出 -//! * ✨终端美化相关 -//! - -util::mods! { - // 输出打印 - pub output_print; - - // 读取行迭代器 - pub readline_iter; - - // NAVM输出缓存 - pub navm_output_cache; - - // Websocket支持 - pub websocket; -} diff --git a/src/cli_support/io/navm_output_cache.rs b/src/cli_support/io/navm_output_cache.rs deleted file mode 100644 index ed6e862..0000000 --- a/src/cli_support/io/navm_output_cache.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! NAVM输出缓存 -//! * 🎯一站式存储、展示与管理NAVM的输出 -//! * 🎯可被其它二进制库所复用 - -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; -use std::{ - ops::ControlFlow, - sync::{Arc, Mutex, MutexGuard}, -}; - -/// 线程间可变引用计数的别名 -pub 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】不附带任何包装类型,仅包装其自身 - pub(crate) inner: Vec, - - /// 流式侦听器列表 - /// * 🎯用于功能解耦、易分派的「NAVM输出处理」 - /// * 📌可在此过程中对输出进行拦截、转换等操作 - /// * 🎯CLI输出打印 - /// * 🎯Websocket输出回传(JSON) - pub output_handlers: FlowHandlerList, -} - -/// 功能实现 -impl OutputCache { - /// 构造函数 - pub fn new(inner: Vec) -> Self { - Self { - inner, - output_handlers: FlowHandlerList::new(), - } - } - - /// 不可变借用内部 - 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())) - } - - /// 从[`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<()> { - // 交给处理者处理 - let r = self.output_handlers.handle(output); - match r { - // 通过⇒静默加入输出 - HandleResult::Passed(output) => self.put_silent(output), - // 被消耗⇒提示 - HandleResult::Consumed(index) => Ok(println!("NAVM输出在[{index}]位置被拦截。")), - } - } - - /// 遍历输出 - /// * 🚩不是返回迭代器,而是用闭包开始计算 - fn for_each(&self, mut f: impl FnMut(&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/output_print.rs b/src/cli_support/io/output_print.rs deleted file mode 100644 index 32bcbf0..0000000 --- a/src/cli_support/io/output_print.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! 输出打印 -//! * 🎯用于规范化、统一、美化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 nar_dev_utils::manipulate; -use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; -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_navm_output(out: &Output) -> impl Display { - let message = manipulate!( - // 新建字符串对象 - String::new() - // 格式化头部 - => Self::format_navm_output_type(out, _) - // 格式化原始内容 - => Self::format_navm_output_content(out, _) - ); - // 载入着色 - OutputType::from(out).to_colored_str(message) - } - - /// 从NAVM输出格式化(详细) - /// * 🎯封装「从NAVM输出打印」 - /// * ✨提供「解析出的Narsese」与「解析出的NARS操作」信息 - #[inline(always)] - pub fn format_from_navm_output_verbose(out: &Output) -> impl Display { - let message = manipulate!( - // 新建字符串对象 - String::new() - // 格式化头部 - => Self::format_navm_output_type(out, _) - // 详细格式化:Narsese、NARS操作 - => Self::format_navm_output_verbose(out, _) - // 格式化原始内容 - => Self::format_navm_output_content(out, _) - ); - // 载入 - OutputType::from(out).to_colored_str(message) - } - - /// 从NAVM输出格式化(详细) - /// * 🎯封装「从NAVM输出打印」逻辑 - /// * 🚩基于「流式添加内容」的做法 - /// * 📄`[OUT]` - #[inline(always)] - fn format_navm_output_type(out: &Output, out_message: &mut String) { - // 返回创建的字符串 - *out_message += "["; - *out_message += out.type_name(); - *out_message += "] "; // ! 🚩使用尾缀空格,以避免「非必要连续空格」 - } - - /// 从NAVM输出格式化(详细) - /// * 🎯封装「从NAVM输出打印」逻辑 - /// * 🚩基于「流式添加内容」的做法 - /// * 📄`[# B>. #]` - #[inline(always)] - fn format_navm_output_verbose(out: &Output, out_message: &mut String) { - // * 🚩先添加Narsese - if let Some(narsese) = out.get_narsese() { - *out_message += "[# "; - *out_message += &(FORMAT_ASCII.format(narsese)); - *out_message += " #]"; - *out_message += " "; // 🚩使用尾缀空格,以避免「非必要连续空格」 - } - // * 🚩再添加操作 - if let Some(operation) = out.get_operation() { - *out_message += "[% "; - // 🚩↓使用尾缀空格,以避免「非必要连续空格」 - *out_message += &operation.to_string(); - *out_message += " %]"; - *out_message += " "; // 🚩使用尾缀空格,以避免「非必要连续空格」 - } - } - - /// * 📄ONA:`Input: G3! :|: occurrenceTime=37 Priority=1.000000 Truth: frequency=1.000000, confidence=0.900000` - fn format_navm_output_content(out: &Output, out_message: &mut String) { - // 最后添加原始内容 - *out_message += out.get_content().trim(); - } - - /// 基于[`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.bright_black(), - // NAVM输出 - "IN" | "OUT" => message.bright_white(), - "EXE" => message.bright_cyan().reversed(), - "ANSWER" | "ACHIEVED" => message.bright_green().reversed(), - "INFO" => message.cyan(), - "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_navm_output(out: &Output) { - println!("{}", Self::format_navm_output(out)); - } - - /// ✨格式化打印NAVM输出(详细) - /// * 🎯BabelNAR CLI - /// * 🎯附带debug效果(检验「输出转译是否成功达到预期」) - #[inline] - pub fn print_navm_output_verbose(out: &Output) { - println!("{}", Self::format_from_navm_output_verbose(out)); - } - - /// ✨格式化打印CLI输出(标准错误) - /// * 🎯BabelNAR CLI - #[inline] - pub fn eprint_line(&self, message: &str) { - eprintln!("{}", self.format_line(message)); - } - - /// ✨格式化打印NAVM输出(标准错误) - /// * 🎯BabelNAR CLI - #[inline] - pub fn eprint_navm_output(out: &Output) { - eprintln!("{}", Self::format_navm_output(out)); - } - - /// ✨格式化打印NAVM输出(标准错误)(详细) - /// * 🎯BabelNAR CLI - /// * 🎯附带debug效果(检验「输出转译是否成功达到预期」) - #[inline] - pub fn eprint_navm_output_verbose(out: &Output) { - eprintln!("{}", Self::format_from_navm_output_verbose(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输出 表达式 - ($navm_output:expr) => { - // 调用内部函数 - $crate::cli_support::io::output_print::OutputType::print_navm_output($navm_output); - }; - // NAVM输出 表达式 | 🪄详细 - (% $navm_output:expr) => { - // 调用内部函数 - $crate::cli_support::io::output_print::OutputType::print_navm_output_verbose($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输出 表达式 - ($navm_output:expr) => { - // 调用内部函数 - $crate::cli_support::io::output_print::OutputType::eprint_navm_output($navm_output); - }; - // NAVM输出 表达式 | 🪄详细 - (% $navm_output:expr) => { - // 调用内部函数 - $crate::cli_support::io::output_print::OutputType::eprint_navm_output_verbose($navm_output); - }; -} - -/// 快捷打印宏/当输出为`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/cli_support/io/readline_iter.rs b/src/cli_support/io/readline_iter.rs deleted file mode 100644 index bfae0d0..0000000 --- a/src/cli_support/io/readline_iter.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! 读取行迭代器 -//! * 🎯以迭代器的语法获取、处理用户输入 -//! * ❌【2024-04-03 14:28:02】放弃「泛型化改造」:[`Stdin`]能`read_line`,但却没实现[`std::io::BufRead`] - -use crate::cli_support::io::output_print::OutputType; -use std::io::{stdin, stdout, Result as IoResult, Stdin, Write}; - -/// 读取行迭代器 -/// * 🚩每迭代一次,请求用户输入一行 -/// * ✨自动清空缓冲区 -/// * ❌无法在【不复制字符串】的情况下实现「迭代出所输入内容」的功能 -/// * ❌【2024-04-02 03:49:56】无论如何都无法实现:迭代器物件中引入就必须碰生命周期 -/// * 🚩最终仍需复制字符串:调用处方便使用 -/// * ❓是否需要支持提示词 -#[derive(Debug)] -pub struct ReadlineIter { - /// 内置的「输入内容缓冲区」 - buffer: String, - /// 内置的「标准输入」 - stdin: Stdin, - /// 输入提示词 - prompt: String, -} - -impl ReadlineIter { - 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("") - } -} - -/// 实现迭代器 -impl Iterator for ReadlineIter { - type Item = IoResult; - - 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) { - return Some(Err(e)); - } - // 返回 - Some(IoResult::Ok(self.buffer.clone())) - } -} diff --git a/src/cli_support/io/websocket.rs b/src/cli_support/io/websocket.rs deleted file mode 100644 index 7d57e9d..0000000 --- a/src/cli_support/io/websocket.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! 基于[`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}; - -/// 从「主机地址」与「连接端口」格式化到「完整地址」 -/// * ✨兼容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监听线程 -/// * ⚠️线程在生成后立即开始运行 -#[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 deleted file mode 100644 index 026385e..0000000 --- a/src/cli_support/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! 命令行支持 -//! * 🎯通用、可选地复用「CIN启动器」等「命令行工具」的内容 -//! * 🎯亦可为后续基于UI的应用提供支持 - -util::mods! { - // CIN搜索 - pub cin_search; - - // 输入输出 - pub io; -} - -// 错误处理增强 -pub mod error_handling_boost; diff --git a/src/bin/babelnar_cli/config_launcher.rs b/src/config_launcher.rs similarity index 100% rename from src/bin/babelnar_cli/config_launcher.rs rename to src/config_launcher.rs diff --git a/src/bin/babelnar_cli/config_search.rs b/src/config_search.rs similarity index 100% rename from src/bin/babelnar_cli/config_search.rs rename to src/config_search.rs diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index aae2129..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! 主模块 -//! * ✨进程IO库 -//! * ✨通用运行时 -//! * ✨运行时的各类实现(可选) - -// 实用库别名 -pub extern crate nar_dev_utils as util; - -util::mods! { - // 必选模块 // - - // 进程IO - pub process_io; - - // NAVM运行时 - pub runtimes; - - // 输出处理者 - pub output_handler; - - // 可选模块 // - - // 各CIN的启动器、运行时实现 - "cin_implements" => pub cin_implements; - - // 命令行支持 - "cli_support" => pub cli_support; - - // 测试工具集 - "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/cin_opennars.hjson" - /// OpenNARS - OPENNARS_158 = "./src/tests/cli/config/cin_opennars_158.hjson" - /// ONA - ONA = "./src/tests/cli/config/cin_ona.hjson" - /// PyNARS - 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" - /// 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" - /// NAL/真值通配 - NAL_TRUTH_WILDCARD = "./src/tests/cli/config/nal_truth_wildcard.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/launch.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/bin/babelnar_cli/main.rs b/src/main.rs similarity index 100% rename from src/bin/babelnar_cli/main.rs rename to src/main.rs diff --git a/src/output_handler/flow_handler_list.rs b/src/output_handler/flow_handler_list.rs deleted file mode 100644 index 0bfcbb0..0000000 --- a/src/output_handler/flow_handler_list.rs +++ /dev/null @@ -1,224 +0,0 @@ -//! 模块:流式处理者列表 -//! * 🎯用于流式处理物件,并在这其中灵活控制处理流程 -//! * 📌组合式处理流程:多个处理者在一个处理函数中处理 -//! * 📌截断式消耗过程:处理的物件可能会在中途被处理者消耗 -//! -//! ? 【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), -} - -/// 统一表示「输出处理者」 -/// * 🎯简化类型表示 -/// * 🚩【2024-04-08 21:04:47】因需进行线程共享,此闭包必须附带`Send`和`Sync` -pub type DynOutputHandler = dyn FnMut(Item) -> Option + Send + Sync; - -/// 流式处理者列表 -/// * 🚩处理者的特征约束:`FnMut(Item) -> Option` -/// * 📝不能显式声明「处理者」类型 -/// * ❗若作为泛型参数,则意味着「需要统一所有类型」 -/// * 📌而各个闭包彼此之间类型都是不同的 -pub struct FlowHandlerList { - /// 存储所有的处理者 - /// * 🚩使用[`Box`]以容纳不同类型的闭包 - 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`] - /// * 📝【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>>) -> 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<&DynOutputHandler> { - // 获取指定位置的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 DynOutputHandler> { - // self.handlers.get_mut(index).map(Box::as_mut) - // } - - /// 添加新的处理者 - /// * ⚠️虽然结构体定义时无需对「处理者」类型约束为`'static`静态周期, - /// * 但此处传入作为参数(的函数指针)是需要的 - pub fn add_handler( - &mut self, - handler: impl FnMut(Item) -> Option + Send + Sync + '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::conversion::string::impl_lexical::shortcuts::*; - 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/output_handler/mod.rs b/src/output_handler/mod.rs deleted file mode 100644 index 902a8fc..0000000 --- a/src/output_handler/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! 存储有关「输出捕获」的工具 -//! * 🎯辅助封装NAVM的「指令-输出 通道」结构 - -// 流式处理者列表 -pub mod flow_handler_list; diff --git a/src/process_io/io_process.rs b/src/process_io/io_process.rs deleted file mode 100644 index 95a407f..0000000 --- a/src/process_io/io_process.rs +++ /dev/null @@ -1,630 +0,0 @@ -//! 一个简单的「IO子进程」类型 -//! -//! ## 功能 -//! -//! * ✅封装「标准IO读写」「进程通信」「线程阻塞」等逻辑 -//! * ✨支持「输出侦听」与「输出通道」两种输出处理方式 -//! -//! ## 疑难问题 -//! -//! * ❗进程残留:可能在调用`kill`方法后,子进程并未真正被杀死 -//! * 🚩【2024-03-25 13:29:14】目前解决方案:调用系统`taskkill`指令,利用进程id强制终止 -//! * ⚠️【2024-03-25 13:32:50】 - -use std::{ - error::Error, - ffi::OsStr, - fmt::{self, Debug, Display, Formatter}, - io::{BufRead, BufReader, ErrorKind, Result as IoResult, Write}, - process::{Child, ChildStdin, ChildStdout, Command, ExitStatus, Stdio}, - sync::{ - mpsc::{channel, Receiver, Sender}, - Arc, Mutex, - }, - thread::{self, JoinHandle}, -}; -// 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; - -/// 简化定义`Arc< Mutex>` -type ArcMutex = Arc>; - -/// 构建一个「IO进程」 -/// * 📌只是作为一个基于配置的「子进程启动器」存在 -/// * 作为真正的`IoProcessManager`的launcher/builder -/// -/// ! 因为有「系统指令」与「函数闭包」,无法派生任何常规宏 -pub struct IoProcess { - /// 内部封装的「进程指令」对象 - command: Command, - /// 内部配置的「输出侦听器」 - out_listener: Option>, -} - -impl IoProcess { - /// 构造函数 - /// * 🚩从路径构造实体 - /// * 📌直接生成[`Command`]对象,无需额外配置 - pub fn new(program_path: impl AsRef) -> Self { - // 实际上是构建了一个新[`Command`]对象 - let command = Command::new(program_path); - Self::from(command) - } - - /// 添加命令行参数 - 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`]尝试启动,然后直接解包 - /// * 🚩【2024-04-02 04:11:27】现在为方便反馈处理错误,重新变为[`Result`]类型 - /// * 📄路径问题:启动路径不合法 等 - pub fn launch(self) -> Result { - // 尝试启动 - Ok(self.try_launch()?) - } - - /// 启动 - /// * 🚩此处只负责创建子进程[`Child`], - /// * ⚠️不负责对子进程的控制(监听、通道)等 - pub fn try_launch(mut self) -> std::io::Result { - // 创建一个子进程 - let child = - // 指令+参数 - self.command - // 输入输出 - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - // 产生进程 - .spawn()?; - println!("Started process: {}", child.id()); - - // 获取输出侦听器 - let out_listener = self.out_listener; - - // 创建「子进程管理器」对象 - Ok(IoProcessManager::new(child, out_listener)) - } -} - -/// 实现/从[`Command`]对象转换为[`IoProcess`] -/// * ✅这里的[`Command`]必定是未被启动的:Launch之后会变成[`Child`]类型 -/// * 📝即便一些地方没法使用`command.into()`,也可使用`IoProcess::from(command)` -impl From for IoProcess { - fn from(command: Command) -> Self { - Self { - // 置入命令 - command, - // 侦听器空置 - out_listener: None, - } - } -} - -/// 子进程管理器 -/// * 🎯负责 -/// * 统一管理子进程 -/// * 封装提供易用的(字符串)输入输出接口 -/// * 🚩现在兼容「输出侦听」与「输出通道」两处 -/// * 🎯「输出侦听」用于「需要**响应式**即时处理输出,但又不想阻塞主进程/开新进程」时 -/// * 🎯「输出通道」用于「需要封装『并发异步获取』延迟处理输出,兼容已有异步并发模型」时 -/// * 📝【2024-04-02 20:40:35】使用[`Option`]应对「可能会移动所有权」的情形 -/// * 📄在「线程消耗」的场景中,有时需要「消耗线程,重启新线程」,此时就需要[`Option`]确保销毁 -#[allow(dead_code)] -pub struct IoProcessManager { - /// 正在管理的子进程 - process: Child, - - /// 子进程的「写(到子进程的)输入」守护线程 - thread_write_in: Option>, - - /// 子进程的「读(到子进程的)输出」守护线程 - /// * 🚩现在兼容「侦听器」「通道」两种模式,重新必要化 - thread_read_out: Option>, - // thread_read_out: JoinHandle<()>, - /// 子线程的终止信号 - termination_signal: ArcMutex, - - /// 子进程输出的「接收者」 - /// * 🚩子进程发送给外部侦听器,同时由外部接收 - /// * 在将输出发送给侦听器时,会在此留下备份 - /// * ⚠️如果直接调用[`Receiver::recv`]方法,可能会导致线程阻塞 - child_out: Mutex>, - // ! 【2024-03-23 19:31:56】现在兼容「输出侦听」与「输出通道」二者 - /// 子进程输入的「发送者」 - /// * 🚩子进程接收来自外部发送的消息,由外部发送 - child_in: Mutex>, - // /// 子进程的「输出监听器」 - // 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 { - // 提取子进程的标准输入输出 - 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 termination_signal = Arc::new(Mutex::new(false)); - - // // 生成「输出计数」共享数据 - // let num_output = Arc::new(Mutex::new(0)); - - // 生成进程的「读写守护」(线程) - 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】↑现在兼容「侦听器」「通道」二者 - - // 构造并返回自身 - Self { - process: child, - thread_read_out, - thread_write_in, - // 捕获通道的两端 - 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`的更可靠方案 - } - } - - /// 生成一个子线程,管理子进程的标准输入,接收通道另一端输出 - /// * 📌读输入,写进程 | stdin >>> child_in_receiver - #[inline] - fn spawn_thread_write_in( - stdin: ChildStdin, - child_in_receiver: Receiver, - termination_signal: ArcMutex, - ) -> thread::JoinHandle<()> { - thread::spawn(move || { - // 从通道接收者读取输入 | 从「进程消息发送者」向进程发送文本 - let mut stdin = stdin; - // ! 注意:这个`for`循环是阻塞的 - for line in child_in_receiver { - // 检查终止信号 | ⚠️不要在终止后还发消息 - if *termination_signal.lock().expect("无法锁定终止信号") { - // println!("子进程收到终止信号"); - break; - } - // 写入输出 - if let Err(e) = stdin.write_all(line.as_bytes()) { - match e.kind() { - // * 🚩进程已关闭⇒退出 - // TODO: 🏗️外包「错误处理」逻辑 - ErrorKind::BrokenPipe => { - println!("子进程已关闭"); - break; - } - // 其它 - _ => println!("子进程写入错误:{e}"), - } - } - } - }) - } - - /// 生成一个子线程,管理子进程的标准输出,传送输出的消息到另一端 - /// * 📌写输出 | 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: ArcMutex, - // num_output: ArcMutex, - ) -> 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 || { - // 创建缓冲区读取器 | ⚠️【2024-03-23 23:42:08】这里的`BufReader`不能简化 - // * 📝`ChildStdout`没有`read_line`功能,但可以通过`BufReader`封装 - let mut stdout_reader = BufReader::new(stdout); - - // 创建缓冲区 | 🎯可持续使用 - let mut buf = String::new(); - - // 持续循环 - loop { - // 从子进程「标准输出」读取输入 - // * ⚠️会阻塞:`read_line` - // * 📄在ONA处不阻塞,但在OpenNARS时阻塞 - // * 🔗 - match stdout_reader.read_line(&mut buf) { - // 没有任何输入⇒检查终止信号 - // * 📌不能在这里中断,需要检查终止信号 - // * 🚩【2024-03-24 01:48:19】目前**允许**在进程终止时获取其输出 - // * 一般侦听器都能侦听到 - Ok(0) => { - if *termination_signal.lock().expect("无法锁定终止信号") { - // println!("子进程收到终止信号"); - break; - } - } - // 有效输入 - Ok(_) => { - // ! 🚩现在兼容「侦听器」「通道」二者 - // 先侦听 | 只传递引用,仅在「实际有侦听器」时拷贝消息 - listener_code(&buf); - // 向「进程消息接收者」传递消息(实际上是「输出」) - if let Err(e) = child_out_sender.send(buf.clone()) { - 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) => { - // 只是「不包含字符」(过早读取)⇒跳过 - let message = e.to_string(); - if message.contains("stream did not contain") { - // 什么都不做 - } else { - println!("无法接收子进程输出:{e:?} in「{buf}」"); - break; - } - } - } - // 清空缓冲区 - buf.clear(); - } - }) - } - - // * 正常运作 * // - - /// 获取子进程id - /// * 🚩调用[`Child::id`]方法 - pub fn id(&self) -> u32 { - self.process.id() - } - - /// (从「输出通道」中)拉取一个输出 - /// * 🎯用于(阻塞式等待)从子进程中收取输出信息 - /// * 🚩以字符串形式报告错误 - /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 - /// * ❌[`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(err)? - // 通道接收者接收 - .recv() - .transform_err(err) - } - - /// 尝试(从「输出通道」中)拉取一个输出 - /// * 🎯保证不会发生「线程阻塞」 - /// * 🚩类似[`Self::fetch_output`],但仅在「有输出」时拉取 - /// * 📝[`Receiver`]自带的[`Receiver::try_recv`]就做了这件事 - /// * ⚠️【2024-03-24 01:22:02】先前基于自身内置`num_output`的计数方法不可靠:有时会遗漏计数 - pub fn try_fetch_output(&mut self) -> Result> { - // 访问自身「子进程输出」字段,但加上`try` - let out = self - .child_out - // 互斥锁锁定 - .lock() - .transform_err(err)? - // 通道接收者接收 - .try_recv() - .ok(); - // ! ↑此处使用`ok`是为了区分「锁定错误」与「通道无输出」 - // 返回 - Ok(out) - } - - /// 向子进程写入数据(字符串) - /// * 🚩通过使用自身「子进程输入」的互斥锁,从中输入数据 - /// * ⚙️返回空,或返回字符串形式的错误(互斥锁错误) - /// * ⚠️此方法需要【自行尾缀换行符】,否则不被视作有效输入 - /// * 📄要触发输入,需传入" B>.\n"而非" B>." - pub fn put(&self, input_line: impl ToString) -> Result<()> { - // 从互斥锁中获取输入 - // * 🚩等待直到锁定互斥锁,最终在作用域结束(MutexGuard析构)时释放(解锁) - // ! ❌【2024-03-23 23:59:20】此处的闭包无法简化成函数指针 - self.child_in - // 锁定以获取`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`导致阻塞 - } - - /// 向子进程写入**一行**数据(字符串) - /// * 🚩功能同[`Self::put`],但会自动加上换行符 - /// * 📌类似[`print`]和[`println`]的关系 - /// * ⚠️此方法在输入后【自动添加换行符】 - /// * 📄传入" B>."将自动转换成" B>.\n" - /// * ✅以此总是触发输入 - pub fn put_line(&self, input: impl ToString) -> Result<()> { - self.put(format!("{}\n", input.to_string())) - } - - /// 等待子进程结束 - /// * 🚩调用[`Child::wait`]方法 - /// * ⚠️对于【不会主动终止】的子进程,此举可能导致调用者死锁 - pub fn wait(&mut self) -> IoResult { - self.process.wait() - } - - /// 杀死自身 - /// * 🚩设置终止信号,通知子线程(以及标准IO)终止 - /// * 🚩调用[`Child::kill`]方法,终止子进程 - /// * ~~⚠️将借走自身所有权,终止并销毁自身~~ - /// * 🚩【2024-04-02 20:37:28】如今不再消耗自身所有权 - /// * ✅【2024-04-02 20:36:40】现在通过「将字段类型变为[`Option`]」安全借走子线程所有权 - /// * 📌销毁自身的逻辑,交给调用方处理 - /// - /// * ❓不稳定:有时会导致「野进程」的情况 - 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(err)?; - *signal = true; - drop(signal); // ! 手动释放锁 - // * 📝【2024-03-24 00:15:10】必须手动释放锁,否则会导致后续线程死锁 - - // ! 解除子线程「write_stdin」的阻塞 - // * 有可能在程序崩溃后还发信息,此时是`SendError` - let _ = self - .put("\n") - .inspect_err(|e| println!("向「进程读取」子线程发送消息失败!{e}")); - - // 等待子线程终止 // - // * 🚩【2024-03-24 18:49:31】现在强制销毁持有的两个子线程,不再等待其结束 - // * 📌主要原因:在测试OpenNARS时,发现`thread_read_out`仍然会阻塞(无法等待) - // * 📌并且一时难以修复:难点在`BufReader.read_line`如何非阻塞/可终止化 - // ! ℹ️信息 from Claude3:无法简单以此终止子线程 - // * 🚩【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()); - - // * 📝此时子线程连同「子进程的标准输入输出」一同关闭, - // * 子进程自身可以做输出 - // * 📄如:ONA在最后会打印程序运行报告 - // * ⚠️这意味着「输出侦听器」仍然能对其输出产生响应 - - // 杀死子进程 // - // * 【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(err) - } -} - -/// 单元测试 -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use crate::tests::cin_paths::ONA as PATH_ONA; - use std::{ - process::exit, - sync::{Arc, Mutex}, - }; - - /// 测试工具/等待子进程输出,直到输出满足条件 - pub fn await_fetch_until(process: &mut IoProcessManager, criterion: impl Fn(String) -> bool) { - loop { - let out = process.fetch_output().expect("无法拉取输出"); - println!("fetch到其中一个输出: {out:?}"); - if criterion(out) { - break; - } - } - } - - /// 实用测试工具:启动一个ONA,并附带「输出缓存」 - fn launch_ona() -> (IoProcessManager, ArcMutex>) { - // 输出缓存 - let outputs = Arc::new(Mutex::new(vec![])); - let outputs_inner = outputs.clone(); - // 从一个系统指令开始构建子进程 - let process = IoProcess::new(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().expect("ONA启动失败"), outputs) - } - - /// 标准案例:ONA交互 - /// - /// ## 测试输入 - /// - /// ```plaintext - /// B>. - /// C>. - /// C>? - /// ``` - /// - /// ## 预期输出 - /// - /// ```plaintext - /// Answer: C>. creationTime=2 Truth: frequency=1.000000, confidence=0.810000 - /// ``` - /// - /// ## 笔记 - /// - /// * 📝[`Arc`]能满足[`Sync`]+[`Send`],但R[`efCell`]不满足 - /// * ❌无法使用`Arc>`组合 - /// * 📝[`Mutex`]能进行进程交互,但无法共享引用 - /// * 🚩最终使用`ArcMutex`作为进程交互的共享引用 - /// * 📌[`Arc`]允许被拷贝并移动入闭包(共享引用,超越生命周期) - /// * 📌[`Mutex`]允许进程间共享的内部可变性(运行时借用检查) - #[test] - fn test_ona() { - // 创建子进程 - let (mut process, outputs) = launch_ona(); - - // 测试:输入输出 // - let output_must_contains = |s: &str| { - let outputs = outputs.lock().expect("无法锁定 outputs"); - let line = outputs - .iter() - .find(|line| line.contains(s)) - .expect("没有指定的输出!"); - println!("检验「{s:?}」成功!所在之处:{line:?}"); - }; - - // 先置入输入 - 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("无法放置输入"); - await_fetch_until(&mut process, |s| s.contains(" C>.")); - - process.put(" C>?\n").expect("无法放置输入"); - 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(EXPECTED_ANSWER); - - // // 等待结束 - // process.wait(); - - // 读取其中缓冲区里边的数据(多了会阻塞!) - { - let r = process.child_out.lock().unwrap(); - for _ in r.try_iter() { - let line = r.recv().expect("接收失败!"); - print!("从输出中读取到的一行(多了会阻塞!):{line}"); - } - // * 此处自动释放锁 - } - - // // 等待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."); - - // 读取检验输出 | 杀死进程后还有 - dbg!(&*outputs); - - // 退出 - exit(0); - } -} diff --git a/src/process_io/mod.rs b/src/process_io/mod.rs deleted file mode 100644 index a822416..0000000 --- a/src/process_io/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! 用于封装抽象「进程通信」逻辑 -//! 示例代码来源:https://www.nikbrendler.com/rust-process-communication/ -//! * 📌基于「通道」的「子进程+专职读写的子线程」通信逻辑 -//! - -util::pub_mod_and_pub_use! { - // 输入输出进程 - io_process -} diff --git a/src/bin/babelnar_cli/runtime_manage.rs b/src/runtime_manage.rs similarity index 100% rename from src/bin/babelnar_cli/runtime_manage.rs rename to src/runtime_manage.rs diff --git a/src/runtimes/command_vm/api/command_generator.rs b/src/runtimes/command_vm/api/command_generator.rs deleted file mode 100644 index fa88fd6..0000000 --- a/src/runtimes/command_vm/api/command_generator.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! 定义一个抽象特征,用于「命令行虚拟机」的命令参数/执行环境 自动生成 - -use std::process::Command; - -/// 命令生成器 -/// * 🎯主管[`Command`]对象的**模板化生成** -/// * 📄OpenNARS使用`java`命令生成器,允许在指定转译器的同时自定义Java启动参数 -pub trait CommandGenerator { - /// 通过自身内部参数,生成指令参数 - fn generate_command(&self) -> Command; -} diff --git a/src/runtimes/command_vm/api/mod.rs b/src/runtimes/command_vm/api/mod.rs deleted file mode 100644 index 48f0cf1..0000000 --- a/src/runtimes/command_vm/api/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! 定义有关「命令行虚拟机」的抽象API -//! * 【2024-03-26 22:33:19】总体想法:💡一个「转译器集成包」+「命令行参数生成器」⇒统一复用的「IO进程启动器」 -//! * 📌转译器集成包:用于将「输入转译器」与「输出转译器」打包成一个统一类型的值以传入 - -util::pub_mod_and_pub_use! { - // 转译器 - translators - // 命令行参数生成器 - command_generator -} diff --git a/src/runtimes/command_vm/api/translators.rs b/src/runtimes/command_vm/api/translators.rs deleted file mode 100644 index a953ed6..0000000 --- a/src/runtimes/command_vm/api/translators.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! 定义有关「输入输出转译器」的API -//! * ✨类型别名 -//! * ✨特制结构 -//! * ✨特有错误类型 - -use anyhow::Result; -use navm::{cmd::Cmd, output::Output}; -use std::error::Error; -use thiserror::Error; - -/// [`Cmd`]→进程输入 转译器 -/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 -/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 -/// * 📌要求线程稳定 -/// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type InputTranslator = dyn Fn(Cmd) -> Result + Send + Sync; - -/// 进程输出→[`Output`]转译器 -/// * 🚩现在不再使用特征,以便在`Option>`中推断类型 -/// * 📝若给上边类型传入值`None`,编译器无法自动推导合适的类型 -/// * 📌要求线程稳定 -/// * 只有转译功能,没有其它涉及外部的操作(纯函数) -pub type OutputTranslator = dyn Fn(String) -> Result + Send + Sync; - -/// 默认输入转译器 -/// * 🎯给「输入输出转译器」提供「默认选项」 -/// * 🚩按照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`作为字段类型 -/// * ⚠️直接使用会意味着「需要编译前确定类型」 -/// * ❌这会【非必要地】要求一些【不直接传入闭包】的「默认初始化」方法具有类型标注 -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) -> Result + Send + Sync + 'static, - O: Fn(String) -> Result + 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) -> Result + Send + Sync + 'static, - O: Fn(String) -> Result + Send + Sync + 'static, -{ - fn from(value: (I, O)) -> Self { - Self::new(value.0, value.1) - } -} - -/// 错误类型 -mod translate_error { - use super::*; - use anyhow::anyhow; - - /// 统一封装「转译错误」 - /// * 🎯用于在[`anyhow`]下封装字符串,不再使用裸露的[`String`]类型 - /// * 🎯用于可识别的错误,并在打印时直接展示原因 - /// * ⚠️若直接使用[`anyhow::anyhow`],会打印一大堆错误堆栈 - /// * 🚩【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<&'_ 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::from(&value.to_string()) - } - - /// 从[`Error`]转换为[`anyhow::Error`] - /// * 🚩【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`] - 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()) - } - } -} -pub use translate_error::*; - -/// 单元测试 -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test() { - // TODO: 【2024-03-27 22:56:26】有待完善 - let _t1 = IoTranslators::default(); - } -} diff --git a/src/runtimes/command_vm/launcher.rs b/src/runtimes/command_vm/launcher.rs deleted file mode 100644 index 9628a37..0000000 --- a/src/runtimes/command_vm/launcher.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! 命令行虚拟机(构建者) - -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}; - -/// 命令行虚拟机(构建者) -/// * 🎯配置化构造[`CommandVmRuntime`] -/// * 封装内部「输入输出进程」的「输出侦听器」逻辑 -/// * 🚩有关「启动」的流程,放在「虚拟机运行时」[`super::runtime`]中 -pub struct CommandVm { - /// 内部存储的「输入输出进程」 - pub(super) io_process: IoProcess, - - /// [`Cmd`]→进程输入 转译器 - pub(super) input_translator: Option>, - - /// 进程输出→[`Output`]转译器 - pub(super) output_translator: Option>, -} - -impl CommandVm { - /// 构造函数 - /// * 🚩接收一个可执行文件路径 - /// * 📌直接生成[`IoProcess`]对象,无需额外配置 - pub fn new(program_path: impl AsRef) -> Self { - let io_process = IoProcess::new(program_path); - Self::from(io_process) - } - - /// 配置/输入转译器 - /// * 💭何时Rust能给特征起别名。。 - /// * 🚩【2024-04-04 02:06:57】不再需要借走所有权 - /// * ✅链式操作现在可以使用[`util::manipulate`]简化 - pub fn input_translator( - &mut self, - translator: impl Fn(Cmd) -> Result + Send + Sync + 'static, - ) { - self.input_translator = Some(Box::new(translator)); - } - - /// 配置/输出转译器 - /// * 🚩【2024-04-04 02:06:57】不再需要借走所有权 - /// * ✅链式操作现在可以使用[`util::manipulate`]简化 - pub fn output_translator( - &mut self, - translator: impl Fn(String) -> Result + Send + Sync + 'static, - ) { - self.output_translator = Some(Box::new(translator)); - } - - /// 配置/输入输出转译器组 - 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); - } -} - -/// 实现/从[`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/runtimes/command_vm/mod.rs b/src/runtimes/command_vm/mod.rs deleted file mode 100644 index d0cacbf..0000000 --- a/src/runtimes/command_vm/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! 基于「进程通信」与「IO转译器」的「命令行运行时」 -//! * 🎯基于进程通信与各CIN交互 - -util::pub_mod_and_pub_use! { - // 抽象API - api - // 启动器 - launcher - // 运行时 - runtime -} diff --git a/src/runtimes/command_vm/runtime.rs b/src/runtimes/command_vm/runtime.rs deleted file mode 100644 index 656e5f5..0000000 --- a/src/runtimes/command_vm/runtime.rs +++ /dev/null @@ -1,558 +0,0 @@ -//! 命令行虚拟机 运行时 -//! * ✨核心内容 -//! * ⇄ 基于「进程通信」的消息互转 -//! * 📌核心IO流程: -//! 1. NAVM指令[`Cmd`] >>> 进程输入 >>> 子进程 -//! 2. 子进程 >>> 进程输出 >>> NAVM输出[`Output`] -//! * 🚩实现方式:两处转译器 - -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; -use navm::{ - cmd::Cmd, - output::Output, - vm::{VmLauncher, VmRuntime, VmStatus}, -}; - -/// 命令行虚拟机运行时 -/// * 🎯封装「进程通信」逻辑 -pub struct CommandVmRuntime { - /// 封装的「进程管理者」 - /// * 🚩使用[`IoProcessManager`]封装「进程通信」的逻辑细节 - process: IoProcessManager, - - /// [`Cmd`]→进程输入 转译器 - input_translator: Box, - - /// 进程输出→[`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)?; - // 当输入非空时,置入转译结果 - // * 🚩【2024-04-03 02:20:48】目前用「空字串」作为「空输入」的情形 - // TODO: 后续或将让「转译器」返回`Option` - // 空⇒提前返回 - if_return! { input.is_empty() => Ok(()) } - // 置入 - // * 🚩没有换行符 - // * 📌【2024-04-07 23:43:59】追踪「Websocket进程阻塞」漏洞:问题不在此,在`ws::Sender::send`处 - self.process.put_line(input) - } - - fn fetch_output(&mut self) -> Result { - let s = self.process.fetch_output()?; - (self.output_translator)(s) - } - - fn try_fetch_output(&mut self) -> Result> { - let s = self.process.try_fetch_output()?; - // 匹配分支 - match 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<()> { - // 给CIN发送「终止」指令:告知CIN内部「需要结束程序」 - // * 📌【2024-05-09 14:20:00】目前似乎通过这一手段,仍然无法彻底关闭Java程序 - // * 🔬【2024-05-09 14:20:22】目前在程序关闭时,即便杀掉了子进程,也会因此被阻塞(需要kill`java.exe`才能解锁) - self.input_cmd(Cmd::EXI { - reason: "CIN terminated by BabelNAR".into(), - })?; - - // 杀死子进程 - self.process.kill()?; - - // (杀死后)设置状态 - // * 🚩【2024-04-02 21:42:30】目前直接覆盖状态 - self.status = VmStatus::Terminated(Ok(())); - - // 返回「终止完成」 - Ok(()) - } -} - -/// 构建功能:启动命令行虚拟机 -impl VmLauncher for CommandVm { - type Runtime = CommandVmRuntime; - fn launch(self) -> Result { - Ok(CommandVmRuntime { - // 状态:正在运行 - status: VmStatus::Running, - // 启动内部的「进程管理者」 - process: self.io_process.launch()?, - // 输入转译器 - input_translator: self - .input_translator - // 解包or使用默认值 - // * 🚩【2024-04-04 02:02:53】似乎不应有如此默认行为:后续若配置载入失败,将难以识别问题 - .unwrap_or(default_input_translator()), - // 输出转译器 - output_translator: self - .output_translator - // 解包or使用默认值 - // * 🚩【2024-04-04 02:02:53】似乎不应有如此默认行为:后续若配置载入失败,将难以识别问题 - .unwrap_or(default_output_translator()), - // * 🚩【2024-03-24 02:06:59】目前到此为止:只需处理「转译」问题 - }) - } -} - -/// 单元测试 -/// * 🎯作为任何NAVM运行时的共用测试包 -/// * 🚩【2024-03-29 23:23:12】进一步开放:仍然只限定在「测试」环境中使用 -#[cfg(test)] -pub mod tests { - use super::*; - 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}, - 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; - - // ! 🚩【2024-04-07 12:09:44】现在路径统一迁移到`lib.rs`的`tests`模块下 - - const COMMAND_JAVA: &str = "java"; - const COMMAND_ARGS_JAVA: [&str; 2] = ["-Xmx1024m", "-jar"]; - - /// 实用测试工具/等待 - pub fn await_fetch_until( - vm: &mut CommandVmRuntime, - criterion: impl Fn(&Output, &str) -> bool, - ) -> Output { - // 不断拉取输出 - loop { - // 拉取输出及其内容 | ⚠️必要时等待(阻塞!) - let output = vm.fetch_output().unwrap(); - let raw_content = output.raw_content(); - // 展示输出 - 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:?}"), - } - // 包含⇒结束 - if criterion(&output, raw_content) { - break output; - } - } - } - - /// 实用测试工具/输入并等待 - pub fn input_cmd_and_await( - vm: &mut CommandVmRuntime, - cmd: Cmd, - criterion: impl Fn(&Output, &str) -> bool, - ) -> Output { - // 构造并输入任务 - 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, - ) -> Option { - // 空预期⇒直接输入 - // * 🎯在后边测试中统一使用闭包,并且不会因此「空头拉取输出」 - // * 📄【2024-03-24 18:47:20】有过「之前的CYC把Answer拉走了,导致后边的Answer等不到」的情况 - // * ⚠️不能简化:区别在「是否会拉取输入,即便条件永真」 - match expected_contains.is_empty() { - true => { - vm.input_cmd(cmd).expect("无法输入NAVM指令!"); - None - } - false => Some(input_cmd_and_await(vm, cmd, |_, raw_content| { - raw_content.contains(expected_contains) - })), - } - } - - /// 实用测试工具/输入并等待「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] - #[ignore = "【2024-04-14 20:24:52】会导致残留子进程"] - fn test_opennars() { - // 构造指令 - let mut command_java = Command::new(COMMAND_JAVA); - // * 📝这里的`args`、`arg都返回的可变借用。。 - command_java - .args(COMMAND_ARGS_JAVA) - .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")`没删干净 - // .args(["null", "null", "null", "null"]) - ; - // dbg!(&command_java); - - /// 临时构建的「输入转换」函数 - /// * 🎯用于转换`VOL 0`⇒`*volume=0`,避免大量输出造成进程卡顿 - fn input_translate(cmd: Cmd) -> Result { - let content = match cmd { - // 直接使用「末尾」,此时将自动格式化任务(可兼容「空预算」的形式) - Cmd::NSE(..) => cmd.tail(), - // CYC指令:运行指定周期数 - Cmd::CYC(n) => n.to_string(), - // VOL指令:调整音量 - Cmd::VOL(n) => format!("*volume={n}"), - // 其它类型 - _ => return Err(TranslateError::UnsupportedInput(cmd).into()), - }; - // 转换 - Ok(content) - } - - /// 临时构建的「输出转换」函数 - fn output_translate(content: String) -> Result { - // 读取输出 - 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 vm = manipulate!( - CommandVm::from(command_java) - // 输入转译器 - => .input_translator(input_translate) - // 输出转译器 - => .output_translator(output_translate) - ) - // 🔥启动 - .launch() - .expect("无法启动虚拟机"); - _test_opennars(vm); - } - - /// 通用测试/OpenNARS - pub fn _test_opennars(mut vm: CommandVmRuntime) { - // 专有闭包 | ⚠️无法再提取出另一个闭包:重复借用问题 - let mut input_cmd_and_await = - |cmd, contains| input_cmd_and_await_contains(&mut vm, cmd, contains); - // ! ✅【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>?"); - 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 - /// * 🚩通过Python命令从**内置文件**启动 - #[test] - fn test_pynars() { - let vm = manipulate!( - CommandVm::from(generate_command("python", Some(PYNARS_ROOT), ["-m", PYNARS_MODULE])) - // 输入转译器:直接取其尾部 - => .input_translator(|cmd| Ok(cmd.tail())) - // 暂无输出转译器 - // => .output_translator(output_translate) - ) - // 🔥启动 - .launch() - .expect("无法启动虚拟机"); - // 可复用的测试逻辑 - _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)); - // ! ↑现在无需睡眠等待:输入会自动在初始化后写入子进程 - - // 专有闭包 - 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>."); - 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>.") - }); - - // 打印所有输出 - 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 - */ - } - - /// 通用测试/简单回答 | 基于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..."); - } -} diff --git a/src/runtimes/mod.rs b/src/runtimes/mod.rs deleted file mode 100644 index bf5d808..0000000 --- a/src/runtimes/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! 用于封装表示「非公理虚拟机」的通用运行时支持 -//! * 📌不与特定的CIN相关 -//! * 📄一个「命令行运行时」可同时适用于OpenNARS、ONA、NARS-Python…… - -util::mods! { - // 命令行运行时 - pub pub command_vm; -} diff --git a/src/test_tools/mod.rs b/src/test_tools/mod.rs deleted file mode 100644 index 1f4b2ab..0000000 --- a/src/test_tools/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! 有关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 deleted file mode 100644 index 6be607a..0000000 --- a/src/test_tools/nal_format/mod.rs +++ /dev/null @@ -1,358 +0,0 @@ -//! 基于NAVM的「统一`.nal`格式」支持 -//! * ✨语法(解析)支持 -//! * 🎯提供一种(部分)兼容现有`.nal`格式文件的语法 -//! * ⚠️对其中所有Narsese部分使用CommonNarsese「通用纳思语」:不兼容方言 - -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 std::{result::Result::Err as StdErr, result::Result::Ok as StdOk, time::Duration}; -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') - .map(str::trim) - .filter(|line| !line.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 { - // * 🚩【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_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_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_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().trim(); - // * 🚩作为所有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().trim(); - // 尝试解析时间 - let duration = parse_duration(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)) - } - // 魔法注释/保存输出 - Rule::comment_save_outputs => { - // 取其中唯一一个「输出预期」 - let file_path = pair.into_inner().next().unwrap().as_str().into(); - Ok(NALInput::SaveOutputs(file_path)) - } - // 魔法注释/循环预期 - Rule::comment_expect_cycle => { - let mut pairs = pair.into_inner(); - // 取其中的「最大步数」 - let max_cycles = pipe! { - pairs.next().unwrap() - => .as_str() - => {.parse::()}# - => {?}# - }; - // 取其中的「每次步长」 - let step_cycles = pipe! { - pairs.next().unwrap() - => .as_str() - => {.parse::()}# - => {?}# - }; - // 取其中的「输出预期」 - let step_duration = pairs.next(); - let step_duration = match step_duration { - Some(step_duration) => { - // 尝试解析时间 - let step_duration = parse_duration(step_duration.as_str())?; - // 封装 - Some(step_duration) - } - None => None, - }; - // 取其中的「输出预期」 - let output_expectation = pipe! { - pairs.next().unwrap() - => fold_pest_output_expectation - => {?}# - }; - // 构造 & 返回 - Ok(NALInput::ExpectCycle( - max_cycles, - step_cycles, - step_duration, - 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:?}"), - } -} - -/// 解析其中的「输出预期」[`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().as_str().to_owned(); - // 操作参数 - 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, - }) -} - -fn parse_duration(duration_raw: &str) -> Result { - Ok(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:?}")) - }) -} - -/// 单元测试 -#[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)) -''terminate(if-no-user)"; - - #[test] - fn test_parse() { - _test_parse(" B>."); - _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("''terminate(if-no-user): 异常的退出消息!"); - _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 deleted file mode 100644 index 5c77157..0000000 --- a/src/test_tools/nal_format/nal_grammar.pest +++ /dev/null @@ -1,247 +0,0 @@ -//! 「统一`.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_save_outputs | comment_expect_cycle | comment_terminate | comment_raw) -} - -/// 注释的头部字符(静默) -comment_head = _{ "'" } - -/// 有关「置入命令」的「魔法注释」 -/// * ✨允许构建并向NAVM置入指令 -/// * 📄用`'/VOL 0`代替非通用的`*volume=0` -/// * 📄亦可直接用三个`'`:`'''VOL 0` -/// * 🚩【2024-04-02 23:44:06】现在使用`$`要求「特殊前缀」紧挨内容,避免误认「被注释掉的注释」为指令 -/// * ✅使用正谓词`&LETTER`要求必须是`/文字`而非其它(如`//`) -comment_navm_cmd = ${ - // 特殊前缀`/`或`''` - ("/" | "''") ~ &LETTER ~ 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 -} - -/// 有关「保存输出」的「魔法注释」 -/// ✨存储缓存的所有输出到指定路径下的文件(阻塞主线程) -comment_save_outputs = { - // 额外的前缀 - "'save-outputs:" ~ output_expectation -} - -/// 有关「循环等待预期」的「魔法注释」 -/// ✨阻塞主线程,循环指定周期,并在其中检查预期; -/// * 每步进「步长」个周期后,检查NAVM输出预期,有⇒终止,打印输出`expect-cycle(【次数】): 【输出】` -/// * 检查后,若存在时间,则等待指定时间 -/// * 若循环后仍无,上报「预期不符」 -comment_expect_cycle = { - // 额外的前缀 - "'expect-cycle" ~ "(" ~ cyc_uint ~ "," ~ cyc_uint ~ ("," ~ comment_expect_cycle_step_time)? ~ "):" ~ output_expectation -} - -/// 「循环等待预期」中的「每步后等待时间」 -/// * 🎯解决「输入CIN后,CIN输出需要时间,来不及反应」的问题 -comment_expect_cycle_step_time = { (!")" ~ !"," ~ ANY)* } - -/// 有关「终止」的「魔法注释」 -/// ✨终止NAVM虚拟机 -/// * 📄参数:选项、理由 -/// * 选项:决定执行的条件 -/// * 📌无 ⇒ 无条件强制退出 -/// * 📌有 ⇒ 有条件,或其它副作用 -/// * 理由:决定返回是否「正常」 -/// * 📌无理由 ⇒ 虚拟机返回 `Ok` -/// * 📌有理由 ⇒ 虚拟机返回 `Err(终止理由)` -comment_terminate = { - // 额外的前缀 | 可选的「错误」参数 - "'terminate" ~ ("(" ~ comment_terminate_option ~ ")")? ~ (":" ~ comment_raw)? -} - -/// 虚拟机终止指令的选项 -/// * 🎯控制终止的前提条件:可以在「终止」后交由用户输入 -comment_terminate_option = @{ "if-no-user" } - -/// 原始注释语法:纯粹的行注释 -/// * ✅`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 deleted file mode 100644 index f959b99..0000000 --- a/src/test_tools/structs.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! 有关「NAVM测试工具」的数据结构支持 -//! * 🎯构造在「NAVM指令」之上的超集,支持与测试有关的数据结构存储 -//! * ✨[`NALInput`]:在「直接对应CIN输入输出」的「NAVM指令」之上,引入「等待」「预期」等机制 -//! * ✨[`OutputExpectation`]:面向NAL测试,具体实现「预期」机制 - -use narsese::{conversion::string::impl_lexical::format_instances::FORMAT_ASCII, lexical::Narsese}; -use navm::{cmd::Cmd, output::Operation}; -use std::{fmt::Display, time::Duration}; -use thiserror::Error; - -/// NAVM测试中的「NAL输入」 -/// * 📌`.nal`文件中一行的超集 -/// * 🎯在原有NAVM指令下,扩展与测试有关的功能 -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NALInput { - /// 置入 - /// * 🎯向CIN置入NAVM指令 - 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), - - /// 对「输出含有」的循环预期 - /// * 📄语法示例:`''expect-cycle(500, 10, 0.1s): ANSWER C>.` - /// * 🎯用于「在『最大步数』的限定下循环尝试获取『期望的输出』,未获得预期输出⇒预期失败」 - /// * 🚩循环指定周期(最大步数),并在其中检查预期; - /// * 每步进1周期后,检查NAVM输出预期,有⇒终止,打印输出`expect-cycle(【次数】): 【输出】` - /// * 若循环后仍无,视作「预期不符」 - /// * 📄在「最大步数=0」的情形之下,`expect-cycle(0)`等价于[`expect-contains`](NALInput::ExpectContains) - ExpectCycle(usize, usize, Option, OutputExpectation), - - /// 保存「输出缓存」到指定文件 - /// * 📄语法示例:`''save-outputs: outputs.log` - /// * 🎯用于「将现有所有输出以『NAVM输出的JSON格式』存档至指定文件中」 - SaveOutputs(String), - - /// 终止虚拟机 - /// * 🎯用于「预加载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>, - }, -} - -/// 输出预期 -/// * 📌对应语法中的`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, -} - -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/mod.rs b/src/test_tools/vm_interact/mod.rs deleted file mode 100644 index 5137e68..0000000 --- a/src/test_tools/vm_interact/mod.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! 与NAVM虚拟机的交互逻辑 - -use super::{NALInput, OutputExpectation, OutputExpectationError}; -use crate::cli_support::{error_handling_boost::error_anyhow, io::output_print::OutputType}; -use anyhow::Result; -use nar_dev_utils::{if_return, ResultBoost}; -use narsese::api::FloatPrecision; -use navm::{cmd::Cmd, output::Output, vm::VmRuntime}; -use std::{ops::ControlFlow, path::Path, time::Duration}; - -// Narsese预期 -mod narsese_expectation; -pub use narsese_expectation::*; - -// 词项判等 -mod term_equal; - -/// 实现/预期匹配功能 -impl OutputExpectation { - /// 判断一个「NAVM输出」是否与自身相符合 - pub fn matches(&self, output: &Output, precision_epoch: FloatPrecision) -> bool { - // 输出类型 - 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, precision_epoch) - => 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 FnMut(&Output) -> ControlFlow) -> Result>; -} - -/// 向虚拟机置入[`NALInput`] -/// * 🎯除了「输入指令」之外,还附带其它逻辑 -/// * 🚩通过「输出缓存」参数,解决「缓存输出」问题 -/// * ❓需要迁移「符合预期」的逻辑 -pub fn put_nal( - vm: &mut impl VmRuntime, - input: NALInput, - output_cache: &mut impl VmOutputCache, - // 不能传入「启动配置」,就要传入「是否启用用户输入」状态变量 - enabled_user_input: bool, - nal_root_path: &Path, - precision_epoch: FloatPrecision, -) -> Result<()> { - use NALInput::*; - match input { - // 置入NAVM指令 - Put(cmd) => vm.input_cmd(cmd), - // 睡眠 - Sleep(duration) => nal_sleep(duration), - // 等待一个符合预期的NAVM输出 - Await(expectation) => nal_await(vm, output_cache, expectation, precision_epoch), - // 检查是否有NAVM输出符合预期 - ExpectContains(expectation) => { - nal_expect_contains(vm, output_cache, expectation, precision_epoch) - } - // 检查在指定的「最大步数」内,是否有NAVM输出符合预期(弹性步数`0~最大步数`) - ExpectCycle(max_cycles, step_cycles, step_duration, expectation) => nal_expect_cycle( - max_cycles, - vm, - step_cycles, - step_duration, - output_cache, - expectation, - precision_epoch, - ), - // 保存(所有)输出 - // * 🚩输出到一个文本文件中 - // * ✨复合JSON「对象数组」格式 - SaveOutputs(path_str) => nal_save_outputs(output_cache, nal_root_path, path_str), - // 终止虚拟机 - Terminate { - if_not_user, - result, - } => nal_terminate(if_not_user, enabled_user_input, vm, result), - } -} - -fn nal_sleep(duration: Duration) -> Result<()> { - // 睡眠指定时间 - std::thread::sleep(duration); - // 返回`ok` - Ok(()) -} - -fn nal_await( - vm: &mut impl VmRuntime, - output_cache: &mut impl VmOutputCache, - expectation: OutputExpectation, - precision_epoch: f64, -) -> Result<()> { - loop { - let output = match vm.fetch_output() { - Ok(output) => { - // 加入缓存 - output_cache.put(output.clone())?; - // ! ❌【2024-04-03 01:19:06】无法再返回引用:不再能直接操作数组,MutexGuard也不允许返回引用 - // output_cache.last().unwrap() - output - } - Err(e) => { - println!("尝试拉取输出出错:{e}"); - continue; - } - }; - // 只有匹配了才返回 - if expectation.matches(&output, precision_epoch) { - break Ok(()); - } - } -} - -fn nal_expect_contains( - vm: &mut impl VmRuntime, - output_cache: &mut impl VmOutputCache, - expectation: OutputExpectation, - precision_epoch: f64, -) -> Result<()> { - // 先尝试拉取所有输出到「输出缓存」 - while let Some(output) = vm.try_fetch_output()? { - output_cache.put(output)?; - } - // 然后读取并匹配缓存 - let result = - output_cache.for_each( - |output| match expectation.matches(output, precision_epoch) { - true => ControlFlow::Break(true), - false => ControlFlow::Continue(()), - }, - )?; - // for output in output_cache.for_each() { - // // 只有匹配了才返回Ok - // if expectation.matches(output) { - // } - // } - match result { - // 只有匹配到了一个,才返回Ok - Some(true) => Ok(()), - // 否则返回Err - _ => Err(OutputExpectationError::ExpectedNotExists(expectation).into()), - } -} - -fn nal_expect_cycle( - max_cycles: usize, - vm: &mut impl VmRuntime, - step_cycles: usize, - step_duration: Option, - output_cache: &mut impl VmOutputCache, - expectation: OutputExpectation, - precision_epoch: f64, -) -> Result<()> { - let mut cycles = 0; - while cycles < max_cycles { - // 推理步进 - vm.input_cmd(Cmd::CYC(step_cycles))?; - cycles += step_cycles; - // 等待指定时长 - if let Some(duration) = step_duration { - std::thread::sleep(duration); - } - // 先尝试拉取所有输出到「输出缓存」 - while let Some(output) = vm.try_fetch_output()? { - output_cache.put(output)?; - } - // 然后读取并匹配缓存 - let result = - output_cache.for_each( - |output| match expectation.matches(output, precision_epoch) { - true => ControlFlow::Break(true), - false => ControlFlow::Continue(()), - }, - )?; - // 匹配到一个⇒提前返回Ok - if let Some(true) = result { - OutputType::Info.print_line(&format!("expect-cycle({cycles}): {expectation}")); - return Ok(()); - } - } - // 步进完所有步数,仍未有匹配⇒返回Err - Err(OutputExpectationError::ExpectedNotExists(expectation).into()) -} - -fn nal_save_outputs( - output_cache: &mut impl VmOutputCache, - nal_root_path: &Path, - path_str: String, -) -> Result<()> { - let file_str = collect_outputs_to_json(output_cache)?; - // 保存到文件中 | 使用基于`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(()) -} - -fn collect_outputs_to_json(output_cache: &mut impl VmOutputCache) -> Result { - 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]"; - Ok(file_str) -} - -fn nal_terminate( - if_not_user: bool, - enabled_user_input: bool, - vm: &mut impl VmRuntime, - result: std::result::Result<(), String>, -) -> Result<()> { - // 检查前提条件 | 仅「非用户输入」&启用了用户输入 ⇒ 放弃终止 - if_return! { if_not_user && enabled_user_input => Ok(()) } - // 终止虚拟机 - vm.terminate()?; - // 返回 - result.transform_err(error_anyhow) -} diff --git a/src/test_tools/vm_interact/narsese_expectation.rs b/src/test_tools/vm_interact/narsese_expectation.rs deleted file mode 100644 index ca7f179..0000000 --- a/src/test_tools/vm_interact/narsese_expectation.rs +++ /dev/null @@ -1,445 +0,0 @@ -//! * 🎯统一存放与「Narsese预期识别」有关的代码 -//! * 🚩【2024-04-02 22:49:12】从[`crate::runtimes::command_vm::runtime::tests`]中迁移而来 - -use super::term_equal::*; -use anyhow::Result; -use nar_dev_utils::if_return; -use narsese::{ - api::{FloatPrecision, NarseseValue}, - conversion::{ - inter_type::lexical_fold::TryFoldInto, - string::impl_enum::{format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, NarseseFormat}, - }, - enum_narsese::{ - Budget as EnumBudget, Punctuation as EnumPunctuation, Stamp as EnumStamp, - Truth as EnumTruth, - }, - lexical::{Narsese, Sentence as LexicalSentence, Task as LexicalTask, Term}, -}; -use navm::output::Operation; -use util::macro_once; - -/// 判断「输出是否(在Narsese语义层面)符合预期」 -/// * 🎯词法Narsese⇒枚举Narsese,以便从语义上判断 -pub fn is_expected_narsese_lexical( - expected: &Narsese, - out: &Narsese, - precision_epoch: FloatPrecision, -) -> bool { - _is_expected_narsese(expected.clone(), out.clone(), precision_epoch) -} - -fn _is_expected_narsese( - mut expected: Narsese, - mut out: Narsese, - precision_epoch: FloatPrecision, -) -> bool { - // 先比对词项 - fn get_term_mut(narsese: &mut Narsese) -> &mut Term { - use NarseseValue::*; - match narsese { - Term(term) - | Sentence(LexicalSentence { term, .. }) - | Task(LexicalTask { - sentence: LexicalSentence { term, .. }, - .. - }) => term, - } - } - // * 🚩特制的「词项判等」截断性逻辑 | 🚩语义层面判等词项 - if_return! { - !semantical_equal_mut(get_term_mut(&mut expected), get_term_mut(&mut out)) - => false - }; - // * 🚩折叠剩余部分,并开始判断 - let fold = PartialFoldResult::try_from; - match (fold(expected), fold(out)) { - // * 🚩若均解析成功⇒进一步判等 - (Ok(expected), Ok(out)) => expected.is_expected_out(&out, precision_epoch), - // * 🚩任一解析失败⇒直接失败 - _ => false, - } -} - -/// 临时的「部分折叠结果」 -/// * 📌用于非词项判等 -/// * 🎯性能提升:避免重复折叠词项 -#[derive(Debug, Clone, Default)] -struct PartialFoldResult { - truth: Option, - stamp: Option, - budget: Option, - punctuation: Option, -} - -/// ! 判等即「预期判断」 -/// * 🎯判断「输出是否(在Narsese层面)符合预期」 -/// * 🚩【2024-06-11 16:02:10】目前对「词项比对」使用特殊逻辑,而对其它结构照常比较 -/// * ✅均已经考虑「没有值可判断」的情况 -impl PartialFoldResult { - fn is_expected_out(&self, out: &Self, precision_epoch: FloatPrecision) -> bool { - macro_once! { - /// 一系列针对Option解包的条件判断: - /// * 🚩均为Some⇒展开内部代码逻辑 - /// * 🚩均为None⇒直接返回true - /// * 🚩其它情况⇒直接返回false - macro both_and { - ($( { $($code:tt)* } ) && *) => { - $( - both_and!(@SINGLE $($code)*) - )&&* - }; - // 🚩空值通配 - // * 🎯用于在「真值为空」「预算值为空」时通配 - // * 📌【2024-06-16 16:58:53】「任务」应该与「空预算的语句」通配 - (@SINGLE @EMPTY_WILDCARD $exp_i:ident @ $exp:expr, $out_i:ident @ $out:expr => $($code:tt)*) => { - match ($exp.as_ref(), $out.as_ref()) { - // * 🚩预期、输出 都有 - (Some($exp_i), Some($out_i)) => { - $($code)* - }, - // * 🚩没预期 ⇒ 通配 - (None, _) => true, - // * 🚩其它⇒否 - _ => false, - } - }; - (@SINGLE $l_i:ident @ $l:expr, $r_i:ident @ $r:expr => $($code:tt)*) => { - match ($l.as_ref(), $r.as_ref()) { - (Some($l_i), Some($r_i)) => { - $($code)* - }, - (None, None) => true, - _ => false, - } - }; - } - // * 🚩开始判等逻辑 - { - // 标点一致 - expected @ self.punctuation, - out @ out.punctuation => - expected == out // * 🚩简单枚举类型:直接判等 - } && { - // 时间戳一致 - expected @ self.stamp, - out @ out.stamp => - expected == out // * 🚩简单枚举类型:直接判等 - } && { - @EMPTY_WILDCARD // ! 空值通配 - // 真值一致 - expected @ self.truth, - out @ out.truth => - is_expected_truth(expected, out, precision_epoch) // * 🚩特殊情况(需兼容)特殊处理 - } && { - @EMPTY_WILDCARD // ! 空值通配 - // 预算值一致 - expected @ self.budget, - out @ out.budget => - is_expected_budget(expected, out, precision_epoch) // * 🚩特殊情况(需兼容)特殊处理 - } - } - } -} - -impl TryFrom for PartialFoldResult { - type Error = (); - /// 从「词法Narsese」中折叠 - /// * 🚩折叠除词项以外的其它字段 - /// * 🚩【2024-06-12 01:54:13】转换失败⇒判等失败⇒返回false「不符预期」 - /// - fn try_from(narsese: Narsese) -> Result { - // * 🚩缩减代码长度的常量 - const FORMAT: &NarseseFormat<&str> = &FORMAT_ASCII_ENUM; - /// * 🚩工具宏:封装「尝试做,不行就抛Err」的逻辑 - macro_rules! some_try { - ($v:expr) => { - Some(match $v { - Ok(v) => v, - Err(..) => return Err(()), - }) - }; - } - // * 🚩批量匹配折叠 - let value = match narsese { - // * 🚩词项⇒全空 - NarseseValue::Term(..) => Self::default(), - // * 🚩语句⇒真值、时间戳、标点 - NarseseValue::Sentence(LexicalSentence { - punctuation, - stamp, - truth, - .. - }) => Self { - truth: some_try!(truth.try_fold_into(FORMAT)), - stamp: some_try!(FORMAT.parse(&stamp)), - budget: None, - punctuation: some_try!(FORMAT.parse(&punctuation)), - }, - // * 🚩任务⇒语句+预算值 - NarseseValue::Task(LexicalTask { - budget, - sentence: - LexicalSentence { - punctuation, - stamp, - truth, - .. - }, - }) => Self { - truth: some_try!(truth.try_fold_into(FORMAT)), - stamp: some_try!(FORMAT.parse(&stamp)), - budget: some_try!(budget.try_fold_into(FORMAT)), - punctuation: some_try!(FORMAT.parse(&punctuation)), - }, - }; - Ok(value) - } -} - -/// 判断「短浮点之间是否相等」(在指定精度范围内) -/// * 🎯应对不同小数精度的NARS输出,统一在某精度内相等 -/// * 🚩【2024-08-01 10:36:31】需要引入配置 -/// * 📝|expected - out| ≤ precision_epoch -fn is_expected_float( - expected: &FloatPrecision, - out: &FloatPrecision, - precision_epoch: FloatPrecision, -) -> bool { - // * 🚩精度=0 ⇒ 直接判等 - if precision_epoch == 0.0 { - return expected == out; - } - // * 🚩其它 ⇒ 绝对值小于等于 - (expected - out).abs() <= precision_epoch -} - -/// 判断「输出是否在真值层面符合预期」 -/// * 🎯空真值的语句,应该符合「固定真值的语句」的预期——相当于「通配符」 -#[inline] -fn is_expected_truth( - expected: &EnumTruth, - out: &EnumTruth, - precision_epoch: FloatPrecision, -) -> bool { - use EnumTruth::*; - match [expected, out] { - // 预期空真值⇒通配 - [Empty, ..] => true, - // 预期单真值⇒部分通配 - [Single(f_e), Single(f_o) | Double(f_o, ..)] => { - is_expected_float(f_e, f_o, precision_epoch) - } - // 预期双真值 - [Double(f_e, c_e), Double(f_o, c_o)] => { - is_expected_float(f_e, f_o, precision_epoch) - && is_expected_float(c_e, c_o, precision_epoch) - } - // 其它情况 - _ => false, - } -} - -/// 判断「输出是否在预算值层面符合预期」 -/// * 🎯空预算的语句,应该符合「固定预算值的语句」的预期——相当于「通配符」 -#[inline] -fn is_expected_budget( - expected: &EnumBudget, - out: &EnumBudget, - precision_epoch: FloatPrecision, -) -> bool { - use EnumBudget::*; - match [expected, out] { - // 预期空预算⇒通配 - [Empty, ..] => true, - // 预期单预算 - [Single(p_e), Single(p_o) | Double(p_o, ..) | Triple(p_o, ..)] => { - is_expected_float(p_e, p_o, precision_epoch) - } - // 预期双预算 - [Double(p_e, d_e), Double(p_o, d_o) | Triple(p_o, d_o, ..)] => { - is_expected_float(p_e, p_o, precision_epoch) - && is_expected_float(d_e, d_o, precision_epoch) - } - // 预期三预算 - [Triple(p_e, d_e, q_e), Triple(p_o, d_o, q_o)] => { - is_expected_float(p_e, p_o, precision_epoch) - && is_expected_float(d_e, d_o, precision_epoch) - && is_expected_float(q_e, q_o, precision_epoch) - } - // 其它情况 - _ => 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, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use narsese::lexical_nse as nse; - use navm::operation; - - #[test] - fn is_expected_narsese_lexical() { - /// 正例断言带精度 - fn test(expected: Narsese, out: Narsese, precision_epoch: FloatPrecision) { - assert!( - super::is_expected_narsese_lexical(&expected, &out, precision_epoch), - "正例断言失败!\nexpected: {expected:?}\nout: {out:?}\nprecision_epoch: {precision_epoch:?}" - ); - } - /// 反例断言带精度 - fn test_negative(expected: Narsese, out: Narsese, precision_epoch: FloatPrecision) { - assert!( - !super::is_expected_narsese_lexical(&expected, &out, precision_epoch), - "反例断言失败!\nexpected: {expected:?}\nout: {out:?}\nprecision_epoch: {precision_epoch:?}" - ); - } - // * 🚩正例 - macro_once! { - macro test { - ( // 分派&展开 - $($expected:literal $op:tt $config:tt => $out:literal $(,)?)* - ) => { - $( - test!(@SPECIFIC $expected, $out, $op, $config); - )* - } - ( // * 📝正例语法:"预期" ==(精度)=> "输出" - @SPECIFIC - $expected:literal, - $out:literal, - ==, - ($epoch:expr) - ) => { - test(nse!($expected), nse!($out), $epoch) - } - ( // * 📝反例语法:"预期" !=(精度)=> "输出" - @SPECIFIC - $expected:literal, - $out:literal, - !=, - ($epoch:expr) - ) => { - test_negative(nse!($expected), nse!($out), $epoch) - } - } - // * 🚩正例 - // 常规词项、语句、任务 - "A" ==(0.0)=> "A", - "A" !=(0.0)=> "B", - "A." ==(0.0)=> "A.", - "A." !=(0.0)=> "A?", - "A?" ==(0.0)=> "A?", - "A?" !=(0.0)=> " B>?", - "A! %1.0;0.9%" ==(0.0)=> "A! %1.0;0.9%" - "$0.5;0.5;0.5$ A@" ==(0.0)=> "$0.5;0.5;0.5$ A@", - "$0.5;0.5;0.5$ A. %1.0;0.9%" ==(0.0)=> "$0.5;0.5;0.5$ A. %1.0;0.9%", - // 真值通配(反向就不行) - "A." ==(0.0)=> "A. %1.0;0.9%", - "A!" ==(0.0)=> "A! %1.0;0.9%", - "A. %1.0;0.9%" !=(0.0)=> "A.", - "A! %1.0;0.9%" !=(0.0)=> "A!", - // 预算值通配(反向就不行) - "A." ==(0.0)=> "$0.5;0.5;0.5$ A.", - "A!" ==(0.0)=> "$0.5;0.5;0.5$ A!", - "A." ==(0.0)=> "$0.5;0.5;0.5$ A. %1.0;0.9%", - "A!" ==(0.0)=> "$0.5;0.5;0.5$ A! %1.0;0.9%", - "$0.5;0.5;0.5$ A." !=(0.0)=> "A.", - "$0.5;0.5;0.5$ A!" !=(0.0)=> "A!", - "$0.5;0.5;0.5$ A. %1.0;0.9%" !=(0.0)=> "A.", - "$0.5;0.5;0.5$ A! %1.0;0.9%" !=(0.0)=> "A!", - // 真值精度内匹配 - "A. %0.5;0.9%" ==(0.00)=> "A. %0.5;0.9%", - "A. %0.5;0.9%" ==(0.10)=> "A. %0.55;0.95%", // +0.10 - "A. %0.5;0.9%" ==(0.10)=> "A. %0.45;0.85%", // -0.10 - "A. %0.5;0.9%" ==(0.10)=> "A. %0.55;0.85%", // ±0.10 - "A. %0.5;0.9%" !=(0.01)=> "A. %0.55;0.85%", // ±0.01 - "A. %0.5%" ==(0.1)=> "A. %0.55;0.85%", // +通配 - "A. %0.5%" !=(-0.1)=> "A. %0.5%", // 负数永不匹配 - "A. %0;1%" ==(FloatPrecision::INFINITY)=> "A. %1;0%", // 正无穷总是匹配 - "A. %0.5%" !=(FloatPrecision::NEG_INFINITY)=> "A. %0.5%", // 负无穷永不匹配 - // 预算值精度内匹配 - "$0.5;0.7;0.9$ A." ==(0.0)=> "$0.5;0.7;0.9$ A.", - "$0.5;0.9$ A." ==(0.051)=> "$0.55;0.85$ A.", // ±0.05,通配,防止极限`0.050000000000000044`情形 - "$0.5;0.9$ A." !=(0.051)=> "$0.55;0.84$ A.", // ±0.05,通配,防止极限`0.050000000000000044`情形 - "$0.5;0.9$ A." !=(0.051)=> "$0.55;0.96$ A.", // ±0.05,通配,防止极限`0.050000000000000044`情形 - "$0.5;0.7;0.9$ A." ==(0.051)=> "$0.55;0.7058;0.85$ A.", // ±0.050,防止极限`0.050000000000000044`情形 - "$0.5;0.7;0.9$ A." !=(0.041)=> "$0.55;0.7058;0.85$ A.", // ±0.040,防止极限`0.050000000000000044`情形 - "$0.5;0.7;0.9$ A." ==(0.041)=> "$0.54;0.7058;0.86$ A.", // ±0.040,防止极限`0.050000000000000044`情形 - "$0.5;0.7;0.9$ A." !=(0.001)=> "$0.55;0.7058;0.85$ A.", // ±0.001,防止极限`0.050000000000000044`情形 - // 源自实际应用 - "<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>. %1.00;0.45%" - ==(0.0)=> "<(&&,<$1 --> key>,<$2 --> lock>) ==> <$2 --> (/,open,$1,_)>>. %1.00;0.45%" - " robin>. %1.00;0.45%" // 四位⇒两位(位数不一,但值相同) - ==(0.01)=> "$0.9944;0.7848;0.7238$ robin>. %1.0000;0.4500%", - " bird>. %1.00;0.47%" // 四位⇒两位(位数不一,精度不同) - ==(0.01)=> "$0.8333;0.7200;0.7369$ bird>. %1.0000;0.4737%", - } - } - - #[test] - fn is_expected_operation() { - // * 🚩正例 - macro_once! { - macro test($( - [$($t_expected:tt)*] => [$($t_out:tt)*] - )*) { - $( - let expected = operation!($($t_expected)*); - let out = operation!($($t_out)*); - assert!( - super::is_expected_operation(&expected, &out), - "正例断言失败!\nexpected: {expected:?}, out: {out:?}" - ); - )* - } - // * 🚩仅有操作名 - ["left"] => ["left"] - // * 🚩带参数 - ["left" => "{SELF}"] => ["left" => "{SELF}"] - ["left" => "{SELF}" "x"] => ["left" => "{SELF}" "x"] - } - // * 🚩反例 - macro_once! { - macro test($( - [$($t_expected:tt)*] != [$($t_out:tt)*] - )*) { - $( - let expected = operation!($($t_expected)*); - let out = operation!($($t_out)*); - assert!( - !super::is_expected_operation(&expected, &out), - "反例断言失败!\nexpected: {expected:?}, out: {out:?}" - ); - )* - } - // * 🚩操作名不同 - ["left"] != ["right"] - ["left" => "{SELF}"] != ["right" => "{SELF}"] - ["left" => "{SELF}" "x"] != ["right" => "{SELF}" "x"] - // * 🚩参数数目不同 - ["left" => "{SELF}"] != ["left" => "{SELF}" "x"] - // * 🚩参数不同 - ["left" => "{SELF}" "x"] != ["left" => "[good]" "x"] - ["left" => "{SELF}" "x"] != ["left" => "{OTHER}" "x"] - ["left" => "{SELF}" "x"] != ["left" => "{SELF}" "y"] - } - } -} diff --git a/src/test_tools/vm_interact/term_equal.rs b/src/test_tools/vm_interact/term_equal.rs deleted file mode 100644 index fff914d..0000000 --- a/src/test_tools/vm_interact/term_equal.rs +++ /dev/null @@ -1,526 +0,0 @@ -use narsese::{ - conversion::string::impl_enum::format_instances::FORMAT_ASCII as FORMAT_ASCII_ENUM, lexical::*, -}; -use std::{cmp::Ordering, collections::HashMap}; - -/// 简单获取词项的「标识符」 -/// * 🎯识别是否为「可交换词项」 -/// * ⚠️对「集合词项」只取其中的左括弧 -fn get_identifier(term: &Term) -> &str { - match term { - Atom { prefix, .. } => prefix, - Compound { connecter, .. } => connecter, - Set { left_bracket, .. } => left_bracket, - Statement { copula, .. } => copula, - } -} - -// 重命名「变量词项」 // - -/// 判断一个原子词项前缀是否为「变量词项」 -fn is_variable_atom_prefix(prefix: &str) -> bool { - // 独立变量 - prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_independent - // 非独变量 - || prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_dependent - // 查询变量 - || prefix == FORMAT_ASCII_ENUM.atom.prefix_variable_query -} - -// /// 判断一个词项是否为变量 -// fn is_variable_atom(term: &Term) -> bool { -// match term { -// Atom { prefix, .. } => is_variable_atom_prefix(prefix), -// _ => false, -// } -// } - -// /// 重命名变量原子词项 -// fn rename_variable_atom(term: &mut Term, new_name: &str) { -// if let Atom { name, .. } = term { -// *name = new_name.to_string() -// } -// } - -type VariableNameMap = HashMap; - -/// 重命名词项中的所有变量 -/// * 🎯给所有词项统一编号 -/// * 🚩返回:是否修改,变量映射表 -#[allow(unused)] -fn rename_variables_in_term(term: &mut Term) -> (bool, VariableNameMap) { - let mut map = VariableNameMap::new(); - rename_variables_in_term_with_map(term, &mut map); - (!map.is_empty(), map) -} - -/// 带映射地递归重命名变量 -fn rename_variables_in_term_with_map(term: &mut Term, map: &mut VariableNameMap) -> bool { - find_variables_renaming(term, map); - let modified = map.iter().any(|(k, v)| k != v); - if modified { - apply_name_substitute(term, map); - } - modified -} - -/// 寻找需要重命名的变量集合 -fn find_variables_renaming(term: &Term, map: &mut VariableNameMap) { - match term { - // * 🚩原子变量词项⇒尝试命名 - Atom { prefix, name } if is_variable_atom_prefix(prefix) => { - let new_name = match map.get(name) { - Some(n) => n.clone(), - None => (map.len() + 1).to_string(), // ? 避免已有的数字变量干扰 | 2 -> 1, 1 -> 2 - }; - // * 📌插入名称 - // if *name != new_name { - map.insert(name.clone(), new_name.clone()); - // } - // rename_variable_atom(term, &new_name); - } - // * 🚩复合词项⇒递归深入 - Compound { terms, .. } | Set { terms, .. } => terms - .iter() - .for_each(|term| find_variables_renaming(term, map)), - Statement { - subject, predicate, .. - } => [subject, predicate] - .into_iter() - .for_each(|term| find_variables_renaming(term, map)), - // * 🚩其它⇒未改变 - _ => (), - } -} - -fn apply_name_substitute(term: &mut Term, map: &VariableNameMap) { - match term { - Atom { name, .. } => { - if let Some(new_name) = map.get(name) { - *name = new_name.clone() - } - } - Compound { terms, .. } | Set { terms, .. } => { - for term in terms { - apply_name_substitute(term, map) - } - } - Statement { - subject, predicate, .. - } => { - apply_name_substitute(subject, map); - apply_name_substitute(predicate, map); - } - } -} - -// 对「可交换词项」排序 // - -/// 判断一个词项前缀是否为「可交换词项」 -/// * 🚩一元词项不被视作【可交换的】词项:无需交换 -fn is_communicative_term(identifier: &str) -> bool { - // 外延集&内涵集 - identifier == FORMAT_ASCII_ENUM.compound.brackets_set_extension.0 - || identifier == FORMAT_ASCII_ENUM.compound.brackets_set_intension.0 - // 外延交&内涵交 - || identifier == FORMAT_ASCII_ENUM.compound.connecter_intersection_extension - || identifier == FORMAT_ASCII_ENUM.compound.connecter_intersection_intension - // 合取&析取 - || identifier == FORMAT_ASCII_ENUM.compound.connecter_conjunction - || identifier == FORMAT_ASCII_ENUM.compound.connecter_disjunction - // 平行合取 - || identifier == FORMAT_ASCII_ENUM.compound.connecter_conjunction_parallel - // 相似&等价 - || identifier == FORMAT_ASCII_ENUM.statement.copula_similarity - || identifier == FORMAT_ASCII_ENUM.statement.copula_equivalence - // 并发性等价 - || identifier == FORMAT_ASCII_ENUM.statement.copula_equivalence_concurrent -} - -/// 比较两个词项的顺序 -/// * 🎯对「均为变量」的情况判断等值 -fn term_comparator(term1: &Term, term2: &Term) -> Ordering { - fn term_comparator_zipped((term1, term2): (&Term, &Term)) -> Ordering { - term_comparator(term1, term2) - } - use Ordering::*; - match (term1, term2) { - // * 🚩原子🆚原子:判断变量情况 - ( - Atom { - prefix: p1, - name: n1, - }, - Atom { - prefix: p2, - name: n2, - }, - ) => match (is_variable_atom_prefix(p1), is_variable_atom_prefix(p2)) { - // * 🚩都是变量⇒判等 - (true, true) => Equal, - (false, true) => Less, - (true, false) => Greater, - // * 🚩其它情况⇒正常按名称判断 - (false, false) => p1.cmp(p2).then(n1.cmp(n2)), - }, - // * 🚩复合🆚复合 | 集合🆚集合 ⇒ 深入 - ( - Compound { - connecter: c1, - terms: t1, - }, - Compound { - connecter: c2, - terms: t2, - }, - ) - | ( - Set { - left_bracket: c1, - terms: t1, - .. - }, - Set { - left_bracket: c2, - terms: t2, - .. - }, - ) => c1.cmp(c2).then( - t1.iter() - .zip(t2.iter()) - .map(term_comparator_zipped) - .fold(Equal, Ordering::then), - ), - // * 🚩陈述🆚陈述 - ( - Statement { - copula: c1, - subject: s1, - predicate: p1, - }, - Statement { - copula: c2, - subject: s2, - predicate: p2, - }, - ) => c1.cmp(c2).then( - ([s1, p1].into_iter()) - .zip([s2, p2]) - .map(|(inner1, inner2)| term_comparator(inner1, inner2)) - .fold(Equal, Ordering::then), - ), - // * 🚩其它类型不同的情况⇒明显的顺序(无需特别安排) - _ => term1.cmp(term2), - } -} - -/// 对内部的「可交换词项」排序 -/// * 🚩可交换词项⇒排序;不可交换⇒对其内子项排序 -fn sort_communicative_terms(term: &mut Term) -> bool { - // * 🚩尝试对内部词项排序 - let mut modified = match term { - // * 🚩复合 & 集合 - Compound { terms, .. } | Set { terms, .. } => { - let mut modified = false; - for term in terms { - // * 🚩必须全部执行排序,不能截断了事 - modified = sort_communicative_terms(term) || modified; - } - modified - } - // * 🚩陈述 - Statement { - subject, predicate, .. - } => { - let modified_subject = sort_communicative_terms(subject); - let modified_predicate = sort_communicative_terms(predicate); - modified_subject || modified_predicate - } - // * 🚩其它⇒未改变 - _ => false, - }; - // * 🚩可交换⇒自身直接元素排序 - if is_communicative_term(get_identifier(term)) { - // * 🚩必须全部执行排序,不能截断了事 - modified = sort_a_communicative_term(term) || modified; - } - modified -} - -/// 对一个「可交换词项」排序 -/// * ⚠️只在当前层中排序 -fn sort_a_communicative_term(term: &mut Term) -> bool { - match term { - // * 🚩复合 & 集合 - Compound { terms, .. } | Set { terms, .. } => { - // * 🚩将引用按原先顺序排列 - let mut ref_terms: Vec<&Term> = terms.iter().collect(); - // * 🚩尝试排序词项引用(不改变原词项序列) | ⚠️若直接改变原序列,会存在借用问题 - ref_terms.sort_by(|&t1, &t2| term_comparator(t1, t2)); - // * 🚩迭代判断相等;不相等⇒被修改(此时已经被排序) - let modified = terms.iter().zip(ref_terms).any(|(t1, t2)| t1 != t2); - // * 🚩若有修改⇒再排序一次 - if modified { - terms.sort_by(term_comparator); - } - modified - } - // * 🚩陈述 - Statement { - subject, predicate, .. - } => { - match term_comparator(subject, predicate) { - // subject ">" predicate - Ordering::Greater => { - // * 🚩调整顺序 - std::mem::swap(subject, predicate); - true // 被修改 - } - _ => false, - } - } - // * 🚩其它⇒不作为 - _ => false, - } -} - -// 对外接口 // - -const MAX_TRIES_FORMALIZE: usize = 0x100; - -/// 规范化一个词项 -/// * 📌语义上相等⇒一定会被规范到同一形式 -pub fn formalize_term(term: &mut Term) -> &mut Term { - let mut map = VariableNameMap::new(); - let mut modified; - // 修改到无法修改为止 - // * 📌循环次数有限,防止死循环 - for _ in 0..MAX_TRIES_FORMALIZE { - // 命名变量 - modified = rename_variables_in_term_with_map(term, &mut map); - // 排序 | 🚩放后头避免截断 - modified = sort_communicative_terms(term) || modified; - // * 🚩若无变化⇒退出 - if !modified { - return term; - } - // 重置 - map.clear(); - } - // * 🚩尝试多次仍未稳定⇒收集信息,panic | 📝这是个程序漏洞,而非可失败的选项:重命名+排序 必定会收敛(有待论证) - const N: usize = 0x10; - let mut stack = Vec::with_capacity(N); - for _ in 0..N { - use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; - // 命名变量 - rename_variables_in_term_with_map(term, &mut map); - // 排序 - sort_communicative_terms(term); - // 打印 - stack.push(format!("modified: {:}", FORMAT_ASCII.format(term))); - // 重置 - map.clear(); - } - panic!( - "异常:程序重复尝试了{MAX_TRIES_FORMALIZE}次,仍未稳定。\n堆栈:\n{}", - stack.join("\n") - ); -} - -/// 入口:词项判等 -/// * 🚩通过「规整化词项」实现判等逻辑 -/// * 📌可交换词项「顺序不影响相等」 ⇒ 固定顺序 ⇒ 排序 -/// * 📌变量词项「编号不影响相等」 ⇒ 固定顺序 ⇒ 统一重命名 -pub fn semantical_equal_mut(term1: &mut Term, term2: &mut Term) -> bool { - *formalize_term(term1) == *formalize_term(term2) -} - -#[cfg(test)] -mod tests { - use super::*; - use narsese::conversion::string::impl_lexical::format_instances::FORMAT_ASCII; - use util::macro_once; - - fn parse_term(s: &str) -> Term { - FORMAT_ASCII - .parse(s) - .expect("Narsese解析失败") - .try_into_term() - .unwrap() - } - - macro_rules! term { - ($s:expr) => { - parse_term($s) - }; - } - - fn fmt_term(term: &Term) -> String { - FORMAT_ASCII.format(term) - } - - fn print_term(term: &Term) { - println!("{:}", fmt_term(term)); - } - - #[test] - fn rename_variables() { - fn t(s: &str) { - let term = parse_term(s); - // 普通验证 - print_term(&term); - let mut renamed = term.clone(); - rename_variables_in_term(&mut renamed); - print_term(&renamed); - // 幂等性 - let mut r_renamed = renamed.clone(); - rename_variables_in_term(&mut r_renamed); - print_term(&r_renamed); - assert_eq!(&renamed, &r_renamed) - } - t("<(&&, <$2 --> $1>, <$3 <-> $2>, S, #4) ==> < $1> ==> <$3 --> {(/, R, _, $3), $2}>>>"); - t("<(&&,<$the_one --> lock>,<$second --> key>) ==> <$the_one --> (/,open,$second,_)>>"); - t("<(&&,<$the_one --> key>,<$second --> lock>) ==> <$second --> (/,open,$the_one,_)>>"); - } - - #[test] - fn sort() { - // 普通验证 - let term = term!("<[#1, B, $1, A, $2, B] <-> (&&, #1, A, {G,E,B}, [C], D>)>"); - print_term(&term); - let mut renamed = term.clone(); - sort_communicative_terms(&mut renamed); - print_term(&renamed); - // 幂等性 - let mut r_renamed = renamed.clone(); - sort_communicative_terms(&mut r_renamed); - print_term(&r_renamed); - assert_eq!(&renamed, &r_renamed); - // 排序判等 - fn sort_eq(term1: &mut Term, term2: &mut Term) -> bool { - sort_communicative_terms(term1); - sort_communicative_terms(term2); - term1 == term2 - } - macro_once! { - macro test_ { - ($($s1:literal $t1:tt $s2:literal)*) => {$( - // ! ⚠️等号/不等号 算一个标签树(tt) - test_!{ @INNER $s1 $t1 $s2 } - )*} - (@INNER $s1:literal == $s2:literal) => { - let mut t1 = term!($s1); - let mut t2 = term!($s2); - assert!(sort_eq(&mut t1, &mut t2), "{} != {}", fmt_term(&t1), fmt_term(&t2)); - } - (@INNER $s1:literal != $s2:literal) => { - let mut t1 = term!($s1); - let mut t2 = term!($s2); - assert!(!sort_eq(&mut t1, &mut t2), "{} == {}", fmt_term(&t1), fmt_term(&t2)); - } - } - "A" == "A" - " B>" == " A>" - " B>" != " A>" - "<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>" == - "<(&&,<$2 --> key>,<$1 --> lock>) ==> <$1 --> (/,open,$2,_)>>" - } - } - - #[test] - fn term_compare() { - macro_once! { - macro test_ { - ($($s1:literal $t1:tt $s2:literal)*) => {$( - test_!{ @INNER $s1 ($t1) $s2 } - )*} - (@VALUE $s1:literal $t1:tt $s2:literal $ordering:expr) => { - let t1 = term!($s1); - let t2 = term!($s2); - let cmp_result = term_comparator(&t1, &t2); - assert_eq!(cmp_result, $ordering, "{} !{} {}", fmt_term(&t1), $t1, fmt_term(&t2)); - } - (@INNER $s1:literal (>) $s2:literal) => { - test_!{ @VALUE $s1 ">" $s2 Ordering::Greater } - } - (@INNER $s1:literal (<) $s2:literal) => { - test_!{ @VALUE $s1 "<" $s2 Ordering::Less } - } - (@INNER $s1:literal (==) $s2:literal) => { - test_!{ @VALUE $s1 "==" $s2 Ordering::Equal } - } - } - "A" < "B" - "$1" == "$2" - "$3" == "$2" - "$3" == "$a" - "(&&, A)" > "#1" - "(&&, #2)" == "(&&, $2)" - "<$1 --> lock>" > "<$2 --> key>" - } - } - - #[test] - fn formalize() { - fn t(s: &str) { - let mut term = parse_term(s); - print_term(&term); - formalize_term(&mut term); - print_term(&term); - // 幂等性 - let term_original = term.clone(); - formalize_term(&mut term); - print_term(&term); - assert!(term == term_original) - } - t("<(&&, <$2 --> $1>, <$3 <-> $2>, S, #4) ==> < $1> ==> <$3 --> {(/, R, _, $3), $2}>>>"); - } - - #[test] - fn semantical_eq() { - macro_once! { - macro test_ { - ($($s1:literal $t1:tt $s2:literal)*) => {$( - // ! ⚠️等号/不等号 算一个标签树(tt) - test_!{ @INNER $s1 $t1 $s2 } - )*} - // * 📌等号的情况 - (@INNER $s1:literal == $s2:literal) => { - let mut t1 = term!($s1); - let mut t2 = term!($s2); - let eq = semantical_equal_mut(&mut t1, &mut t2); - assert!(eq, "{} != {}", fmt_term(&t1), fmt_term(&t2)); - } - // * 📌不等号的情况 - (@INNER $s1:literal != $s2:literal) => { - let mut t1 = term!($s1); - let mut t2 = term!($s2); - let eq = semantical_equal_mut(&mut t1, &mut t2); - assert!(!eq, "{} == {}", fmt_term(&t1), fmt_term(&t2)); - } - } - // * 🚩源自实际场景的例子 - "<(&&,<$1 --> lock>,<$2 --> key>) ==> <$1 --> (/,open,$2,_)>>" - == "<(&&,<$1 --> key>,<$2 --> lock>) ==> <$2 --> (/,open,$1,_)>>" - "(&&,<$1 --> lock>,<$2 --> key>)" // * 📝先重命名,根据「变量均相等」交换key和lock,最后 - == "(&&,<$1 --> key>,<$2 --> lock>)" - "(&&,<$1 --> 🔒>,<$2 --> 🔑>)" // * 对emoji也差不多 - == "(&&,<$1 --> 🔑>,<$2 --> 🔒>)" - "(&&,<#1 --> 🔒>,<$2 --> 🔑>)" // ! 但这样就不行 - != "(&&,<#1 --> 🔑>,<$2 --> 🔒>)" - "$1" == "$2" - "$1" != "A" - "(/,open,$2,_)" == "(/,open,$1,_)" - "<$1 --> (/,open,$2,_)>" == "<$2 --> (/,open,$1,_)>" - "<$1 --> $2>" == "<$2 --> $1>" - "<$1 --> 2>" != "<$2 --> 1>" - "<1 --> $2>" != "<$2 --> 1>" - "<$1 --> #2>" != "<#2 --> $1>" - " #2>" != "<#2 --> ?1>" - "<$1 --> ?2>" != " $1>" - "<$1 --> #2>" == "<$2 --> #1>" - " #2>" == " #1>" - "<$1 --> ?2>" == "<$2 --> ?1>" - } - } -} diff --git a/src/tests/cli/config/_arg_parse_test.opennars.hjson b/src/tests/cli/config/_arg_parse_test.opennars.hjson deleted file mode 100644 index b24ffc1..0000000 --- a/src/tests/cli/config/_arg_parse_test.opennars.hjson +++ /dev/null @@ -1,23 +0,0 @@ -#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/cin_cxin_js.hjson b/src/tests/cli/config/cin_cxin_js.hjson deleted file mode 100644 index 3999356..0000000 --- a/src/tests/cli/config/cin_cxin_js.hjson +++ /dev/null @@ -1,19 +0,0 @@ -#hjson -// * 🎯用于测试CXinNARS(JS版本) -// * 📌使用Node.js启动 -{ - // 转译器支持单独指定「输入转译器」和「输出转译器」 - 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/cin_native_il_1.hjson b/src/tests/cli/config/cin_native_il_1.hjson deleted file mode 100644 index 35597d4..0000000 --- a/src/tests/cli/config/cin_native_il_1.hjson +++ /dev/null @@ -1,16 +0,0 @@ -#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 diff --git a/src/tests/cli/config/cin_ona.hjson b/src/tests/cli/config/cin_ona.hjson deleted file mode 100644 index d8ce203..0000000 --- a/src/tests/cli/config/cin_ona.hjson +++ /dev/null @@ -1,22 +0,0 @@ -#hjson -// * 🎯用于测试ONA -// * ⚠️启动需要cygwin -// * 🔗中文官网: http://www.cygwin.cn/ -{ - // 转译器支持单独指定「输入转译器」和「输出转译器」 - 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/cin_opennars.hjson b/src/tests/cli/config/cin_opennars.hjson deleted file mode 100644 index bde0b8d..0000000 --- a/src/tests/cli/config/cin_opennars.hjson +++ /dev/null @@ -1,23 +0,0 @@ -#hjson -// OpenNARS 3.0.4 的启动配置 -{ - // 转译器 - translators: "opennars" - // 启动命令 - command: { - // 命令:启动java运行时 - cmd: "java" - // 传入的命令参数 - cmdArgs: [ - // 设置最大堆内存为1024M - "-Xmx1024m" - // 启动jar包 - -jar - ./opennars-304-T-modified.jar - ] - // 启动时的工作目录 - // * 🚩现在基于「固定位置的CIN程序包」运行测试 - // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables - currentDir: ./../../../../executables - } -} \ No newline at end of file diff --git a/src/tests/cli/config/cin_opennars_158.hjson b/src/tests/cli/config/cin_opennars_158.hjson deleted file mode 100644 index abeb52e..0000000 --- a/src/tests/cli/config/cin_opennars_158.hjson +++ /dev/null @@ -1,23 +0,0 @@ -#hjson -// OpenNARS 1.5.8 的启动配置 -{ - // 转译器 - translators: "opennars" - // 启动命令 - command: { - // 命令:启动java运行时 - cmd: "java" - // 传入的命令参数 - cmdArgs: [ - // 设置最大堆内存为1024M - "-Xmx1024m" - // 启动jar包 - -jar - ./opennars-158-shell.jar - ] - // 启动时的工作目录 - // * 🚩现在基于「固定位置的CIN程序包」运行测试 - // * 回溯路径:config(`./`) => cli => tests => src => BabelNAR.rs / executables - currentDir: ./../../../../executables - } -} \ No newline at end of file diff --git a/src/tests/cli/config/cin_pynars.hjson b/src/tests/cli/config/cin_pynars.hjson deleted file mode 100644 index a11d001..0000000 --- a/src/tests/cli/config/cin_pynars.hjson +++ /dev/null @@ -1,15 +0,0 @@ -// PyNARS的启动配置 -{ - translators: pynars - command: { - cmd: python - cmdArgs: [ - "-m" - // * 🚩【2024-04-07 14:41:20】使用扩展了「附加指令」的「高级控制台」 - pynars.ConsolePlus - ] - // * 🚩【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/matriangle_server.hjson b/src/tests/cli/config/matriangle_server.hjson deleted file mode 100644 index bac6e35..0000000 --- a/src/tests/cli/config/matriangle_server.hjson +++ /dev/null @@ -1,18 +0,0 @@ -#hjson -// * 🎯对接Matriangle服务器 -// * ✨兼容旧BabelNAR与Matriangle的Websocket交互逻辑 -{ - // Websocket服务端地址 - websocket: { - // * ❌【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 diff --git a/src/tests/cli/config/nal_higher_deduction.hjson b/src/tests/cli/config/nal_higher_deduction.hjson deleted file mode 100644 index 453b251..0000000 --- a/src/tests/cli/config/nal_higher_deduction.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 3df33eb..0000000 --- a/src/tests/cli/config/nal_i_var_elimination.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 2144a80..0000000 --- a/src/tests/cli/config/nal_operation.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index d533c72..0000000 --- a/src/tests/cli/config/nal_simple_deduction.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 8b3f9c7..0000000 --- a/src/tests/cli/config/nal_simple_operation.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 7b54610..0000000 --- a/src/tests/cli/config/nal_temporal_induction.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#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/nal_truth_wildcard.hjson b/src/tests/cli/config/nal_truth_wildcard.hjson deleted file mode 100644 index 75186be..0000000 --- a/src/tests/cli/config/nal_truth_wildcard.hjson +++ /dev/null @@ -1,10 +0,0 @@ -#hjson -// * 🎯测试「真值通配」 -// * ℹ️测试环境交由`prelude_test.hjson`加载 -// * 📌原则:每个配置文件中引用的相对路径,均基于「配置文件自身」的路径 -{ - preludeNAL: { - // 预置的NAL测试文件(相对配置文件自身) - file: ./../../nal/test_truth_wildcard.nal - } -} \ No newline at end of file diff --git a/src/tests/cli/config/prelude_test.hjson b/src/tests/cli/config/prelude_test.hjson deleted file mode 100644 index f075a3c..0000000 --- a/src/tests/cli/config/prelude_test.hjson +++ /dev/null @@ -1,14 +0,0 @@ -#hjson -// * 🎯测试「预加载NAL输入」,并统一设置「测试环境」 -// * ⚠️不包括具体的「预引入NAL」文件定义 -{ - // preludeNAL: "" - // 禁止用户输入 - 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 deleted file mode 100644 index 1b09197..0000000 --- a/src/tests/cli/config/websocket.hjson +++ /dev/null @@ -1,8 +0,0 @@ -#hjson -// 用于测试可作补丁的Websocket配置 -{ - websocket: { - host: localhost - port: 8080 - } -} \ No newline at end of file diff --git a/src/tests/nal/test_higher_deduction.nal b/src/tests/nal/test_higher_deduction.nal deleted file mode 100644 index ad22c56..0000000 --- a/src/tests/nal/test_higher_deduction.nal +++ /dev/null @@ -1,33 +0,0 @@ -' 用于测试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>? - -' 检验输出 -''expect-cycle(100, 10, 0.25s): ANSWER D>. -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 -' * 📝【2024-04-19 21:46:42】OpenNARS 158、ONA、PyNARS等均能在五步之内回答 - -' 用户无法输入时退出(正常退出) -''terminate(if-no-user) diff --git a/src/tests/nal/test_i_var_elimination.nal b/src/tests/nal/test_i_var_elimination.nal deleted file mode 100644 index defa381..0000000 --- a/src/tests/nal/test_i_var_elimination.nal +++ /dev/null @@ -1,24 +0,0 @@ -' 用于测试「自变量消除」 -' * 📍所涉及NAL层级:NAL-5、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>? - -' 检验输出 | ⚠️0.25s需要兼容PyNARS -''expect-cycle(200, 20, 0.25s): ANSWER C>. - -' 用户无法输入时退出(正常退出) -''terminate(if-no-user) diff --git a/src/tests/nal/test_operation.nal b/src/tests/nal/test_operation.nal deleted file mode 100644 index 01283d2..0000000 --- a/src/tests/nal/test_operation.nal +++ /dev/null @@ -1,67 +0,0 @@ -' 用于测试CIN对「操作」的支持 -' * 📍所涉及NAL层级:NAL-7、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词项)】)` -' * 🚩【2024-04-03 02:10:19】有时对操作需要等待足够的时长,才能捕获到输出 -' 日志存储 -' * ✨存储所有「NAVM输出」到指定文件(JSON格式):`''save-outputs: 【文件相对路径】` -' * 📌以`.nal`文件自身所在目录为根目录 - -' 降低音量,减少无关输出 -' * 📄【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 - -A. :|: -<(*, {SELF}) --> ^left>. :|: -G. :|: -' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 -' ! ⚠️不加下边这条问句,OpenNARS将测试失败 -<(&/, A, <(*, {SELF}) --> ^left>) ==> G>? -A. :|: -G! :|: -''expect-cycle(2000, 200, 0.2s): EXE (^left, {SELF}) -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 -' * 📝【2024-04-19 21:46:42】OpenNARS 158、ONA、PyNARS等均能在5步之内回答 - -A2. :|: -<(*, {SELF}, P) --> ^left>. :|: -G2. :|: -' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 -' ! ⚠️不加下边这条问句,OpenNARS将测试失败 -<(&/, A2, <(*, {SELF}, P) --> ^left>) ==> G2>? -A2. :|: -G2! :|: -''expect-cycle(2000, 200, 0.2s): EXE (^left, {SELF}, P) -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 -' * 📝【2024-04-19 21:46:42】OpenNARS 158、ONA、PyNARS等均能在5步之内回答 - -A3. :|: -<(*, {SELF}, (*, P1, P2)) --> ^left>. :|: -G3. :|: -' ? 📝【2024-04-07 14:10:03】OpenNARS相比于ONA,需要进行「提问」以「提示」需要操作 -' ! ⚠️不加下边这条问句,OpenNARS将测试失败 -<(&/, A3, <(*, {SELF}, (*, P1, P2)) --> ^left>) ==> G3>? -A3. :|: -G3! :|: -''expect-cycle(2000, 200, 0.2s): EXE (^left, {SELF}, (*, P1, P2)) -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 -' * 📝【2024-04-19 21:46:42】OpenNARS 158、ONA、PyNARS等均能在5步之内回答 - -''sleep: 500ms - -' * 保存输出 -''save-outputs: nal_operation_outputs.log.json - -' * 终止CIN -''terminate(if-no-user) diff --git a/src/tests/nal/test_simple_deduction.nal b/src/tests/nal/test_simple_deduction.nal deleted file mode 100644 index b581637..0000000 --- a/src/tests/nal/test_simple_deduction.nal +++ /dev/null @@ -1,34 +0,0 @@ -' 用于测试CIN的「简单演绎推理」 -' * 📍所涉及NAL层级:NAL-1 -' * 📝在「文件表示」上利用现有`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>. -' * 🚩【2024-04-19 13:28:46】增加睡眠时间,以让OpenNARS 158、CXinNARS有时间输出 - C>. -' // ''await: IN C>. - C>? - -' 检验输出 | 每10步有0.25s睡眠延时,给足输出呈现时间 -''expect-cycle(100, 10, 0.25s): ANSWER C>. -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 -' * 📝【2024-04-19 21:46:42】OpenNARS 158、ONA、PyNARS等均能在五步之内回答 - -' 用户无法输入时退出(正常退出) -''terminate(if-no-user) diff --git a/src/tests/nal/test_simple_operation.nal b/src/tests/nal/test_simple_operation.nal deleted file mode 100644 index 3b6c851..0000000 --- a/src/tests/nal/test_simple_operation.nal +++ /dev/null @@ -1,34 +0,0 @@ -' 用于测试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>! :|: -' * 🚩一次性执行一百步(并延时等待),若无预期输出⇒上报 -''expect-cycle(100, 100, 0.25s): EXE (^left, {SELF}) - -<(*, {SELF}, P) --> ^left>! :|: -' * 🚩一次性执行一百步(并延时等待),若无预期输出⇒上报 -''expect-cycle(100, 100, 0.25s): EXE (^left, {SELF}, P) - -<(*, {SELF}, (*, P1, P2)) --> ^left>! :|: -' * 🚩一次性执行一百步(并延时等待),若无预期输出⇒上报 -''expect-cycle(100, 100, 0.25s): 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 deleted file mode 100644 index 86bd533..0000000 --- a/src/tests/nal/test_temporal_induction.nal +++ /dev/null @@ -1,36 +0,0 @@ -' 用于测试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>>? - -' 检验输出 -''expect-cycle(500, 50, 0.25s): ANSWER < B> =/> D>>. -' * 🚩【2024-04-19 21:45:12】↑OpenNARS 3.x引入了大量心理操作,因此在「逐步递进」测试中性能不佳 - -' 用户无法输入时退出(正常退出) -''terminate(if-no-user) diff --git a/src/tests/nal/test_truth_wildcard.nal b/src/tests/nal/test_truth_wildcard.nal deleted file mode 100644 index cea965a..0000000 --- a/src/tests/nal/test_truth_wildcard.nal +++ /dev/null @@ -1,22 +0,0 @@ -' 用于测试BabelNAR CLI的「真值通配」功能 -' * 📍所涉及NAL层级:NAL-1 -' * 📝在「文件表示」上利用现有`Narsese`语法 -' -' 输出预期 `expect-contains` -' * 📝统一的NAL测试语法:`''expect-contains: 【输出类别】 【其它内容】` -' * 📄预期「回答」:`''expect-contains: ANSWER A. [可选的真值]` - -' 🚩降低音量,减少无关输出 -'/VOL 0 - -' 🚩【2024-04-07 14:22:28】兼容PyNARS:给启动留足时间 -''sleep: 0.5s - -A. -A? - -' 检验输出 | 每10步有0.25s睡眠延时,给足输出呈现时间 -''expect-cycle(100, 10, 0.25s): ANSWER A. - -' 用户无法输入时退出(正常退出) -''terminate(if-no-user) diff --git a/src/bin/babelnar_cli/vm_config.rs b/src/vm_config.rs similarity index 100% rename from src/bin/babelnar_cli/vm_config.rs rename to src/vm_config.rs diff --git a/src/bin/babelnar_cli/websocket_server.rs b/src/websocket_server.rs similarity index 100% rename from src/bin/babelnar_cli/websocket_server.rs rename to src/websocket_server.rs