ステップ4:実行時の動作

パーサがエラーを処理し、入力を消費し、最適なパフォーマンスのためにキャッシングを制御する方法を理解します。

解析メソッド

parseAll(...).getOrThrow()

入力全体が消費されることを要求します:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val number = +Regex("[0-9]+") map { it.value.toInt() } named "number"

fun main() {
    number.parseAll("123").getOrThrow()      // ✓ 123を返す
    // number.parseAll("123abc").getOrThrow() // ✗ ParseException
    // number.parseAll("abc").getOrThrow()    // ✗ ParseException
}

例外の種類

この例外は、詳細なエラー情報のためのcontextプロパティを提供します。

エラーコンテキスト

DefaultParseContextは、ユーザーフレンドリーなエラーメッセージを構築するために解析の失敗を追跡します:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val letter = +Regex("[a-z]") map { it.value } named "letter"
val digit = +Regex("[0-9]") map { it.value } named "digit"
val identifier = letter * (letter + digit).zeroOrMore

fun main() {
    val result = identifier.parseAll("1abc")
    val exception = result.exceptionOrNull() as? ParseException

    check(exception != null)  // 解析失敗
    check((exception.context.errorPosition ?: 0) == 0)  // 位置0で失敗

    val expected = exception.context.suggestedParsers
        .mapNotNull { it.name }
        .distinct()
        .sorted()
        .joinToString(", ")

    check(expected == "letter")  // "letter"が期待される
}

エラー追跡プロパティ

解析が進むにつれて:

  1. パーサがerrorPositionより遠くで失敗した場合、更新され、suggestedParsersがクリアされます
  2. 現在のerrorPositionで失敗したパーサがsuggestedParsersに追加されます
  3. 名前付きパーサは割り当てられた名前を使用して表示されます

例外でのエラーコンテキストの使用

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val number = +Regex("[0-9]+") map { it.value.toInt() } named "number"
val operator = (+'*' + +'+') named "operator"
val expr = number * operator * number

fun main() {
    val result = expr.parseAll("42 + 10")
    val exception = result.exceptionOrNull() as? ParseException

    check(exception != null)  // 解析失敗
    check((exception.context.errorPosition ?: 0) > 0)  // エラー位置が追跡される
    val suggestions = exception.context.suggestedParsers.orEmpty().mapNotNull { it.name }
    check(suggestions.isNotEmpty())  // 提案がある
}

リッチなエラーメッセージ

formatMessage拡張関数を使用すると、エラー位置、期待される要素、該当行のソースコード、エラー箇所を示すキャレット表示を含むユーザーフレンドリーなエラーメッセージを生成できます:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val number = +Regex("[0-9]+") map { it.value.toInt() } named "number"
val operator = +'+' + +'-'
val expr = number * operator * number

fun main() {
    val input = "42*10"
    try {
        expr.parseAll(input).getOrThrow()
    } catch (exception: ParseException) {
        val message = exception.formatMessage()
        val lines = message.lines()
        check(lines[0] == "Syntax Error at 1:3")
        check(lines[1] == "Expect: \"+\", \"-\"")
        check(lines[2] == "Actual: \"*\"")
        check(lines[3] == "42*10")
        check(lines[4] == "  ^")
    }
}

formatMessage関数は以下を提供します:

メモ化とキャッシング

デフォルトの動作

DefaultParseContextはデフォルトでメモ化を使用して、バックトラッキングを予測可能にします:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val parser = +Regex("[a-z]+") map { it.value } named "word"

fun main() {
    // メモ化有効(デフォルト)
    parser.parseAll("hello").getOrThrow()
}

(parser, position)ペアがメモ化されるため、同じ位置での繰り返しの試行はメモ化された結果を返します。

メモ化の無効化

文法が大量のバックトラックをしない場合、メモリ使用量を減らすためにメモ化を無効化します:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val parser = +Regex("[a-z]+") map { it.value } named "word"

fun main() {
    parser.parseAll("hello") { s -> DefaultParseContext(s).also { it.useMemoization = false } }.getOrThrow()
}

トレードオフ:

エラー伝播

map関数が例外をスローした場合、それは伝播して解析を中止します:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val divisionByZero = +Regex("[0-9]+") map { value ->
    val n = value.value.toInt()
    if (n == 0) error("Cannot divide by zero")
    100 / n
} named "number"

fun main() {
    divisionByZero.parseAll("10").getOrThrow()  // ✓ 10を返す
    // divisionByZero.parseAll("0").getOrThrow()  // ✗ IllegalStateException
}

マッピング前に検証するか、回復が必要な場合はエラーをキャッチしてラップします。

デバッグのヒント

結果からエラー詳細を検査

解析結果からエラーコンテキストにアクセス:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val parser = +Regex("[a-z]+") named "word"

fun main() {
    val result = parser.parseAll("123")
    val exception = result.exceptionOrNull() as? ParseException

    check(exception != null)  // 解析失敗
    check((exception.context.errorPosition ?: 0) == 0)  // 位置0でエラー
    check(exception.context.suggestedParsers?.any { it.name == "word" })  // "word"を提案
}

巻き戻し動作の確認

optionalzeroOrMoreが失敗時にどのように巻き戻すかを確認:

import io.github.mirrgieriana.xarpeg.*
import io.github.mirrgieriana.xarpeg.parsers.*

val parser = (+Regex("[a-z]+") named "letters").optional * +Regex("[0-9]+") named "digits"

fun main() {
    // optionalは失敗するが巻き戻し、数値パーサが成功できる
    val result = parser.parseAll("123").getOrThrow()
    check(result != null)  // 成功
}

リファレンスとしてテストを使用

観測された動作についてはテストスイートを確認:

重要なポイント

次のステップ

エラー報告とソースマッピングのための位置情報を抽出する方法を学びます。

ステップ5:解析位置