From a6628f5fd2ae89a87636dccf60a0f26488dda03a Mon Sep 17 00:00:00 2001 From: ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:45:49 +0800 Subject: [PATCH] =?UTF-8?q?1.1.0=EF=BC=9A=E5=85=BC=E5=AE=B9=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=C2=B7=E6=B5=8B=E8=AF=95=E4=BC=98=E5=8C=96=C2=B7?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=88=86=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅解析器对象「获取目标类型」函数支持 ✅解析器自动识别「词项/语句」(从数据到目标的「目标类型」参数不再必选) ✅分离XML、JSON、序列化三种解析器,使包依赖轻量化 ✅优化测试集:重用代码&宏展示优化 --- Manifest.toml | 71 +-- Project.toml | 5 +- src/Conversion.jl | 5 - src/Conversion/core/ast.jl | 15 +- src/Conversion/core/string.jl | 57 ++- src/Conversion/extra/json.jl | 115 ----- src/Conversion/extra/serialization.jl | 41 -- src/Conversion/extra/string_shortcut.jl | 36 +- src/Conversion/extra/xml.jl | 629 ------------------------ src/Conversion/template.jl | 107 ++-- test/.gitignore | 0 test/commons.jl | 94 ++++ test/runtests.jl | 8 +- test/test_conversion.jl | 142 +----- test/test_narsese.jl | 12 +- 15 files changed, 242 insertions(+), 1095 deletions(-) delete mode 100644 src/Conversion/extra/json.jl delete mode 100644 src/Conversion/extra/serialization.jl delete mode 100644 src/Conversion/extra/xml.jl create mode 100644 test/.gitignore create mode 100644 test/commons.jl diff --git a/Manifest.toml b/Manifest.toml index 581af04..221ee3c 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,78 +2,9 @@ julia_version = "1.9.1" manifest_format = "2.0" -project_hash = "b618f0bb9b0aef939358c434d47108d24dffce9d" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.OrderedCollections]] -git-tree-sha1 = "2e73fe17cac3c62ad1aebe70d44c963c3cfdc3e3" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.6.2" - -[[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "4b2e829ee66d4218e0cef22c0a64ee37cf258c29" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.7.1" - -[[deps.PrecompileTools]] -deps = ["Preferences"] -git-tree-sha1 = "9673d39decc5feece56ef3940e5dafba15ba0f81" -uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.1.2" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.4.0" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +project_hash = "9e8639383807ffd6257aa516ae29f54e67bf56f3" [[deps.Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" uuid = "189a3867-3050-52da-a836-e630ba90ab69" version = "1.2.2" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.3" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[deps.XML]] -deps = ["Mmap", "OrderedCollections"] -git-tree-sha1 = "cbf82009944525df5b6407bff97ba554b85f20fe" -uuid = "72c71f33-b9b6-44de-8c94-c961784809e2" -version = "0.3.0" diff --git a/Project.toml b/Project.toml index 85ee5e1..02cc39e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,10 +1,7 @@ name = "JuNarsese" uuid = "11330a76-bea1-45e0-8f80-7114e2f607b1" authors = ["ARCJ137442 <61109168+ARCJ137442@users.noreply.github.com>"] -version = "1.0.0" +version = "1.1.0" [deps] -JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" -Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -XML = "72c71f33-b9b6-44de-8c94-c961784809e2" diff --git a/src/Conversion.jl b/src/Conversion.jl index 737690e..a84148b 100644 --- a/src/Conversion.jl +++ b/src/Conversion.jl @@ -29,9 +29,4 @@ include("Conversion/core/ast.jl") # 附加 include("Conversion/extra/string_shortcut.jl") # 使用eval的字符串 -# 外部文件格式 # TODO: 整体完成后分离独立成包,以便让整体支持轻量化 -include("Conversion/extra/serialization.jl") # 序列化支持 -include("Conversion/extra/json.jl") -include("Conversion/extra/xml.jl") - end diff --git a/src/Conversion/core/ast.jl b/src/Conversion/core/ast.jl index 9197aeb..e2edae6 100644 --- a/src/Conversion/core/ast.jl +++ b/src/Conversion/core/ast.jl @@ -154,9 +154,6 @@ abstract type ASTParser <: AbstractParser end "类型の短别名" const TAParser::Type = Type{<:ASTParser} -"Julia的Expr对象" -Base.eltype(::TAParser) = Expr - """ 声明「原生类型」 - 解析器直接返回自身 @@ -177,7 +174,7 @@ const AST_PRESERVED_TYPES::Type = Union{ } """ -声明「结构类型」 +声明「目标类型」 - 能被解析器支持解析 """ const AST_PARSE_TARGETS::Type = DEFAULT_PARSE_TARGETS @@ -189,6 +186,12 @@ const AST_PARSE_TARGETS::Type = DEFAULT_PARSE_TARGETS """ const AST_PRESERVED_HEAD::Symbol = Symbol(":preserved:") +"目标类型:词项/语句" +parse_target_types(::TAParser) = STRING_PARSE_TARGETS + +"数据类型:Julia的Expr对象" +Base.eltype(::TAParser)::Type = Expr + # 【特殊链接】词项↔字符串 # "重载Expr的构造方法" @@ -450,9 +453,9 @@ begin "解析器入口" - 封装性:只能调用它解析Narsese词项/语句 """ function data2narsese( - parser::TAParser, ::Type{T}, + parser::TAParser, ::Type, # 【20230808 10:33:39】因「兼容模式」不限制此处Type ex::Expr - )::T where {T <: AST_PARSE_TARGETS} + )::AST_PARSE_TARGETS return ast_parse( parser, ex, Narsese.eval, # 使用Narsese模块作解析の上下文 diff --git a/src/Conversion/core/string.jl b/src/Conversion/core/string.jl index 9f13352..6976df0 100644 --- a/src/Conversion/core/string.jl +++ b/src/Conversion/core/string.jl @@ -165,7 +165,7 @@ end (默认)实例化,并作为一个「转换器」导出 - 来源:文档 `NARS ASCII Input.pdf` """ -StringParser_ascii::StringParser = StringParser( +const StringParser_ascii::StringParser = StringParser( Dict( # 原子前缀 Word => "", # 置空 IVar => "\$", @@ -242,7 +242,7 @@ StringParser_ascii::StringParser = StringParser( (LaTeX扩展)实例化,并作为一个「转换器」导出 - 来源:文档 `NARS ASCII Input.pdf` """ -StringParser_latex::StringParser = StringParser( +const StringParser_latex::StringParser = StringParser( Dict( # 原子前缀 Word => "", # 置空 IVar => "\$", @@ -315,8 +315,17 @@ StringParser_latex::StringParser = StringParser( -"普通字符串" -Base.eltype(::StringParser) = String +""" +定义「字符串转换」的「目标类型」 +- String↔词项/语句 +""" +const STRING_PARSE_TARGETS::Type = DEFAULT_PARSE_TARGETS + +"目标类型:词项/语句" +parse_target_types(::StringParser) = STRING_PARSE_TARGETS + +"数据类型:普通字符串" +Base.eltype(::StringParser)::Type = String ## 已在template.jl导入 # using ..Util @@ -471,7 +480,7 @@ end - `[A,B]`/`{A,B}`: 词项集 - `?A`: 原子词项 """ -function data2narsese(parser::StringParser, ::Type{Term}, s::String) +function data2narsese(parser::StringParser, ::TYPE_TERMS, s::String) # 预处理覆盖局部变量 s::String = parser.preprocess(s) @@ -898,8 +907,8 @@ begin "语句相关" function data2narsese( parser::StringParser, ::Type{Punctuation}, s::String, - default = Judgement, - ) + default::Type = Judgement, # 📌【20230808 9:46:21】此处不能用Type{P}限制,会导致类型变量连锁,类型转换失败 + )::Type{ <: Union{Punctuation, Nothing}} get(parser.punctuation2type, s, default) end @@ -965,6 +974,9 @@ begin "语句相关" """ 总解析方法 : 词项+标点+时态+真值 + - 【20230808 9:34:22】兼容模式:词项语句均可 + - 在「目标类型」中统一使用Any以避免「各自目标类型不同」的歧义 + - 「真值」「时态」「标点」俱无⇒转换为词项 默认真值 default_truth - 核心功能:在没有真值时,自动创建真值 @@ -976,16 +988,17 @@ begin "语句相关" - (预处理去空格后)`B>.:|:%1.00;0.90%` """ function data2narsese( - parser::StringParser, ::Type{Sentence}, + parser::StringParser, + ::Type{Any}, # 兼容模式 s::String, F::Type=Float16, C::Type=Float16; default_truth::Truth = Truth16(1.0, 0.5), # 动态创建 - default_punctuation::Type = Judgement - ) + default_punctuation::Type = Judgement # 默认类型 + )::STRING_PARSE_TARGETS # 预处理覆盖局部变量 str::String = parser.preprocess(s) # 从尾部到头部,逐一解析「真值→时态→标点→词项」 - index::Integer = lastindex(str) + index_start::Integer = lastindex(str) truth::Truth, index = _match_truth(parser, str, F, C; default_truth) str = str[begin:index] # 反复剪裁 @@ -997,17 +1010,31 @@ begin "语句相关" str = str[begin:index] # 反复剪裁 term::Term = data2narsese(parser, Term, str) # 剩下就是词项 + + # 「真值」「时态」「标点」俱无⇒转换为词项 + index == index_start && return term + # 构造 return Sentence{punctuation}(term, truth, tense) end """ - 重定向默认值处理: AbstractSentence => Sentence + 兼容化后的「语句转换方法」:兼容+类型断言 """ function data2narsese( - parser::StringParser, ::Type{AbstractSentence}, - args...; kwargs...) - data2narsese(parser, Sentence, args...; kwargs...) + parser::StringParser, ::TYPE_SENTENCES, + s::String, + F::Type=Float16, C::Type=Float16; + default_truth::Truth = Truth16(1.0, 0.5), # 动态创建 + default_punctuation::Type = Nothing # 默认类型 + )::AbstractSentence # 使用类型断言限制 + data2narsese( + parser, Any, # Any对接兼容模式 + s, + F, C; + default_truth, + default_punctuation, + ) end """ diff --git a/src/Conversion/extra/json.jl b/src/Conversion/extra/json.jl deleted file mode 100644 index bfb6371..0000000 --- a/src/Conversion/extra/json.jl +++ /dev/null @@ -1,115 +0,0 @@ -#= -JSON转换 -- 基于AST的原理 -=# - -# 导入 - -import JSON - -# 导出 - -export JSONParser - -""" -JSON转换的两种形式 - -1. 「字典形式」 -``` -{ - "Word": ["词项"] -} -{ - "ExtDifference": [ - { - "Operator": "操作" - }, - { - "IVar": "独立变量" - } - ] -} -``` -2. 「数组形式」 -``` -[ - "Word", - "词项" -] -[ # 反正head只有一个 - "ExtDifference", - [ - "Operator", "操作" - ] - [ - "IVar", "独立变量" - ] -] -``` -""" -VTypes = Union{Dict, Vector} - -""" -提供JSON互转方法 - -初步实现方式: -- JSON↔object↔AST↔词项 -""" -abstract type JSONParser{Variant <: VTypes} <: AbstractParser end - -"以JSON表示的字符串" -Base.eltype(::Type{JSONParser{T}}) where T = String - -begin "基础方法集" - - "默认方法:用于递归处理参数(无需类型判断,只需使用多分派)" - function _preprocess(::Type{T}, val::Any) where T - val # 数字/字符串 - end - - "预处理:表达式⇒字典" - _preprocess(::Type{Dict}, ast::Expr)::Dict = Dict( - string(ast.head) => _preprocess.(Dict, ast.args) # 批量处理 - ) - - "预处理:表达式⇒数组(向量)" - _preprocess(::Type{Vector}, ast::Expr)::Vector = [ - string(ast.head), # 头 - _preprocess.(Vector, ast.args)... # 批量处理并展开 - ] - - "默认方法:用于递归处理参数(无需类型判断,只需使用多分派)" - _preparse(::Type, v::Any) = v # 数字/字符串 - - "预解析:字典⇒表达式" - _preparse(::Type{Dict}, d::Dict)::Expr = begin - pair::Pair = collect(d)[1] - return Expr( - Symbol(pair.first), # 只取第一个键当类名 - _preparse.(Dict, pair.second)..., # 只取第一个值当做参数集 - ) - end - - "预解析:数组(向量)⇒表达式" - _preparse(::Type{Vector}, v::Vector)::Expr = Expr( - Symbol(v[1]), # 取第一个当类名 - _preparse.(Vector, v[2:end])..., # 其后当做参数 - ) -end - -begin "具体转换实现(使用AST)" - - "JSON字符串⇒表达式⇒词项" - function data2narsese(::Type{JSONParser{V}}, ::Type{T}, json::String)::T where {T <: Conversion.DEFAULT_PARSE_TARGETS, V} - obj = JSON.parse(json) - expr::Expr = _preparse(V, obj) - return data2narsese(ASTParser, T, expr) - end - - "词项⇒表达式⇒JSON字符串" - function narsese2data(::Type{JSONParser{V}}, t::Conversion.DEFAULT_PARSE_TARGETS)::String where V - expr::Expr = narsese2data(ASTParser, t) - obj = _preprocess(V, expr) - return JSON.json(obj) - end -end diff --git a/src/Conversion/extra/serialization.jl b/src/Conversion/extra/serialization.jl deleted file mode 100644 index 55da3df..0000000 --- a/src/Conversion/extra/serialization.jl +++ /dev/null @@ -1,41 +0,0 @@ -import Serialization - -export S11nParser - -""" -提供字节流(序列化/反序列化)处理方法 -- ⚠尚不稳定:读取可能会出错 -""" -abstract type SerializationParser <: AbstractParser end -const S11nParser = SerializationParser - -const Bytes8::DataType = Vector{UInt8} - -"类型の短别名" -TSParser = Type{S11nParser} - -"字节流对象:Vector{UInt8}" -Base.eltype(::TSParser) = Bytes8 - -# 正式开始 # - -# 具体词项对接 - -""" -总「解析」方法:任意词项都可序列化 -- 任意词项类型都适用 -""" -function data2narsese(::TSParser, ::Type{T}, bytes::Bytes8)::T where {T <: Conversion.DEFAULT_PARSE_TARGETS} - Serialization.deserialize( - IOBuffer(bytes) - ) -end - -""" -所有词项的序列化方法 -""" -function narsese2data(::TSParser, t::Conversion.DEFAULT_PARSE_TARGETS)::Bytes8 - b::IOBuffer = IOBuffer() - Serialization.serialize(b, t) - return b.data::Bytes8 # 断言其必须是Bytes8 -end diff --git a/src/Conversion/extra/string_shortcut.jl b/src/Conversion/extra/string_shortcut.jl index a47f091..0f326d6 100644 --- a/src/Conversion/extra/string_shortcut.jl +++ b/src/Conversion/extra/string_shortcut.jl @@ -7,24 +7,50 @@ export ShortcutParser abstract type ShortcutParser <: AbstractParser end "类型の短别名" -TSCParser = Type{ShortcutParser} +const TSCParser::Type = Type{ShortcutParser} -"以代码表示的字符串" -Base.eltype(::TSCParser) = String +"目标类型:默认" +const SHORTCUT_PARSE_TARGETS::Type = Conversion.DEFAULT_PARSE_TARGETS + +"数据类型:以代码表示的字符串" +Base.eltype(::TSCParser)::Type = String ## 已在template.jl导入 # using ..Util # using ..Narsese +""" +翻译其中会导致「变量未定义」错误的符号 +- ⚠会改变对象本身 +""" +function _translate_words!(expr::Expr)::Expr + expr +end + """ 总「解析」方法 - !问题:遇到没有语法对应的「词项」无法处理 """ -function data2narsese(::TSCParser, ::Type{Term}, s::String) +function data2narsese(::TSCParser, ::Conversion.TYPE_TERMS, s::String) + try # 尝试解析 + expr::Expr = s |> Meta.parse + # TODO: 替换其中的符号,使原子词项正常显示 + return expr |> _translate_words! |> Narsese.eval + catch e + @error e + return nothing + end +end + +""" +重定向:兼容模式→词项 +-【20230808 10:18:31】此实为缓兵之计 +""" +function data2narsese(::TSCParser, ::Type{Any}, s::String) try # 尝试解析 expr::Expr = s |> Meta.parse # TODO: 替换其中的符号,使原子词项正常显示 - return expr |> Narsese.eval + return expr |> _translate_words! |> Narsese.eval catch e @error e return nothing diff --git a/src/Conversion/extra/xml.jl b/src/Conversion/extra/xml.jl deleted file mode 100644 index fb6d150..0000000 --- a/src/Conversion/extra/xml.jl +++ /dev/null @@ -1,629 +0,0 @@ -#= -XML转换 -- 基于AST的原理 -=# - -# 导入 - -import XML - -# 导出 - -export XMLParser, XMLParser_optimized, XMLParser_pure - -""" -提供XML互转方法 - -## 初步实现方式 -- 词项↔AST↔XML -- 📄解析后XML内只有: - - String(位于「文本」类型的XML.Node) - - 这意味着Number、Symbol类型需先从String中解析 - - Number => parse(类型, 字符串值) - - 照应AST中的`数字 => Expr(:类型, 值)(视作「结构类型」)` - - Symbol => Symbol(字符串值) - - 非保留特征头⇒类名 - - 其它XML.Node - -## 「基于AST解析器+附带优化」的一般逻辑 - -核心:半「AST机翻」半「自行润色」 -0. 可扩展性: - - 区分「AST方法」与「私有方法」 - - 使用ASTの递归回调机制,回调指向「私有方法」实现「内层预润色」 -1. 解析の逻辑(XML:XML.Node⇒目标对象) - - 参数集:解析器,被解析对象(XML.Node) - - 「eval函数」「递归回调函数」均由「私有解析方法」决定 - - 其它参数用法同AST - - 若有「特别解析通道」:(XML:纯翻译模式不走此路) - - 协议:特别解析函数(解析器, 识别出的类型, 被解析对象) - 1. 通过「特别方式」直接组装成Expr - - (XML)原生类型String:节点类型==XML.Text - - 返回value - 2. 用AST解析Expr,其中回调「解析函数」(XML:`recurse_callback=xml_parse`) - - 此举相当于「先回调解析,再AST解析单层Expr」 - - 默认: - 1. 拆分XML,得到「数据对象」+未解析参数集(可能中途返回) - - (XML)ASTの结构类型:自动消转义(或根据类分派「特别方式」) - 1. 类名::String = 标签==结构转义标签 ? 取type属性 : 标签 - 2. 类::Type = AST解析类名 - 3. 分派「特别方式」:调用「特别解析函数」 - - 用于「带优化模式」中词项、语句的优化 - - 同时存在 - 4. 若无分派(返回「被解析对象」自身):获取头 - - 头::Symbol = Symbol(类名) - - (XML)ASTの保留类型:标签==保留类标签 - - 头::Symbol = Symbol(取head属性) - - (XML)[新] 数值类型:标签==数值类标签 - 1. 读取「类型」「字符串值」属性 - 2. 调用「字符串⇒数值」方法:`Base.parse(type, value)` - 3. 直接返回解析后的数值 - - 例:`` => `Base.parse(Int8, "127")` => `127::Int8` - 2. 将「未解析参数集」作为args,组装出Expr(XML:子节点children) - 3. 用AST解析Expr(头, args),其中回调「解析函数」(XML:`recurse_callback=xml_parse`) - - 相当于「先拆分XML,再逐一转换参数集,最后用AST解析单层」 -2. 打包の逻辑(XML:目标对象⇒XML.Node) - - 参数集:解析器,被打包对象 - - 「eval函数」「递归回调函数」均由「私有打包方法」决定 - - 其它参数用法同AST - - 若走「特别打包通道」:(XML:纯翻译模式不走此路) - - 实现方法:「被打包对象」的类型派发 - - 对其内所有参数回调「打包函数」 - - 通过「特别方式」直接组装成数据对象(XML) - - (XML)例: - - 字符串:返回「纯文本」`XML.Node(字符串)` - - 数值:返回「数值类型」 - - `127::Int8` => `` - - 默认: - 1. 用AST打包一层得Expr,其中回调「解析函数」(XML:`recurse_callback=xml_parse`) - - 或:翻译一层对「待解析参数集」回调「打包函数」 - 2. 拆分Expr,得到「数据对象」(XML)+已解析参数集(Any) - - (XML)ASTの结构类型:根据类名决定是否转义 - - 转义:<结构转义标签 type="类名">... - - (XML)ASTの保留类型:<保留类标签 head="表达式头"> - - (XML)ASTの原生类型:会被「特别打包通道」分派 - - 字符串 - - 数值 - 3. 组装成分,得到完整的「数据对象」(XML) - - -## 已知问题 - -### 对节点标签带特殊符号的XML解析不良 - -例1:前导冒号丢失——影响「保留特征头」 -``` -julia> s1 = XML.Node(XML.Element,":a:", 1,1,1) |> XML.write -"<:a: 1=\"1\">1" - -julia> XML.parse(s1, Node) |> XML.write -"1\n" -``` - -例2:带花括号文本异位——影响「结构类型の解析」 -``` -julia> n = XML.Node(XML.Element,"a{b}", (type="Vector{Int}",),1,1) -Node Element (1 child) - -julia> XML.write(n) -"1" - -julia> XML.parse(XML.write(n),Node)[1] -Node Element (1 child) -``` - -### 对单自闭节点解析失败 - -例: -``` -julia> s1 = XML.Node(XML.Element,"a") |> XML.write -"" - -julia> XML.parse(s1, Node) |> XML.write -ERROR: MethodError: no method matching isless(::Int64, ::Nothing) - -Closest candidates are: - isless(::Real, ::AbstractFloat) - @ Base operators.jl:178 - isless(::Real, ::Real) - @ Base operators.jl:421 - isless(::Any, ::Missing) - @ Base missing.jl:88 - ... -``` - -## 例 -源Narsese: -`<(|, A, ?B) --> (/, A, _, ^C)>. :|: %1.0;0.5%` - -AST: -``` -:SentenceJudgement - :Inheriance, - :IntIntersection, - :Word, - "A" # 字符串与符号通用 - :QVar, - "B" - :ExtImage, - 2, - :Word, - "A" - :Operator, - "C" - :Truth - 1.0 - 0.5 - :Stamp{Present} - 【保留特征头】 - :vect - :Int - 0 - :Int - 0 - :Int - 0 -``` - -纯翻译模式`XMLParser{Expr}` -``` - - - - A - B - - - 1 - A - C - - - - 1.0 - 0.5 - - <结构转义标签 type="Stamp{Present}"> - <保留类标签 head="vect"/> - 0 - 0 - 0 - - -``` - -带优化模式`XMLParser` -``` - - - - - - - - - - - - - - <保留类标签 head="vect"/> - - - - - -``` -""" -abstract type XMLParser{Varient} <: AbstractParser end - -"类型の短别名" -const TXMLParser::Type = Type{<:XMLParser} # 泛型の不变性の要求 - -"声明各个「子模式」:纯翻译、带优化" -const XMLParser_optimized::Type = XMLParser # 不带参数类 -const XMLParser_pure::Type = XMLParser{Dict} # 带参数类Dict - -const TXMLParser_optimized::Type = Type{XMLParser_optimized} # 仅一个Type -const TXMLParser_pure::Type = Type{XMLParser_pure} - -""" -声明「目标类型」 -- 能被解析器支持解析 -""" -const XML_PARSE_TARGETS::Type = DEFAULT_PARSE_TARGETS - -""" -声明「原生类型」 -- 解析器直接返回Node(自身) -""" -const XML_NATIVE_TYPES = Union{ - String # 字符串 -} - -""" -声明用于「保留类型识别」的「保留类标签」 -- ⚠已知问题:该「保留类标签」可能与AST中「保留特征头」不同 - - 前导冒号缺失:如`:a:` => `a:` - - 【20230806 18:07:36】目前尚不影响 -""" -const XML_PRESERVED_TAG::String = XML.parse( - XML.Node( - XML.Element, - string(Conversion.AST_PRESERVED_HEAD), # 作为字串 - 1,1,1 # 后面几个是占位符,避免「单自闭节点解析失败」的Bug - ) |> XML.write, - XML.Node -)[1].tag # `[1]`从Document到Element,`.tag`获取标签(字符串) - -""" -声明「结构转义标签」 -- 用于可能的「Vector{Int}」的转义情况 -""" -const XML_ESCAPE_TAG::String = "XML_ESCAPE" - -""" -用于判断「是否需要转义」的正则表达式 -- 功能:判断一个「构造函数名」是否「符合XML节点标签」标准 -- 逻辑:不符合标准⇒需要转义 -""" -const XML_ESCAPE_REGEX::Regex = r"^\w+$" - -""" -声明「数值类标签」 -- 用于定义XML「字符串⇒数值」的「数值类」 -""" -const XML_NUMBER_TAG::String = "Number" - -""" -声明「数值类」(用于打包) -""" -const XML_NUMBER_TYPES = Union{ - Number -} - -"以XML表示的字符串" -Base.eltype(::TXMLParser) = String - -begin "解析の逻辑" - - """ - 默认解析方法 - - 仅用于: - - XML.Element - - XML.Text - """ - function xml_parse( - parser::TXMLParser, n::XML.Node, - eval_function = Narsese.eval - )::Any - # 原生类型:字符串 - if n.nodetype == XML.Text - return n.value - end - - local tag::String = n.tag - local head::Symbol, args::Vector, type::Type, literal::String - # 保留类型 - if tag == XML_PRESERVED_TAG - head = Symbol(n.attributes["head"]) - args = n.children - # 数值类型 - elseif tag == XML_NUMBER_TAG - return xml_parse_special(parser, Number, n) - # 结构类型 - else - literal = ( - tag == XML_ESCAPE_TAG ? - Base.eval(n.attributes["type"]) : - tag - ) - # 字符串⇒类型 - type = eval_function(Meta.parse(literal)) # 可能解析出错 - # 尝试「特别解析」:取捷径解析对象 - parse_special::Any = xml_parse_special( - parser, type, n - ) - if parse_special isa XML.Node # 返回自身⇒继续 - head = Symbol(literal) - args = n.children - else - # 直接返回原对象 - return parse_special - end - end - # 统一解析 - expr::Expr = Expr(head, args...) - return Conversion.ast_parse( - ASTParser, - expr, - Narsese.eval, - xml_parse, - parser, # 递归回调解析器 - ) - end - - """ - (面向Debug)预打包@Symbol:xml将Symbol解析成构造函数 - """ - xml_pack(parser::TXMLParser, s::Symbol)::XML.Node = XML.Node( - "Symbol", - nothing, - nothing, - nothing, - xml_pack(parser, string(s)) - ) - - """ - 默认预打包:任意对象⇒节点 - """ - function xml_pack(parser::TXMLParser, v::Any)::XML.Node - # 先打包一层得「args全是Node的Expr」 - expr::Expr = Conversion.ast_pack( - ASTParser, v, xml_pack, - parser, # 递归回调解析器 - ) - - local tag::Union{String,Symbol} - local attributes::Union{NamedTuple,Nothing} - local children::Vector{XML.Node} - # 保留类型:此时是Expr(保留特征头, 表达式头, 表达式参数...) - if expr.head == Conversion.AST_PRESERVED_HEAD - tag = XML_PRESERVED_TAG # 保留类标签 - attributes = (head=String(expr.args[1]),) # 获取第一个元素作「类名」(Symbol) - children = expr.args[2:end] # 从第二个开始 - # 结构类型:此时是Expr(:类名, 表达式参数...) - else - tag = string(expr.head) # Symbol→string - # 转义的条件:类名包含特殊符号 - if isnothing(match(XML_ESCAPE_REGEX, tag)) - tag = XML_ESCAPE_TAG - attributes = (type=tag,) # 具名元组 - else # 否则无需转义 - attributes = nothing - end - children = expr.args - end - # 返回节点 - return XML.Node( - XML.Element, # 类型:元素 - tag, - attributes, - nothing, # 无value - children, - ) - end - - """ - 默认「特别解析」:返回节点自身 - - 亦针对「原生类型」 - """ - xml_parse_special(::TXMLParser, ::Type, n::XML.Node)::XML.Node = n - - """ - 预打包:原生类型⇒XML节点: - - 用于处理可以直接转换的原始类型数据 - - 最终会变成字符串 - """ - xml_pack(::TXMLParser, val::XML_NATIVE_TYPES)::XML.Node = XML.Node(val) - - """ - 特别解析@数值:节点⇒数值 - """ - xml_parse_special(::TXMLParser, ::Type{Number}, n::XML.Node) = Base.parse( - Base.eval(n.attributes["type"]), - n.attributes["value"] - ) - - """ - 预打包:数值类型⇒XML节点: - - 任何XML解析器都支持解析 - - 用于处理可以直接转换的原始类型数据 - - 最终会变成字符串 - - 【20230806 20:32:37】已知问题:对带有Rational的数字类型,parse会产生解析错误 - """ - xml_pack(::TXMLParser, num::Number)::XML.Node = XML.Node( - XML.Element, - XML_NUMBER_TAG, # 数值打包 - ( # 两个属性:类型&字符串值 - type=string(typeof(num)), # 类型 - value=string(num), # 数值 - ) # 后续属性空着不写 - ) - - """ - 特别解析@带优化:节点⇒原子词项 - """ - function xml_parse_special(::TXMLParser_optimized, ::Type{T}, n::XML.Node)::Term where {T <: Atom} - type::DataType = Narsese.eval(Symbol(n.tag)) # 获得类型 - name::Symbol = n.attributes["name"] |> Symbol - return type(name) # 构造原子词项 - end - - """ - 预打包:原子词项⇒XML节点 - - 示例:`A` ⇒ `` - """ - xml_pack(::TXMLParser_optimized, t::Atom)::XML.Node = XML.Node( - XML.Element, # 类型:元素 - typeof(t) |> string, # 词项类型⇒元素标签 - (name=string(t.name),), # 属性:name=名称(字符串) - ) - - """ - 特别解析@带优化:节点⇒陈述 - """ - function xml_parse_special(parser::TXMLParser_optimized, ::Type{T}, n::XML.Node)::Statement where {T <: Statement} - type::DataType = Narsese.eval(Symbol(n.tag)) # 获得类型 - ϕ1::Term = xml_parse(parser, n[1]) - ϕ2::Term = xml_parse(parser, n[2]) - return type(ϕ1, ϕ2) # 构造原子词项 - end - - """ - 预打包:陈述⇒XML节点 - - 示例:` B>` ⇒ ``` - - - - - ``` - """ - xml_pack(parser::TXMLParser_optimized, t::Statement)::XML.Node = XML.Node( - XML.Element, # 类型:元素 - typeof(t) |> string, # 词项类型⇒元素标签 - nothing, # 无属性 - nothing, # 无value - XML.Node[ - xml_pack(parser, t.ϕ1) # 第一个词项 - xml_pack(parser, t.ϕ2) # 第二个词项 - ] - ) - - """ - 特别解析@带优化:节点⇒词项集/陈述集(像除外) - """ - function xml_parse_special(parser::TXMLParser_optimized, ::Type{T}, n::XML.Node)::Term where {T <: Union{ATermSet, AStatementSet}} - type::DataType = Narsese.eval(Symbol(n.tag)) # 获得类型 - args = isnothing(n.children) ? [] : n.children # n.children可能是nothing - terms::Vector = [ - xml_parse(parser, child)::Term - for child::XML.Node in args - ] # 广播 - return type(terms...) # 构造原子词项 - end - - """ - 预打包:词项集/陈述集(像除外) - - 特点:逐一打包其元素terms - """ - xml_pack(parser::TXMLParser_optimized, t::Union{ATermSet, AStatementSet})::XML.Node = XML.Node( - XML.Element, # 类型:元素 - typeof(t) |> string, # 词项类型⇒元素标签 - nothing, # 无属性 - nothing, # 无value - [ # 子节点 - xml_pack(parser, term)::XML.Node - for term::Term in t.terms # 统一预处理 - ] - ) - - """ - 特别解析@带优化:节点⇒像 - """ - function xml_parse_special(parser::TXMLParser_optimized, ::Type{T}, n::XML.Node)::TermImage where {T <: TermImage} - type::DataType = Narsese.eval(Symbol(n.tag)) # 获得类型 - args = isnothing(n.children) ? [] : n.children - terms::Vector = [ - xml_parse(parser, child)::Term - for child::XML.Node in args - ] # 广播 - relation_index::Integer = parse(UInt, n.attributes["relation_index"]) # 📌parse不能使用抽象类型 - return type(relation_index, terms...) # 构造原子词项 - end - - """ - 预打包:像 - - 唯一区别就是有「占位符位置」 - """ - xml_pack(parser::TXMLParser_optimized, t::TermImage)::XML.Node = XML.Node( - XML.Element, # 类型:元素 - typeof(t) |> string, # 词项类型⇒元素标签 - (relation_index=string(t.relation_index),), # relation_index属性:整数 - nothing, # 无value - [ # 子节点 - xml_pack(parser, term)::XML.Node - for term::Term in t.terms # 统一预处理 - ] - ) - - """ - 特别解析@带优化:节点⇒真值 - """ - function xml_parse_special(::TXMLParser_optimized, ::Type{T}, n::XML.Node)::Truth where {T <: Truth} - type::DataType = Narsese.eval(Symbol(n.tag)) # 获得类型 - # 解析其中的f、c值:从类名中获得精度信息 - f_str::String, c_str::String = n.attributes["f"], n.attributes["c"] - f_type::Type, c_type::Type = type.types # 获取所有类型参数(一定是两个参数,不受别名影响) - f::f_type, c::c_type = parse(f_type, f_str), parse(c_type, c_str) - # 构造 - return type(f, c) - end - - """ - 预打包:真值⇒XML节点 - - 示例:`%1.0;0.5%` ⇒ `` - """ - xml_pack(::TXMLParser_optimized, t::Truth)::XML.Node = XML.Node( - XML.Element, # 类型:元素 - typeof(t) |> string, # 词项类型⇒元素标签 - (f=string(t.f),c = string(t.c)), # 属性:f、c - ) - - """ - 特别解析@带优化:节点⇒时间戳 - """ - function xml_parse_special( - parser::TXMLParser_optimized, - ::Type{T}, - n::XML.Node - )::Stamp where {T <: Stamp} - type::Type = Narsese.eval(Symbol(n.tag)) # 获得根类型 - tense::Type{<:Tense} = Narsese.eval(Symbol(n.attributes["tense"])) # 获得类型参数 - # 构造:当结构类型 - args = isnothing(n.children) ? [] : n.children - return type{tense}( - ( - # 这里把第四个参数留作默认值 - xml_parse(parser, arg) - for arg::XML.Node in args - )... - ) - end - - """ - 预打包:时间戳⇒XML节点 - - 前提假定:此中Stamp的「类型参数」一定是实例所属类型的「类型参数」 - - 亦即协议:`具体时间戳类{tense <: AbstractTense} <: AbstractStamp{tense}` - - 例:对`StampBasic{Eternal}` - - `StampBasic{Eternal} <: Stamp{Eternal}`提取出「时态」`Eternal` - - `StampBasic{Eternal}.name.name == :StampBasic`提取出「母类名」 - """ - function xml_pack(parser::TXMLParser_optimized, s::T)::XML.Node where { - tense <: Tense, # 先有时态 - T <: Stamp{tense} # 然后通过「继承Stamp{时态}」断言,提取出「时态类型」 - } - # 先打包一层得「args全是Node的Expr」 - expr::Expr = Conversion.ast_pack( - ASTParser, s, xml_pack, - parser, # 递归回调解析器 - ) - # 再利用里面的「子节点」构建节点 - XML.Node( - XML.Element, # 类型:元素 - typeof(s).name.name, - (tense=string(tense),), # 属性:时态类型 - expr.args - ) - end - -end - -begin "入口" - - "XML字符串⇒XML节点⇒表达式⇒目标对象" - function data2narsese(parser::TXMLParser, ::Type{T}, xml::String)::T where {T <: XML_PARSE_TARGETS} - document::XML.Node = XML.parse(xml, XML.Node) # 使用parse(字符串, Node)实现「字符串→Node」 - @assert document[1].nodetype == XML.Element "文档字符串的首个子节点$(document[1])不是元素!" - return xml_parse(parser, document[1])::XML_PARSE_TARGETS # 「文档节点」一般只有一个元素 - end - - "目标对象⇒表达式⇒XML节点⇒XML字符串" - function narsese2data(parser::TXMLParser, t::XML_PARSE_TARGETS)::String - node::XML.Node = xml_pack(parser, t) - @assert node.nodetype == XML.Element "转换成的子节点$(document[1])不是元素!" - return XML.write(node)::eltype(parser) # 使用write实现「Node→字符串」 - end -end diff --git a/src/Conversion/template.jl b/src/Conversion/template.jl index e5eb4f4..f2bb034 100644 --- a/src/Conversion/template.jl +++ b/src/Conversion/template.jl @@ -9,17 +9,17 @@ using ..Narsese # 导出 export narsese2data, data2narsese # 数据互转 -export narsese2data, data2narsese # 泛型构造方法 +export parse_target_types, TYPE_TERMS, TYPE_SENTENCES """ 陈述转换器的抽象类型模板 使用方法: 1. 其它「类型转换器」注册一个type继承AbstractParser - - 目标(data)类型「TargetData」:`Base.eltype(type)::DataType = TargetData` + - 目标(data)类型「TargetData」:`Base.eltype(type)::Type = TargetData` 2. 转换「词项→数据」: 使用`narsese2data(type, term)::TargetData` 3. 转换「数据→词项」: 使用`data2narsese(type, T <: Term, data::TargetData)::T` - - 「总转换入口」:使用「根部方法」`data2narsese(type, T::Type{Term}, data::TargetData)::Term` + - 「总转换入口」:使用「根部方法」`data2narsese(type, T::Conversion.TYPE_TERMS, data::TargetData)::Term` """ abstract type AbstractParser end @@ -44,14 +44,37 @@ const TAbstractParser::Type = Union{ const DEFAULT_PARSE_TARGETS::Type = Union{ AbstractTerm, AbstractSentence, -} + } + +""" + const TYPE_SENTENCES::Type = Type{<:AbstractSentence} + +声明用于「目标类型参数」转换所需的「词项类型」 +""" +const TYPE_TERMS::Type = Type{<:AbstractTerm} + +""" + const TYPE_SENTENCES::Type = Type{<:AbstractSentence} + +声明用于「目标类型参数」转换所需的「语句类型」 +- 【20230808 10:26:34】「兼容模式」的出现,是否意味着「类型参数」转换的过时? +""" +const TYPE_SENTENCES::Type = Type{<:AbstractSentence} """ (默认)返回其对应「词项↔数据」中「数据」的类型 -""" # 📌【20230727 15:59:03】只写在一行会报错「UndefVarError: `T` not defined」 -function Base.eltype(::Type{T})::DataType where {T <: AbstractParser} - Any -end +- 未注册→报错 +""" +Base.eltype(::TAbstractParser)::ErrorException = error("未注册「数据类型」!") + +"解析器将「目标类型」转换成的「数据类型」" +# function Base.eltype end + +""" +解析器的「目标类型」:一般是词项/语句 +- 未注册→报错 +""" +parse_target_types(::Any)::ErrorException = error("未注册「目标类型」!") """ 纳思语→数据 声明 @@ -64,17 +87,17 @@ function narsese2data end function data2narsese end """ -直接调用(解析器作为类型):根据参数类型自动转换(词项) +直接调用(解析器作为类型):根据参数类型自动转换(目标) - 用处:便于简化成「一元函数」以便使用管道运算符 - 自动转换逻辑: - - 数据→词项 - - 词项→数据 -- 参数 target:词项/数据 + - 数据→目标 + - 目标→数据 +- 参数 target:目标/数据 """ function (parser::Type{TParser})( - target, # 目标对象(可能是「数据」也可能是「词项」) - TargetType::Type{T} = Term, # 只有「数据→词项」时使用(默认为「Term」即「解析成任意词项」) -) where {TParser <: AbstractParser, T <: DEFAULT_PARSE_TARGETS} + target, # 目标对象(可能是「数据」也可能是「目标」) + TargetType::Type = Any, # 只有「数据→目标」时使用(默认为「Term」即「解析成任意目标」) +) where {TParser <: AbstractParser} if target isa eltype(parser) return data2narsese(parser, TargetType, target) else @@ -86,63 +109,25 @@ end 直接调用(解析器作为实例):根据参数类型自动转换(词项) - 用处:便于简化成「一元函数」以便使用管道运算符 - 自动转换逻辑: - - 数据→词项 - - 词项→数据 -- 参数 target:词项/数据 + - 数据→目标 + - 目标→数据 +- 参数 target:目标/数据 """ function (parser::AbstractParser)( - target, # 目标对象(可能是「数据」也可能是「词项」) - TargetType::Type{T} = Term, # 只有「数据→词项」时使用(默认为「Term」即「解析成任意词项」) - ) where {T <: DEFAULT_PARSE_TARGETS} + target, # 目标对象(可能是「数据」也可能是「目标」) + TargetType::Type = Any, # 【20230808 9:38:26】现采用「兼容模式」,默认为Any + )::Any if target isa eltype(parser) - return data2narsese(parser, TargetType, target) + return data2narsese(parser, TargetType, target)::parse_target_types(parser) # 使用「目标类型」检测是否合法 else return narsese2data(parser, target)::eltype(parser) end end -""" -数组索引(类型):根据参数类型自动转换(语句) -- 用处:便于简化成「一元函数」以便使用索引 -- 自动转换逻辑: - - 数据→词项 - - 词项→数据 -- 参数 target:词项/数据 -""" -function Base.getindex( - parserType::Type{TParser}, - target, # 目标对象(可能是「数据」也可能是「语句」) -) where {TParser <: AbstractParser} - if target isa eltype(parserType) - return data2narsese(parserType, AbstractSentence, target) - else - return narsese2data(parserType, target) - end -end - -""" -直接调用(实例):根据参数类型自动转换(语句) -- 用处:便于简化成「一元函数」以便使用索引 -- 自动转换逻辑: - - 数据→词项 - - 词项→数据 -- 参数 target:词项/数据 -""" -function Base.getindex( - parser::AbstractParser, - target, # 目标对象(可能是「数据」也可能是「语句」) - ) - if target isa eltype(parser) - return data2narsese(parser, AbstractSentence, target) - else - return narsese2data(parser, target) - end -end - """ 设定「解析器」的广播行为 - 默认和「函数」是一样的 - 用于`x .|> 解析器`的语法 - 参考:broadcast.jl/713 """ -Base.broadcastable(parser::AbstractParser) = Ref(parser) +Base.broadcastable(parser::TAbstractParser) = Ref(parser) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/test/commons.jl b/test/commons.jl new file mode 100644 index 0000000..32bb88a --- /dev/null +++ b/test/commons.jl @@ -0,0 +1,94 @@ +if !isdefined(Main, :JuNarsese) + push!(LOAD_PATH, "../src") # 用于直接打开(..上一级目录) + push!(LOAD_PATH, "src") # 用于VSCode调试(项目根目录起) + + # 自动导入JuNarsese模块 + using JuNarsese +end +if !isdefined(Main, :Test) + using Test + """ + ======== + 标准测试集: + - 测试集类型:具名元组 + - terms: 测试用词项 + - sentences: 测试用语句 + ======== + """ |> println + + # 原子词项 + w,i,d,q,o = w"词项", w"独立变量"i, w"非独变量"d, w"查询变量"q, w"操作"o + A,B,C,D,R = "A B C D R" |> split .|> String .|> Symbol .|> Word + # 像、陈述、乘积 + @show s1 = (/(R, A, B, ⋄, D) → C) ⇒ (*(A, B, C, D) → R) + # 词项集、陈述逻辑集 + @show s2 = (|(A, B, C) → D) ⇒ ∨((A → D), (B → D), (C → D)) + # 快捷方式解析 + @show s3 = ShortcutParser.( + """( w"A"q * w"B"i ) → w"C"o """ + ) + # 词项集&词项逻辑集 + @show s4 = Base.:(&)(w, i, d, q, o) - Base.:(|)(w, i, d, q, o) + @show s5 = ∩(A, B, C) - ∪(A, B, C) + # 时序合取 + @show s6 = ∨(⩜(A→B, B→C, C→D), ⩚(A→B, B→C, C→D)) ⇒ (A→D) + # 副系词|时序蕴含/等价 + s7 = ParConjunction(StringParser_ascii.([ + "" + "" + "" + + raw" B>" + raw" B>" + raw" B>" + + raw" B>" + raw" B>" + raw" B>" + ])...) + @show s7 + # 极端嵌套情况 + s8 = *( + ⩚( + ⩜(A→B, B→C, C→D), + ∨(ExtSet(A, B, C)→D, w→o), ⩚(A→B, B→C, C→D) + ), + ∧(s1, s2), + \(A, ⋄, s3, s5) → s2, + /(s1, ⋄, B, s4) → s3, + ¬(Base.:(&)(w, i, d, q, o) → IntSet(s6, ∩(A, B, C))) + ) → (s6 ⇒ s7) + @show s8 + + terms = [w, i, d, q, o, s1, s2, s3, s4, s5, s6, s7, s8] + @info "terms: " terms + + # 测试语句 【20230808 11:06:37】暂无「快捷构造方式」 + f_s = s -> StringParser_ascii(s) + sentences = f_s.([ + "B>. :|: %1.00;0.90% " + "! :|: " + "<<(*, A, B) --> (*, C, D)> ==> (&&, C>, D>)>@ %1.00;0.90%" + "<(*, A, B, C, D) --> R>? " + ]) + @info "sentences: " sentences + # ASTParser.(ASTParser.(ASTParser.(sentences, Sentence), Sentence), Sentence) + # XMLParser_optimized.(XMLParser_optimized.(XMLParser_optimized.(sentences, Sentence), Sentence), Sentence) + # @info "sentences@AST: " ASTParser.(ASTParser.(ASTParser.(sentences, Sentence), Sentence), Sentence) + # @info "sentences@XML: " XMLParser.(XMLParser.(XMLParser.(sentences, Sentence), Sentence), Sentence) + # @info "sentences@JSON: " JSONParser{Dict}.(JSONParser{Dict}.(JSONParser{Dict}.(sentences, Sentence), Sentence), Sentence) + # for (t1, t2) in zip(tss, sentences) + # if t1 ≠ t2 + # # dump.(ASTParser.([t1, t2]); maxdepth=typemax(Int)) + # @info t1==t2 t1 t2 + # end + # @assert t1 == t2 "Not eq!\n$t1\n$t2" + # end + # @show sentences + + "标准测试集 = 词项 + 语句" + test_set::NamedTuple = (;terms, sentences) # 具名元组 + # test_set = (;terms=[s7], sentences=[]) + + "测试集:\n$test_set" |> println +end diff --git a/test/runtests.jl b/test/runtests.jl index e12ac81..6081d79 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,9 @@ push!(LOAD_PATH, "../src") # 用于直接打开(..上一级目录) push!(LOAD_PATH, "src") # 用于VSCode调试(项目根目录起) -include("test_narsese.jl") -include("test_conversion.jl") +include("commons.jl") # 已在此中导入JuNarsese + +@testset "JuNarsese" begin + include("test_narsese.jl") + include("test_conversion.jl") +end diff --git a/test/test_conversion.jl b/test/test_conversion.jl index 670d75b..b2482e4 100644 --- a/test/test_conversion.jl +++ b/test/test_conversion.jl @@ -1,85 +1,4 @@ -push!(LOAD_PATH, "../src") # 用于直接打开(..上一级目录) -push!(LOAD_PATH, "src") # 用于VSCode调试(项目根目录起) - -using JuNarsese - -using Test - -# 快捷构造 # - -# 原子词项 -w,i,d,q,o = w"词项", w"独立变量"i, w"非独变量"d, w"查询变量"q, w"操作"o -A,B,C,D,R = "A B C D R" |> split .|> String .|> Symbol .|> Word -# 像、陈述、乘积 -@show s1 = (/(R, A, B, ⋄, D) → C) ⇒ (*(A, B, C, D) → R) -# 词项集、陈述逻辑集 -@show s2 = (|(A, B, C) → D) ⇒ ∨((A → D), (B → D), (C → D)) -# 快捷方式解析 -@show s3 = ShortcutParser.( - """( w"A"q * w"B"i ) → w"C"o """ -) -# 词项集&词项逻辑集 -@show s4 = Base.:(&)(w, i, d, q, o) - Base.:(|)(w, i, d, q, o) -@show s5 = ∩(A, B, C) - ∪(A, B, C) -# 时序合取 -@show s6 = ∨(⩜(A→B, B→C, C→D), ⩚(A→B, B→C, C→D)) ⇒ (A→D) -# 副系词|时序蕴含/等价 -s7 = ParConjunction(StringParser_ascii.([ - "" - "" - "" - - raw" B>" - raw" B>" - raw" B>" - - raw" B>" - raw" B>" - raw" B>" -])...) -@show s7 -# 极端嵌套情况 -s8 = *( - ⩚( - ⩜(A→B, B→C, C→D), - ∨(ExtSet(A, B, C)→D, w→o), ⩚(A→B, B→C, C→D) - ), - ∧(s1, s2), - \(A, ⋄, s3, s5) → s2, - /(s1, ⋄, B, s4) → s3, - ¬(Base.:(&)(w, i, d, q, o) → IntSet(s6, ∩(A, B, C))) -) → (s6 ⇒ s7) -@show s8 - -terms = [w, i, d, q, o, s1, s2, s3, s4, s5, s6, s7, s8] -@info "terms: " terms - -# 测试语句 -f_s = s -> StringParser_ascii[s] -sentences = f_s.([ - "B>. :|: %1.00;0.90% " - "! :|: " - "<<(*, A, B) --> (*, C, D)> ==> (&&, C>, D>)>@ %1.00;0.90%" - "<(*, A, B, C, D) --> R>? " -]) -@info "sentences: " sentences -# ASTParser.(ASTParser.(ASTParser.(sentences, Sentence), Sentence), Sentence) -# XMLParser_optimized.(XMLParser_optimized.(XMLParser_optimized.(sentences, Sentence), Sentence), Sentence) -# @info "sentences@AST: " ASTParser.(ASTParser.(ASTParser.(sentences, Sentence), Sentence), Sentence) -# @info "sentences@XML: " XMLParser.(XMLParser.(XMLParser.(sentences, Sentence), Sentence), Sentence) -# @info "sentences@JSON: " JSONParser{Dict}.(JSONParser{Dict}.(JSONParser{Dict}.(sentences, Sentence), Sentence), Sentence) -# for (t1, t2) in zip(tss, sentences) -# if t1 ≠ t2 -# # dump.(ASTParser.([t1, t2]); maxdepth=typemax(Int)) -# @info t1==t2 t1 t2 -# end -# @assert t1 == t2 "Not eq!\n$t1\n$t2" -# end -# @show sentences - -"标准测试集 = 词项 + 语句" -test_set = (;terms, sentences) # 具名元组 -# test_set = (;terms=[s7], sentences=[]) +include("commons.jl") # 已在此中导入JuNarsese、Test # 通用测试の宏 macro equal_test( @@ -91,9 +10,11 @@ macro equal_test( # 词项 # # 二次转换 local converted_terms = ($parser).(($test_set).terms) - @info "converted_terms@$($parser):" join(converted_terms, "\n") + @info "converted_terms@$($parser):" + join(converted_terms, "\n") |> println local reconverted_terms = ($parser).(converted_terms) - @info "reconverted_terms@$($parser):" join(reconverted_terms, "\n") + @info "reconverted_terms@$($parser):" + join(reconverted_terms, "\n") |> println # 比对相等 for (t1, t2) in zip(reconverted_terms, ($test_set).terms) if t1 ≠ t2 @@ -104,10 +25,12 @@ macro equal_test( end # 语句 # # 二次转换 - local converted_sentences = ($parser).(($test_set).sentences, ASentence) - @info "converted_sentences@$($parser):" join(converted_sentences, "\n") - local reconverted_sentences = ($parser).(converted_sentences, ASentence) - @info "converted_sentences@$($parser):" join(reconverted_sentences, "\n") + local converted_sentences = ($parser).(($test_set).sentences) + @info "converted_sentences@$($parser):" + join(converted_sentences, "\n") |> println + local reconverted_sentences = ($parser).(converted_sentences) + @info "converted_sentences@$($parser):" + join(converted_sentences, "\n") |> println # 比对相等 for (t1, t2) in zip(reconverted_sentences, ($test_set).sentences) if t1 ≠ t2 @@ -151,16 +74,6 @@ end # 陈述↔字符串 @show s0 = *(A,B,C) ⇔ w"op"o @test string(s0) == "<(*, A, B, C) <=> ^op>" - # @test ( - # string(s1) == "<(&&, B>, C>) ==> C>>" || - # string(s1) == "<(&&, C>, B>) ==> C>>" - # ) - # @test ( - # string(s2) == "<(||, B>, B>) ==> <[A, C] --> B>>" || - # string(s2) == "<(||, B>, B>) ==> <[A, C] --> B>>" || - # string(s2) == "<(||, B>, B>) ==> <[C, A] --> B>>" || - # string(s2) == "<(||, B>, B>) ==> <[C, A] --> B>>" - # ) end @@ -169,39 +82,6 @@ end # end @testset "ASTParser" begin - # s = ASTParser.(test_set.terms) - # s .|> dump - # @show ASTParser.(s) - # @test ASTParser.(s) == test_set.terms @equal_test ASTParser test_set end - - @testset "S11nParser" begin - # s = S11nParser.(test_set.terms) - # str = s .|> copy .|> String # 不知为何,转换多次字符串就空了 - # @show join(str, "\n\n") - # # @test str .|> !isempty |> all # 所有转换过来都非空 - # # 📌【20230730 11:52:26】避免「EOFError: read end of file」:使用数据前先copy - # @test S11nParser.(s .|> copy) == test_set.terms # 确保无损转换 - @equal_test S11nParser test_set - end - - @testset "JSONParser" begin - @equal_test JSONParser{Dict} test_set - @equal_test JSONParser{Vector} test_set - # s = JSONParser{Dict}.(test_set.terms) - # s .|> println - # @test JSONParser{Dict}.(s) == test_set.terms # 确保无损转换 - - # s = JSONParser{Vector}.(test_set.terms) - # s .|> println - # @test JSONParser{Vector}.(s) == test_set.terms # 确保无损转换 - end - - @testset "XMLParser" begin - @equal_test XMLParser test_set - # s = XMLParser.(test_set.terms) - # s .|> println - # @test XMLParser.(s) == test_set.terms # 确保无损转换 - end end diff --git a/test/test_narsese.jl b/test/test_narsese.jl index c484121..2acdf24 100644 --- a/test/test_narsese.jl +++ b/test/test_narsese.jl @@ -1,20 +1,10 @@ -push!(LOAD_PATH, "../src") # 用于直接打开(..上一级目录) -push!(LOAD_PATH, "src") # 用于VSCode调试(项目根目录起) - -using JuNarsese - -using Test +include("commons.jl") # 已在此中导入JuNarsese、Test A,B,C,D = "A B C D" |> split .|> String .|> Symbol .|> Word @assert (∨(⩜(A→B, B→C, C→D), ⩚(A→B, B→C, C→D))) == (∨(⩜(A→B, B→C, C→D), ⩚(A→B, B→C, C→D))) @testset "Narsese" begin - # 快捷构造 # - - w,i,d,q,o = w"词项", w"独立变量"i, w"非独变量"d, w"查询变量"q, w"操作"o - A,B,C,D = "A B C D" |> split .|> String .|> Symbol .|> Word - # 原子词项 @show w i d q o