news 2026/9/24 18:19:34

mal 项目 C++ 实现编译与运行指南:macOS、Ubuntu 与 Docker 三种构建路径详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
mal 项目 C++ 实现编译与运行指南:macOS、Ubuntu 与 Docker 三种构建路径详解
  • 示例工程

【免费下载链接】mal

mal - Make a Lisp

项目地址:https://gitcode.com/gh_mirrors/ma/mal
点击查看免费下载

导读

本文围绕 mal(Make a Lisp)开源项目中 C++ 实现的编译与运行展开,系统讲解 impls/cpp/README.md 中记载的 macOS(Homebrew + g++)、Ubuntu(clang + libreadline-dev)与 Docker 容器三种构建路径,并深入 Makefile、Dockerfile、docker.sh 与源码入口(step0_repl.cpp 到 stepA_mal.cpp),帮你理解 C++ 版 mal 的依赖关系、构建产物与运行方式。读完本文,你可以在一台干净的机器上从零编译并运行完整的 stepA_mal REPL,也能独立编写脚本化构建流程。

一、项目背景:mal 的 C++ 实现是什么

mal 是一个以 "Make a Lisp" 为目标的系列教程项目,用上百种语言分别实现同一套 Lisp 解释器。C++ 实现位于 impls/cpp 目录,包含从step0_replstepA_mal的 11 个递进阶段(详见 process/guide.md),每个阶段都在前一个阶段基础上增加语言特性。

该实现的源码结构如下:

  • 通用基础:String.h/String.cpp(字符串封装与格式化)、MAL.h(全局类型声明与READ/EVAL/PRINT/rep接口)
  • 核心运行时:Types.h/Types.cpp(mal 值类型体系)、Environment.h/Environment.cpp(词法环境)、Reader.cpp(读取器)、Core.cpp(内置函数)、ReadLine.cpp(交互行输入与历史记录)
  • 构建脚本:MakefileDockerfiledocker.shrun
  • 测试与辅助:tests/目录下 1 个.mal测试文件、Debug.hValidation.cpp/Validation.h(参数校验)

二、macOS 编译:g++ + GNU Readline(Homebrew)

根据 impls/cpp/README.md,该 C++ 实现最初在 Mac OS X Yosemite 上开发,使用系统自带的 g++ 编译器,唯一的额外依赖是 GNU Readline。

2.1 安装依赖

Readline 通过 Homebrew 安装:

brew install readline

2.2 调整 Makefile 中的 READLINE 路径

README 明确指出:安装后可能需要编辑 Makefile 中的 READLINE 路径。查看 impls/cpp/Makefile 可以确认,Darwin 平台下的构建路径默认指向 Homebrew 的 readline 安装位置:

uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') CXX=g++ ifeq ($(uname_S),Darwin) # Native build on yosemite. Requires: brew install readline READLINE=/usr/local/opt/readline INCPATHS=-I$(READLINE)/include LIBPATHS=-L$(READLINE)/lib endif

如果你的 readline 安装在不同位置(例如 Apple Silicon Mac 上 Homebrew 前缀不同),需要同步修改READLINE变量,使-I指向其include目录、-L指向其lib目录。

2.3 编译参数要点

Makefile 中关键的编译与链接参数如下:

DEBUG=-ggdb CXXFLAGS=-O3 -Wall $(DEBUG) $(INCPATHS) -std=c++11 LDFLAGS=-O3 $(DEBUG) $(LIBPATHS) -L. -lreadline -lhistory
  • -std=c++11:要求 C++11 标准支持;
  • -O3 -Wall:开启最高优化与全部警告;
  • -ggdb:生成 GDB 调试信息,方便用调试器跟踪 EVAL 执行;
  • -lreadline -lhistory:链接 GNU Readline 及其历史记录库。

所有step*.cpp主程序通过通配符自动收集并逐一链接:

MAINS=$(wildcard step*.cpp) TARGETS=$(MAINS:%.cpp=%)

编译全部目标只需:

make

make clean可清除.o、可执行文件、libmal.a.depsmal链接产物。

三、Ubuntu 编译:clang + libreadline-dev

在 Ubuntu 14.10/15.04 上,README 推荐安装以下三个软件包:

apt-get install clang-3.5 libreadline-dev make
  • clang-3.5:C++ 编译器(对应版本可替换为当前发行版提供的 clang);
  • libreadline-dev:GNU Readline 的开发头文件与静态库,ReadLine.cpp#include <readline/readline.h><readline/history.h><readline/tilde.h>均依赖它;
  • make:构建工具。

注意:Linux 下 Makefile 走非 Darwin 分支,不设置READLINE路径,直接使用系统默认头文件路径。因此只要 libreadline-dev 安装到位,make即可完成构建。

四、Docker 构建:一条命令搞定环境

对于其他平台或希望隔离环境的用户,仓库提供了 impls/cpp/Dockerfile 与 impls/cpp/docker.sh,README 给出了完整的四步用法。

