℘ make now just

if you wanna break free you better listen to me

MayJunJulAugSepOctNovDecJanFebMarApr/post/2025-04-27-diary//post/2025-04-28-diary//post/2025-04-29-diary//post/2025-04-30-diary//post/2025-05-01-diary//post/2025-05-02-diary//post/2025-05-03-diary//post/2025-05-04-diary//post/2025-05-05-diary//post/2025-05-06-diary//post/2025-05-07-diary//post/2025-05-08-diary//post/2025-05-09-diary//post/2025-05-10-diary//post/2025-05-11-diary//post/2025-05-12-diary//post/2025-05-13-diary//post/2025-05-14-diary//post/2025-05-15-diary//post/2025-05-16-diary//post/2025-05-17-diary//post/2025-05-18-diary//post/2025-05-19-diary//post/2025-05-20-diary//post/2025-05-21-diary//post/2025-05-22-diary//post/2025-05-23-diary//post/2025-05-24-diary//post/2025-05-25-diary//post/2025-05-26-diary//post/2025-05-27-diary//post/2025-05-28-diary//post/2025-05-29-diary//post/2025-05-30-diary//post/2025-05-31-diary//post/2025-06-01-diary//post/2025-06-02-diary//post/2025-06-03-diary//post/2025-06-04-diary//post/2025-06-05-diary//post/2025-06-06-diary//post/2025-06-07-diary//post/2025-06-08-diary//post/2025-06-09-diary//post/2025-06-10-diary//post/2025-06-11-diary//post/2025-06-12-diary//post/2025-06-13-diary//post/2025-06-14-diary//post/2025-06-15-diary//post/2025-06-16-diary//post/2025-06-17-diary//post/2025-06-18-diary//post/2025-06-19-diary//post/2025-06-20-diary//post/2025-06-21-diary//post/2025-06-22-diary//post/2025-06-23-diary//post/2025-06-24-diary//post/2025-06-25-diary//post/2025-06-26-diary//post/2025-06-27-diary//post/2025-06-28-diary//post/2025-06-29-diary//post/2025-06-30-diary//post/2025-07-01-diary//post/2025-07-02-diary//post/2025-07-03-diary//post/2025-07-04-diary//post/2025-07-05-diary//post/2025-07-06-diary//post/2025-07-07-diary//post/2025-07-08-diary//post/2025-07-09-diary//post/2025-07-10-diary//post/2025-07-11-diary//post/2025-07-12-diary//post/2025-07-13-diary//post/2025-07-14-diary//post/2025-07-15-diary//post/2025-07-16-diary//post/2025-07-17-diary//post/2025-07-18-diary//post/2025-07-19-diary//post/2025-07-20-diary//post/2025-07-21-diary//post/2025-07-22-diary//post/2025-07-23-diary//post/2025-07-24-diary//post/2025-07-25-diary//post/2025-07-26-diary//post/2025-07-27-diary//post/2025-07-28-diary//post/2025-07-29-diary//post/2025-07-30-diary//post/2025-07-31-diary//post/2025-08-01-diary//post/2025-08-02-diary//post/2025-08-03-diary//post/2025-08-04-diary//post/2025-08-05-diary//post/2025-08-06-diary//post/2025-08-07-diary//post/2025-08-08-diary//post/2025-08-09-diary//post/2025-08-10-diary//post/2025-08-11-diary//post/2025-08-12-diary//post/2025-08-13-diary//post/2025-08-14-diary//post/2025-08-15-diary//post/2025-08-16-diary//post/2025-08-17-diary//post/2025-08-18-diary//post/2025-08-19-diary//post/2025-08-20-diary//post/2025-08-21-diary//post/2025-08-22-diary//post/2025-08-23-diary//post/2025-08-24-diary//post/2025-08-25-diary//post/2025-08-26-diary//post/2025-08-27-diary//post/2025-08-28-diary//post/2025-08-29-diary//post/2025-08-30-diary//post/2025-08-31-diary//post/2025-09-01-diary//post/2025-09-02-diary//post/2025-09-03-diary//post/2025-09-04-diary//post/2025-09-05-diary//post/2025-09-06-diary//post/2025-09-07-diary//post/2025-09-08-diary//post/2025-09-09-diary//post/2025-09-10-diary//post/2025-09-11-diary//post/2025-09-12-diary//post/2025-09-13-diary//post/2025-09-14-diary//post/2025-09-15-diary//post/2025-09-16-diary//post/2025-09-17-diary//post/2025-09-18-diary//post/2025-09-19-diary//post/2025-09-20-diary//post/2025-09-21-diary//post/2025-09-22-diary//post/2025-09-23-diary//post/2025-09-24-diary//post/2025-09-25-diary//post/2025-09-26-diary//post/2025-09-27-diary//post/2025-09-28-diary//post/2025-09-29-diary//post/2025-09-30-diary//post/2025-10-01-diary//post/2025-10-02-diary//post/2025-10-03-diary//post/2025-10-04-diary//post/2025-10-05-diary//post/2025-10-06-diary//post/2025-10-07-diary//post/2025-10-08-diary//post/2025-10-09-diary//post/2025-10-10-diary//post/2025-10-11-diary//post/2025-10-12-diary//post/2025-10-13-diary//post/2025-10-14-diary//post/2025-10-15-diary//post/2025-10-16-diary//post/2025-10-17-diary//post/2025-10-18-diary//post/2025-10-19-diary//post/2025-10-20-diary//post/2025-10-21-diary//post/2025-10-22-diary//post/2025-10-23-diary//post/2025-10-24-diary//post/2025-10-25-diary//post/2025-10-26-diary//post/2025-10-27-diary//post/2025-10-28-diary//post/2025-10-29-diary//post/2025-10-30-diary//post/2025-10-31-diary//post/2025-11-01-diary//post/2025-11-02-diary//post/2025-11-03-diary//post/2025-11-04-diary//post/2025-11-05-diary//post/2025-11-06-diary//post/2025-11-07-diary//post/2025-11-08-diary//post/2025-11-09-diary//post/2025-11-10-diary//post/2025-11-11-diary//post/2025-11-12-diary//post/2025-11-13-diary//post/2025-11-14-diary//post/2025-11-15-diary//post/2025-11-16-diary//post/2025-11-17-diary//post/2025-11-18-diary//post/2025-11-19-diary//post/2025-11-20-diary//post/2025-11-21-diary//post/2025-11-22-diary//post/2025-11-23-diary//post/2025-11-24-diary//post/2025-11-25-diary//post/2025-11-26-diary//post/2025-11-27-diary//post/2025-11-28-diary//post/2025-11-29-diary//post/2025-11-30-diary//post/2025-12-01-diary//post/2025-12-02-diary//post/2025-12-03-diary//post/2025-12-04-diary//post/2025-12-05-diary//post/2025-12-06-diary//post/2025-12-07-diary//post/2025-12-08-diary//post/2025-12-09-diary//post/2025-12-10-diary//post/2025-12-11-diary//post/2025-12-12-diary//post/2025-12-13-diary//post/2025-12-14-diary//post/2025-12-15-diary//post/2025-12-16-diary//post/2025-12-17-diary//post/2025-12-18-diary//post/2025-12-19-diary//post/2025-12-20-diary//post/2025-12-21-diary//post/2025-12-22-diary//post/2025-12-23-diary//post/2025-12-24-diary//post/2025-12-25-diary//post/2025-12-26-diary//post/2025-12-27-diary//post/2025-12-28-diary//post/2025-12-29-diary//post/2025-12-30-diary//post/2025-12-31-diary//post/2026-01-01-diary//post/2026-01-02-diary//post/2026-01-03-diary//post/2026-01-04-diary//post/2026-01-05-diary//post/2026-01-06-diary//post/2026-01-07-diary//post/2026-01-08-diary//post/2026-01-09-diary//post/2026-01-10-diary//post/2026-01-11-diary//post/2026-01-12-diary//post/2026-01-13-diary//post/2026-01-14-diary//post/2026-01-15-diary//post/2026-01-16-diary//post/2026-01-17-diary//post/2026-01-18-diary//post/2026-01-19-diary//post/2026-01-20-diary//post/2026-01-21-diary//post/2026-01-22-diary//post/2026-01-23-diary//post/2026-01-24-diary//post/2026-01-25-diary//post/2026-01-26-diary//post/2026-01-27-diary//post/2026-01-28-diary//post/2026-01-29-diary//post/2026-01-30-diary//post/2026-01-31-diary//post/2026-02-01-diary//post/2026-02-02-diary//post/2026-02-03-diary//post/2026-02-04-diary//post/2026-02-05-diary//post/2026-02-06-diary//post/2026-02-07-diary//post/2026-02-08-diary//post/2026-02-09-diary//post/2026-02-10-diary//post/2026-02-11-diary//post/2026-02-12-diary//post/2026-02-13-diary//post/2026-02-14-diary//post/2026-02-15-diary//post/2026-02-16-diary//post/2026-02-17-diary//post/2026-02-18-diary//post/2026-02-19-diary//post/2026-02-20-diary//post/2026-02-21-diary//post/2026-02-22-diary//post/2026-02-23-diary//post/2026-02-24-diary//post/2026-02-25-diary//post/2026-02-26-diary//post/2026-02-27-diary//post/2026-02-28-diary//post/2026-03-01-diary//post/2026-03-02-diary//post/2026-03-03-diary//post/2026-03-04-diary//post/2026-03-05-diary//post/2026-03-06-diary//post/2026-03-07-diary//post/2026-03-08-diary//post/2026-03-09-diary//post/2026-03-10-diary//post/2026-03-11-diary//post/2026-03-12-diary//post/2026-03-13-diary//post/2026-03-14-diary//post/2026-03-15-diary//post/2026-03-16-diary//post/2026-03-17-diary//post/2026-03-18-diary//post/2026-03-19-diary//post/2026-03-20-diary//post/2026-03-21-diary//post/2026-03-22-diary//post/2026-03-23-diary//post/2026-03-24-diary//post/2026-03-25-diary//post/2026-03-26-diary//post/2026-03-27-diary//post/2026-03-28-diary//post/2026-03-29-diary//post/2026-03-30-diary//post/2026-03-31-diary//post/2026-04-01-diary//post/2026-04-02-diary//post/2026-04-03-diary//post/2026-04-04-diary//post/2026-04-05-diary//post/2026-04-06-diary//post/2026-04-07-diary//post/2026-04-08-diary//post/2026-04-09-diary//post/2026-04-10-diary//post/2026-04-11-diary//post/2026-04-12-diary//post/2026-04-13-diary//post/2026-04-14-diary//post/2026-04-15-diary//post/2026-04-16-diary//post/2026-04-17-diary//post/2026-04-18-diary//post/2026-04-19-diary//post/2026-04-20-diary//post/2026-04-21-diary//post/2026-04-22-diary//post/2026-04-23-diary//post/2026-04-24-diary//post/2026-04-25-diary//post/2026-04-26-diary/
  • Less
  • More

