概述

  NPL 是 YSLib 提供的语言集合,它在语言规范层次上被设计为可扩展的。

  通过派生(derive) 现有的语言( NPL 方言),避免完全重新设计新的语言,来满足需要一些新语言的场合下的需求。被派生的语言是 NPL 的抽象语言实现。翻译或执行 NPL 或 NPL 抽象语言实现的程序是 NPL (方言)的具体语言实现。具有具体语言实现的方言仍可以派生新的语言作为领域特定语言

  派生领域特定语言的一个其它的例子是 XML

语法

  NPL 的语法基于形式文法上可递归构造的表达式,其中的子表达式在此称为

  非正式地, NPL 使用类似 S 表达式的基本语法,但不使用二元有序对(和终止符号)而直接支持列表;即表达式直接以是否为括号作为边界,分为列表表达式非列表表达式

  正式语法中,项可以是列表或非列表项的混合表达式(composite-expression) 规约列表项中的一部分代替有序对的地位,以要求任意项可被无歧义地进行从左到右的语法分析。

  NPL 只要求小括号作为列表表达式的边界。其它替代的括号由派生实现约定。对于适用于多个 NPL 实现的可移植代码,避免使用其它语言中习惯作为代替括号边界的字符表示替代的括号以外的含义,特别地, ISO C++ 文法 balanced-token 中的边界字符 ()[]{}

  NPL 对标识符的限制较为宽松。 ISO C 和 ISO C++ 的所有标识符都是 NPL 标识符。但派生实现可加以限制。

  NPL 不提供专用的注释语法。以特定形式的项(如表示字符串的字面量)替代注释是预期的惯用法(idiom) 。这不妨碍派生语言可能添加预处理器扩展特性。

语义

  NPL 提供了一些通用的概念但不构成语义规则。语义规则由派生实现约定。

实现

  在 YFramework/NPL 提供一些参考具体实现。当前 YFramework 主要使用抽象实现 NPLA 的具体派生(derived) 的实现 NPLA1 ,在这个基础上用于不同的目的,如程序配置动态加载的 GUI 等。

  这些实现基于模块 YSLib::Core::ValueNode 提供的数据结构,可以方便地修改和组合语法和语义节点的内容。

  NPLA 提供了比大多数现有的程序设计语言更强大的一般抽象。这集中体现在:

  • 和 NPL 的原始设计一致,不提供也不要求区分实现的阶段(phase) 。
  • 支持一等环境(first-class environment) 不修改现有语言的求值算法即可实现共享类似语法的新语言。
  • 允许以一般手段表达求值和未求值表达式的差异。

  这意味着 NPLA 是本质上动态的语言,但和一般语言不同,用户可以很大程度上动态地替换现有语言实现,包括在运行时替换一个解释实现为一个或多个优化编译器。这也意味着语言设计上既不需要区分解释实现和编译实现(本质上不对立),也不需要区分动态和静态(因为随时能从基础语言上构造出静态子集)。

  这样的特性设计在绝大多数语言中不存在并且几乎无法支持。作者已知唯一的例外是 Kernel ,在这些特性上有极大的相似,尽管实际上基本特性是独立设计的,并且在基本设计哲学上有极大不同( NPL 和 RnRK 中明确的 guidelines有很大不同且基本不兼容)。不过,考察设计的完整性, NPL 的派生语言也从中借鉴了一些重要的附加特性:

  • $vau合并子(combiner) /应用子(applicative) /操作子(operative) 等术语。
  • 在绑定构造中支持模式匹配的形式参数树(parameter tree) 。
  • 一些以合并子形式提供的操作。
    • 一般的作为接口提供的合并子在 NPL 中仍称为函数;合并子是作为表达式的函数的特定的求值结果。(在 Scheme 中,合并子中的应用子对应过程(procedure) 。)
    • 相似的操作主要体现在名称和语义上。因为一些基本设计的差异,不保证完全兼容。
    • 相似操作的实现不尽相同,但其中不通过宿主语言的直接的实现(称为派生(derivation) )有一部分几乎完全相同。
    • 使用 $ 作为一些标识符(如 $lambda )的前缀是独立设计的巧合;现在含义已和 Kernel 一致,表示 special form

  一些值得注意的和类似语言的主要设计差异(原理详见开发文档):

  • NPL 和 Kernel 类似,强调一等对象(first-class object) ,但含义有所不同。此处的“对象”和 ISO C 及 ISO C++ 中的定义类似,具有比 Kernel 更严格的含义。
    • 和 Kernel 合并子及 Scheme 过程类似, NPLA 默认使用按值调用(call by value) ;但与之不同,不隐式对实际参数别名(aliasing) ,不共享对象。
  • NPLA 和派生实现的语法和整体的求值类似 Scheme 和 Kernel 大多数基于 S 表达式的 Lisp 方言,但有一些显著的区别。
    • NPLA1 明确区分约定包括列表项的求值规则。和传统习惯不同, NPLA1 中括号明确不需要表示应用的含义,这可以减少一些场合(如命令行)需要输入的过多的连续括号。
    • 和 RnRS 定义的 Scheme 以及 Kernel 一致,不支持某些 Lisp 方言的方括号 [] 替代圆括号 () 的语法。
    • 不提供注释语法。
    • 语言实现中内置预处理处理中缀 ;, ,作为前缀合并子 $sequencelist 的语法糖。两者的含义和 Kernel 中的相同(类似 Scheme 的 beginlist )。
  • 和 Kernel 相似而和 Scheme 不同,使用操作子及一等环境和 eval 代替 Scheme 的卫生宏(hygienic macro)(en-US) 及宏展开的作用。
    • 和 Kernel 类似,鼓励使用直接求值风格而不是引用(quote) 。
    • 不过 NPLA 也提供了 $quote 的派生而非如 Kernel 一样完全避免。
  • 和 Kernel 不同, NPL 明确支持资源抽象,不保证支持循环引用,而 NPLA 明确不支持循环引用。
  • NPLA 明确支持基于 C++ 实现的对象模型和互操作,且明确不要求支持全局 GC
    • 从在互操作的目的出发,和 C++ 具有相似性和相容性。
      • 支持基于 C++ 抽象机语义的更一般的副作用(除原生 volatile 外)和一等状态(first-class states) 。
      • 和 ISO C++ 类似,在语言规格中保留特定的未定义行为,而不要求实现避免。
      • 暂时不直接支持多线程环境,但可以在不同线程上同时使用不同的实现的实例。
      • 函数默认使用不隐式别名的按值调用和返回传递复制或转移值,和 C++ 对应上下文的复制初始化(copy initialization) 语义一致。(不过求值为操作子的 NPL 函数在 C++ 没有直接的对应。)
    • vau 演算的论文 中,提及不支持全局 GC 有较大的 admistrative cost 但没有详细讨论和语言特性的联系。
  • 即便不支持全局 GC ,当前实现仍然明确支持 PTC(proper tail call) 。
    • PTC 基于语言规则而不是实现行为定义,详见 proper tail recursion ,这里和 Kernel 提供的保证含义一致。
    • 没有在其它语言发现这种不支持全局 GC 和支持类似 C++ 副作用的情形下的 PTC 支持的先例。
  • 和 Kernel 不同, NPLA 不完全强制对象类型的封装(encapsulation) ;且基于支持互操作的考虑,支持开放的类型系统,而不要求覆盖所有值(即要求对象类型分区(partition) )。
  • 对机器数(不论是整数还是浮点数)的操作被剥离了,当前不被支持,需要用户代码添加个别操作。
  • NPLA 的规约(reduction) 框架和 vau 演算的操作语义几乎完全一致,不过实际上(因为先前语言设计上的不确定)显著地保留了更多的可扩展和可修改性。

