MUMPS 76 入门指南 – 周年纪念版

Hacker News Top 工具

摘要

一个项目,旨在忠实实现1976年的MUMPS标准,包括解析器、解释器和集成层次数据库,以研究原始架构并运行该时代的旧软件。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/28 13:57

rochus-keller/MUMPS

来源:https://github.com/rochus-keller/MUMPS

本项目的目标是忠实实现 1976 年的 MUMPS 标准(参见 NBS Handbook 118,即 MUMPS 语言的第一个正式规范,档案链接:https://archive.org/details/bitsavers_mumpsNBSHaageStandardJan1976_6795659),包括 MUMPS 76 解析器、解释器以及集成的层次数据库——这一设计比 NoSQL 早了四十年。2026 年恰逢该标准发布 50 周年,也是 MUMPS 诞生 60 周年(同时与本人同龄),因此这是一个启动该项目的好时机。这是一系列探索重要遗留编程语言项目中的最新一个;其他值得一提的项目包括 Simula (https://github.com/rochus-keller/Simula)、Smalltalk (https://github.com/rochus-keller/Smalltalk)、Cedar (https://github.com/rochus-keller/Cedar)、Interlisp (https://github.com/rochus-keller/Interlisp) 和 Lisa Pascal (https://github.com/rochus-keller/LisaPascal)。

我二十岁出头时第一次接触 MUMPS——在一次进修课程中,军医得知我在苏黎世联邦理工学院学习后,热情地展示了他用这门特殊语言编写的医疗信息系统。此后,我反复回到 MUMPS,积累了 Intersystems Caché 的实践经验,甚至在自己的数据库(参见 Sdb (https://github.com/rochus-keller/Sdb) 和 Udb (https://github.com/rochus-keller/Udb))中实现了 MUMPS 的层次数据模型。

所有现有的 MUMPS 实现都基于更大、更新的标准(例如 GT.M、YottaDB 和 RSM 针对 1995 年的 ANSI 标准,部分带有扩展,命令和函数数量超过两倍)。目前看来,还没有人实现过 仅包含 1976 年标准 的版本。通过使用现代语言和工具实现这个最小、最原始的标准,我们可以:

  • 研究其最初设计的架构;1976 年标准代表了 MUMPS 社区对语言 本质 的共识,去掉了厂商扩展和尚未成熟的功能。
  • 运行那个时代的遗留软件;正如 testcases 目录中所示,该时期的大量源代码仍然可用。我对 DECUS 档案特别感兴趣,但运行 COSTAR(计算机存储门诊记录,20 世纪 70 年代在麻省总医院开发)和 DHCP(后来成为 VistA)也将是极好的。
  • 展示 MUMPS 为何重要:一门 19 条命令的语言,集成层次数据库,在 PDP-11 上运行历史医院软件,比 NoSQL 早了四十年。

什么是 MUMPS

MUMPS(麻省总医院多用途编程系统)是一种指令式高级编程语言,与键值数据库紧密集成。其标志性特征是语言和数据库并非分离的概念:持久的、层次化的稀疏数组(称为 globals)本身就是语言的一等公民,只需在变量名前添加脱字符(^)即可访问。该语言只有一种通用数据类型(隐式转换为字符串、整数或浮点数),支持动态数组,以 B 树形式按排序规范顺序存储,并允许几乎所有命令缩写为一两个字符(这是资源受限时代的遗留产物)。

MUMPS 于 1966 年由 Neil Pappalardo、Robert A. Greenes 和 Curt Marble 在波士顿麻省总医院 Octo Barnett 博士的实验室创建,最初运行在一台捐赠的 DEC PDP-7 上。它来自美国国立卫生研究院资助的医院信息系统项目,从一开始就设计为多用户操作、跨硬件平台可移植,并能高效处理医疗中常见的文本型层次数据。由于是在政府研究资助下开发的,它被发布到公共领域,并迅速在医学界传播,导致在 PDP-8、PDP-11、VAX 及其他架构上出现了大量实现。到 20 世纪 70 年代中期,即创建仅十年后,美国大多数医院都在运行基于 MUMPS 的系统中管理患者记录和临床数据。

该语言在 1976 年的 NBS(国家标准局)手册 118 中标准化,随后被 ANSI X11.1-1977 采纳,并经过 ANSI X11.1-1995 和 ISO 11756:1999 修订,后者于 2020 年由 ISO 再次确认。时至今日,MUMPS 仍活跃在生产环境中,主要应用于医疗和金融领域。在美国医疗行业,基于 MUMPS 的系统仍服务于超过 78% 的患者。仅美国退伍军人事务部的 VistA 系统就在超过 1500 个 VA 站点(包括 172 个医疗中心)上运行,服务超过 900 万退伍军人。

在此阅读我的 MUMPS 76 入门指南 (https://github.com/rochus-keller/MUMPS/blob/main/docs/MUMPS_Primer.adoc) (PDF 下载)

为什么 MUMPS 有趣

MUMPS 诞生于一个简单而激进的洞察:医院需要一个系统,让医生和护士能够同时从多个终端实时访问和更新患者数据,且硬件要负担得起。1966 年,“负担得起的硬件”意味着 DEC PDP-7,只有 8K 字的 18 位内存和一个小磁盘。由此诞生的东西,数据库世界直到四十年后才给它命名:一种带集成编程语言的 NoSQL 数据库。

MUMPS 的 globals 是核心数据抽象,是持久存储在磁盘上并在所有并发用户之间共享的层次化稀疏数组。用现代术语来说:

SET ^PATIENT(12345,"NAME")="Smith, John" SET ^PATIENT(12345,"DOB")="1945-03-15" SET ^PATIENT(12345,"LAB","2024-01-15","GLUCOSE")=95

这是一个具有复合键和自然字典顺序的层次键值存储。^ 符号意味着“持久”;这些数据保存在磁盘上,系统上每个用户都能看到。没有 CREATE TABLE,没有模式定义,没有 INSERT 语句。

想想 MUMPS 在六十年代末所拥有的,而数据库世界却在接下来的五十年里重新发明:

  • 无模式存储:无需声明。节点在首次赋值时创建,按需删除。树结构完全动态。这正是 MongoDB 在 2009 年所谓的“无模式文档”。
  • 层次数据模型:患者记录自然形成树状;一个患者有实验室检查,每个检查有结果,每个结果有值。MUMPS 的 globals 直接映射这一结构。这正是 Firebase 在 2012 年所谓的“实时层次数据”。
  • 有序键遍历$NEXT 函数(后来的 $ORDER)按排序顺序遍历兄弟节点,无需显式索引。这正是 Redis 在 2009 年所谓的“有序集合”,或者 LevelDB 在 2011 年所谓的“有序迭代”。
  • 原子多用户访问:多个用户可以同时读写同一个全局变量。LOCK 命令提供应用级并发控制。这正是现代数据库所说的“乐观并发”。
  • 集成语言 + 数据库:编程语言与数据存储之间没有阻抗失配。变量和数据库节点使用相同的语法、相同的数据类型、相同的操作。这是一些现代系统追求但尚未实现得如此简洁的。

所有这一切都运行在一台只有 8 千字节内存的机器上,同时为许多用户提供实时服务。

传统的数据库历史沿袭是这样的:层次型(IMS,1966)→ 网络型(CODASYL,1969)→ 关系型(Codd,1970)→ SQL(System R,1974)→ 面向对象(1980 年代)→ NoSQL(2000 年代)。MUMPS 并未出现在这段历史中,因为它是由医院程序员而非计算机科学研究人员创建的,并且发表在医学信息学期刊上,而非 ACM 论文集。然而,MUMPS 的 globals 比 NoSQL 运动早了四十年;而且与 2000 年代出现的 NoSQL 系统不同,MUMPS 将数据库与完整的编程语言和多用户操作系统结合在一起,只消耗今天单个 MongoDB 进程所用内存的一小部分。

SQL 赢得了 1980 年代和 1990 年代的数据库之战,结果业界在 2000 年代发现层次键值存储更适合许多实际工作负载。当亚马逊在 2007 年发表 Dynamo 论文时,当 Google 发布 Bigtable 时,当 MongoDB 在 2009 年推出时,他们(不知不觉地)验证了 Barnett、Pappalardo 和 Marble 在 1966 年波士顿一家医院中所做的设计决策。

现在,在第一个标准发布 50 周年和 MUMPS 诞生 60 周年之际,是回顾他们当年已经拥有的成就的好时机,或许也能为未来学到些什么。

MUMPS 长什么样

来自 DSM-11 源代码的 %DOC 例程,压缩格式,单空格缩进,就像在 VT100 终端上显示的那样:

mumps DOC ;GEF;DSM UTILITIES;OCTAL - DECIMAL CONVERTER S $ZT="%ERR^%DOC" %A R !,"Enter number > ",%X G %Q1:%X="?" I %X=""!(%X="^") K %X,%ZE Q I %X?1N.N S %DO=%X D %DO I $D(%DO) W " #",%DO K %DO G %A I %X'?1"#"1N.N D %IV G %A S %OD=$E(%X,2,999) D %OD I '$D(%OD) D %IV G %A W " ",%OD K %OD G %A %DO ; I %DO'?1N.N K %DO Q S %A="",%Y=%DO F %I=1:1 S %A=%Y#8_%A,%Y=%Y\8 Q:'%Y I %Y S %A=%Y_%A S %DO=%A K %A,%Y,%X,%I Q %OD ; I %OD"8"!(%OD["9") K %OD Q S %M=1,%S="" F %I=$L(%OD):-1:1 S %S=%S+(%M*$E(%OD,%I)),%M=%M*8 S %OD=%S K %I,%M,%S Q %Q1 W !!,?5,"Enter ^ or To quit" W !,?8,"or A decimal integer" W !,?8,"or # followed by an octal integer" W !,?8,"or ? for this message",! G %A %IV W !,?5,"Incorrect response - Enter '?' for more information" Q %ERR I $ZE?1"".E W " Too large" G %DOC W !,$ZE Q

同样的 %DOC 例程,但使用完整命令名、有意义的变量,并带有注释:

mumps %DOC ; Octal-Decimal Converter (GEF, DSM-11 Utilities, ca. 1978) SET $ZTRAP = "%ERR^%DOC" ; on error jump to %ERR %A READ !,"Enter number > ",input ; prompt user GOTO %Q1 : input="?" ; show help IF input="" ! (input="^") KILL input QUIT ; Decimal to octal IF input ? 1N.N SET result=input DO %DO DO . IF $DATA(result) WRITE " #",result . KILL result GOTO %A ; Validate "#" prefix IF input '? 1"#"1N.N DO %IV GOTO %A ; Octal to decimal (strip "#") SET result = $EXTRACT(input,2,999) DO %OD IF '$DATA(result) DO %IV GOTO %A WRITE " ",result KILL result GOTO %A ; --- Decimal to Octal: divide by 8, prepend remainders --- %DO IF result '? 1N.N KILL result QUIT SET accum="",value=result FOR i=1:1 SET accum=value#8_accum,value=value\8 QUIT:'value IF value SET accum = value _ accum SET result = accum KILL accum,value,i QUIT ; --- Octal to Decimal: sum digits * powers of 8 --- %OD IF result["8" ! (result["9") KILL result QUIT SET mult=1,sum="" FOR i=$LENGTH(result):-1:1 DO . SET sum = sum + (mult * $EXTRACT(result,i)) . SET mult = mult * 8 SET result=sum KILL i,mult,sum QUIT %Q1 WRITE !!,?5,"Enter ^ or to quit" WRITE !,?8,"or a decimal integer" WRITE !,?8,"or # followed by an octal integer" WRITE !,?8,"or ? for this message",! GOTO %A %IV WRITE !,?5,"Incorrect response - Enter '?' for more information" QUIT %ERR IF $ZERROR ? 1"".E WRITE " Too large" GOTO %DOC WRITE !,$ZERROR QUIT

如何尝试

本项目包含一个完整的单用户 MUMPS 76 解析器、解释器(支持批处理和 REPL 模式)以及数据库,您可下载运行(见下方“预编译版本”)。启动并输入命令后,您会看到类似这样的界面:

MUMPS 76 REPL
REPL 支持光标导航和命令历史。您可以复制/粘贴 [MUMPS 入门指南 (https://github.com/rochus-keller/MUMPS/blob/main/docs/MUMPS_Primer.adoc) 中的许多示例命令,或尝试运行 testcases 目录中七八十年代的原始 MUMPS 应用程序。例如,testcases/corpus/decus/startrek 中有原始的 STARTREK 应用,可用 mumps STARTREK.rou 运行;这是 运行中的截图

由于原始标准非常紧凑,整个系统用不到 10,000 行 C++ 代码实现,使得代码库易于学习。

开发日志

截至 2026 年 6 月 18 日的状态

词法分析器和解析器目前运行良好,通过了超过 90% 的测试用例(排除截断问题)。EBNF 语法覆盖了全部 19 条标准命令 + Z 命令 + DSM-11 扩展,但仍存在歧义。LL(1) 解析器使用 EbnfStudio (https://github.com/rochus-keller/EbnfStudio) 生成。词法分析器现在能很好地处理 MUMPS 不寻常的空格语义(包括 CmdSep 发射、括号深度跟踪、扩展全局引用和括号跳过),这一部分曾经很难处理正确。它可能是我收集的分析器中最复杂的词法分析器。下一阶段是解释器和某种中间表示形式。还存在长行处理和间接寻址(@)的问题,需要更好的错误恢复能力。

截至 2026 年 6 月 22 日的状态

解释器已通过专用测试和 PCT_DOC.mps;globals 已实现,但尚无数据库。全部 19 条命令均已实现;OPEN、CLOSE、USE、LOCK、VIEW、JOB、ZCMD 目前仅为桩函数。内建函数 $T 和 $V 也是桩函数,但 L、E、P、A、C、D、O、S、F、J、G、TR 和 $R 已实现。解析器可在运行时调用;跨例程调用和 XECUTE 已实现。解释器的目标是功能性、完整性和可理解性,而非性能。

截至 2026 年 6 月 25 日的状态

现在有了交互式解释器模式。您可以运行 MUMPS 例程文件,也可以进入解释器。我还写了一篇 MUMPS 入门指南(见 docs 子目录),专门描述 1976 版本,包含大量实际示例。1976 年的原始文档阅读起来有些困难,且对今天的程序员使用了不常见的术语。现在还有一个 BUSY 构建版本和适用于 Linux 的预编译版本。全局存储也取得了良好进展。

截至 2026 年 6 月 27 日的状态

全局存储已可工作并集成到解释器中。其代码比我预期的少(不到 1000 SLOC),因此整个项目低于 10 kSLOC 的 C++ 代码。CLI/REPL 现在拥有更健壮的实现,支持多命令粘贴和命令历史。入门指南已完成,示例已在解释器上测试过。令我惊讶的是,解释器似乎能够运行大部分 corpus 和 dsm11 代码(我估计 > 70%,改天应该更精确地分析)。MVP 版本发布。

预编译版本

目前提供以下二进制文件:

  • Linux x86_64 (https://software.rochus-keller.ch/mumps76_linux_x64.tar.gz)
  • Windows x86 (https://software.rochus-keller.ch/mumps76_windows_x86.zip)
  • macOS arm64 (https://software.rochus-keller.ch/mumps76_macOS_arm64.zip)

注意:macOS 每个版本都变得更“专横”,因此您可能无法直接启动可执行文件;如果遇到问题,请尝试 xattr -dr com.apple.quarantine /path/to/mumps;如果不起作用,可以尝试 codesign --force --deep --sign - /path/to/mumps;或者直接使用 Windows 二进制文件并通过 Wine 运行。

构建步骤

如果您想自己构建应用程序,请按照以下步骤操作。同样的代码可在 Linux、macOS 和 Windows(以及许多其他操作系统和架构)上构建:

  1. 确保系统上安装了与您的 C++ 编译器兼容的 Qt 5.x(库和头文件)。
  2. 从 https://github.com/rochus-keller/mumps/archive/master.zip 下载源代码并解压。
  3. 进入解压后的目录,执行 QTDIR/bin/qmake MpsMain.pro(关于 QTDIR 请参阅 Qt 文档)。
  4. 运行 make;几秒钟后,您将在构建目录中找到可执行文件。
    或者,您也可以打开 *.pro 文件。

相似文章

探索 PDP-1 Lisp (1960年)

Hacker News Top

关于运行1960年历史性的PDP-1 Lisp实现的详细介绍,包括启动过程及其作为首个交互式编程环境的重要意义。

CP/M-86 & MS-DOS 交叉开发环境

Hacker News Top

本项目为CP/M-86和MS-DOS提供了一个交叉开发环境,包括用于复古计算的编译器、汇编器和模拟器。

Plexus P/20 模拟器

Hacker News Top

一款全新的开源 WebAssembly 模拟器,重现 1980 年代 Plexus P/20 Unix 服务器,让用户可在浏览器中运行 SystemV Unix。

MemPro:作为可进化程序的智能体记忆系统

arXiv cs.CL

MemPro 是一个系统级进化框架,它将记忆构建-检索管道视为一个可进化的程序,使用进化智能体(Evolving Agent)迭代诊断失败并创建改进版本。在长期任务基准上的实验表明,与静态和提示级基线相比,它在性能-成本权衡方面取得了持续改进。