ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PHP 实现 mal(Make a Lisp):Web 托管运行、PHP 互操作与源码剖析

PHP 实现 mal(Make a Lisp):Web 托管运行、PHP 互操作与源码剖析 示例工程【免费下载链接】malmal - Make a Lisp项目地址https://gitcode.com/gh_mirrors/ma/mal点击查看免费下载本篇技术指南围绕 impls/php/README.md 展开讲解如何把用 PHP 编写的 malMake a Lisp解释器直接跑在普通 PHP 虚拟主机上执行.mal脚本并深入剖析php*求值、php/原生函数调用、超全局变量访问等 PHP 互操作能力。读完本文你将掌握mal-web.php的构建与部署流程、mal.php单文件发行版的生成原理以及互操作桥接层的完整实现机制可直接用于本地开发或线上共享主机。背景mal 是什么malMake a Lisp是一个用多种语言实现 Lisp 解释器的教学项目仓库根目录 README.md 将该项目定位为通过分步step0 到 stepA构建一个完整 Lisp 解释器来学习各语言特性的教程。每个步骤对应一个独立的实现文件如step0_repl.php、step1_read_print.php…… 直至stepA_mal.php逐步加入读取-求值-打印循环REPL、环境、求值器、尾调用优化、宏、异常处理、自举用 mal 语言自身实现核心函数等能力。impls/php/目录就是这套流程在 PHP 语言下的完整实现。本指南聚焦于该目录 README.md 明确给出的两大主题Web 化运行把 mal 解释器包装成.php文件在 PHP 内置开发服务器或线上 PHP 主机上直接执行.mal脚本PHP 互操作interop在 mal 代码中求值 PHP 代码片段、调用 PHP 原生函数、访问超全局变量与常量。一、在 PHP 托管环境运行 .mal 脚本1.1 核心思路符号链接symlink 单文件包装PHP 的require/include机制按文件路径解析文件而符号链接并不会改变这一点——ln -s mal-web.php myscript.php创建的是一个指向mal-web.php的链接访问myscript.php时 PHP 实际执行的是mal-web.php的代码。mal-web.php中嵌入了 Web 上下文检测逻辑从而把对.php的访问翻译成对同名.mal脚本的执行。Web 运行时的核心逻辑位于 webrunner.php?php // if were called in a webserver context, auto-resolve to mal file if (php_sapi_name() ! cli) { $malfile str_replace(.php, .mal, $_SERVER[SCRIPT_FILENAME]); rep((load-file . $malfile . )); exit(0); } ?其工作流程是用php_sapi_name() ! cli判断当前运行环境是 Web 服务器而非 CLI。php_sapi_name()在 CLI 下返回cli在 Apache、NginxFPM、内置开发服务器等 Web 上下文下返回对应 SAPI 名因此这一判断可以区分两种运行模式在 Web 模式下从$_SERVER[SCRIPT_FILENAME]取到被请求的 PHP 脚本绝对路径例如/path/to/myscript.php通过str_replace(.php, .mal, ...)把扩展名替换为.mal得到/path/to/myscript.mal调用rep((load-file …))执行该 mal 脚本——rep是 REPL 封装函数定义于 stepA_mal.php内部依次完成READ读入解析、MAL_EVAL求值、MAL_PRINT打印输出因此脚本中的println输出会直接进入 HTTP 响应体显示在浏览器中。从源码结构可以推断同一份mal-web.php被多个.php符号链接指向时每次请求都会基于SCRIPT_FILENAME解析出各自对应的.mal文件因此一个解释器可以同时服务多个不同的 mal 脚本。1.2 本地开发构建 mal-web.php 并验证README.md 给出了完整的本地开发流程。首先在 PHP 实现目录下构建mal-web.phpcd mal/php make mal-web.phpmake mal-web.php的执行逻辑定义在 Makefile 中它先由源文件拼接出单文件mal.php再由mal.php生成 Web 版mal.php: $(SOURCES) cat $ | grep -v ^require_once $ mal-web.php: mal.php cat $ | ( IFSNON-MATCHING-IFS; while read -r line; do if [ $$line // run mal file ]; then echo ?; cat webrunner.php; echo ?php; fi; printf %s\n $$line; done $ ) $可见mal-web.php是把所有源文件拼成一个文件后在标记行// run mal file位于 stepA_mal.php 的运行 mal 文件分支之前处插入webrunner.php的代码从而保证Web 模式下先执行load-file逻辑并exit不会走到 CLI 专属的参数处理与 REPL 循环。构建完成后创建可 Web 执行的 mal 脚本echo (println Hello world!) myscript.mal ln -s mal-web.php myscript.php然后启动 PHP 内置开发服务器并访问php -S 0.0.0.0:8000 # 浏览器访问 http://localhost:8000/myscript.php浏览器中应能看到Hello world!说明myscript.mal已被当作 PHP 脚本执行。1.3 线上托管单文件 符号链接部署README.md 同时说明了线上 PHP 共享主机shared hosting上的部署方式把mal.php上传到主机再为每个希望可 Web 执行的.mal文件创建同名.php符号链接。需要说明的两点差异线上场景上传的是mal.php而非mal-web.php实际上mal-web.php正是由mal.php加工而来两者唯一的区别就是是否内嵌 webrunner.php 的 Web 检测逻辑。在支持 shell 的主机上更稳妥的做法是构建mal-web.php并上传其中已含 Web 模式分支在不方便执行构建命令的环境里则需手动保证被链接的目标文件具备 Web 分支。README 的原意是核心要点在于上传解释器单文件、并为每个.mal脚本建立对应符号链接。很多虚拟主机控制面板无法创建符号链接此时可以直接复制mal-web.php为myscript.php在允许的情况下亦可使用ln -s效果相同——因为 Web 模式的SCRIPT_FILENAME替换逻辑对链接和复制两种情形都成立。1.4 单文件发行版 mal.php 与 CLI 入口make mal.php的产物是去掉所有require_once后拼接而成的单文件版见 Makefile 中cat $ | grep -v ^require_once $顺序依次为 readline.php、types.php、reader.php、printer.php、interop.php基础层以及 env.php、core.php、stepA_mal.phpLisp 层。make mal则在mal.php头部加上#!/usr/bin/env phpshebang 并赋予可执行权限得到可直接./mal script.mal运行的 CLI 可执行文件。目录下的 run 脚本则通过环境变量STEP选择运行某个步骤的实现默认执行stepA_mal.php#!/usr/bin/env bash exec php $(dirname $0)/${STEP:-stepA_mal}.php ${}例如STEPstep4_if_fn_do ./run即可运行第 4 步if/fn/do的实现。容器化运行可参考 Dockerfile其基础镜像为ubuntu:20.04并安装了make python3 php-cli——其中python3用于运行仓库顶层的runtest.py测试框架。二、PHP 互操作InteropREADME.md 指出完整的 PHP 互操作示例位于 tests/stepA_mal.mal本仓库的 runtest.py 测试框架会将该测试文件逐行喂给解释器并与期望输出比对。以下三类互操作均以该测试为验证依据。2.1 php*求值 PHP 代码php*是 mal 求值器中的一个特殊形式special form实现在 stepA_mal.phpcase php*: $res eval($ast[1]); return _to_mal($res);它接收一个字符串参数直接交给 PHP 的eval()执行返回值经_to_mal()转换为 mal 对象后返回。注意 stepA_mal.php 中*host-language*被设置为php这与php*等 PHP 专属能力一致。README.md 与 tests/stepA_mal.mal 给出了可验证的示例(php* return 7;) ;7 (php* return 7;) ;7 (php* return array(7,8,9);) ;(7 8 9) (php* return array(\abc\ 789);) ;{abc 789} (php* print \hello\n\;) ;/hello ;nil类型转换规则由_to_mal()见 interop.php定义PHP标量int/float/string/bool直接作为 mal 对应类型返回PHP顺序数组键为 0、1、2…转换为 mal列表_listPHP关联数组存在非数字键转换为 mal哈希表_hash_map内部借助exchangeArray填充PHP对象转换为 mal 哈希表get_object_vars取属性。因此array(7,8,9)得到(7 8 9)而array(abc 789)得到{abc 789}。php*还支持在字符串中操作 PHP 变量与函数测试文件中有如下用例(php* global $foo; $foo8;) (php* global $foo; return $foo;) ;8 (php* global $f; $f function($v) { return 1$v; };) (php* global $f; return array_map($f, array(1,2,3));) ;(2 3 4)从源码结构看第二个php*能否取到$foo的值依赖于同一个 PHP 进程内eval()对全局变量作用域的保持——这也意味着php*跨多次调用是有状态的可作为 mal 与 PHP 之间共享状态的一种手段。2.2 php/ 前缀调用 PHP 原生函数php/是 reader读取器层定义的语法糖见 reader.phpcase php/: $reader-next(); return _list(_symbol(to-native), read_form($reader));任何以php/开头的符号都会被展开为对to-native特殊形式的调用求值器中的to-native分支stepA_mal.php转而调用 interop.php 的_to_native()function _to_native($name, $env) { if (is_callable($name)) { return _function(function() use ($name) { $args array_map(_to_php, func_get_args()); $res call_user_func_array($name, $args); return _to_mal($res); }); // special case for language constructs } else if ($name print) { … } } else if ($name exit) { … } } else if ($name require) { … } } else if (in_array($name, [_SERVER, _GET, _POST, _FILES, _REQUEST, _SESSION, _ENV, _COOKIE])) { $val $GLOBALS[$name]; } else if (defined($name)) { $val constant($name); } else { $val ${$name}; } return _to_mal($val); }调用 PHP 原生函数的流程is_callable($name)判断函数是否可调用若可调用则返回一个_function包装的 mal 函数该函数在调用时把 mal 参数经_to_php()还原为 PHP 值用call_user_func_array调用原生函数最后把结果经_to_mal()转回 mal 对象。README.md 中的示例(php/date Y-m-d 0) ;1970-01-01测试文件 tests/stepA_mal.mal 还展示了向 PHP 原生函数传递 mal 回调函数的能力——mal 函数对象经_to_php()后仍可被 PHP 调用(php/array_map (fn* [t] (if ( t 3) t)) [1 2 3 4 5 6]) ;(nil nil nil 4 5 6)_to_php()的类型转换规则interop.php与_to_mal()互为逆过程列表/向量/哈希表递归转换为 PHP 数组以\x7fDEL 字符开头的字符串识别为 mal keyword 并转为:keyword形式symbol 会尝试按同名 PHP 变量取值${$obj-value}atom 取其内部值。print、exit、require三个 PHP 语言结构language constructs不是可调用函数因此_to_native为它们做了特判包装。2.3 超全局变量与常量访问_to_native()对以下超全局变量名做了特判interop.php[_SERVER, _GET, _POST, _FILES, _REQUEST, _SESSION, _ENV, _COOKIE]这些名字对应的值直接从$GLOBALS[$name]取出并转换为 mal 哈希表因此可以在 mal 中像普通哈希表一样访问。README 中的示例(get php/_SERVER PHP_SELF) ;./malphp/_SERVER会被 reader 展开为(to-native _SERVER)得到$_SERVER超全局变量的 mal 哈希表表示再用get取出PHP_SELF键。同理若名字是已定义的 PHP 常量defined($name)则返回constant($name)的值否则退回按同名变量解析。测试用例验证了常量访问php/FILE_APPEND ;8FILE_APPEND是 PHP 内置文件常量值为 8通过php/FILE_APPEND即可在 mal 中直接使用。而_to_php()对 mal keyword 的:前缀处理保证了 mal 侧 keyword 与 PHP 侧:keyword字符串互相转换时语义一致。三、互操作背后的类型桥接总结两张转换函数是整套互操作的地基都定义在 interop.php方向函数核心规则mal → PHP_to_php()列表/向量/哈希表→PHP 数组keyword\x7f开头字符串→:namesymbol→同名变量值atom→内部值PHP → mal_to_mal()顺序数组→列表关联数组→哈希表对象→哈希表标量→原样两条规则配合php*eval 一段 PHP 代码、php/调用原生函数/取超全局变量/取常量这两类入口构成了完整的双向互操作闭环。测试文件 tests/stepA_mal.mal 即是对该闭环的系统性验证;注释给出了每个用例的期望输出。四、验证与注意事项本地快速验证按 1.2 节流程构建并运行myscript.php浏览器输出Hello world!即代表 Web 链路打通CLI 验证php stepA_mal.php myscript.mal或make mal后./mal myscript.mal可直接运行脚本进入交互模式后输入(php/date Y-m-d 0)、(get php/_SERVER PHP_SELF)等即可观察互操作行为运行环境前提Web 场景依赖 PHP Web SAPIApache/NginxFPM/内置服务器均可CLI 场景依赖php-cli测试依赖python3运行 runtest.pyDocker 环境参见 impls/php/Dockerfile递归限制PHP 实现无法承受无限递归。tests/step5_tco.mal 注明其跳过非 TCO 递归测试原因是在 10,000 层递归时可完成、20,000 层时会发生不可恢复的段错误segfault因此依赖尾调用优化TCO来处理深递归是必要的——求值器的while (true)主循环stepA_mal.php正是为此设计的。结语impls/php/README.md 虽然篇幅不长却浓缩了两个极具实操价值的能力一是通过单文件解释器 同名符号链接把.mal脚本跑在任何 PHP 托管环境上二是通过php*、php/与类型桥接函数实现 mal 与 PHP 的深度互操作。配合 impls/php 目录下的源码interop.php、webrunner.php、reader.php、stepA_mal.php、Makefile与测试文件 tests/stepA_mal.mal、tests/step5_tco.mal读者既可以照章操作也能深入理解其底层实现原理。赞分享示例工程【免费下载链接】malmal - Make a Lisp项目地址https://gitcode.com/gh_mirrors/ma/mal点击查看免费下载相关推荐用 Standard ML 实现 Make-A-LispSML-MAL 的构建、运行与源码剖析用 Standard ML 实现 Make A LispSML MAL 的构建、运行与源码剖析 本文围绕仓库 impls/sml 目录下的 SML MAL 实示例工程从零实现一个 Lisp 解释器Mal 的 11 步 Make-A-Lisp 实战指南从零实现一个 Lisp 解释器Mal 的 11 步 Make A Lisp 实战指南 导读 本文以 malMake a Lisp仓库的 process/g示例工程RevokeMsgPatcher完整教程Windows平台微信QQ防撤回终极解决方案RevokeMsgPatcher完整教程Windows平台微信QQ防撤回终极解决方案 你是否曾经遇到过重要消息被撤回却无法查看的尴尬情况RevokeMsgP桌面应用即时通讯上一篇iOS开发架构实践UITableView-FDTemplateLayoutCell与Clean Architecture下一篇favicon-cheat-sheet娱乐产业吸引用户的图标设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表