解析if-else if语句算法

2023-12-21

我正在尝试为 if-else 类型结构创建一个非常简单的解析器,它将构建并执行 SQL 语句。

我不会测试执行语句的条件,而是测试构建字符串的条件。

声明的一个例子是:

select column1
from
#if(VariableA = Case1)
table1
#else if(VariableA = Case2)
table2
#else
defaultTable
#end

如果变量等于情况 1,则结果字符串应为:select column1 from table1

一个更复杂的例子是嵌套的 if 语句:

select column1
from
#if(VariableA = Case1)
#if(VariableB = Case3)
    table3
#else
    table4
#else if(VariableA = Case2)
table2
#else
defaultTable
#end

这就是我真正遇到麻烦的地方,我想不出一个好方法来正确识别每个 if-else-end 组。

另外,我不确定有什么好方法来跟踪“else”子句中的字符串是否应计算为 true。

我一直在网上寻找不同类型的解析算法,所有这些算法看起来都非常抽象和复杂。

对于这个非计算机科学专业,有什么好的起点建议吗?


我编写了一个简单的解析器,并根据您提供的示例对其进行了测试。如果您想了解更多有关解析的信息,我建议您阅读编译器构建 https://people.inf.ethz.ch/wirth/CompilerConstruction/index.html来自尼克劳斯·沃斯。

第一步始终是以适当的方式写下您的语言的语法。我选择了EBNF,非常容易理解。

|分开替代方案。

[ and ]附上选项。

{ and }表示重复(零次、一次或多次)。

( and )组表达式(此处未使用)。

此描述并不完整,但我提供的链接更详细地描述了它。

EBNF 语法



LineSequence = { TextLine | IfStatement }.
TextLine     = <string>.
IfStatement  = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine.
IfLine       = "#if" "(" Condition ")".
ElseLine     = "#else".
ElseIfLine   = "#else" "if" "(" Condition ")".
EndLine      = "#end".
Condition    = Identifier "=" Identifier.
Identifier   = <letter_or_underline> { <letter_or_underline> | <digit> }.
  