当前实现

  当前派生实现的 NPLA1 由 YFramework 提供 API 。其中包括 REPL (read-eval-print loop) 的解释实现。外部文件的形式的 NPLA1 脚本可被基于这些 API 实现的 stage 1 SHBuild 调用并用于 YFramework 的构建。

  由 YFramework 对外部编码的假设,NPLA1 实现加载的文件流的剩余内容的编码视为 UTF-8 ;同时支持 CR+LF 或 LF 为换行符。

进一步阅读

  语言特性设计的需求参见这里(en-US)

  关于 NPL 语言及当前使用的实现的规格,参照 YSLib 项目文档 doc/NPL.txt

Kernel 实现

  NPL 文法是 S-expression 语法和 Kernel 兼容语义的简化,某些 NPLA1 程序可以原封不动地作为 Kernel 程序运行。因为参考文献可用性以及相似性几乎独一无二,建议深入使用前参照 Kernel 相关的文档并实际使用,以和 NPL 进行比较(当前 NPL 开发文档也引用包括 RnRK 在内的一些规格说明)。现有较完整可用的一个 Kernel 实现是 Klisp

计划支持特性

  在 Kernel 中已存在但当前 NPLA1 未支持,而计划在未来添加的特性:

  • 一等续延(first-class continuation)
  • Promises
    • 虽然 NPLA1 之前有部分原生实现,但计划改用基于封装类型派生。
  • Keyed Variables
    • 有效的 keyed dynamic varabile 实现可能依赖一等续延数据结构。
    • 有效的 keyed static varabile 实现需要调整环境数据结构。
  • Ports
    • 需要调查内存映射文件和其它系统支持。
  • Libraries
    • 可能需要提供多种不同的接口。

  已提供但考虑可能优化实现的特性:

  • 封装类型
    • 需要调查互操作相关的特性和持久化支持。

  计划考虑提供其它设计替代选项的 Klisp 特性:

  • Characters
    • 满足互操作要求的前提下,使用设计更合理的类型替代。
  • Vectors
    • 使用更完善的用户自定义派生类型替代。

  其它 NPL 计划实现特性: