はじめに

Renaはテキストをパースするライブラリです。
トップダウンパーシングを採用しているので、数式のような再帰的なパターンを記述することもできます。
Renaは合成属性および継承属性を扱うこともできます。
'Rena’はREpetation(またはREcursion) Notation APIの接頭語です。

Renaによるパーサの作成

マッチオブジェクト

Renaはマッチオブジェクトを組み合わせてパーサを生成します。

マッチオブジェクトファクトリ

マッチオブジェクトのファクトリはRenaクラスを使用します。
ジェネリクスで属性の値の型を指定します。

Rena<Double> fac = new Rena<Double>();

マッチオブジェクトの生成

マッチオブジェクトはファクトリから生成します。
引数として単純な文字列、正規表現、関数、マッチオブジェクトが使用できます。
生成したいマッチオブジェクトとメソッドの関係は以下の通りです。

Table 1. マッチオブジェクト
メソッド 内容

string

文字列

regex

正規表現

then

PatternMatcherそのもの

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.string("string");

文字列のマッチ

文字列をマッチさせたいときはマッチオブジェクトのparseメソッドを使用します。 parseの結果は以下のプロパティを持つオブジェクトまたはnull(マッチしなかったとき)です。

Table 2. マッチオブジェクトの戻り値
プロパティ 内容

match

マッチした文字列

lastIndex

マッチした最後の位置

attribute

属性

マッチオブジェクトのマッチメソッドには他に以下の種類があります。

Table 3. マッチメソッド
メソッド 内容

parse

文字列全体にマッチ

parsePart

文字列の最初にマッチしたところ

parsePartGlobal

パターンにマッチする文字列全て

連接

連接はマッチオブジェクトのthenメソッドを使用します。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.string("765").then(fac.string("pro"));
System.out.println(parser.parse("765pro").getMatch());  // "765pro"

選択

選択は静的メソッドのorを使用します。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.or(fac.string("765"), fac.string("346"), fac.string("283"));
System.out.println(parser.parse("765").getMatch());  // "765"

繰り返し

n回以上の繰り返し

n回以上の繰り返しにはマッチオブジェクトまたはファクトリのatLeastを使用します。
また、マッチオブジェクトのthenAtLeastを使用すればthenと複合することができます。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.regex("[0-9]").atLeast(3);
System.out.println(parser.parse("765").getMatch());  // "765"

ショートカットとして0回以上の繰り返しにはzeroOrMore、1回以上の繰り返しにはoneOrMoreが用意されています。

Caution

繰り返しの後にthenを記述することはできません。

n回からm回までの繰り返し

n回からm回までの繰り返しにはtimesを使用します。
thenとの複合としてthenTimesも用意しています。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.regex("[0-9]").times(2, 4);
System.out.println(parser.parse("765").getMatch());  // "765"

ショートカットとして0回からm回の繰り返しにはatMostを、0回および1回の出現にはmaybeを用意しています。

デリミタで区切られたパターン

デリミタで区切られたパターンにマッチするにはdelimitを使用します。 thenとの複合としてthenDelimitも用意しています。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser =  fac.regex("[0-9]").delimit(",");
System.out.println(parser.parse("7,6,5").getMatch());  // "7,6,5"

先読み

先読みにはlookaheadを用意しています。
否定の先読みとしてlookaheadNotも用意しています。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.string("765").lookahead(fac.string("pro"));
System.out.println(parser.parseStart("765pro").getMatch());  // "765"
System.out.println(parser.parseStart("765pr"));              // null

キーワード

演算子等を定義するときにトークン列を最長一致でマッチさせたいということがあります。
そのときはRenaコンストラクタの引数でトークンの集合を定義し、keyメソッドでマッチさせることができます。

Rena<Double> fac = new Rena<Double>(new String[] { "+", "++", "+=" });
PatternMatcher<Double> parser = fac.key("+");
System.out.println(parser.parse("+").getMatch());  // "+"
System.out.println(parser.parse("+-"));            // null

自己再帰

式の記述等で自分自身に再帰させたいことがあります。
そのときはRena.letrec静的メソッドを使用すれば自分自身に再帰させることができます。

下記の例は釣り合いの取れたかっこについてマッチします。

Rena<Double> fac = new Rena<Double>();
var parser = Rena.letrec(
  x -> fac.maybe(fac.string("(").then(x).then(fac.string(")"))
);
System.out.println(parser.parse("((()))").getMatch());  // "((()))"
System.out.println(parser.parse("((())"));              // null

空白等の読み飛ばし

Renaコンストラクタに空白文字の正規表現を指定することで 文字列の途中に入る空白等を読み飛ばすことができます。

Rena<Double> fac = new Rena<Double>(" +");
PatternMatcher<Double> parser = fac.oneOrMore(fac.regex("[0-9]"));
System.out.println(parser.parse("7   6  5").getMatch());  // "7   6  5"

属性

Renaの特徴として、属性を扱えることがあります。
属性には連接前に与えられた継承属性と適用したパターンが返した合成属性があります。
属性を使用するにはthen等のメソッドの呼び出しの後に属性を受け取って新しい属性を返すアクション関数を付け足します。
アクション関数は以下の引数を受け取ります。

Table 4. アクション関数
引数 内容

1番目

マッチした文字列

2番目

適用したパターンの属性(合成属性)

3番目

連接前の属性(継承属性)

繰り返しを扱うメソッドについてはアクションの後に属性の初期値を与えることもできます。

アクション関数は以下のように使用します。整数を計算するを以下に示します。

Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.oneOrMore(
    fac.regex("[0-9]", (match, synthesize, inherit) -> Double.parseDouble(match)),
    (match, synthesize, inherit) -> inherit * 10 + synthesize, 0);
System.out.println(parser.parse("765").getAttribute());  // 765.0

四則演算

Rena<Double> r = new Rena<Double>();
PatternMatcher<Double> expr = r.then(Rena.letrec(
  (term, factor, element) -> r.then(factor).thenZeroOrMore(r.or(
               r.string("+").then(
                   factor,
                   (match, synthesize, inherit) -> inherit + synthesize),
               r.string("-").then(
                   factor,
                   (match, synthesize, inherit) -> inherit - synthesize))),
  (term, factor, element) -> r.then(element).thenZeroOrMore(r.or(
               r.string("*").then(
                   element,
                   (match, synthesize, inherit) -> inherit * synthesize),
               r.string("/").then(
                   element,
                   (match, synthesize, inherit) -> inherit / synthesize))),
  (term, factor, element) -> r.or(
               r.regex("[0-9]+",
                   (match, synthesize, inherit) -> Double.parseDouble(match)),
               r.string("(").then(term).then(r.string(")"))))).end();

// outputs 7
System.out.println(expr.parse("1+2*3", 0).getAttribute());

// outputs 1
System.out.println(expr.parse("4-6/2", 0).getAttribute());