Love(♥)是一门口袋大小的 Prolog 风格逻辑编程语言,用 MoonBit
从零实现。它的架构参考了 reference/ 目录中的经典 Prolog 实现:
- Trealla Prolog(C):WAM 风格堆单元 + trail 回溯,引擎设计的主要参考;
- SWI-Prolog(C):内置谓词集、运算符优先级表、
findall/3等语义参照; - Scryer Prolog(Rust):项表示与干净的错误处理参考;
- Mercury(逻辑+函数式):语言设计文档风格参考。
Love 的目标不是复刻 ISO Prolog 的全部,而是用一套小而完整的核心(项、合一、SLD 求解、 回溯、cut、内置谓词、动态数据库、REPL)演示逻辑编程语言的完整实现路径。
完整设计文档见 DESIGN.md。
# 编译并运行(模块默认 native 目标,直接运行即可)
moon run cmd/main
# 加载程序文件进入 REPL
moon run cmd/main -- examples/family.lv
# 批处理:运行单个查询并打印全部解
moon run cmd/main -- examples/family.lv "?- father(F, C)."
# 运行测试
moon testREPL 交互参考 Scryer Prolog 的 toplevel:提示符 ?- ,多行输入续行提示 | ;
答案独占一行(首个缩进 3 空格),后续答案以 ; 开头(分号在行首),确定解以 . 结尾,
枚举耗尽输出 false.,答案与下一个提示符之间空一行。
?- father(F, C).
F = tom, C = bob
; F = tom, C = lisa
; F = bob, C = ann
; F = bob, C = pat
; F = pat, C = jim
; ... .
?- X is 1 + 2 * 3.
X = 7.
?- append([1, 2], [3], L).
L = [1, 2, 3]
; false.
?- findall(X, member(X, [a, b, c]), L).
L = [a, b, c].
?- halt.
答案之后可输入的按键(与 Scryer 一致):在交互终端中按单键即可,无需回车
(原始模式读取,参考 Scryer 的 get_single_char/1;管道/重定向时自动退回按行读取)。
| 按键 | 含义 |
|---|---|
; / 空格 / n |
下一个解 |
Enter / . |
停止枚举(显示 ; ... .) |
a |
枚举全部解 |
f |
再显示 5 个解 |
h |
显示帮助 |
输入查询时(交互终端):方向键移动光标(左/右/Home/End)、Delete 删除光标处字符、
Backspace 删除、上/下箭头召回历史(会话内)、Ctrl-L 清屏、Ctrl-D 退出、
Ctrl-C 取消当前行。查询支持多行输入:未以 . 结尾时回车会以 | 提示续行。
halt. 或 Ctrl-D 退出。(行编辑参考 Scryer 所用的 rustyline。)
% 事实
likes(alice, bob).
likes(bob, carol).
% 规则:Head :- Body.
happy(X) :- likes(X, bob).
% 查询(REPL 中直接输入)
?- happy(X).
X = alice
; false.- 变量:
X、Name、_(匿名,每次出现都是新变量) - 原子:
alice、'hello world' - 整数 / 浮点数:
42、-7、3.14、1e-3 - 字符串:
"hello"(字符串项) - 复合项:
likes(alice, bob) - 列表:
[]、[a, b, c]、[H | T] - 剪切:
!
Love 内置与 ISO/SWI 兼容的运算符优先级表::- -->(1200) table(1150, 前缀)
;(1100) ->(1050) ,(1000) \+(900) = \= == \== is =:= =\= < =< > >= =..
@< 等(700) + -(500) * / // mod rem div(400) ^(200) 以及前缀
- + \(200)。
自定义运算符:op(Prec, Type, Name) 动态增删运算符(Type 为
xfx/xfy/yfx/fx/fy,Prec 为 0..1200,0 删除,Name 为原子或原子列表),
对之后解析的查询生效:
?- op(500, yfx, foo).
true.
?- X = a foo b.
X = foo(a, b).| 类别 | 谓词 |
|---|---|
| 控制流 | true/0 fail/0 !/0 call/1 ,/2 ;/2 ->/2,3 \+/1 once/1 repeat/0 catch/3 throw/1 |
| 合一与项 | =/2 \=/2 ==/2 \==/2 unify_with_occurs_check/2 var/1 nonvar/1 atom/1 number/1 integer/1 float/1 string/1 atomic/1 compound/1 callable/1 ground/1 functor/3 arg/3 =../2 |
| 原子操作 | atom_length/2 atom_concat/3 sub_atom/5 compare/3 @</2 @=</2 @>/2 @>=/2 |
| 列表 | member/2 append/3 length/2 reverse/2 sort/2 msort/2 sum_list/2 maplist/2 nth0/3 nth1/3 |
| 算术 | is/2 =:=/2 =\=/2 </2 =</2 >/2 >=/2 between/3 plus/3;可求值函子:+ - * / // rem mod div abs max min sign sqrt exp log sin cos tan floor ceiling round truncate float integer ^ |
| 动态库 | assertz/1 asserta/1 retract/1 clause/2 listing/0 |
| 收集 | findall/3 bagof/3 setof/3 |
| 表驱动 | table/1 abolish_table/1 abolish_all_tables/0;指令 :- table p/1. |
| DCG | phrase/2 phrase/3;规则用 --> 定义 |
| 运算符 | op/3 |
| I/O | write/1 writeln/1 nl/0 read/1 |
% 斐波那契(examples/fib.lv)
fib(0, 0).
fib(1, 1).
fib(N, F) :-
N > 1,
N1 is N - 1,
N2 is N - 2,
fib(N1, F1),
fib(N2, F2),
F is F1 + F2.?- fib(10, F).
F = 55
; false.max(X, Y, M) :- (X > Y -> M = X ; M = Y).?- catch(throw(bad), bad, true).
true.
?- catch(throw(x), y, true). % x 与 y 不合一 → 未捕获 → 查询失败
false.?- bagof(X, member(X-Y, [1-a, 2-b, 3-a]), L).
X = _0, Y = a, L = [1, 3]
; X = _0, Y = b, L = [2].% examples/grammar.lv:a^n b^n
s --> [].
s --> [a], s, [b].
?- phrase(s, [a, a, b, b]).
true
; false.
?- phrase(s, [a, b]).
true
; false.
?- phrase(s, [a]).
false.?- assertz(cat(tom)), assertz(cat(pat)), cat(X).
X = tom
; X = pat.:- table p/1. 把谓词标记为表驱动(变体记忆化 + 全局不动点):左递归程序
不再无限循环,传递闭包等查询可正常终止(见 examples/reach.lv):
:- table reach/2.
reach(X, Y) :- edge(X, Z), reach(Z, Y).
reach(X, Y) :- edge(X, Y).
edge(a, b). edge(b, c). edge(c, a). edge(b, d).?- reach(a, X).
X = b
; X = c
; X = d
; X = a.
?- setof(X, reach(a, X), L).
L = [a, b, c, d].表在每个查询开始时清空,因此 assertz/retract 对后续查询立即可见;
abolish_table/1、abolish_all_tables/0 用于当前查询求解过程中的手动清除。
love.mbt 公开 API 门面(parse_program / solve / REPL 服务)
syntax.mbt LoveTerm、Clause、Program 等类型(含内置库子句、运算符表)
lexer.mbt 词法分析(token 流)
parser.mbt Pratt 递归下降解析器(运算符优先级表、op/3 动态表)
dcg.mbt DCG 规则 → 普通子句的差表翻译
unify.mbt 合一 + trail + occurs check
engine.mbt SLD 引擎(目标栈 + choice point 栈 + cut barrier + catch 标记)
tabling.mbt 表驱动(变体规范化、全局不动点、答案回放)
builtins.mbt 内置谓词分发
arith.mbt 算术表达式求值
pretty.mbt 项打印(运算符、列表、引号规则)
read_io_*.mbt read/1 的 stdin 读取(native FFI / 其它目标占位)
stub.c read/1 的 C 实现
cmd/main CLI:加载文件 + 查询 + REPL
求解器采用惰性迭代器:每次 next() 只推进到下一个解。回溯通过
trail + choice point(含目标栈快照) 实现,cut 通过帧屏障截断 choice point 栈实现;
catch/3 通过目标栈上的内部标记帧实现,throw/1 向上恢复标记帧入口状态。
- 字符串
"..."是字符串项,不是字符码列表; - 合一默认带 occurs check(
X = f(X)失败),另提供unify_with_occurs_check/2; - 算术错误使目标失败而非抛出异常;
- 未捕获的
throw使整个查询失败(无错误打印与error/2); - 无模块系统、
current_op/3、postfix 运算符; read/1只读取单行,wasm/js 目标上失败;atom_concat/3在两个变量加一个原子的组合时不枚举所有切分;- 表按谓词名(不区分元数)生效;表在每查询开始时清空(assert/retract 立即可见); 表内 cut 作用域限定在批量求值的子引擎内。
- v0.1:解析器、合一、SLD 引擎、cut、内置谓词、算术、动态库、REPL
- v0.2:
catch/throw、bagof/setof、DCG、read/1、自定义运算符 - v0.3:表驱动(tabling)
- v0.4:约束(CLP)、模块系统