English Version

简体中文版

Contents(zh-CN)/主题目录

Copyright of this wiki

© 2013-2020 FrankHB and wiki editors.

Except where otherwise specified explicitly, materials in this repository are licensed under following terms:

http://creativecommons.org/licenses/by-sa/4.0/

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

About

The YSLib Project is a project of platform-neutral framework consists of several multi-licensed open source libraries. It is aiming to develop native applications in a naturally cross-platform manner.

See LICENSE.TXT in the source directory for licensing affairs.

The main part (except libraries not being maintained in this project) of the libraries is coded in C++, which is strictly conforming to ISO/IEC 14882. (For features being used, see here. For some issues concerned with this project, see here.)

The project is currently in Alpha state(zh-CN) with following interface compatibility strategies:

  • ABI compatibility for any revision depending on specific platform, toolchain or build configuration is never guaranteed.
  • ABI compatibility may not be retained between releases with same platform, toolchain and build configuration.
  • API of libraries may be modified locally and there is no guarantee for compatibility between releases.
  • UI of tools (see contents(zh-CN) for the list) may be modified progressively across builds; unless specified elsewhere, added features are kept being backward compatible within at least one next releases.

See here(zh-CN) for basic steps to use YSLib in development.

See here(zh-CN) for releases and plans.

Currently supported platforms

  • (Nintendo/iQue) DS (arm-none-eabi)
  • MinGW32 (i686-w64-mingw32, compatible with i686-pc-mingw32)

Pending supported platform

  • Android (arm-linux-androideabi)
  • MinGW64 (x86_64-w64-mingw32)
  • Linux32 (i686-linux-gnu)
  • Linux64 (x86_64-linux-gnu)

Documentation

Documentation in the YSLib project

Documentation in the YSLib project consist of several parts:

  • The development documentation which is intended for the project maintainers, see development(zh-CN).
    • The document in doc/vsd can be viewed by Microsoft Office Visio or Microsoft Visual Studio 2013.
  • Souce code documentation, which can be generated using Doxygen, see building documentation(zh-CN).
    • Currently the Doxygen commands in the source code are mainly coded in simplified Chinese. It is planned to utilize Doxygen's ~[LanguageId] command to generate multilingual documents in future. All other comments, however, should be in English by default.
  • Other non-generated documents are text files in the repository, like Readme.zh-CN.txt.

About this wiki

This wiki is a project related to the YSLib project. They are currently the only two members in the same project group, each has separated repository. Common documentation for the project group, general user documentation and list of significant features for the YSLib project are dominated by this wiki. Documentation in the YSLib project is mainly for maintainers of the project. They are bidirectionally referenced, namely the content of this wiki may be refenenced in the YSLib project and vice versa.

Unless otherwise specified, the content of this wiki is fit for the current last master branch revision (i.e. the revision tip of master branch in the repository).

See here for rules to edit this wiki.

It is intended to reference every pages of this wiki in this page.

Contributions

Contributions to the projects are welcomed. Provided materials thereby shall be adjusted by contributors (and the project maintainer) to conform the license of the corresponding projects if necessary.

Rules in this wiki are treated as consensus. Rules in project documentation have effect on maintainers but not other contributors. However, the contents in the projects shall always be conforming to the project rules.

To feedback or report issues, use Bitbucket issue tracker, or contact the project maintainer as noted below.

Rules for project contents

The following philosophy are generally accommodated throughout the projects.

  • Do not reinvent the wheel. Projects here only accepts modular components which would be better elsewhere. Here "better" is defined as "superior than current solution in at least one aspect for consensually known need".

    • See the notes in features(zh-CN) for the list of invented wheels and the rationale.
  • Decline premature optimization. However, what is "premature" is determined by the need, which would be probably variable.

Contacts

概要

  YSLib 项目是一个提供多个开源库组成的平台中立框架的项目,主要致力于以自然的方式开发跨平台本机应用。

  使用的许可证见源代码目录中的 LICENSE.TXT 。

  主要部分(非此项目维护的库除外)使用 C++ 编码,严格符合 ISO/IEC 14882 。(使用的 ISO 特性参见这里(en-US) 。和本项目相关的被报告的问题见这里(en-US) 。)

  项目当前为 Alpha 状态,具有以下接口兼容性策略:

  • 任意版本不保证依赖特定平台、工具链或构建配置的 ABI 兼容性。
  • 同一平台、工具链和构建配置的发布版本之间的 ABI 可能不兼容。
  • 库的 API 设计在局部调整而不保证在发布版本之间兼容。
  • 工具(列表参见目录)的 UI 可能会在构建中渐进修改;除非另行指定,添加的特性在之后的至少一个发布版本中保持向后兼容

  关于使用 YSLib 开发的基本步骤,参见入门

  目前已发布版本、进展和计划看这里

当前支持平台

待定支持平台

  • Android (arm-linux-androideabi)
  • MinGW64 (x86_64-w64-mingw32)
  • Linux32 (i686-linux-gnu)
  • Linux64 (x86_64-linux-gnu)

文档

YSLib 项目中的文档

  YSLib 项目中的文档主要包括以下几个部分:

  • 主要供项目维护者参考的开发文档,详见开发说明
    • 目录 doc/vsd 下的文档可使用 Microsoft Office Visio 或 Microsoft Visual Studio 2013 查看。
  • 源代码文档,可使用 Doxygen 生成,参见构建文档
    • 当前源代码中的 Doxygen 命令主要编码为简体中文。未来计划使用 Doxygen 的 ~[LanguageId] 命令生成多种语言的文档。其它注释主要使用英文。
  • 其它非生成的文本文件,如 Readme.zh-CN.txt

关于本 wiki

  本 wiki 是和 YSLib 项目关联的项目。本 wiki 和 YSLib 项目组成了同一个项目组当前仅有的两个成员,每个项目都有单独的版本库。项目组的公共文档、YSLib 的一般用户文档和重要特性列表主要由本 wiki 提供。YSLib 项目中的文档主要面向项目的维护者。两者的文档构成双向引用,即 YSLib 项目可能引用本 wiki 的内容,反之亦然。

  除非另行指定,本 wiki 的内容适合当前最新的主分支版本(即版本库 master 分支标识为 tip 的版本)。

  编辑本 wiki 的规则参见这里(en-US)

  本页面有意引用所有本 wiki 的所有页面。

贡献

  对项目的贡献受到欢迎。由此提供的材料应在必要时由贡献者(及项目维护者)调整,以符合相应项目的许可证。

  这个 wiki 规则被视为共识。 在项目文档内部的规则对维护者而不是其他贡献者有效。但是,项目中的内容应符合项目规则。

  要反馈或报告问题,使用 Bitbucket 问题跟踪系统,或按如下方式联系项目维护者。

项目内容规则

  下列指导原则一般地适用于所有项目。

  • 重复发明轮子 这里的项目只接受比别处更好的模块化组件。此处“更好”定义为“在至少一个方面比现有解决方案更有效地满足一致同意的已知需求”。
    • 参见特性注记的轮子列表和原理。
  • 拒绝不成熟的优化。 然而,何谓“不成熟”由需求决定,这很可能变化。

联系方式

主题目录

导航

  YSLib 是什么?

  返回目录查看其它内容。

目标读者

  本文预期的读者是对了解如何使用 YSLib 创建项目感兴趣的开发者

基础知识

  本文假定读者已经掌握或了解以下知识,不详细展开讨论:

  • 计算机体系结构常识
  • 了解规范 C++ 语言的标准(ISO/IEC 14882)
  • 了解什么是语言的实现(如编译器和链接器等)并掌握常见实现的使用方法(如 G++ 命令行)
  • 有必要时,能避免依赖只在特定实现支持的方言特性

概要

  本文档说明开发 YSLib 程序的简易操作。以下仅提供操作步骤和主要意义的解释。

注意 若需自行定制构建,参照以下引用的文档的全文,而非某个特定章节;再按照构建中的步骤执行。

  当前只有关于 Windows 下使用 MinGW32 的内容,基于 MSYS2 环境。

环境配置

  使用 YSLib 开发需要配置 YSLib 环境,以确保 YSLib 的库和相关环境可用。

  当前建议直接在本机环境中从源代码构建安装 YSLib ,参照以下步骤和要点:

  • 参照先决条件PC(MinGW32) 一节给出的链接下载 MSYS2 并配置环境,执行脚本安装所需的工具。
    • 注释 若不需要构建 GUI 应用程序,也可以参照先决条件的 PC/Linux 一节,在 Linux 环境下本机构建。
  • 获取源代码
    • 若直接从版本库获取,直接构建可能找不到库而在链接时失败。可以自行参照文档构建缺少的外部依赖项,或从归档仓库下载二进制文件放置到指定的库文件路径中,详见开发说明
    • 文件不包含 POSIX 权限。若需在依赖文件权限的环境(包括典型的 Linux 环境)中直接运行 shell 脚本,需确保(可能被间接调用的)脚本文件可执行,如运行 find Tools -type f -name "*.sh" -exec chmod +x {} \;
  • 参照 Sysroot ,运行 Tools/install-sysroot.sh 脚本以构建 YSLib 并安装头所需的文件,得到 Sysroot 环境。然后,把 sysroot/usr/bin 目录的完整路径加入 PATH 环境变量(一般应添加在最前端)。
    • 注意 除非了解确切的作用,不要添加 YSLib 中的其它目录到 PATH ;添加不合适的目录可能会导致脚本运行失败。

注释 以上是对新手的建议步骤。为了避免在之后的步骤中出现需要回溯解决的问题,这些步骤务求详尽。若已了解构建环境配置或为自动化部署,通常总是可以使用一行脚本在受支持的环境中直接构建 Sysroot 。

开发基础

  以下介绍使用 YSLib 开发须知的基础要点,并说明如何开发示例程序。

控制台程序和图形用户界面(GUI) 程序

  需要注意,在 Windows 中控制台程序仍然可以包含 GUI 。

  YSLib 支持各种用户应用程序。为了简便起见,以下只介绍 GUI 程序的使用。更具体的说明参见 Windows 子系统

编写 Hello World 程序

  新建 C++ 源文件(名称任意,但需要.cpp 等作为扩展名以保证 SHBuild 作为 C++ 源文件处理),内容如下:

#include <YSBuild.h>
#include YFM_Helper_GUIApplication
#include YFM_YSLib_UI_Label
#include YFM_Helper_HostedUI

int
main()
{
	using namespace YSLib;
	GUIApplication app;
	UI::Label wgt({480, 360, 160, 24});

	wgt.Text = u"Hello world!";
	Host::ShowTopLevel(wgt, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 0);
	Execute(app);
}

关于头文件

  可以安全地重复包含头文件。

  注意到 #include 后可以是一个宏。这里表示路径的宏 YFM_*<YSBuild.h> 保证定义,因此这个头文件需要在使用这些宏之前被包含。

  使用宏表示 YFramework 的大多数头文件名是兼容性的需要。用户程序不需要使用这种策略,尤其是集成开发环境可能会对这样引入的头文件的修改不敏感而导致的构建遗漏。

  MinGW32 平台的 Helper::HostedUI 的头文件已经保证包含了 <Windows.h> 中宿主窗口的声明,不需要显式包含。

使用 SHBuild-BuildApp 脚本构建程序

  把上面的文件保存到一个空的目录下,以下以 $SRC 指定这个目录名。

  执行以下命令:

bash -c "cd $SRC; SHBuild-BuildPkg.sh release ."

  在目录 $SRC/.release 下找到构建好的可执行文件(具体文件名取决于源文件名),执行观察结果。

说明

  bash -c 启动新的 shell 执行命令。

  cd $SRC 切换 shell 的当前工作目录到源代码目录。

  SHBuild-BuildPkg.sh 调用构建脚本。脚本的参数分别是:

  • release 是一个配置名称。配置名称决定保存生成的文件的输出目录相对当前工作目录的位置,这里输出目录是当前工作目录下的 .release 子目录。可以更换配置名称以指定不同的输出目录。   * 若配置名称以 debug 起始,启用调试配置,自动使用调试库和构建选项。
  • . 表示构建的目录源代码的根目录为当前工作目录。
  • 之后的可选参数在此省略。

  构建脚本以特定的选项调用 SHBuild 工具作为脚本解释器执行 NPLA1 脚本 SHBuild-BuildApp.txt ,详见 Sysroot

  SHBuild-BuildApp.txt 内部再次调用 SHBuild 工具进行构建。默认会递归扫描整个目录(除了名称以 . 起始的子目录外)。若存在多个源文件,这些文件都会被一起编译并链接。为了避免预期以外的结果,之前要求源文件在空目录中。

其它方法

  也可以直接调用 SHBuild 或者编译器命令行,但需要手动指定调用编译器的参数以及链接的库等,较为复杂,在此从略。

清理

  无论是 SHBuild 还是 SHBuild-BuildPkg.sh 当前都不提供清理功能。因为包括中间文件的输出文件都在同一个输出目录中,直接删除输出的目录即可。

进一步阅读

  参见开发说明的相应章节和进阶的教程

开发说明

  关于环境配置,详见运行。当前没有其它的面向最终用户的说明。

  以下为面向开发者的说明。

准备

  YSLib 项目文档位于 YSLib 项目而不是本 wiki 项目中。

  维护者参考的细节和一般规则详见 YSLib 项目文档 doc/ProjectRules.txt ;术语的完整定义详见 YSLib 项目文档 doc/CommonRules.txt

项目过程

  基本规则参见 YSLib 项目文档 doc/ProjectRules.txt

  本 wiki 项目作为用户手册和开发者补充文档的形式作为实现及之后阶段输出。

  实现的附加输出为库和工具。

  之前阶段(如设计)为前期过程,其文档和适用于维护过程的项目规则位于 YSLib 项目文档(位于 doc/ )。

  其中整体过程由 doc/Designation.txt 指定。当前内联设计以外的过程,因此不存在设计外前期过程的单独文档。

开发

平台

  关于外部依赖平台目标平台宿主平台等概念的一般定义参见术语概要

  YSLib 项目约定一个体系结构和使用的外部依赖是一个平台。

  YSLib 支持不同的目标平台。类似 ISO C/C++ 的独立实现(freestanding implementation) 和宿主实现(hosted implementation) ,平台分为两类:独立实现平台宿主实现平台。后者存在操作系统的支持而前者没有。

语言使用和实现要求

  本节适用于本项目,不直接限制依赖项和用户程序。项目中特定部分的规则及适用性详见 YSLib 项目文档 doc/ProjectRules.txt

  除脚本(见以下相关章节)外,使用 ISO C++ 作为主要开发语言。

  不使用和 ISO C++03 以后被接受的特性不兼容的特性,包括但不限于:

  • 被取消的特性,如 ISO C++03 后导出模板的 export 关键字。
    • 不限制有条件使用的之后的其它特性,如 export 被作为模块。(当前不使用模块。)
  • 在 ISO C++03 中标记为 deprecated 而在之后版本去除的特性,如 const char 数组类型左值到 char* 右值的转换。
  • 在 ISO C++03 中标记为 deprecated 但在之后版本重新取消 deprecated 的特性,如修饰命名空间作用域声明的 static
  • 实现的 Defect Report ,如 CWG 615
  • 显式排除的特定的语言特性:

  注意即使使用特定模式,一些实现也可能引入之后的 Defect Report 而不保证兼容,如 GCC PR 65890

  关于精确的特性使用规则、具体使用及备选的特性的清单等,详见标准使用(英文)

  以下对语言实现的要求和支持情况适用整个项目。具体内容可能会在未来变动。

基准实现要求

  YSLib 依赖 ISO C++ 独立实现或宿主实现,附加以下要求:

  • 满足标准使用(en-US) 中具体特性的要求。
    • 注释 默认基于 ISO C++11 环境,但并不要求实现完整支持所有特性。
    • 注释 一些特性具有替代实现或者是可选的,而不被依赖。
  • 满足 ISO C++11 [implimits] 建议的最小实现要求。
  • 标准库基于 ISO C++11 定义的宿主实现,并满足以下要求:
    • 注释 语言实现不需要是完整的宿主实现。
    • 提供以下符合标准的整数类型:
      • 定宽整数 std::intN_tstd::uintN_t(其中 N 为 8 、16 、32 或 64 )。
      • 类型 std::uintptr_t
      • 注释 定宽整数在 ISO C++11 中为可选支持。
    • 满足以下实现定义行为的要求:
      • 至少支持 std::placeholders::_7
      • 注释 按 ISO C++11 Annex B [implimits] ,符合标准的下限为 10 ,因此已被上述规则涵盖。
    • 假定用于迭代器的 difference_type 或坐标计算的有符号整数作为显式转换的目标类型且结果不能在范围内表示时,不引起副作用且结果的值不是能在此范围内表示的任意值(即为小于 0 的值)。
      • 注释 一个典型的例子是 std::ptrdiff_t
      • 当前标准中,转换到有符号数的结果由实现定义。
      • WG21 P0907 已提议修改使用补码表示,并在 ISO C++20 采纳,按,符合此要求。
      • 注释 一般的到有符号数转换的由实现定义的行为仍不被依赖。
    • 假定特定类型的特定操作无异常抛出(但不依赖异常规范的行为),当前包括:
      • std::string 的默认构造函数( WG21 N4002 引入了显式指定 noexcept ,仅从标准草案 WG21 N4296 起有效)。
  • 假定被包含在具有外部链接实体的函数体或声明命名空间作用域中外部链接名称的被 ODR 使用(odr-used) 的 lambda 表达式相同。
  • 对宿主环境中程序外部的状态的并发修改不引起未定义行为。
    • 现有操作系统和文件系统提供的接口和实现普遍不能保证避免 TOCTTOU 访问(en-US) 导致的问题。具体修改的结果未指定,但应不直接引起无法预测的程序行为。
    • 除非另行指定,本项目的实现不保证检查外部程序的修改。

  假定 YSLib 实现和用户程序的代码满足以下要求:

  • 假定异常和标准库 RTTI 对象满足 ODR ,即使是在使用动态库的宿主实现中。
    • 但影响用户代码生成的实现的二进制约定(如 ARM64 )且不使用以下错误实现变通的情形除外。
    • 这要求用户代码不依赖影响相关符号可见性而导致 ODR 失效的特性。
      • 例如,这不允许如 dlopen 使用 RTLD_LOCAL 加载具有相关符号的库。
    • 这允许 std::type_info 的比较操作和散列操作的高效实现,并避免一些实现错误。
      • 使用 libstdc++ (libsupc++) 时,需要重定义宏 __GXX_TYPEINFO_EQUALITY_INLINE 和宏 __GXX_MERGED_TYPEINFO_NAMES1
        • 这是对 std::type_info 比较操作的错误实现的变通。
        • 一般在编译器命令行中使用 -U 选项取消可能存在的预定义再使用 -D 选项重新显式定义,以避免预定义宏被重定义。
        • 调整 __GXX_TYPEINFO_EQUALITY_INLINE 对启用 __GXX_MERGED_TYPEINFO_NAMES 是必要的。
        • 注释 __GXX_MERGED_TYPEINFO_NAMES 默认值被修改 以支持上述影响符号可见性的特性。
      • 其它实现暂不使用选项支持,以避免和上述二进制约定冲突。
        • libc++ 不支持另行调整,由实现根据平台环境指定 _LIBCPP_HAS_NONUNIQUE_TYPEINFO
        • 另见以下关于 std::type_info 比较的限制。
    • 链接时除上述二进制约定,假定 std::type_info 相关的符号被共享。
      • 这不允许在实现生成以外显式地隐藏相关的符号(如通过 #pragma 预处理指令或 -fvisibility=hidden 编译器命令行选项)。
      • 这一般要求在兼容 GNU ld 的链接器在命令行选项中使用 --dynamic-list-cpp-typeinfo 等方式导出相关符号(也允许但不要求使用 -rdynamic-export-dynamic )。
  • 不依赖 RTTI 的 std::type_info 对象在未命名命名空间中同名不同实体的比较结果:
  • 假定在标准库宏 NDEBUG 被定义的翻译单元中的代码不违反异常规范。
    • 这允许改进代码生成,如使用 G++ 的 -fno-enforce-eh-specs 选项
    • 程序应不依赖违反异常规范时调用标准库的函数的行为。否则,程序行为未定义。

  YSLib 中具体子项目可要求更严格的实现假定,违反这些假定要求诊断,并可能在不保证支持的构建配置环境下终止构建。需要显式提供诊断时,若可行,应使用符合上述要求的可移植特性。此外,YSLib 实现可依赖可选的语言实现扩展。具体的更严格的假定和可选特性使用参见项目版本库中 doc 目录下的相关项目文档。

注释 一些诊断通过语言规则保证而无需显式提供。提供诊断使用的可移植特性的一个例子是 #error 预处理命令。

原理 YBase 对标准库的修正实现要求更严格的关于实现细节的假定。YBase 对标准库的不分替代(如 std::addressof )需依赖更严格的实现特性才能完整实现和标准库相同的保证(尽管不分保证作为 YBase 替代是可选提供的)。依赖更严格的实现假定也允许依赖和向用户代码提供特定平台配置的功能特性,以及提供质量更好的高性能实现。

历史实现要求

  以下要求已被修改或取消。

  • 假定提供撤销标准库未定义行为的保证:
    • 撤销 [res.on.functions]/2.5 对特定不完整类型作为模板实际参数引起未定义行为的限制,包括:
      • 使用默认分配器( std::allocator 的实例)的 std::vectorvalue_type 类型。
        • 这被包含在 WG21 N4510
        • ystdex::pmr::pool_resource 的实现从 b843[2018-11-10] 起依赖这项特性。
        • 从 b863[2019-07-26] 起不使用不完整类型的元素而不再依赖这项特性。
      • 使用默认分配器( std::allocator 的实例)的关联容器(即 std::mapstd::set )的 value_type 类型。
        • std::vectorstd::liststd::forward_listWG21 N4510 引入了不完整类型的支持。这不在此处要求。关联容器的要求预期在未来被添加。
        • 已知 libstdc++ 的实现符合这个条件。
        • 在 YSLib 中仅被 YSLib::ValueNode 通过在 std::map 使用递归的键类型的实现从 b338[2012-09-13] 起依赖。
        • 因为使用 ystdex::map 替代 std::map ,从 b830[2017-08-11] 起取消这个要求,不再依赖标准库实现提供的扩展特性。
  • 假定特定类型的特定操作无异常抛出(但不依赖异常规范的行为),包括:
    • std::function::swapLWG 2062 起有效)。
      • 因为使用 ystdex::function 替代 std::function ,从 b848[2018-12-24] 起取消这个要求。

可选实现支持

  允许使用 ISO C++11 以后兼容最新标准草案的正式标准中的特性(可通过 __cplusplus 宏和 SG10 建议的特性检查判断)。

扩展特性

  不依赖实现的方言扩展,但在确保实现能支持时,在特定的代码中可通过条件包含等方式选用。

  若实现默认具有不符合标准的特性,在本项目的代码中不依赖这些特性,即便外部依赖项可能对此进行配置(如 MinGW G++ 为了和 Microsoft VC++ 兼容启用的 -mms-bitfields ,而 MSYS2 安装的 freetype2 的 pkg-config 的 CFLAGS 隐含此参数)。

保留名称

  YSLib 项目中,除了 YBase.LibDefect 是对标准库实现的修正外,并不是语言的实现,因此公开接口遵循 ISO C++ 对保留名称的使用,如不引入以 __ 起始的标识符。

  对实现环境已经以保留标识符提供的接口,适用以下规则:

  • <ydef.h> 提供宏包装特定实现的标识符。
  • 除标准预定义的(如 __cplusplus )和用于特性检查的标识符(以 __cpp__has 起始),以及上述被包装时的宏定义,不在注解(作为宏 YB_ATTRYB_ATTR_STD 的参数)外直接使用保留标识符。

  <ydef.h> 和其它一些 YSLib 项目头文件保留特定的不被标准保留的标识符,详见 YSLib 项目文档 doc/Definitions.txt

库概述

  YSLib 项目由多个子项目组成。其中主要的有顶级子项目:YBase 和 YFramework 。它们是开发 YSLib 应用的必备的库。每个库被构建为单独的映像(静态库或动态库)。

  YSLib 的组件有些是依赖于特定平台的,但更多是平台中立的。关于库的组件在此的不同,详见下文的解释。

  静态库、动态库或其它可能被库构建时依赖的输入以及构建使用的工具是库构建的依赖项。关于依赖项的一般说明,详见术语中关于依赖管理的说明。

平台模拟

  除非另行指定,文档中的狭义的“模拟”概念指程序模拟。

  YSLib 项目中,平台模拟(platform emulation) 主要指直接以运行时环境适配层嵌入宿主平台运行时,在具体程序中提供类似被模拟的目标平台的具体特性和接口。

  完整的定义详见 YSLib 项目文档 doc/CommonRules.txt

外部依赖项

  外部依赖项在构建项目的上下文中,指构建时可能使用的外部依赖,是不由本项目维护和单独发布的依赖项。

  YSLib 项目严格使用 ISO C++ 的子集和特定实现的可选的扩展。关于依赖的语言特性,参见以上语言使用和实现要求的说明。

  在所有目标平台上,除了系统库外,外部依赖项是相同的,但可能使用不同的版本,也不一定按相同的配置构建。系统库的概念和 GNU GPLv3system library exception 中的 system library 定义类似,在此指特定平台或操作系统提供运行时支持的、由特定第三方环境提供开发支持的外部依赖,例如提供特定平台的 ISO C++ 标准库部分实现的 libstdc++ 和提供 Windows API 实现的 GDI32 等。

  除了系统库外,一部分外部依赖项可选或必须使用自行构建的(可能被修改的)版本。这些外部依赖项的修改和构建脚本位于版本库的 3rdparty 目录,默认按原始许可证发行。

  YBase 只直接依赖 ISO C++ 标准库。

  YFramework 默认依赖经过修改的 FreeType2 和 FreeImage 。其中前者当前仅修改头文件,经过特别处理和官方发布的直接构建的版本二进制兼容,可以被系统库替换。

  当前版本中,存储库中不同宿主平台对应的静态库文件(后缀名为 .a )的目录如下:

  • YFramework/DS/lib
  • YFramework/MinGW32/lib
  • YFramework/MinGW64/lib
  • YFramework/Android/lib
  • YFramework/Linux/lib

  其中,使用的 FreeImage 静态库对应 YFramework 的 debug 和非 debug 配置,文件名分别为 libFreeImaged.alibFreeImage.a 。这可在同一个目录树中共存。

  (当前 Android 和 Linux 仅支持单一本机体系结构,实际仅测试 Android ARMv7 和 Linux x86_64 。)

  构建 YFramework 时需包含 3rdparty/include 目录的头文件。Sysroot 安装脚本 Tools/install-sysroot.sh 会复制包括上面的头文件在内的文件。

  当前已经使用的详细外部依赖项详见 YSLib 项目文档 doc/Dependencies.txt

注意 版本库历史中包括静态库(.a) 文件,但为减少版本库大小,不再更新且可能移除,使用外部源或自行构建的方式替代。

  可使用以下方式安装外部依赖项:

  在 build 885 之前,版本库历史的对应的宿主平台中的静态库位于以下位置:

  • YFramework/DS/lib
  • YFramework/MinGW32/lib-i686
  • YFramework/Android/lib
  • YFramework/Linux/lib-x86_64

  其中 Android 平台只包括 FreeType2 ,Linux 平台只包括 FreeImage 。其它平台包括 FreeType2 和 FreeImage 库文件。

  其它平台中,只有随其它文件的发布版本包含完整更新,否则压缩包中可能只有其中一个库文件。

  关于自行构建外部依赖项的方法,参见构建说明

模块

  YSLib 项目组织为一个逻辑上的树形结构,其叶节点称为模块。类似文件系统,非叶节点称为模块目录。在源代码中使用模块路径来标识不同的模块,其分隔符为 :: 。在 YSLib 项目下直接划分的顶级子项目的名称仅在必要时出现在模块路径中,完整模块路径一般从次级子项目的名称起始。

  C 和 C++ 源代码的一个模块由以下三种形式之一构成:

  • 一个头文件
  • 一个非头文件的源文件
  • 一个头文件和对应的源文件

  作为公开接口的模块是公开模块。公开模块的头文件在单独的目录中以便部署。

  按 ISO C 和 ISO C++ 规定,C 或 C++ 模块中存在的非头文件的源文件和包含的头文件构成一个 C 或 C++(预处理)翻译单元,简称单元。注意这里包含的头文件不仅限于模块中的头文件。

  关于模块的进一步说明以及模块路径的形式文法和头文件依赖的基本规则,参见 YSLib 项目文档 doc/ProjectRules.txt

文件系统布局

  作为 C++ 项目,YSLib 把每个顶级子项目的源文件和公开模块头文件分别保存在不同的目录中,即 includesource 。非公开模块若有头文件,也位于 source

  特定于目标平台配置的代码会直接位于 平台名 目录下,称为平台扩展。对应的两个目录为 平台名/include平台名/source

  除了平台扩展的内容,文件系统目录和模块目录的每一级对应。平台扩展的模块目录名是对应平台中立部分加上后缀 _(平台名)

  举例:顶级子项目 YFramework 下的次级子项目 Helper 的非平台扩展的源代码在目录 YFramework/include/HelperYFramework/source/Helper 中,它的 DS 平台扩展的源代码位于 YFramework/DS/include/HelperYFramework/DS/source/Helper 中。

  提供平台扩展的次级子项目只有 YCLib 和 Helper 。

  编译项目时包含的头文件是合并的,如编译器命令行 -IYFramework/DS/include -IYFramework/include 在一次编译中同时使用平台中立和特定于平台 DS 的模块的 YFramework 头文件。

YSLib 及其本体

  在 YBase 和 YFramework 分离之前,YSLib 是一整个库。原 YSLib 大部分仍然在 YFramework 中,仍然可称为 YSLib ,是一个 YFramework 下的次级子项目。注意和整个项目名的不同,以下称为 YSLib 库,以示区分。

  YSLib 库中,Adaptor 用于适配特定于具体外部依赖的接口。可以通过修改其中的代码替换外部依赖,包括部分标准库兼容接口。

  其它部分的接口和实现都是严格平台中立且不依赖外部特定接口而变化的,称为本体。本体中提供了 YSLib 的主要功能。

Helper

  若需要开发依赖平台特定的应用,本体接口可能不足,而需要使用平台扩展。此外,可能需要一些便利功能。

  在 YSLib 库之上,Helper 对此类需求提供了一致而灵活的接口。

  若需要更接近特定平台实现的接口,可以使用 YCLib 及其平台扩展。

  YCLib 和 Helper 在宿主实现上都提供了更加丰富的功能。

代码规范

  YSLib 项目维护的代码规范符合 YSLib 项目中的文档约定的规则,包括:

  • doc/CommonRules.txt :一般规则。
  • doc/ProjectRules.txt :项目规则。
  • doc/LanguageConvention.txt :语言使用约定。

  YSLib 中的脚本代码应符合以下相关章节的约定。

代码格式化

  doc/CommonRules.txt 中规定了命名风格和参考的代码格式。由于格式化代码涉及语义分析,并不保证可以完全自动化进行,需要在编码时注意调整。

  以下工具配置可以把其中的主要工作自动化进行:

  • clang-format
    • 可通过 MSYS2 包 mingw-w64-i686-clangmingw-w64-x86_64-clang 安装,以下配置以这里的 3.7 版本为基准测试
    • 配置选项的文档参见这里
    • 通过命令行 -style= 指定使用选项文件 Tools/YSLib.clang-format
    • 注意 至少以下格式需要手动调整
    • 通过命令行 -i 直接编辑文件而不是打印结果到标准输出
    • 命令行示例: find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs clang-format -i -style=file
    • 命令行示例: find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs -i sh -c "clang-format -i -style=file {}"
    • 命令行示例(同时替换 template < ): find YBase YFramework YSTest -name "*.h" -o -name "*.hpp" -o -name "*. cpp" | xargs -i sh -c "clang-format -i -style=file {} && sed -bi 's/template </template</g' {}"
  • astyle
    • 可通过 MSYS2 包 mingw-w64-i686-astylemingw-w64-x86_64-astyle 安装,以下命令行以这里的 2.05.1 版本为基准测试 *(版本 2.05 )命令行选项的文档参见这里
      • --help 取得的文档可能有问题--delete-empty-lines 对应的短选项应为 -xe 而不是 -xd
    • 使用命令行选项 -A1 -T -p -U -k1 -xj -xy -xC80
      • 短选项可以缩写
      • 和上述 clang-format 比较,少了部分功能,主要有
        • AlignAfterOpenBracket: DontAlign
        • AlignTrailingComments: true
        • AlwaysBreakAfterReturnType: All
        • MaxEmptyLinesToKeep: 2
    • 注意 至少以下格式需要手动调整
      • 无法正确识别 constexpr 导致错误的缩进
      • 虽然右值引用识别问题已解决,但实测对模板参数无效
      • extern "C" 块冗余缩进
      • 断行后的缩进
      • lambda 表达式的捕获列表中的 = 周围的冗余空格以及对应的 { 断行
      • static_cast 等关键字后的 <> 周围的冗余空格
      • 宏实际参数列表头部的 ( 和作为第一个参数的标点可能有冗余空格
      • 宏实际参数列表尾部的 ,) 没有以空格隔离
      • 初始化数组的列表 { 和之前的 ] 存在冗余空格
    • 不使用 -xp ,尽管一些注释需要(移除行首 * 后保持一级缩进)的此类格式,但它会不必要地影响大部分 Doxygen 注释块
    • 默认备份文件后缀 .orig ;可选使用 -n 取消备份文件,或 --suffix= 修改备份文件后缀
    • 使用 -r 递归处理子目录
    • 可选使用 -v 显示详细过程
    • 可选使用 -Q 只显示被处理的文件
    • 可选使用 --dry-run 不实际处理文件
    • 命令行示例:astyle -vQnrA1TpUk1xjxyxC80 YBase/*.h* YBase/*.cpp YFramework/*.h* YFramework/*.cpp YSTest/*.h* YSTest/*.cpp

构建选项

  一般地,在脚本中默认指定的工具链的警告选项应能支持代码规范。

脚本

  YSLib 版本库中包含若干脚本。这些脚本和 YSLib 安装部署的脚本使用命令行程序运行环境和本章中的约定。

  脚本一般通过调用解释器运行。脚本的运行要求宿主环境(符合 YSLib 源码 YF_Hosted 定义支持的平台,指具有操作系统的环境)。

  除非另行指定,脚本以文件的形式部署。

外部环境

  • 除非另行指定,脚本不假定自身的存储位置。
    • 一般地,这允许脚本的内容不总是可通过文件系统访问。这包括执行时脚本的来源被移除和不公开以文件的形式部署等情形。
  • 对文件系统中的脚本:
    • 脚本文件总是普通文件或可解析到普通文件的有效的符号链接。
    • 除非另行指定,不假定脚本文件所在的位置或其父路径可写。

  项目中提供的脚本在版本库中具有固定的相对路径。除解释环境适用的公共的约定(如提供一般类 UNIX 系统使用的文件系统布局的 FHS(文件系统层次结构标准)),脚本不预设绝对路径的假定。

警告 小心处理路径。当前脚本直接或间接使用的所有涉及文件系统递归操作均不假设检查遍历的目标之间是否重复,若使用不恰当的目录链接,可能造成未预期的行为(如复制指向父目录的目录链接时可引起无限递归)。部署时应提前确保不存在这样的链接。

  脚本实现假定没有可能冲突的并发文件访问。

脚本文件

  脚本文件总是使用以下约定的扩展名。

  除了 Windows 命令解释器使用的 .cmd 文件以及 NPLA1 脚本使用的 .txt 文件,所有脚本使用 Shebang 明确需要使用的解释环境(详见 shell 脚本的说明)。

  Windows 命令解释器脚本因为实现限制使用本机 ANSI 代码页的编码,约定为兼容 ASCII 。

  NPLA1 脚本约定使用带有 BOM 的 UTF-8 编码(去除 BOM 的脚本可能兼容 ASCII )。详见 stage 1 SHBuild 中关于 NPL 支持的说明

接口约定

  脚本变量可能被读取和加载。

  除非另行指定,脚本引入的非导出的变量和名称以 _ 结尾的变量,都不是公开接口。

原理 基于名称约定便于和构成公开接口的名称区分。

路径

  路径(path) 是由有限的路径组件构成的序列。在不引起歧义时,路径即指文件系统路径。文件系统的路径组件能以字符串形式表示。

  在支持相对路径的环境中,是否支持相对路径取决于具体脚本。

  内部的路径的分隔符使用 / 。通过工具脚本提供的 SHBuild_2w 等函数,可转换为带有不同路径分隔符的路径字符串。这种转换通常仅在必要时(如明确作为外部工具的输入)使用。

  字符串形式的路径(即路径字符串)可能是保证不以分隔符结尾的文件名和确保以分隔符结尾的目录路径字符串。目录路径指目录路径字符串或其对应的非字符串形式的路径。

  两个路径字符串的拼接可构成新的路径字符串。这两个路径字符串分别是路径前缀和路径后缀。除非另行指定,作为路径前缀使用的路径字符串是文件名。这要求通过串接路径后缀构成访问前缀指定的目录的新的路径时,路径后缀需要以分隔符起始。

注意 YBase 的 ystdex::path 和 YFramework 的 YSLib::IO::Path 等数据结构表示根路径外无视结尾分隔符的非字符串形式的路径,因为有效的分隔符仅在根路径中出现。这些实现一般不检查分隔符的合法性,如其中具有包含分隔符的路径组件,也可能正常转换为字符串形式的合法路径。当前脚本只使用字符串形式的路径。

Shell 脚本

  扩展名 .sh 的脚本文件是 shell 脚本文件。大多数脚本需要使用 GNU bash 运行,如:

#!/usr/bin/bash

  需要考虑兼容性时,一般使用以下替代:

#!/usr/bin/env bash

  其它可以直接兼容 POSIX shell 的 .sh 脚本使用 Shebang 如:

#!/usr/bin/sh

  需要考虑兼容性时,一般使用以下替代:

#!/usr/bin/env sh

  为简化脚本代码,用户需要保证调用 shell 脚本时,环境应满足以下条件,否则行为未指定:

  • 使用满足要求的 Shell 语言实现:
    • 对 bash 脚本:
      • 满足版本要求:当前 Bash 最低版本为 4.4 。
      • 不使用 POSIX 兼容模式。
    • 对其它 POSIX shell 脚本:
      • 使用符合 POSIX 的 shell ,或使用以上要求相同的 bash 运行 shell 脚本。
    • 可支持使用特定的公共非 POSIX 扩展:
      • 使用 mktemp 命令创建临时文件名。
      • 注释 这些扩展在应在受支持的平台环境中可用。
    • 注释 除非另行指定,不要求对 shell 环境进行检查。
  • 不论是否设置了变量 POSIXLY_CORRECT ,命令解释环境的变量满足:
    • 特殊内建工具没有被用户定义的同名变量或别名覆盖。
    • 被脚本调用的 POSIX 定义的工具命令没有被用户定义的同名变量或别名覆盖为调用时可观察行为不等价的实体。
    • 变量 IFS 未设置或设置为默认值。
  • 命令解释器同 bash-p 选项启用时效果相同。
    • 用户应保证不设置 CDPATH-p 忽略的环境变量$BASH_ENV 等启动文件,以避免未预期的不同行为。
  • 脚本文件不是目标在不同目录中的符号链接。
    • 对 bash 脚本,允许使用变量 BASH_SOURCE 的值(如 ${BASH_SOURCE[0]}${BASH_SOURCE%/*} 等形式) 较可靠地判断脚本文件的路径。此时,隐含需要附加假定通过脚本文件路径确定其它资源的位置。
    • 部分脚本可能有更进一步的使用限制或不依赖 . 命令及命令解释器调用的差异。此时,可直接使用 "$0" 判断路径。
  • 除非另行指定,调用的外部命令的程序满足以下可用性要求:
    • 以 POSIX 实用程序名称直接调用的满足 POSIX 对应的要求。
    • 当前使用的 shell 的名称(如 bash )总是能被 POSIX env 实用程序搜索到并调用。
    • /usr/bin/env 总是提供可用的 POSIX env 实用程序
      • 注释 这允许上述 Shebang 可用。同时,/usr/bin/env 作为命令名称一般等同于 env
    • 注释 除非另行指定,不使用 which
      • 在 GNU bash 中,对检查一个操作数的情形,使用 type -P 代替。
      • 注意 command -v 对别名等非 $PATH 中可搜索的程序处理不同。
      • 另见 ShellCheck 警告 SC2230相关讨论

  脚本实现中的调用不受以上限制。

  若脚本可以确保兼容 POSIX shell ,使用后者而不是前者。

  如果对应解释器位于其它目录,可以符号链接到上面指定的路径。

  除非另行指定,本文档约定使用的路径字符串的分隔符为 / ,不连续出现在路径中,且不在结尾出现。例外:

  • 平台相关的绝对路径转义可出现 //
  • 构成路径的路径前缀可能以分隔符结尾,直接表示上层目录。

注意 当前版本库中的文件不保证跟踪权限。在一些环境中可能因为可执行权限问题导致无法立即执行脚本,参见这里的说明

  正式支持的发布版本中的公开 Shell 脚本应保证用 ShellCheck 0.7 或以上版本(任选)检查没有诊断消息。检查的命令行为 shellcheck -x -P SCRIPTDIR 跟随文件名。除非另行指定,使用明确的方式避免引起检查的问题而不是使用指令消除检查的结果,如使用 ${BASH_SOURCE[0]} 代替 $BASH_SOURCE 以避免 ShellCheck 警告 SC2128

Shell 语言使用规约

  脚本程序不调整影响别名扩展的 shell 选项(如 bash 的 shopt -u expand_aliases )。

  依照运行时环境的约定,除非另行指定,脚本不区分未设置的环境变量和已设置但具有空值的环境变量,以允许在外部环境配置脚本的执行。这两种情形下,被用于脚本中的变量的环境变量都可能指定一个非空的初始值,称为变量的默认值(default value) 。变量是否使用默认值总在先于第一次使用变量的值的初始化时确定。

  变量的初始化和默认值满足以下约定:

  • 初始化的变量是否只读未指定。
  • 初始化变量时若需使用默认值,可能发生附加的求值(如命令调用)以其结果确定具体默认值。
  • 除非变量的初始化被指定为由特定的函数确定,变量在未指定的脚本被执行时无条件初始化,而不需要使用变量的值前调用特定的函数。
  • 除非另行指定:
    • 在特定函数中初始化的变量由此函数指定其默认值。
    • 初始化时使用的外部环境变量的值可假定和脚本运行环境直接或间接调用脚本前相同。

  除非另行指定,shell 函数满足以下约定:

  • 已定义的 shell 函数假定不被其它变量覆盖。
    • 原理 这允许实现省略 readonly -fdeclare -g -r -f 声明,且允许使用相同的定义覆盖以简化调试。
  • Shell 函数返回值是通过接口语义中蕴含的最后的命令调用的返回值;或当不存在这样的命令时,默认为 0
  • Shell 函数的结果是标准输出的内容。
    • 注释 通常使用 echo 或功能蕴含 echo 的命令调用输出返回的值。

  非生成的脚本源代码中不使用包含 __ 的名称。

  公开函数的函数名及公开的变量名不以 _ 结尾,否则以一个 _ 结尾。

  对 SHBuild 相关工具使用的函数,使用前缀 SHBuild

  函数出错且无法恢复时退出脚本。

  版本库中的提供的脚本一般应能通过 ShellCheck 检查。对需要使用 ShellCheck 指令指定脚本起始构造的检查时,在之前添加一行空命令(对 bash 脚本使用 : ,非 bash 脚本命令使用 true )以避免非预期地使指令作用于整个脚本。

环境变量

  Shell 脚本可使用以下能通过外部环境指定值的环境变量:

  • 用于指定入口位置:
    • SHBuild :外部 SHBuild 可执行文件路径。
      • 默认值和特定脚本被设计适应的部署环境相关(例如,视不同情形,脚本可使用 ${BASH_SOURCE[0]}"$0" )。
    • SHBuild_ToolDir :工具目录中的脚本目录(参见脚本)。
      • 默认值由脚本根据存储库所在目录确定。
  • 用于指定存储库中的资源:
    • SHBuild_BaseDir : SHBuild 目录,是工具目录中提供 SHBuild 的源代码的目录。
      • 默认值按 SHBuild_ToolDir 初始化后的相对位置确定,为 "$SHBuild_ToolDir/../SHBuild"
    • YSLib_BaseDir :YSLib 目录,即版本库检出后的工作目录。
      • 默认值按 SHBuild_ToolDir 初始化后的相对位置确定,为 "$SHBuild_ToolDir/../.."
  • 用于指定输出目录:
    • SHBuild_BuildDir :构建使用的中间输出文件路径。
      • 默认值定义如下:
        • 对不依赖 Sysroot 的以及 YSLib/YSTest 目录下的项目构建脚本,默认值指定在完整的版本库中和 "$YSLib_BaseDir/build/$(SHBuild_GetBuildName)" 相同的目录,且构建 stage 1 YSLib 时确保为绝对路径。
        • YSLib/YDE 目录下的项目构建脚本,默认值是 ".$(SHBuild_GetBuildName)"
        • 否则,默认值是当前工作目录 .
        • 以上调用的函数 SHBuild_GetBuildName脚本 Tools/Scripts/SHBuild-common.sh 中,依赖具有非空值的变量 SHBuild_Host_OSSHBuild_Host_Arch
    • SHBuild_SysRoot :输出的 Sysroot 根路径。
      • 在 stage 1 中,默认值是 "$YSLib_BaseDir/sysroot"
  • 用于配置构建环境:
    • 其它情形不用于指定输出目录,不保证具有默认值。
    • SHBuild_Env_TempDir :构建时使用的临时目录路径。
    • SHBuild_Env_Arch :构建系统架构。
    • SHBuild_Env_OS :构建系统操作系统。
      • 初始化和确定值的方式同 SHBuild_Env_Arch
    • SHBuild_Host_Arch :宿主架构。
      • 由脚本 Tools/Scripts/SHBuild-common.sh 的函数 SHBuild_PrepareBuild 初始化。
      • 默认值使用以下方式指定:
        • SHBuild_Env_OS 的值是 Win32 且外部变量 MSYSTEM 的值是 MSYSTEM64 时,默认值为 x86_64
        • SHBuild_Env_OS 的值是 Win32 且外部变量 MSYSTEM 的值是 MSYSTEM32 时,默认值为 i686
        • 其它情形默认值同 SHBuild_Env_Arch 初始化后的值。
    • SHBuild_Host_OS :宿主操作系统。
      • 初始化的方式同 SHBuild_Host_Arch
      • 默认值同 SHBuild_Env_OS 初始化后的值。
    • SHBuild_VCS_ 起始的变量:指定使用的版本控制系统。
      • SHBuild_VCS_hg :若非空,指定使用 Mercurial
      • SHBuild_VCS_git :若非空,指定使用 Git
      • 除非另行指定,若同时指定使用 Mercurial 和 Git 两者,则使用 Mercurial 。
      • 指定使用版本控制系统是提示,不保证是实际的选择。具体使用前,脚本可能自动对可用性(命令行 hggit 及当前工作目录是否位于对应的版本库)进行检查,当不满足要求时可能忽略提示。
      • 原理 若当前工作目录同时位于多个版本控制系统的版本库,使用提示可以明确选择其中之一。
      • 注释 本项目的源代码可从 Mercurial 或 Git 中取得
    • SHBuild 构建使用的变量:详见 SHBuild 的帮助信息。
    • 其它构建脚本使用的变量:详见以下具体脚本的说明。

  若以上变量在上述任一情形存在作为路径的默认值且外部环境指定变量的值,则指定的值应表示合法的路径。

  一般地,部署的 shell 脚本需引用其它脚本时,以如下方式使用指定入口的环境变量:

  • 若脚本只支持 Sysroot ,以 SHBuild 在 Sysroot 的安装位置推断其它脚本的路径。
  • 若脚本只支持工具目录中的脚本目录布局,以 SHBuild_ToolDir 指定。
    • 这隐含目录在版本库中的布局,可以此初始化 YSLib_BaseDir 等变量的默认值。
  • 若脚本同时支持 Sysroot 中部署的位置和工具目录中的脚本目录布局:则不使用以上的变量。
    • 引用其它脚本文件时可直接指定包含,如:. "$(dirname "${BASH_SOURCE[0]}")/SHBuild-common.sh"

  除指定入口的环境变量,是否支持外部可覆盖默认值的上述环境变量是可选的,取决于各个脚本的具体支持。

  典型地,Shell 指定的环境变量可被以下方式在被 shell 运行的命令中使用:

  关于这些环境变量如何影响其它程序,参见运行时的环境变量

NPLA1 脚本

  NPLA1 脚本可被 SHBuild 调用。SHBuild 支持特定的选项作为 NPLA1 的脚本解释器。脚本解释器支持 NPLA1 脚本文件。此外,在 shell 脚本中,NPLA1 脚本代码可能以字符串的形式存储和被 SHBuild 调用。

NPLA1 环境变量

  NPLA1 脚本可使用和 shell 脚本通用的环境变量,如 SHBuild

  除 stage 1 外,shell 脚本使用环境变量 NPLA1_ROOT 指定的根目录作为 SHBuild 加载 NPLA1 脚本使用的起始目录。

  脚本(包括直接或间接调用 NPLA1 脚本的其它脚本)除默认值外不需要依赖 Sysroot 的安装路径下的目录和文件布局,环境变量 SHBuildNPLA1_ROOT 可分别指定相互无依赖的路径。当仅指定 SHBuild 时,通过指定的 SHBuild 路径推断 NPLA1_ROOT 默认值,此时使用假定符合 Sysroot 约定的相对路径。

  NPLA1 脚本可使用以上约定含义和默认值的 shell 环境变量并初始化 NPLA1 脚本内的同名变量,但部分变量的作用域和初始化方式可能不同:

  • 在 Tools/Scripts/SHBuild-YSLib-common.txt 中直接初始化变量 SHBuild_Env_ArchSHBuild_Env_OS
  • 在 Tools/Scripts/SHBuild-YSLib-common.txt 的函数 SHBuild_GetPlatformStrings(详见版本库中的 doc/NPL.txt)的调用中初始化变量 SHBuild_Host_ArchSHBuild_Host_OS

  注意在 NPLA1 脚本中创建的变量不是环境变量;但和 shell 脚本类似,若需作为其它外部命令的环境变量,可先导出再调用对应的程序。

NPLA1 函数

  NPLA1 脚本可提供部分 shell 脚本中的同名函数。除非另行指定,这些函数的调用接口以及功能和 shell 脚本中的同名函数一致,但实现和以下行为不保证相同:

  • 出错时的诊断方式。
  • 缓存的变量及相关的输出提示信息。
  • 性能。
  • 影响的非公开环境变量等其它外部环境状态。

  作为公开接口的 NPLA1 函数另见 doc/NPL.txt 中的描述。

构建脚本

  一些开发脚本被用于构建。构建脚本的一般形式提供配置和生成阶段的自动化功能。

  配置阶段设置交互环境。典型地,通过执行命令前设置的环境变量指定可配置项。

  生成阶段调用合适的工具完成构建。

  配置阶段的接口可能对外隐藏,此时使用默认配置进行构建。

  一些公用的构建脚本可适用于整个项目。其它的构建脚本可构建具体子项目中的目标。

  构建脚本可以是 makefile 或其它可执行的脚本。其中,makefile 可能使用以上约定含义的 shell 环境变量,但不保证可被外部指定覆盖脚本中指定的默认值。

参考

  • YSLib 项目文档 doc/Dependencies.txt 了解组织结构、开发规则、默认使用的外部依赖项(包括语言实现)和相关约定。
  • 结构和特性 中的树形结构了解项目依赖性。
  • YSLib 项目文档 doc/ProjectRules.txt 了解组织结构、开发规则和相关约定。
  • YSLib 项目文档 doc/YBase.txt 了解顶级子项目 YBase 。
  • YSLib 项目文档 doc/YFramework.txt 了解顶级子项目 YFramework 。
  • YSLib 项目文档 doc/YSLib.txt 了解 YFramework 的次级子项目 YSLib 。

先决条件

  本文档列出受支持的构建配置的关联的先决条件,以及这些环境下的已知问题和相关应对。

注释 构建配置和平台的相关定义详见术语。对问题的描述可能仅适用于个别的配置和源代码版本。仅需要配置构建环境的用户可以跳过关于非目标平台和不相关的问题的内容。

  特定历史版本依赖的详细环境和平台库版本参见开发文档 doc/Dependencies.txt 。以下仅为当前最新主分支版本中用于构建 YSLib 自身及基于 YSLib 开发应用的环境配置说明。

  关于本项目对平台的定义和项目保持的环境约束,包括开发需要的以下基础环境外的其它第三方依赖,在了解下文内容后,参见开发说明

注意 获取到源代码并不保证能够直接构建 YSLib(特别是对于直接从版本库获取的途径)。如需自行构建,应注意开发说明中有关外部依赖项的部分获取必要的其它文件。

  关于运行环境,另见运行时平台环境的说明

  详细提交测试的环境参见开发文档 doc/Test.txt

  若不使用特定的特性或构建平台,构建环境的部分要求是可选的。

  以下各节的环境配置以“宿主平台/目标平台(目标平台名)”为标题指定,其中目标平台名是正式的配置名称(也在其它项目文档中作为配置名称使用),若和平台一致则被省略;其它平台的标识当前仅为明确开发流程,在用户文档中使用。对宿主平台和目标平台两者一致的情形,只标识一个平台。关于“平台”的一般含义,参见术语

公共构建环境

  构建环境提供工具链(语言实现)和构建需要的工具。某些工具被不同的构建环境共享(但可能仍需针对不同构建平台使用不同二进制程序)。

  Windows 平台上的 MSYS2 基础环境符合以下公共构建环境的所有要求。当前发布和一般提交(包括本机平台和交叉构建)时仅测试 MSYS2 。

命令行环境

  除非另行指定,构建环境的命令兼容 POSIX shell 。当前非 POSIX shell 的命令行构建环境仅在 Sysroot stage 2 环境 被支持。

工具链

  使用 make 的平台需要 POSIX shell 支持。

  除非另行指定,make 使用 GNU make ,版本 3.78 以上(建议使用 4.0 以上版本,虽然 YSLib makefile 不使用不兼容的特性);不使用 mingw32-make

  注意 make 可能被构建工具使用,详见以下链接时优化的说明。

  POSIX shell 实现可使用和工具脚本兼容的脚本解释环境 ,一般即 bash (建议版本 4.0 或以上)。

  除非另行指定,直接支持的 C++ 实现为 GNU C++ 4.9 或以上版本。

自动环境检测

  在命令行环境中使用脚本构建时,脚本可能自动检测构建环境而需要附加的工具。这些工具可能在具体的构建环境(如以下各节)中可能已被提供或可选安装,参阅各个构建环境附带的文档。

  • grep :用于判断线程支持的命令行选项。
  • uname :用于查询平台配置。

  使用特定的选项跳过自动检测构建时,可不依赖某些工具。详见具体脚本的说明。

控制台和终端

  除了自动检测环境( Win32 或非 Win32 下使用 ANSI 转义序列)开启的彩色(前景色)文本,构建环境没有使用依赖特定终端的字符界面。因此可以使用不同的控制台或终端模拟器。

注意 在 Windows 平台上若使用 ConEmu 出现 0xC0000005 错误,尝试升级到最新版本后勾选 Inject ConEmuHk 选项 ,或直接使用 cmd 代替。

线程执行环境

  除非另行指定,线程执行环境使用 ISO C++ 的定义,参见 ISO C++ [intro.multithread]

  库或接口的集合对多线程环境的依赖性满足以下要求:

  • 明确不依赖多线程执行环境时,保证在指定平台上可用的的公开 API 可构建并满足接口约定的语义,可在单线程执行环境中运行。
  • 明确严格不依赖多线程执行环境时,在不依赖多线程执行环境的要求上,还应保证语义一致。
  • 否则,不保证接口提供的功能可用。

  YSLib 整体不依赖多线程执行环境。特定的依赖多线程执行的情形包括以下话题:

  • 使用 ISO C++ 线程环境(首先要求实现满足整体要求):
    • 运行时环境需要部署的线程支持库,参见以下章节对各个平台的说明。
    • 特定的依赖线程环境语义的接口,参见具体组件的开发文档(如 doc/YSLib.txt )及源代码中的 API 注释或以此构建 API 文档
  • 特定的平台实现使用非 ISO C++ 线程模型或实现(不保证平台中立):
    • 不会作为对所有平台必要的公开接口。
    • YCLib 提供 POSIX API 封装。
    • 其它实现细节参见开发文档 doc/YCLib.txt 等。

链接时优化(LTO)

  YSLib 默认在 release 构建配置下通过向编译器和链接器传递命令行参数 -flto 等启用 LTO 。若工具链不支持 LTO ,可能需要自行修改脚本去除相关选项——这不被正式支持。

  MSYS2 的 MinGW 目标的 binutils-git 包的 gnu-ar 较早启用了 LTO 插件,可以直接使用。现在 MSYS2 的 MinGW 目标的 binutils 包也应支持。其它工具链需自行验证。

注意 GCC 4.9 起使用 LTO 默认不启用 -ffat-lto-objects 选项,因此需要链接器具有 LTO 插件支持,否则在启用 -flto 选项时会出现无法解析符号的链接错误。因为本项目当前只支持 GCC 5.0 以上的版本,使用 GCC 时这总是应当被注意。

注意 GCC 的 LTO wrapper 默认使用 make ,若没有 make 也没有通过指定 MAKE 环境变量替代,构建时使用 -flto 会失败。

PC/DS(DS)

  支持的构建环境:

  • DS 交叉编译环境,包括
    • 使用 make 的公共构建环境
    • ARM 目标平台 C/C++ 交叉编译环境
    • 生成 .nds 文件的 ndstool 工具
    • 环境变量 DEVKITARM 指定工具路径(见以下说明)

  支持的运行环境:

  • Nintendo/iQue DS[Lite/i/LL]
    • 仅测试了 iQue DS Lite
  • DeSmuME 0.9.9 (注意新版本因为自动 DLDI 存在缺陷,暂时不支持运行自制程序。)

  环境变量 DEVKITARM 指定一个 POSIX 路径,其中的 bin 子目录下包括工具。示例( POSIX shell 使用 Windows 路径):

export DEVKITARM=/C/devkitPro/devkitARM

  建议使用发布时最新版本的 devkitPro

  devkitPro 在 Windows 下提供 MSYS ,在 devkitPro 安装目录下 msys/bin 包含 bash 等程序。可把此路径添加到环境变量 PATH 。也可使用 MSYS2 替代。

  发布和一般提交时仅测试 devkitPro 最新版本,目标平台三元组 arm-none-eabi。

PC(Win32)

  Windows 平台使用 Microsoft VC++ 构建。因为一些已知的问题,暂不正式支持。参见以下 MinGW32 平台的构建条件以获取 Windows 可执行文件。

  但以下的部分信息和 MinGW 通用。

Windows 子系统

  Windows NT 内核以上支持不同的称为子系统(subsystem) 的用户模式运行时环境 ,主要包括 Win32 子系统、OS/2 子系统和 POSIX 子系统。

  因为标准库实现以及 YFramework 的部分组件依赖 Win32 API ,YSLib 在 Windows 上的实现依赖 Win32 子系统。

  Microsoft Visual C++ 提供 /SUBSYSTEM 链接器选项来指定不同的子系统。在此 CONSOLE 指定控制台程序,而 WINDOWS 指定非控制台的通常使用 GUI 的程序,它们都使用 Win32 子系统实现。

  控制台程序和 GUI 程序的入口不同,也有一些行为上的不同。例如,启动控制台程序默认会显示命令行窗口;GUI 程序忽略命令行的交互式输入。

操作系统版本

  Windows 操作系统版本一般对(非交叉编译的)构建没有直接影响,但有以下例外:

  GCC 的预编译头文件不保证对系统兼容:升级系统后可能出现错误,例如升级 Windows 10 1803 以后的系统,使用旧的预编译头文件(而不更改编译器二进制文件),可能出现以下内部错误:

internal error in mingw32_gt_pch_use_address, at config/i386/host-mingw32.c:184: MapViewOfFileEx: 试图访问无效的地址。

  此时删除所有预编译头文件,或不使用预编译头文件重新构建即可。类似的问题在其它的项目中也是已知的问题,使用类似的方式解决。

  另外,部分 Windows 版本可能存在不明原因的内核缺陷导致类似的问题。不仅限于构建,这些问题可能对运行也有影响。

  硬件故障(如有缺陷的物理内存)也可能导致类似的故障,但这和使用的操作系统版本无关。

预编译头文件

  部分构建使用可选的预编译头文件。

PC(MinGW32)

  MinGW32 包含 32 位和 64 位宿主和目标平台。关于 MinGW 或 MinGW32 的称谓,参见这里的说明。

  支持的构建环境:

  • 目标 32 位(i686) MinGW GCC 工具链。
    • 支持不同的 MinGW 运行时
    • 要求标准库多线程支持,否则只支持构建 YBase ,不支持构建 YFramework 。
      • 当前 GCC 官方的 libstdc++ 只提供 POSIX 线程模型的支持,不支持 Win32 线程模型。
      • 可考虑使用 mcfgthread
    • 仅测试 Windows 上的本机构建(构建平台三元组同目标平台三元组)。
      • 一般提交时仅测试 MinGW-W64 的发行版。

  非正式支持的构建环境:

  • 目标 64 位(x86_64) MinGW GCC 本机工具链(宿主和目标平台三元组 x86_64-w64-mingw32 或 x86_64-pc-mingw32 )。
  • 以上工具链中使用 LLVM 和 Clang++ 代替 GCC 。
    • 仅测试 MSYS2 的 MSYSTEMMINGW32MINGW64 替换 GCC 。
    • 虽然可支持,没有测试 MSYSTEMCLANG32CLANG64 的工具链。
    • 当前 32 位平台暂不支持 LLD( MSYS2 包 mingw32/mingw-w64-i686-lld )。
  • 虽然可支持,没有测试 MSYSTEMUCRT64 的工具链。
  • 暂不在配置脚本以外支持和测试 MSYSTEMCLANGARM64 的工具链(宿主平台三元组 aarch64-w64-mingw32 )。

  对 i686 平台,存储库中 YFramework/MinGW32/lib-i686 目录用于保存外部依赖项 FreeType2 和修改版 FreeImage 的二进制文件。

  对 x86_64 平台,需要自行编译依赖项。若构建环境存在可二进制兼容的 FreeType2 ,只需要编译修改版本的 FreeImage 。

  支持的运行环境:

  • Microsoft Windows XP 或以上版本的操作系统。
    • 对于 64 位目标工具链构建的程序,需要 64 位操作系统。
    • 当前默认发行的二进制文件使用 MSYS2 环境(详见以下小节)构建,要求 Microsoft Windows 7 64 位或以上版本的操作系统。
  • 构建环境匹配的附加运行库,默认支持的工具链要求包括以下动态库文件:
    • libwinpthread-1.dll
    • libgcc_s_dw2-1.dll
    • libstdc++-6.dll
    • libquadmath-0.dll(自 build 932 起)

  注意以上运行库未在发布版本中打包。此处依赖的详细说明参见开发文档 doc/Dependencies.txt @1.5.2 。

  若缺少 DLL ,也可在 MinGW32 GCC 发行版中的 bin 目录下找到。

特别注意 应使用对应的正确的线程模型(使用 POSIX 或 MCF 而不是 Win32 ,后者没有实现标准库的线程支持)以及异常模型( i686 为 Dwarf2 和 SjLj 之一,特定构建版本相关)。运行时 DLL 不匹配会导致错误。

MSYS2

  建议使用 MSYS2 环境。

  MSYS2 以从 Arch Linux 移植的 pacman 作为包管理器,可以直接通过命令行安装和移除不同的包,包括开发工具。

  以下环境也可用于开发其它 MSYS2 或 MinGW32 应用(忽略标注为 YSLib 的步骤即可)。

体系结构和命令行调用

  一个 MSYS2 环境同时支持 MSYS2 和 i686(x86) 和 x86_64(x64) 的 MinGW32 三个目标平台。其中 MSYS2 目标类似 Cygwin ,运行时依赖特定的 POSIX 兼容层 DLL (这里一般是 msys-2.0.dll ),性能往往较依赖 MSVCRT (默认版本现在 Windows 系统中应都已存在部署)的 MinGW32 目标低。所以不需要严格的 POSIX 兼容的应用尽量使用 MinGW 而不是 MSYS2 目标。

  MSYS2 自身也存在不同的体系结构而成为目标平台。和 MinGW32 不同,每个基础环境是 i686 和 x86_64 之一。在 x64 Windows 上,可以通过部署多个基础环境,合理设置环境变量 PATH 来切换使用 i686 或 x86_64 (而 32 位 Windows 只支持 i686 )路径;但一般情形下只需要使用其中之一。这个决定一般会作用到所有 MSYS2 程序,如 bash

  不论是 i686 还是 x86_64 的 MSYS2 基础环境,都可以选择安装 i686 或 x86_64 的 MinGW32 目标的包,但因为使用不同的 MSYS2 程序,两者并非可完全替换。

注意 因为基础环境中 uname 是位于 /usr/bin 的 MSYS2 程序,基础环境的体系结构会影响 uname 的结果而导致目标体系结构的自动判断结果不同,当前可能会影响后续需要的命令,详见 MinGW Sysroot 开发指令

注意 MSYS2 程序和不同体系结构的(本机或其它 MSYS2 )程序混用可能出现问题。参见下文关于 MSYS2 程序运行问题的说明。

  一个 MSYS2 环境中可能同时存在不同目标的程序,通过合理控制环境变量 PATH 的顺序或在调用时加前缀(prefix) 控制。例如 /usr/bin/gcc.exe/mingw32/bin/gcc.exe 可能同时存在,分别是 MSYS2 和 i686 MinGW32 的 GCC ,可通过以下方式确定调用 MinGW32 GCC :

  • PATH 中使 /mingw32/bin 尽量靠前来指定 gcc 调用 MinGW32 而不是 MSYS2 GCC
  • 若存在 i686-w64-mingw32-gcc 等明确目标平台的版本,一般可以直接在命令行中代替 gcc
  • 明确路径前缀,即使用 /mingw32/bin/gcc 代替 gcc

  某些工具可能在附加路径前缀 /mingw32 指定此类程序的安装位置。但当前 YSLib 不提供工具控制这些部署方式,默认只使用第一种方式:手动配置 PATH

  MSYS2 MinGW 工具链使用 POSIX 线程模型。

  MSYS2 MinGW i686 工具链使用 Dwarf2 异常模型。

  当前 YSLib 不支持 MSYS 目标。

主要配置步骤

  (某些步骤和原理也可参考官方 Wiki 。)

  • 进入官方发布站点/北京理工大学镜像/清华大学 TUNA 镜像/中国科学技术大学开源软件镜像的其中一个子目录下载 MSYS2 基础环境:
    • 目录 i686x86_64 针对 MSYS2 而非 MinGW32 目标。
    • 一般尽量选择较新的 x86_64 版本。
    • 如果有能解压缩 .xz 的软件,可以直接下载压缩包,否则选择 .exe 文件;对应版本内容一致。
  • 安装 MSYS2 基础环境到自定义的目录如 C:\msys2 (以下称为MSYS2 根目录)。
    • 注意 Win32 下 PATH_MAX260 ,此处最好避免过长的路径以使一些软件具有更好的兼容性。
    • 对压缩包直接利用现有工具解压;对可执行文件直接执行安装。
    • 之后,必须执行 MSYS2 根目录下的 msys2_shell.bat 确保完成安装。
  • 设置环境变量:
    • 可总是使用 MSYS2 根目录下自带的批处理文件打开 终端(默认是 mintty ),会自动在终端环境的 PATH 变量前加入对应所需的路径,不需要另行设置。
      • 对较新的版本(安装了 filesystem-2016.05-2 或更新版本的包)的 MSYS2 基础环境安装,分别运行启动脚本 msys2_shell.cmd -msysmsys2_shell.cmd -mingw32mingw64_shell.cmd -mingw64 进入对应的 MSYS2 、MinGW32 和 MinGW64 的 shell 。
      • 对未更新 filesystem-2016.05-2 或更新版本的包的 MSYS2 旧版本基础环境安装,分别运行启动脚本 msys2_shell.cmdmingw32_shell.batmingw64_shell.bat 进入对应的 MSYS2 、MinGW32 和 MinGW64 的 shell 。
    • 也可直接一次性设置 Windows 系统环境变量,这样可以对其它命令行环境(如 cmd )生效。
      • 注意 环境变量的值需要符合 Windows 的路径格式,每个路径之间以分号分隔。
      • 需要设置 bash 所在的 MSYS 工具的目录(即 MSYS2 根目录下的 /usr/bin )以及 MinGW 目标编译器和其它工具所在的目录(对 32 位的目标为 MSYS2 根目录下的 /mingw32/bin )。
      • 注意 因为 MSYS 和 MinGW 是不同的目标,使用不同的 GCC (可能被同时安装),需要保证 MinGW 编译器被优先搜索,所以 MinGW 目录一般应在 MSYS 目录之前。
    • 一些构建环境可能会依赖环境变量 MSYSTEM 的值,MSYS2 根目录批处理文件进入的不同环境对此有不同的设置。YSLib 中的构建脚本可使用此变量判断平台。MSYS2 中对应的启动脚本会自动完成这些设置。
  • 确保能搜索到正确的 MinGW 编译器。
    • 可以进入 Shell 执行命令 type -P gcc ,确定输出的结果为 MinGW 编译器(默认为 /mingw32/bin/gcc.exe/mingw64/bin/gcc.exe 之一)而不是 MSYS 编译器(默认为 /usr/bin/gcc.exe
    • 若已确保 MSYS 根目录下 /usr/bin 目录在环境变量 PATH 中,直接进入 cmd 或 Shell 直接执行 which gcc ,方法和效果与上述方法相同
    • 可以直接在命令行执行 gcc -v 查看输出,确认 Target: 带有后缀 -w64-mingw32
  • 建议手动执行 pacman -Syu 自行升级,或 pacman -S 安装所需的包的最新版本
  • 若用于构建 YSLib ,建议获取源代码后,进入 bash (此处也可以使用其它 Shell )后运行 POSIX Shell 脚本 Tools/msys2-pacman-update.sh 以确保 YSLib 构建需要的依赖项被安装
    • 脚本不会重复安装已经安装的包

  上述脚本调用 MSYS2 的包管理器 pacman 分批下载安装或更新所需工具:首先安装必要的工具,其次安装可选(但推荐)的工具。具体内容详见脚本代码。若安装工具时需要确认,按 Y 并回车。

配置安装源

  官方镜像配置参见 pacman-mirrors

  若 pacman 下载过慢,可用文本编辑器打开 MSYS 根目录下的 /etc/pacman.d 中的配置文件,编辑对应的镜像地址。如对于 32 位的 MinGW 包,在 mirrorlist.mingw32 的其它非注释行之前插入:

## 清华大学 TUNA 镜像( 2016-09-23 已变更,注意大小写)
Server = http://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686
## 中国科学技术大学开源软件镜像(之前曾停止更新,最后更新 2015-06-18 ;2015-11-01 已恢复)
Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686
## 北京理工大学镜像(最后更新早于 2017-02-09)
Server = http://mirror.bit.edu.cn/msys2/REPOS/MINGW/i686

  因为包 mingw-w64-x86_64-freetype 的 FreeType2 可二进制兼容,x86_64 平台只需要编译修改版本的 FreeImage 。

MSYS2 程序运行问题

  MSYS2 类似 Cygwin ,依赖 fork 调用创建新进程。这可能会出错。

  在执行以下步骤(参考这里)后,重新运行程序:

  • 关闭窗口或用任务管理器终止 sh.exebash.exe 等进程并尽量确保没有基于 MSYS2 环境的其它程序正在被运行
  • 执行 MSYS 根目录下的 autorebase.bat

  也可以参考 Cygwin 的解决方法

  混用 x86/x64 程序(不管是 Win32 还是 MSYS 程序)导致的类似问题可能经以上步骤仍然无法修复,这可由 MSYS2 在特定操作系统上的一个缺陷引起。变通做法是只使用固定目标的程序,例如只使用 i686 MSYS 基础系统执行 i686 MinGW32 GCC 。注意 i686 MinGW32 GCC 是 x86 的 Win32 程序。尽管它不是 MSYS2 程序,但可在 LTO 时调用 MSYS2 程序 make ,这时候可能出错。

  如果确定只使用少数程序,在命令行指定替换。例如 x86_64 的 MSYS2 bash 环境中使用 i686 MinGW32 GCC ,指定 LTO 调用的 make

export MAKE=/path-to-another-msys2-root/mingw32/make

  或直接替换所有 make

export make=/path-to-another-msys2-root/mingw32/make

注意 %SystemRoot%\System32\cmd.exe 在 x64 Windows 下是 x64 程序,对应的 x86 版本是 %SystemRoot%\SysWOW64\cmd.exe

GCC with the MCF thread model

  也可使用 MCF 线程模型构建的 GCC ,基于 MinGW-W64 和 mcfgthread ,使用 MCF 线程模型。

  这个发行版没有 make

  需自行编译依赖项。

  未在提交中测试。

MinGW-Builds

  也可使用 MinGW-W64 发行版,如 mingw-builds ,并自行获得和配置脚本 Tools/msys2-pacman-update.sh 中被安装的工具。

  需使用基于 POSIX 线程模型的版本,否则只可用 YBase 。

  需自行编译依赖项。

  当前未测试。

Nuwen.net

  对 x86_64 平台可使用 newen.net 提供的 MinGW 发行版,包含比较多的第三方库。

  仍需自行编译依赖项以支持 YFramework (当前不支持)。

  因为 15.2 之前的版本仅支持 Win32 线程模型,不支持 std::thread 等标准库线程 API ,不支持生成 YFramework ,只支持生成 YBase 。

  当前未测试。

Windows 子系统

  基本内容参见以上 PC(Win32) 平台关于 Windows 子系统的介绍。

  在 MinGW 工具链的命令行中,选项 -mconsole-mwindows 对应 Microsoft VC++ 工具链的子系统选项。若没有指定,一般默认为前者。通常 -mwindows 隐式使用一些附加的库,如 -lgdi32 -lcomdlg32 。和 VC++ 不同,当前使用的 MinGW 实现一般可以支持统一使用 main 作为入口,不需要特别修改。

  对 gccg++ ,使用 -dumpspecs 查看内建支持的 specs 。Clang/Clang++ 不适用 specs ,需要查看文档和源代码。

PC/Android

  支持的构建环境:

  • Android 开发环境(仅测试 Windows x64 宿主环境),包括:
    • 使用 make 的公共构建环境。
    • JDK(仅测试 Oracle JDK 1.8 ;注意 Android 仅支持部分 Java 8 特性)。
    • Android SDK 。
    • Android SDK Build-tools 。
    • Android NDK (仅使用独立工具链)。
    • 用于打包 APK 的私钥(用 Tools/create-android-debug-keystore.sh 可生成默认私钥)。

  支持的运行环境:

  • Android 模拟器,运行操作系统 Android 5 (对应 API level 21 ),或更高版本(未测试,可能有兼容性问题)。
  • 物理设备,操作系统要求同上(未正式测试,可能有兼容性问题)。

PC/Linux

  当前仅用于可移植性测试。要点:

  • 当前仅测试 x86-64 (提供 FreeImage 二进制文件)
  • 可用被支持版本的 G++ 或 Clang++
  • 因为不提供 freetype2 的二进制库,需要保证 freetype2 库文件可用,可以通过 pkg-source 找到或位于 /usr/lib 路径中
  • 使用 XCB 作为系统库,但功能不全

  Windows 10 可使用 WSL(Windows Subsystem for Linux)

  注意 Windows 10 秋季创意者更新(版本 1709 )的 WSL 存在已知可导致并行构建失败的缺陷 ,以及可能有的其它问题 ,本项目不针对这些问题单独提供变通。

其它构建环境

  集成开发环境是可选的,参见构建

  关于文档构建需要的依赖,参见构建文档

  关于执行工具脚本需要的环境,参见脚本

获取源代码

  建议通过 MercurialGit 获取仓库中的最新代码。

注意 构建过程可依赖源代码中不直接提供的二进制文件。详见构建中的说明。

通过 Mercurial

  安装 Mercurial ,使用命令行

hg clone https://hg.osdn.net/view/yslib/YSLib YSLib

  得到源代码的本地副本。

  若已有本地副本,可使用

hg pull -u

  同步更新到最新版本。

  或者安装 GUI 客户端(如 TortoiseHg )进行以上操作。具体使用参考软件附带的手册。

通过 Git

  安装 Git ,使用以下命令行之一得到源代码的本地副本:

git clone https://github.com/FrankHB/YSLib.git YSLib
git clone https://gitee.com/FrankHB/YSLib.git YSLib

  若已有本地副本,可使用

git pull

  同步更新到最新版本。

  或者安装 GUI 客户端(如 TortoiseGit )进行以上操作。具体使用参考软件附带的手册。

注意 Git 版本库是 Mercurial 版本库的镜像。不同版本库可具有不同的传输性能;使用合适的镜像可能大幅改善同步的体验。上述版本库都是官方维护的,大多数时候不需要担心同步时效性,但镜像版本库的更新仍可能比主版本库稍晚数分钟到数小时。

下载已发布版本

  已经发布的版本列出了可以直接下载的测试发布版本。

  可以在以下归档中获得特定发布版本的源代码和部分二进制文件。

  注意这些代码通常比版本库中的旧。在 Beta 阶段前也不表示更稳定。

文件权限

已知缺陷 Windows 上的 Mercurial 不跟踪可执行位。

  若在其它环境中因此无法执行脚本,在版本库根目录下执行以下 shell 命令变通:

find . -type f -name "*.sh" -exec chmod +x {} \;

  因为约定总是使用 *.sh 作为可执行的脚本的扩展名,不需要考虑其它文件。

历史资源

  因为代码托管站点不再提供服务,以下资源已过时:

  • 曾经可用,当前服务已经停止:
    • Google Code: hg clone https://yslib.googlecode.com/hg/ YSLib
    • GitCafe: git clone https://gitcafe.com/FrankHB/YSLib.git YSLib
  • 已经无法同步:
    • Bitbucket: hg clone https://bitbucket.org/FrankHB/yslib YSLib
  • 被取代:
    • OSChina: git clone http://git.oschina.net/FrankHB/yslib.git YSLib(使用 Gitee 代替)

准备

外部依赖项构建

  这个步骤是可选的。若已获取二进制的库文件,可以跳过。

  YSLib 发布的文件和对应使用的外部依赖项(包括二进制的库文件)可以从归档仓库下载

  进入 3rdparty 对应库的目录查看 Readme 文件说明。按此说明进行构建,一般方法为:

  • 自行获取指定的公开发行的源代码
  • 复制 3rdparty 库的目录中的文件到源代码,替换源代码中的原始文件
  • 运行指定的构建脚本命令行

库和示例程序构建

  以下主要描述构建项目的默认目标:库和示例程序 YSTest 。对特定的构建方式,支持附加的其它目标。

注意 除了 SHBuild 外,当前没有实现子项目间的依赖管理。由于非正式版不保证二进制兼容性,直接增量构建可能在更新代码后失败。此时可手动清理构建生成的中间目录即 build

  另见构建文档

使用构建工具和脚本

  这是宿主平台的建议构建方式,支持所有的构建目标。

  基本使用详见 SHBuildSysroot

  b600 起用于发布和测试的二进制文件使用 SHBuild 构建。

  当前版本的构建使用以下方式:

  • 调用 Tools/install-sysroot.sh 构建 Sysroot 。
    • 确保环境变量 SHBuild_UseDebugSHBuild_UseRelease 的值非空。
    • 确保环境变量 SHBuild_NoStaticSHBuild_No3rdSHBuild_NoDev 的值为空。
  • 在 Sysroot 部署完成后,设置环境变量 PATH 包含 Sysroot 布局下的二进制目录
    • 对配置名 debug-staticdebugrelease-staticrelease ,以配置名作为第一参数分别调用 YSTest/SHBuild-YSTest.sh 构建示例项目 YSTest 的各个配置。
    • 类似地,调用 YDE/install-all.sh 构建 YDE 中所有包的各个配置。

Microsoft Visual Studio

  源代码包含 Microsoft Visual Studio 解决方案 .sln 文件。可以直接生成解决方案。注意若修改被依赖项目代码后直接生成,依赖项目也不会重新编译,需要手动清除或重新编译。

注释 早期支持可追溯到 Microsoft Visual Studio 2012 。当前仅对提交时的正式发布的 Microsoft Visual Studio 最新版本和前一个正式版本提供支持。这里的支持仅包括集成开发环境可打开其中的文件,不保证其中的构建工具可用。

  当前包含了平台中立项目(便于源代码管理,不实际生成)、DS 项目 、MinGW32 项目和 Android 项目,但仅支持生成使用 make 的平台( DS 和 Android ,需要对应的工具链支持)。

  因为正确支持的语言特性不足或存在缺陷,Visual Studio 使用的工具链不保证正常生成其中的 Win32 项目,仅有以下不依赖 YSLib 主要输出目标的项目被支持:

  • Tools/CreationTimeManager
  • Tools/PredefinedMacroDetector

  使用 Microsoft Visual Studio 打开文件,需特定的环境变量以保证使用 makefile 的项目可构建。以下(使用兼容 shell 语言的)路径仅为示例:

  DS 项目:

DEVKITPRO=/opt/devkitpro

  Android 项目:

ANDROID_SDK=/d/Android/sdk
PATH=/d/clang-android/bin:/c/Program\ Files/Java/jdk1.8.0_211/bin:$PATH

注意 Visual Studio 项目使用的 makefile 命令行依赖 bash 以便及时检测到错误停止生成,可参见先决条件配置宿主环境

  支持生成的 Visual Studio 项目包含依赖性,不需要另行设置;也支持清理命令。

Code::Blocks

  从 b217 (2011-06-13) 起支持。

  源代码包含 Code::Blocks 工作空间 .workspace 文件,建议使用 Code::Blocks 12.11 或以上版本打开。

注意 打开工作空间后直接生成,其中的项目不都能成功构建,因为并非所有项目都保证能生成。项目名没有平台后缀的不用于构建,仅作为项目原型便于开发。

  当前包含了平台中立项目(项目名不含下划线,便于源代码管理,不实际生成)、DS 项目和 MinGW32 项目,支持构建 DS 项目和 MinGW32 项目,每个分别包含 debug 和 release 配置。除此之外,MinGW32 项目还支持 debug_DLL 配置和 release_DLL 配置,用于生成动态链接库或使用动态链接库生成可执行文件。

  对于 DS 可以分别编译其它各个项目,但不能运行。可以在工具菜单添加 DeSmuME 命令行运行。

  b599 (2015-05-21) 起支持通过脚本 GenerateProjects.sh 生成 .cbp 文件。

  和 Visual Studio 不同,生成 .cbp 的项目文件的内容不保证随提交版本保持最新。这些文件以后仅保证在发布版本更新。

  b600 前用于发布和测试的二进制文件使用此方式构建。

  因为编译时使用的文件路径和可能存在动态确定的个别选项不同等原因,构建的二进制文件和通过 SHBuild 和 Sysroot 环境构建的输出不完全等价,但受支持的功能相同。

make 命令行

  和 Visual Studio 类似,Code::Blocks 项目文件仅用于查看项目文件,当前不作为首要的构建途径被支持。

  虽然 Code::Blocks 具有模块化设计,但仍有一些欠缺可配置性及文档的逻辑被集成在核心(使用 C++ 实现,且不容易改动)而造成问题,如:

  • 头文件搜索使用字符串匹配。
    • 这直接导致在编辑器中无法定位宏名形式的 #include 的路径。
    • 这种形式实际被 YFramework 及 FreeType 使用。
  • 在运行时环境插入 PATH 环境变量的前缀,不保证和构建时相同也不可由用户指定去除。
    • 前缀包括 . 、链接器和调试器的可执行文件所在的路径。
    • 因为是前缀,当前路径及链接器和调试器可执行文件的路径直接污染环境变量,无法通过其它设置去除。
    • 当这些路径具有和构建时不同版本的动态库时,使用 Code::Blocks 运行程序会使用错误的动态库。
    • 因此,使用 Code::Blocks 运行程序时,为了使用正确的动态库,不同的版本的动态库不能和工具链共存。

  由于这些自身的局限性,Code::Blocks 不适合作为现代的集成开发环境,没有在现状以外其它的改进支持的计划。

  使用 make 构建适用于使用 Makefile 的平台,当前包括 DS 和 Android 。

  设 $Configuration 是生成配置名称,则

  在各个项目目录下运行

make -r BUILD=$Configuration

  即可生成。

  在各个项目目录下运行

make -r BUILD=$Configuration rebuild

  即可重新生成。

  在各个项目目录下运行

make -r BUILD=$Configuration clean

  即可清除生成。

注意 因为不同子项目的 makefile 之间不追溯依赖关系,直接运行 makefie 需明确依赖顺序,参见项目依赖性

  和其它构建方式类似,YFramework 依赖于 YBase 。对 DS 项目,注意附加的项目依赖性以确定生成顺序:

  • YSTest/DS_ARM9 依赖 YFramework 。
  • YSTest 依赖 YSTest/DS_ARM7 和 YSTest/DS_ARM9 。

准备

  • 获取源代码
  • 安装带有图形界面前端的 Doxygen
  • 安装 Graphviz/Dot 工具(见注意事项)

构建

  使用 Doxywizard(Doxygen GUI) 打开目录下的 Doxygen 文件,选择 Run 选项卡,点击 Run doxygen 按钮等待完成即可。

  构建文档的内容主要来自源文件中的注释。

  默认的输出路径在 doc/html 。打开其中的 index.html 进入主页。

注意事项

  • Dot 工具的路径是被硬编码的。
    • 如果不确定安装的位置,使用前清空 DOT_PATH ,并设置 PATH 环境变量使二进制文件能被找到。可以直接编辑 Doxyfile 或者在 Expert 选项卡的 Dot 项中设置 DOT_PATH
  • 当前配置使用 DOT_IMAGE_FORMATsvg 而不是默认的 png ,生成的文件不支持 Intenet Explorer 9 以前版本的浏览器。

概述

  这是单列的测试顶级子项目。

  预期覆盖整个项目中的各个,但目前仅测试 YBase 。框架部分的主要测试由示例程序 YReader 完成。

准备

依赖

  当前测试脚本 test.sh 依赖工具脚本 ,并使用相同的解释环境。

运行

  测试脚本 test.sh 构建测试程序并运行。

  当前测试程序只包括 YBase::YStandardEx 的相关内容。

  以标准输出显示测试项的数量和每个测试项的结果。通过为 PASS. ,失败为 FAIL.

配置

  变量 TestDir 表示当前测试使用的目录,默认值即为 test 目录。

  和工具脚本类似,变量 SHBuild_ToolDir 指定工具脚本目录。测试脚本据此包含构建配置所需的工具脚本。因为默认定位到此路径,不需要依赖 Sysroot 安装脚本。

  在包含工具脚本前,测试脚本使用(当前被直接硬编码在脚本中)以下配置:

CXXFLAGS_OPT_UseAssert=true
SHBuild_Debug=debug
SHBuild_NoAdjustSubsystem=true

  脚本不会调用 SHBuild 。脚本直接接受的参数附加为使用 $CXX 构建时命令行选项。

  和构建 YBase 和 YFramework 库的工具脚本类似,测试脚本支持预编译头文件。但因为默认直接使用 TestDir 作为输出路径,修改配置后可能需要手动清理 gch 文件以免预编译头文件失效。

准备

平台环境

  以下适用包含 YSLib 程序和其它程序的构建和运行环境。并非每一个程序都需要区分特定平台,一些程序的行为是平台中立的。

YSLib 程序

注意 示例程序最大化地使用了 YSLib 的平台模拟特性,支持特定的目标平台在运行时作为宿主平台对另一平台的平台模拟,即运行的原生示例程序和被模拟平台的原生程序保持基本相同的功能效果;但并不保证所有其它 YSLib 程序同样支持。

  被正式支持的平台模拟列表如下:

  • PC 模拟 DS

  被非正式支持的平台模拟列表如下:

  • Android 模拟 DS

硬件资源要求

  占用的硬件资源不确定,取决于平台配置以及具体应用程序。

注意 特定平台可能有额外限制,详见各个平台运行时环境的说明。

环境变量

  环境变量是程序的实现环境(典型地,如操作系统)的运行环境提供的一种机制。程序在运行时可能访问这些环境变量,以提供不同的行为,或在程序之间、程序与外部环境之间传递信息。

注释 非宿主实现的平台可能不支持环境变量。本项目的程序在这些平台上不使用环境变量。

  没有依赖这些具体应用或其提供的实现环境时,如有可能,应当避免使用依赖这些具体的使用方式。

  一些情形程序通过判断特定的环境变量配置功能。特别地,设置特定的环境变量为非空值可能启用功能。

  一般地,只要环境变量被支持,一个环境变量总是能被设置为非空的字符串(至少本项目支持的平台能假定如此)。但是,访问环境变量的行为仍可能依赖具体平台的不同细节,而影响可移植性:

  • 环境变量的名称可能是大小写敏感或不敏感的:名称仅有大小写不同的环境变量可能被视为不同或同一个。
    • 例如,Windows(指 Win32 环境,下同)的环境变量名对大小写不敏感。其它大部分实现环境对环境变量的大小写敏感。
    • 因此,作为公开接口提供的环境变量应当总是具有固定的、大小写敏感的拼写,且避免出现仅有不同大小写的可能因同名冲突的不同环境变量名。
  • 环境变量可被设置为空值。具有空值的环境变量可能会或不会被视为没有被设置(或被取消设置)的环境变量。
    • 例如,设置 Windows 的某个环境变量为空值,效果即删除环境变量。其它大部分实现环境中,设置环境变量为空值,环境变量不被删除。
    • 一些环境可能指定不同的行为,如较新版本的 MSYS2 支持配置 MSYS=noemptyenvvalues
    • 特定的实现被设置的环境变量具有空值,如 PowerShell
    • 为避免这些不同实现的复杂性,不直接判断环境变量被设置,而访问环境变量并检查是否为空值。未设置的环境变量被视为具有空值。
  • 环境变量名可能具有不同的限制。
    • 仅假定 ISO C 基本字符集的标识符(大小写拉丁字母、数字和下划线 _ )可被用于环境变量名。
  • 具体环境变量的访问方式可能和具体宿主平台中访问环境变量的具体程序相关,如:
  • 以下周知的(well-known) 环境变量因在各个被本项目支持的宿主实现平台普遍可用,而可被直接使用,但使用方式并非完全相同:
    • 原理 以下约定简化可移植的使用。
    • PATH 表示程序中执行(外部程序提供的)命令使用的搜索路径。
      • 对 Windows ,另见 path 命令(其中使用 %PATH% 引用环境变量 PATH )。
      • 另见 POSIX 的规定。
      • PATH 的值可支持多个路径名称,其中分隔符不一定相同,如 Windows 使用 ; ,而 POSIX 使用 :
      • Windows 可能直接提供 Path 环境变量,而在 cmd.exe 中仍然被识别为 PATH 。此时,使用 PATH
    • 环境变量 COMSPECComSpec (en-US) 指定命令解释器。
      • Windows 可能直接提供 ComSpec 环境变量,而在 cmd.exe 中仍然被识别为 COMSPEC 。此时,使用 COMSPEC

  除非需要依赖具体实现环境或另行指定,本项目中提供的环境变量应当避免上述依赖差异。

应用程序变量

  应用程序继承环境,可能根据环境变量指定不同的行为。

  除非另行指定,以下环境变量若被本项目提供的程序支持,则具有一致的含义:

  • NO_COLOR :若这个环境变量被设置为非空值,宿主实现环境忽略终端支持的彩色和其它视觉效果格式(如下划线)特性。
    • 注释 NO_COLOR 的检查符合 no-color.org 的约定。
    • 注释 一些命令解释环境的实现直接支持 NO_COLOR ,如 PowerShell

  除标准库和系统配置外,YFramework 可使用特定的环境变量改变运行时行为,参见 YSLib 项目文档 doc/YFramework.txt

  除非另行指定,程序可假定以上环境变量不变,即仅在程序初始化时检查一次。

  提供给程序的环境变量可能被命令行工具的选项覆盖,如 SHBuild 构建模式中指定变量定义的选项 。

多任务环境

  以下执行其它程序的约定在以下情形适用:

  • 通过用户界面运行 YSLib 程序。
  • 以上方式间接运行的其它程序。
    • 注释 例如,在程序中调用脚本,或者程序使用基于 std::system 等本机 API 实现的互操作。YSLib 安装程序可能间接调用其它程序。

  除非另行指定,通过 YSLib 安装程序调用的程序或者依赖 YSLib 的命令行程序假定以下的外部环境和交互方式:

  • 宿主环境:执行其它程序的宿主(host) 未指定。
    • 被视为默认不支持外部程序。
    • 注释 程序使用 std::system 等实现定义的行为可能导致非预期但可被定义的结果。
  • 宿主环境:
    • 以被执行的当前程序或脚本解释器作为执行其它程序的宿主。
      • 注释 一般由操作系统在用户空间规范提供约定,且系统的安装提供完整的环境。例如,Windows 提供 cmd.exe 作为命令行解释器,而 UNIX 系统提供 shell 程序解释 shell 语言命令和脚本。
    • 以宿主定义的规则解析命令行参数。
      • Windows :命令行由程序实现指定。对未指定宿主程序的情形,视为和 cmd.exe 一致。
        • 注释 Windows 可通过 shell 程序传递命令,这不是默认假定的环境。
        • Windows :对引号的解析,兼容未被文档指定的新规则,即仅在引号有效地块中 "" 转义未 "
    • 使用环境变量的约定。
    • 环境变量 SHELL 被设置,则:
      • 假定被正确设置至支持的 shell 程序的路径。
      • 假定 POSIX.1-2004 环境的命令可用。
      • 注释 在 shell 环境内部通常不需要检查。可能使用其它 POSIX 兼容 shell 兼容 POSIX shell 或 GNU bash 的运行环境。
    • 非 Windows 宿主平台:使用 POSIX shell 或 GNU bash 作为执行环境的 shell 。
    • Windows :使用随系统分发的 cmd.exe 作为命令行解释器。
    • 不要求支持嵌套不同的命令行环境最终重入到非 POSIX 兼容 shell 的情形。
      • 注释SHELL 被设置为非空值,即视当前 shell 是 POSIX 兼容 shell 。
      • 原理 被继承的环境变量检查可能嵌套的命令行环境不可靠。可靠检查通常依赖进程间通信确定当前的程序,为减少复杂性,不被要求。
    • 以上环境变量在相关规范定义明确允许的情形以外,可被程序假定不被修改。
    • 运行其它程序时,不检查或隔离外部环境。
      • 警告 尽管 YSLib 项目提供的程序的大部分实现会避免明显的安全性问题,任何可能执行第三方程序仍然可能有潜在的安全性风险。因此,在完全审计环境并确保外部环境可信之前,不建议使用特权运行包括 YSLib 安装脚本内的这些程序
        • 注释 利用方式如拦截在程序中已知会调用的外部命令(包括命令解释器),以及使用环境变量 IFS 等特定外部命令的机制注入。
    • 使用包含带有 Windows 环境变量展开语法(以 % 起始和结尾)的参数未指定。
      • 原理 Windows 命令行解释器脚本可使用 %% 转义,但这对交互式命令行环境的调用不适用。因此,包含这种形式的参数可因是否使用 Windows 命令行解释器表现不同的行为。
      • 注释 NPLA1 脚本可调用依赖 Windows 命令行解释器执行的命令。
  • 原理 POSIX shell 和 cmd.exe 是平台默认分发的应用,通过构建平台或宿主平台直接区分是可行的。
    • 同时,一些语法兼容,使有些命令不需要区分两者。即便在一些基本语法上(如重定向错误流)不完全兼容,默认仍然可以通过特定的环境区分两者。
    • 环境变量 SHELL 在不同实现的支持情形不同,不适合提供统一的假定:
    • 环境变量 COMSPECComSpec 缺乏标准化,且会被继承。
      • 这通常仅用于判断在 Windows 同时使用兼容 POSIX shell 环境。
      • Windows 可能使用嵌套的兼容 POSIX 的 shell 而同时继承 COMSPEC 且被设置 SHELL 的情形。
      • 为避免复杂性,一般避免单独检查这些环境变量。
      • C 运行时实现使用 std::system::_wsystem 实际可能总是依赖 COMSPECcmd.exe ,因此成功互操作时已经隐含依赖了这项运行时配置。
  • 注释 PowerShellPowerShell Corepwsh 默认不被支持。
    • 原理 PowerShell 和上述命令行解释器支持的语法都不兼容,且不直接提供简便可靠的可移植的方式检查差异。
    • 注释 可靠检查 PowerShell 通常依赖进程间通信

  关于 YSLib 版本库内的及安装 YSLib 部署的脚本的运行环境,另见脚本运行的相关说明。

文件系统

  为最大化可移植性,原则上 YSLib 的程序逻辑不依赖自身组件具有显式编码的具体文件路径,因此默认不要求在具体系统约定的周知(well-known) 的文件系统位置(如 Windows DLL 搜索路径中的目录和 FHS 定义的目录)部署。但具体平台配置可以自行限定目录布局作为默认运行时环境,如 Sysroot 。其中外部文件可有默认路径,参见以下相关章节。

  YSLib 中的库使用的文件系统路径具有以下约束:

  • 不依赖兼容 POSIX 的文件系统和路径表示。
    • 特别地,不依赖文件系统是否具有文件名的大小写敏感性。
    • 推论 为保证可移植性,文件系统路径大小写敏感,需要显式区分而加以明确。
    • 这样仍可兼容 FAT 等大小写不敏感的文件系统。
  • 要求支持符合 YSLib 项目规范的开发(参见项目的 doc/ProjectRules.txt )。

  一般地,YBase 不要求文件系统访问;使用 YFramework 框架的程序,由 YFramework 中的框架初始化等逻辑引入文件系统访问。

二进制依赖项

  按以上文件路径约定,构建得到的目标程序没有预设特定的位置限制,可按需转移。

  运行程序前,应确保程序能找到二进制依赖项:

  • 确保先决条件中的依赖项可以在 PATH 环境变量的目录被找到,或者复制到程序所在的目录。
  • 对于静态链接 YFramework 和 YBase 程序,不需要其它二进制程序文件的部署。
  • 使用 DLL 的程序可能依赖 YFramework.dll 和 YBase.dll ,或者 debug 配置的 YFrameworkd.dll 和 YBased.dll 。这些库已经在 sysroot 的 bin 目录下安装,因此可以直接把 bin 目录加入 PATH 环境变量,而不必复制或移动库文件。

说明 在任意被支持的平台上,YSLib 避免使用导致在特定平台上被特殊处理且无法可靠避免这种行为而确保兼容性一致的外部依赖项名称(若因为外部环境更新导致此类问题,则需在 YSLib 的新版本适当修改后支持)。例如,支持 API Set Schema 的 Windows 版本加载 DLL 时,LdrLoadDll 解析以 api-EXT- 起始的文件名进行重定位 ,因此设计时确保外部依赖项对应的文件名不以这些前缀起始。

外部文件

  外部文件包括:

  • 在文件系统中部署的外部二进制依赖项。
  • 配置文件:可被修改以改变程序的运行时行为。
  • 其它数据文件。

  数据文件提供程序运行时所需的必要或可选的信息。配置文件可能作为数据文件使用。

  外部文件的路径应符合前述关于文件路径的约定。

文件格式

  除非另行指定,使用以下文件格式:

  • 配置文件是内容为 NPL 配置的 UTF-8 文本文件,具有 UTF-8 BOM
  • 其它数据文件为二进制文件

文件系统布局

  除配置文件可能指定默认的具体位置(详见下文)外,YFramework 程序的不需依赖特别的文件系统布局。

  除非另行指定(暂无),YFramework 不创建文件系统目录。

  Sysroot 提供类 UNIX 系统使用的布局作为可选项。

文件访问约定

  除非另行指定,YFramework 对外部文件的访问适用本节约定。

注意 除非另行指定,程序访问配置文件的时机是未指定的。

基本要求

  应确保外部文件满足以下条件,否则运行时出错,或在另行指定(暂无)的特定情形下不保证行为符合预期:

  • 具有适当的(可读和可写)权限以允许程序访问其内容及元数据,或在必要时进行创建。
  • 需要写入的文件所在的位置应该具有足够的空间。

文件映射和内存映像

  文件读写使用 YCLib::MemoryMapping ,优先使用内存文件映射。在文件无法访问时,部分文件可能使用内存映像代替。

注意 除非使用内存映像,应确保外部文件的内容可读;否则,可在之后引发无法恢复的错误;参见当前实现使用的 YCLib::MemoryMapping 的注意事项

共享文件

  宿主平台使用协同锁(advisory lock) 对并发文件内容访问提供有限的共享保护,避免基于 YFramework 程序并发访问外部文件时的竞争。不使用强制锁(mandatory lock) 以利于提升性能。

注意 除检查文件创建外,不提供相关文件系统元数据并发访问保护。

注意 因为锁定非强制,宿主不保证其它机制访问文件的程序(如非 YFramework 程序)在这里产生冲突,有可能破坏文件内容。用户应避免对相关文件造成此类访问的破坏性编辑。

注意 因为访问顺序未指定,不同 YFramework 实例可能修改相同的文件(如配置文件)而导致外部可见的影响。

  未来可能添加完整的对共享文件内容修改的传递和检查的机制。

配置生成和输入输出

  配置文件可能提供默认内容,在外部文件不存在或读取失败时尝试创建。默认内容一般由框架直接提供。

  • 自动生成并保存外部文件时需确保程序可在指定目录下创建文件。
    • 具体目录视具体文件而定。
    • 可在运行前部署正确的配置文件以避免自动生成。
  • 若无法满足,生成保存在内存中的临时配置映像之后的修改不会保存到外部文件。
    • 注意 某些版本的 Windows (如 Windows Vista 以后的版本)在特定的目录(如 %PROGRAMFILES% )中创建文件默认需要管理员权限。

公共配置文件

  文件 yconf.txt 为 YFramework 程序的公共配置文件,在框架初始化时,先于其它配置文件之前处理。若不存在,提供默认内容。其位置是预先指定的,和平台相关。详见下一章对框架初始化的说明。

  为避免 YFramework 程序在运行时出错或行为不符合预期,注意确保满足前述访问约定的要求。若无法在程序映像文件所在目录(详见以下关于框架初始化的说明)创建文件,配置不能保存到外部文件。

  在 Win32 上一个正确的公共配置文件的内容可能是这样的:

YFramework
(
	DataDirectory "C:\Data\\"
)
(
	FontDirectory "C:\Windows\Font\\"
)
(
	FontFile "C:\Windows\Font\FZYTK.TTF"
)

  这里指定的路径分别为数据目录路径、字体文件目录路径和字体文件路径。

数据目录

  用于存放 YFramework 程序必要的数据以及运行时的配置。

  通过 yconf.txt 中项 DataDirectory 的值指定数据目录的路径。

  自动生成的配置中数据目录的默认路径如下:

  • DS :/Data 目录
  • Win32 :同默认生成配置文件所在的目录
  • Android :SD 卡目录(自动检测同上)下的 Data 目录
  • 其它平台:当前工作目录

  当前可用数据文件位于 YSLib 存储库的 Data 目录下,可自行复制到数据目录。

CHRLib 的 GBK 转换例程初始化

  对使用 CHRLib 提供的 GBK 编码转换的程序,需保证数据目录下存在数据文件 cp113.bin

  对 Win32 平台,CHRLib 初始化 GBK 转换例程读取数据文件失败时,首先尝试使用 NLS 替代:

  • 通过读取注册表取得 NLS 文件路径,默认为系统目录下的 C_936.NLS(文件名大小写可能不同,这不影响加载)。
  • 注意 并非所有 Windows 安装带有指定的 NLS 文件。
    • 具体支持的 NLS 文件名由注册表读取。这些文件在系统目录(%WINDIR%\System32%WINDIR%\SysWOW64)存在。这些目录中是否存在注册表中指定文件的 NLS 文件首先和系统支持的语言相关。
    • 新近版本的 64 位 Windows 10 可能在 %WINDIR%\System32\C_936.NLS 但不存在 %WINDIR%\SysWOW64\C_936.NLS 。由于 64 位 Windows 默认对系统目录重定向,导致 32 位系统中直接读取系统目录找不到 NLS 文件。在 build 937 前,YFramework 初始化加载 NLS 文件时不特别处理这种情况,因此 NLS 不可用。
  • 若 Win32 NLS 初始化仍然失败,则 CHRLib 初始化 GBK 转换例程失败。

  对其它平台,数据文件读取失败则 CHRLib 初始化 GBK 转换例程失败。

  初始化失败后,使用 CHRLib 转换 GBK 编码的程序在调用转换例程时抛出异常,可能导致程序非正常退出。

MIME 数据

  数据目录下的配置文件 MIMEExtMap.txt 存储 MIME 数据。若不存在,提供默认内容。

字体目录和文件路径

  只要其中之一有效即可(若没有成功指定字体文件路径,则默认字体文件路径不确定)。

  自动生成的配置中字体文件的默认路径如下:

  • DS :/Font/FZYTK.ttf
  • Win32 :系统字体目录下的 SimSun.ttc
  • Android :/system/fonts/DroidSansFallback.ttf
  • Linux :./SimSun.ttc

  自动生成的配置中字体目录的默认路径如下:

  • DS :/Font 目录
  • Android :/system/fonts 目录
  • 其它平台:同数据目录

特定平台运行环境

DS

  以下说明中,$NDSEmulator 为模拟器 DeSmuME 可执行文件的绝对路径,$TargetPath 为 ROM 映像文件( .nds 文件)的路径,$FAT_Path为 FAT 目录路径(见以下说明)。

  之前经测试的最小版本为 0.9.6 。建议使用最新版本。

  运行时,需在运行模拟器的宿主机中准备一个目录作为映射到设备中的虚拟 FAT 目录。宿主的目录在运行中不会被 DeSmuME 修改;宿主目录的文件系统不要求为 FAT 。运行时会加载虚拟目录的内容。

注意 官方版的 DeSmuME 不支持包含非 ASCII 字符的宿主路径而忽略这些文件。部分修改版可能没有这个限制。

注意 因为 DS 模拟器和烧录卡需要加载 ROM 映像到 RAM 后才能运行,映像大小会影响可用运行内存,使用 debug 配置构建的 YSTest 示例版本不一定在 DS 上支持所有功能。相关支持限制及变更详见 YSLib 项目文档 doc/Test.txt

  可通过以下方式之一指定 FAT 目录。

Slot 1 映射

  Slot 1 (即 NDS ROM 卡槽,硬件介质可以是 NDS 卡带,或烧录卡和存储卡的组合)的模拟从 DeSmuME 0.9.8 版本起支持。

  运行命令行示例:

"$NDSEmulator" "$TargetPath" --preload-rom --slot1-fat-dir="$FAT_Path"

  其中 --preload-rom 对 0.9.10 和 0.9.11 版本是必须的(以支持自动 DLDI(en-US) 补丁),否则加载失败。这个选项可以在模拟器(Windows 版本)的 GUI 菜单中 ConfigROM Loading 中选择。最新版本的源码会对自制 ROM 自动启用 ROM 预先加载的选项而不用另行配置,可以省略这个选项。

Slot 2 映射

  Slot 1 (即 GBA ROM 卡槽,硬件介质可以是 GBA 卡带或其它扩展)的模拟需要配置为使用 GBA Movie Player (Compact Flash)

  运行命令行示例:

"$NDSEmulator" "$TargetPath" --cflash-path="$FAT_Path"

  对 0.9.10 和 0.9.11 版本,需同 Slot 1 映射添加 --preload-rom 或使用菜单设置加载选项,否则同样无法加载(尽管源码中表示 --preload-rom 是适用 Slot 1 而不是 Slot 2 的选项)。

  这个选项也可以通过 GUI 配置。在 0.9.9 版本前,使用菜单 EmulationGBA Slot 。自 0.9.9 版本起,使用菜单 ConfigSlot 2 (GBA Slot)

注意 DeSmuME svn4030 及之后的一些版本(至少包括 0.9.8 )会在载入 MPCF 映像时错误地忽略 --cflash-path 选项,以 仿真(Emulation) → GBA 插槽(GBA Slot) 菜单中的设置或对应配置文件 desmume.ini 中指定正确的路径。

  在 desmume.ini 中有效的最小配置:

[GBAslot]
type=1
[GBAslot.CFlash]
fileMode=0
path=H:\NDS\efsroot\

  这里 H:\NDS\efsroot$FAT_Path 的一个示例。

注意 自 Slot1 0.9.10 起,当 Slot 1 设置为 R4 时会覆盖 Slot 2 设置加载 R4 路径( Dev+ 版本显示自动 DLDI 补丁在 Slot 1 设置为 R4 从 GBA Movie Player (Compact Flash) 改为 R4(DS) - Revolution for DS ),因此需确保Slot 1 设置不为 R4 ,或正确设置了 Slot 1 R4 的路径(而改用 Slot 1 映射)。

MinGW32

  自 build 431 起,YFramework 使用的 FreeImage 修改版集成 libjepg-turbo ,需要 CPU 支持 SSE2 指令集扩展。

  除 2005 年前生产的硬件外绝大多数环境已经满足这个条件。当前几乎所有兼容 IA-32 的市售 CPU 都包含 SSE2 支持。特别地,支持 x86_64 指令集的 CPU 要求支持 SSE2 ,在使用 x86_64 的 64 位 Windows 上运行 32 位 Win32 程序(通过 WoW64 )也支持 SSE2 。

Android

  可使用 adb 命令安装 APK 包,如:

"$ANDROID_SDK/platform-tools/adb" -e install -r "$1"

  此处 -e 指定 TCP/IP 设备(通常是模拟器),若使用 USB 设备(通常是物理机器)应移除或使用较新的 adb-d 选项代替;"$1" 指定 APK 文件路径。

  之后,在 Android 的 GUI 环境下直接运行安装的程序。

  若因签名变更等原因无法覆盖安装或更新,需要先卸载后再安装。

注意 Android 安装 .apk 包时需要占用额外存储资源。为避免内置存储空间不足导致不直接表现安装失败(需要 logcat 查看)但直至运行时找不到二进制库而导致启动失败,应保留足够的空间,特别是对占用较大的 debug 配置构建的映像。保留空间的大小一般大于 .apk 作为 .zip 解压缩占用的空间。另见此处报告的未确认的类似问题

框架初始化

  框架初始化服务于整个框架。

  初始化的重要策略之一是在程序启动时减少不必要的初始化,以允许实现以下目的:

  • 减少可能的外部依赖(如不需要使用文字的程序就不初始化字体缓存,也不需要依赖外部字体文件和字体配置等)。
  • 减少可能的运行时程序资源占用。
  • 保留静态链接时优化去除没有调用的代码以减少二进制可执行文件大小的可能性。

  按当前框架的设计,框架初始化在 DS 平台在程序运行初始阶段完成。其它平台可延迟初始化,按需调用。

  并非所有 YFramework 中的 API 都要求框架初始化。以下功能隐含自动进行的框架初始化:

  • 使用默认字体缓存。
  • 使用 MIME 数据时。

  框架初始化加载配置文件,其中配置文件路径的确定方式参见以下节的说明。成功公共加载配置文件后,框架初始化检查配置文件的内容,并访问框架公共配置。若检查失败,抛出异常。

  修改 YFramework 模块 Helper::Initialization 重新编译后,可修改默认设置改变初始化行为。以下行为可能会在未来改变

根路径

  根路径(root path) 是框架初始化时参考的基本路径,由如下方式确定:

  • Win32 和 Linux(除 Android ):程序映像所在的目录。
  • Android :SD 卡目录(自动按顺序检测 /sdcard/mnt/sdcard/storage/sdcard0 之一)。
  • 其它平台:第一次初始化时的当前工作目录。

  确定根路径在框架初始化或要求确定根路径时进行。若定位程序映像,同时会解析符号链接。若定位根路径失败,抛出异常。

  抛出的异常默认不被框架处理,可使程序退出。用户代码捕获特定异常可改变默认退出行为。

配置路径

  公共配置文件 yconf.txt 所在目录的路径前缀(结尾带有路径分隔符)称为框架的配置路径(configuration path) ,决定和平台相关的配置加载起始位置。配置路径和配置文件相对路径(对 yconf.txt ,即配置文件名)组合得到配置文件路径。

  配置路径的确定方式和平台相关。

  任意平台总能确定一个首选的配置路径。

  除首选的配置路径外,一些平台还支持一个或多个不同的后备(fallback) 配置路径。在读写特定的配置文件时,若根路径访问失败,但存在后备配置路径,依序使用这些路径重试直至成功或全部访问失败。这样的配置文件当前包括 yconf.txt 。具体应用可使用初始化 API 以类似的方式加载其它配置文件。

  以下是具有后备配置路径的平台中确定配置路径的顺序:

  • Linux(除 Android ):
    • 首先使用首选的配置路径。
    • 若环境变量 HOME 的值非空,则路径 $HOME/.YSLib/ 是后备配置路径。
  • Win32 :
    • 同 Linux 平台的顺序(对应使用 Win32 的环境变量和路径语法,即后备配置路径 %HOME%\.YSLib\ )。
    • 若环境变量 USERPROFILE 的值非空,则路径 %USERPROFILE%\.YSLib\ 是后备配置路径。

  后备配置路径中若子目录 .YSLib 不存在则被创建。若创建失败,配置路径访问失败。

  对不具有后备路径的平台,首选的配置路径总是根路径。否则,首选的配置路径由以下方式确定:

  • 程序映像所在的位置可推断出符合类似 FHS 的文件系统布局(称为局部 FHS 目录布局),则首选的配置路径为程序映像所在的目录的上一级目录的 var 子目录下的 YSLib 子目录。
    • 推断文件系统布局为以 POSIX 环境变量语法表示为 $PREFIX/$PREFIX/$BIN/$PREFIX/lib/$PREFIX/share/ 这些路径前缀都存在且可作为目录访问,其中 $PREFIX 是程序映像所在的目录的上级目录,而 $BIN 是程序映像所在的目录的名称(按 FHS 通常为 bin ,此处不检查)。
    • 这保证创建映像的可执行程序映像的目录中内容不被修改,以符合 FHS 。
  • 否则,首选的配置路径为根路径。
    • 确定配置路径要求确定根路径。

  对不具有后备路径的平台不检查文件系统布局,也不要求实现确保程序映像路径的操作,以简化实现。

注意 若后备配置路径的配置文件可访问,直接使用此配置文件保存配置,不再创建配置文件。若需要恢复在首选配置路径创建配置文件的行为,需确保后备配置路径的配置文件不可访问(例如,移除所有后备配置路径的这些配置文件)。

配置文件加载

  加载配置文件 yconf.txt 时,首先按上述的顺序确定各个配置路径,每确定一个路径时访问其中的配置文件。若全部失败(如找不到可读的文件),则尝试自动生成配置并创建配置文件。创建配置文件的位置和顺序同上述确定配置路径的顺序。若创建配置文件全部失败,则放弃创建配置文件,直接使用生成的配置。

  不存在配置文件时,配置不能通过 Helper::Initialization 的 API 持久化保存,尝试保存配置会失败。

其它初始化

  成功后,框架进一步处理公共配置文件以外的其它初始化;详见以上具体配置文件的相关章节。

Introduction

DeSmuME is a cross-platform, open source Nintendo DS/DS Lite/DSi/DSi LL(XL) emulator.

Use "dev" version to debug with GDB.

It is possible to debug with Visual Studio debugger with WinGDB.

Resources

See official website for details.

Note that due to the DLDI bug, homebrew programs are not supported on newest versions. Use 0.9.9 or a suitable custom build instead.

版本库结构和文件说明

  版本库依赖支持子目录的层次文件系统。以下只包括版本库根目录下的顶级子目录和少数特定的二级子目录及其中的部分文件的说明。

  • .git Git 目录(可通过 hg-git 同步)
  • .hg Mercurial 目录
  • 3rdparty 第三方依赖项
  • Data 最终用户环境中部署的数据目录
  • Tools 开发使用的(辅助)工具
    • Tools/SHBuild SHBuild 构建工具
    • Tools/Scripts 用于构建和建立 SHBuild 环境的工具脚本
  • YBase 顶级子项目 YBase
  • YFramework 顶级子项目 YFramework
  • YSTest 测试用示例项目目录(项目名为 YSTest ,以实际内容命名的项目名为 YReader )
    • YSTest/Android 示例项目 YReader 的 Android 平台项目(未完成正式支持)
    • YSTest/DS_ARM7 示例项目 YReader 的 ARM7 项目(仅用于 DS 平台,用于生成 ARM7 ELF 二进制映像)
    • YSTest/DS_ARM9 示例项目 YReader 的 ARM9 项目(包括 DS 平台 ARM9 部分,于生成 ARM9 ELF 二进制映像;源代码和其它平台共享)
    • YSTest/DS 示例项目 YReader 的 DS 平台可执行文件项目(仅用于 DS 平台,用于生成 NDS 文件)
    • YSTest/MinGW32 示例项目 YReader 的 MinGW32 平台可执行文件项目(仅用于 MinGW32 平台,用于生成 EXE 文件)
  • build 默认构建目录(可选构建时生成)
  • doc 开发文档
  • doc/vsd 架构示意 Visio 文档
  • test 测试代码目录
  • .gitignore Git 忽略文件
  • .hgeol Mercurial EOL 插件配置文件
  • .hgignore Mercurial 忽略文件
  • .hgtags Mercurial 标签文件
  • CC BY-SA 3.0 legalcode.txt 文档许可证: Creative Commons Legal Code Attribution-ShareAlike 3.0 Unported
  • Doxyfile Doxygen 配置文件
  • FTL.TXT 许可证: The FreeType Project LICENSE
  • gpl-2.0.txt 许可证: GNU GENERAL PUBLIC LICENSE Version 2
  • gpl-3.0.txt 许可证: GNU GENERAL PUBLIC LICENSE Version 3
  • LICENSE.txt 整体许可证
  • LICENSE_HISTORY.txt 整体许可证:历史版本
  • license-fi.txt 许可证: FreeImage Public License - Version 1.0
  • Readme.zh-CN.txt 自述文件
  • YSTest.sln Visual Studio 解决方案文件
  • YSTest.workspace Code::Blocks 工作空间文件

注释 以上布局中的顶级子目录可作为顶级子项目或作为项目模块在开文档中引用,其名称起始使用大写字母,以目录的名称原文作为模块名称。其它构建时生成以外的目录也可被引用,但不使用目录名称作为模块名称。

  顶级子项目或其中的平台子目录中,可包含以下目录:

  • include 可被安装部署的公开头文件
  • source 被构建的源文件

  以下目录会被默认支持的构建配置生成:

  • YDE/ 下以 . 起始的特定宿主平台为子目录名的构建目录是 stage 2 环境下的 YDE 项目的生成文件目录
  • build/ 下以特定的宿主平台为子目录名的默认构建目录,默认配置构建时会创建不同的平台目录,其中的子目录可包括:
    • .stage1 stage 1 环境 构建和生成目录
    • .test 测试项目构建和生成文件目录
    • . 起始的其它 Sysroot 构建目录,用于创建 stage 2 环境的 YSLib 库以及基于 stage 2 环境的 Sysroot 方式的 YSTest 的构建
    • 其它非 Sysroot 的 YSLib 和 YSTest 项目生成文件目录
  • sysroot 使用脚本构建的默认 Sysroot (含 stage 2 环境) 生成目录

文件系统布局约定

  版本库遵循一定的文件系统布局规则。这些规则在这里和一些开发文档中被描述。没有通过这些描述被明确的布局应被视为实现细节,其改变可不被文档描述。

  不论是否是实现细节,特定的路径都可被版本库的忽略文件路径涵盖。

注意 版本库中的文件系统布局和 Sysroot 约定的部署实例的布局相互独立,不适用此处的规则,且布局可能存在差异。

  虽然没有在项目初始指定,版本库中部分目录和文件结构及使用方式与现有的一些约定类似或兼容:

  • 部分文件系统布局和使用符合 cxx-pflR1
    • 顶级子目录 build 符合 build/ 指定的目的和作用。
    • 顶级子项目或其中的平台子目录中的子目录 includesource 对应符合 include/src/ 指定的目的和作用,其使用符合分离放置布局
    • 顶级子目录 test 符合 tests/ 指定的主要目的和作用,但不限于非单元测试。
    • 顶级子目录 Data 符合 data/ 指定的主要目的和作用,但强调最终用户环境,且不适用生成文档的源代码。
    • 顶级子目录 doc 符合 docs/ 指定的目的和作用。
    • 因为不使用版本管理的子模块机制,不使用 libs/extras/ ,符合指定的规则。
  • 部分文件系统布局和使用符合 Jonathan de Boyne Pollard 约定的 slashpackage 内部结构
    • 顶级子项目或其中的平台子目录中的子目录 source 符合指定的目的和作用。
    • 默认构建目录是名为 build 的一级子目录,可能不存在,符合指定的目的和作用。
    • 除构建目录外的文件预期不应被构建过程修改,默认与之兼容。
    • 不要求 source 链接到 build ,与之不兼容。
    • 构建 Sysroot 可能直接链接文件到输出目录,与之不兼容。
    • 仅要求部署过程正常时的部署目标目录的完整性,不要求更新是原子的或可事务性回滚,与之不兼容。

项目结构和特性

  项目内容以模块为单位组织。关于模块的概念及其表示形式,详见 YSLib 项目文档 doc/ProjectRules.txt 。因为特性众多,在此只列出较上层的基本结构,不总是精确到具体模块。

YBase :YFramework 基础库

  详见 doc/YBase.txt

  替代标准库的特性另见 StandardUsing(en-US)

  • YStandardEx : YFramework 标准扩展库
  • LibDefect :标准库修正
  • YTest :YFramework 基础测试库

YFramework : YFramework 框架库

  详见 doc/YFramework.txt

  • CHRLib :字符编码处理库
    • 编码标识
    • 目前支持 UTF-8/UCS-2/UCS-4/GBK ,可扩充
    • 默认编码 UTF-8
  • YCLib : YSLib 基础库
    • Platform :平台定义
    • 平台相关 API 封装
    • 平台相关硬件抽象
    • Win32 专用接口(略)
  • YSLib :YShell 库(主体,暂定)
    • Adaptor :适配器层
      • Configuration : 库配置
      • YReference :智能指针封装(基于 Loki 实现【已移除】/使用 ISO C++11 标准库)
      • 简单的内存调试设施
      • 附加容器(可选 Loki 的 yasli 容器,默认不启用)
      • Anti-Grain Geometry 2.4 移植(修复了一处在 2.5 仍然存在的 bug ),因在 DS 上效率太低所以默认不启用【已移除】
      • Font :字体管理(基于 FreeType 2 实现)
      • Image :平台中立的图像输入和输出(基于 FreeImage 实现)
    • Core : YSLib 核心
    • Service :YSLib 服务
    • UI : YSLib 用户界面
    • NPL
    • Helper :助手库:初始化、宿主支持等

YReader :示例

  • ShlExplorer :文件列表浏览
  • 控件测试:按钮点击、窗口拖曳、FPS (伪)显示等
  • Shell 切换
  • ShlReader :文本浏览
    • 读取并显示文本文件的内容
    • 随机跳转
    • 书签设置、读取和保存
  • HexBrowser:十六进制浏览

构建

  支持使用多种途径进行构建。支持多平台构建。另见 Sysroot 和以下的“轮子列表”章节。

  依赖以模块为单位组织。接口(如公开 API 头文件)不出现循环依赖。

  可通过修改代码以裁剪、定制库实现内部的模块构建(如为了减小编译后的二进制映像体积),但应注意在 YSLib 项目文档中列出的显式依赖规则。未来可能会支持构建时的定制配置工具。

ABI

  目前除 YBase::LibDefect 和具体实现兼容外,不保证 ABI 稳定。

  具体策略详见 YSLib 项目文档 doc/ 下各个库的文档。

API

  对各 API 的说明和注意事项详见 YSLib 项目中的源代码,可构建文档

平台相关 API 注意事项

  • YCLib::MemoryMapping 中 platform::MappedFile 的实现需确保文件内容可访问,否则取指针后,可在之后的操作中引发不可恢复的错误。
    • 在硬件支持 MMU 的平台上,使用内存映射实现。
      • 读取错误产生不可恢复的错误。
      • 因此不支持不可靠的远程文件。
    • 其它平台(当前包括 DS ),使用文件流模拟。
      • platform::MappedFile 初始化失败会直接抛出异常。除非用户代码忽略,可避免引发后续失败(引起未定义行为)。
    • 当前仅支持只读映射。若修改取得的映射指针指向的存储,在使用内存映射实现的平台上也可引发上述行为类似的不可恢复的错误。

标准库使用

  YSLib 对 C++ 标准库具有明确的要求,且仅使用明确的标准库特性

  和 LLVM 编码标准对 C++ 标准库的使用(en-US)类似,YSLib 强调尽可能依赖标准库的公开接口。仅在具有充足理由时不依赖标准库而提供和使用自定义 API (基本上总是因为设计和实现的质量不足够满足需求和以上要求的原因)。

  对标准库特性使用的具体限制见开发说明及 YSLib 项目文档 doc/LanguageConvention.txt 等规则。

  相关的其它原理另见下节。

轮子列表

  本项目发明了一些轮子。为了符合不重复发明轮子的项目内容规则,在发现更好的替代时,其中一些会被逐渐移除。

  但是,其余一些轮子,因为特定的原因,不视为对此规则的违反而得以保留,包括一些在当前实现中使用的和较新版本语言规范中提供的兼容的特性。

  主要包括以下内容:

  • YBase.YStandardEx
    • 为了兼容性需要,一些标准库接口的实现会被保留。但当合适的版本可用时,通过条件包含选择别名声明,优先使用标准库提供的版本。
    • Meta 、TypeTraits 以及其它元编程设施
      • 其它元编程设施因为没有被标准化、风格和可用性问题,提供了 C++14 兼容的元函数和类似 Range-v3Boost.Hana 的实现使用的接口。
    • IntegerSequence
      • 主要接口兼容 C++14 的 std::integer_sequence ,但早于提案 N3493
      • 提供了更丰富的底层接口如 make_peano_sequence
    • Operators
      • 接口类似 Boost.Operators ,但有以下不同:
        • 要求 C++11 支持。
        • 使用代码量较少的紧凑的实现,不支持对不符合标准的编译器的多数实现细节上的变通。
        • 不支持链式模板参数(这是为了兼容不支持偏特化的编译器的变通)。
        • 提供对 noexcept 的支持。
      • 接口类似 The Art of C++ / Operators(原 df.operators )。
      • 除以上列出外,还有以下差异和特性:
        • 操作符模板使用别名而不保证是类。
        • 支持可选的操作符参数,默认推断 constexpr
        • 重载操作符实现为模板友元以支持推断 constexpr 同时允许在派生类中提供显式覆盖实现。
      • 此外,提供部分不在上述类似接口中提供的成员操作符重载。
    • Any
      • 接口类似 Boost.Any ,包括内部非公开的 unsafe_any_cast 作为扩展;此外 any 成员也有一些扩展。
      • 除了 any 外还提供底层实现相关的扩展,用于实现 AnyIterator ,这无法被简单取代
      • Any 的提案最早于 2006 年基于 Boost.Any 提出,未考虑 C++11 特性。此实现早于标准库提案的后续修订版本实现。
      • 现在和 WG21 N4081std::experimental::any 同步,但仍然保持扩展。
      • 使用了小对象优化实现,这也符合后来更新的提案中的建议。
      • 提供 emplace 相关扩展,可避免构造 any 对象或赋值时冗余初始化目标类型的值。
      • 提供 unchecked_* 成员和 unchecked_any_cast 模板扩展,要求参数非空,因为省略检查比对应的检查空值的接口( unchecked_any_cast 对应 unsafe_any_cast )更高效。
    • Optional
      • ISO C++17 的 <optional> 在 ISO C++11 下的移植和扩展。
      • 支持几乎所有特性,除 operator-> 因为核心语言特性限制不总是支持结果是常量表达式。
      • 支持少量扩展接口: make_optional_inplaceref_opt
    • Map
      • ISO C++17 的 <map> 在 ISO C++11 下的移植和扩展。
      • 支持不完整键类型,不依赖特定扩展(如 libstdc++ 的 std::map)。
    • Invoke
      • 提供 std::invoke 的替代。
    • Apply
      • 提供 std::apply 的替代。
    • Function
      • 提供 std::function 的替代。
    • Rational
      • 类模板 fixed_point 和提案 P0037R1 中的有相似之处,但模板参数类型更明确,且提出较早。
    • 一些非直接的(以同名接口一一替代的)标准库替代接口
      • 减少了标准库对应的限制,提升了易用性。
      • 不一定和标准库的对应功能的 API 同名。
      • lref 部分取代 std::reference_wrapper (涉及 std::bind 的使用无法取代)。
      • mapped_setystdex::map 模拟 std::set
        • 不要求键类型保持 const 。这意味着(和 C++98/03 类似)只要不影响比较操作的等价关系,元素可以使用无 mutable 修饰的成员而保持 const 类型检查。
        • 使用 ADL set_value_move 优化插入等操作,允许转移值。
        • 在 C++11 下即支持泛型查询接口。
        • 因为使用 ystdex::map ,支持不完整键类型。
    • 一些通常典型地用于标准库内部实现的接口
      • 实际上也适于被用户代码使用。
      • has_mem_valuehas_nested_allocatorenable_if_transparent_t 等检查类型要求的元编程设施。
      • search_map 提供关联容器查找,适合实现 map 等的 operator[]try_emplace 等设施。
        • 支持迭代器提示参数。使用比当前(2016-03) 最新开发版本的 libstdc++ 更紧凑的实现(而 libc++ 对 map::try_emplace,用 lower_bound 近似,也包括对用户代码提供提示参数的情况(直接忽略提示),后者尽管满足复杂度要求但特定情况下容易更低效。
    • 其它可在 C++11 下使用的标准库扩展
      • ISO/IEC TS 19568:2015 C++ Extensions for Library Fundamentals (最终草案 N4480) 以及 ISO/IEC DTS 19568 C++ Extensions for Library Fundamentals, Version 2 (草案 N4564)的一些接口在 C++11 下的实现和 YStandardEx 扩展。
        • 提供元函数 and_or_not_
          • 是早于提案 P0013R0 的实现。
          • 比原始提案及 libstdc++ 中使用的实现更简单。
          • 以别名模板的形式支持 P0013R1
        • 函数模板 apply 利用更加强大(投影到不同参数位置进行调用)的 call_projection 实现,而非直接基于 integer_sequence
        • 类模板 optional
        • any 大体兼容这里的接口规范。
        • 类模板 string_view
        • 类模板 observer_ptr
        • 部分比较操作使用 Operators 实现,保证一般预期的可用性同时简化实现及改善编译性能,但不完全和规范一致。
      • 函数模板 destroy 是早于提案 P0040R0 的实现。
        • 使用不同的参数形式被重命名为 destruct 等模板。
        • 实现和 P0040R2 兼容的相关接口。
      • 类型特征 unwrap_reference 早于提案 P0318R0 提供,功能近似。
  • YFramework
    • 一些接口存在第三方的高效实现,但不足以同时在性能和功能上取代现有接口和实现。(以下性能测试因为实际环境基准问题当前仅用于内部测试,未正式支持。)
    • CHRLib
      • 提供的 UTF-8 解码实现之一在主要支持平台(即 MinGW32 )上实测为当前已知最快的实现。
        • 第二快的是 RapidJSON 。虽然同样使用状态转移查找表,它使用刻意避免分支判断的实现机制,使用的表较大而对缓存可能不友好,导致在典型的实现中反而不一定更快。此外实测表明,使用 GCC 的 __attribute__((flatten)) 即能明显影响这里的性能,程度和算法优化相比无法忽视。
        • 其它朴素实现都明显落后于使用状态查找表算法的性能。
    • YSLib.Core.YEvent
      • 在不进行体系结构和 ABI 相关优化的限制前提下,YSLib::GEvent 的性能超过这里的除了 jl_signal 所有实现。而后者基于特定实现的 ABI 进行了较为复杂的优化。(待定:整合新的测试。)
      • 除了没有 Boost.Signal2 的线程安全和定制返回类型策略的接口外,所有功能都不弱于上述测试中的例子。
      • 此外,基于 ystdex::make_expanded 提供允许省略未使用的参数这个实用特性,是以上测试用例都不具备的。事实上,尚没有已知的 C++ 库中提供类似的特性。其它语言可能提供类似的特性简化 GUI 开发,如 {x:Bind} ,但实现原理上,其运行时性能不可能超过此处的方式(静态语言在翻译时确定参数列表)。

项目依赖性

  示例项目 YSTest 在 DS 上依赖 YSTest/DS_ARM7 和 YSTest/DS_ARM9 两个项目。 YSTest/DS_ARM9 依赖于 YFramework ;在其它平台直接依赖 YFramework 。

  YFramework 依赖 YBase 。

  除了 MinGW 外使用 Make 构建。需要添加静态库时,把相应的 .a 文件加入相应的 lib 目录下,否则需要手动修改对应的 Makefile

  MinGW 使用 Code::Block 或 Sysroot 构建。

生成路径

  默认构建的根目录位于版本库下的 build 目录。

注意 本节以下内容不适用于 Sysroot

  设 $(Platform) 是平台名称,$(Configuration) 是生成配置名称,则各个项目生成的文件和中间文件都在项目的 build/$(Platform)/$(Configuration) 子目录下。修改 Makefile 的配置可以分离生成文件和中间文件的输出路径。

  默认生成配置名称可以是 debugrelease(注意此项的大小写会在 Makefile 中表现出区别)。

概述

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

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

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

语法和语义

  NPL 的语法基于形式文法上可递归构造的表达式

  在操作语义(基于项重写系统)的意义上,其中的子表达式又称为

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

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

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

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

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

  NPL 提供了一些通用的概念和公共规则,但不构成具体语言实现的完整语义规则。语义规则由派生实现补充完整。

注释

  排除注释及中缀标点 ;, ,NPL 的语法和 Scheme 语言Kernel 语言的语法近似。不过,NPL 不支持构造循环引用,也不提供相关语法。

需求概述

  设计满足的需求描述参见这里(en-US)

  需求来源:

  • 出发点:构建一个可用计算机实现的语言。
  • 基本目的:在以标准 C++ 环境( [ISO C++] 定义的宿主实现(hosted implementation) )的程序框架中嵌入配置和脚本操作。
  • 扩展目的:渐进地向独立的计算机系统演进,探究能适用于各个领域并以计算机实现的通用目的语言(general-purpose language)

  本文档描述基于此出发点的 NPL(Name Protocoling Language) (一个替代的递归缩写是“NPL's not a Programming Language”,因其不仅适合作为 PL 的元语言特性及其参照实现。

  和大部分其它设计不同,为了确保一定程度的适应通用目的的性质,它们被设计整体首要考虑。这样的设计的语言是(自设计(by desing) 用于)满足通用目的的语言(general-purposed language) 。

其它设计和实现参考

  NPL 是独立设计的语言,但它和 [RnRK] 定义的 Kernel 语言有许多核心设计的相似之处,尽管设计的一些基本特征(如资源可用性基本约定)以及基本哲学相当不同。

  NPL 的主要实现的核心部分实质上支持了 Kernel 的形式模型—— vau 演算(vau calculi) 。

注释 另见操作语义

  具体的 NPL 语言在这些模型的基础上提供。

  NPL 的命名即体现了 vau 演算和传统 λ 演算为模型的语言的核心差异:

  强调允许在对象语言中指定求值上下文的显式求值(explicit evaluation)(而非 Lisp 方言中以 quote 为代表的显式干预默认的隐式求值)的风格以及表达式求值前后的不同,特别地,关注在语言中直接表达的名称求值后指称的实体的不同。

  更进一步地,NPL 普遍地支持区分一等引用和被引用的一等实体并具有更精确的资源控制机制,这是与 Kernel 的主要设计上的差异。

  关于 vau 演算的形式模型和其它相关内容,详见 [Shu10] 。特别地,vau 演算提供了 fexpr 类似的抽象。

注释 另见求值算法设计的实例

  关于一些其它支持 fexpr 特性的语言设计,参见:

  和 Kernel 以及本设计不同,这两个例子的设计使用动态作用域;在主要的特性中存在一些关键的不同而在形式模型的适用性上有显著的区别。

注释

  NPL 和历史上同名的 John DarlingtonNPL (New Programming Language) 没有直接渊源;特别地,后者的多个等式的函数定义语法和高阶类型没有被内建支持,而静态类型和纯函数式限制被避免。

  Kernel 语言的原始参照实现是 SINK 依赖 MzScheme version 103 的解释器实现,和 [RnRK] 有一定差异。例如,字面量 #ignore#inert%ignore%inert 代替。

  klisp 是 Kernel 语言的一个更完善的实现。

  有些特性(如复数支持)都没有在这两个中实现提供,而仅在 [RnRK] 中指定。

实现

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

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

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

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

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

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

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

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

当前具体实现

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

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

注释 这些实现基于 YSLib API 提供互操作支持。

绪论

正式引用

  仅在此给出本文档中的外部引用的名称。其它引用文献的内容详见 YSLib 项目文档 doc/NPL.txt

领域设计原则

  本节描述被本文档中的一些原理讨论引用的的公共依据。

  原则指关于设计和实现的哲学,同时作为一般规则约束设计和实现的工程阶段。

  关于需求特别是通用目的语言的讨论,参见需求概要(en-US)

本体论

  为使论述有效,约定本体论(ontology) 规则。

  基本的本体论规则是约束逻辑系统构造的公理。

正规性

  有效的陈述(如需求描述)应保证操作上可预期结果。

  在此意义下,缺乏约束性的规则不可预期的风险是代价。

  推论:规则应适当约定适用范围,以避免外延不清。

存在性

  语义的存在体现本质。

  仅仅应用语法规则,即限定为语法的文法(syntactic grammar) 的形式系统归纳的设计,不被视为表示任何有效的含义。

名实问题

  名义概念的内涵和外延应被足够显式指定,避免指涉上的歧义,允许构造有效的陈述

不可分的同一性

  不可分的同一性(the identity of indiscernibles) (en-US) 比较陈述的客体之间是否相同而不需要被重复地处理。

价值观

  价值观是关于价值判断的规则,其输出为二元的值,决定是否接受决策。

  作为应对普遍需求场景的不同解决方案选型时的价值判断的抽象归纳,价值观被作为比较是否采用设计相关决策的全局依据。

  以下陈述形式表达价值优先的选项,同时作为公理。

注释 相同推理结果仍然可能不唯一,这来自于自然语言描述的输入的不精确性。

变化的自由

  在明确需求的前提下,尽可能保证对现状按需进行改变的可行性和便利性。

  适用于一般需求。

  对计算机软件或其它可编程的实体:尽可能避免不必要地损失可修改性,便于保障按需引入或除去接口及其实现的自由。

原理

  一般地,需求可能随着不可控的外部条件变化。假设已明确的需求不变只能适合相当有限的情形。积极应对变化能提供价值。

避免不必要付出的代价

  尽可能消除对满足需求无意义的代价,减少影响需求实现的整体成本。

  适用于一般需求中设计决策的比较。

  对计算机软件或其它可编程的实体:不为不需要的特性付出代价。

注释

  一个类似的表述:

Efficiency has been a major design goal for C++ from the beginning, also the principle of “zero overhead” for any feature that is not used in a program. It has been a guiding principle from the earliest days of C++ that “you don’t pay for what you don’t use”.

  — ISO/IEC TR 18015

最小接口原则

  在满足需求的前提下,尽可能使用符合倾向减小实现需求代价的单一良基关系下具有极小元的接口设计。

注释 减小实现需求的代价,如减小设计工作量。

  这是一条模式规则,依赖具体情形何者符合良基关系的极小元这条非模式规则作为输入。

  实际使用时,非模式规则可以直接指定为二元关系的子集,或者一种良序的度量。

注释 例如,“公开函数声明数”“模块数”。

  这个输入也可能直接对应符合需求集合的某种最小功能集合而不需要附加度量,如表示某种设计的裁剪。

  注意规则指定的基数是对实现需求有意义的代价,因此不涵盖避免不必要付出的代价

  在确定的范围内尽可能少地提供必须的接口,避免不必要的假设影响接口适应需求的能力,同时减少实现需求的各个阶段的复杂性。

  适用于一般需求的实现,特别地,强调“通用”目的时。

  对需要在计算机上实现的人工语言设计:设计语言不应该进行功能的堆砌,而应该尽可能减少弱点和限制,使剩下的功能显得必要。

Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.

  — [RnRS] & [RnRK]

注释

  其它各个领域中的实质等价一些表述包括:

关注点分离原则

  关注点分离(separation of concerns, SoC) 原则 :局部设计的内容应和需求的陈述或其它隐含的设计前提分别一一对应。

  适用于一般需求的实现,特别是其中依赖认识论观点的过程。

原理

  这条规则利用需求和设计内容陈述中概念外延普遍蕴含的局域性(locality) ,提供给定代价下更多的可行性或求解给定问题时使用较小的代价,用于:

  • 应对不可控复杂条件下使问题可解。
  • 局部可复用现有解的子集。

  此外,尽管并非总是必要,应用知识内容的简单假设、最小接口原则和本原则可在认识论上导出还原论。

形而上学

  根据作为需求的价值观,归纳适用于通用目的语言应有的构成及其性质(形而上学(metaphysics) )的设计规则,包括三条价值判断实现公理:

  • 设计应尽可能满足正确性
  • 设计应尽可能满足简单性
  • 设计的正确性应优先于简单性。

  具备这些性质的设计可视为由价值判断蕴含,预设前提为真的设计方法论的实现。

注释

  注意和 worse is betterthe MIT approach 不同,设计的性质并非完全并列。特别地,完整性一致性都被作为正确性的一部分考虑。

  因为变化的自由,具体需求以及判断正确性和简单性的确切依据都可能会随着项目的进展而变化。

正确性

  设计应正确地反映需求,不在需求的基础上新增作为实现细节以外的不确定性。

  无法确保满足这种正确性(correctness) 要求时,不应继续设计。

  正确性包含可行性(feasibility)

  若无法满足正确性,则需求输入存在问题。

  正确性不包含但应逻辑蕴含设计的一些其它性质。若无法实现,则具体性质的定义存在问题。

  保持正确性作为设计评价的首要依据以使决策简单,同时能符合价值判断

完整性

  正确性应蕴含完整性(completeness) ,即确保没有需求被遗漏。

  推论:设计应包含完整的需求响应。

原理

  对通用编程语言的一个完整性要求是支持计算上的可表达性(expresiveness)

  这种性质被称为可有效计算性(effective computability) ,或 Turing 完备性(Turing completeness) 。在可物理实现的计算普遍遵循 Church–Turing 论题(Church–Turing thesis) 的情形下,这同时是可计算性(computability) 。以上性质一般不加分辨。

  具体的语言中允许的表达的可计算性是表达能力(expressive power) 。另见 [Fl91] 。

  特定的场合要求更弱的性质。例如,类型检查等情形需要全(total) 计算而确保实现总是可终止。这种要求在完整的实现中可通过附加的设施(用户提供的标注或证明)保证,而不应通过系统设计的规则静态地排除,否则实现是不完整的。仅在作为领域特定语言时,通过从需求中排除可计算性,静态规则作为优化是被允许的。

一致性

  正确性应蕴含一致性,即内部的逻辑无矛盾性。

  推论:设计应保证一致性。

简单性

  在满足正确性的前提下,接口设计应尽可能满足简单性(simplicity),即尽可能少地具有可被继续简化的内容。

  接口设计的简单性优先于实现的简单性。

可修改性

  可修改性(modifiablity) :在满足需求的前提下,修改应尽可能少地有碍于其它的接口。

  这是变化的自由的推论。

避免抽象泄漏

  泄漏的抽象(leaky abstraction) 指抽象的底层复杂性没有被抽象合理地隐藏,而在一定程度上构成了利用抽象时的不必要的依赖。

  这种抽象泄漏(abstraction leak) 的结果直接和避免不必要付出的代价关注点分离原则简单性冲突。

  同时,抽象的有效性被削弱,泄漏构成的不被预期的依赖难以满足正确性;只要有避免抽象泄漏的方法,就不满足最小接口原则

  因此,只要可能,避免抽象泄漏。

注释 在信息安全意义上,抽象泄漏还可能提供难以抵御的附加的攻击信道。

关注资源限制

  为了可实现性,宿主(host) 系统对总的资源(典型地,运行程序需要的存储)有未指定的上限。

  除此之外,接口抽象不附加接口语义要求以外的限制。

  这个原则同时利于满足正确性简单性。而不遵循这个原则的设计在接口描述上违反最小接口原则

  在允许实现的前提下,附加具体特性上的使用限制(如 [ISO C] )可放宽对实现的要求;但无原则地随意选取此处的限制不足以直接证明具体的限制的有效性,而依赖实际实现的情况才能判断,造成抽象泄漏

注释 实例:PicoLisp 使用符合此原则的设计。

开放性

  开放性(openness) :除非另行指定,不假定实体不存在。

  这个原则主要用于建模(modeling) 的依据。对一般的模型,这个原则称为开放世界假定(open-world assumption)

  与之相对,封闭世界假定(closed-world assumption) 需要提前设置一个全集(universe) 以保持至少在逻辑的意义上合规

  开放世界的元素的全集是模型的结构化规则推断得到的,而非名义上的定义决定。这同时称为模型的语言的论域(universe of disclosure)

原理

  封闭世界假定表面上可能简化实现,但在一般的模型中是不必要的,因为保持问题合规性的论域应已由清晰的需求描述规范,不应为此阻碍实现变化的自由

  使用封闭世界假定的一个主要实用意义是使模型在有限的信息下能推理出逻辑上更强的结论。在重视结论的知识系统中,这通常是一种优化;但在重视表达能力(而通过其它方式辅助求解问题)的通用模型中,这种前提是一种直接的限制。同时,封闭世界假定的优化不保证对所有输入有效,对否定输入还可能导出一些矛盾。

注释

  开放世界包含的元素的外延及其语言的论域伴随随语言规则的修改而改变。

  开放世界不限制论域中的某个子集是封闭的。例如,论域中可能存在某个子集的所有元素通过一定方式被枚举。

结构和依赖原则

接口设计和实现分离

  语言设计独立于语言实现

  这是同时应用最小接口原则关注点分离原则的推论。

  这种分离允许避免抽象泄露

  典型地,使用提供接口抽象层作为必要构造的架构方法,即分层设计。

最小特权原则

  最小特权原则(principle of least privilege, PoLA) :除非有必要,接口抽象不提供满足需求以外的其它信息和资源。

  这是最小接口原则在限制适用领域前提下的等价表述之一,用于避免不必要的访问路径引入额外的安全(safety) 风险,更容易满足(针对恶意使用风险的)安全性(security) 和可信性保证相关的需求。

  实质上提供例外的必要性之一是接口正确性:不附加不存在于需求以外的安全设计;根据可修改性,这应是实现细节。

最小依赖原则

  最小依赖原则(principle of least dependencies) :除非有必要,接口实现仅使用必要的依赖。

  这是最小接口原则的推论之一,其非模式规则的输入为:

  • 已知必要的依赖较已知必要的依赖和不必要的依赖的并集要求较小的使用和维护成本。
  • 这里的使用包括演绎抽象自身的推理(reasoning) 。依赖较少时,推理时需要搜索的解空间也越小。
单一模块依赖倒置原则

  依赖倒置原则(dependence inversion principle) 在单一模块下包含以下含义:

  • 抽象(的接口)不应该依赖(实现)细节。
  • (实现)细节应依赖抽象(的接口)。

  这是最小依赖原则应用在不同抽象的模块化设计中使用以下公设的推论:

  抽象是细节包含的子集,依赖抽象的接口较依赖实现细节具有更少的依赖。

可复用性

  设计应具有可复用性(reusability) :高层抽象设计的实现应包括复用此设计的实现的设计。

  这是最小接口原则的推论之一,其非模式规则的输入为以下公设:

  一般地,高层抽象设计和复用此设计的实现较单一的高层设计的实现更复杂。

  此前提条件由对需求工作量可行性分析中的度量验证总是成立。

  推论:除非必要,不分离抽象设计的实现和复用此设计的实现的设计,避免复杂性。

  全局意义上的不分离设计不违反关注点分离原则

注释

  典型实例:语言是一种高层抽象设计,语言的库(library) 是一种复用语言的设计。因此,语言实现应包括库设计。

  另一个实例是对象语言设计复用元语言语言规则

可组合性

  组合(composition) 是一种特定形式的涉及多个实体的复用,允许复用时不修改被复用的其它实体。

  可组合(composability) 原则:接口的设计应允许不同设计之间的组合满足这些设计响应以外的需求。

  这是最小接口原则的推论之一,其非模式规则的输入为以下过程推断得到的引理。

  公设:一般地,在存在充足基础解决方案的情形下,组合现有解决方案的设计较重新给出不依赖这些解决方案的设计的解节约成本。

  应用避免不必要付出的代价,得到引理:

  一般地,在存在充足基础解决方案和满足需求限制的情形下,组合现有解决方案的设计优于重新设计。

  即提升可组合性可减少实现被复用的设计的成本。

接口设计性质和原则

统一性

  接口的设计应具有统一性(uniformity) :尽可能避免特例。

  这是要求变化的自由的推论之一,以一致性作为非模式规则输入。

  无限制的特例要求指定更多的附加规则避免潜在的违反一致性的风险,而违反这个要求。

  因为不需要特设只有对象语言中可用的规则,复用元语言规则有利于实现统一性。

  以统一的方式复用元语言和对象语言公共设施在语法设计上称为光滑性(smoothness) ,而这可推广到语义上(另见正交性),以避免对抽象能力(power of abstraction) 的限制([Shu10] §1.1.2) 。

原理

  在语言设计上,这类似 [RnRK] 的设计原则 G1 :

  • G1a 对象状态(object status) :语言操作一等对象
  • G1b 可扩展性(extensibility) :用户定义的设施能重现内建特性能力(capability)

  以上原则在 NPL 中略有变化。

  同 [RnRK] ,被 G1b 重现能力的特性是内建的(built-in) 。这不同于如 [RnRK] G2 指定的基本的(primitive) 特性。

  [RnRK] 的基本特性指不要求作为派生的(derived) ,即以对象语言程序实现的特性。而内建特性适合整个语言规范的接口设计约定,不论其实现是否被派生。不被要求重现的部分是实现细节。

  但是,因为基本特性不要求能通过对象语言特性的组合实现,在不考虑派生特性的可实现性时,G1b 不会限定基本特性的能力。

  整体上的 G1b 在和正确性冲突时不被要求。这也避免了 [RnRK] §0.1.1 指出的“妥协”。

  因为语言规范不依赖使用对象语言表达,G1b 仅表示用户使用语言的扩展,不表示语言自身的可扩展性;后者通过满足需求的能力和强调支持开放性体现。

  仅通过用户程序实现的这种原则在 NPL 的设计中不被视为必要。但偏离这个原则的设计一般同样是不必要的。

注释

  关于 G1a 的改变,详见一等实体和一等对象

适用性

  设计应提供适用性(usability) :合乎预期满足的问题领域的特性

  对通用目的的领域,应进行权衡。

注释

  这个原则存在以下的侧重不同使用方式或场景的具体表述。

  结合用户的经验,这个规则的变体是之一最小惊奇原则,强调降低接口的学习和适应成本。

易预测性

  设计应符合易预测性(predictability) :允许但难以偶然实现的危险操作。

  同 [RnRK] 的设计原则 G3 。

  这里的危险的操作指引起较大代价的不预期或无法预期结果的操作。

  这是变化的自由避免不必要付出的代价的推论,包含两方面:

  避免危险的操作在许多上下文中可减少程序中易错(error-prone) 的实现的风险。

可用性

  一旦提供特性,应提供可用性(availablity) :保证一定程度的典型场景下能被使用。

  绝大多数情形都不能使用的特性是对接口设计的一种浪费,很难符合也通常不符合简单性

  可用性的概念有时也指抽象和实体具有的符合这个原则的属性。

最小惊奇原则

  最小惊奇原则(principle of least astonishment):在保持合理性的前提下,若能评估目标用户的接受能力,避免违反其直觉的设计。

  其中,合理性至少应蕴含正确性,一般也蕴含简单性适用性同时不违反其它原则(特别应注意尽量保持可复用性可组合性)。

  这个原则主要适用于人机交互接口的设计,但也适用于一般的 API

  推论:约定优于配置(convention over configuration) :约定接口的合理的默认行为,而不是隐藏其行为而提供配置另行实现。

正交性

  在满足正确性的前提下,接口的设计应具有正交性(orthogonality) :根据需求适当分解为排除冗余和重复能合理组合的部分。

  这是最小接口原则关注点分离原则在接口设计上的应用。

  一般地,正交的设计使相同目的可使用更精简的接口组合方式实现。这也使接口具有更强的抽象能力

方法论

  方法论(methodology) 是严格独立价值判断的规则,是关于价值判断结果参数化的判断规则。

  不同的价值判断的结果作为方法论输入,决定是否适用此方法。

  其它方法详见以下各节。

注释

  一些规则因其主要表述包含价值判断而不在此归纳为方法论,尽管其中一些表述中的前提可以被参数化(如奥卡姆剃刀的“如无必要”的具体必要条件)。

避免不成熟的优化

Premature optimization is the root of all evil (or at least most of it) in programming.

  — The Art of Computer Programming

  原始含义适合计算机程序设计中以效率为目标的决策。

  扩展的外延适用于一般需求,要求:

  • 适时收缩理论长度以照顾可操作性。
    • 注意断言一个优化过早自身可能就是一个过早的优化。
  • 主动适应需求变更。
    • 不同时明确全部的具体需求,只限定需求范围:能使用计算机实现部分语义的任务。

封装

  封装(encapsulation) 是接口设计的合理性准则。

  封装是不可分的同一性的一种实现方式:封装提供的接口以下的所有实现在接口从使用者的角度都是不可分的。

注释 若存在使用者可感知的抽象泄漏,这种实现可能失效。

  以接口的预设风格的价值判断为输入,封装性要求接口满足以下多态性(polymorhism)

  给定接口的替代接口,则替代接口应能代替原接口,当且仅当不引起非预期的可观察的差异。

  在语言设计中,去除风格参数化的这条原则被作为 LSP(Liskov Substitution Principle)

  参数化风格限定并非任意符合 LSP 的接口设计都符合封装性要求。这便于从不期望的设计中剔除不符合其它原则的设计。

注释

  一些程序设计语言中的封装提供符合 LSP 的面向对象风格的设施。这些设施把数据和代码组合在一起提供,但仅仅组合并不体现封装性。因此,同时具有信息隐藏特性,如 [ISO C++] 的类成员的访问控制的机制,被认为是典型的封装。

  即便如此,封装在严格意义上和信息隐藏是相互独立的。即便语言不提供信息隐藏而仅仅指定违反封装性不关心实现细节的假设的操作未定义,也不失去封装性。事实上,[ISO C++] 中,使用 reinterpre_cast 无视类的访问控制就是这种例子。

  另一方面,LSP 事实上关于子类型,不限于以类作为类型的基于类的面向对象风格,实际外延更广。

信息隐藏

  信息隐藏(information hiding) 保持不需要公开的信息不被公开,以使设计符合最小接口原则并支持避免抽象泄漏

  适用于接口及其实现。

  信息隐藏以是否需要公开信息的价值判断(特别地,关于如何符合最小接口原则)的结果参数化。

注释

  封装的接口通常有助于实现信息隐藏。直接限定避免接口规格具有过多的信息,是另一种直接的实现方式。

  例如,基于类的面向对象通过对名称组成的表达式限制对类成员的外部访问,隐藏了类成员的信息,同时提供封装性

  其它方式可直接不在外部提供任何访问被封装实体的名称,如 [RnRK] 的封装类型(encapsulate type) 和 [ECMAScript] 通过 WeakMap 实现的封装。这些封装也同时实现了被封装实体的信息隐藏。

  即便如此,如关于封装的讨论指出的,封装不一定需要实现信息隐藏。更一般地,信息隐藏的目的也不一定是提供封装。例如,系统的安全性可能直接在需求上要求隐藏特定信息,不论这种信息是否关于某种接口的实现。

模块化

  接口和实现的设计应具有足够的模块化(modularity) :被划分为若干保持联系的组件即模块(module) ,至少满足正确性可组合性,并强调实现可复用性

  模块化设计通常有利于使设计具有正交性,但模块化相对可复用性,更侧重可组合性。

  参数化的输入是需被评估模块化程度的结构设计(包括模块的粒度(granularity) 和组成部分的依赖关系)相对给定需求的实现质量的价值判断。

其它推论和比较

  从对正确性的强调可知,较简单性优先考虑通用性(generality)

  这和 [RnRK] 中讨论的设计哲学虽然相当不同,但仍允许和 Kernel 具有相似的特性。

  作为典型的 NPL 的一个派生实现NPLA1 具有以下和 Kernel 相似的核心设计:

规格说明

  在附录之前的以下章节给出 NPL 的正式规格说明的公共部分,即语言规范

  本文档仅提供部分派生实现的规格说明。关于其它具体规格说明,详见 YSLib 项目文档 doc/NPL.txt

  在不和其它语言规则冲突时,派生实现可能补充或覆盖更确切范围中生效的定义和具体语言规则。

略称

  仅在不致混淆时使用。

  • 实现(implementation) :语言实现
  • 环境(environment) :实现环境
    • 外部环境:(当前描述的系统边界的)外部的实现环境。
  • 派生实现(derived implementation) :派生语言实现

补充领域定义

  以下术语的定义参见计算机体系结构

  • 指令
  • 指令集
  • ISA

整体设计

  一些语言规则可能显式地由派生实现指定,或补充具体规则。

注释

  具体讨论设计策略另见需求描述文档。

  另见设计原则的讨论;对本章内容的描述的理解应符合其中的原则。

模型

  可用计算机实现的语言首先是计算的模型(model of computation) ,或者计算模型,对计算进行建模得到。

  与之相关地,为计算机系统建模作为计算机的模型(model of computer) ,需对有限计算资源的现实进行适应。

  这些模型可使用形式方法建立,即形式模型

  被计算机实现的语言应同时具有这两方面的特征。

  作为实用的语言,语言还应强调提供可编程性以允许用户利用;这样的语言称为编程语言(programming language)

  本设计尝试在语言的原生设计中应对现有语言缺乏模型问题以避免这些妥协带来的消极影响,同时取得比非模型方法更强的可用性。

  这种可用性至少体现在语义的精确性可通过模型直接决定;仅为精确性,不需要另行补充模型设计(尽管现有模型可能仍然是不完全形式化的)。

原理

  以无限的计算资源为前提,理想的模型无法被物理地完全实现,无法直接作为计算机实现的语言的模型。

  同时,这些模型仅适合对计算建模,并没有强调允许可编程性的实现;扩充可编程设计而保持模型自身的主要性质相当困难。

  因此,基于计算的模型适配编程语言的设计必然需要妥协:对这些模型的裁剪和补充能提供若干编程语言的模型,但这无可避免地显著地复杂化模型自身,且不利用用户使用简单有效的规则实现通用目的上的可编程性。

  事实上,使用严格形式化的模型描述编程语言的行为较编程语言自身的发展更落后:

  • 大部分编程语言并没有使用模型支持它们的设计。
  • 现实的实用语言,特别地,包括所有主流的工业语言(industrial language) ,几乎都没有在语言规范中给出完整的模型。
  • 通常的实用语言只形式化基本的语法上的规则,无法指导用户精确理解程序的含义。

  这些落后集中体现在的语义模型的缺失,使对编程语言语义的判断取决于规格说明中模型外规则的理解。

  后验(postpone) 的语义模型可以使用不同形式语义方法设计,但和语言规范差异的一些本应避免的附加工作,并且通常难以完整地作为标准规格的描述。

注释

  Turing 机、无类型 λ 演算(untyped lambda calculus) 等早期计算模型不考虑有限计算资源限制。

计算复杂度约定

  特定的算法过程具有计算复杂度要求。除非另行指定:

  • 这些复杂度是任意避免符合错误条件的方式调用时求值蕴含的渐进(asymptotic) 时间复杂度。
  • 若指定边界,明确的输入规模以哑变量(dummy) n 表示。
  • 指定复杂度的计算保证可终止(terminate)

注释

  算法过程也适用对象语言上的操作。

资源可用性基本约定

  在抽象机的配置中,任意通过元语言(metalanguage) 语法描述的资源总是可用的。

  为避免对具体资源的总量和实现细节做出假设,除此之外,本设计只要求模型蕴含所有权语义(即便不严格形式化——注意作为元语言的描述模型使用的形式语言仍然可能是实现细节)。

  具体计算机系统的实现中,保证基本可用的资源被直接映射到程序执行(execution) 的环境中。尽管和适配的软件环境相关,这最终由硬件实现物理地保证。

原理

  在严格的资源限制要求下,模型不能隐藏预设的无限资源的前提。

  因此,有必要做出基本的可用性约定以允许表达明确的要求以避免不可实现。

适用领域

  为尽可能解决模型相关的问题,优先以通用目的而不是领域特定(domain-specific) 语言作为评估语言特性设计的参考原则。

  领域特定语言的特性应能合理地从支持通用目的的特性中派生,且不影响实际的可用性。

形式语义方法

  形式语义方法是建立语义模型的形式方法。

  形式语义方法主要有公理语义(axiomatic semantics)指称语义(denotational semantic)操作语义(operational semantics)

  操作语义可分为在模型中指定具体规约步骤状态的结构化操作语义(structural operational semantics)(或小步(small-step) 语义),及仅指定规约的输入和输出的自然语义(natural semantics)(或大步(big-step) 语义)。

注释 抽象机演算是使用操作语义的模型的两类例子,虽然后者也可以对对象语言以外的表示建模而实现其它的语义方法。

  非确定语义:经验语义,不需要使用自然语言解释的部分。

  本文档不直接给出形式语义。语言规则确定的经验语义可在一定条件下转写为上述形式语义方法表达的形式。

程序实现

  程序是语言的具体派生。实现程序即在语言的基础上指定具体派生规则。

  语言实现外的程序是用户程序(user program)

  以程序或另行指定的其它形式实现的可复用程序被归类为

注释 一般地,不论是语言实现还是用户程序,都可能使用库。

  除非另行指定,一个程序支持多个库的实例,之间不共享内部的状态。

  语言特性包含不依赖库的核心语言特性(core language feature)库特性(library feature)

规范模型

  NPL 是抽象的语言,没有具体语言实现,但一些直接影响实现表现形式的规则被本节限定。

  NPL 的实现可进行抽象解释(abstraction interpret) ,其目标不一定是程序

  任一 NPL 实现(和派生实现)的符合性由以下 NPL 符合性规则定义:文档指定的满足对实现的要求语言规则子集,包括本节、基本文法语义和其它派生实现定义的规则。

  这类规则总是包含对应语言的语义的 NPL 公共子集,且蕴含实现行为的要求。

  语言规则约定的未指定的程序或实现的属性及实现行为在符合性要求上等价。满足这类规则的前提下,实现选取特定的未指定的属性及对未指定行为的特定实现的选择不影响实现的符合性。

原理

  基于抽象机可直接定义最小的符合性要求,如 C++ 的规则

  NPL 没有直接在此定义同等具体的规则,而以一般的要求取代。这允许派生实现对不同的具体规则进行补充和调整。特别地,这允许不同的方式提供语义规则。

  蕴含实现行为的要求的一个主要例子是关于状态的规则。除了允许由实现定义和派生实现指定的不同,这实质上提供和上述具体规则等价的默认情形,而简化派生实现需要的对语言规则的补充和调整。

实现的执行阶段

  一个 NPL 的完整实现应保证行为能符合以下的执行阶段(phase of execution)

  • 分析(analysis) 阶段:处理代码,取得适当的 IR
  • (目标)代码生成(target code generation) :以 IR 作为输入,生成可被其它阶段执行的代码,即目标代码(target code)
    • 注释 一般意义的代码生成可以有多个子阶段,包括多种内部 IR 的翻译,直至得到最终目标代码(final target code) 作为输出。
  • 运行:运行生成的最终目标代码。

  其中分析阶段是任意实现必要的,依次包含:

  • 词法分析(lexical analysis) :必要时转换字符编码;转义(escape) 并提取记号。
  • 语法分析(syntactic analysis) :语法检查(检验语法正确性)并尝试匹配记号和语法规则中的语法元素
  • 语义分析(semantic analysis) :语义检查(检验语义正确性)并实现其它语义规则。

  以上的具体阶段不要求和实际实现中的一一对应,但应保证顺序一致。

  运行之前的阶段总称为翻译(translation) ,包含各个翻译阶段(phase of translation)

  对有宿主语言支持的嵌入实现或目标不是程序的情况,代码生成及之后的阶段不是必须的。

  宿主语言实现可提供作为客户语言的 NPL 的本机(native) 实现。

  宿主语言实现提供 NPL 实现环境,同时对 NPL 环境的操作可影响 NPL 程序,这些情形都是元编程,NPL 在此同时是对象语言

  嵌入实现的宿主语言可直接运行语义分析的结果(中间表示)。

  在语义不变的前提下,允许实现一次或多次翻译部分代码产生部分中间结果并复用。

  运行时(runtime) 程序实现运行阶段。

  其它可能的阶段由派生实现定义,但应满足所有阶段具有确定的全序关系,且不改变上述指定的阶段的顺序。符合这些条件的附加阶段称为扩展阶段。

注释

  字符编码是被翻译的源中的二进制表示相关的模式。

并发实现

  一个实现可能具有计算模型意义上的并发属性,即并发实现(concurrent implementation)

  一个实现中顺序执行以上执行阶段的一组状态称为一个执行线程(thread of execution) ,简称线程(thread)

  一个实现在整个执行过程中可以有一个或多个线程被执行。是否支持多线程执行(多线程翻译和/或多线程运行)由派生实现定义。

  若实现支持多线程执行,则执行阶段的状态区分不同的并发执行线程,此时具体的状态构成由实现定义。

阶段不变量约束

  若某些状态在某个执行阶段 k 被唯一确定为不可变状态,且在之后的状态下是不变量(invariant) ,则此状态称为满足 k 阶段不变量约束的。

正确性

  正确性(correctness) 规则约束被执行的程序,包含语法正确性和语义正确性。

  当正确性规则被发现违反时,实现进入异常执行状态。

  翻译时正确性规则以外的异常执行条件和状态由派生实现定义。

翻译时正确性规则

  翻译时的异常状态要求给出用于区分正常状态特定的行为作为诊断,包括诊断消息和其它派生实现定义的实现行为

  语法正确性规则是翻译时正确性规则。

  部分形式上的正确性规则在翻译时确保。

  允许翻译时确保的形式上正确的程序是合式的(well-formed) ;反之不合式(ill-formed)

  合式的程序符合语法和语义的正确性的规则。

  其中,实现被要求确保通过翻译的程序符合语法规则和翻译时确保的可诊断(diagnosable) 语义规则。

  不合式的程序不保证被完整地翻译,应在运行前终止执行阶段。

错误

  错误(error) 是不满足预期的正确性或其它派生实现定义的不变性质时的特定诊断。

  非正确性或不满足这些不变性的条件是错误条件(error condition)

  满足错误条件时,实现可引起(signal) 错误。

注释

  和 [RnRS] 中的某些版本指定错误可以不诊断不同,引起错误蕴含诊断。

实现行为

  实现的行为由具有存在非特定空间上限的存储的抽象机(abstract machine) 描述。这种描述对应的语言的语义是抽象机语义(abstract machine semantics)

  若语言规则明确特定的行为可被忽略,则被忽略之后的实现行为与之前在语言规则中视为等价。翻译的实现可选取这些等价行为中的任一具体行为。

  派生实现可通过显式的未指定规则定义附加的等价性。

  不论程序是否满足正确性规则,实现对程序的执行都可能存在未定义行为,此时实现的行为不需要满足正确性规则指定的行为要求。

  特定的语言规则引入未定义行为。程序的执行在适用这些规则指定的条件时,引起未定义行为。

  特定的语言规则排除未定义行为的引入,以满足一定的可用性。这不排除程序的执行可能因同时使用的其它语言规则引起的未定义行为。

注释

  抽象机语义是一种操作语义

  抽象机语义也可非形式地定义语言的正式的(normative) 语义和行为要求,例如 C++ 抽象机

简单实现模型约定

嵌入宿主语言实现

  一个派生实现使用外部语言 L 简单实现模型 NPL-EMA ,若满足:

  • 以 L 为宿主语言的嵌入实现,不包含扩展执行阶段
  • 单一实现不保证提供多线程执行的支持,但对资源的使用进行适当的分组,以允许多个实现同时在宿主中多线程执行。

  宿主语言提供的实现环境称为宿主实现环境,简称宿主环境(host environment)

注释

  若支持多线程执行,需要附加的显式同步。

  这种实现可能提供宿主多线程对应的实体,其中包含需要的被隔离的资源。

  其它语言的实现也可能提供类似的设计,例如 V8 的 v8::Isolate

  另见可移植互操作意义上的宿主环境

基本文法

  本章约定基本的 NPL 文法规则中,包括语法及对应的基础词法。对应的语义在下文列出

  多态文法规则:派生实现可完全不提供本章明确定义的词法和语法构造的支持,仅当提供同构的替代文法且符合语义规则。

基本文法概念

  • 字符(character) :组成语言代码的最小实体。
  • 基本翻译单元(basic transation unit) :作为翻译输入的任意连续字符的有限序列(可以是空序列)。
  • 翻译单元(translation unit) :基本翻译单元的集合,之间满足由派生实现定义的规则。

  程序以翻译单元或具体操作指定的以翻译单元进行翻译得到的其它变换形式表示。

字符集和字符串

  • 字符集(character set) :对一个实现而言不变的字符的有限集合。
  • 基本字符集(basic character set) :实现环境必须支持的字符集。具体由派生实现定义。
  • 字符串(character string) :字符集上的序列。

  除非另行指定,关于字符集定义的其它概念同 [ISO C++11] 对 character 和 character set 的有关定义。

注释

  字符编码基于字符集定义。

  一般地,一个翻译单元只具有同一个字符编码。

词法规则

  词法规则(lexical rules) 约定在字符基础上的最小一级的可组合为语法元素单位直接关联的文法规则。

  约定元语言语法 <x> 表示词法元素 x::= 表示定义,| 表示析取。

基本词法构造

  文法:

<token> ::= <literal> | <$punctuator> | <$identifier>
  • 分隔符(delimiter) :代码中标记特定字符序列模式的字符序列。
  • 词素(lexeme) :代码中以分隔符确定边界的字符序列。
  • 记号(token) :词素的顶级分类。

  属于记号的语法元素可以是以下的词法分类:

  • 字面量(literal) :一种记号,参见以下描述
  • 标点(punctuator) :由派生实现定义的特定字符序列的集合,可具有一定语义功能。
  • 标识符(identifier) :除字面量和标点以外的记号

  代码中邻接的分隔符和非分隔符不构成一个词素。

  不在记号内包含的空白符是分隔符,而不是词素。

  标点是分隔符,也是词素。

  超过一个字符的标点可能在匹配字符序列确定是否构成词素时具有词法歧义。此时,应指定消歧义规则确保存在唯一可接受的匹配方式,或引起词法错误终止翻译。

  除非派生实现指定,字面量以外的记号不包含分隔符。

  记号是可能附带附加词法分析信息的词素。词法分析后得到的记号可以用词素映射到词法分类的有序对表示,但 NPL 不要求在此阶段保持分类也不限定表示的构造。

  可以保证 [ISO C++11] 的 identifier 的定义,或在上述标识符中插入字符 $ 构造得到的标识符属于 NPL 标识符。

  派生实现可定义其它能构成标识符的词素。

注释

  NPL 不指定超过一个字符的分隔符,因此默认没有词法歧义。派生实现可指定这些规则。

  NPL 是自由形式(free form) 的语言,空白符原则上不构成字面量以外的词素和语义。

转义序列和字符序列

  文法:

<char-escape-content-seq> ::= <$single-escape-char> | <$escape-prefix-char><$escape-content-seq>
<char-seq> ::= <$literal-char> | <char-escape-seq>

  包含 <char-escape-seq><char-seq> 包括:

  • \'
  • \"
  • \\
  • \a
  • \b
  • \f
  • \n
  • \r
  • \t
  • \v

  <char-seq> 的含义同 [ISO C++] 的对应转义序列。

注释 这是 [ISO C++] 的 <simple-escape-sequence> 词法分类中除了 "\?" 的情形,也是 [R6RS] 在 <string element> 中支持的字面情形。

字面量

  文法:

<literal-content> ::= <char-seq> | <literal-char-seq><literal-data>
<code-literal> ::= '<literal-content>'
<data-literal> ::= "<literal-content>"
<string-literal> ::= <code-literal> | <data-literal>
<literal> ::= <string-literal> | <$derived-impldef-literal>
  • 代码字面量(code literal) :以 ' 作为起始和结束字符的记号。
  • 数据字面量(data literal) :以 " 作为起始和结束字符的记号。
  • 字符串字面量(string literal) :代码字面量或数据字面量。
  • 扩展字面量(extended literal) :由派生实现定义的非代码字面量或数据字面量的记号。
  • 字面量(literal) :代码字面量、数据字面量、字符串字面量或扩展字面量。

  派生实现定义的解释可排除代码字面量作为字符串字面量。

原理

  传统的字面量一般是自求值项,这包括一般的字符串字面量。

  代码字面量可提供非自求值项的处理方式。

分隔符

  以下单字符标点是 NPL 图形分隔符:

  • (
  • )
  • ,
  • ;

  以下单字符标点是 NPL 分隔符:

  • NPL 图形分隔符
  • 空白符(字符串 " \n\r\t\v" 中的字符之一)

注释

  空白符同 [ISO C++] std::isspace 在 C 区域下的定义,不含空字符(null character)

原理

  NPL 图形分隔符可不和其它字符组合而作为单独的记号。因此,这不包含构成字面量的字符 ' 和字符 "

  NPL 分隔符用于一般分隔记号(而不是识别字面量)的外部描述,也没有显式地包含这些字符,但词法分析仍应把按字面量规则把这些字符作为必要时区分不同记号的边界。

词法分析

  词法分析输入翻译单元,输出记号序列。

  以下规则(按优先顺序)定义了词法分析转换输入为输出的步骤:

  • 反斜杠转义:连续两个反斜杠被替换为一个反斜杠。
  • 引号转义:反斜杠之后紧接单引号或双引号时,反斜杠会被删除。
  • 断行连接:反斜杠之后紧接换行符的双字符序列视为续行符,被删除使分隔的行组成逻辑行。
  • 字面量:未被转义的单引号或双引号后进入字面量解析状态,无视以下规则,直接逐字节输出原始输入,直至遇到对应的另一个引号。
  • 窄字符空白符替换:单字节空格、水平/垂直制表符、换行符被替换为单一空格;回车符会被忽略。
  • 原始输出:其它字符序列逐字节输出。

  不对空字符特殊处理。

注释

  因为不一定是 NPL 分隔符,转义字符不总是分隔标识符。

语法

  本节指定 NPL 作为对象语言语法

  约定元语言语法 <x> 表示语法元素 x::= 表示定义,| 表示析取。

  程序被作为语言实现组成部分的语法分析程序规约,结果能确定其和一定的语法元素匹配。

  规约时应进行语法规则的检查。

基本语法构造

  NPL 的基本语法单元是可递归构造的表达式,或派生实现指定的其它语法构造。

  构成基本语法单元的规则参见词法规则

  合式基本翻译单元应是一个或多个基本语法单元。

表达式

  文法:

<expression> ::= <atom-expression> | <composite-expression> | <list-expression>

  表达式(expression) 是受表达式语法约束的记号序列,可以是:

  构成表达式的表达式是被构成的表达式的子表达式(subexpression)

原子表达式

  文法:

<atom-expression> ::= <token>

  原子表达式不能被表示为其它表达式的语法构成形式的复合。

复合表达式

  文法:

<composite-expression> ::= <token-expression> | <expression-token>

  复合表达式是原子表达式和表达式的复合,即语法意义上的直接并置连接(juxtaposition) ,不在被复合的表达式之间存在其它记号。

  同一个表达式可能被按原子表达式出现的位置以不同的方式规约为复合表达式。允许的规约复合表达式的方式由派生实现定义。

列表表达式

  文法:

<list-expression> ::= <left-list-bound> <expression>* <right-list-bound>
<left-list-bound> ::= ( | <extended-left-list-bound>
<right-list-bound> ::= ) | <extended-right-list-bound>

  列表表达式是在其他表达式的序列(可能为空)左右附加一组 <left-list-bound><right-list-bound> 作为边界构成的表达式。

  <left-list-bound><right-list-bound> 是不同的标点。

  边界为 () 的表达式是基本列表表达式。其它可能的边界由派生实现定义,构成扩展列表表达式。

注释

  列表表达式的边界是 NPL 图形分隔符

名称

  NPL 的名称(name) 是符合语法规则约束的若干记号的集合。

  存在非空的名称集合可被作为表达式

原理

  名称的集合是广义实体实体的差集。

  语言规则对语言可表达的名称添加要求,以使语言的源代码能够直接使用名称。

  名称在源代码形式之外也可广泛存在,且能通过不唯一的方式构造。因此,语言规则允许不和源代码形式一一对应的名称。

注释

  构成名称的集合的表现形式不唯一。

  特定的名称可能为空集。

  约束通常包含顺序,即其中的记号构成确定顺序的序列。

  记号或记号集合经编码,一般可实现为可表达的字符串。

语法形式

  语法形式(syntactic form) 是词法上满足特定形式的语法构造。

  除非派生实现另行指定,语法形式总是表达式。

语句

  以派生实现定义的标点结尾的表达式称为语句(statement)

  语句语法的分组(grouping) 规则以及是否隐式地作为列表表达式求值由派生实现定义。

简单文法约定

  一个派生实现使用简单文法 NPL-GA ,若满足:

原理

  NPL-GA 允许一些典型的分析器(parser) 简化设计作为实现。

  在表达式的形式文法仅作为语法规则,使用词法分析的结果提供作为语法类别(syntactic category)词素作为输入的情况下,NPL-GA 支持 LL(1) 文法分析,即使用 NPL-GA 语法。

  若延迟复合表达式列表表达式中的选择到分析器外(之后可能由语义处理),检查语法的判定程序可进一步简化,仅判断记号 () 的匹配。

  若词法分析处理直接对 () 和进行记号化(tokenize) 标记,则 NPL-GA 分析器不需要支持其它判定。这样的分析器实现的 NPL-GA 子集等效 LL(0) 文法。但由于 NPL-GA 不限定语法元素具体数量,等效 LL(0) 分析器当且仅当输入的串终止时接受输入,因此是平凡的(trivial) ,通常不具有实际意义,因为:

  • 形式上这里只有算法步骤的多少的差异,而几乎所有实现的语言都不把它作为可观察行为
  • 即便需要统计串的长度,也应可以在之前(词法分析)计算,使用语法分析完成这个任务在此是低效的。

  反之,在分析 NPL-GA 语法前扩展其它语法预处理(preprocessing) 规则可以支持更多的文法扩展。这样的文法扩展可接受扩展的非 NPL-GA 文法,但仍允许保持语法分析器的实现使用 NPL-GA 语法。

NPL 公共语义

  NPL 的语义规则构成演绎系统(deductive system) (en-US) ,通过对翻译单元中的表达式求值表达。

  除非派生实现另行指定,仅使用表达式指定关于对象语言中的计算的语义。

  基本语义规则要求:

  NPL 允许程序具有语义等价的未指定行为。派生实现可能通过约定和限制其具体选项的选取以指定更具体的实现行为

基本语义概念

  • 区域(region) :和特定位置代码关联的有限实体集合。
  • 范围(range) :一个连续区间。
    • 此处“连续”的概念由派生实现定义,默认参照数学的形式定义。
  • 声明(declaration) :引入单一名称的表达式。
  • 声明区域(declarative region) :对某一个声明及其引入的名称,通过声明区域规则决定的范围。
  • 有效名称(valid name) :可以唯一确定指称的实体的名称。
  • 有效命名实体(valid named entity) :有效名称指称的实体。
  • 名称隐藏(name hiding) :若同一个名称在同一个位置属于超过一个声明区域,则应能通过名称隐藏规则确定唯一有效的声明以指定有效名称和对应的有效命名实体,此时有效名称隐藏其它声明区域声明的名称,有效命名实体隐藏可以使用被隐藏名称指称的实体。
  • 作用域(scope) :声明区域的子集,满足其中指定的名称是有效名称。
  • 生存期(lifetime) :逻辑上关于实体的可用性的连续区间的抽象,是一个闭集。
  • 属性(property) :实体表现的性质。
  • 同一性(identity) :实体上的一种等价关系,允许实体具有标识不相等特定的属性。
    • 注释 特定属性的例子如占据存储。
  • 对象(object) :可确定同一性的实体。
  • 值(value) :表达式关联的不可变状态。   * 作为实体,对象总是关联值作为它的内容,称为对象的值(value of object)
  • 未指定值(unspecified value) :未指定的值。
  • 修改(modification) :使状态改变的操作。
  • 作用(effect) :语言支持的一定上下文内的表达式规约蕴含的计算作用
  • 副作用(side effect) :对表达式的值以外的表示的改变的作用。
  • 幂等性(idempotence) :重复后即不改变状态的性质。
  • 项(term) :特定的演绎系统中处理的对象,是带有基本递归构造的元素,可对应语法中的表达式。
  • 子项(subterm) :具有递归形式构造的文法描述的参与构成项的项。
  • 变量(variable) :通过声明显式引入或通过演绎系统规则隐式引入的以名称指称的实体。
  • 绑定(binding) :引入变量的操作或结果,其中后者是变量的名称和引入的被变量表示的实体构成的有序对。
  • 约束变量(bound variable) :子项中出现的名称被绑定的变量,即其指称可能依赖具体上下文的变量。
    • 同名的约束变量的整体重命名替换不保证不改变指称进而可能影响语义。
  • 自由变量(free variable) :子项中出现的非约束变量。
  • 组合子(combinator) :不是变量也不含相对任何项的自由变量作为子项的项。
  • 常量(constant) :满足某种不变量的约束以和不可变状态关联的实体。具体由派生实现定义。
    • 注释 不和变量对立:蕴含不可变状态的变量可能是常量。
  • 转换(conversion) :根据基于特定等价性(假设)前提的两个项之间的自反的演绎。
  • 规约(reduction) :两个项之间的、实例是某个转换的子集的满足反自反的演绎。
  • 抽象求值(abstract evaluation) :对表达式的不取得作用的规约。
  • 具体求值(concrete evaluation) :对表达式的取得作用的规约。
  • 求值(evaluation) :抽象求值或具体求值。
    • 注释 即对表达式的规约。
  • 求值结果(evaluation result) :作用的子集,是求值得到的用于替换被求值的表达式作为它的值的实体,或其它由派生实现定义的实体。
    • 不和其它结果混淆时,简称结果(result)
    • 求值中取得求值结果中的表达式的值的过程称为值计算(value computation)
    • 值计算包含确定用于替换的实体以及替换的过程,两者之间具有因果性。
  • 值对象(value object) :表示值的对象。
    • 注释 值对象是可作为值使用的对象,例如作为求值结果的一部分。和值不同,值对象不一定是不可变状态。
  • 控制状态(control state) :实现中决定求值的状态。
    • 程序表现的控制状态通称控制(control)
    • 特定控制状态的改变使不同的实体被求值,这对应控制转移(transfer)
    • 调度(schedule) 特定可能改变控制作用的实体可决定如何转移控制状态。
    • 除非派生实现另行指定,控制状态是区分多线程执行中不同线程的状态
  • 控制作用(control effect) :引起控制状态改变的作用。
    • 在 NPL 中,控制作用是在对象或派生实现定义的实体上引起改变的副作用。
  • 相等关系(equality relationship) :定义在值的集合上的等价关系。
  • 布尔值(boolean value):逻辑真或逻辑假。
  • 谓词(predicate) :若具有结果,则结果是布尔值的实体。
  • 数据结构(data structure) :数据的构造性表示。
  • 一等实体(first-class entity) :语言表达的允许支持足够特性的子集的实体,其中特性支持包括:
    • 可作为语言中的有效命名实体。
    • 可作为语言中的值在特定的对象语言构造中使用。
    • 可表达数据结构。
    • 满足以上支持的值域中没有任意特设的限制。
    • 注释 使用的判定准则和 [RnRK] Appendix B 的 first-class object 的约定实质上一致。
  • 一等对象(first-class object) :可确定同一性的一等实体。
  • 访问(access) :从实体上取得状态或修改实体。

原理

  一些设计中,值对象是专用于表示(不可变的)值的对象(en-US) 。本设计不使用这个定义,因为:

  • 值对象作为对象,蕴含表示的目的,在语言设计而非实现的上下文中不是值的等义词。
  • 以实现角度考察值对象提供值的表示时,不关心它是否可作为一等对象而要求不可变可允许其上的副作用替换表示具有其它的值。
  • 作为对象的值,它可能因为互操作等目的在外部被直接作为其它语言实现中可作为(允许可变的)一等对象的实体。

注释

  在实现执行的上下文,生存期概念兼容 ISO/IEC 2382 的 lifetime 定义:

portion of the execution duration during which a language construct exists

  定义绑定的有序对作为抽象表示,不需要被对象语言支持。对象语言可支持其它具体的有序对数据结构。

  典型地,作用包括计算得到的值、引起的副作用以及其它可由区域和变化的状态二元组描述的实体。

  一等对象同时是对象。

  为满足可在表达式中通过求值被使用,一等实体总是能关联表达求值结果的值,称为实体的值。

表示

  表示用于表现演绎实例、具体实现及其中一部分实体的状态。

注释 其中的一部分实体可以是某个值。

  因为保证同一性,对象的值作为可变状态的表示时,即对象存储的值。

注释 变量不一定是可变状态的表示。

  外部表示内部表示是相对的。不同外部环境可以有不同的外部表示,这些外部表示相对其它外部环境而言可以不是外部表示。

  外部表示可能被读取(read) 处理为内部表示。内部表示可能被写入(write) 处理为外部表示。

  读取和写入操作的副作用分别是输入(input)输出(output)

  外部表示为元素序列时,读取和写入是非特定格式数据和元素序列之间的转换,若不含其它作用,其操作是进行反序列化(deserialize)序列化(serialize)

  内部表示为对象时,读取和写入包含对象和非特定格式数据之间的转换,其操作是进行列集(marshall)散集(unmarshall)

  除非另行指定,不要求对象语言提供内部表示到外部表示的转换。

  文法约定基准的表示作为翻译的输入。这种表示是翻译所在外部环境的外部表示,即源代码;翻译结果是对象语言代码,简称对象代码(object code) ,可以是另外的外部表示。

  翻译单元是这里被翻译的外部表示。

  由基本文法,空白符和参与的表示,不一一对应。为便于输出标准化,NPL 约定以下规范(canonical) 外部表示:

  • 对列表,输出的表示是以 () 作为边界,元素以单个 为分隔符的序列,其中的元素在括号中被递归地嵌套表示。
  • 对非列表的存在唯一的对应词法形式(如字面量)的值,输出这个值的词法形式。
  • 其它值的外部表示未指定

  谓词在模型中表示为数学关系、映射或单值函数;在对象语言中可有不同的表示,如函数

  其它外部表示和内部表示的外延由派生实现定义。

同像性

  外部表示和内部表示可能部分共享相同的规则。这些表示是同像的(homoiconic) 。语言支持同像的表示及其有关特性的性质是同像性(homoiconicity)

  典型地,同像性允许复用代码和数据之间的表示。特别地,同像性允许对象语言中的代码作为数据(code as data) ,而不需要显式地处理为和代码不同的数据结构,显著简化元编程的接口复杂性。

  除非另行指定,NPL 和派生实现不限制语言中任何不同表示之间可能具有的同像性。

原理

  存储程序的体系结构自然而普遍地依赖代码和数据具有相同的表示,以便有效地把存储的数据直接作为代码提供给控制部件。

  存储程序型计算机因此能自然地支持自修改代码。在更高层次的抽象中,高级语言可能改变这些性质的可用性,使其符合最小接口原则,符合安全设计的需要。

  但是,自修改程序在一些情形下仍然必要。为了通用目的,这些设计应符合易预测性,而非完全禁止。

  通过语言规则指定的同像性不具有体系结构设计时依赖的具体数据表示容易引起非预期操作的风险,有必要作为公开特性。

  不需对代码和数据分别提供不同的特性有利于语言设计和使用的简单性

  这同时使对象语言不需要提供特设的对自身的反射(reflection) 特性,因为潜在可被反射的对象伴随一般的元编程无处不在而可被随时引入或排除,直至另行指定的规则限制这种能力。

  这也是使对象语言的设计符合光滑性的主要机制。

注释

  自修改程序在一般意义下对运行时生成代码的 JIT(just-in-time) 编译器 是必要的。这有助于提升程序运行时性能。

  另行指定的规则包含显式的涉及表示的转换规则,例如语法分析等阶段可能转换外部表示为不同的(不确定种类的)内部表示,这些表示不保证其中的组成在变换前后一一对应。但是,NPL 规则没有明确指定破坏可能具有对应的规则,因此不同内部表示之间的非同像性仅在派生实现中可能指定。

演绎规则

  演绎系统具有的演绎规则决定演绎推理(deductive reasoning) 的输出。

  指定转换输入和输出之间的关系的演绎规则是转换规则。

  两两可转换的对象的传递闭包构成等价类,称为可转换等价类。除非另行指定,以下只讨论具有单一可转换等价类的转换规则演绎系统,即(抽象)重写系统(rewriting system)

  对象之间的转换保持某种等价关系的等价变换(transformation) 。对象之间的规约是其中的子集,即以存在等价关系的一个对象替代另一个对象的有向转换。

  若两个对象具有规约到相同结果的变换,这两个对象可连接的(joinable)

  若任意两个对象等价蕴含对象可连接,则此重写系统具有 Church–Rosser 属性(Church–Rosser property)

  若可从任意一个对象规约到的任意两个对象可连接,则重写系统具有汇聚性(confluence)

  若可从任意一个对象的一步规约到的任意两个对象可连接,则重写系统具有局部汇聚性(local confluence) ,或称为弱汇聚性(weak confluence)

  若可从一个对象规约到的任意两个对象可连接,则此对象具有汇聚性。

  若可从一个对象的一步规约到的任意两个对象可连接,则此对象具有局部汇聚性,或称为弱汇聚性。

  规约中可包括涉及实现环境的交互。

  若规约用于求值,汇聚性限定为:满足任意以此规则变换前和变换后的项被分别规约时,两者的作用相等。

项重写系统

  作为重写系统的实例,一个项重写系统(term rewriting system) 包含以下组成:

  • 语法元素(syntactic element) 的集合。
    • 项及其子项是语法元素的非空的串。
  • 辅助语义函数(auxiliary semantic function) 的集合。
    • 可通过语义变量(semantic variable) 指称其中的元素。
  • 重写规则(rewrite rule) 的集合。
    • 重写规则指定重写(rewrite) :接收项输入并产生作为重写的输入的项,和被重写的项之间满足某种等价关系重写关系(rewrite relation)
    • 重写规则集合以包含语法元素和语义变量的重写关系在元语言中表达为模式(scheme)

  语法上蕴含自由变量的项是开项(open term)闭项(closed term) 是开项在项上的补集。

  为表达计算,限制特定的重写关系使之不满足自反性,得到规约关系(reduction relation) ,即指定规约。对应地,双向的重写规则限制为其子集的单向的规约规则(reduction rule) 。经限制的系统是项规约系统(term reduction system)

  规约关系视为表达计算查询(computational query) 的项和答案(answer) 的项之间的映射。此时,项规约系统被作为一种计算模型

  • 注释 为表达计算的答案的确定性,需要确保规约可能取得范式

  一般地,项规约系统关联的结构总称为演算(calculus) 。

  对每个演算,存在和项对应的上下文(context) 。元语言中,一般的上下文以语义变量 C 表示,形式化为具有元变量(meta variable) □ 的以下构造:

C ::= □ | ...

  其中 ... 是演算支持的项的语法中替换子项得到的对应构造。

  一般的项记作语义变量 T ,则 C[T] 表示上下文 C 中作为元变量通过语法代换(syntactic replacement) 为项 T 的结果,它是一个项。

  作为对象语言的变量的项可依赖不同的上下文指称不同的实体。

  一个变量 x 被上下文 C 捕获(capture) ,若对任意 x 是其自由变量的项 TT 中自由出现的 xC[T] 中是约束变量

  • 注释 C 中仍可因自由出现的 x 而使 xC[T] 中的自由变量。

注释 例如,对作为对象语言的 λ 演算,语义变量 x 表示约束变量,其上下文为:`C ::= □ | (CT) | (TC) | (λx.C) 。

状态和行为

  状态不变蕴含语言规则中或可选地由实现定义的等价关系决定。

  除非派生实现另行指定,约定:

  • 实现行为总是可使用状态进行描述。
  • 存在副作用可观察(observable) 行为的必要条件。
  • 在实现外部访问某个状态的操作(输入/输出操作)是副作用。

  若存在状态等价性以外描述的行为描述,由派生实现指定。

  可观察行为如有其它外延,由派生实现指定;否则存在副作用是存在可观察行为的充分条件。

  实现应满足实现行为和语义蕴含的可观察行为等价。除派生实现指定的更特定的具体行为等价性外,其余的行为等价性未指定

  实现可支持实体具有对外部不引起可观察行为差异的隐藏状态(hidden state)

  隐藏状态和程序约定的一些状态作为管理状态(administrative state) ,以隐藏局部的状态变化对程序中其它状态的影响。

  非管理状态是数据状态(data state)

原理

  形式上,可观察的性质影响特定的项的操作等价性(operational equivalence) :替换操作等价的项得到的两个规约在可观察性质上是等价的,即两个规约的结果相等(对应行为不可分辨)。因此,可观察的性质可形式化为作为这些等价规约的结果的参数。

  最简单的做法,如 [Shu10] §8.3.2 把具有可观察性质的项处理为常量语法域(syntactic domain) ,不需要附加定义相等性或影响其它规约规则。

  对语义蕴含的可观察行为等价的要求指定了允许实现进行语义保持变换(semantic preserving transformation) 不能修改可观察性质的内涵,进而明确了实现对程序的可优化的界限。

  数据状态和管理状态的分类类似 [RnRK] 中改变对象的性质上对状态的划分,但不仅仅应用在关于改变对象的判断上。

  改变对象意义上和 [RnRK] 对应的具体实例是实体的不可变性

注释

  关于实体的状态,参见实体的等价性

  不严格要求实现行为和抽象机语义蕴含的所有推论一致。

  NPL 派生实现不保证是纯函数式语言,其中的计算允许描述状态的改变。表达式的求值的作用和 [ISO C] 以及 [ISO C++] 类似。不同的是,本文档的定义明确指定控制作用的更一般外延:改变控制状态,即便这些状态并非从属一等实体。特别地,最简单的条件分支也明确具有副作用。

作用使用原则

  派生实现可定义其它的作用。

  在推理实现行为时,副作用应仅在必要时引入。

  作用具有种类(kind)值计算是作用的种类。

  副作用中,对象的改变是一种作用的种类。

  是否存在副作用是互斥的,即一种作用不可能同时是副作用和不是副作用。其它作用的种类可能相交,即可能同属不同的作用。

  派生实现可定义其它作用的种类。

  求值可引起副作用的起始(initiation) 。副作用的存在(如改变状态)可继续保持到求值结束后,并可影响可观察行为

  副作用的完成(completed) 即副作用的存在的终止(如改变状态完成)。

  引起作用的求值蕴含(imply) 求值关联的作用,以及其中蕴含的副作用的起始决定的其它作用。派生实现可定义特定的求值使之蕴含的其它的作用。

  作用之间可存在等价关系。等价的作用相互替换不影响可观察行为。

原理

  允许派生实现定义不同的作用以维护变化的自由

  不同副作用行为的影响可能依赖作用之间的顺序。

  因此,副作用应仅在必要时引入,不能在推理行为时无中生有(out of thin air) ,除非证明引入的副作用不蕴含被许可的等价的实现行为以外的其它行为。通常需明确区分是否依赖副作用以避免非预期的行为。这有助于保持易预测性可组合性

  NPL 及其派生实现中的作用可描述一般的计算作用,不限定作用的种类的外延。

  明确副作用的起始是必要的,因为语言至少需要支持允许无法反馈外部状态完成改变的副作用,即 I/O 操作,此时副作用的存在应被允许保持到求值结束后,否则求值无法终止而被阻塞(blocked)

  副作用的完成是和起始相对的概念,在讨论有关顺序时可能实用。

注释

  派生实现可定义的其它的作用可能是副作用。

  副作用的起始在 [ISO C++] 的关于求值(引起的作用)的规则中同样被明确。

实体语义

  实体是语言中主要表达的目标。

  本节提供和实体相关的公共定义和语义规则,并归纳关于一等实体一等对象的性质。

  除非另行指定,语言中不引入非一等实体。仅在特定局部上下文中操作非一等实体。

原理

  限制非一等实体出现的规则有助于统一性

注释

  根据一等实体和一等对象规则 G1a 是限制非一等实体的规则的推论。

  一等实体的一等(first-class) 性质体现在语言支持的操作限制足够小,使之实例的全集可以涵盖任意求值上下文中。

  一个一等性质的反例是 [ISO C] 的数组类型的无法作为函数的形式参数。推论:[ISO C] 的数组对象不是一等对象。

实体的等价性

  等价谓词(equivalence predicate) 是判断等价关系谓词

  等价谓词可定义一些等价类划分。

  语言提供等价谓词判断两个项之间是否满足等价关系,满足判断等价关系的需要。

  作用于实体的值的等价谓词(若存在)定义实体的相等(equality) 关系。

注释 这类似一般的值的集合上可能存在的相等关系。

  决定相等关系的谓词是相等谓词,可判断实体和实体的值相等(equal)

  除非另行指定,默认实体上的具体等价关系是实体的同一性

  对象语言不要求提供默认的具体等价关系,即任意两个实体不一定可以比较等价。

  已知可比较等价的任意实体之间的等价关系也不具有唯一性。

  一般地,设计等价谓词需注意避免一些现实的使用困难,如关于相等性的困难

  为使等价关系在实体全集上良定义,等价谓词可能在特定情形弱化为同一性。

  一般地,弱化应具有可用性理由,这可能和既有等价谓词和等价关系的蕴含的设计相关。

原理

  等价谓词在避免依赖良序(well-ordering) 和良基(well-founded) 的理论中满足最小依赖原则,尽管其实现仍可能依赖序关系。

  等价谓词的用途和上下文相关。

  同一性在作为等价关系蕴含实体上的任何其它等价关系。被蕴含的等价关系可具有更多的限制条件。

  实体的同一性是普遍的,但不是普适的,它仍不足以在所有上下文中都被关注。

  同时,同一性无法保证在对象语言的所有实体上被实现;否则,会引起根本上显著的问题,限制语言的可扩展性,而和通用目的语言的一般属性冲突:

  决定普适的等价谓词中蕴含的统一的等价关系是不可能的,因此语言中应允许共存多个等价谓词。具体等价谓词的设计可由派生实现及语言的用户提供。

  等价谓词设计中弱化等价性的一个例子是 [R6RS] 的记录(record) 对象的相等性

注释

  实体的同一性是实体上的可用于定义状态不变的等价关系的例子。它蕴含了实体没有被替换为不同的实体的判断,满足保持这种判断的不变量

  关于实体的关联的是相对同一性更弱的等价关系。因为不可分的同一性,同一实体蕴含其值相等。

  一些情况部分值的集合不满足数学意义上的等价(如浮点数的 NaN ),但在此忽略这种可被单独扩展的情况。

  以下不同准则的操作是相等关系的实例(参见 [EGAL] ):

  • 抽象相等(abstract equality)
  • 引用相等(reference equality)
  • EGAL ([EGAL])

实体的同一性

  同一性是实体上的等价关系的一个主要实例。

  同一性决定的等价类之间的实体相同,即其整体和任意的属性在任意上下文中等价。

  相同的实体在语言中不需要被区分,可直接替换而不影响程序的语义和行为。后者蕴含可观察行为等价

  实体的同一性可体现在以下隐含的默认规则:

  • 不同上下文的实体默认相互独立而不保证能被视为相同(在任意上下文中等价)。
  • 通过语言构造引入的超过一个实体,默认为不相同的实体。
  • 除非另行指定,表示具有同一性的实体的语言构造和其它实体不被要求共享指称相同的具有同一性的实体。

  语言在一等实体上提供的同一性的具体判断依据和具体语言支持的特性相关。

原理

  同一性决定任意两个实体可在语言中不依赖具体操作的行为被直接区分,即满足 Leibneiz 法则(Leibneiz's law) ,或称为不可分的同一性

  基于这个性质,可在实体上定义和 [So90] 相容的更强的(不依赖语言设计中不存在副作用的)引用透明性(referential transparency)

  同一性的引入默认是名义的,即断言具有同一性的实体和其它实体上的行为相互独立,而不需要附加证明。这种假设避免了一般地证明任意实体具有同一性的困难。

  若不依赖直接在实体上标记等价类等依赖名义同一性假设的方法,证明一个实体具有同一性而非已知的其它实体,需证明任意的其它允许在程序中构造的实体和这个实体上的任意作用的可观察行为无关。在不限定具体的计算作用属于会影响可观察行为的计算作用的确切集合时,这是计算上不可能的。因此,支持这类证明会有效地限制语言在支持不同的计算作用种类上的可扩展性

  反之,从不同的对象上取消同一性(而允许实现共享资源等目的)一般是容易的:只要证明不存在影响可观察行为的计算作用即可。这种证明可以由程序名义地表达,例如标记某个实体上只涉及纯计算而没有副作用。

  另一方面,这也提示纯计算在各种计算作用中具有的特殊性不足以使其作为唯一的可扩展配置的起点。

  最平凡的起点应是没有任何计算作用的空计算。这无法表达计算,而必须要求扩展才具有实用性。而通用目的语言需要支持一般的计算作用,这同时包含支持纯计算。

  从一般的计算作用排除副作用而得到纯计算,只需要添加可被系统证明的假设,这种机制可以嵌入到系统的规约规则中;而以支持纯计算的系统扩展表达一般的计算,需要引入不足以被对象语言求值规则描述其语义的间接表示(即需要被规约以外的规则翻译),并暴露更多和表达一般计算的目的无关的实现细节。

注释

  按不可分的同一性,实体的属性在形式逻辑中通过量化的谓词判断而实现。

  和不可分的同一性相对,存在同一性的不可分性(the indiscernibility of identicals) 。两者可被二阶语言形式地描述。

  对象语言可提供同一性的相关操作,如:

  • [ISO C] 的非空对象指针的比较操作比较指向的相同类型对象的同一性。
  • [RnRS] 和 [RnRK] 的 eq? 过程/应用子比较两个操作数的同一性。

实体的不可变性

  通过特定的等价关系可定义具体的不可变状态的集合。

  这些集合可用于定义以这些状态为值的实体的不可变性(immutability) ,进而定义不保持可变性的改变操作和具体的其中可能影响可观察行为修改操作。

  通过限定不同的修改操作,定义不同的可修改性(modifiability) 和对立的不可修改性(nonmodifiability)

  通过明确不可修改性拒绝支持修改操作(例如通过通过实体的类型检查拒绝特定的修改操作),或通过不提供修改操作,语义规则保证实体不被修改操作改变状态。

注释 例如,关于 [ISO C++] 的非类且非数组类型的纯右值不可修改,尽管要求非纯右值的语义规则可被视为是一种类型检查。

  (不依赖和影响实体同一性的)同一个实体上的修改操作是改变操作。只有具有可变状态的实体可能支持这些操作。

  不论是否能区分同一性,实体可能关联不排除影响可观察行为的可变状态。

  一般地,一个实体不一定保证可区分是否具有不可变性以及具有何种不可变性(也蕴含一般不可区分可修改性),因为不可变性依赖实体的表示进行约定。

  改变操作可能继续保持实体不变。

  潜在引起实体的一些内部状态的变化的操作可不被视为影响不可变性而不被视为实体的(整体意义上的)改变操作。这种实体具有内部可变性(interior mutability)

  可引起实体变化的状态按设计上是否隐藏局部变化分为两类:

  • 可变管理状态(mutable administrative state)
    • 可变管理状态的改变作为管理状态的改变,不被视为对象(整体)改变的对象内部状态的改变。
  • 可变数据状态(mutable data state)
    • 可变数据状态的改变是对象的改变。

  隐藏状态在可变性的意义上视为可变管理状态。

  推论:

  • 引起实体内的可变管理状态的改变的操作不一定是改变对象的操作。
  • 引起实体内的隐藏的可变状态的改变的操作不一定是修改操作。

原理

  基于等价关系而不是预设具体表示之间的相等定义可变性,避免抽象的目的(如封装性依赖特定相等关系的实现细节,支持开放世界假定

  这种设计的一类典型反例是在预设排除副作用的纯的的设计为基础进行扩展定义改变操作,包括:

  • 默认使用不可变数据结构,并在此基础上扩展出可变的数据结构(如 [Rust] )。
  • 默认支持保证排除副作用的纯求值,仅在有限的上下文中通过特定构造模拟支持非纯求值(如 Haskell 等纯函数式语言)。

  一般地,这类策略对通用目的语言是过度设计,因为这实质上要求所有不存在改变操作的实体操作都完全排除副作用,不支持指定不同类别或层次保留不同改变操作并划分不同等价类的可能性,而限制表达的能力或增加实现相同抽象的复杂性。

  关联可变状态的实体通常是对象,因为支持区分同一性而能支持发生在不同实体上的作用引起独立的状态的改变而分别影响可观察行为,但这并非绝对。只要允许构造出按等价关系判断具有不相同状态,非对象实体仍可支持内部可变性等不能排除影响可观察行为的性质。这不通过需要区分同一性的状态改变。

  不区分同一性允许实现任选其中的实例代替其它实例。因此,在抽象机语义上依赖这些实体的不同等价状态表现的所有良定义行为都应被允许,即未指定行为。

  内部可变性同 [Rust] 的 RefCell<T> 等使用的模式以及 [ISO C++] 的 mutable ,允许对象具有可变管理状态,而不影响依赖可变或可修改的对象整体意义上的类型检查。

  和 [Rust] 不同而和 [ISO C++] 更加类似,这里的内部可变性仅限关于对象不可变性,和对象是否被别名正交(一些实例分析参见这里)。

  但是,和 [Rust] 及 [ISO C++] 都不同,这里不要求不可变性通过类型检查强制。

注释

  可变和不可变的状态的区分类似 [RnRK] 。

  其它语言也遵循类似的设计。作为非对象实体的可变性的一个例子,C++ 引用是否要求存储未指定,尽管占用存储这一状态并非是语言支持的可变状态。这一规则直接允许 C++ 实现不需要依赖 as-if 规则即可选取占用和不占用存储的方式实现引用的实例(乃至在运行时改变选取策略),即便是否占用存储可能对应 C++ 程序的不同的可观察行为。

  改变或修改实体后,实体可能不变,即仍然具有和之前等价的状态。例如:

  • 改变操作使用等价的状态替换先前的状态。
  • 连续的改变操作使回复原始的状态,则这些改变操作的组合的作用不改变实体。

  按定义,蕴含引起表达式的值以外的改变的操作的作用是副作用。这里的改变是名义的,允许改变前后的状态等价。

  支持不同等价的不可变性的一个用例是,有序的数据结构中的键需要保持的(通过序关系定义的)等价关系和键的可修改性是两种不同的等价关系。作为它的一个具体的反例,C++ 标准库要求关联容器的键具有 const 修饰,没有区分两种等价性,导致无法修改等价的键(除非具有 mutable 数据成员),而引起一些不必要的复杂。

实体的副本

  在已知的实体以外,实体,作为其副本(copy) ,满足:

  除非另行指定,若实体的副本无法被创建,引起创建副本的操作引起错误

  若实体的副本可被创建,它可能通过:

  • 复制(copy) 实体:创建副本后,保持原实体的值不变
  • 转移(move) 实体:创建副本后,原实体被转移而具有有效但未指定(valid but unspecified) 的状态;若可能取得实体的值,其值未指定
  • 析构性转移(destructively move) 实体:创建副本后,原实体的生存期结束,不再可访问
  • 其它派生实现指定的创建实体副本的不同方式。

实体数据结构

  实体的集合上可定义关联关系:集合的包含关系或其它实现定义的严格偏序关系。被关联的实体称为子实体(subentity)

  子实体可以是作为数据结构的一部分。这种数据结构可以是一般的图(graph)

  数据结构也可在对象语言中通过实体包含关系以外的途径定义。

注释

  例如,限定包含关系构成的图中的所有权关系附加限制,详见自引用数据结构和循环引用

续延

  续延(continuation) 是特定上下文中描述未来的计算的实体。

  续延描述的计算可能在尚未发生的后继的规约中实现,在此之前可能被调度,其中可指定不同的计算内容。上下文可决定这些计算中可变的参数化部分。

  计算可通过切换续延蕴含控制状态改变而具有控制作用

  当前规约的上下文中对应的续延是当前续延(current continuation)

  按对控制的限制,续延可分为无界续延(undelimited continuation)有界续延(delimited continuation) 等不同形式。

  续延蕴含子续延(child continuation) 作为最后的一系列子计算。

  形式上,若续延表示为一个计算步骤的序列,子续延的表示是续延的表示的后缀。

  推论:无界续延的子续延是无界续延;有界续延的子续延是有界续延。

注释

  续延可由符合项规约系统规约步骤的集合或未指定的其它形式的表示

  不同形式的续延的调用都能具有类似的控制作用,但表达能力不尽相同。

  有界续延可从无界续延或有界续延通过添加续延的界限(delimiter) (或称为提示(prompt) )派生。派生的结果是原续延的一部分,表达原续延对应的计算的子计算(subcomputation) ,又称部分续延(partial continuation)

  在仅使用局部变换(local transformation) 即 Felleisen 宏表达(macro-expressible) ([Fl91]) 的意义上,[Fi94] 指出:

  • 有界续延和可变状态(存储)可实现无界续延。
  • 嵌入在语言中的任意表达计算作用单子(monad) 的构造可用有界续延实现。

一等实体和一等对象

  NPL 区分两类不同的一等实体:只关心关联(作为对象时)的值的,和同时关心作为对象的其它属性的一等对象。

  其中,后者允许更多的操作,且允许作为前者使用,反之无法直接保证:一等对象总是一等实体,一等实体不保证可作为一等对象使用。

  逻辑上,一等实体可以关联其它对象(作为一等对象时关联可以是存储)。关联的对象的(表达式相关的)值是一等实体关联的值,可对应一等对象存储值。关联的值或存储的值是一等实体或一等对象的属性

  除非派生实现指定,NPL 的一等实体都是一等对象。

  结合实体语义相关规则,存在推论:除非另行指定,语言中不引入非一等对象。

原理

  一等对象的值是一等对象的属性。

  一些设计中,显式地不区分对象和值,因为这些设计中不支持普遍的一等对象。在这些设计中,一等实体被称为一等对象。因为不保证提供其它属性,一等对象的值和一等对象也不再被区分。这有助于简单性,但阻碍实体语义的可扩展性,直接无法从语言设计中允许在一等实体中区分一等对象。因此,NPL 不使用这种设计。

注释

  派生实现可以定义非一等对象的其它一等实体。

  除非派生实现指定,非一等对象也不是一等实体。

  显式地不区分对象和值如 [RnRS] 。这些设计中,值对象若被使用,仍被作为实现细节;且因为互操作和允许支持副作用,值对象并非全部一等对象的内部表示。

一等对象的同一性

  一等对象通过保证具有同一性强调不相同的对象总是存在至少一种总是不相同的属性

  一般地,语言规则选取其中一种属性作为名义(nominal) 同一性属性。

  一等对象具有名义同一性,定义为可比较名义同一性属性相等;名义同一性的相等即名义同一性属性相等。

  名义同一性在名义上标识相同的对象,区分不相同的对象,即便后者可能仍然在行为上完全符合同一性的要求。

  形式上,一等对象是名义同一性属性和它作为一等实体的关联的对象作为非对象(无视同一性)的其它属性集合(如存储的值)的二元组。

  为简化设计,NPL 约定以下默认规则:

  • 除非另行指定,名义同一性属性指定为对象在抽象机语义下的存储位置。
    • 对象占据存储位置起始的若干存储。
    • 存储位置的表示未指定;派生实现可指定具体的表示。
  • 在语言规则中,一等对象满足实体的同一性的默认规则。

原理

  由语言特性而非用户程序提供表达同一性的支持是必要的,这体现在通过在通用目的语言中省略同一性的表达再由实现或用户程序引入的做法一般是不可行的:

  • Rice 定理,非平凡(non-trivial) 的程序语义性质无法被可计算地实现,而确定程序中任意对象的同一性蕴含判定“和特定程序行为一致”这种非平凡语义性质,无法被通过证明程序行为的等价或其中的实体在任意上下文上的等价任意地引入,因此若无法确定用户程序不需要任意的同一性(这是一种平凡情形),指定“不需要引入同一性”总是只能在特定的程序上由语言设计者或用户具体地决定。
  • 作为通用目的语言若需要描述能适应语言自身实现问题的特性,总是依赖具体语言的逻辑上的直谓(predicative) 的规则(如资源抽象),除非语言规则是空集(这是一种平凡情形),这不可能完全由用户程序提供。

  语言的设计中显式区分一等实体和一等对象的支持而非只直接支持一等对象仍然是必要的,主要原因是:

可变状态和普遍性

  NPL 对一等实体提供普遍的支持。

  除非另行指定,NPL 不限制一等实体上可能具有的作用,包括副作用

原理

  一等实体的普遍支持体现在:

  • 在一般的一等实体上引入可变状态,实质上提供了一等副作用(first-class side effect) ,而不把可修改性限于特定的数据结构(如求值环境)。
  • 允许以一致的方式和实现的外部环境进行互操作,特别地,允许物理上提供状态抽象的设备实体的状态直接映射为一等对象。

注释

  特别地,一等对象默认支持可变状态。

  派生实现可附加规则改变本节中对一等对象的默认要求,提供不同的保证或性质,包括非一等对象上的其它一等实体上的不同作用。

同一性关联扩展性质

  NPL 中,对象的同一性关联的属性包括明确开始和终止的生存期

  推论:对象是表示能明确生存期开始和终止的实体。

  一等对象之间总是能准确地判断影响程序语义的同一性:仅当能证明不改变可观察行为时,两个一等对象的同一性可能未指定。

原理

  通过一等对象关联同一性,允许语言提供依赖同一性差异的特性。

注释

  同一性在这个意义上不是对象自身确定的性质(而是对象和解释对象表示的可能由外部提供的实现的共同确保的性质),不是应被隐藏的内部实现,因此 [EGAL] 中有关自我诊断(autognosis) 的结论不适用;而代理(proxy) 仍然可通过语言提供适当的隐藏同一性的手段可靠地实现。

一等状态

  确保区分同一性的状态一等状态(first-class state)

  一等对象能直接表示一等状态。

  一等状态是否通过其它特性派生是未指定的。

原理

  一等对象相对一等实体的附加规则限制集中体现在允许一等对象映射到的支持上。

  注意并非所有一等对象都需要支持一等状态;否则几乎总是会付出本不必要的代价也难以避免违反适用性;因此有必要区分一等状态的对象和非一等状态的对象。

  这种区分实质上更普遍地对具体的计算操作也存在意义,自然地引入了类似 [ISO C++] 的值类别;最简单的设计如区分左值(lvalue)右值(rvalue) 分别关联是否需要支持一等状态的对象。

  为支持一等状态,有必要支持判断两个对象的同一性,确保修改某个对象的操作不会关联到任意其它对象,以允许特定对象关联特定的一等状态。

  为允许一等状态和外部环境互操作,不能总是假定只有一类总是可被程序局部预知的修改操作(典型地,定义为“设置被引用对象”操作,如 [RnRK] §3.1 )影响状态,而应允许和特定对象关联的求值时的不透明的副作用。

  若不考虑互操作,则一等对象用有限的不同等价谓词]即能提供区分同一性的操作;否则,等价谓词的设计即便保持正交,也需区分不同的一等对象对各种副作用的不同支持情况。

  避免指定一等对象的可派生方式有助于统一性

  基于 [Fi94] ,结合可变状态能被表达为单子(如这里)的事实,有界续延可实现状态。

  相对地,基于 [Fl91] ,无界续延异常不能实现一般意义的可变状态,参见这里的推论 5.13 。

  因为同一性可以在引入状态时被编码而在之后不需改变,使用有界续延等非一等的状态可支持实现状态的同一性。因此,在此不对是否基本要求作出限定。

  但是,使用有界续延实现状态仅仅是实现细节,且通常具有一些非预期的实现性质:

  • 这在控制状态和支持一等状态的实现之间建立的不对等(地位不同,相互之间交换后不等效)的偶然耦合;这种耦合不存在简化实现等益处而具有必要性。
    • 注释 例如,一等状态可能直接使用对应的寄存器(register) 实现。实现控制状态则通常需要更复杂的实现。
    • 尽管理论可行,没有必要只是用其中一种作为另一种的实现的基础实现。
      • 在现有实现普遍提供状态的原生支持(存储器)的常见情况下,单独通过其它方式编码状态反而会付出本不必要的代价。
  • 这实质要求实现同一性无界续延具有区分同一性的能力(相当于 [ISO C++] 的左值),而引起不正交的内部设计。

  为满足非常规的实现环境或更优先的原则(如变化的自由正确性),派生实现仍可使用有界续延派生一等状态,同时提供访问更基本的不依赖可变状态的接口,以使上述影响不再是非预期的。

  用户程序仍不被禁止使用这种方式自行提供类似的实现,以确保不约定一等状态作为基本的内建特性时,语言的设计不违反 G1b

注释

  实现在一般实体上支持的隐藏状态不被程序可编程地指定,不是一等状态。

  允许和特定对象关联的求值时的不透明的副作用的一个实例是 [ISO C] 和 [ISO C++] 的 volatile 类型对象。

一等作用

  语言可指定特定的求值自动创建对象。

  基于此规则可在传递时附加不同的作用,即实现可随一等对象传递的一等作用(first-class effect)

原理

  典型地,按值传递时,被传递后的对象和实际参数表示的对象具有不同的同一性,即按值传递时创建新的对象

  基于被创建的副本不变性,这里的一等作用可包括用于维护对象的不变性作用包括可能的副作用,作为契约式编程(programming by contract) 的基础实现方式。

  这种不变性可包括对象的生存期。通过限制特定表达式求值的作用域内销毁对象以确保对象生存期有限,即基于作用域的对象管理(scope-based object management) 。

  基于作用域的对象管理可直接对应有限资源的普遍性质,使一等对象作为资源的抽象,确保资源的创建和销毁的副作用符合资源操作的语义,同时避免隐式的泄漏。

  配合一等状态,对象语言中的一等对象允许直接表示超过程序运行时自身的生存期的状态。这允许不在程序运行时持久储存的数据能直接被一等对象进行操作,而不需要依赖外部系统的约定并减少冗余操作(例如,从外部持久的“文件”上打开“流”以及其上的持久化操作),更符合简单性

注释

  这里的资源抽象的惯用法在 C++ 中称为 RAII(resource aquisition is initialization) 。

所有权抽象

  配合一等作用,实体的所有权(ownership) 自然地适用对抽象为对象的资源进行约束。

  使用对象代表资源,则所有者(owner) 约束被其所有的其它对象的创建和销毁的时机。被所有的对象的生存期是所有者的生存期的并集的子集,且:

  • 被所有的对象的生存期的起始不先序所有者的生存期起始。
  • 被所有的对象的生存期的终止不后序所有者的生存期终止。

  NPL 的设计避免要求对象语言隐含单一的根(root) 所有者作为其它资源的所有者。

原理

  避免单一所有者适应抽象不同系统的需要,并满足变化的自由

  注意规约允许蕴含非一等对象的所有者用于提供规约时不在对象语言中可抽象为一等对象访问的资源,这样的所有者不需要是全局的;若实现为在不同规约实例乃至全局共享的资源,也不应在对象语言被依赖。

  只要程序没有明确要求所有者,单一的全局所有者违反最小依赖原则,且不支持不清楚所有者状态时对特定对象之间进行所有权的局部推理(local reasoning) :

  • 这种情形若不配合原始的明确目的(而间接明确资源的所有者)的设计说明,人类读者直接阅读实现理解和验证其正确性是困难的,即损失了可读性。
    • 一种解决方式是读者自行模拟运行程序再从中推理出可简化的资源所有关系,这首先相当于要求读者模拟非确定性垃圾回收(GC, garbage collection) 的运行机制。这通常是困难的工作。
  • 而机器通常更无法推理这些问题,因为设计和抽象的目的一般不是以机器可读的方式编码的。
    • GC 可以回收资源,但无法准确统计哪些回收是必要的,也无法准确追溯原始实现并推理出应当在何种情况下静态地插入释放资源的操作,因为 GC 自始至终缺乏“允许任意延迟释放操作”以外的程序变换的保持语义不变的证明所需的程序元信息(包括目的)。

  为满足变化的自由,当需要表达局部所有权关系时,使用单一的全局所有者使用户无法直接在对象内嵌(embedding) 这种关系而需另行编码所有权信息,这存在以下问题:

  此外,即便使用时不要求区分对象的局部所有权关系,全局的分配释放机制也比局部的机制有更大的实现复杂性和约束。为实现对内部有限的资源的有效管理,局部所有权在实现中仍是必要的。

  在使用全局所有者如全局的垃圾回收的实现中,这种必要性被隐藏在全局所有者内部实现,语言的整体设计不会更简单

  使用全局所有者的资源管理假定启发式(heuristic) 策略以节约现实中无法接受的非预期性开销。这仍无法保证总是对不同的场景同样有效,以至于默认存在以下问题:

  • 设计至少违反变化的自由和简单性之一。
  • 在不引入支持用户配置策略的扩充设计时,违反变化的自由总是无法避免的。
  • 若引入其它设计支持用户配置策略,简单性违反难以避免,且实际基本上没有被避免。
  • 即便能通过扩充设计避免违反简单性,也不能避免不必要付出的代价
  • 不论是否引入扩充设计,都会使资源管理的一般开销更难以估计,而使设计整体的可用性评估更困难,容易使用户决策和避免不必要付出的代价冲突。

一等引用

  NPL 的一等对象即对象自身,不要求区分引用和被引用对象(referent) 的普遍概念。

  反之,通过使引用和其它一些非引用的对象同为一等对象,NPL 支持作为一等对象的一等引用(first-class reference)

  一等引用支持一等对象作为被引用对象。除非另行指定,若实现允许非一等对象作为被引用对象,可作为被引用对象的非一等对象由实现定义。

  特定的操作可能预期非引用,或总是隐含通过引用访问被引用对象,这不改变引用被作为一等对象使用的普遍支持。

  一等引用的相等关系定义为被引用对象的名义同一性相等

  一等对象的使用仍然可以通过要求引用访问以避免在任意上下文中需要不同的对象副本。但这并不应排除其它形式的一等对象操作。

原理

  尽管满足 [RnRK] Appendix B 的准则(criteria) ,一等对象和 [RnRK] 及 Java 等语言要求的设计不同。

  注意有引用的语言的语义中不能排除被引用对象,否则无法确定引用对象的值的表达式的求值结果(例如来自对象存储的值)以表达计算;相反,无视引用而直接对值操作仍然能实现一些足够有意义的程序。

  因此,若存在引用,无法忽略非引用(即便非引用不能在对象语言被直接使用)。

  另一方面,引用可以由不指定为引用的一般对象上添加语义规则区分,而作为一般的对象的特例。

  要求语言操作的一等对象总是关联到引用的设计实质上使对象语言的一等对象都是引用。但这不表示引用是自然的一等实体,因为引用的作用仅是操作被引用对象,不要求引用自身能被作为一等对象。

  一等引用的相等性定义允许在相等的引用上推理引用透明性

  考虑此设计决策时关注的有以下几节中的依据。其它依据参见 YSLib 项目文档 doc/NPL.txt

共享引用

  共享引用是共享资源的引用。共享的资源(通常是存储空间)自身具有同一性,以位置(location) 标记。共享不同位置(即作为不同一等对象的)的引用可能引用同一个被引用对象。

原理

  合理的共享引用可以节约实现占用的资源,提供更好的性能。但共享引用的实现仍可能有附加的开销,因此并不能保证使用共享引用一定能提供更好的性能。通常这种情形至少包括一些典型的对资源独占一次使用(具有独占所有权(unique ownership) )的情况。

  更重要地,并非任意引用的共享都不改变程序的语义和行为,不合理的使用可能造成非预期的作用。

  任意地引入共享引用而使用户不便预测其作用破坏适用性

  区分是否需要表达共享的情形一般不能由语言实现预知。和使用全局所有者的问题类似,使对象默认共享的设计若需避免违反避免不必要付出的代价,在此相对不默认共享引用的设计违反简单性

  默认共享引用可能是隐式的,即语言的实现不通过程序代码中的显式标注的操作而引入共享的引用,且往往无法保证通过一等对象上的操作避免被引用的对象被其它一等对象引用——无法使用对象语言的操作排除共享引用(即便是新创建的对象也没有保证,尽管实现上不必要)。

  在要求一等对象都是引用的设计中,一般地,只有不要求名义同一性的非对象的实体才能安全地共享引用,但在非对象实体上的类似引用的机制并没有保证通过一等引用提供为语言特性。

  其它情形中,允许引用之间的隐式的共享使不相同的对象可能共享状态而破坏同一性的行为保证:程序无法可靠地避免共享状态导致的对可观察行为的影响,此时共享状态的改变非预期地影响其它对象,其行为不具有一致性

  为了排除破坏同一性和适用性的问题,语言的设计需要限制引起问题的操作的可用性(例如,[RnRK] 和 [RnRS] 不提供使用一等引用的改变操作以保证变化能通过程序源代码中有限的语法上下文被推理),但这样的策略限制设计的通用性

  因为共享引用的影响的普遍性,不提供可避免隐式共享引用的设计的造成的缺陷也是普遍的。

  由于显式的引用可以由用户控制在局部使用,更容易推理其影响,可避免类似的缺陷。

  关于共享改变和程序无法可靠地避免共享状态导致的对可观察行为的影响,参见参见 YSLib 项目文档 doc/NPL.txt

注释

  一些语言的设计指定或隐含的规则在程序代码操作的一等对象上普遍地引入隐式共享的引用,如:

  [RnRK] 中的引用和被引用对象明确地分离,且 $define!set-car!改变操作要求设置对象引用的其它对象为特定的操作数确定的被引用对象,无法排除被设置的引用被共享;这实质要求所有可能包含其它引用的可被改变的对象中的引用都需要能构成隐式的共享。

  [RnRS] 明确指出特定的空对象的唯一性(即便因为不保证具有位置,不一定保证以位置决定的名义同一性),蕴含这些对象上总是可构造或超过一个引用必须构造隐式的共享引用;其它变量引用(variable reference) 未指定排除隐式的共享。

对象别名

  除非在语言规则中添加复杂的约束(如通过类型的机制)以证明特定上下文可避免共享引用,无法避免引用引入不必要的对象别名(aliasing)

  若公开这样的性质作为接口约束,违反最小接口原则

  隐式的共享使涉及修改的操作的特性更难设计,参见共享改变。

  关于共享改变,参见 YSLib 项目文档 doc/NPL.txt

原理

  对象别名一旦引入,通常难以在所有被别名的对象生存期结束前消除。

  证明对象不被别名是困难的,因为这逻辑上要求在局部知悉所有被别名的对象的存在性,而不具有局域性

自引用数据结构和循环引用

  特定的数据结构在逻辑上要求内部具有相互指涉的引用,即自引用(self-referencing)

  自引用可实现为一等对象集合内的循环引用(cyclic reference) ,即允许对象属于有限次迭代访问被引用对象的操作的传递闭包(非空的链(chain) ,称为引用对象链)的构造。

  NPL 的不保证支持这种方式实现自引用。

原理

  NPL 的设计不保证支持通过循环引用实现自引用,以避免一些固有缺陷。即便派生语言允许提供扩展支持,但本节讨论的原理仍然适用。

  避免自引用的构造使实体构成的数据结构由一般的退化为(可共享节点的)树形数据结构,即 DAG(Directed Acyclic Graph ,有向无环图)。

  这样的设计在实现上避免外部所有者(如全局 GC )。

  避免一般的循环引用的普遍理由是:非直谓性(impredicativity) 并非是抽象上必要的普遍特性。一般的循环引用在抽象上即应通过特殊进行归纳,这并非泄漏抽象

  反之,需求决定的抽象上不必要的情形下,假定循环引用的存在反而妨碍抽象的构造,可能避免某些有用的普遍性质(例如,保证程序可终止;另见强规范化性质),而违反简单性统一性适用性,并引起若干具体设计问题。

  关于通过任意对象支持循环引用的问题,参见 YSLib 项目文档 doc/NPL.txt

一般引用数据结构的一般实现

  通过一些替代原语,在不支持循环引用的情形仍可支持自引用数据结构。

  语言可以提供在不支持一般的循环引用的对象构造中保存无所有权的一等实体引用其它实体,构造出不蕴含所有权的仅以特定对象构成的循环引用,而在外部引入对象作为所有这些构成引用的对象的所有者的机制。

  在这个基础上,一般的自引用或循环引用需要的附加指涉仍然可通过添加不蕴含所有权语义的引用解决。这些引用是弱引用(weak reference) ,区分于具有所有权的引用是强引用(strong reference)

  强引用总是可转换为弱引用使用。弱引用通过解析(resolve) 取得强引用。解析可能失败,以允许弱引用指涉已经不存在的对象,而避免影响对象生存期和所有权关系。

  若支持这种受限形式的循环引用,具体特性由派生实现定义。

原理

  没有理由表明通过任意对象支持循环引用是自引用数据结构的唯一实现方式,不论使用自引用数据结构的普遍程度。

  自引用数据结构可通过在更高的抽象层次上编码,转换为由用户(而不是语言实现)指定明确的外部所有者的形式消除上述所有问题,同时对外部保证同等的功能正确性

  使用受限的循环引用同时避免带有所有权的循环引用也是 C 和 C++ 等语言惯用的实现图(graph) 的数据结构的合理方式。

实体类型

  NPL 不要求预设具体的实体及对象类型的设计,因此不要求用户使用语言体现整体上的可扩展性

  特别地,NPL 不要求表达式具有预设的不同类型

原理

  放弃对预设类型的要求允许由派生实现指定类型的外延而满足变化的自由

  除不必涉及引用外,[RnRK] 中定义的封装的(encapsulated) 类型的概念及类型封装性( [RnRK] 原则 G4 )仍然适用,且一般仍然需要满足;差异是派生实现因为扩展不满足的情形也不影响此实现的一致性(尽管使用扩展的程序可能不可移植)。

  尽管值类别可抽象为特殊的类型,表达式中的对象的类型和值类别的规则应分别讨论,因为两者正交:两者的确定检查机制都相互独立。

名称规则

  名称和能标识特定含义、符合名称词法约束的表达式一一对应。

  具体的外延由派生实现定义。

  表示名称的表达式不同于名称,但在无歧义时,语言中可直接以名称代指表达式和对应的词法元素。

  求值算法中对名称的处理应满足本节的要求。

原理

  名称规则约定通过程序源代码确定的静态的语法性质。

  部分规则中的概念定义和约定仅为便于描述这些性质。和这些约定对应的结构不一定需要在求值算法的实现中出现。

声明区域约定

  对引入名称 n声明 D ,对应的声明区域始于紧接 n 的位置,终于满足以下条件的记号)(若存在)或翻译单元末尾(不存在满足条件的记号 ) ):

  • 记号 ) 和与之匹配的记号 ( 构成的表达式包含 D
  • 此记号之前不存在满足上一个条件的其它的记号 )

可见名称

  名称隐藏规则:若声明 D 是表达式 E 的子集,且不存在 D 的子集声明同一个名称,则 D 声明了有效名称,隐藏了 E 中其它同名的名称。

  在声明区域中,没有被隐藏的名称是可见(visible) 的。有效名称实质蕴含可见名称。

名称解析

  名称解析(name resoultion) 是通过名称确定名称指定的实体的操作。

  不保证名称解析总是成功。

  除非另行指定,成功的名称解析没有副作用

  除非另行指定,直接作为求值算法步骤的不成功的名称解析引起错误

  一般地,名称解析包括名称验证(name verification)名称查找(name lookup) 两个阶段。

  名称验证确定名称是可见名称,同时可能排除部分无效名称。

  名称查找进一步确定名称唯一指称的实体的(蕴含确定名称有效),仅在名称验证成功后进行。

  不同名称经过名称查找的结果可能等效。等效的有效名称视为同一的,规则由派生实现定义。

  名称解析从保存名称的目标中查找名称。若查找失败,解析可继续从替代的其它目标中进行。这种机制称为重定向(redirection) 。重定向后的解析可继续包含名称验证和名称查找的步骤。

  以上约定以外的具体规则以及失败的行为由派生实现定义。

命名空间

  命名空间(namespace)实体。命名空间可以由名称指称。

  是否实现命名空间为程序中可由用户指定可变的实体及求值环境,由派生实现定义。

命名空间指称

  总是没有名称指称(denotation) 的命名空间是匿名命名空间(anonymous namespace)

  没有有效名称指称的命名空间是未命名命名空间(unnamed namespace)

  NPL 定义一个抽象的匿名命名空间,称为根命名空间(root namespace) 。未命名命名空间的支持由派生实现定义。

  NPL 约定一个在实现中的有效名称总是指称一个命名空间。有效名称指称的命名空间的同一性有效名称的同一性对应。

注释

  匿名命名空间和未命名命名空间不同。前者可能是一个系统的默认约定,一般整体唯一存在(如全局(global) 命名空间);后者只是对某些接口隐藏,可以有多个。

命名空间成员

  除了用于指称的名称外,一个命名空间可以和若干其它名称关联。

  通过派生实现定义的对命名空间的操作可以取得的名称是这个命名空间的成员(member)

  若无歧义,命名空间的成员指称的实体也称为这个命名空间的成员。

  命名空间直接包含成员,称为直接成员。

  除了根命名空间和其它派生实现定义外,命名空间可以作为另一个命名空间的成员,此时命名空间内的成员(若存在)是包含其的命名空间的间接成员。

  命名空间对成员的直接包含和间接包含总称为包含,是反自反的、反对称的、传递的二元关系。

简单名称和限定名称

  命名空间的直接成员的标识符在这个命名空间中是有效名称,称为简单名称(simple name)

  命名空间及其成员按包含关系依次枚举标识符组成的序列是一个名称,称为在这个命名空间中的限定名称(qualified name)

  根命名空间的限定名称称为全限定名称(fully qualified name)

  限定名称的语法由派生实现定义。

注释

  限定名称的语法的一个实例是标识符之间作为逻辑上的分隔符记号

规约规则和求值

  对象语言的操作语义可通过作为计算模型的项规约系统的规约规则中由规约规则描述的规约步骤(step) 指定。

  除非派生实现另行指定,规约蕴含 NPL 程序的执行,可完全表示程序执行的语义。

  推论:NPL 规约规则形式地蕴含 NPL 语义规则。

  为表达明确的目的,语言规则也可约定其它更抽象形式的求值规则,以蕴含这些规约规则,而不是直接描述规约规则的形式语义。

  描述 NPL 对象语言的操作语义也可被视为特定的对象语言,其规约可以视为求值。但除非另行指定,以下表达式仅指对象语言的表达式,其求值仅指关于对象语言中表达式的求值,而非一般的规约。

  规约规则可要求被规约的项符合一定的结构(如具有特定类型的值)作为前提,否则规约出错,程序执行中止。

  根据规约规则描述的行为是否对应对象语言中的求值,规约分为两类:表达式的求值规约管理规约

求值规约

  一个规约可以描述表达式的求值。直接表达一个表达式求值的规约是一个求值规约。

  以项重写系统描述,求值规约的输入是作为表达式的表示,称为待求值项(evaluating term)

  待求值项经求值规约取得求值结果

  除非另行指定,求值结果是通过值计算取得的值。

原理 求值结果也可能是异常退出的等其它作用对应的实体。这些求值结果可能需要派生实现定义的不同规则的处理。

  以下项称为被规约项(reduced term)

  • 待求值项。
    • 注释 因为可附加等价空求值的恒等规约,不需要区分项是否已被规约。即使表达式从未被求值,其表示也可视为待求值项。
  • 规约步骤的中间表示中完全依赖求值规约的输入的子集的项。
  • 表示求值结果的项。

  求值规约规则构成对象语言的求值算法(evaluaton algorithm)

  求值算法的输入是被求值的表达式和支持上下文相关求值中的上下文。

  求值的基本操作以满足特定规则的替换(substituion) 规则或其组合表示。

  除非另行指定,以下讨论的排除求值副作用的重写系统具有汇聚性

  这保证求值满足值替换规则:表达式的值计算通过已知的子表达式替换决定。

  除非派生实现另行指定,子表达式的值仅由求值得到。

注释 此时递归蕴含规则中的求值依赖规则是这个规则的推论。

管理规约

  求值规约以外的规约称为管理(administrative) 规约。

  管理规约可以是一个不完整的求值规约,或者和求值规约的步骤没有交集。

  管理规约可使用对象语言不可见和不可直接操作的非一等状态管理状态

  表示非一等对象的项的规约总是管理规约。

  抽象求值中不在对象语言求值结果中可表达的中间规约是管理规约实现。

注释

  管理规约描述语言的表达式以外的操作语义

  实现也可使用的管理规约描述特定于实现的(而在对象语言中未指定的)语义性质。

规约顺序

  先序(sequenced before) 关系是两个规约之间存在的一种严格偏序关系,对实现中规约之间的顺序(order) 提供约束。

  后序(sequenced after) 是先序的逆关系。

  非决定性有序(indeterminately sequenced) 是先序或后序的并集。

  无序(unsequenced) 是非决定性有序在求值二元关系全集上的补集。

  规约规则的顺序直接适用于求值,其顺序为求值顺序(evaluation order)

  规约规则的顺序也适用在能以其形式描述相对顺序的事件(event) 上。程序中蕴含的这些事件称为规约事件(reduction event) ,包括:

  一些事件的顺序是通过推理具有因果性(causality)依赖(dependency) 关系决定的,包括:

  • 规约中值计算依赖规约的输入,即被求值的表达式和其它可能影响规约的状态。
  • 被副作用的起始决定的其它作用依赖这个副作用。
  • 从一个实体上确定作为值的属性的读(read) 依赖这个属性。
  • 在一个实体上可以作为值保留的属性的写(write) 被这个属性依赖。
  • 由派生实现定义的其它情形。

注释 外部表示作为实体的读取和写入是这里的属性的特例。

  为了确定相关的值,依赖关系可直接替换为后序关系。

  由二元关系的一般性质(特别地,偏序关系的传递性),可推导其它一些事件之间的确定顺序,如同一个实体属性上的读依赖(已知的)决定了这个属性的先前的写。

  作为先序和后序的扩展,规约事件可符合在先发生(happens before)在后发生(happens after) 的严格偏序关系,满足:

  • 对同一个执行线程中的事件,在先发生和在后发生分别同先序和后序。
  • 组合在先发生或在后发生的关系的不存在环(cycle)
  • 派生实现定义的其它要求。

  NPL 约定以下非决定性规约规则:除因果性和二元关系的一般性质的推论外,任意项之间的规约之间无序。

  应用在求值顺序上,有以下推论(非决定性求值规则):除因果性和二元关系的一般性质的推论外,任意表达式的求值之间无序。

原理

  在先发生和在后发生可描述系统中的并发的事件。原始定义包括对时钟(clock) 的抽象,但此处不要求指定。

  [ISO C++] 和 [Rust] 等使用类似的方式描述并发的求值的支持。这些设计中,不同执行线程中具有特定的操作定义具体的顺序关系。其中具体规则的设计可能不同而不保证完全一一对应。

  因可扩展简单性 NPL 不在此明确指定此类具体操作,而由派生实现定义。

  及非决定性规约规则允许在语言中表达并发实现

注释

  读和写作为影响可观察行为的事件结果,具有因果性。此外,也可以抽象为计算作用并由程序操作;这里不做要求。

求值性质

  两个具体求值等价,当且仅当两者的作用相等。

  两个求值等价,当且仅当作为具体求值时等价,或其中每个求值的变换实质蕴含另一个。

  没有副作用的求值是纯的(pure)

注释 推论:纯求值仅有值计算或抽象求值。

  值为被求值的表达式自身的具体求值或不包含变换为存在不等价求值的表达式的抽象求值为恒等(identity) 求值。

  恒等的纯求值是空求值(empty evaluation)

  作用是空集的表达式求值是空作用求值(null effect evaluation)

注释 推论:空作用求值是空求值。

  语法形式固定且求值总是空求值的表达式是空表达式(empty expression) ,这仅由派生实现可选提供。

范式

  规范化形式(normalized form) ,或简称范式(normal form) ,是由派生实现定义的表示,被一组规约规则确定,满足:

  • 通过有限的规约步骤后得到。
  • 按规约规则,规范形式上不存在不和空求值等价的进一步规约。

  在具有 Church–Rosser 属性的重写系统中,一个对象若具有范式则唯一。

  表达式在得到规范形式后规约终止,且蕴含求值终止。

  得到范式的规约步骤称为规范化(normalization)

  若表达式规约总是能得到规范形式(求值总是能在有限规约步骤后终止),则具有强规范化(strong normalization) 性质。

  实现应避免引起对象语言的语义表达以外的无法保证强规范化性质的操作(如直接无条件的递归规约调用)。

  除非派生实现另行指定,不保证强规范化性质。

  保证得到范式的规约是规范化规约。

  具体求值得到的范式若可作为表达式,其求值结果是和被求值的项等价的表达式的,即仅允许恒等求值而仍是范式;这样的项称为自求值项(self-evaluating term)

  作为表达式的自求值项是自求值表达式(self-evaluating expression)

  重复求值直至取得自求值项的求值结果是最终求值结果(final evaluation result)

注释

  推论:最终求值结果上可能的求值是纯求值。因此,取得最终求值结果后,即排除具有副作用的继续求值。

规范化中间表示

  第一个子表达式(头表达式)是范式的表达式是 HNF(Head Normal Form ,头范式)。

  头表达式是可直接求值为范式的表达式是 WHNF(Weak HNF,弱头范式)。

注释 约定求值到 WHNF 提供保证强规范化性质的一般手段,可用于非严格求值

  WHNF 的头表达式是操作符(operator) ,存在对应 HNF 的头表达式的最终求值结果

注释 详见合并子

  WHNF 中除了操作符以外的子表达式是操作数(operand)

  操作数以具有限定顺序或不限定顺序的数据结构表示。

  按操作数的数据结构对应有操作数列表(operand list)操作数树(operand tree) 。其中操作数树是有限的树形数据结构的 DAG ,其具体构造和表示由派生实现定义。

注释 操作数树和 [RnRK] 类似。语言可能进一步约定有序的数据结构表示操作数的组成部分之间在求值上不等价。

  这种能以操作符和操作数的组合表达的计算形式是操作(operation)

  操作的结果(result) 是表达规约步骤得到的范式;操作的作用是取得对应结果的规约步骤的作用。

注释 函数合并求值结果中可蕴含操作的结果,也可具有其它作用。若操作的结果存在,则同时是这个合并子的调用的结果,即返回值

  若操作的结果不依赖管理规约,操作的结果和作用即这种可求值为 WHNF 表达式的求值结果和作用。

注释 另见函数值

  关于 DAG ,参见 YSLib 项目文档 doc/NPL.txt

组合求值

  表达式和子表达式之间的求值需满足一定约束。

递归蕴含规则

  表达式和子表达式之间的求值满足以下递归蕴含规则:

  • 求值依赖规则:除非另行指定,表达式被求值实质蕴含子表达式被求值。
  • 顺序依赖规则:求值子表达式的值计算先序所在的表达式的值计算。
  • 平凡求值规则:指定一个表达式是纯求值空求值对应实质蕴含其子表达式的求值被指定为纯求值或空求值。

注释

  一般地,一些求值策略可以不遵循求值依赖规则。

  顺序依赖规则是因果性的具体表现之一。对不被求值的表达式,此规则不生效。构造不同的表达式进行计算可实现和直接违反此规则等效的作用,但因为是不同的表达式,实际上不违反此规则。

  附加的顺序依赖规则可由特定的实体构成的表达式的求值隐含指定。相同的表达式可能在不同上下文中使用不同的规则。

严格性

  若表达式的任意子表达式的求值总是非空求值先序表达式求值,则这个表达式的求值是严格的(strict) ;反之,求值是非严格的(non-strict)

  推论:严格求值满足顺序依赖规则

  非严格求值在规约时可保留未引起作用(通常即未被求值)的部分子表达式,允许实现根据先序的求值作用确定的选择性求值,即包括未指定是否作为空求值的子表达式求值,如分支判断或短路求值。

注释 例如:ISO C++ 的条件表达式存在可能未被求值的操作数,属于非严格求值;++ 表达式不作为完全表达式(full expression) 时,副作用可超出此表达式的求值(不满足顺序依赖规则),也是非严格求值。

  表达式经过严格性分析(strictness analysis) 确定是否严格求值,通过严格性分析器(strictness analyzer)语义分析时实现。

  中间值(thunk) 是保留不直接实现具体求值的部分子表达式的特定的数据结构。

注释 例如,通过保留中间值待延迟求值,可实现子表达式值的按需传递

顺序求值

  明确的词法顺序可为同一个表达式的若干子表达式提供一致的有序求值策略:从左到右或从右到左。为一致性,不需要考虑其它特定顺序作为一般规则。

  递归文法表示的表达式和子表达式之间存在相对内外顺序:子表达式在表达式的内部。此求值顺序可对应表达式树的遍历顺序。

替换策略

  对应项的规约规则的表达式的重写规则由派生实现定义,典型的可选项包括:

  • 名称替换:保证替换前后项对应的名称不变。
  • 实体替换:保证替换前后项关联的实体不变。
  • 值替换:保证替换前后项关联的表达式的值满足实现定义的相等关系。这包括以下不同的变体:
    • 值副本替换:保证替换前后项关联的表达式的值满足值替换的关系,且以实现定义的方式引用不同的实体的副本
    • 引用替换:保证替换前后项关联的表达式的值满足值替换的关系,且以实现定义的方式引用同一实体。

求值策略

  组合严格、顺序求值和替换策略可得到不同性质的求值策略。

  除非派生实现约定,表达式求值策略可以随具体语法形式不同而不同。

  典型性质组合如下:

  • 严格求值:
    • 应用序(applicative order) :以最左最内(leftmost innermost) 优先的顺序求值。
      • 最左的顺序仅在操作数是有序数据结构时有意义;不考虑操作数内部构造时,仅表示操作数作为子表达式总是被求值,和严格求值等价。
    • 按值传递(pass by value) :使用值替换的严格求值。
      • 按值的副本传递(pass by value copy) :创建值的副本进行替换的严格求值。
      • 引用传递(pass by reference) :使用引用替换的严格求值。
    • 共享对象传递(pass by shared object) :使用的共享机制以及对象和值或引用的关系由派生实现定义。
    • 部分求值(partial evaluation) :允许求值分为多个阶段(phase) 分别进行。
  • 非严格求值:
    • 正规序(normal order) :以最左最外(leftmost outmost) 优先的顺序求值。
      • 最左的顺序的意义同应用序。
    • 按名传递(pass by name) :使用名称替换且保持作为名称的表达式最后被替换的求值。
    • 按需传递(pass by need) :按名传递但允许合并作用相同的表达式。
  • 非决定性求值:
    • 完全归约(full reduction) :替换不受到作用之间的依赖的限制。
    • 按预期传递(pass by future) :并发的按名传递,在需要使用参数的值时同步。
    • 乐观求值(optimistic evaluation) :部分子表达式在未指定时机部分求值的按需求值,若超出约定时限则放弃并回退到按需求值。

可选求值规则

  应满足的本节上述约定的最小求值规则和语义外的具体求值的规则和语义由派生实现定义。

  派生实现的求值可满足以下节指定语义,此时应满足其中约定的规则。

  若可选求值规则逻辑上蕴含规约规则,则被蕴含的规约规则的直接表述可在语言规则中被省略。

上下文相关求值

  在被求值的表达式以外,对应的规约规则在实现此规约的元语言中可能是上下文相关的,这种附加依赖的上下文为求值上下文(evaluation context)

  求值上下文被作为元语言实现对象求值规则时的输入,可指定项所在的位置等不被被规约的项必然蕴含的附加信息。

  由派生实现定义的特定求值上下文称为尾上下文(tail context) 。以尾上下文求值可提供附加的保证。

  作为项重写系统的上下文的实例,元语言中,一般的求值上下文 C 形式化为具有占位符 [] 和可选前缀 v 及可选后缀 e 的递归组合的串:

C ::= [] | Ce | vC

  其中 e 是被求值表达式,v 是作为范式的值。

  除非另行指定,NPL 对象语言的求值算法使用的求值上下文总是求值环境

原理

  通过附加适当的求值规则保证对象语言中的表达式总是可唯一地被分解为这种表示,抽象的求值上下文可直接实现对象语言的求值。但语义描述和实现的基准都以抽象机替代,因为:

  • 抽象机语义允许不依赖源程序表示和构造(如特定的表达式的文法)。
  • 这种分解一般要求遍历对象语言的源程序而难以具有较好的可实现性质,如计算复杂度
  • 为满足良好的可实现性质,需描述实现中可能具有的离散状态与只和其中个别状态关联的局部的求值规则时,这种分解通常会渐进演化为某种抽象机的表示。

注释

  使用求值环境作为默认的上下文确保一般的求值总是能支持变量的绑定

  对象语言的实现同时能够支持其它上下文,即使它不在求值算法中出现。这样的上下文可能被求值上下文蕴含而可被推理确定。

λ 完备语义和对应语法

  作为通用语言,求值规则表达的系统可具有和无类型 λ 演算对应的形式和计算能力。

  基于此语义的派生实现应允许以下几种互不相交的表达式集合:

  NPL 不要求以上表达式中函数以外的表达式求值的强规范化

注释

  无类型 λ 演算保证名称表达式(变量)和函数( λ 抽象)的规约的强规范化,但不保证函数应用规约的强规范化。

  扩展的 λ 演算(如简单类型 λ 演算)可保证规约函数应用的强规范化。

名称表达式

  名称表达式(name expression) 是表示变量的 λ 项。

  原子表达式的由派生实现定义的非空子集是名称表达式。其它作为名称表达式的表达式语法形式由派生实现定义。

  名称表达式不被进一步规约;其求值是值替换规则的平凡形式。

函数

  函数(function) 是一种参与特定规约规则的实体,也可以指求值为函数实体的表达式。

  一般地,函数表达式在 WHNF 下作为操作符被求值,其最终求值结果为函数实体,或函数对象(若函数在语言中允许作为对象)。

  NPL 中,作为一等对象的函数表达式的最终求值结果是合并子

  一个函数表达式是以下两种表达式之一:

  • 保持等价地求值到其它函数表达式上的名称表达式,称为具名函数表达式(named function expression) ,简称具名函数(named function)
  • 满足本节以下规则的由派生实现定义的匿名函数表达式(anonymous function expression) ,简称匿名函数(anonymous function)

  函数应确定替换重写规则被替换的目标,即函数体(function body)

  除非派生实现另行指定,函数不需要被进一步规约,此时其求值是值替换规则的平凡形式。

  在类型系统中,函数可被指派函数类型(function type) 。函数类型能蕴含参数和结果的类型

注释 例如,在简单类型 λ 演算中,函数类型是类型构造器 组合输入(参数)和结果(输出)类型的复合类型

函数内部的变量

  匿名函数可以显式指定(绑定(bind) )包含若干变量使之成为约束变量的语法构造。

  通过创建函数时的显式的语法构造引入的这种变量称为函数的形式参数(formal parameter, parameter)

  除绑定外,匿名函数蕴含上下文可以捕获若干在函数体以外的同名的自由变量

  通过绑定或捕获引入的变量允许在函数体中允许使用。

  使用词法作用域时,若匿名函数所在作用域的存在同名的名称,则被捕获的名称被隐藏。形式参数隐藏被捕获的变量名

  派生实现的语义规则应满足和 λ 演算的语义的 α-转换(alpha-conversion) 规则不矛盾。

注释 Vau 演算在没有限定环境时不考虑一般意义上的自由变量。

  函数应用的求值决定被绑定的变量和函数体内的变量之间的关系,参见函数合并。此时,求值策略蕴含的替换策略蕴含被绑定的变量和函数体内的变量之间的同一性

  类似地,在被捕获的变量到函数体内捕获的变量之间,也有和替换策略一一对应的不同捕获策略。

  除非另行指定,变量被按引用捕获(captured by reference) 而非按值的副本捕获(captured by value copy) ,即通过捕获引入的变量是被捕获变量的引用而不是副本。

原理

  捕获为引用而不是副本,保持被捕获的变量和函数体内同名变量的同一性,在实体是对象时不影响可观察行为。若这些捕获未被使用,可被实现直接移除。

过程

  过程(procedure) 是操作符具现可调用(callable) 的实体,决定特定的可提供求值的作用(包括决定求值结果)的计算

  函数表达式的最终求值结果由过程实体的作用中的结果决定,以派生实现定义的方式关联。

  通过函数表达式可指定可选的实际参数,发生过程调用(call) 。过程的调用蕴含计算。

  过程中和过程外的计算的组合满足因果性

  • 以求值描述的过程中的作用整体非后序于引起过程中作用的外部环境的计算。
  • 以求值描述的过程中的任意作用非后序于取得对应结果的值计算,即结果是决定值的作用的依赖

  主调函数(caller function)调用者(caller) 或其它引起过程中的计算的实体转移计算蕴含的控制到过程中的计算而使之进入(enter)被调用者(callee) 的过程。

  过程可能被限制只有一次(one-shot) 调用有效;其它过程是多次(multi-shot) 的。

  多次过程调用时控制可能通过调用被再次转移,即嵌套调用(nested call)

  一些被多次调用的过程可能被多次进入,即重入(reenter)

  一个调用中的重入相同或不同过程的次数称为调用的深度(depth)

  推论:嵌套调用是深度大于 1 的调用。

  通过嵌套调用直接(总是以自身作为调用者)或间接(通过其它调用者转移控制)的重入是递归调用(recursive call)

  过程可以返回(return) 取得计算的值并可同时改变控制状态,影响之后的计算。

原理

  一次过程,特别是在其内部涉及和续延闭包的实现交互时,相对多次过程可能具有因其对持有资源的要求较宽松,而具有较小的性能开销。

注释

  对象语言中的过程在描述操作语义的元语言中可表示为函数,其应用可对应对象语言中过程的隐式调用。

  违反一次过程调用有效地约束的程序典型地引起错误

  注意过程不一定可作为可被对象语言直接表达的一等(first-class) 函数,但同时在元语言中仍然可能可行。如无界续延,因为可能不符合函数的类型要求,详见续延的捕获和调用中的原理。

  一次重入的过程调用分配的资源对应一个活动记录帧

过程调用的计算顺序

  按计算的顺序约束和默认返回控制的方式,可能有不同的形式。

  例程(routine) 的求值不交叉(interleave) ,即例程中的计算和例程外的计算非决定性有序

注释 典型地,例程中的计算通过例程作为函数实体创建时的函数体确定。

  作为不同的例程,不考虑例程中的计算的续延被保存时:

  • 子例程(subroutine) 在返回一次后不重入
  • 协程(coroutine) 则可能被多次重入并引起多次返回。

  和子例程的正常控制不同,即便其中的计算不涉及显式地改变控制状态,协程可能蕴含控制从协程中的计算到协程外的计算的转移

  • 引起多次返回对应改变控制作用
  • 转移控制后,函数体中的计算被暂停(suspended)
  • 重入的协程可恢复(resume) 被暂停的计算。
    • 不排除可被重入的协程作为函数实体,是可恢复函数(resumable function)
  • 可被暂停和恢复的计算是异步的(asynchrnous) 。这和正常控制的同步的(synchronous) 的计算相对。

  一般的续延支持返回多次并可能支持和调用者并发的计算,包括异步的计算;而协程蕴含的控制作用的改变对应不同续延的替换,也能实现类似的支持。

  语言的语法可显式指定例程创建协程,也可以当前的控制状态创建和现有的例程没有直接对应的协程。后者类似续延捕获

  NPL 支持函数求值得到过程。对象语言中的过程可能支持使用这些形式的一种或多种,具体形式由派生实现指定。

  协程可能限制转移向下一步骤的计算转移的方向,即调用者和被调用者被通过创建其的语法构造确定,而不能在之后改变。

  根据是否只提供一种不区分转移方向的原语,协程分为对称(symmetric)非对称(asymmetric) 协程:

  • 对称协程转移控制到另一个协程,不需要单独区分不同的操作。
  • 非对称协程对控制的转移分为调用(invoke)出让(yield) 操作,其中:
    • 调用操作从调用者转移控制到被调用者,恢复之前保存的上下文(若有)或创建时的初始上下文。
    • 出让操作暂停和保存当前上下文并返回(转移)控制到它的调用者。
      • 一般地,转移控制的具体时机未指定,可蕴含(对应续延的)调度并发执行。
  • 一些协程称为半(semi) 异步协程(半协程),以体现实现异步计算的控制转移形势受限的非典型性。对应地,没有此类限制的协程被称为全(full) 异步协程(全协程)。
    • 通常半协程指对控制的转移(相对传统的例程调用)受限,不能仅通过调用而需要单独的出让操作实现计算的暂停。这是非对称协程的同义词。
    • 但半协程也可能指特指暂停在特定上下文受限的协程实现。
    • 注释 另见这里的说明。

  根据是否协程持有活动记录帧,协程分为有栈(stackful)无栈(stackless) 的。

  • 两者提供不同的资源所有权,而可能影响使用这些特性的程序中的资源的可用性
  • 特别地,无栈协程不保证活动记录的可用性,无法直接支持创建的协程作为一等对象使用。

  因为具有类似的改变控制的能力,有栈的、可作为一等对象全协程(full coroutine) 可替代一等续延

原理

  协程可视为是在计算上可表达性等价的一次续延

  • 参见这里
  • 其中,对称协程类似一次无界续延,非对称协程类似一次有界续延
    • 对称协程可通过非对称协程补充操作实现。
    • 类似地,有界续延可通过添加显式的界限实现无界续延
    • 但是,这种功能相似不表示一一对应。
      • 注释 参见以下关于出让操作使用续延实现的讨论。
  • 在核心语言支持存储(store)可修改一等状态副作用的前提下,非对称协程和对称协程在可表达性上等价。
  • 一等续延和协程在一定条件下可互相实现。
    • 对称协程可实现一次续延。
      • 注释 另见这里,但这个实现没有检查续延调用内部可能的非预期重入,且不满足 [Hi90] 中的 PTC 要求
      • 因此,作为一等对象时,协程可作为一等续延的替代实现方式。
    • 非对称协程的出让操作可通过无界续延实现。
      • 虽然可实现出让操作的续延捕获并非续延调用,但续延调用对控制的转移不必然蕴含区分调用和调用者。
        • 事实上,使用 call/cc 捕获续延创建的是无界续延。
      • 有界续延可实现无界续延,因此出让操作也可使用有界续延实现。
      • 注释 另见这里

  典型的设计中,函数表达式默认创建例程,而协程使用特设的语法标记过程得到。特设的关键字(如 yield )提供语法,对应非对称协程中的出让操作。

注释

  关于过程的参数和过程调用之间的计算顺序,参见求值策略

λ 抽象

  λ 抽象(lambda abstraction) 是 λ 演算中的基本构成之一,提供匿名函数。

注释 λ 抽象的语法包含的形式是典型的操作符。

  在原始的无类型 λ 演算中,λ 抽象不支持蕴含副作用,子表达式求值顺序任取而不改变范式的存在性和值。

  在使用热情求值的语言中,λ 抽象创建的过程是应用合并子

vau 抽象

  Vau 抽象(vau abstraction) 是 vau 演算 ([Shu10]) 中的基本构成之一。

  Vau 抽象创建的过程是操作合并子

注释 使用 vau 抽象可实现引入 λ 抽象的操作符,如 [RnRK] 提供的 $vau 操作合并子。

函数合并

  具有操作符和操作数的项的组合可被特定的方式进行规约。这种组合是函数合并(function combination) ,包含:

  • 具有至少一个约定位置的子项 E1复合表达式 E ,当且仅当 E1 是被求值作为操作符的函数时,E函数合并表达式(function combination expression)
  • 其它具有操作符和操作数的项是非表达式形式的函数合并。以下操作符和操作数记作 E1E2

  以下规则中,非表达式形式的函数合并也可被视为表达式求值。

  求值函数合并时,子项 E1 总是被求值。

  除 E1 外表达式的剩余子项 E2操作数,在 E 被求值时以操作数决定的值等效替换(substitute) 函数的形式参数

  替换形式参数的值是实际参数(actual argument, argument)

  函数合并的求值是值替换规则的非平凡形式。

  若替换操作数 E2 在合并中被求值,函数合并 E 是函数应用表达式,简称函数应用(function application)

  若操作符是 λ 抽象,E2 视为一个整体,则函数应用替换规则对应 λ 演算的 β-规约(beta-reduction) 规则。

  其它函数合并使用的替换规则由派生实现指定。

  派生实现应指定函数合并规约的结果是规范形式,它对应的值是函数合并的求值结果替换被求值的表达式的实体,称为函数值(function value)

  函数应用匹配实际参数和对应的引入形式参数的构造。匹配可能失败。确定匹配参数成功的条件是等价关系,称为参数匹配一致性,由参数匹配的等价关系指定。

  匹配成功的每个实际参数和被匹配的目标(可能是形式参数)具有一对一或多对一的对应关系。

  伴随参数匹配,实现可引入其它必要的操作(如为匹配分配资源和确定上述对应关系)。这些操作可具有和确定参数对应关系的匹配之间非决定性有序的副作用。

  仅当上述必要操作及所有实际参数的匹配成功,替换 E1 决定的某个关联表达式中和形式参数结构一致的子表达式为实际参数。替换参数的结构一致性是等价关系。

  表达式相等蕴含参数匹配一致性和替换结构一致性。实现可分别定义其它规则扩充这些等价关系的外延。

  替换参数的值蕴含对实际参数的计算的依赖,即参数若被求值,其值计算先序函数应用的求值;但其它求值顺序没有保证。

注释

  一般地,根据 E1 的值,操作数或操作数的值计算的结果被作为实际参数。

  过程及其调用在其操作语义的元语言中通常表达为函数及函数合并。

  若过程的结果被忽略,则通常表达为单元类型的值。

  此外,一些语言中忽略过程的结果是空类型,以检查错误的使用。NPL 不要求语言具有静态类型规则,也不要求这些检查。

函数调用

  求值函数合并包含子表达式的求值:总是求值操作符,并可能求值操作数。若这些求值都没有退出,则发生函数调用(call) ,函数是被调函数(called function)

  若被调函数存在形式参数,函数调用首先以操作数的直接子表达式作为实际参数,匹配实际参数和形式参数。

  若实际参数匹配的目标可指定一个变量,则伴随参数匹配的操作包括以特定规则绑定的形式参数。

  绑定的实际参数和对应的形式参数作为不同的实体时,作为伴随参数匹配的必要操作的一部分,发生参数传递(parameter passing) 。参数传递使形式参数具有作为实际参数值的副本。参数传递可能使和实际参数相关的资源被复制或转移。

  实现在函数合并的求值中应提供函数调用的支持。

  函数调用确定副作用的边界:保证参数表达式在函数应用被求值之前被求值。

  在控制返回时,函数调用内部确定的值最终替换被求值的函数合并而作为函数值,即为返回值(return value)

  若函数是过程,对应的函数调用是过程调用(procedure call)

  若一个函数的调用仍待返回,则该函数调用是活动的(active)

  调用总是不蕴含非纯求值的函数是纯函数(pure function)

  函数调用的中蕴含的求值对应的规约步骤的集合是它的动态范围(dynamic extent)

  函数中被捕获的实体的引用和求值函数中的计算创建的对象的引用构成函数计算的结果时,引用可能逃逸(escape) ,即在调用的动态范围以外可访问。

  派生实现可能约定附加的名义特征区分其它情形相同的调用,称为调用约定(calling convention)

注释

  典型实现的函数指称过程,函数调用是过程调用。

  一般地,被调用的函数及函数调用的作用的等价性通常不能被确定。

  一个重要的子类是不能确定具体表示的情形,参见合并子。其它函数一般也有类似限制。

  关于函数调用中的求值,另见函数调用的终止保证

  和 [RnRS] 不同,动态范围仅对求值定义,而不是关于环境中的绑定显示计算作用的属性。这种属性事实上对象的生存期,仅对对象而非更一般的实体有效。

  续延可用其动态范围表示。

  本文档的动态范围的概念定义和 [RnRK] §7.1 的定义兼容,但不依赖其对续延的描述,也适用抽象机语义,是 [RnRK] 的一般化。

  [Racket] 使用求值的规约步骤在表达式上定义动态范围。NPL 不在表达式上采用类似的定义,因为:

  • 类似 [RnRK] ,NPL 强调支持对象语言中的显式求值风格及表达式求值前后的不同。
  • 类似 [RnRK] ,进一步地,NPL 派生语言(如 NPLA1 )可明确支持在对象语言中指定求值环境而改变求值的上下文,表达式不能被预期通常以上下文无关的方式被求值。

  调用约定可提升实现细节,为互操作提供接口保证,避免非预期的不兼容实现的混合。

合并子

  除非另行指定,NPL 假定函数合并满足以下典型情形,即函数合并的操作符求值为以下类型的合并子(combiner) 之一:

  • 对操作数的直接操作(而不要求对操作数求值)的合并子是操作合并子(operative combiner) ,简称操作子(operative)
  • 进行函数应用的合并子是应用合并子(applicative combiner) ,简称应用子(applicative)
  • 由派生实现定义的扩展合并子(extended combiner)

  合并子的函数应用(依赖对操作数进行至少一次求值)是合并子应用(combiner application)

  合并子应用使用应用序

  应用子总是对应一个底层(underlying) 合并子,可通过底层合并子上的一元的包装(wrap) 操作得到;其逆操作为解包装(unwrap)

  解包装结果不是扩展合并子的合并子称为真合并子(proper combiner)

  合并子上可以定义若干等价关系,这些等价关系蕴含关于函数应用替换的基本形式:

  若对任意上下文,替换一个应用中的合并子为另一个不改变函数应用替换的结果,则这两个合并子等价(对应 λ 演算的 β-等价)。

注释

  合并子被调用时通常返回且仅返回一次。

注释 详见续延的捕获和调用

  由于程序可能引入未知具体表示的合并子(如从其它模块链接),以上等价可能无法判定,不要求实现提供。

  因为本设计不依赖 λ 抽象的内部表示(特别是支持惰性求值为目的的),不依赖 η-变换的可用性,也不要求支持更强的 βη-等价。

  派生实现可按需定义较弱的等价谓词,保证其判定结果蕴含上述等价关系的结果。

续延的捕获和调用

  语言可提供作为一等实体续延一等续延(first-class continuation)

  续延的捕获(capture) 具现当前续延为对象语言中可操作的一等续延。

  类似过程,续延可被一次或多次调用,称为续延调用(continuation call)

  续延调用接受一个实际参数作为传递给后继规约步骤使用的。除非另行指定,续延参数被按值传递。被调用的续延可访问参数并执行其蕴含的其余规约步骤。

  和接受实际参数对应,续延可被假定关联一个等效的应用子,具有一个形式参数,这个应用子的底层合并子调用非正常地传递它的操作数给关联的续延。

  对象语言可支持符合函数类型要求的一等续延作为函数。作为一等续延的函数可直接作为合并子构成函数合并进行函数调用,而实现续延调用。

  除非派生实现另行指定,NPL 的一等续延不是函数。

  函数应用(如合并子调用)可隐含(非一等对象的)续延调用。

  续延调用的其它的具体形式由派生实现定义。

  除非在捕获的续延上存在特定的控制作用,合并子被调用时以当前续延返回且仅返回一次。

  类似函数应用表达式续延应用(continuation application) 表达式是求值时蕴含续延调用的表达式。

原理

  在 Scheme 中,一等续延即过程。

  在限制元语言的函数不蕴含控制作用时,类似 Scheme 等支持的无界续延不是函数。一个理由不能以常规方式为无界续延指定是函数类型。参见这里的介绍。

  在 Kernel 和其它一些语言中,续延不是过程,而具有不同的名义类型。这种不同于 Scheme 的设计是有意的。

  NPL 一等续延不限制是否和函数类型同一,因此无界续延仍可被视为函数(或更确切地,即程序入口作为边界的有界续延)。

  类似 [RnRK] 的设计,因为一等续延的调用可引起和更常见的过程调用显著不同的控制作用,续延调用有必要和过程调用在对象语言的语法上显式区分以满足易预测性,因此一等续延一般不是函数。

  续延关联的等效应用子的原理同 [RnRK] §7 和 §7.2.5(应用子 continuation->applicative)的原理,但略有不同:

  • 作为一等对象的续延和续延的实际参数是否求值无关,因此不是合并子,求值算法不需要支持续延作为函数合并被求值;但续延可通过特定的操作转换为应用子。
  • 续延和操作子在被调用时都接受一个实际参数对象。
    • 对前者,对象典型地表示计算的结果,即已被求值。
    • 对后者,对象是操作数。典型地,操作子作为应用子的底层合并子,操作数已被作为应用子的实际参数被求值算法求值。
    • 类似 [RnRK] 而和 [RnRS] 不同,因为函数合并可接受非真列表作为参数,非列表的操作数可以和非列表的续延实际参数直接对应。
      • 因为函数合并的这种性质,续延关联的应用子的应用和续延应用存在直接的一一对应关系。
  • 但是,为避免和简单性冲突,[RnRK] 的选择器(selector) 支持在此未被要求。

注释

  类似过程,续延及其调用在其操作语义的元语言中能表示为元语言的函数应用,通常表达为函数函数合并

  续延捕获在语法上类似函数对变量的捕获。被捕获的实体通常以引用保存。被捕获的实体通常是隐式的,即不在对象语言程序中出现。

  在支持一等续延且捕获的续延可被复制的语言中,实现需要考虑活动记录的复制,参见 [Hi90] 。

  关于控制作用,另见续延调用对程序控制的改变

活动记录

  活动的合并子分配的对象称为活动记录(activation record)

  函数调用以活动记录引用涉及的变量。每个调用的活动记录中可保存多个变量。活动记录可能因此持有状态,即便不一定可被函数调用外的操作直接修改。

  嵌套的函数调用具有多次分配的活动记录。为强调其中的对应关系,每一个调用关联其中的一个帧(frame)

  在确定一次分配的一个活动记录对应一次函数调用的实现中,一个活动记录和一个活动记录的帧同义。

  活动记录的集合可能构成特定的数据结构。例如限制只支持嵌套的子例程调用(而不支持一般的续延调用)时,具有后入先出(LIFO, last-in-first-out) 的栈的结构。

λ 求值策略

  在变量绑定值后,兼容 λ 演算规约语义(特别地,β-规约)的表达式的具体求值根据是否传递操作数对使用按需传递的情形分为三类:

  • (完全)惰性求值(lazy evaluation)
  • 部分惰性求值
  • 热情求值(eager evaluation)

  其中,惰性求值总是使用按需传递,热情求值总是不使用按需传递,部分惰性求值不总是使用或不适用按需传递。

  在保证不存在非纯求值时这些求值的计算作用没有实质差异。存在非纯求值时,使用的 λ 求值策略由派生实现定义。

  非严格求值严格蕴含惰性求值。两者经常但不总是一致,例如,实现可能并行地热情求值,并舍弃部分结果以实现非严格求值。

  热情求值蕴含严格求值。两者也经常但不总是一致,例如,实现可能使用应用序严格求值。但因为非严格的热情求值缺乏性能等可局部优化的实用动机,这种不一致的情况通常不作为附加的语言特性提供(而仅为简化实现默认作为全局策略使用)。

注释

  由于实现可能确定特定表达式的作用对约定必须保持的程序行为没有影响而可能省略求值,按抽象机语义的严格求值在实际实现中通常是不必要的。

  惰性求值可通过中间值延迟求值实现。

上下文

  上下文(context) 是表达式关联的状态的特定集合。

注释 这里不是自指概念

  一个上下文是显式的(explicit) ,当且仅当它可以通过名称表达式访问。

  一个上下文是隐式的(implicit) ,当且仅当它不是显式的。

  隐式的上下文通常是管理状态

  确定上下文的状态或对可变上下文的修改是对上下文的访问

  规约规则中,以未指定子项参数化的项是一个上下文。

  本节以外其它关于上下文的具体规则由派生实现定义。

注释

  参数化的子项可在(元语言的)语法上被表示为一个洞(hole) ,详见上下文相关求值中的语法 []

  过程实体能影响函数表达式关联的上下文,参见函数和函数应用的求值环境

求值环境

  求值环境(evaluation environment) 是在求值时可访问的隐式上下文,提供可通过名称解析访问的变量的绑定

  不和实现环境相混淆的情况下,求值环境简称(变量或对应的局部绑定所在的)为环境(environment)

  具有可见名称的绑定是可见的(visible)

  环境包含(contain) 若干个局部绑定(local binding) ,即不通过其它环境即保证可见的被绑定实体(bound entity)

  环境展示(exhibit) 可见的绑定。

  一个环境是空环境(empty environment) ,当且仅当其中包含的局部绑定集合是空集。

注释

  按绑定的定义,求值环境的局部绑定集合即变量的名称和通过声明引入的被变量表示的实体构成的映射。

  可见绑定可能被通过名称解析成功访问变量。

  包含和展示的定义同 [RnRK] 。除此之外,环境对象具有直接包含的绑定的所有权

实现环境提供的求值环境

  实现环境可能在实现以外提供附加的求值环境作为任务通信的机制,如环境变量。

  除非派生实现另行指定,语言支持的求值环境和这些机制蕴含的求值环境的交集为空。语言可以库的形式提供 API 另行支持。

函数和函数应用的求值环境

  在典型的对象语言中 λ 抽象中指定的替换构造具有局部作用域(local scoping) ,其中可访问 λ 抽象外部词法意义上包含的(enclosing) 求值环境的变量,对应求值环境为局部环境(local environment)

  在基于词法作用域(lexical scoping) 的对象语言中,引入 λ 抽象对应的语言构造支持捕获引入函数时所在的作用域的环境,称为静态环境(static environment)

  相对地,动态作用域(dynamic scoping) 根据求值时的状态指定指称。

  Vau 抽象进一步支持在局部环境中提供访问函数应用时的求值环境,即动态环境(dynamic environment) 的机制。

  除非另行指定,按词法闭包(lexical closure) 规则捕获,即只根据词法作用域确定捕获的指称;若需要支持依赖求值状态动态确定指称时,使用派生实现提供的对求值环境的操作,而不依赖动态作用域。

  作为过程的实现,词法闭包规则捕获实体创建闭包(closure)

  除非另行指定,NPL 只存在一种作用域,即所有作用域都使用相同的名称解析和捕获规则。

注释

  历史上,闭包首先在 SECD 抽象机中引入。术语闭包来自 λ 演算的闭项

互操作上下文

  用于互操作的和求值关联的隐式上下文是互操作上下文(interoperation context)

  除非派生实现另行指定,语言不提供访问互操作上下文的公开接口。

注释

  一个典型的实例:由 ISA约定的通用架构寄存器的状态,可能需要在函数调用或任务切换过程中保存和重置。

类型

  类型(type) 是上下文中和特定的实体直接关联或间接关联的元素,满足某个执行阶段的不变量约束

  类型规则(type rule) 是和类型相关的对象语言的语义规则。

  实体关联的类型可能被显式地指定,或通过隐式的限定规则推断确定。符合指定和限定要求的类型可有任意多个。

  实体的类型是被显式指定的实体关联的类型。实体具有实体的类型以及通过其它规则限定的类型。实体是类型的实例(instance)

  类型可用集合表示。集合的元素是具有其表示的类型的实体。

  表示类型的集合为空时,表示类型没有实例,是空类型(empty type)

  推论:由集合的形式表达,空类型是唯一的。

  表示类型的集合只有一个元素时,类型只有一个不可区分的实例,这样的类型是单元类型(unit type)

  和表达式直接关联的类型满足起始阶段不变量约束,称为静态类型(static type)

  和表达式的关联的类型满足运行阶段的不变量约束,称为动态类型(dynamic type)

  其它可能存在类型或实现执行阶段的扩展由派生实现定义。

  除非另行指定,对象的类型是对象的值的类型。

  NPL 对象类型和存储的值的类型之间的关联未指定。

  类型在描述类型规则的元语言中可作为对象。

  生成对象的元语言函数是类型构造器(type constructor) 。类型构造器的参数是类型,的函数值是组合这些参数得到的复合类型(compound type)

类型系统和类型机制

  称为类型的具体实体和之间的关联由派生实现的类型系统(type system) 规则指定。

  默认类型系统不附加约束,所有表达式或关联的项都没有指定类型(untyped) ,为退化的平凡类型系统(trivial type system)单一类型系统(unityped system) ,实质上是动态类型。

  对类型系统的分类中,类型也指确定类型的过程称为类型机制(typing discipline) ,其中确定类型的过程称为定型(typing)

  在静态类型之后阶段确定的类型机制是动态定型(dynamic typing)

  除非另行指定,被确定的静态类型的阶段是翻译时阶段;被确定的动态类型的阶段是翻译时之后,即运行时

  语言可提供定型规则(typing rule) (en-US) ,指定作为实体在特定的上下文(称为类型环境(typing environment) )中的类型。项是类型在这个上下文中的居留(inhabitant)

  类型环境确定类型指派(type assignment) ,即项和类型的之间的定型关系(typing relation) 。定型确定的这种定型关系的实例定型判断(typing judgement)

  不违反类型系统规则下的良定义的程序构造是良型的(well-typed)

  根据是否要求项首先都是良型的再指派语义,带有类型的形式系统可具有内在(intrinsic)外在(extrinsic) 的解释

  除非另行指定,NPL 使用外在的解释。

原理

  默认使用外在解释的理由是:

  • 类型的外在解释允许在一个没有指定具体类型系统设计的单一类型系统为基础扩展不同的类型系统,能满足语言自身可扩展的需要。
    • 扩展通用目的语言特性的顺序应是从简单到复杂的,而不是相反,因为并不存在已知的万能语言可供裁剪。
    • 这也符合历史顺序:无类型 λ 演算被扩展到不同的有类型 λ 演算,而不是相反;因为有类型 λ 演算的规则明显较无类型 λ 演算多且复杂。
    • 从无类型 λ 演算可以扩展到的一些特性更丰富其它系统,如 λμ 演算 (en-US) 和 vau 演算,首先都是无类型的,并不存在可用的内在解释。
  • 为了描述类型规则,外在解释最终需要在整个系统中引入和对象语言不同的元语言,而增加复杂性。
    • 即便存在强调可扩展的对象语言(如 MLPolyR),至少语言规范中定义的元语言没有被证明可以和被描述类型规则的对象语言合并。
    • 即便能证明可以合并,这种方式也显著地大大增加了设计的复杂性,违反避免不必要付出的代价
    • 根本上,这种方式损害对象语言设计的光滑性,很可能大大削弱对象语言的可用性
  • 没有确切的充分依据证明引入类型系统带来的性质是通过非类型论的直接扩展演绎系统的方式不能实现或者其实现有现实困难的。
    • 因此先验地要求类型的存在缺乏必要性。即便可实现需求,在通用目的上通常是舍近求远的过度设计。
    • 即便引入类型的方式有现成的工程实践而可以提升工程效率,也可能是过早的优化
    • 更何况现实并没有证据表明存在这样的成功实践。
  • 跳出先验地引入类型的做法,使用先验的内在解释而排除不够清晰明确的含义(meaning) 的语法的方式,在历史上存在更显著的失败。
    • [Chu41] §18 试图排除原始的 λ 演算(称为 λ-K-转换,在 [Bare84][Shu10] 中称为 λK 演算)中无法取得范式的项(以使之更适用于符号逻辑的目的):限制 λ 抽象中的约束变量是第二子项的自由变量。
      • 非正式地,这在语法上要求每个函数体中的每个变量必须是某个唯一的函数的形式参数,且这个函数的函数体是语法上包含这个变量的表达式,即语言在语法上禁止出现(在声明以外)未使用的形式参数。
      • 限制的 λ 演算在现代(如 [Bare84][Shu10] )称为 λI 演算,不支持表达 K 组合子
    • [Bare84] §2.2 指出 λI 演算具有的一些问题,如:
      • 对应的理论 λI 翻译到组合子逻辑的理论 CL 时,项能取得范式的性质不被保持。
      • 范式的概念过于侧重语法,所以在模型中不确定含义。
      • 试图识别编码偏函数(partial function) (en-US) 需要的“未定义”的项是不可能的。
      • λI 演算定义的偏函数的组合对应的项不一定是 λI 演算定义的被组合的偏函数的项的组合。
    • [Bare84] §2.2 指出,这些问题都来自 [Chu41] 选择用无法取得范式的项编码“未定义”的概念。
      • 定义可解性(solvability) ,以不可解代替不能取得范式编码“未定义”可解决这个问题。
        • 项的可解性定义为存在有限的项序列使前者在后者顺序应用得到 I 组合子(即 λx.x )。
      • 在 λI 演算中,不可解等价不能取得范式。而在 λ 演算中,不可解等价不能取得 HNF
      • λ 演算没有 λI 演算的上述问题。
      • λI 表述的 Church–Turing 论题仅限于全函数,而 λ 函数表述的论题能扩展到一般形式的偏函数。
    • 即便不考虑上述整体性质,尽管计算上 λI 演算是 Turing 完备的,它不能编码常量函数。
    • 以上问题一定程度上揭示了去除似乎冗余但实际在语义上可能非平凡的语法构造是不成熟的简化,损害系统的可用性
    • 要求(可被类型检查的)类型系统直接排除不能取得范式的项,在这个意义上比 λI 演算对去除特定的项的组合更彻底。
      • 即便类型系统能引入其它语义,这以引入不能被对象语言表达的规则为代价,通常需要元语言。
      • 相比之下,同样是引入对象语言表达式无法表达的语义,管理规约是对象语言规则能直接蕴含的,相对具有更小的(工作量和避免兼容问题上的)代价。
    • 注释 内在解释又被称为 Church 风格的。
  • 哲学意义上,内在解释或本体论上的(ontological) 解释,相比外在解释或语义上的(semantical) 解释需要更强的假设。
    • 本体论上的逻辑,如 Frege-Church 本体论 (en-US),可能解决一些悖论。
    • 但根本上,没有充分动机指出,不涉及演绎规则的悖论必须在通用语言内部直接提供规则消除,而不能通过其它方式(例如,由用户程序补充前提)解决。
      • 指定管理规约可以编码非平凡的表达语言规则外的语义的项可以对这样的前提建模并在语言中适当编码表达。
      • 编码表达这种方式是 NPL 强调 N(name) 和其它实体分离的主要理由。
    • 本体论假设要求名称以外附加实体以使假设生效。一般地,这些假设以不同语言的陈述作为断言实现。这些陈述涉及特称对象时,在完备性上是可疑的,且容易和开放世界假定冲突。
    • 约定不涉及的语言规则的本体论假设在这些意义上也可被认为在效用上的不成熟的优化

注释

  在元语言的意义上,类型系统包含语法和对应的语义,但在对象语言中,定型规则和其它推理规则(如类型检查规则)作为语言规则是语义规则,和语法相对独立。

  实体的类型可被指定为未指定类型,以明确类型的存在性,但不明确具体的类型的构造和表示。

  形式地,在类型系统中,类型环境和项作为前提,通过定型规则(typing rule) 得到定型判断。定型规则在逻辑上可以是公理或定理。

  在数理逻辑中,使用结构主义数学方法,集合可以作为描述类型规则的理论(句子集合)的模型,和理论支持描述的类型一一对应。

类型等价性

  通过显式指定标识(如名称)的方式定义类型的方法是名义类型(nominal typing) ,否则是结构化类型(structrual typing)

  除非另行指定,不同的名义类型不蕴含等价关系。结构化类型之间的等价关系由实现定义。

  类型的相等关系是一种类型之间的等价关系。两个类型相等,当且仅当它们的实例作为元素的两个集合对应相等。

  除非另行指定,相等的类型不在语言中区分,且元语言(描述对象语言的规则)中类型作为实体的同一性即类型相等性。

  推论:除非另行指定,不同的类型不等价。

  除非另行指定,对象语言使用类型相等性实现类型等价性。

原理

  对象语言中的类型实质上是类型的一种间接的表示,作为实体仍然可以具有不同的同一性。

  这避免程序可能需要枚举类型的外延(即精确实现出表示它的集合)才能确保确切表示出这个类型这样的计算上不可行的困难。

  因为可支持的表示的类型全集不同,类型相等是相对的,依赖类型系统的具体实现。一个类型系统可能支持无法在另一个类型系统中精确表示的类型。

注释

  本节的主要例外参见公共子类型

类型标注

  根据是否需要特定的文法元素指定和项关联的类型即类型标注(type annotation) ,对确定类型的机制可进行分类。

  类型系统可使用显式类型(explicit typing) ,即在定型时要求类型标注。

  不使用类型标注的方式是隐式类型(implicit typing)

  在引入实体(特别地,如变量)时指定实体的显式类型标注称为清单类型(manifest typing)

  不使用清单类型而使隐式引入的实体(如值)关联具体类型的机制称为潜在类型(latent typing)

  清单类型是显式类型的实例;除此之外,显式类型还包括铸型(casting) ,即显式指定表达式求值的结果应具有的类型。

  潜在类型是隐式类型的实例;除此之外,隐式类型还包括类型推断(type interferece) ,即通过隐含的上下文信息判断表达式关联的类型。

  类型推断的逆过程是类型擦除(type erasure) 。类型擦除支持使一个良型的程序中的已被定型的实体表示擦除前按类型规则不允许表示的其它实体。

  若类型机制可保证在某个执行阶段内有确定强规范化性质的算法确定类型,则类型机制在该阶段是静态定型

注释 强规范化性质的算法保证终止。

  语言可能个别指定引入这些类型相关的规则,在保持逻辑相容的前提下可混合使用。

  显式类型可编码接口的要求,即类型签名(type signature)

  类型签名通常直接指定名义类型,但同时也可允许非特定的满足结构类型约束的类型。这些类型和类型签名兼容(compatible)

原理

  历史上,表达式的类型和变量的类型在简单类型 λ 演算中同时被引入。后者修饰 λ 抽象中的自由变量,而前者限定剩余的所有项。

  即便从项重写系统中两者是形式上统一的,在实际语用中具有很不同的差异。这集中体现在后者是名义的,除非附加其它不同的语法设施,并不具有结构化推导的性质,原则上只适合描述接口;而前者能兼容结构化类型,同时适合描述接口及其实现。

  作为接口的名义类型在作为自由变量以外的上下文中重新复用为不关心其类型(并消除依赖这些信息的其它机制)的其它程序构造(一般意义上的表达式),通常需要类型擦除等更复杂的机制和支持的类型系统规则,以消去不再预期和其它类型系统规则交互的类型。

  和 [RnRK] 类似,NPL 不要求使用清单类型,以避免一些一般意义上的全局设计缺陷。这些缺陷包括:

  • 过于积极地(非预期地)排除危险但对程序有用的使用,而违反易预测性
  • 因为移除类型标注需要上述的复杂机制和类型系统规则,具体的清单类型阻碍派生语言定义其它不容易冲突的类型标注规则而使语言具有更好的可扩展性
  • 因为名义类型的相关规则更容易直接拒绝一些和类型规则不兼容的程序构造而难以简单地变通,往往对程序构造的组合具有更多直接的可表达性限制而破坏通用计算意义上的正确性

  若有必要,派生语言仍可限定使用清单类型。一般仍然建议仅在局部引入而避免全局复杂性和因此带来的限制。

注释

  类型签名来自数理逻辑术语。

类型检查

  类型检查(typechecking) 解答程序是否满足类型规则的判定性问题。

  使用翻译时语义分析运行时的类型检查分别为静态类型检查和动态类型检查。

  静态类型检查规则是可诊断语义规则

  语言可能个别指定引入类型检查相关的规则,在保持逻辑相容的前提下可混合使用。

  类型检查失败引起的错误称为类型错误(type error)

注释

  注意静态类型检查和静态定型以及动态类型检查和动态定型的区别。类型检查和类型机制是不同的规则,不必然包含蕴含关系。

  类型检查的一个典型的使用场景是类型签名的兼容性校验。

类型全集

  类型全集(type universe) 是语言规则中允许表达的类型的总称。

注释 表达类型的规则构成的模型的语言是语言规则的子集。

  NPL 避免限定类型全集。派生语言可指定不同的规则。

  除非派生实现另行指定,程序的用户不能依赖语言规则的限定枚举类型全集中的所有类型。

原理

  类型全集是论域的实例。避免限定类型全集符合开放世界假定

类型谓词

  判断值是否满足类型居留(inhabitant)谓词类型谓词(type predicate)

注释

  和 [RnRK] 的基本类型谓词不同,类型谓词定义为只接受一个参数。

类型序

  类型之间可具有序关系。

  被定型的类型的实体可完全地满足其它类型的约束。前者具有后者的子类型(subtype)

  子类型(subtyping) 关系是一种预序(preorder) 关系,即自反的、反对称的二元关系。

  相等的类型符合子类型关系,是平凡的(trivial) 。排除平凡的子类型关系是严格子类型关系。

  严格子类型是严格预序关系,即反自反、反对称的二元关系。

  子类型和严格子类型对应的逆关系是超类型(super typing)严格超类型(strict supertyping) 关系。

  多个类型可具有公共的(严格)超类型。这些类型同为一个类型的子类型而等价

  除非另行指定,在程序的行为不依赖其中特定的个别不相等的类型而具有差异时,具有相等超类型的等价的子类型视为相同的类型。

  复合类型中其中一部分的类型替换为其子类型,得到的结果和原复合类型可能有如下变化(variance) 的对应关系之一:

  • 协变(covariant) :类型序被保持,即结果类型是原复合类型的子类型。
  • 逆变(contravariant) :类型序的逆被保持,即结果类型是原复合类型的超类型。
  • 不变(invariant) :不保持类型序,即结果类型和原复合类型之间没有确定的子类型关系。

  同时存在以下派生归类:

  • 互变(bivariant) :同时协变和逆变。
  • 可变(variant) :至少协变或逆变之一。

  对接受参数类型得到结果类型的函数类型构造器 → ,以下关系是确定的:

  • 参数类型对函数类型逆变。
  • 结果类型对函数类型协变。

  把LSP要求子类型经替换前后保持性质的谓词视为类型构造器,则 LSP 要求的性质是协变的。

注释

  关于 → 的变化关系的陈述通常直接被作为类型系统中的定型规则表达的公理,以和 → 既有的定型规则兼容。

  一些非普遍的局部类型序的构造器,如数组的下标 [] ,也可对参数有确定的可变关系。

  在 LSP 的原始论文提供了两个满足 LSP(文中称为子类型要求(subtype requirement) )的在过程签名定义子类型的方法,兼容以上传统的函数类型构造器的子类型变化关系,是 → 上的上述关系的扩展:这些定义还支持表达过程的具体前置条件(precondition) 和其中引发的异常

  对一般的谓词,LSP 的行为多态(behavioral polymorphism) 是不可判定的。因此,一般的 LSP 无法被类型检查。在类型系统中应用 LSP 需依赖具体能表达性质的谓词,如使用的类型构造器。

类型边界元素

  一个类型系统可指定唯一的底类型(bottom type) (en-US) 作为其它任何不同类型的严格子类型,记作⊥。若类型全集包含空类型,则底类型是空类型(empty type) (en-US)

  一个类型系统可指定唯一的顶类型(top type) (en-US) 作为其它任何不同类型的严格超类型,记作⊤。这种类型即通用类型(universal type)

  NPL 支持空类型作为底类型,但不要求在对象语言中支持其表示。

  NPL 避免要求唯一的顶类型的存在以符合开放世界假设

  派生语言可指定不同的规则。

原理

  以空类型作为子类型在类型序的推理上是自然的。

  就非特定的类型全集,通用类型的的构造和表示不唯一,因此不能直接断言其存在。

  否则,假定存在这种类型,则断言不存在其超类型,这可能和其它语义规则冲突。

  即使在名义上定义具体的超类型(如 Java 的 java.lang.Object),也面临不能向上扩展(得到比 Object 更基本的类型)的问题,违反最小接口原则通用性

  具体的顶类型在断言当前类型系统不存在公共超类型可能仍然有实用意义;此时,顶类型即一等实体构成的类型,而不需要定义具体名义类型

多态类型

  特定的类型系统支持类型签名能对应多种不同的兼容类型。这样的类型是多态的(polymorphic)

  一般地,类型上的多态(polymorphism) 有:

  • 特设(ad-hoc) 多态:仅对项上局部的项上的类型作用使之满足上下文兼容要求的多态:
    • 函数重载(overload) :同一个名称对应的不同的函数实体,允许按实际参数的类型选择调用不同的函数。
    • 强制(coercion) :求值时使值向某个上下文要求的类型的隐式转换。
  • 参数(parameteric) 多态:接口签名指定以具体类型作为值的变量,组合为函数或者其它接口对应实体的类型。
  • 子类型多态:接口签名编码接受子类型关系作为兼容类型。
  • 行(row) 多态:对组成具有名称和实体对构成的元素作为成员(member) 的实体,兼容限定部分成员的类型。

  多型(polytipic) 的接口在同一个接口签名上以结构化类型的隐式类型构造支持不同的类型而支持多态。

注释

  重载在一些语言中自动地对函数对应的具体可调用实体适用。

  行多态以结构化类型约束取代通常通过名义类型指定的子类型关系。

类型种类

  种类(kind)静态类型系统的语法表示中具有特定类型模式(pattern) 的分类。

  一定意义上,种类是类型系统的元语言中一种元静态类型。

  一般地,实体类型的种类记作 *

  除非另行指定,作为项的函数应具有函数类型,即符合类型种类为 * → * 的结果的类型,如为简单类型 λ 演算兼容的函数类型实例。

  其中, 是函数类型的类型构造器。

  种类作为元语言中的类型多态,实现种类多态(kind polymorphism) :接口签名接受类型的编码中对应位置具有不同种类的类型。

注释

  在实现中,种类也被作为互操作的归类,如视为函数调用调用约定

  但这不足以涵盖一般的形式定义;特别地,调用是仅仅关于过程这类实体的互操作,而种类适合一般实体的静态类型。例如,在不考虑进一步地实现时,多变(levity) 多态的类型不需要限定过程(函数)。

  类型系统中的种类也可扩展到特定的计算作用的作用系统(effect system) 上以描述作用的种类,此处从略。

一等类型

  一等对象的类型是一等类型(first-class type)

  非一等类型的居留可能不在对象语言中可表达,即对象语言中无法构造这些类型的值。

  非一等类型仅用于构造其它类型(可能是一等类型)和类型检查等依赖类型的推理。

注释

  一个典型的非一等类型的例子是 [ISO C] 和 [ISO C++] 等语言支持的类型 void

  在语义的角度上,void 可视为依赖翻译阶段求值时得到的对应 void 居留的表示替换为表示语义错误的单元类型,并在翻译结束前拒绝接受带有这种居留的程序,而这种居留在对象语言中始终不可取得。

  若不限制翻译阶段,可通过在传递时始终限制正常控制的值实现类似的效果,例如不考虑类型消除时 [ISO C++] 中在复制或转移构造函数始终抛出异常的类类型。

程序的控制执行条件

  程序的执行可被控制作用影响。蕴含这些影响的条件即执行条件(execution condition)

  程序的控制状态决定求值使用的续延

注释 这和过程的调用类似。

  更一般地,规约规则指定语言的实现决定程序行为时使用的(对程序不保证可见的)续延,这种在实现中对应的控制状态称为控制执行条件。

  和控制状态不同,控制执行条件描述语言提供的不同控制机制的分类,而不被作为语言可编程的特性提供。

  除非另行指定,仅由求值算法中蕴含的规约规则决定的执行条件是正常(normal) 的。

  合并子调用以当前续延返回是正常执行的。

注释 这是正常控制执行条件的一个主要实例。

  改变程序的正常的控制要求存在控制作用,此时,控制执行条件是非正常(abnormal) 的。

  除非另行指定,隐含在求值算法中蕴含的规约规则确定的函数应用外的续延调用是非正常的。

注释 这是非正常控制执行条件的一个主要实例。

  具有规约语义的语言总是支持正常控制条件。NPL 中,非正常的控制条件的支持是可选的。

异常

  由派生实现定义的非正常的控制条件是异常(exceptional) 条件。

  异常(excpetion) 是通过抛出(throw) 实体(称为异常实体)同时表达满足异常条件的控制作用的语言构造。

  语言的实现或用户通过特定操作(如求值一个表达式)指定程序满足异常条件,使程序的控制进入异常执行状态,允许程序具有正常条件下可分辨不同行为。

  程序通过捕获(catch)处理(handle) 被抛出的实体,程序可满足不同的恢复正常执行的条件。

  进入违反翻译时正确性规则的异常执行状态时,由语言实现提供的异常执行机制实现行为。

注释 这些行为至少蕴含满足翻译时正确性规则要求的诊断

  进入其它异常执行状态的异常条件包括所有运行时异常条件和直接引起程序异常的用户操作。

  这些异常条件的具体行为和正常条件下的不同由派生实现指定的运行时状态或直接引起异常(改变程序的控制)或语言构造的语义决定。此时,由实现定义使用的异常执行机制。

注释 其它异常条件的异常执行机制可能和上述相同或不同。

  派生语言实现可指定以下规则:

  • 符合以上约定的判断改变(进入和退出)异常执行状态的执行机制。
  • 包括抛出和捕获的语言构造和其它可选的引起改变异常条件的上下文。

  若派生实现不指定以上要求的执行机制和上下文,则不支持异常。

  除非派生实现另行指定,异常的控制作用总是被同步(synchronized) 的,即:

  • 在初始化异常实体时,保证存在与异常条件关联且可确定单一的执行线程的状态作为引起控制状态改变即引发异常的来源。
  • 异常条件的满足不依赖未和引发异常状态同步的程序中的其它的执行状态(包括其它未同步的线程的状态)。
  • 确认满足异常条件和进入异常执行状态之间,上述执行线程内程序仅在引发异常的线程上的程序允许存在计算作用(这保证不被引起可观察行为改变的其它线程的操作中断)。

  除非派生实现另行指定,未捕获的异常总是确定性地(deterministically) 持续引发异常的执行线程中引起控制的转移:

  • 若捕获操作有效的上下文,控制转移捕获构造处理对应异常的异常处理器(exception handler)
  • 否则,若在活动函数调用中,则单向地从当前活动的函数向其主调函数转移控制,使后者活动。
  • 否则,若没有找到剩余的活动函数调用,则程序异常终止。

  除非派生实现另行指定,上述转移活动函数若成功(包括异常在活动的主调函数嵌套的特定语言构造中被捕获),先前不再活动的活动记录中的资源在控制成功转移后应立即被释放。

  典型的设计中,求值规则使的正常状态的函数调用要求的活动记录分配和释放满足 FIFO(Last-In First-Out ,后入先出)的顺序,构成了栈(stack) ,活动记录是栈帧(stack frame)

  除非派生实现另行指定,活动函数的转移释放资源,应保证按和创建被其所有的实体的顺序的相反顺序一致的形式释放。这种释放活动记录占用资源的机制称为栈展开(stack unwinding)

终止保证

  特定的求值具有(确定性地)终止(termination) 保证,当且仅当预期求值总是在有限计算步骤内可描述的计算作用

  具有终止保证的求值总是取得值或通过非正常控制的计算作用退出求值。

  不具有终止保证的求值可能不终止,此时它具有取得值以外的计算作用;这种计算作用是副作用

  若一个函数的调用总是具有终止保证,则此函数是终止函数(terminating function)

  若一个函数的调用总是取得值,则此函数是全函数(total function)

注释 全函数总是终止函数。

NPLA

  当前维护的主要派生语言为 NPLA ,是 NPL 的抽象语言实现派生实现

  NPLA 的参照实现 NPLA1 是具体语言实现,约定特定于当前参照实现的附加规则和实现。

  作为原型设计,NPLA 重视可扩展性。

  作为 NPL 的派生实现,NPLA 对象语言的设计遵循 NPL 符合性规则,并满足如下要求或附加限制。

注释

  NPLA1 是 NPLA 的一个派生实现。

NPLA 领域语义支持

  • 位(bit) :表示二进制存储的最小单位,具有 0 和 1 两种状态。
  • 字节(byte) :基本字符集中一个字符需要的最少的存储空间,是若干位的有序集合。
  • 八元组(octet) :8 个位的有序集合。

NPLA 整体约定

NPLA 实现环境

  NPLA 使用宿主语言为 [ISO C++11](及其之后的向前兼容的版本)的简单实现模型 NPL-EMA

  以下要求和宿主环境一致:

  • 字节占用的位(至少占用 8 个二进制位)。
  • 作为事件顺序的在先发生和在后发生和宿主语言中的定义一致。
    • 注释互操作,一般应避免和之后的(受实现支持的)[ISO C++] 版本冲突。

  NPLA 实体的内部表示是宿主语言中可表达的数据结构。

  NPLA 实体的外部表示是宿主语言中可通过输入/输出操作处理的数据。

  除非另行指定,NPLA 使用宿主语言提供的异常作为异常执行机制

  除非另行指定,程序不使用使宿主语言区域指定的行为(locale-specific behavior) 改变的特性。

原理

  默认避免改变区域指定行为简化设计约定。

注释

  关于类似的对宿主语言程序的要求,另见 YSLib 项目文档 doc/LanguageConvention.txt

附加功能

  NPLA 支持数值(numerical value) ,但不要求支持具体的数值计算。

  NPLA 实现为派生实现提供数值类型和相关的操作的基本支持。

  除非另行指定,若派生实现支持数值计算,其实现兼容 NPLA 数学功能的实现。

NPLA 词法和语法

  词法分析可接受多字节文本编码的字符串形式的源代码,但不假设其编码中除 0(空字符 NUL )以外的具体代码点被编码的数值,不转换编码

  使用可选的语法预处理和 NPL-GA 语法

  字符集的约定同宿主环境

NPLA 标识符

  NPL 标识符外的以下词素也是 NPLA 标识符:

NPLA 扩展字面量

  NPLA 扩展字面量包括:

  • #+- 起始的但不全是 +- 构成的、长度大于 1 的词素。
  • 十进制数字字符起始的词素(当被支持时)。

  全由十进制数字字符的词素表示十进制数值。派生实现可定义其它作为数值的词素。这些词素作为字面量时,是数值字面量(numerical literal)

NPLA 名称和字面量求值

  名称仅被实现为和字符串的一个真子集一一对应的表示(参见类型映射)。

  除非派生实现另行指定,只有代码字面量不是自求值表达式,其余字面量都求值为右值

  代码字面量求值时解释为名称。

  数据字面量是自求值的字符串的外部表示

  数值字面量是自求值的数值的外部表示。

  存在不保证先求值的子表达式语法形式特殊形式(special form)

  特定的名称是保留名称(reserved name)

  除非另行指定,在源代码中使用保留名称作为实体的名称的程序行为未定义

NPLA 求值的表示

  规范形式是特定类型的 [ISO C++] 对象。

  名称解析失败可被忽略而不终止实现演绎;保证名称表达式求值的强规范化

  不要求提供命名空间实现的可变实体。

  不保证求值都是纯求值;非特殊形式使用热情求值;其它情形使用热情求值或惰性求值的方式由具体特殊形式约定。

  对象语言的函数默认为过程,过程默认实现为子例程。过程指定的计算结果和函数表达式最终求值结果关联过程调用结果的恒等映射。

注释 即过程调用的结果总是同函数值。

  除非另行指定,实现函数的宿主数据结构生存期要求默认同宿主语言

  除非另行指定,按值传递支持复制初始化对象的一等作用

原理

  NPLA 函数不支持类似 [ISO C++] 的类型退化(decay) 。作为动态类型语言,需要被转换的值在操作内部实现,不需要在返回值上另行附加转换。

  按值传递的复制初始化和宿主语言的对应语义类似。

NPLA 类型系统

  NPLA 使用隐式类型而非显式类型

  NPLA 使用潜在类型具有类型;不指定动态类型以外的类型

  显式类型(如清单类型)的机制可由派生实现指定可选地引入。用户程序也可能添加类型标注和不同的类型机制的支持。

  除非派生实现另行指定,引入的静态类型应和动态类型一一对应。

  NPLA 使用和宿主语言相容的动态类型检查。除非派生实现另行指定或类型映射的需要,使用的类型检查规则和宿主语言一致。

  宿主语言对象的值描述状态,且宿主语言要求的对 volatile 左值的操作属于可观察行为

NPLA 互操作支持

  NPLA 的宿主语言应能提供 NPLA 及派生实现的本机实现

  NPLA 的派生实现提供特定的和宿主语言的互操作支持,可其中和 NPLA 提供的关于互操作的具体行为不同的部分应由实现定义。

注释 对派生实现,NPLA 约定的具体默认互操作特性是可选的。但是,一般的约定如开放类型系统仍被要求。

  NPLA 和派生实现可约定互操作的具体实现的要求,以确保实现的状态可预测。

  本机实现可以具有 C++ 的实现兼容的二进制接口的函数提供,这些函数称为本机函数(native function)

  本机实现可直接支持本机函数在实现中被调用。若被支持,具体接口由派生实现指定。

  本机函数作为函数的实现,其调用的求值可具有和非本机的函数一致的作用,但不需要具有可被对象语言表达的函数体

  为确保函数求值的作用可能保持一致,本机函数应符合和本机函数调用时使用的规约一致的方式使用,即在宿主语言的意义上至少符合以下规约调用约定

  • 被调用时的子项被作为以 WHNF 形式表示的被调用的表达式使用。
  • 调用后具有项被重写为必要的值以表示函数调用返回值

  本机函数的返回值应能表达任意的非本机函数调用的返回值,即通过求值函数调用中函数体的非本机函数的求值结果

原理

  实体的内部表示和外部表示满足实现环境的要求允许在宿主语言程序中直接实现关于表示的操作,简化了互操作机制的设计和实现。

注释

  宿主语言自身的调用约定(通常和实现的 ISA 相关)作为 C++ 实现自身的 ABI ,在此是中立的,没有提供特设的支持的要求。

  另见 NPLA 基础存储和对象模型

类型映射

  类型映射(type mapping) 指定对象语言和宿主语言之间的实体类型之间的关系,是前者中的类型到后者中的类型的映射。

  作为类型映射目标的宿主语言类型或其子类型称为宿主类型(hosted type)

  作为宿主语言类型的宿主类型是典型的。其它宿主类型是非典型的。

  具有特定动态类型的对象语言的在宿主语言具有宿主类型,以宿主语言的值表示,称为宿主值(hosted value)

  在互操作的意义上,宿主值在作为对象语言的值的表示中以宿主对象(hosted object) 的形式被保存并可在宿主语言中访问。

  对象语言的值被对象语言的实体类型表示蕴含它被映射的宿主类型表示,反之亦然。

  类型映射可以是非空的多对一、一对多或一一映射。

  若类型映射是一一映射,其类型等价性同宿主语言的语义规则;否则,由类型的语义规则约定。

  因需提供与作为宿主语言的 [ISO C++] 的互操作支持,所以明确约定实现中部分实体类型对应的 C++ 类型:

  • 用于条件判断的单一值的宿主类型是 bool
  • 字符串的宿主类型都是 string 类型。
  • 和字符串的子集一一对应的词素的宿主类型是能映射到 string 的另一种类型。

注释 string 是占位符,不要求是和 [ISO C++] 的 std::basic_string 相关的类型。但一般地,string 类型应具有和 std::string 相近的操作以便实现对象语言语义及支持互操作。

  推论:字符串和词素可直接比较相等性或排序。

  NPLA 数值在对象语言中具有数值类型,具体类型映射未指定,但在 NPLA 数学功能提供可选实现。派生实现可显式扩充或替换定义其它数值类型的类型映射。

  其它宿主类型由实现定义。具体宿主类型参见以下各节和对象语言类型对应的描述。

  宿主类型在对应的 C++ API 中可能以类型别名的形式引入。

原理

  类型系统开放的,可能提供不被对象语言支持的宿主语言类型和值。

  但符合已指定的类型的实体需能被视为同种类型的实体使用,即子类型。

注释

  非典型的宿主类型可以是特定的宿主类型的值的子集,即便这样的类型不被宿主语言的类型系统直接表示。

  不被对象语言支持的值的一个例子是实现使用的中间值(thunked value)

  关于中间值、string 类型的具体要求、NPLA 数学功能的规格说明和由实现定义的命名空间,参见 YSLib 项目文档 doc/NPL.txt

NPLA 未定义行为

  一般地,NPLA 规则不排除未定义行为。其中,宿主语言的未定义行为是非特定体系结构或其它 [ISO C++] 意义上不可预测或不可移植的行为。

  除非派生实现另行指定,NPLA 约定仅有具有以下情形的程序引入未定义行为:

  • 互操作中引起宿主语言的未定义行为或不满足约定的要求而可能引入派生实现定义的未定义行为。
  • 本机实现无法提供资源而引起宿主语言的未定义行为(如宿主语言的实现无法提供宿主语言函数调用的自动对象隐式使用的资源)。
  • 违反资源所有权语义约束的操作,包括但不限于:
  • 使用特定的词法构造。

  除非派生实现另行指定,NPLA 约定:

  • 若程序的执行蕴含宿主语言中不保证排除未定义行为的操作,执行可包含宿主语言的未定义行为。
  • 否则,非互操作引入的管理规约可能存在未定义行为,当且仅当它是求值规约的一部分且求值规约可能存在未定义行为。

原理

  满足错误条件的程序可能引起错误,也可引起未定义行为而不要求引起错误。这允许减少实现的复杂性。

  对宿主语言的未定义行为的单独处理允许描述互操作。

  程序的执行允许宿主语言的未定义行为,同时允许形式上不可靠,但仍可通过宿主的外部环境提供附加保证的实现,而保留可实现性:

  • 典型地,宿主语言不保证调用的活动记录总是可用。
    • 例如,[ISO C++] 指定程序在自动对象无法分配时具有未定义行为。
    • 这种情形形式上无法排除,但不影响实用(否则,任意 [ISO C++] 程序都是不可移植的)。
  • 实现仍应保守使用资源,以尽可能地避免引起宿主语言的未定义行为。
  • 通过宿主的外部提供附加保证的实现类似保证为完整性的前提下通过加入附加的限制来使设计符合要求。

  对管理规约的约定同时蕴含对 NPLA 实现的要求。这保证未定义行为不会被任意地在对象语言以外被引入。

注释

  为简化互操作实现,部分 NPLA 未定义行为可能在实现中被检查以预防(尽可能避免)宿主语言的未定义行为,但这种检查不保证完全覆盖所有引起未定义行为的条件,不应预期其行为可移植。

  关于构造循环引用可能引起的问题,另见内存泄漏

常规宿主资源分配要求

  一般地,本机实现要求资源分配失败时,引起(可能派生)std::bad_alloc 或另行指定的宿主异常而非宿主语言的未定义行为;但因为宿主语言缺乏保证,可能并非所有宿主语言实现都能保证实现这项特性。

  实际的实现中非极端条件下(如宿主调用栈接近不可用)通常可支持实现这些行为。

  宿主语言实现支持时,具有可预期的失败(而 NPLA 或宿主语言的非未定义行为)的 NPLA 实现的要求称为常规宿主资源分配要求。

嵌套调用安全

  宿主语言的 API 提供嵌套调用安全(nested call safety) ,当且仅当:

  若调用没有宿主语言无法分配资源的未定义行为,则同时避免因宿主语言的嵌套调用深度过大时引起的这样的未定义行为。

  嵌套调用安全应包括支持可能通过对象语言构造的输入使对应宿主语言的操作中的嵌套调用不保证的情形。

  对象语言的实现可假定限制避免无限创建活动记录即满足嵌套调用安全的要求。

原理

  嵌套调用安全允许不限制嵌套深度的可靠的调用,如递归调用。

  宿主语言实现在宿主语言的尾上下文可能支持宿主 TCO 而使递归调用满足嵌套调用安全,但这并不是语言提供的保证,不应在可移植的实现中依赖。

  [ISO C++] 并没有明确指定关于深度的限制,嵌套调用可能因资源耗尽而引起未定义行为。

  严格来说,这种未指定深度是可移植性上的缺陷,因为任意小的深度的调用(甚至深度为 1 的非嵌套调用)都可引起未定义行为而不需要遵循任何 [ISO C++] 的要求,却仍然满足实现的符合性

  [ISO C] 也有相同的问题。

  实际实现中,具体深度限制依赖实现。在宿主语言缺乏保证的状况下,添加附加假定对可实现性是必要的。

注释

  对应宿主语言的操作中的嵌套调用不保证的情形的主要例子是保证宿主语言中立

  非嵌套调用安全的情形在过程嵌套调用深度过大时,可因为宿主语言的存储资源消耗导致的宿主语言实现的未定义行为,典型地包括实现中的栈溢出(stack overflow)

  不限深度的重入不一定引起无限的活动记录的创建:尾调用应能保证嵌套调用安全。

NPLA 并发访问

  当前所有 NPLA 实现中都没有显式的并发访问控制,但可通过互操作引入。

注释

  一般地,为避免并发访问引起的宿主语言的未定义行为,需要通过本机实现在外部使用不同的资源实例或附加适当的同步。

  另见并发访问安全

NPLA 一等对象类型

  除类型映射,NPLA 约定能作为一等对象的类型支持的抽象的类型,作为实现的最小要求的一部分。

  以下章节扩充 NPLA 的其它类型,这些类型中的一部分可能作为一等对象。

  基于开放类型系统,派生实现可定义其它类型,不论是否被互操作支持。

原理

  这些类型在求值算法等规则的描述中适用。

有序对

  两个不同对象可作为元素(element) 构成有序对(ordered pair, pair)

  有序对的元素是子对象

  当且仅当若有序对的两个元素不同,交换元素得到的有序对和原有序对不同。

注释

  一些编程语言中,构造有序对的操作称为 cons ,有序对又称为 cons 对。

广义列表

  列表(list) 一种类型,它的对象可能具有子对象

  空列表(empty list) 是不含有子对象的列表。其它列表是非空(nonempty) 列表。

  每个非空列表是一个有序对对象,满足:

  • 有序对对象的第一个元素是列表的元素。
  • 若有序对对象的第二个元素是有序对,则这个有序对对象的第一个元素是列表的元素;否则,最后一个不是有序对对象的子对象是列表的元素。

注释 推论:同一个列表的元素不是另一个元素的子对象;不同元素之间不具有所有权,生存期不相交。

  从非空列表对象中取得元素分解(decompose) 列表对象。若经有限次分解,不再可取得列表对象的元素,则列表对象被完全分解。

  完全分解的列表的最后一个元素之外的其它元素是列表的前缀(prefix) 元素。

  对象具有前缀元素,当且仅当对象是列表且具有前缀元素。

  真列表(proper list) 是空列表,或能经完全分解得到最后元素是空列表的列表。其它列表是非真列表(improper list)

注释 推论:非真列表是非空列表。

  广义列表(generalized list) 是真列表或非真列表。

  广义列表的元素是一等对象。广义列表对元素具有所有权。

  广义列表是完全分解的元素的序列(sequence)

  作为广义列表的非真列表是无环的(acyclic) ,不包含环(cycle)

注释 同一般的 NPL 约定,NPLA 对象不支持自引用和循环数据结构

  除非另行指定,以下列表指真列表。

  子有序对(subpair) 是一个有序对完全分解的序列中的元素的真子集构成的子对象。

  子列表(sublist) 是一个列表中的元素的真子集构成的列表子对象。

注释

  无环非真列表和真列表类似,可通过 cons 逐次构造。

  非列表的有序对的元素可能具有自引用,而不是广义列表的元素,因此不是广义列表。NPLA 的一等对象不支持这种情形。

符号

  符号(symbol) 是未被求值的非字面量记号的类型。

  符号值可构成名称表达式

存储和对象模型

  NPLA 使用统一的模型对存储和对象进行抽象,并提供关于存储、对象和作为对象的表示以及子项的若干保证。

  对象语言的存储被视为资源进行管理,称为存储资源(memory resource)

原理

  语言中默认不引入非一等对象。因此,存储和对象模型作用到所有实体,有助于保持简单性

注释

  一等对象的使用可能受到其它规则的限制,不总是能同时通过对象语言的构造创建和访问。

  NPL 允许派生实现引入实体的规则不受限制。

NPLA 基础存储模型和对象模型

  因需提供宿主语言互操作支持,除不支持静态(static) 存储和没有提供支持的存储操作外,NPLA 的基础存储模型和对象模型和 [ISO C++11] 相同。

  当前不支持的存储操作包括分配函数(allocation function) 取得的存储和线程局部(thread-local) 存储。

  NPLA 还允许类似对象具有未指定的存储或不需要存储的实体,以使一等实体可涵盖宿主语言在功能上等价的非对象类型(如 C++ 的引用)。这些实体若被支持,其存储实现和互操作接口由派生实现定义。

  NPLA 中不是一等对象一等实体仅由派生实现定义。

  保证存储性质的差异不被依赖时,不区分一等实体和一等对象的实现方式。

  在此情况下对象都是固定(pinned) 的,即对象在存储期(storage duration) 内具有宿主语言意义上的确定不变的地址。派生实现可约定扩展作为例外。

  推论:若一等实体不是一等对象,存储可能和一等对象的存储方式不同。派生实现可在必要时约定与其它一等实体存储的差异。

  对象的生存期是存储期的子集。创建对象基于已确保可访问的存储;销毁对象结束后释放存储。

  NPLA 支持特定的非一等对象作为引用值被引用对象

注释 和宿主语言类似。

  作为一等对象相同方式传递的一等实体都视为一等对象。仅当不依赖一等对象的性质时,实现以非一等对象的方式实现一等实体的操作。

原理

  实体的内部表示满足实现环境的要求决定和 NPLA 和宿主语言之间共享一些基本的假定。

间接值

  特定的间接值(indirect value)

  间接值可以关联(associated) 一个对象。通过间接值可以间接访问这个对象。

  间接值可能是一等对象非一等对象

  非一等对象的间接值由实现定义,参见 YSLib 项目文档 doc/NPL.txt

  派生实现可以定义其它间接值,称为 NPLA 扩展间接值。

  一个间接值有效(valid) ,当且仅当存在关联的对象且访问对象不引起未定义行为

  其它间接值是无效(invalid) 的。

  除非另行指定,通过无效的间接值试图间接访问关联的对象不满足内存安全而引起未定义行为。

  有效的引用值可能被无效化(invalidate) 而不再有效。

  派生实现可指定能使间接值无效化的操作。

  因关联的对象存储期结束而被无效化的间接值是悬空(dangling) 的。

原理

  间接值可用于代替非间接值,避免求值时改变环境所有的非临时对象所有权

  间接值可实现和 [ISO C++] 引用类型的表达式类似的行为。

  间接访问默认没有对象的生存期检查,因此不是安全的。这可能被具体的间接值的规则改变。

  限制具体的操作能避免或减少在可能访问间接值的操作随意引入具有潜在未定义行为风险。

注释

  作为一等对象的间接值可能允许复制或转移关联的对象以恢复对应的非间接值作为一等对象直接访问。

  在使用约定后,本节以下约定要求被 NPLA 实现支持作为一等对象的间接值。非一等对象的间接值由实现定义。派生实现可以定义其它的 NPLA 扩展间接值。

间接值使用约定

  间接值生存期规则:被规约对象中间接值的生存期被引用的环境中的对象的生存期的子集。

  不满足间接值生存期规则的情形,除非提供派生实现定义的其它保证,不保证内存安全

  以含间接值的项替代不含间接值的项,称为引入(introduce) 间接值。

  包含间接值的项可被不含引用值的项替代,称为消除(eliminate) 间接值。

  在特定的适当情形下实现应复制或转移间接值关联的对象以保证满足生存期要求,包括:

  除非另行指定引起错误,若不能满足上述适当情形条件,则行为未定义

  派生实现可基于本节约定其它规则。

原理

  为保证间接访问关联对象的内存安全,约定间接值生存期规则。

  参见局部间接值安全保证和返回值转换。

注释

  如需直接替换项表示的值,需消除间接值。否则,没有必要提前对项进行操作以提前移除间接值。

  关于实现定义和派生实现定义的其它情形,参见 YSLib 项目文档 doc/NPL.txt

  另见被求值的被规约项中的对象的所有权

环境间接值

  环境引用间接访问环境对象

引用间接值

  项引用(term reference) 作为间接值引用一个项,访问这个以这个项作为表示的被引用对象作为关联对象。

  项引用具有标签

求值和对象所有权

  被求值的表达式的内部表示中的对象具有 NPLA 对象的所有权

  这些内部表示包括环境对象被求值的表达式中的项的情形。

  对象是表示它的被规约项项对象(term object)

  NPLA 临时对象的存储未指定,但部分临时对象被项所有

  求值结束而不被使用的项的资源在求值终止时被释放,包括被项独占所有权的这些临时对象。

  求值终止包括可被实现确定的异常退出。

  对名义上被项所有的临时对象,必要时实现可分配内部存储转移项(包括在环境中分配),以满足附加要求(如生存期附加约定)。

  对象的所有权随可随对象被转移,参见对象的复制和转移

  求值结果可以是:

  • 作为值计算的结果的一等对象,称为结果对象(result object)
  • 传递异常的状态的实体。
  • 派生实现可定义的其它实体。

注释求值规约,其它的求值结果的存在未指定,若存在则可能需要其它处理,可能依赖和处理一等对象的值不同的语义规则。

  函数调用时以活动记录保持被引用对象的所有权。活动记录及其帧的具体结构、维护方式和生存期由派生实现定义。

  除非另行指定,NPLA 只有一种作用域,这种作用域中的名称由环境提供。

  除非另行指定,NPLA 的活动记录不需要和宿主语言的结构保证直接对应关系。

原理

  因为宿主语言函数调用实现(典型地,调用栈(call stack) 及其中的栈帧)不提供可移植的互操作,不要求实现提供活动记录之间的映射关系。

注释

  临时对象的存储未指定、异常退出和所有权转移类似宿主语言。

  结果对象和 [ISO C++17](由提案 [WG21 P0135R1] 引入)中的概念对应。

  另见环境对象环境引用对其中的对象的所有权。

项对象和关联对象所有权

  仅在泛左值中允许引入可能访问关联对象的间接值。

  推论:泛左值的项对象和它作为间接值可关联的对象(若存在)不是临时对象,被环境所有。

  通常纯右值作为其它项的子项而被独占所有权,求值时可能通过临时对象实质化转换标识创建的临时对象

  表示临时对象的项被纯右值所有,也间接被其它项所有。

  特定的纯右值可能被环境所有,但应只通过复制等方式访问其值而不依赖所有权关系。

  关于实现中项的宿主类型和构成以及纯右值被环境所有的例子,参见 YSLib 项目文档 doc/NPL.txt

原理

  基于间接值的性质,为保证内存安全,避免非预期地超出存储期的间接值访问,限制引入间接值的表达式的值类别

  因临时对象可能具有和一等对象不同的表示,在此特设规则约定。

并发访问安全

  蕴含按抽象机语义不等价副作用的并发的访问是冲突的(conflict)

  不共享相同的控制状态无序规约事件潜在并发的(potentially concurrent)

  若程序包含蕴含冲突的作用的潜在并发的求值,且这些求值之间没有附加的数据竞争避免(data race avoidence) 保证,程序的执行包含数据竞争(data race) ,不满足并发访问的内存安全。其中,以下机制数据竞争避免保证:

  • 所有潜在并发的求值都是宿主实现提供的原子操作(atomic operation) 时,避免数据竞争。
  • 派生实现另行指定的数据竞争避免机制。

  并发访问相关的概念和 [ISO C++11] 相容。

内存安全

  (非并发)内存安全(memory safety) 是存储资源避免特定类型不可预测错误使用的性质。

  基本的内存安全保证蕴含非并发访问时不引入未定义行为。这至少满足:

  • 对存储的访问总是在提供存储的对象的存储期内,除非有其它另行指定的机制(如宿主环境互操作)保证存储的访问不违反其它语义规则。
  • 宿主环境中不访问未被初始化的值。

注释 实现仍可能因其它规则引起未定义行为;特别地,这包括本机实现无法提供资源的未定义行为。

  派生实现可能扩展内存安全,提供语言规则避免非预期的内存访问错误,提供更一般的高级安全(security) 保证。

注释 例如,保密性(secrecy)完整性(integrity)

  除非另行指定,派生实现不提供扩展的内存安全保证。

  不满足并发访问安全的访问是非内存安全的。

原理

  关于内存安全含义的讨论,另见这里

注释

  用户代码应注意避免违反内存安全的访问,包括非并发的,以及并发访问的内存冲突。

非内存安全操作

  非内存安全操作是不保证内存安全的操作,在对象语言中即可能引起违反内存安全。

  这些操作违反内存安全时,引起 NPLA 未定义行为,且可能未被实现检查而同时引起宿主语言的未定义行为。

  对象语言中的非内存安全特性可能直接调用这些操作。NPLA 外依赖此类操作的其它操作也具有类似的性质。

注释

  派生实现或用户程序可能使用补充检查等方式避免未定义行为。

NPLA 对象语言内存安全保证

  NPLA 中,确定地引入具有非内存安全操作的对象的操作应仅只包括引入特定的间接值或其它派生实现指定类型的值的操作:

  • 调用引入不保证内存安全的间接值的 NPLA API
  • 调用 NPLA 中其它取对象内部表示的值的间接值使之被修改的 API 。

  排除非内存安全操作以及非内存安全的本机实现,NPLA 实现的对象语言提供基本内存安全保证。

NPLA 内存安全保证

  满足 NPLA 对象语言内存安全保证同时排除引起宿主语言未定义行为的非内存安全的操作,NPLA 实现提供基本内存安全保证。

注释 宿主语言未定义行为的非内存安全的操作如超出生存期访问

  除非通过接口约束另行指定,使用 NPLA 实现的派生实现应提供相同的保证。

注释 例如,添加断言检查可能改变实现行为

运行时内存安全检查

  运行时检查可能帮助排查内存安全的实现行为。这包括蕴含运行时检查的接口约束(失败时抛出异常或断言)。

  此外,实现可能提供可选的运行时检查。这些可选的检查帮助排查未定义行为,而不应被程序实现依赖。

局部间接值安全保证

  访问间接值涉及维护内存安全保证时,可能需要提升项消除间接值,以移除允许非内存安全访问的间接值。

原理

  使用删除策略实现过程调用时,其中分配的局部(local) 资源随包含资源引用的引用返回可能逃逸。一般的间接值也有类似的逃逸问题。

  若其关联的对象(如项引用关联的被引用对象)在调用后不再存在,则间接值不再有效,构成悬空间接值。若这些间接值被调用者获取(如被作为返回值传递),继续访问这个间接值关联的对象非内存安全。

  为维护内存安全保证,这些情形应被避免,如通过:

  • 通过分析调用处的代码证明确保不存在这样的内存不安全访问。
  • 通过间接值的消除移除这些间接值使这种悬空间接值在调用者中自始不存在。

  替代消除间接值的方式包括通过逃逸分析(escape analysis) 替换间接值,这也能减少间接值的访问而提供更优化的实现。例如,通过对环境中被绑定对象的使用进行逃逸分析提供优化实现。

  但是,这不在 NPLA 中被要求,因为:

  • 逃逸分析需要完整的所有权信息,这需要附加的开销,否则不总是可行(例如涉及跨多个过程的调用)。
  • 对删除策略,逃逸分析也没有提供不可替代的优化。

资源泄漏

  资源泄漏(resource leak) 是不能预期地(决定性地)访问之前被分配的资源的情形。

  内存泄漏(memory leak) 是存储资源的泄漏。

  强内存泄漏状态是指存在存储无法通过任何途径访问的状态。若存在存储不被任意对象或其它另行指定的代替对象的实体(如宿主环境)所有权的传递闭包包含,即所有权依赖不可达(unreachable) ,则存在强内存泄漏。

  弱内存泄漏是除了强内存泄漏以外的内存泄漏,和具体预期相关。

原理

  一般意义下,[Cl98] 中定义的任一空间复杂度类都可以作为形式的预期。因为内存作为存储资源被空间复杂度类度量,满足某个空间复杂度类的无空间泄漏(space leak) 蕴含对应的无内存泄漏。

  弱内存泄漏的预期的可实现性和实现细节相关,因此 NPLA 不指定具体预期。

资源回收策略

  单一作用域内的资源回收有删除(deletion)保留(retention) 的策略。

  NPLA 不限定具体使用的回收策略,但要求应支持:

  为简化语义规则同时避免限制特定的可用资源(如系统中剩余的内存)的变化被派生实现抽象为副作用,除非派生实现指定,不对内存使用保留策略,不使内存超出对象生存期

  NPLA 要求实现完全避免除用户程序显式管理资源的资源泄漏以外的强内存泄漏。

  除非另行指定,NPLA 释放资源的作用顺序未指定。NPLA 不依赖释放的作用的顺序。

  派生实现可以要求使用不同的规则:

  • 指定释放资源的顺序。
  • 可选地支持非确定的释放资源的副作用。

  NPLA释放可能具有的副作用顺序的存储资源和其它资源共享更普遍的所有权抽象资源的所有权语义上的操作:

  使用删除策略时,活动的过程调用对其中分配的资源具有所有权。

  注意多个对象构成的系统中,仅存在平等的所有权时的循环引用造成强内存泄漏:除非即从循环引用的对象中区分出具有不同类所有权的对象子集实现所有权正规化,总是存在无法被释放资源的对象。

  NPLA 不要求实现 GC 等机制避免这类循环引用。

  关于循环引用避免,另见 YSLib 项目文档 doc/NPL.txt

原理

  NPLA 不要求实现 GC

  未指定的资源释放的作用顺序使其中可能具有的副作用影响的可观察行为成为未指定行为

  除非派生实现要求使用不同的规则支持非确定的资源的副作用,NPLA 的实现不依赖不保证确定性释放资源的副作用顺序的追踪(tracing) GC 。这使追踪 GC 可能被可选地添加(opted-in) 到实现支持特性中。这允许自动资源管理机制中一定程度的变化的自由。[ISO C++11] 起直至 [ISO C++20] ,C++ 语言规则支持类似的策略。

  资源释放副作用的确定性要求和作用顺序未指定的规则不影响实现使用基于引用计数的 GC 策略。这允许实现以简单的方式以用户程序不直接可见的方式引入共享资源,在避免资源泄漏的意义上兼顾正确性简单性。但为避免单一所有者,此时在对象语言应提供特性使用户程序可以创建隔离共享者的资源实体。

  基于非预期的循环引用不可避免地造成实现开销而违反避免不必要付出的代价(即使这种开销可能并不总是可观察)NPLA 不要求实现 GC 和对一般对象区分强弱引用等机制避免循环引用。此时,程序应自行避免所有权意义上的循环引用以避免资源泄漏。

  由于 GC 通常基于具有特定操作的单一资源所有权的所有者的对象池的这一实现特例,不依赖共享所有者的 GC 的设计一般也更容易满足统一性最小接口原则关注点分离原则

  以上规则允许程序中:

  • 不依赖释放可能具有的副作用顺序的资源。
  • 使存储资源和其它资源共享基于更普遍的所有权抽象的资源所有权语义的操作的作用,以一致的方式实现资源管理。

  关于不同的资源回收策略(其中一部分可能引起存储空间资源泄漏)的讨论,详见 [Cl98] 。

  使用所有权抽象活动记录的资源能更好地满足资源管理机制和具体操作的可复用性作用使用原则的要求。

资源回收安全性

  派生实现可补充定义规则在资源回收的作用上提供更强的安全保证。

原理

  内存泄漏是和内存安全不同的另一类非预期的问题,表明语言设计、实现或程序存在缺陷。

  即便不违反内存安全保证,涉及弱化空间复杂度类预期的内存泄漏仍可损害程序的可用性而引起安全(security) 问题。

  内存泄漏和违反内存安全同属违反特定的存储访问不变量的错误条件(error condition) ,但因为不论在语言还是程序的设计和实现中,避免的机制相当不同,在此被区分对待。

  即便不扩展规则提供更强的内存安全保证,仅在资源回收的作用上避免错误条件也是有意义的。

  存在其它语言使用类似的区分内存泄漏和非内存安全的设计,如 [Rust](详见相关文档)。

子对象

  对象的子实体是对象时,子实体是对象的子对象(subobject)

  除非另行指定,子对象及其性质同宿主语言的约定:在宿主语言的表示中表现为子对象的对象语言中的对象,也是对象语言的子对象。

  对象语言的其它具有子对象的情形由派生实现定义。

  对象对它的子对象具有平凡的所有权

  对象的子对象的生存期先序对象的生存期起始,对象的子对象的生存期结束不后序对象的生存期结束。

  对象的子对象的生存期起始后序对象的生存期起始,对象的子对象的生存期结束先序对象的生存期结束。

  除非另行指定,同一个的对象不同子对象的存储期起始、存储期结束、生存期起始、生存期结束之间分别无序。

  对象对其存储期和生存期的其它约束和宿主语言相同。

  对象可通过子对象引用关联和与其生存期相关或无关的其它对象。

  通过子对象访问的被引用对象上的副作用是否蕴含对象上的副作用未指定。

  关于内部对象,参见 YSLib 项目文档 doc/NPL.txt

原理

  子对象不一定支持可修改一等状态。修改子对象可能导致或不导致对象或先前通过相同方式取得的子对象的改变

  [ISO C++] 通过类型定义具有的隐含的对象布局共享同类对象的内部表示。与之不同,为简化非一等对象表示的项上的操作,子对象之间不一定共享表示。

  特别地,通过子对象引用项访问的对象的子对象之间不一定具有同一性

  关于具体表示,参见 YSLib 项目文档 doc/NPL.txt

注释

  作为支持子对象作为内部对象的逻辑前提,NPLA 不支持循环引用

  [ISO C++] 允许 const 成员提供不支持修改的状态。NPLA 不要求类似的类型系统支持,没有类似的设计。

项的子对象

  作为对象的子项是项对象的子对象。

  因为子项可以递归地具有子项,项对象作为数据结构构成树(tree) 。项对象是树的节点,即项节点(term node)

  项节点具有如下互斥的基本分类:

  • 枝节点(branch node)非叶节点(non-leaf node) :具有子节点的节点。
  • 叶节点(leaf node) :不具有子节点的节点。      除子项外,项具有值数据成员(value data member) 作为其子对象。

  表示项对象的被规约项的值数据成员提供时间复杂度为 O(1) 的操作判断:

  值数据成员可能具有空值。

  值数据成员和子项可构成对象的内部表示

  • 列表节点(list node) 是值数据成员为空的节点,表示真列表。
  • 空节点(empty node) 同时是叶节点和列表节点,表示空列表
  • 实现可定义其它的节点作为其它的内部表示。

  若项存在其它子对象,作为对象内部表示的具体规则由实现定义。

  满足以下条件的替换变换替代项或其子对象,称为项的提升(lifting) :被提升的项(源)是提升后得到的项(目标)的一个直接、间接子项或项的子对象变换得到的项。

  提升可能包含附加检查,检查失败时可能引起错误而不实际进行提升。

  除非另行指定,提升项修改被替换的对象。

原理

  项的子对象确定的表示可能被具体的互操作依赖。

  项的提升可以视为作为语法变换的消去 λ 抽象lambda 提升 (en-US) 的一般化,但此处和 λ 抽象没有直接关联。

  项的提升的变换可以是恒等变换,即直接以子对象作为替换的来源。其它变换如创建间接值和取间接值关联的对象,对应的提升引入和消除间接值

  项的提升的检查可包括为满足接口行为的语义检查和实现为预防宿主语言的未定义行为的附加检查。

  被提升的项往往被转移,因此一般地,需要在宿主语言中可修改。若被提升的项表示对象语言的值,一般也需要在对象语言中可修改。

对象属性

  除以上性质外,对象可关联其它元数据以指定对象的属性

  和属性对应的可组成对象的表示的非一等实体统称为标签(tag)

  对象具有的标签决定以下正交的性质:

  • 唯一(unique) 引用:指定对象的值关联到自身以外的不被其它对象别名的对象。
    • 以唯一引用关联的对象进行复制初始化时,不需要保留关联的对象的值。
  • 不可修改(nonmodifying) :指定对象的值保持不变。
  • 临时(temporary) 对象:指定对象的值被临时使用。

  唯一引用和不可修改是引用值的属性。对象语言中,引用值以外的对象是否具有这些属性未指定。为互操作目的可能具有实现定义的更强的假设。派生实现也可定义更强的假设。

  临时对象属性类似唯一引用,但限定的可以是对象自身而非关联的其它对象,即引用值自身和被引用对象可以分别具有临时对象属性。但除了引用值属性外,临时对象属性仅限在临时对象上出现。

注释

  不可修改的对象类似 [ISO C++] 的 const 类型的对象。[ISO C++] 的非类和非数组类型的对象不具有 const 修饰。

  对象的标签不在大多数对象中可见。另见引用值的属性

NPLA 环境

  求值环境维护名称和作用域。

  变量名(variable name)变量名称

  除非另行指定,环境维护的名称都是变量名。

  NPLA 的求值环境可以是:

  • 一等环境(first-class environment) ,即作为对象语言中的一等对象的环境。
  • 作为 NPLA 非一等对象环境记录(environment record)

  环境可引用若干个关联的其它环境为父环境(parent environment) ,用于重定向

  除非派生实现另行指定:

  • 环境可引用有限多个父环境,其数量的上限未指定。
  • 父环境在创建时指定,作为实体,之后不可变

原理

  如需求中指出的,本设计避免命名空间隔离,因此只有一种被环境支持且被求值算法统一处理的名称。

  若派生实现需要,可修改环境的内部表示和求值算法的名称解析步骤以对不同的名称添加支持。相反,在已有多种名称的设计中还原为一种设计是更复杂和不可行的。因此,在本设计中不预设多种名称。

环境对象

  环境作为可保持可变状态的对象,是环境对象(environment object)

  环境对象包含变量名到表示被绑定实体的映射,称为名称绑定映射(name binding map) ,实现变量绑定

  被绑定实体是对象时,称为被绑定对象(bound object) 。NPLA 环境对象中的被绑定实体包含一等对象,因此被绑定实体总是被绑定对象。

  环境对象对其中的名称绑定映射具有独占的所有权。名称绑定映射对其中的对象可具有独占或共享的所有权。因此,环境对象可对包括被绑定实体的名称绑定映射中的对象具有独占或共享的所有权。

  环境记录之间共享所有权,以环境引用访问。

  环境对象是名称解析时查找名称的目标

  父环境可共享环境记录。通过共享环境记录实现重定向的环境表示是链接的(linked) 而非平坦的(flat)

原理

  仅在可证明符合语义要求等价时,使用平坦的环境表示。

  对支持一等对象语义的设计,因为明确要求区分同一性,对象的存储不能被任意地复制。

  一般地,仅在可证明父环境对应的环境记录在对象语言和实现内部都不被共享访问(不具有共享引用且不被别名),且不存在任意派生实现定义的对释放顺序引起的可观察行为差异时,才能唯一具有这个父环境的环境为平坦的表示而保持语义不变

注释

  变量名通过以和字符串一一对应的值表示,没有直接的值的限制,可能为空串。

  若环境记录直接持有被引用对象,则这些对象是环境记录的子对象

环境引用

  环境引用是对象语言中访问环境记录的一等对象

注释 环境引用不是引用值。后者关联的被引用对象是一等对象。

  环境引用共享环境对象的所有权

  根据所有权管理机制的不同,环境引用包括环境强引用环境弱引用

  环境强引用可能共享环境对象的所有权,对环境对象的名称绑定映射持有的项具有间接的所有权。

  作为间接值,环境引用可被复制或转移。

  复制或转移环境引用不引起被引用的环境对象被复制。因此,按值传递环境引用不引起其中所有的对象被复制。另见引用

原理

  区分环境对象和环境引用在纯函数式语言不是必要的,因为不需要关心环境中的子对象的复制影响可观察行为

  否则,为支持影响可观察行为的环境的修改,非环境记录的环境引用是必要的。

  环境引用也是一种较简单且一般普遍高效的父环境的实现表示,可直接实现链接的环境而不需要证明和实现特设的其它内部表示能和抽象机意义上链接的环境保持语义等价。

  续延捕获若复制续延,可能引起关联的环境的复制,影响可观察行为并引起不必要的实现开销。为此,区分环境引用是必要的。

  以环境引用作为一等对象使访问被引用对象等环境记录的子对象时需要间接访问,在环境实际不需要被复制的大部分其它场景引起开销。这种开销是可接受的,因为:

  • 考虑到一等环境的普遍性,有必要有效支持对象语言中创建环境临时对象(而不仅仅是环境对象的引用值)的使用使之避免复制。
  • 实现可能提供附加的证明以在优化的翻译过程中替换环境引用为环境记录或其它不需要间接访问的中间表示,以消除这些开销。

  不论这样的证明是否存在,环境强引用和弱引用仍在对象语言中区分,以明确接口上的所有权语义

  引入环境弱引用作为一般的引用机制,且仅在必要时使用环境强引用,以避免过于容易引入循环引用引起强内存泄漏,符合适用性

当前环境

  NPLA 对象语言中,表达式的求值隐含对应一个环境对象作为求值算法需要的上下文输入,称为当前环境(current environment)

NPLA 表达式语义

  本节约定对象语言中的表达式相关的语义规则,特别是求值规则

  列表表达式作为一等对象列表

值类别

  表达式归类为具有以下基本的值类别(value category) 之一:

  • 泛左值(glvalue) :求值用于决定被表示的对象的同一性的表达式。
  • 纯右值(prvalue) :求值不用于决定对象同一性(而仅用于初始化临时对象或计算对象中存储的值)的表达式。

  一个泛左值可能被标记为消亡值(xvalue) ,以提供基于不同的所有权的行为。

  纯右值蕴含对象在可观察行为的意义上不被共享,类似不被别名的引用的被引用对象不被共享

  左值(lvalue) 是除了消亡值外的泛左值。

  右值(rvalue) 是消亡值或纯右值。

  基本的值类别、消亡值、左值和右值都是值类别。

  求值涉及表达式的值类别仅在必要时约定。

  表达式的值类别是上下文相关的,相同表达式构造在不同的上下文可能具有不同的值类别。

  NPLA 表达式允许在源语言语法之外的形式被间接构造,这些表达式同样具有值类别。

  求值规约可能重写一个表达式为具有不同值类别的为被规约项。即便不能被对象语言表达,只要不和其它语义规则冲突,它们在此被视为其它形式的表达式的表示,即项对象也对应地具有值类别。

  一般地,NPLA 的表达式不限定从源代码翻译确定,且一个表达式的求值结果不排除继续构成表达式而被求值,因此表达式的值也普遍具有值类别。

  除非另行指定,若一个 NPLA 表达式没有指定未被求值,则其值类别是其求值结果的值类别。

原理

  值类别根据是否只关心表达式关联的(对象的或非对象的)值,在需要对象时提供区分两类一等实体的机制,同时避免在仅需要表达式关联的值时引入不必要的其它对象。

注释

  对象语言表达式的值类别和 [ISO C++17](由提案 [WG21 P0135R1] 引入的特性)类似。

  值类别在 [ISO C++] 中实质上是一种静态类型系统。在 NPLA 中以更灵活的可在运行时访问的元数据代替,仍能体现类似的上下文相关性。

  除了标记消亡值,附加其它元数据也允许区分不同的所有权行为。

  NPLA 值类别和 [ISO C++] 也有显著的不同,体现在如下扩展:源语言语法外的被规约项的项对象视为 NPLA 表达式,也具有值类别。

  因此,作为求值结果的表达式的值也普遍具有值类别。若存在结果对象,可直接通过其类型确定

  作为静态语言,[ISO C++] 缺乏允许在运行时确定的求值特性,这些不同不在 [ISO C++] 中可用,可以被视为保守的扩展。

类型系统和值类别

  NPLA 中,值类别作为实体类型,被作为一种内建的类型系统

注释

  这和 [ISO C++] 不同。[ISO C++] 的“类型”的定义排除值类别,尽管值类别具有类型论意义上所有可作为类型讨论的对象的性质。

  另见引用类型

初始化

  对象被创建后可通过初始化(initialization) 决定其值,并可能存在其它作用。被决定的值是初始值(initial value)

  决定初始化这些作用的表达式是初始化的初值符(initializer)

  初值符的求值可能有副作用,其求值结果指定特定被初始化的对象的初始值。

  初始化包括被绑定对象的初始化和作为函数值的返回值对象的初始化。

  初始化被绑定对象可能以修改操作的形式体现,此时修改绑定具有副作用。若这样的副作用存在,每个被初始化的值后序于对应初始的计算。

注释

  初值符的求值的副作用不属于初始化,其求值结果和对象的初始值不一定相同。

  和宿主语言不同,初始化不是独立的依赖特定语法上下文的概念,但此处语义上的作用类似。

  对象的初始化一般可蕴含子对象的初始化。

复制初始化和直接初始化

  初始化包括直接初始化(direct initialization)复制初始化(copy initialization)

  函数可能接受引用值参数和返回值,是对函数的形式参数或函数值的复制初始化;其它初始化是直接初始化。

  复制初始化形式参数和函数值时,函数参数或返回值作为初值符。

注释

  区分两者和宿主语言类似。

函数参数和函数值传递

  部分函数可保证被初始化的对象副本中的值和初值符的值及元数据一致。

  这样的参数或返回值的初始化的求值称为转发(forwarding)

  转发也包括只部分保留上述部分元数据的情形。

  在允许保留元数据不变的上下文,转发在本机实现中可直接通过转移项实现。

  转发保持引入这些初始化的表达式(通常是被求值取得函数值的函数表达式)时,其求值结果(函数值)的值类别和初值符保持一致。

注释

  这里的元数据的一个例子是引用值的属性

  转发类似宿主语言的完美转发(perfect forwarding)

  另见函数值传递

对象的复制和转移

  可使用初值符为参数进行复制或转移操作以复制初始化对象,创建对象的副本

注释 这类似宿主语言中的类类型的值。其它情形另见复制消除

  对象的复制和转移不改变被转移后的类型

  对象的复制和转移对应蕴含其子对象被复制和转移。在互操作的意义上,若项具有子对象的独占所有权,这些子对象的复制构造函数和转移构造函数被对应调用。特别地,这里的子对象包括宿主值

  可使用转移操作时,不对作为对象的表示进行复制,因此不要求其中的子对象可复制,而避免引起错误

注释 这类似 [ISO C++11] 起选择类的转移构造函数代替复制构造函数。

  和 [ISO C++11] 起不同,上述可使用转移操作的条件和语法上下文无关:引起选择转移操作的条件由对初值符的谓词而非类似宿主语言的构造函数判断(详见默认值类别转换约定)。

注释 同宿主语言。

  除非另行指定,需要创建实体的副本时:

  • 若对象满足可转移条件,则转移而不是复制。
  • 其它情形实体被复制。

注释 一个主要的实例是按值的副本传递

项的转移

  一定条件下,作为对象的表示可被整体转移,而避免其中包含的对象的初始化在对象语言中具有可见的作用

  在互操作的意义上,因作为对象的表示的项的转移,项及其子对象的转移构造函数会被调用,但项的值数据成员中的宿主类型的转移构造函数不会被调用。

注释 这一般要求实现使用某种类型擦除使子对象类型的转移构造函数的调用不蕴含宿主类型的转移构造函数的调用。

  项的转移是析构性转移

  一般地,当对象需要被转移且没有约定转移后要求类型不变时,项的整体转移可代替对象的转移,避免初始化新的宿主对象,称为宿主对象转移消除。

注释 若需调用宿主类型的转移构造函数,需明确避免在代替对象的转移的上下文中进行操作。派生实现可提供这些操作。

  返回值转换上下文的转移蕴含宿主对象转移消除。

  若被复制消除的对象来自不同的项,则复制消除蕴含宿主对象转移消除。这包括所有对象转移的返回值转换上下文的情形。

引用值

  在对象语言中,引用值(reference value) 是作为引用,可保存在一等对象中。这样的一等对象是引用对象(reference object)

  引用值和引用对象的值具有引用类型(reference type)

  在特定上下文中,引用和其它一等对象的值的相同具有不同的语义,主要体现在引用值被按值直接初始化传递和按引用传递时。

注释 差异和 [ISO C++] 中使用对象类型和引用类型作为参数类似。

  NPLA 引用值总是假定和被引用对象关联。

注释 和宿主类型类似,引用类型没有空值。

  仅当以下情形中,NPLA 引用值的被引用对象是非一等对象

原理

  由于左值项对象被环境所有,为允许规约求值其中的被绑定对象,需要不被环境所有的(其它不同的)被规约项作为表示项对象作为中间值。

  这种中间值通过间接引用作为一等对象使用,也是一种间接值,即引用值。

子对象引用

  特定的引用值是子对象引用(subobject reference) ,其被引用对象是被另一个对象所有的、作为这个对象的子对象的一等对象。

  子对象引用对特定操作可表现和其它一等对象不同的行为。

  以下引用是子对象引用:

  • 子有序对引用(subpair reference)子有序对作为被引用对象的引用。
  • 子列表引用(sublist reference)子列表作为被引用对象的引用。

  语言可能引入其它的子对象引用。

引用值的有效性

  作为一种间接值,引用值有效当且仅当访问被引用对象不引起未定义行为。

  以下约定要求被 NPLA 实现支持的有效的引用值总是无条件地允许访问对象。

  有效的引用值应通过特定的构造方式引入,包括:

  • 在对象语言通过被引用对象初始化引用值。
  • 互操作引入的保证不引起未定义行为的引用值。

注释

  一些对象语言的操作可能引起引用值无效。例如,改变被引用对象可以使已被初始化的有效的引用值成为悬空引用(dangling reference)

多重引用

  被引用对象也可以是引用值。

  被引用对象不是引用值的引用值是完全折叠(fully collapsed) 的。

  除非另行指定,未折叠的(uncollapsed) 引用值指未完全折叠的引用值。

注释

  这和宿主语言不同。

引用值的属性

  引用值可以具有和作为引用值表示保存的属性相互独立的属性,保存其作为一等对象的状态。

  属性不可分割:一个引用值明确具有或者不具有一种属性。

  和对象属性对应,NPLA 指定的引用属性可以是:

  • 唯一引用。
  • 不可修改引用。
  • 临时对象引用。

  引用值属性指定通过引用对被引用对象的访问假定允许具有的性质,即便被引用对象自身没有具有这些属性。

  特定的操作使用引用值作为操作数,根据不同的属性决定行为,包括在违反属性引入的假定时引起错误

  在本节要求以外,除非派生实现另行指定,违反这些假定不引起 NPLA 未定义行为

  具体的引用属性满足以下语义规则:

  • 唯一引用允许通过引用值访问被引用对象时,对象可被假定不被其它引用而仅通过这个途径访问,即便实际存在其它途径的引用时可能引起不同的行为;在假定的基础上程序具有何种可能的行为是未指定的。
  • 唯一引用可被假定不被共享,被引用对象不被别名
  • 通过不可修改引用的左值的对象访问不包含修改。否则,若没有引起错误,程序行为未定义;但除非另行指定,不引起宿主语言的未定义行为
  • 具有临时对象引用属性的引用值是临时对象的引用值,其被引用对象是临时对象。

原理 宿主语言的互操作不被总是要求保证对象语言程序的可移植性,但不应引起实现自身的行为无法预测。

  对引用值的操作传播(propagate) 特定的引用属性,当且仅当:

  若操作数是具有特定引用属性的引用值,且结果是引用值时,结果具有和操作数相同的特定属性。

注释

  引用值属性和对象属性相互独立,类似 [ISO C] 和 [ISO C++] 在指针和引用等复合类型的 const 等限定独立于指向的对象或被引用对象上的类型不同。通过 const 等属性可以在指针或引用类型上单独限制类型,而不影响对应的被间接访问的对象。

  唯一引用蕴含的假定类似 [ISO C] 约定的 restrict 关键字,但程序违反假定的约束时不引起未定义行为。

  和 [ISO C++] 核心语言(但不是 [res.on.arguments] 中的标准库绑定到右值引用实际参数的约定)的右值引用类似,唯一引用不总是表示被引用对象不被共享。

  接受唯一引用的操作可能只假定被引用对象的子对象不被共享,也可能完全不进行假定,这依赖具体操作的语义。若需要和具体操作无关的无条件非共享假定,使用纯右值而非作为左值的唯一引用。

  和宿主语言的 const 限定类型类似,不可修改引用仅针对特定左值的访问;通过共享的其它未被限定的引用仍可修改对象。

  违反不可修改引用引入的假定的错误可能通过类型检查或其它方式引起。

  临时对象引用类似 [ISO C++] 的转发引用(forwarding reference) 中保留在表达式声明中的类型信息。

  因为 NPLA 不支持声明元数据,这些信息保存在对象的表示中,且在初始化时被引用值保存;也因此这些元数据可跟随一等对象传递。对临时对象,绑定操作可确保元数据被添加

  这也和宿主语言不同。在宿主语言中:

  • 无论是标记消亡值的右值引用类型还是标记是否可转发的引用的转发引用推断的类型信息(左值引用或右值引用)都是静态的。
  • 并且,转发的类型信息只在函数模板的局部有效,而不存在对应的跨过程传递机制。

引用值的消除

  作为间接值,引用值可被消除,即被其(可能多重)引用关联的被引用对象替代。

  未折叠的引用值消除一次引用值,结果仍是引用值。

  消除完全折叠的引用值的结果总是右值

  推论:因为引用值不循环引用自身,除非引用值已完全折叠,继续消除引用值得到的值和引用值是不同的值。

原理

  特定的引用值消除可蕴含对不可修改的传播的要求。这和 [ISO C++] 初始化引用时遵循的 const 安全性,属于类型安全性的一种。

  但是,消除引用不一定总是预期这种性质,特别当折叠不被预期时。

  例如,[ISO C++] 内建指针的不同级 const 不会被隐式转换直接折叠合并。消除间接的指针值不是隐式的(而依赖内建一元 * 操作符),这是因为指针作为类型构造器自身的类型安全需要;是否消除 const 限定符仍然需要基于其它理由考虑。

  而当被引用对象实现子对象时,修饰被指向的类型的 const 不会自动传播到子对象的类型中,此时可有 std::experimental::propagate_const 可选引入这种性质。

  对具有非间接访问的子对象的类型,这相当于 [ISO C++] 的 mutable 修饰符,可实现内部可变性。而允许子对象以外直接不传播不可变性,是一种结构性的平凡的扩展:这允许把被引用对象直接视为一种子对象的实现,而非要求引入新的名义类型

  在 NPLA 这样没有要求显式类型编码是否可变的语言中,首先要求总是具有不可修改的传播性质会显著增加规则形式上的复杂性。若具体操作需要传播不可修改性,仍可进一步约定。

注释

  典型地,消除引用值包括:

  和引用折叠不同,引用值提升转换不满足对不可修改引用属性的传播性质。

引用折叠

  和 [ISO C++] 类似,引用值在 NPLA 中默认不被继续引用,使用引用初始化引用会引用到被引用对象上,即引用折叠(reference collapse)

  引用值被折叠后结果和原引用值不同,当且仅当原引用值是未折叠的引用值

  和 [ISO C++] 不同,NPLA 不限制派生实现利用未折叠的引用值。

注释 特定的操作可能区分未折叠的引用值。

  引用折叠的结果是不可修改引用,若引用值和作为引用值的被引用对象之一是不可修改引用。

  引用折叠的结果满足不可修改引用属性的传播性质。推论:

  • 引用折叠的结果是唯一引用,当且仅当引用值和作为引用值的被引用对象都是唯一引用。
  • 引用折叠的结果是临时对象引用,当且仅当被引用对象是临时对象引用。

原理

  内部表示可支持间接的引用,以允许在对象语言中实现一等引用

  引用折叠对不可修改的传播性质的要求和 [ISO C++] 的引用折叠对 const 限定符的处理类似。

  引用折叠对唯一引用的要求和 [ISO C++] 的右值引用仅通过被折叠的引用都是右值引用类型折叠类似。注意 [ISO C++] 右值引用推断仅用于推断转发引用(forwarding reference) 参数,而非直接声明特定的右值引用类型。

  和唯一引用不同,临时对象相对唯一引用更接近 [ISO C++] 的声明的右值引用类型信息(而非推断值类别时使用的消亡值表达式的右值引用类型),一般不预期被折叠。

注释

  未折叠的引用值被折叠时,用于初始化的被引用对象可能仍然是未折叠的引用值。

对象的可转移条件

  根据项是否具有特定元数据的引用值可判断使用复制代替对象转移的条件

  对象的可转移(movable) 条件的判断基于首先基于值的类型

  • 非引用值(纯右值)总是可转移的。
  • 否则,对象是引用值。可转移由引用值的属性决定:当引用值是唯一引用且非不可修改,引用值是可转移引用,对应的被引用对象是可转移的。

引用值的表示

  作为引用值的表示引用项(reference term) 是包含项引用

  引用项中的项引用对象引用一个(其它的)项,即被引用项(referenced term) ,用于在必要时引入可被引用的一个项而不在 TermNode 中直接储存这个项的值。

  被引用项表示引用项作为引用值对应的被引用对象

  引用项在作为项对象外,保存标签作为引用值的属性表示

  临时对象可作为引用值的被引用对象。

  与此不同,非临时对象的引用值可作为一等对象而总是需要区分作为不同对象的同一性

  带有临时对象属性的引用值可在特定的操作中被视为和临时对象引用近似的引用值。

  子对象引用的表示是子对象引用项(subojbect reference term) ,和本节中的其它引用类型的表示兼容,但不完全相同。

  关于引用项的构成,另见 YSLib 项目文档 doc/NPL.txt

原理

  因为临时对象不是一等对象,临时对象的引用值可代替关联的被引用对象使之作为一等对象被访问。

  为在对象语言中区分引用值和非引用值的一等对象是必要的,引用项这样的特设表示是必要的。

  非引用项的表示则是针对临时对象的一种优化,因为使被引用对象总是在作为引用值的表示而:

  带有临时对象属性的引用值和临时对象的引用值不同,参见绑定临时对象属性

引用值的子类型

  根据表示和属性,引用类型具有如下子类型

  • 左值引用(lvalue reference) :以引用项表示的非唯一引用
  • 右值引用(rvalue reference) :以引用项表示的唯一引用。

  引用值是否作为左值使用取决于上下文。除非另行指定,引用值都是左值。

注释 在要求右值的上下文发生左值到右值转换

  引入不同的引用子类型后,NPLA 一等对象的值的类型和值类别存在以下一一对应关系:

  • 若类型是左值引用,则对应的值类别是左值。
  • 若类型是右值引用,则对应的值类别是消亡值。
  • 否则,对应的值类别是右值。

原理

  左值引用和左值引用与宿主语言中的对象类型的左值引用与右值引用分别类似。

注释

  在要求右值的上下文,作为左值的引用值发生左值到右值转换

不安全引用值

  特定的引用值是不安全引用值(unsafe reference value) ,可能和常规的其它引用值具有不同的内部表示。

  若实现支持不安全引用值,和其它引用值的行为不同由实现定义。

  派生实现可能添加更多对不安全引用值的假设。

原理

  不安全引用值可能放弃常规的引用具有元数据而能被更高效地访问。

值类别转换

  具有特定值类别的表达式可转换为不同值类别的表达式:

  • 除非另行指定,泛左值总是允许作为纯右值使用。从泛左值取对应右值的操作称为左值到右值转换(lvalue-to-rvalue conversion)
  • 从纯右值初始化可被对象语言作为一等对象使用的临时对象的引用值作为消亡值,称为临时对象实质化转换(temporary materialization conversion)

  左值到右值转换没有副作用。临时对象实质化转换没有副作用,当且仅当其中初始化临时对象时没有副作用。

  临时对象实质化转换中,纯右值被实质化(materialized)

  在求值子表达式时,按表达式具有的语义,必要时(如按相关规则判断上下文的值类别)进行值类别转换。

  NPLA 还提供可能使结果具有不同的值类别的引用值提升转换(reference value lifting conversion) 。以下规则确定引用值提升转换的结果:

  • 若操作数是引用值,则结果是操作数的被引用对象
  • 否则,结果是操作数。

  引用值提升转换蕴含引用提升,即使用被引用对象替换操作数。

原理

  为支持引用值作为一等对象(特别是未折叠的引用值),NPLA 提供比左值到右值转换更精细的引用值提升转换。

  值类别转换在特定求值中适用,因此不影响构造性的规则。

  特别地,列表左值(列表的引用值)不能代替列表,因此以空列表的引用作为最后一个元素的嵌套有序对是非真列表。这和 [R7RS] 约定空列表总是同一对象不同。

  这种设计使语言规则更容易在局部一致,同时显著减少实现(对象内部表示)的复杂性,并有助于提升实现性能的可预测性。

注释

  不同值类别表达式的转换和宿主语言中的部分标准转换类似。

  根据引用值的性质,易知左值到右值转换的规约是引用值提升转换的规约的传递闭包,即:

  • 若操作数是已完全折叠的引用值,则引用值提升转换等价左值到右值转换。
  • 否则,有限次的引用值提升转换等价左值到右值转换。

  引用值提升转换不传播引用值的属性,参见引用值的消除

  引用值提升转换不传播不可修改属性,类似 [ISO C++] 非引用值的转换在结果中不保留源操作数中的 const 类型。

  临时对象实质化可实现为空操作,因为项在先前(如返回值转换蕴含的引用值提升转换引用项提升操作的实现中)已被创建。

  互操作可能引入不以项表达的右值而需要首先创建项。

默认值类别转换约定

  除非另行指定:

原理

  类似宿主语言规则,并非所有上下文都需要转换。类似地,宿主语言的操作符(括函数调用的第一个子表达式)可直接使用左值而不需要转换。但和宿主语言不同,因为多重引用,不确定次数的连续的转换结果不同。因此除了上下文要求,有必要约定默认仅转换一次,而非确保转换结果到右值。

  必要时,具体操作仍可指定不同的规则。

  值类别和左值到右值转换在一些上下文的行为类似箱和自动拆箱,约定存在默认转换并不利于维护简单性

  • 特别地,和宿主语言不同,函数不包含充分的信息(参数类型)推断是否接受左值操作数,因此在不提供针对函数的重载(overloading) 一般机制的前提下,本机实现不能预知输入的操作数是否是左值,通常需分别支持左值和右值的操作数。
  • 即便提供重载,仍然较单一的值类别更复杂。

  但 NPLA 的设计中,值类别转换已被通过正确反映需求的存储和对象模型的设计隐含在项的内部性质中,因此不是可选的。

  由正确性的优先规则完整性应先于简单性被满足。

  而考虑统一性,对存储和对象模型的设计,用户自行的实现仍要求这些设施(尽管更困难)。

  关于箱和自动装箱,参见 YSLib 项目文档 doc/NPL.txt

返回值转换

  返回值转换(return value conversion) 是一次引用值提升转换和可选的一次临时对象实质化转换的顺序复合。

  返回值转换用于在对象语言中确定函数调用的返回值可包含函数体的求值结果到返回值的转换。

  引用值作为间接值,适用局部间接值安全保证。在返回值转换上下文中确定函数返回值的实质化转换上下文的部分操作消除引用值,即返回值转换,是这种情形的主要实例。

  这可约束作为间接值的完全折叠的引用值逃逸(因此访问被引用对象的值可不超出指向对象的存储期),而保证只考虑项可能是引用值时的内存安全。

  除非证明不需要临时对象,返回值转换中初始化临时对象作为返回值的项对象,否则临时对象被复制消除。是否存在复制消除是未指定行为。

  不论是否存在返回值转换,返回值的项对象来自返回的右值关联的临时对象实质化转换。这可能在返回值转换蕴含的项提升操作或之前的求值规约中蕴含。

注释

  返回值转换不保证未折叠的引用值在消除引用值后的结果不逃逸。

  为确保内存安全,程序仍需要保证被引用的对象的间接引用的对象生存期结束后,不能访问间接引用的对象。

  其它间接值的内存安全需要另行保证。

  是否需要返回值转换由实质化转换上下文中的被调用的函数而非上下文是否需要使用右值决定,无关被转换的表达式是否是左值,因此返回值转换不是左值到右值转换

  当前未实现是否需要临时对象的证明。

  另见项对象和关联对象所有权局部间接值安全保证

临时对象

  特定的 NPLA 非一等对象临时对象(temporary object)

  NPLA 允许(但不要求对象语言支持)一等对象构成的表达式通过特定的求值,在中间结果中蕴含这种非一等对象。

注释 这样的非一等对象不在源语言中可见,一般仅用于互操作

  临时对象的子对象不是临时对象。

  NPLA 对象语言在特定的上下文引入其它临时对象,包括:

原理

  为简化规约和互操作机制的设计,和 [ISO C++17] 不同,引入临时对象不包括延迟初始化或异常对象的创建。

  关于临时对象的子对象的规则,参见绑定临时对象中的原理。

注释

  关于临时对象的存储和所有权,参见求值和对象所有权

  关于临时对象的表示,参见临时对象的表示

  关于避免特定相关对象的初始化的要求,参见复制消除

  引入临时对象的一些上下文的和宿主语言类似。

实质化转换上下文

  可具有(但不保证具有)临时对象实质化转换的上下文包括:

注释

  一般地,被绑定为引用值的变量在活动调用关联的环境分配临时对象。此时,对象被调用表达式的项独占所有权,同时被绑定的环境独占资源所有权,并实现复制消除

  在不具有转换时,优化的实现可能消除函数调用(内联(inline) 展开)而不分配关联的环境,把临时对象分配到其它环境或者语言不保证可见的存储(如 CPU 寄存器)中,并同时实现复制消除。

  临时对象实质化转换引入临时对象的规则和 [ISO C++17] 不同:

  • 不论表达式是否作为子表达式使其值被使用(未使用的情形对应 [ISO C++] 中的 discarded-value expression ),都允许存在临时对象。
  • 要求复制消除而避免特定对象的初始化

返回值转换上下文

  返回值转换可引入实质化的临时对象,其中可能转移求值的中间结果;否则,对象被复制。

  此处被转移对象符合求值和对象所有权规则中的临时对象的定义,但除非另行指定,被转移的对象不在对象语言中可被访问。

  仅在对象被复制且复制具有副作用时,返回值转换具有等价复制的副作用。

复制消除

  NPLA 要求特定上下文中的复制消除(copy elision) ,排除复制或转移操作且保证被消除操作的源和目的对象的同一性

  复制消除仅在以下转换上下文中被要求,即直接使用被转换的源表达式中的对象作为实质化的对象而不初始化新的临时对象:

  非本机实现函数的函数体内指定的返回值不属于上述的确定返回值的上下文,但也不要求被复制消除。

  实现仍可根据当前环境来判断是否在允许消除对象复制的上下文中,而进行复制消除。

  复制消除不在被初始化对象以外引入新的对象语言可见的对象。

原理

  为维护语言规则的简单性和使用这些规则的程序的行为的易预测性,NPLA 的复制消除限于临时对象的消除。

  在完成实质化转换前的不完整的求值规约中的临时对象在逻辑上不需要作为一等对象存在,但纯右值作为对象表示中的子项,随纯右值在宿主语言中作为对象存在,以允许互操作。

  复制消除的目的 [ISO C++17] 类似。同时,提供语言支持也允许更简单地实现 C++ 互操作性。

  和 [ISO C++17] 不同的一些要求可简化语言规则和实现,例如:

  • 不区分求值结果是否被作为返回值或求值是否为常量表达式。
  • 本机实现函数的规则不要求 return 语句中的特定的表达式,而不需要依赖特定上下文的语法性质。
  • 同时,NPLA 不限制对象的类型([ISO C++17] 则要求特定的 C++ 类类型)。

注释

  在实现中,被转换的源表达式中的对象是待求值项项对象

  当前未实现按当前环境判断是否在允许消除对象复制的上下文中进行复制消除。

生存期扩展

  在使用纯右值初始化引用值时,扩展(extend) 源表达式的项对象生存期使之比其它规则决定的生存期延长。

  这和初始化非引用值类似,但实现需区分是否初始化的是延长生存期的临时对象,以确保之后能区分引用值初始化时是否按引用传递

绑定临时对象属性

  若实质化转换上下文支持绑定临时对象,按引用绑定(即绑定初始化使用按引用传递)的被绑定对象临时对象

  引入引用值的形式参数需要满足的要求由引入绑定的操作或派生实现指定。

原理

  绑定临时对象时指定临时对象属性允许区分通过引用绑定延长生存期的临时对象和非引用绑定的对象。

  一般地,表达式中的纯右值(非引用值)被绑定为临时对象,即被绑定的对象在初始化后具有临时对象属性

  这对应宿主语言中的转发引用参数(如 std::forward )中的情形:

  • 若模板参数 P 对应转发引用函数参数 P&& ,其中 P 是对象或对象的右值引用类型,保留从实际参数推导(deduce) 得到的但不是实际参数类型的信息。
  • 没有绑定临时对象属性的对象则同一般的非引用类型的对象类型参数(非转发引用)。

  P 在宿主语言中通过值类别推断,但不表示值类别。

  类似宿主语言,这种操作数表达式的值类别以外的形式是一种类型推断。因为推断规则和宿主语言的类型推导(type deducing) 相似,这种上下文可支持类似宿主语言的参数转发。但和宿主语言的 std::forward 不同,此处推断的右值除了是消亡值外,也可以是纯右值

  临时对象属性在绑定特定形式的参数时具有和 P 编码的附加信息类似的作用:

  • 不具有临时对象属性的引用值作为表达式,在初始化临时对象的引用时被视为左值(不影响其余属性)。
  • 其它表达式的推断结果是右值。

  带有临时对象属性的引用值和临时对象的引用值不同。特别地,作为引用属性值的临时对象属性允许在运行时作为对象的元数据访问以及跟随对象被跨过程传递,这无法被宿主语言支持,因为 P 表示的静态类型信息不在函数外可用,仅在模板的类型参数 P 中而不在运行时可访问的元数据中(事实上,也不在对象的动态类型中)保留。关于其应用,参见进一步讨论

注释

  因为宿主语言的引用折叠,以上 PP&& 一致。

  被绑定的这些对象可作为临时对象引用关联的被引用对象

  另见绑定操作

临时对象的表示

  作为一等对象的临时对象和其它一等对象表示方式一致。

  非一等对象临时对象包括:

  对临时标签对象决定的非一等对象,去除临时对象标签后,应具有一等对象表示。

  在项引用以外的临时对象标签仅在被绑定对象上存在。

  关于一等对象表示,参见 YSLib 项目文档 doc/NPL.txt

原理

  至少在逻辑上,临时对象作为对象语言中不可见的对象和一等对象相同的宿主类型(即项)作为内部表示。因此,区分其内部表示并非通过宿主语言中的类型,而需通过运行时性质确定。

  一些表示可能仅出现在临时对象中,而不是合法的一等对象表示。实现可据此进行一定运行时检查,以排除互操作或者错误实现中的误用。

  对临时对象标签决定的非一等对象和一等对象表现之间的要求简化实现的一些操作,使实质化不需依赖另行分配的资源。

  临时对象的子对象不是临时对象,简化对临时对象的一些操作,也减少可能使临时对象标签扩散(到非预期的上下文影响一等对象表示)而误用。

  关于被绑定对象的规则,参见绑定临时对象中的原理。限制标签的使用范围以使之不和其它表示冲突。

  关于实现中项的宿主类型和简化实现的例子,参见 YSLib 项目文档 doc/NPL.txt

注释

  直接构成项的非一等对象可以是通过源代码中的外部表示翻译变换得到的具有内部表示的数据结构的非一等对象,参见上述实现中项的宿主类型

表达式的类型

  NPLA 的类型系统使用隐式类型;默认使用潜在类型,保证表达式的值具有类型

  NPLA 表达式的类型是表达式求值结果的类型。

  空求值的求值结果要求未求值的合式的表达式应具有和语法分析的实现的输出兼容的类型。

  实现对特定的上下文的表达式可使用类型推断。由此确定的类型类似宿主语言的表达式的类型。

  表达式具有值类别。值类别的指派规则作为定型规则类型系统的一部分。但除非另行指定,值类别和 NPLA 及派生语言规则中描述的表达式的类型正交

  关于语法分析的实现和其中处理的类型,参见 YSLib 项目文档 doc/NPL.txt

注释

  类型系统和 Scheme 及 Kernel 语言类似;除了表达式具有值类别这点和 Scheme 和 Kernel 不同而类似宿主语言。

  表达式的类型和 [R7RS] 的 expression type 无关,后者是语法形式的约定(在 [R5RS] 和 [R7RS] 中称为 form );因为存在合并子作为一等对象的类型,不需要这种约定。

  NPLA 中值类别和表达式的类型正交,这类似宿主语言。这简化了相关类型规则的描述。

生存期附加约定

  和宿主语言不同,NPLA 子表达式的求值顺序可被不同的函数(特别允许显式指定对特定操作数求值的操作子)中的求值调整,不需要特别约定。

  NPLA 不存在宿主语言意义上的完全表达式,但在按宿主语言规则判断生存期时,使用本机实现函数合并视同宿主语言的完全表达式,其本机函数调用不引起函数内创建的对象的生存期被延长。

  临时对象生存期同时约束隐含的隐式宿主函数调用(如复制构造)。

  为保证求值表达式取得的临时对象的内存安全,函数合并同时满足以下规则:

  • 操作符和未被求值的操作数的直接或间接子表达式关联的对象以及求值操作数的子表达式引入的临时对象的生存期结束的作用应不后序活动调用结束。
  • 生存期起始和结束的顺序被确定(determined) 时,和对应所在的表达求值之间的先序关系同构;否则,其顺序满足非决定性有序关系。

注释

  生存期的顺序约束确保引入临时对象时,其生存期不会任意地被扩展而超过函数合并的求值。

  具体操作可在以上约束下指定被求值的操作数可能引入的临时对象的生存期。

尾上下文约定

  NPLA 表达式求值规约中最后一个规约步骤中的上下文是尾上下文

  尾上下文在 NPLA 中可满足一些附加的性质。

真尾规约

  尾上下文涉及的存储在特定情况下满足调用消耗的空间有上界(即空间复杂度 O(1) )。

  满足这种情况下的规约称为真尾规约(proper tail reduction)

尾调用和 PTC

  在尾上下文规约的调用尾调用(tail call)

  以真尾规约的实现尾调用允许具有不限定数量的(unbounded) 活动调用,称为 PTC(proper tail call ,真尾调用)。

  PTC 占用活动记录满足真尾规约的上界的要求。

  当宿主语言提供函数调用支持 PTC 时,可直接使用宿主语言的 PTC 调用,否则,需要使用其它替代实现机制确保 PTC 。

  非对象语言的调用的上下文中,若被调用时间接使用,也仍需要保证 PTC 。

  PTC 确保仅有一个活动的调用。不满足 PTC 的情形下,语言没有提供用户访问非活动记录帧资源的手段,因此可以认为是资源泄漏。但为简化语义规则,NPLA 不要求避免相关的弱内存泄漏

  NPLA 不添加保证活动记录帧中保存引用,销毁活动记录的帧可能影响环境中的变量生存期而改变语义。

注释 NPL 不保证一般对象存在引用

  因此,除非依赖本节中以下的规则,NPLA 不保证提供 PTC 支持;实现更一般的 PTC 依赖派生实现定义的附加规则。

  除非另行指定,NPLA 要求至少在被求值算法中蕴含的函数应用的求值支持 PTC 。

  为满足 PTC ,在生存期附加约定的基础上,尾上下文内可以确定并调整对象生存期结束时机:

  • 作为临时对象合并子及其参数可以延长生存期至多到尾上下文结束。
  • 被证明不再需要之后引用的对象,或未被绑定到活动记录上的项中的对象,可以缩短生存期。
  • 被延长生存期的对象生存期起始和结束的相对顺序保持不变。
  • 被缩短生存期的不同对象生存期结束的相对顺序保持不变。

  推论:被缩短生存期和延长生存期的对象的生存期结束的相对顺序保持不变。这由没有被调整生存期的对象与被调整生存期对象之间的生存期结束的顺序关系的传递性保证。

  延长临时对象生存期和宿主语言中允许扩展非完全表达式内的临时对象的效果类似,但条件不同。

原理

  要求 PTC 主要用例是支持 PTR 。相对 PTR ,PTC 更具有一般性,也适合对象语言可观察行为以外的普遍性质。

注释

  以上规则中被调整生存期的对象一般仅是参数和函数体内创建的对象。因此,不保证理论上允许的尾上下文的都满足 PTC 。一个例子是合并子中可以保存动态环境,这个环境可能被递归的调用引用,而无法提前释放。

  理论上 PTC 不要求延长生存期,仅要求特定情形下缩短生存期,且其它情形被释放的对象生存期不延长到尾上下文外。

  允许延长生存期是生存期附加约定的结果。

PTR

  PTC 的活动记录性质也在一般的递归规约时体现,被称为 PTR(proper tail recursion ,真尾递归)。

  和 PTC 不同,PTR 要求的递归规约不一定是对象语言中的调用,以 PTR 描述时仅强调递归,不考虑尾上下文的适用性。

  通过特定的保持语义等价的变换,对象语言可要求尾上下文作用于函数调用以外的上下文中(例如非函数合并的语法上下文)使用真尾规约实现。

  除非派生实现另行指定,NPLA 对象语言不指定使函数调用以外的上下文作为尾上下文的要求;函数调用以外的尾上下文规约的仅可能用于实现的元语言中的管理规约;非管理规约的真尾规约都用于尾调用。

  此时,PTR 等价被递归调用的 PTC 。但由于支持 PTC 在非递归规约情形时也影响语言实现的一般构造,所以描述要求时一般不以 PTR 代替 PTC 或真尾规约。

  关于 PTR 在 Scheme 为基础的形式模型,参见 [Cl98] 。

  PTR 的一个更激进的实现优化方式是 evlis tail recursion ,参见以下文献和参考资料:

  因为 NPLA 使用链接的环境,不支持实现其中更激进的 safe-for-space 保证。

原理

  支持 PTR 使重入的函数调用保持较小的空间开销。这允许使用递归的函数调用代替尾上下文中特设的循环(loop) 和迭代语法实现等效的算法功能,满足简单性通用性

  与控制状态和支持一等状态的实现之间具有的偶然耦合不同,使用支持 PTR 的递归函数调用代替循环的耦合可以是足够必要的:它排除了特设的循环语法的需要,同时也能满足实现自身的简单性,也因此可能更高效。

  为支持一等对象,可被共享的环境一般不支持平坦的表示

  不具有 safe-for-space 保证时,实现对程序的闭包变换(closure conversion) 可能创建多余的循环引用且无法被运行时有效地分辨,而造成资源泄漏。通过明确支持这类保证的变换(如这里描述的设计 )可避免变换引起资源泄漏。

  只要捕获自由变量的静态环境可被程序在创建函数时明确指定,safe-for-space 保证就不是必须的:避免在语义规则约定之外的生存期延长和资源泄漏是用户程序的责任;NPLA 程序可精确控制对象生存期,同时应当避免循环引用

  对 safe-for-space 保证的证明(如 [Cl98] 和 Closure Conversion Is Safe for Space ),隐含要求和上述一等对象支持冲突的条件:环境中引用的对象总是可被复制的没有可观察行为的值。这实质上要求支持共享引用乃至可能要求一等对象都是引用

  因为使用链接的环境的要求,一般情形不支持对 safe-for-space 的变换。

  即便允许类似的变换,这也仅保证不存在不可达,仍然不保证资源被及时回收——全局机制可能具有不确定的延迟而造成的实时资源泄漏。

TCO

  TCO(Tail Call Optimization ,尾调用优化)是在以尾上下文规约时,允许减少修改规约状态的优化。

  一般地,TCO 可重新排列规约过程中的被语义允许调整的副作用和其它不影响可观察行为的状态的调用,减小空间开销。

  TCO 的这种性质可以在宿主语言不支持 PTC 时用于实现对象语言的 PTC

注释

  关于 TCO 和 PTC 的差异,另见这里

宿主语言中立

  C++ 不要求实现支持 PTC ,也不保证支持 TCO 。因此,对象语言的 PTC 要求显式的 TCO 实现。

  为可移植地支持 TCO ,NPLA 不依赖宿主语言中不可移植的互操作的活动记录(通常是体系结构相关的栈)。

注释

  尾调用可避免尾上下文中非嵌套调用安全的情形的宿主语言实现的未定义行为,但不保证非尾上下文中具有类似的性质。

TCO 实现策略概述

  TCO 包括以下形式:

  • 静态 TCO :实现时替换宿主语言中不保证满足 PTC 的构造为满足 PTC 的构造。
  • 动态 TCO :运行时调整直接或间接表示对象语言构造的数据结构和状态,使状态占用的空间复杂度满足 PTC 要求。

  静态 TCO 也适合非对象语言的调用的上下文

  不依赖宿主语言特性的静态 TCO 包括以下形式:

  • 替换宿主语言实现中的不保证满足 PTC 的递归调用为满足 PTC 的结构(如循环结构),包括直接编码和自动的变换(transformation) ,称为宿主(host) TCO 。
  • 替换不满足 PTC 的对象语言原语为满足 PTC 的表达形式,称为目标(target) TCO 。

  不依赖宿主语言特性的动态 TCO 包括以下形式:

  • 通过合并不同活动调用中活动记录占用的冗余状态,减少宿主语言的活动调用同时占用的总空间,称为 TCM(Tail Call Merging ,尾调用合并)。
  • 引入具有便于操作控制作用的构造,同时作为一些其它优化的基础,以消除部分活动记录状态的分配,称为 TCE(Tail Call Elimination ,尾调用消除)。

注释

  本节的内容不影响对象语言的语义,但可能影响互操作的接口兼容性和实现质量。

  关于对实现的具体影响,参见 YSLib 项目文档 doc/NPL.txt

NPLA 数学功能

  NPLA 数学功能(模块 NPLAMath )提供数学功能和相关支持。

  关于 NPLA 数学功能的规格说明的其它部分,参见 YSLib 项目文档 doc/NPL.txt

数值类型

  NPLA 数值是 NPLA 对数学意义上的数(number) 的建模。

  被建模的数是 NPLA 数值的真值(true value)

  NPLA 数值的集合到真值的集合的映射是满射;除此之外,也存在不被 NPLA 数值建模的数,这些数可能被 NPLAMath 未来的版本支持作为真值。

  除非另行指定,NPLA 数值的行为由对应的真值的数学含义决定。

  基于宿主语言的类型系统,NPLA 支持以下按数值范围从小到大排列的本机整数和浮点数作为宿主类型

  • signed char
  • unsigned char
  • signed short
  • unsigned short
  • int
  • unsigned
  • long
  • unsigned long
  • long long
  • unsigned long long
  • float
  • double
  • long double

  文法表示:

  支持的数值类型以 <number> 表示,具有以下表示数值的子类型

  • <complex> :复数。
  • <real> :实数。
  • <rational> :有理数。
  • <integer> :整数。

  其子类型由数学定义蕴含,即以上类型中,后者依次是前者的子类型。

  当前所有数值都是 <real> ,因此暂时没有针对 <number> 值是否属于 <real><complex> 的类型检查。

  和数学意义上的实数不同,<real> 也包含以下可带符号(sign) 的特殊值(special value)

  对应地,<complex> 也包含实部和/或虚部是上述特殊值的特殊值。

注释 当前所有复数都是实数,因此虚部总是 0 。

  根据数值是否完全保留真值在数学上的唯一性即精确性(exactness) ,数值分为精确数(exact number)不精确数(inexact number)

  精确数和对应的真值总是相等;不精确数和真值不严格相等。

  有限的不精确数的偏离程度可通过实数描述,即(绝对)误差(error) 。精确数的误差恒等于 0 。

  除非另行指定,特定不精确数的具体的误差是未指定的。

  数值的绝对精度(precision) 是其内部表示蕴含的误差的上界的倒数。对确定使用进位制的表示,精度也指精确表示的数值位数。

  数值的任意精度(arbitrary precision) 指除实现环境的可用资源(一般即存储空间)限制外,不限制精度。

注释 为支持更多数学上有意义的真值,未来可能引入其它类型来表示任意精度的整数、有理数及数学意义上的扩展(如复数和四元数)。

  数值的内部表示中能以实数描述的度量应至少具有整数数量级精度,即误差不大于 1 。

  精确数和不精确数在数值上可能相等,而类型不同

  宿主类型中的本机整数和浮点类型是数值类型的子类型,分别称为 fixnum 和 flonum 。这些类型在项的内部表示预期直接占据本机存储而不需要动态分配。

  Fixnum 总是精确数;flonum 总是不精确数。

注释 当前实现中,所有数值是 fixnum 或 flonum 之一。两者分别是宿主的整数类型(排除字符和 bool 类型)以及浮点数类型。

  Flonum 支持带符号的无限大值以及 NaN 值作为特殊值。其它值都是有限值。特殊值可能具有不唯一的内部表示,但和有限值的表示都不同。

  Flonum 中可存在小的非零数,可能和其它数值不同的内部表示而更容易在计算中损失精度,即非规格化(denormalized) 数值。

  整数值的数值具有整数类型。这包括所有的 fixnum ,以及 flonum 中是整数的数值。这不和宿主类型直接对应。

注释 一个 flonum 是整数,当且仅当它的值取整后结果和原值相等。这里的取整使用可使用任意的舍入。[R7RS] 对不精确数有类似的定义(仅使用 round )。

  对 fixnum ,+0-0 是相等的数值。Flonum 不同符号的零值在值的表示中可以不同,但在数学意义上相等,表示同一个数。实现中的其它和具体表示无关的等价谓词是否表现这种不同是未指定的。

注释

  NPLA 的不同的名义数值类型的集合到宿主类型的集合的类型映射是满射,即本节指定的宿主类型总是关联至少一个能表示它的值的 NPLA 数值类型。

  同时,NPLA 数值类型可以映射到其它类型,特别地,NPLA 整数的类型映射目标是宿主语言整数类型和包含整数值的非典型宿主类型的并。所以,NPLA 数值整体的类型映射不构成简单的一对多或多对多关系。

  特殊值同 ISO/IEC 10967–1 (LIA–1) 定义,引用 [IEC 60559] (IEEE-754) 的具体值,仅适用于浮点数。

  无限大值在数学上属于超实数(hyperreal number) ,在浮点数实现中属于扩展实数(extended real number)

  无限大值的符号在数学意义上是必要的,因此也被要求区分。

  精确性、fixnum 和 flonum 等区分同 [R6RS] ,但具体实现要求不尽相同。

  NaN 不是数学意义上的数,表示特定的没有数学定义的计算结果。NaN 和任何数值比较总是不相等。

  NPLA 的宿主语言支持的 NaN 值带有符号。不是所有实现都区分符号,如 [ECMAScript] 。

  整数精度外的数量级精度仅在确定使用的进位制底数时和绝对精度可比较,因此常用于描述特定实现的内部表示(例如,[ISO C] 定义的浮点数精度即有效数字的位数)。但是,不比较具体大小时,有限的数量级精度和绝对精度性质可以一致,这种上下文可不区分两者。

  大多数不精确数的浮点表示的高效实现使用底数 2 。

  参照 [IEC 60559] ,浮点格式的无限大值和有限数值是浮点数;NaN 不是浮点数。两者统称为浮点数据(floating-point datum)

  宿主类型中实现为 [IEC 60559] 的非规格化(denormalized) 浮点数是具有不唯一的内部表示的小的非零数值。使用 IEEE-754 2008 以来的定义,这些数是非规格(subnormal) 数。

  和 [RnRK] 不同,本文档没有指定可选的模块,也没有指定精确的 ±∞ 值。

  和 [RnRK] 不同,不精确数不指定边界和主值(primary value) ,NaN 值被显式提供而不是唯一的 #undefined 值,非规格数不作为 #undefined 。这同时不要求在任意的操作中检查 #undefined 值并引起错误

  尽管容易损失精度,区分不同的非规格数的数值仍然有意义。同时,也避免和 NaN 用以表示数学上未定义操作的结果(如 0 除以 0 )引起混淆。

  数值相等和一般对象相等可使用不同的等价谓词。和一般对象比较不同,数值相等比较可对参数要求数值类型,否则引起错误。两者比较结果可能也不总是相同。如 Scheme 的 =eqv? 以及 Kernel 的 =?equal?

数值操作约定

  在对象语言中,数值操作是可使用数值作为算法输入的值的操作。NPLA1 提供本机 API 支持这些操作的实现。

  数值操作数和非数值操作数分别是具有和不具有数值类型的操作数。

  数值操作蕴含对应的数值计算,接受至少一个数值或非数值操作数,预期得到计算结果

注释 非预期情形可引起错误

  其中,计算结果依赖影响计算结果的操作数,并依赖至少一个数值操作数。

  除非另行指定:

  • 在数学上有意义的前提下,数值操作同时支持以上尽可能多的数值类型的操作数。
  • 数值操作对预期的数值操作数进行类型检查,失败时出错。
  • 数值操作不区分数值操作数中对应的真值相等的精确数或不精确数。
  • 可假定数值操作数和计算过程中不出现 SNaN(signaling NaN) (en-US) 值。
  • 若作为操作数的精确数决定计算结果在数学上未定义,则引起错误。
  • 不精确数计算中的舍入方式未指定。
  • 若计算结果是数值,则:
    • 若被计算结果依赖的任一操作数中具有 NaN 值,则依赖这个操作数的数值操作结果也是 NaN 值。
    • 输出的类型的值域能表示操作结果;除操作的语义和本节的其它规则蕴含外,具体类型未指定。
    • 若作为操作数的精确数决定的计算结果是不精确数表示的有限数值,则这个不精确数应是所有相同内部表示的数值中和结果的真值误差最小的数值。
    • 对数学上封闭的计算,结果具有不超过所有数值操作数范围的数值类型。
    • 除非不能在结果类型中表示计算结果的范围:
      • 若数值操作的所有数值操作数都是精确数,结果不是不精确数。
      • 数值操作的实现不损失按数学定义得到的中间结果的精度;结果的误差仅来自其依赖的数值操作数引入的累积误差。
    • 若计算结果是不精确数,则:
      • 若计算结果是小于最小可唯一表示的 <real> 值,则对应的数值操作结果是不精确数 0 。
      • 计算结果中真值等于 0 的数值以及 NaN 值的符号是未指定的。
      • 若计算结果中无限大数值不能通过数学上有意义的方式确定符号,则对应的数值操作结果是无限大值或 NaN 之一,具体选择未指定。
    • 数值的宿主类型未指定。

原理

  因为典型的高效实现实现依赖外部环境对浮点数的支持,设计策略以保持互操作的便利性相关。

  • NPLAMath 实现不访问 SNaN 值,也不需要访问宿主语言的浮点环境,但不假设总是使用默认浮点环境。
    • 这不阻止和使用 SNaN 的本机实现的程序链接和调用,这有助于保持互操作性。
    • NPLAMath 实现不保证检查访问浮点环境的副作用是否存在。若互操作需要改变浮点环境,应避免破坏实现的假设。
  • 不依赖零值的符号、NaN 的符号以及 SNaN 的处理和许多宿主实现的默认情形一致而能简化一般的实现,如:
    • GCC
    • Microsoft VC++
    • 不要求使用 GCC 时启用 -ffloat-store 。Microsoft VC++ 默认的 /fp:precise 的类似语义也不被依赖。

注释

  数值操作可能允许非数值的操作数,这些操作数也可被计算结果依赖。

  因为 flonum 能表示所有实数数值范围,所以实数范围以内的操作不会引入操作数以外的其它 flonum 类型。

  数值操作抛出异常的要求不一定在每一个实现数值操作的 API 中蕴含,因为这些 API 不一定是数值操作的完整实现。

  抛出异常和宿主环境的异常和浮点异常没有直接关联。

  若数值操作指定非数值计算结果(如布尔值)或者不能表示的 NaN 值的计算结果,则即使依赖 NaN 数值操作数,也不是 NaN 值。

  和 [RnRK] 不同,数值操作不支持不同的全局模式。

  特定情形下,精确数可能替换计算结果中的不精确数:

  • 当按数学定义能被精确表示时,计算结果可以是符合要求的任意一个类型的精确数。
  • 否则,当实现能证明不精确数值足够小到不足以影响结果的表示,且存在真值相等的可用的精确数时,可使用这个精确数代替不精确数。
  • 当前实现没有这类证明机制。

  IEEE-754 使用渐进下溢(gradual underflow) ,使零值和相邻的非零浮点数的真值之差不会显著大于其它两个浮点数真值的差,而使小的非零浮点数之间的差不等于零。这体现了支持非规格化数的实际作用。但一般数值计算仍需要累积误差。

  虽然可能影响结果,浮点数实现的内部状态(如舍入模式)的访问不被直接支持。

  浮点数 0 和 0 之差的符号可能取决于舍入模式。数值操作一般不保证结果 0 的符号,但以依赖表示的形式仍可确定符号。

  除满足必要的精度要求的前提外,互操作以外目的的数值的宿主类型的具体选择在维持计算正确性的意义上通常不重要,因此默认不要求指定。

  对特殊值,因为 [RnRS] 只要求 .0 后缀的特殊值字面量,需保持兼容时,程序可只使用这些形式的字面量。为此,实现可使用带有 .0 后缀特殊值的数值字面量的对应的宿主类型,以减少潜在的可移植问题。

  关于派生实现支持的数值字面量,参见 NPLA1 数值字面量

数值表示

  支持解析的数值以字符串作为外部表示。作为字面量时,构成数值字面量的词法

  数值的外部表示和内部表示应支持往返(round-trip) 转换,即转换的内部或者外部表示输出可被输入接受。

  往返转换中,精确数转换保持任意(无限)精度;不精确数经有限次转换不继续损失精度。

注释 即便损失精度,也应总是满足结果至少不低于整数精度

  支持的外部表示和对应的含义具体包括:

  • 数值的外部表示中起始的一个 +- 字符指定符号。
    • 这可能是可选的。若符合规则的数值字面量没有指定符号,则隐含为 +
    • 注释 不精确数可能在内部表示支持不同符号的零值
  • 以下优先匹配较长的模式。
  • 匹配正则表达式 (+|-)?[0-9]+ :十进制整数值。
    • 不论符号,当前精确数数值字面量默认都具有宿主类型 int ,除非其绝对值太大而无法被表示,使用其它类型代替。
    • 除非精确数字面量的数值超过所有 fixnum 的可表示范围,都具有 fixnum 值。
    • 除非精确数字面量的数值超过所有支持的精确数的可表示范围,都是精确数。
    • 注释 当前精确数的表示范围是 fixnum 中宿主类型的值域的并集,因此超过 fixnum 可表示范围的数值不是精确数。
  • 匹配正则表达式 (+|-)?[0-9]+\.[0-9]*(+|-)?[0-9]+(\.[0-9]*)?(E|e|S|s|F|f|D|d|L|l)(+|-)?[0-9]+ :十进制不精确数数值。
    • 不精确数数值字面量的解析使用未指定的浮点数舍入模式,其误差不大于最后一个在规格化范围内表示的十进制小数位为 1 时的绝对值的真值大小。
    • 解析不精确数外部表示得到的真值和内部表示可具有误差。
      • 注释 误差和具体宿主语言支持相关,通常以任意可能符合宿主语言要求的舍入模式下的最大值计。
    • 第一种形式是直接记法。
    • 第二种形式是科学记数法(scientific notation) ,在指示指数的指数字母前后匹配的数字序列分别是有效数字(significand) 和指数(exponent) 。
      • 指数字母表示作为 flonum 的不同精度:
        • Ee :默认精度。
        • Ss :短(short) 精度。
        • Ff :单精度(float) 。
        • DD :双精度(double) 。
        • Ll :长精度(long) 。
        • 同组的字母含义等价。以上精度中,默认精度不低于双精度,其它精度依次不低于之前的一个。
      • 精度可影响内部存储的宿主类型。
    • 若字面量指定的数值小于或大于使用的类型的数值表示范围,则值为对应类型具有相同符号的零值或无限大值
  • 匹配正则表达式 (+|-)(inf|nan)\.(0|f|t) :带符号的 flonum 特殊值。
    • 其中,inf 指定无限大值,nan 指定 NaN 值。
    • 后缀指定精度:
      • 0 :默认精度。
      • f :单精度。
      • t :扩展(extended) 精度。
      • 以上精度中,默认精度不低于双精度,扩展精度不低于默认精度。

  除非派生实现另行指定,以上要求外的数值的子类型和内部表示未指定。

原理

  浮点数解析存在不同精度的算法。

  若以二进制浮点数和经过舍入的十进制表示相互转换不损失精度为前提,宿主语言的 std::numeric_limitsmax_digits10 位十进制数字足够表示。

  (对 [IEC 60559] 的二进制浮点数情形的证明参见这里的 Theorem 15 。)

  但是,对任意有效输入的结果误差都不大于 1 ULP(unit in the last place) 的不经舍入的值完全精确值(full precision) 的精确解析算法,对实现的要求较高,且性能可能明显较低,故不作要求。

  (对 [IEC 60559] 的二进制浮点数的情形,需要数十倍的中间存储,参见这里。)

  和宿主语言的 std::strtod 不同,允许使用宿主语言中的任意浮点数舍入模式,而不要求不同浮点数舍入模式下的结果一致性。

  浮点数精度的 floatdouble 在典型实现中的内部表示格式同 [IEC 60559] 的二进制的单精度和双精度浮点数。

注释

  串模式 (+|-) 表示带有可选前缀符号(仅限一个),影响值的数值。

  同 klisp 而不同于 [RnRS] 的字面量词法,小数点不能出现在词素中符号以外的第一个字符;但 klisp 的 string->number 没有这个限制。

  同 [RnRS] 而不同于 klisp(两者包括字面量词法和 string->number ),小数点允许出现在词素的结尾。

  当前不精确数数值都具有宿主类型 double 。即便 long double 可能具有更大的数值范围,也不能通过解析数值表示直接取得。

  类似地,[Racket] 默认不启用 extflonum。关于数值操作也类似,参见数值操作约定

  当前允许在宿主值不能完全存储不精确数的字面量数字时,解析十进制不精确数字面量存储的值可能和字面量的数值的真值之间具有超过 1ULP 的误差。这可能影响和精确数之间的比较。

  当前实现使用四舍五入。

  关于宿主语言中 std::strtod(同 [ISO C] 标准库的 strtod )舍入要求的一些问题,参见:

  数值字面量的词法同 [RnRS] 的一个子集。

  [RnRS] 指出实现可能允许用户修改不同的默认精度。这指出精度不是固定的,但不是实现要求。   [RnRK] 的有限数的对应子集接近 [RnRS] 的设计,但没有明确指定字面量词法规则。其中对精度的表述略有不同:

  • 没有指定大写字母。
  • 指定 sfdl 的精度递增,没有显式允许不同的精度映射到相同的内部格式。
  • 没有显式允许用户指定的默认精度。

  但是,SINK 和 klisp 实际上都不符合前两点,而更符合 Scheme 的实现。[RnRK] 在此可能不完善或表述有误。

  当前 klisp 的实现不允许 Ee 之前没有小数点,且在存在 Ee 时省略之后的指数的任意部分,和 SINK 以及 [RnRS] 都不同。本设计遵循后者。

  当前 NPLAMath 精度对应的宿主类型指派如下:

  • 非特殊值:
    • e :同 d
    • s :同 f
    • ffloat
    • ddouble
    • llong double
  • 特殊值:
    • 0double
    • ffloat
    • tlong double

  指定特殊值的精度的词素语法兼容 [Racket] 。

NPLA1 核心语言

  NPL 是独立设计的,但其派生语言和其它一些语言有类似之处;这些语言和 NPL 方言之间并不具有派生关系。但为简化描述,部分地引用这些现有语言规范中的描述,仅强调其中的不同。

  NPLA1 符合 NPL 和 NPLA 的语言规则,其实现环境还应提供本章起的其它程序接口。

  互操作中的一些接口处理的值可约定宿主类型。但这些类型不一定在对象语言层次上稳定,可能在之后的版本变化。稳定性由具体实现提供的附加规则(若存在)保证。

  NPLA1 和 Kernel 语言(即 [RnRK] )的特性设计(如 vau和作为一等对象的环境表达过程抽象)有很多类似之处,因此许多概念是通用的;但从设计哲学到本章介绍的各个细节(如默认求值规则)都存在深刻的差异。

  部分名称指定的操作和 [RnRS] 或 klisp 指定的类似。

  以下章节主要介绍和 Kernel 约定不同的设计。各节的通用约定不再在之后的各个接口单独说明。

NPLA1 对象语言约定

  NPLA1 仅使用宿主语言类型作为在对象语言可表达的状态

  在 NPLA 的基础上,NPLA1 要求对象语言支持以一等对象作为表达式并被求值

  类型等价性基于类型映射及其实现,由 [ISO C++] 的语义规则定义。

  值等价性由宿主环境== 表达式的结果定义。

  除非另行指定,所有类型的外部表示都是允许从作为内部表示的项节点确定的同宿主类型的空字符结尾的字符串(即 [ISO C++] 的 NTCTS )。

  关于作为表达式的求值和类型映射的实现,参见 YSLib 项目文档 doc/NPL.txt

标识符附加规则

  当前仅支持标识符作为名称

  部分名称是保留名称:含有 $$ 的名称保留给宿主交互使用;含有 __ 的名称保留给 NPLA1 实现。

  在 NPLA 规则的基础上,在内部表示中显式使用保留给实现的标识符的程序行为未定义

注释 这包含在源代码以外的中间表示使用的情形,但不包含作为用户输入的数据。

NPLA1 互操作约定

  基本规则参见 NPLA 互操作支持

  非 NPLA1 实现提供的类型的宿主 == 操作不要求支持嵌套调用安全

  作为 NPLA1 嵌套调用安全的约定的扩展,若存在 == 操作不支持嵌套调用安全的类型,具体类型由派生实现的定义。

  对象语言操作和互操作不修改对象语言中已经可见的一等环境父环境

原理

  NPLA1 中提供的类型仍需要支持嵌套调用安全,以满足嵌套调用安全的约定中的要求。

  关于 NPLA1 嵌套调用安全的具体约定和其它实现原理,参见 YSLib 项目文档 doc/NPL.txt

  避免修改已在对象语言可访问的一定对象的父环境符合同 [RnRK] 的环境封装性对对象语言的要求。这允许实现假定仅在有限的上下文中父环境可修改,而减少优化实现的难度。

NPLA1 程序实现

  本章指定 NPLA1 对象语言的核心语言特性。包含库特性的其它设计参见 NPLA1 参照实现环境

原理

  一般的语言能支持不同实现形式的库,包括源程序和其它无法判断是否和特定源程序关联的翻译后的程序。

  复用这些程序时,可能需要根据不同的形式而分别处理:源代码被读取和求值而加载,而其它格式的翻译形式可能直接映射存储后经特定的检查即被加载。

  但是在可复用的意义上,这些不同的形式是一致的,都视为库。

注释

  和 [RnRK] 不同,库不限定其实现形式。[RnRK] 指定的库实质上是可使用对象语言派生实现的库。

  典型的静态语言不保证程序执行时能对源程序进行翻译,因此加载程序的限制通常更大,可能无法处理源程序形式的库而首先需要分离翻译为其它格式。NPL 一般不具有这个限制。

  关于对象语言的派生实现,参见 YSLib 项目文档 doc/NPL.txt

外部环境

  基于 NPLA 整体约定,由 NPL-EMA ,NPLA 的实现不假定存在多线程执行环境。

  但是,宿主语言可支持多线程环境执行,可引起宿主语言的未定义行为

  作为 NPLA 的派生,NPLA1 对象语言程序也具有相同的性质,除非另行指定需要和外部环境交互的特定操作,不需要假定 NPLA1 引入存在多线程执行环境。

附加元数据

  NPLA1 实现可提供和实现环境或具体 NPLA 对象关联的附加的资源,用于提供程序运行时可得到的附加信息,如源代码位置。

  是否存在这些附加元数据(extra metadata) 和附加元数据的具体内容可影响特定的行为。

注释 如符合诊断中要求的实现的具体行为。

  这些影响是未指定的,但除 NPLA1 程序直接依赖具体数据而进行的操作外,不应影响程序的其它语义(例如,引起程序终止)。

NPLA1 扩展支持

  本章中除循环引用的限制外,不支持的特性可能会在之后的实现中扩展并支持。

NPLA1 未定义行为

  NPLA1 对象语言程序中的未定义行为包括 NPLA 未定义行为和以下扩展 NPLA 未定义行为:

  派生语言可约定其它未定义行为。

原理

  扩展 NPLA 未定义行为可提供更严格的要求使实现更简化。

  关于环境的一些未定义行为可视为违反内存安全,而不需要单独实现。

接口文法约定

  为描述对象语言规则和程序接口,本节约定文法形式。

注释 这仅用于描述接口,不依赖 NPL 语言的基本文法

  规约操作中项的约束通过以 <> 中的同类名称表示。

  为区分同类约束的不同项,约束的名称后(在 > 之前)的可带有以 1 起始的正整数序数。除非另行指定,这些序数仅用于区分不同的同类约束项,无其它附加含义。

  本节描述的项是被用于求值(参见求值算法)的或它们的直接文法组合。前者应能涵盖原子表达式、其求值结果以及预期在对象语言中实现对象语言求值算法所需的 NPLA1 用户程序构造。

  库可参照本节的方式约定通过项的文法,以支持仅在特定库使用的操作数。

  除非另行指定,本节的对应要求同时适用于本节中和这些库中引入的项。

元文法基本约定

  元语言文法:

  • ... :Kleene 星号,重复之前修饰的项 0 次或多次。
  • + :重复之前修饰的项 1 次或多次。
  • ? :重复之前修饰的项 0 次或 1 次。

注释

... 一般在结尾出现,表示元素构成列表。

  和 [RnRK] 不同,不使用 . 分隔有序对,不使用元素名称的复数表示列表。

实体元素文法约定

  指定具名的函数的文法中,第一项以符号值的形式在所在的环境中提供,指定求值结果指称为合并子的函数的名称;其后指定的文法中不同的元素对应合并子的操作数或其被作为调用时的形式参数树的子项。

  除非另行指定,在操作数可能是左值时,仅当对应以 ...? 形式中最后的一项(若存在)时,支持匹配作为被引用对象的有序对的非前缀元素不是空列表的引用值的情形。

  名义不同的约束可能蕴含相同的检查

  除非另行指定,应用子的操作数的约束也适用其底层合并子

  文法形式上,使用本节约定指定应用子的操作数时,指定表达式形式的求值结果。

注释 这和 [RnRK] 和 [Shu10] 中的斜体标识符的标记不同,但含义(表示语义变量(semantic variable) )和效果实质相同。

  操作数可能是左值或右值,按具体操作的需要,在必要时可被转换。

  除可能具有的子类型关系,本节约定的不同类型的操作数构成的集合之间不相交。一般规则参见类型分类

  根据是否可作为操作子中指定不被求值的函数参数,本节的操作数及其子项分为未求值的操作数求值得到的操作数

原理

  约束可用于区分特定的含义,但不直接指定和具体的检查对应,以便被实现优化,例如合并名义不同的检查。

  文法形势的匹配应避免歧义。

注释

  底层合并子适用应用子的约束,意味着按求值算法,被求值的函数合并对象不能是非真列表

  对非前缀元素的支持和绑定匹配规则对应。其中:

  • 结尾序列支持匹配以空列表以外的值的引用值作为非前缀元素的有序对操作数左值的被引用对象中的非前缀元素子对象
  • 非结尾序列的元素因计算前缀元素数而被要求在同一个对象的前缀元素中。
  • 作为操作数被绑定时,若元素是引用值:
  • 若有序对操作数的非前缀元素是空列表的引用值,则有序对操作数构成列表。
  • 关于操作数匹配的规则避免匹配操作数序列时对文法元素的对应关系可能具有歧义。

未求值的操作数

  未求值的操作数的文法约定如下:

  • <symbol>符号
    • 注释 内部使用和 <string>一一对应的表示,不提供符号和外部表示的其它映射关系。
  • <symbols> :元素为 <symbol> 的列表,形式为 (<symbol>...)
  • <eformal> :表示可选提供的环境名称的 <symbol>#ignore ,或这些值的引用值。
    • 注释 通常为动态环境。
  • <expression> :待求值的表达式。
    • 注释 这是 NPL 语法的直接实现。作为右值,它是词法元素的,或这些元素的真列表
  • <expressions> :形式为 <expression>... 的待求值形式。
    • 求值时,<expressions> 被作为单一表达式(即视为求值 (<expression>...) )。
  • <binding> :绑定列表的元素,形式为 <symbol> <body> ,用于指定被求值的表达式和绑定参数的符号值。
    • 和 Kernel 不同,<symbol> 后不要求是整个 <expression>
  • <binding> 绑定列表,形式为 <symbol> <expressions> ,用于指定被求值的表达式和绑定参数的符号值。
  • <bindings> :绑定列表,即元素为 <binding> 的列表,形式为 (<binding>...)
  • <body>: 出现在元素的末尾 <expressions> 形式,用于函数体等替换求值的目标。
  • <expression-sequence> :同 <expression>... 但蕴含顺序求值其中的子项。
    • 求值 <expression-sequence> 的结果是求值其最后一个子表达式(若存在)的结果,或当不存在子表达式时为未指定值
  • <consequent> :同 <expression> ,仅用于 <test> 求值结果经左值到右值转换不为 #f 时。
  • <alternative> :同 <expression> ,仅用于 <test> 求值结果经左值到右值转换为 #f 时。
  • <ptree> :形式参数树,是包含符号值或 #ignore 及其它形式参数树构成的 DAG 的表达式。
    • 语法要求由上下文无关文法描述:<ptree> ::= <symbol> | #ignore | () | (<ptree>...)
  • <definiend> :被绑定项的目标的 <ptree>
  • <formals> :作为形式参数的 <ptree> 。同 <definiend> 但允许派生实现定义更多检查。
  • <clauses> :元素为条件分支的列表,形式为 (<test> <body>)...
  • <variable>变量。用于表示被声明的名称。
    • <symbol> ,其中的处理与作为非列表的 <formals> 相同。

  关于 eval ,参见 YSLib 项目文档 doc/NPL.txt

原理

  以 <expressions> 代替 <expression> 可避免语法中要求过多的括号及 eval 等求值形式中显式构造列表的需要。

  因为 <body> 存在元素的末尾,明确元素中的其它词法元素后即可自然确定边界。

  <body> 可以是多个表达式的词法组合,允许具体使用时不需要附加括号即可实现整体求值。

  特别地,作为其它 <body> 嵌套的 <body> 实例在这种情况下,可以更有效地减少嵌套一层以上的括号。

  <body> 整体求值的一个必要条件:构成 <body> 的表达式不被以其它方式分别求值,如蕴含顺序求值

注释

  和 [RnRK] 不同,<body> 可以是多个表达式的词法组合。

  尽管 <body> 不保证可直接构成一个表达式(而是构成某个表达式的所在元素中的多个子表达式),一般仍被作为一个整体求值。

  被整体求值时,这些表达式被视为某个假想的表达式,这个表达式包含被整体求值的表达式作为子表达式。

  若 <body> 存在超过一个子表达式,按求值算法的 NPLA1 规范求值算法步骤,表达式分别作为合并子和之后的参数。

  若 <body> 不存在子表达式,则结果是 () 而不是 #inert 。这和 [RnRK] 的经重定义而隐含 $sequence$vau 以及 $let 等合并子不同,但和 eval 仍然相同。

求值得到的操作数

  求值得到的操作数的文法约定如下:

  • <object> :一般对象,包括引用对象的引用值
  • <reference> :对象引用值。
  • <pair>有序对
  • <list>列表:空列表或第二个元素为空列表的有序对。
  • <lists> :元素都是列表的列表。
  • <boolean>布尔值,值为 #t#f 的集合。
    • 类型映射指定的用于条件判断的单一值的类型。
    • 推论:<boolean> 对应的宿主类型是 bool
  • <test> :类似 <object> ,通常预期为 <boolean> ,作为条件。
    • 求值结果#f 时条件成立。
    • 原理 和 Scheme 类似但和 Kernel 不同,非 #t 的值在决定分支时视同 #f ,以允许在 <boolean> 外自然扩展的逻辑代数操作。
    • 原理 和 Common Lisp 不同,不使用空列表(或符号值 nil )代替 #f ,以避免需要特设的规则以特定的其它类型的值(如 Common Lisp 的符号值 t )表示逻辑真(这在逻辑非操作中不可避免)。
  • <combiner>合并子
  • <applicative>应用子
  • <operative>操作子
  • <predicate>谓词,是应用操作数的求值结果的值为 <test><applicative>
    • 注释 通常实现结果是 <boolean>纯求值
  • <environment>一等环境
  • <parent> :指定环境的父环境的值,包括:
    • 环境引用值:<environment> 或以 <environment> 值作为被引用对象的 <reference>
    • 元素为环境引用值的 <list>
    • 被引用对象是元素为环境引用值的 <list><reference>
  • <string> :字符串。
    • 字符串是包括数据字面量作为表示的值的类型。
    • 字符串的内部表示在具体实现中保持一致。除非另行指定,使用 ISO/IEC 10646 定义的 UCS 的 UTF-8 编码,其值不包含空字符(编码数值为 0 的 UCS 代码点)。
    • 关于当前实现,另见 YSLib 项目文档 doc/NPL.txt
    • 注释互操作的兼容性,一般建议实现使用兼容 [ISO C++] 中定义的 NTMBS(null-terminated multibyte string) 的方式表达。
  • 此外,支持的数值操作数参见 NPLA 数值类型

原理

  <object> 等求值得到的操作数不保证是语法意义上连续的词法组合,不能由多个表达式构成,因此即便出现在元素末尾,也不能如 <body> 一样减少括号。

  <object> 作为类型全集,其元素可被断言在论域内,即任何其它类型都是 <object>子类型类型检查可对此进行验证。

  和 [RnRK] 的理由不同,允许布尔代数以外扩展的比较判断在此不认为是易错的,而是有意的设计(by design) 。这避免预设地假定类型的名义语用作用(“角色(role) ” ),也避免限制语言和派生语言的类型全集的设计。

注释

  空列表构成的单元类型是真列表的子类型,而不是有序对的子类型。

  非空真列表是有序对的子类型。

文法元素补充约定

  • 除非另行指定,以 <symbols> 指定的值被作为 <definiend><formals> 使用时不引起错误。
    • 注释 <symbols> 在被其它上下文使用时仍可能引起错误。
  • <symbols> 形式的符号列表在绑定变量名时支持引用标记字符 &%。符号作为被绑定的初值符时,移除符号中发现的这些引用标记字符。
  • <definiend><formals> 不要求重复符号值检查。另见绑定操作
  • 使用 <formals> 的情形包括合并子基本操作和可通过这些操作派生的操作在对应位置的操作数。
    • 原理 [RnRK] 和 [Shu10] 都没有显式区分 <definiend><formals> ,两者在上下文中实质可互换,差别仅在 [RnRS] 中的 define 形式中定义的位置可具有和 <formals> 不兼容的扩展形式。
    • 注释 这实质等价使用 [Shu09] 中的记法,即 <formals> 用于除和 [Shu09] 的 $define! 类似外的所有操作(包括 $set!$let 等,而不论是否对应 <body> )。这些上下文中总是隐含了上述的可派生实现的要求。
  • <body> 不蕴含顺序求值子项。
    • 原理 这也允许 <body> 中的表达式被整体求值

  关于合并子基本操作,参见 YSLib 项目文档 doc/NPL.txt

原理

  和传统 Lisp 方言(包括 [RnRS] 和 [RnRK] )的函数体不同,<body> 的各个表达式之间不蕴含顺序求值。

  因此,和 [RnRK] 不同,$vau 不需要在基本操作之后再次派生。这使操作的功能更加正交。

注释

  和 [RnRK] 不同,<symbols><definiend><formals> 具有一些附加的约定支持;<body> 不蕴含顺序求值子项;NPLA1 的符号可通过代码字面量求值得到。

NPLA1 对象语言语法

  基于 NPLA 基本语法约定参见 NPLA 整体约定

  NPLA1 表达式符合 NPL 表达式语法

注释

  NPL-GA包含的转义规则中包含 <char-escape-seq><char-seq> 要求类似 [R6RS] 在字符串中的元素;其中仅有 \ 和 " 被 [R5RS] 直接支持,而 [R7RS] 不支持 \v 。

  后者支持的其它转义字符序列词法可被派生实现以 <$literal-char> 的形式另行指定(其中 [R7RS] 可涵盖对应 <char-escape-seq> 的功能)。

NPLA1 字面量

  基于 NPLA 词法规则,本节指定字面量的词素集合。

  派生实现可指定不同的字面量,但不应和已指定词法构造的记号冲突,包括本节指定的字面量。

  NPLA1 字面量都是纯右值,但总是允许实质化转换为消亡值并引入允许互操作临时对象

注释 这和宿主语言的字符串字面量是左值不同。当前 NPLA1 对象语言不提供能引起互操作差异的接口(字符串字面量不被修改),以后可能改变。

字符串字面量

  字符串字面量的类型为 <string>

NPLA1 数值字面量

  基于 NPLA 数值类型数值字面量,NPLA1 数值字面量的类型为 <number>

  除非另行指定,数值的具体宿主类型未指定。

注释 部分数值可指定具体的子类型

  NPLA1 支持 <integer> 类型的精确数数值字面量和 flonum 不精确数数值字面量。

  支持的字面量包括词素符合 NPLAMath 数值表示的字面量。

  派生实现可定义其它数值字面量。

注释

  以上字面量包含十进制数值的字面量。其它字面量是 NPLA 扩展字面量

  无限大值和 NaN 值同 [Racket] 的字面量词法,除这些类型总是启用,且使用明确属于 flonum 且对应明确宿主类型的 long double 代替不被作为一般 flonum 的 extflonum 。

  [SRFI-73](已撤消)提出扩展 [R5RS] 的带有 #e#i 前缀的精确数和不精确数无限大值字面量,其中前缀及精确数的支持和 [RnRK] 类似。NPLA1 不支持无限大值精确数。

  关于无限大值的在 [RnRS] 的一些实现情形,另见这里

NPLA1 扩展字面量

  NPLA1 支持 NPLA 扩展字面量作为部分数值字面量。

  NPLA1 还支持以下以 # 起始的扩展字面量:

  • #t布尔值逻辑真,类型为 <boolean>
  • #f :布尔值逻辑假,<boolean>
  • #true :同 #t
  • #false :同 #f
  • #inert :类似 Kernel 的 #inert 字面量,
  • #ignore :类似 Kernel 的 #ignore 字面量。

原理

  #inert#ignore 类似 [RnRK] 。

  从表达上,#inert#ignore 仍都可以被视为特定单元类型的值:等价的类型判断谓词可以直接使用值的相等关系确定。

  和 [RnRS] 及 klisp 不同,不需要因兼容性支持扩展字面量中不同的大小写变体,特别是 [R6RS] 的 #T#F

  和 [RnRS] 类似而和 [RnRK] 不同,NPLA1 表达结果结果通常不依赖 #inert ,而直接使用未指定值。这避免用户必须引入 #inert 等具体的值实现相同隐式效果而违反关注点分离原则

  尽管在接口意义上通常是不必要的,若有需要(如派生结果等效 #inert 的操作),#inert 的值仍可被显式使用。

注释

  [R5RS] 和 [RnRK] 指定 #t#f 。[R7RS] 指定同义的 #true#false(参见 R7RS ticket 219 )。

  后者被认为可提供若干可读性,但具有冗余。本文档中,以下不使用 #true#false 替代 #t#f

  派生实现可扩展支持,提供非 <boolean> 类型的布尔值,使用与这些字面量不同的对应表示。

NPLA1 函数合并

  以下使用 ...作为函数的操作数时,可支持没有操作数的函数合并。此情形下应用表达式仍需要前缀 () ,但不在以下规约文法中显式地表示。

注释

  和 Scheme 及 Kernel 不同,求值算法决定求值为函数合并表达式的语法表达不需要括号,且具有不同的函数合并形式

对象语言内存安全保证

  对象语言可能提供关于内存安全的检查。

  除非另行指定,假定实现进行互操作无法保证内存安全。

对象语言基本内存安全保证

  对象语言提供关于内存安全的基本保证:不存在违反内存安全相关的要求以外的未定义行为(包括循环引用等)、不存在不保证内存安全的互操作且不存在不安全间接值访问时,对象语言的程序执行保证内存安全。

  非内存安全操作在对象语言中以不安全间接值访问的一部分情形体现。

不安全操作

  不安全(unsafe) 操作是可能在程序的执行中引入未定义行为操作

  这里的未定义行为包含在操作中直接引入的未定义行为,以及因为操作被执行而使程序在之后无法确保排除的未定义行为。

  不安全操作是实现可选提供的。

  当前对象语言不支持并发访问对象。数据竞争仅可由和宿主语言的互操作引入。

不安全间接值访问

  对象语言的不安全间接值访问包括:

无效的环境引用

  环境对象被销毁导致作为间接值的环境引用无效化

注释

  另见环境生存期

无效的引用值

  作为间接值的引用值是间接值的实例,因此无效的间接值包含无效的引用值,通过无效间接值访问包括无效的引用值的访问。

  对象语言不提供悬空引用以外构造无效引用值的操作。

  对象语言中可引入悬空引用的情形包括:

  • 调用可能返回引用值的合并子,且没有另行保存函数体求值所在的当前环境返回值对应的对象不在生存期内:
    • 绑定到形式参数的右值保存在过程调用的局部环境中,退出函数体的求值,局部环境被释放后,返回的其引用是悬空引用。
    • 实现等效上述情形的派生操作的使用,如:

  关于间接保留引用值和互操作可能引入悬空引用的情形,参见 YSLib 项目文档 doc/NPL.txt

注释

  另见对象语言的引用值

其它无效的间接值

  使用其它不保证内存安全的操作可引入不具有内存安全保证的间接值访问实体。

  这些间接值可能因为和悬空引用相同的情形无效化

保留间接值

  对象的(直接或间接)子对象是间接值时,对象包含间接值。

  修改对象为间接值或使之包含间接值时,对象保留间接值。

  被保留的间接值是对应的通过修改得到或包含的间接值。

  本节中的概念对应适用于具体的间接值,如被保留的引用值和函数在结果中保留引用值

被保留的引用值的目标

  函数调用返回(在对象语言中允许出现的,下同)间接值或包含间接值的对象时,在函数值中保留间接值。

  函数调用修改环境使环境对象保留间接值(绑定间接值或包含间接值作为子对象的对象作为被绑定对象)时,在环境中保留间接值。

  函数调用修改一等对象或其子对象,使之保留间接值时,在对象中保留间接值。

  在函数值中保留间接值、在环境中保留间接值、在对象中保留间接值的函数保留间接值。

  被保留的间接值被函数调用的求值结果蕴含时,函数在结果中保留间接值。

注释

  函数调用的求值结果排除副作用即函数值。

  在结果中保留间接值包含以下情形:

  • 在函数值中保留间接值。
  • 在环境中保留间接值,环境是函数值或其子对象。
  • 在对象中保留间接值,对象是函数值的子对象。

被保留的引用值的来源

  函数返回包含间接值的对象由参数的值决定时,保留参数中的间接值。

  按被保留的间接值的来源,这分为以下两个子类:

  • 直接保留间接值:接受间接值参数。
  • 间接保留间接值:接受的参数或参数在特定环境中被求值得到的结果决定是否直接保留间接值。

保留间接值的操作

  操作可保留间接值:

  • 使用函数调用实现的操作可通过函数调用保留间接值。
  • 其它实现方式可等效地保留间接值。

注释

  在结果中保留间接值的操作不区分被保留的间接值的来源和目标。但多数情形下,这通过函数值保留参数中的间接值蕴含。

保留间接值和内存安全

  保留间接值操作的内存安全的一个必要条件是所有被保留的间接值在之后的使用中都满足内存安全。

  保留间接值在操作后可能因间接值无效(如悬空引用),无法继续保证内存安全。

  在环境中保留间接值时,应保证环境具有足够的生存期,以避免间接值依赖无效的环境引用导致访问环境中对象的未定义行为。

对象语言接口的安全保证机制

  对象语言接口的安全保证机制提供不同接口的分类,通过允许区分是否具有内存安全保证的接口帮助程序利用对象语言基本内存安全保证

  通过避免或限制使用不安全操作,实现上述安全保证。

  因为允许引入 NPLA 未定义行为,无法提供安全证明的互操作应视为不安全操作。

  基于求值算法的安全保证的非形式的证明框架概述如下:

  • 任意步骤中,访问间接值指定的目标对象是安全的,仅当间接值是安全的。
  • 符号值的求值是安全的,仅当引用的环境是安全的。
  • 合并子调用的求值是安全的,仅当合并子、操作数及调用的操作是安全的。

原理

  满足安全保证的推理如下:

  • 因为 NPLA 实现的非互操作引入的、非求值规约的管理规约不存在未定义行为,以上求值算法中的步骤中通过排除不安全的实体能保证规约中不存在未定义行为。
  • 因为规约决定程序执行的语义,在求值中排除不安全的实体可以保证不存在未定义行为,而满足安全保证。

安全性附加证明

  一些不安全操作是否蕴含未定义行为可能依赖具体调用使用的操作数。

  若能证明特定的前提保证任意的调用实例中的操作数满足附加的安全假设,则这些不安全操作的调用仍可保证安全。

  排除不确保安全性假设的互操作时,NPLA1 提供附加调用安全:若不存在隐藏环境中绑定的可修改对象的引用,则仅因可能违反值稳定性的不安全操作的调用是安全的。

  派生实现可对特定调用附加使用限制以便提供证明,或定义其它的调用并提供更强的保证。

诊断

  NPLA1 的特定求值步骤可引起诊断

  引起诊断时求值被终止,或在失败可被恢复时以其它派生实现定义的方式继续求值。

  其它引起诊断的条件可被派生实现补充指定。

注释 注意未定义行为取消对诊断的要求。

  本节以外的诊断消息的其它形式未指定。

注释

  引起诊断的求值包括:

  • 抽象求值的失败。
    • 例如,REPL(read-eval-print loop) 中进行的翻译。
  • 环境中访问指定名称的对象失败时。
  • 特定的函数应用。

  其它求值条件详见具体操作的规定。

NPLA1 错误

  NPLA1 中的错误是按接口的约定不符合预期的正常条件(如不被正常处理的操作数类型)引起的诊断。

  求值特定的表达式可引起错误,包括:

  • 违反求值算法步骤中的要求而直接引起的语法错误(syntax error)
  • 其它情形引起的语义错误(semantic error)

  以接口文法约定的形式约定的操作中,除类型检查外,绑定初始化之前的参数绑定失败是语法错误。

  语法错误包含两类:

  • 总是依赖程序运行时确定的值不满足特定操作的要求引起动态语法错误。
  • 其它语法错误违反语法正确性要求,是静态语法错误。

  类似地,语义错误包含两类:

  • 总是依赖程序运行时确定的值不满足特定操作的要求引起动态语义错误。
  • 其它语义错误违反语义正确性要求,是静态语义错误。

  静态语法错误可能通过语法分析从源代码决定。

  引起动态语法错误或动态语义错误依赖的值是合并子的具体实际参数的值,以及派生实现可选指定的其它的值。

  引起动态语法错误或动态语义错误的情形包括求值特定的函数应用,由具体操作指定。

  程序可通过引发(raise) 一个错误对象(error object) 指定引起诊断。

  除非另行指定,NPLA1 的错误对象不需要是 NPLA1 支持的对象,而可以仅在宿主环境中可见。

  因果性引起的错误可构成错误之间具有依赖关系。

  错误对象的其它具体形式由派生实现指定。

NPLA1 异常

  NPLA1 的当前诊断使用的异常执行机制由宿主语言支持,通过宿主语言中的异常类型区分不同的异常条件。

  NPLA1 约定的所有要求引起异常的诊断情形都是错误。

注释 用户操作引起异常不一定是错误。

  不引起未定义行为的翻译失败应抛出异常

  引发错误对象可能通过抛出异常实现。此时,被抛出的宿主语言异常对象是错误对象。被抛出的异常类型可具有被显式指定的 public 基类,这些基类应无歧义以允许宿主语言捕获。

  若存在依赖错误且引发被依赖的错误对象使用抛出异常实现,使用宿主语言标准库的嵌套异常(nested error) 机制实现依赖错误。

  当前没有提供相关操作,但抛出的宿主异常在具有表示的意义上是 NPLA1 的一等对象

  关于抛出异常的宿主类型,参见项目文档 doc/NPL.txt

运行时错误条件

  除非另行指定,实现应对以下全局的运行时错误条件按要求引起诊断。

  当实现无法提供需要的资源,资源耗尽(resource exhaustion) 。此时,引发特定的关于资源耗尽的错误对象。

  除非另行指定,上述表示资源耗尽的错误对象满足宿主语言的以下类型的异常对象:

注释 [ISO C++] 的本机实现宿主资源耗尽时,一般抛出派生 std::bad_alloc 的异常对象。这不包括本机实现无法提供资源的未定义行为

错误检查

  检查(check) 是限定成功的操作应满足的(必要非充分)条件引起诊断的操作。检查失败时要求引起诊断。

  良定义的检查应具有强规范化性质,以保证有限数量的检查总在有限的计算步骤内终止。在进行检查的上下文,实现假定检查良定义。

注释 实现不需在此之前对检查的这个性质附加检查。

  检查条件限定检查的通过或失败。除非另行指定,通过的检查没有作用,失败时总是具有作用。

注释 检查失败通常可引起副作用

  NPLA1 要求在特定上下文进行类型检查。派生实现可定义其它检查。

  函数操作的语义可单独指定检查,具体形式由具体操作指定。

NPLA1 类型检查

  基于名义类型,对象语言实现应具有语义规则指定的类型检查,以确保程序的执行符合操作的必要前置条件。

  操作的语义可要求以下的类型检查:

  实现可能添加其它不违反语义要求的类型检查。

  基于表达式的类型,对应对象语言表达式的表示实体的元素可指定操作数上明确的类型要求。

  部分实体从属于其它实体类型而构成子类型关系;部分的规约操作取得求值结果保证结果中的值可能具有的特定类型集合,这些类型也一并在以下描述中给出;其它情形不指定类型。

  规约预期符合约束。若违反由项的表示的对象的动态类型不匹配导致,则求值失败;否则,行为未指定。

  类型检查的完成应先序依赖被检查特定类型的值的访问。

  除非另行指定,类型检查和程序中的其它作用(包括不同的其它类型检查)的顺序未指定。

  类型错误引发错误对象。

  若合并子调用不接受非真列表参数构成函数合并,检查参数是真列表,即参数列表(parameter list) 。对参数列表的类型检查的完成应先序于其中任意子表达式的求值。

原理

  类型检查有助于维护程序的正确性,并及早发现编程错误。

  但是,类型检查自身存在开销;在一个阶段中集中检查类型的限制不是必要的。特别地,静态类型检查不被要求。

  这些设计同时确保程序容易在程序在实现的不同执行阶段重现相同的检查逻辑乃至直接复用其实现。

  为减小开销等目的,实现可能合并不同类型检查,而不改变程序的可观察行为

  对子表达式的求值需访问子表达式。因此,对参数列表的检查蕴含顺序要求。

注释

  一个值可被多次针对不同的对象进行类型检查。

  不同的类型检查中,对特定类型的值的访问之间没有必然的隐含联系。

NPLA1 外部表示

  外部表示若被确定,由实现和派生实现定义。

  NPLA1 不要求对象和其它实体存在外部表示,也不要求外部表示唯一。

注释

  对外部表示的存在性要求和 [RnRK] 不同。

  NPLA1 当前直接使用其它已被指定的表示规则,如互操作隐含的宿主语言对象表示。

  NPLA1 当前不提供可移植的互操作接口(包括一些基本 I/O 操作),也不约定其涉及的外部表示形式。

表达式语义

  表达式具有和语法构造不直接相关的且可能上下文相关的语义。

  部分语义不需要通过求值体现。

NPLA1 规范求值算法

  以被求值的表达式和所在的环境作为参数,NPLA1 使用以下规范(canonical) 求值算法取得表达式的求值结果

  1. 自求值:若被求值的表达式不是符号值且不是有序对,则求值结果是自身。
  2. 名称解析:若被求值的表达式是一个符号值,则被视为变量名,求值结果是它在上下文(当前环境确定的词法作用域)中变量绑定确定的对象的经引用折叠左值引用
  3. 否则: 注释 被求值的表达式是有序对。
    1. 若被求值的表达式是具有一个元素子表达式)的列表,则求值结果是这个子表达式的求值结果。否则,继续以下求值步骤。 注释 被求值的表达式是具有不少于一个元素的列表或非真列表。
    2. 若被求值的表达式第一个子表达式是空列表,则移除,并继续以下求值。 注释 起始空列表的语法用于继续求值可能不提供实际参数函数合并。具有实际参数的函数合并不一定需要起始空列表。
    3. 对第一个子表达式求值。
    4. 以第一个子表达式的值计算的求值结果作为操作符,以其余子表达式作为操作数,求值合并

  有序对以外的表达式被求值时:

  • 标识符的值是构成标识符的符号值。
  • 代码字面量的值是去除其边界的 ' 的标识符构成的符号值。 注释 代码字面量可表达直接作为标识符时不能作为符号值的词素的转义,例如 '' 是一个空的符号值;而 '#ignore'42 这样的形式允许其中的表达作为变量名,而不是字面量。
  • #t#f 求值为自身,是布尔值
  • #ignore#inert 求值为自身,具有和其它值不同的单元类型
  • 数值字面量求值为数值(numerical value)

  非空列表和代码字面量以外的对象作为表达式,都是自求值表达式

原理

  NPLA1 规范求值算法和 [RnRK] 中定义的 Kernel 求值算法(以及 [Shu10] 中定义的 vau 演算)类似,差异为:

  • 求值算法不直接约定取得 WHNF 以外的子项是否被求值,而由被调用的第一个子项决定。
  • 对符号值的求值包含对引用值的区分。
  • 要求一个子项的列表总是使用其列表元素求值。

  最后一个差异在对象语言中是实质性的,它决定列表表达式和其中的子表达式的求值总是等价。

  求值算法保持当前环境

  NPLA1 翻译单元中,未求值的表达式满足以下性质:

  NPLA1 规范求值算法和 [RnRK] 的求值算法具有近似的简单性。

  因为 NPLA1 不支持存在环的非真列表cons 对的描述被对应替换。

  求值算法使用的环境同 [RnRK] 。

  同 [RnRK] ,而非 [RnRS] ,NPLA1 规范求值算法避免对顶层(top-level) 的特殊引用,以避免上下文相关性的不同规则带来的复杂性和限制。

  使用顶层的不同求值规则的限制可能简化一些编译实现需要的假设。但这泄漏了抽象,且在实际使用中引起大量问题

  特别地,不同的顶层的特设规则相对更动态,反映一些用户对 fexpr 的期望,但在此这已被 vau 抽象替代。因此,使用不同的顶层求值规则以提供更强的动态性是多余的。

  另一方面,当前环境一般允许被具现一等环境在程序中可编程地访问而代表求值算法使用的上下文。为不同的上下文特设不同的顶层求值规则也是多余的。

  关于实现,参见 YSLib 项目文档 doc/NPL.txt

  以下各节补充描述 NPLA1 规范求值算法的局部性质。

注释

  关于 WHNF 求值在 Kernel 中的描述,参见 [RnRK] 关于 unwrap 的 Rationale 的描述。

  语法分析器的实现应使结果取得和这些性质兼容的中间表示。

函数合并求值

  求值算法向函数合并传递当前环境作为函数合并的动态环境

  为支持没有操作数的函数应用,需约定其它表达式表达求值为函数合并的应用表达式

  • 复合表达式的第一个子表达式是空列表(())时,求值为一个函数合并。
    • 注释 对没有操作数的情形,这是唯一被直接支持函数应用的语法
  • 否则,求值的作用同移除第一个子项 () 后的剩余形式。

注释

  关于区分函数类型的替代设计(使用 $ 作为第一个子项)的一个例子,参见这里

  基于其中类似的对语义的影响(区分函数合并是否针对一个操作子)上的理由,这不被使用。

  与此不同,尽管在对象语言中接受 () 的使用也需要求值算法的显式支持,这在目的上是纯语法意义上的——仅在无法避免语法歧义时,才必须使用。

  只要能确定求值算法使用的环境,就能静态地区分复合表达式是否是函数合并。此时,其中的第一个子表达式是否显式为 () 不影响关于语义的推理。使用不同的内部中间表示可完全消除是否使用 () 的函数合并的差异;或者,也可以约定在代码中默认使用第一个子项是 () 的表达式作为函数合并的规范形式,而把第一个子项不是 () 的形式视为隐含 () 的语法糖。

  因此,相对使用 $ 而言,使用 () 的设计具有更少的缺陷(尽管需要更多的字符)。

空列表求值

  空列表 () 作为表达式是自求值表达式,而不是没有函数的空过程调用。

原理

  关于 () 的求值规则避免这种简洁有用的语法导致语法错误

注释

  这和 [RnRS] 不同而同 [RnRK] 。在前者构造空列表需要 '()

  和 Kernel 不同的函数合并求值规则使这个设计和函数求值字面上没有直接的关联,避免了 Kernel 中为什么 () 不是词法上类似的如 (f x) 这样的表达式的特例的问题。

  注意以 () 作为前缀并不要求要求特定函数的子类型而可能破坏子类型封装性的假设。

记号求值

  具有不同大小写字符的标识符不同。

  可使用(能在求值时作为名称的)代码字面量即 '' 分隔)表达没有分隔符时被解释为字面量或其它值的符号值。

  符号值作为名称表达式,经名称解析求值,访问当前环境中的被绑定对象

  其中,若被绑定对象是引用值,结果是被折叠一次的引用值;否则,结果是被绑定对象作为被引用对象的引用值。

  求值的结果是确保为左值引用值。

  结果不继续特别处理。引用值在此作为一等对象,作为表达式时不发生左值到右值转换

注释

  标识符大小写敏感的设计和 [R5RS] 及 klisp 不同,而和 [R6RS] 相同。和 [R7RS] 的默认行为相同,但不提供切换大小写不敏感的方法。

  代码字面量和 klisp 使用 || 作为分隔符的语法不同,但作用类似。

  和 klisp 不同,NPLA1 允许使用 . 作为变量名,但在特定的上下文不被求值时符号值 . 可被特别处理,如绑定匹配时忽略以 . 为符号值的绑定。

  和 klisp 不同,NPLA1 允许使用 ++ 等全以 +- 组成的字符序列构成标识符。

  以 #+- 起始的不能构成标识符的词素是 NPLA 扩展字面量

对象语言求值算法

  除非另行指定,NPLA1 对象语言的求值总是使用 NPLA1 规范求值算法

  在输入求值算法接受的语法形式之前,求值使用基于中缀语法识别的分隔符进行处理。

  由此引起的其它语法差异参见绑定构造

中缀语法

  NPLA1 提供符合特定谓词指定的过滤条件的中缀分隔项替换为特定名称表达式指定的前缀操作形式的列表。

  这些中缀变换作为预处理操作,可识别和接受 NPL-GA 语法外的记号,即转换扩展的 NPL-GA 文法输入为严格的 NPL-GA 语法要求的源语言。

  中缀变换递归替换构成表达式的形如 <expression> (<infix> <expression>)* 的记号序列为 <transformed-infix> <expression>+ 形式的记号序列。

  其中,被支持的中缀记号 <infix>;, ,而 <transformed-infix> 是语法不可见的中缀变换函数。

  其中,分隔符 , 优先组合。

  分隔符对应的 <transformed-infix> 分别表示对被分隔的序列参数进行有序和无序列表求值(替换后合并子功能对应参照实现环境中函数 $sequencelist% 求值后的合并子)。

  对分隔符的处理使用和组合顺序相反的两遍分别对 ;, 遍历替换。

  变换的不同 <expression> 的实例以相同的词法顺序在变换后的结果中被保存。

求值算法实现风格

原理

  和 Scheme 不同而和 Kernel 类似,求值通常使用显式的风格(详见 [Shu10] )而不是依赖 quote 的隐式风格;这和不需要括号的语法特性无关。

值类别和类型

  基本内容参见 NPLA 值类别表达式的类型

  特定的表达式维护可修改性

注释 这类似宿主语言的 const 类型限定,但只适合左值且仅使用隐式类型

  特定的操作集合可约定关于确定结果值类别和类型的具体规则,如子对象访问约定

绑定操作

  绑定操作决定符号值或具有符号值的数据结构与项的对应关系,并初始化被绑定对象而引入变量

  作为函数语法的推广,两者分别由绑定操作使用形式参数操作数指定。

  操作数的表示具有树的构造,即操作数树

  为决定形式参数对应的操作数,形式参数和操作数树或它们的子对象的结构被比较,即绑定匹配(match) 。匹配操作数树的形式参数对应也可具有树的构造,即形式参数树(formal parameter tree)

  被匹配的操作数是操作数树作为有序对元素。类似地,形式参数是形式参数树作为有序对的元素。

  绑定操作初始化对应的变量的名称和值分别由形式参数树和操作数树决定。

  NPLA1 形式参数树具有特定的语法规则:树的叶节点为符号值、符号的引用值或其它形式参数树构成的 DAG 。若构造的形式参数树不符合语法规则,引起错误,不进行绑定。

  成功的匹配决定形式参数对应的操作数或其子项,作为其实际参数。这种对应关系是单射但不一定是满射,即匹配成功后,每个参数总存在对应的操作数或其子项,而操作数和子项允许不对应形式参数而被忽略。

  被绑定的项的操作数中的元素对应是中的元素。

  形式参数树中的引用值可能被间接访问其被引用对象一次,其余元素在匹配时被视为右值

  绑定操作符合以下节的绑定规则。

原理

  被绑定的参数可作为函数的形式参数。绑定操作对形式参数的处理也可以作为其它初始化变量的语法构造的基础。

  作为推广,绑定操作也可以引入函数的形式参数以外的变量。

注释

  形式参数树的节点可以是符号的引用值,但不支持多重引用

  关于对形式参数树的具体的语法要求,另见 <ptree> 的定义

  因为 NPLA1 支持的绑定构造都具有函数合并的形式,操作数或其子项总能直接被作为函数的实际参数。

  DAG 要求和 Kernel 类似。

  和 Kernel 不同,操作数树同时支持作为引用左值和非引用的右值,在实现上需要解析引用。

绑定初始化

  绑定的对象节点的值和子节点元素被复制初始化

  绑定前不对形式参数或实际参数中的元素求值。

  除非另行指定,不同变量的绑定初始化之间非决定性有序

  绑定初始化不修改形式参数,但可能因初始化转移初值符而修改操作数

注释

  初始化元素类似宿主语言的参数传递中可发生初始化。

  若形式参数或实际参数可能由求值得到,需在匹配前另行处理。

  由非决定性规约规则,一般地,变量仅通过初值的求值决定的依赖关系子对象决定之间初始化的相对顺序。

  因为绑定的初始化不负责实际参数的求值,一般地,即使初值符位于相邻的语法构造,也不保证隐含顺序;这和宿主语言不同。

  初始化的顺序规则和宿主语言初始化不同的函数参数类似。

绑定临时对象

  被绑定的临时对象子对象不具有临时对象属性

原理

  因为记号求值保证求值符号值是左值,被绑定的对象名称解析最终得到的引用值不包含唯一引用属性

  这不清除绑定临时对象引入到表示被绑定对象的项或引用值中的其它属性,因此其它属性可跟随一等对象被跨过程传递(若不经过返回值转换或其它操作)。

  同绑定临时对象属性的讨论,被传递的属性类似宿主语言的指定转发引用参数类型,以下记作 P

  特别地,被传递的属性包含临时对象属性。这对应宿主语言中 P 是左值引用。

  跨过程传递并不被宿主语言支持。因此,一般仅限为了实现类似宿主语言的根据值类别和类型转发参数的转发上下文(forwarding context) 中使用。

  通过从传递的属性中提取的标签访问引用引用值的属性代替保存环境引用并以其它底层的方式查询作为被引用对象的被绑定对象的元数据能以更低的开销实现一些常见的相同的目的,如判断被引用对象是否表示可被转移的资源。

  另见临时对象的表示非递归绑定递归绑定

注释

  使用引用标记字符可保留来自引用值实际参数的作为引用值属性的临时对象属性。

  使用引用标记字符 & 可启用转发推断值类别。

绑定匹配

  绑定匹配以一个形式参数树和操作数树作为输入,比较两者的结构并尝试关联形式参数树中的子项到操作数蕴含的对象,以创建变量绑定

  若绑定匹配成功,则可能进行以符号值为名称的对应变量的绑定初始化;否则,绑定匹配失败,引起错误。

  绑定匹配确定每一个符号值的过程先序这个符号值确定的变量的绑定初始化。

  绑定匹配不修改形式参数,在匹配成功进行绑定初始化前不修改操作数。

  匹配使用如下算法搜索形式参数树和操作数的对应位置:

  • 初始化输入的形式参数树为当前形式参数,函数合并构成的操作数树作为当前操作数。
  • 对每一对当前形式参数和当前操作数,比较两者(除非另行指定,操作数的值是引用值的,视为匹配被引用对象,下同):
    • 若两者都是有序对,则:
      • 若形式参数有序对元素的结尾元素不是符号也不是有序对,则参数匹配失败。
      • 若形式参数是列表,且元素的结尾元素是以 . 起始的符号值,则存在省略(ellipsis) ;保存移除 . 的符号值,并从子项中移除结尾元素,继续进行比较。
      • 若形式参数和操作数的(直接)前缀元素数相等,或存在省略时移除结尾元素后的形式参数前缀元素数不大于操作数子节点的元素数,则:
        • 注释 直接比较前缀元素数,不计算有序对的非前缀元素是引用值且其被引用对象是非空列表时具有的元素数。
        • 忽略形式参数中的省略的元素,以深度优先搜索从左到右逐一递归匹配两者的元素。
        • 若存在省略的元素,若保存移除 . 的符号值非空,以移除 . 的符号值作为形式参数,匹配操作数构成的结尾序列(trailing sequence)
        • 否则,若形式参数是非列表的有序对(最后的元素非空),匹配结尾序列。
          • 注释 结尾序列支持匹配有序对操作数的非前缀元素。这个元素可能是引用值,它的被引用对象被作为操作数继续匹配并进行非递归绑定
        • 否则,若所在的形式参数列表的结尾元素是 . ,参数匹配成功,忽略结尾序列,不绑定对象。
        • 否则,没有其余元素需要匹配,参数匹配成功。
          • 注释 先前对形式参数和操作数的节点数判断同时确保结尾序列为空。
        • 匹配结尾序列的规则参见非递归绑定。
          • 注释 结尾序列预期匹配的操作数是空列表或有序对。对操作数是列表的情形,结尾序列是结尾列表(trailing list)
      • 否则,若不存在省略,列表的元素数不相等,参数匹配失败。
      • 否则,操作数的子节点不足,参数匹配失败。
    • 若形式参数是空列表,则:
      • 若实际参数不是空列表,则参数匹配失败。
      • 否则,参数匹配成功。
    • 若形式参数是引用值且没有因为本条匹配规则递归进入匹配,则以其被绑定对象代替当前形式参数递归匹配。
    • 若形式参数不是符号,则参数匹配失败。
    • 若形式参数不是 #ignore ,则尝试绑定操作数到以符号值确定的名称的形式参数。
      • 若符号值以一个引用标记字符起始,则被绑定的变量名中去除此前缀。
      • 若去除前缀得到的符号为空,则忽略操作数,不绑定对象。

  绑定匹配时不检查重复的符号值。若形式参数树中出现重复的符号值,可被多次匹配成功。这可导致之后的绑定初始化中,只有其中某个未指定的绑定生效,其它绑定被覆盖。

原理

  虽然可能匹配被引用对象,操作数匹配不蕴含时引用值不被消除

  和 [RnRK] 不同,明确直接比较前缀元素数,因为:

  • 这允许在元素数不同时给出更具有针对性的诊断,避免误用。
  • 这能避免匹配在任何情形都总是顺序地依赖每一个操作数的值,允许并发实现

  实现使用的表示允许访问元素数具有 O(1) 的时间复杂度,而访问前缀元素数具有 O(n) 时间复杂度。但限制不访引用值时,不会有较大的附加开销。

注释

  函数合并构成的操作数树包括作为合并子的第一个子项和作为操作数的之后余下的子项。

  数据结构和匹配算法类似 Kernel 中用于 $define!$vau操作子的递归的匹配机制,但有以下不同(另见 NPLA1 合并子):

  • 不支持 cons 对的中缀 . ,但支持形式参数树中的列表最后以带省略的符号值匹配多个列表项的参数,绑定结尾序列。
  • 对参数子项的符号值中可选的 . 起始以及之后可选的前缀作为标记字符作为引用标记进行处理。
  • 不提供转义,若符号值去除可选的前缀及标记字符 . 后为空则忽略绑定。
  • 若参数子项按引用传递则间接访问并绑定被引用对象
  • 只支持无环列表,且不检查(因为 API 已经保证只支持真列表)。
  • 列表外的 . 起始的词素当前视为普通的符号,但此行为可能会在未来改变)。

  被忽略的绑定不保存绑定的对象。

  不在列表内最后位置的带有前缀 . 的形式参数绑定的是普通的变量,不忽略绑定。

  和 Kernel 不同,不检查重复符号值,且绑定匹配对特定模式的形式参数进行不同的处理

  其它一些不支持 cons 对的语言,如 [ECMAScript 2019] 的 rest 参数支持类似结尾列表的效果。

  绑定匹配和创建绑定的初始化之间的顺序约定是必要的,因为这里约定的是一般的规约规则而非求值规则,递归蕴含规则等求值的默认规则不适用。

  绑定匹配允许并行化

引用标记字符

  应用在形式参数树叶节点符号值的前缀 %&@ 为标记字符表示名称绑定的可按需引入引用,称为引用标记字符(sigil)

  绑定引用时,可使用引用推断规则:

  标记字符引起的绑定的差异为:

注释

  除复制消除转移有序对操作数的子对象外,绑定时不修改被绑定操作数。

  支持修改操作数的绑定的其它标记字符可能在未来支持。

非递归绑定

  非递归绑定在一次匹配之后创建对应的变量绑定。

  合并使用或不使用引用标记字符的情形,非结尾序列的单一参数对象的绑定初始化包含以下过程:

  • 若不存在标记字符 @ ,则:
    • 若操作数为可转移的对象的引用值,则被绑定对象是按以下规则初始化的蕴含隐含的引用折叠的引用值:
      • 存在标记字符时,使用引用推断规则,被绑定对象是操作数直接初始化的引用值,其属性由操作数的(引用值)的属性决定:
        • 当存在标记字符 & 、绑定非结尾序列且作为操作数的引用值的属性包含唯一引用属性时,其中包含绑定临时对象属性
          • 注释 使用 % 可避免操作数中的唯一引用属性在被绑定对象中蕴含临时对象属性
        • 否则,被绑定对象的属性和作为操作数的引用值的属性相同。
      • 否则,被绑定对象是操作数复制初始化(复制或转移)的值。
    • 否则,若操作数属性指定可修改的临时值或有标记字符 % 时的临时值,操作数是可转移的非引用值,被绑定的对象是临时对象。
    • 否则,当存在标记字符 & 时,被绑定对象是操作数的引用值,其属性是操作数属性和操作数项的属性的并,但总是排除绑定临时对象属性。
    • 否则,被绑定对象是复制自操作数的值。
  • 否则,被绑定对象的是操作数的引用值:
    • 绑定操作数的引用时,要求引用的是列表中的项,否则引起错误。
    • 被绑定的对象应是不唯一的值(直接绑定操作数右值以外的值),被绑定对象是操作数的引用值。

  绑定结尾序列包含以下情形:

  • 若不存在标记字符 @ ,则:
    • 若操作数为可转移的对象的引用值,按非结尾序列的规则绑定操作数。
    • 否则,若操作数属性指定可修改的临时值或有标记字符 % 时的临时值,按非结尾序列的规则绑定操作数。
    • 否则,创建新的有序对,在其中以相应的标记字符(若存在)绑定各个元素子对象。
  • 否则,创建新的有序对,在其中以标记字符 @ 绑定各个元素子对象。

  绑定结尾序列创建新的有序对并绑定元素子对象时,作为列表完全分解得到的每个元素组合的列表,满足:

  • 若操作数是临时对象,则操作数子项在绑定元素子对象时被复制消除
  • 组合的列表是非真列表,当且仅当操作数是非真列表。
  • 子对象的元素是对应的操作数以对应的引用标记字符(若存在)绑定单一参数得到的值。
    • 注释 若不存在引用标记字符,元素被对应复制初始化。
  • 若操作数是非真列表:
    • 注释 此时需初始化组合中的非列表结尾元素。
    • 当不存在标记字符或存在标记字符 % 时,组合的最后一个元素是操作数中的最后一个元素的副本。
    • 否则,组合中的最后一个元素是新创建的子对象引用
      • 其被引用对象的表示中没有子项。
  • 创建的有序对初始化完成后,参与初始化被绑定对象:
    • 若存在标记字符 & ,则创建子对象引用作为被绑定对象,其被引用对象是创建的有序对。
      • 被创建的子对象引用的被引用对象的表示应避免复制初始化任何操作数一等对象。
    • 否则,创建的有序对直接被作为被绑定对象。
  • 被绑定对象的元素总是不具有临时对象属性。

  绑定临时对象外的引用临时对象视为对被引用对象的访问

注释 这意味着除绑定临时对象外,若绑定操作数的初始化的引用值时实际引用临时对象,则因超出生存期的对象访问,行为未定义

  仅在绑定临时对象且操作数可转移或使用标记字符 % 时使用复制消除。

原理

  绑定的默认行为对引用值特殊处理,是为了满足 G1b ,而不是像某些语言(如 [ISO C] 和 [Rust] )仅通过内建的机制提供特定的左值上下文(lvalue context)

  绑定的默认行为不使用析构性转移的操作(类似 [Rust] 的设计),原因是考虑到绑定的副作用影响操作数(即便因为对象被销毁而不一定是修改操作)和破坏幂等性(特别是指定过程调用的形式参数时)违反易预测性原则

  为允许调用宿主对象转移构造函数,限制复制消除。初始化引用之外的参数创建也不是 [ISO C++17] 约定要求消除复制的上下文。

  作为操作数的引用值中的唯一引用在使用 & 引用标记字符时可同时蕴含绑定临时对象属性,这使绑定为变量的消亡值可能以名称表达式求值结果(不会是消亡值)的引用值访问时,能和其它引用值区分。提供这种设计的理由是:

  • 以下两种涉及消亡值的资源访问可被统一:
    • 直接访问消亡值表达式。
      • 消亡值表示即将被转移的资源。
    • 以消亡值初始化一个带有 & 引用标记字符的非结尾序列变量,并以这个变量的名称作为表达式进行访问。
      • 这通常需要使变量指称消亡值引用的资源,而不仅仅是表示即将被转移的消亡值自身。
      • 具有临时对象属性的引用值通过右值初始化,相当于宿主语言中的右值引用,典型地表示能被转移的资源(而不一定需要立刻被转移)。
      • 初始化变量同时转移资源,相当于宿主语言中复制初始化时调用转移构造函数转移操作数的资源到变量(对象或绑定到临时对象的引用),使之表示转移后的资源。
    • 尽管值类别可能不同,这两种表达式都可以表示蕴含被转移的资源的对象。
  • 直接求值名称表达式往往比其它替代方式更直接高效,但结果总是左值而不具有唯一引用属性,而使用临时对象属性允许在求值的结果中被保留。
    • 项引用(而不是临时对象引用)中的临时对象属性不影响值类别。
    • 类似地,在宿主语言中,和值类别不同的状态以右值引用类型声明的形式编码在类型系统中。
      • 引用值中的临时对象属性接近宿主语言中转发引用蕴含的静态类型。
    • 但和宿主语言不同,临时对象属性能随初始化后的引用值跨过程传递,而无需多次转发并在每次推导引用类型。
  • 基于上述规则,对象语言中特定的转发操作处理可统一的方式处理两种表达式以转移资源。这种设计能简化一般的使用。
    • 引用值支持临时对象属性对有效的转发对象应用子的实现是必要的。
      • 通过对象属性,转发对象操作可避免总是从实际的操作数提取值类别的需要,允许作为应用子而非操作子
      • 若不使用临时对象属性,则需要其它方式编码和值类别不同的状态以和消亡值区分,例如宿主语言的静态类型信息。
        • 这会增加语言规则的复杂性。
    • 需要转发资源时,一般只需要使用转发对象操作;其它情形可安全忽略引用值中的临时对象属性。
    • 类似地,在宿主语言中,编码在类型系统中的状态在特定上下文中用于实现完美转发
      • 宿主语言中,右值引用类型的变量作为左值(而不是消亡值)被访问,在大多数操作中没有和其它左值区分的意义。
      • std::forward 这样需要区分引用类型的转发操作(实例是一个函数,而不是宏)中,右值引用类型在局部是有意义的。
      • 但是这仍然存在限制:因为没有跨过程传递的状态支持,明确具体类型还是需要程序显式指定 std::forward 的类型参数(或者宏),而不是 C++ 函数(应用子)的方式实现。
  • 消亡值应和纯右值在初始化其它变量时转移资源的作用一致,对应唯一引用属性和临时对象属性的相似处理。
    • 通过唯一引用属性仍可区分一个具有临时对象属性的引用值以消亡值还是纯右值初始化。
  • 可使用 & 以外的引用标记字符避免这里的行为而被初始化的被绑定对象(引用值)中引入非预期的临时对象属性。

  绑定结尾序列和非结尾序列的非递归绑定规则略有不同。

  • 特别地,除非被绑定对象是引用值,引用标记字符(不论是否存在)同时被作用到作为一等对象的元素上。这是因为:
    • 此时,需要把操作数作为一等对象进行分解,使用引用标记字符或者不使用引用标记字符不破坏其它语义规则。
    • 和非引用结尾序列相比,这使有序对的两个元素在初始化时的规则不同,但这具有合理性,因为:
      • 有序对作为(非真)列表时,结尾元素和其它元素的地位不是相同的。
      • 有序对的元素在 NPLA 对象表示中即已不对称,地位不可交换。
      • 这种设计简化了一些重要的派生实现。
  • 蕴含绑定临时对象属性的规则不适用绑定结尾序列中的元素,因为:
    • 结尾序列的元素不具有名称,而不是通过绑定创建的能作为名称访问表达式的变量。访问元素需通过其它方式(如对象语言中的子对象访问操作),方法和结果不唯一(如可能具有不同的值类别,可能即时转移资源等),不具有和消亡值的统一性。
    • 引入附加的临时对象属性容易引起非预期的转移。除子对象访问时可能发生的直接转移(通常较明确),随引用值跨过程传递的临时对象属性在之后可能继续引起其它转移。因为首先通过子对象而非名称表达式访问,这种转移的存在性在创建变量绑定的位置通常不显然而无法预知,容易误用。
    • 一旦不需要附加的临时对象属性,去除属性而得到引用值通常是较为困难的,需要对象语言提供特设的操作或绑定新的对象(尽管引入临时对象属性可能同样困难)。

注释

  引用折叠的结果满足不可修改引用属性的传播性质。其它情形应满足 NPLA1 引用值使用约定。因此,仅有使用标记字符 % 进行消除引用时,被消除的引用值的不可修改属性被忽略。

  绑定临时对象外不和 [ISO C++] 一样可能延长右值类类型子对象的生存期。

  具有引用标记字符的形式参数支持引入引用值并支持绑定引入临时对象的实际参数。

递归绑定

  形式参数树子项和操作数树的子项成功匹配后绑定子项。

  递归的绑定匹配对应递归的绑定创建,允许以操作数树的子项对应初始化形式参数树的各个子项。

注释 和形式参数树中的结尾列表的符号值被视为整体不同,递归绑定可包含项和其子项的多次递归的匹配。

  绑定算法应确定和当前处理的操作数树的属性,即操作数属性(operand property) 。其中蕴含的表示操作数的项对应的标签,称为操作数标签(operand tags)

  操作数属性和形式参数的引用标记字符结合决定是否按引用传递初始化,并判断绑定初始化时是否允许转移。

  绑定匹配递归处理子项时,应确定子项的操作数标签,以指定子项可能具有的上下文相关的差异。

  绑定初始时,操作数应为纯右值。此时,以临时对象标签作为初始操作数标签。

注释 这指定指定操作数是可被唯一使用的临时值。与此不同,若项表示作为一等对象求值结果,应不具有临时对象标签

  一个项的子项的操作数标签由这个项的操作数标签(处理子项时,代表先前确定的所在的项的标签;以下称为当前操作数标签)和本节中以下约定的子项继承规则决定:

  • 若操作数子项不是引用值,则子项的操作数标签和当前操作数标签相同。
  • 否则,匹配的子项是这个引用值的被引用对象,子项的操作数标签以操作数子项中的引用值的属性和当前操作数标签按以下引用项继承约束限定:
    • 子项的操作数标签不包含临时对象标签
    • 子项的操作数标签是否包含唯一引用标签同引用值的属性。
    • 子项的操作数标签是否包含其它标签同对应的当前操作数标签。
    • 在以上基础上,引用值的属性向子项的操作数标签对应的属性传播:若前者包含不可修改属性,后者应包含不可修改标签。

  绑定需转移子项(包括绑定子项的复制消除)时,使用项的转移

  绑定临时对象属性标签可影响参数转发。若需按类似宿主语言的成员表达式的值类别而不是成员是否为非左值引用进行转发,需确保被转发的值不是带有临时对象标签的引用值。

  操作数标签中:

  • 唯一引用标签由所在的项单独决定。
  • 临时对象标签仅在递归绑定时所在的所有列表项都是非引用值时包含。

原理

  引用值的不可修改属性标记不可修改项而避免非临时对象的转移。这和宿主语言中的非 mutable 类数据成员访问操作符决定 const 限定符的规则类似。

  子项标签继承规则保证使用 &% 标记字符时,值类别的决定规则和宿主语言的成员访问操作符类似:

  • 列表左值中的元素总是被绑定为左值
  • 列表右值的元素按元素是否为引用被绑定左值或消亡值
  • 特别地,项引用的临时对象标签不被继承到作为子项的被引用对象,因为即便被引用的列表对象是一个临时对象,它的元素不被作为纯右值匹配。这和宿主语言中成员访问操作符访问的右值是消亡值而不是纯右值类似。

  使用对象语言,若需判断列表左值中的元素是否为引用值,可直接绑定列表操作数为引用并按需转换为消亡值再递归绑定列表元素。

绑定构造

  部分函数合并的求值包含形式参数树,通过绑定规则环境中引入绑定,其调用指定绑定操作。具有这样的语法构造的表达式是绑定构造(binding construct)

  一些绑定构造使用 <binding>提供在一个表达式多次出现的形式参数树和操作数树。

注释

  绑定在符号值上的引入变量

  按绑定初始化的约定,操作数树的子节点初始化被绑定的形式参数树的对应子节点。

  和 [RnRK] 不同,各种绑定构造可使用 <body> 提供操作数。

  对绑定项的处理和 [RnRK] 的其它不同参见文法元素补充约定

  另见初始化

强递归绑定

  除类似 Kernel 的常规绑定外,NPLA1 的部分绑定构造支持延迟附加的绑定的形式。

  强递归绑定支持若同时绑定的递归符号值构成循环引用,则递归绑定的值都是未指定的内部表示而不引起错误

  强递归绑定是对象语言的绑定构造实现的附加机制,形式参数树的递归匹配仍使用递归绑定

参数转发

  绑定构造可支持参数转发(argument forwarding) ,根据实际参数确定形式参数中是否为引用值,保留值类别和可修改性

注释 类似宿主语言中的转发引用参数。

作用顺序

  绑定构造引起的绑定初始化的作用顺序满足初始化的约定。

  若其中存在副作用,其顺序还满足:

  • 若存在同一形式参数树子节点的不同绑定的操作,则这些操作的副作用之间非决定性有序
  • 不同符号值的形式参数树子节点的绑定操作的副作用之间无序
  • 形式参数树的子节点上的绑定操作的副作用先序所在的节点上的绑定操作的其它副作用。

原理

  这些规则允许并行的深度优先遍历的绑定实现。深度优先遍历使任一时刻成功完成绑定的对象集中,相对其它策略其状态更易预测

对象语义

  关于对象的存储,基本内容参见 NPLA 存储和对象模型

  另见对象语言内存安全保证

NPLA1 对象同一性

  NPLA1 的对象是一等对象。由定义,NPLA1 的对象默认确保同一性

  例外参见 NPLA1 子对象

  对象的引用值通常不保证其作为被引用对象和其它对象都不同一,包括唯一引用的情形。但除非另行指定,作为函数实际参数的对象若是右值引用,则实现可假定被引用对象唯一。

注释

  关于右值引用的保证类似 [ISO C++] [res.on.arguments] 。注意这在对象语言而非宿主语言中适用。

NPLA1 子对象

  基本内容参见 NPLA 子对象

  子对象可具有引用值,即子对象引用

  子对象引用访问的被引用对象不保证具有同一性。

原理

  和宿主语言不同,通过相同方式构造的子对象引用访问的被引用对象未指定是否为同一对象

  这允许实现使用和宿主语言不同的方式创建非一等对象作为子对象的表示

注释

  和宿主语言不同,NPLA1 对象语言不直接提供访问子对象的内建语法。

子对象访问约定

  作为使用名称表达式访问对象的推广,特定操作可使用非环境的其它对象显式地访问其子对象。

  除非另行指定,这些访问操作以本节约定的规则确定结果的值类别和类型

  若指称非环境对象的表达式 E1 是访问操作的(被求值的)实际参数,子对象来自这个参数指定的对象;否则,子对象来自 E1 引用的环境对象中的被绑定对象

  具体的访问操作确定具体的被访问的子对象。

  访问操作中:

  访问操作的结果值的类型和值类别满足一一对应,且结果经值类别转换后和被访问的子对象的类型相同。访问操作中没有明确指定的结果的值类别以此通过结果的类型推断。

  若通过以上约定,仍没有明确结果的值类别,则按以下默认规则确定:

  • 若被访问的子对象是引用值,则结果是泛左值
  • 否则,若 E1左值,则结果是左值。
  • 否则,结果是右值

  结果是具有被访问的子对象类型对应的值,它的更具体的值类别通过上述等价关系按结果的类型对应确定。

  E1 或被访问的子对象的传播引用值的不可修改属性

  被访问的子对象访问若具有影响值类别或被传播以外的其它属性被保留,对应在结果中出现。

  成员访问(member access) 操作访问称为对象的成员(member) 的子对象,满足本节的约定。

  具体操作可具有其它改变结果的值类别和类型的约定而实际使用不同的规则。

原理

  确定结果的值类别和类型的方式类似按宿主语言的成员访问确定对表达式 E1.E2 的值类别和类型。E1 不一定是左值。

  NPLA1 没有形如 E1.E2 的特设对象访问表达式语法,而以具体的访问操作代替,因此可具有近似但不同的规则。

  特别地,除非 E1 引用环境,在 E1.E2 中显式指定被访问的子对象的表达式 E2 在访问操作中一般并不存在。代替这里的 E2 的是由具体访问操作指定被访问的子对象,其类型直接代替 E2 的类型。

  其中,按有序对访问列表的最后一个元素时,被访问的子对象不是引用值,即视为纯右值。这里不蕴含求值,不会有值类别转换

  通过被访问的子对象的类型和 E1 的值类别确定结果的值类别的默认规则类似 C++ 成员访问表达式 E1.E2 确定值类别的规则,但略有不同:

  • 因为不保证存在名称表达式 E2 ,不需要求值算法使用类似 C++ 的 unqualified-id 一致的方式使结果总是左值。
  • 被访问的对象是右值引用值时,结果是右值引用值(即消亡值),而不一定是左值。
  • 和 C++ 不同,NPLA 消亡值总是右值引用类型,NPLA 纯右值也此类似 C++ 纯右值实质化转换初始化的消亡值,因此逻辑上需要 C++ 消亡值的情形和此处的 NPLA 右值近似。

注释

  初始化非引用值的复制初始化(包括以下的替换消亡值为右值)可能通过返回值转换实现。

  在对象表示上,传播引用值的不可修改属性决定 E1 指定的被访问对象或被访问的子对象若具有不可修改属性,结果也具有不可修改属性。

  典型地,被保留的其它属性包括临时对象引用。临时对象引用可被继续绑定而可实现按需转发被引用对象。

  按默认规则访问相当于这些规则确定的值类似 C++ 表达式 std::forward<decltype(E1.E2)>(E1.E2) 的值。

  具体操作可具有其它改变结果的值类别和类型的约定而实际使用不同的规则。

  例如,推断结果的值类别的规则中的左值和消亡值可被替换为右值,则近似 C++ 表达式 std::forward<std::remove_cvref_t<decltype(E1.E2)>>(E1.E2)

  传播引用值属性和宿主语言及递归绑定规则类似。

  关于子对象的修改,参见对象的修改和改变

对象的修改和改变

  对象作为实体可修改和改变,可具有可变数据状态可变管理状态

  NPLA 约定表示宿主环境的对象,其修改也同这些对象的修改。

  隐藏状态在针对对象语言的的讨论中被排除。除非另行指定(由具体操作的语义蕴含),所有可变状态都不属于这些被排除的状态。

  改变对象可引起诊断

  • 对明确不可变的对象进行改变的操作引起错误
  • 具体操作的语义中,所有操作都允许的不要求诊断错误的改变操作隐式地指定可变管理状态的改变。

  以下状态是可变管理状态:

  • 环境中的被绑定对象。

  除非另行指定,其余可变状态都是可变数据状态。

  类似宿主语言(如关于 const 限定符的语义),生存期开始前或结束后的(可能并未完成构造的)对象中的子对象的修改不是对象的修改;对应地,此处的子对象的变化也不是对象的改变操作。

  改变上述的被排除的状态的修改操作不被视为对象语言中的对象的改变操作。

  对包含所有权的子对象的修改是对所在对象的修改。

  除非另行指定,NPLA1 不限制任意对象不可修改。

  等价关系和限制不可修改性的方法的方式不唯一,因此不可修改性也不唯一。

  因为外部表示不唯一,不需要基于此定义一种正规的关于外部表示的等价判断形式。

  对象的不保证同一性子对象的修改和改变不保证蕴含对对象的修改和改变。

原理

  开放类型映射不保证非特定对象之间的不可修改性具有唯一的定义。

  对象的修改和改变作用在确定的对象上。

  若不同的对象之间不具有同一性,则作用之间无关。因此,修改和改变作为副作用,不保证在不同一的对象之间共享

注释

  所有对对象的状态的约定针对同一个对象。

  对象的子对象作为可变管理状态,使不可变对象具有允许这些状态改变的内部可变性而和对象的可变性不同。

  对诊断的要求类似 [RnRK] 。

  环境中的被绑定对象在仅讨论不可变性的意义外仍是数据对象。

  引起对象内的可变管理状态的改变而不改变对象的操作在宿主语言可通过类的 mutable 数据成员实现,但 NPLA1 不提供特性使任意的子对象的可修改性的限制如宿主语言的 const 限定符自动传播(而一般需要使用成员访问操作),因此也不需要提供对应的类型检查修改机制。

  和 [RnRK] 不同,NPLA1 支持直接修改对象,而不只是通过指定子对象关联的被引用对象的改变操作

  冻结操作是使环境对象上具有类似宿主语言的 const 传播约束的操作;和宿主语言不同,这不是静态类型系统约束。

赋值

  NPLA1 的赋值(assignment) 操作专指以引用值操作数指定对象且不引起同一性改变的对象修改

  被修改的对象由赋值操作的目的操作数决定,可能是操作数对象或其引用的对象。赋值操作后,被修改对象的值来自源操作数。

  操作数和源操作数相同的赋值是自赋值(self assignment)

  除非另行指定,赋值操作不保留源操作数的值类别和可修改性。

  赋值可引起源操作数对象的复制或转移,分别称为复制赋值(copy assignment)转移赋值(move assignment)

  复制赋值时不会复制消除对象。若被赋值的源操作数的值在复制出错,目的操作数引用的对象不被修改。

  通过对象的子对象引用修改对象的子对象不保证作用在对象上。

原理

  赋值不引起同一性改变的保证和区分复制赋值和转移赋值类似宿主语言。

  宿主语言中,通过源操作数的静态类型(左值或右值引用类型)明确区分两者,但 NPLA 不要求类型系统(尽管支持类似作用的元数据),两者区分实际依赖具体行为。

  子对象引用不一定保证引用完整对象,而修改的副作用可能需要完整对象的信息:

  • 例如,修改作为列表的子对象的有序对时需要维护保持子对象关系的内部状态,而子有序对引用若不提供所在的列表的引用,则无法实现维护状态。
  • 为维持子对象引用实现的简单性,不对这类情形进行一般要求。
  • 特定操作可以提供更强的保证以允许满足变化的自由

注释

  赋值操作可能伴随赋值以外的其它副作用,如转移导致的修改

  特定的赋值操作可能不支持自赋值,指定自赋值具有未定义行为或引起错误。

  注意避免使用引用值作为操作数的自赋值引起循环引用:此时除非另行指定,引起 NPLA 未定义行为

  不引起同一性改变的保证和 Kernel 的赋值操作包含以特定对象进行替换(可使用项的转移实现)而使对象被修改的情形不同。

  赋值不保证子对象的同一性不被改变;子对象的引用仍可能被赋值无效化

转移导致的修改

  转移可导致被转移对象的外部可见的修改。

  转移不需要是直接显式求值特定的函数调用的副作用。

注释 例如,使用唯一引用初始化对象,可转移表示被引用对象的项。

  和宿主环境不同,当前实现不直接通过初始化转移宿主对象

  被转移的对象在转移后具有有效但未指定的状态。

注释

  当前实现中,当项被转移后,表示的值为 () 。这和返回值转换等引入实质化临时对象时可能具有的转移的作用(仅在互操作时可见)不保证相同。

  作为赋值规则的推论,通过转移对象的子对象引用修改对象的子对象不保证作用在对象上。但和其它修改不同,这同时被转移对象后的状态的规则覆盖。

驻留

  出现在表达式中多个位置的值在实现中可共享一个对象作为内部表示。这个对象被驻留(intern)

  当前实现不使用对象驻留,以简化存储对象的互操作

原理

  因为子对象允许通过引用值被直接修改,驻留对象创建的共享可能影响可观察行为

  因此兼容 NPLA1 语义的驻留要求排除可修改的操作,且被驻留的值对应的对象的同一性不被外部依赖。    注释

  驻留的对象在实现上共享存储,但仍区分同一性。

  一般地,驻留仅适合不可变对象,或改变后提供不同副本区分同一性的可变对象。

  [RnRS] 等不可变的符号可被驻留,但没有特别要求。

  [R7RS] 明确要求空列表的唯一性。和驻留一致,这可实现为全局共享对象。

无效化

  若对象的引用值保持有效,则指称的左值的对象同一性不变。

  作为间接值的派生实现,对象语言中的引用值的无效化包括以下情形:

  • 被引用的对象存储期已结束(此时引用值是悬空引用)。
  • 对象被除通过重绑定赋值和另行指定的情形以外的方式修改,而引起对象同一性的改变。

注释

  对项的重绑定或赋值仍可能因为对子项的修改蕴含被替换的对象的销毁,引起子对象生存期结束,而使其表示的对象的引用值无效化。

类型分类

  NPLA1 不要求支持任意类型的集合表示不相交,即分区(partition)

  但除非另行指定,基于实体元素文法引入的类型仍被分区。

原理

  不要求分区这避免全局地假定类型全集的具体表示,并支持开放的类型映射

  NPLA1 的类型谓词是一元谓词,只接受一个参数,以强调语言提供的接口的正交性

注释

  通过指定子类型关系可使两个名义类型作为集合相交。

  列表类型只包括真列表

  不要求分区、类型判断谓词、列表类型的设计都和 Kernel 不同。

NPLA1 对象语言数据结构

  本节指定在 NPLA1 允许以一等实体被使用的基本元素。

  NPLA 一等对象是 NPLA1 一等对象。

注释

  部分设计原则和规则和 Kernel 不同。

  另见对象语义

NPLA1 引用

  NPLA1 基于 NPLA 项引用支持实体的引用

  NPLA1 语义中对广义实体的构成依赖的使用也被称为引用,这不限被对象语言中的引用值表达。另见环境引用

  NPLA1 明确允许不通过对象的引用保存对象,但是也允许使用对象引用;即对象和对象的引用都可作为一等对象

  这也允许子对象直接被所在的对象蕴含。

  左值都通过引用值表示。另见一等引用表示存储和对象模型

  引用值在创建时即引用在生存期内的对象。

注释

  引用和 Kernel 及 Scheme 的引用类似。

  明确允许不通过对象的引用保存对象和 Kernel 不同详见实体语义

NPLA1 引用值使用约定

  除非另行指定:

原理

  按 NPLA1 规范求值算法,隐含当前环境直接求值名称表达式的求值结果是左值。这和宿主语言求值的 unqualified-id 在除了枚举器(enumerator) 外的大多数情形中类似。

  在此,这被约定为默认情形。其它情形需要附加的规则指定。

  特定的显式指定环境和名称表达式的操作访问环境中的被绑定对象,其求值结果可以是左值消亡值。这和宿主语言的涉及成员访问的表达式(形如 E1.E2E1->E2 )类似。环境相当于宿主语言中形如 E1 的对象表达式。

  因为类型系统的不同,类比成员访问的表达式时,忽略 C++ 的位域(bit-field) 、静态成员、成员函数和枚举器的访问规则。

  此处 E1 总是被视为左值,所以类似宿主语言的规则,结果的值类别由环境中的对象类型确定:当且仅当对象是左值时,结果是左值;否则是消亡值。

  因为 NPLA 的左值和消亡值是引用值,存在推论:

  访问不作为被绑定对象的子对象时,通常并非如宿主语言为支持推断参数类型的方式使用引用折叠,构造折叠的引用值默认不直接使用引用折叠的规则(除临时对象标签外同 [ISO C++] ),而直接由被引用对象确定。

  对访问列表中的子项构成的子对象引用,这也和宿主语言的涉及成员访问的表达式类似,除以下不同:

  • 元素是引用值时允许结果是唯一引用(而不是宿主语言的左值)。
    • 这是因为在此唯一引用指定的是结果的值的类型,而非类似宿主语言声明的右值引用类型。
  • 元素是临时对象的引用值时,允许引用值上的临时对象属性在访问中被区分(类似宿主语言以成员访问表达式作为 decltype 的操作数的结果)。

  访问被绑定对象使用引用值也满足不可修改引用属性的传播性质,避免被绑定对象被任意非预期地修改。

  其它情形是否需要满足不可修改引用属性的传播性质和具体操作相关,因此不明确要求。

  对违反不可修改引用引入的假定的修改操作要求错误避免隐式的 NPLA 未定义行为,因此引入类型错误作为违反引用值的属性引入的错误

注释

  互操作可能引入右值引用。

  访问子对象的具体规则参见子对象访问约定

  作为列表或者环境中绑定对象的一部分,引用值可能通过求值算法或对象语言提供的操作访问。这同时确定值类别。

  蕴含一次引用值提升转换的方式包括返回值转换

循环引用

  除非另行指定(如强递归绑定),对象中的循环引用引起 NPLA 未定义行为

原理

  循环引用破坏一些实现的假设而引起非预期的访问。

  不显式访问环境的操作也可能引入循环引用(而引起未定义行为),例如 NPLA1 参照实现环境 下:

$def! l ();
$def! l list% l;

  典型实现中,具有所有权的循环引用可引起资源泄漏;无条件遍历访问循环引用子对象的求值不具有终止保证

  例如 NPLA1 参照实现环境下求值以下表达式:

$let ((nenv () make-environment)) $set! nenv self nenv

  可引起被捕获的环境中存储的对象无法释放。

  能同时保证避免资源泄漏的实现引起一般意义上更根本的设计限制,因此不被使用。详见自引用数据结构和循环引用

  此外,为了避免 $lambda 等引起不经意的循环引用误用,根据易预测性原则,这些合并子构造器默认不使用强引用作为静态环境。

  若需保持静态环境的所有权,使用显式指定静态环境的构造器(如 $lambda/e )和 lock-current-environment 等。

  否则,容易引起循环引用,如以下表达式:

$def! f $lambda ()

  会相当于当前设计的:

$def! f $lambda/e (() lock-current-environment)

  此处锁定的当前环境的强引用被作为闭包的一部分绑定到当前环境中,引起循环引用。

  而求值当前设计中等价的:

$def! f $lambda/e (() get-current-environment)

  不引起未定义行为。

自引用数据结构

  因为不支持循环引用,不支持引用自身的自引用数据结构

注释 另见列表

NPLA1 环境

  NPLA1 支持一等环境

  环境对象也可能是语言中显式约定的和环境引用不同的非一等对象。

  NPLA1 的环境关连的父环境重定向使用 DFS(Depth-First Search ,深度优先搜索)遍历目标。

  环境中的一等对象是环境对象的子对象。子对象是环境,即环境子对象。

注释

  除了支持非一等对象的环境,和 Kernel 类似。

隐藏环境

  语言实现可提供非一等环境。总是不能被对象语言以一等对象访问的环境是隐藏环境(hidden environment)

  一般地,隐藏环境是某一个(非隐藏的)一等环境的直接或间接父环境(而能通过求值等间接操作被访问)。

新环境

  新(fresh) 环境是新创建的环境。

  新环境和先前的其它的(特别地,包括当前环境)不共享相同环境对象。

  除非另行指定,新环境是空环境

  创建新环境的一个例子是 vau 抽象实现过程调用

环境的稳定性

  环境在特定情形保证稳定性(stability) :一个环境是稳定的(stable) ,仅当总是可假定绑定维持一定意义的等价性,而可确保访问其中同名实体的可观察行为等价

  违反关于环境的稳定性的要求的程序具有扩展 NPLA 未定义行为

  当前要求确保的稳定性包括:

  环境中的绑定的对象可以在引入后通过对象的引用被修改(#对象的修改和改变)。

原理

  一般地,环境的稳定性要求构造环境时不能依赖非特定的动态环境(作为被名称解析访问的父环境),因为这些环境的绑定可能具有在构造环境之后确定的绑定,而不能确保环境中的名称具有可预知的含义。

  环境的稳定性简化分析程序的推理过程,也在许多上下文中允许程序更易被优化。

  从稳定的环境多次访问对象的计算作用是幂等的。这允许合并多次访问为一次而不改变程序的行为,允许具有较小的实现开销。

  环境的稳定性不易被可靠地判定甚至不可能被判定(例如,一个无法检查但可信的来源提供的环境),因此语言规则在此不要求进行检查。

  和 [RnRK] 不同,环境基本操作合并子基本操作及基于这些操作的一些派生操作操作数树构造时不检查其中的非列表项是否都为符号#ignore(而延迟到匹配时检查);匹配时不检查符号重复;若形式参数中的符号重复,则绑定的目标未指定。

  此外,NPLA1 提供单独的递归绑定符号的机制,且明确支持在操作数中同时递归绑定之前未被绑定的多个符号

  要求隐藏环境稳定允许实现共享隐藏环境作为父环境而提供标准环境

注释

  [RnRK] 的 make-kernel-standard-environment 若通过共享基础环境作为隐藏的父环境实现,也具有这里的稳定性。

  但是,[RnRK] 不提供同一性保证,也没有通过对象的引用修改被引用对象的操作,因此不需要支持不可修改引用即可保证值稳定性。

  环境对象符合默认的等价比较规则以及绑定的对象可通过引用被修改和 [RnRK] 不同。

  关于要求的环境稳定性,存在推论:稳定环境中的同名被绑定实体可证明排除通过对象的引用使其改变的副作用(如被修改)或总是具有同一性

环境生存期

  对象语言的实现提供给用户程序使用的初始环境的环境对象及其中的子对象满足:

  • 其创建先序于用户程序的对象的创建。
  • 除非提供为不满足环境的稳定性的环境中的被绑定对象,其销毁后序于用户程序的对象的销毁。

  程序引用环境中的名称时,应确保环境在生存期内。

注释

  环境中不满足稳定性的被绑定对象可能被修改且具有外部可见的可观察行为。若这个对象是一个环境的唯一强引用,则对应的环境对象在替换为其它值时被销毁。

  特别地,应注意使用函数时引入父环境的生存期。

  另见对象语言内存安全保证

环境中的绑定

  环境中的绑定的抽象不依赖对象语言中表达的引用的概念,允许直接关联一个没有引用的值。

  环境中的绑定对被绑定的对象具有所有权。除在环境中绑定中间值的不安全操作,这种直接所有权是独占的。

  绑定的变量名符号值构成的名称表达式解析的结果总是左值

注释

  环境中的绑定不依赖引用以及绑定所有权和 Kernel 的设计不同。

  另见一等引用绑定构造

重绑定

  环境中允许变量以相同的名称被重新绑定,即重绑定(rebinding)

注释 和 Scheme 类似。

  被绑定对象引用不因其引用的对象被重绑定操作替换值而被无效化。

  重绑定替换被绑定对象的值,不改变对象的同一性。若其中存在子对象,则子对象被销毁,任何子对象的引用值被无效化

  特别地,若继续访问已被求值指称的引用值引用的对象,则超出生存期访问而引起 NPLA 未定义行为

  任意隐藏环境 e 应满足以下绑定有效稳定性:通过引用值间接访问 e 中绑定的对象时绑定保持有效(蕴含不被移除或重绑定),保持被绑定对象的生存期和 e 对其所有权

  这避免因为上述访问违反内存安全而引起 NPLA 未定义行为。

  关于无效化,参见 YSLib 项目文档 doc/NPL.txt

被绑定对象的值和可观察行为

  任意隐藏环境e 的任意同一被绑定对象 o 应满足以下的值稳定性(value stability) :若 o 上发生使其改变的副作用(如被修改),则之后在以 e 或任意以 e 作为直接或间接父环境的环境中直接以名称解析o 的引用值访问 o 时,o 的值和发生作用前的 o 的值在影响可观察行为的意义上等价。

  若不满足值稳定性,访问副作用发生后的对象引起扩展 NPLA 未定义行为

  以下情形使对象改变的副作用不受值稳定性要求的约束:

注释

  通过限制引用值不可修改可以维护被引用对象的值稳定性。

  对象间接访问具有内部可变性的对象的可变管理状态的一类典型实例是一等环境中的绑定中的子对象(即便这个一等环境对象是隐藏环境中的子对象)。

冻结

  环境可进行冻结(freeze)冻结的(frozen) 环境中取得的绑定和引用值不可修改

  特定的环境修改要求环境不在冻结状态以确保不变量,要求类型检查。检查失败则引起类型错误

  冻结一个已被冻结的环境没有作用。

注释 冻结环境是幂等操作。

  NPLA1 隐藏环境是冻结的。

  当前 NPLA1 对象语言不提供在已有环境撤销冻结或在冻结的环境中添加、移除绑定或重绑定的方法。

  若程序中使用其它方法(附加初始化或提供本机实现操作)撤销冻结或在冻结的环境中添加、移除绑定或重绑定而使对象语言安全性保证失效,这种方法应由派生实现定义,否则程序行为未定义

  关于对象语言安全性保证,参见 YSLib 项目文档 doc/NPL.txt

原理

  环境的冻结操作类似 [ECMAScript] 的对象的冻结操作。类似地,冻结环境不会冻结其中的变量绑定中可能存在的环境子对象

NPLA1 广义列表

  NPLA1 的广义列表真列表或者无环的非真列表,其元素不构成

  列表的引用构成其它对象时,也不构成

原理 排除环使处理列表的操作不需要考虑一些复杂的自引用情形。

  绑定构造形式参数树是可能是符号或真列表。

注释 形式参数树可作为表达式直接在源程序中表达。

  通常意义的列表即真列表

  除非另行指定,NPLA1 列表类型指真列表。非真列表的类型是有序对

原理

  和 Scheme 及 Kernel 不同,NPLA 支持的列表都是真列表。另见关于自引用数据结构和循环引用的分析

  列表的这些特性确保基于列表的数据结构在对象语言逻辑上的简单性。也因此 NPLA1 对应的操作中,没有对环存在性的检查。

  没有环的结构能保证所有权语义能按需嵌入(embed) 到列表中,即列表可保证表示为同构的具有对节点所有权的嵌套 cons 对

有序对的子对象和子对象引用

  有序对的元素是有序对的子对象,有序对对作为元素的表示具有所有权。同一个有序对的元素节点之间没有所有权关系。

  部分操作可能修改有序对的子对象。

  除非另行指定,有序对的子对象被转移,使用项的转移。被转移的子对象在被转移后不在被转移的有序对中存在。

  子有序对引用可被绑定构造引入。

  关于子对象被修改和转移,参见 YSLib 项目文档 doc/NPL.txt

原理

  有序对的子对象是表示它的项的子项和值数据成员对应表示的对象。

  对象被转移后通常其子对象不需要再被访问,此时保持子对象的同一性和转移前的状态一一对应缺乏意义。作为析构性转移,使用项的转移可以复用已使用类型擦除或其它的间接存储方式持有的子对象,减小不必要的开销。

  作为默认规则,明确要求项的转移,而不是未指定是否使用项的转移,以满足语言规则自身的简单性易预测性。这类似 [WG21 P0135R1] 引入强制复制消除(madatory copy elision) 对 [ISO C++] 的规则起到简化作用。不同的是,因为没有静态类型的限制,子对象的类型能在程序运行时改变,而不需要引入静态分析开销,这同时使实现也更简单。

注释

  子有序对引用子列表引用子对象引用

  当前有序对引用总是引用至少一个有序对的元素。

  由绑定使用引用标记字符的非递归绑定的规则,绑定列表的子对象引用不直接共享操作数有序对对象,而共享元素是原容器元素的(经折叠的)引用值的有序对,是子有序对引用。

  这类似宿主语言的容器对象一般不能转换为共享容器部分元素的 C++ 对象引用。

NPLA1 合并子

  除非另行指定(如强递归绑定),对象语言中的所有合并子都是真合并子

  NPLA1 对象语言不提供其它合并子的普遍操作。

注释互操作意义上的 NPLA1 API 可支持其它合并子。

  合并子和操作数组合构成的函数合并是一个 NPLA1 对象,称为函数合并对象(function combination object)

注释 求值算法可接受的函数合并对象是有序对函数合并表达式作为函数合并对象是列表

  NPLA1 的合并子使用包装数(wrapping count) 存储可能需要求值操作数的次数。

  在不出错时行为和不使用包装数而直接使用嵌套子对象实现的行为完全一致,但在到达实现支持的最大包装数时继续包装即包装数溢出(wrapping count overflow) ,行为可能不相同:

  若某个操作使合并子超出上限,则符合非宿主资源耗尽的错误条件

  实现支持的最大包装数应满足:若发生包装数溢出,则直接创建和包装数相同个数的合并子符合宿主资源耗尽的错误条件

原理

  为可修改性,允许非真合并子。这可在互操作中表示类似合并子但在语言中不可见的非一等对象。

  为维护语言规则的简单性,合并子默认是真合并子。

  NPLA1 对象语言不提供其它合并子的普遍操作,这不保证完全满足类似 G1b 的原则。

  尽管没有要求,这种规约也更符合 G1b ;同时,这易于移植 Kernel 代码。

注释

  Kernel 的合并子对应 NPL 的真合并子

  对最大包装数的要求需要实现支持包装数是能和宿主资源的空间相较规模的值,这保证使用包装数的实现的空间效率不弱于不使用包装数而直接分配合并子包装的实现。

  这也表示通常用户程序的操作不会发生包装数溢出:若包装操作的次数导致包装数溢出,则直接分配合并子的替代操作也应由于宿主资源耗尽而失败。

NPLA1 数值

  数值支持的实现兼容NPLA 数学功能

  NPLA1 数值字面量求值结果是数值。

函数的间接值使用约定

  引用值作为间接值,首先符合作为实体的引用的使用约定。

间接值作为实际参数

  除非另行指定,一般地,函数接受左值引用操作数,使用引用的对象的值和直接使用右值作用相同,但不会修改被左值引用的对象。

  这等价隐含无副作用的左值到右值转换,被视为蕴含左值到右值转换。

注释

  另行指定的例子如函数参数转发

  此处的左值引用和宿主语言中的( constvolatile )左值作用类似。

间接值作为函数值

  部分函数值总是非引用值。

  这些操作对应的函数调用返回非引用值。

  返回非引用值的行为应等价返回值转换

  其它操作可具有引用值结果,对应的函数调用可返回引用值。

原理

  函数值非引用值可满足具体操作的语义要求(如非引用值的构造器),减少误用的可能性,并帮助提供内存安全保证

保留引用值

  保留间接值,包括直接保留间接值和间接保留间接值,适用间接值引用值的情形,对应地称为保留引用值、直接保留引用值和间接保留引用值。

  除非另行指定,被保留的引用值不被折叠

原理

  必要时要求引用折叠可避免引入非预期的引用的引用值。

  被保留的引用值可能逃逸或不逃逸而通常不能直接证明具有内存安全保证

保留环境引用

  保留间接值适用环境引用

注释

  和保留引用值的情形不同,因为只允许通过环境引用在对象语言中访问环境对象及其子对象,访问环境但不保留环境引用的操作只可能在(不保证内存安全的)互操作中出现

函数参数和函数值传递约定

  函数可能接受引用值参数返回引用值,是对函数的形式参数或函数值的初始化

  在复制初始化形式参数和函数值时,部分函数保证被初始化的值和初值符值类别和可修改性一致。这些初始化是转发操作。

注释

  另见函数参数和函数值传递

传递非引用值参数

  一些函数的参数进行左值到右值转换,实现参数的按值传递

  这类似宿主语言中直接使用对象类型的形式参数。

函数参数转发

  一些求值为引用值的函数的部分实际参数被保留,而不进行左值到右值转换

  这些值以保留值类别不变的形式被直接作为操作数,用于调用其它合并子。这种参数被转发

注释

  这些参数的转发类似绑定构造支持的参数转发

  参数转发的实现可判断值类别后分别对传递非引用值或直接传递引用值提供实现,或直接使用绑定构造。前者支持本机实现。

返回非引用值

  返回非引用值和参数的按值传递类似:若初始化函数值的初值符是引用,复制或转移被引用对象的值而不是引用值。

注释

  这类似宿主语言中返回 auto 类型。

函数值转发

  一些其它保留引用值的操作中,引用值来自参数,且难以通过自身的逻辑单独决定可否安全地直接返回引用值。

  此时,在返回之前根据特定参数是否为引用值,可选地转换函数值以确定是否保留引用值,即进行转发。

  特定的显式转发操作转发临时对象引用值使临时对象被转移,以转发的值作为结果,可不同于使用返回值转换

  • 同返回值转换,转发转移右值,复制左值。
  • 但当转发临时对象可确定唯一使用时,也转移临时对象。

原理

  函数值转发使某些操作在默认情况下满足间接值生存期规则而保持内存安全,符合适用性原则

注释

  确定是否保留引用值的机制类似 [ISO C++14] 中从没有括号的 id-expression 上推断返回 decltype(auto) 类型是否为引用类型。

  函数值转发的实现可通过判断是否需要转发引用而按需决定返回引用值或非引用值,或使用标准库的相关函数。前者支持本机实现。

  另见对象的可转移条件

  显式转发操作把右值、消亡值和带有临时对象属性左值引用视为被转发的目标。

  转发列表对象的子对象可能转移这个对象。

创建和访问对象的函数

  构造器(constructor) 是用于创建对象的函数。

  除非显式指定创建的对象具有引用值类型,构造器是典型的(typical)返回非引用值

  部分操作涉及对其它对象具有所有权的对象。

  一部分对象的构造器创建的对象完全通过其它对象的引用或对象的值作为构造器的参数而决定,且创建的对象对这些参数具有所有权,这样的对象称为容器(container) 。

  容器构造器的参数作为容器的子对象,是容器的元素(element)

  以容器对象或其引用作为参数,取得容器元素对象或其引用的函数是容器元素访问器(accessor)

  标准库提供一些属于构造器和访问器的操作。除非另行指定,标准库的访问器符合子对象访问约定

注释

  容器的元素扩展了有序对和列表的元素的概念。

  一些不是容器的对象(如真合并子)可通过非容器形式的构造器创建。

转发参数或返回值的实现

  没有约定需要转发的情形不使用显式的转发。

注释

  可转发参数转发返回值的函数可包含以下实现方式:

  • 使用特定的操作,以需被转发的表达式作为其操作数。
  • (仅对参数转发)使用标记字符 %参数绑定的变量。

  上述特定的操作可在被求值的表达式中构造显式的转发。

NPLA1 参照实现环境

  NPLA1 提供参照实现环境。其实现可在内部使用 NPLA1 库特性,提供给 NPLA1 用户程序

  NPLA1 参照实现环境和用户程序遵循部分不同的要求和约定。

  本章中的其它约定适用 NPLA1 参照实现环境,且可选地被用户程序使用。

NPLA1 初始求值环境

  NPLA1 以环境对象中的绑定作为公开的接口提供库特性,以进一步提供初始环境(initial environment) 作为求值环境,即用户程序初始的当前环境

  这些环境对象包含两类:

  这些环境应按语言规范要求的方式包含和展示所有可见的绑定

  除非另行指定,这些环境对象初始化后在用户程序访问前被冻结

  基础环境(ground environment) 是 NPLA1 程序可假定存在的一个根环境。

  基础环境是隐藏环境

  除非另行指定:

  • 基础环境不展示名称语言规范要求的除保留名称外的绑定。
  • 根环境是否展示基础环境中的绑定未指定。

  初始环境是一个包含基础环境作为直接或间接父环境的空环境

  实现可提供基础环境以外的根环境,允许派生实现定义在用户程序中修改其中的绑定的机制(而不一定是隐藏环境),直至被实现初始化参考环境的特定用户程序封装或冻结而避免进一步修改。

  如有必要,用户程序可通过派生实现定义的方式引入其它根环境。

原理

  这些环境对象设计为在参照实现环境提供,因为:

  • 不都保证能在用户程序中可移植地创建,而有必要在参照实现环境中提供。
  • 提供的库特性在可移植程序中可能经常出现,而适合在参照实现环境中提供。

  根环境在功能上不需要展示基础环境中的绑定,这允许简化初始化。但允许展示基础环境中的绑定也使派生实现能被简化。

注释

  提供基础环境和 Kernel 类似。

  互操作可能直接访问基础环境,这些操作应避免破坏实现和程序的假定。

  特性设计注记:

  • 为避免依赖逻辑上复杂的形式,一些特性当前在当前设计中排除。
    • 例如,依赖一阶算术的操作、其硬件加速形式的 ISA 表示的整数操作及依赖这些操作实现的数值塔(numerical tower) (en-US) 被整体忽略。
  • 上述忽略的特性可由派生实现补充,在派生根环境后按需进行 AOT(ahead-of-time) 优化(如 Kernel 的 $let-safe! 中包含的内容,其中引用基础环境的符号不再可变),然后组成类似基础环境。

  通过派生实现定义的方式一般依赖本机实现

NPLA1 实现环境初始化

  实现环境的初始化完成初始环境的准备,包括蕴含所有初始环境依赖的资源基础上下文(ground context) 的初始化。

  初始化基础上下文蕴含的根环境是求值默认使用的求值环境,初始化后可直接封装为基础环境使用。

  派生实现可在以上初始化结束之后,在运行用户程序之前完成其它初始化。

  初始化成功后,用户程序被运行;否则,程序非正常终止。

  以上初始化同时可能提供扩展字面量支持。

注释

  对初始化失败而终止的程序,建议但不要求实现给出诊断。

导入符号

  在环境中定义另一个环境中的同名变量,使被定义的变量是后者的引用值或值的副本,则称指定此变量名符号值在后者被导入(import) 前者。

注释 用户程序可导入环境中的符号值使用库中的绑定。

模块

  NPLA1 以绑定提供的语言特性被分组归类为模块

注释 同 [RnRK] 。

  模块的源(source) 提供特性的实现,可以是本机实现或者 NPLA1 程序。对应的模块分别是本机模块和源程序模块。

  模块的源可以是实现内建的,或位于实现环境提供的外部资源(如文件系统)。

  因为模块以绑定的集合的形式提供,需被包含在可访问的环境,或包含环境作为子对象的其它对象中。

  以环境对象作为模块的源的模块化方式称为环境作为模块(environment as module) 。[RnRK] 的 get-module 的结果和参照实现扩展环境的模块是这种方式的例子。

  模块可能包含子模块(submodule) 提供其特性子集。以环境作为模块时,环境子对象可作为子模块。

  从模块的源得到提供一个模块的所有绑定集合的环境对象的过程称为模块的加载(loading)

  模块加载可能失败。失败的模块加载引起错误

  根环境加载的失败不被直接依赖这些环境的 NPLA1 用户程序处理(而视为实现初始化的运行时错误)。

  一般地,模块和加载模块得到的环境对象没有直接对应关系:一个模块的绑定可以由一个或多个环境提供,一个已被加载的环境可能提供零个或多个程序可见的模块。但除非另行指定,一个模块的绑定不在超过一个的不相交的环境(之间没有直接或间接父环境关系)中提供。

  程序可通过加载外部模块来源取得模块。除非另行指定,这种模块以一个一等环境对象(可包含作为环境的直接或间接子对象)中的绑定提供。

标准库模块的初始化和加载

  标准库实现可作为语言实现实现环境初始化以提供模块时,可访问不作为公开接口提供的模块的源。

注释 派生实现可同时以标准库以外形式提供这些源为公开接口,用户程序也可显式地加载这些源对应的模块。

  除非另行指定:

  • 若这些源可能引起引入非公开的接口的副作用,则对应的模块不应被用户程序直接加载。
  • 假定加载这些模块时,当前环境是和标准环境或与其等价的其它环境。   * 注释 关于标准环境,参见 make-standard-environment
    • 其中,等价指使用其它环境不引入程序可观察行为差异。
    • 注释 等价的环境的例子包括以标准环境为父环境的空环境,以及这样的空环境导入符号的得到的结果。
    • 注释 若模块的加载不访问加载时初始的当前环境(通常仅在本机模块上适用),加载模块使用的环境可不影响可观察行为而不影响假定(即便和标准环境不等价)。

  违反以上要求或假定的程序行为未定义

原理

  以源程序模块实现时,一般不要求检查初始环境。这能有效减少实现的复杂性。

  因为标准环境不提供用户程序检查是否和其中定义的实体一致的直接的方法,通过替代的检查可能排除符合假定的初始环境。

注释

  这里的初始化可包含派生实现定义的其它初始化。

  虽然 NPLA1 标准库不作为接口保证提供这些源,这里的假定和 [ISO C++] [using.headers] 对引入标准库头的程序位置的限制类似:语言实现能有效地假定源程序中引入标准库头的上下文,因此标准库中的名称具有预期的含义。

模块稳定性

  提供模块绑定的环境依赖已知来源的绑定而确保稳定

  除非另行指定,模块中的特性依赖提供模块绑定的环境的生存期。

  除非另行指定,标准库实现应确保其中的模块在程序的生存期中可用。

原理

  特性依赖性允许实现操作的模块中绑定的合并子可具有静态环境是提供模块绑定的环境的子对象的合并子的实现。

注释

  稳定要求同 [RnRK] 的 get-module 的约定。但因为值稳定性和 Kernel 不同,NPLA1 的稳定绑定一般不可修改

  对标准库模块,稳定性要求一般表示其中的特性不能依赖用户程序运行时的非特定的当前环境,而可依赖从基础环境及从基础环境派生的新环境

  生存期可用的规则一般要求标准库实现在初始化后保存环境强引用

库接口约定

  基础环境的特性在根环境中直接绑定,统称根环境特性。

  关于特性的约束作用于接口描述。不改变可观察行为时,实现可使用不同的未指定的根环境提供绑定。

  描述模块接口的小节可以指定适用于该小节的模块约定。此时,描述的边界应能和其余的实体区分。

  接口可能提供关于宿主语言互操作的约定,作为对提供这些支持的实现的要求。

注释

  具体特性参见 NPLA1 根环境特性

  具体根环境的存在性未指定。在同一个环境中可见的不同变量可能来自不同的根环境。

  接口描述的顺序同 [RnRK] §4 的原理,允许接口仅依赖先前出现的接口派生实现。

  和 [RnRK] 不同,库主要提供一元谓词,也不需要为 <body> 隐含 $sequence 支持重新定义 $vau 等操作,不需要拆分 Kernel 的核心库特性到 [RnRK] §5 和 §6 。

库接口实体

  按实体区分,NPLA1 的库特性有两类:对象操作

  对象语言中可实现的操作以函数的形式提供,可以是本机实现宿主语言函数或由现有操作派生的合并子

  除此之外,派生实现可指定提供对象或操作对应的非常规函数

  操作的结果是对应的函数调用正常控制下取得的求值结果,即函数值;操作的作用即函数调用的作用

  根据操作的功能描述,对应的函数可能具有非正常的控制条件。此时,函数调用不取得函数值,操作不具有结果。

注释 非正常退出时,函数调用的求值结果可以是错误对象或派生实现定义的其它表示求值结果的实体。

  除非另行指定,函数调用时具有的错误条件是非正常的控制条件;其中,以异常实现错误条件的情形具有异常条件

  特定的操作约定对应的函数是终止函数全函数;这不适用于满足错误条件的情形。

  特定的操作约定对应的函数作为算法过程满足计算复杂度约定

注释 排除错误条件,指定复杂度的函数是终止函数。

  本章其余各节适用 NPLA1 对象语言中的这些操作。

  操作中的大部分具有特定的名称,满足函数名称约定

  其它操作不具有特定名称,可由上述操作间接地提供,如蕴含在某些操作涉及的函数值中。

注释

  在对象语言中不能直接表达的操作不能作为库特性,这些操作不对应库接口实体,其结果和作用仍照更一般的规则处理。

  渐进复杂度常以 O 记号指定上界。

  若函数调用总是取得值,指定复杂度的函数同时是全函数。

库特性实现分类

  库特性分为基本的(primitive)派生的(derived)

原理

  前者在设计上不分解为更小的其它特性的组合,通常需要本机实现;后者可由可移植的 NPLA1 源代码实现。

注释

  区分基本和派生的特性在设计上类似 [RnRK] 中的基本和库特性。

  注意和 [RnRK] 的库特性不同(而更接近宿主语言),NPLA1 的库特性是以 NPLA1 程序使用的接口而非实现的角度定义的,不总是使用对象语言实现,外延更广。

标准库

  本文档中要求的通过基础环境直接或间接提供的总称标准库(standard library)

  标准库的接口随语言规范在本章和参照实现扩展环境约定。

  核心库(core library) 是提供直接绑定在基础环境中的、保证可派生实现的接口的标准库模块。

  在参照实现环境中的不同标准库模块的绑定都可在基础环境访问。

  在参照实现扩展环境中的标准库模块以其它环境(通常作为基础环境的子对象提供)中的绑定和基础环境隔离。

  派生实现可以库的形式提供语言扩展或其它功能特性,扩充标准库。

注释

  因为库的定义和 [RnRK] 指定的不同,类似 [RnRK] §4 约定的基本特性,属于 NPLA1 库特性

扩展库

  基础环境也可提供的附带的其它接口,和标准库使用相同的约束。

  一些操作的描述使用等价的表达式求值指定。除非另行指定,这些表达式中:

  • 符号值和先前出现的函数同名,则指称对应的操作。
  • 默认使用基础环境作为求值环境。

常规函数约定

  本节提供作为库特性的函数的默认规则以简化库特性的描述。

注释 库的一般派生实现和用户程序的实现也建议参照本节约定。

  除非另行指定:

  • 操作以指定名称的变量的形式提供,求值为可参与函数合并的一等实体(但函数合并不一定保证是合式的可求值的表达式)。
  • 函数作为表达式,求值为合并子,其函数合并的求值蕴含函数调用
  • 本文档约定的函数在其调用不依赖用户程序提供的非终止函数时,总是终止函数
  • 本文档约定的函数蕴含以下情形时,调用非纯求值
  • 在指定对应的函数调用是纯求值的操作上,排除满足引起非纯求值的条件的作用的情形外的其余求值,仍应是纯求值。

原理

  一些操作因不保证排除副作用 ,对应的调用非纯求值。

  但通过补充约定,特定的作用可能视为未指定行为,且不被程序的其它行为依赖(如可变管理状态的改变),仍可假定其求值是视为纯求值。

注释

  无条件遍历访问循环引用子对象的程序具有 NPLA 未定义行为,在讨论终止函数时已被排除。

  对象的修改和改变可包括转移参数。

函数值约定

  除非另行指定:

实际参数约定

  除非另行指定:

错误处理

  除非另行指定:

  求值时引起的错误使求值中断,可引起副作用,这样的副作用总是后序于已被求值的表达式中引起的副作用。

  被错误处理和检查的函数不修改参数或者函数调用外创建的对象。

非常规函数

  续延是默认不符合常规函数约定的例外。

  非常规函数归类为对象而非操作,但调用时错误处理同常规函数。

注释

  类似 [RnRK] 而和 [RnRS] 不同,作为一等对象的续延和续延的实际参数是否求值无关,因此不是合并子,且默认求值算法不支持续延作为函数合并被求值;但续延可通过特定的操作转换为应用子。

函数名称约定

  除非另行指定,本文档以指定名称的函数表示具有名称的操作时,其命名由本节的规则约定。

  函数名(function name) 即函数的名称。

注释 除非派生实现另行指定,函数名是变量名

  为提供库的描述,本节同时约定这些函数名关联的函数具有的性质。

函数名称前缀

  确定为求值为操作子的函数名以 $ 作为命名的前缀。

原理

  同 [RnRK] ,操作子一般应在视觉上被强调而避免误用。$ 来自 Special form 。

函数名称后缀

  函数名的最后的字符表示函数预期满足特定的约束或具有特定的目的。

  以引用标记字符结尾的表示涉及引用的操作,参见以下约定。

谓词名称后缀

  NPLA1 中的谓词<predicate>,即返回类型为 <boolean> 的函数。

  谓词的名称使用 ? 结尾。

注释 这类似 [RnRS] 和 [RnRK] 。

  除非另行指定,以下引入的对象语言中的谓词对应的函数调用的求值是纯求值

  以下是谓词的典型实例:

  • 类型谓词:接受一个 <object> 参数,判断参数是特定的类型的对象。
    • 调用这些类型谓词不引起错误。
    • 仅当参数指定的对象具有对应类型时结果是 #t
    • 除非另行指定,这些类型谓词忽略值类别的差异。
  • 等价谓词:接受两个参数,判断参数是否属于同一个等价类。

  因为 <boolean><test>子类型,按照返回值为 <test> 的函数可在不严格要求 <boolean> 的上下文中起类似的作用,视为广义谓词(general predicate)

  谓词是广义谓词的子类型。

注释

  大多数上下文接受 <test> 而不严格要求 <boolean> 。这和 [RnRK] 不同

  大多数类型谓词判断的类型一般同文法约定,而无关值类别

  以具名的函数提供的类型谓词,其函数名称通常和文法指定的类型对应。

  引起错误可具有副作用,其求值不保证是纯求值。

  广义谓词不使用后缀 ? 。另见 [RnRK] §6.1.2 关于 $and? 的原理及 [R6RS-Rationale] §15.1 。

赋值函数名称后缀

  修改一个对象而不要求第一参数是引用值且不改变被赋值对象类型的赋值操作对应的函数名以 <- 结尾。

注释

  这通常和宿主语言的赋值操作对应,可能有附加的副作用而不是简单地替换值。

修改函数名称后缀

  除函数名以 <- 结尾外的操作中,为了蕴含(不直接通过求值操作数或其子表达式引起的副作用的)修改的函数是修改函数(modification function) ,其名称使用 ! 结尾。

  可变管理状态的改变不需要指示可修改;此外,类似地,不改变可观察行为隐藏状态的修改不属于上述修改。

注释 这类似 [RnRK] 。

  这类操作同时是改变操作

注释 这类似 [RnRS] 和 [RnRK] 。

  一些其它情形下,特定的函数不被视为改变操作但其调用仍可能引起副作用,这样的函数名不要求带有 ! 后缀:

  • 直接求值操作数、其子表达式关联的对象可能引起副作用。
    • 注释 这和 [RnRK] 类似。
  • 一些操作可能非确定性地包含使操作数指称或引用的对象的值有效但未指定的修改。

  修改函数的调用的求值可能因为不同的操作数的影响,而可能依赖具体的条件确定是否改变对象:

  • 当通过操作数直接指定的条件确定是否修改时。
  • 当通过操作数确定被修改的对象时。
  • 当通过操作数确定引起修改的对象时。

  这些函数的返回值未指定值

注释

  修改函数的调用不一定总是具有能视为修改的副作用,可能不改变任何对象。

  这些函数的返回值的规定和 [RnRS] 相同,但和使用可用谓词 inert? 判断的惰性(inert) 值的 [RnRK] 不同。

引用标记字符的函数名后缀

  一些操作以(可能的 ! 前)结尾引用标记字符和不以引用标记字符结尾的名称提供多个变体。其中不含结尾的引用标记字符的表示操作的结果不是引用值,要求按值传递

  其它一些操作可能只提供以 % 结尾的变体。

  不使用引用标记字符的函数及其函数值时,不因引入引用值违反内存安全

原理 这允许通过避免公开带有引用标记字符后缀的操作提供一个内存安全的子集,以在派生实现对语言进行裁剪。

  名称以引用标记字符结尾的操作属于以下分类之一:

  对可能在函数值中间接保留引用值的操作,以 % 结尾表示对应的函数返回时不要求返回非引用值

  其它可能在函数值中直接保留引用值的提供不同引用标记字符的多个变体的操作:

  • % 结尾表示函数使用不进行左值到右值转换折叠的引用值参数,或返回折叠的引用值。
  • & 结尾表示函数使用不进行左值到右值转换的折叠的引用值参数,或返回折叠的引用值。
  • @ 结尾表示函数使用不进行左值到右值转换的未折叠的引用值参数,或返回未折叠的引用值。

  以上引用值参数的使用指以依赖这些参数的方式构成函数的返回值和/或决定引起的相应的副作用;返回的引用值来自引用值参数(若存在)。

原理

  为满足适用性,同时考虑避免误用和允许使用引用避免复制,对一些操作显式使用以 %& 结尾的函数名称以得到特别关注。

  因为语义相关,结尾引用标记字符使用和绑定的引用标记字符相同的字符,但不复用具体规则。

  尽管设计时没有参照,使用函数结尾的引用标记字符和其它一些语言的类似特性的使用惯例也一致,如 PHP 的 function & 语法

注释

  按这些规则,函数名以 % 结尾的操作在取折叠的引用值时,可能同时实现被引用对象的转发。这相当于被访问的被引用对象作为宿主语言的 std::forward 的参数后的调用结果作为操作的结果,但此处一般仍然保证支持 PTC

  函数名以 & 结尾的操作取得的折叠的引用值可能是唯一引用

引用折叠的约定

  取得折叠的引用值的默认约定同NPLA1 默认规则

  以上函数返回值中:

  • 折叠的引用值对调用时引入的引用值(不论是否来自参数)有效。
  • 除非另行指定,不对同一个对象引用折叠多次。
  • 注释 这些规则不保证结果是完全折叠的引用值。返回折叠引用值的函数可因未完全折叠的引用值参数等返回未折叠的引用值。

  这允许函数的内部实现引入一次引用值时,对来自每个参数的引用值至多只需要实现一次折叠。

  推论:若参数都不是未折叠的引用值,调用名称不以 @ 结尾的函数不引入未折叠的引用值。

  若指定的操作按不同操作数可涉及或不涉及和当前不同环境下的求值,提供不保留引用值和保留引用值的多个变体的操作以便保证内存安全。

注释

  提供不保留引用值和保留引用值的多个变体的操作以便保证内存安全的操作包括可提供以引用标记字符结尾变体的函数不提供结尾引用标记字符对应变体的函数指定的操作。

引用标记字符函数名与内存安全的关系

  利用区分引用标记字符结尾的操作,可指定具体关于具体操作的对象语言接口的安全保证机制

原理

  函数名结尾的引用标记字符用于强调无法总是保证内存安全的危险操作

  一般地,仅在明确需要引用值时使用引用标记字符结尾的操作,而避免返回悬空引用。这类似宿主语言函数的 auto 而非 auto&& 的返回类型,但宿主语言中返回非引用类型的表达式两者含义不同。

  函数名不带有引用标记字符结尾的操作通过避免保留引用值提供一定的内存安全保证,而带有引用标记字符结尾的操作较容易引起注意。

  这符合易预测性

注释

  一个典型例子是在函数中返回标识符求值的表达式:

保留引用值的约定

  可能直接保留引用值的操作中,不带有引用标记字符的操作传递非引用值参数,其它函数转发参数

  可能直接保留引用值的操作包括容器构造器或访问器,以及可能使对象中包含引用值修改操作

  这些操作的结果或引起的副作用完全由实际参数(根据是否存在引用标记字符 % 指定是否不经过隐含的左值到右值转换)的值确定。

  其中,带有引用标记字符结尾的操作是直接保留引用值操作。

  容器构造器可在元素保留参数的引用值。作为结果的容器总是作为非引用值返回,即在结果中保留参数的引用值。

原理

  以上操作是否确定地保留引用值在一些情形容易证明附加调用安全,此时可放宽安全特性子集的条件确保安全性;在此不作要求。

  对构造器及部分修改操作区分引用标记字符结尾可强调一些非预期保留引用值的容易误用情形;尽管总是返回非引用值。

  因转发参数而被保留的引用值不会被返回值转换或类似的操作影响,在构造的容器对象作为非引用值返回时,仍会保留引用值。对应宿主语言中,可有更显著的差异,如构造器对应的 std::tuplestd::make_tuplestd::forward_as_tuple

可提供以引用标记字符结尾变体的操作

  部分操作使用以引用标记字符结尾的函数名

  可提供不同变体的操作被严格限制,以避免过度区分造成在使用上不必要的复杂性。

可能使结果包含引用值的容器构造器

  容器构造器作为包括典型的构造器,可提供不同的变体(或其中之一):

  • 不在函数值中保留引用值,实际参数发生左值到右值转换作为容器的元素,这减少误用悬空引用的可能性。
  • 在函数值中保留引用值,实际参数不发生左值到右值转换而直接作为容器的元素,是不安全操作,但可以确保构造的对象中包含参数指定的引用值。

注释

  当只提供没有结尾引用标记字符对应名称的操作时,不需要满足以下的约定。

  在结果中保留参数的引用值的容器构造器可能保留可能无效的间接值而属于不安全操作。

可能使结果包含引用值的容器元素访问器

  带有引用标记字符结尾的操作是直接保留引用值操作。

  函数名不带有标记字符结尾的访问器属于参数转发操作函数值转发操作

可能使对象中包含引用值的修改操作

  修改对象或对象的子对象无效化引用值而影响内存安全。

  对可能保留参数中的引用值的操作,内存安全也依赖这些操作的指定修改后的值的内存安全性。

  在判定内存安全的意义上,以下操作的所有参数都可能是被保留的间接值

  • 简单赋值(simple assignment)(包含于赋值操作)。
  • 列表元素改变器(mutator)

  修改的结果由实际参数(必要时经过隐含的左值到右值转换)的值确定。

  以上操作都要求检查表示被修改的参数是左值。

  以上操作中,带有引用标记字符结尾的操作在对象中直接保留引用值

可能间接保留引用值的操作

  一些操作可涉及不同的环境,参数在这些环境中被求值可能得到引用值。

  这些操作包括求值为操作子的以下函数:

  • 以求值 <body> 作为尾上下文的操作。
  • 以求值 <expressions> 初始化 <defindiend> 指定的对象的绑定构造
  • 以求值 <expression> 或视为 <expression><object>(及可能发生的返回值转换)作为唯一作用的函数。

  以上操作中,带有引用标记字符结尾的操作是间接保留引用值操作,表示求值结果不要求按值传递并可返回引用值

原理

  不提供函数值转发的形式,因为:

  和此处直接在参数中给出被求值表达式不同,应用子中的一些求值的操作不属于上述操作,而不提供结尾引用标记字符对应名称的操作

不提供结尾引用标记字符对应名称的操作

  其它操作不使用以引用标记字符结尾的函数名

  若这些操作的结果直接来自操作数或其子对象(和以 % 结尾操作的情形类似),则:

  否则,这些操作不具有引用值结果。

  部分操作的结果直接来自实际参数。此时,若不具有引用值结果,则蕴含左值到右值转换

原理 这些操作不会使用临时对象作为环境,所以不需要使用以引用标记字符结尾的变体要求注意区分返回引用值而避免误用。因此,不提供区分涉及引用的变体,这也使接口设计更清晰。

  这些操作包括以下小节的情形。

  部分操作涉及参数转发函数值转发。这些操作不包含可提供以引用标记字符结尾变体的操作中的个别变体

  其它不提供结尾引用标记字符对应名称的操作暂不保证支持保留引用值。

  部分操作的内存安全性和可提供以引用标记字符结尾变体的操作类似,也是在函数值中保留引用值的不安全操作,但仅在引用值参数被保留且以此访问被引用对象时体现。

  这包括直接保留引用值和间接保留引用值的不同情形。

  除可直接以引用值作为结果的操作和其它节的操作不相交,以下分类对操作的参数和函数值分别约定,可能相交。

可直接以引用值作为结果的操作

  一些求值为操作子提供的函数选取特定的参数进行求值,作为控制操作。

  操作数中被求值的参数直接决定是否为引用部分操作直接返回引用值。

  被求值的 <test> 蕴含左值到右值转换,其它被求值的参数不蕴含左值到右值转换,调用者需负责决定是否求值其它参数。

注释

  这类似宿主语言中参数传递和返回 auto&& 类型。

不以引用值作为结果的操作

  部分操作类似容器构造器保证返回非引用值,但并非直接以参数实现决定函数值:

  若非构造器的操作总是返回列表和其它对元素具有所有权的容器对象,返回的对象总是按值传递。

  为简化接口以及满足其它分类(如直接参数转发操作),不提供不保留引用值的操作。

  和提供不同的变体的作为构造器的操作不同,此处的情形的结果可能包含引用值(和以 % 结尾构造器的情形类似)。

  若需要排除通过参数引入的引用值,应进行适当处理使参数中不含有会使这些操作引入引用值的构造。

  类似保留引用值的容器构造器,这些操作可在结果中保留参数的引用值

直接参数转发操作

  部分不带有引用标记字符的参数转发操作可能直接保留引用值的操作,称为直接参数转发操作。

  函数名不使用引用标记字符,和可直接保留引用值的函数名使用引用标记字符不一致:

  • 本节约定的函数和可直接保留引用值的函数名中带有 % 结尾的函数同属参数转发操作,但后者同时有不带有引用标记字符的变体。
  • 本节不约定和可直接保留引用值的函数名中不带有引用标记字符结尾的函数对应的操作。

  这种不一致(和函数值转发操作不同)是预期的特性:

  和可直接保留引用值的操作不同:

  • 这些操作并非用于构造对参数具有所有权的对象,不适合提供不保留引用值的操作。
  • 这些操作并非用于取子对象,返回值不一定是引用值,和具体操作相关,不适合使用引用标记字符区分。
  • 为简化接口及满足其它分类(如不以引用值作为结果的操作),不适合提供不保留引用值的操作。

  本节约定的函数对引用标记字符的使用和可提供以引用标记字符结尾变体的操作的函数名的使用不一致,含义相当于前者的结尾的 %

  以下的函数值转发操作同时也是直接参数转发操作。

  其它函数的参数传递的一般规则参见引用值作为实际参数函数参数和函数值传递约定实际参数约定

函数值转发操作

  若其它情形确需非转发操作取得引用值,可使用带有 %& 结尾的操作及可直接以引用值作为结果的操作替代实现。

  本节约定的函数不使用引用标记字符,和容器元素访问器的函数名不使用引用标记字符一致:

  本节约定的函数和上述容器元素访问器的函数名中不带有引用标记字符结尾的函数同属函数值转发操作,但后者同时有带有引用标记字符的变体。

原理

  和容器构造器引入引用值的情形不同,不带有后缀 % 相对不容易引起误用,因为返回值保留的引用可以继续被返回值转换影响。

  例如,使用保证返回非引用值的涉及环境中求值的操作,引用值会在引用的对象生存期结束前被返回值转换而不影响内存安全。

可能间接保留引用值的无引用标记字符对应名称的操作

  类似可提供以引用标记字符结尾变体的对应操作,部分不带有引用标记字符的操作可能间接保留引用值

  这包括由类型为合并子的参数(而非 <body><expressions> )决定是否保留引用值同时对其它参数进行转发的操作。

函数名称中缀

  中缀 -> 在函数名中可能出现一次,表示其(移除前缀和后缀之后的函数名中的)左边为源类型名的值到右边为目标类型名的值的转换操作。

  除作为源类型的值外,可能支持可选的附加其它参数。

  除非另行指定,转换得到的值是纯右值

  除非另行指定,转换函数调用的求值是纯求值

  除非另行指定,若被转换的值的复制可能影响可观察行为,被转换的值被转发初始化返回值。其中的右值被转移时,使用对象的转移项的转移未指定。

  除非另行转定,按接口文法约定引入的操作数作为对应名称之间的转换,仅在引入其中之一的模块提供。具体规则如下:

  • 源或目标具有在同一个根环境或其中的环境引入的对象类型时,在根环境中提供转换函数的名称。
  • 源或目标具有从根环境中的 std.strings 以外的环境中引入的类型时,仅在引入其中之一提供转换函数的名称。
  • 否则,在模块 std.strings 提供转换函数的名称。

原理

  如有可能,被转换的值一般应避免被复制。在接口上要求转发右值避免不必要的复制。

  标准库模块 std.strings 支持 <string> 的值的有关操作。因为相关转换的潜在的普遍性,在此进行特殊约定。

注释

  一般地,转换操作是源类型的值作为单一实际参数的转换目标类型的构造器

不安全操作约定

  除非另行指定,执行时蕴含以下操作的操作是不安全操作

  按不安全操作引起不同的未定义行为和不同的间接值,以下小节对不安全操作进行分类。

  分类之间的关系详见保留间接值

  分类可能不完全的且可能相交的(不安全操作可能不属于任何一个分类或同时属于多个分类)。

在函数值中保留引用值的操作

  在函数值中保留引用值的操作包括按函数名称约定具有引用标记字符结尾的操作。

  直接保留引用值操作可配合带有返回值转换的操作,指定个别函数参数不再保留引用值。

  这些操作可引起之后的不安全引用值访问

  保留的引用值同时可能被构造循环引用

注释

  一些修改操作无效化引用值。这些引用值若被保留且被访问,可引起未定义行为。

  不引起被绑定对象无效的修改操作不被视为不安全操作,即便它们无效化子对象的引用值。

在函数值中保留环境引用的操作

  环境引用被返回时,总是被保留

  创建环境强引用的操作是在函数值中保留环境引用的操作。

  这些对象可能因为没有及时保存环境引用使环境对象和其中的绑定一并被销毁,而使引用值访问其中的对象的程序具有未定义行为。

  通过非引用的形式引入环境循环引用的操作同时可破坏环境的资源所有权。

注释

  直接返回有效的环境弱引用的操作不引起环境失效,不在此列。

在函数值中保留其它间接值的操作

  特定的支持强递归绑定而在函数值中保留其它间接值,可能存在其它无效的间接值

  在函数值中保留其它间接值的操作的强递归绑定过程中引用共享对象的中间值。

在环境中保留环境引用的操作

  环境中的被绑定对象可具有环境引用子对象,间接地在环境中保留环境引用。

  这些操作使当前环境或参数指定的环境(而不是合并子调用时创建的新环境)中的变量绑定包含间接值,后者可能依赖合并子调用时创建的新环境。

  被绑定的对象中可能保留环境引用,而使用环境间接地保留对象中的引用。

  使用这些操作时应总是注意被依赖的环境的可用性。

  若环境对象销毁,所有直接和间接依赖环境对象的间接值被无效化。这些间接值的不安全间接值访问引起未定义行为。

注释

  绑定的对象中可能保留环境引用的典型的例子是合并子对象的静态环境。

  创建合并子可在合并子中的环境中保留环境引用。

无效化被绑定对象或环境引用的操作

  特定的操作蕴含被绑定对象的存储期的结束而无效化它的引用值。

  若子对象的引用值已被绑定,这些引用值不需要通过其它不安全操作,而仅通过之后访问标识符求值结果即可引起未定义行为。

  因为环境稳定性要求,NPLA1 实现环境不提供这类绑定,因此这些操作不是不安全操作。

  但派生实现可能在语言实现中提供不满足环境稳定性的一等环境,其中对象的子对象的引用值被绑定为变量,且前者可能被修改。

  此时,这些操作可能允许无效化引用后的被引用对象被访问,成为不安全操作。

  类似地,无效化环境引用而无效化环境对象也可使其中包含的被绑定对象的引用无效化。

  但环境生存期要求,除非作为不满足环境稳定性的环境的被绑定对象,NPLA1 实现环境不提供唯一的环境强引用可被用户程序修改而使环境对象被销毁。

  在这个前提下,要通过使环境引用作为子对象被修改而结束环境对象的生存期,首先要求通过在函数值中保留环境引用的操作取得环境引用,得到包含环境引用作为子对象的对象,且保证只有这个对象保存环境强引用

  因此,若不存在其它不安全操作,即蕴含不存在在对象语言操作中无效化环境引用的情形。

  类似地,派生实现可提供不满足环境生存期中的销毁顺序的环境,而使用户无效化对应的环境对象。

  此时,这些操作可能允许无效化环境引用后的环境对象被访问,成为不安全操作。

副作用可能引入循环引用的操作

  一些操作不依赖其它不安全操作(保留引用值或环境引用)即可引入循环引用:

注释

  通过已有的不安全操作构造的引用值也可能引入循环引用(而引起未定义行为),但不是单一操作的副作用,不属于本节的实例。

  例如循环引用中使用 list% 的例子,$def!list% 不会因此被视为此处的不安全操作,因为单一操作的语义不引入循环引用值。

可能破坏环境稳定性的操作

  通过引用值进行的修改操作可因破坏环境稳定性而引起扩展 NPLA 未定义行为(不一定违反内存安全)。

  这包括以下可无效化对象包含的引用值而使可通过环境访问的某个子对象的同一性被改变,从而破坏环境稳定性的操作:

安全操作子集

  作为对象语言安全性保证的一部分,用户程序通过限制或避免依赖特定的不安全操作,在特定情形下可实现对象语言内存安全保证,而不需要分析具体操作的语义:

参照实现约定

  本节约定对 NPLA1 参照实现内部有效,不作用在用户程序。

  除非另行指定,NPLA1 参照实现环境作为公开接口提供的变量在根环境中绑定。

  除非另行指定,以变量绑定的提供接口没有严格的跨版本兼容性保证。

参照实现接口描述

  除非显式指定,空环境没有父环境

  约定的接口通过绑定在根环境中的名称提供,参见以下各节。

  描述操作的上下文中,结果指操作的结果而非求值结果

  除非是语义蕴含的操作结果或另行指定,所有取得函数值的操作满足:

  • 结果未指定值
    • 未指定值可以是 #inert或其它值,但满足忽略值时不引起可观察行为的改变。
      • 注释 这排除了引入 volatile 类型或非平凡析构的宿主值
    • 若这些操作在 [RnRK] 或 klisp 中存在结果是 #inert 的对应的操作,且未指定作为函数值的结果,则结果是等于 #inert 的右值。
  • 这些操作若存在对应的函数名,满足函数名称约定
  • 这些操作中隐式分配的和按接口约定转移所有权给操作实现的资源不存在资源泄漏

原理

  #inert 是单元类型的值。但是,未指定值并不依赖单元类型的性质,而只是需要一种可在对象语言中判断是否为实现(如 [RnRK] 的 inert? ),而避免使用特设的规则。

  取而代之,一些语言或运行时支持非一等的特设返回类型:void ,在对象语言中无法构造其值。这种特性引起一些实用上的困难而被考虑改进,替换成一等类型,如:

  即便被改进,这仍只是单元类型,而不够清晰地反映未指定的意图。

  未指定值蕴含的不确定性在对象语言中难以建模,因此直接通过语言规则约定直至作为内建的支持特性,能使语言的设计更简单

  未指定值的未指定性在接口意义上仍然不是一等实体,因为当前不提供如 inert? 这样的谓词。即便提供 inert? 也仅仅是判断值是否为 #inert ,而不是任意的未指定值。

  相对 inert? ,这类谓词可能是有用的,例如元语言可能需要判断未指定性质以简化其派生实现。但这类需求和实现细节相关,且当前缺乏实例显示在对象语言中无条件提供的必要性。更重要的是,若要求提供这种谓词,限制派生实现在未指定值的类型上维持开放类型设计。

  未指定值作为内建特性时,用户也无法直接提供这类谓词的派生(而不符合统一性)。在需要这类谓词时,派生语言设计可能视具体实现需要满足的条件补充。

操作符合性

  除非另行指定,以下关于操作实现的未指定行为

  • 运行时错误条件检查诊断的顺序。
  • 满足运行时错误条件时,按要求诊断之后的操作内部分配的资源的状态。
  • 变量绑定或其它可选地由派生实现定义的可调试实体的引入是否由本机实现的机制提供。
  • 是否存在不改变操作语义的附加的可被捕获的续延和这些续延的操作数。
  • 操作的实现使用的续延或者其它实体互操作和调试目的保留的名称。

原理

  以上未指定行为允许同一操作的接口约定和实现之间及不同实现之间允许存在引起调用操作时的可观察行为不同的次要差异。

实体实现约定

  在实现的意义上,库特性提供的实体的方式分为两类:

  • 调用本机 API ,提供本机实现
    • 直接调用本机 API 提供绑定和组合本机 API 实现功能的派生实现都是本机实现。
  • 通过组合既有对象语言提供的接口实现,即派生(derivation)

  因不预期通过派生实现,一些特性被设计为基本(primitive) 特性,总是通过直接调用本机 API 的本机实现提供。

  其它特性都是派生特性,可通过派生(derived) 实现提供:通过组合基本特性及其它已提供实现的派生特性实现。

  典型地,派生实现通常不依赖实现特定的互操作接口的本机实现,这类派生实现是非本机的(non-native) ;其它的派生是本机的。

  以下的派生操作应能以派生的方式提供。派生操作是否以派生的形式提供未指定。

注释

  派生特性的实现方式可类似 [RnRK] 。

  非本机的派生实现通常以求值特定的对象语言源代码引入。

  非本机的实现最终依赖本机实现。

预定义对象

  除操作外,实现可定义特定名称的对象以变量绑定的形式在库中初始化,直接提供具有预设目的的可编程特性。

NPLA1 根环境特性

  本章指定在根环境提供的 NPLA1 标准库特性,即根环境特性

  根环境基本特性是单独的模块。

  基础派生特性起的各节提供其余要求 NPLA1 实现直接支持的各个模块的操作。

原理

  一些特性参照和类推扩展 [RnRK] 。

  同 [RnRK] 的设计,eval$vau是体现对象语言设计的光滑性的主要原语。

  因为保留引用值的不安全操作的支持,类推 eval%$vau%

  在本设计中,后者在逻辑意义上更基本,即便不指定为派生

注释

  一些特性可约定处理的值的宿主类型

不安全函数索引

  本节按不安全操作约定的分类对提供根环境中的不安全操作的函数进行归类。

  在函数值中保留引用值的不安全操作已被命名归纳和函数分类枚举,此处从略。

  不安全操作中,在参数以外直接引入间接值的操作仅有以下的在函数值中保留引用值的不安全操作:

  附加调用安全包括在函数值中保留引用值的不安全操作的调用。

  当前,这种操作包括 assign!

  隐藏环境排除可修改对象的引用,通过冻结环境保证而提供静态的证明。

在函数值中保留环境引用的函数

  在函数值中保留环境引用的操作包括:

  • 基本操作
    • make-environment
    • copy-environment
    • lock-environment
  • 派生操作
    • lock-current-environment
    • derive-current-environment
    • make-standard-environment
    • derive-environment
    • $provide/let!
    • $provide!

在函数值中保留其它间接值的函数

  在函数值中保留其它间接值的操作包括:

在环境中保留环境引用的函数

  在环境中保留环境引用的操作包括派生操作

  • $provide/let!
  • $provide!

无效化被绑定对象的函数

  当前不提供无效化被绑定对象的操作

  这可包含直接移除变量绑定的操作。

副作用可能引入循环引用的函数

  副作用可能引入循环引用的操作包括可能自赋值而引入循环引用值的操作:

  • assign@!
  • assign%!

可能破坏环境稳定性的函数

  可能破坏环境稳定性的操作包括下列两类:

函数分类

  本节对函数按名称和其它不同性质进行分类。

  在 NPLA1 参照实现环境提供的函数具体详见根环境基本特性和基础派生特性。

  除非另行指定,本节约定的函数属于 NPLA1 参照实现环境。

  本节约定的函数提供的部分操作属于转发

注释

  转发参数或返回值的实现中可使用 forward!

可提供以引用标记字符结尾变体的函数

  本节中的可提供以引用标记字符结尾变体的操作的以下分类不相交,但部分分类中函数名不带有引用标记字符结尾的操作可能和不提供结尾引用标记字符对应变体的函数中的操作相交。

  除非另行指定,符合以下分类的操作:

注释

  函数值约定已指定函数值默认不保留引用值;这没有涵盖 <body> 的求值结果。

可能使结果包含引用值的容器构造器函数

  可能使结果包含引用值的容器构造器包括:

可能使结果包含引用值的容器元素访问器函数

  可能使结果包含引用值的容器元素访问器包括基本派生特性中的以下操作:

  • first
  • first@
  • first%
  • first&
  • rest%
  • rest&
  • restv

  注意 restvrest% 总是构造列表,并不直接返回子对象的引用(另见引用值构造函数);其它访问器若带有引用标记字符,可直接返回引用值。

  此外,标准库中的函数值转发操作中部分函数也符合容器元素访问器的要求,但当前不提供带有后缀标记字符的变体。这些函数包括:

可能使对象中包含引用值的修改函数

  可能使对象中包含引用值的修改操作包括:

可能间接保留引用值的函数

  可能间接保留引用值的操作包括以求值 <body> 作为尾上下文的操作:

  • 结果是合并子或用于在环境中绑定合并子的构造器操作。
  • 核心库函数中的绑定操作。
  • 在尾上下文中求值 <expression> 参数视为的 <object> 的函数,包括 eval@evaleval%

  参见环境基本函数核心库

注释

  以上操作中的求值符合词法闭包规则。

不提供结尾引用标记字符对应变体的函数

  本节列举不提供结尾引用标记字符对应名称的操作

注释

  派生实现中通常使用 forward!进行转发实现上述保证。

可直接以引用值作为结果的函数

  可直接以引用值作为结果的操作包括:

不以引用值作为结果的函数

  不以引用值作为结果的操作的返回值总是按值传递的操作涉及的容器包括列表、其它的封装类型对象,包括:

直接参数转发函数

  直接参数转发操作包括:

  核心库函数中的绑定操作的非 <environment> 的形式参数支持转发。

  参数形式上被转发但操作的语义并非总是转发到其它操作的操作不使用本节的名称约定,如以下仅有第二参数支持转发的操作是提供结尾引用标记字符对应名称的函数,有对象基本函数中的:

  • assign%!
  • assign@!

注释

  map1 同时是不以引用值作为结果的操作

函数值转发函数

  NPLA1 参照实现环境的函数值转发操作包括以下访问对象或被引用对象自身或子对象的函数:

  基本操作的不具有名称的相关操作中参数和函数值原生支持的转发操作包括:

  使用 make-encapsulation-type返回的访问器合并子。

可能间接保留引用值的函数

  可能间接保留引用值的操作包括:

引用折叠相关函数

  本节列举引用折叠相关操作。

  只有函数名以 @ 结尾的函数可能引入未折叠的引用值,包括:

  此外,在参数保留引用值的修改操作可能使现有的引用值成为未折叠的引用值,包括:

  函数 uncollapsed?区分未折叠的引用值。

  蕴含左值到右值转换的函数可以消除引用值,如:

  其中,collapse 依赖 uncollapsed? 而针对未折叠的引用值消除引用值,实现引用折叠。

  其它一些函数可能在非本机实现中依赖未折叠引用但不在接口中体现,如 rulist

引用值构造函数

  当前构造子对象引用的操作有:

非引用值构造器函数

  一些函数构造非引用值,包括:

  • 核心库转换函数:
    • $bindings/p->environment
    • $bindings->environment
    • symbols->imports
  • 续延库转换函数:
    • continuation->applicative

  其中,明确转移而不是复制被转换的右值的函数有:

  • continuation->applicative

根环境基本特性

  NPLA1 通过预定义对象的形式提供可选的模块

  根环境基本特性是除了这些模块的以变量绑定形式提供的不要求可派生实现的特性。

  根环境基本特性的被绑定对象包括基础环境提供的预定义对象和在基础上下文的根环境中初始化的基础基本操作(grounded primitive operations) 的实现。

  派生实现可以通过提供不公开不安全操作的根环境,但不符合此处的规格要求

  若派生实现不提供在函数值中保留其它间接值的操作,可以简化部分对象基本函数中与之关联的操作的实现。

  当前在根环境中的直接提供绑定的特性不依赖 <number>

原理

  和 [RnRK] 不同,为简化设计,不提供可选(optional) 的合并子。

  可选功能不应被必要功能依赖。

  根环境的基本特性为组合其它实用特性提供,而数值在设计中不是必要的功能特性。

  部分其它原理参见根环境对象定义。关于引用值的处理另见函数分类

注释

  部分可选的 Kernel 合并子被直接提供。

  和 [RnRK] 不同,一些函数显式地操作引用值,包括未折叠的引用值

  和 [RnRK] 不同,求值算法不直接处理对象的引用值。

  为简化实现,部分提供 % 等后缀的函数不被派生。

  因为设计原因,不提供以下 Kernel 合并子对应的操作:

  • copy-es-immutable
  • inert?
  • ignore?

  考虑(可变对象的)一等引用和绑定构造绑定引用的的平摊复杂度,不提供需要同时转换不同层次子项的 copy-es-immutable 操作。

  其它没有包含在以下节中的 Kernel 合并子对应的操作可能会在之后的版本中被支持。

根环境对象定义

  除为提供根环境特性的模块以外,当前根环境不定义对象。

  以下各节引入的变量都表示操作。

等价谓词基本函数

模块约定:

  本节的操作不修改参数对象。

  本节的操作的结果是 <boolean> 类型的纯右值。

  用户定义的类型提供的等价谓词应满足和 NPLA1 提供的等价谓词的语义一致的等价关系,否则若谓词被求值,行为未指定。

  一些具有项节点作为表示的对象的子对象具有递归相等性,仅当子对象符合以下递归相等关系:

  • 对不表示有序对的节点,同 eqv?
  • 否则,同每个元素对应 eqv? 对应满足 eqv?

  判断子对象递归相等性的对象相等时,其续延未指定。

  若右值之间 eqv? 比较结果是 #teq? 比较结果未指定。

操作:

eq? <object1> <object2>

  判断参数同一

  当且仅当两个参数是指定同一对象时,比较结果是 #t

  eq? 的复杂度是 O(1)

eql? <object1> <object2>

  判断表示参数的项的值数据成员相等。

  忽略表示参数的项的值数据成员以外的子对象:若参数是列表,则视为空列表;若参数是有序对,则视为仅具有最后一个元素。

  若参数是引用值,则被比较的项是表示它的被引用对象的项。

  当且仅当被比较的项的值数据成员相等时,比较结果是 #t

  值数据成员相等蕴含参数的动态类型相同

eqr? <object1> <object2>

  判断表示参数的项的数据成员同一。

  当且仅当表示被比较的项的值数据成员指定宿主语言中的同一对象(即引用相等)时,比较结果是 #t

eqv? <object1> <object2>

  判断非枝节点表示的值相等。

  若参数是引用值,则被比较的值是它的被引用对象。

  根据项的内部表示:

  • 当表示值的项都是枝节点时,同 eq?
  • 否则,若这两个参数的类型不同,则结果是 #f
  • 否则,若这两个参数的 eql? 比较结果是 #t ,则结果是 #t

  若两个参数的 eqv? 比较结果是 #f ,则这两个参数以 eq? 比较结果总是 #f

  除非互操作(参见以下描述)或派生实现另行指定,不等价的函数的 eqv? 比较结果是 #f

  除以上规则确定的结果外,eqv? 对合并子或列表的比较结果未指定。

  在互操作的意义上,当前 eqv? 定义的合并子的相等性由宿主类型== 或不影响可观察行为的其它宿主环境提供的 == 操作通过和 eql? 比较相同的方式确定。

  除非另行指定,具有本文档引入的类型且不涉及互操作意义上用户自定义值的比较的操作数使用以上 eq? 以外的谓词比较的求值应保证能终止。

原理

  除任何其它类型都可作为 <object> 的子类型开放类型映射类型系统通常要求避免依赖 <object> 上的其它的良序和良基的理论,以避免对现有类型系统的扩展时需要修改已有的类型的相关操作。

  不需要依赖序的等价谓词可为名义类型提供直接的支持。

  NPLA1 提供默认相等为抽象相等,对任意的值适用。

  NPLA1 还提供对一等对象保证结果有意义的引用相等操作。非一等实体的引用相等关系未指定。

  当前 NPLA1 不支持 [EGAL] ,因为 [EGAL] 要求存在分辨任意对象的值是否可被修改的元数据。

  因为对应等价的不变性关系不具有唯一性,且可能允许不唯一的方式引起副作用(如缓存),和 [RnRK] 不同,不以基本操作提供 equal? 对任意对象提供一般的相等操作。

  未指定 eq? 的比较结果可允许实现复用存储右值的驻留对象。

  eql? 实际比较宿主值的相等。允许 eqv?eql? 的不同可允许一对多的类型映射下比较对象语言的值的相等。(而多对一的类型映射 eql?eqv? 可一致地比较。)

  但是,当前实现中,大多数一对多映射的类型(如环境)都没有引起使 eql?eqv? 不同的比较实现,因为不同宿主值类型的对象具有足够显著的差异,在大多数上下文不通过一些具有不可忽略开销的转换机制(如锁定环境弱引用转换为环境强引用),无法直接相互替换而保证行为差异可被忽略,因此逻辑上不适合定义为相等的。

  而基于性能等理由,等其它一对多映射的类型(特别是可能基于宿主类型的值的子集的,如 NPLA 数值类型中的 <integer> )的值的比较也没有特别的处理,而引起 eqv?eql? 的不同。

  这些类型可能需要其它针对特定类型的等价谓词(如 =?)进行相等性的比较。

  类似 [RnRS] ,不同类型决定 eqv? 的结果是 #f ,但此处类型相同的含义不通过类型分区定义。

  类似 [RnRS] ,行为不等价的函数的 eqv? 结果原则上应为 #f ,但这种等价性一般不可证明而无法保证,特别在关于语言实现以外的调用上。

  为支持互操作使用本机实现及避免限制合并子的子类型的开放性,允许这些实现另行指定规则,假定引起程序可观察行为差异的函数调用调用名义等价。

注释

  通常,等价谓词比较的求值应保证能终止且对非列表项和 n 个子项的列表分别具有 O(1)O(n) 平摊复杂度。这是依赖数据结构实现的细节;语言不需要约束这个性质。

控制基本函数

$if <test> <consequent> <alternative>

  条件分支,按条件成立与否返回 <consequent><alternative> 之一,可能是引用值。

$if <test> <consequent>

  省略第三操作数的条件分支,条件成立时返回 <consequent>

  和 [RnRK] 不同而和 [RnRS] 类似,如 <test> 的求值结果非 #f 即选择 <consequent> ,且支持省略第三参数。

  若省略 <alternative><test> 求值为 #f ,则结果未指定

注释

  对 <test> 的处理的主要原理和 Kernel 的 $and? 不要求尾上下文的表达式求值检查类型一致。

  若需要检查类型避免误用,可以派生提供其它函数;相反的派生无法利用更简单的实现。

原理

  和 [R7RS] 类似,但和 [Racket] 及 [RnRK] 不同,省略 <alternative> 被支持。

  和 [R7RK] 不同,不使用 #inert ,参见关于参照实现接口描述的原理。

  和 [RnRK] 中的相关讨论结论不同,是否省略 <alternative> 的形式应是一致的。这是因为:

  • NPLA1 不假设作为基本控制操作的 $if 的作用(仅要 <consequent><alternative> 求值的结果,或仅为了副作用)。
  • $if 不假设用户对 <consequent><alternative> 顺序选择性偏好,以避免限制用户选择否定谓词简化 <test> ,从而支持变化的自由

  此外,NPLA1 使用显式的 <expression-sequence>(而不是 <consequent><alternative> )语法表示顺序求值,这不适合基本的控制操作子:

  若分离二操作数和三操作数其它形式,则二操作数可以使用 <expression-sequence> ,即 $when

  但依赖 <expression-sequence>$when 不应是比具有 <consequent> 的二操作数形式更基本的操作。

  因此,仍然需要有 $when 以外的省略第三参数的基本控制操作子。基于统一性,对应函数名仍然为 $if 。(尽管使用了相同的原则,这和 Kernel 的结论恰好相反。)

  与此类似,和 [Racket] 的理由不同,不因为 $when 提供只强调副作用的操作而取消 $if<alternative>

  NPLA1 不会如 [Racket] 一样在此避免遗漏 <alternative> 导致的非预期结果。这并不违反适用性,因为不使用 <alternative> 的结果非常显然,同时选择使用 $if 这样的基本控制操作而不是更特定派生控制操作或更高级的抽象已蕴含注意误用的必要性。

  一般地,NPLA1 不提供强调只存在副作用的操作。返回未指定(而不要求被使用)的求值结果的情形并不表示只有副作用,因为副作用是否存在原则上依赖具体操作。这和 Kernel 的 #inert 以及 [Racket] 的 #<void> 值即便在实现上都一致,但含义不同。

  另见 $when 的说明。

对象基本函数

模块约定:

  因为真列表的限制,列表左值只能引用完整的列表的对象,而不支持部分列表。

  这影响 set-rest!set-rest%! 的第一个参数。

操作:

null? <object>

  判断操作数是否为空列表。

nullv? <object>

  判断操作数是否为空列表纯右值。

  同 null? ,但不支持引用值。

branch? <object>

  判断操作数是否具有枝节点表示。

branchv? <object>

  判断操作数是否为具有枝节点表示的纯右值。

  同 branch? ,但不支持引用值。

pair? <object>

  <pair>类型谓词

pairv? <object>

  判断操作数是否为有序对纯右值。

  同 pair? ,但不支持引用值。

symbol? <object>

  <symbol> 的类型谓词。

reference? <object>

  判断操作数是否为引用值。

unique? <object>

  判断操作数是否为唯一引用

modifiable? <object>

  判断操作数是否为可修改对象或可修改对象的引用值。

temporary? <object>

  判断操作数是否为临时对象或临时对象的引用值。

bound-lvalue? <object>

  判断操作数是否为被引用的被绑定对象左值。

  绑定临时对象的引用类型的参数不被视为左值引用。

  配合[$resolve-identifier% 引用标记绑定的变量,可确定实际参数是否为左值;参见 $lvalue-identifier?](#基本派生特性) 。

  使用 bound-lvalue?& 引用标记字符绑定的变量,可确定实际参数是否为引用。

uncollapsed? <object>

  判断操作数是否为未折叠的引用值。

deshare <object>

  取指定对象取消共享的值。

  同 idv ,但显式转换操作数中具有共享持有者的值数据成员为不共享的值,且不转移宿主值

原理

  因为提供在函数值中保留其它间接值的操作,这个区别是必要的。否则,使用 idv 替代应不影响可观察行为

as-const <object>

  取指定对象的不可修改的引用。

  同 id ,但当参数是引用值时,结果是和参数引用相同对象的不可修改的引用值。

expire <object>

  取指定对象的消亡值

  同 id ,但当参数是引用值时,结果是和参数引用相同对象的唯一引用。

  可用于显式地指定之后被转移的对象,而不需要直接转移参数。

  特别地,指定列表的引用值被转移时,不需要立即转移列表的每个元素,而允许之后通过绑定构造等方式选择转移的子对象

  可能包含立即转移的操作如 forward!

原理

  这不直接转移对象,而不是修改操作函数名不以 ! 结尾

注释

  这个函数类似宿主语言标准库中作用在对象类型实际参数的 std::move ,可能减少没有经过复制消除复制或转移而改变使用这个函数的结果对象的副作用。

move! <object>

  转移对象。

  若参数是不可修改的左值,则以复制代替转移;否则,直接转移表示参数对象的项

  结果是不经返回值转换的项。

注释 另见转移的注意事项

transfer! <object>

  转移对象。

  同 move! ,但使用对象的转移,而不是项的转移,避免宿主对象转移消除而允许调用宿主对象的转移构造函数

注释 参数被转移后,和返回值转换引入实质化临时对象时可能具有的转移的效果(仅在互操作时可见)可能相同。

ref& <object>

  取引用。

  对引用值同 id ;对具有共享持有者的值数据成员的对象也视为左值。通过后者构造的引用值不被检查。

  取得的引用值是不安全引用值

原理

  因为提供在函数值中保留其它间接值的操作,对共享持有者的值数据成员的对象使用不同的处理。否则,对引用值参数的情形,使用 id 替代应不影响可观察行为。

assign@! <reference> <object>

  赋值<reference>被引用对象为指定对象的值,且 <object>蕴含左值到右值转换不被折叠

  赋值被引用对象前首先检查 <reference> 是可修改的左值。

  赋值对象直接修改被引用的对象,但不无效化参数指定的引用。

  支持修改 <reference> 指定的子对象引用的被引用对象。

注释 被赋值替换的子对象的引用可被无效化。Scheme 的 set![SRFI-17] 提供具有类似作用的支持,但第一操作数限于 set! 且为特定的过程调用;Kernel 没有类似的操作。另见赋值的注意事项

有序对基本函数

cons <object1> <object2>

  构造参数指定的两个元素构成的有序对。

  结果是 <pair> 类型的值。

注释

  不保留 <object2> 的引用值,但这不涉及 <object2> 是有序对或有序对的引用值时其中可能具有的元素。

  若 <object2> 中存在元素,直接被作为结果的元素,不经过返回值转换。

cons% <object1> <object2>

  构造参数指定的两个元素构成的有序对,保留引用值。

  同 cons ,但参数是引用值时,直接以其值作为元素的值,而不以其被引用对象的值创建有序对。

注释 这允许被构造的结果中存在和参数相等的引用值,而非其被引用对象的副本。

set-rest! <pair> <object>

  修改列表的第一个以外的元素。

注释 和 [RnRK] 的 set-cdr! 类似,但检查列表是左值,且不保留被添加元素中的引用值。

set-rest%! <pair> <object>

  同 set-rest! ,但保留引用值。

注释 和 [RnRK] 的 set-cdr! 类似,但检查列表是左值。

注释

  和 [RnRK] 不同,NPL 不支持列表中存在环。

  不使用相同的对象左值的 cons% 调用或修改操作导致循环引用,用户应自行避免未定义行为

  结果具有的属性不被影响。

符号基本函数

desigil <symbol>

  移除符号中的引用标记字符 &%

  判断符号非空且以 &% 起始,结果是移除起始字符的参数。否则,结果是参数。

  不处理引用标记字符 @

环境基本函数

模块约定:

  为避免引入过于容易引入循环引用,仅通过个别操作引入环境强引用

  • make-environment
  • lock-environment

操作:

eval@ <object> <environment>

  在参数指定的环境中求值,结果作为函数值。

  <object> 在求值前被视为 <expression>

  <object>蕴含左值到右值转换

eval-string% <string> <environment>

  在参数指定的环境中求值作为外部表示的字符串。

注释

  类似 klisp 的同名操作,但保留引用值。

  不提供类似 eval@eval-string@ ,因为不论参数的值类别,求值总是依赖的参数字符串的值。

eval-unit <string>

  规约字符串表示的翻译单元以求值。

  直接使用当前环境,但其中求值不在尾上下文,也不改变当前续延。

注释

  和 eval-string% 类似,但不支持指定环境,求值不在尾上下文,也不改变当前续延。

  使用的实现环境可以是 REPL 环境。

bound? <string>

  判断指定字符串对应的符号是否被绑定。

$resolve-identifier <symbol>

  解析当前环境中的标识符。

  结果是解析结果中的项。

注释 参数不按成员访问规则确定值类别,也不按解析名称表达式的规则确保结果总是左值,可保留消亡值

$move-resolved! <symbol>

  转移解析标识符的对象。

  和 $resolve-identifier 类似,但直接取被绑定对象并尝试从环境中转移。

  若环境被冻结,则复制被绑定对象;否则,直接转移对象的项

  一般应仅用于被绑定的对象不需要再被使用时。

() copy-environment

  递归复制当前环境。当前忽略特定的父环境

  结果是新创建的环境的强引用。

警告 这个函数仅用于测试时示例构造环境,通常不应被用户程序使用,且可能在未来移除。未确定环境宿主值时可引起未定义行为。

freeze-environment! <environment>

  冻结环境。

  这个操作处理操作数指定的一等环境。

注释隐藏环境初始化时的相同操作参见冻结操作

lock-environment <environment>

  锁定环境:使用环境弱引用创建环境强引用

  检查参数是环境弱引用,若失败则引起类型错误。结果是对应的环境强引用。

  强引用可能引起环境之间的不被检查的循环引用,用户应自行避免未定义行为

make-environment <environment>...

  创建以参数为父环境的环境。

  和 [RnRK] 不同,除对象类型外,没有对列表和绑定的附加检查。

  结果是新创建的环境,是环境强引用,具有宿主值类型 shared_ptr<Environment>

weaken-environment <environment>

  使用环境强引用创建环境弱引用。

  检查参数是环境强引用,若失败则引起类型错误。结果是对应的环境弱引用。

原理 因为 NPLA1 需要精确控制所有权而不依赖 GC,这可用于派生实现某些操作(如 $sequence 必要的)。

$def! <definiend> <expressions>

  定义:修改当前环境中的绑定。满足绑定构造的约定。

  $def!$defrec! 在求值 <expressions> 后,进行类型检查,确保环境没有被冻结后添加绑定。

  对 <definiend> 中已存在的标识符的绑定,保证直接替换对象的值,对象的引用不失效

注释

  类似 [RnRK] ,对在 [<body>] 中某些未被直接求值的子表达式(如 $lambda<body>),因为其中的求值依赖 $def! 表达式求值后的环境,在之后仍可以实现递归。

  类似 [RnRK] 的 $define! ,但绑定构造的约定存在不同规则。

  求值 <body> 后进行类型检查和 [RnRK] 的 $define! 不同。

  由于递归调用依赖环境中的绑定,修改以上定义引入的绑定后可影响被递归函数的调用。

$defrec! <definiend> <expressions>

  递归定义:修改绑定,同 $def! ,但在绑定时针对 <definiend> 指定的操作数树中的绑定名称有附加的处理以支持直接递归。

  除和 $def! 相同过程的常规绑定(求值 <expressions> 和绑定符号)外,支持强递归绑定,其操作数树的附加处理分为两阶段;每个阶段深度优先遍历 <definiend> 指定的操作数树,对每个符号进行附加处理:

  • 在常规绑定前,每个遍历的待绑定符号在目标环境(被定义影响的环境)中预先进行绑定,保证指称一个对默认对象的弱引用,其中默认对象具有调用总是抛出异常的非真合并子的值;和这个弱引用的共享的强引用被临时另行保存。
  • 在常规绑定后,再次遍历操作数树,对每个实现支持的合并子的的值,替换之前在环境中保存的共享定义为默认对象的共享强引用,最后释放先前临时保存的默认对象的强引用。

  调用默认对象时:

  常规绑定后转移未被 <expressions> 求值影响的绑定中的默认对象的所有权到环境中,但不影响绑定目标在对象语言中指称的值。

  在环境中未被 <expressions> 求值替换的绑定,在 $defrec! 求值仍指称默认对象(而不会是持有真合并子的值),若被作为合并子调用,则显示存在循环递归调用。

  和 $def! 不同,求值 $defrec!<expressions> 前保证 <defindiend> 中的名称已存在默认定义,求值 <expressions> 可访问对应的名称而不因名称解析失败而引起错误

注释

  和 $def! 不同,即使不计绑定修改环境的副作用,常规绑定后的操作使 <expressions> 不在尾上下文求值。

  这允许递归定义的名称在绑定完成前指称对象。例如,派生标准环境,当前环境中未绑定变量 ab 时:

  • 求值表达式 $def! (a b) list b ($lambda () 1) 因为被求值的 b 未被绑定而引起错误。
  • 求值表达式 $defrec! (a b) list b ($lambda () 1) 不需要 ab 已被绑定(即便 b 并不在 $lambda<body> 中),求值后 a 为默认对象。
  • 求值表达式 $defrec! (b &a) list ($lambda () 1) b 绑定要求同上,但求值后 a 可能为默认对象(操作数树中的同级叶节点被未指定的绑定顺序影响)。

  这也允许在 $vau/e等表达式的 <environment> 指定的静态环境使 <body> 不能访问目标环境时,直接定义递归函数。

  递归定义的对象中的值数据成员可能具有共享的持有者。若为合并子,直接调用会利用替换的值重新访问所在的环境。复制和转移这样的值不会改变被访问的环境。若访问的环境失效,则抛出异常,或无限递归调用自身。

  特定情形使用 deshare可去除共享和避免以上可能非预期的行为。

  另见]环境](#npla1-环境)。

合并子基本函数

  和 [RnRS] 及 [RnRK] 不同,<body> 可以是多个项,而不再派生另外的变体支持顺序求值。

  引入合并子的操作子不求值 <body> ,后者在被调用时替换操作数以后被求值。这允许安全地使用 $def! 而不需要 $defrec! 进行递归绑定

  检查失败的错误是(可能依赖类型错误的)语法错误

$vau/e <parent> <formals> <eformal> <body>

  创建指定静态环境的 vau 抽象

  创建的对象是操作子

$vau/e% <parent> <formals> <eformal> <body>

  同 $vau/e ,但保留引用值。

wrap <combiner>

  包装合并子为应用子

  包装应用子可能符合包装数溢出的错误条件

wrap% <combiner>

  同 wrap ,但参数不蕴含左值到右值转换,在结果中保留引用值

unwrap <applicative>

  解包装应用子为底层合并子

  左值参数解包装的结果是合并子的子对象引用

原理

  指定 <parent> 作为静态环境可通过被绑定实体的所有权控制一等对象的生存期。同时,在没有 safe-for-space 保证时,仍可有效避免资源泄漏

注释

  和 [RnRK] 不同,因为支持保存环境的所有权,提供 $vau/e 作为比 $vau 更基本的操作。

  不考虑所有权时,eval$vau 可派生 $vau/e

  和 [RnRK] 不同,参数是右值时解包装的子对象被复制。由这些合并子创建的操作子当前仍不足以取代内置的一等操作子,因为不支持只能转移而不能复制的对象。传递这些对象作为操作数会引起构造失败的异常。

错误处理和检查基本函数

  以下函数提供错误处理的相关支持。

raise-error <string>

  引发表示错误的异常

raise-invalid-syntax-error <string>

  引发包含参数指定的字符串内容的语法错误

raise-type-error <string>

  引发包含参数指定的字符串内容的类型错误

check-list-reference <object>

  检查对象是否是列表引用:若检查通过转发参数作为结果,否则引发错误对象。

check-pair-reference <object>

  检查对象是否是有序对引用:若检查通过转发参数作为结果,否则引发错误对象。

封装基本函数

() make-encapsulation-type

  创建封装类型。

  和 [RnRK] 类似,结果是三个合并子组成的列表,其元素分别表示用于构造封装类型对象的封装(encapsulate) 构造器、判断封装类型的谓词和用于解封装(decapsulate) 的访问器:

  创建的封装类型支持判断相等(参见 eqv?),相等定义为被封装的对象的子对象递归相等性

注释

  和 [RnRK] 不同,使用构造器初始化封装的对象作为容器,具有作为其子对象的被封装的对象的所有权。

  需要注意保存被构造的封装对象。

  另见 Unique Types[SRFI-137]

  和 [RnRK] 及 [RnRS] 的各种实现(如这里提到的)不同,对相同类型的封装对象,eqv?equal? 基于被封装对象的子对象(及子对象被引用的对象)递归比较,即使用封装的对象的 equal? 定义 eqv? 结果。

  另见等价谓词的设计用例

基础派生特性

  根环境特性中,除根环境基本特性的剩余接口是派生特性。其中在基础环境中提供的特性是基础派生特性(grounded derived feature)

基本派生特性

  基本派生特性可使用派生实现。这可能蕴含使用根环境基本特性或已在基本派生特性中提供的特性中的部分非派生实现。

模块约定:

  引入合并子的操作子对 <body> 的约定同合并子基本函数

  因互相依赖,一些操作实现为派生操作时,不能用于直接派生特定一些其它操作。

  和 $vau/e$vau/e%以及 $lambda/e$lambda/e%不同,不指定静态环境的合并子构造器隐含总是使用环境弱引用形式的静态环境,以避免过于容易引入循环引用

  本节约定以下求值得到的操作数

  • <box>箱(box) :可包含引用的容器。

注释

  注意 $let 等函数的 <body> 形式和 [RnRK] 不同。

操作:

eval <object> <environment>

  同 eval@ ,但 <object> 蕴含左值到右值转换且不保留引用值。

注释

  若 <object> 为元素中有引用值的列表,元素不会被特殊处理,不蕴含左值到右值转换

  [RnRK] 中同名合并子的第一参数为 <expression> ,但这不是已求值的操作数的类型。

eval% <object> <environment>

  同 eval@ ,但 <object> 蕴含左值到右值转换。

注释eval ,但保留引用值。

eval-string <string> <environment>

  同 eval-string ,但不保留引用值。

() get-current-environment

  取当前环境:取当前环境的弱引用

注释

  派生需要非派生实现的 $vau/e

() lock-current-environment

  锁定当前环境:取当前环境的强引用

$vau <formals> <eformal> <body>

  创建 vau 抽象

  类似 $vau/e,但以当前环境代替额外的求值环境作为静态环境。

注释

  和 [RnRK] 不同,可通过 $vau/e和(非派生的)get-current-environment 派生,不是基本操作

$vau% <formals> <eformal> <body>

  同 $vau,但保留引用值。

$quote <expression>

  求值引用操作。结果是返回值转换后的未被求值的操作数。

  考虑通常引用操作对符号类型未被求值的左值操作数使用,保留引用值没有意义,因此不提供对应保留引用值的操作。

id <object>

  结果是不蕴含左值到右值转换的参数,在结果中保留引用值。

  其作用等价返回值转换,可能引起对象转移

idv <object>

  同 id ,但结果是返回值转换后的值。

注释 使用 idv 可指定在函数值中保留引用值的不安全操作 的个别操作数不再保留引用值。

list <object>...

  创建列表(类型为 <list> )对象。

  list底层合并子接受 <pair>(而不要求是列表)作为函数合并对象,结果是操作数的元素经蕴含左值到右值转换的值。

注释 除元素的转换,类似 [RnRK] 的 list

$lvalue-identifier? <symbol>

  解析当前环境中的标识符(同 $resolve-identifier)并判断是否为左值(同 bound-lvalue?)。

$expire-rvalue <symbol>

  解析当前环境中的标识符,判断是否为左值(同 $lvalue-identifier? ),取对应的转换操作:

  • 若判断为左值,则转换操作同 id
  • 否则,则转换操作同 expire

  结果是在根环境求值转换操作对应的函数名得到的引用值。

注释 结果相同即以 eq? 和对应的值比较结果为 #t

forward! <object>

  转发可能是引用的值

  转移可修改的右值操作数(包括消亡值和临时对象)。

  其中,需转移时,使用使用项的转移。这和对象的转移不同,不保证调用宿主环境的转移构造函数。

原理 和宿主语言不同,直接转移允许区分消亡值和纯右值,同等地作为一等对象(如作为列表的元素)。

注释

  被转发的值若是形式参数树中的变量,一般应以带有标记字符 & 的形式绑定;否则,转发的不是对应的实际参数,而可能是其按值绑定的副本。

  这个函数类似宿主语言以对象类型参数和推断的函数参数类型作为模板参数调用 std::forward ,但若需转移,直接转移而非如 expire返回指定结果是消亡值唯一引用

list% <object>...

  同 list ,但每个参数都不蕴含左值到右值转换,在结果中保留参数的引用值

  list% 的底层合并子接受 <pair> 作为操作数(而不要求是列表),结果是操作数。

注释 类似 [RnRK] 的 list

rlist <list>

  转换参数为引用列表元素的列表。

  若参数是左值,则结果是参数的元素的左值引用值构成的列表;否则,结果同 idv

$remote-eval <expression> <environment>

  在动态环境求值第二参数得到的环境中求值第一参数,结果作为函数值。

$remote-eval% <expression> <environment>

  同 $remote-eval ,但保留引用值。

$deflazy! <definiend> <expressions>

  修改绑定。

  同 $def! ,但不求值参数;在添加绑定前仍对冻结环境进行检查

$set! <environment> <definiend> <expressions>

  设置:修改指定环境的变量绑定。

  在当前环境求值 <environment><body> ,再以后者的求值结果修改前者的求值结果指定的环境中的绑定。绑定效果同使用 `$def!

  类似 [RnRK] 的 $set! ,但明确使用 <definiend> 而不是 <formals> 。注意 <body> 的形式不同。允许的递归操作参见 $def!

  和 [RnRK] 不同而和 NPLA1 的 $def! 等类似,在修改绑定前对冻结环境进行检查。

$setrec! <environment> <definiend> <expressions>

  递归设置:修改指定环境的绑定,绑定效果同使用 $defrec!

  同 $set! ,但允许不同的递归操作。

注释 参见 $defrec!

$wvau <formals> <eformal> <body>

  创建包装的 vau 抽象

  同 $vau ,但创建的是调用时对操作数的元素求值一次的应用子

  参数的作用同 $vau 的对应参数。

$wvau% <formals> <eformal> <body>

  同 $wvau ,但允许函数体求值返回引用值。

$wvau/e <parent> <formals> <eformal> <body>

  同 $wvau ,但支持显式指定求值环境参数作为静态环境。

$wvau/e% <parent> <formals> <eformal> <body>

  同 $wvau/e ,但保留引用值。

$lambda <formals> <body>

  创建 λ 抽象

  同 $vau ,但创建的是调用时对操作数的元素求值一次的应用子,且忽略动态环境。

注释 可通过 vau 抽象或 $lambda/e 和(非派生的)get-current-environment 派生。

  除未提供的 <eformal> ,参数的作用同 $vau 的对应参数。

$lambda% <formals> <body>

  同 $lambda ,但允许函数体求值返回引用值。

$lambda/e <parent> <formals> <body>

  同 $lambda ,但支持显式指定求值环境参数作为静态环境。

$lambda/e% <parent> <formals> <body>

  同 $lambda/e ,但保留引用值。

list? <object>

  <list>类型谓词

  若参数是列表或非真列表,时间复杂度不大于 O(n) ,其中 n 是其中的元素数。

注释 本机实现可实现 O(1) 时间复杂度。

list* <object>+

  在列表前附加元素创建对象或有序对。

  类似 cons ,但支持一个和多个参数。

  对一个参数的情形结果同参数,否则结果同右结合嵌套调用参数的数量减 1 次的 cons

注释 一个参数的情形结果经返回值转换。因为不需要如 Kernel 支持派生 $vau ,可直接使用 apply 派生;但因底层合并子还需检查列表,所以可使用 apply-list 。而和 apply-list 内部共用实现的派生可能更高效。

list*% <object>+

  同 list* ,但创建有序对类似 cons% ,且元素保留引用值。

注释 一个参数的情形结果不经返回值转换。

apply <applicative> <object> <environment>?

  转发第一参数指定的应用子和第二参数指定的参数构成函数合并,在环境中应用。其中,环境是:

  • 新环境,若第二参数不存在。
  • 否则,第二参数指定的环境。

注释 检查 <environment> 和 [RnRK] 的参考派生不同。

  apply 的函数值保留引用值。

apply-list <applicative> <list> <environment>?

  转发第一参数指定的应用子和第二参数指定的参数列表构成函数合并,在环境中应用。其中,环境同 apply 中的方式指定。

  同 apply ,但首先检查第二参数的类型,若失败则引发错误

$sequence <expression-sequence>

  顺序求值。

  操作数非空时结果是最后的参数,可能是引用值。

注释 类似 [RnRK] 的同名操作子。

  求值每个 <object> 的副作用包括其中临时对象的销毁都被顺序限制。

注释 这类似宿主语言的语句而不是保证子表达式中的临时对象的生存期延迟到完全表达式求值结束的逗号表达式。这也允许实现和 [RnRK] 同名操作类似的 PTC 要求。

collapse <object>

  折叠可能是引用的值。

forward <object>

  转发可能是引用的非临时对象的值。

  同 forward! ,但对可修改的临时对象操作数,使用复制代替转移。

注释

  按在所在的环境中解析的操作数的类型可选地进行返回值转换作为结果,其作用 ididv 之一。

  转移(而不是复制)可修改的右值操作数。

  若右值操作数不可修改,复制不可复制构造的宿主对象会失败。

assign%! <reference> <object>

  同 assign@!,但 <object> 是引用值时赋值的源操作数是 <object> 折叠后的值。

assign! <reference> <object>

  同 assign%! ,但 <object> 蕴含左值到右值转换。

注释

  因为左值到右值转换,即便 <object> 指定的值来自 <reference> ,也可赋值而不因此引起未定义行为。

  另见赋值的注意事项

$defv! <variable> <formals> <eformal> <body>

  绑定 vau 抽象,等价 $def! <variable> $vau <formals> <eformal> <body>

$defv%! <variable> <formals> <eformal> <body>

  绑定 vau 抽象,等价 $def! <variable> $vau% <formals> <eformal> <body>

$defv/e! <variable> <parent> <formals> <eformal> <body>

  绑定指定静态环境的 vau 抽象,等价 $def! <variable> $vau/e <parent> <formals> <eformal> <body>

$defv/e%! <variable> <parent> <formals> <eformal> <body>

  绑定指定静态环境的 vau 抽象,等价 $def! <variable> $vau/e% <parent> <formals> <eformal> <body>

$defw! <variable> <formals> <eformal> <body>

  绑定包装的 vau 抽象,等价 $def! <variable> $wvau <formals> <eformal> <body>

$defw%! <variable> <formals> <eformal> <body>

  绑定包装的 vau 抽象,等价 $def! <variable> $wvau% <formals> <eformal> <body>

$defw/e! <variable> <parent> <formals> <eformal> <body>

  绑定包装的指定静态环境的 vau 抽象,等价 $def! <variable> $wvau/e <parent> <formals> <eformal> <body>

$defw/e%! <variable> <parent> <formals> <eformal> <body>

  绑定包装的指定静态环境的 vau 抽象,等价 $def! <variable> $wvau/e% <parent> <formals> <eformal> <body>

$defl! <variable> <formals> <body>

  绑定 λ 抽象,等价 $def! <variable> $lambda <formals> <body>

$defl%! <variable> <formals> <body>

  绑定 λ 抽象,等价 $def! <variable> $lambda% <formals> <body>

$defl/e! <variable> <parent> <formals> <body>

  绑定指定静态环境的 λ 抽象,等价 $def! <variable> $lambda/e <parent> <formals> <body>

$defl/e%! <variable> <parent> <formals> <body>

  绑定指定静态环境的 λ 抽象,等价 $def! <variable> $lambda/e% <parent> <formals> <body>

forward-first% <applicative> <list>

  取列表的第一元素并转发给指定的应用子。

  设参数列表 (&appv (&x .)) ,作用同求值:

(forward! appv) (list% ($move-resolved! x))

  其中,调用 appv底层合并子的当前环境同调用 forward-first% 的动态环境。

first <pair>

  取有序对的第一个元素的值。

  当 <list> 是左值时结果是折叠的引用值,否则结果是返回值转换后的值。

注释 类似传统 Lisp 及 [RnRK] 的 car 。命名和 [SRFI-1] 及 Clojure 等现代变体一致。

first@ <pair>

  同 first ,但结果不被折叠而总是未折叠的引用值

  首先同调用 check-pair-reference的方式检查参数是有序对引用,对右值引发错误。

first% <pair>

  同 first ,但结果总是转发的值。

  转发的值是经过折叠但没有返回值转换的列表元素的值,无论参数是否为引用值。

first& <pair>

  同 first@ ,但结果总是折叠的引用值。

  若元素是引用值,在结果中保留元素中的唯一引用属性。

原理 详见 NPLA1 引用值使用约定

firstv <pair>

  同 first ,但结果总是返回值转换后的值。

rest% <pair>

  取有序对的第一个元素以外的元素值经过转发的值构成的有序对。

  若结果构成子有序对,可能引入子有序对引用

rest& <pair>

  取有序对的第一个元素以外的元素值的引用值构成的有序对的子对象引用

  首先同调用 check-pair-reference 的方式检查参数是有序对引用,对右值引发错误。

  若结果构成子有序对,引入子有序对引用。

restv <pair>

  取有序对的第一个元素以外的元素值构成的有序对。

  结果是有序对对象。

set-first! <pair> <object>

  修改有序对的第一个元素。

  和 [RnRK] 的 set-car! 类似,但可派生,检查列表是左值,且不保留引用值。

set-first@! <pair> <object>

  同 set-first%! ,但保留未折叠的引用值

set-first%! <pair> <object>

  同 set-first! ,但保留引用值。

  不保证检查修改操作导致循环引用。

注释 用户应自行避免未定义行为

equal? <object1> <object2>

  判断一般相等。

  类似 eqv?,但同时支持表示中具有子项作为子对象的对象。

  判断的相等定义为子对象的递归相等性

注释

  类似 [RnRK] 和 [RnRS] 的同名的二元谓词,但在此保证可通过 eqv? 直接构造。:

  因为列表的性质,不需要支持循环引用,可以直接派生。后者被视为基本的抽象而非实现细节。

  和 make-encapsulation-type创建的对象的相等比较不同,本机实现和派生实现都依赖当前上下文,允许捕获续延,尽管续延是未指定的。

check-environment <object>

  检查环境

  若参数是 <environment> 则检查通过,结果是转发的参数;否则,引发错误对象

注释 当前实现中其它要求 <enviornment> 参数的操作中类型检查失败和 check-environment 失败的行为一致。

check-parent <object>

  检查作为环境的父环境的对象。

  若参数是可以作为合并子环境的 <parent> 则检查通过,结果是转发的参数;否则,引发错误对象。

  检查环境通过的条件同创建合并子时的检查

  引发错误对象的作用同创建合并子时环境检查失败引起错误或引发其依赖的错误对象(后者保证不是语法错误)。

$cond <clauses>

  条件选择。

  类似 [RnRK] 的同名操作子,但 <test> 的判断条件和 <body> 形式不同。

$when <test> <expression-sequence>

  条件成立时顺序求值。

  类似 klisp 的同名操作子,但若 <expression-sequence> 被求值,结果是 <expression-sequence> 的最后一个 <expression> 的求值结果,而不是 #inert

注释 这类似 [Racket] 的 when 而和 [R7RS] 的 when 或 klisp 的同名操作不同,因为 $when 被作为和 $sequence 类似的操作处理(对应 Racket 中的 whenbegin 并列)。

$unless <test> <expression-sequence>

  条件不成立时顺序求值。

  类似 klisp 的同名操作子,但若 <expression-sequence> 被求值,结果和设计原理同上。

not? <object>

  逻辑非。

  被求值的参数同 <test> ,进行左值到右值转换。

  若参数非 #f 时结果是 #f ,否则结果是 #t

注释 和 [RnRK] 不同而和 Scheme 类似,视所有非 #f 的值为 #t

$and <test>...

  逻辑与。

  顺序短路求值。操作数为空时结果是 #t ;参数求值存在 #f 时结果是 #f ;否则结果是最后一个参数的值。

  结果保留引用值。

原理

  [RnRK] 的 $and?$or? 的实现使用 applywrap ,这没有必要:

  • 按 [RnRK] 的 apply 的原理,这种对任意合并子适用的操作 combine 容易实现且干扰意图的理解。
  • 对 NPLA1 的 apply ,还保证在第一参数是空列表时,为适应合求值函数合并(前缀 (),被继续求值的对象仍是有序对(即函数合并,而不是单独的函数),但这在 NPLA1 的 $and$or 中不必要,因为对应的情形(即 $and$or 没有参数时)应被单独处理。
  • 对派生实现,apply 通常比 eval% 更低效(因为包含了无用的检查和更多的非本机实现)。

注释

  和 [RnRK] 的 $and? 不同,不检查类型,也不保证结果类型是 <boolean> ,所以命名不以 ? 结尾

  和 [RnRK] 中的原理描述的不同,这同时允许直接的满足 PTC 要求的派生实现。

$or <test>...

  逻辑或。

  顺序短路求值。操作数为空时结果是 #f ,参数求值存在不是 #f 的值时结果是第一个这样的值;否则结果是 #t

  结果保留引用值。

原理

  参见以上 $and 的原理。

注释

  和 [RnRK] 的 $or? 不同,具体差异参见以上 $and 的注释。

accl <object1> <predicate> <object2> <applicative1> <applicative2> <applicative3>

  在抽象列表的元素上应用左结合的二元操作。

  对 <object1> 指定的抽象列表进行处理,取得部分和。

  当谓词 <predicate> 成立时结果是 <object2> ,否则继续处理抽象列表中余下的元素。

  处理抽象的列表的操作通过余下的应用子分别定义:取列表头、取列表尾和部分和的二元合并操作。

  参数 <applicative1> 和参数参数 <applicative2> 应接受两个参数,否则引起错误

  参数 <applicative3> 应接受两个参数,否则引起错误。

  调用参数中的应用子的 <object1> 实际参数在不同的应用子调用中可能同一

  调用参数中的应用子的底层合并子的当前环境同调用 accl 的动态环境。

accr <object1> <predicate> <object2> <applicative1> <applicative2> <applicative3>

  在抽象列表的元素上应用右结合的二元操作。

  操作方式同 accl

  和 accl 不同,可保证合并操作是尾调用;相应地,递归调用不是尾上下文而无法满足 PTC 要求。

foldr1 <applicative> <object> <list>

  作用同符合以下要求的 accr 调用:

  • 指定 accr 的参数为 <list>null?(forward! <object>)first%rest%<applicative>
  • 调用应用子 rest% 时不复制 <object> 或其子对象。

  参数指定的应用子的调用不添加或移除列表元素,否则行为未定义

注释

  类似 [SRFI-1]) 的 fold-right ,但只接受一个真列表

  foldr1 名称中的 1<list> 参数的个数。(更一般的其它形式可接受多个 <list> 。)

map1 <applicative> <list>

  单列表映射操作:使用指定应用子对列表中每个参数进行调用,结果是其返回值的列表。

  参数 <applicative> 应接受一个参数,否则引起错误。

  操作中的应用子和列表构造的结果的确定满足过程调用的因果性;其余任意 <applicative> 调用的求值、列表构造操作和销毁列表中的元素的操作的相对顺序未指定。

  <applicative> 的调用不添加或移除列表元素,否则行为未指定。

注释

  类似 [RnRK] 的 map ,但只接受一个真列表

  map1 名称中的 1 的含义类似 fold1

first-null? <list>

  复合 firstnull? 操作。

rulist <list>

  转换参数为可带有唯一引用的引用列表元素的列表。

  同 rlist ,但在参数是左值时,参数中的非引用值元素在结果中对应转换为其唯一引用。

注释 消亡值处理和 rlist 不同。

list-concat <list> <object>

  顺序连接列表和对象。

注释 当且仅当 <object> 实际参数是 <list> 值时,结果是 <list> 值。

append <list>...

  顺序连接零个或多个列表。

注释 若没有参数,结果是空列表。

list-extract-first <list>

  以 first 在参数指定的 <pair> 的列表中选取并合并内容为新的列表。

  设参数列表 (&l) ,结果同在新环境中求值表达式 map1 first l ,其中 map1first 是标准库函数。

list-extract-rest%! <list>

  以 rest% 在参数指定的 <pair> 的列表中选取并合并内容为新的列表。

  设参数列表 (&l) ,结果同在新环境中求值表达式 map1 rest% l ,其中 map1rest% 是标准库函数。

list-push-front! <list> <object>

  在列表前插入元素。

  要求 <list> 可修改,否则类型检查失败。

  参数 <object> 被转发。

$let <bindings> <body>

  局部绑定求值:创建以当前环境为父环境的空环境,在其中添加 <bindings> 指定的变量绑定,再求值 <body>

  在添加绑定前,<bindings> 中的初值符被求值。

  返回非引用值

注释 类似 [RnRK] 的同名操作子,但返回非引用值。

$let% <bindings> <body>

  同 $let ,但保留引用值。

$let/e <parent> <bindings> <body>

  指定静态环境并局部绑定求值。

原理

  显式控制 <parent> 以允许传递引用值并在外部确保环境(可以是环境强引用)被正确传递作为求值的父环境,而无需支持扩展 <parent> 中的环境为右值时其中的环境临时对象的生存期

  注意此时 <parent> 中的环境中创建的环境对象在表达式求值后仍会因引入的合并子生存期结束而被销毁。

注释

  类似 [RnRK] 的 $let-redirect ,但使用 $lambda/e 而非 $lambda 作为抽象且支持 <parent> ,并返回非引用值。

$let/e% <parent> <bindings> <body>

  同 $let/e ,但使用 $lambda/e% 而非 $lambda/e 创建抽象,保留引用值。

$let* <bindings> <body>

  顺序局部绑定求值。

  同 $let ,但 <bindings> 中的被用于绑定的表达式从左到右顺序求值,被用于初始化变量的表达式在求值时可访问 <bindings> 中之前绑定的符号。

注释 类似 [RnRK] 的同名操作。

$let*% <bindings> <body>

  同 $let* ,但保留引用值。

$letrec <bindings> <body>

  允许递归引用绑定的顺序局部绑定求值。

注释 类似 [RnRK] 的同名操作。

  和 $let$let* 不同,操作求值 <bindings> 的初值符时保证使用和求值 <body> 时的同一环境作为当前环境,因此可配合 lock-current-environment 传递具有所有权的环境。

$letrec% <bindings> <body>

  同 $letrec ,但保留引用值。

derive-current-environment <environment>...

  创建当前环境的派生环境:以参数指定的环境和当前环境为父环境空环境

  当前环境以外的父环境顺序同参数顺序。当前环境是最后一个父环境。

() make-standard-environment

  创建标准环境(standard environment) :以基础环境为父环境的空环境。

  类似 [RnRK] 的 make-standard-kernel-environment ,但创建的环境基于 NPLA1 基础环境。

注释 标准环境同 [RnRK] 约定的定义。

derive-environment <environment>...

  创建基础环境的派生环境:以参数指定的环境和基础环境为父环境的空环境。

  当前环境以外的父环境顺序同参数顺序。基础环境是最后一个父环境。

  创建的环境是标准环境,当且仅当没有实际参数。

注释 类似 make-standard-environment ,但具有参数指定的环境作为其它的父环境。

$as-environment <body>

  求值表达式以构造环境。

  创建以当前环境为父环境的空环境,并在其中求值参数指定的表达式。

  结果是创建的环境强引用。

$bindings/p->environment (<environment>...) <binding>...

  转换绑定列表为以指定的环境列表中的环境为父环境的具有这些绑定的环境。

  类似 [RnRK] 的 $binding->environment ,但指定父环境,且具有适当的所有权。

  使用 make-environment而不是 $let/e 等绑定构造实现。

$bindings->environment <binding>...

  转换绑定列表为没有父环境的具有这些绑定的环境。

注释 类似 [RnRK] 的同名操作子,但因为要求对内部父环境环境所有权,使用 $bindings/p->environment 而不是 $let/e 等绑定构造派生。

symbols->imports <symbol>...

  转换符号列表为未求值的适合初始化符号导入列表的初值符列表。

  结果是包含同 desigil的方式移除标记字符后的参数作为间接子项的列表。

  求值这个列表,结果是同 forward! 的方式转发每个符号的列表,其元素顺序和 <symbols>... 中的值的顺序对应。

  结果的结构和使用满足以下约定:

  • 结果中可能存在合并子作为其子对象,其包装数未指定。
  • 取结果中的子对象进行求值的行为未定义。
  • 若结果被修改(如被转移),再求值时行为未定义。
  • 若结果中的合并子在求值整个结果外的上下文被调用,行为未定义。

原理 这些约定可允许更有效的本机实现。

注释

  类似 [RnRK] 的 $provide!$import! 提供符号列表的方式,但有以下不同:

  • 支持移除引用标记字符。
  • 支持转发参数。
  • 不带有引用标记字符和符号指称的对象不是临时对象的默认情形复制值而不是初始化引用。

$provide/let! <symbols> <bindings> <body>

  指定局部绑定后在当前环境中提供绑定。

  蕴含 $let <bindings> <body> ,在求值 <body> 后以结果作为操作数绑定到 <symbols> 的符号。

  <symbols> 应能被作为 <definiend> 使用。

  结果是对这些绑定具有所有权的环境强引用。

  需要导入符号,即 <symbols>... 具有至少一个实际参数时,以同 symbols->imports 的方式确定初值符。其中,等效的 symbols->imports 的调用次数未指定。

注释 绑定后的符号可通过作为 vau 抽象的父环境等形式依赖这个环境,因此用户需适当保存返回值使其生存期覆盖在被使用的绑定符号指称的对象生存期。

$provide! <symbols> <body>

  在当前环境中提供绑定。

  同 $provide/let! ,但不指定单独的 <bindings>

  作用同 <bindings> 为空列表的 $provide/let!

  结果是创建的环境的强引用。

  需要导入符号时,以同 symbols->imports 的方式确定初值符。其中,等效的 symbols->imports 的调用次数未指定。

注释 类似 [RnRK] 的同名操作子,但结果是创建的环境的强引用,且确定初值符的方式被显式要求。

  仅当 <symbols> 类型检查通过时求值 <body>

  检查当前环境可修改失败时的副作用和以上任一等效求值 symbols->imports 应用子的结果可能具有的副作用非决定性有序

$import! <environment> <symbol>...

  从指定的环境导入指定的符号。

  对第一参数之后的其余参数指定的符号列表中的每个符号,修改第一参数指定的环境,创建和指定的符号具有相同的名称和值的变量绑定。

  类似 [RnRK] 的同名操作子,但需要导入符号时,以同求值 symbols->imports 应用子的结果的方式确定初值符。其中,等效的 symbols->imports 的调用次数未指定。

  当指定的环境中的指定符号对应的绑定以临时对象创建时,导入符号可修改指定的源环境的被绑定对象。

注释 由于求值 symbols->imports 应用子的结果蕴含的转发语义,这和 [RnRK] 不同。

  检查 <environment> 可修改失败时的副作用和以上任一等效求值 symbols->imports 应用子的结果可能具有的副作用非决定性有序。

$import&! <environment> <symbol>...

  从指定的环境以引用绑定导入指定的符号。

  同 $import! ,但以 ensigil的方式指定绑定的符号。

nonfoldable? <list>

  判断参数是否不可被继续折叠映射:存在空列表。

  参数是同 [RnRK] 的 map 操作可接受的列表参数或空列表,但排除非真列表

  若参数是空列表,结果是 #f

assq <object> <lists>

  取关联列表中和参数的引用相同的元素。

  第二参数指定的列表中的元素应为有序对。

  以 eq? 依次判断第二参数指定的列表中的每个元素的第一个元素是否和第一参数指定的元素等价。

  若不存在等价的元素,结果为空列表右值;否则是同 first% 访问得到的等价的列表的值。

原理

  和 [RnRK] 不同,NPLA1 只支持真列表,因此可以要求顺序,提供关于等价的元素的更强的保证。

  尽管和 [RnRK] 相同而和 [RnRS] 不同,<test> 支持非布尔值,不存在元素时的 #f 结果可以简化比较,但和 [RnRK] 的原理类似,这不利于提供清晰的类型错误,且没有如空列表值这样作为求值算法的特殊值的自然推论。使用空列表值和传统 Lisp 也一致。

注释

  类似 [RnRK] 的同名应用子,但保证顺序且转发参数。

  类似 [RnRS] 的同名过程,但失败的结果不是 #f

assv <object> <lists>

  取关联列表中和参数的值相等的元素。

  第二参数指定的列表中的元素应为有序对。

  以 eqv? 依次判断第二参数指定的列表中的第一个元素是否和第一参数指定的元素等价。

  若不存在等价的元素,结果为空列表右值;否则是同 first% 访问得到的等价的列表的值。

原理 参见 assq

注释

  类似 [RnRK] 的 assoc ,但使用 eqv? 而不是 equal? ,保证顺序且转发参数。

  类似 [RnRS] 的 assv ,但失败的结果不是 #f

box <object>

  装箱:构造参数对象经左值到右值转换的箱(类型为 <box> 的对象)。

box% <object>

  同 box ,但参数不蕴含左值到右值转换,在结果中保留参数的引用值

box? <object>

  <box>类型谓词

unbox <box>

  拆箱:从箱中还原对象。

  作为函数值转发操作,保留引用值。

注释

  以上 4 个函数除引用标记字符对应处理引用值的差异外,功能和使用方式对应类似 [SRFI-111] 的 3 个过程 boxbox?unbox

  类型分区使 box?<list> 类型的参数的结果总是 #f 。若没有这个限制,不考虑封装性时,用 <list> 的相关操作可整体替换进行功能等价的代替:listlist%first 可代替 boxbox%unbox

  和 关于 Scheme 的装箱的描述不同,这样的代替不一定保证有更好的性能。

  以上这些函数可使用 make-encapsulation-type实现。

  和 Scheme 等不同,箱具有被装箱对象的所有权,因此使用 box%unbox 时,需注意保存被构造的箱或被箱中引用值引用的对象。

标准派生特性

  标准派生特性(standard derived feature)基本派生特性,但其派生依赖标准库其它模块

ensigil <symbol>

  修饰引用字符。

  若参数非空且没有 & 前缀,则结果是添加 & 引用标记字符作为前缀的符号;否则是参数值。

$binds1? <environment> <symbol>

  判断指定符号是否在指定表达式求值后指称的环境中绑定。

注释 类似 [RnRK] 的 $binds? ,但只支持判断一个 <symbol> 操作数。

核心库

  核心库(core library) 提供以下操作,即核心库函数:

map-reverse <applicative> <list>...

  映射并反转结果。

  参数 <applicative> 应满足以下要求,否则引起错误

  • <list>... 中的参数的元素数都相等。
  • <list>... 中的参数的元素数量等于 <applicative> 接受的形式参数的元数。

注释 类似 [RnRK] 的 map ,但支持空的 <list>... 且保证顺序。

for-each-ltr <applicative> <list>...

  从左到右映射取副作用。

注释 类似 [RnRK] 的 for-each ,但支持空的 <list> 且保证顺序。

NPLA1 参照实现扩展环境

  类似 NPLA1 根环境特性,NPLA1 以根环境的形式提供其它一些模块的操作,但默认不以根环境中的绑定而是以其中的环境子对象中的绑定提供。

  除非派生实现另行指定,这些模块都应被提供。

  这些模块和 NPLA1 参照实现环境提供的特性一同构成标准库

  除非派生实现定义,每个标准库模块都不是可选的。否则,作为被加载的模块的环境的名称由派生实现定义。

  修改这些环境的程序行为未定义

  默认加载使用 . 分隔标识符得到的符号作为名称。

  加载的模块依赖初始化的根环境

  当前实现中部分加载的环境依赖之前加载的环境,这些环境的名称是固定的。用户程序需要保证这些环境在加载时的静态环境中可用。

  在调用其中的合并子时,可能求值符号引用依赖的环境。其中的环境可能在求值定义时不依赖而不作为对应的本机 API 的前置条件。

  环境是否具有依赖的环境的绑定绑定是未指定的。

  用户程序需保持加载为环境的模块具有适当的生存期,以避免其中的合并子调用引起未定义行为。

  本章的特性可使用本机实现非本机的派生实现,分别符合根环境基本特性基础派生特性的规则。具体使用何种实现是未指定的。

  派生实现可定义更具体的实现要求,以便互操作的兼容性。

原理

  这些绑定不需要被直接引入基础上下文的根环境中,因为:

  • 同时满足以下关于接口依赖的约束,而不必要以基础环境可访问的名称提供:
    • 它们不是使用作为环境的模块时被依赖的主要操作。
      • 为了使用非根环境的模块,需要绑定在根环境的函数引入其中的绑定。这样的接口应在根环境中提供而保证默认可访问,避免引入绑定这样的功能的在逻辑上的循环依赖。
      • 注释 这样的操作如 $import!
    • 它们不被根环境特性的在接口意义上依赖。
  • 接口的功能不对一般的实体具有足够普遍性,而不需要以基础环境默认可访问的名称提供。

  判定上述的足够普遍性的具体规则包括:

  • 普遍性以实体类型体现为接口的功能对非特定类型的对象适用,最终不依赖具有更特定的类型特有的性质。
    • 注释 一般的实体作为一等对象,即具有 <object>的值。<object> 是足够普遍的类型。
    • 注释 <reference><box>等由 <object> 构造的值最终依赖 <object> 的值,而非其它更特定类型特有的性质。
  • 求值算法中出现决定具体步骤的具体实体类型,被认为是足够普遍的。
  • 若接口的功能仅依赖比一般的实体更具体的特定类型的值,则不以基础环境默认可访问的名称提供。
    • 注释 功能上的依赖包含区分这些特定类型的值,如类型谓词
  • 接口的功能描述涉及的类型的足够普遍性对以基础环境默认可访问的名称提供是必要非充分条件。
    • 这些类型仅决定接口功能的一部分。
    • 注释 若接口的功能仅依赖足够普遍的类型,但功能不足以涵盖它的任何的子类型或者值,也可在参照实现扩展环境中提供。
    • 注释 一个主要特例:足够普遍的具体类型的类型谓词涵盖所有值,因此类型的足够普遍性可直接作为对应的类型谓词的足够普遍性的充分必要条件。

  具有足够普遍性而应在根环境而非参照实现扩展环境提供的操作具体包括以下几类:

  • 创建非特定的不同名义类型的对象使用的普遍机制的主要操作。
  • 不改变一般的实体可能蕴含的对象同一性而同时附加非特定种类副作用的操作。
    • 同一性是所有对象的属性。显示同一性不依赖具体副作用的种类,因此要求特定种类的接口削弱普遍性。另见可变状态和普遍性
    • 注释 根环境中这样的操作如 box%unbox
  • 不依赖特定对象类型,直接引入副作用的操作。
    • 因为引入副作用可能是接口的关键功能及主要目的,此处的普遍性不限制非特定种类。
    • 注释 仅具有控制作用为副作用的操作仍被视为是普遍的。因此,可具有控制作用$if等函数仍在根环境中提供。
    • 注释 依赖一等续延控制作用因续延类型而不视为足够普遍,因此根环境不直接提供一等续延关联的操作。

  在此基础上,这些绑定被设计为环境子对象提供的子模块,因为以下的一个或多个原因:

  • 它们可能具有非全局含义的名称而更适合隔离在不同命名空间中以避免使用时的歧义。
  • 它们可能仅关注(如作为操作的参数或返回类型)特定的求值得到的操作数类型。
  • 它们中的每一个模块具有足够或内聚性而不和其它子模块耦合,且绑定提供的实体关注相同的功能集合,适合被派生实现直接配置为可选的(#根环境基本特性)特性分组。
  • 允许实现使用特殊的环境子类型延迟加载
    • 注释 当前 NPLA1 没有提供支持。

注释

  类似根环境特性,一些特性可约定处理的值的宿主类型

  使用 . 分隔标识符得到的符号类似 CHICKEN Scheme 的转换 R7RS 的标准模块名

续延库

  提供续延支持。

  默认加载为根环境下的 std.continuations 环境。

模块约定:

  本节约定以下求值得到的操作数:

原理

  类似 Kernel 语言,NPLA1 续延不是合并子。这一设计的基本原理参见 [RnRK] §7(以及续延的捕获和调用),但理由不充分。更完整的分析如下:

  • 使用守卫选择子(guard selector) 是 Kernel 的具体特性的设计,不是一般求值算法中蕴含需要实现的选项,因此仅在 [RnRK] 内部生效。
    • 这和类似的其它机制(如 [RnRS] 的 dynamic-wind thunk )事实上都同以下关于续延组合性的理由类似,不需要单独列出。
  • 如 [RnRK] ,一般的合并子确实无法保证作为续延的父续延(parent continuation) 。
    • [RnRK] 在此没有进一步明确:
      • 一般的合并子无法作为父续延的原因是因为(作为函数)不保证能接受操作数。
      • 父续延对续延组合(接受两个续延结果是和两者连续调用等效的续延)操作是必要的输入。
      • 更一般地,即便不支持续延组合操作,在抽象机语义描述续延的捕获得到一等续延依赖这种语义(即便父续延不是一等对象)。
        • 只要描述依赖可组合的求值算法,除了最后一个无界续延,其它续延都对应可组合的求值步骤,因此这隐含续延的可组合性。
        • 其它在求值算法以外的机制的描述也可能依赖这种组合性,如:https://docs.racket-lang.org/reference/eval-model.html#(part._mark-model) 。
        • 最后一个无界续延可不考虑可组合性。但只要它不是唯一的,为作为一等续延被直接捕获和调用,它仍应当接受程序指定的操作数。
          • 仅当这个续延唯一时可不提供单独的续延类型。
            • 此时,这个续延被隐含而不当作一等续延,其中的操作数直接通过非续延的函数直接表示,如 [ISO C] 标准库的 exit 函数。
            • 然而这样的设计要求其它续延是可组合的,否则根本不支持一等续延。
          • 可组合续延时,这个唯一的续延会被这作为被组合的其它续延的公共后缀。
            • 此时仍然需要支持指定操作数,以便最后一个续延能表现程序指定的不同行为;否则,显式提供这样的续延缺少实际意义。
          • [RnRK] 要求 root-continuationerror-continuation 这两个不同的一等无界续延能作为后缀,因此不是唯一的。
    • 所以,为符合正确性,一般的合并子不是续延的子类型
  • 因为包装是独立在具体合并子类型外的操作,[RnRK] 说明了一般续延不应作为应用子的子类型,而这并没有有效说明续延无法作为操作子的子类型。
  • 一些观点认为无界续延不能作为函数。但这实际依赖具体对象语言的规则,同样无法说明一般的续延无法作为操作子的子类型。
    • 典型的分析如参见这里
    • 这仅论述了续延界限和续延(调用)的某种可组合性(composablility) 的要求之间的关系。
      • 特别地,此处的可组合性局限于取得函数值。
    • 因为这种可组合性的限制,这隐含一个前提:函数不能不返回。这对一般的函数并不成立,因为一般的函数允许存在副作用,这种副作用不一定局限于蕴含此处可组合的控制作用
    • 在类型系统中,不返回的函数仍可能是良型的,因为函数类型的构造器是否接受空类型和具体类型系统的设计相关。
      • 类型系统规则能保证编码可组合的函数的机制是确保语法上可构造可组合的嵌套函数调用(函数值可作为另一个函数的实际参数),以此构成传统数学函数复合的自然扩展,并保证作用可复合,但这不保证函数值可复合。
        • 对总是不返回的函数,非终止(non-terminization) 是其作为后缀的一种单一副作用,吸收(absorb) 任何返回函数值的这一计算作用。这破坏函数调用具有返回值的预期,但并非在作用上不可组合。
      • 事实上,不提供一等控制作用机制的语言也可能允许这种类型规则。
        • 如 [ISO C] 的 _Noreturn 函数实质上返回类型就是空类型(因为没有任何值可作为返回值居留),尽管 C 的类型系统编码中 _Noreturn 函数返回类型并不唯一(返回类型这在转换等其它类型检查中仍然有效)且空类型不被检查(违反 _Noreturn 约束是未定义行为)而可能显得不典型。
        • 注释 [ISO C] 仅提供 setjmp/longjmp 改变通常的控制状态。此外,[ISO C++] 的类似的 [[noreturn]] 用于标注总是抛出异常的函数,而提供异常的(替代)实现是一等控制操作符的一个典型使用场景。
    • 根本地,使用这种值而非一般计算作用的可组合性定义的理由,仅来自某种描述语言规则的元语言上推理的要求或约定。
      • 注释 例如,为了便于使用等式理论(equational theory) 进行推理,证明被描述的对象语言总是符合某种静态的性质。
      • 一般地,基于目的的不确定性,元语言不总是遵循这种约定。
      • 对象语言更没有必要遵循这种约定,因为这蕴含对对象语言功能完整性的任意地、不必要的限制。
      • 避免非预期的终止性可很容易通过对维护外部语言实现环境的运行时的互操作实现:添加一个破坏维护非终止的运行时条件的操作(例如,撤除硬件电源),即便需要按某种方式保留运行时状态,通常仍远远比提供静态的证明更容易。
      • 此外,不论对象语言是否有必要表达,普遍的组合性不总是有益的,自身可能不被预期。
        • 易于排除非终止这种计算作用的实现方法,正好是得益于语言实现和外部系统之间的计算作用不能自发维持组合性的直接应用。
  • 实际决定对象语言中区分续延和合并子(且续延明确不使用合并子表示)的设计有不同的其它理由:
    • 求值算法对合并子的处理决定合并子对非正常控制透明。作为引入非正常控制的机制,续延调用和遵循 β-规约的合并子调用具有不同来源的语义规则,即便后者在元语言中可能实现前者。
      • 因此,是否把续延表示为传统的过程或合并子等其它蕴含 β-规约的实体是实现细节。抽象上,这支持保持续延的表示不透明而和合并子相互独立。
      • 对一等续延相关的控制操作的一种一般的语义描述参见这里
    • 避免把续延作为合并子还有语用因素。具体地,续延调用通常使用续延应用的形式,而非操作子调用。续延应用不直接复用函数合并的语法。
      • 以操作子的方式类似进行续延调用可能依赖对象的内部表示而暴露不必要公开的抽象,并不常用,也并不被当前语言普遍支持。
        • 注释 大多数语言根本不支持操作子。
        • 但禁用这种调用,在一等续延的调用规则中添加偶然耦合,阻碍功能完整性和设计的简单性
      • 考虑到捕获的一等续延通过变量绑定提供,因为合并子不是应用子的子类型,若一等续延是合并子,只能是指称操作子的变量。
        • 于是,程序中的续延应用总是要求对其包装后使用。
        • 这种设计使程序的实现和简单性冲突,并违反多个结构设计规则,而削弱提供这些绑定的续延捕获特性的可用性
      • 因此,一般的续延不是应用子的子类型,程序中一等续延的应用需转换续延为应用子。
      • 推论:一般的续延不是合并子的子类型。
  • 综上,结合以上合并子不是续延的子类型以及续延不是合并子的子类型,一般的续延和合并子类型是正交的。

  NPLA1 首先提供关于一等续延而不是一等协程的控制操作,因为:

  • 协程要求支持具有更多的原语,包括基于(而非并行于)一般的例程的创建操作(构造器)。
  • 具有相等的一等协程的内部表示逻辑上更加复杂(总是涉及可变状态),即便具体实现并不一定更复杂(和内部表示相关)。
  • 使用非对称协程派生对称协程比使用有界续延派生无界续延在逻辑上依赖暴露更多的细节,如包含分支的跳板循环。

  尽管逻辑上有界续延能不依赖其它副作用而表达状态,提供有界续延或无界续延作为原语的差异相对次要,因为:

  • NPLA1 不是纯函数式语言而支持一等状态在内的基本设计,在实现中不需要排除可变状态。
  • NPLA1 不提供特设的和续延并行的异常或者其它替代的非正常控制机制,而不具有控制作用之间的互操作难以组合的问题
  • 尽管逻辑上引入续延界限可能是有益的,且有微妙的语义上的效果,这并没有简化用户程序和语言实现,因此当前不要求。
    • 当前不支持多次续延和续延复制。
      • 当续延仅在同一个上下文中捕获时,缺少续延界限不会是一个明显的问题,因为被调用的续延总是会重新引入共享的子续延。
    • 一些当前语言设计在 [RnRS] 的操作上扩展了界限。
    • 缺少续延界限可能引起控制操作符关联的一些模型之间的语义不等价问题而难以使用其中的一种严格准确地表达其中的另一种
      • 在 SML/NJ 等语言中,因为不存在顶层的续延界限,这些问题容易成为在程序中真正阻碍使用有界续延的困难。
      • 在 [RnRK] ,正常程序运行的顶层续延界限以 root-continuation 提供。此外,提供 error-continuation 处理错误。
      • 在完善的设计中,提供一个顶层的续延界限并非困难。因此,可能缺少取得续延界限的方法的问题不是核心困难。此外,如 [RnRK] 的 error-continuation 显示了其它类似的续延的实用性。
        • 类似的特性可能会被加入此处的设计中。

注释

  类似 [RnRK] 而和 [RnRS] 不同,续延具有单独的类型,续延应用也不是蕴含过程调用的函数应用。

操作:

call/1cc <combiner>

  捕获一次续延具现为一等续延作为参数调用合并子。

  续延首先在尾上下文中按引用捕获,再作为操作数,调用 <combiner>

  来自同一具现的一次续延的任何副本只允许一次显式(续延应用)或者隐式(如被函数调用的返回蕴含)地成功调用;否则,调用被重入引起错误

  捕获的续延之后允许被复制。

注释

  call1/cc 的名称来源同 Chez Scheme 。

continuation->applicative <continuation>

  转换续延为关联的等效应用子。

  结果是转换的 <applicative> 类型的值。

  在构造结果时,<continuation> 被转发。

  结果的底层合并子被调用时,传递操作数树给 <continuation>

apply-continuation <continuation> <object>

  应用续延。

  以第二参数作为参数,以 apply 应用第一参数指定的续延转换的应用子。

原理

  同 [RnRK] ,apply-continuation 不接受可选的环境,因为非正常控制忽略动态环境。

注释

  即同求值:apply (apply-continuation <continuation>) <object>

  apply-continuation 同 [RnRK] 。取得非 <list> 结果依赖 apply<pair> 的支持,这在 Scheme 中无法实现。

代理求值

  代理求值支持保存求值为代理对象以实现延迟求值。

  默认加载为根环境下的 std.promises 环境。

模块约定:

  本节约定以下求值得到的操作数:

  • <promise> :求值代理:表示可被求值而取得结果对象的对象。

原理

  代理求值的原语可实现惰性求值和透明的记忆化。

  和一些流行的误解不同,尽管这些原语的原始设计是关于并行处理的,这不必然蕴含并发的投机执行(speculative execution) ,只是因为解析(resolve) 内部状态并不在用户程序中可见,而蕴含必要的最小同步。

  由于当前语言不支持并发访问,即使对 <promise>修改操作导致变化,在语言中其状态也不可见,没有要求支持这种同步;未来可能会附加要求以提供更完善的并发支持。

  关于 API 的设计,参见 [RnRK] §9 和 [SRFI-45]

注释

  在 <promise> 上的并发访问并不具有特别的同步保证和要求。

  除 $lazy/d 外,同 [RnRK] 的 promises 模块。

  和 [RnRK] 不同,通过 force 引起 <promise> 对象的求值可能修改这个对象自身而使其中的状态失效(如通过 assign!对这个对象赋值)。

  因此,实现中需要重新访问状态,而重新进行类型检查。

操作:

promise? <object>

  <promise>类型谓词

memoize <object>

  记忆化求值:以参数作为已被求值的结果创建 <promise> 对象。

  在结果中保留参数的引用值

$lazy <body>

  惰性求值:以参数为待求值的表达式,以当前环境作为这个表达式被求值的动态环境,创建 <promise> 对象。

  当前环境的环境弱引用的副本被保存到结果。

$lazy% <body>

  同 $lazy ,但保留引用值。

$lazy/d <environment> <body>

  同 $lazy ,但以参数指定环境替代动态环境。

$lazy/d% <environment> <body>

  同 $lazy% ,但以参数指定环境替代动态环境。

  参数指定的一等环境值的副本被保存到结果。

force <object>

  若参数是 <promise> 值,立即求值指定的 <promise> 对象,得到的结果对象作为结果;否则,转发参数作为结果。

  若参数在求值时修改为非 <promise> 类型的值,需要继续迭代求值时,引起类型错误

数学库

  数学库提供数值类型的操作和其它数学功能。

  默认加载为根环境下的 std.math 环境。

模块约定:

  以下操作中:

原理

关于比较操作:

  同 [RnRK] 而不同于 [R5RS] 、[R6RS] 和 [R7RS] ,比较操作不明确要求传递性(但精确数仍然因真值的数学性质而保证传递性),以允许操作数存在不精确数时,转换不精确数 flonum 的简单实现。

  [R6RS] 继承了 [R5RS] 要求过程 = 具有传递性(注释指出传统类 Lisp 语言的实现不要求),而 [R7RS] 的对应注释指出这不能通过把所有操作数都转换为不精确数实现。

  不要求不精确数的传递性和除法约定对除数为不精确数 0 值的处理兼容。

  不同的语言在此可能有不同的规则,可见:

  • 一些现代 Lisp 语言可能满足(即便不是 Scheme 实现,如 SBCL )或不满足(即便是 Scheme 实现的派生,如 Racket )此要求。
  • 一些语言的不同版本的实现可能使用不同的规则(如 Ruby 1.8.7 和 Ruby 2.0 )。

  使用以上链接中的测试用例,可发现一些 [RnRS] 的实现实际可能不符合 = 的传递性要求,如 x86-64 Linux 上:

  • Chez Scheme 9.5.6 、Chibi Scheme 0.10.0 和 Gauche 0.9.11 不符合要求。
  • Chicken 5.3.0 、Guile 2.2.7 和 Gambit 4.9.4 符合要求。

  上述符合性问题已在以下实现中报告并被修复:

注释

  和数值操作约定不同,幂等操作要求超过一次应用时,结果和参数的宿主类型也相同。

操作:

number? <object>

  <number>类型谓词

complex? <object>

  <complex> 的类型谓词。

注释number? ,因为当前 <number> 值都是 <complex> 值。

real? <object>

  <real> 的类型谓词。

注释complex? ,因为当前 <complex> 值都是 <real> 值。

rational? <object>

  <rational> 的类型谓词。

注释 当前实现仅需排除无限大和 NaN 值

integer? <object>

  <integer> 的类型谓词。

exact-integer? <object>

  判断参数是否为 <integer> 类型的精确数对象。

fixnum? <object>

  判断参数是否为 fixnum对象。

flonum? <object>

  判断参数是否为 flonum对象。

exact? <number>

  判断参数是否为精确数。

注释 当前精确数都是 fixnum 。

inexact? <number>

  判断参数是否为不精确数

注释 当前不精确数都是 flonum 。

finite? <number>

  判断参数是否为有限值

infinite? <number>

  判断参数是否为无限大值。

nan? <number>

  判断参数是否为 NaN 值。

=? <number1> <number2>

  比较相等。

<? <real1> <real2>

  比较小于。

>? <real1> <real2>

  比较大于。

<=? <real1> <real2>

  比较小于等于。

>=? <real1> <real2>

  比较大于等于。

zero? <number>

  判断参数是否为零值。

positive? <real>

  判断参数是否为正数。

negative? <real>

  判断参数是否为负数。

odd? <real>

  判断参数是否为奇数。

even? <real>

  判断参数是否为偶数。

max <real1> <real2>

  计算参数中的最大值。

min <real1> <real2>

  计算参数中的最小值。

add1 <number>

  计算参数加 1 的值。

sub1 <number>

  计算参数减 1 的值。

+ <number1> <number2>

  加法:计算参数的和。

- <number1> <number2>

  减法:计算参数的差。

* <number1> <number2>

  乘法:计算参数的积。

/ <number1> <number2>

  除法:计算参数的商。

abs <real>

  计算参数的绝对值。

  abs幂等操作

注释 同 [RnRS] 和 [RnRK] ,当前仅支持 <real> ,尽管数学上这对 <complex> 也有意义。

floor/ <integer1> <integer2>

  数论除法:计算参数向下取整的整除的商和余数。

floor-quotient <integer1> <integer2>

  数论除法:计算参数向下取整的整除的商。

floor-remainder <integer1> <integer2>

  数论除法:计算参数向下取整的整除的余数。

truncate/ <integer1> <integer2>

  数论除法:计算参数截断取整的整除的商和余数。

truncate-quotient <integer1> <integer2>

  数论除法:计算参数截断取整的整除的商。

truncate-remainder <integer1> <integer2>

  数论除法:计算参数截断取整的整除的余数。

inexact <number>

  取和参数值最接近的不精确数:

  • 若参数是不精确数,则结果是参数值。
  • 否则,若参数超过任意不精确数内部表示的有限值的范围,则结果是未指定宿主类型的对应符号的无限大值。
  • 否则,若不存在和参数数值相等(以 =? 判断)的不精确数,则引起错误。
  • 否则,结果是和参数数值相等的不精确数。

  inexact 是幂等操作。

原理

  这里明确约定了错误条件,这和 [RnRK] 及 [RnRS] 宽松地允许引起错误(也允许不引起要求诊断的错误)不同。

  允许返回无限大值使程序容易判断非预期值的原因。使用浮点数作为不精确数,无限大值的结果符合 [IEC 60559] 的定义。实际 [RnRS] 实现及 klisp 普遍使用这种实现。

  对其它情形保证 =? 比较的后置条件允许用户定义严格相等的转换函数(通过对结果应用 infinite? 可发现并排除无限大值)。

  典型实现中,当参数是 fixnum值时,结果通常不超过不精确数能表示的范围,也不需要引起错误。

注释 同 [R6RS] 和 [R7RS] 的同名过程,及 [R5RS] 的 exact->inexact ;因为当前实现不支持不是 <real><number> ,也同 [RnRK] 的 real->inexact 。关于 [RnRS] 中的命名,另见 R7RS ticket 328 和 [R7RS] 的相关注释。

string->number <string>

  转换字符串为数值。

  若转换成功,结果是对应参数作为外部表示的数值。否则,结果是 #f

  不因转换失败引起错误。

  数值包括所有实现支持的值。

注释 同 [R7RS] 的同名过程,但不支持附加的可选的进位制参数。

number->string <number>

  转换数值为字符串。

注释 同 [R7RS] 的同名过程,但不支持附加的可选的进位制参数且不因特定的参数值出错。

除法约定

  二元除法或者取余数的操作中,第一个参数是被除数,第二个参数是除数。

  数论除法的结果中的数值具有整数类型。

  当除数是不精确数 0 时:

  • 若被除数是非零有限数值或无限大值,则商的符号同被除数的符号。
  • 否则,商的符号未指定。

  当被除数是不精确数时,若除数是精确数 0 ,则结果除符号外同除数是不精确数 0 的情形。

  同时计算商和余数的操作的结果是商和余数构成的列表。

原理

  Scheme 方言及实现中普遍存在不同的除零错误的条件。

  [R5RS] 的过程 / 除以零的条件没有被明确约定。

  [R6RS] 则明确要求过程 / 中:

  • 在所有参数为精确数,若除数存在零值时,引发条件类型为 &assertion 的异常。
  • 否则,以例子指定除数存在零值时的结果:
    • 若被除数为非零有限数值,结果为符号同被除数的。
    • 否则,结果为正的 NaN 值。

  [R7RS] 中修订 [R5RS] 的过程 / 中存在精确数 0 作为除数的除法结果是错误,但这不要求引起错误。实现可以引起错误,或结果具有未指定的错误的值。

  [R7RS] 实际维持 [R5RS] 的条件不变,而非 [R6RS] 附加更多的要求;参见 R7RS ticket 367 ,但其中关于 [R6RS] 实现可转换操作数为不精确数和其它操作一致有误,因为 [R6RS] 的过程 = 不能以此方式实现(参见以上关于比较操作的原理的讨论)。

  不同的 Scheme 实现对零值的处理(包括除零错误的条件)另见这里

  [RnRK] 的合并子 / ,存在任意除数为零值则引起错误。

  因为 [RnRK] 的不精确数是可选模块,不讨论除数的零值是否精确值而保持简单性是合理的。

  [RnRS] 明确要求支持精确数和不精确数(存在不同的字面量),但是只有 [R6RS] 要求存在不精确值时的确切结果。

  NPLAMath的数值操作约定和 [R7RS] 类似,但在此附加和 [R6RS] 类似的要求而覆盖数值操作约定,因为:

  • 不精确数 0 往往来自表示下溢的计算结果,而不是精确的真值 0 ,因此数学上商应存在定义。
  • 同 [RnRS] ,NPLAMath 支持的不精确数允许自然地定义除数为不精确数 0 时的结果:
    • NPLAMath 明确要求支持不精确数(而非 [RnRK] 作为可选的特性提供),能区分不精确数 0 和精确数 0 的不同结果。
    • NPLAMath 的不精确数 0 允许支持符号据此确定作为商的无限值的符号(而非 [RnRK] 无法区分符号而无法按数学定义确定极限值的符号)。
  • 这些规则允许更优化的实现:
  • 不存在 [RnRS] 需要关心继续使用 [R6RS] 规则的一些问题
    • 在被除数和除数都存在零值时,没有同 [R6RS] 的 / 的例子要求结果的符号,逻辑上仍然能保持一致。
    • NPLA1 不需要考虑不完全支持不精确数的(不满足 [RnRS] 符合性)的实现的兼容性等问题。

注释

  同 [R7RS] ,若被除数是零值,且除数不是精确数 0 ,计算结果可能是精确数 0 。但当前实现没有提供精确数替换计算结果中的这种机制。

  NPLA1 std.math 中名称以 floortruncate* 起始的函数的语义同 [R7RS] 的定义,要求参数是整数。一些 Scheme 实现不严格要求整数。

  [R6RS] 的整除基本操作(过程 divmoddiv0mod0 )没有严格定义,仅通过例子说明一些差异,但明确不要求整数。

  [RnRK] 的整除同 [R6RS] ,但描述和 [R6RS] 矛盾;klisp 实现行为同 [R6RS] 。

  更一般的定义参见 [SRFI-141] 和其它相关参考文献:

  • http://dl.acm.org/citation.cfm?id=128862
  • http://people.csail.mit.edu/riastradh/tmp/division.txt
  • https://www.gnu.org/software/guile/manual/html_node/Arithmetic.html
  • https://github.com/johnwcowan/r7rs-work/blob/master/WG1Ballot3Results.md#185-add-sixth-centered-division-operator
  • https://wiki.call-cc.org/eggref/5/srfi-141

字符串库

  提供字符串和正则表达式的相关操作。

  默认加载为根环境下的 std.strings 环境。

模块约定:

  本节约定以下求值得到的操作数:

  • <regex> :正则表达式。

  为提供宿主语言互操作支持,正则表达式以 std::regex 类型表示,实现保证可通过 <string> 对应的 string 值初始化。

  除非另行指定,以上所有正则表达式的操作使用 [ISO C++11] 指定的默认选项,即:

  • std::regex_constants::ECMAScript
  • std::regex_constants::match_default
  • std::regex_constants::format_default

操作:

string? <object>

  <string>类型谓词

++ <string>...

  字符串串接。

string-empty? <string>

  判断字符串是否为空。

注释[SRFI-152] 的过程 string-null?

string<- <string1> <string2>

  字符串赋值

  以第二参数为源,修改第一参数指定的目标。

string-trim <string1>

  删除字符串中指定的连续前缀空白符。

  空白符是 C++ 字符串中 " \n\r\t\v" 的字符之一。

注释 同 [SRFI-152] 的过程 string-trim-both ,但不支持可选参数,且默认指定的空白符不同。

string-trim-left <string1>

  删除字符串中指定的连续后缀空白符。

  空白符同 string-trim 中的定义。

注释 同 [SRFI-152] 的过程 string-trim ,但不支持可选参数,且默认指定的空白符不同。

string-trim-right <string1>

  删除字符串中指定的连续前缀和后缀空白符。

  空白符同 string-trim 中的定义。

注释 同 [SRFI-152] 的同名过程,但不支持可选参数,且默认指定的空白符不同。

string-prefix? <string1> <string2>

  判断第一参数是否包含第二参数作为前缀子串。

注释 同 [SRFI-152] 的同名过程,但不支持可选参数。

string-suffix? <string1> <string2>

  判断第一参数是否包含第二参数作为后缀子串。

注释 同 [SRFI-152] 的同名过程,但不支持可选参数。

string-contains? <string1> <string2>

  判断第一参数是否包含第二参数作为子串。

注释

  一个串总是包含相等的串。空串被任何串包含,且总是不包含任何非空串。

  同 [SRFI-152] 的过程 string-contains ,但不支持可选参数,且结果是 #t#f

string-contains-ci? <string1> <string2>

  判断第一参数是否包含第二参数作为子串,忽略大小写。

  只在单字节字符集内的字符中区分大小写。

注释 除不区分大小写外同 string-contains?

string-split <string1> <string2>

  取第二参数分隔第一参数得到的字符串的列表。

注释 同 [SRFI-152] 的同名过程,但不支持可选参数。

string->symbol <string>

  转换字符串为符号。

symbol->string <symbol>

  转换符号为字符串。

  不检查值是否符合符号要求。

string->regex <string>

  转换字符串为以这个字符串作为串的正则表达式。

  若正则表达式无效,则引起错误

regex-match? <string> <regex>

  判断字符串中是否匹配正则表达式的模式串。

  若 <string> 匹配 <regex> 指定的模式串,结果是 #t ,否则结果是 #f

regex-replace <string1> <regex> <string2>

  替换字符串中的模式串,构造新字符串。

  在 <string1> 的副本中搜索正则表达式指定的模式串的所有匹配,替换为 <string2> 指定的格式字符串。

  结果是替换后的字符串。

注释

  当前实现不处理实现抛出的 std::regex_error 异常。

输入/输出库

  提供输入/输出操作。

  默认加载为根环境下的 std.io 环境。

模块约定:

  文件系统中创建或移除项的函数的返回值是表示操作是否成功的 <bool> 值。

  除非另行指定:

  • 对创建目录的操作,在若目录已存在,则视为操作失败且无作用;否则,若创建失败,则引起错误
  • 对创建多个目录的操作,仅在所有目录都创建成功时操作成功。操作失败可能仍存在部分目录被创建成功。
  • 对访问文件的操作,若读或写失败,则引起错误。
  • 对访问文件系统路径的操作,除非另行指定,若实现环境支持跟随文件系统对象的链接,隐含跟随链接(即解析指定链接的文件系统路径到最终的非链接对象作为路径指定的资源)。
    • 解析链接可能引起错误。
    • 注释 文件系统对象链接的实例如 POSIX 符号链接(symbolic link) 和 Windows 重解析点(reparse point) 。

原理

  文件系统解析文件名可支持链接,但不是所有环境有同等支持。

  Scsh 的 file-not-readable? 等明确不支持 chase? 指定跟随链接,理由是不检查符号链接权限,但这并不充分。

  事实上:

  因此,一般的 API 应当允许区分访问文件是否跟随链接。

注释

  当前派生实现依赖可用的 std.strings环境。

  当前实现的文件系统操作失败可能修改宿主环境的 errno

操作:

file-exists? <string>

  判断参数指定的文件名对应的文件是否存在。

注释

  同:

file-directory? <string>

  判断参数指定的文件名对应的文件是否存在且为目录文件。

注释

  同:

file-regular? <string>

  判断参数指定的文件名对应的文件是否存在且为常规文件。

注释

  同:

readable-file? <string>

  判断参数指定的文件名对应的文件是否存在且可读。

注释

  同:

readable-nonempty-file? <string>

  判断参数指定的文件名对应的文件是否存在、可读且文件内容非空。

writable-file? <string>

  判断参数指定的文件名对应的文件是否存在且可写。

注释

  同:

() newline

  输出换行并刷新缓冲。

  若无法输出,则没有作用。

put <string>

  输出字符串。

puts <string>

  输出字符串和换行并刷新缓冲。

注释putnewline 的组合。

load <string>

  加载参数指定的翻译单元作为源的模块

  加载时在当前环境读取翻译单元后求值,以求值后的这个环境对象作为调用的结果。

  当前实现中,参数为文件系统路径。

  被加载的翻译单元视为对象的外部表示,经读取翻译为 NPLA1 对象。

原理

  和 [R7RS] 不同,load 不支持指定环境,而总是使用当前环境。

  类似 Kernel ,当前环境可通过不同机制改变,而不需由 load 提供特设的支持。例如,可使用 eval指定蕴含 load 的调用的求值使用的环境。

  和其它一些语言的类似命名的功能(如 Lua 的 loadfile )不同,load 的语义隐含从外部来源取得求值构造(evaluation construct) 后在当前环境求值,其中的求值明确允许隐含副作用。在此,load 的求值被视为初始化加载的模块过程中的一部分。

  因为当前不提供取得求值构造的读取(read) 等函数,不要求 load 具有非本机的派生实现。并且,取得求值构造可能有其它方式,如从二进制映像映射(map) 到内部表示等替代,这些实现通常不应被要求为总是具有本机派生实现而降低实现质量。

注释

  参数一般指定视为外部翻译单元的文件名。

  类似 klisp 的同名操作。类似地,不使用 klisp 的 find-required-filename 机制,直接以宿主的运行环境为基准使用路径。

  和 klisp 不同,在尾上下文中求值被加载后读取的对象,并以其求值结果作为操作的结果,且错误不影响操作的结果。

  [Shu09] 缺少 load 的详细描述而仅有标题。

  另见以下关于 get-module 的说明。

get-module <string> <environment>?

  创建标准环境并在其中加载模块。

  创建标准环境并以这个环境为当前环境加载 <string> 指定的翻译单元作为源的模块。

  若第二参数非空,则在加载前首先绑定创建的环境中的 module-parameters 变量为第二参数的值。

  结果在加载完成后取得,是先前被创建的标准环境。

注释

  第一参数的作用同 load 的参数。

  类似 klisp 和 [Shu09] 中的同名操作。

  klisp 文档中的 load 描述中求值环境有误:

  • 按 [Shu09] 一致的描述和 klisp 的实际实现,调用 load 时应在当前环境求值,而不同于 [Shu09] 的 get-module 中描述的使用创建的新标准环境进行求值。
  • 否则,使用 [Shu09] 的 get-module 的派生不能实现 klisp 和 [Shu09] 中描述的 get-module 的预期语义。

absolute-path? <string>

  判断参数是否指定绝对路径。

注释

  同:

path-parent <string>

  参数字符串视为路径,结果是字符串指定的路径所在的父目录(当路径非根目录),或路径自身转换到的字符串值。

  结果的路径不带有结尾分隔符。不检查路径实际存在。

注释

  同:

remove-file <string>

  移除参数指定的路径命名的文件。若成功结果为 #t ,否则结果为 #f

注释

  同:

  • [STklos 的同名过程](https://stklos.net/Doc/html/stklos-ref-4.html#delete-file) ,但通过函数值指定调用失败而非引起错误。
  • [R7RS] 的 delete-file ,但通过函数值指定调用失败而非引起错误。

create-directory <string>

  创建参数指定的名称的文件系统目录。

原理

  create-directory 在一些其它语言的实现中命名为 make-directory

  NPLA1 中,作为非正式约定,make 前缀被预留给通过函数值得到创建的对象的过程。

  这和 Gauche 不区分两者不同。

  使用 create-directory 的命名也和 [SRFI-170] 一致,但 NPLA1 中,目录存在时不引起错误而以函数值 #f 指示。

  此外,[ISO C++] 中存在类似名称的函数 std::filesystem::create_directory

注释

  类似:

create-directory* <string>

  创建参数指定的名称的文件系统目录及其必要的父目录。

  create-directorycreate-directory* 只创建一级目录时的行为确保一致。

注释

  类似:

create-parent-directory* <string>

  创建参数指定的名称对应的父目录及其必要的父目录。

注释

  类似 [Racket] 的 make-parent-directory* ,但不支持非字符串参数和可选的权限参数。

  当前实现中,若创建目录失败而引起错误,抛出 std::system_error 异常。

系统库

  提供和实现环境的交互功能。

  默认加载为根环境下的 std.system 环境。

对象:

version-string

  当前实现的版本字符串。

  类型为 <string>

build-number

  当前实现的构建版本号。

  类型为 <integer> ,值为正整数。

revision-description

  实现的版本说明。

  类型为 <string>

操作:

() get-current-repl

  取表示当前 REPL 环境的引用值。

() cmd-get-args

  返回宿主环境程序接受的命令行参数列表。

  其中参数数组保存在实现内部访问的对象中。

  传递给 REPL 的命令行参数通常是宿主程序中主函数的 argv 参数数组中处理后去除特定参数后的程序。

  宿主程序复制 argv 到这个数组作为副本后,作为返回值的列表的来源。

env-get <string>

  取宿主环境的环境变量字符串。

  字符串参数指定环境变量的名称。

env-set <string1> <string2>

  设置宿主环境的环境变量字符串。

  两个字符串参数分别指定环境变量的名称和设置的值。

  使用 env-getenv-set 及对应宿主环境的操作不保证线程安全。

env-empty? <string>

  判断字符串指定名称的环境变量是否为空。

system <string>

  以 std::system 兼容的方式调用外部命令。

  结果是宿主环境命令返回的命令退出状态。

注释

  类似 Chez Scheme 的外部接口 ,但更接近 [ISO C] 和 [ISO C++] 的原始含义,当前不提供关于信号等依赖特定实现环境的保证。

  使用 [ISO C] 和 [ISO C++] 的宿主环境的命令退出状态是 int。由 NPLA 实现环境,这里的结果的宿主类型是确定的。而 NPLA1 数值使用 NPLA 数学功能确保其被映射确定的数值类型

system-get <string>

  调用命令,返回命令调用结果。

注释 在典型的 C++ 宿主实现中,命令的调用结果是来自标准输出的数据。

  返回一个两个元素的列表,分别是管道输出字符串和宿主环境命令返回的命令退出状态。

  调用命令和 system 的方式类似。

make-temporary-filename <string1> <string2>

  取适合命名新创建的临时文件的随机文件名。

  参数分别指定前缀和后缀。参数可能为空。随机文件名以生成的有限长度的随机字符串添加前缀和后缀组成。

  生成的字符串只包含 C++ 基本字符串的可打印字符,且不使用大写字母,以兼容大小写不敏感的文件系统。

  随机的文件名被打开以检查是否可访问。若失败则重试,若最终失败,则引起错误。

注释

  功能类似 POSIX ::tmpnam::mkstemp ,以及 Guile 对应的过程 tmpnammkstemp ,有以下不同:

  • tmpname 类似,结果是文件名,而不是打开的文件或端口。
  • mkstemp 类似,使用模板字符串作为文件名,但模板不在参数中指定。

   保证生成的文件名在不同宿主实现(文件系统)中的兼容性。

  当前实现中的随机字符串长度为 6 。当前实现中重试的上限是 16 次(宿主平台)或 1 次(非宿主平台)。若最终失败,抛出 std::system_error 异常。

模块管理

  提供使用模块和其中符号的相关功能。

  默认加载为根环境下的 std.modules 环境。

模块约定:

  需求字符串(requirement string) 是具有 <string> 类型的非空字符串。

  若操作的形式参数是需求字符串,实际参数是空字符串,则引起错误

  本模块共享可变管理状态,以支持操作访问确定的模块字符串集合。

  本模块隐含一个字符串序列作为需求字符串模板。

  除非派生实现另行指定,需求字符串模板初始化后不可变。

  需求字符串模板初始化的值由实现定义。

原理

  为避免名称污染等问题,不提供显式指定命名环境的模块创建操作,如 Lua 5.1 的 module 函数存在一些问题

  不提供访问创建的环境的操作,以避免污染外部的访问。若需公开其中的变量绑定,可使用返回或模块参数。

注释

  当前实现中,若环境变量 NPLA1_PATH 的值非空,则需求字符串模板是这个值以单字符子串 ";" 分隔后的结果;否则,默认值是序列 "./?" 和 "./?.txt" 构成的序列。

  当前派生实现依赖可用的 std.stringsstd.iostd.system环境。

  通过不经过本模块的操作(如互操作)、重复字符串模板的重复项、符号链接和字符串大小写不敏感的文件名等可能绕过本模块的注册机制而重复加载同一个外部文件。本模块的操作不对这些情形进行任何检查。

操作:

registered-requirement? <string>

  判断参数是否是已在本模块注册的需求字符串。

register-requirement! <string>

  在本模块注册参数为需求字符串。

  若已被注册,则引起错误;否则,在内部创建标准环境

  结果是创建的环境的弱引用

unregister-requirement! <string>

  在本模块注册解除参数为需求字符串。

find-requirement-filename <string>

  查找需求字符串对应的文件名。

  在需求字符串模板中顺序地搜索字符串。若不存在这样的结果,则引起错误;否则,结果是匹配字符串的搜索结果。

  判断需求字符串模板中的每一个字符串是否能被需求字符串匹配时,首先替换字符串中的单字符子串 "?" 为需求字符串,取得替换结果,再判断它是否为可读的文件的文件名。

  替换字符串时,每一个子串被同时一次替换;不对替换结果进一步递归地替换。

require <string> <environment>?

  按需在新标准环境加载需求字符串对应的模块。

  若第二参数非空,则在加载前首先绑定创建的环境中的 module-parameters 变量为第二参数的值。

  若参数指定的需求字符串没有注册,则注册需求字符串并加载同调用 find-requirement-filename 等价的方式搜索得到的结果,并保存加载的结果;否则不进行加载。

  结果是保存的加载的结果。

注释

  类似 klisp 的 ports 模块中的同名应用子,但有以下不同:

  • 同时保存创建的环境,以避免因程序没有保存环境引用而无效化,使访问变量绑定的程序具有未定义行为。
  • 结果是加载结果(同模块 std.io 中的 load)而不是 #inert
  • 支持同 std.io 中的 get-module 的可选参数。

附录

进一步阅读

  关于 NPL 语言派生实现的具体实现,参见 YSLib 项目文档 doc/NPL.txt

Kernel 实现

  NPL 文法是 S-表达式语法和 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 计划实现特性:

概述

  Sysroot 是具有特定逻辑布局根目录兼容的目录,一般在文件系统中实现。

  Sysroot 主要用于本机程序的开发环境构建和部署,以后也可能用于交叉构建(cross building) 。

  在利用 Sysroot 开发之前有必要了解开发注意事项:

  Sysroot 使用的文件系统路径满足运行环境对外部文件的路径的约定。

  当前 Sysroot 仅支持 MinGW 和 Linux 环境。

  关于本文档讨论以外的脚本的其它行为,参见脚本的有关说明。

布局

  在满足前述要求的前提下,Sysroot 部署实例使用的文件系统布局原则上和一般类 UNIX 系统使用的布局同构,具体子目录布局如下:

  • .shbuild :使用 SHBuild 构建时的临时目录(可以在部署后手动删除)。
  • 系统前缀字符串指定的作为安装路径前缀的最后一个路径组件的目录文件名(如 usr ):被部署的用户空间目录,其中的内容使用 Linux 等现代 UNIX 变体类似的约定:
    • bin :二进制文件。
    • include :头文件。
    • lib :库文件。
    • share :共享的数据文件。
      • share/NPLA1 :NPLA1 脚本部署位置。
    • var :程序运行时可修改的数据。

  其中,除 var 外,这些目录中的内容在安装或维护部署情形外通常应保持只读。

  对 MSYS2 支持的宿主和目标平台,系统前缀字符串指定的路径和 MSYS2 构建系统的 /etc/msystem 脚本指定的 MSYSTEM_PREFIX 的值一致。

  • 对 MinGW32 和 MinGW64 ,这也对应 MSYS2 支持的子系统路径
  • MSYS2 可能支持其它平台的组合,可能直接可用,但并不保证每个配置都提供支持和被测试。详见先决条件中关于平台的描述。

  上述 bin 目录可以加入环境变量 PATH 中以便其中的程序被命令行执行。当前若使用 YDE 构建脚本,这是必要的。

  依赖 Sysroot 的程序可预期以上布局符合约定,且功能允许时可访问这些子目录或其它文件,而不被要求能在不符合预期的环境下正常运行。这些程序可能根据上述子目录中的内容判断需要的不同操作。安装或者维护 Sysroot 布局的程序提供附加的保证,不依赖全部或者部分路径符合预期,而在必要时可创建文件使布局满足这些预期。具体的操作可在不同程序的功能描述中明确。

  没有在此约定的 Sysroot 中的子目录和其它文件路径为未来的版本保留:这些路径不被当前版本使用,但可能被之后的版本使用。依赖 Sysroot 的程序和用户应注意避免占用保留路径引起变更版本时引起的兼容问题。

注意 Sysroot 实例的布局不是版本库文件布局。但是,关于部署文件时有关被部署的文件的要求,参见版本库文件布局中的相关约定。

安装脚本

  YSLib 项目提供生成可打包的 Sysroot 的安装脚本,位于 Tools/Scripts/install-sysroot.sh 。输出的 Sysroot 的默认位置为版本库根目录下的 sysroot 目录。安装 Sysroot 时构建和部署多个项目,详见以下各节。

基本安装

  使用 SHBuild 构建和部署运行环境,包括库和工具。

建议阅读以下关于选项以及脚本说明后再运行命令。

  可进入 bash 运行以下 GNU bash 脚本:

Tools/install-sysroot.sh

  直接在默认输出位置构建和部署基础环境。

注意 当前构建脚本默认使用 uname 判断构建系统的体系结构。对 MSYS2 等环境,结果为基本系统的体系结构而不一定和作为构建目标的宿主系统相同。如使用 64 位基本系统构建 32 位目标,需确保环境变量 MSYSTEM 设置为 MINGW32(在启动 MSYS2 提供的 shell 入口时应正确设置,但直接调用 bash 并不保证),或使用以下命令代替:

SHBuild_Host_Arch=i686 Tools/install-sysroot.sh

  此处自动环境检测的机制同 shell 脚本 Tools/Scripts/SHBuild-common.sh 中的函数 SHBuild_PrepareBuild ,详见脚本的说明。

构建和部署

  构建过程包括 SHBuild 可用的多阶段构建

  构建时在 Sysroot 输出目录种保存中间输出。当前不清理这些中间输出。

  通过脚本选项,在 stage 2 SHBuild 需要的库时,也可生成其它的 YSLib 库(静态库和调试版本的库)。

  YSLib 库和依赖的外部的第三方库的二进制文件和对应的头文件在构建后被部署到 Sysroot ,并在之后用于构建 stage 2 SHBuild 。

  完成构建 stage 2 SHBuild 后,脚本部署 stage 2 SHBuild 和脚本(参见以下节)到 Sysroot 。

脚本部署

  在 stage 2 SHBuild 构建后,部署 SHBuild 同时安装(复制)版本库中的以下位置的 脚本文件(无视路径前缀)到 Sysroot 的对应安装路径:

  • NPLA1 脚本安装到 share/NPLA1
    • Tools/Script/SHBuild-BuildApp.txt
    • Tools/Script/SHBuild-YSLib-common.txt
  • Shell 脚本安装到 bin
    • Tools/Script/SHBuild-common.sh
    • Tools/Script/SHBuild-common-options.sh
    • Tools/Script/SHBuild-common-toolchain.sh
    • Tools/Script/SHBuild-BuildApp.sh
    • Tools/Script/SHBuild-BuildPkg.sh

可选工具

  通过脚本选项,在部署 stage 2 SHBuild 后,也可安装 Tools 下的其它工具:

构建中间文件

  当前工具不对构建的中间文件显式管理。若需清理,可以手动删除对应的文件或所在的生成目录。一般生成的文件和源代码的目录结构对应。

  若需要预编译头文件在后续重复构建时的性能提升,或者意图重新生成这些文件,可以手动清理这些文件。预编译头文件一般在对应项目生成其它的文件的顶层目录相同;可手动搜索构建目录中所有 .gch 文件并删除后再运行构建脚本。默认生成预编译头文件前硬链接被预编译的头文件并在同一个目录中生成,包括以下位置:

构建时的诊断

  一些构建时的诊断可被安全忽略:

  若不需要使用预编译头文件或仅需要避免警告,可参考脚本的说明设置变量跳过预编译头文件或添加忽略警告的选项

Sysroot 开发指令

  使用 Sysroot 开发,首先需安装 Sysroot 环境,参见以上安装脚本的说明

应用程序构建脚本

  YSLib 应用程序可使用 SHBuild 构建。为简化构建流程,使用应用程序构建脚本包装对 SHBuild 的调用。

  应用程序构建脚本不限制实现使用的语言,但在版本库中提供的脚本应符合公共的约定;此外,具体脚本可能需要指定适当的 PATH 以确保外部的工具可被调用。

  典型地,应用程序构建脚本是 shell 脚本,可依赖 Sysroot 环境已构建的 Sysroot 环境,并调用 NPLA1 脚本。

  YSTest 支持使用脚本 /YSTest/SHBuild-YSTest.sh 构建,可作为例子参考。和其它在安装脚本部署的 stage 2 应用不同,当前默认情况下使用版本库中的 build 目录下的平台目录存放生成的文件(中间文件和生成的可执行程序示例)。

部署

  除安装脚本外,当前不提供直接部署程序。

  参照运行确定二进制依赖项位置和调整运行时的配置。

概述

  YDE 是 YSLib Desktop Environment 的简称,预期作为一个基于 YFramework 的桌面环境。

  当前是一个 YSLib 的顶级子项目,构建需要 Sysroot 支持。

  YDE 包含一系列的包(package) 在单独的目录中作为次级子项目。

构建

  当前只提供 build-all.sh 使用 SHBuild-BuildApp.txt 构建所有包。

准备

  • 参见开发说明
  • 参见 Sysroot入门 的说明配置环境,确保 Sysroot 部署的 usr/bin 目录在 PATH
  • 运行 build-all.sh ,可以提供参数

  构建的结果参见 SHBuild-BuildApp.txt 的文档。

包列表

  • ImageBrowser 图像浏览工具
  • Clock 模拟时钟

运行

  参见运行了解所有基于 YFramework 的程序的说明。

使用配置 yconf.txt ,详见关于运行的说明

ImageBrowser

  运行程序会打开命令行第一个参数作为路径的文件。支持从资源管理器拖拽到程序。

  支持 YFramework 支持的图像格式:

  • BMP
  • GIF
  • PNG
  • 非渐进 JPEG

  支持滚轮缩放。

  单击右上角退出按钮退出程序。

  右键单击界面其它部分弹出上下文菜单。有以下菜单项:

  • 退出:关闭窗口,退出程序。
  • 查看原始大小:当显示的图像不是原始大小时,缩放图像到原始大小。
  • 翻转:旋转 180° 。
  • 顺时针旋转:顺时针旋转 90° 。
  • 逆时针旋转:逆时针旋转 90° 。
  • 复制:复制当前显示的(可能经过缩放的)图像内容到剪贴板。

  旋转时保持图像中心位置不变。

  中键单击界面其它部分查看原始大小。

  支持键盘快捷键:

  • Ctrl+C 复制

Clock

  右键单击界面退出程序。

概要

  本文档记录发布工程(release engineering) 相关的规则、历史记录和相关计划。

  发布策略控制版本更新。

  关于版本(revison) 的详细规则参见 YSLib 项目文档 doc/ProjectRules.txt

分支

  一个项目可有多个分支(branch) ,包括至少一个保持工作进行的活动(active) 分支。

  发布时使用活动分支,以项目版本库的分支为准。

  当前只有主分支(master branch)

标识

  具体的版本(version)修订版本(revision) 可具有标识(identification) 以便明确地被引用和指称其中的状态。

  阶段(phase) 是连续的版本的一种标识,描述一个分支在一个时期内版本的发布状态。

  当前位于 Alpha 非正式发布阶段。

迭代阶段

  迭代阶段(iteration phase) 是工程意义上的版本标识,适用于所有在开发、测试和维护生命周期中的分支,包括仅包含开发和测试版本的场合。

  PreAlpha 阶段不对 API 的稳定性进行任何保证。

  Alpha 阶段尽量保证公开 API 的非向后兼容的修改最小化,但仍然不保证兼容。

  Beta 阶段保证主要项目的 API 向后兼容性。非兼容的改动不再在当前迭代周期内引入,否则视为缺陷。

  Beta 阶段开始提供面向非开发者的部署方案。

  允许在每个迭代周期中按以上顺序补充新的阶段。

  关于项目内的提交版本(revision) ,详见项目文档 doc/Dependencies.txt 的约定。

发布阶段

  发布阶段(release phase) 使用的版本标识是被用户用于区分产品特性的标识。

  正式标记形式为以 V 起始,跟随符合语义化版本规范语法的版本号。作为扩展,表示分支的版本号中的 .y.y.z 以及表示具体版本的非可选部分的后缀 .0.0.0 可省略。对省略后缀 .0.0.0 的具体版本号,视为和不省略这些后缀的版本具有等价的含义。

注释 标记的大小写敏感。

  发布(release) 提供可重复分发(redistributable) 的、和版本控制机制相对独立的、集成的项目输出,通常以包(package) 为单位。

  典型的发布的包可以是:

  • 源代码包。
  • (二进制)目标文件包。

  公开提供版本标识发布的版本是发布版本(released version)

  为发布准备的分支发布分支(relase branch) 。发布分支可使用语义化版本的版本号标记。

  语义化版本之间能确定一个全序,即版本顺序(version order) ,允许同一个项目目标的任意的不同版本之间全局地比较大小,以确定版本演进的拓扑顺序。

  版本顺序不一定和发布时间顺序完全对应,但只有一个版本组件的不同的两个语义化版本之间,顺序和发布顺序应保持一致。

原理 序是反自反(irreflexive) 的。这保证了同一个发布分支版本演进的因果性(causality) 。

  版本号同时符合语义化版本规范的语义:在语言实现保持兼容的前提下,相同主版本(major version) 的发布应保持公开的命令行调用接口、API 和 ABI 向后兼容。主版本变化可引入不和先前迭代周期中保证兼容的改动。

  由公开的兼容性约定,除非另行指定,作为公开的命令行接口的工具在相邻的次版本(minor version) 之间向后兼容。一般地,若工具实现的公开功能被继续支持,命令行工具不在提供向后兼容功能的替代工具前被移除。

注意 非标记为发布版本的开发版本不需要保证以上要求。

  不兼容未来计划的改动的特性可被标记为废弃以表示可能在之后不继续支持。

非正式版

  非正式版的版本号小于 1.0.0 ,不保证任何 ABI(应用程序二进制接口)兼容性。

  所有非正式版共享一组迭代阶段。

正式版

  正式版的初始版本号为 1.0.0

  正式版提供版本检查的 API 。

  正式版对应的开发和测试版本使用以上除 PreAlpha 以外的阶段,具有上述相同的要求。

支持策略

  支持策略基于发布规则。

  当前仅对最新的非正式版及版本库活动开发分支上的最新提交版本(tip) 提供公开支持。

  正式版适用的规则另行具体指定。

持续集成

  持续集成(continuous integration) 是支持使项目保持持续演进的开发流程和实践。

构建

  提供有限的构建自动化(build automation) 支持。

  关于使用的构建工具,参见先决条件,并参见构建过程。

  在宿主环境下使用项目内自行开发的工具支持,参见 SHBuildSysroot

里程碑和路线图

  本节略述发布版本的参照目标,包括重要的特性实现。

  关于包含较详细特性变更说明的发布版本的发布注记(release note) ,参见版本库中的文档 doc/Dependencies.txt ;关于提交版本的详细变更记录(change log) ,参见项目文档 doc/ChangeLog.*.txt

已实现的重要特性

PreAlpha

  PreAlpha 阶段实现平台中立的基础设施和 GUI 。每个发布版本包含重要的新增特性。

  • PreAlpha 1 实现可在 DS 和 DeSmuME 运行的 GUI 程序。
  • PreAlpha 2 完善 GUI 部件并提供框架。
  • PreAlpha 3 支持 MinGW32 移植和动态库:平台中立设计的验证实现。
  • PreAlpha 4 支持基于 NPL 的配置设置、构建工具 SHBuild 和运行时加载的 GUI 。
  • PreAlpha 5 支持桌面程序特性:界面风格、动画及分离构建等。

Alpha

  Alpha 阶段仍包含若干重要特性改进,但发布版本不再以添加单一特性为重点,而侧重提升环境的整体可用性。

  • V0.6 完善项目结构并提升宿主环境下的开发可用性支持。
    • 添加整体测试。
    • 添加桌面环境 YDE 。
      • 当前主要用于示例。
    • 添加和完善若干专用于支持开发的工具。
      • 添加 ProjectGenerator ,支持生成 Code::Blocks 项目。
      • 支持宿主环境下的 SHBuild 自举 Sysroot 部署。
  • V0.7 完善各个平台的实现行为和互操作接口。
    • 替换 DS 的文件系统实现。
    • 支持宿主环境下的 YSLib 多个映像实例共存。
      • 每个映像实例包括动态库和配置文件等运行时支持环境
      • 映像实例可以是 Sysroot ,也可以手动部署。
      • 不要求映像实例具有相同的版本。
      • 当前对环境变量透明;必要时,仍需手动指定 PATH
      • 当前全面支持仅限 MinGW32 :配置位置不再依赖当前工作目录。
    • 按独立的语言解释实现添加 NPL 接口。
  • V0.8 完善内部互操作性和部署。
    • 改进运行时对象操作 API 。
    • 扩充 NPLA/NPLA1 。
      • 设计和实现新的求值规则。
      • 提供较完整的 REPL 支持。
    • 在 SHBuild 中使用 NPLA1 代替系统默认的 shell 环境,用于实现部分 Sysroot 部署。
  • V0.9 增强 NPL 和部署环境。
    • 继续完善 NPL 接口并提升实现性能。
      • 默认使用异步调用实现,避免嵌套调用过深时的未定义行为,并支持 PTC 。
      • 节点和环境等数据结构支持分配器。
      • 显著优化分析器和求值的性能。
      • 添加 NPLA1 源代码信息支持,并增强错误信息。
      • 添加更多的标准库操作。
    • 增强配置管理。
      • 支持后备路径。
      • 支持检测类 FHS 的文件系统布局。
    • Sysroot 安装构建脚本。

重点计划目标

  • V0.10 计划进一步增强 NPL 和构建环境。
    • 继续完善 NPL 接口并提升实现性能。
    • 支持多个运行时实例交互。
    • 基于 Sysroot 的多平台自动化部署方案。

待定事项(TODOs)

  版本发布的待定事项参照上述目标。

  贡献到问题跟踪系统的内容可能包含特性请求和没有立刻解决的缺陷记录,其实现会被考虑列入待定事项。

  完整列表当前仅被内部参照,没有公开。

版本发布记录

  发布页可能包括后续开发信息。

  关于被发布的资源,参见归档

  发布页基于历史因素归档。链接可能失效。

PreAlpha 发布页

PreAlpha 1

PreAlpha 2

PreAlpha 3

PreAlpha 4

PreAlpha 5

Alpha 发布页

0.6

0.7

版本日程记录

  记录的时间来自存档和提交记录,使用时区 UTC+8 。

  发布版本的计划时间为截止时间。评估计划完成情况时,实际时间应早于此时间。

PreAlpha

  早期开发版本计划未定。发布版本为每 100 个开发更新版本中形成。

  计划(追溯)及里程碑版本:

  • PreAlpha 0 2009-02 W3
    • PALibTest: 2009-10-10
  • PreAlpha 1 2010-03 W1
    • YSTest b98: 2010-05-05
  • 使用 Mercurial
    • YSLib b132 2010-07-13

  使用版本控制,开发更新版本对应提交版本。提交版本仅主分支版本。更新频率逐步控制为 8 提交版本/月,其中月以自然月计,折合 4 周工作量。

  计划及实际发布版本:

  • PreAlpha 2 2011-03 W4
    • YSLib b200: 2011-04-11
  • PreAlpha 3 2012-04 W2
    • YSLib b300: 2012-04-12
  • PreAlpha 4 2013-04 W4
    • YSLib b400: 2013-04-24
  • PreAlpha 5 2014-05 W2
    • YSLib b500: 2014-05-30

Alpha

  保持提交和发布版本更新频率不变。

  计划及实际发布版本:

  • V0.6 2015-05 W4
    • YSLib b600: 2015-05-26
  • V0.7 2016-06 W2
    • YSLib b700: 2016-06-11

  为便于特性整合和进度统计,2017 Q2 计划提交和发布版本基准周期增加 1 倍。

  新的发布计划日程安排于 2017-07-01 启用。追溯 Alpha 发布历史并调整截止日程:

  • V0.6 2016-06 W2
  • V0.7 2018-07 W2

  使用以下日程同步计划过渡到新的截止日期:

  • 同步目标外推 4 次,并对每个发布版本递增添加 4 周发布准备,至 V0.11 2027-01 W2 。
  • 计划使用在新的基准周期外的至多和原提交周期相同的额外缓冲时间和截止日程同步。
    • 因此,提交版本周期至多为原周期的 3 倍;即更新频率控制为至少 8/3 提交版本/月 。
  • 同时,抽取 6 周用于 V0.8 发布准备。
    • 原计划外推 V0.8 发布时间为 2017-06 W4 ,推迟后为 2017-08 W2 。
    • 新计划外推 1 次 2018-08 W2 ,同步版本后相差 24 月,补充到后续发布版本中。
  • V0.10 计划周期起每发布版本补充 8 周缓冲准备时间。
    • 原计划外推 V0.10 发布时间为 2023-12 W2 ,推迟后为 2024-12 W2 。
    • 新计划外推 1 次 2027-01 W2 ,同步版本后相差 4 月,补充到后续发布版本中。

  随之改订的计划及待定发布版本:

  • V0.8 2017-08 W2
    • YSLib b800: 2017-08-10
  • V0.9 2020-10 W2
    • YSLib b900: 2020-10-13
  • V0.10 2024-02 W2
    • YSLib b1000: TBD
  • V0.11 2027-05 W2
    • YSLib b1100: TBD

Beta

  待定。

概要

  归档文件目前存放于以下位置:

注意 作为归档的压缩包文件一般统一使用 7z ,但不同的归档使用不同的压缩算法和选项,对系统资源要求不尽相同。作为极端情况,V0.6 后的 doc 下的压缩包使用具有 1.5GiB 字典的 PPMd 达到约 5% 的压缩比,这一般也要求解压缩时需预留 20 倍压缩包大小的空余空间,以及 1.5GiB 空余(物理)内存

命名规约

  文件名使用 [平台环境标识-]包名-版本[-配置].后缀 。“[]”中为可选项。特定平台的目录名可参照此规约。

  对于二进制文件和库文件,平台环境标识使用目标三元组分类:archc-OS-toolchain ;不限定体系结构的使用 "any" 。

  发布时若文件没有更新,则不另行更新版本。若某个包不存在预期的版本,尝试使用之前最近的版本代替。

顶级目录结构

  • src 可用于直接构建的源代码(包括外部依赖项的二进制库文件)包
  • example 示例代码
  • doc 文档(打包的 Doxygen 文档)
  • any 体系结构中立的文件(头文件和数据文件)
  • arm-ds-eabi DS 平台文件
  • i686-w64-mingw32 Win32 平台文件
  • arm-linux-androideabi Android ARM 平台文件
  • x86_64-linux-gnu Linux x64 平台文件

注意 因为包含所有平台的库文件过大且文件重复,考虑到存储和传输开销,V0.9 起不提供 YSLib 整体的源代码包(位于 src)。请直接参照获取源代码中的来源,同步版本库或下载源代码,再下载所需平台的外部依赖项的归档文件,并按开发说明存储到相应位置。Sysroot 归档中仍包含对应的外部依赖项。

包概要

以下为各个具体体系结构目录下的包的概要(并不一定保证每个体系结构具有所有这些包)。

  • External 外部依赖项
  • Sysroot 打包的 Sysroot 文件
  • YSTest YSTest 二进制文件
  • YDE 二进制文件

  内容详见以下二进制文件说明。

  以下为 any 目录下的包的概要。

  • yslib-data YSLib 运行时依赖的数据文件,参见运行说明
  • ybase-header YBase 头文件
  • yframework-header YFramework 头文件

V0.6 起不单独提供头文件,可使用源代码包或 Sysroot 包代替。

  此外,源代码包名以 -src 为后缀,在 src 目录下;而 Doxygen-html 是通过 Doxygen 生成的 HTML/XHTML 文档的包名,在 doc 目录下。

二进制文件说明

  Beta 阶段前,二进制文件不用于对所有被支持的平台提供完整可用的环境。

  除以下另行指定,V0.6(build 600) 起,仅第一类支持(支持等级详见 YSLib 项目文档 doc/ProjectRules.txt )且具有 Sysroot 的平台配置提供完整的二进制文件,包括库和示例项目等。

External

  单独构建外部依赖项,一般是二进制库文件。对所有支持的平台提供。

  V0.6(build 600) 起版本库中不再保持二进制文件。源代码包 src-*.7z 中包括已经编译的外部依赖项。此外,各个平台目录下的 External-*.7z 单独对依赖项打包,可参照前文中的位置自行放置。

Sysroot

  自 build 600 起提供在 release 配置下构建部署的 MinGW Sysroot ,包名为 Sysroot

  因为归档文件限制,符号链接压缩为空文件。如需使用符号链接位置的库,可以手动恢复,流程参见构建脚本

YSTest

  源代码中默认构建的测试用示例项目,按内容命名为 YReader 。

YDE

  自 build 600 起提供 release 配置下在 Sysroot 上构建的 YDE 合集,包名为 YDE

  其中的可执行文件依赖 YBase 和 YFramework 动态库。

构建环境

  这里提供了 YSLib 历史和当前版本使用的构建环境相关的归档,其中 YSLib 目录即上述发布仓库。

基本环境和工具链

  仅提供宿主环境操作系统为 Windows 的二进制文件。

  这些资源仅作为存档以重现历史版本的构建。一般开发建议首先尝试使用最新版本的构建环境。

MinGW32 目标和宿主环境

DS

Android

Linux

文档工具

外部依赖项的源代码

SHBuild

  位于 Tools/SHBuild ,是可以用来递归遍历目录编译链接本机程序和调用其它相关功能的命令行工具。当前仅支持 MinGW32/MinGW64 环境。其它平台详见以下章节。

  直接(不带参数)运行 SHBuild 查询使用方法和选项等说明。

构建

  本节说明 SHBuild 的构建。关于使用 SHBuild 构建用户程序,参见前述概要和之后的章节。

  除非另行指定,其它章节也适用于本节内构建的非最终阶段 SHBuild 。本章仅补充仅适用于这些 SHBuild 的特点和功能。

  关于使用 bash 构建库、SHBuild 、测试项目和基于 SHBuild 上的用户程序,参见关于 MinGW Sysroot 的说明

  当前只支持本机构建

  除了指定调用编译器等工具的名称外的其它情形对 SHBuild 是透明(transparent) 的,即 SHBuild 不使用目标平台的标识执行不同逻辑。这样,也可能不经修改调用命令的情况下成功交叉构建,但当前未测试。

引导

  SHBuild 支持引导(bootstrapping) ,即在受支持的构建环境中直接构建 SHBuild ,不依赖预先构建的 SHBuild 二进制映像。

  运行脚本 Tools/Scripts/SHBuild-build.sh 生成 SHBuild 可执行文件。

  脚本直接选取 YSLib 中的源文件作为依赖进行构建。因为直接调用编译器驱动链接,不支持显式指定并行编译,相对比较慢。编译的结果是静态链接的,二进制映像较大。

  这个过程只需要 C++ 工具链和 shell ,不需要直接调用 make ,但 GCC 使用 -flto 进行优化仍然需要 GNU make 。

  如能成功使用以下的多阶段构建方法(会按需调用引导脚本),一般不建议使用直接构建的版本。

自举

  SHBuild 支持自举(self hosting) ,即可使用构建的 SHBuild 继续构建自身。

  除需 SHBuild 可执行程序外,对外部环境的要求和引导一致。

  SHBuild 一直使用简单的源代码结构,设计初期即支持自举,现时仍不限制自举需要的版本。但由于仅测试相同版本的自举构建,因此一般建议使用相同版本。

  以下多阶段构建在引导的基础上自举构建 SHBuild 。

  此外,也可以直接使用 SHBuild-self-host.shSHBuild-self-host-DLL.sh 进行直接自举。这两个脚本主要用于内部测试。

多阶段构建

  使用工具脚本安装 Sysroot 时,按需构建 SHBuild 。当前这个过程分为两步:

  运行Sysroot 安装脚本可直接完成以下多个阶段的构建和目标程序的部署(deployment) :

  • Stage 1 ,即第一阶段:引导
    • 这个阶段运行 Tools/Scripts/SHBuild-build.sh 构建 stage 1 SHBuild 。
      • 若已存在构建了的 stage 1 SHBuild 可执行程序,Sysroot 安装脚本不再重新构建 stage 1 SHBuild 。
        • 可指定非空的 SHBuild_Rebuild_S1 环境变量值以要求 Sysroot 安装脚本无视已存在的 stage 1 SHBuild 的可执行程序而重新构建。
      • 脚本检查 GNU parallel 的可用性。默认使用 GNU parallel 并行构建。
      • 原理 为可维护性,stage 1 SHBuild 的构建直接使用静态链接,且过程依赖避免系统库(工具链提供的环境)以外的外部二进制依赖项和网络连接。
        • 不依赖外部工具的串行构建可能相当费时,但仍可被接受。
    • 引导结果为 stage 1 SHBuild 程序,主要用于下一阶段使用,不保证具有所有 SHBuild 的功能特性。
      • 可执行程序不被另行部署。
      • 一般不建议直接使用。
    • 原理 因为 stage 1 的目的是引导宿主构建的 SHBuild 并构建 YSLib 和其它依赖 YSLib 的应用,所以使用不依赖 YSLib 项目输出的原始的宿主环境
      • 依照开发说明配置的宿主环境是受支持的可用于运行构建工具的环境作为 stage 1 构建和运行环境。
        • 构建时依赖这个环境的脚本运行环境运行构建脚本,并依赖其中的工具链进行编译链接。
      • 显然地,stage 1 不使用已被部署的 SHBuild 可执行程序。
      • 因为不假设 NPLA1 实现(默认由 SHBuild 提供)的可用性,构建 stage 1 SHBuild 的脚本shell 脚本而非 NPLA1 脚本
  • Stage 2 ,即第二阶段:自举
    • 这个阶段在 Sysroot 安装脚本内调用成功引导的 stage 1 SHBuild 运行 NPLA1 脚本程序继续构建。
      • 继续构建通过运行 Tools/Scripts/SHBuild-YSLib-build.txt 实现,步骤包括:
        • 构建 YBase 和 YFramework 库。
        • 自举构建 SHBuild ,链接到之前构建的 YFramework 和 YBase 动态库。
      • Sysroot 安装脚本可通过 SHBuild 环境变量指定 YSLib 库构建过程中使用的 SHBuild 命令。
        • 若不指定变量 SHBuild ,默认值是(先前应已被检查并确保按需构建的)stage 1 SHBuild 的路径。
        • 原理 默认不需要另行部署 SHBuild 即可完成自举和之后的安装。但若在当前宿主环境中已存在可用的 SHBuild 可执行程序,指定 SHBuild 可避免 Sysroot 安装脚本中默认的不必要的 stage 1 SHBuild 构建。
    • 自举结果为 stage 2 SHBuild 。
      • 这是当前最终生成的 SHBuild ,链接和运行依赖上述 YBase 和 YFramework 动态库。
      • 当前 stage 2 SHBuild 总是需要 release 动态库,因此除非先前已部署(足够新版本的兼容的)对应配置的库,不包含这个目标时 stage 2 SHBuild 会符合预期地构建失败。
    • 原理 构建 YSLib 库是整个 Sysroot 环境安装的主要目标,是构建 stage 2 SHBuild 前的必要步骤,整体一般比构建 stage 1 SHBuild 更加费时,但是:
      • 借助 SHBuild ,这些构建目标的编译过程是确保可并行的。
      • NPLA1 脚本接收的环境变量可通过调用 Sysroot 安装脚本指定,允许更灵活地控制构建使用的并行任务数等选项,且同时允许选择性仅构建和安装部分构建配置(包括 debug 或 release 模式,静态或动态库)的目标而节约开销。
  • 使用 SHBuild 运行当前 YSLib 存储库中的特定的 NPLA1 脚本可选地构建其它目标,包括 Tools 中的 SHBuild 以外的二进制工具。
    • 默认使用新近生成的 stage 2 SHBuild 可执行程序。
    • 可通过 SHBuild 环境变量指定使用的 SHBuild 命令以被 NPLA1 脚本递归地调用。
    • 这同时作为直接的部署后环境可用性测试,因而不提供指定其它 SHBuild 工具的选项。
  • 安装 stage 2 SHBuild 和(可选的)其它工具。

  两个阶段需要的外部环境对应称为 stage 1 环境和 stage 2 环境;部署 stage 2 SHBuild(和可选的目标)后的环境是部署后环境。

  作为更一般的开发环境和最终用户部署基于 Sysroot 的 YSLib 的应用的运行环境,stage 2 和部署后环境相对 stage 1 环境具有更多的假设和更少的限制:

  • 用于构建时,假定 PATH 中存在兼容的 SHBuild 可执行程序的命令。
    • 在 Sysroot 构建 YSLib 的库时,默认使用先前 stage 1 SHBuild ,其它情形默认使用 stage 2 SHbuild 。
    • 应确保 SHBuild 程序和使用的 NPLA1 脚本的版本匹配,一般来自同一个 Sysroot 安装。
      • SHBuild 没有与更新版本的脚本匹配,运行可能失败,这不被支持。
        • 此时仍建议重新构建 stage 1 SHBuild ;参见以上 stage 1 的说明。
      • 但除非另行指定,NPLA1 脚本的内容不依赖具体实现环境,且仅通过 SHBuild 调用。
      • 这些实际脚本通过可直接复制到兼容的安装的位置部署。
        • 但这些位置不保证作为公开接口而保持不变;同时需注意在运行环境中具有可读权限。
      • 因此除非必要,一般仍建议使用 Sysroot 默认配置进行部署。
    • 除非另行指定,Sysroot 构建不依赖 stage 1 和 stage 2 的 SHBuild 中可能具有的差异。
      • 因此,可(通过环境变量)指定任意的预先安装的和 NPLA1 脚本匹配版本的 SHBuild 程序。
    • 一般建议开发环境中把某个 Sysroot 的 bin 目录加入 PATH 环境变量,以使用其中的 SHBuild 命令。
      • 使用这个环境开发时,运行环境和 stage 2 构建环境相同,且可使用 stage 2 SHBuild 和其它工具。
      • 需要使用不同的 SHBuild 程序时仍可通过 SHBuild 环境变量指定。否则,变量 SHBuild 的默认值是 SHBuild ,因此 PATH 环境变量应存在名为 SHBuild 可执行程序。这被这个环境中默认满足。
      • 在 Windows 系统中,被 Sysroot 构建和部署的 SHBuild 的可执行文件名总是 SHBuild.exe 。除非有同名的目录(在部署中不会出现),在 Windows 的命令行运行这个可执行文件不需要后缀。
      • 在其它运行环境中,被 Sysroot 部署的 SHBuild 的可执行文件通常是 SHBuild 。但在此之前,为避免和同名的目录冲突,使用 SHBuild 构建时直接得到的可执行文件总是带有 .exe 扩展名
  • 这些环境使用 NPLA1 脚本而不是 stage 1 的 shell 脚本提供可用的构建功能。

  由于多平台构建的自然要求,这些环境需要相同(本机构建),或至少保持兼容性。当前工具脚本没有另行显示指定使用不同平台的接口,因此只支持本机构建。

运行

  通过以下方式调用 SHBuild 命令输出帮助消息并退出:

  • 没有命令行参数。
  • 使用 -h--help 作为命令行参数。

  通过以下方式调用 SHBuild 命令输出版本信息并退出:

  • 使用 -V--version 作为命令行参数。

  其它情形调用 SHBuild ,使用参数执行命令,支持不同的运行模式:

  • 构建模式:调用递归扫描指定的目录以调用构建后端工具。
  • 命令模式:执行内建的功能。

  命令模式以 -xcmd, 起始的选项指定。其它情形的运行使用构建模式。

  命令行参数 -- 钱的参数中,以 -x 起始的特定选项(详见帮助消息)被作为 SHBuild 选项。命令行参数 -- 后的参数不被识别为 SHBuild 选项,而被传递给后端或作为命令模式的参数。

  以下仅列出部分选项。关于环境、选项和退出状态等的详细说明参见帮助消息。

构建模式

  构建模式递归扫描指定的源代码目录,以其中符合内建规则要求的文件作为输入,调用相应的后端命令行构建。构建包括对符合内建规则的输入的编译,以及对得到的目标文件进行链接。除 SHBuild 选项的命令行参数直接传除递给后端编译器。若需要构建复杂项目,可以使用其它的脚本支持。

  调用的构建后端工具的名称以及链接器命令行选项可使用环境变量指定。此外,若环境变量 SHBuild_CFLAGSSHBuild_CXXFLAGS 被定义为非空值,调用 C 和 C++ 编译器命令行时,变量的值会先于生成的命令选项以及上述命令行选项被传递。关于支持的环境变量,详见运行 SHBuild 的说明。

  调用的后端命令行的不同的命令行选项之间应确保以空白符间隔。环境变量确定的选项不被检查;由 SHBuild 生成的命令行选项之间以一个空格分隔。

  使用 SHBuild 构建若得到的库文件,则按宿主平台确定添加的文件扩展名;而可执行文件总是带有 .exe 后缀,以避免递归扫描生成的输出文件目录和可执行文件重名导致无法生成。

  构建模式支持以 -xdef, 起始的选项指定变量配置,可覆盖环境的值。

  通过开发脚本等方式部署 SHBuild 输出的文件时,可以调整安装的文件名。

内建规则

  遍历扫描目录时,不需要使用领域特定语言的外部脚本,SHBuild 识别名称符合特定模式串的文件。

  当前支持后缀名区分源文件。

  文件名符合以下通配符模式的文件视为 C 源文件:

  • *.c

  文件名符合以下通配符模式的文件视为 C++ 源文件:

  • *.cpp
  • *.cc
  • *.cxx

  对以上文件,对应的内建 C 和 C++ 编译器规则分别被调用。其它文件不被视为源文件。

  和 make 类似,SHBuild 的内建规则依赖文件修改时间(mtime) 判断一个目标是否最新。

  内建规则使用的编译器和环境变量参见帮助消息。

命令模式

  参见帮助消息。

构建应用程序

  使用 SHBuild 构建应用程序所需的环境和多阶段构建的最后一个阶段的相同。当前和自举时相同,即 stage 2 环境。

其它平台

  除构建 stage 1 SHBuild 外,当前未正式支持 Linux 。

  因为 SHBuild 本身和直接依赖的代码已经保证了可移植性,所以静态链接可以成功。得到的可执行文件除了没有扩展名,用法和前述 MinGW 下相同。

  使用进一步编译 YFramework 可能会出错,因为 YFramework 中宿主 GUI 支持未在 Linux 上实现。这不影响已经构建的 SHBuild 的可用性。

已知限制 若系统时间不正确,可能导致冗余的重复构建或无法构建。若文件时间戳无法被正确更新(如编译器或者文件系统实现问题导致无法在命令执行后正确地更新被修改的文件时间,或者不恰当的缓存配置导致修改时间没有立即更新),可能导致冗余的重复构建。

  当前不支持上述以外的其它平台。

NPL 支持

  SHBuild 运行 -xcmd,RunNPL-xcmd,RunNPLFile 命令支持解释 NPLA1 翻译单元。

  调用方式详见帮助文本。

  后者的翻译单元为文本文件。当前支持文件头可选的 UTF BOM 。**若找到 BOM ,直接跳过继续读取文件内容。在 SHBuild 中不假设文件编码,直接以窄字符流透明地处理。后续编码内容格式按 YFramework 提供的当前实现的方式支持。

  因此,一般应使用 UTF-8 + BOM ,或不带 BOM 的 UTF-8 作为编码。

应用

  当前 stage 1 SHBuild 构建 YSLib 库使用脚本已以此方式实现。Shell 脚本调用 stage 1 SHBuild 解释这些脚本,其中进一步调用 stage 1 SHBuild 执行其它构建命令。

RevisionPatcher

  位于 Tools/RevisionPatcher ,是开发过程中用于修改版本工具。

  当前只支持从标准输入中提取版本号,内容要求为补丁文件。

  默认使用标准输出打印结果,内容为从补丁文件提取的目标文件名以及新的版本号。

  不修改任何其它文件。

  版本号通过补丁内的信息计算。使用规则参见 YSLib 项目文档 doc/ProjectRules.txt

  仅测试了 hg diff 导出的补丁文件。

构建

  依赖 YBase 和 YFramework 库,没有特别的构建支持,可以使用 stage 2 SHBuild ,详见 sysroot 安装脚本

清单(manifest) 文件

  因为文件名包含 patchWindows 可能默认要求以管理员权限运行 。安装脚本使用 manifest 文件指定免除此需要。

RevisionPatcher

  位于 Tools/SXML2XML ,用于转换 SXML 文档到 XML 文档。

  当前只支持 UTF-8 编码,不支持 XML 命名空间。

  默认使用标准输出打印结果。

构建

  依赖 YBase 和 YFramework 库,没有特别的构建支持,可以使用 stage 2 SHBuild ,详见 sysroot 安装脚本

ProjectGenerator

  位于 Tools/ProjectGenerator ,用于生成项目文件。

  当前只支持 UTF-8 编码的 Code::Blocks .cbp 文件。

  使用标准输出打印结果。

构建

  依赖 YBase 和 YFramework 库,没有特别的构建支持,可以使用 stage 2 SHBuild ,详见 sysroot 安装脚本

概述

  YSLib 项目提供一系列工具脚本。这些脚本主要集中位于 ToolsTools/Scripts 。其余特定局部用途的脚本可能位于其它目录,这些脚本可能依赖版本库内的工具脚本或 YSLib 安装时部署的工具脚本。

  关于脚本的解释环境和其它一般规则,参见关于脚本的开发说明。关于 shell 脚本,同时参见 shell 语言使用规约

  本文档附加约定,除非另行指定:

  • 公开的 shell 脚本和 NPLA1 脚本被 Tools/install-sysroot.sh 分别部署到安装路径下的 binshare/NPLA1 目录下。
  • 用于构建选项的环境配置外,提供的 shell 变量可能设置为只读。
  • 脚本引入不具有后缀 _ 的名称若不是被导出的变量,可在脚本之间使用,满足关于名称的约束,但不保证接口稳定性。

注释 维护者和开发者需要阅读开发说明,以保持脚本程序符合文档的描述。其中的一些信息(如关于环境变量等运行时环境的描述)也可能提供对脚本的非开发者用户有帮助的说明,作为运行环境中说明的补充。

Tools

  未归类的工具目录。这个目录的脚本是可用于整个项目或非特定子项目的工具脚本,如公用的构建脚本。

  工具脚本位于版本库目录 Tools 及其子目录 Scripts 下。

  工具主要用于开发,包括构建和版本库维护。

  一部分工具脚本也可被部署到 Sysroot 。在 stage 2 SHBuild 安装后运行的脚本可依赖安装的 Sysroot 实例的文件系统布局。支持维护版本库的开发脚本应保证在此之前(不依赖 Sysroot 的实例或仅依赖构建 stage 1 后可用的有限环境)可用。

环境配置

  一些环境变量可能被可选地在外部环境或在调用脚本的其它脚本中指定。若没有被指定为非空值,则可被特定上下文按需初始化。初始化后的值可能被断言非空,断言失败则非正常地退出脚本。

  这些变量可能指定外部环境的配置。

  在开发文档中的环境变量的基础上,以下各节补充指定适用于多个脚本的变量。在对应的脚本的文档中可能补充描述。

  一些环境变量具有特定的命名模式:

  • 开发文档中的环境变量具有相同前缀的外部扩展环境变量:
    • 具有前缀 SHBuild_ :可能适用于所有构建脚本流程。
      • 注释 具有前缀 SHBuild_S1_SHBuild_S2_ 的名称不指定环境变量,而是函数名称。
    • 具有前缀 YSLib_ :指定和 YSLib 库相关的环境和配置。
    • 注释 这些前缀的变量通常在开发文档中指定。仅在个别脚本中适用或不在 shell 脚本(而仅在 NPLA1 脚本)适用时,在本文档指定扩展。
  • 具有前缀 SS_公共构建配置变量
    • 注释 环境变量名称中的 SS 前缀表示 SHBuild Settings 。
  • 具有前缀 S1_ :在 stage 1 SHBuild 构建流程中作用的变量。
    • 注释 环境变量名称中的 S1 前缀表示 Stage 1 。
  • 具有前缀 CFLAGSCXXFLAGSLDFLAGS 等:指定构建工具使用的选项。

公共构建配置变量

  以下配置行为的环境变量可在多个构建脚本中被支持且具有一致的含义:

  • SS_DebugEnv 启用脚本执行时系统环境相关的调试输出。
  • SS_DirectExtract 启用直接解压缩。
  • SS_NoParallel 不使用并行命令调用。
  • SS_Offline 离线模式:不使用互联网。
  • SS_Verbose 启用详细消息输出。

  除非另行指定,这些变量的值当且仅当非空表示启用特性。

  具体作用在具体脚本的说明中详细描述。

Stage 1 共享变量

  以下变量影响 stage 1 的多个构建脚本的行为。

  • S1_BuildConf 内部配置名称。
    • 通常指定不同的值对应不共享的构建配置组合。
    • 注释 可影响默认构建目录。
  • S1_BuildDir Stage 1 SHBuild 构建和输出文件目录路径。
  • S1_CacheFile 缓存文件名。
  • S1_DistDir Stage 1 SHBuild 可执行程序目录路径。
  • S1_SHBuild Stage 1 SHBuild 可执行文件路径。
    • 注释 Win32 平台实际的可执行文件可隐含后缀 .exe

  具体作用可在具体脚本的说明中详细描述。

Tools/install-sysroot.sh

  Sysroot 安装脚本。用于直接构建和部署基础环境。

  构建时会调用 Tools/Scripts 目录下的脚本,按需构建 stage 1 SHBuild 后间接调用 SHBuild 构建 YBaseYFramework 的静态库和动态库,再构建依赖于动态库的 SHBuild

  构建使用变量 SHBuild_BuildDir 指定的路径作为中间输出目录。

注释 另见 Tools/Scripts/SHBuild-bootstrap.sh

  构建脚本同时可安装文件,完成 Sysroot 所需文件的部署。安装的起始目标位置由称为安装路径的目录路径指定,其字符串形式去除结尾分隔符的目录文件名为安装路径前缀。安装过程在必要时可创建 Sysroot 根目录、安装路径指定的目录及其子目录。安装路径的确定方式详见以下的使用方式。

  部署时使用 stage 1 SHBuild 更新文件和目录,在必要时创建符号链接或硬链接,若失败则改为普通复制。仅当被部署的文件为中间目标启用硬链接,以免后续操作意外覆盖源文件。注意在 Windows 上创建符号链接可能因为权限不足失败,取决于用户和组策略。建议使用系统管理权限运行以避免可能的权限问题。

基本使用

  脚本接受在 stage 2 使用的 SHBuild 命令行中的选项为命令行参数,如

Tools/install-sysroot.sh -xj,2

  使用 2 个并行线程构建。

  脚本也支持变量配置构建使用的路径,默认相当于使用如下 bash 命令配置变量:

: ${SHBuild_SysRoot:="$YSLib_BaseDir/sysroot"}
: ${SHBuild_BuildDir:="$YSLib_BaseDir/build/$(SHBuild_GetBuildName)"}

  其中:

  在 stage 1 SHBuild 构建调用 NPLA1 脚本 Tools/Scripts/SHBuild-YSLib*.txt ,调用方式和接受的配置(构建目标等)、具体默认设置和注意事项见对应 shell 脚本的文档相关章节。

Tools/Scripts

  这个目录的脚本可用于整个项目或项目核心部分的构建工具使用。

  当前有以下脚本忽略重复的 .source 命令:

  • SHBuild-common.sh

  以前缀 SHBuild_ 起始的名称保留使用。

  其中,前缀 SHBuild_Env_ 总是表示环境配置的只读变量名。这些变量若未被指定,可在第一次访问时(具体时机未指定)初始化为:

  • SHBuild_Env_Arch :参见函数 SHBuild_CheckUName
  • SHBuild_Env_OS :参见函数 SHBuild_CheckUName
  • SHBuild_Env_TempDir :缓存函数 SHBuild_GetTempDir 的输出。
  • SHBuild_Env_uname :缓存命令 uname 的输出。
  • SHBuild_Env_uname_m :缓存命令 uname -m 的输出。

Tools/Scripts/GenerateProjects.sh

  调用 ProjectGenerator 生成项目文件。

  当前支持生成所有 .cbp 文件。

  要求可使用 hggit 命令取版本库根目录,否则不保证输出到正确的路径。

变量 ProjectGenerator

  调用 RevisionPatcher 的命令。默认直接使用 type -P ProjectGenerator 的结果,一般要求可执行文件在环境变量 PATH 中。

Tools/Scripts/PatchRevision.sh

  开发过程中使用 RevisionPatcher 维护源文件中版本号的脚本。

  当前只支持 Mercurial 或 Git 版本库的已添加或修改的未提交文件。

  使用的版本控制系统会被检查。通过检查的条件、确定使用的版本控制的机制及支持的控制检查的环境变量同 Tools/Scripts/SHBuild-YSLib*.txt 公用的版本控制系统支持,参见以下相关章节的说明。

  若检查都失败,则脚本出错,不再继续运行。

  脚本利用 hggit 命令把未提交的这些修改导出为补丁备份到版本库根目录的 bak.patch ,然后使用这些内容调用 RevisionPatcher 取得文件和对应的新的版本号列表,最后使用 sed 查找对应文件并更新版本号。

  脚本依赖 sed 命令。使用的 sed 应支持 -b -i 选项。可使用 Linux 或 MSYS2 的发行版中的 sed 4.8 程序。

警告 某些 Win32 版本的 sed ,如 MSYS2 MinGW64 sed 4.4 可能损坏文本文件的行尾。有些替代版本可能解决这一问题

注释 当前不检查特定版本 sed 对选项的支持。

  若没有找到 \version r 模式的版本号前缀则忽略写入版本号。写入的版本号不影响换行符。

注释 这个脚本可用于自动化。例如,在 Mercurial 仓库的 hgrc[hooks] 节中添加 precommit.PatchRevision = bash Tools/Scripts/PatchRevision.sh 可在每次提交前调用这个脚本。在 YSLib 中,仅在主分支版本中启用。

变量 PatchBegin

  匹配版本号的起始行,应为一个表示行数的正整数。默认值为 "1"

变量 PatchEnd

  匹配版本号的结束行,应为一个表示行数的正整数。默认值为 "20"

变量 RevisionPatcher

  调用 RevisionPatcher 的命令。默认值为 type -P RevisionPatcher 的结果。

注释 可执行文件可以在环境变量 PATH 中。

变量 PatchHg

  指定使用 Mercurial 。参见以上确定使用 Mercurial 或 Git 的说明。

变量 PatchGit

  指定使用 Git 。参见以上确定使用 Mercurial 或 Git 的说明。

Tools/Scripts/SHBuild-bootstrap.sh

  编译 stage 1 SHBuild 时被包含的脚本。

  脚本执行构建配置的环境初始化。其中指定静态链接需要依赖的 YSLib 源文件以及头文件路径等的必要变量。

  脚本按需设置变量 S1_BuildConf 的值,以确保部分变量之后在包含 Tools/Scripts/SHBuild-YSLib.sh 时能被按需初始化。默认值为 stage1

  • 注释 有的脚本如 Tools/install-sysroot.sh 在此之前应已初始化部分变量。其它脚本可能依赖这里的初始化。

Tools/Scripts/SHBuild-build.sh

  编译 stage 1 SHBuild 的脚本。

  以下公共构建配置变量影响脚本的特定行为:

  • SS_NoParallel 的作用当前包括:
    • 不检查并行命令的可用性。
    • 不使用并行命令构建。
  • SS_Verbose 的作用当前包括:
    • 调用命令前回显。

  使用变量 SHBuild_Output 指定输出路径。默认值为 SHBuild ,即在当前工作目录下生成名为 SHBuild 的可执行文件(视宿主平台不同可能带后缀如 Win32 带 .exe )。

  调用函数 SHBuild_CheckPCH 检查预编译头:若变量 SHBuild_NoPCH 非空则跳过预编译头,否则使用预编译头包含标准库头。预编译的头文件目标由 YBase 下的 stdinc.h ,之后构建时包含预编译头路径为 $SHBuild_PCH_stdinc_h 。后者的默认路径为当前工作目录下的 stdinc.h

  因为升级或更换编译器和/或选项,可导致预编译头文件( .gch 文件)不和生成的环境匹配而不被识别。

注意 预编译头文件不保证对不同的操作系统版本兼容,参见先决条件中 PC(Win32) 平台关于操作系统版本的说明。

  不被识别的预编译头文件通常:

  • 可引起编译器警告,并忽略预编译头文件。
    • 若仅需避免产生警告,确保编译器命令行使用恰当选项,如添加 -Wno-invalid-pch
  • 可能引起无法构建的错误。

  一般仍然需要避免使用不匹配的预编译头文件。若无法保证预编译头文件和使用的工具链和选项匹配:

  • 可设置变量 SHBuild_NoPCH 的值非空以跳过预编译头文件的使用。
  • 可以手动删除生成的预编译头文件。在没有设置变量 SHBuild_NoPCH 的情形下构建通常会默认自动生成。
    • 注释 具体的支持和生成预编译头文件的位置参见具体构建目标的说明,如 Sysroot

已知缺陷 构建时不自动更新预编译头。

  除非环境变量 SS_NoParallel 的值非空,构建前可选地检查可用的并行命令。支持如下:

  • 支持 GNU parallel 命令 parallel
  • 不支持 moreutils parallelparallel 命令的可用性会忽略。
  • 不对并行数指定选项。
    • 原理 默认应已能充分使用宿主环境的计算资源。

Tools/Scripts/SHBuild-BuildApp.sh

  应用程序构建脚本。这个脚本被保留,不再具有实际功能。

  这个脚本是公开的工具,被安装脚本部署。

Tools/Scripts/SHBuild-BuildPkg.sh

  包构建脚本。当前只支持构建应用程序,具体步骤和使用的参数参见 Tools/Script/SHBuild-BuildApp.txt

  这个脚本是公开的工具,被安装脚本部署。

Tools/Scripts/SHBuild-common.sh

  被应用程序构建脚本包含的脚本,提供公共基础功能。

  这个脚本是公开的工具,被安装脚本部署。

注意 这个脚本包含 INC_SHBuild_common 守卫变量检查,默认重复包含只被执行一次。

函数 SHBuild_Popd

  同 bash 内建 popd 但不回显标准输出。

函数 SHBuild_Pushd

  同 bash 内建 pushd 但不回显标准输出。

函数 SHBuild_Put

  使用 printf 输出非格式字符串。

  使用 $* 形式传递字符串,此时 IFS 是默认值。

函数 SHBuild_Puts

  使用 printf 输出非格式的换行的字符串。

  使用 $* 形式传递字符串,此时 IFS 是默认值。

  换行符由变量 SHBuild_EOL 指定。若值为空,则首先初始化为全局只读变量。当前默认值通过检查 $COMSPEC 是否定义,以确保 Windows 环境(包括 MSYS )使用 CR+LF ,其它情况使用 LF 。

  这个函数在可用时可用于代替 echo ,以取得对环境更好的适应性。

注意 具体的检查逻辑实现可能在以后改变。

函数 SHBuild_Puts_Err

  同函数 SHBuild_Puts,但重定向标准输出到标准错误。

函数 SHBuild_Puts_Exit

  第一参数指定错误码,以之后的参数调用函数 SHBuild_Puts_Exit,然后以错误码退出。

函数 SHBuild_Puts_Verbose

  当变量 SS_Verbose 的值非空时,调用函数 SHBuild_Puts

函数 SHBuild_AssertNonempty

  断言第一参数为名称的变量非空,否则显示出错并退出。

  使用 eval 实现。

函数 SHBuild_CheckedCall

  检查第一参数为名称的命令存在,否则显示出错并退出。

  使用 hash 实现以优化性能。

函数 SHBuild_CheckedCallSilent

  同 SHBuild_CheckedCall ,但不显示错误外的标准输出。

函数 SHBuild_InitReadonly

  断言第一参数为名称的变量非空,若空则使用 eval 对后续求值并初始化第一参数指定的只读变量。

  初始化时在当前 shell 中求值初值。

  若发生初始化且变量 SS_Verbose 的值非空,则在标准输出中显示。

已知限制 当前实现使用临时文件 /tmp/InitReadonly 暂存作为初值的调用结果。需确保这个临时文件所在的目录和这个文件可写。

原理

  保证当前 shell 中求值允许初值中调用可能修改当前 shell 环境的 shell 函数。这使一个 SHBuild_InitReadonly 调用中,间接的嵌套调用可使用先前已通过同一个调用者初始化的变量。

函数 SHBuild_2m

  接受 1 个表示路径的参数,调用 cygpath 转换路径到 Windows 混合风格路径。

  当 cygpath 不存在时返回原路径。

函数 SHBuild_2u

  接受 1 个表示路径的参数。

  调用 cygpath 转换 Windows 路径到 UNIX 路径。

  当 cygpath 不存在时返回原路径。

函数 SHBuild_EchoEscape

  当标准输出使用终端时调用 echo -ne 输出参数指定的 ANSI 转义序列。

已知缺陷 不检查 $TERM 支持。

函数 SHBuild_EchoString

  接受 2 个参数,输出转义序列指定格式的内容,包含以下步骤:

  • 使用 SHBuild_EchoEscape 输出第二参数指定的转义序列。
  • 使用 SHBuild_Put 输出第一参数的内容。
  • 使用 SHBuild_EchoEscape 输出复位格式的转义序列。

函数 SHBuild_EchoVar

  接受 2 个参数 xy ,以特定颜色显示为 x = "$y" 的形式,其中 $yy 的值。

  第二参数一般是字符串。

函数 SHBuild_EchoVar_N

  接受 1 个参数 x ,调用 SHBuild_EchoVar 显示为 x = $x 的形式。

  右侧求值时会替换参数中的 ._

  参数一般是字符串。

函数 SHBuild_EchoVarA

  接受 2 个参数 xy ,以特定颜色显示为 x = ($y) 的形式,其中 $yy 的值。

  第二参数一般是数组。

函数 SHBuild_EchoVarA_N

  接受 1 个参数 x ,调用 SHBuild_EchoVarA 显示为 x = $x 的形式。

  右侧求值时会替换参数中的 ._

  参数一般是数组。

函数 SHBuild_CheckUName

  调用 SHBuild_CheckedCall 按需初始化只读变量 SHBuild_Env_OSSHBuild_Env_Arch 的值。

  变量 SHBuild_Env_OS 的值通过分类系统的值(一般即 SHBuild_Env_uname 的值)标识操作系统:

  • OS_X :输入匹配 *Darwin* ,用于标识 OS X 系统。
  • Win32 :输入匹配 *MINGW**MSYS* ,用于标识 Windows 系统。
  • Linux :输入匹配 *Linux* ,用于标识 Linux 系统。
  • unknown :不支持的系统。

  变量 SHBuild_Env_Arch 的值通过分类输入的处理器体系结构的值(一般即 SHBuild_Env_uname_m 的值)标识体系结构:

  • x86_64 :输入匹配 x86_64i*86-64
  • i*86 :输入匹配 i*86 ,使用原值。
  • aarch64 :输入是 aarch64
  • unknown :不支持的体系结构。

  若同时指定环境变量 SHBuild_Env_ArchSHBuild_Env_OS ,不进行自动环境检测,不依赖 uname

函数 SHBuild_GetTempDir

  取临时目录的路径。

  依次检查以下环境变量的值,若非空则作为结果:

  • TMPDIR
  • TEMP
  • TMP

  若这些环境变量都没有非空值,则使用经过 SHBuild_2m 转换的 /tmp(被 POSIX.1 要求支持)作为结果。

注意 以上过程在所有平台上都一致。这是自适应环境的基本接口,因此不对环境变量的值的合法性进行判断。若结果不表示一个可访问的目录,在访问以此构造的文件路径时可能引发错误。应用程序一般需自行检查或保证使用的环境中这些路径可访问。

说明 以上检查中,支持的环境变量符合惯例(en-US) 。检查环境变量的顺序(偶然地)和一些类似功能的实现(如 MySQL 在 Windows 上)一致,和其它一些特定平台的 API(如 Win32 API )及另一些不作为公开行为的实现(如 libiberty 的 choose_tmpdir )可能不一致。被支持的环境变量可用性举例:

已知限制 不检查路径是否表示实际可写的目录。另见文件访问约定。

  当前脚本实现假定临时目录可写,不满足条件时,文件操作可能失败。脚本使用的临时文件前也不保证检查文件可写。若此文件不可写(例如,在之前被 root 等高权限用户创建),则依赖文件可写的操作可能失败。对构建脚本,这可能导致依赖临时文件进行检查判断失效,而使错误的选项被使用。

  一般地,脚本可使用特定的例程(如 Shell 脚本可选地使用在许多环境中可用的 mktemp 命令)随机化文件名减少冲突。若需要更可靠地避免上述问题,可在运行脚本前清理临时目录,或预先设置 SHBuild_GetTempDir 访问的环境变量指定确保可写的空目录,同时避免并发调用脚本导致不安全并发访问此目录中的临时文件。脚本不使用其它方法确定直接使用的临时目录,但脚本间接调用的外部工具仍可能导致不安全的访问,而无法保证可靠。

函数 SHBuild_Platform_Detect

  通过参数指定的操作系统和体系结构名,结合环境变量,确定平台名称,检查非空并返回。

  第一和第二参数分别指定操作系统名和体系结构名,接受的取值参见函数 SHBuild_CheckUName

  当前 Win32 系统外的结果和 SHBuild_CheckUName 初始化 SHBuild_Env_OS 的结果一致。处理如下:

  • 若操作系统名为 Win32
    • 若环境变量 MSYSTEM 设置为 MSYS2 支持的环境,结果对应如下:
      • MINGW64MinGW64
      • MINGW32MinGW32
      • CLANG64MinGW_Clang64
      • CLANG32MinGW_UCRT64
      • CLANGARM64MinGW_ClangARM64
      • UCRT64MinGW_UCRT64
    • 否则,若体系结构为 x86_64 ,则结果为 MinGW64
    • 否则,结果为 MinGW32
  • 否则,结果为操作系统名。

函数 SHBuild_PrepareBuild

  准备构建环境。按以下方式初始化变量使之具有非空值:

  • 调用函数 SHBuild_GetTempDir 初始化 SHBuild_Env_TempDir
  • 调用函数 SHBuild_CheckUName 初始化 SHBuild_Env_OSSHBuild_Env_Arch
  • 初始化 SHBuild_Host_OSSHBuild_Env_OS 的值。
  • 当变量 SHBuild_Host_OS 的值是 Win32 时,且环境变量 MSYSTEM 的值是 MSYS2 支持的环境支持的值,初始化 SHBuild_Host_Arch 对应的目标体系结构;否则,初始化 SHBuild_Host_ArchSHBuild_Env_Arch 的值。

函数 SHBuild_GetBuildName

  取用于进一步初始化构建路径的构建名称。

  首先调用函数 SHBuild_PrepareBuild 按需初始化变量 SHBuild_Env_ArchSHBuild_Env_OS 为非空值。

  结果为以变量 SHBuild_Env_OSSHBuild_Env_Arch 的值作为参数调用函数 SHBuild_Platform_Detect 的结果。

函数 SHBuild_BuildGCH

  构建 GNU 预编译头文件,依次执行:

  • 以第二参数指定的路径附加 .gch 后缀确定输出路径。
  • 检查输出路径是否已存在文件,若存在则视为目标已被构建,输出消息并跳过以下步骤。
  • 确保输出路径所在的目录被创建。
  • 输出开始构建的消息。
  • 硬链接第一参数指定输入的头文件路径到第二参数指定的安装路径。
  • 按第一参数指定的输入路径和输出路径调用第三个参数指定构建命令。
  • 输出构建完成的消息。

已知限制 构建命令仅支持 GNU 兼容工具链。

函数 SHBuild_CheckPCH

  检查和按需构建 GNU 预编译头文件并设置变量,依次执行:

  • 检查变量 SHBuild_NoPCH 的值,若为空值,则:
    • 以第一参数、第二参数和 $CXX -xc++-header $CXXFLAGS 调用 SHBuild_BuildGCH 生成 GNU 风格预编译头文件。
    • 设置内部变量 SHBuild_IncPCH 为合适的命令行选项的数组(以 "-include" 和头文件名作为其元素)用于包含生成的预编译头文件。
  • 否则:
    • 输出跳过消息。
    • 设置 SHBuild_IncPCH 的值为空数组。

已知限制 构建命令仅支持 GNU 兼容工具链。

函数 SHBuild_2w

  接受 1 个表示路径的参数,调用 cygpath 转换 UNIX 路径到 Windows 路径。

  当 cygpath 不存在时返回原路径。

函数 SHBuild_Install

  接受 2 个表示路径的参数,安装前者指定的文件到后者。

  首先调用 rsync ,若失败调用 cp

注意 防火墙可能导致 rsync 超时失败。

函数 SHBuild_InstallDir

  接受 2 个表示路径的参数,安装前者指定的目录到后者。

  首先调用 rsync ,若失败调用 cp

注意 防火墙可能导致 rsync 超时失败。

函数 SHBuild_Install_Exe

  接受 2 个表示路径的参数,安装前者指定的可执行文件到后者。

  首先调用 SHBuild_Install ,然后在目标上设置可执行权限。

  接受 2 个表示路径的参数,安装前者指定的文件到后者为硬链接。

  首先删除目标,其次调用 Windows 命令解释器的 mklink ,若失败调用 ln

  接受 2 个表示路径的参数,安装前者指定的可执行文件到后者为硬链接。

  首先调用 SHBuild_Install_HardLink ,然后在目标上设置可执行权限。

注意 mklink 需要 Windows Vista 后的命令解释器(cmd) 的支持。硬链接需要文件系统(如 NTFS )支持。

  接受 2 个表示路径的参数,安装前者指定的文件到后者为符号链接。

  首先删除目标,其次调用 Windows 命令解释器的 mklink ,若失败调用 ln

注意 mklink 需要 Windows Vista 后的命令解释器(cmd) 的支持。符号链接需要文件系统(如 NTFS )支持。权限不足可能导致 mklink 创建符号链接失败,可在组策略改变相关默认行为。在一些版本的系统上,可能需要进一步的配置以通过链接执行文件。

函数 SHBuild_LoadCache

  加载环境缓存。

  函数接收 2 个参数,分别是缓存文件路径和被缓存的变量名的正则表达式模式的数组。

  加载缓存通过解析文件内容验证后包含实现。

  文件的内容应为可执行的 shell 赋值代码。当前检查支持的格式如下:

  • 每行一个条目,忽略首尾空白符。
  • 若条目的形式是 if ... then; ASSIGNMENT; fi ,简化为 ASSIGNMENT 进行下一步检查,忽略其余部分。
  • 进一步地,若条目的形式是 declare -X VAR=... ,其中 X 是匹配正则表达式模式 (-|[Aagirx]+) 的属性之一,简化为 VAR 进行下一步检查,忽略其余部分。
    • 注释 作为全局变量,declare 条目一般应具有 -g 属性,否则声明为局部变量,实际没有加载配置。
  • 进一步地,VAR 应为合法的标识符,匹配正则表达式模式 [A-Za-z_][A-Za-z_0-9]*

  若任意检查失败,则缓存格式无效,内容不会被加载;否则,包含缓存文件,以执行其中的赋值代码。

  函数的返回值如下:

  • 0 :成功。
  • 1 :内容无效。
  • 2 :指定的缓存文件无法读取。

函数 SHBuild_SaveCache

  保存环境缓存。

  函数接受的参数及其含义和函数 SHBuild_LoadCache 一致。

  首先打开文件,然后解析 declare -p 结果,把其中变量名匹配第二参数中任意的正则表达式且内容满足函数 SHBuild_LoadCache 格式要求的行写入指定的缓存文件。

  写入前,替换内容中的 declare --declare -declare -g 以确保声明全局变量。

注释 不要求支持 declare -p 结果中的所有变量属性。不支持的属性被忽略。

  函数的返回值如下:

  • 0 :成功。
  • 2 :指定的缓存文件无法写入。

函数 SHBuild_GetSystemPrefix

  转换参数指定的平台名称字符串为系统前缀字符串。

  系统前缀用于在文件系统中安装部署。

  通常系统前缀因为之前仍有非空的其它前缀(如 SHBuild_SysRoot 指定的值)而不是绝对路径的前缀。因此,系统前缀以 / 起始而不需要考虑所在的环境是否符合 FHS 的问题。

  当前支持的结果包括:

  • 参数为 MinGW64 时,结果为 /mingw64
  • 参数为 MinGW32 时,结果为 /mingw32
  • 否则,结果为 /usr

  本函数的结果符合 Sysroot 中关于 Sysroot 的目录布局的约定。

注释 参数典型地来自调用函数 SHBuild_Platform_Detect 的结果。

函数 SHBuild_S1_InitConf

  初始化 stage 1 Sysroot 构建配置,依次执行:

  • 断言变量 SHBuild_ToolDir 非空。
  • 按需初始化变量 YSLib_BaseDir ,默认值为 "$SHBuild_ToolDir/../.."
    • 注释 Stage 1 环境下总是可通过脚本所在目录推断 YSLib 源代码和存储库中的其它源文件的位置。
  • 尝试以切换当前目录的方式访问 $YSLib_BaseDir
  • 按需初始化变量 SHBuild_BuildDir ,默认值为 YSLib 版本库根目录下的 build/$(SHBuild_GetBuildName)
  • 确保变量 SHBuild_BuildDir 指定的目录被创建,并尝试以切换当前目录的方式访问。
  • 变量 S1_BuildDir 的值为空,断言变量 S1_BuildConf 的值非空。
    • 注释 这排除以下的初始化默认值使用非预期路径。
  • 按需初始化变量 S1_BuildDir ,默认值为 "$SHBuild_BuildDir/.$S1_BuildConf"
  • 按需初始化变量 S1_DistDir ,默认值为 "$S1_BuildDir/.stage1"
  • 按需初始化变量 S1_SHBuild ,默认值为 "$S1_DistDir/SHBuild"
  • 确保变量 S1_BuildDir 指定的目录被创建,并尝试以切换当前目录的方式访问。
  • 确保变量 S1_DistDir 指定的目录被创建,并尝试以切换当前目录的方式访问。

  以上变量初始化后只读。

函数 SHBuild_S2_Prepare

  准备 stage 2 Sysroot 环境,依次执行:

  • 确保变量 SHBuild_SysRoot 的初始化。
    • 使用第一参数作为这个变量的默认值。若变量这个未被设置,则以默认值赋值。
  • 断言这个变量的值非空,以其值作为创建目录,若指定的目录已存在则忽略。
  • 初始化变量 SHBuild_SystemPrefix
  • 初始化变量 SR_Prefix 的值为 "$SHBuild_SysRoot$SHBuild_SystemPrefix"

函数 SHBuild_S2_Prepare_Build

  准备 stage 2 Sysroot 构建环境,依次执行

  • 以第一参数调用 SHBuild_S2_Prepare
  • 导出变量 SHBuild 的值为 "$SR_Prefix/bin/SHBuild"

Tools/Scripts/SHBuild-common-options.sh

  被应用程序构建脚本包含的基础功能,提供默认的编译器和链接器命令行选项。

  若某个变量提供默认值且执行脚本时没有非空值,则设置为脚本提供的默认值。

  这个脚本是公开的工具,被安装脚本部署。

  包含 Tools/Scripts/SHBuild-common-toolchain.sh 确定工具链。

注意 G++ 和 Clang++ 不完全兼容。以下部分变量通过 Tools/Scripts/SHBuild-common-toolchain.sh 中的例程判断 G++ 和 Clang ,并自动使用不同的选项默认值。因此直接通过名称和符号链接等方式伪装会失效而可能导致错误。

  以下所有变量仅在外部环境设置为空或未设置时提供默认值,按顺序被指定。可在外部设置为非空值以避免被本脚本中的值覆盖。以下仅列出部分相对不容易变动的默认值,其它默认值参见脚本源代码。若不需要默认值,可以提前设置非空值或在 . 指令后直接设置其它(可能为空的)值。

变量 SHBuild_Debug

  默认值为空。

  非空时,指定变量的值:

CXXFLAGS_OPT_DBG='-O0 -g -D_GLIBCXX_DEBUG_PEDANTIC'
LDFLAGS_OPT_DBG=' '

变量 C_CXXFLAGS_GC

  C/C++ 编译器生成二进制节 GC 选项。

  默认值为 -fdata-sections -ffunction-sections

  设置后会被检查是否支持,参见下文。

变量 LDFLAGS_GC

  链接器生成二进制节 GC 选项。

  默认值为 -Wl,--gc-sections

  设置后会和 C_CXXFLAGS_GC 通过 $CXX 作为编译器编译链接简单程序测试是否支持。若不支持,此变量和 C_CXXFLAGS_GC 都会被置空。

已知限制 Windows 上的工具链可能缺乏 /dev/null 的必要支持,因此此项检查使用的输出路径指定为 /tmp/null

变量 C_CXXFLAGS_PIC

  C 和 C++ 编译器共用的 PIC ( Position Independent Code ,位置无关代码)生成选项。

  默认值在 Win32 上为空,其它平台上为 -fPIC -fno-semantic-interposition

  用于保证生成的对象文件可被用于生成动态库。

变量 LDFLAGS_STRIP

  链接器剥离符号选项。

  默认值为 -s

变量 C_CXXFLAGS_EXT

  指定 C/C++ 语言扩展的选项。

  默认值和平台相关:若为 Win32 环境则为空,否则为 -D_POSIX_C_SOURCE=200809L

注释 若实现环境没有提供适当的宏定义,YFramework 中使用 POSIX 平台文件系统 API 的实现要求不被满足而可能构建失败。

变量 C_CXXFLAGS_ARCH

  C 和 C++ 编译器共用的体系结构相关选项。

  默认值为空。

  不限制具体形式,使用 G++ 时可以是 -march=native

变量 C_CXXFLAGS_COMMON

  C 和 C++ 编译器共用的公共选项。

  默认值为 -pipe $C_CXXFLAGS_GC $C_CXXFLAGS_ARCH -pedantic-errors $C_CXXFLAGS_EXT

变量 C_CXXFLAGS_OPT_LV

  C 和 C++ 编译器优化等级选项。

  默认值为 -O3

函数 SHBuild_Get_C_CXXFLAGS_WARNING

  取 C 和 C++ 编译器共用的警告命令行选项的默认值。

  结果包括以下列表中的内容:

  • -Wall
  • -Wcast-align
  • -Wdeprecated
  • -Wdeprecated-declarations
  • -Wdouble-promotion
  • -Wextra
  • -Wfloat-equal
  • -Wformat=2
  • -Winvalid-pch
  • -Wlogical-op
  • -Wmissing-declarations
  • -Wmissing-include-dirs
  • -Wmultichar
  • -Wno-format-nonliteral
  • -Wredundant-decls
  • -Wshadow
  • -Wsign-conversion
  • -Wstringop-overflow=0
  • -Wsuggest-attribute=const
  • -Wsuggest-attribute=noreturn
  • -Wsuggest-attribute=pure
  • -Wtrampolines

  通过检查 C++ 编译器和版本已知不支持的选项会被替换为其它功能近似的选项或被排除。

注释 当前仅检查 C++ 编译器,假定 C 编译器和 C++ 编译器版本对应一致(即 G++ 蕴含 GCC ,Clang++ 蕴含 Clang )。

变量 C_CXXFLAGS_WARNING

  C 和 C++ 编译器共用的警告命令行选项。

  默认值是调用函数 SHBuild_Get_C_CXXFLAGS_WARNING 得到的值。

变量 C_CXXFLAGS_IMPL_WARNING

  和特定实现相关的 C 和 C++ 编译器共用的警告命令行选项。

  默认值为空值。

变量 CXXFLAGS_IMPL_WARNING

  和特定实现相关的 C++ 编译器警告命令行选项。

  默认值为空值。

变量 CXXFLAGS_IMPL_COMMON

  和特定实现相关的 C++ 编译器一般命令行选项。

  默认值包括若干特定实现的选项。

线程命令行选项

  线程命令行选项是一组未指定名称的内部命令行选项,包含编译器选项和链接器选项的默认值。其中,编译器选项被变量 CFLAGS变量 CXXFLAGS 的默认值使用,链接器选项被变量 LDFLAGS 的默认值使用。

  默认值按以下方式检查和指定线程参数:

  • 通过 -dumpspecs 的内容检查是否匹配 mthreads: 。若成功,编译器和链接器选项添加 -mthreads
  • 否则,测试带有 -mthread 选项的构建。若(直接调用编译器驱动)构建通过,则链接器选项添加 -mthreads ,并检查编译。
    • 若编译不通过,则编译器选项添加 -D_MT ,否则添加 -mthreads
  • 否则,若通过 -dumpspecs 的内容检查匹配 no-pthread: 且带有 -pthread 时无法构建,则保持编译器和链接器选项不变。
  • 否则,编译器和链接器选项添加 -pthread

注意 不论 C 或 C++ ,当前实现在调用编译器测试构建时总是使用函数 SHBuild_CXX_TestSimple

变量 CXXFLAGS_IMPL_OPT

  和特定工具相关的 C++ 编译器优化命令行选项。

  默认值包括若干特定实现的选项。

变量 CFLAGS_STD

  指定 C 标准的编译器命令行选项。

  默认值为 -std=c11

变量 CFLAGS_WARNING

  C 编译器警告命令行选项。

  默认值包含以下列表中的内容:

  • 变量 C_CXXFLAGS_WARNING 的内容
  • 变量 C_CXXFLAGS_IMPL_WARNING 的内容

变量 CXXFLAGS_STD

  指定 C++ 标准的编译器命令行选项。

  默认值为 -std=c++11

函数 SHBuild_Get_CXXFLAGS_WARNING

  取 C++ 编译器的警告命令行选项的默认值。

  结果包括以下列表中的内容:

  • 变量 C_CXXFLAGS_WARNING 的内容
  • 变量 C_CXXFLAGS_IMPL_WARNING 的内容
  • -Wconditionally-supported
  • -Wctor-dtor-privacy
  • -Wdeprecated(仅当旧版本 GCC 中这个选项支持 C++ 但不支持 C 时)
  • -Wno-deprecated-register
  • -Wno-mismatched-tags
  • -Wno-missing-braces(仅当 Clang++ < 6.0 ,作为这个问题的变通)
  • -Wnon-virtual-dtor
  • -Woverloaded-virtual
  • -Wsign-promo
  • -Wshorten-64-to-32
  • -Wstrict_null_sentinal
  • -Wsuggest-final-methods
  • -Wsuggest-final-types
  • -Wweak-vtables
  • -Wzero-as-null-pointer-constant
  • 变量 CXXFLAGS_IMPL_WARNING 的内容

  除以上变量中的内容外,通过检查 C++ 编译器和版本已知不支持的选项会被替换为其它功能近似的选项或被排除。

变量 CXXFLAGS_WARNING

  C++ 编译器警告命令行选项。

  默认值是调用函数 SHBuild_Get_CXXFLAGS_WARNING 得到的值。

变量 CXXFLAGS_OPT_DBG

  C++ 编译器优化和调试相关的命令行选项。在未设置非空的 SHbuild_Debug 时。

  默认值包含以下列表中的内容:

  • 变量 C_CXXFLAGS_OPT_LV 的内容
  • 变量 CXXFLAGS_OPT_UseAssert 没有被设置非空值时包含 -NDEBUG
  • 变量 CXXFLAGS_IMPL_OPT 的内容
  • -fomit-frame-pointer

变量 CFLAGS

  C 编译器使用的命令行选项。

  默认值为 $CFLAGS_STD $C_CXXFLAGS_PIC $C_CXXFLAGS_COMMON $CFLAGS_WARNING $C_CXXFLAGS_IMPL_THRD_ $C_CXXFLAGS_COMMON_IMPL_ $CXXFLAGS_OPT_DBG 。其中,C_CXXFLAGS_COMMON_IMPL_编译器线程命令行选项,而 C_CXXFLAGS_COMMON_IMPL_ 是根据支持的编译器在内部定义的非公开变量。

注意 当前和 C++ 编译器选项共用 CXXFLAGS_OPT_DBG

变量 CXXFLAGS

  C++ 编译器使用的命令行选项。

  默认值为 $CXXFLAGS_STD $C_CXXFLAGS_PIC $C_CXXFLAGS_COMMON $CXXFLAGS_WARNING $C_CXXFLAGS_IMPL_THRD_ $CXXFLAGS_IMPL_COMMON $CXXFLAGS_OPT_DBG 。其中,C_CXXFLAGS_COMMON_IMPL_编译器线程命令行选项,而 CXXFLAGS_IMPL_COMMON 的默认值包含变量 C_CXXFLAGS_COMMON_IMPL_ 的内容。

变量 LDFLAGS_OPT_DBG

  链接器优化和调试相关的命令行选项。在未设置非空的 SHbuild_Debug 时。

  默认值为 $LDFLAGS_STRIP $LDFLAGS_IMPL_OPT $LDFLAGS_GC

变量 LDFLAGS

  链接器使用的命令行选项。

  默认值依次包含以下内容:

  默认使用 -Wl, 传递链接器特定的命令行。

Tools/Scripts/SHBuild-common-toolchain.sh

  被应用程序构建脚本包含的基础功能,提供默认的编译器和链接器等工具的名称。

  支持 GCC/G++ 和 Clang/Clang++ 。

  支持 ar 及与其兼容的工具 gcc-ar/llvm-ar 的自动检测。对 Clang++ 和 G++ ,分别使用 llvm-argcc-ar

  以下可在环境外部配置,在值确定后被导出:

  • CC
    • 若操作系统为 Win32MSYSTEM 指定为默认使用 llvm 工具链(即 Clang )的环境,默认值为 clang ,否则为 gcc
  • CXX
    • 若操作系统为 Win32MSYSTEM 指定为默认使用 llvm 工具链(即 Clang++ )的环境,默认值为 clang++ ,否则为 g++
  • AR
    • 默认值为变量 CXX 指定的 C++ 编译器确定的自动检测结果;若非 Clang++ 和 G++ ,则使用 ar
  • ARFLAGS
    • 默认值为 rcs
  • LD
    • 默认值为变量 CXX 的值。

  关于 MSYSTEM 指定的环境和使用的工具链的对应关系,参见 MSYS2 支持的环境;关于支持的 MSYSTEM 的值和 MSYS2 环境,另见函数 SHBuild_Platform_Detect

  以下只读变量在初始化配置时被按需初始化并断言非空:

  • SHBuild_CC_Name :C 编译器名称。
    • 在调用函数 SHBuild_CC_GetVersion 时被首先初始化。
    • 初值是 $CC 作为参数调用函数 SHBuild_CheckCC 的结果。
  • SHBuild_CC_Version :C 编译器版本号。
    • 当前未使用。
    • 初值是调用函数 SHBuild_CC_GetVersion 的结果。
  • SHBuild_CXX_Name :C++ 编译器名称。
  • SHBuild_CXX_Version :C++ 编译器版本号。
    • 在脚本 Tools/Scripts/SHBuild-common-toolchain.sh 确定 C++ 版本号前初始化。
    • 初值是调用函数 SHBuild_CXX_GetVersion 的结果。

  上述按需初始化使用函数 SHBuild_InitReadonly

注释 可通过外部执行环境指定这些变量具有非空值而跳过初始化。

  这个脚本是公开的工具,被安装脚本部署。

函数 SHBuild_CheckCompiler

  尝试以参数指定的编译参数和输入调用参数指定的编译器,并按检查结果选择和输出参数的值。

  检查编译器时,首先排除参数指定的编译器不可执行的情形,然后通过尝试编译以参数指定的源程序进行。

  前四参数分别指定编译器的路径、尝试编译的源程序、检查成功时输出的结果和检查失败时输出的结果,之后的参数指定编译选项和源文件。选项和源文件参数中,应在语言选项(如 -xc++ )后出现一次表示输入源程序的 -

注释 这允许自由安排参数中 - 的顺序。一般地,- 应出现在 -xc++ 等参数之后,而出现在链接的库选项之前(如有)。若指定链接的其它库选项在 - 之前,其中的符号在使用 GNU ld 等对出现顺序敏感的链接器时,无法在源程序中引用而链接失败。

  检查前断言前两个参数非空。指定编译选项的参数为空时,第三和第四参数可能不提供或为空。

  源程序和换行符通过管道输入编译器。

注释 对 ISO C 以及 ISO C++11 前的版本,翻译单元不以空行结尾引起未定义行为。此处指定源程序的参数可以不以换行结尾也可安全处理。

  结果是以下之一:

  • 空值(第一参数指定不可执行的路径)。
  • 第三参数(检查成功时的结果)。
  • 第四参数(检查失败时的结果)。

注意 当前不检查失败原因。编译命令调用依赖可写的临时目录。参见函数 SHBuild_GetTempDir 的已知限制。

函数 SHBuild_CheckCC

  尝试调用参数指定的编译器以检查 C 编译器风格。

  第一参数指定编译器可执行文件路径。

  结果是以下之一:

  • 空值(不支持的编译器)。
  • Clang
  • GCC

函数 SHBuild_CheckCXX

  尝试调用参数指定的编译器以检查 C++ 编译器名称。

  第一参数指定编译器可执行文件路径。

  结果是以下之一:

  • 空值(不支持的编译器)。
  • Clang++
  • G++

函数 SHBuild_CC_Test

  测试 "$CC" 指定的编译器 C 编译器命令行是否可成功调用。

  第一参数指定源程序,之后的参数指定编译选项。编译选项前隐含 -pipe -xc

  以 SHBuild_CheckCompiler 实现编译器调用。

函数 SHBuild_CC_TestSimple

  测试 "$CC" 指定的编译器 C 编译器命令行编译最小程序是否可成功调用。

  参数指定编译选项。

  以 SHBuild_CC_Test 实现编译器调用。

  使用的最小程序是 int main(void){return 0;}

函数 SHBuild_CXX_Test

  测试 "$CXX" 指定的编译器 C++ 编译器命令行是否可成功调用。

  第一参数指定源程序,之后的参数指定编译选项。编译选项前隐含 -pipe -xc++

  以 SHBuild_CheckCompiler 实现编译器调用。

函数 SHBuild_CXX_TestSimple

  测试 "$CXX" 指定的编译器 C++ 编译器命令行编译最小程序是否可成功调用。

  参数指定编译选项。

  以 SHBuild_CXX_Test 实现编译器调用。

  使用的最小程序是 int main(){}

函数 SHBuild_CC_GetVersion

  尝试调用 "$CC" 指定的编译器以管道输入的程序取 C 编译器版本号。

  结果是以下之一:

  • 成功时:GCC 或 Clang 版本号的 X.Y.Z 转换为 X * 10000 + Y * 100 + Z 的数值的字符串。
  • 失败时:空。

已知缺陷 不支持无法取得 __GNUC_PATCHLEVEL__ 的旧版本 GCC 。 已知缺陷 不支持 Apple Clang 。

函数 SHBuild_CXX_GetVersion

  尝试调用 "$CXX" 指定的编译器以管道输入的程序取 C++ 编译器版本号。

  结果是以下之一:

  • 成功时:G++ 或 Clang++ 版本号的 X.Y.Z 转换为 X * 10000 + Y * 100 + Z 的数值的字符串。
  • 失败时:空。

已知缺陷 不支持无法取得 __GNUC_PATCHLEVEL__ 的旧版本 GCC 。 已知缺陷 不支持 Apple Clang 。

Tools/Scripts/SHBuild-self-host.sh

  SHBuild 自举测试用脚本。

  使用 SHBuild 编译并静态链接构建 SHBuild 。和 stage 1 SHBuild 类似,直接使用 YSLib 源文件。

Tools/Scripts/SHBuild-self-host-DLL.sh

  SHBuild 自举测试用脚本。

  使用 SHBuild 编译并动态链接构建 SHBuild 。依赖 /usr/lib 中存在的 YFramework 和 YBase 动态库文件。

Tools/Scripts/SHBuild-YSLib.sh

  作为 stage 1 SHBuild 公共配置脚本被不同的 stage 1 构建脚本包含。

  包含脚本依次执行:

注意 这个脚本包含 INC_SHBuild_YSLib 守卫变量检查,默认重复包含只被执行一次。

变量 SHBuild_PCH_stdinc_h

  预编译头文件名称,默认值为 "$S1_BuildDir/stdinc.h"

函数 SHBuild_S1_InitializePCH

  初始化 stage 1 使用的预编译头文件:即调用:

SHBuild_CheckPCH "$INCLUDE_PCH" "$SHBuild_PCH_stdinc_h"

Tools/Scripts/SHBuild-YSLib*.txt

  构建 YSLib 用的 NPLA1 脚本,包含以下文件:

  调用方式详见 stage 1 SHBuild 中关于 NPL 支持的说明

  可通过外部环境变量配置脚本行为。

版本控制系统支持

  脚本以相同的方式检查和识别版本控制系统。

  以下环境变量提示使用 Mercurial 或 Git :

  命令可用需要满足以下条件:

  • 被检查的命令应在 $PATH 中。
    • 若在 Windows 中使用 shell ,可能需要提前设置环境以确保继承环境变量。
    • 注意 MSYS2 提供的 mercurial 包的可执行文件是脚本而不是可执行文件,不被 NPLA1 脚本调用的 Windows 命令行支持。
    • 注释 不使用 HG 环境变量,因为可能设置为不被支持的 hg 程序路径。事实上,MSYS2 的包 mercurial 安装到 /etc/profile.d/mercurial.sh 设置 HG 为脚本,覆盖从其它位置继承的 hg.exe
  • 且当前工作目录求在对应的版本库中,同时确定仓库的顶层目录路径。
    • 对 Git ,要求存在工作区。

Tools/Scripts/SHBuild-YSLib-common.txt

  Tools/Scripts/SHBuild-YSLib-common.txt 包含一些公共的库,包括支持类似 Tools/Scripts/SHBuild-common-options.shTools/Scripts/SHBuild-common-toolchain.sh 的选项以环境变量的方式配置,但 C 编译器相关的选项除外(不使用而被忽略)。

  除关于 shell 脚本NPLA1 脚本之间的一般差异外,与 Tools/Scripts/SHBuild-common-options.shTools/Scripts/SHBuild-common-toolchain.sh 的不同为:

  • 通过调用函数进入构建环境变量检测并在之后进入回调函数中构建。
    • 按需初始化变量。构建环境变量检测可能延迟访问以避免不必要初始化的值。配置时为确定变量的默认值的检查的调用顺序可能不同。
    • 使用 debug 模式时,CXXFLAGS_OPT_DBG 设置为 -O0 -g -D_GLIBCXX_DEBUG_PEDANTIC ,不再被环境变量覆盖。
    • 生成变量默认值的选项之间的空白符可能不同(通常可确保为一个空格)。
    • 附加支持构建应用的配置,在导出的变量的默认值中添加扩展的选项(参见以下相关说明)替换 shell 脚本中指定的默认值(可影响其它默认值)。
    • 附加检查 sanitizer 。参见以下相关章节的说明。
  • 支持更多变量和默认值。
    • 除非另行指定,这些变量在构建环境变量检测中使用。参见以下各节。
    • 在构建动态库或应用程序时,使用空 C_CXXFLAGS_GC 值。
      • 原理 这些选项影响编译时代码生成,不总是优化的。对静态库,因为始终无法预测可能需要排除的定义,启用这些选项是合理的。否则,通常从源代码直接确保排除冗余定义更有效。
      • 注释 LDFLAGS_GC 的值仍被保持。对使用空的 C_CXXFLAGS_GC 值构建的目标文件这没有预期的作用,但对其它情形(典型地,静态库)仍然有效。
  • 提供部分和 shell 脚本不同的函数,详见版本库中的 doc/NPL.txt

  这个脚本被 Tools/Scripts/SHBuild-YSLib-build.txt 加载,并被 Tools/install-sysroot.sh 间接调用。

  以下公共构建配置变量影响脚本的特定行为:

  • SS_DebugEnv 的作用当前包括:
    • 对环境变量修改时输出修改的变量名和对应的值。
  • SS_Verbose 的作用当前包括:
    • LDFLAGS 变量中附加 -mwindows 时提示。
    • 在安装文件时输出安装类型、目标和源。

  脚本支持外部调用这个脚本的命令行设置变量的默认值,以覆盖直接指定构建环境变量检测确定的选项,如:

CXX=clang++ CXXFLAGS='-std=c++11 -O2' Tools/install-sysroot.sh

警告 使用预编译头选项和缺陷同 Tools/Scripts/SHBuild-build.sh 。其中的特性未指定使用 shell 实现,和后者可能存在 shell 环境中可见的差异。

  这个脚本是公开的工具,被安装脚本部署。

变量 LDFLAGS_DYN_BASE

  指定动态库基础链接选项。

  默认值和平台相关:若为 Win32 环境则使用 -shared -Wl,--dll ,否则为 -shared

变量 LDFLAGS_DYN_EXTRA

  指定动态库附加链接选项。

  默认值等价于 -Wl,--no-undefined,--dynamic-list-data,--dynamic-list-cpp-new,--dynamic-list-cpp-typeinfo

变量 LDFLAGS_DYN

  指定动态库链接选项。

  默认值为 $LDFLAGS_DYN_BASE $LDFLAGS_DYN_EXTRA

变量 LIBS_RPATH

  用于指定在运行时 ELF 映像需要的动态库的路径的链接器选项。

  在 Win32 默认不设置,其它平台默认值为 -Wl,-rpath,'\$ORIGIN:\$ORIGIN/../lib'

变量 LIBPFX

  库前缀。

  在 Win32 默认不设置,其它平台默认值为 lib

变量 DSOSFX

  动态库文件名后缀。

  在 Win32 默认值为 .dll ,其它平台默认值为 .so

变量 EXESFX

  可执行文件名后缀。

  在 Win32 默认值为 .exe ,其它平台默认不设置。

Sanitizer 检查支持

  构建环境变量检测支持 sanitizer :若在变量 CFLAGSCXXFLAGSLDFLAGSSHBuild_CXXFLAGS 中包含启用被支持的 sanitizer 的选项 (以 -fsanitizer=address 起始),配置最终在 CFLAGSCXXFLAGSLDFLAGS 最后添加自动调整的选项。

  构建配置支持 ASan、TSan、MSan、UBSan 和 LSan 。Sanitizer 自身支持的系统和编译器详见 sanitizer 的文档。

  ASan 使用的选项要求和建议参见 FAQ

注意 GCC 不支持 MSan 。MSan 要求标准库使用带 MSan 的选项构建,否则会有假阳性结果

注意 具体调整的选项参见脚本具体实现,不保持构建版本之间稳定。* 注意 构建脚本不保证构建的二进制程序的具体可用性。构建的程序可能因为程序及 sanitizer 实现的缺陷运行时出错,可能另需具体排查原因修复。

扩展选项的默认值

  变量 LDFLAGS 的默认值依次包含:

  • 和 shell 脚本中相同的默认值。
  • 生成可执行程序时且要求按需调整链接器参数时,附加的值:
    • 当需要生成 Win32 子系统程序时,附加 -mwindows
  • 构建动态可执行程序时,变量 LIBS_RPATH 的内容。
  • 构建动态库或应用时,变量 LDFLAGS_DYN 的内容。

SHBuild 附加构建选项环境变量

  若回调函数中调用 SHBuild 构建,可在此之前设置扩展环境变量并调用函数 SHBuild_Extend_CallVariables 以更新被 SHBuild 使用的环境变量 LDFLAGSLIBS 的值。这些影响 SHBuild 工具调用构建工具的命令行的 SHBuild 附加构建选项环境变量,包括:

  • SHBuild_CFLAGS
  • SHBuild_CXXFLAGS
  • SHBuild_LDFLAGS
  • SHBuild_LIBS

  除非另行指定,SHBuild 附加构建选项环境变量的默认值为空,不被脚本设置。

其它函数和可在外部设置的其它变量

  详见版本库中的 doc/NPL.txt

  其它变量不被构建环境变量检测访问,而通过函数调用生效,如 SHBuild_Extend_CallVariables

Tools/Scripts/SHBuild-YSLib-build.txt

  这个脚本当前包括和安装相关的流程,实现 Tools/install-sysroot.shstage 1 SHBuild 构建后的主要逻辑

  脚本支持在构建前自动从网络安装外部依赖项二进制归档文件以准备从源代码构建安装 YFramework 库。这仅在环境变量 YSLib_DistDir 被设置非空值时启用。这需要一些外部工具命令的支持:

  脚本使用 Tools/Scripts/SHBuild-YSLib-common.txt 中提供的一些函数。

  脚本支持以下外部扩展环境变量指定构建和部署目标:

  • SHBuild_UseDebug 非空时启用构建和安装 debug 配置的库。
  • SHBuild_UseRelease 非空时启用构建和安装 release 配置的库。
  • SHBuild_NoStatic 非空时跳过静态库构建。
  • SHBuild_NoDynamic 非空时跳过动态库构建。
  • SHBuild_No3rd 非空时跳过第三方库安装。
    • 外部依赖项中的库,只安装启用的配置决定的必要的外部依赖。启用的配置由 SHBuild_UseDebugSHBuild_UseRelease 指定。
  • SHBuild_NoDev 非空时跳过可选的开发工具构建和安装。
  • SHBuild_Rebuild_S1 非空时跳过文件存在性检查,总是重新构建 stage 1 SHBuild 。
  • SHBuild_NoDev 非空时跳过 stage 2 SHBuild 后的开发工具构建和安装。
  • YSLib_DistDir 非空时应指定包含一个绝对路径作为储存 YSLib 归档的目录。

  以下公共构建配置变量影响脚本的特定行为:

  • 所有调用 Tools/Scripts/SHBuild-YSLib-common.txt 而生效的公共构建配置变量在此作用相同。
  • SS_DirectExtract 的作用包括:
    • 安装下载的归档时,直接用工具命令行覆盖对应位置的文件,而不先解压缩释放到临时目录后再更新。
  • SS_Offline 的作用包括:
    • 不检查下载工具命令的可用性。
    • 不使用网络下载资源。

  以下构建时的中间变量可被外部配置,当外部没有配置或为空值时使用默认值:

  • SHBuild_SystemPrefix :系统前缀,在 Sysroot 根路径下决定安装路径。
    • 默认值由脚本 Tools/Scripts/SHBuild-YSLib-common.txt 中的函数的调用确定:同 SHBuild_GetSystemPrefix (SHBuild_Platform_Detect SHBuild_Host_OS SHBuild_Host_Arch) 的结果。
    • 脚本 Tools/Scripts/SHBuild-common.sh 中提供 shell 脚本的等价调用:$(SHBuild_GetSystemPrefix (SHBuild_Platform_Detect "$SHBuild_Host_OS" "$SHBuild_Host_Arch"))
  • SHBuild_YF_Libs_freetype :freetype 库链接参数。
    • 默认值为 -lfreetypepkg-config --libs freetype2 的输出结果。
      • 其中存在 Sysroot libfreetype.a 时默认值为前者。
  • SHBuild_YF_Libs_FreeImage :FreeImage 库链接参数。
    • 默认值为 -lFreeImaged-lFreeImage ,对应 debug 和非 debug 配置。

  这个脚本也可能使用其它变量用于传递参数给被调用的命令,包括一些 SHBuild 预期的变量;后者作为公开接口,但其具体含义和使用不保证在不同版本间稳定

  在自举构建 stage 2 SHBuild 前构建安装 YFramework 库时,接受以下环境变量:

  • INCLUDES_freetype 指定覆盖包含路径的编译器命令行选项。
    • 默认值以 -I 起始,使用版本库目录下的 3rdparty/freetype/include 目录带有适当引号的完整路径。
  • 环境变量 SHBuild_VCS_hgSHBuild_VCS_git 指定构建时的版本控制系统,作用参见以下说明。

  在 stage 2 环境中构建其它目标时接受以下外部环境变量(部分被 SHBuild 直接以环境变量的方式接受):

  • INCLUDES :包含路径,和非 SHBuild 中的 Makefile 惯用法含义类似。
  • LDFLAGS :链接命令行选项。
  • LIBS :作为命令行选项的链接时使用的库路径。
  • LIBS_RPATH :非 Windows 平台使用的 rpath 路径。
  • SHBuild 附加构建选项环境变量:参见 Tools/Scripts/SHBuild-YSLib-common.txt 。其中变量 SHBuild_CXXFLAGS 同时作用在预编译头构建。

版本字符串

  在 stage 1 构建 YFramework 前,通过选择的版本控制系统指定确定版本字符串,可在被构建的 YFramework 库中引用。

  确定版本字符串时,检查对应的命令,具体方式详见关于版本控制系统支持的描述。若检查都失败,则版本字符串为空串。否则,使用第一个检查成功的命令生成对应的版本字符串。

已知限制 若使用 git 生成版本字符串,当前同时依赖 sed 命令。

  若版本字符串非空,则通过宏定义的方式参与之后的构建。当前影响以下源文件所在的翻译单元:

  • YFramework/source/YSLib/Core/YCoreUtilities.cpp

  在构建前,若被影响的上述翻译单元已被构建,在生成目录中对应的(以 .d 为扩展名的)依赖文件被修改,以添加对版本控制系统中的特定文件的依赖。这能使最新的版本控制系统的修改影响生成的目标代码,而无需手动修改这些翻译单元的源文件。

已知限制 当前自动更新依赖的实现同时依赖 sed -i 命令。

Tools/Scripts/SHBuild-BuildApp.txt

  NPLA1 应用程序构建脚本。可利用此工具脚本调用 SHBuild 构建特定配置(configuration) 下使用 YSLib 库和基础环境开发的应用程序。

  这个脚本是公开的工具,被安装脚本部署。

基本原理

  配置是特定用途的一组程序输出的集合。常见软件配置可以区分目标平台,是否为调试配置等。

  脚本通过设置特定的环境变量并调用 SHBuild 递归扫描指定目录完成构建。其中调用命令由环境变量 SHBuild 指定。若变量 SHBuild 为空,则假定使用脚本程序在 Sysroot 中,由 Sysroot 的布局确定的 SHBuild 的位置作为变量 SHBuild 的默认值。

  脚本支持区分 debug 和非 debug 配置以及静态和 DLL 配置。详见以下说明。

  debug 配置总称 debug 模式。非 debug 配置总称 release 模式。

调用方式

  无参数调用时,显示帮助文本。以第一参数指定配置名称,执行脚本直接一次性配置后构建。之后可选的其它参数被脚本传递给 SHBuild ,详见以下的操作说明。

使用须知

  脚本依赖 Sysroot 。

  这个脚本被 Tools/Scripts/SHBuild-BuildPkg.sh 调用。

  当前只支持构建,不支持部署。

  构建时调用的工具链命令行及配置详见 Tools/Scripts/SHBuild-YSLib-common.txt 的说明。

  需要先确保源代码可访问。注意源代码目录会被递归扫描,建议在目录中只包含所有需要构建的源文件或被包含的文件。

操作说明

  一般步骤:

  • 新建一个 GNU bash 脚本(以下称为用户构建脚本 ),调用此脚本(若无法在 PATH 找到,需要使用完整路径)。
  • 以源代码所在目录的路径作为参数,执行通过此脚本包含的 SHBuild_BuildApp 函数,等待构建完成。
  • 直接包含后的脚本仍可使用无参数调用用户构建脚本查看选项和说明。

  简化操作:也可以不创建用户构建脚本,直接在命令行中执行,例子见入门

  若有必要,在调用本脚本之前设置 SHBuild_BuildDir 变量为指定输出文件所在的目录的完整路径,如:

export SHBuild_BuildDir=$(dirname "$0"`/../build)

  上述命令行指定相对于用户构建脚本上一层目录的 build 子目录下作为基准输出路径。若不显式设置此变量,工具脚本会指定其默认值为用户构建脚本所在的目录。

  调用本脚本。脚本会自动加入必要的参数调用 SHBuild ,传递的参数依次具体如下:

  • 中间变量 SHBOPT 的值,包括根据配置决定的目录设置选项、-xid,include -xmode,2 以及用户在脚本命令行指定的剩余选项 SHBOPT_BASE
  • 传递给本脚本的配置名称以外的可选参数。
  • SHBuild_BuildApp 的值,用于编译器的库配置(包含路径以及使用 DLL 需要的宏定义 -DYF_DLL -DYB_DLL ),由脚本根据静态或动态库配置自动确定,无需重复输入类似选项。

特别注意 脚本执行以输出基准路径作为当前工作目录,需要以此为基准指定源文件路径( SHBuild 使用的 SRCPATH 参数)。

  通过脚本命令行间接传递给 SHBuild 的参数 SHBOPT_BASE 以及函数 SHBuild_BuildApp 的参数都可以进一步对构建过程进行调整,如 -xj,2 指定 2 个并行线程构建。

注意 以 SHBuild 作为 NPLA1 脚本解释器时,传递的参数可能会被 SHBuild 截获,而不被继续传递给 脚本中调用的 SHBuild 。为避免这种情形,在 -xcmd,RunNPLFile 和本的脚本文件名选项后,可加上 -- 分隔其余命令行参数。

配置设置

  传递给 SHBuild 指定使用 .配置名称 相对路径(无需另外指定 -xd, 参数)。如 -cdebug 指定输出路径为 .debug 。省略此项默认配置名为 shbuild

  脚本根据以下规则自动检测配置:

  • 若配置名称以 debug 起始,或环境变量的 SHBuild_Debug 值非空,则视为使用 debug 配置。
  • 若配置名称以 static 结束,活环境变量 SHBuild_Static 的值非空,则视为使用 static 配置。

环境变量

  环境变量 SHBuild_DebugSHBuild_Static 可按上述自动检测配置过程指定配置类型。

  默认情况下,release 配置会在链接器命令行加入 -mwindows和 debug 配置编译的程序行为不保证相同。设置非空变量 SHBuild_NoAdjustSubsystem 禁用此行为。

  脚本使用包含 YSLib 库的编译器命令行。脚本已经导出了用于链接器的包含使用 YSLib 库命令行参数的变量 LDFLAGSLIBS 。若有必要,可设置 SHBuild 附加构建选项环境变量(参见 Tools/Scripts/SHBuild-YSLib-common.txt )。

准备

  在开始教程前,掌握以下知识:

  并对以下内容有基本了解:

内容目录

基础约定

关于头文件的补充说明

  在入门中已经解释了头文件宏的使用。

  公开的头文件宏的命名是模块路径的直接应用:替换 ::_ ,加上前缀 YFM_YFM_平台名

  这些宏名直接定义在各个 include 目录的 YModules.h 中。而头文件 <YSBuild.h> 则包含了一系列的 YModules.h 和其它模块的头文件,因此可以直接包含它来简化使用。

命名空间

  YSLib 中不同的次级子项目引入不同的命名空间。

  关于每个顶级子项目下不同的命名空间参见 doc/YBase.txtdoc/YFramework.txt 以及 Doxygen 生成的文档。

注意 在以下讨论中,若没有特别说明,所有非宏名的标识符都在 namespace YSLib 中。

  注意避免命名空间使用冲突。除非是在一个很有限的块作用域内,一般而言,using namespace std; 总是不被推荐的,但 using namespace YSLib;非头文件中则是被允许的。

  在 YSLib 的头文件中,无论是 using 声明还是 using 指令或是命名空间别名,都是经过慎重考虑的,特别是为了实现平台中立的兼容。

  • 如 YCLib::Mutex 模块支持标准库的对应接口,即使语言的实现如 libstdc++ 在单线程平台上不提供支持,也通过回退到 YBase::PseudoMutex 而允许不改动用户代码并直接保证空行为。允许不改动用户代码的性质可通过 using namespace platform::Concurrency 实现。

  所有其它不经意的在头文件中使用都应被避免。

标识符命名

  因为无法完全避免使用不同风格命名的外部依赖,YSLib 强调区分命名的来源而不是使用单一命名风格。

  YBase 使用和 ISO C++ 标准库相容的风格。YFramework 则约定使用框架名称,其中可能包含一些前缀,如 I 表示只有抽象方法的类(接口类),G 表示用于泛型(与元编程目的相区别)的类模板的名称等。

  详细的命名规约参见 doc/CommonRules.txtdoc/YFramework.txt

YSLib 应用程序基本模型

程序的启动、运行和终止

  启动一个应用一般可以分为两个部分:创建内容;让内容被显示。YSLib 对此提供了整体性的便利解决方案。以下说明以典型的 GUI 程序为例(非 GUI 程序实际上不必要使用特别的支持)。

  Helper::GUIApplication 模块提供的 GUIApplication 类的单例对象初始化中集成了 GUI 程序必要的初始化。之后,可以部署必要的操作,例如决定哪些内容被初始地呈现。程序通过 Helper::GUIApplication 提供的函数 Execute 调用被封装的消息循环(message loop) ,接受用户输入并按需响应。最后,当特定条件被满足时,退出 Execute 函数,程序结束。

消息循环、消息和消息队列

  消息循环,或称事件循环(event loop) ,是交互式应用的典型实现方式。程序通过处理(handling) 不同的消息(message) 完成期望的行为。除了少数特殊消息(例如表示“退出”以结束程序的消息),消息循环自身通常不能决定如何处理一个消息,因此需要分发(dispatching) 到用户提供程序中。

  基本的消息循环的是一个轮询(polling) 操作,一次循环尝试确定一个需要被处理的消息。所有被处理的消息储存于消息队列(message queue) 内。当消息循环从消息队列取得消息时,通常即在队列中删除此消息。适当的操作产生消息发送至消息队列内以待处理。这种机制能让消息之间具有一定的顺序保证。但要注意,为了满足调度的需要,此处的队列并不一定需要是严格先进先出的。YSLib 使用模块 YSLib::Core::YMessage 提供的 Messaging::Message 类表示消息,Messaging::MessageQueue 类表示支持优先级的消息队列。

  当没有消息需要被处理时即进入空闲(idle) 状态,库需要保证消息循环不被立刻终止,可以采取如下操作:

  • 发送特定的空闲消息并立即处理。
  • 当可能有其它机制发送消息至消息队列时,放弃处理器资源以便节约能源或(在多任务环境中)使其它程序被调度,预定等待一定时间后再次轮询。
  • 结合以上操作。

  交互式程序中,用户动作和其它外部输入可以被抽象为一个消息。输入消息包含必要的状态数据。用户程序指定此处的响应逻辑,更新特定应用的状态,绘制屏幕图形等。由于一个 GUI 程序在此需要完成的任务具有典型性,可以在更高层次上加以抽象,不一定需要直接控制消息循环。但了解消息循环仍然是必要的。

Shell

  YSLib 提供称为 Shell 的抽象使交互式程序易于被分隔为不同的实现。每个 Shell 类可以通过覆盖的虚函数 OnGotMessage 提供响应不同的消息一整套逻辑。

进一步阅读

图形支持

  YSLib 现阶段不强调图形学功能。作为 GUI 的基础,YSLib 主要在命名空间 Drawing 内提供以下两类图形接口:

  • 在 YSLib::Core::YGDIBase 描述图形的位置、大小等几何属性的对象 PointSizeRect
  • 在 YSLib::Service 中提供简单图形和字形的光栅化、像素操作和块传输等绘制功能。

  在了解 YSLib 开发时会少量涉及上述的第一类接口,需要了解其意义和简单的用法。进一步描述详见接口文档。

标量类型

  YSLib 默认使用整数坐标。表示屏幕坐标位置的有符号数类型 SPos 和大小的无符号数类型 SDst 的范围和平台相关,由 YCLib 提供,一般保证至少 16 位,但应避免依赖其具体范围。

类模板和类类型

  类模板 GBinaryGroup 表示两个标量的有序对,被用于表示屏幕坐标。以 SPos 作为模板参数的实例 PointVec 表示点的位置和二维向量,当前是一致的。

  类 Size 包括两个 SDst 分量,用于表示大小。

  类 Rect 约定了一个边和屏幕坐标系总是共线的矩形:保存一个左上角位置的 Point 和表示宽度和高度的 Size 对象。

  一个 Rect 对象可以直接通过位置和大小构造:

Rect r1(Point(10, 20), Size(40, 50));
Rect r2({10, 20}, {40, 50}); // 同上。
Rect r3(10, 20, 40, 50); // 同上。
Rect r4{10, 20, 40, 50}; // 同上。

  类模板 GGraphics 表示二维图形接口上下文 ,是一个表示缓冲区的指针(不保证具有所有权)和大小组成的数据结构。一般使用的是其实例 GraphicsConstGraphics

  类 PaintContext 包含了 GUI 绘制中的一些必要信息,其中由 Graphics 对象表示目标,Point 对象表示参考位置,Rect 对象表示需要保证绘制的边界范围。

GUI 应用接口概述

Shell 和 GUIState

  Helper::GUIShell 模块提供的类 Shells::GUIShell 是专用于不同平台 GUI 程序处理的 shell ,它隐藏了控制和响应 GUI 需要处理的具体消息,使用户输入被分发到更高级的 UI::GUIState 类的对象中。

  GUIState 是模块 YSLib::UI::YGUI 提供的平台中立的 GUI 公共逻辑处理的实现。默认 GUIState 对象预先构造的单态(monostate) 对象,即被全局共享,可以储存和 GUI 相关的公共状态。

  通过 YSLib::UI::YGUI 提供的函数 UI::FetchGUIState 取得取默认图形用户界面公共状态:

using namespace UI;
auto& state(FetchGUIState());

事件(event)

  UI::GUIState 的成员函数被 Shells::GUIShell 直接或间接调用以按需构造不同的事件

  一般意义的事件本质上是可以容纳回调(callback) 的对象,是发布-订阅模式的实现。用户通过事件提供的接口注册回调,在事件被触发调用时订阅者可以调用这些被预先发布的回调。

  YSLib 事件由模块 YSLib::Core::YEvent 提供的 GEvent 类模板提供,包含多播支持,即 GEvent 中可以有多个回调函数,在触发事件时依优先级和插入顺序调用这些回调函数。GEvent 支持的回调通过事件处理器(event handler) GHEvent 类模板提供,除限定返回类型 void 外,使用方式基本兼容于 std::function (提供 ISO C++ 定义的可调用对象(callable object) 作为回调),此外提供两个方面的增强:

  • 可比较相等。这意味着 GEvent 不需要保存特定的引用即可支持查询或移除特定回调(若可调用对象自身支持 == 则通过 == 操作定义结果,否则总是认为同类型可调用对象都相等)。
  • 通过 YBase 库模块 YStandardEx::Functional 的 ystdex::make_expanded 模板提供允许比事件处理器提供模板更少参数的可调用对象的支持,允许省略在右边的若干参数。缺乏此支持时,一个可调用对象的函数形参即使未被使用,仍然需要出现在声明的参数列表中,带来一些不便。

  另外,GEvent 的 operator() 会忽略回调抛出的 std::bad_function_call 异常。

  和消息不同,YSLib 的事件默认总是被同步处理的。不同的事件使用不同的枚举标识,携带特定类型的事件参数(event argument) 对象。

  用户通过给事件提供不同的可调用对象作为回调,在其中可以实现应用程序特定的逻辑。

部件(widget)

  部件是 GUI 的可见元素抽象。体现 GUI 逻辑的事件最终被分发到具体的部件上,在部件持有的事件上进行响应。

  部件具有一系列基本的可视化属性,例如位置和大小。其它一些状态决定它如何被绘制以及和其它部件的关系或者具有用户程序关心的数据。YSLib 中的部件实现模块 YSLib::UI::YWidget 提供的 UI::IWidget 接口以及作为基类的 UI::Widget ,通过处理 UI::Paint 事件使其被绘制。

  基本的部件只能处理 UI::Paint 标识的绘制事件,可以处理其它事件(例如直接响应表示用户输入的事件)的部件称为控件(control) 。模块 YSLib::UI::YControl 提供了控件的基类 UI::Control 。其它大部分部件派生于这个类以提供不同的功能。

  调整部件的特定属性即可以基本完成一个 GUI 应用。

  模块 YSLib::UI::YWidgetEvent 提供了 UI::VisualEvent 枚举标识默认支持的 GUI 事件,其中不同的枚举项表示不同的 GUI 事件,可能对应不同的事件参数类型。默认支持的事件参数类型都是类类型的右值引用类型,但和标准库的右值引用参数类型不同,约定被转移后状态可预测。

  从一个控件取得一个指定事件标识的的事件左值,可以使用 YSLib::UI::YControl 提供的函数模板 UI::FetchEvent 。可以直接在取得的事件上进行操作,所以不必要直接声明一个变量。

using namespace Drawing; // 使用 Drawing::Size 等。模块 YSLib::UI::YControl 已在命名空间 YSLib 中有此声明,若包含了对应的头文件,可以省略。
using namespace UI;
Control ctl(Size(80, 20)); // 创建一个 Control 类型的控件 ctl ,初始大小为 (80, 20) 。
auto& paint_event(FetchEvent<Paint>(ctl)); // 取 ctl 的 UI::Paint 事件,通常是不必要的。

// 使用 lambda 表达式添加回调是简便的做法:
FetchEvent<Click>(ctl) += [](CursorEventArgs&&){
	std::cout << "Control clicked!" << std::endl;
};
FetchEvent<Click>(ctl) += []{ // 得力于 GHEvent 允许省略参数的特性,这里可以省略没有用到的形式参数。
	std::cout << "Control clicked again!" << std::endl;
};

  当 ctl 被点击时,上面 UI::Click 事件上添加的两个回调会被依次执行,在标准输出上输出两行字符串。

指定部件的视图属性

  部件的视图属性包括位置大小等。

  上例中通过指定 Size 对象表示部件初始化时的大小。这个类型和下面涉及的位置以及边界类型都由模块 YSLib::Core::YGDIBase 提供,头文件默认已经被包含在 GUI 中所以不需要显式包含。

  位置由二维的点 Point 类型表示。此处并没有明确位置是相对于哪个坐标系的。创建部件以后,并没有直接指定部件应在哪被显示,所以这里的位置本身只具有相对意义。

  也可以通过直接指定 Rect 类型的矩形的边界

Control ctl2(Rect(10, 20, 40, 20)); // 位置为 (10, 20) ,大小为 (40, 20) 。
Control ctl3({10, 20, 40, 20}); // 同上。

  出于动态加载部件的需要,YSLib 提供的部件总是可以通过一个 Rect 值构造以及默认构造(相当于 Rect 为空),因此不限于 UI::Control 使用。

  在创建部件之后,使用函数 GetLocationOfGetSizeOfGetBoundsOf 等查询这些属性;相对地,使用函数 SetLocationOfSetSizeOfSetBoundsOf 等设置这些属性。通过这些函数设置位置和大小会分别触发 MoveResize 事件。

视图树和容器

  YSLib 提供了单一的视图树结构作为显示视图的抽象。通过指定部件所在的容器限定显示的范围。容器自身是一个部件,可以嵌套在另外的部件中。容器中的部件(子部件)的位置使用容器部件的边界的左上角作为原点,即位置表示子部件的边界的左上角相对于容器的边界的左上角。这样的结构典型地构成一颗树,只有作为树根的顶层部件没有容器。

  容器通过 UI::IWdiget 提供一般的迭代遍历支持。不是所有容器都支持动态添加子部件。模块 YSLib::UI::YPanel 提供一般的面板容器 UI::Panel 支持这个特性:

using namespace Drawing;
using namespace UI;
Panel pnl(Size(200, 100));
Control ctl({10, 20, 100, 40});

pnl += ctl; // ctl 作为 pnl 的子部件,在 pnl 左上角的位置 (10, 20) 显示。
yassume(&pnl == FetchContainerPtrOf(ctl)); // 宏 yassume 由 YBase 提供,默认和标准库的宏 assert 行为一致,表示断言;函数 FetchContainerPtr 取得指向容器的指针。

顶层窗口(top level window)

  部件最终需要通过顶层部件的 UI::Paint 事件被显示。在独立实现中,顶层部件一般是模块 YSLib::UI::YDesktop 提供的 UI::Desktop 。在宿主实现中,由于需要和宿主环境的 GUI 集成,使用不同的接口。在一个有桌面环境的宿主平台中,这样的顶层部件一般称为顶层窗口

  Helper 模块对顶层窗口提供了和平台相关的实现。通过模块 Helper::HostedUI 提供的函数 Host::ShowTopLevel 使一个部件成为一个顶层窗口。

using namespace UI;
Control ctl(Size(200, 100));

Host::ShowTopLevel(ctl);

  对 Windows ,Host::ShowTopLevel 支持更多的样式和扩展样式参数调整宿主窗口。具体使用详见 MSDN 。当调用 Host::ShowTopLevel 时,会自动创建具有独立线程和( Win32 意义上的)消息循环 Windows 窗口,这个过程可能发生阻塞。

常用部件

  UI 命名空间下除了 WidgetControlPanelYDesktop 几类直接作为框架必要类型的部件外,还有更多提供不同实用功能的部件。

  注意,非控件的部件不能取 UI::Paint 外的事件,否则会抛出异常。

UI::Label

  这是显示文本标签的部件。

  简单的用法是创建部件后,修改 Text 属性。它接受的是模块 YSLib::Core::YString 提供的 String 类型的 UCS-2 字符串,可以直接通过 std::u16stringconst char16_t* 等构造。因此内建的 u 前缀的字面量可以直接用于赋值 Text 。YSLib 中 GUI 的其它部分也使用这样的字符串。

UI::Button

  按钮控件。

  类似 UI::Label 可以显示文本,但它是一个控件,可以点击并添加 UI::Click 事件回调。

动态加载

  模块 YSLib::UI::Loader 提供了在运行时读取 NPLA1 配置加载部件视图树的 API 。

  这些 API 当前相对不稳定,可能较容易改变,因此不作详细介绍。具体用法可参照示例程序 YReader 的源代码。

小结

  通过以上讨论,读者可以回顾入门中的示例,分析其中的各个部分的具体意义。

  在这个基础上,参阅 Doxygen 文档查找 UI 命名空间下的其它 API 以开发自己的 GUI 应用。

程序配置概述

  因为可移植性需求,YSLib 不直接使用特定平台提供的机制(如 Windows 注册表)。考虑表达能力和实现冗余,YSLib 不使用 INI 等简单的配置格式。考虑实现复杂性和用户输入配置的简便性,YSLib 避免使用 SGML 及其派生的标记语言,特别地,XML 及其派生语言实现作为配置格式。

  YSLib 提供相关解决方案为基于 NPL 派生实现 NPLA1 的配置,在 YFramework 模块 NPL::Configuration 中提供相关 API 。

规则和意义

  除了整个文件构成的 NPL 表达式外,每一个 NPL 表达式的第一个项应为标识符,表示配置项的名称。其后的项可以是字符串或列表,表示配置项的内容。

用例

  YFramework 的一部分使用配置决定程序运行时需要的信息,如字体文件的位置。参照程序运行

术语概要

  本文档概述及约定 YSLib 基本的概念含义,主要用于开发

  一些术语概念适用各种不同的上下文,主要用于开发过程中的设计和规则说明。

  术语以列表形式的条目列出。通过使用元语言语法 <相关范畴/上下文> 标记指示被修饰或被限定的概念的适用范围。不需要消歧义时,省略标记。

通用领域

  除非另行指定,适用于任意上下文;但存在更具体的上下文的特定概念定义时,优先适用后者。

经验语义

  经验语义解释的术语的含义总是假定可被实证,不需要进一步解释。

  元语言中的标记使用经验语义。标记可被本文档中已归类的章节提供。

  本文档中非形式地使用在特定理论中严格定义的、和本章的条目具有逻辑上相容的含义的概念时,不进一步解释。

自指

  自指是概念定义形式上的自我指涉。

举例本章”是关于位置的自指。

  有必要通过自指定义的概念,隐含引入定义内容的过程的循环论证。这些概念的精确内涵和外延依赖经验语义的实证,否则在自然语言语言中可能需要循环论证而失去定义的意义。此处的条目内容(包括链接的外部定义)仅仅供参考,而不是精确的内涵和外延。为简化复杂度,限制自指定义的概念都是名词。

举例 上述注释中,内涵外延约定为以下非自指的概念,因此可依据本文档给出明确的定义来源。

举例 在程序语言理论中,上下文(context) 指形式上可继续补充内容的构造;文档可能非形式地使用和这个含义相容的概念。

  • 实体(entity) :任意被自然语言表达的目标;不需要通过自然语言先验定义;参见经验语义。
  • 语义(semantics) :参见经验语义。
  • 经验(experience) :参见哲学或一般等价的经验语义。
  • 范畴(category) :参见范畴论
  • 态射(morphism) :参见范畴论。
  • 归纳(induction) :一种态射,可操作性参见经验语义。
  • 方法学(methodology) :一个归纳经验得到的范畴;参见哲学或一般等价的经验语义。
  • 方法(method) :方法学的一个子范畴;可操作性参见经验语义。
  • 概念(concept) :参见逻辑学。
  • 上下文(context) :一种概念范畴适用的态射;参见经验语义。

非自指

  包含多个一般领域的概念。

  • <名词> 形式(form) :参见经验语义和数学。
  • <概念> 内涵:参见逻辑学。
  • <概念> 外延:参见逻辑学。
  • <概念> 定义(definition) :确定概念内涵和外延的方法;参见任意一种形式逻辑学。
  • <动词> 抽象(abstracting) :通过经验语义定义概念范畴或集合的方法。
  • <名词> 抽象(abstraction) :<动词> 抽象的结果。
  • <动词> 封装(encapsulating) :从某一个范畴中抽象一个子范畴的方法。
  • <名词> 封装(encapsulation) :<动词> 封装的结果。
  • 规范(specialization) :一种提供在特定上下文中可定义的描述的封装,参见工程学(特别是软件工程学,下同)。
  • 接口(interface) :一种封装,参见工程学。
  • 实现(implementation) :一种封装,参见工程学。
  • 重用(reusing) :参见经验语义和工程学。
  • 不变性(invariance) :满足某种等价关系(自反、传递、对称的二元关系)。
  • 不变量(invariant) :具有不变性的实体。参见数学和契约式程序设计。
  • 状态(state) :可以和其它实体关联的、可在某个上下文中保持变化或不变的实体。同一状态总是保持变化或保持不变。状态变化的含义参见经验语义、数学或另行指定。
    • 可变状态(mutable state) :在某个上下文中满足以下条件的状态:
      • 可能映射到超过一个其它状态。
      • 存在对应的重新配置可变状态映射到不同的其它状态的改变(mutate) 操作。
    • 不可变状态(immutable state) :不是可变状态的状态。
  • <动词> 派生(deriving) :基于重用的操作。
  • <名词> 派生(derivation) :<动词> 派生的结果。

计算机科学

  包含多个关于数学、逻辑学和计算机领域的概念。

  • <动词> 形式化(formalize) :建立数学意义上的严格形式
  • <名词> 形式化(formalization) :建立形式的过程。
  • 形式方法(formal method) :包含形式化的方法
  • <动词> 建模(model) :建立形式化输出的形式。
  • <名词> 模型(model) :建模的结果。
  • 集合(set) :一种数学模型,参见 NBG 集合论
  • (class) :参见 NBG 集合论和范畴论。
  • 真类(proper class) :参见 NBG 集合论和范畴论。
  • 二元关系(binary relationship) :一种基于集合上定义的数学实体。
  • 等价关系(equivalence relationship) :自反的(reflexive)对称的(symentric)传递的(transitive) 二元关系。
  • 偏序关系(paritial order relationship) :自反的、反对称的(asymentric) 且传递的的二元关系。
  • 严格偏序关系(paritial order relationship) :反自反的(irreflexive)、反对称的且传递的的二元关系。
  • 等价类(equivalence class) :等价关系划分集合得到的类。
  • 可计算性(computability) :参见数学。
  • 计算(computation) :由可计算性定义的操作的等价类,可表现为特定的外在的行为(behavior) 。
  • 计算模型(computation model) :描述计算的模型,是对计算建模的结果。
  • 序列(sequence) :有序集合。
  • 形式语义(formal semantics) :使用形式化的方式表达的语义。
  • 形式语言(formal language) : 特定形式化的方式确定的元素的全集。
    • 注释 这也可能作为语言规则对应的某种形式语义。这同时是语言对应的语法的外延。
  • 计算复杂度(computational complexity) :某个形式化计算模型中以有限的正整数作为模型决定的规模(metric) 作为参数的渐进(asymptotic) 性质确定的度量。
    • 时间复杂度(time complexity) :描述步骤规模的计算复杂度。
    • 空间复杂度(space comlexity) :描述存储规模的计算复杂度。
  • 并发(concurrency) :计算的非确定性的(non-deterministic) 组合(composition) 的性质。
    • 并发的(concurrent) :已被并发方式组合的。
  • 并行(parallelism) :确定性的(deterministic) 计算行为蕴含的可提升渐进效率(asymptotic efficiency) 而不改变计算预期的其它行为的性质。
    • 并行的(parallel) :已蕴含并行的。
    • 可并行化的(parallelizable) :允许改变而蕴含并行的。
    • 注释 渐进效率可使用渐进形式的复杂度描述。
    • 注释 一个表达渐进效率的具体例子是大 O 记号(en-US)
  • 二进制(binary) :实数的二进位制表示格式。

规范

  包含提供规范实体定义。

  • 符合性(conformance) :满足规范的实现性质。
  • 要求(requirement) :规范对实现的作为判断符合性的条件。
  • 约束(constraint) :可被形式表达,用于限制和明确行为的规则。不一定使用形式表达。
  • 违反(violation) :对约束指定的条件的不满足。
  • 过时的(obsolesent) :已确认因为存在更合适的选项而建议不继续使用的(接口/特性)。
  • 废弃的(deprecated) :过时的但因为兼容性等原因,暂时保留的、一般可提供替代的接口或特性。
  • 语言:模型或者非形式地其它方式定义的一种接口
  • <语言> 接口(<language> interface) :和表达语义有关的语言的可见的特征。

  包含关于语言的规范的定义。

  • <语言> 实现(<language> implementation):对语言规则中的要求的<非自指> 实现
  • <语言> 人类接口(human interface) :语义仅对人类有意义(内容改变时可以导致语义的差异性),不提供为涉及作为计算模型实现的语言接口。
  • <语言> 机器接口(machine interface) :对机器(或特定语言实现的特定部分)有意义的语言接口。注意不同语言实现组成部分可以不同。
    • 举例C 语言的预处理器,C 源代码中的空白符是机器接口,而对翻译器来说则不是。就源代码而言,机器接口总是人类接口的子集。
  • <语言> 特性(*<language> feature) :作为功能提供的人类接口。
  • 语言规则(language rule) :约定可实现及应被实现的语言接口的描述,可包含语言特性的表达。
  • 语言规范(language specialization) :包含正式的(normative) 的语言规则的集合的规范;或称语言规格说明。
  • 语言实现(language implementation) :语言提供的接口的实现,是语言的表现形式,可以是具体语言实现或抽象语言实现之一。
    • 具体语言实现(concreate language implementation) :能最终完全表达为可预测的物理现象一一对应的表达可计算性的实现,一般应为程序。
    • 抽象语言实现(abstract language implementation) :非具体语言实现的语言实现。形式意义的标准定义的语言属于此类。
  • 派生语言实现(derived language implementation) :派生已有实现的部分或全部得到的语言实现。以下简作“派生实现”。
  • <语言> 实现环境(environment of implementation) :对应特定语言实现的特定不变状态(对机器来说可以是配置项,对人来说不确定,所以一般忽略)的集合。
  • <语言> 互操作(interoperation) :不同的语言实现环境中发生的交互。
  • <语言> 嵌入实现(embedded implemenation) :在现有的其它语言实现上建立的可共享部分实现环境支持互操作的语言实现。
  • 宿主语言(host language) :提供嵌入实现的现有实现中使用的语言。
  • 客户语言(guest language) :嵌入实现中被宿主语言支持实现的语言。
  • 未定义的(undefined) :可能导致违反规范的约束但语言规范同时没有要求提供任何可能影响符合性的保证(如具有诊断消息)的。
    • 注释 表示置于语言规则下的行为等不可预测。
  • 良定义的(well-defined) :明确地非未定义的。
  • 未指定的(unspecified) :规范隐式或显式地允许但不要求唯一确定的至少一个实现选项。
    • 注释 通常允许多种不同的选项;但在特定实现配置下,规则中未指定的选项也可被限制为只有一种可行的选项。
    • 注释 同一个实现或者不同实现可能确定地或非确定地选取不同的选项而不保证表现一致。
  • 由实现定义的(implementation-defined) :取决于各个具体语言实现的,要求有文档说明。
  • 由派生实现定义的(derived-implementation-defined) :取决于各个派生语言实现的,要求除存在默认定义或被派生实现的部分有明确的文档说明。

程序设计语言

  提供上下文 <程序设计语言> ,特别是语言规范的定义。

  主要用例参见 NPL

  • 广义实体:<通用领域> 实体。语言抽象的目标,不另行定义(意义最终取决于自然语言)。
  • 名称(name) :一种特殊的可比较相等的广义实体,专用于和另一个的广义实体关联。
  • <动词> 指称(denote) :名称对广义实体的关联动作。
  • <名词> 指称(denotation) :被名称指称而关联的实体。
  • 实体(entity) :非名称的广义实体。
  • 表示(representation) :以一个符合某种形式的约束的实体指称另一个实体。
  • 符号(symbol) :语言规则允许的不使用其它对象表示的对象。符号可实现名称。
  • 字母表(alphabet) :符号在语言中的全集。
  • 串(string) :可能重复出现的符号的有限序列。
  • 文法(grammar) :描述任意的可形式化的语言规则。
  • 语法(syntax) :以语言中的串作为基本元素,描述语言的字面(literal) 结构模式(pattern) 的语言规则,通常是文法的一部分。
  • 语义(semantics) :非语法的考虑逻辑上的释义(interpretation) 或含义(meaning) 的规则、原理和过程,通常可被语法以外的文法描述并可约束含义的表达。
  • 实例(instance) :具有代表性含义的集合的元素。
  • 代码(code) :任意有限的语言的实例片段组成的语法范畴。
  • 伪代码(pseudo code):抽象语言实现的语言的代码。
    • 注释 习惯上和具体语言实现代码完全一致的代码可以不作为伪代码考虑。
  • 程序(program) :具体语言实现接受的以代码表示的输入,或被变换后对应的输出。
  • 数据(data) :程序处理的一般信息。
    • 注释 除以下的编码外,通常不直接作为代码处理。
  • 编码(encode) :变换数据为确切格式的代码。
    • 注释 格式通常使用语言定义。
  • <名词> 编码(encoding) :经编码得到的结果。
    • 原理 相对被编码的数据,因可预测格式,这可能更容易处理。因此,同时可能被视为数据。
  • 解码(decode) :变换确切格式的代码为数据。
    • 注释 编码和解码可以是对应可逆的。
  • 行为(behavior) :语言实现或在满足符合性的具体语言实现中的程序的外部表现。
    • 基于可操作性考虑,一般仅约束实现的机器接口
    • 注释 程序的行为是具体的计算行为的实例。
  • 计算作用(computational effect) :可被某个形式化计算模型描述的行为。
  • 翻译(translation) :不同语言的程序之间的变换,可作为语言实现的形式。
    • 注释 输入和输出具有相同表示的恒等变换不被视为翻译。
  • IR(intermediate representation ,中间表示):翻译过程中使用的和输入及输出都不同的表示。
  • 翻译器(translator) :实现翻译的程序。
  • 运行(run) :实现程序或组成程序的实体的行为的动作。
  • 加载(load) :运行程序或组成程序的实体时从实现环境取得相关实体的动作,可蕴含创建这些实体的副本或翻译其中的代码到特定形式。
  • 执行(execute) :处理程序或组成程序的实体,使这些实体或实体的副本作为资源被消费而蕴含这些实体被运行,同时可能蕴含消费实现环境的其它资源。
    • 注释 执行强调资源的消费,是运行的子集。资源被消费后不再可用。因此,除非同时蕴含资源的再生(reclaim) ,被执行的同一实体不预期被再次执行。再生资源包括实体加载时翻译或取得副本,及实现环境中补充的替代资源。
  • 解释(interpretation) :通过不依赖显式指定的附加的程序翻译而直接运行表现行为的具体语言实现的形式。
  • 解释器(interpreter) :实现解释的程序。
    • 注释 解释器是翻译器的真子集。
  • 编译(compilation) :通过依赖显式指定的附加的程序翻译而运行翻译的输出表现行为的具体语言实现的形式。
    • 注释 典型的编译以生成 IR 作为附加的程序翻译过程。
  • 编译器(compiler) :实现编译的程序。
    • 注释 编译器是翻译器的真子集。
  • 源语言(source language) :翻译的输入的语言。
  • 目标语言(target language) :编译的输出的语言。
  • 转译器(transpiler) :源语言和目标语言都能作为典型的源语言的翻译器。
    • 注释 或称为源到源翻译器(source-to-source translator)
  • 转译编译器(transcompiler) :源语言和目标语言都能作为典型的源语言的编译器。
    • 注释 或称为源到源编译器(source-to-source compiler)
    • 注释 通常转译器需要明确的 IR ,所以都是转译编译器。
  • 源代码(source code) :源语言编码的代码。
  • <翻译> 目标代码(target code) :目标语言编码的代码。
  • 目标代码(code code) :编译器输出的代码。
    • 注释 编译器输出的目标代码是翻译的目标代码的特例,一般具有二进制编码。
  • 源程序(source program) :形式为作为翻译的输入的源代码程序。
  • 复杂度(complexity) :以程序的规模作为参数的关于程序的直接执行的计算复杂度
  • 元语言(metalanguage) :描述其它语言的语言。
  • 对象语言(object language):被元语言操作或实现的语言。
  • 元编程(metaprograming) :使用元语言编程。
  • 反射(reflection) :元语言和对象语言相同的元编程。
  • 具现(reification) :在对象语言中以数据模型作为关联实体以表示程序的语义。
  • 诊断(diagnostics) :明确的对特定预期或非预期执行的行为的响应的总和。
  • 诊断消息(diagnostic message) :用于和用户交互的表现诊断的告知及提示。
  • 未定义行为(undefined behavior) :未定义的行为。
  • 良定义行为(well-defined behavior) :良定义的行为。
  • 未指定行为(unspecified behavior) :未指定的行为。
    • 注释 由实现选取语言规范中可能允许的指定行为的不确定选项,这些选项可能由显式或隐式的语言规则确定。
    • 注释 推论:由实现定义的行为是未指定行为。在本文档中,为最小化依赖,不在正式规则中明确这些关系。
  • 语言特性(language feature) :语言提供的功能接口,可以是具体语言特性或抽象语言特性之一。
  • 具体语言特性(concrete language feature) :完全没有派生语言实现定义的语言特性。
  • 抽象语言特性(abstract language feature) :非具体语言特性的语言特性。
  • 外部环境(external environment) :和程序及被翻译的程序没有交集的和实现环境无关的状态。
  • 外部表示(external representation) :具有特定形式的用于和外部环境交互的表示。
  • 内部表示(internal representation) :非外部表示的表示。
  • 可编程性(programmability) :允许使用程序提供实现的性质。
  • API(application programming interface ,应用程序编程接口):提供可编程性以实现程序之间交互的接口。
  • ABI(application binary interface ,应用程序二进制接口):提供可编程性以实现二进制编码的程序之间交互的接口。
    • 注释 ABI 可以是明确指定了二进制形式的程序之间的 API ,也可以是不依赖二进制编码实现的程序的 API 的实现细节。

计算机体系结构

  提供上下文 <计算机体系结构> ,特别是作为语言实现补充的定义。

  • 指令(instruction) :构成代码的具有有限的表示的基本单元,其表示一般具有二进制编码。
    • 注释 指令序列可作为一种目标代码的形式,可实现程序
    • 注释 典型地,指令是通过特定的机器的实现支持,即机器指令(machine instruction) 。这些机器可以是物理的(physical) 或虚拟的(virtual) 。一般物理的机器由特定的机器硬件直接提供指令功能实现的支持,而虚拟的机器指通过软件方式在其它硬件的基础上提供适配支持。
  • 指令集(instruction set) :通过指令的集合提供可编程功能的接口
    • 注释 指令集是提供接口为目的设计的具有相近格式的指令构成的统一规范,而非任意指令的集合。
  • ISA(instruction-set architecture ,指令集架构):依赖和蕴含特定指令集设计的程序之间交互的接口,包括指令集和实现环境的相关假设。
    • 注释 实现环境假设可蕴含明确的 ABI 和指令集以外的配置。
    • 注释 ISA 可作为具体语言实现的一部分。
    • 注释 ISA 可为特定的物理的或虚拟的机器设计,而不需要具有适应不同机器的可移植性(potability)
    • 注释 典型地,ISA 是软件实现可编程性的最底层次的机器接口;更低层次的接口通常依赖不能被纯软件方式访问的机器实现细节。但这并非绝对。
  • 地址(address) :在实现中指定实体位置的编码
  • 地址空间(address space) :映射到地址的操作中,地址的陪域(codomain) 。
  • 寻址(addressing) :在地址空间中确定实体关联的地址。
  • CPU(central processing unit,中央处理单元) :带有运算部件和控制部件,允许对物理计算资源寻址的物理 ISA 的通用实现。
    • 注释 CPU 是 ISA 的主要实现。
    • 物理的实现又称为中央处理器。强调具有缓存(cache) 时,又称为中央处理机。
    • 除非另行指定,讨论特定的(物理)机器的体系结构时,默认指 CPU 支持的 ISA 。
  • GPU(graphics processing unit,图形处理单元):带有图形处理功能的物理 ISA 的专用实现。
    • 注释 GPU 是非通用 ISA 的常见主要实现。
    • 物理的实现又称为图形处理器或显示核心,因其主要以处理 2D/3D 图形计算而得名。
    • 但 GPU 也能适用于比 CPU 更(计算时间和能效意义上)高效的特定计算任务,不一定和图形处理相关,即 GPGPU
    • 为通用的计算加速的类似设备仍可能被称为 CPU ,即便其中没有光栅化单元(rasterization unit) 或最后用于输出的 ROP(渲染输出单元,render output unit ),但保留了和图形处理流水线共用的物理部件,如支持着色器(shader) 的硬件实现。

项目管理

  提供上下文 <项目>

  主要用例参见版本库中的项目文档 doc/ProjectRules.txt

  • 涉众(skateholder) :项目关联的各方的主体。
  • 角色(role) :依据项目过程中起到的作用,对项目涉众实行一些附加归类。
    • 注释 项目的角色同时可用于项目阶段的描述中。
  • 用户(user) :使用项目输出的项目涉众。
  • 维护者(maintainer) :决定项目中各个部分的内容的用户。
    • 注释 维护者可参与和维护部分相关的项目决策。
  • 开发者(developer) :参与程序库和开发工具的功能修改的用户。
    • 注释 开发者可参与公开的构建过程以完成这些修改。
  • 最终用户(end user) :独立为项目过程的用户。
    • 注释 最终用户可以不参与提前(ahead-of-time) 构建的项目过程。基于认知需求的差异可能需要从一般用户中单独区分。

依赖管理

  项目管理的客体被分解为特定关联的依赖项。任意两个依赖项之间存在反对称传递二元关系称为依赖关系严格依赖关系是反自反的依赖关系。

  依赖项和依赖项之间的严格依赖关系统称为依赖(dependency)

依赖引用

  因为依赖关系的传递性,多个依赖关系可能存在无法满足严格依赖关系的情形,即循环依赖(cyclic dependency) 。这导致以确定的顺序解析依赖不可行,增加维护成本。

  为了避免一定层次上的循环依赖,以该层次内组件为顶点的依赖关系的关系图应明确组织为有向无环图。 在最简单情况下依赖关系可退化为线性顺序依赖。

内部依赖和外部依赖

  项目中的组成部分之间的依赖称为内部依赖,其它依赖为外部依赖

源代码

  源代码用于生成指定目标代码。

  通常源代码以文件形式保存,即源代码文件(source code file) ,简称源文件(source file) 。

版本库

  项目使用的版本控制系统(version controlling system) 具有存储库(repository) 作为持久存储实体,即版本库。

  当前使用的主要版本控制系统为 Mercurial 。因为是分布式版本控制,也用于直接分发源代码。

  每个文件系统上存储的版本库实例中,.hg 目录存储版本库元数据。

设计和模型

环境

  程序中的某一部分的外界称为环境(environment)。根据限定程序的范围,可以有更确切的定义,如实现环境(对一类语言实现而言)、运行时环境(对共享实现环境的一类程序而言)。

  一般地,实现环境可以分为独立环境(freestanding environment)宿主环境(hosted environment) ,区分依据为是否依赖宿主(对部署在单一计算机上的实现,一般指操作系统)的支持。因此,环境有时指操作系统及其提供的外部服务的集合。

  一些语言,如 ISO C 和 ISO C++ ,可以同时支持宿主环境和独立环境的实现,对应独立实现(freestanding impementation)宿主实现(hosted impementation)

平台

  环境中决定程序适用环境的被依赖的特定资源集合称为平台环境(platform environment),简称平台(platform)。平台的典型例子有:

  • 运行时支持的 ISA
  • 操作系统和 ABI

  平台的内涵是资源的集合,其构成并非任意。构成平台的特定准则应使之保持相对的稳定和可预期,即可配置;即平台是名义的(nominal) 可配置的资源集合。

  若平台包含的资源是已知的,则不需要平台的观念,分析其资源子集(即便不构成平台)即可解决几乎平台抽象涉及的所有技术问题(同时这也是定义一个具体平台的基础)。但在简化资源集合的全局性质分析(如比较资源配置方案)和名义抽象以隐藏实现(如为开发者提供预设环境集合)的应用角度上,平台仍有被单独讨论的意义。

兼容性和可移植性

  若一个依赖项对应的平台可以替换,则此依赖项和此平台兼容(compatible)兼容性(compatibility) 是平台兼容的二元关系。兼容性不是一种等价关系,因为不保证传递。

  替换平台的过程称为移植(porting) 。移植的可行性称为可移植性(portability)

  兼容任意平台的依赖项被称为是平台中立(platform-neutral) 的。

  当平台中立的依赖项的依赖能被自动满足而不需要考虑时,是平台无关(platform-independent) 的。平台中立实质蕴含平台无关。

依赖和外延

  若平台之间不出现平台的实现(如开发语言的实现)和环境自身的相互依赖,则这些平台相互独立(independent) 。总是保持相互独立的一组平台称为独立平台(independent platforms) 。每一组独立平台保证可以相对于其它独立平台分离开发和测试。

  注意以上术语和 ISO C 和 ISO C++ 定义的宿主实现(hosted implementation)独立实现(freestanding implementation) 的关联和区别。

  典型的应用场景约定以下类型的平台:

  • 构建平台(build platform) :运行开发环境的平台。
  • 宿主平台(host platform) :运行构建平台输出代码的平台。
  • 目标平台(target platform) :运行最终目标代码的平台。

  若宿主平台和构建平台一致,称为本机构建(native build) ;否则,称为交叉构建(corss build)

  通常构建工具在本机构建时提供对构建平台的检查以确定自身是否能够运行;交叉构建环境需要显式指定。

  目标平台通常和宿主平台一致。指定目标平台的理由是,存在最终不一定在宿主平台上运行的程序,其运行的环境可能需要宿主平台不保证支持的特性,这典型地包括:

  • 构建的程序自身是生成其它程序的程序,如编译器和链接器。这些程序生成的平台是目标平台,不需要和它们的宿主平台相同。
  • 构建的程序可以在宿主平台上运行,但在其它平台上具有更完全的特性集。后者被作为目标平台。

注意 此处的宿主平台具有相对意义,不一定脱离被运行的目标平台。一个宿主平台通常自身是宿主实现平台,但这点不被保证。

模拟和仿真

  模拟(emulation) 指适配和运行为不同平台设计的程序,广义上包括以下两类:

  • 环境模拟(environment emulation) :使用模拟器(emulator)或虚拟机(virtual machine) 等作为宿主平台的程序,模拟运行环境的通用解决方案。
  • 程序模拟(program emulation) :直接以运行时环境适配层嵌入宿主平台运行时,在具体程序中提供类似被模拟的目标平台的具体特性和接口。

  运行模拟程序的环境和被模拟环境分别是宿主平台和目标平台。

  注意虚拟机在这个意义下是广义的模拟器,但一般仍然分别对待。

  环境模拟和程序模拟的主要差异为是否独立的、专用的宿主平台程序作为中介以维护目标平台和宿主平台的隔离。

  在一般意义上,仿真(simulation) 指对需要分析的问题建立的模型的过程、方法和机制,在软件工程以外也被称为模拟,如计算机模拟(computer simulation) 。对于以计算机系统为目标的仿真,建立的模型可以是具体的实物(包括硬件和软件),称为仿真器(simulator) 。以软件接口为主要操作方式实现的仿真器同时实现了环境模拟,但侧重不同:精确重现需要分析行为,而非实用的功能等价性和体系中的可替换性。

平台配置

  实际的平台实现可能复用部分实现,配置之间可存在某种构成依赖关系的偏序关系(如继承关系)。这些在项目中所有被配置的平台称为公共平台(common platform) ,其中能对应生成输出的称为具体平台(concrete platform) ,否则为抽象平台(abstract platform)

  对一个平台配置,程序可提供更多的子配置共用现有的相同的配置。子配置可继续对平台特性具体特化,而对原始配置的用户程序隐藏细节。

  普遍适用于一般功能配置也可被作为平台配置的一部分而作为子配置。

注释 例如,多线程和非多线程版本、调试和非调试版本可作为平台配置的功能子配置。

原理 尽管功能配置提供的特性可能是普遍的,但它的实现依赖的特性不都在每个平台中存在,而可能需要一定程度的模拟和仿真,或可选地提供部分特性。提供功能子配置可把这些特性作为次要的实现细节,和原始的平台上的更显著特性隔离。

  若存在这样的子配置,应当满足:

  • 这些子配置应当在每个部署的用户程序的依赖中保持唯一,即一个环境中不能同时依赖不同的子配置的程序映像。
    • 原理 这样不但不需要保持子配置之间的 ABI 兼容性,同时 API 也可以存在不兼容(而不仅仅是调试符号等附加元数据的差异)。
  • 相应地,依赖不同子配置的用户程序也具有对应的子配置。用户程序可以仅提供其中的部分子配置的程序映像。
    • 原理 对每个子配置需要提供单独的依赖路径以避免冲突,因此一般不能在同一个部署中直接复用不同子配置的程序映像。为了避免不必要的资源占用,支持用户程序仅提供部分子配置(而非所有子配置的映像)是必要的。
  • 除非另行指定,被复用的公开库的程序名称在不同子配置中应当存在差异。
    • 原理 尽管不会同时被一个用户程序依赖,每个部署中,不同的子配置的程序映像通常仍需共存。在名称上要求差异允许文件系统直接支持这种策略。

平台标识

  不同平台可以标识符加以区分。由于平台受到不同环境因素决定的正交性,通常此类标识符可以分解为表示这些正交环境的标识符的元组形式,用 - 等字符分隔。

  一种常用的方式是 GNU 构建系统的系统类型,经典表示方式为三元组(triplet) ,或其省略形式:

  • 一般包括体系结构(architecture) 、系统厂商(system vendor) 和系统软件环境。
  • 第一项不可省略,之后的项可省略。
  • 体系结构一般指定 CPU 要求的最小 ISA
  • 系统厂商指集成平台的环境厂商。
  • 系统软件环境保证满足 ABI 要求,可以包含操作系统及本机语言运行时实现的名称。

  确定为宿主环境时,系统软件同时指定操作系统和运行时环境而拆分为两项,三元组扩充为四元组,如 Gentoo 使用的 CHOST

  在不够充分体现平台的必要差异(尤其是体系结构相关的配置)时也可通过自行定义标识符并指定与三元组的对应关系,如 Debian multiarch

  系统类型用来提供一定程度的兼容(替换和互操作):

  • 系统类型代表了系统厂商的预设的配置集合,因此可提供符合相对上的(事实)标准的构建和运行环境。
  • 这种兼容性有时会被过度依赖,乃至被误认为完全的 ABI 兼容:
    • 系统类型不保证涵盖所有 ABI 细节,满足相同的系统类型的程序实例之间不一定符合完全相同的 ABI 而可相互替换或互操作,即原则上即不保证完全的 ABI 兼容。
      • 原理 实现的差异原则上不适合或无法通过系统类型区分。否则,系统类型事实上需要任意地长以涵盖不同细节,而会导致程序部署环境的碎片化,使维护兼容性的原始目的失去主要意义。
    • 一个主要实例:不同构建工具链生成的二进制程序之间不总是保证完全的 ABI 兼容。
      • 同一个版本的同一工具链通过某些构建选项即可能构建出不能保证 ABI 兼容的二进制程序映像。
        • 例如,GCC 使用 -m 前缀选项可能影响 ABI 。
      • 即便使用工具链发行版固定的默认预设选项,不同版本工具链在设计上不能完全保证二进制兼容性。
        • 例如,为兼容 ISO C++11 中关于 std::basic_stringstd::listAPI 改动,GCC 5 显式提供不同的 ABI 配置 而在支持这些改动的配置上[放弃对原有 libstdc++ 的 ABI 兼容保证。
    • 相同的系统类型可以对应不同的依赖集合,已被作为实用的维护兼容性的方法。
      • 这种差异通常隐藏在系统库之下,此时提供不同的二进制兼容映像可以在部署时对依赖系统库的程序隐藏这些差异。
    • 注释 现代 Microsoft Windows(基于 Windows NT 执行体)是应用包括这类兼容性在内的多种策略的一个典型实例:
      • 隐藏二进制差异的主要实例是 Microsoft Windows NT 的 Win32 和 POSIX 子系统,用户程序通过链接到不同的子系统 DLL ,可共享上层 ABI 。
      • Cygwin 是一个类似的 POSIX 子系统替代实现,其用户程序当前默认依赖系统库 cygwin1.dll
      • Windows Subsystem for Linux (WSL) 则不是这种兼容性的实例。
        • 因为映像格式的差异,它的用户空间程序使用的系统类型更接近 x86_64-pc-linux(具体系统类型取决于安装的发行版;典型地是 x86_64-pc-linux-gnu )。
        • 部署 WSL 环境需要涉及一整套不同系统类型的二进制映像,仅在子系统的内部实现存在(对 Windows NT 执行体而言的)隐藏实现差异的情形。
      • 不涉及子系统但同样通过 DLL 隐藏二进制的实例是隐藏系统库的版本差异:不同版本的 C 运行时(CRT) 和 Microsoft VC++ 运行时库可以在一个 Windows 系统实例中共存。
        • 特别地,MSVCRT 和 UCRT 作为不同的 CRT ,可在同一个系统映像中安装,并作为同一个系统类型的不同实现提供。
        • 同时,不同的 CRT 可具有不同的工具链支持。
        • 不同版本的运行时中每个子配置(多线程和非多线程版本、调试和非调试版本)共用相同的配置,在此不视为存在差异。
        • CRT 的多版本部署和其它一些系统中 libc 显式影响系统类型不同。
          • 原理 尽管同样是作为语言实现的一部分部署的系统库(至少在通常以此为由取得许可证豁免的意义上),仅有后者通常是影响整个系统部署的库,而不适合通过相同的系统类型提供二进制不兼容的版本。否则,这会使整个系统中的几乎所有二进制程序映像之间都不具有二进制兼容性,而无法通过通常的机制共享二进制代码。
      • MSYS2 提供的不同环境 是体现上述所有各种不同兼容性方式的一个复杂实例。
        • 其中,MSYS2 环境基于 Cygwin ,是通过类似子系统 DLL 部署的兼容层,其用户程序当前默认依赖系统库 msys-2.0.dll
        • 其余环境被视为原生的 Win32 应用,使用 Win32 子系统。按体系结构归类分组,每一组内可存在原生的共用相同系统类型的不同实现。
          • 例如,/mingw64/clang64/ucrt64 中部署的二进制程序共享系统类型 x86_64-w64-mingw32
          • 其中 /mingw64 中的程序依赖的 CRT 和另两种共享 x86_64-w64-mingw32 的环境不同,而 /clang64 中的程序依赖的 C++ 运行时库也和另两种环境不同。
      • 上述的这些配置仍然没有穷尽系统库的 ABI 的差异。
      • 包括上述 MSYS2 中的每个环境的使用 GCC 的不同发行版都预设了具体的内部依赖,如:
        • MSYS2 中,x86_64-w64-mingw32 环境使用 SEH 异常处理模型,其用户程序当前默认依赖系统库 libgcc_s_seh-1.dll
        • MSYS2 中,i686-w64-mingw32 环境使用 Dwarf2 线程处理模型,其用户程序当前默认依赖系统库 libgcc_s_dw2-1.dll
        • 一些发行版如 MinGW-builds 默认使用 SjLj 异常处理模型,其用户程序当前默认依赖系统库 libgcc_s_sjlj-1.dll
        • MSYS2 和大多数其它 MinGW-w64 发行版中长期使用 POSIX 线程模型提供较完善的 C++ 标准库线程特性实现,当前使用 winpthreads ,其用户程序当前默认依赖系统库 libwinpthread-1.dll
        • 预期可在现代环境中替代 winpthreadsmcfgthread上游的修改已被讨论,使用新的线程模型 mcf ,其用户程序会依赖不同的系统库。
      • 实现系统库时,可依赖非系统库 API 。通常,系统库可直接在运行时依赖 Windows NT 执行体。系统库之间也可存在其它单向的内部依赖。这些情形下,系统库自身不是符合环境要求的程序。
        • 例如,mcfgthread 依赖非 Win32 API ,自身不是严格意义的 Win32 程序(尽管构建时仍在运行于 Win32 子系统的假定下链接)。
        • POSIX 子系统依赖 Win32 系统。
  • 为了避免兼容性保证过程的复杂性,除非另行指定,关于系统类型:
    • 可标识通过源代码部署的单一配置。构建支持和其它外部环境应当被文档明确。
    • 避免认为其它形式部署的唯一依据。特别地,支持可共享相同二进制部署的平台子配置

  除非另行指定:

  • 本项目的文档描述使用和三元组兼容的方式指定平台标识的基本形式。
  • 若同一个平台配置存在多个不同的标识符,默认使用以下规则确定:
  • 注释 通常不使用 Clang 的三元组
    • 原理 尽管形式上更清晰,<sys>-<abi> 的划分实际使之成为四元组而非三元组,这种明确划分不总是符合现实的复杂需求。
      • 这在关于 ABI 兼容的不明确性和歧义更加显著。这类不一致对适配更多不在现有清单上的平台更加困难,特别是未指定 <vendor> 时。
        • 注释 一个歧义的例子:gnu 在 Linux 上特指 glibc ,而在 Windows 上却指 libstdc++ 代表的 Itanium C++ ABI 实现。后者的 C 运行时库(通称 CRT )却和 msvc 兼容。
      • 和 GNU guess 不同,Clang 三元组使用的情形相当有限,甚至长期以来并不具有用户文档中明确的列表或检查规则
      • 默认 <unknown> 但不一定在清单中被排除。这种随意性引起一些解析和理解上的困难。
        • 注释 在清单中滥用 <unknown> 的大量(却不是每个)例子可在 rustc --print target-list 的输出中找到。例如,通常读者(和系统维护者)难以理解:为何存在 wasm32-unknown-emscriptenwasm32-wasi 的同时还有 wasm32-unknown-unknown 而非 wasm32-unknown
  • 具体构建过程可按需不同形式的标识符。
    • 原理 平台配置不一定通过系统类型描述。
      • 注释 例如,cmake -G 支持的标识依赖生成系统的配置,而非运行程序的系统类型。
    • 原理 在不需要关心系统类型中各个组成部分的情形,其它的平台标识符可提供更简单明确的替代。
      • 注释 cmake -G clang --print-targets 的结果是一个实例。

多平台构建

  构建系统中可能涉及多个平台。

  运行构建系统的环境和被构建的程序的环境不需要相同,对应的平台分别是宿主平台目标平台 。宿主平台和目标平台相同时称为本机(native) 构建;不同时称为交叉(cross) 构建。

  多个构建过程可能串联组成更大的构建过程。不同构建过程存在输出和输入之间的依赖。此时,前一过程输出的目标平台需要兼容于后一过程作为输入的宿主平台,否则无法直接运行。典型情况下这些平台是相同的,但也可以存在平台之间自身保证二进制互操作兼容性(如支持 x86_64 的体系结构上混用 i686 和 x86_64 )的情况。

  一些构建系统如 GNU 工具链使用更复杂的术语,单独引入构建(build) 平台。为确保一般性并简化模型,本文档不要求单独使用这个概念,而默认构建平台是第一级构建过程(即 GNU autoconf 的“配置”)的宿主平台。

Introduction

This page specifies the language features from the standard shall be used in this project, by introducing several rules and then clearifying which features are applied to the project from specific project build revision and time.

This page also includes some related information of external projects about various language implementations.

The interface documentation for this project may be specified in the doc directory of the repository, in the pages of wiki, or in the Doxygen comments around the declarations in the header files in the source code. All of them are in zh-CN by default.

Unless otherwise specified, notations of date and time are in UTC+8.

Notation

"NOTE" indicates note for maintainers and it is not directly from the source of the concerned contents.

References

Some external materials are referenced here by links in several categories. Entries in same category are listed ordinally specified by document numbers (if any).

Since only adopted revisions are applied, most superseded reversions are placed together. A paper of a collection of issues can have multiple revisions and it can be adopted more than once (e.g. P0165); these revisions does not superseds each other.

Revision and timestamp

Revisions are designated in forms of brevision-number[time], where revision-number is the number of revision and time is the timestamp issued for that revision in any of ISO 8601:2004 calendar time formats. The timestamp is usually a date conventionally with YYYY-MM-DD.

List of items

Items of features are categorized in different lists with various status. The indentation of a list indicates the context: items with less indentation level cover the topic covering items with more indentation level.

Items and tags

Each item in a list is normally specified by (committee) document or CWG/LWG/EWG/LEWG issue/DR(defect report) number with a link and a title. Paper as editor's report or issue/DR list (but not list about contents out of these contents, e.g. specifically drafted resolution paper of one ore more DRs) should be collectively listed in a few list (so issues can be top-level items otherwise). Each item may be noted with following tags:

  • "adopted" indicating the feature has been incorperated into the working paper (but not in this document) since the followed time which is usually the content of "Disposition" column from the official document list
  • "based on" indicating one of the base specifications
  • "dropped" indicating abandoned in development
  • "overriden by" indicating new item taking effect instead of the current one
  • "part of" or "parts of" indicating not all contents of the specification
  • "revised" indicating one of the revised specifications
    • is often unique
    • should be significant, e.g. adopted previously; otherwise it can already be indexed by other lists of items, and if there are no other list is appropriate, it shall be in the "outdated" list
  • "see" pointing to revised revisions
  • "see also" for related materials
  • "see other derivation" pointing to siblings
  • "see subsequent" pointing to subsequent new series
  • "since" with initial project build revision and time of applying (which is only applicable for features adopted or tentatively adopted in this document)
  • "split from" for the original item containing the material
  • "subsumed by" for the target of migration (e.g. for duplicated issues)
  • "with" indicating additional specifications not covered by specified one in the context

Basic rules

The Forwarding Compatibility Rule: All language features incompatible with published/normative future versions of the language specification shall not be mandated or depend on.

The Baseline Implementation Rule: Each feature being used shall have been implemented in general available version of at least 2 FOSS implementations.

General status

The default configuration, baseline, is specified here. The features explicitly introduced to clearify dependences on the language implementations are also listed in this and following sections.

Conformance

The conformance of languages and environments used in this project is specified using published versions of standards and technicle specifications, as well as drafts and proposals. For purpose of specification in this project, all of them are designated as specifications.

Several C++0x features are used since b206[2011-05-03]. These features are also in ISO C++11(ISO/IEC 14882:2011). Previously C++03 with TR1(ISO/IEC TR 19768:2007) was used. New versions of ISO C++ and technicle specifications are also considered:

  • ISO C++14(ISO/IEC 14882:2014)
  • ISO C++17(ISO/IEC 14882:2017)
  • ISO C++20(ISO/IEC 14882:2020)

Baseline

C++11 is now default to conform.

As per the forwarding compatibility rules, all C++03 features conflicted with future versions of C++ (e.g. export) and all features already removed from C++17 (e.g. std::auto_ptr) are disallowed to rely on.

TR1 features are avoided and have been substituted by their C++11 counterparts.

Draft standard is considered in sake of avoiding conflicts with future versions of the standard.

Additional specifications

The considered and (possibly incompletely) reviewed technical specifications beyond C++11 are:

  • ISO/IEC TS 19568:2015 Programming Languages — C++ Extensions for Library Fundamentals

(See here for information about adoption in C++17.)

Replacements

Several features in specifications beyond C++11 is not directly relied on, but this project provides some replacements (possibly with some custom extensions) as features to ease the work about compatibility.

Replacements are provided in a "(mostly) drop-in" manner, i.e. the base name of API are same, but with allowence of difference on qualified prefix and/or prefixes(of namespaces, mostly, to std; or of headers to be included. This allows user code replacing interface with a few alias declarations without changing of the program well-formedness and behavior. It is easy to change the underlying set of API as well as to pick a subset of them on purpose.

The replacements provided are bidirectionally drop-in replaceable, which can be used interchangably with the features they replace, except the compatibility exceptions specified below. However, such replacements are not guaranteed to be mixed arbitrary with the feature being replacements. The granularity of mixture with guaranteed usablity is a type or a template with any related operations relying on the type signature derived from that entity, unless otherwise specificed in documentation.

Some of other replacements are ony-way drop-in replacements, i.e., guaranteed being able to replace the correspoinding features in specifications without changing of meaning but not the other way.

Compatibility exceptions

Several features cannot be implemented portably due to ISO C++ core language features are not available in virous target environments (e.g. in the baseline).

Emulation of (esp. core language) features involved such cases is limited, and it would be implemented in replacements with best effort when it is practibly implementatble.

NOTE If the core language in the baseline does not limit the feasibility, the replacements shall still be the drop-in replaceable.

Corollary Features limited by the core language are available conditionally depending on which language dialect is being used.

NOTE Extensions of specific language implementations may loose the limitations, but there are still no guarantees.

For cases where the replaced features cannot be implemented portably in the baseline, the limitations shall be documented on related interface either with \warning command on each instances or with descriptions in the header including them, to inform users the existence of portability risks and the condition of availablity (i.e. which dialects it is guaranteed to work). To avoid frequent needs, features totally unusable (e.g. features relying on variable templates) are not provided at all, thus only a few cases should be concerned.

Currently, compatibility limitations include:

  • Availablilty of constexpr on functions and function templates may be limited, when the dialect of an earlier standard (before the edition of the standard requires them) is used.
    • As a result, operations relies on such replacements may not work in contexts requiring constant expressions.
    • Additional implementation support may improve the availability of constexpr.
      • NOTE Sometimes, compiler builtins may provide constexpr being implementable without the limitation even an earlier standard is used; sometimes there are just no such extensions.
    • Replacements of features beyond ISO C++20 shall have constexpr required by the interface, whenever they are implementable portably.
      • Rationale With std::is_constant_evaluated since C++20, different paths of implementations of portable C++ code are allowed, so reducing the quality specifically for contexts not requring constant expressions is not a problem.
    • If there are problems of implementation complexity of the replacments targeting before C++20 to meet the requirements of constexpr reducing the quality of the features used in the contexts not requiring constant expressions (e.g. expectable performance degration), constexpr may not be provided by design.
      • Rationale Before C++14, constexpr has extra limitations on the function body. To work with the baseline without additional implementation complexity, inline may be used instead of constexpr.
      • Whether the specialized implementation with constexpr provided for C++14 and higher edition of the language standard is unspecified.
      • NOTE Providing specialized implementations is a QoI (quality of implementation) issue. Typically, constexpr would be directly enabled when C++14 or some higher edition of the language standard is used.
    • Concrete instances missing constexpr are listed below.
      • ystdex::addressof may not have constexpr for specific types.
        • NOTE Even the type is complete, there is no guarantee to rule out user-defined overloaded operator& on the object.
        • In cases where operator& is used, C++11 provides std::addressof to get the correct result. However, it is not designated with constexpr, which is available portably only since C++17.
      • Replacements of features in <bits> (since C++20) may not have constexpr.
  • To determine whether a class is declared with final may be impossible.
    • It is not implementable without some implementation support before C++14 introduces std::is_final.
    • NOTE std::is_empty is not implementable in portable C++ without such support. However, std::is_empty is provided by C++11 (in the baseline), so there shall be no limitation on determine whether a class is empty.

For better compatibility to evolution, additional exceptions are granted here:

  • Type equivalence between provided and replaced types is not guaranteed even if they coexist in a same configuration of C++ implementation.
    • This allows mixture of different components served as different parts of the replaced interface at same time, as well as complement of interface already (insufficiently) implemented by the current C++ implementation.
    • Types may provided based on old revision of specification (e.g. by inheritance of classes).
  • The constexpr specifier may be effectively omitteed in interface or implementation of the library when the environment is limited in a configuration with insufficient core language support.
    • The constexpr guaranteed by C++14 is not guaranteed available in a configuartion ealier than C++14, since it may be missing of relaxing constraints support in the core langauge implementation.
    • Any constexpr on lambda-expressions is not guaranteed available in a configuration eariler than C++17 even after adoption of constexpr lambda, since it may be missing in the core langauge implementation.
  • Operators may be introduced in declarations different to current version of specifications.
    • This is like [objects.within.classes] for private class members.
    • Comparison operators are no longer guaranteed accessible as independent entities (e.g. as operand of & or accessing using qualified-id).
      • This is same to the direction of future standardization proposed in P0790R0.
      • This dependes on C++20 <=> operator, but it is not the only way.
    • Other operators can be similar to get potentional great simplication of implementation.
      • Notably, with Barton–Nackman trick, simplification can be achived partially as <=> on most overloadable operators (not only comparison).
      • Currently, most operators in this project is simplified by using of YBase.YStandard.Operatos API.

Subproject structure

All of the replacements live in the top-level subproject YBase. The overall structure concerned here is:

  • YBase
    • YDefinition (header file ydef.h)
    • LibDefect
    • YStandardEx
    • others
  • YCLib
  • other YSLib or YSLib-like top-level subprojects

For YBase, all interface of C++ code is in the namespaces specified here. This includes replacements.

There is no well-defined behavior guaranteed if library-wise rules are violated. The rules are:

  • No namespaces name can be introduced as top-level (directly enclosed in the global namespace) namespace names except the namespaces specified here:
    • Namespaces specified by the standard are used according to these rules.
    • The namespace ystdex provides most code not from YBase.LibDefect.
    • Namespace name (glob) pattern 'ystdex_*' are reserved.
      • Some of them may be used as top-level namespaces providing code additional to namespace ystdex without clash in ystdex, e.g. when ADL(argument dependent lookup) is unavoidable.
    • Reserved namespace with public interface as well as namespace ystdex are collectively specified as public top-level namespaces.
      • Currently public top-level namespaces are:
        • namespace ystdex
        • namespace ystdex_swap
    • To simplify the code, any top-level names without explicit qualified preifx :: shall be used as-if they are prefixed with with ::.
  • Only YBase.LibDefect can inject names into namespace std and other namespaces specified by the standard (as parts of implementation) when necessary in provide being conforming. Otherwise, they shall be conform to the rules of the library user code.
  • Documented reserved marco names besides the standard rules shall be also avoided by the library user code.
  • All other cases are conform to the standard rules except that the namespaces provided by the standard are replaced by the top-level namespaces specified above.

Except for the file specified as "drop-in replacement", a header specified by a rule in a published standard or specification may or may not be directly aligned to the actual one in YBase, but there are clear many-interface-to-one-header relationship across the interface and headers by splitting each standard header to plural correspoinding header files.

Rationale The purpose is to break down dependencies and to reduce the overhead of inclusion performed in preprocessing phase.

For other top-level subprojects, no replacements need to be considered. Unless otherwise specified, the baseline applies directly. If there need replacements, use interface proveded by YBase for preference (but usually not YBase.LibDefect).

Components

Replacements in YBase consist of following components with caveats:

  • YDefinition, i.e. ydef.h, provides various vendor-neutral compatible interface of language implementation by conditioanlly-defined preprocessing macros.
    • A few replacements are same to YStandardEx direct replacements below, except provided in the top-level namespace directly.
  • LibDefect implementation provides some interface directly conforming to the standard, as complements of the language implementations provided by system and toolchain vendors.
    • The public headers shall be capable for direct use with any implementation meeting the requirements specified by the YSLib documentation (including following sections) as in-drop replacements for corresponding standard library headers.
  • YStandardEx replacements provide remain interface meet the functionality needs.
    • YStandardEx direct replacements provide adaptive interface compatible and (almost) conforming to multile version of specifications.
      • To support adaptive use, the direct part of replacements may conditionally include different source, whose interface is specified as "conditionally" in this document.
      • The comformance requirements shall be clear to each interface.
      • Nonconforming interface may only occur with exactly same or one-to-one mapping of interface between namespace std and ystdex in the user code.
      • Nonconforming interface shall not introduce differences on requirements on the program using that interface.
      • Nonconforming interface shall be still compatible to other rules. That is, use of the interface shall not alter well-formness and the well-defined behavior of the program, except cases restricted by compatibility limitations.
    • YStandardEx dedicated replacements provide interface compatible to specifications as well as their drafts by less conformance requirements compared to direct replacements.
      • Nonconformance may occur in a form same to that in the case of YStandardEx direct replacements.
      • Nonconformance shall occur in a form that loosing the requirements of rules in the published standard or specification, or in a form with explicitly supported extensions which are not compatible to these rules; otherwise, the interface should be designed as direct replacements or non replacements.
  • YCLib replacements
    • As YStandardEx direct and dedicated replacements, respectively, with platform-dependent implementation details relied on. Some features may be conditonally supported depending on the standard library implementatinos.
    • The names are provided in namespace platform.

See following paragraphs for precise definition of components and more detailed descriptions on policies of their use.

Evolution

For compatibility, the project may use different sets of rules in each parts. YBase is more stable and conservative to utilize new C++ features.

Longterm policy

The longterm policy holds as:

  • ydef.h: For compatibility reasons, only a subset of C++11 would be mandated. The precise subset is unspecified and can vary between revisions.
  • YBase except LibDefect discussed above: Library features beyond C++11 would be used only available.
  • Other parts of the project: C++ features would be used aggressively (but still restrictd by the rules here, esp. the basic rules). Currently it is still in the baseline.

Core language compatibility features

Some core language feature have fallback in C++03 (e.g. constexpr) or library (e.g. alignof), mostly workaround in ydef.h, as well as some optional extensions (e.g. __has_feature and __builtin_expect) wrapped as implementation details. They are provided by YDefnition.

Some core language features not in the standard but provided by various adapted language implementations have been wrapped in a platform-neutral interface provided by YDefinition.

Library implementation integreations

Due to limitations of specific environments, some standard library features might be not usable without alternative implementations. They may be enabled by using additional headers in the module YBase.LibDefect instead of the corresponding standard headers.

As patches, namely parts of language implementations, the code in YBase.LibDefect can be highly implementation-specific. It has significant differences (which may cause undefined behavior without further guarantees) to the usual library and user code:

  • It can be directly intrusive to the global namespace, namespace std and other implementation-specific namespaces, which may be reserved by the standard.
  • As part of the standard library implementation, it may use names reserved by the standard.
  • It may have implementation-specific contents intended only for internal use and guarded by conditional inclusion, because they are direct replacements for the standard library.

The code has been carefully tuned to be compatible to supported environments, to keep out undefined behavior merely caused by this implementation.

Library compatibility features

To reduce impact on user code to adopt new versions of specifications, several post-C++03 library features are provided in the top-level namespace ystdex (with inlined namespace if the features have been in published standards) in module YBase.YStandardEx, either by using declarations from namespace std iff. provided by the standard library, or being implemented from scratch when the features are not available from the standard library (not the library implementation, i.e. implementation-specific interface are still forbidden).

They are designed to be (bidirectionally) drop-in replacements (with necessary filename change in #include directives) of corresponding specific version of std or std::experimental interface, with a few exceptions:

  • Except for some specialization of standard library templates, the enclosing namespace is not std so name lookup may behave differently to the standard library interface.
    • Use additional 'using ystdex::NAME;' to enable ADL(argument dependent lookup) for NAME.
    • Note [global.functions]/4 is still conforming similarly. Any ADL beyond namespace ystdex shall be specified by the interface documentation.
  • The overloaded operators may be implemented by ADL-only manner, i.e. declared as friend functions, rather than namespace scope entities.
    • Currently no entities are declared in this manner except for interface are ready for post-C++14 standard library features.
  • It is unspecified that whether the concerned types of replacement API is identical to the types in the standard.
    • It is important to know this to avoid type introspection based on wrong type identical assumptions, including:
      • Using static_assert or some other meta operations based on static type equivalence.
      • RTTI or exception handling based on dynamic type identity.

They are collectively called as YStandardEx direct replacements for the corresponding features being replaced.

YBase user code may use interface in public top-level namespaces instead of the counterparts in the namespaces mentioned above to simplify the migration. For components of direct replacements interface specified in the standard (but not technical specifications) beyond ISO C++11, they are in correspoinding enclosed standard replacement namespaces which are inline namespaces enclosed unambiguously in public namespaces with a common namespace prefix pattern with same nested namespaces to the standard interface. For example:

  • Interface first occurred or last updated in ISO C++14 in namespace std is in inline namespace ystdex::cpp2014.
  • std::pmr interface introduced since ISO C++17 is in inline namespace ystdex::pmr::cpp2017.

Note that a similar approach is also proposed for std separatedly as shadow namespace in P1473R0 for some slightly different purposes.

If there are multiple published versions of standard having modification on some entities denoted by a same name, there shall be one candidate or at least one extra alias declaration to reference it in the public top-level namespaces unambiguously, allowing omission of the inline namespace name normally but distinguishing on need. Deprecated features is not guaranteed provided unless explicitly specified to reduce confusion and restriction as per the future direction of feature using implied in this document. To avoid misconceptions, other replacements shall neither be provided in standard replacement namespaces or their enclosing namespaces thereof.

Ambiguity across different namespaces with mixture use of them shall also be avoided.

Components for the upcoming standard (in the working draft or Technicle Specifications, but not the published Internaltional Standard) are deliberately experimental as library compatibility features, so they are directly declared in public top-level namespaces.

As per the compatibility limitations of replacements, exceptional rules of compatibility to std interface are granted hereby:

  • For entities used specifically in tag dispatching, only such use is required to be compatible as drop-in replacement. The differences shall be defined in documents about the replacement.

Revised components of library compatibility features shall not be deprecated in the current (newest formal) standard.

Extended library compatibility features

Some other interfaces in YBase.YStandardEx are designed as replacements for corresponding specific versions of std or std::experimental interface, as the compatibility features above, but with extra extensions, and with no restriction about the solution of LWG 2013 (that is, they may be with extra constexpr). They can be used as ony-way drop-in replacements.

Entities with extended library compatibility features to the replaced entites are collectively called as YStandardEx dedicated replacements of the corresponding features being replaced, where each of them meets following requirements:

  • Except extensions, it shall be able to one-to-one mapped to the entity being replaced with exact functionality (albeit the name can be different in several cases, see below).
  • If it is neither a type nor a template of type (class template or alias template), or it is provided as some part of other compatibility features, its base name (unqualified-id) shall be same to the name of entity being replaced.
  • Its name shall be declared in public top-level namespaces or enclosed namespaces thereof, except the namespaces excluded by the rules specified previously.

Components of extended library compatibility features not being dedicated replacements shall be declared in public top-level namespaces and may be declared in the same header of the compatibility features above.

Potential library compatibility features

Some other interfaces in YBase.YStandardEx are designed close to correspongding specific versions of std or std::experimental interface, to be an implemantation base of above compatibility library features or extended library compatibility features. They are not replacements as they are not designed to be conforming to any version of the standard or technical specifications, nor always provided in a drop-in manner. Nevertheless, they may have features which can be directly mapped to the said specifications, with or without some resolutions of LWG issues applied.

Although as implementation of above features, they are totally in details, they can also be used as public interfaces as other parts of YSLib.

Reviewed

The following editor's report has been fully reviewed (b593[2015-04-23]), which means all the resolutions in the paper are categorized in the following clauses:

NOTE There might exist minor differences between editor's report and the paper in the list, e.g. N3059 (rev 5.2) in N3091 is (rev 5.1). For these cases, only the later revisions at the point of time are reviewed and probably would not be updated unless necessary.

To be done

Reviewing of following defect reports and resolutions are work in progress.

  • CWG 215: Template parameters are not allowed in nested-name-specifiers
  • CWG 218: Specification of Koenig lookup (see also CWG 113 and CWG 143)
  • CWG 397: Same address for string literals from default arguments in inline functions?
  • CWG 667: Trivial special member functions that cannot be implicitly defined
  • CWG 1135: Explicitly-defaulted non-public special member functions
    • CWG 1136: Explicitly-defaulted explicit constructors
    • CWG 1140: Incorrect redefinition of POD class
    • CWG 1145: Defaulting and triviality
    • CWG 1149: Trivial non-public copy operators in subobjects
    • CWG 1208: Explicit noexcept in defaulted definition
  • N4320: Make exception specifications be part of the type system

Reviewing of following editor's reports is work in progress.

No actions

There are nothing to do of coding and further documentations for some resolutions.

In WG21 terms:

  • NAD means "not a defect".
  • TC1 means "Technical Corrigendum 1".

In the baseline

These issues are resolved as NAD and thus have been rejected by WG21 in the baseline, and there are no further changes to reopen:

  • CWG 37: When is uncaught_exception() true?
  • CWG 61: Address of static member function "&p->f"
  • CWG 109: Allowing ::template in using-declaration​s
  • CWG 130: Sequence points and new-expression​s
  • CWG 182: Access checking on explicit specializations
  • LWG 84: Ambiguity with string::insert()
  • EWG 91: [tiny] Core issue 622, Relational comparisons of arbitrary pointers
    • CWG 622: Relational comparisons of arbitrary pointers (see EWG 91)
  • parts of N2173: Core Extensions for Evolution
    • CWG 13: extern "C" for Parameters of Function Templates
    • CWG 107: linkage of operator functions
    • CWG 168: C linkage of static members
    • CWG 229: Partial specialization of function templates
    • CWG 294: can static_cast drop exception specs (see also CWG 87)
    • CWG 359: Type definition inside anonymous union

These issues were once confirmed but are in NAD status now due to newer feature changes which have been adopted by the standard and by this project, so there are nothing further to do:

  • CWG 395: Conversion operator template syntax
  • LWG 178: Should clog and cerr initially be tied to cout?
    • see also bullet 6 in N1569
    • see also LWG 455: cerr::tie() and wcerr::tie() are overspecified
  • LWG 2386: function::operator= handles allocators incorrectly

These resolutions are already adopted as TC1, i.e. in C++03, and still effective (probably revised) in later versions of the standard:

  • CWG 25: Exception specifications and pointers to members
  • CWG 30: Valid uses of "::template"
  • CWG 84: Overloading and conversion loophole used by auto_ptr
  • CWG 137: static_cast of cv void*
  • CWG 178: More on value-initialization (see also CWG 543)
  • CWG 304: Value-initialization of a reference
  • LWG 29: Ios_base::init doesn't exist
  • LWG 35: No manipulator unitbuf in synopsis
  • LWG 61: Exception-handling policy for unformatted output
  • LWG 129: Need error indication from seekp() and seekg()
  • LWG 136: seekp, seekg setting wrong streams?
  • LWG 209: basic_string declarations inconsistent
  • LWG 227: std::swap() should require CopyConstructible or DefaultConstructible arguments
  • LWG 250: splicing invalidates iterators
  • N1219: PROPOSED RESOLUTION TO LIBRARY ISSUE 60
    • LWG 60: What is a formatted input function?

These issues are resolved as NAD or NAD editorial and thus nothing to do for user code:

  • CWG 1384: reinterpret_cast in constant expressions
  • CWG 1415: Change "declararation or definition" to "declaration"
  • CWG 1520: Alias template specialization vs pack expansion (see also CWG 1558)
  • LWG 299: Incorrect return types for iterator dereference
  • LWG 392: 'equivalence' for input iterators
  • LWG 408: Is vector<reverse_iterator<char*> > forbidden?
  • LWG 526: Is it undefined if a function in the standard changes in parameters?
    • NOTE However, not all implementations are conforming, albeit they have been fixed at current.
  • LWG 529: The standard encourages redundant and confusing preconditions
  • LWG 580: unused allocator members
  • LWG 867: Valarray and value-initialization
  • LWG 1079: RandomAccessIterator's operator- has nonsensical effects clause
  • LWG 1211: move iterators should be restricted as input iterators
  • LWG 2006: emplace broken for associative containers
    • proposed by N3178: emplace broken for associative containers
  • LWG 2204: reverse_iterator should not require a second copy of the base iterator

These post-C++03 issues are still open, but it is technically not implying requirements of changes of any conforming implementations or programs:

  • CWG 2054: Missing description of class SFINAE
    • NOTE Althoug no official actions are made, this is considered not related to user code for several reasons:
      • The standard rules have already support the use.
        • Rules in [temp.spec.partial.order] have already cover the cases, albeit quite implicit, by assuming there are no exceptional case (hard errors) on the deduction process described in the rules.
        • The example of [temp.spec.partial.match/2] shows the use of rules.
        • Then the rules in [temp.deduct] apply and no rules can definitely render a program merely having such deduction error as ill-formed.
        • The problem is that the reference to [temp.deduct] rules is not clear, so it can be an issue to improve.
      • The idiomatic way of N4436 requires exactly such use.
      • Major implementations have been support the feature for years.

These post-C++03 draft resolutions are non-normative, purely editorial or conceptional, so no actions could be taken (revised b941[2022-03-15]):

  • CWG 113: Visibility of called function
  • CWG 119: Object lifetime and aggregate initialization
  • CWG 357: Definition of signature should include name
  • CWG 404: Unclear reference to construction with non-trivial constructor (NOTE partially superseded by new wording in [basic.life] proposed by CWG 1751 and CWG 2256)
  • CWG 413: Definition of "empty class"
  • CWG 452: Wording nit on description of this, partially adopted
  • CWG 525: Missing * in example
  • CWG 538: Definition and usage of structure, POD-struct, POD-union, and POD class
    • CWG 327: Use of "structure" without definition
  • CWG 582: Template conversion functions
  • CWG 594: Coordinating issues 119 and 404 with delegating constructors (see CWG 119 and CWG 404)
  • CWG 618: Casts in preprocessor conditional expressions
  • CWG 627: Values behaving as types
  • CWG 999: “Implicit” or “implied” object argument/parameter?
  • LWG 542: shared_ptr observers
  • LWG 610: Suggested non-normative note for C++0x (i.e. small function object optimization)
  • LWG 616: missing 'typename' in ctype_byname
  • LWG 628: Inconsistent definition of basic_regex constructor
  • LWG 640: 27.6.2.5.2 does not handle (unsigned) long long (i.e. for ostream::operator<<), outdated
  • LWG 724: DefaultConstructible is not defined
  • LWG 868: Default construction and value-initialization
  • LWG 972: The term "Assignable" undefined but still in use
  • part of LWG 2135: Unclear requirement for exceptions thrown in condition_variable::wait()
  • LWG 2310: Public exposition only member in std::array
  • LWG 2434: shared_ptr::use_count() is efficient
  • LWG 2755: §[string.view.io] uses non-existent basic_string_view::to_string function
  • LWG 3310: Replace SIZE_MAX with numeric_limits<size_t>::max()
  • N2775: Small library thread-safety revisions
  • N3066: Iterators in C++0x
  • N3966: Fixes for optional objects (revised by N4078)
  • editorial change in N4714
  • P0134R0: Introducing a name for brace-or-equal-initializer​s for non-static data members
  • P0583R0: std::byte is the correct name
  • P1076R1: Editorial clause reorganization with modification
  • P0509R1: Updating "Restrictions on exception handling" (adopted: accepted by N4664)
    • see national body comments GB 41 and GB 42 in N4664: ISO/IEC CD 14882, C++ 2017, National Body Comments

These post-C++03 draft resolutions would never be depended on because they are only intended useful for language implementations and there shall be no compatibility problems for conforming code (revised b593[2015-04-23]):

  • N2194: decltype for the C++0x Standard Library

These post-C++03 papers are used for changes spcifically on technical specifications and not relied on:

  • N4041: Concerns with changing existing types in Technical Specifications

Outdated

These resolutions are only about TR1 or features have been formally deprecated/removed from current ISO C++, so shall never be depended on:

  • LWG 527: tr1::bind has lost its Throws clause
  • LWG 588: requirements on zero sized tr1::arrays and other details (for std::array, resolved by LWG 776)

These issues are duplicate (in "dup" status):

  • CWG 133: Exception specifications and checking (subsumed by CWG 87 and CWG 92)
  • CWG 595: Exception specifications in templates instantiated from class bodies (subsumed by CWG 1330)
  • CWG 1300: T() for array types (duplicate of CWG 914)
  • CWG 1568: Temporary lifetime extension with intervening cast (duplicate of CWG 1376)
  • LWG 105: fstream ctors argument types desired (duplicate of LWG 454)
  • LWG 479: Container requirements and placement new (duplicate of LWG 580)
  • LWG 486: min/max CopyConstructible requirement is too strict (duplicate of LWG 281)
  • LWG 2775: reverse_iterator is does not compile for fancy pointers (duplicate of LWG 1052)

These resolutions are superseded by later modification on the working paper before the later publication of the standard:

These resoultions are issues for TSes and later superseded in the working paper and the TSes are not used:

These papers are superseded and newer revisions have been reviewed, so no further actions would be taken:

  • N1489: Templates aliases for C++ (revised by N2112)
  • N1599: Issue 431: Swapping containers with unequal allocators (see subsequent N2525)
  • N1890: Initialization and initializers (see subsequent N1919)
  • N1919: Initializer lists (revised by N2100)
  • N1932: Random Number Generation in C++0X: A Comprehensive Proposal (revised by N2032)
  • N1961: Wording for range-based for-loop (revised by N2196)
  • N1968: Lambda expressions and closures for C++ (revised by N2329: Lambda expressions and closures for C++ (Revision 1))
  • N2032: Random Number Generation in C++0X: A Comprehensive Proposal, version 2 (revised by N2079)
  • N2062: POD's Revisited (revised by N2102)
  • N2079: Random Number Generation in C++0X: A Comprehensive Proposal, version 3 (revised by N2111)
  • N2095: long long Goes to the Library (revised by N2114)
  • N2100: Initializer lists (Rev 2.) (revised by N2215)
  • N2102: POD's Revisited; Resolving Core Issue 568 (Revision 1) (revised by N2172)
  • N2112: Templates Aliases (revised by N2258)
    • NOTE The page missed the link to Previous Version.
  • N2151: Variadic Templates for the C++0x Standard Library (revised by N2192)
  • N2172: POD's Revisited; Resolving Core Issue 568 (Revision 2) (revised by N2230)
  • N2192: Variadic Templates for the C++0x Standard Library (Revision 1) (revised by N2242)
  • N2196: Wording for range-based for-loop (revision 1) (revised by N2243)
  • N2210: Defaulted and Deleted Functions (revised by N2326)
  • N2202: C99 Compatibility : __func__ and predeclared identifiers (revised by N2251)
  • N2215: Initializer lists (Rev. 3) (revised by N2385)
  • N2217: Placement Insert for Containers (revised by N2268)
  • N2230: POD's Revisited; Resolving Core Issue 568 (Revision 3) (revised by N2294)
  • N2236: Towards support for attributes in C++ (revised by N2379)
  • N2243: Wording for range-based for-loop (revision 2) (revised by N2394)
  • N2251: C99 Compatibility : __func__ and predeclared identifiers (revision 1) (revised by N2340)
  • N2268: Placement Insert for Containers (Revision 1) (revised by N2345)
  • N2294: POD's Revisited; Resolving Core Issue 568 (Revision 4) (revised by N2342)
  • N2326: Defaulted and Deleted Functions (revised by N2346)
  • N2329: Lambda expressions and closures for C++ (Revision 1) (revised by N2413)
  • N2345: Placement Insert for Containers (Revision 2) (revised by N2642)
  • N2379: Towards support for attributes in C++ (Revision 2) (revised by N2418)
  • N2385: Initializer lists WP wording (revised by N2531)
  • N2394: Wording for range-based for-loop (revision 3) (revised by N2778)
  • N2413: Lambda Expressions and Closures: Wording for Monomorphic Lambdas (revised by N2487)
  • N2418: Towards support for attributes in C++ (Revision 3) (revised by N2553)
  • N2477: Uniform initialization design choices (Revision 2) (revised by N2532)
  • N2487: Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 2) (revised by N2529)
  • N2529: Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 3) (revised by N2550)
  • N2531: Initializer lists WP wording (Revision 2) (see subsequent N2575)
  • N2532: Uniform initialization design choices (Revision 2) (see subsequent N2575)
  • N2550: Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 4) (revised by N2927)
  • N2553: Towards support for attributes in C++ (Revision 4) (revised by N2751; the unavailable revised revision N2663 is wrong on document list)
  • N2575: Initializer Lists — Alternative Mechanism and Rationale (revised by N2640)
  • N2635: Local and Unnamed Types as Template Arguments (revised by N2657)
  • N2640: Initializer Lists — Alternative Mechanism and Rationale (v. 2) (revised by N2672)
  • N2642: Proposed Wording for Placement Insert (revised by N2680)
  • N2751: Towards support for attributes in C++ (Revision 5) (revised by N2761)
  • N2778: Wording for range-based for-loop (revision 4) (revised by N2930)
  • N2820: Adding heterogeneous comparison lookup to associative containers (revised by N2882)
  • N2882: Adding heterogeneous comparison lookup to associative containers for TR2 (Rev 1) (revised by N3465)
  • N2904: Defining default copy and move (revised by N2953)
  • N2953: Defining Move Special Member Functions (revised by N2987)
  • N2987: Defining Move Special Member Functions (revised by N3044)
  • N3044: Defining Move Special Member Functions (revised by N3053)
  • N3149: From Throws: Nothing to noexcept (revised by N3195)
  • N3248: noexcept Prevents Library Validation (revised by N3279)
  • N3433: Clarifying Memory Allocation (revised by N3537)
  • N3465: Adding heterogeneous comparison lookup to associative containers for TR2 (Rev 2) (revised by N3657)
  • N3537: Clarifying Memory Allocation (revised by N3664)
  • N3597: Relaxing constraints on constexpr functions (revised by N3652)
  • N3598: constexpr member functions and implicit const (revised by N3652)
  • N3727: A proposal to add invoke function template (revised by N4169)
  • N3873: Improved insertion interface for unique-key maps (revised by N4006 and N4240)
  • N4002: Cleaning‐up noexcept in the Library (revised by N4227)
  • N4006: An improved emplace() for unique-key maps (dropped; see other derivation of N3873)
  • N4017: Non-member size() and more (revised by N4155)
  • N4056: Minimal incomplete type support for standard containers (revised by N4371)
  • N4151: TriviallyCopyable reference_wrapper (revised by N4277)
  • N4155: Non-member size() and more (Revision 1) (revised by N4280)
  • N4227: Cleaning-up noexcept in the Library (Rev 2) (revised by N4258)
  • N4228: Refining Expression Evaluation Order for Idiomatic C++ (see subsequent P0145R0)
  • N4240: Improved insertion interface for unique-key maps (revised by N4279)
  • N4334: Wording for bool_constant (revised by N4389)
  • N4371: Minimal incomplete type support for standard containers, revision 2 (revised by N4390)
  • N4390: Minimal incomplete type support for standard containers, revision 3 (revised by N4510)
  • N4429: Rewording inheriting constructors ((core issue 1941 et al) (revised by P0136R0)
  • N4446: The missing INVOKE related trait (i.e. is_callable) (revised by P0077R2)
  • P0136R0: Rewording inheriting constructors ((core issue 1941 et al) (revised by P0136R1)
  • P0145R0: Refining Expression Evaluation Order for Idiomatic C++ (Revision 1) (revised by P0145R1)
  • P0068R0: Proposal of [[unused]], [[nodiscard]] and [[fallthrough]] attributes. (revised by P0188R0 and P0189R0 in parts, see subsequent P0212R0)
  • P0077R0: is_callable, the missing INVOKE related trait (revised by P0077R1)
  • P0077R1: is_callable, the missing INVOKE related trait (revised by P0077R2)
  • P0145R1: Refining Expression Evaluation Order for Idiomatic C++ (Revision 2) (revised by P0145R2)
  • P0145R2: Refining Expression Evaluation Order for Idiomatic C++ (revised by P0145R3)
  • P0188R0: Wording for [[fallthrough]] attribute. (revised by P0188R1)
  • P0189R0: Wording for [[nodiscard]] attribute. (revised by P0189R1)
  • P0212R0: Wording for [[maybe_unused]] attribute. (revised by P0212R1)
  • P0302R0: Deprecating Allocator Support in std::function (revised by P0302R1)
  • P1089R2: Sizes Should Only span Unsigned (see subseqent P1227R1)
  • P1227R0: Signed ssize() functions, unsigned size() functions (revised by P01227R1)
  • P1227R1: Signed ssize() functions, unsigned size() functions (revised by P01227R2)

These papers have been previously adopted but superseded by newer adopted papers, so no further actions would be taken:

  • N2525: Allocator-specific Swap and Move Behavior (adopted 2008-03, in editor's report N2589; revised by N2982)
  • N3672: A proposal to add a utility class to represent optional objects (Revision 4) (adopted 2013-04; revised by N3793)
  • P0077R2: is_callable, the missing INVOKE related trait (adopted 2016-02; revised by P0604R0)

These features were still under development but the approved versions were superseded (note that the superseded versions may still newer than currently adopted version), and not adopted in this document:

  • EWG 22: N4030, 3745, N3694 Feature-testing recommendations for C++, N3435 Standardized feature-test macros (for adopted version, see N4200)
    • N3435: Standardized feature-test macros
    • N3694: Feature-testing recommendations for C++
    • N3745: Feature-testing recommendations for C++
    • N4030: Feature-testing recommendations for C++
    • P0096R0: Feature-testing recommendations for C++
    • P0096R4: Feature-testing recommendations for C++ (see subsequent P0941R0)
    • P0941R0: Integrating feature-test macros into the C++ WD
    • P0941R1: Integrating feature-test macros into the C++ WD
  • N3890: Container<Incomplete Type> (partially superseded by N4056)

Revoked

These resolutions are overriden by later issues after publications of the standard:

  • LWG 22: Member open vs. flags (see LWG 409)
    • NOTE This is non-editorial but finally changes only informal texts.

These features were once adopted by the working paper but later removed away, so no actions would be taken until introduced to the draft again:

  • CWG 1308: Completeness of class type within an exception-specification (see CWG 1330)
  • LWG 1029: Specialized algorithms for memory management need to be concept-constrained templates
  • LWG 1001: Pointers, concepts and headers (see LWG 1178)
  • N2525: Allocator-specific Swap and Move Behavior (i.e. allocator_propagate_*)
  • N2620: Concepts for the C++0x Standard Library: Diagnostics library
  • N2736: Concepts for the C++0x Standard Library: Numerics (Revision 3)
  • N2755: Concepts for the C++0x Standard Library: Chapter 17 -Introduction (Revision 2)
  • N2758: Iterator Concepts for the C++0x Standard Library (Revision 5)
  • N2759: Concepts for the C++0x Standard Library: Algorithms (Revision 5)
  • N2768: Allocator Concepts, part 1 (revision 2)
  • N2770: Concepts for the C++0x Standard Library: Utilities (Revision 5)
  • N2773: Proposed Wording for Concepts (Revision 9)
  • N2774: Foundational Concepts for the C++0x Standard Library (Revision 5)
  • N2776: Concepts for the C++0x Standard Library: Containers (Revision 4)
  • N2777: Concepts for the C++0x Standard Library: Iterators (Revision 4)
  • N2779: Concepts for Clause 18: Part 2
  • N2780: Named Requirements for C++0X Concepts, version 2
  • N2786: Simplifying unique_copy (Revision 1)

There are actions taken by the committee to remove away some former working draft features which are never used in this project:

  • N2549: Excision of Clause 31 (i.e. <date_time>)

Non defects

These out-of-baseline issues are resolved as NAD:

  • CWG 1005: Qualified name resolution in member functions of class templates (see also CWG 1017; note CWG 515 is still effective)
  • CWG 1334: Layout compatibility and cv-qualification
  • LWG 466: basic_string ctor should prevent null pointer error
  • LWG 760: The emplace issue (see LWG 2164 which is still open)
  • LWG 763: Renaming emplace() overloads (N2680 renamed one of the overloads to emplace_hint; for related discussion see LWG 1302)
  • LWG 851: simplified array construction
    • see N4480: Programming Languages — C++ Extensions for Library Fundamentals
      • N4391: make_array, revision 4
  • LWG 1202: integral_constant needs a spring clean
  • LWG 1302: different emplace semantics for sequence and associated containers
  • LWG 1651: Lifetime extension of temporary via reference to subobject
  • LWG 2311: Allocator requirements should be further minimized
  • LWG 2319: basic_string's move constructor should not be noexcept
  • LWG 2446: Unspecialized std::tuple_size should be defined

Not applicable

Some proposals are not approved.

  • N3400: A proposal for eliminating the underscore madness that library writers have to suffer

Some proposals are not applicable yet and to be resolved in future. There also may be workaround provided by C++ implementation extensions or user code.

  • N2683: issue 454: problems and solutions
    • LWG 454: basic_filebuf::open should accept wchar_t names

In development

These issues are in "extension" state and not ready to be adopted:

  • CWG 476: Determining the buffer size for placement new (see also CWG 256)

Adoption

C++11 core language features and headers below now are being used.

Note: the "adopted" time notes listed in following entries are relative to the working paper.

Imported TR1 Headers

TR1(see N1836) headers imported to C++11 are used:

  • <array> (since b218[2011-06-14])
  • <type_traits> (since b206[2011-05-03])
  • <tuple> (since b206[2011-05-03])
    • not relying on LWG 1118: tuple query APIs do not support cv-qualification
      • replaced by ystdex::tuple_size (see below)
    • with LWG 1191: tuple get API should respect rvalues
    • with LWG 1382: pair and tuple constructors should forward arguments
    • with LWG 1384: Function pack_arguments is poorly named (i.e. forward_as_tuple; since b206[2011-05-03])
    • with N2244: Wording for decay, make_pair and make_tuple
    • with N2299: Concatenating tuples, with modification (since b303[2012-04-23])
  • <unordered_map> (since b206[2011-05-03])
  • <unordered_set> (since b206[2011-05-03])

New headers

New headers after C++03 may be used.

C++11 baseline

New non-TR1 headers from C++11 are used:

  • <atomic> (for all platforms from b590[2015-04-03]; for platforms supporting multithreading since b328[2011-07-25]; for platform MinGW32 since b299[2013-04-08])
  • <chrono> (since b291[2012-03-07])
  • <forward_list> (since b218[2011-06-14]; see N2543)
    • without LWG 1340: Why does forward_list::resize take the object to be copied by value?
  • <initializer_list> (since b297[2012-03-27]; see N2672)
  • <typeindex> (since b468[2014-01-20])
  • <regex> (since b795[2017-06-11])
  • <system_error> (since b476[2014-02-16]; see N2241)

Multithreading

For all implementations supporting multithreading, these headers are also used:

  • <condition_variable> (since b328[2011-07-25]; for platform MinGW32 since b299[2013-04-08])
  • <future> (since b520[2014-07-23])
  • <mutex> (since b328[2011-07-25]; for platform MinGW32 since b299[2013-04-08])
  • <thread> (since b328[2011-07-25]; for platform MinGW32 since b299[2013-04-08])

Note: LWG 1360 specified single-threaded program should be able to use <atomic>, and resolved by N3256. However, the current YSLib doesn't require it for single-threaded programs.

Beyond C++11

For rules of baseline, new headers are only used conditionally, mostly for avoiding need of replacements in non-C++11 modes where the specified headers are available.

  • `<memory_resource> (since b842[2018-10-27]
    • replacement provided by <ystdex/memory_resource.h> if not available
  • `<string_view> (since b833[2018-07-31]
    • replacement provided by <ystdex/string_view.hpp> if not available
  • <optional> (since b831[2018-07-13])
    • replacement provided by <ystdex/optional.h> if not available

Technical Specifications

Technical Specifications are retargetted to standards finally. Not all features are used until they are adopted to the standard draft.

TR1

TR1 features was once used, but now retired and corresponding ISO C++ features shall be used, since they are imported from TR1 (see editor's report N2008 and the correspoinding working draft N2009) (since b206[2011-05-03]):

  • TR1 <type_traits> including metafunctions and std::tr1::aligned_storage (since b175[2010-12-23])
    • replaced by C++11 std::aligned_storage, see N2341 (since b206[2011-05-03])
  • TR1 <memory> std::tr1::shared_ptr (since b203[2011-04-22])

Some TR1 features are not used, as C++11 features are directly used instead:

  • TR1 <memory> std::tr1::function is superseded by std::function (since b207[2011-05-09])
    • with LWG 2132: std::function ambiguity
  • TR1 <cstdint> types in std::tr1 are superseded std types in ISO C++11 (since b209[2011-05-14])
    • NOTE Previously, <stdint.h> from GCC or ISO C99 was used optionally (since b133[2010-07-16]).
  • TR1 <cmath> std::tr1::llround is superseded by std::llround (since b260[2011-11-15])
    • NOTE The feature was not actually relied on, instead ISO C99 llround in the global namespace was used as a workaround for implementations, until YBase.LibDefect.CMath was settled (since b556[2014-11-27]).

Some features are not used, but being compatible (see also Neutral of existence features below).

  • TR1 additions to header <cfloat>

Other superseded TR1 features in the listed headers above may be also relied on since then.

Other TSes

Although not used directly for evolution rules, some adopted modifications have already in the final draft of technical specifications:

  • N4480: Programming Languages — C++ Extensions for Library Fundamentals
    • LWG 2409: [fund.ts] SFINAE-friendly common_type/iterator_traits should be removed from the fundamental-ts (adopted 2014-06)
    • N4288: Strike string_view::clear from Library Fundamentals (adopted 2014-11)
    • N3843: A SFINAE-Friendly std::common_type (adopted 2014-03, removed for LWG 2409 2014-11)
    • N4391: make_array, revision 4 (adopted 2015-04)
      • see also direct replacement ystdex::make_array
  • N4562: Working Draft, C++ Extensions for Library Fundamentals, Version 2
    • parts on N3793: A proposal to add a utility class to represent optional objects (Revision 5)
      • revised previously-adopted N3672: A proposal to add a utility class to represent optional objects (Revision 4) (adopted 2013-04)
        • moved to Library TS by LWG motion 6 in N3769
    • with N3765: On Optional
    • based on N4282: A Proposal for the World's Dumbest Smart Pointer, v4

They are listed here for exposition-only use. The actual adoption depends on items depending them on.

Adopted changes

Features adopted shall be compatible with rules for general status.

To be reviewd

These pre-C++11 resolutions are believed have been relied on. It is yet to determine the "since" clause (reviewed b865[2019-08-30]).

  • some issues resolved by N2757: Expedited core issues handling (revision 2)
    • CWG 614: Results of integer / and %
    • CWG 624: Overflow in calculating size of allocation (see also CWG 476)
      • CWG 256: overflow calculating size of new array
      • see also std::bad_array_new_length from N2932 (since b941[2022-03-15])

Restrictive improvement

The conformance of following lists of clearer specification with probable stronger restrictions (to the implementation and program-provided code including this project) or fixed specifications aligned with all existed implementations are concerned and took into account (revised b971[2023-04-04]).

For specifications before C++11:

  • CWG 96: Syntactic disambiguation using the template keyword
  • LWG 201: Numeric limits terminology wrong
  • CWG 220: require de-allocation not throw
  • LWG 274: a missing/impossible allocator requirement (partially overriden by LWG 2447)
  • LWG 294: User defined macros and standard headers
  • LWG 300: list::merge() specification incomplete
  • LWG 386: Reverse iterator's operator[] has impossible return type
  • LWG 416: definitions of XXX_MIN and XXX_MAX macros in climits, reworded slightly
  • LWG 422: explicit specializations of member functions of class templates, reworded slightly
  • LWG 456: Traditional C header files are overspecified
  • LWG 420: is std::FILE a complete type?

For specifications between C++11 and C++14:

  • CWG 1376: static_cast of temporary to rvalue reference (see also CWG 1568)
  • CWG 1430: Pack expansion into fixed alias template parameter list
  • CWG 1493: Criteria for move-construction
  • CWG 1570: Address of subobject as non-type template argument
  • CWG 1596: Non-array objects as array[1]
  • CWG 1629: Can a closure class be a literal type?
  • CWG 1672: Layout compatibility with multiple empty bases
  • CWG 1751: Non-trivial operations vs non-trivial initialization
  • CWG 1885: Return value of a function is underspecified
  • LWG 2013: Do library implementers have the freedom to add constexpr? (see also here)
  • LWG 2014: More restrictions on macro names
  • LWG 2447: Allocators and volatile-qualified value types
  • N3436: std::result_of and SFINAE
  • N3644: Null Forward Iterators (adopted 2013-04)

For specifications between C++14 and C++17:

  • LWG 2129: User specializations of std::initializer_list
  • LWG 2139: What is a user-defined type?
  • LWG 2150: Unclear specification of find_end
  • LWG 2419: Clang's libc++ extension to std::tuple
  • P0180R2: Reserve a New Library Namespace Future Standardization
    • adopted by LWG motion 34 in editor's report N4603
    • NOTE The title in LWG motion 34 in N4603 is "Reserve a New Library Namespace Future for Standardization". This seems more correct.

For specification beyond C++17 (including drafting and WP state):

  • CWG 2256: Lifetime of trivially-destructible objects
  • LWG 3031: Algorithms and predicates with non-const reference arguments
  • LWG 3140: COMMON_REF is unimplementable as specified (adopted 2018-11, in editor's report N4792)

Deprecation

Features in the following adopted deprecation are not depended on (revised b863[2019-08-01]):

  • P0174R2: Deprecating Vestigial Library Parts in C++17 (adopted 2016-06; since b835[2018-08-13])
  • deprecation of shared_ptr unique in P0521R0: Proposed Resolution for CA 14 (shared_ptr use_count/unique) (adopted 2016-11; since b835[2018-08-14])
    • LWG 2776: shared_ptr unique() and use_count()
    • see national body comment CA 14 in P0488R0: WG21 Working Paper, NB Comments, ISO/IEC CD 14882
  • P0767R1: Deprecate POD
    • CWG 2323: Expunge POD
    • see national body comment US 101 in P0488R0: WG21 Working Paper, NB Comments, ISO/IEC CD 14882 (adopted 2017-11; since b853[2018-02-15])
  • P0806R2: Deprecate implicit capture of this via [=] (adopted 2018-06)

Removal

Several features removed in C++17 are not depended on (revised b935[2021-12-26]):

  • LWG 2385: function::assign allocator argument doesn't make sense
  • LWG 2487: bind() should be const-overloaded, not cv-overloaded
    • in libstdc++ this is an extension (although declared deprecated by _GLIBCXX_DEPR_BIND) but the implementation (std::__volget) is essentially not type-safe (with const_cast to remove volatile in the calls)
  • P0001R1: Remove Deprecated Use of the register Keyword
  • P0002R1: Remove Deprecated operator++(bool)
  • P0004R1: Remove Deprecated iostreams aliases
  • P0302R1: Removing Allocator Support in std::function (rev 1) (adopted 2016-06)
    • N2308: Adding allocator support to std::function for C++0x, with modification
    • LWG 2370: Operations involving type-erased allocators should not be noexcept in std::function
    • LWG 2501: std::function requires POCMA/POCCA
    • LWG 2502: std::function does not use allocator::construct
    • see also the replacement ystdex::function
  • all parts but mentioned below of removal in P0619R4: Reviewing Deprecated Facilities of C++17 for C++20
    • D.4 C++ standard library headers [depr.cpp.headers]
    • D.7 uncaught_exception [depr.uncaught]
    • D.8 Old adaptable function bindings [depr.func.adaptor.binding]
    • D.9 The default allocator [depr.default.allocator]
    • D.10 Raw storage iterator [depr.storage.iterator]
    • D.12 Deprecated type traits [depr.meta.types]
    • D.14 Deprecated shared_ptr observers [depr.util.smartptr.shared.obs]

A few features already removed are conditionally enabled only when available (for old versions of the language dialects):

  • P0003R1: Removing Deprecated Exception Specifications from C++17
  • D.3 Deprecated exception specifications [depr.except.spec] in P0619R4: Reviewing Deprecated Facilities of C++17 for C++20

A few features already removed are replaced directly:

  • std::get_temporary_buffer in P0619R4 are replaced by ystdex::get_temporary_buffer

C++11 features

C++11 library features used indirectly (not mandated, but with design in mind that could make more interface usable, e.g. macro substitution or template instantiations):

  • N2761: Towards support for attributes in C++ (Revision 6)

Other C++11 core and library features used directly (without the features the program will either be ill-formed or has unexpected behavior for some input allowed by the API):

  • CWG 45: Access to nested classes (since b273[2012-01-01])
    • CWG 8: Access to template arguments used in a function return type and in the nested name specifier
    • CWG 10: Can a nested class access its own class name as a qualified name if it is a private member of the enclosing class?
  • CWG 87: Exception specifications on function parameters (see also CWG 25, CWG 92 and CWG 133; since b249[2011-10-15])
  • CWG 208: Rethrowing exceptions in nested handlers (since b461[2013-12-23])
  • CWG 222: Sequence points and lvalue-returning operators (since b297[2012-03-27])
    • with CWG 637: Sequencing rules and example disagree
  • CWG 226: Default template arguments for function templates (since b387[2013-03-11])
  • CWG 254: Exception types in clause 19 are constructed from std::string (since b643[2015-10-08]; i.e. const char* parameter in constructor of standard exception classes)
  • CWG 302: Value-initialization and generation of default constructor (since b206[2011-05-03])
  • CWG 339: Overload resolution in operand of sizeof in constant expression (since b591[2015-04-10])
  • CWG 382: Allow typename outside of templates (since b421[2013-07-03])
  • CWG 519: Null pointer preservation in void* conversions (since b204[2011-04-26])
  • CWG 542: Value initialization of arrays of POD-structs (since b206[2011-05-03])
  • CWG 765: Local types in inline functions with external linkage (since b282[2012-02-04])
  • CWG 1104: Global-scope template arguments vs the <: digraph (since b493[2014-04-16])
  • CWG 1330: Delayed instantiation of noexcept specifiers (since b792[2017-06-05])
  • LWG 49: Underspecification of ios_base::sync_with_stdio (since b565[2015-01-16])
  • LWG 91: Description of operator>> and getline() for string<> might cause endless loop (since b663[2015-12-18])
  • LWG 130: Return type of container::erase(iterator) differs for associative containers (since b216[2011-06-08])
  • LWG 254: Exception types in clause 19 are constructed from std::string (since b643[2015-10-08])
  • LWG 265: std::pair::pair() effects overly restrictive (since b206[2011-05-03])
  • LWG 280: Comparison of reverse_iterator to const reverse_iterator (since b408[2013-05-30])
  • LWG 371: Stability of multiset and multimap member functions (since b216[2011-06-08])
  • LWG 376: basic_streambuf semantics (since b616[2015-07-21])
  • LWG 419: istream extractors not setting failbit if eofbit is already set (since b437[2015-01-16])
  • LWG 438: Ambiguity in the "do the right thing" clause (since b274[2012-01-04])
  • LWG 453: basic_stringbuf::seekoff need not always fail for an empty stream (since b617[2015-07-25])
  • LWG 534: Missing basic_string members (i.e. basic_string pop_back, back and front; since b315[2012-06-08])
  • LWG 559: numeric_limits<const T>, reworded slightly (since b440[2013-08-29])
  • LWG 564: stringbuf seekpos underspecified (since b743[2016-11-21])
  • LWG 589: Requirements on iterators of member template functions of containers (since b216[2011-06-08])
  • LWG 596: 27.8.1.3 Table 112 omits "a+" and "a+b" modes (since b326[2012-07-19])
  • LWG 611: Standard library templates and incomplete types (since b206[2011-05-03])
  • LWG 694: std::bitset and long long (since b932[2021-12-04])
  • LWG 704: MoveAssignable requirement for container value type overly strict (since b206[2011-05-03])
  • LWG 711: Contradiction in empty shared_ptr (since b784[2017-04-29])
  • LWG 762: std::unique_ptr requires complete type? (since b238[2011-09-07])
  • LWG 811: pair of pointers no longer works with literal 0 (since b206[2011-05-03])
    • partially superseded when N4387 is conditonally used
  • LWG 868: Default construction and value-initialization (since b206[2011-05-03])
  • LWG 771: Impossible throws clause in [string.conversions] (since b375[2013-01-22])
  • LWG 772: Impossible return clause in [string.conversions] (since b375[2013-01-22])
  • LWG 806: unique_ptr::reset effects incorrect, too permissive (since b206[2011-05-03])
  • LWG 809: std::swap should be overloaded for array types (since b620[2015-08-02])
  • LWG 817: bind needs to be moved (since b401[2013-05-02])
  • LWG 900: Stream move-assignment (tentatively since b620[2015-08-01]; since b727[2016-09-17])
  • LWG 911: I/O streams and move/swap semantic (tentatively since b620[2015-08-01]; since b727[2016-09-17])
  • LWG 922: [func.bind.place] Number of placeholders (since b437[2013-08-22])
  • LWG 929: Thread constructor (since b384[2013-03-01])
  • LWG 993: _Exit needs better specification (since b565[2015-01-16])
  • LWG 1019: Make integral_constant objects useable in integral-constant-expression​s (since b590[2015-04-10])
  • LWG 1040: Clarify possible sameness of associative container's iterator and const_iterator (since b496[2014-05-01])
  • LWG 1178: Header dependencies (since b338[2012-09-13])
    • LWG 343: Unspecified library header dependencies (see also N2259)
  • LWG 1192: basic_string missing definitions for cbegin / cend / crbegin / crend (since b546[2014-10-17])
  • LWG 1382: pair and tuple constructors should forward arguments (since b206[2011-05-03])
  • N1626: Proposed Resolution for Core Issue 39 (Rev. 1) (since b447[2013-09-25])
    • CWG 39: Conflicting ambiguity rules
  • N1653: Working draft changes for C99 preprocessor synchronization (since b257[2011-11-04])
    • NOTE Previously only the support of variadic macros and empty macro arguments as a GCC extension is used.
  • N1720: Proposal to Add Static Assertions to the Core Language (Revision 3) (since b206[2011-05-03])
    • with CWG 676: static_assert-declaration​s and general requirements for declarations
  • N1757: Right Angle Brackets (Revision 1) (since b206[2011-05-03])
  • N1780: Comments on LWG issue 233: Insertion hints in associative containers (since b216[2011-06-08])
  • N1811: Adding the long long type to C++ (Revision 3) (since b206[2011-05-03])
    • with specializations of numerical_limits in N2114: long long Goes to the Library, Revision 1 (since b932[2021-12-04])
  • N1822: A Proposal to add a max significant decimal digits value to the C++ Standard Library Numeric limits (since b260[2011-11-12]; i.e. numeric_limits::max_digits10)
    • without LWG 613: max_digits10 missing from numeric_limits
  • N1836: Draft Technical Report on C++ Library Extensions (Built-in type traits) (since b206[2011-05-03])
    • with LWG 1182: Unfortunate hash dependencies
    • with LWG 1255: declval should be added to the library (since b260[2011-11-14])
    • with LWG 1270: result_of should be moved to <type_traits> (since b245[2011-09-23])
  • cbegin/cend/crbegin/crend from N1913: A Proposal to Improve const_iterator Use (version 2) (since b206[2011-05-03])
  • N1858: Rvalue Reference Recommendations for Chapter 23, reworded (since b216[2011-06-08])
  • N1984: Deducing the type of variable from its initializer expression (revision 4) (since b206[2011-05-03]; i.e. auto-typed variables)
    • with CWG 615: Incorrect description of variables that can be initialized
  • N1986: Delegating Constructors (revision 3) (since b311[2011-05-25])
  • N1987: Adding "extern template" (version 2) (since b206[2011-05-03])
  • N2005: A maximum significant decimal digits value for the C++0x Standard Library Numeric limits (since b301[2012-04-13])
  • N2179: Language Support for Transporting Exceptions between Threads (since b538[2014-09-28]; i.e. exception_ptr and current_exception, etc)
    • with LWG 829: current_exception wording unclear about exception type
    • with LWG 1130: copy_exception name misleading (i.e. make_exception_ptr; since b550[2014-11-04])
    • with N3195: From Throws: Nothing to noexcept (version 2)
  • N2235: Generalized Constant Expressions—Revision 5 (since b246[2011-09-23]; i.e. constexpr)
  • N2238: Minimal Unicode support for the standard library (revision 3) (since b253[2011-10-18])
  • N2239: A finer-grained alternative to sequence points (revised) (since b297[2012-03-27]; notably order of aggregate initialization, see also CWG 1030)
  • N2240: Two missing traits: enable_if and conditional, added specification when B is false (since b206[2011-05-03])
  • N2241: Diagnostics Enhancements for C++0x (Rev. 1), reworded (since b476[2014-02-16]; i.e. <system_error>)
    • with LWG 805: posix_error::posix_errno concerns (since b550[2014-11-04]; i.e. errc)
  • N2242: Proposed Wording for Variadic Templates (Revision 2) (since b251[2011-10-08])
  • N2244: Wording for decay, make_pair and make_tuple (since b206[2011-05-03])
  • features from N2246: 2 of the least crazy ideas for the standard library in C++0x (i.e. next/prev/is_sorted_until/is_heap_until)
    • next/prev (since b375[2013-01-22])
  • N2249: New Character Types in C++, library part reworded (since b253[2011-10-18])
  • N2255: Minor Modifications to the type traits Wording Revision 2, reworded (since b206[2011-05-03])
  • N2258: Templates Aliases (since b433[2013-08-01])
  • N2259: Specify header dependency for <iostream> (since b338[2012-09-13]; see also LWG 343)
  • features(e.g. alignof) from N2341: Adding Alignment Support to the C++ Programming Language / Wording
    • std::aligned_storage used instead of std::tr1::aligned_storage since b206[2011-05-03]
    • other features except std::aligned_union used since b315[2012-06-08]
  • N2342: POD's Revisited; Resolving Core Issue 568 (Revision 5) (since b206[2011-05-03])
    • CWG 543: Value initialization and default constructors
    • CWG 568: Definition of POD is too strict
  • N2343: Decltype (revision 7) (since b206[2011-05-03])
  • N2346: Defaulted and Deleted Functions (since b207[2011-05-05])
  • N2347: Strongly Typed Enums (revision 3), with modification (since b261[2011-11-19])
  • N2348: Wording for std::numeric_limits<T>::lowest() (since b242[2011-09-16])
  • N2349: Constant Expressions in the Standard Library — Revision 2, with modification (since b260[2011-11-12])
  • N2350: Container insert/erase and iterator constness (Revision 1) (since b531[2014-08-31])
  • N2351: Improving shared_ptr for C++0x, Revision 2 (i.e. make_shared etc; since b529[2014-08-24])
  • N2431: A name for the null pointer: nullptr (revision 4) (since b206[2011-05-03]; the replacement as compatible layer introduced since b204[2011-04-26]) NOTE The replacement is direct in YDefinition.
  • N2437: Explicit Conversion Operator Draft Working Paper (revision 3) (since b260[2011-11-15])
  • N2439: Extending move semantics to *this (revised wording) (since b591[2015-04-11])
  • N2442: Raw and Unicode String Literals; Unified Proposal (Rev. 2)
    • unicode string literals used since b253[2011-10-18]
    • raw string literals used since b431[2013-07-23]
    • other features used since b434[2013-08-04]
  • N2530: Making It Easier to Use std::type_info as an Index in an Associative Container (since b468[2014-01-20]; i.e. type_info::hash_code)
  • N2535: Namespace Association ("inline namespace") (since b427[2013-07-11])
  • N2540: Inheriting Constructors (revision 5) (since b538[2014-09-24])
  • N2541: New Function Declarator Syntax Wording (since b207[2011-05-05])
  • N2543: STL singly linked lists (revision 3) (since b218[2011-06-04]; i.e. <forward_list>)
  • N2544: Unrestricted Unions (Revision 2) (since b569[2015-01-29])
  • N2546: Removal of auto as a storage-class specifier (since b206[2011-05-03])
  • N2559: Nesting Exception Objects (Revision 1) (since b477[2014-02-19])
    • with LWG 819: rethrow_if_nested
    • with LWG 1136: Incomplete specification of nested_exception::rethrow_nested()
  • N2634: Solving the SFINAE problem for expressions (since b591[2015-04-10])
    • CWG 339: Overload resolution in operand of sizeof in constant expression
  • N2657: Local and Unnamed Types as Template Arguments (since b206[2011-05-03])
    • CWG 488: Local types, overload resolution, and template argument deduction
    • with CWG 765 above
  • N2659: Thread-Local Storage (since b425[2013-07-08])
    • with CWG 810: Block-scope thread_local variables should be implicitly static (since b425[2013-07-08])
  • parts of N2666: More STL algorithms (revision 2)
    • all_of (since b494[2014-04-24])
    • any_of (since b360[2012-12-08])
    • none_of (since b547[2014-10-25])
    • find_if_not (since b408[2013-05-30])
    • copy_n (since b292[2012-03-12])
    • partition_point (since b968[2023-02-17])
  • N2672: Initializer List proposed wording (since b297[2012-03-27])
    • CWG 1030: Evaluation order in initializer-lists used in aggregate initialization (since b297[2012-03-27]; see also N2239)
  • N2680: Proposed Wording for Placement Insert (Revision 1) (since b286[2012-02-19])
  • N2709: Packaging Tasks for Asynchronous Execution (since b520[2014-07-23]; i.e. packaged_task)
  • N2756: Non-static data member initializers (since b360[2013-04-29])
  • N2764: Forward declaration of enumerations (rev. 3) (since b658[2015-12-08])
  • N2844: Fixing a Safety Problem with Rvalue References: Proposed Wording (Revision 1) (since b206[2011-05-03])
    • CWG 1138: Rvalue-ness check for rvalue reference binding is wrong (since b206[2011-05-03])
  • N2927: New wording for C++0x Lambdas (rev. 2) (since b212[2011-05-27])
  • N2930: Range-Based For Loop Wording (Without Concepts) (since b316[2011-06-11])
  • std::bad_array_new_length from N2932: Fixing freestanding: iteration 2.2 (since b941[2022-03-15])
  • features from N2982: Allocators post Removal of C++ Concepts (Rev 1)
    • std::addressof used since b288[2012-02-26]
      • LWG 970: addressof overload unneeded
    • std::allocator_traits except propagation traits used since b592[2015-04-19]
    • propagation traits in std::allocator_traits used since b830[2018-07-08]
      • LWG 431: Swapping containers with unequal allocators
  • N3050: Allowing Move Constructors to Throw (Rev. 1) (since b319[2012-06-24]; i.e. noexcept and std::move_if_noexcept)
    • with LWG 1349: swap should not throw
    • with N3180: More on noexcept for the Strings Library (since b329[2012-08-05])
    • with N3279: Conservative use of noexcept in the Library (since b461[2013-12-23])
  • N3052: Converting Lambdas to Function Pointers (since b360[2012-12-07])
  • features except in <type_traits> from N3053: Defining Move Special Member Functions (since b230[2011-08-07])
    • with LWG 1309: Missing expressions for Move/CopyConstructible
      • LWG 1283: MoveConstructible and MoveAssignable need clarification of moved-from state
  • N3143: Proposed wording for US 90 (since b206[2011-05-03]; i.e. std::forward)
  • N3168: Problems with Iostreams Member Functions (Amended from US 137) (since b805[2017-09-26])
  • N3189: Observers for the three handler functions (since b550[2014-11-04])
  • N3272: Follow-up on override control (since b311[2011-05-25])

Tentatively applied

All adopted changes are confirmed being included in the newest working paper and would not be removed in future unless they are not in the working paper or published standard any longer.

Platform-dependent features

Some features only are relied in platform-dependent implementation details where all of supported platforms of the project has been confirmed to support. Currently (revised b971[2023-04-05]) there are none, but see the YCLib replacements below.

Conditionally used features

Some C++11 features are not requried to reduce compatibility impact on implementations, but can be utilized when available, i.e. used conditionally (by conditional inclusion or being transparent):

  • N2340: C99 Compatibility : __func__ and predeclared identifiers (revision 2) (since b638[2015-09-24])

Some post-C++11 features are used conditionally:

  • CWG 616: Definition of “indeterminate value” (since b663[2016-01-11]) (see also CWG 1213)
    • without CWG 129: Stability of uninitialized auto variables
    • without CWG 240: Uninitialized values and undefined behavior (see also CWG 129)
    • without CWG 312: “use” of invalid pointer value not defined (see also CWG 623)
    • without CWG 623: Use of pointers to deallocated storage (see also CWG 312)
  • CWG 1581: When are constexpr member functions defined? (since b834[2016-08-03])
  • CWG 1558: Unused arguments in alias template specializations (since b653[2015-11-25]; see also CWG 1430, CWG 1520 and CWG 1554)
  • LWG 2285: make_reverse_iterator (since b595[2015-05-01])
    • replaced by ystdex::make_reverse_iterator conditionally since b833[2018-07-29]
  • N3421: Making Operator Functors greater<> (since b679[2016-03-19]; see also N3657)
  • N3478: Core Issue 1512: Pointer comparison vs qualification conversions (since b562[2014-12-22])
    • CWG 73: Pointer equality
    • CWG 1512: Pointer comparison vs qualification conversions
  • N3493: Compile-time integer sequences (since b589[2015-04-03])
  • N3652: Relaxing constraints on constexpr functions/constexpr member functions and implicit const (adopted 2013-04; since b591[2015-04-15])
  • N3655: TransformationTraits Redux, v2 (since b595[2015-05-01])
  • N3656: make_unique (Revision 1) (since b617[2015-07-23])
  • N3657: Adding heterogeneous comparison lookup to associative containers (rev 4) (adopted 2013-04; rev 3 not in the list)
    • without is_transparent since b678[2016-03-17]
    • with is_transparent since b679[2016-03-19]
  • N3671: Making non-modifying sequence operations more robust: Revision 2 (since b627[2015-08-30])
  • N3778: C++ Sized Deallocation (since b842[2018-10-29])
  • N4169: A proposal to add invoke function template (Revision 1) (since b617[2015-07-23])
    • without P1065R2: constexpr INVOKE
  • pair constructors improvement in N4387: Improving pair and tuple, revision 3 (adopted 2015-05; since b850[2019-01-14])
    • missed in N4528, see this commit.
    • without LWG 2051: Explicit tuple constructors for more than one parameter
    • without partially addressed LWG 2312: tuple's constructor constraints need to be phrased more precisely
    • LWG 2397: map::emplace and explicit V constructors
      • see also EWG 114: N4074 Let return {expr} Be Explicit, Revision 2, N4131 explicit should never be implicit, N4094 Response To: Let return {expr} Be Explicit, N4029 Let return Be Direct and explicit, N3452 (unpublished) Let {x,y,z} => explicit (resolved as NAD)
  • N4389: Wording for bool_constant, revision 1 (since b617[2015-07-23])
  • some C11 features in P0063R3: C++17 should refer to C11 instead of C99 (adopted 2016-06)
    • DBL_HAS_SUBNORM, FLT_HAS_SUBNORM and LDBL_HAS_SUBNORM (since b932[2021-12-04])
  • P0035R4: Dynamic memory allocation for over-aligned data (since b835[2018-08-14])
  • P0188R1: Wording for [[fallthrough]] attribute. (adopted 2016-02; since b793[2017-06-06])
  • P0189R1: Wording for [[nodiscard]] attribute. (adopted 2016-02; since b823[2018-07-26])
  • P0212R1: Wording for [[maybe_unused]] attribute. (adopted 2016-02; since b823[2018-07-26])
  • P0386R2: Inline Variables (adopted 2016-06; since b831[2018-07-13])
  • part (with only the signature available in C++11) of P0674R1: Extending make_shared to Support Arrays (adopted 2017-07; since b849[2018-12-30])
    • LWG 2070: allocate_shared should use allocator_traits::construct
  • P0593R6: Implicit creation of objects for low-level object manipulation (adopted 2020-02; since b966[2023-02-01])
    • NOTE This was incorrectly relied on since b865[2019-08-25].
  • P1774R8: Portable assumptions (adopted 2022-08; since b971[2018-04-02])

Feature testing

Following Feature Testing study group (SD-6 recommendations approved by SG 10) documents after EWG 22 have been reviewed, with some of them applied or updated:

  • N4200: Feature-testing recommendations for C++ (since b591[2015-04-15]; i.e. __has_cpp_attribute, etc; see subsequent N4440)
  • N4440: Feature-testing recommendations for C++ (superseded)
  • N4535: Feature-testing preprocessor predicates for C++17 (i.e. __has_include and __has_cpp_attribute, see below)
  • P0096R1: Feature-testing recommendations for C++ (since b679[2016-03-20]; see subsequent P0941R0)
  • P0941R2: Integrating feature-test macros into the C++ WD (rev. 2) (since b831[2018-07-12])

Following documents override feature testing paper which is incorporated into the standard draft as normative features:

  • P0061R1: __has_include for C++17 (adopted 2015-10; since b831[2018-07-12])

Some additional feature testing support are also conditionally reviewd. Some are already extensions of Clang++ and other implementations for several revisions. The identifiers with __has_ in following list are being used and considered for conditional inclusion:

  • __has_attribute since b628[2015-09-01], after in macro detection candidate list since b492[2014-04-10]
  • __has_builtin since b535[2014-09-14]
  • __has_extension and __has_feature since b484[2013-03-09]

Tentatively not applied

Used deprecations

There are no features eventually removed in future versions of ISO C++ are yet to be resolved currently.

Workarounds

Several acknowledged but not adopted (by the draft) issues (revised b834[2018-07-31]) are assumed not effect user code with workarounds provided by implementations, including:

  • LWG 2472: Heterogeneous comparisons in the standard library can result in ambiguities
    • no effect on YStandardEx replacement as there is no std::rel_ops-like operators in namespace ystdex
  • LWG 2858: LWG 2472: actually an incompatibility with C++03 (see LWG 280)
    • no effect on ystdex::reversed_iterator

Primarily replaceable

The following features are not relied on, but confirmed still being compatible, with further rules to incooperate:

  • part of P0551R3: Thou Shalt Not Specialize std Function Templates! (adopted 2018-03)
    • though not relied on, any of the entities overloadable with standard components as the subset of the direct replacements specified in following clauses conform to the definition of the term customization point specified here
    • designated customization point is extended to any direct replacments implicitly (i.e. a direct replacement is also implied to be customized by user code if it comforms to a designated customization point specified here), or explicitly following the designation otherwise specified by the interface documentation and with further rules to be intercooperated

The following changes about standard library are not depended on, but were considered some replacements have been used as forcing the requirements for implementations other than using of YStandardEx replacements:

  • part of N4258: Cleaning up noexcept in the Library (Rev 3) (adopted 2014-11)
    • only for part of noexcept which is not exclusively covered by the applying specified below, as noexcept can be added in user-code by introducing new classes meets the changes here like direct replacements (e.g. inheriting the base containers) when direct replacements is not yet provided by YStandardEx

Replaced directly

These C++ features are directly provided by YDefinition as historical interests, as all supported configuartions have conform to the standard versions already including them:

  • N2431: A name for the null pointer: nullptr (revision 4) (since b206[2011-05-03])
    • replaced by ystdex::nullptr_t and ystdex::nullptr
    • The type ystdex::nullptr_t is also provided for C++/CLI without depending on std.

These C++ features are not required, but the replacements are used instead.

  • LWG 1118: tuple query APIs do not support cv-qualification
    • replaced by ystdex::tuple_size (since b958[2022-10-15])

These post-C++11 library features are not required currently (revised b969[2023-02-21]) but direct replacements (to be used conditionally once the draft standard is approved) are provided in YBase:

  • parts of LWG 1234: "Do the right thing" and NULL
    • replaced by ystdex::basic_string (since b832[2018-07-25])
    • see also dedicated replacement ystdex::list
  • LWG 2108: No way to identify allocator types that always compare equal
    • with part of N4258: Cleaning-up noexcept in the Library (Rev 3)
    • with LWG 2467: is_always_equal has slightly inconsistent default
    • std::allocator_tratits::is_always_equal is provided as member of ystdex::allocator_traits; see also dedicated replacement ystdex::map for one of the drop-in replacements
  • LWG 2112: User-defined classes that cannot be derived from
    • i.e. is_final
    • replaced by ystdex::is_final with compatibility excptions (since b938[2022-02-09])
      • implemented in a best effort way: either std::is_final or __is_final builtin is used
  • LWG 2141: common_type trait produces reference types
    • replaced by ystdex::common_type and ystdex::common_type_t (since b937[2022-02-06])
  • LWG 2148: Hashing enums should be supported directly by std::hash
    • replaced by ystdex::hash (since b967[2023-02-07])
    • without LWG 2543: LWG 2148 (hash support for enum types) seems under-specified
      • NOTE This is conditonally available if the implementation of the primary template of std::hash is conforming.
  • LWG 2188: Reverse iterator does not fully support targets that overload operator&
    • replaced by ystdex::reverse_iterator
    • adopted by LWG motion 3 in editor's report N3938 with editorial fix
  • LWG 2268: Setting a default argument in the declaration of a member function assign of std::basic_string
    • replaced by ystdex::basic_string (since b833[2018-07-31])
  • parts of LWG 2193: Default constructors for standard library containers are explicit
    • replaced by ystdex::basic_string (since b832[2018-07-25])
    • see also dedicated replacements ystdex::list, ystdex::map and ystdex::unordered_map
  • LWG 2247: Type traits and std::nullptr_t
    • replaced by ystdex::is_null_pointer
  • LWG 2296: std::addressof should be constexpr
    • replaced by ystdex::addressof with compatibility excptions
      • implemented in a best effort way: constepxr works with most usual but not all cases like C++17 due to compatibility limitations where no __builtin_addressof is supported by the implementation
      • with LWG 2598: addressof works on temporaries
    • adopted by part of LWG motion 16 in editor's report N4583
  • part of common_type changes in LWG 2408: SFINAE-friendly common_type/iterator_traits is missing in C++14
    • adopted from N3843: A SFINAE-Friendly std::common_type
    • replaced by ystdex::common_type and ystdex::common_type_t
  • part of LWG 2455: Allocator default construction should be allowed to throw
    • replaced by ystdex::basic_string
  • LWG 2465: SFINAE-friendly common_type is nearly impossible to specialize correctly and regresses key functionality
    • first part of LWG 2460: LWG issue 2408 and value categories
    • replaced by ystdex::common_type and ystdex::common_type_t
  • LWG 2579: Inconsistency wrt Allocators in basic_string assignment vs. basic_string::assign
    • replaced by ystdex::basic_string
  • LWG 2583: There is no way to supply an allocator for basic_string(str, pos)
    • replaced by ystdex::basic_string
  • LWG 2763: common_type_t<void, void> is undefined
    • replaced by ystdex::common_type and ystdex::common_type_t
  • LWG 2770: tuple_size<const T> specialization is not SFINAE compatible and breaks decomposition declarations
    • replaced by ystdex::tuple_size (since b958[2022-10-15])
  • LWG 2778: basic_string_view is missing constexpr
    • replaced by ystdex::basic_string_view
  • LWG 2788: basic_string range mutators unintentionally require a default constructible allocator
    • replaced by ystdex::basic_string
  • LWG 2812: Range access is available with <string_view>
    • replaced by "string_view.hpp"
  • LWG 2817: std::hash for nullptr_t
    • replaced by ystdex::hash (since b967[2023-02-07])
  • N3911: TransformationTrait Alias void_t
    • replaced by void_t in namespace ystdex::cpp2017, with workaround for CWG 1558
    • see N3843 for motivation
  • parts of N4258 (adopted 2014-11)
    • replaced by ystdex::allocator_traits::is_always_equal; see LWG 2108
    • replaced by ystdex::basic_string, except the part revised by LWG 2455
    • see also dedicated replacement ystdex::map
    • see also LWG 2455 of change on std::vector (not replaced)
  • N4277: TriviallyCopyable reference_wrapper (Revision 1)
    • with P0357R2: reference_wrapper for incomplete types
    • replaced by ystdex::lref
  • N4280: Non-member size() and more (Revision 2) (adopted 2014-11)
    • replaced by size, empty and data in namespace ystdex::cpp2017 conforming to designated customization point specified by P0551R3, also ystdex::range_size with addtional interface for std::initializer_list
  • N4436: Proposing Standard Library Support for the C++ Detection Idiom
    • partially replaced by is_detected, detected_t, detected_or, detected_or_t, is_detected_exact and is_detected_convertile in namespace ystdex
  • std::experimental::fundamentals_v2::observer_ptr in N4562
    • replaced feature based on N4282: A Proposal for the World's Dumbest Smart Pointer, v4
  • P0007R1: Constant View: A proposal for a std::as_const helper function template (adopted 2015-11)
  • P0013R1: Logical Operator Type Traits (revision 1) (adopted 2015-10)
    • replaced by conjunction, disjunction and negation in namespace ystdex, based on and_, or_ and not_ (see dedicated replacements below)
  • parts of P0031R0: A Proposal to Add Constexpr Modifiers to reverse_iterator, move_iterator, array and Range Access
    • partially replaced by reverse_iterator, begin, cbegin, end, cend, rbegin, crbegin, rend and crend in namespace ystdex
    • adopted by part of LWG motion 16 in edtitor report N4583
  • P0091R3: Template argument deduction for class templates (Rev. 6) (adopted 2016-06)
  • interface in headers <optional>, <string_view> and <memory_resource> in P0220R1: Adopt Library Fundamentals V1 TS Components for C++17 (R1)
    • <optional> replaced by <ystdex/optional.hpp>
      • std::bad_optional_access replaced by ystdex::bad_optional_access
      • std::optional replaced by ystdex::cpp2017::optional
    • <string_view> replaced by <ystdex/string_view.hpp>
      • std::basic_string_view replaced by ystdex::cpp2017::basic_string_view
      • std::string_view replaced by ystdex::cpp2017::string_view
      • std::wstring_view replaced by ystdex::cpp2017::wstring_view
      • std::u16string_view replaced by ystdex::cpp2017::u16string_view
      • std::u32string_view replaced by ystdex::cpp2017::u32string_view
    • <memory_resource> replaced by <ystdex/memory_resource.h>
      • std::pmr::memory_resource replaced by ystdex::pmr::cpp2017::memory_resource
      • std::pmr::polymorphic_allocator replaced by ystdex::pmr::cpp2017::polymorphic_allocator
      • std::pmr::new_delete_resource replaced by ystdex::pmr::cpp2017::new_delete_resource
      • std::pmr::null_memory_resource replaced by ystdex::pmr::cpp2017::null_memory_resource
      • std::pmr::set_default_resource replaced by ystdex::pmr::cpp2017::set_default_resource
      • std::pmr::get_defualt_resource replaced by ystdex::pmr::cpp2017::get_defualt_resource
      • std::pmr::pool_options replaced by ystdex::pmr::pool_options
      • std::pmr::cpp2017::synchronized_pool_resource replaced by ystdex::pmr::cpp2017::synchronized_pool_resource
      • std::pmr::unsynchronized_pool_resource replaced by ystdex::pmr::cpp2017::unsynchronized_pool_resource
      • std::pmr::monotonic_buffer_resource replaced by ystdex::pmr::cpp2017::monotonic_buffer_resource
    • std::apply in <tuple> replaced by ystdex::apply
      • N3915: apply() call a function with arguments from a tuple (V3) (adopted 2014-02)
      • with LWG 2418: [fund.ts] apply does not work with member pointers
    • partially adopted by LWG motion 6 in editor's report N4583
      • <optional>, <string_view>, <memory_resource> and <tuple> in parts of LWG motion 6: P0220R1 "Adopt library fundamentals v1 TS components for C++17") (incompletely applied from N4562)
      • with LWG 2283: [fund.ts] optional declares and then does not define an operator<() (this is actually targetting the standard draft, not the library fundmental draft)
      • with N4078: Fixes for optional objects (adopted 2014-06)
    • with P0254R2: Integrating std::string_view and std::string (adopted 2016-06, by LWG motion 14 in N4603)
      • replaced by ystdex::basic_string and ystdex::basic_string_view
      • with LWG 2742: Inconsistent string interface taking string_view
      • with LWG 2758: std::string{}.assign("ABCDE", 0, 1) is ambiguous
        • LWG 2757: std::string{}.insert(3, "ABCDE", 0, 1) is ambiguous
      • with LWG 2771: Broken Effects of some basic_string::compare functions in terms of basic_string_view (see LWG 2758)
    • with P0337R0: Delete operator= for polymorphic_allocator (adopted by LWG motion 26 in N4603)
    • with P0339R6: polymorphic_allocator<> as a vocabulary type (adopted 2019-03)
    • with changes on pmr::memory_resource in P0619R4
    • with LWG 2724: The protected virtual member functions of memory_resource should be private
    • conditionally with LWG 2740: constexpr optional<T>::operator->
      • only with constexpr addressof (i.e. since C++17 or with some extensions)
    • with LWG 2756: C++ WP optional<T> should 'forward' T's implicit conversions
    • without LWG 2825: LWG 2756 breaks class template argument deduction for optional, as the resolution need deduction guides unsupported here yet
    • with LWG 2806: Base class of bad_optional_access
    • with LWG 2842: in_place_t check for optional::optional(U&&) should decay U
    • with LWG 2843: Unclear behavior of std::pmr::memory_resource::do_allocate()
    • with part (for optional) of LWG 2857: {variant,optional,any}::emplace should return the constructed value
      • partially replaced by ystdex::any
    • with LWG 2900: The copy and move constructors of optional are not constexpr
    • with LWG 2961: Bad postcondition for set_default_resource (similart to LWG 2522 targetting Library Fundamentals V2)
    • with LWG 2969: polymorphic_allocator::construct() shouldn't pass resource()
    • with parts (concerned with polymorphic_allocator) of LWG 2975: Missing case for pair construction in scoped and polymorphic allocators
    • with LWG 3000: monotonic_memory_resource::do_is_equal uses dynamic_cast unnecessarily
    • with LWG 3036: polymorphic_allocator::destroy is extraneous
    • with LWG 3037: polymorphic_allocator and incomplete types
    • with LWG 3038: polymorphic_allocator::allocate should not allow integer overflow to create vulnerabilities
    • with LWG 3113: polymorphic_allocator::construct() should more closely match scoped_allocator_adaptor::construct()
  • P0185R1 Adding [nothrow-]swappable traits, revision 3 (adopted 2016-02)
    • replaced by is_swappable_with, is_swappable, is_nothrow_swappable_with and is_nothrow_swappable in namespace ystdex, and swap in namespace ystdex_swap (see also P0879R0)
    • LWG 2456: Incorrect exception specifications for 'swap' throughout library
    • LWG 2554: Swapping multidimensional arrays is never noexcept
    • see also LWG 2766
  • P0209R2 make_from_tuple: apply for construction (adopted 2016-06, by LWG motion 23 in N4603)
    • replaced by ystdex::make_from_tuple
  • P0298R3: A byte type definition
    • replaced by ystdex::byte, except when the core language support unavailable before C++17, where the name is provided as an alias of unsigned char
    • see also P0583R0.
  • <flat_map> from P0429R9
    • with part (except deduction guides) of LWG 3803: flat_foo constructors taking KeyContainer lack KeyCompare parameter
    • with LWG 3816: flat_map and flat_multimap should impose sequence container requirements
    • without but waiting LWG 3802: flat_foo allocator-extended constructors lack move semantics
    • without part of deduction guides of LWG 3803
    • without LWG 3786: Flat maps' deduction guide needs to default Allocator to be useful
    • without LWG 3804: flat_foo missing some allocator-extended deduction guides
    • replaced by <ystdex/flat_map.hpp>
      • with exception of exception specification of swap by P0429R8 which seems like a defect
  • P0435R1: Resolving LWG Issues re common_type
    • LWG 2465: SFINAE-friendly common_type is nearly impossible to specialize correctly and regresses key functionality
    • LWG 2763: common_type_t<void, void> is undefined
    • replaced by ystdex::common_type and ystdex::common_type_t
  • part of common_type in P0548R1: common_type and duration
    • replaced by ystdex::common_type and ystdex::common_type_t
  • P0550R2: Transformation Trait remove_cvref
    • replaced by ystdex::remove_cvref
  • P0604R0: Resolving GB 55, US 84, US 85, US 86 (adopted 2017-03)
    • partially replaced by invoke_result, invoke_result_t, is_invocable, is_invocable_r, is_nothrow_invocable and is_nothrow_invocable_r in namespace ystdex::cpp2017
    • revised previously-adopted P0077R2: is_callable, the missing INVOKE related trait (adopted 2016-02)
    • national body comments GB 55, US 84, US 85, US 86 accepted with modifications in N4664
    • LWG 2017: std::reference_wrapper makes incorrect usage of std::result_of
    • LWG 2021: Further incorrect usages of result_of
    • with LWG 2219: INVOKE-ing a pointer to member with a reference_wrapper as the object expression
    • LWG 2767: not_fn call_wrapper can form invalid types
  • all new function templates in P0591R4: Utility functions to implement uses-allocator construction (adopted 2018-11)
    • std::uses_allocator_construction_args replaced by ystdex::uses_allocator_construction_args
    • std::make_obj_using_allocator replaced by ystdex::make_obj_using_allocator
    • std::uninitialized_construct_using_allocator replaced by ystdex::uninitialized_construct_using_allocator
  • P0607R0: Inline Variables for the Standard Library
    • depends on CWG 1713, the "minimalistic suggestion" and "additional suggestion" in paper are fully (both) taken in N4659, which is not explicitly indicated by the editor's report N4661
    • partially replaced by ystdex::nullopt
  • P0619R4: Reviewing Deprecated Facilities of C++17 for C++20
    • std::reference_wrapper partially replaced by ystdex::lref
    • std::iterator is not used (see also LWG 2438)
    • changes on std::pmr::memory_resource is implemented for ystdex::pmr::memory_resource
  • P0653R2: Utility to convert a pointer to a raw pointer (adopted 2017-11)
    • std::to_address is replaced by ystdex::to_address
  • P0879R0: Constexpr for swap and swap related functions
    • partially replaced by swap in namespace ystdex_swap
    • LWG 2800: constexpr swap
    • this is now in the working draft, though not in the list of official page as of b834[2018-08-03]
    • adopted by LWG motion 21 in editor's report N4764
  • part of common_type in P0898R3: Standard Library Concepts
    • with LWG 3205: decay_t in the new common_type fallback should be remove_cvref_t
    • replaced by ystdex::common_type and ystdex::common_type_t
  • <flat_set> from P1222R4
    • with LWG 3751: Missing feature macro for flat_set
    • with part (except deduction guides) of LWG 3803: flat_foo constructors taking KeyContainer lack KeyCompare parameter
    • without but waiting LWG 3802: flat_foo allocator-extended constructors lack move semantics
    • without part of deduction guides of LWG 3803
    • without LWG 3774: <flat_set> should include <compare>
    • without LWG 3804: flat_foo missing some allocator-extended deduction guides
    • without LWG 3879: erase_if for flat_{,multi}set is incorrectly specified
    • replaced by <ystdex/flat_set.hpp>
      • with exception of exception specification of swap by P1222R3 which seems like a defect

Some replaced features do not have exact the same features (but a subset) of the corresponding std features, due to limitation of implementations or by design. Except listed above, the limitations are:

  • The following templates do not support program-defined specializations by design:
    • ystdex::common_type
    • ystdex::hash
    • Use explicit specializations of std templates instead.
  • ystdex::hash
    • not with P0513R0: Poisoning the Hash
      • NOTE This is conditonally available if the implementation of the primary template and the corresponding explicit specializations of std::hash is conforming. The following parts are only supported when the implementation of std::hash is conforming.
      • The national body comment FI 15 in P0488R0 is supported (with both std::hash and ystdex::hash) for ystdex::optional is conditionally supported.
      • LWG 2791 is supported with ystdex::hash (but not std::hash) on the basis between corresponding ystdex::basic_string and ystdx::basic_string_view instances, but not for std ones (even when std::basic_string is used as std::basic_string).

Some previously applied pre-C++11 resolutions also work on replacements, listed here for exposition-only use:

  • LWG 280: Comparison of reverse_iterator to const reverse_iterator
    • partially replaced by ystdex::reverse_iterator
  • LWG 386: Reverse iterator's operator[] has impossible return type
    • replaced by ystdex::reverse_iterator::operator[]

Replaced alternatively

These features were once adopted by the working paper but later removed away, and further actions different to the committee are considered by this project for practical reasons (e.g. compatibility and availability in lack of other features):

  • parts of P0032R3: Homogeneous interface for variant, any and optional (Revision 3) (adopted 2016-06)
    • see national body comment CH 3 relavent to parts of P0032R3 in P0488R0: WG21 Working Paper, NB Comments, ISO/IEC CD 14882
    • see P0504R0: Revisiting in-place tag types for any/optional/variant
    • removed by this commit
    • this is not always taken in the direct replacements in namespace ystdex::cpp2017 to avoid impaction from lack of varaible template support

Replaced dedicately

There are several core language issues are resolved by YDefinition core language compatiblitiy features conditioanlly, even though they may be still not adopted or even without a concrete proposed resolution currently (revised b835[2018-08-14]):

  • CWG 2097: Lambdas and noreturn attribute

There are several YBase features which can currently (revised b967[2023-02-07]) be used as replacement for similar post-C++11 library features. See also Features(zh-CN).

  • ystdex::and_, ystdex::or_ and ystdex::not_ are provided to replace logical operation traits introduced in P0013R1
    • currently aliases of them are also direct replacements in namespace cpp2017
  • ystdex::lref is provided as a replacement for std::reference_wrapper with less verbosity
    • supports N4277: TriviallyCopyable reference_wrapper (Revision 1)
    • otherwise mostly same to boost::reference_wrapper besides its name and constexpr requirements (needing core language support)
      • no deprecated members of std::reference_wrapper, see P0619R4: Reviewing Deprecated Facilities of C++17 for C++20
      • supports incomplete value type, see P0357R2
      • not same type to any instance of std::reference_wrapper so not treated specially by std::bind and other std API
    • with part of P0604R0
    • with part of LWG 2993
  • ystdex::unwrap_reference, ystdex::unwrap_reference_t, ystdex::unwrap_ref_decay and ystdex::unwrap_ref_decay_t are provided with extensions for ystdex::lref, as replacement for the following C++20 features
    • P0318R1: unwrap_ref_decay and unwrap_reference (adopted 2018-11)
      • LWG 3202: P0318R1 was supposed to be revised
  • ystdex::get_temporary_buffer is provided to replace std::get_temporary _buffer deprecated by P0174R2 and removed by P0619R4
  • ystdex::any is provided as a replacement for std::any with richer features and greater availability
    • already with following C++17 std::any features
      • LWG 2744
      • LWG 2769
      • parts of P0032R3
      • NOTE: LWG 2509 is superseded by the resolution and mostly not applicable because of the support of non CopyConstructible types (see below)
      • NOTE: LWG 2754 is not applicable because the exact same reason above
    • supports non CopyConstructible types (if a ystdex::any object holding an object of such type is eventually copied, exception would be thrown
    • with underlying interface for custom holders and handlers (for ystdex::any_iterator, etc)
    • supports constructor overload with minimal construction overhead for the empty object with ystdex::default_init_t parameter (at the cost of not supporting constepxr as the default constructor)
    • supports unsafe cast operations and more extensions to bypass checks with narrow contracts
    • early available before several standard proposals, including the any class itself and members like emplace (see P0032R0)
      • actually being first to replace boost::any before std::any was proposed as a post-C++11 feature by N3390: Any Library Proposal (Revision 1)
      • see also the original proposal N1939: Any Library Proposal for TR2
  • ystdex::function is provided as a replacement for std::function with richer features
    • with LWG 2062
    • with allocator support in construction like the feature removed by P0302R1
    • (since C++20) with P0771R1: std::function move constructor should be noexcept(adopted 2016-11)
  • ystdex::list is proveded as a replacement for std::list with enhanced features
    • (since C++11) with the part of LWG 1234: "Do the right thing" and NULL
    • (since C++17) with the part of N4510: Minimal incomplete type support for standard containers, revision 4
    • (since C++20) with the part of P0084R2: Emplace Return Type (Revision 2) (adopted 2016-06)
    • with the part of LWG 2839: Self-move-assignment of library types, again
  • ystdex::map and ystdex::unordered_map are provided as replacements of std::map and std::unordered_map (other associative containers TBD) including follwing enhancement (full ISO C++17 features support):
    • N3657
    • part of N4258 (adopted 2014-11)
    • N4279 (adopted 2014-11)
    • P0083R3 (adopted 2016-06)
    • parts of LWG 2005
    • LWG 2059
    • part of LWG 2354
    • incomplete key and mapped types support which is not supported by C++17 (like N4510) yet (so it is not in namespace ystdex::cpp2017; although lisbstdc++ can support it)
    • part of P0458R2 (adopted 2018-06)

Some YCLib replacements depend on the implementation details of the standard libaray:

  • There are severl replacements provided by module YCLib::FileIO:
    • platform::basic_filebuf
    • platform::basic_ifstream
    • platform::basic_ofstream
    • platform::basic_fstream
    • alias of these templates
    • These types having extensions on supported parameter types of open, as well as standard features:
      • with N1981: Uniform Use of std::string Revision 1
      • with parameter type support similar (a bit like the resolution of LWG 3430) but different to LWG 105 and LWG 454
      • without LWG 2676: Provide filesystem::path overloads for File-based streams
      • without LWG 3430: std::fstream & co. should be constructible from string_view

Neutral of existence

These post-C++03 (adopted, any proposed or any not proposed but theorotically allowed having well-defined behavior) resolutions or features (with diagnostic suggestions) are not depended on currently (revised b972[2023-04-13]) but confirmed still being compatible:

NOTE However, user code can still be affected; some resolutions may have been applied to similar other interface of YBase, see sections above.

  • CWG 446: Does an lvalue-to-rvalue conversion on the "?" operator produce a temporary?
    • CWG 86: Lifetime of temporaries in query expressions
  • CWG 462: Lifetime of temporaries bound to comma expressions
  • CWG 468: Allow ::template outside of templates
  • CWG 475: When is std::uncaught_exception() true? (take 2)
  • CWG 515: Non-dependent references to base class members (see also GCC PR 21008)
  • CWG 569: Spurious semicolons at namespace scope should be allowed
  • CWG 573: Conversions between function pointers and void* (see also CWG 195/CWG 1120)
  • CWG 760: this inside a nested class of a non-static member function
  • CWG 790: Concatenation of raw and non-raw string literals
  • CWG 903: Value-dependent integral null pointer constants
    • NOTE: This is actually adopted after C++11 despite the compatibility clause is agianst to C++03.
  • CWG 1120: reinterpret_cast and void* (depends on CWG 573)
  • CWG 1164: Partial ordering of f(T&) and f(T&&)
  • CWG 1213: Array subscripting and xvalues (see also CWG 616)
  • CWG 1227: Mixing immediate and non-immediate contexts in deduction failure
  • CWG 1255: Definition problems with constexpr functions
  • CWG 1301: Value initialization of union
    • CWG 1324: Value initialization and defaulted constructors
    • CWG 1368: Value initialization and defaulted constructors (part 2)
  • CWG 1310: What is an “acceptable lookup result?”
  • CWG 1399: Missing non-deduced context following a function parameter pack
    • CWG 1388: Deduction with multiple function parameter packs
  • CWG 1402: Move functions too often deleted
    • CWG 1491: Move construction and rvalue reference members
  • CWG 1412: Problems in specifying pointer conversions
  • CWG 1579: Return by converting move constructor
  • CWG 1591: Deducing array bound and element type from initializer list
  • CWG 1626: constexpr member functions in brace-or-equal-initializers
  • CWG 1665: Declaration matching in explicit instantiations
  • CWG 1693: Superfluous semicolons in class definitions (depends on CWG 569)
  • CWG 1778: exception-specification in explicitly-defaulted functions
    • LWG 2165: std::atomic<X> requires X to be nothrow default constructible
  • CWG 1875: Reordering declarations in class scope
  • CWG 1854: Disallowing use of implicitly-deleted functions
  • CWG 1895: Deleted conversions in conditional operator operands
    • CWG 1932: Bit-field results of conditional operators
  • CWG 1952: Constant expressions and library undefined behavior
  • CWG 1980: Equivalent but not functionally-equivalent redeclarations
  • CWG 2137: List-initialization from object of same type
  • CWG 2248: Problems with sized delete
  • CWG 2267: Copy-initialization of temporary in reference direct-initialization
  • CWG 2278: Copy elision in constant expressions reconsidered
  • CWG 2313: Redeclaration of structured binding reference variables
  • CWG 2430: Completeness of return and parameter types of member functions
  • CWG 2446: Questionable type-dependency of concept-ids
  • CWG 2523: Undefined behavior via omitted destructor call in constant expressions
  • LWG 208: Unnecessary restriction on past-the-end iterators
  • LWG 251: basic_stringbuf missing allocator_type
  • LWG 281: std::min() and max() requirements overly restrictive
  • LWG 283: std::replace() requirement incorrect/insufficient
  • LWG 365: Lack of const-qualification in clause 27
  • LWG 387: std::complex over-encapsulated
  • LWG 402: wrong new expression in [some_]allocator::construct
  • LWG 409: Closing an fstream should clear error state
  • LWG 455: cerr::tie() and wcerr::tie() are overspecified
  • LWG 497: meaning of numeric_limits::traps for floating point types
  • LWG 531: array forms of unformatted input functions (i.e. for istream::get)
  • LWG 543: valarray slice default constructor
  • LWG 551: <ccomplex>
  • LWG 562: stringbuf ctor inefficient
  • LWG 565: xsputn inefficient
  • LWG 566: array forms of unformatted input function undefined for zero-element arrays (i.e. for istream::get)
  • LWG 576: find_first_of is overconstrained
  • LWG 578: purpose of hint to allocator::allocate()
  • LWG 586: string inserter not a formatted function
  • LWG 593: __STDC_CONSTANT_MACROS
  • LWG 619: Longjmp wording problem
  • LWG 643: Impossible "as if" clauses
  • LWG 646: const incorrect match_result members
  • LWG 659: istreambuf_iterator should have an operator->()
  • LWG 679: resize parameter by value
    • replaced by ystdex::list::resize
  • LWG 776: Undescribed assign function of std::array (i.e. array::fill)
  • LWG 779: Resolution of #283 incomplete
  • LWG 787: complexity of binary_search
  • LWG 807: tuple construction should not fail unless its element's construction fails
  • LWG 844: complex pow return type is ambiguous
  • LWG 848: Missing std::hash specializations for std::bitset/std::vector<bool>
  • LWG 850: Should shrink_to_fit apply to std::deque? (i.e. deque::shrink_to_fit)
  • LWG 852: unordered containers begin(n) mistakenly const
  • LWG 900: Stream move-assignment
  • LWG 1004: Clarify "throws an exception"
    • see national body comment UK 179 in N2837: C++0X, CD 1, National Body Comments
  • LWG 1012: reverse_iterator default ctor should value initialize (adopted by LWG motion 1 in N3001)
  • LWG 1071: is_bind_expression should derive from integral_constant<bool>
  • LWG 1215: list::merge with unequal allocators
    • replaced by ystdex::list::merge
  • LWG 1334: Insert iterators are broken for some proxy containers compared to C++03
  • LWG 1340: Why does forward_list::resize take the object to be copied by value?
  • LWG 1399: function does not need an explicit default constructor
  • LWG 1402: nullptr constructors for smart pointers should be constexpr
  • LWG 2005: unordered_map::insert(T&&) protection should apply to map too
    • partially replaced by ystdex::map
    • with LWG 2571: §[map.modifiers]/2 imposes nonsensical requirement on insert(InputIterator, InputIterator)
      • replaced by ystdex::map
  • LWG 2045: forward_list::merge and forward_list::splice_after with unequal allocators
  • LWG 2059: C++0x ambiguity problem with map::erase
  • LWG 2103: std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment (see also LWG 2108)
  • LWG 2104: unique_lock move-assignment should not be noexcept
  • LWG 2135: Unclear requirement for exceptions thrown in condition_variable::wait()
  • LWG 2233: bad_function_call::what() unhelpful
  • LWG 2247: Type traits and std::nullptr_t (i.e. is_null_pointer)
  • LWG 2261: Are containers required to use their 'pointer' type internally?
  • LWG 2291: std::hash is vulnerable to collision DoS attack
  • LWG 2313: tuple_size should always derive from integral_constant<size_t, N>
    • replaced by ystdex::tuple_size (since b958[2022-10-15])
  • LWG 2321: Moving containers should (usually) be required to preserve iterators
    • replaced by ystdex::map and some other containers in YBase.YStandardEx which support the resolution
  • LWG 2354: Unnecessary copying when inserting into maps with braced-init syntax
  • LWG 2360: reverse_iterator::operator*() is unimplementable (see also LWG 2204)
  • LWG 2362: unique, associative emplace() should not move/copy the mapped_type constructor arguments when no insertion happens (see also LWG 2006)
  • LWG 2376: bad_weak_ptr::what() overspecified
  • LWG 2393: std::function's Callable definition is broken
  • LWG 2437: iterator_traits::reference can and can't be void
  • LWG 2438: std::iterator inheritance shouldn't be mandated
  • LWG 2442: call_once() shouldn't DECAY_COPY()
  • LWG 2455: Allocator default construction should be allowed to throw
  • LWG 2466: allocator_traits::max_size() default behavior is incorrect
  • LWG 2470: Allocator's destroy function should be allowed to fail to instantiate (split from LWG 2447)
  • LWG 2483: throw_with_nested() should use is_final
  • LWG 2485: get() should be overloaded for const tuple&&
  • LWG 2503: multiline option should be added to syntax_option_type
    • LWG 2343: Is the value of the ECMA-262 RegExp object's multiline property really false?
  • LWG 2509: [fund.ts.v2] any_cast doesn't work with rvalue reference targets and cannot move with a value target
    • NOTE The proposed resolution is targeting the working paper of the TS. It is not applied to std::any in ISO C++17 and it is still not in post-C++17 working draft N4778, and it is overriden by the resolution of LWG 2769 partially.
  • LWG 2543: LWG 2148 (hash support for enum types) seems under-specified
  • LWG 2547: Container requirements (and other library text) should say "strict total order", not just "total order"
  • LWG 2549: Tuple EXPLICIT constructor templates that take tuple parameters end up taking references to temporaries and will create dangling references
  • LWG 2567: Specification of logical operator traits uses BaseCharacteristic, which is defined only for UnaryTypeTraits and BinaryTypeTraits
    • LWG 2568: [fund.ts.v2] Specification of logical operator traits uses BaseCharacteristic, which is defined only for UnaryTypeTraits and BinaryTypeTraits
  • LWG 2577: {shared,unique}_lock should use std::addressof
  • LWG 2591: std::function's member template target() should not lead to undefined behaviour
  • LWG 2729: Missing SFINAE on std::pair::operator=
  • LWG 2744: any's in_place constructors
  • LWG 2754: The in_place constructors and emplace functions added by P0032R3 don't require CopyConstructible
  • LWG 2766: Swapping non-swappable types
  • LWG 2769: Redundant const in the return type of any_cast(const any&)
  • LWG 2781: Contradictory requirements for std::function and std::reference_wrapper
    • replaced by ystdex::function with the resolution
    • see also GCC PR66284
  • LWG 2794: Missing requirements for allocator pointers
  • LWG 2857: {variant,optional,any}::emplace should return the constructed value
    • partially replaced by ystdex::any
  • LWG 2993: reference_wrapper<T> conversion from T&&
    • partially replaced by ystdex::lref
  • LWG 3006: Constructing a basic_stringbuf from a string — where does the allocator come from?
  • LWG 3017: list splice functions should use addressof
  • LWG 3087: One final &x in §[list.ops]
  • LWG 3130: §[input.output] needs many addressof
  • LWG 3190: std::allocator::allocate sometimes returns too little storage
    • NOTE However, LWG 3237 is adopted for the direct replacement ystdex::polymorphic_allocator.
  • LWG 3430: std::fstream & co. should be constructible from string_view
  • LWG 3679: Is <ranges> sufficient for istream_view?
  • LWG 3681: Further considerations on LWG 3679
  • N1981: Uniform Use of std::string Revision 1
  • N1990: Proposed Text for minmax (N1840) (i.e. minmax and minmax_element in <algorithm>)
  • N1991: Proposed Text for defaultfloat (N1842) (i.e. defaultfloat in <ios>)
  • N2007: Proposed Library Additions for Code Conversion
  • N2111: Random Number Generation in C++0X: A Comprehensive Proposal, version 4
    • with LWG 609: missing static const
  • N2253: Extending sizeof to apply to non-static data members without an object (revision 1)
  • N2292: Standard Library Applications for Deleted Functions
  • N2321: Enhancing the time_get facet for POSIX® compatibility, Revision 2
  • N2353: A Specification for vector<bool>
  • N2547: Allow atomics use in signal handlers
  • N2554: The Scoped Allocator Model (Rev 2)
  • N2760: Input/Output Library Thread Safety
  • N2765: User-defined Literals (aka. Extensible Literals (revision 5))
  • N2782: C++ Data-Dependency Ordering: Function Annotation (i.e. [[carries_dependency]])
  • N3026
    • CWG 408: sizeof applied to unknown-bound array static data member of template
    • CWG 490: Name lookup in friend declarations
    • CWG 722: Can nullptr be passed to an ellipsis?
    • CWG 734: Are unique addresses required for namespace-scope variables?
    • CWG 935: Missing overloads for character types for user-defined literals
    • CWG 1000: Mistaking member typedefs for constructors
  • N3059: Proposal to simplify pair (rev 5.2) (i.e. piecewise_construct_t, etc.)
    • LWG 1321: scoped_allocator_adaptor construct and destroy don't use allocator_traits
  • N3282: Resolution for core issues 1207 and 1017
    • CWG 945: Use of this in a late-specified return type
    • CWG 1017: Member access transformation in unevaluated operands (see also CWG 1005; note CWG 515 is still effective)
    • CWG 1207: Type of class member in trailing-return-type (see also CWG 945)
      • GCC may not work. This is GCC PR 52869, fixed in GCC 9, but it has regression in GCC 11.1 (see GCC PR 100752). Clang++ works.
  • N3651: Variable Templates (Revision 1) (adopted 2013-04)
    • with CWG 1713: Linkage of variable template specializations
    • with CWG 2032: Default template-arguments of variable templates
  • N3664: Clarifying Memory Allocation (adopted 2013-04)
  • N3843: A SFINAE-Friendly std::common_type (adopted 2014-11)
    • LWG 2408 SFINAE-friendly common_type/iterator_traits is missing in C++14
  • N4081: Working Draft, C++ Extensions for Library Fundamentals (see N4480)
  • N4279: Improved insertion interface for unique-key maps (Revision 2.3) (adopted 2014-11)
  • N4510: Minimal incomplete type support for standard containers, revision 4 (adopted 2015-05; however, similar requirements for associative containers (not proposed yet) were required when replacments were not used, see here (zh-CN))
    • replaced by extension of ystdex::map and ystdex::unordered_map
  • P0022R2: Proxy Iterators for the Ranges Extensions
  • P0083R3: Splicing Maps and Sets (Revision 5) (adopted 2016-06)
    • LWG 839: Maps and sets missing splice operation
    • LWG 1041: Add associative/unordered container functions that allow to extract elements
    • partially replaced by ystdex::map and ystdex::unordered_map
  • P0136R1: Rewording inheriting constructors (core issue 1941 et al) (adopted 2015-10)
    • CWG 1573: Inherited constructor characteristics
    • CWG 1645: Identical inheriting constructors via default arguments
    • CWG 1715: Access and inherited constructor templates
    • CWG 1736: Inheriting constructor templates in a local class
    • CWG 1903: What declarations are introduced by a non-member using-declaration?
    • CWG 1941: SFINAE and inherited constructor default arguments
    • CWG 1959: Inadvertently inherited copy constructor
    • CWG 1991: Inheriting constructors vs default arguments
  • following resolution of P0165R3: C++ Standard Library Issues to be moved in Issaquah (adopted 2016-10)
    • LWG 2062: Effect contradictions w/o no-throw guarantee of std::function swaps
      • replaced by ystdex::function since (revised b848[2018-12-24])
  • P0409R2: Allow lambda capture [=, this] (adopted 2017-07)
  • P0458R2: Checking for Existence of an Element in Associative Containers (adopted 2018-06)
    • partially replaced by ystdex::map and ystdex::unordered_map
  • P0513R0: Poisoning the Hash (adopted 2016-11)
    • national body comment FI 15 in P0488R0: WG21 Working Paper, NB Comments, ISO/IEC CD 14882
    • LWG 2791: string_view objects and strings should yield the same hash values
  • P0588R1: Simplifying implicit lambda capture (adopted 2017-11)
    • CWG 1913: decltype((x)) in lambda-expressions
    • CWG 1632: Lambda capture in member initializers
  • P0600R1: [[nodiscard]] in the Library, Rev1 (adopted 2017-11)
  • P0616R0: de-pessimize legacy <numeric> algorithms with std::move (adopted 2017-11)
    • LWG 2055: std::move in std::accumulate and other algorithms
  • P0641R2: Resolving Core Issue #1331 (const mismatch with defaulted copy constructor) (adopted 2017-11)
    • CWG 1331: const mismatch with defaulted copy constructor
      • see also CWG 1426: Allowing additional parameter types in defaulted functions
  • P0692R1: Access Checking on Specializations (adopted 2017-11)
  • P0840R2: Language support for empty objects (adopted 2018-03)
  • P0935R0: Eradicating unnecessarily explicit default constructors from the standard library (adopted 2018-06)
  • P1227R2: Signed ssize() functions, unsigned size() functions (Revision 2) (adopted 2019-03)
  • P1668R1: Enabling constexpr Intrinsics By Permitting Unevaluated inline-assembly in constexpr Functions (adopted 2019-07)
  • P2242R3: Non-literal variables (and labels and gotos) in constexpr functions (adopted 2021-10)
  • P2314R4: Character sets and encodings (adopted 2021-10)
    • CWG 2455: Concatenation of string literals vs translation phases 5 and 6
  • P2647R1: Permitting static constexpr variables in constexpr functions (adopted 2022-11)

Avoided

These already adopted (by the standard or the working paper) post-C++11 resolutions (which may need no modification on a previous conforming C++ implementation) are not depended on currently (revised b971[2023-04-05]) but confirmed still being compatible, and also would not be relied on in future based on the fact of reducing expresiveness in the language and lacking of portable extensions to prevent underming of code quality based on current status:

  • P0145R3: Refining Expression Evaluation Order for Idiomatic C++ (adopted 2016-06)
  • P0218R0: Adopt the File System TS for C++17
    • with LWG 2676: Provide filesystem::path overloads for File-based streams
    • Rationale There are dedicated APIs from YSLib providing potentionally more powerful and portable platform-dependent features with a different design based on ystdex::path (a container template being filesystem-agnostic) and YCLib platform APIs instead of specific string-based path types. To simplify the design, the features are not used, and interoperations are not supported yet.
  • P1847R4: Make declaration order layout mandated (adopted 2021-06)

List of reported issues

There are some issues related reported, or originally found and forwarded by author of this project. Some issues are related to the to language or enviornment specifications. See reported issues for details.

Introduction

There are some issues related reported, or originally found and forwarded by author of this project. Some issues might block the project in some sense. To review them conveniently, reported issues are collect in this document.

Unless otherwise specified, notations are same to those in StandardUsing.

List of reported issues

Issues not rejected or withdrawn are collected and listed here (revised b840[2018-10-07]).

Specifications

Some issues are related to the to language or enviornment specifications.

The entries may duplicate with above.

ISO/IEC 14882 (ISO/IEC JTC1 SC22/WG21 issue list)

Implementations of standards or specifications

Some issues are related to the to implementations of language or enviornment specifications.

GNU C++ (Bugzilla)

  • PR 53872: [C++11] ADL bug in std::thread (RESOLVED FIXED 4.7.2)
  • PR 53873: [C++11] strange error message for template overloading (RESOLVED DUPLICATE of bug 53862)
    • PR 53862: [4.6/4.7 regression] [C++11] sorry, unimplemented: use of 'type_pack_expansion' in template (RESOLVED FIXED 4.7.1)
  • PR 54216: Missing diagnostic for ill-formed anonymous enum declarations (RESOLVED FIXED 4.9.0)
  • PR 55053: std::is_explicitly_convertible should be removed (RESOLVED WORKSFORME 4.8.0)
  • PR 56699: [4.8/4.9 regression] Failed for sizeof (non-static member) in lambda expression (RESOLVED FIXED mainline/4.8.1)
  • PR 57183: [C++11] auto and -Wunused-variable (RESOLVED FIXED 4.8.1)
  • PR 57444: [4.8/4.9 Regression] ICE in instantiate_type for invalid use of member with using-declaration (RESOLVED DUPLICATE of bug 58457)
    • PR 58457: [4.8/4.9 Regression] ICE when placement new operator is used with using keyword and custom constructor (RESOLVED FIXED 4.8.2/4.9.0)
  • PR 58395: Undefined behavior vs. exception (RESOLVED WONTFIX)
  • PR 59682: Invalid syntax accepted: new-placement without expression-list (RESOLVED FIXED 6.0)
  • PR 59931: Wrong wording of diagnostic about imaginary "member function type"
  • PR 60709: [C++11]ICE when using a braced-init-list as function argument to initialize a reference to array (RESOLVED FIXED 4.8.3)
  • PR 61019: ICE: incomplete type of class template as pseudo-destructor-name (RESOLVED FIXED 6.0)
  • PR 63400: [C++11]precision of std::chrono::high_resolution_clock (UNCONFIRMED)
  • PR 65343: unexpected exception thrown during destruction of static object in debug mode
    • Since some version of Windows 10, the crash dialog would not show, made it less annoying.
  • PR 65748: [C++11][C++14]Invalid copy elision on operand of throw-exception (RESOLVED DUPLICATE of bug 57533)
    • PR 57533: When throwing local variable, it's being move-constructed even if not going out of scope. (NEW)
    • This misfeature is carefully avoided in YSLib source.
  • PR 65890: [C++03]sizeof(qualified-id) accepted when the operand denotes a non-static member (RESOLVED INVALID)
    • This does not effect YSLib since C++11 is the baseline.
  • PR 67238: [C++11][C++14]cc1plus crash for nested decltype expression in parameter pack in trailing return type when '-g' enabled (RESOLVED FIXED 6.0)
    • This was worked arounded in YSLib source, before 6.0 is actually depended on.
  • PR 67795: Wrong code generated for conditional expression with cast (WAITING)
    • Given that it is not confirmed fixed, this may still block some code.
  • PR 70480: Reduce RTTI code bloat for specified types (NEW)
    • For the DS platform, YSLib uses ystdex::type_id instead typeid to work around.
  • YSLib issue 30
    • PR 71444: Error constants for MinGW-w64 (RESOLVED FIXED 5.5 6.4 7.1)
    • This was worked around in YSLib source and fixed since G++ 7.1.
  • PR 86734: [DR 2188] reverse_iterator::operator-> does not support overloaded operator& (RESOLVED FIXED 7.4 8.3)
  • PR 90966: [9/10 Regression] ICE in tsubst_copy, at cp/pt.c:16155 (RESOLVED FIXED 9.3 and 10)
  • PR 91127: Incorrect checking of nonnull attribute with argument to a constructor of class with a virtual base (NEW)
  • PR 91480: Nonconforming definitions of standard library feature-test macros (NEW)
  • PR 91531: _Rb_tree's copy assignment should respect to POCCA regardless of is_always_equal (NEW)
  • PR 91541: [C++17] Exception specification of operator= of node-based containers may be broken (RESOLVED WONTFIX)
    • Actually it is then fixed.
  • PR 91620: std::[forward_]list::remove_if/unique should respect to DR 526 (RESOLVED FIXED 11.0)
  • PR 93470: [C++2a] [9 Regression] [C++2a] std::reference_wrapper to function type is broken with Clang (RESOLVED FIXED 9.3)
  • PR 94602: wrong semantic check to prvalue as decltype operand (UNCONFIRMED)
  • PR 97362: __deref in <functional> in debug mode clashes with internal macro in Windows system header (RESOLVED FIXED 8.5 9.4 10.3 11.0)
  • PR 100472: [C++17] Wrong template non-type argument handling on function reference to noexcept functions (RESOLVED DUPLICATE of bug 97420)
  • PR 100682: Outdated manual about the debug mode using (RESOLVED FIXED 11.3 12.0)
  • PR 104191: Incorrect max_size() for node-based containers (NEW)
  • PR 107189: Inconsistent range insertion implementations in std::_Rb_tree in <bits/stl_tree.h> (RESOLVED FIXED 13.0)
  • PR 108771: Incorrect noexcept for merging in <bits/stl_tree.h> (UNCONFIRMED)

LLVM/Clang (Bugzilla and GitHub issues)

  • PR 25306: __attribute__((returns_nonnull)) does not work for std::add_pointer_t<T> (NEW)
  • PR 27504: Inherited constructor with dependent base class introduced by a typedef-name may not work (NEW)
  • PR 27443: [CWG 734] Nonconforming aliasing of block scope objects (RESOLVED DUPLICATE of bug 18538)
    • PR 18538: non-conforming optimization -fmerge-all-constants is enabled by default
    • Note they are not identical. PR 27443 is for ISO C++ and PR 18538 is for ISO C.
  • PR 43275: Pure attribute and C++ exceptions (NEW)
  • PR 45542: wrong semantic check to prvalue as decltype operand (NEW)
  • issue 60622 (NEW)

Microsoft Visual C++

New issues are now reported to Microsoft Visual Studio Developer Community.

As Microsoft Connect is down now, any status would not be updated. Note some posts have been inaccessible even before the service stopped.

  • Microsoft Connect issue 1641428: Wrong Win32 error to errno mapping (ACTIVE)
    • YSLib code does not rely on Universal source, instead implements the functionality spearatedly, so it has no effect.
    • This seems to be fixed before Microsoft Connect closed.
  • YSLib issue 33
  • YSLib issue 34: [Win32] std::unique_ptr::operator-> not conforming in Microsoft VC++ 2015
  • problem 417142: [LWG 2070][P0674R1] std::allocate_shared is not conforming (Under Investigation)
  • YSLib issue 35: [Win32] Microsoft VC++ 2017 failed to evaluate the dependent noexcept expression inside noexcept-specifier
    • problem 431598: VC++ fails to compile the noexcept expression inside noexcept-specifier with a template-parameter instantiated from an explicit destruct call (Closed - Fixed)(Fixed In: Visual Studio 2019 version 16.1)(Fixed In: Visual Studio 2019 version 16.1 Preview 1)
  • YSLib issue 36: [Win32] Microsoft VC++ 2017 failed to initialize with string arrays with a constexpr u8 string literal
    • problem 431628: VC++ fails to initialize with string arrays with a constexpr u8 string literal (Closed - Fixed)(Fixed In: Visual Studio 2019 version 16.0)(Fixed In: Visual Studio 2019 version 16.0 Preview 5)
  • YSLib issue 37: [Win32] Microsoft VC++ 2017 internal compiler error when building SHBuild
    • problem 431665: VC++ 2017 internal error for overloaded qualified function template function call in the function default argument (Closed - Fixed)(Fixed In: Visual Studio 2019 version 16.1)(Fixed In: Visual Studio 2019 version 16.0)
  • YSLib issue 38: [Win32] Microsoft VC++ 2017 failed to select expected std::swap specialization
    • problem 431904 (Closed - Fixed)(Fixed In: Visual Studio 2019 version 16.1)(Fixed In: Visual Studio 2019 version 16.1 Preview 2)(Fixed In: Visual Studio 2019 version 16.1 Preview 1)
  • YSLib issue 39: [Win32] Microsoft VC++ 2017 rejects dependent noexcept-specifier in template partial specializations
    • problem 441268 (Closed - Not a bug)
      • According to this reply, it is a defect of ISO C++, see CWG 2355. However, the wrong compilation error C2057 has been fixed.
  • microsoft/STL issue 2093: fiopen handling of non-standard flags (Open)
  • microsoft/STL issue 2609: <memory_resource>: Conformance issues on monotonic_buffer_resource::do_allocate (Open)

Other projects

As complement, some issues are related to other tools used by the project or referenced in documentation of this project, which may loosely concern with issues listed above.

DrMemory

devkitPro

libfat

  • pull request 1: Removed redundant check (Merged)
  • issue 2: Wrong root sectors count determined for FAT12 and FAT16? (Open)
  • issue 3: Redundant function calls (Open)
  • issue 4: _FAT_directory_entryFromPosition issues (Open)
  • issue 5: Redundant call of _FAT_directory_getRootEntry for "/" (Open)
  • issue 6: ENOTDIR vs ENOENT for invalid path prefix (Open)
  • issue 7: Pathname with or without trailing slashes for directories (Open)
  • issue 10: Setting errno when removing a non-empty directory (Open)
  • issue 19: Uninitialized timespec fields in stat (Open)

libnds

newlib

  • issue 5: Nonconforming return type of ftell (Closed FIXED)

Other language implementations

As Mercurial support of BitBucket has ended now, any status of issues of Mercurial repositories would not be updated, and the links are not accessible.

Resolved implementation issues

Some issues were tagged BLOCKED status previously. Issues known resolved and confirmed to work would be put in this chapter.

General

Scope

This document describes a set of rules for creating and maintaining documentations or some other kinds of materials, which is not project-specific. This document is also self-conforming to these rules.

Notation

Specific formats may be used. The visual output may depend on the method of rendering.

Texts intended to be handled differently (for example, portions of program source code) may be in some specific format. Other texts are considered normal.

Hyperlinks may be used in normal texts pointing to external references, local pages, or anchors in specific documents.

Normal text empasized in general are in the specific format, usually (visually) bold.

Local terms in the normal text are emphasized at first appearence in the specific format, usually (visually) italic.

For terms used globally in this document and any other derivations, see below.

Terms and definitions

Resources

Contents of materials are split as resources (e.g. files) in possibly nested namespaces (e.g. directories). A namespace is also considered as a resource for convenience.

Paths and identifiers

A path is used to identifying or locating a resource, which can be in various forms (e.g. filesystem path or URL).

A path may have several components denoting different levels of namespace or the last level non-namespace resource.

An empty path is a path without any components.

A path with more than one components shall have syntactic separators (e.g. a slash(/) or whitespace) to split different components.

An identifier is a path with exactly one components without any separators, which can be used to differentiate resources in the same namespace or to collectively name some sets of resources in various namespaces.

A resource may be denoted with not necessarily the unique identifier or path. However, all resources this document discussed below are named.

Languages

Rules of natural languages are specified in this subclause. They have effects on normal texts.

Normal text of noun phrases may have embedded translations for different natural languages or more detailed descriptions following its first occurence, in parentheses (( and )).

Different letter cases (if appropriate) may be used for sentences, acronyms and words in the titles of clauses.

Editions in languages

A set of documentation may be in one (natural) language. The IETF language tag with at least one subtag and an additional prefix dot(.) shall be placed in the end of identifier of the resource before the dot and the extension name (if any). Otherwise the documentation shall be in multiple languages or without text contents (e.g. containing only ideographic images), and no language code shall be in the identifier of the resource.

When the additional dot and tag is removed, all different resource with same names shall refer to the same set of contents only in different languages, or at least one of them shall be incomplete which means to be completed as in former case. The resource is one edition in the specific language of the documentation.

Unless explicitly specified, when the meaning is in conflict for multiple editions in different languages, the complete one shall be valid over others. If there is not only one complete edition, the validity is specified in following order:

  • en-US
  • en
  • zh-CN
  • zh

NOTE The form of these literals conforms to the recommendation of IETF language tag, specifically, the "language" and "region" syntax elements in RFC 5646.

If no one edition in above languages is complete, the documentation is defective.

A language tag may be used to annotate one or more words in text. An annotation of such use is a language tag annotation, which consists of a tag combined with one pair of enclosing parentheses (namely, ( and )).

Hyperlinks in pages should preferrably link to localized contents corresponding to the language or one of the major languages used in the page (if any) when suitable. If contents of the linked target is in other languages (esp. when there are more than one semantically identical editions in multiple languages), at least one language tag for majority of the contents should be noted subsequent to the hyperlink; otherwise, the tag should be omitted.

For compatibility of client programs, each link of URI should be encoded in form of normalized Percent-Encoding in RFC 3986.

Additionally, several hyperlinks are normalized with the same form for a specific language. Currently the rule consists of following cases:

In English

Stylistic usage of letter cases shall be respected in the following precedence:

  1. All uppercase should not be used normally.
  2. Acronyms and other proper noun (pharses) shall be in the appropriate styles.
  3. The title case style shall be used for page or document titles.
  4. Either the title case or the sentence case shall be used in the titles in a page. This shall be consistent within a document.
  5. Either the title case or the sentence case shall be used in the detailed descriptions for acronyms in parentheses. This may vary in the same page.
  6. Detailed descriptions for acronyms in parentheses may use title case or sentence case.
  7. All lowercase style shall be used for words in the embedded translations or detailed descriptions in parentheses in other cases.
  8. Sentence case should be used otherwise.

English wording documentation is intended to be conforming to the ISO/IEC directive, part 3.

NOTE The use of modal verbs is distinct with RFC 2119.

For wording referenced from RFC documents, RFC 2119 is preferred, but not necessary with the case clarification (i.e. RFC 8174) for documents published earlier than RFC 8174 due to compatibility issues.

The following grammartical forms of English (with en or en-US tags) are considered idiomatic and application of such forms may be preferred:

  • answer ellipsis to elide the subject in the summary of commit messages where a question for the topic of the log message is assumed
  • bare passive clause omitting the auxiliary verb for short descriptive notes (e.g. commit messages in repositories and assertions messages in programs)
  • null subject and pronoun dropping in imperative forms
  • zero article for singular form of a countable noun denoting a specialized term being referenced, usually used in a terse-style title or in a list term (like this line)

Informative notes: The tense and mood used in the logs in version control systems are opinion-based. However, the implied rules are choosed here to avoid imperative forms by default, because:

  • First, it should be respected same in all information processing system: to make sure who are the messages in the logs serve to.
    • Version control systems are capable for reading and writing operations on the version history, with asymmetric operational frequency in general.
      • For most stakeholders to a repository in most cases, read-only accesses of the version history are more frequent compared to changing opertions.
      • This is also consistent with the idiom pattern used in programming: do not abuse imperative updates with side effects.
    • For most users, commit logs are entries of journal of the version history.
      • They do not and should not care about imperative changes in the logical perspective.
  • Unconstrained changes in the version history as effectful operations can make messes easily.
    • They are usually only well-behaved enough within some local context (e.g. in a single branch of a reliable instance of the version history).
    • They often make troubles in other cases (e.g. when stripped as patches possibly reordered).
  • Messages in the logs may be cooperated with other instances of version history.
    • No imperative mood can essentially assume the changes described will always be applied in the exactly same way.
    • As mentioned above, out-of-order changes make messes. If the messages are precise, they also make messes like other changed contents.
  • In general, messages in the logs work for distributed repositories.
    • There is simply no standpoint for the global view of the universe of the version history by default.
    • Messages should be ready to be audited by random accesses, besides being applied subsequently in some replays.
    • These facts further undermines the necessity of imperative changes.

Format-specific rules

Text files

Unless otherwise specified, all text files should be encoded as UTF-8 with BOM enabled.

Any use of encoding which may not be converted verbatim and losslessly in binary form to UTF-8 shall be explicit specified in documantation.

BOM should be omitted for text files dedicated to tools without capability of properly handling it. Otherwise, BOM shall be used as possible when it can clarify the encoding being used.

Unless definitely intended and explictly specified in documentation, newlines shall be consistent. Default use of newline is CR+LF.

Two subsequent newlines indicate an EOF logically. Subsequent newlines out of verbatim quoted text (including source code) should only be used at EOF.

There are also some default rules on typography implemented by ordinary characters in plain texts:

  • No space characters should be at EOL.
  • For text other than verbatim quoted, no more than one whitespace characters should be used to represent a single indent, except there are preferred combination in the language.
    • Rationale By default, no more than one whitespaces should be used to represent an indent, because there should be no chance to insert a character in the middle of an indent.
    • NOTE An example of preferred exceptional case is that the hanging indent (in the first line of a paragraph) in east Asian languages where dedicated combination of fullwidth whitespaces are preferred. Typically, the sequence consists of 2 ideographic space (U+3000).
    • NOTE To keep the semantics rules clear, when possible (in horizontal texts and out of the context of making tables) and no other forms are more preferred by the rules of the language, use horizontal tab character(U+0009) instead of other spaces (i.e. U+0020) to indent.
  • For Western languages, except at the first of line, each word which consists of alphanumeric characters should be seperated by a single space character (U+0020) with other words.
  • Space characters (U+0020) should be used for alignment when portability is required.
    • Rationale This makes the visal effect easy to predicate in the usual settings with monospaced fonts in contexts like source code of programs.
    • NOTE Other spaces like non-breaking space (U+00A0) may be better in specific uses, but not portable as U+0020.

Markdown

Names of markdown files should be with .md extension.

Dialects

Unless explicitly specified elsewhere, only common dialects are to be used. Currently this should be GFM (GitHub Flavored Markdown).

NOTE This is not GLFM (GitLab Flavored Markdown), which also abbreviated as GFM formerly.

And if the content may be presented on Bitbucket wiki, stricter rules applies, notably:

NOTE This repository is not intended deployed in Bitbucket wiki now. The stricter rules on Bitbucket wiki above are not applicable here.

Syntactic restrictions

As text files, markdown files shall obey the same rules above. The indentation rule is necessary to avoid some compatibility issues, e.g. this.

As specified, reserved characters defined by RFC 3986 should be percentage-encoded. Notably, the parentheses(()) in hyperlinks shall be encoded to make it more fault-tolerent for some editors.

Headers should be prefixed by #s.

There should be no redundant characters allowed between the annotated words and annotation (esp. whitespace characters), even there are whitespaces in the words. The annotation in this rule includes any language tag annotation defined in previous subclause.

Rational This is for the sake of compact annotation representations.

NOTE The whitespace rules in the language annotation is also applicable. Instead, it is also allowed to use word combination (instead of the annotation) when gramatically correct, so this rule does not apply.

Cross references

This document is used by the YSLib project. It may be also referenced by other repositories.

Except for the following list, do not edit unless ultimately necessary.

Known referenced by:

Annex (Informative)

Alternaive imcompatible rules

Usually there the rules of documentation here are compatible to other rules in various specifications. However, some of the well-known rules are considered overspecified (albeit not rigouous) and with insufficient quality in specification. Thus, these rules are deliberately kept incompatible, and never accepted here:

  • The specification may be too vague by missing separating the comformance rules and the suggestions, so it is difficult to manually verify the conformance just by the specification text.
    • Some confusions may be from the lack of rules on the modal verbs.
    • Some rules may be underspecified for external resources. For example, the claim of "be a valid Markdown file" is unclear without further notes, because there is no unique standard to determine the definition of "valid", since there are multiple dialects of the Markdown language and no flavor is definitely more representative than others.
  • The rules of mandated letter cases (in particular, capitalization) may be too restrictive.
    • This may be generally too subjective. It can be good to sticking to a well-name for to ease for use cases for technical merits (like for machine verication), but the fixed spelling on cases may be overspecified.
      • The exception is when the name is standardized and machine-oriented by default.
      • As a notable instance, RFC 5646 recommends but does not mandate the capitalization for the codes from ISO 639-1 and ISO 3166-1, while the preferred capitalization diverges in the 2 standards.
    • On the other hand, mandotory like "README" instead of "Readme" is too restrictve. It will be problatic to be transferred between case-insensitive enviornments and case-sensitive environments (e.g. names in filesystems), where one environment may allow entries of README and Readme coexisting but another may not.
      • When techically feasible to having different cases coexisting, "README" and "Readme" are symmentric, i.e. no one is definitely more preferred than the other for machines. It is then not intuitive to reason why "README" must be preferred to "Readme" instead of the exact opposite in the specification, in particular with the fact that such entry is mainly created for human readers but not machines.
      • Instead, keeping one overridable as well as a recommended default form (which does not necessarily to be all capitalized) of spelling is better for both portability and other needs.
      • Further, names like "README.md" are less consistent to "README.MD". The latter is at least required in some ancient systems not support the small case, hence even more preferred for portability (in extreme cases).
  • Prioritizing non-regional subtags for languages should not be recommended normally, because this is less accurate, and the confusion may even be offensive to specific culture, since there can be lack of consensus that one subtag can override another without changing the meaning of the text (which is not the case of the relationship between tags and subtags).
  • Validation of hyperlinks should be acknowledged not always possible when the linked resource is out of the control (i.e. external) in a document.
    • Anyway, there is no persistency guarantee for most hyperlinks in the Web.
    • Mandating the state of the referenced resource of hyperlinks unconditionally will make any verification result one-time, because the exteranl links may be broken immediately after the verification. Then the conformance is non-deterministic.
    • Such mandatory is applicable only for hyperlinks provable to be persistent. But this is infeasible with automatic methods at least for external links on the Web, because the test of persistency may be unreliable until the link is broken.
    • So, unless external links are not allowed (which seems an overkill), rules having impractical assumptions of the validation process should be in the specification.

An example of most bullets above can be found in the specification of standard-readme.