非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

自由モノイドのイテレーター(30)

自由モノイドプログラミング言語の作成(MonIter バージョン1.2 仕様(14))

構文解析のクラス

C# のパーサコンビネータ Sprache (Sprache/README.md at develop · sprache/Sprache · GitHub)を使っています。Visual Studio のソリューションエクスプローラーでプロジェクト名(MonIter)を右クリックしてメニューから「NuGet パッケージの管理」を選択するとウィンドウが表示されます。「参照」タブで Sprache を検索して Sprache をインストールします。

using Sprache;

と書くと Sprache を使うことができます。

拡張メソッドのためのクラス(ParserExtension)

「拡張メソッド」については「自由モノイドのイテレーター(13) - エレファント・ビジュアライザー調査記録」でも書きましたが詳しいことは拡張メソッド - C# | Microsoft Learnを見てください。

ある静的クラスを作って(上記のページでは非ジェネリックの静的クラスとなっています)そのクラスの静的メソッドで最初のパラメーターに this をつけたものが拡張メソッド(最初のパラメーターのクラスのメソッドとして使えます)となります。

このシステムでは以下のクラスを作成します。

このクラスでは以下の Sprache のメソッドを使っています。

Contained ある要素で囲まれた構文のパース
Char 文字のパース
Token 前後に空白などを含んでも良い文字列のパース:その文字列を返す
DelimitedBy ある要素で区切られたリストのパース

from … select という「クエリ式」の形で書くことができます。from に書いたパーサーの結果を select 内で使用することができます。

    /// <summary>
    /// パーサーの拡張メソッドのためのクラス
    /// </summary>
    internal static class ParserExtension
    {
        /// <summary>
        /// かっこで囲まれた式の構文解析
        /// </summary>
        /// <typeparam name="T">構文解析で取得される型</typeparam>
        /// <param name="parser">式のパーサー</param>
        /// <returns>構文解析の結果の構文要素</returns>
        public static Parser<T> ParenEnclosed<T>(this Parser<T> parser)
        {
            return parser.Contained(Parse.Char('(').Token(), Parse.Char(')').Token());
        }
        /// <summary>
        /// 区切り文字で区切られたリストの構文解析
        /// </summary>
        /// <typeparam name="T">構文解析で取得される型</typeparam>
        /// <param name="parser">各要素のパーサー</param>
        /// <param name="sep">区切り文字</param>
        /// <returns>構文解析の結果の構文要素</returns>
        public static Parser<IEnumerable<T>> ListBy<T>(this Parser<T> parser, char sep)
        {
            return parser.DelimitedBy(Parse.Char(sep).Token());
        }
        /// <summary>
        /// 単項演算の構文解析
        /// </summary>
        /// <typeparam name="T">演算数の構文解析で取得される型</typeparam>
        /// <typeparam name="S">構文解析で取得される型</typeparam>
        /// <param name="parser">演算数のパーサー</param>
        /// <param name="op_parser">演算子のパーサー</param>
        /// <param name="syngen">構文要素を作る関数</param>
        /// <returns>構文解析の結果の構文要素</returns>
        public static Parser<S> Unary<T, S>(this Parser<T> parser, Parser<string> op_parser, Func<string, T, S> syngen)
        {
            return from op in op_parser
                   from exp in parser
                   select syngen(op, exp);
        }
    }
構文解析のクラス(MonParser)

このクラスで BNF のような形で構文を書けるようにするか、または「ドキュメントコメント」から構文の説明ができるようにしたいと考えているのですが、どちらもまだできていません。

Visual Studio で「ドキュメントコメント」の XML ファイルを出力するには、メニューの「プロジェクト - MonIter のプロパティ」でプロパティのウィンドウを開き、「ビルド」の「出力」を選択して「ドキュメントファイル」をチェックします。

このクラスでは以下の Sprache のメソッドを使っています。

