欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在 .NET 中 使用 ANTLR4構(gòu)建語法分析器的方法

 更新時間:2025年04月02日 09:27:44   作者:黑洞視界  
本文將介紹如何在 .NET 中使用 ANTLR4 構(gòu)建語法分析器,本文不會深入講解 ANTLR4 的語法規(guī)則,相關(guān)內(nèi)容可參考 ANTLR4 的官方文檔或其他資料,本文將涵蓋以下內(nèi)容:ANTLR4 的開發(fā)環(huán)境搭建、語法規(guī)則編寫、語法分析器生成以及語法分析器的使用,感興趣的朋友一起看看吧

前言

本文將介紹如何在 .NET 中使用 ANTLR4 構(gòu)建語法分析器。由于篇幅限制,本文不會深入講解 ANTLR4 的語法規(guī)則,相關(guān)內(nèi)容可參考 ANTLR4 的官方文檔或其他資料。本文將涵蓋以下內(nèi)容:ANTLR4 的開發(fā)環(huán)境搭建、語法規(guī)則編寫、語法分析器生成以及語法分析器的使用。

本文中的例子相對簡單,且未經(jīng)過詳細(xì)測試,旨在演示 ANTLR4 的基本用法。

實際開發(fā)的過程中,建議先去官方的這個 repo 查看是否已經(jīng)有現(xiàn)成的 grammar 文件可以使用:https://github.com/antlr/grammars-v4

文中的代碼示例已上傳到 GitHub:
https://github.com/eventhorizon-cli/Antlr4Demo

ANTLR4 簡介

ANTLR(Another Tool for Language Recognition)是一個強(qiáng)大的語法分析器生成器,屬于編譯技術(shù)中的前端工具。它可以用來構(gòu)建語法分析器,并借此開發(fā)編譯器、解釋器和翻譯器等。

ANTLR4 是 ANTLR 的最新版本,它支持多種編程語言的語法分析器生成,包括 Java、C#、Python、JavaScript 等。ANTLR4 的語法規(guī)則使用一種類似于正則表達(dá)式的語法來定義,可以很方便地描述復(fù)雜的語法結(jié)構(gòu)。

ANTLR4 的工作流程如下:

  • 編寫語法規(guī)則:通常使用 ANTLR4 的語法規(guī)則文件(.g4 文件)來定義語法規(guī)則。
  • 生成語法分析器:使用 ANTLR4 工具來生成目標(biāo)語言的語法分析器。
  • 使用語法分析器進(jìn)行語法分析:編寫代碼來使用生成的語法分析器進(jìn)行語法分析。分析的結(jié)果通常是一個抽象語法樹(AST)。
  • 訪問 AST:可以使用訪問者模式(Visitor Pattern)或者監(jiān)聽器模式(Listener Pattern)來訪問 AST,進(jìn)行后續(xù)的處理,例如解釋執(zhí)行、編譯等。

語法分析基本概念

語法分析的過程分為兩個階段:詞法分析(Lexical Analysis)和語法分析(Syntax Analysis)。

  • 詞法分析:將字符聚集為單詞或者符號(token),例如將 1 + 2 分解為 1、+2 三個 token。

  • 語法分析:輸入的 token 被組織成一個樹形結(jié)構(gòu),稱為抽象語法樹(Abstract Syntax Tree,AST),它表示了輸入的語法結(jié)構(gòu)。樹的每個節(jié)點表示一個語法單元,這個單元的構(gòu)成規(guī)則就叫做語法規(guī)則。每個節(jié)點還可以有子節(jié)點。

例如,表達(dá)式 1 + 2 * 3 的抽象語法樹如下:

  +
 / \
1   *
   / \
  2   3

如何使用 ANTLR4

1. 安裝 Antlr4.Runtime.Standard 包

我們以加減乘除四則運(yùn)算為例來介紹如何使用 ANTLR4 來構(gòu)建語法分析器。

新建一個C#項目,在項目中添加 Antlr4.Runtime.Standard 包。

dotnet add package Antlr4.Runtime.Standard

2. 編寫 ANTLR4 的語法規(guī)則文件