解析器严格遵循语法,即重复被转换为循环,替代被转换为 if-else 语句,等等。

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Example.SqlPreprocessor
{
    class Parser
    {
        enum Symbol
        {
            None,
            LPar,
            RPar,
            Equals,
            Text,
            NumberIf,
            If,
            NumberElse,
            NumberEnd,
            Identifier
        }

        List<string> _input; // Raw SQL with preprocessor directives.
        int _currentLineIndex = 0;

        // Simulates variables used in conditions
        Dictionary<string, string> _variableValues = new Dictionary<string, string> { 
            { "VariableA", "Case1" },
            { "VariableB", "CaseX" }
        };

        Symbol _sy; // Current symbol.
        string _string; // Identifier or text line;
        Queue<string> _textQueue = new Queue<string>(); // Buffered text parts of a single line.
        int _lineNo; // Current line number for error messages.
        string _line; // Current line for error messages.

        /// <summary>
        /// Get the next line from the input.
        /// </summary>
        /// <returns>Input line or null if no more lines are available.</returns>
        string GetLine()
        {
            if (_currentLineIndex >= _input.Count) {
                return null;
            }
            _line = _input[_currentLineIndex++];
            _lineNo = _currentLineIndex;
            return _line;
        }

        /// <summary>
        /// Get the next symbol from the input stream and stores it in _sy.
        /// </summary>
        void GetSy()
        {
            string s;
            if (_textQueue.Count > 0) { // Buffered text parts available, use one from these.
                s = _textQueue.Dequeue();
                switch (s.ToLower()) {
                    case "(":
                        _sy = Symbol.LPar;
                        break;
                    case ")":
                        _sy = Symbol.RPar;
                        break;
                    case "=":
                        _sy = Symbol.Equals;
                        break;
                    case "if":
                        _sy = Symbol.If;
                        break;
                    default:
                        _sy = Symbol.Identifier;
                        _string = s;
                        break;
                }
                return;
            }

            // Get next line from input.
            s = GetLine();
            if (s == null) {
                _sy = Symbol.None;
                return;
            }

            s = s.Trim(' ', '\t');
            if (s[0] == '#') { // We have a preprocessor directive.
                // Split the line in order to be able get its symbols.
                string[] parts = Regex.Split(s, @"\b|[^#_a-zA-Z0-9()=]");
                // parts[0] = #
                // parts[1] = if, else, end
                switch (parts[1].ToLower()) {
                    case "if":
                        _sy = Symbol.NumberIf;
                        break;
                    case "else":
                        _sy = Symbol.NumberElse;
                        break;
                    case "end":
                        _sy = Symbol.NumberEnd;
                        break;
                    default:
                        Error("Invalid symbol #{0}", parts[1]);
                        break;
                }

                // Store the remaining parts for later.
                for (int i = 2; i < parts.Length; i++) {
                    string part = parts[i].Trim(' ', '\t');
                    if (part != "") {
                        _textQueue.Enqueue(part);
                    }
                }
            } else { // We have an ordinary SQL text line.
                _sy = Symbol.Text;
                _string = s;
            }
        }

        void Error(string message, params  object[] args)
        {
            // Make sure parsing stops here
            _sy = Symbol.None;
            _textQueue.Clear();
            _input.Clear();

            message = String.Format(message, args) +
                      String.Format(" in line {0}\r\n\r\n{1}", _lineNo, _line);
            Output("------");
            Output(message);
            MessageBox.Show(message, "Error");
        }

        /// <summary>
        /// Writes the processed line to a (simulated) output stream.
        /// </summary>
        /// <param name="line">Line to be written to output</param>
        void Output(string line)
        {
            Console.WriteLine(line);
        }

        /// <summary>
        /// Starts the parsing process.
        /// </summary>
        public void Parse()
        {
            // Simulate an input stream.
            _input = new List<string> {
                "select column1",
                "from",
                "#if(VariableA = Case1)",
                "    #if(VariableB = Case3)",
                "        table3",
                "    #else",
                "        table4",
                "    #end",
                "#else if(VariableA = Case2)",
                "    table2",
                "#else",
                "    defaultTable",
                "#end"
            };

            // Clear previous parsing
            _textQueue.Clear();
            _currentLineIndex = 0;

            // Get first symbol and start parsing
            GetSy();
            if (LineSequence(true)) { // Finished parsing successfully.
                //TODO: Do something with the generated SQL
            } else { // Error encountered.
                Output("*** ABORTED ***");
            }
        }

        // The following methods parse according the the EBNF syntax.

        bool LineSequence(bool writeOutput)
        {   
            // EBNF:  LineSequence = { TextLine | IfStatement }.
            while (_sy == Symbol.Text || _sy == Symbol.NumberIf) {
                if (_sy == Symbol.Text) {
                    if (!TextLine(writeOutput)) {
                        return false;
                    }
                } else { // _sy == Symbol.NumberIf
                    if (!IfStatement(writeOutput)) {
                        return false;
                    }
                }
            }
            return true;
        }

        bool TextLine(bool writeOutput)
        {
            // EBNF:  TextLine = <string>.
            if (writeOutput) {
                Output(_string);
            }
            GetSy();
            return true;
        }

        bool IfStatement(bool writeOutput)
        {
            // EBNF:  IfStatement = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine.
            bool result;
            if (IfLine(out result) && LineSequence(writeOutput && result)) {
                writeOutput &= !result; // Only one section can produce an output.
                while (_sy == Symbol.NumberElse) {
                    GetSy();
                    if (_sy == Symbol.If) { // We have an #else if
                        if (!ElseIfLine(out result)) {
                            return false;
                        }
                        if (!LineSequence(writeOutput && result)) {
                            return false;
                        }
                        writeOutput &= !result; // Only one section can produce an output.
                    } else { // We have a simple #else
                        if (!LineSequence(writeOutput)) {
                            return false;
                        }
                        break; // We can have only one #else statement.
                    }
                }
                if (_sy != Symbol.NumberEnd) {
                    Error("'#end' expected");
                    return false;
                }
                GetSy();
                return true;
            }
            return false;
        }

        bool IfLine(out bool result)
        {
            // EBNF:  IfLine = "#if" "(" Condition ")".
            result = false;
            GetSy();
            if (_sy != Symbol.LPar) {
                Error("'(' expected");
                return false;
            }
            GetSy();
            if (!Condition(out result)) {
                return false;
            }
            if (_sy != Symbol.RPar) {
                Error("')' expected");
                return false;
            }
            GetSy();
            return true;
        }

        private bool Condition(out bool result)
        {
            // EBNF:  Condition = Identifier "=" Identifier.
            string variable;
            string expectedValue;
            string variableValue;

            result = false;
            // Identifier "=" Identifier
            if (_sy != Symbol.Identifier) {
                Error("Identifier expected");
                return false;
            }
            variable = _string; // The first identifier is a variable.
            GetSy();
            if (_sy != Symbol.Equals) {
                Error("'=' expected");
                return false;
            }
            GetSy();
            if (_sy != Symbol.Identifier) {
                Error("Value expected");
                return false;
            }
            expectedValue = _string;  // The second identifier is a value.

            // Search the variable
            if (_variableValues.TryGetValue(variable, out variableValue)) {
                result = variableValue == expectedValue; // Perform the comparison.
            } else {
                Error("Variable '{0}' not found", variable);
                return false;
            }

            GetSy();
            return true;
        }

        bool ElseIfLine(out bool result)
        {
            // EBNF:  ElseIfLine = "#else" "if" "(" Condition ")".
            result = false;
            GetSy(); // "#else" already processed here, we are only called if the symbol is "if"
            if (_sy != Symbol.LPar) {
                Error("'(' expected");
                return false;
            }
            GetSy();
            if (!Condition(out result)) {
                return false;
            }
            if (_sy != Symbol.RPar) {
                Error("')' expected");
                return false;
            }
            GetSy();
            return true;
        }
    }
}