Ref 後で定義されているパーサーを使うときに使う
Number 数値のパース
Letter アルファベットの文字のパース
LetterOrDigit 英数字のパース
Or どちらかのパースが成功したときその結果を返す
Identifier 最初の文字とその後の文字列を指定した文字列のパース
String 文字列のパース
Text パースの結果を(配列ではなく)文字列として返す
ChainOperator 連続する二項演算のパース
    /// <summary>
    /// 構文解析を行うクラス
    /// </summary>
    internal class MonParser
    {
        /// <summary>
        /// 式(無限自由モノイド)の構文解析
        /// </summary>
        private static readonly Parser<Expression> ParseExpression =
            Parse.Ref(() => ParseConjunction);

        /// <summary>
        /// 式(数値)の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainExpression =
            Parse.Ref(() => ParseDomainSum);

        /// <summary>
        /// 単項演算(数値)の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainUnary =
            Parse.Ref(() => ParseDomainUnary_);

        /// <summary>
        /// 数値の構文解析
        /// </summary>
        private static readonly Parser<Number> ParseNumber =
            from num in Parse.Number.Token()
            select Prog.Number(num);

        /// <summary>
        /// 名前(関数名)の構文解析
        /// </summary>
        private static readonly Parser<Identifier> ParseIdentifier =
            from name in Parse.Identifier(Parse.Letter, Parse.LetterOrDigit.Or(Parse.Char('_'))).Token()
            select Prog.Identifier(name);

        /// <summary>
        /// パラメーター名の構文解析
        /// </summary>
        private static readonly Parser<ParameterName> ParseParameterName =
            from name in Parse.Identifier(Parse.Letter, Parse.LetterOrDigit.Or(Parse.Char('_'))).Token()
            select Prog.ParameterName(name);

        /// <summary>
        /// 数値の式の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainTerm =
            ((Parser<DomainExpression>)ParseIdentifier)
            .Or(ParseNumber)
            .Or(ParseDomainExpression.ParenEnclosed());

        /// <summary>
        /// 単項演算子の構文解析
        /// </summary>
        private static readonly Parser<string> ParseUnaryOperator =
            (Parse.String("-")).Token().Text();

        /// <summary>
        /// 単項演算(数値)の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainUnary_ =
            ParseDomainUnary.Unary(ParseUnaryOperator, Prog.Unary)
            .Or(ParseDomainTerm);

        /// <summary>
        /// 乗法系演算子の構文解析
        /// </summary>
        private static readonly Parser<string> ParseProdOperator =
            (Parse.String("*").Or(Parse.String("/")).Or(Parse.String("%"))).Token().Text();

        /// <summary>
        /// 乗法系演算(数値)の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainProd =
            Parse.ChainOperator(ParseProdOperator, ParseDomainUnary, Prog.Binary);

        /// <summary>
        /// 加法系演算子の構文解析
        /// </summary>
        private static readonly Parser<string> ParseSumOperator =
            (Parse.String("+").Or(Parse.String("-"))).Token().Text();

        /// <summary>
        /// 加法系演算(数値)の構文解析
        /// </summary>
        private static readonly Parser<DomainExpression> ParseDomainSum =
            Parse.ChainOperator(ParseSumOperator, ParseDomainProd, Prog.Binary);

        /// <summary>
        /// 数値の式のリストの構文解析
        /// </summary>
        private static readonly Parser<IEnumerable<DomainExpression>> ParseDomainExpressionList =
            ParseDomainExpression.ListBy(',');

        /// <summary>
        /// 関数(数値のパラメーター)呼び出しの構文解析
        /// </summary>
        private static readonly Parser<DomainFunction> ParseDomainFunction =
            from name in ParseIdentifier
            from list in ParseDomainExpressionList.ParenEnclosed()
            select Prog.DomainFunction(name, list);

        /// <summary>
        /// 式(無限自由モノイド)のリストの構文解析
        /// </summary>
        private static readonly Parser<IEnumerable<Expression>> ParseExpressionList =
            ParseExpression.ListBy(',');

        /// <summary>
        /// 関数(自由モノイドのパラメーター)呼び出しの構文解析
        /// </summary>
        private static readonly Parser<Function> ParseFunction =
            from pr in Parse.Char('$').Token()
            from name in ParseIdentifier
            from list in ParseExpressionList.ParenEnclosed()
            select Prog.Function(name, list);

        /// <summary>
        /// 数値の式からなる項の構文解析
        /// </summary>
        private static readonly Parser<Term> ParseDomainUnit =
            from exp in ParseDomainExpression
            select Prog.DomainExpression(exp);

        /// <summary>
        /// 項(無限自由モノイドの元)の構文解析
        /// </summary>
        private static readonly Parser<Term> ParseTerm =
            ((Parser<Term>)ParseFunction)
            .Or(ParseDomainFunction)
            .Or(ParseDomainUnit);

        /// <summary>
        /// 無限自由モノイドの元の構文解析
        /// </summary>
        private static readonly Parser<Expression> ParseConjunction =
            from list in ParseTerm.ListBy('&')
            select new Expression(list);

        /// <summary>
        /// 無限自由モノイドの積の構文解析
        /// </summary>
        private static readonly Parser<Statement> ParseExpressionStatement =
            from exp in ParseExpression
            select Prog.ExpressionStatement(exp);

        /// <summary>
        /// パラメーターのリストの構文解析
        /// </summary>
        private static readonly Parser<IEnumerable<ParameterName>> ParseParameterList =
            ParseParameterName.ListBy(',');

        /// <summary>
        /// 関数定義の構文解析
        /// </summary>
        private static readonly Parser<Definition> ParseDefinition =
            from pr in Parse.String("def").Token()
            from name in ParseIdentifier
            from list in ParseParameterList.ParenEnclosed()
            from eq in Parse.Char('=').Token()
            from exp in ParseExpression
            select Prog.Definition(name, list, exp);

        /// <summary>
        /// ステートメントの構文解析
        /// </summary>
        private static readonly Parser<Statement> ParseStatement =
            ParseDefinition
            .Or(ParseExpressionStatement);

        /// <summary>
        /// プログラム全体の構文解析
        /// </summary>
        private static readonly Parser<Prog> ParseProgram =
            from list in ParseStatement.ListBy(';')
            select new Prog(list);

        /// <summary>
        /// プログラム全体の構文解析
        /// </summary>
        public static Prog ParseProg(string src) { return ParseProgram.Parse(src); }
    }