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

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

モノイド的論理プログラミング(6)

前回の結果は、BNF構文解析の関数が対応しているのですが、もう少しちゃんと対応するようにできそうなので、さらに ChatGPT で調べてみます。

入力1

以下のように入力しました。

以下のように変更してください:
パースのとき文字列を直接渡すかわりに次の情報を保持するクラス(「パース情報」クラス)を作って渡してください:

  • ソースの文字列
  • ソースの文字列の(次の要素の)パースを開始する位置
  • パースが成功したか失敗したか
  • パースが成功したときの結果の代数的構造のオブジェクト

「パース情報」クラスでは true、false をオーバーロードして true は成功、false は失敗を表すようにしてください
「名前」に対応するクラスを作ってください
「名前」をパースする関数を作って、成功したときは「名前」オブジェクトを含む「パース情報」オブジェクトを返すようにしてください

この結果、パース関数の引数が「パース情報」オブジェクトになっていなかったので以下のように入力しました。

入力2: パース関数の引数は「パース情報」オブジェクトにしてください

この結果、ParseClause、ParseProgram がなくなっていたので以下のように入力しました。

入力3: ParseClause、ParseProgram も同様に作ってください

結果:

using System;
using System.Collections.Generic;

namespace PrologParser
{
    public class ParseInfo<T>
    {
        public string Source { get; }
        public int Position { get; private set; }
        public bool Success { get; private set; }
        public T Result { get; private set; }

        public ParseInfo(string source, int position)
        {
            Source = source;
            Position = position;
            Success = false;
        }

        public void SetSuccess(T result, int newPosition)
        {
            Success = true;
            Result = result;
            Position = newPosition;
        }

        public static implicit operator bool(ParseInfo<T> parseInfo) => parseInfo.Success;
    }

    public class Name
    {
        public string Value { get; }

        public Name(string value)
        {
            Value = value;
        }

        public override string ToString()
        {
            return Value;
        }
    }

    public class Term
    {
        public string Type { get; }
        public string Value { get; }
        public List<Term> Args { get; }

        public Term(string type, string value, List<Term> args = null)
        {
            Type = type;
            Value = value;
            Args = args ?? new List<Term>();
        }

        public override string ToString()
        {
            if (Type == "compound")
                return $"{Value}({string.Join(", ", Args)})";
            return Value;
        }
    }

    public class Clause
    {
        public Term Head { get; }
        public List<Term> Body { get; }

        public Clause(Term head, List<Term> body)
        {
            Head = head;
            Body = body ?? new List<Term>();
        }

        public override string ToString()
        {
            if (Body.Count > 0)
                return $"{Head} :- {string.Join(", ", Body)}.";
            return $"{Head}.";
        }
    }

    public static class PrologParser
    {
        public static void ParseName(ParseInfo<Name> parseInfo)
        {
            var source = parseInfo.Source;
            var startPosition = parseInfo.Position;

            if (startPosition >= source.Length || !char.IsLetter(source[startPosition]))
            {
                return; // Failure: Not a valid name start
            }

            int position = startPosition;
            while (position < source.Length && (char.IsLetterOrDigit(source[position]) || source[position] == '_'))
            {
                position++;
            }

            var nameValue = source.Substring(startPosition, position - startPosition);
            parseInfo.SetSuccess(new Name(nameValue), position);
        }

        public static void ParseTerm(ParseInfo<Term> parseInfo)
        {
            var source = parseInfo.Source;
            var startPosition = parseInfo.Position;

            var nameParseInfo = new ParseInfo<Name>(source, startPosition);
            ParseName(nameParseInfo);

            if (!nameParseInfo)
                return; // Failure: Not a valid term

            var name = nameParseInfo.Result;
            var position = nameParseInfo.Position;

            if (position < source.Length && source[position] == '(')
            {
                position++;
                var args = new List<Term>();

                while (position < source.Length && source[position] != ')')
                {
                    var argParseInfo = new ParseInfo<Term>(source, position);
                    ParseTerm(argParseInfo);

                    if (!argParseInfo)
                        return; // Failure: Invalid argument

                    args.Add(argParseInfo.Result);
                    position = argParseInfo.Position;

                    if (position < source.Length && source[position] == ',')
                    {
                        position++; // Skip comma
                    }
                }

                if (position >= source.Length || source[position] != ')')
                    return; // Failure: Missing closing parenthesis

                position++; // Skip closing parenthesis
                parseInfo.SetSuccess(new Term("compound", name.Value, args), position);
                return;
            }

            var termType = char.IsUpper(name.Value[0]) ? "variable" : "constant";
            parseInfo.SetSuccess(new Term(termType, name.Value), position);
        }

        public static void ParseClause(ParseInfo<Clause> parseInfo)
        {
            var source = parseInfo.Source;
            var startPosition = parseInfo.Position;

            var headParseInfo = new ParseInfo<Term>(source, startPosition);
            ParseTerm(headParseInfo);

            if (!headParseInfo)
                return; // Failure: Invalid head

            var head = headParseInfo.Result;
            var position = headParseInfo.Position;

            if (position < source.Length && source[position] == ':')
            {
                position++; // Skip ':'
                if (position >= source.Length || source[position] != '-')
                    return; // Failure: Expected ':-'

                position++; // Skip '-'
                var body = new List<Term>();

                while (position < source.Length && source[position] != '.')
                {
                    var bodyParseInfo = new ParseInfo<Term>(source, position);
                    ParseTerm(bodyParseInfo);

                    if (!bodyParseInfo)
                        return; // Failure: Invalid body term

                    body.Add(bodyParseInfo.Result);
                    position = bodyParseInfo.Position;

                    if (position < source.Length && source[position] == ',')
                    {
                        position++; // Skip comma
                    }
                }

                if (position >= source.Length || source[position] != '.')
                    return; // Failure: Missing terminating period

                position++; // Skip terminating period
                parseInfo.SetSuccess(new Clause(head, body), position);
                return;
            }

            if (position < source.Length && source[position] == '.')
            {
                position++; // Skip terminating period
                parseInfo.SetSuccess(new Clause(head, null), position);
                return;
            }

            return; // Failure: Missing terminating period or invalid syntax
        }

        public static void ParseProgram(ParseInfo<List<Clause>> parseInfo)
        {
            var source = parseInfo.Source;
            var position = parseInfo.Position;

            var clauses = new List<Clause>();
            while (position < source.Length)
            {
                var clauseParseInfo = new ParseInfo<Clause>(source, position);
                ParseClause(clauseParseInfo);

                if (!clauseParseInfo)
                    return; // Failure: Invalid clause

                clauses.Add(clauseParseInfo.Result);
                position = clauseParseInfo.Position;

                while (position < source.Length && char.IsWhiteSpace(source[position]))
                {
                    position++; // Skip whitespace
                }
            }

            parseInfo.SetSuccess(clauses, position);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string source = "p(X, Y). q(a) :- r(b), s(c).";

            var programParseInfo = new ParseInfo<List<Clause>>(source, 0);
            PrologParser.ParseProgram(programParseInfo);

            if (programParseInfo)
            {
                Console.WriteLine("Parsing succeeded:");
                foreach (var clause in programParseInfo.Result)
                {
                    Console.WriteLine(clause);
                }
            }
            else
            {
                Console.WriteLine("Parsing failed.");
            }
        }
    }
}

かなり BNF に対応したものができました。まだ

  • スペースの処理をパース関数の中で行っているのをどこでやるか検討
  • リストの構文解析を追加

などがやりたいことですが ChatGPT でできるのかどうかはわかりません。