はじめに
Renaはテキストをパースするライブラリです。
トップダウンパーシングを採用しているので、数式のような再帰的なパターンを記述することもできます。
Renaは合成属性および継承属性を扱うこともできます。
'Rena’はREpetation(またはREcursion) Notation APIの接頭語です。
Renaによるパーサの作成
マッチオブジェクト
Renaはマッチオブジェクトを組み合わせてパーサを生成します。
マッチオブジェクトファクトリ
マッチオブジェクトのファクトリはRenaクラスを使用します。
ジェネリクスで属性の値の型を指定します。
Rena<Double> fac = new Rena<Double>();
マッチオブジェクトの生成
マッチオブジェクトはファクトリから生成します。
引数として単純な文字列、正規表現、関数、マッチオブジェクトが使用できます。
生成したいマッチオブジェクトとメソッドの関係は以下の通りです。
メソッド | 内容 |
---|---|
string |
文字列 |
regex |
正規表現 |
then |
PatternMatcherそのもの |
Rena<Double> fac = new Rena<Double>();
PatternMatcher<Double> parser = fac.string("string");
文字列のマッチ
文字列をマッチさせたいときはマッチオブジェクトのparseメソッドを使用します。 parseの結果は以下のプロパティを持つオブジェクトまたはnull(マッチしなかったとき)です。
プロパティ | 内容 |
---|---|
match |
マッチした文字列 |
lastIndex |
マッチした最後の位置 |
attribute |
属性 |
マッチオブジェクトのマッチメソッドには他に以下の種類があります。
メソッド | 内容 |
---|---|
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等のメソッドの呼び出しの後に属性を受け取って新しい属性を返すアクション関数を付け足します。
アクション関数は以下の引数を受け取ります。
引数 | 内容 |
---|---|
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());