2026-04-14: HIRの仕様を考えている

やったこと

Naraku

HIRなどの仕様を考えている。

まずパース後の正規表現をpreprocessする。
preprocessでは以下のことを行う。

  1. GroupNameResolver
    • 名前付きグループのグループ番号の設定
  2. GroupRefResolver
    • 後方参照・条件グループ・部分式呼び出しの参照先のグループ番号の解決
    • 深さ付きの参照が使われるキャプチャの記録
  3. EmptyMatchAnalyzer
    • 各ノードが空文字列にマッチするかどうかの判定
  4. InfiniteCalAnalyzer
    • 部分式の呼び出しが空文字列で無限に再帰しないか (左/右再帰) のチェック
  5. EmptyLoopCapsAnalyzer
    • 空ループが依存しているキャプチャのグループ番号の計算
  6. CharClassExpander
    • 文字クラス ([...]) を範囲の配列に変換する
    • 文字タイプ (\w) やプロパティ (\p{...}) なども同様
  7. IgnoreCaseExpander
    • ignore_case に従って文字列を展開 (unfold) する
      • /abc//[aA][bB][cC]/ のようにする
      • ただし展開後が大きくなりそうならそのままを保つ
    • 文字クラスも展開 (unfold) する

次にpreprocess後の正規表現をHIRへと変換する。
HIRは次のような構造:

// HIRにloweringされた正規表現
struct HIR:
  entry: BlockId
  blocks: Map[BlockID, Block]

// ブロック
struct Block:
  insts: Array[Inst]
  term: Term

// 命令
enum Inst:
  // 通常の文字列のマッチ
  case Match(lit: String, info: MetahInfo)
  // `.` のマッチ
  // `Class` でもいいけど最適化用
  case Dot(newline: Bool, dir: Dir)
  // 文字クラスとのマッチ
  // (`\w` や `\p{...}` なども `intervals` に展開されている前提)
  case Class(intervals: Array[Interval], info: MetahInfo)
  // 後方参照とのマッチ
  case Ref(cap_ids: Array[CapId], depth: Option[Int], info: MetahInfo)
  // ゼロ幅アサーション
  case Assert(assertion: Assertion)
  // キャプチャ
  case CapBegin(cap_id: CapId
  case CapEnd(
    cap_id: CapId,
    // 深さ指定での後方参照に対応するために、`call_caps` へ記録する必要があるか
    needs_depth: Boolean,
    // 空ループのチェックのために `cap_gen` に記録する必要があるか
    empty_check: Boolean
  )
  // アトミックグループ
  case AtomicBegin // `MarkAtomic`を`bt_stack`に積む
  case AtomicEnd   // `ProtectAtomic`を`bt_stack`に積む
  // カウンタによる繰り返し
  case ResetReg(reg_id: RegId)
  case IncReg(reg_id: RegId)
  // 空ループのチェック
  // `loop_gen[loop_id]` を更新し、`current_gen`をインリメントする
  case SaveCapGen(loop_id: LoopId)
  // メモ化
  // 複数のブロックから遷移してくる可能性のある分岐の前、
  // あるいはLookAroundの開始位置に追加される (メモ化可能なパターンの場合)
  case Memo(info: MemoInfo)

// 文字を読み進める命令のメタ情報
struct MatchMeta:
  // `i` フラグが有効か
  ignore_case: Bool
  // case foldingの情報 (`ignore_case` が `true` のときだけ意味がある)
  fold: FoldFlag
  // マッチの向き
  dir: Dir

// マッチの向き
enum Dir:
  // 通常、先読み中
  case Forward
  // 後読み中
  case Backward

// ゼロ幅アサーションの種類
enum Assertion:
  case WordBoundary(dir: Dir)    // `\b`
  case NonWordBoundary(dir: Dir) // `\B`
  case LineBegin                 // `^`
  case LineEnd                   // `$`
  case Begin                     // `\A`
  case End                       // `\z`
  case EndLoose                  // `\Z`
  case MatchBegin                // `\G`

// ブロック終端の命令
enum Term:
  // ブロックの遷移
  case Next(tr: Trans)

  // 部分式の呼び出し
  // `sub_stack` に `Call(pos, ret)` をpush、`bt_stack` に `PopCall` をpushしてsubに飛ぶ
  case Call(sub: BlockId, ret: BlockId)

  // 先読み・後読み
  // `sub_stack` に `LookAround(pos, positive, dir, to)` をpush、`bt_stack` に `MarkLookAround` をpushしてsubに飛ぶ
  case LookAround(positive: Bool, dir: Dir, sub: BlockId, to: BlockId)

  // 非包含オペレータ `(?~...)`
  // `sub_stack` に `Absence(pos, sub, to)` をpush、`bt_stack` に `MarkAbsence` をpushして、subに飛ぶ
  case Absence(sub: BlockId, to: BlockId)

  // 条件付きグループ `(?(ref)yes|no)`
  // refが有効ならyes、無効ならnoに分岐するが、
  // この分岐はアトミック (yesで失敗したからといってnoにバックトラックしない) なため、
  // 特別な命令が必要になる
  case Conditional(cap_nums: Array[Int], depth: Option[Int], yes: BranchId, no: BranchId)

  // パターン全体の終端
  // マッチを終了する
  case End

  // マッチの失敗 (バックトラックが起こる)
  // `bt_stack` をpopして状態を戻していく
  // 次の特別なアイテムをpopした場合止まる (`ProtectAtomic` や `ProtectLookAround` の対応の中ではスキップされる、`call_stack` の `pop` は行う)
  // - `Bt(pos, tr)`: `pos`に戻して `tr` の遷移を試みる。遷移できなければバックトラックを続ける
  // - `MarkLookAround`: `call_stack` をpopして、肯定か否定か確認。肯定ならそのままバックトラックを続ける、否定ならマッチに成功しているので`pos`に戻してから`to`に飛ぶ
  // - `MarkAbsence`: `call_stack` をpopして、`bt_stack`に `Absence(next_pos, sub, to)`をpushして、`to`に飛ぶ。`next_pos`は次の`pos`の位置
  // - `Absence(next_pos, sub, to)`: `Absence`と同等のことを `next_pos` で行う
  case Fail

  // LookAroundの終端
  // `call_stack`をpopして `pos` と `to` を得て、肯定か否定かで意味が変わる:
  // - 肯定の場合: `bt_stack` に `ProtectLookAround` をpushして、`pos` に戻してから`to`に飛ぶ
  // - 否定の場合: 対応する`MarkLookAround` (ネストを考慮して) までpopしてからバックトラック
  case EndLookAround

  // Callの終端
  // `call_stack`をpopして`ret`を得て、`bt_stack`に`PushCall(ret)`をpushして`ret`にジャンプする
  case Return

  // Absenceの終端
  // absence operatorで終端に到達する (= マッチに成功する) ということは、マッチに失敗するということ
  // `call_stack`をpopして、`bt_stack`を`AbsenceMark`が出るまでpopしてからバックトラックする
  case EndAbsence

// ブロックの遷移
// 複数に分岐する場合のためにリンクリストになっている
enum Trans:
  // バックトラックを伴う分岐の遷移
  // 条件を満たした場合 `bt_stack` に `Bt(pos, tr)` をpushして、`to` に飛ぶ
  case Fork(
    cond: Array[Cond], // 遷移の条件 (AND)、空なら無条件
    to: BlockId,
    tr: Trans
  )
  // バックトラックしない遷移
  case Jump(
    cond: Array[Cond], // 遷移の条件 (AND)、空なら無条件
    to: BlockId
  )

enum Cond:
  // カウンタによる繰り返し
  case LtReg(reg_id: RegId, n: Int)
  // 空ループのチェック
  case CheckCapGen(loop_id: LoopId, cap_ids: Array[CapId])
  // 他にも、次のバイト値が何である必要があるか、残り最低でも何バイト必要か、
  // などの条件が最適化で設定されうる

HIRの実行モデル:

struct HIRRuntime:
  // マッチ中の文字列の位置
  pos: Int
  // バックトラック用のスタック
  bt_stack: Stack[Bt]
  // 部分式の呼び出しや先読みなどのスタック
  sub_stack: Stack[Sub]
  // キャプチャ
  caps: Map[CapId, CapState]
  call_caps: Array[Map[CapId, CapState]]
  // バックトラックが有効かの
  // 空ループの判定用
  current_gen: Int
  loop_gen: Map[LoopId, Int]
  cap_gen: Map[CapId, Int]

// バックトラック用のスタック
// マッチに失敗したときなどにここからpopしていく
enum Bt:
  // posに戻して、trの遷移を試みる
  // `Fork`の際に積まれる
  case Bt(pos: Int, tr: Trans)
  // キャプチャの状態を戻す
  // `CapBegin` の際に積まれる
  case UndoCap(cap_id: CapId, cap_state: CapState)
  // `call_stack`からpopする
  // `Call`の際に積まれる
  case PopCall
  // `call_stack`に`ret`を積み直す
  // `Return`で部分式の呼び出しを抜けた際に積まれる
  case PushCall(ret: BlockId)
  // 先読み・後読み用のマーカー
  case MarkLookAround
  case ProtectLookAround
  // `Absence` 用のマーカー
  case MarkAbsence
  // アトミックグループ用のマーカー
  case MarkAtomic
  case ProtectAtomic
  // `SaveCapGen` の逆の操作
  case RestoreCapGen(loop_id: LoopId, prev_gen: Int)

// 部分式の呼び出しのスタック
// `End` に到達するとき空になっているはず
enum Sub:
  case Call(ret: BlockId)
  case LookAround(pos: Int, positive: Boolean, dir: Dir, to: BlockId)
  case Absence(pos: Int, sub: BlockId, to: BlockId)

// キャプチャの状態
enum CapState:
  case None
  case Partial(begin: Int)
  case Set(begin: Int, end: Int)

メモ化の仕様や、メモ化のためのキー (posとHIR上のカウンタを考慮した位置) をどのように決めるか考えなければいけない。


2026-04-15: 誕生日だった

2026-04-14: HIRの仕様を考えている

2026-04-13: Rubyでプロトタイピングしている