rewrite API

rewrite 包在项的某一个位置应用一条重写规则,并准确报告发生了什么:之前与之后的项、规则名,以及从根到被重写位置的路径。有界范式化与轨迹都是通过重复这样的单步构建的。每个函数都有 Term[T] 版本,并在注明处提供适用于任意 @syntax.BindingSyntax AST 的版本。

import {
  "Luna-Flow/type_theory/syntax",
  "Luna-Flow/type_theory/rewrite",
}

规则是一个函数 (Term[T]) -> Term[T]?,当它作为整体适用于某个项时返回 Some(result),否则返回 None。遍历函数决定在何处尝试规则。相关理论见 rewrite 设计。

规则名

RuleName

RuleName 是归约规则的非空标识符。

pub struct RuleName {
  value : String
} derive(Compare, Eq, Hash, @debug.Debug)

规则名用于在结果和轨迹中标记步骤。它们按文本进行比较、排序和哈希;顺序为 String 的顺序:较短的文本在前,然后按 UTF-16 码元比较。

RuleName::new

RuleName::new 验证并创建一个规则名。

pub fn RuleName::new(String) -> Result[Self, RuleNameError]

对空字符串返回 Err(RuleNameError::Empty),否则返回 Ok(name)。

RuleName::unsafe_new

RuleName::unsafe_new 从已知非空的字符串创建规则名。

pub fn RuleName::unsafe_new(String) -> Self

遇到空字符串时中止。对 "beta" 这类不变式显而易见的字面量使用它;对来自输入的名字使用 RuleName::new。

RuleName::value

RuleName::value 返回规则名的文本。

pub fn RuleName::value(Self) -> String

RuleName::equal, RuleName::compare, RuleName::hash

这些方法是提升的 Eq、Compare 和 Hash 实现。

pub fn RuleName::equal(Self, Self) -> Bool
pub fn RuleName::compare(Self, Self) -> Int
pub fn RuleName::hash(Self) -> Int
test "rule names" {
  assert_eq(@rewrite.RuleName::new(""), Err(@rewrite.RuleNameError::Empty))
  match @rewrite.RuleName::new("beta") {
    Ok(name) => inspect(name.value(), content="beta")
    Err(_) => fail("non-empty name rejected")
  }
  // shortlex order: shorter names first
  let eta = @rewrite.RuleName::unsafe_new("eta")
  assert_true(eta < @rewrite.RuleName::unsafe_new("beta"))
}

RuleNameError

RuleNameError 列出规则名无效的原因。

pub(all) enum RuleNameError {
  Empty
} derive(Eq, @debug.Debug)
pub fn RuleNameError::equal(Self, Self) -> Bool

Empty 是唯一的情形:规则名不得为空。

位置

ReductionFrame

ReductionFrame 是从一个节点到其某个子节点的一步。

pub(all) enum ReductionFrame {
  BinderBody
  ApplyHead
  ApplyArgument(Int)
} derive(Eq, @debug.Debug)
pub fn ReductionFrame::equal(Self, Self) -> Bool

BinderBody 进入 Bind 的体,ApplyHead 进入 Apply 的头部,ApplyArgument(i) 进入其第 i 个参数(从 0 开始计数)。

ReductionPath

ReductionPath 是从根到某个位置的帧序列。

pub struct ReductionPath {
  frames : Array[ReductionFrame]
} derive(Eq, @debug.Debug)

空路径即根。

ReductionPath::root, ReductionPath::prepend, ReductionPath::to_array

这些函数构造和读取路径。

pub fn ReductionPath::root() -> Self
pub fn ReductionPath::prepend(Self, ReductionFrame) -> Self
pub fn ReductionPath::to_array(Self) -> Array[ReductionFrame]

prepend(frame) 在根端添加一帧,遍历正是借此把子节点中某一步的路径提升到父节点。to_array 返回以根开头的帧。

ReductionPath::equal

ReductionPath::equal 逐帧比较两条路径。

