繰り返しに対応するよう、さらに ChatGPT で書いてもらうことにします。
入力1: 項のリストなどの処理のために、「拡張 BNF」のようにパース関数を繰り返す機能を追加したいのですが、どのようにすれば良いでしょうか
結果:
「拡張 BNF」のようにパース関数を繰り返して項のリストやその他の複雑な構造を扱うために、ParseInfo クラスやパース関数に以下のような仕組みを追加すると便利です。
概要
1. 繰り返し処理の追加: 任意個の要素をパースするための新しいヘルパー関数を ParseInfo クラスに追加します。
- パース対象の区切り文字(例: `,`)を指定できる。
- 最低限の繰り返し回数(0回や1回など)を指定できる。
2. BNF のような記述を関数化:
- `ParseOneOrMore`、`ParseZeroOrMore` などの関数を追加し、それを呼び出すことでリスト構造を扱えるようにする。
実装
以下のコードでは、新しい繰り返し処理用の関数を ParseInfo クラスに追加し、それを使ったリストのパース例を示します。
C# コード
using System; using System.Collections.Generic; namespace PrologParser { public class ParseInfo<T> where T : IPrologStructure { public string Source { get; } public int Position { get; } public bool Success { get; } public T Result { get; } public ParseInfo(string source, int position, bool success, T result) { Source = source; Position = position; Success = success; Result = result; } public static implicit operator bool(ParseInfo<T> parseInfo) => parseInfo.Success; /// <summary> /// 繰り返しパース処理を行うヘルパー関数。 /// </summary> /// <typeparam name="TItem">リスト内の各要素の型。</typeparam> /// <param name="parseItem">1つの要素をパースする関数。</param> /// <param name="separator">要素の区切り文字。</param> /// <param name="minCount">最低繰り返し回数。</param> /// <param name="skipSpaces">スペースをスキップするか。</param> /// <returns>新しい ParseInfo リスト。</returns> public ParseInfo<List<TItem>> ParseRepeated<TItem>( Func<ParseInfo<IPrologStructure>, ParseInfo<TItem>> parseItem, string separator = null, int minCount = 0, bool skipSpaces = true) where TItem : IPrologStructure { var items = new List<TItem>(); var currentInfo = new ParseInfo<IPrologStructure>(Source, Position, true, null); while (true) { // スペースをスキップ if (skipSpaces) { while (currentInfo.Position < Source.Length && char.IsWhiteSpace(Source[currentInfo.Position])) { currentInfo = new ParseInfo<IPrologStructure>( currentInfo.Source, currentInfo.Position + 1, true, null); } } // パース処理 var itemInfo = parseItem(currentInfo); if (!itemInfo) { break; } items.Add(itemInfo.Result); currentInfo = new ParseInfo<IPrologStructure>(itemInfo.Source, itemInfo.Position, true, null); // 区切り文字があればパース if (!string.IsNullOrEmpty(separator)) { var separatorInfo = currentInfo.ParseString(separator, skipSpaces: true); if (!separatorInfo) { break; } currentInfo = new ParseInfo<IPrologStructure>(separatorInfo.Source, separatorInfo.Position, true, null); } } // 繰り返し回数チェック if (items.Count < minCount) { return new ParseInfo<List<TItem>>(Source, Position, false, null); } return new ParseInfo<List<TItem>>(Source, currentInfo.Position, true, items); } /// <summary> /// 指定された文字列をパース。 /// </summary> public ParseInfo<IPrologStructure> ParseString( string pattern, bool skipSpaces = true, bool captureAsName = false) { int position = Position; if (skipSpaces) { while (position < Source.Length && char.IsWhiteSpace(Source[position])) position++; } if (position >= Source.Length) { return new ParseInfo<IPrologStructure>(Source, Position, false, null); } string remainingSource = Source.Substring(position); var match = System.Text.RegularExpressions.Regex.Match(remainingSource, $"^{System.Text.RegularExpressions.Regex.Escape(pattern)}"); if (match.Success) { string matchedValue = match.Value; position += matchedValue.Length; IPrologStructure result = null; if (captureAsName) { result = new Name(matchedValue); } return new ParseInfo<IPrologStructure>(Source, position, true, result); } return new ParseInfo<IPrologStructure>(Source, Position, false, null); } } // 他の代数的構造クラスは省略 public static class PrologParser { public static ParseInfo<Name> ParseName(ParseInfo<IPrologStructure> info) { return info.ParseString(@"\w+", skipSpaces: true, captureAsName: true) as ParseInfo<Name>; } public static ParseInfo<List<Term>> ParseTermList(ParseInfo<IPrologStructure> info) { return info.ParseRepeated( parseItem: ParseTerm, separator: ",", minCount: 1); } public static ParseInfo<Term> ParseTerm(ParseInfo<IPrologStructure> info) { var nameInfo = ParseName(info); if (!nameInfo) return new ParseInfo<Term>(info.Source, info.Position, false, null); var name = nameInfo.Result; var position = nameInfo.Position; if (position < info.Source.Length && info.Source[position] == '(') { position++; var argsInfo = new ParseInfo<IPrologStructure>(info.Source, position, true, null).ParseRepeated( parseItem: ParseTerm, separator: ",", minCount: 0); if (!argsInfo || position >= info.Source.Length || info.Source[argsInfo.Position] != ')') return new ParseInfo<Term>(info.Source, info.Position, false, null); position = argsInfo.Position + 1; return new ParseInfo<Term>(info.Source, position, true, new Term("compound", name.Value, argsInfo.Result)); } var termType = char.IsUpper(name.Value[0]) ? "variable" : "constant"; return new ParseInfo<Term>(info.Source, nameInfo.Position, true, new Term(termType, name.Value)); } } }主なポイント
1. ParseRepeated 関数:
- 任意のパース関数を繰り返し適用し、リストを生成します。
- 区切り文字の処理も含めます。
2. ParseTermList 関数:
- 項のリストをパースするための例。区切り文字 `,` に対応。
3. 柔軟な組み合わせ:
使用例
入力
p(X, Y, Z).出力
Compound Term: p(X, Y, Z)