4.1 README 中的标准流程

# 构建 docker 镜像 ./docker build # 编译 MAL 二进制 ./docker make # 运行其中一个实现(如完整的 stepA_mal) ./docker run ./stepA_mal # 在 docker 容器内打开 shell ./docker run

注意:README 中./docker build是文档笔误,实际脚本名为docker.sh,正确命令为./docker.sh build./docker.sh make./docker.sh run

4.2 docker.sh 的工作机制

查看 impls/cpp/docker.sh 源码可以了解其背后逻辑:

IMAGE_NAME=mal-cpp CONTAINER_NAME=mal-cpp-running run() { docker rm -f $CONTAINER_NAME > /dev/null 2>/dev/null docker run -v $PWD:/mal -ti --name $CONTAINER_NAME $IMAGE_NAME "$@" } case $1 in build) docker build -t $IMAGE_NAME . ;; run) shift run "$@" ;; make) shift run make "$@" ;; ...
  • build:用当前目录的 Dockerfile 构建名为mal-cpp的镜像;
  • make:在容器内执行make,编译全部 step 可执行文件;
  • run:在容器内执行任意命令;核心技巧是通过-v $PWD:/mal把宿主当前目录挂载到容器/mal,因此编译产物会直接落在宿主的 impls/cpp 目录中,无需进入容器即可直接运行./stepA_mal
  • 每次run前会先删除同名旧容器mal-cpp-running,保证环境干净。

4.3 Dockerfile 环境内容

impls/cpp/Dockerfile 基于ubuntu:20.04,核心安装项为:

RUN apt-get -y update RUN apt-get -y install make python3 RUN ln -fs /usr/bin/python3 /usr/local/bin/python # Specific implementation requirements RUN apt-get -y install g++ libreadline-dev
  • g++libreadline-dev:C++ 编译链与 readline 依赖;
  • make:构建工具;
  • python3(并软链为python):供仓库根目录的 runtest.py 测试驱动脚本使用;
  • WORKDIR /mal:容器内工作目录与挂载点对应。

五、构建产物与运行方式

5.1 可执行文件清单

编译完成后,impls/cpp下会出现从step0_replstepA_mal共 11 个可执行文件。此外:

dist: mal mal: stepA_mal cp $< $@

make mal会把stepA_mal复制为mal,便于统一命名调用。

5.2 通过 run 脚本选择步骤

仓库提供通用启动脚本 impls/cpp/run:

#!/usr/bin/env bash exec $(dirname $0)/${STEP:-stepA_mal} "${@}"

默认运行stepA_mal,也可通过环境变量指定任意阶段,例如:

STEP=step4_if_fn_do ./run

该脚本同时服务于仓库根目录的测试框架:STEP变量配合 tests 目录下的step0_repl.malstepA_mal.mal等测试文件,可逐阶段验证实现正确性(测试驱动见 runtest.py)。

5.3 运行 REPL 与脚本文件

直接运行:

./stepA_mal

进入 REPL,提示符为user>。历史记录保存在~/.mal-history(见 ReadLine.cpp 中read_history/add_history/append_history的调用)。

带参数运行会加载并执行脚本(见 stepA_mal.cpp 的 main 逻辑):

if (argc > 1) { String filename = escape(argv[1]); safeRep(STRF("(load-file %s)", filename.c_str()), replEnv); return 0; }

./stepA_mal hello.mal等价于在 REPL 中执行(load-file "hello.mal")argv[2]起的参数会以字符串列表形式绑定到*ARGV*

static void makeArgv(malEnvPtr env, int argc, char* argv[]) { malValueVec* args = new malValueVec(); for (int i = 0; i < argc; i++) { args->push_back(mal::string(argv[i])); } env->set("*ARGV*", mal::list(args)); }

六、深入源码:C++ 实现的内部结构

结合编译配置,理解该实现的核心组件有助于排查构建与运行问题。

6.1 类型体系(Types.h)