pub fn ReductionPath::equal(Self, Self) -> Bool
test "paths are built from the redex up" {
  let path = @rewrite.ReductionPath::root()
    .prepend(@rewrite.ApplyArgument(1))
    .prepend(@rewrite.BinderBody)
  assert_eq(path.to_array(), [@rewrite.BinderBody, @rewrite.ApplyArgument(1)])
  assert_eq(@rewrite.ReductionPath::root().to_array(), [])
}

Term 上的单步

StepResult

StepResult[T] 是一次归约尝试的结果。

pub(all) enum StepResult[T] {
  NoStep
  Reduced(before~ : @syntax.Term[T], after~ : @syntax.Term[T], rule~ : RuleName, path~ : ReductionPath)
} derive(Eq, @debug.Debug)
pub fn[T : Eq] StepResult::equal(Self[T], Self[T]) -> Bool

NoStep 表示遍历没有找到规则适用的位置。Reduced 记录整个项 before、整个项 after、规则以及被重写位置的路径。恰好有一个位置被重写:before 在 path 处的子项是该规则的可约式,而 after 是将该子项替换为规则结果后的 before。

top_down_once

top_down_once 按前序重写规则适用的第一个位置。

pub fn[T] top_down_once(@syntax.Term[T], RuleName, (@syntax.Term[T]) -> @syntax.Term[T]?) -> StepResult[T]

规则先在节点本身上尝试,再在其子节点上尝试;子节点的访问顺序是先头部,再从左到右依次访问参数,并且会进入绑定子的体。所选位置是最左最外的可约式。NoStep 表示该规则在项中任何位置都不适用。

bottom_up_once

bottom_up_once 按后序遍历,改写规则适用的第一个位置。

pub fn[T] bottom_up_once(@syntax.Term[T], RuleName, (@syntax.Term[T]) -> @syntax.Term[T]?) -> StepResult[T]

子节点先于其父节点尝试,顺序同样是从左到右。所选位置是最左最内的可约式。NoStep 同样表示该规则在任何位置都不适用。

fn drop_zero(t : @syntax.Term[String]) -> @syntax.Term[String]? {
  match t {
    Apply(Value("+"), [Value("0"), other]) => Some(other)
    _ => None
  }
}

test "outermost versus innermost" {
  let rule = @rewrite.RuleName::unsafe_new("drop_zero")
  let inner : @syntax.Term[String] = Apply(Value("+"), [Value("0"), Value("1")])
  let outer : @syntax.Term[String] = Apply(Value("+"), [Value("0"), inner])
  match @rewrite.top_down_once(outer, rule, drop_zero) {
    Reduced(after~, path~, ..) => {
      assert_eq(after, inner)
      assert_eq(path.to_array(), [])
    }
    NoStep => fail("expected a step")
  }
  match @rewrite.bottom_up_once(outer, rule, drop_zero) {
    Reduced(after~, path~, ..) => {
      assert_eq(after, Apply(Value("+"), [Value("0"), Value("1")]))
      assert_eq(path.to_array(), [@rewrite.ApplyArgument(1)])
    }
    NoStep => fail("expected a step")
  }
}

在 Term 上重复执行步骤

NormalizationResult

NormalizationResult[T] 是对步进函数进行有界重复的结果。

pub(all) enum NormalizationResult[T] {
  NormalForm(term~ : @syntax.Term[T], steps~ : Int)
  StepLimitReached(term~ : @syntax.Term[T], steps~ : Int)
} derive(Eq, @debug.Debug)
pub fn[T : Eq] NormalizationResult::equal(Self[T], Self[T]) -> Bool

NormalForm(term, steps):步进函数在 term 上返回 NoStep,该项是经过 steps 步得到的。StepLimitReached(term, steps):steps 步的上限已用尽,且 term 仍可继续归约。

normalize

normalize 反复应用步进函数,直到它返回 NoStep 或达到步数上限。

pub fn[T] normalize(@syntax.Term[T], (@syntax.Term[T]) -> StepResult[T], Int) -> NormalizationResult[T]

normalize(term, step, max_steps) 至多执行 max_steps 个成功步骤,并且至多调用 step max_steps + 1 次:在最后一个允许的步骤之后,它会再调用一次 step,以判定结果是 NormalForm 还是 StepLimitReached。当 max_steps <= 0 时,它不执行任何步骤,只对 term 进行分类。归约策略由步进函数决定;现成的步进函数见 eval。