接著我們需要編寫一個 ANTLR4 的語法規(guī)則文件,文件的后綴名為 .g4,例如 Arithmetic.g4,文件的內(nèi)容如下:

grammar Arithmetic; // grammar name 需要和文件名一致
// 語法規(guī)則
// op=('*'|'/') 表示 op 將 ‘*' 或者 ‘/' 標(biāo)記為一個操作符號
// # MulDiv 將這個規(guī)則命名為 MulDiv,訪問 AST 時會用到
expr:   expr op=('*'|'/') expr   # MulDiv
    |   expr op=('+'|'-') expr   # AddSub
    |   INT                      # Int
    |   '(' expr ')'             # Parens
    ;
// 詞法規(guī)則
INT :   [0-9]+ ;
WS  :   [ \t\r\n]+ -> skip ; // 表示忽略空格

g4 文件的內(nèi)容分為兩部分:詞法規(guī)則(Lexer Rules) 和 語法規(guī)則(Parser Rules)

詞法規(guī)則是用來定義詞法單元的,例如數(shù)字、運(yùn)算符、括號等。詞法規(guī)則通常以大寫字母開頭。

語法規(guī)則是用來定義語法結(jié)構(gòu)的,例如表達(dá)式、語句等。語法規(guī)則通常以小寫字母開頭。

在上面的例子中,我們定義了一個簡單的四則運(yùn)算語法規(guī)則,支持加減乘除和括號運(yùn)算。我們還定義了一個整數(shù)類型的詞法規(guī)則 INT,表示一個或多個數(shù)字。

expr 規(guī)則表示一個表達(dá)式,用 | 分隔的部分表示或的關(guān)系,例如 expr op=('*'|'/') expr | expr op=('+'|'-') expr 表示一個表達(dá)式可以是乘法或除法,也可以是加法或減法。

而加減乘除的優(yōu)先級通過定義的順序來決定,乘除法的規(guī)則在加減法之前,所以乘除法的優(yōu)先級高于加減法。

在語法規(guī)則中,我們還可以使用 # 來為規(guī)則命名,例如 # MulDiv,表示這個規(guī)則的名字是 MulDiv。這個名字在訪問 AST 時會用到。

規(guī)則支持遞歸定義,例如 expr: expr op=('*'|'/') expr 。

這邊因為舉的例子比較簡單,可以直接在一個 g4 文件中同時定義語法規(guī)則和詞法規(guī)則。對于復(fù)雜的語法規(guī)則,可以將語法規(guī)則和詞法規(guī)則分開定義。

在 Rider 或 VS Code 中安裝 ANTLR4 的插件,可以檢查語法規(guī)則的正確性。

在 Rider 中安裝 ANTLR4 的插件后,可以在 g4 文件選中 expr 規(guī)則,右鍵選擇 Test Rule expr 來測試語法規(guī)則是否正確。

左側(cè)的輸入框中輸入要測試的表達(dá)式,右側(cè)的輸出框中會以樹形結(jié)構(gòu)的方式顯示語法分析的結(jié)果。

3. 生成語法分析器

ANTLR4 是基于 Java 開發(fā)的,所以我們需要安裝 Java 運(yùn)行環(huán)境才能使用 ANTLR4 工具來生成語法分析器。

我們有兩種方式來使用 ANTLR4 生成語法分析器,優(yōu)先推薦使用 Antlr4BuildTasks 項目來自動生成語法分析器。

直接使用 ANTLR4 官方提供的工具來生成語法分析器。

首先,我們需要下載 ANTLR4 工具,可以從 ANTLR4 的官方網(wǎng)站下載:https://www.antlr.org/download.html

寫本文時,最新的版本是 4.13.2,下載地址為:
https://www.antlr.org/download/antlr-4.13.2-complete.jar

本文為方便演示,將 antlr-4.13.2-complete.jar 下載到 g4 文件所在的目錄下。

接著就可以使用 Java 運(yùn)行 ANTLR4 工具來生成語法分析器。

java -jar antlr-4.13.2-complete.jar -Dlanguage=CSharp Arithmetic.g4

其中,Arithmetic.g4 是我們編寫的語法規(guī)則文件,-Dlanguage=CSharp 表示生成 C# 語言的語法分析器。