请注意,嵌套的 if 语句会以非常自然的方式自动处理。首先,语法是递归表达的。 ALineSequence可以包含IfStatments and IfStatments 包含LineSequences。其次,这导致语法处理方法以递归方式相互调用。因此,语法元素的嵌套被转换为递归方法调用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解析if-else if语句算法 的相关文章

  • 是否可以使静态控件透明?

    我正在尝试实现一个静态控件 该控件刷新 更改文本 以响应每秒发生一次的某个事件 由于我不想每秒绘制整个客户区域 所以我决定使用静态控件 现在的问题是父窗口被蒙皮 这意味着它有自定义位图作为背景 而静态控件没有适应 所以我正在寻找使静态控件的
  • 如何使用 LINQ ForEach 更改 List

    我有一个List
  • 获取当前用户的 NetworkCredential (C#)

    我正在尝试从控制台应用程序调用 Web 服务 并且我需要向客户端提供System Net NetworkCredential object 是否有可能创建一个NetworkCredential启动应用程序的用户的对象而不提示输入用户名 密码
  • OWIN AuthenticationOptions 在 mvc5 应用程序中运行时更新

    Hi 情况如下 我在 iis 7 上有一个带有 Identity 2 的 MVC 5 应用程序 该应用程序为多个网站提供服务 主机名是某些网站的关键 网站 另一个网站 等等 我决定在我的所有网站上使用谷歌外部登录 每个网站都应该是带有个人
  • 在异步方法中使用时 HttpClient 标头被清空

    我正在使用 NET Framework 4 6 1 我的 Web api 中有一个控制器 其中有静态 HttpClient 来处理所有 http 请求 在 IIS 上托管我的应用程序后 大约每月一次 我的应用程序的所有传入请求都会出现以下异
  • 在宏中使用 # [重复]

    这个问题在这里已经有答案了 请解释一下代码 include
  • 外部剃刀视图看不到外部模型

    我对外部剃刀视图有疑问 在我的项目中 我有主 mvc Web 程序集和动态加载的外部类库程序集 来自 DB 及其自己的控制器 视图和模型 这些程序集在运行时不会直接引用和加载 我能够通过为控制器创建自定义控制器工厂 为视图创建自定义虚拟路径
  • 指向指针的指针和指向二维数组的指针之间的区别

    如果我有一个二维数组 B 定义为 int B 2 3 1 3 5 2 4 6 Is int p B与 一样int p 3 B int f B printf d f 1 gives 5作为输出 同时printf d f 给出 1 作为答案 为
  • 错误 C2065:'cout':未声明的标识符

    我正在处理我的编程作业的 驱动程序 部分 但我不断收到这个荒谬的错误 错误 C2065 cout 未声明的标识符 我什至尝试过使用std cout但我收到另一个错误 IntelliSense 命名空间 std 没有成员 cout 当我宣布u
  • 如何将 QSerialPort 模块添加到 CMake 中?

    我想将 QSerialPort 模块添加到 CMake 中 根据我的理解 我需要将QT 串口添加到 pro中 我只想使用 CMake 所以我尝试编译简单的 CMake 文件 但有错误 QtCore 正在工作 qDebug 可以毫无问题地显示
  • 使用私有构造函数的 C# 单元测试类?

    好吧 我刚刚收到一个作业 我必须对具有私有构造函数的类执行单元测试 现在 当所有方法也都是非静态时 我该如何在不初始化类的情况下进行单元测试 有什么方法可以对具有私有构造函数的类进行单元测试 无需反射 如果您无法将类公开 您仍然可以通过以下
  • 测试从 ComboBox 派生的自定义控件

    我创建了一个从 ComboBox 派生的控件 并希望对其行为进行单元测试 但是 它在我的单元测试中的行为似乎与实际应用程序中的行为不同 在实际应用程序中 Combobox DataSource 属性和 Items 同步 换句话说 当我更改
  • ASP.NET 中的 thread.sleep

    我正在为我的网站模拟彗星实时馈送协议 因此在我的控制器中我添加 while nothing new before timeout Thread Sleep 1000 但我注意到添加此功能后整个网站变慢了 调试后我得出结论 当我打电话时Thr
  • 如何“全局”捕获对象实例中引发的异常

    我目前正在编写一个 winforms 应用程序 C 我正在使用企业库异常处理块 遵循我所看到的相当标准的方法 IE 在 Program cs 的 Main 方法中 我已将事件处理程序连接到 Application ThreadExcepti
  • argc 和 argv 在 Windows 中没有用吗?

    在 Linux 中 argc 和 argv 计算终端中的参数 但在 Windows 中 我找不到放置第二个参数的地方 事实上 每次我运行该程序时 它都会创建那个丑陋的黑色窗口 我什至没有机会给出任何争论 那么这两个变量在Windows平台下
  • WPF MVVM后台打印数据绑定问题

    我正在使用 wpf mvvm 开发一个销售点应用程序 在交易生命周期的许多阶段 都会在后台打印收据 我已经使用其他示例在后台生成和打印收据 我正在后台打印一个 UserControl 一切看起来都很棒 然后 我为该控件创建了 ViewMod
  • 合并大文件的最佳方法是什么?

    我必须合并数千个大文件 每个大约 200MB 我想知道合并这些文件的最佳方法是什么 行将有条件地复制到合并文件中 可以使用 File AppendAllLines 或使用 Stream CopyTo 吗 使用 File AppendAllL
  • Task.Delay 到底是如何工作的?

    他们说 Task Delay 是一个异步 Thread Sleep 为了测试这一点 我写了下面的代码 我希望立即打印 One 然后 3 秒后将打印结果变量 15 2 秒后 将打印 Two 但似乎并非如此 一 不会立即打印 3 秒后打印 On
  • 64 位随机生成器种子

    我目前正在运行一个具有 8 个以上管道 线程 的多线程模拟应用程序 这些管道运行非常复杂的代码 该代码取决于种子生成的随机序列 然后该序列被归结为单个 0 1 我希望在将种子从主线程传递到处理管道后 这种 随机处理 具有 100 的确定性
  • Json.net 将数字属性序列化为字符串

    我正在使用 JsonConvert SerializeObject 序列化模型对象 服务器期望所有字段都是字符串 我的模型对象具有数字属性和字符串属性 我无法向模型对象添加属性 有没有办法将所有属性值序列化为字符串 我必须只支持序列化 而不

随机推荐

  • Python查找所有出现的连字符单词并替换该位置

    我必须用连字符替换所有出现的模式c c c c come or oh oh oh oh等与最后一个标记即come or oh在此示例中 其中 连字符之间的字符数是任意的 可以是一个或多个字符 要匹配的标记是连字符中的最后一个标记 因此com
  • 在 Dart/Flutter 中反序列化 json 数组

    如何反序列化这个json数组 i 737 n 1 得到变量 i e n 要反序列化的类 class PortasAbertas int i int n PortasAbertas this i this n PortasAbertas fr
  • 不建议在 ES6 中使用“use strict”?

    我还不熟悉 ECMAScript 6 我刚刚克隆了 React Starter Kit 存储库 它使用 ES6 作为应用程序代码 我很惊讶地发现 linter 是配置好的 https github com kriasoft react st
  • 在指令之间共享数据

    我有一些数据称为foo它所在的范围是三个孩子的父级 div div
  • 删除C中链表中每个奇数位置的节点

    我试图在 C 中创建一个函数来删除每个奇数位置的节点 例如1 2 3 4变成2 4 这是我尝试过的 但似乎不起作用 我正在谈论的是deletee功能 我修改了它 但列表似乎没有改变 include
  • Activity.setContentView、View.setContentView?

    我注意到 Activity 类有一个 setContentView 方法 可以在其中加载 xml 资源文件 我想对最终继承自 View 的类做同样的事情 这似乎是一个死胡同 因为 View 类不存在 setContentView 方法 这引
  • 正则表达式 \\s*,\\s* 的作用是什么?

    我想知道这行代码对 surl 字符串中包含的 url 有何作用 String stokens surl split s s 让我们假设这是 surl http myipaddress 8080 Map MapServer html 斯托克斯
  • 在android中检测拨出电话和呼叫挂断事件

    我有一个要求 其中我想检测与 Android 中的呼叫相关的两种事件 每当拨打电话时 我的应用程序都应该知道这一点以及被叫号码 当呼叫挂断时 由于成功 失败 我的应用程序应该了解这一点以及挂断的原因 这在 Android 中可能吗 您应该创
  • JQuery向div标签添加多个类

    我想使用 JQuery 获取下一行代码 div class something something else div I use document createElement div 创建div 但是我该如何添加something and
  • 如何让我的 .bat 文件运行 linux 命令到远程 linux

    以下是我当前的 bat 内容 我在window cmd上运行它 它将连接到远程 Linux 服务器并提示我密码 但是当我输入密码并以远程主机身份登录后 Linux 服务器将不会运行我的 ls 命令 请帮忙 echo off ssh emai
  • 自动rebase Git 子分支

    假设我有这样的 git 历史记录 A B C D
  • Python 中的静态类型检查工具

    我正在使用现有的大型 Python 代码库 并希望开始添加类型注释 以便获得一定程度的静态检查 我在想象类似的事情Erlang http www erlang org doc reference manual typespec html 强
  • 将 HttpServletRequest 转发到不同的服务器

    I got a HttpServletRequest我想将 Spring Servlet 中的请求按原样 即 GET 或 POST 内容 转发到不同的服务器 使用 Spring 框架最好的方法是什么 我是否需要获取所有信息并构建一个新的HT
  • 从intel到arm交叉编译库

    我正在使用开源 C 库 DCMTKhttp dicom offis de dcmtk php en http dicom offis de dcmtk php en 我已经使用 VC IDE MacOS Xcode Mac iOS 模拟器在
  • jQuery 显示/隐藏 Div

    我将其用于显示 隐藏 div 扩展器 它工作正常 但是 HTML 实体没有被输出 document ready function slickbox hide slick toggle toggle function this text 96
  • ZADD 或 HMGET 等 Redis 命令可以处理的参数数量是否有限制?

    我想使用单个 ZADD 或 HMGET 命令而不是 MULTI EXEC ZADD 可以处理的 分数 成员 元组的数量有限制吗 HMGET 可以处理的字段数量有限制吗 理论上的限制相当高 但你应该以不会达到它的方式设计你的读数 保持读取大小
  • OpenThread() 返回 NULL Win32

    我觉得这个问题有一个明显的答案 但它一直在逃避我 我这里有一些 C 遗留代码 当它尝试调用 OpenThread 时 这些代码会中断 我在 Visual C 2008 Express Edition 中运行它 程序首先获取调用线程的 Thr
  • 过程没有参数并且提供了参数 3

    存储过程返回一个值 ALTER PROCEDURE dbo spCaller AS BEGIN DECLARE URL nvarchar 255 EXECUTE spBuscarUrl MIREX 2017 00001 url URL OU
  • Jenkins + Git:仅当 PR 在子目录中引入更改时才构建

    我们有一个大型 monorepo 其中包含多个项目 A 和 B 我目前将 Jenkins 设置为多分支管道项目 用于监视 monorepo 的 PR 如果创建了 PR Jenkins 会同时构建 A 和 B 现在 我希望 Jenkins 变
  • 解析if-else if语句算法

    我正在尝试为 if else 类型结构创建一个非常简单的解析器 它将构建并执行 SQL 语句 我不会测试执行语句的条件 而是测试构建字符串的条件 声明的一个例子是 select column1 from if VariableA Case1