impls/cpp/Types.h 以malValue为基类,定义了 mal 的全部数据类型:

  • malConstant(nil/true/false 单例)、malInteger(64 位整数)、malString/malKeyword/malSymbol
  • malSequence派生出的malListmalVector
  • malHash(基于std::map<String, malValuePtr>的哈希表)
  • malApplicable派生的malBuiltIn(内置函数)与malLambda(用户函数,含宏标记)
  • malAtom(原子类型,支持deref/reset

所有对象均以RefCountedPtr(见 RefCountedPtr.h)管理生命周期,VALUE_CAST/DYNAMIC_CAST宏(定义于 Types.h 顶部)负责类型安全转换。

6.2 环境(Environment.h)

impls/cpp/Environment.h 提供malEnv,内部是一个std::map加指向外层环境的m_outer指针,实现词法作用域与闭包:

class malEnv : public RefCounted { public: malEnv(malEnvPtr outer = NULL); malValuePtr get(const String& symbol); malEnvPtr find(const String& symbol); malValuePtr set(const String& symbol, malValuePtr value); malEnvPtr getRoot(); private: Map m_map; malEnvPtr m_outer; };

6.3 EVAL 与特殊形式

stepA_mal.cpp 中的EVAL采用「循环而非递归」的 TCO(尾调用优化)写法:iflet*doquasiquotetry*等特殊形式在修改ast/envcontinue,避免 C++ 栈随深递归增长。同时支持DEBUG-EVAL调试开关:当环境中DEBUG-EVAL为真时,每次 EVAL 前打印当前 AST。

6.4 REPL 错误处理

safeRep统一捕获三类异常并格式化为Error: ...输出:

  • malEmptyInputException:空输入直接忽略;
  • malValuePtr:mal 值类型的异常(如throw抛出的对象);
  • String:普通字符串异常。

这保证了 REPL 遇到错误不会崩溃,可以持续交互。

七、常见问题排查

现象可能原因与解决
macOS 编译报找不到readline/readline.hbrew install readline未执行,或 Makefile 中READLINE路径与安装位置不符,按 2.2 节调整
Linux 编译报readline链接错误缺少libreadline-dev,执行apt-get install libreadline-dev
Docker 构建网络失败需保证可访问 Ubuntu apt 源;也可改用本地已安装的 g++ 直接make
运行./docker提示 command not found脚本名为docker.sh,应执行./docker.sh build
输入命令无历史记录检查~/.mal-history是否可写(ReadLine 每次append_history都会写入)

八、结语

本文完整覆盖了 impls/cpp/README.md 中记载的三条构建路径:macOS 的 g++ + Homebrew Readline(需注意 Makefile 路径)、Ubuntu 的 clang + libreadline-dev、以及普适性最强的 Docker 方案(docker.sh的 build/make/run 三命令)。结合 Makefile、Dockerfile 与 stepA_mal.cpp 等源码,可清晰理解该实现的依赖结构、构建产物与运行机制。无论你是在本机学习 Lisp 解释器实现,还是在 CI 环境中自动化构建测试,上述路径都提供了开箱即用的方案。

  • 示例工程

【免费下载链接】mal

mal - Make a Lisp

项目地址:https://gitcode.com/gh_mirrors/ma/mal
点击查看免费下载

相关推荐

上一篇:终极指南:如何用Mac Mouse Fix让普通鼠标在macOS上超越苹果触控板
下一篇:N_m3u8DL-RE:一条命令完整保存 m3u8/MPD 流媒体,自动解密自动合并

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 18:19:19

Windows 11记事本原生支持Markdown?轻量写作与避坑全攻略

上午整理旧项目文件&#xff0c;翻出一批 .md 草稿&#xff0c;顺手用 Windows 11 自带记事本打开。本来只是打算快速看一眼内容&#xff0c;结果在设置面板里发现了一个之前完全没注意到的选项&#xff1a;语法高亮&#xff0c;下拉列表里赫然写着 Markdown。我当时愣了一下—…

作者头像 李华
网站建设 2026/9/24 18:18:30

API接口敏感数据加解密实战:AES+RSA混合加密与Spring Boot透明接入

前两周给一家做医疗信息化的团队做技术评审&#xff0c;对方安全负责人提了个很现实的需求&#xff1a;身份证号、手机号、银行卡这些字段在接口传输里全是明文&#xff0c;虽然开了HTTPS&#xff0c;但等保评测和客户审计都盯着这一点&#xff0c;要求“不能直接看到明文”。这…

作者头像 李华
网站建设 2026/9/24 18:18:01

JavaEE图书管理系统实战:Spring Boot+MyBatis选型、并发借阅与避坑指南

简介&#xff1a;这是一套面向JavaEE初学者与课程设计者的图书管理系统完整源码&#xff0c;基于MVC三层架构实现&#xff0c;适合用于毕业设计、课程作业或企业级开发入门练手。压缩包共93个文件&#xff0c;约6.62MB&#xff0c;以java源文件、jsp页面、xml配置、class字节码…

作者头像 李华
网站建设 2026/9/24 18:16:54

2026年头戴式耳机怎么选?10款热门机型横评推荐

又是一年盘点时间。前两天在后台看到一条留言&#xff0c;问我“2026年了&#xff0c;头戴式耳机到底还有没有买的必要”&#xff0c;说实话这个问题本身就很能代表一部分人的心态&#xff1a;手机厂商都在卷TWS&#xff0c;头戴式耳机这个品类看着好像没那么“便携”&#xff…

作者头像 李华
网站建设 2026/9/24 18:16:25

基于YOLOv8的智慧工厂危险区域闯入识别系统:从训练到部署

简介&#xff1a;这份资源面向计算机、人工智能、自动化等专业的在校学生与教师&#xff0c;提供一套可直接运行的智慧工厂危险区域闯入识别方案&#xff0c;适合作为毕业设计、课程设计或大作业的完整参考。项目以YOLOv8目标检测为核心&#xff0c;配套可视化界面&#xff0c;…

作者头像 李华