2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIとつくる日本語プログラミング言語Aoi (あおい)(ステップ7)~「意味解析と名前解決」の最小実装~

2
Last updated at Posted at 2026-08-30

はじめに

生成AI(Microsoft CopilotとGitHub Copilot)に日本語プログラミング言語Aoi(あおい)の実装言語仕様を読み込ませて設計タスクから実装を展開しています。本記事では「意味解析と名前解決」の最小版を実装します。

日本語プログラミング言語 Aoi (あおい) という自作言語の企画趣旨はこちらの記事をご参照ください。開発コード名を「Re:Mind」としており、AIとのやりとりではこの名称が使われています。

生成AI

Microsoft Copilot

Windows11アプリケーションのMicrosoft Copilot。昨年末から日本語プログラミング言語 Aoi(Re:Mind)の原型を作成してもらった生成AI123。モードは「無料プラン」。サインインした状態で使用します。GitHub Copilotの使い方アドバイザーとして利用。

GitHub Copilot

GitHub Copilotのフリー版を導入。こちらの記事でAmazonのkiro-cliのskillsとかなり近いことができることが判明して結果オーライでした。スーパバイザとしてMicrosoft Copilotを使いGitHub CopilotのCodeAgent推論量を最小化するプロンプトをCopilotで生成しGitHub CopilotのCodeAgentで実行という工法?を採用しています。

本記事のステップ

こちらの記事で生成された「Re:Mind コンパイラ全体設計」を細分化したこちらの回の記事の「1. 最小セット(MVP)」の具体的な実装の途中です。今回は「意味解析と名前解決」の最小版を実装します。

1. 最小セット(MVP)

まずは「1つの小さなプログラムを通せる」レベルに絞る構成。
Phase 1. Lexer タスク
前々回記事以前で完了
Phase 2. Parser タスク
前々回記事で完了
Phase 3. AST タスク
前々回記事で完了

4. 意味解析と名前解決

  1. Symbol Table を導入し、以下を解決する。
    • ローカル変数・引数・フィールド・メソッド名
    • クラス名・名前空間名
    • import された AliasClasses とそのメンバー
  2. Imports の AliasClasses を活用して、Re:Mind の日本語メンバー呼び出しをターゲット言語側の英字名へ変換する。
  3. 例として、以下のような解決を実現する。
    • コンソール.一行表示する → Console.WriteLine
    • メンバー取り扱い方法.年齢 → object.age
    • 変数・メソッドの型とスコープを解決する
  4. 変数宣言前の使用や未定義名の検出を行う。

Microsoft Copilotとの事前準備

プロンプト1

Phase 4:意味解析と名前解決の最小タスク化プロンプトを作成してください。

生成レスポンス1

🧩 タスク 1:SymbolTable の骨格追加
以下は意味解析の仕様です。
SemanticResolver.cs に「SymbolTable の最小構造」を差分として追加してください。
変数・メソッド・クラス名を登録/検索するための最小限の Dictionary を持つ構造だけ追加してください。
既存コードは保持してください。
🧩 タスク 2:スコープ管理(BlockScope / MethodScope / ClassScope)
SemanticResolver.cs に「スコープ管理(BlockScope / MethodScope / ClassScope)」の
最小構造だけを差分として追加してください。
親スコープへの参照と、ローカル変数の登録/検索だけを実装してください。
既存コードは保持してください。

以下では「既存コードは保持してください。」を割愛しています。

🧩 タスク 3:ResolveIdentifier(識別子の名前解決)
SemanticResolver.cs に「ResolveIdentifier」の実装部分だけを
差分として追加してください。
スコープから変数名を検索し、見つからない場合はエラーを返す最小構造だけ追加してください。
🧩 タスク 4:AliasClass の名前解決(日本語 → 英語)
SemanticResolver.cs に「AliasClass の名前解決」部分だけを差分として追加してください。
AST の ImportDeclaration / AliasClass / AliasMember を参照し、
日本語名(コンソール)を英語名(Console)へ変換する最小構造だけ追加してください。
🧩 タスク 5:ResolveMember(メンバー呼び出しの名前解決)
SemanticResolver.cs に「ResolveMember」の実装部分だけを差分として追加してください。
MemberAccessExpression の Target が AliasClass の場合、AliasMember の英語名へ変換する最小構造だけ追加してください。
🧩 タスク 6:ResolveInvocation(呼び出しの意味解析)
SemanticResolver.cs に「ResolveInvocation」の実装部分だけを差分として追加してください。
InvocationExpression の Target が解決済みのメンバーであることを確認し、
引数の型を解決する最小構造だけ追加してください。
🧩 タスク 7:型推論(最小構造)
SemanticResolver.cs に「型推論の最小構造」だけを
差分として追加してください。
Number → int
String → string
Identifier → 変数の型
など、基本的な型推論だけ追加してください。
🧩 タスク 8:Assignment の意味解析(左辺・右辺の型一致チェック)
SemanticResolver.cs に「Assignment の意味解析」部分だけを
差分として追加してください。
左辺と右辺の型が一致するかを確認する最小構造だけ追加してください。
🧩 タスク 9:If(◇構文)の意味解析
SemanticResolver.cs に「IfStatement(◇構文)の意味解析」部分だけを
差分として追加してください。
条件式が bool 型であることを確認する最小構造だけ追加してください。
🧩 タスク 10:Loop(〇構文)の意味解析
SemanticResolver.cs に「LoopStatement(〇構文)の意味解析」部分だけを
差分として追加してください。
条件式が bool 型であることを確認する最小構造だけ追加してください。

🧭 mylifewithviolinさんのプロジェクトに最適な実行順序(Phase 4)
CodeAgent のクレジット消費を最小化するため、
以下の順番でタスクを投げるのが最適です。
1.SymbolTable
2.Scope 管理
3.ResolveIdentifier
4.AliasClass 名前解決
5.ResolveMember
6.ResolveInvocation
7.型推論
8.Assignment の意味解析
9.If(◇)
10.Loop(〇)
この順番は AST → Semantic → IR の依存関係に完全準拠しています。

GitHub Copilot

Microsoft Copilotが生成したGitHub CopilotへのPhase 4の各タスクの実行プロンプトを逐次実行した結果、下記のソースコードが生成されました。SemanticResolver.csのひな形は前回の記事で作成したものです。

SemanticResolver.cs
using System;
using System.Collections.Generic;
using ReMindAst;

namespace ReMindParser
{
    /// <summary>
    /// 意味解析と名前解決を行うクラス
    /// AST に対してシンボルテーブルを構築し、型チェックと名前解決を実行する
    /// </summary>
    public class SemanticResolver
    {
        private CompilationUnit _ast = null!;
        private SymbolTable _symbolTable = null!;
        private Scope _currentScope = null!;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="ast">解析対象の CompilationUnit</param>
        public SemanticResolver(CompilationUnit ast)
        {
            _ast = ast;
            _symbolTable = new SymbolTable();
            _currentScope = _symbolTable.GlobalScope;
        }

        /// <summary>
        /// AST 全体を意味解析する
        /// </summary>
        public void Resolve()
        {
            // TODO: AST 全体を走査して意味解析を実行
        }

        /// <summary>
        /// 式を意味解析する
        /// </summary>
        /// <param name="expression">解析対象の式</param>
        public void ResolveExpression(Expression expression)
        {
            // TODO: 式の型チェックと名前解決を実行
        }

        /// <summary>
        /// ステートメントを意味解析する
        /// </summary>
        /// <param name="statement">解析対象のステートメント</param>
        public void ResolveStatement(Statement statement)
        {
            // TODO: ステートメントの意味解析を実行
        }

        /// <summary>
        /// 識別子の解決を行う
        /// スコープから変数名を検索し、見つからない場合はエラーを返す
        /// </summary>
        public Symbol? ResolveIdentifier(string identifierName)
        {
            var symbol = _currentScope.LookupSymbol(identifierName);
            if (symbol != null)
            {
                return symbol;
            }

            throw new InvalidOperationException($"Undefined identifier '{identifierName}' in current scope.");
        }

        /// <summary>
        /// ImportDeclaration / AliasClass / AliasMember を参照して、
        /// 日本語名を英語名へ変換する最小実装。
        /// 例: コンソール -> Console
        /// </summary>
        public string ResolveAliasName(string name)
        {
            foreach (var import in _ast.Imports)
            {
                foreach (var aliasClass in import.AliasClasses)
                {
                    if (string.Equals(aliasClass.OriginalName, name, StringComparison.OrdinalIgnoreCase))
                    {
                        return aliasClass.TranspiledName;
                    }

                    foreach (var aliasMember in aliasClass.Members)
                    {
                        if (string.Equals(aliasMember.OriginalName, name, StringComparison.OrdinalIgnoreCase))
                        {
                            return aliasMember.TranspiledName;
                        }
                    }
                }
            }

            return name;
        }

        /// <summary>
        /// MemberAccessExpression の Target が AliasClass の場合、
        /// AliasMember の英語名へ変換する最小実装。
        /// 例: コンソール.一行表示する -> Console.WriteLine
        /// </summary>
        public string ResolveMember(MemberAccessExpression memberAccess)
        {
            if (memberAccess.Expression is IdentifierExpression targetIdentifier)
            {
                var targetName = ResolveAliasName(targetIdentifier.NameJa);

                foreach (var import in _ast.Imports)
                {
                    foreach (var aliasClass in import.AliasClasses)
                    {
                        if (string.Equals(aliasClass.TranspiledName, targetName, StringComparison.OrdinalIgnoreCase))
                        {
                            foreach (var aliasMember in aliasClass.Members)
                            {
                                if (string.Equals(aliasMember.OriginalName, memberAccess.MemberName, StringComparison.OrdinalIgnoreCase)
                                    || string.Equals(aliasMember.TranspiledName, memberAccess.MemberName, StringComparison.OrdinalIgnoreCase))
                                {
                                    return $"{aliasClass.TranspiledName}.{aliasMember.TranspiledName}";
                                }
                            }
                        }
                    }
                }
            }

            return $"{memberAccess.Expression}";
        }

        /// <summary>
        /// InvocationExpression の Target が解決済みのメンバーであることを確認し、
        /// 引数の型を解決する最小実装。
        /// </summary>
        public string ResolveInvocation(InvocationExpression invocation)
        {
            var targetText = invocation.Target.ToString();
            var resolvedTarget = targetText;

            if (invocation.Target is MemberAccessExpression memberAccess)
            {
                resolvedTarget = ResolveMember(memberAccess);
            }
            else if (invocation.Target is IdentifierExpression identifier)
            {
                resolvedTarget = ResolveIdentifier(identifier.NameJa).Name;
            }

            foreach (var argument in invocation.Arguments)
            {
                if (argument is IdentifierExpression argumentIdentifier)
                {
                    var argumentSymbol = ResolveIdentifier(argumentIdentifier.NameJa);
                    _ = argumentSymbol.Type;
                }
            }

            return resolvedTarget;
        }

        /// <summary>
        /// 基本的な型推論を行う最小実装。
        /// Number -> int, String -> string, Identifier -> 変数の型
        /// </summary>
        public string InferType(Expression expression)
        {
            if (expression is LiteralExpression literal)
            {
                if (literal.Value is int || literal.Value is long || literal.Value is short || literal.Value is byte)
                {
                    return "int";
                }

                if (literal.Value is string)
                {
                    return "string";
                }

                if (literal.Value is bool)
                {
                    return "bool";
                }

                return "object";
            }

            if (expression is IdentifierExpression identifierExpression)
            {
                var symbol = ResolveIdentifier(identifierExpression.NameJa);
                return symbol?.Type ?? "object";
            }

            if (expression is BinaryExpression binaryExpression)
            {
                var leftType = InferType(binaryExpression.Left);
                var rightType = InferType(binaryExpression.Right);
                if (leftType == rightType)
                {
                    return leftType;
                }

                return "object";
            }

            if (expression is UnaryExpression unaryExpression)
            {
                return InferType(unaryExpression.Operand);
            }

            return "object";
        }

        /// <summary>
        /// Assignment の意味解析を行う。
        /// 左辺と右辺の型が一致するかを確認する最小実装。
        /// </summary>
        public bool ResolveAssignment(AssignmentStatement assignment)
        {
            var leftType = InferType(assignment.Left);
            var rightType = InferType(assignment.Right);

            if (string.Equals(leftType, rightType, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            throw new InvalidOperationException($"Type mismatch in assignment: left is '{leftType}', right is '{rightType}'.");
        }

        /// <summary>
        /// IfStatement(◇構文)の意味解析を行う。
        /// 条件式が bool 型であることを確認する最小実装。
        /// </summary>
        public bool ResolveIfStatement(IfStatement ifStatement)
        {
            var conditionType = InferType(ifStatement.Condition);

            if (string.Equals(conditionType, "bool", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            throw new InvalidOperationException($"If condition must be bool, but was '{conditionType}'.");
        }

        /// <summary>
        /// LoopStatement(〇構文)の意味解析を行う。
        /// 条件式が bool 型であることを確認する最小実装。
        /// </summary>
        public bool ResolveLoopStatement(WhileStatement whileStatement)
        {
            var conditionType = InferType(whileStatement.Condition);

            if (string.Equals(conditionType, "bool", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            throw new InvalidOperationException($"Loop condition must be bool, but was '{conditionType}'.");
        }
    }

    /// <summary>
    /// シンボルテーブル
    /// 変数・メソッド・クラス・名前空間などのシンボル情報を管理する
    /// </summary>
    public class SymbolTable
    {
        private Scope _globalScope;
        private readonly Dictionary<string, Symbol> _variables = new();
        private readonly Dictionary<string, Symbol> _methods = new();
        private readonly Dictionary<string, Symbol> _classes = new();

        public Scope GlobalScope { get { return _globalScope; } }

        public Dictionary<string, Symbol> Variables => _variables;
        public Dictionary<string, Symbol> Methods => _methods;
        public Dictionary<string, Symbol> Classes => _classes;

        public SymbolTable()
        {
            _globalScope = new GlobalScope();
        }

        public void RegisterVariable(string name, Symbol symbol)
        {
            _variables[name] = symbol;
        }

        public Symbol? LookupVariable(string name)
        {
            return _variables.TryGetValue(name, out var symbol) ? symbol : null;
        }

        public void RegisterMethod(string name, Symbol symbol)
        {
            _methods[name] = symbol;
        }

        public Symbol? LookupMethod(string name)
        {
            return _methods.TryGetValue(name, out var symbol) ? symbol : null;
        }

        public void RegisterClass(string name, Symbol symbol)
        {
            _classes[name] = symbol;
        }

        public Symbol? LookupClass(string name)
        {
            return _classes.TryGetValue(name, out var symbol) ? symbol : null;
        }
    }

    /// <summary>
    /// スコープの基底クラス
    /// 変数やメソッドのシンボル情報を保持する
    /// </summary>
    public abstract class Scope
    {
        protected Dictionary<string, Symbol> _symbols = new();
        protected Scope? _parentScope;

        public Scope ParentScope => _parentScope!;

        public Scope(Scope? parentScope = null)
        {
            _parentScope = parentScope;
        }

        /// <summary>
        /// シンボルを登録する
        /// </summary>
        public void DefineSymbol(string name, Symbol symbol)
        {
            _symbols[name] = symbol;
        }

        /// <summary>
        /// ローカル変数を登録する
        /// </summary>
        public void DefineLocalVariable(string name, Symbol symbol)
        {
            DefineSymbol(name, symbol);
        }

        /// <summary>
        /// シンボルを検索する
        /// </summary>
        public Symbol? LookupSymbol(string name)
        {
            if (_symbols.TryGetValue(name, out var symbol))
            {
                return symbol;
            }

            if (_parentScope != null)
            {
                return _parentScope.LookupSymbol(name);
            }

            return null;
        }

        /// <summary>
        /// ローカル変数を検索する
        /// </summary>
        public Symbol? LookupLocalVariable(string name)
        {
            if (_symbols.TryGetValue(name, out var symbol))
            {
                return symbol;
            }

            if (_parentScope != null)
            {
                return _parentScope.LookupLocalVariable(name);
            }

            return null;
        }
    }

    /// <summary>
    /// グローバルスコープ
    /// プログラム全体の名前空間・クラス・グローバル関数を管理
    /// </summary>
    public class GlobalScope : Scope
    {
        public GlobalScope() : base(null)
        {
        }
    }

    /// <summary>
    /// ブロックスコープ
    /// if / while / for などのブロック内のローカル変数を管理
    /// </summary>
    public class BlockScope : Scope
    {
        public BlockScope(Scope parentScope) : base(parentScope)
        {
        }
    }

    /// <summary>
    /// メソッドスコープ
    /// メソッド内のパラメータとローカル変数を管理
    /// </summary>
    public class MethodScope : Scope
    {
        public MethodScope(Scope parentScope) : base(parentScope)
        {
        }
    }

    /// <summary>
    /// クラススコープ
    /// クラスのフィールドとメソッドを管理
    /// </summary>
    public class ClassScope : Scope
    {
        public ClassScope(Scope parentScope) : base(parentScope)
        {
        }
    }

    /// <summary>
    /// シンボル
    /// 変数・メソッド・クラスなどの情報を保持
    /// </summary>
    public class Symbol
    {
        public string Name { get; set; } = "";
        public string Type { get; set; } = "";
        public SymbolKind Kind { get; set; }

        public Symbol(string name, string type, SymbolKind kind)
        {
            Name = name;
            Type = type;
            Kind = kind;
        }
    }

    /// <summary>
    /// シンボルの種類
    /// </summary>
    public enum SymbolKind
    {
        Variable,
        Parameter,
        Method,
        Class,
        Namespace,
        Field,
        Constant
    }
}

本ステップの評価

かなりよいと評価しています。サンプルソースに対してトランスコンパイルが実行できる状況づくりを急ぎます。前回の記事で作成したSemanticResolver.csのひな形に対してすべてがうまっていないことは認識しています。これらは今回の最小構成実装の対象外で後のフェーズで対応していきます。

おわりに

以上です。次回の方向性はPhase 5: IRの実装の実行からとなります。

  1. Microsoft Copilotで日本語トランスコンパイラ言語 Re:Mindをいじりたおす - Qiita
    「Re:Mindをいじりたおす」の記事リストです。(ステップ降順)
    ステップ26 ターゲット言語JavaとVB.NETへの展開
    ステップ25 ターゲット言語C#へのASTからの生成処理
    ステップ24 ASTを生成するサンプル実装をC#で生成
    ステップ23 マッピングルール生成
    ステップ22 AST(抽象構文木)風の構造化表示
    ステップ21 バブルソート 実行可能形式 ターゲットJava C# VB.NET
    ステップ20 二分探索法 実行可能形式 ターゲットJava C# VB.NET
    ステップ19 線形探索法 実行可能形式 ターゲットJava C# VB.NET
    ステップ18 バブルソート 1案件づつやりなおし
    ステップ17 二分探索法 1案件づつやりなおし
    ステップ16 線形探索法 1案件づつやりなおし
    ステップ15 3つの定番アルゴリズムソースの忘却原因調査
    ステップ14 バブルソートソースの忘却発生再確認
    ステップ13 二分探索法ソースの忘却発生
    ステップ12 線形探索法の6言語変換
    ステップ11 バージョン情報付きの3定番アルゴリズムソースのフィードバック修正
    ステップ10 バージョン情報付きの3定番アルゴリズムソースのフィードバック修正
    ステップ9 バージョン情報付きで3定番アルゴリズムソースの生成
    ステップ8 バージョニングが可能か確認
    ステップ7 Re:Mindの詳しい言語仕様の学習結果をバブルソートで確認
    ステップ6 Re:Mindの詳しい言語仕様の学習結果を二分探索法で確認
    ステップ5 Re:Mindの詳しい言語仕様の学習結果を線形探索法で確認
    ステップ4 Re:Mindの詳しい言語仕様の学習成果を保持するスコープを確認
    ステップ3 Re:Mindの詳しい言語仕様を学習できる方法を確認
    ステップ2 Re:Mindの言語仕様の認知度を確認
    ステップ1 Re:Mindの認知度を確認

  2. Copilotが生成した日本語トランスコンパイラ言語 Re:MindのASTパーサが動作するまで - Qiita
    「Re:MindのASTパーサが動作するまで」の記事リストです。(ステップ降順)
    ステップ12 いったん生成
    ステップ11
    ステップ10 生成済コードの伝達完了
    ステップ9
    ステップ8
    ステップ7
    ステップ6 生成済コードの伝達開始
    ステップ5
    ステップ4 Program.csが動作
    ステップ3
    ステップ2
    ステップ1 検証環境構築

  3. Copilotが生成した日本語トランスコンパイラ言語 Re:MindのC#コード生成が動作するまで - Qiita
    「Re:MindのC#コード生成が動作するまで」の記事リストです。(ステップ降順)
    ステップ4
    ステップ3
    ステップ2
    ステップ1

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?