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

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

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

前言

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

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

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

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

ANTLR4 簡(jiǎn)介

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

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

ANTLR4 的工作流程如下:

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

語(yǔ)法分析基本概念

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

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

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

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

  +
 / \
1   *
   / \
  2   3

如何使用 ANTLR4

1. 安裝 Antlr4.Runtime.Standard 包

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

新建一個(gè)C#項(xiàng)目,在項(xiàng)目中添加 Antlr4.Runtime.Standard 包。

dotnet add package Antlr4.Runtime.Standard

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

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

grammar Arithmetic; // grammar name 需要和文件名一致
// 語(yǔ)法規(guī)則
// op=('*'|'/') 表示 op 將 ‘*' 或者 ‘/' 標(biāo)記為一個(gè)操作符號(hào)
// # MulDiv 將這個(gè)規(guī)則命名為 MulDiv,訪問 AST 時(shí)會(huì)用到
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) 和 語(yǔ)法規(guī)則(Parser Rules)。

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

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

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

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

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

在語(yǔ)法規(guī)則中,我們還可以使用 # 來(lái)為規(guī)則命名,例如 # MulDiv,表示這個(gè)規(guī)則的名字是 MulDiv。這個(gè)名字在訪問 AST 時(shí)會(huì)用到。

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

這邊因?yàn)榕e的例子比較簡(jiǎn)單,可以直接在一個(gè) g4 文件中同時(shí)定義語(yǔ)法規(guī)則和詞法規(guī)則。對(duì)于復(fù)雜的語(yǔ)法規(guī)則,可以將語(yǔ)法規(guī)則和詞法規(guī)則分開定義。

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

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

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

3. 生成語(yǔ)法分析器

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

我們有兩種方式來(lái)使用 ANTLR4 生成語(yǔ)法分析器,優(yōu)先推薦使用 Antlr4BuildTasks 項(xiàng)目來(lái)自動(dòng)生成語(yǔ)法分析器。

直接使用 ANTLR4 官方提供的工具來(lái)生成語(yǔ)法分析器。

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

寫本文時(shí),最新的版本是 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 工具來(lái)生成語(yǔ)法分析器。

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

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

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

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

借助 Antlr4BuildTasks 項(xiàng)目自動(dòng)生成語(yǔ)法分析器。

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

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

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

除了安裝 Antlr4BuildTasks 的包之外,我們還需要在項(xiàng)目文件(.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"/> 表示將項(xiàng)目中所有的 .g4 文件都添加到 Antlr4 任務(wù)中。當(dāng)然也可以指定具體的 .g4 文件路徑。

在 build 項(xiàng)目時(shí),Antlr4BuildTasks 會(huì)將 .g4 文件編譯成的文件放在 obj 文件夾下,我們可以在 obj 文件夾下找到生成的語(yǔ)法分析器。

obj 文件夾下的文件是臨時(shí)文件,會(huì)在每次 build 時(shí)重新生成,我們不需要將 obj 文件夾下的文件添加到項(xiàng)目中。

4. 編寫代碼來(lái)使用語(yǔ)法分析器

接下來(lái)我們就可以編寫代碼來(lái)使用生成的語(yǔ)法分析器了。

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

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

使用 Visitor 實(shí)現(xiàn)

下面我們以訪問者模式為例,編寫一個(gè)簡(jiǎn)單的 C# 程序來(lái)使用語(yǔ)法分析器。

ANTLR4 會(huì)為我們生成一個(gè) ArithmeticBaseVisitor 類,我們可以繼承這個(gè)類來(lái)完成對(duì) AST 的訪問。

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

我們可以通過重寫這些方法來(lái)實(shí)現(xiàn)對(duì) AST 的訪問:

public class ArithmeticVisitor : ArithmeticBaseVisitor<int>
{
    // 解析乘除法
    public override int VisitMulDiv(ArithmeticParser.MulDivContext context)
    {
        // context 包含了當(dāng)前節(jié)點(diǎn)的信息
        // context.expr(0) 和 context.expr(1) 分別表示乘除法的兩個(gè)操作數(shù)
        // 訪問子節(jié)點(diǎn),獲取操作數(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.")
        };
    }
    // 去掉括號(hào),訪問括號(hào)內(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 之后,我們就可以使用它來(lái)解析表達(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)建語(yǔ)法分析器,傳入詞法分析器的輸出的token流
    var parser = new ArithmeticParser(tokens);
    // 用 visitor 模式解析表達(dá)式
    var visitor = new ArithmeticVisitor();
    return visitor.Visit(parser.expr());
}

使用 Listener 實(shí)現(xiàn)

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

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

public class ArithmeticListener : ArithmeticBaseListener
{
    // 使用棧來(lái)存儲(chǔ)操作數(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 方法在這里不需要做任何操作,因?yàn)槲覀円呀?jīng)在 MulDiv 和 AddSub 中處理了括號(hào)內(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)建語(yǔ)法分析器,傳入詞法分析器的輸出的token流
    var parser = new ArithmeticParser(tokens);
    var listener = new ArithmeticListener();
    // 解析表達(dá)式
    parser.AddParseListener(listener);
    parser.expr();
    // 獲取結(jié)果
    return listener.Result;
}

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

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

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

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

這里我們將詞法規(guī)則和語(yǔ)法規(guī)則分開定義,詞法規(guī)則定義在 SqlLexer.g4 文件中,語(yǔ)法規(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)識(shí)符
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 的插件來(lái)檢查語(yǔ)法規(guī)則的正確性,選中 query 規(guī)則,右鍵選擇 Test Rule query 來(lái)測(cè)試語(yǔ)法規(guī)則是否正確。

下面我們先定義一組類型用來(lái)表示 SQL 語(yǔ)句的 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; } 
}

我們定義一個(gè) SqlAstBuilder 類來(lái)實(shí)現(xiàn)對(duì) SQL 語(yǔ)句的解析:

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 類用來(lái)執(zhí)行 SQL 語(yǔ)句并從 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)建語(yǔ)法分析器,傳入詞法分析器的輸出的token流
        var tokens = new CommonTokenStream(lexer);
        var parser = new SqlParser(tokens);
        // 將查詢語(yǔ)句解析為自定義的 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);
        }
    }
}

接下來(lái)我們就可以開始測(cè)試了:

測(cè)試用的 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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論