執(zhí)行上面的命令后,會生成一些文件,其中包括 ArithmeticLexer.csArithmeticParser.cs。

后面我們就可以使用生成的語法分析器來進(jìn)行語法分析了。

借助 Antlr4BuildTasks 項目自動生成語法分析器。

上面的方式需要手動下載 ANTLR4 工具,然后使用 Java 運(yùn)行 ANTLR4 工具來生成語法分析器,還會生成一些必須需要添加到項目中的文件。這樣的方式比較繁瑣,我們可以使用 Antlr4BuildTasks 項目來自動生成語法分析器。

Antlr4BuildTasks 的 GitHub 地址為:
https://github.com/kaby76/Antlr4BuildTasks

Antlr4BuildTasks 是一個 MSBuild 任務(wù),它可以自動下載 ANTLR4 工具,然后使用 ANTLR4 工具來生成語法分析器,最后將生成的語法分析器添加到項目中。它也會嘗試下載 java 運(yùn)行環(huán)境,如果 build 過程中出現(xiàn)錯誤,可以嘗試手動安裝全局的 java 運(yùn)行環(huán)境。

除了安裝 Antlr4BuildTasks 的包之外,我們還需要在項目文件(.csproj)中添加一些配置,完整 .csproj 文件如下:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Antlr4.Runtime.Standard" Version="4.13.1"/>
        <PackageReference Include="Antlr4BuildTasks" Version="12.8.0" PrivateAssets="all"/>
    </ItemGroup>
    <ItemGroup>
        <Antlr4 Include="**\*.g4"/>
    </ItemGroup>
</Project>

<Antlr4 Include="**\*.g4"/> 表示將項目中所有的 .g4 文件都添加到 Antlr4 任務(wù)中。當(dāng)然也可以指定具體的 .g4 文件路徑。

在 build 項目時,Antlr4BuildTasks 會將 .g4 文件編譯成的文件放在 obj 文件夾下,我們可以在 obj 文件夾下找到生成的語法分析器。

obj 文件夾下的文件是臨時文件,會在每次 build 時重新生成,我們不需要將 obj 文件夾下的文件添加到項目中。

4. 編寫代碼來使用語法分析器

接下來我們就可以編寫代碼來使用生成的語法分析器了。

訪問 AST 的方式有兩種:Visitor和 Listener。我們可以選擇其中一種方式來訪問 AST。

ANTLR4 會為我們生成一個 Parser,Parser 在遍歷 AST 時會調(diào)用 Visitor 的 VisitXXX 方法,或者 Listener 的 EnterXXX 和 ExitXXX 方法。

使用 Visitor 實現(xiàn)

下面我們以訪問者模式為例,編寫一個簡單的 C# 程序來使用語法分析器。

ANTLR4 會為我們生成一個 ArithmeticBaseVisitor 類,我們可以繼承這個類來完成對 AST 的訪問。

在前面的 g4 文件中,我們?yōu)槊總€ AST 節(jié)點定義了一個名字, MulDiv、AddSub、IntParens 這些,對應(yīng) ArithmeticBaseVisitor 中的 VisitMulDivVisitAddSub、VisitInt、VisitParens 方法。

我們可以通過重寫這些方法來實現(xiàn)對 AST 的訪問:

public class ArithmeticVisitor : ArithmeticBaseVisitor<int>
{
    // 解析乘除法
    public override int VisitMulDiv(ArithmeticParser.MulDivContext context)
    {
        // context 包含了當(dāng)前節(jié)點的信息
        // context.expr(0) 和 context.expr(1) 分別表示乘除法的兩個操作數(shù)
        // 訪問子節(jié)點,獲取操作數(shù)的值
        int left = Visit(context.expr(0));
        int right = Visit(context.expr(1));
        return context.op.Text switch
        {
            "*" => left * right,
            "/" => left / right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
    }
    // 解析加減法
    public override int VisitAddSub(ArithmeticParser.AddSubContext context)
    {
        int left = Visit(context.expr(0));
        int right = Visit(context.expr(1));
        return context.op.Text switch
        {
            "+" => left + right,
            "-" => left - right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
    }
    // 去掉括號,訪問括號內(nèi)的表達(dá)式
    public override int VisitParens(ArithmeticParser.ParensContext context) => Visit(context.expr());
    // 解析整數(shù)
    public override int VisitInt(ArithmeticParser.IntContext context) => int.Parse(context.INT().GetText());
}

定義好了 visitor 之后,我們就可以使用它來解析表達(dá)式了。

Console.WriteLine(Evaluate("1 + 2 * 3")); // 7
Console.WriteLine(Evaluate("(1 + 2) * 3")); // 9
int Evaluate(string expression)
{
    // 創(chuàng)建詞法分析器
    var lexer = new ArithmeticLexer(new AntlrInputStream(expression));
    var tokens = new CommonTokenStream(lexer);
    // 創(chuàng)建語法分析器,傳入詞法分析器的輸出的token流
    var parser = new ArithmeticParser(tokens);
    // 用 visitor 模式解析表達(dá)式
    var visitor = new ArithmeticVisitor();
    return visitor.Visit(parser.expr());
}

使用 Listener 實現(xiàn)

ANTLR4 的 Parser 在遍歷 AST 時會調(diào)用 Listener 的 EnterXXX 和 ExitXXX 方法,我們可以通過重寫這些方法來實現(xiàn)對 AST 的訪問。

EnterXXX 方法在進(jìn)入節(jié)點時調(diào)用,ExitXXX 方法在離開節(jié)點時調(diào)用。
我們可以在 ExitXXX 方法里將操作數(shù)壓入棧中,下次訪問時就可以從棧中彈出操作數(shù)進(jìn)行計算。

public class ArithmeticListener : ArithmeticBaseListener
{
    // 使用棧來存儲操作數(shù)
    private readonly Stack<int> _stack = new();
    public int Result => _stack.Pop();
    public override void ExitMulDiv(ArithmeticParser.MulDivContext context)
    {
        int right = _stack.Pop();
        int left = _stack.Pop();
        int result = context.op.Text switch
        {
            "*" => left * right,
            "/" => left / right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
        _stack.Push(result);
    }
    public override void ExitAddSub(ArithmeticParser.AddSubContext context)
    {
        int right = _stack.Pop();
        int left = _stack.Pop();
        int result = context.op.Text switch
        {
            "+" => left + right,
            "-" => left - right,
            _ => throw new NotSupportedException($"Operator {context.op.Text} is not supported.")
        };
        _stack.Push(result);
    }
    public override void ExitParens(ArithmeticParser.ParensContext context)
    {
        // ExitParens 方法在這里不需要做任何操作,因為我們已經(jīng)在 MulDiv 和 AddSub 中處理了括號內(nèi)的表達(dá)式
    }
    public override void ExitInt(ArithmeticParser.IntContext context)
    {
        int value = int.Parse(context.INT().GetText());
        _stack.Push(value);
    }
}
Console.WriteLine(Evaluate("1 + 2 * 3")); // 7
Console.WriteLine(Evaluate("(1 + 2) * 3")); // 9
int Evaluate(string expression)
{
    // 創(chuàng)建詞法分析器
    var lexer = new ArithmeticLexer(new AntlrInputStream(expression));
    var tokens = new CommonTokenStream(lexer);
    // 創(chuàng)建語法分析器,傳入詞法分析器的輸出的token流
    var parser = new ArithmeticParser(tokens);
    var listener = new ArithmeticListener();
    // 解析表達(dá)式
    parser.AddParseListener(listener);
    parser.expr();
    // 獲取結(jié)果
    return listener.Result;
}

構(gòu)建自定義 AST 以解決復(fù)雜問題

上面的例子中,我們在遍歷 AST 時直接計算了表達(dá)式的值,這種方式在簡單的表達(dá)式中是可以的,但如果表達(dá)式的處理邏輯比較復(fù)雜,更建議將 原始AST 轉(zhuǎn)換成一個我們自定義的 AST,然后在后續(xù)的處理邏輯中使用這個自定義的 AST,將解析和處理邏輯分開,可以讓代碼更清晰,功能也容易實現(xiàn)。

下面我們定義一個比加減乘除法更復(fù)雜的需求:指定一個文件夾,用 sql 語句來查詢文件夾下的 csv 文件,支持過濾條件、排序等操作。表名是文件名,字段名是 csv 文件的列名。

為簡化起見,我們只支持簡單的查詢語句,支持 SELECT、FROMWHERE、ORDER BY 等關(guān)鍵字。數(shù)據(jù)類型僅用字符串類型做示范,支持的過濾方式有 =、!=、LIKE,過濾條件之間只能用 AND 連接,排序方式支持 ASC 和 DESC。

這里我們將詞法規(guī)則和語法規(guī)則分開定義,詞法規(guī)則定義在 SqlLexer.g4 文件中,語法規(guī)則定義在 SqlParser.g4 文件中。

SqlLexer.g4 文件的內(nèi)容如下:

lexer grammar SqlLexer;
options {
  caseInsensitive = true; // 忽略大小寫
}
// 關(guān)鍵字
SELECT : 'SELECT' ;
FROM   : 'FROM' ;
WHERE  : 'WHERE' ;
ORDER  : 'ORDER' ;
BY     : 'BY'  ;
ASC    : 'ASC' ;
DESC   : 'DESC' ;
AND    : 'AND' ;
OR     : 'OR' ;
COMMA  : ',' ;
STAR   : '*'  ;
// 運(yùn)算符
EQ     : '=' ;
NEQ    : '!=' ;
LIKE   : 'LIKE' ;
// 字面量
STRING_LITERAL
    : '\'' ( ~('\'' | '\\') | '\\' . )* '\'' // 字符串字面量
    ;
// 標(biāo)識符
IDENTIFIER
    : [a-z_][a-z0-9_]* // 用于表名、列名等
    ;
WS     : [ \t\r\n]+ -> skip ; // 忽略空格

SqlParser.g4 文件的內(nèi)容如下:

parser grammar SqlParser;
options { tokenVocab=SqlLexer; }
query
    : SELECT selectList FROM tableName (WHERE whereClause)? (ORDER BY orderByClause)?
    ;
selectList
    : columnName (COMMA columnName)* 
    | STAR
    ;
columnName: IDENTIFIER ;
tableName: IDENTIFIER ;
whereClause
    : whereCondition (AND whereCondition)*
    ;
whereCondition
    : columnName op=(EQ | NEQ) STRING_LITERAL 
    | columnName op=LIKE STRING_LITERAL
    ;
orderByClause
    : orderByCondition (COMMA orderByCondition)*
    ;
orderByCondition
    : columnName (ASC | DESC)?
    ;

定義完后可以在 Rider 中使用 ANTLR4 的插件來檢查語法規(guī)則的正確性,選中 query 規(guī)則,右鍵選擇 Test Rule query 來測試語法規(guī)則是否正確。

下面我們先定義一組類型用來表示 SQL 語句的 AST:

public abstract class Expression;
public class QueryExpression : Expression
{
    public required string TableName { get; init; }
    public required bool SelectAll { get; init; }
    public required IEnumerable<string> SelectList { get; init; }
    public required IEnumerable<WhereCondition> WhereConditions { get; init; }
    public required IEnumerable<OrderByCondition> OrderByConditions { get; init; }
}
public class WhereCondition : Expression
{
    public required string ColumnName { get; init; }
    public WhereConditionOperator Operator { get; init; }
    public required string Value { get; init; }
}
public enum WhereConditionOperator
{
    Equal,
    NotEqual,
    StartsWith,
    EndsWith,
    Contains
}
public class OrderByCondition : Expression
{
    public required string ColumnName { get;  init; }
    public  bool IsDescending { get; init; } 
}

我們定義一個 SqlAstBuilder 類來實現(xiàn)對 SQL 語句的解析:

public class SqlAstBuilder : SqlParserBaseVisitor<QueryExpression>
{
    public override QueryExpression VisitQuery(SqlParser.QueryContext context)
    {
        var selectList = context.selectList();
        bool selectAll = selectList?.STAR() != null;
        var columns = selectList?.columnName()
            .Select(c => c.GetText())
            .ToList() ?? [];
        var tableName = context.tableName().GetText();
        var whereConditions = context.whereClause()
            ?.whereCondition()
            .Select(c =>
            {
                var stringValue = c.STRING_LITERAL().GetText().Trim('\'');
                var opText = c.op.Text.ToUpperInvariant();
                var op = WhereConditionOperator.Equal;
                if (opText == "=")
                {
                    op = WhereConditionOperator.Equal;
                }
                else if (opText == "!=")
                {
                    op = WhereConditionOperator.NotEqual;
                }
                else if (opText == "LIKE")
                {
                    if (stringValue.StartsWith("%") && stringValue.EndsWith("%"))
                    {
                        op = WhereConditionOperator.Contains;
                        stringValue = stringValue.Substring(1, stringValue.Length - 2);
                    }
                    else if (stringValue.StartsWith("%"))
                    {
                        op = WhereConditionOperator.EndsWith;
                        stringValue = stringValue.Substring(1);
                    }
                    else if (stringValue.EndsWith("%"))
                    {
                        op = WhereConditionOperator.StartsWith;
                        stringValue = stringValue.Substring(0, stringValue.Length - 1);
                    }
                }
                else
                {
                    throw new NotSupportedException($"Operator {c.op.Text} is not supported.");
                }
                return new WhereCondition
                {
                    ColumnName = c.columnName().GetText(),
                    Operator = op,
                    Value = stringValue
                };
            })
            .ToList() ?? [];
        var orderByConditions = context.orderByClause()
            ?.orderByCondition()
            .Select(c => new OrderByCondition
            {
                ColumnName = c.columnName().GetText(),
                IsDescending = c.DESC() != null
            })
            .ToList() ?? [];
        return new QueryExpression
        {
            SelectAll = selectAll,
            SelectList = columns,
            TableName = tableName,
            WhereConditions = whereConditions,
            OrderByConditions = orderByConditions
        };
    }
}

SqlToCsvEngine 類用來執(zhí)行 SQL 語句并從 CSV 文件中讀取數(shù)據(jù):

public class SqlToCsvEngine(DirectoryInfo csvDirectory)
{
    public IEnumerable<Dictionary<string, string>> ExecuteQuery(string query)
    {
        // 創(chuàng)建詞法分析器
        var lexer = new SqlLexer(new AntlrInputStream(query));
        // 創(chuàng)建語法分析器,傳入詞法分析器的輸出的token流
        var tokens = new CommonTokenStream(lexer);
        var parser = new SqlParser(tokens);
        // 將查詢語句解析為自定義的 AST
        var astBuilder = new SqlAstBuilder();
        var expression = astBuilder.Visit(parser.query());
        // 處理 AST,執(zhí)行查詢
        if (expression is not { } queryExpression)
        {
            throw new InvalidOperationException("Expected a query expression");
        }
        // 讀取 CSV 文件
        var csvData = ReadCsv(queryExpression.TableName);
        // 過濾數(shù)據(jù)
        var filteredData = csvData.Where(row =>
        {
            // 處理 WHERE 條件
            // 處理 WHERE 條件
            bool isMatch = true;
            foreach (var condition in queryExpression.WhereConditions)
            {
                if (row.TryGetValue(condition.ColumnName, out var value))
                {
                    isMatch = condition.Operator switch
                    {
                        WhereConditionOperator.Equal => value == condition.Value,
                        WhereConditionOperator.NotEqual => value != condition.Value,
                        WhereConditionOperator.StartsWith => value.StartsWith(condition.Value),
                        WhereConditionOperator.EndsWith => value.EndsWith(condition.Value),
                        WhereConditionOperator.Contains => value.Contains(condition.Value),
                        _ => throw new ArgumentOutOfRangeException()
                    };
                }
                else
                {
                    throw new InvalidOperationException($"Column {condition.ColumnName} does not exist in CSV file.");
                }
            }
            return isMatch;
        });
        // 處理 ORDER BY 條件
        foreach (var orderByCondition in queryExpression.OrderByConditions)
        {
            Func<IEnumerable<Dictionary<string, string>>, Func<Dictionary<string, string>, string>,
                IEnumerable<Dictionary<string, string>>> orderByFunc;
            if (filteredData is IOrderedEnumerable<Dictionary<string, string>> orderedData)
            {
                orderByFunc = orderByCondition.IsDescending
                    ? (_, keySelector) => orderedData.ThenByDescending(keySelector)
                    : (_, keySelector) => orderedData.ThenBy(keySelector);
            }
            else
            {
                orderByFunc = orderByCondition.IsDescending
                    ? Enumerable.OrderByDescending
                    : Enumerable.OrderBy;
            }
            filteredData = orderByFunc(filteredData, row =>
            {
                if (row.TryGetValue(orderByCondition.ColumnName, out var value))
                {
                    return value;
                }
                throw new InvalidOperationException(
                    $"Order by column {orderByCondition.ColumnName} does not exist in CSV file.");
            });
        }
        // 處理 SELECT 條件
        if (queryExpression.SelectAll)
        {
            return filteredData;
        }
        var selectedData = filteredData.Select(row =>
        {
            var selectedRow = new Dictionary<string, string>();
            foreach (var columnName in queryExpression.SelectList)
            {
                if (row.TryGetValue(columnName, out var value))
                {
                    selectedRow[columnName] = value;
                }
                else
                {
                    throw new InvalidOperationException($"Column {columnName} does not exist in CSV file.");
                }
            }
            return selectedRow;
        });
        return selectedData;
    }
    private IEnumerable<Dictionary<string, string>> ReadCsv(string tableName)
    {
        var csvFile = new FileInfo(Path.Combine(csvDirectory.FullName, $"{tableName}.csv"));
        if (!csvFile.Exists)
        {
            throw new FileNotFoundException($"CSV file {csvFile.FullName} does not exist.");
        }
        using var reader = new StreamReader(csvFile.FullName);
        var headerLine = reader.ReadLine();
        if (headerLine == null)
        {
            throw new InvalidOperationException($"CSV file {csvFile.FullName} is empty.");
        }
        var headers = headerLine.Split(',');
        while (!reader.EndOfStream)
        {
            var line = reader.ReadLine();
            if (line == null) continue;
            var values = line.Split(',');
            yield return headers.Zip(values).ToDictionary(x => x.First, x => x.Second);
        }
    }
}

接下來我們就可以開始測試了:

測試用的 CSV 文件內(nèi)容如下:

Name,City,Occupation,Company
Alice,New York,Engineer,TechCorp
Bob,Los Angeles,Designer,Creative Inc
Ben,Atlanta,Writer,Publishing House
Charlie,Chicago,Manager,Finance Group
David,Houston,Teacher,School District
Eve,Miami,Student,University
Frank,Seattle,Chef,Restaurant Co
Grace,San Francisco,Doctor,HealthCare
Hannah,Boston,Lawyer,Legal Partners
Ian,Denver,Architect,BuildIt
var directory = new DirectoryInfo("/Users/hkh/Desktop/test");
var engine = new SqlToCsvEngine(directory);
var sql =
  """
  SELECT Name, City, Occupation, Company
  FROM Employee
  WHERE City != 'Miami'
    AND Occupation LIKE '%er'
  ORDER BY Name ASC, Company DESC
  """;
  var result = engine.ExecuteQuery(sql);
// 打印頭部
foreach (var column in result.First().Keys)
{
    Console.Write($"{column}\t");
}
foreach (var row in result)
{
    Console.WriteLine();
    foreach (var (_, value) in row)
    {
        Console.Write($"{value}\t");
    }
}

輸出結(jié)果如下:

Name    City    Occupation      Company 
Alice   New York        Engineer        TechCorp        
Ben     Atlanta Writer  Publishing House        
Bob     Los Angeles     Designer        Creative Inc    
Charlie Chicago Manager Finance Group   
David   Houston Teacher School District 
Hannah  Boston  Lawyer  Legal Partners  

參考資料

https://github.com/antlr/grammars-v4

https://wizardforcel.gitbooks.io/antlr4-short-course/content/basic-concept.html

https://github.com/antlr/antlr4/blob/master/doc/csharp-target.md

到此這篇關(guān)于如何在 .NET 中 使用 ANTLR4的文章就介紹到這了,更多相關(guān).NET 使用 ANTLR4內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論