ReductionTrace

ReductionTrace[T] 记录一次有界运行:初始项、每个成功的步骤以及最终结果。

pub struct ReductionTrace[T] {
  initial : @syntax.Term[T]
  steps : Array[StepResult[T]]
  result : NormalizationResult[T]
} derive(Eq, @debug.Debug)
pub fn[T] ReductionTrace::initial(Self[T]) -> @syntax.Term[T]
pub fn[T] ReductionTrace::steps(Self[T]) -> Array[StepResult[T]]
pub fn[T] ReductionTrace::result(Self[T]) -> NormalizationResult[T]
pub fn[T : Eq] ReductionTrace::equal(Self[T], Self[T]) -> Bool

steps 中的每个元素都是一个 Reduced 值;相邻步骤首尾相接,前一步的 after 即后一步的 before。steps().length() 等于 result 中的 steps 计数。steps() 返回一个副本。

trace

trace 运行与 normalize 相同的循环,并记录每一步。

pub fn[T] trace(@syntax.Term[T], (@syntax.Term[T]) -> StepResult[T], Int) -> ReductionTrace[T]
fn decrement(t : @syntax.Term[Int]) -> @syntax.Term[Int]? {
  match t {
    Value(n) if n > 0 => Some(Value(n - 1))
    _ => None
  }
}

test "normalize and trace" {
  let rule = @rewrite.RuleName::unsafe_new("decrement")
  let step = (t : @syntax.Term[Int]) => @rewrite.top_down_once(t, rule, decrement)
  assert_eq(
    @rewrite.normalize(Value(3), step, 10),
    NormalForm(term=Value(0), steps=3),
  )
  let run = @rewrite.trace(Value(3), step, 2)
  assert_eq(run.steps().length(), 2)
  assert_eq(run.result(), StepLimitReached(term=Value(1), steps=2))
  assert_eq(run.initial(), Value(3))
}

感知绑定的 AST

GenericStepResult

GenericStepResult[N] 是针对下游 AST N 的 StepResult。

pub(all) enum GenericStepResult[N] {
  NoStep
  Reduced(before~ : N, after~ : N, rule~ : RuleName, path~ : ReductionPath)
} derive(Eq, @debug.Debug)
pub fn[N : Eq] GenericStepResult::equal(Self[N], Self[N]) -> Bool

generic_top_down_once

generic_top_down_once 是适用于任意 BindingSyntax AST 的 top_down_once。

pub fn[N : @syntax.BindingSyntax] generic_top_down_once(N, RuleName, (N) -> N?) -> GenericStepResult[N]

遍历使用 BindingSyntax::project 查找子节点,并使用 trait 的构造函数重建被改写节点的各级父节点。Opaque 节点和变量节点没有子节点;规则仍会在它们上面尝试。

GenericNormalizationResult

GenericNormalizationResult[N] 是针对下游 AST 的 NormalizationResult。

pub(all) enum GenericNormalizationResult[N] {
  NormalForm(term~ : N, steps~ : Int)
  StepLimitReached(term~ : N, steps~ : Int)
} derive(Eq, @debug.Debug)
pub fn[N : Eq] GenericNormalizationResult::equal(Self[N], Self[N]) -> Bool

generic_normalize

generic_normalize 使用同一条规则重复执行 generic_top_down_once,直到没有步骤可用或达到步数上限。

pub fn[N : @syntax.BindingSyntax] generic_normalize(N, RuleName, (N) -> N?, Int) -> GenericNormalizationResult[N]

它与 normalize 遵循相同的步数约定,只是策略固定为自顶向下。

test "generic rewriting on Term" {
  let rule = @rewrite.RuleName::unsafe_new("drop_zero")
  let inner : @syntax.Term[String] = Apply(Value("+"), [Value("0"), Value("1")])
  let outer : @syntax.Term[String] = Apply(Value("+"), [Value("0"), inner])
  assert_eq(
    @rewrite.generic_normalize(outer, rule, drop_zero, 10),
    NormalForm(term=Value("1"), steps=2),
  )
}

关于下游 AST,请参阅适配器教程。