#input-output #format #parser-generator #programming #specify #turingarena #iospec

nightly app turingarena-iospec

用于指定 I/O 格式和自动化验证、解析器生成等工具。TuringArena 的一部分:创建编程挑战的工具。

1 个不稳定版本

0.1.0 2020 年 8 月 28 日

#2010过程宏

MIT 许可证

130KB
3.5K SLoC

Turingarena IOspec

一种指定输入/输出格式的语言,以及自动化 I/O 验证、解析器生成等工具。

注意:Turingarena IOspec 正在开发中。待实现的功能在此 README 中用 "TODO" 标记。

什么是 Turingarena IOspec?

当基于类似文件的输入和输出定义编程问题时,需要与问题解决者定义一个合同,其中指定以下内容

  • 输入格式,
  • 关于输入的假设,这些假设将得到保证,
  • 输出格式,
  • 关于输出的假设,输出必须满足这些假设才能被接受。

Turingarena IOspec 允许正式指定输入和输出的格式,以及它们的一些假设(只要遵循一些常规,这在 IOI-like 问题中非常常见)。

一旦指定了格式,以下任务可以自动化

  • 生成
    • 在许多不同的编程语言中(TODO:目前仅支持 C++),
      • 框架 代码,该代码解析输入并格式化输出,提供对 I/O 的抽象(例如,在 CMS 问题中以意大利格式扮演“评分员”的角色),
      • 模板 代码,包含问题解决者需要实现的功能,一旦由问题解决者填写,无需管理 I/O,与框架结合后即可得到一个有效解决方案(TODO),
    • 输入/输出格式及其假设的易读描述,以多种输出格式(TODO),
  • 验证(TODO)
    • 输入文件,
    • 输出文件,给定它们各自的输入
    • 动态交互式问题中的输入和输出流
  • 规范化(待办事项)
    • 输入文件或流,以便输入生成器无需关注正确格式(例如,空格)
    • 输出文件或流,以便输出检查器可以假设输出格式正确(例如,空格)
  • 转换输入/输出文件
    • 从文本到二进制以及反之(待办事项)
    • 从普通(文本或二进制)到结构化(例如,JSON)以及反之(待办事项)

规范语言

在IOspec中,格式使用一种类似于编程语言的语肓来描述,它从问题解决者的视角定义

  • 输入应该如何解析
  • 解决方案代码何时以及如何执行以计算输出
  • 输出应该如何格式化
  • 何时以及如何检查假设(待办事项)

举例胜于千言。以下是在边列表编码的图查找循环问题的I/O规范。

read N: n32, M: n32;

assume 2 <= N && N <= 100_000;
assume 1 <= M && M <= 1_000_000;

for i upto M {
    read A[i]: n32, B[i]: n32;

    assume 0 <= A[i] && A[i] < B[i] && B[i] < N;
}

call find_cycle(N, M, A, B) -> L: n32; // length of the cycle
assert 2 <= L && L <= N;

write L;

for i upto L {
    call get_cycle_node(i) -> u: n32;
    assert 0 <= u && u < N;

    write u;
}

该语言支持许多编程语言常见的特性,但它也有很多限制,这使其可用于自动化某些任务(否则会更难或不可能),同时使其更加紧凑。

特性

  • 标量类型
    • 布尔值:bool(真或假,分别表示为10
    • 自然数:n8n16n32n64(自然数是非负整数,可以适应给定位大小的有符号整数,例如,n32可以包含从02^31 - 1的任何数字)
    • 整数:i8i16i32i64(适应给定位大小的有符号整数,可以是正数或负数,但不包括最小负值,例如,i32可以包含从-2^31 + 12^31 - 1的任何数字)
  • 聚合类型
    • 数组,在for循环中初始化
    • 可选(待办事项)
  • 静态类型变量
  • I/O指令(readwrite
  • 调用问题解决者实现的函数(call
    • 具有标量或聚合参数
    • 可选返回标量
  • 基于范围的循环(for
  • 泛型循环(loopbreakcontinue)(待办事项)
  • 条件语句和值匹配(ifswitch)(待办事项)
  • 算术和布尔表达式(待办事项)
  • 使用单赋值变量重用表达式(let)(待办事项)
  • 检查输入和输出假设(assumeassert)(待办事项)

限制

  • 变量必须在 exactly once place 赋值.
  • 聚合值只能通过在控制结构内部逐个分配标量值隐式定义。 例如,在一个 for i upto N 循环中读取 A[i]: n32A 定义为大小为 Nn32 数组。在 ifswitch 语句内部读取 X: n32X 定义为可选的 n32 (待办)。
  • 所有数据变量必须具有不同的名称。 这简化了从规范外部(例如,在文档中)引用任何给定变量,并简化了骨架代码的生成。注意:只有变量的名称具有全局作用域,而不是变量本身,因此仍然会在定义作用域外引用变量时出错。
  • 问题解决函数的参数必须是简单变量,而不是表达式。 这确保参数和相应的变量是同一对象,并且只能定义/记录一次。通过在 call 之前使用 let 语句,可以轻松克服这种限制(待办)。
  • 不支持可重用函数和/或递归。 这允许将代码中的每个词法位置映射到解析器的特定状态,并确保任何给定变量在任何给定时间最多只有一个值(即,没有栈帧)。

使用方法

检查 I/O 规范

    turingarena-iospec lint
        [--spec-file <spec-file>]

解析并检查 <spec-file> 中的 I/O 规范。

生成代码

    turingarena-iospec code
        [--spec-file <spec-file>]
        [--target <file>]
        [--kind skeleton|template]
        [--language <lang>]

为给定语言生成骨架或模板代码。

验证输入和输出文件/流(待办)

    turingarena-iospec run
        [--spec-file <spec-file>]
        [--input-source <input-file-or-pipe>]
        [--output-source <output-file-or-pipe>]
        [--input-target <input-file-or-pipe>]
        [--output-target <output-file-or-pipe>]
        [--ignore-assumptions]
        [--ignore-assertions]

根据 I/O 规范解析和检查输入,以及可选的输出,文件或流。问题报告在 stderr 上。如果需要,生成输入或输出的规范形式。

实现设计

Turingarena IOspec 使用 Rust 实现。

它利用常用的解析框架解析规范语言,该框架通常用于实现 Rust 过程宏。

生成代码示例

这是一个生成代码的示例,该代码要求在输入中作为邻接表编码的图中查找循环。

规范文件

read N: n32; // number of nodes

for u upto N {
    read D[u]: n32; // degree of u
    for i upto D[u] {
        read A[u][i]: n32; // adjacency list
    }
}

call find_cycle(N, D, A) -> L: n32; // length of cycle

write L;

for i upto L {
    call get_cycle_node(i) -> u: n32; // i-th node in the cycle
    write u;
}

生成的 C++ 代码

#include <cstdio>
#include <cstdint>

int32_t find_cycle(int32_t N, int32_t* D, int32_t** A);

int32_t get_cycle_node(int32_t i);

int main() {
    int32_t N;
    scanf("%d", &N);

    int32_t* D = new int32_t[N];
    int32_t** A = new int32_t*[N];
    for(int32_t u = 0; u < N; u++) {
        scanf("%d", &D[u]);

        A[u] = new int32_t[D[u]];
        for(int i = 0; i < D[u]; i++) {
            scanf("%d", &A[u][i]);
        }
    }

    int32_t L = find_cycle(N, D, A);

    printf("%d\n", L);

    for(int32_t i = 0; i < L; i++) {
        int32_t u = get_cycle_node(i);

        printf("%d\n", u);
    }
}

依赖关系

~4MB
~63K SLoC