首页
社区
课程
招聘
[原创] LL(1) 语法分析器解析算数表达式生成AST,和我学习编译的原因
2018-12-24 19:03 12925

[原创] LL(1) 语法分析器解析算数表达式生成AST,和我学习编译的原因

2018-12-24 19:03
12925

先说一下为什么学习编译原理

因为前一段时间一直在研究VMP,把VMP的大致结构看了一下。个人认为,提取伪代码其实稍微花一些功夫就可以提取出来的,代码混淆也不是什么难题,难的是几行汇编代码变成了成千上万行虚拟代码。现在VMP分析插件基本上可以提取出2.x版本的伪代码,但是真的要使用VMP分析插件去分析一个稍微复杂一点的程序,这个工作量不敢想象,虽然做了模板匹配但是数量级在这里。

所以我一直在想一个解决方案,经过了一段时间的思考我想到了一个解决方案,但是我不知道是否有人尝试过这么干。或者说尝试过,已经放弃(失败了)。

我的想法是:
首先VMP是什么?
VMP在加壳虚拟化代码的时候显示的为什么是程序编译中?
仔细想想编译器是什么?
C语言编译器 就是把程序员能看懂的C语言代码翻译成机器能看懂的汇编代码。
而VMP编译器就是把机器能看懂的汇编代码翻译成VMP虚拟机能看懂的代码。

 

试想是否可以写一个反VMP编译器。就是把VMP代码再次编译成机器能看懂的汇编代码? 可行性我现在不能100%的确定,但是理论上是可以实现的。只不过是时间长短的问题。 比如说VMP的虚拟栈分析我的一些看法是:

 

比如一段VMP代码:
vPushImm4 100
vPushReg r1
vPopReg r2
vPushImm 200
vPopReg r3

 

默认有一个栈偏移
SP = 0

  1. 当vPushImm 100 执行的时候这个时候提取出 两个词法单元

    1. (vPushImm4)
    2. (IMM, 100) // IMM 是立即数, 4是词法单元的属性
      当我们看到vPushImm4的时候可以当做生成了一个临时变量。
      首先把 SP + 4。

      SP = SP + 4
      然后生成一个中间变量
      vLocal4 = 100 // 这个vLocal4 中的4就是SP的偏移

  1. 执行 vPushReg r1 的时候同样也是生成一个变量
    SP = SP + 4
    生成中间变量
    vLocal8 = r1
  1. 执行 vPopReg r2 的时候获取到两个词法单元

    1. vPopReg
    2. r2

      此时把当前SP的值给r2,然后SP = SP - 4
      生成类似的代码
      r2 = vLocal8
      SP = SP - 4 (此时SP = 4)

  2. 执行 vPushImm4 200的时候
    SP = SP + 4 (此时SP = 8)
    生成代码
    vLocal8 = 200

上述的VMP语句综合起来就是
vLocal4 = 100
vLocal8 = r1
r2 = vLocal8
vLocal8 = 200

 

如果在应用 机器无关优化的话:
vLocal4 = 100
r2 = r1
vLocal8 = 200

 

当然更加具体的,我学习的还没有那么深入。还没学到中间代码生成,我是先有这个想法再去学编译原理的。现在看到语法翻译的部分。我会的东西都会发帖子出来的。有一起学的朋友可以留言一起。


进入主题LL(1)语法分析器

首先给出LL(1)文法的定义

图片描述

LL(1)文法是一个自顶向下语法分析算法,自顶向下的语法分析算法还有递归下降语法分析算法,但是递归下降语法分析器相对有LL(1)语法分析器有很多缺点。 关于递归下降语法分析算法我这里不多说了。说一下LL(1)文法相对于递归下降算法的优点。

  • 速度快
  • 可自动化分析
  • 不需要回溯

在说LL(1)算法之前 我们必须要了解几个东西:

  • 文法
  • FIRST集
  • FOLLOW集
  • 消除左递归
  • 提取左公因

消除左递归,和提取左公因子。其实相对来说没有那么重要,这个部分可以手动转换。但是算法也在我之前的帖子中给出了。

 

如果说一点编译原理没有看过的话,那么这篇文章你看起来可能云里雾里的。

 

文法 是必须要所了解的点。
FIRST集FOLLOW集 是所有语法分析器都要用到的算法,非常非常重要。 如果说 FIRST集FOLLOW集 没有掌握好的话,那么所有的LL文法LR文法都是掌握不了的,更别说写出对应的算法了。

 

和大家简单介绍一下文法:
文法的全称是 上下文无关法, 他是一种规则用来约定一个语言的语法规则。其实正则表达式也是一种上下文无关法,但是不代表文法就是正则。相对于正则表达式,文法的表达能力更强,描述能力更强。

 

文法由四个部分组成:

  • 一个终结符号集合
  • 一个非终结符号集合
  • 一个产生式列表
  • 一个开始符号

本文不会将所有概念说到,因为太多了。

 

比如说一个算数表达式文法:

 

S -> E
E -> E + E
E -> E - E
E -> E / E
E -> E * E
E -> (E), num

 

在 -> 左部称为产生式头部(左部), 在 -> 右部的称为产生式体(右部)。
所有的产生式头 都是一个非终结符号。
所谓非终结符号就是 抽象的描述了一种规则。
而终结符号就是一个具体的事物。

 

在这个文法中 {S, E} 这两个是非终结符号, { +, -, *, /, num, (, ) }这些是终结符号。
但是上面的例子中说的文法并不是一个LL(1)文法。因为他是一个左递归文法。
E -> E + E 产生式头部和产生式体的第一个符号是相同的,就会导致LL(1)文法陷入一个死循环。LL(1)文法是一个文法最左推导过程,从左往右的推导。

 

当我们看到 S -> E的时候 我们会去推导 产生式体E
当我们看到 E -> E + E 的时候我们会去推导产生式 E + E, 但此时产生式体的第一个符号 E 让我们又陷入了要解析产生式E,然后就陷入一个死循环出不去了。(更详细的大家看书中所说的)
所以当我们看到 E -> E + E 的时候就要消除左递归。(可能还有间接左递归, 具体看我另外一篇文章)

 

在按照算数表达式优先级(括号优先级最大,其次是乘除法, 在是加减法)我们给出的文法是:
S -> E
E -> E + T | E - T | T
T -> T * F | T / F | F
F -> (E) | num

 

这里的 "|" 是用来分割产生式体的, 这些用 | 分开的产生式他们拥有相同的 左部。
下面的产生式是用我的程序消除左递归后的文法。
S -> E |
E -> TE| T -> FT |
F -> (E) | num |
E-> +TE | -TE| ε | T -> *FT| /FT | ε |

 

上文的图片中说了, LL(1)文法就是根据向前看1个输入就可以确定应用哪个产生式进行解析。
比如说 现在我们要推导E: 他有3个产生式体: +TE | -TE| ε FIRST(E) = {+, -, ε} 这是他的FIRST集。
当我们输入中的符号是 + 号时, 我们就选用+TE这个产生式来推导, 是 - 号时选用-TE
但是我们发现这个E还可以推导出ε (空串), 也就是说 E是可以为空的。 那我们什么时候应用 E-> ε这个产生式? 此时我们就要查看什么终结符号可以跟在 E的后面也就是 FOLLOW(E) = { ), $ } 这两个符号。也就是说当遇到 ), $ 这两个终结符号时。我们就应用 E -> ε这个产生式。

 

举个简单的例子:

 

S -> ABC
A -> a | ε
B -> b
C -> c
这个文法有4个产生式: S 是文法的开始符号。
这个产生式要匹配一个 abc 或者 bc 的字符串, 也就是终结符a是可以不存在的, 因为非终结符A是可以推导出空串的。那我们看看什么时候应用这个产生式。 当非终结符A可以推导出空串的时候 我们就要获取FOLLOW(A)的值,此时可以跟在A后面的只有B,而FIRST(B)的值是b.就是说当A产生式遇到终结符b时那么就应用 A -> ε这个产生式。

LL(1)是基于预测分析表生成的。

预测分析表的算法其实我上面已经说了,现在给出书中的定义:

图片描述

LL(1)算法的伪代码:

图片描述
这个伪代码其实很简单,不过我的程序中加上了生成语法树。可能略微复杂。

 

现在给出完整代码:
https://github.com/hixiaosan/cpp_dragon.git

 

Parser.h

#pragma once

#include "CFG.h"
#include "Lex.h"

// 语法分析基类
class Parser 
{
public:
    Parser(CFG::CFG *cfg, Lex *lex);
    ~Parser();

protected:
    /// 获取FIRST集合
    std::set<std::string> First(CFG::GrammarSymbolic *symbolic);

    /// 获取FOLLOW集合
    std::set<std::string> Follow(std::string symbolic);

private:
    std::set<std::string> follow(std::string symbolic);

protected:
    CFG::CFG *cfg_;
    Lex *lex_;

private:
    std::set<std::string> follow_record_;
};

Parser.cpp

#include "Parser.h"



Parser::Parser(CFG::CFG *cfg, Lex *lex) : cfg_(cfg), lex_(lex)
{

}


Parser::~Parser()
{
}

// 获取FIRST集合, 文法必须是消除左递归后的文法 可以处理绝大多数文法,但不是所有文法,有一些特例。
std::set<std::string> Parser::First(CFG::GrammarSymbolic * symbolic)
{
    std::set<std::string> first_set;

    // 规则一 如果符号是一个终结符号,那么他的FIRST集合就是它自身
    if (symbolic->Type() == CFG::SYMBOLIC_TYPE_TERMINAL)
    {
        first_set.insert(symbolic->Name());

        return first_set;
    }

    // 规则二 如果一个符号是一个非终结符号(A)
    // (1) A -> XYZ 如果 X 可以推导出nil 那么就去查看Y是否可以推导出nil
    //              如果 Y 推导不出nil,那么把Y的First集合加入到A的First集合
    //                如果 Y 不能推导出nil,那么继续推导 Z 是否可以推导出nil,依次类推
    // (2) A -> XYZ 如果XYZ 都可以推导出 nil, 那么说明A这个产生式有可能就是nil, 这个时候我们就把nil加入到FIRST(A)中

    std::string current_symbolic_name = symbolic->Name();

    auto productions = cfg_->Productions();

    for (size_t i = 0; i < productions.size(); i++)
    {
        // 不匹配的产生式
        if (productions[i]->Header() != current_symbolic_name)
        {
            continue;
        }

        // 一个非终结符号有一个或者多个产生式体 要一一求出他的FIRST集
        for (size_t k = 0; k < productions[i]->Body().size(); k++)
        {
            auto body = productions[i]->Body()[k];
            bool has_nil = false;

            // 推导这个产生式体的所有FIRST集合
            for (size_t t = 0; t < body.size(); t++)
            {
                has_nil = false;
                auto fset = First(body[t]);

                for (auto terminal_sym : fset)
                {
                    if (terminal_sym == "ε") // 查看集合中是否包含空 
                    {
                        has_nil = true;
                        continue;
                    }

                    // 插入到当前符号
                    first_set.insert(fset.begin(), fset.end());
                }

                // 如果当前符号不为空的话 则不需要继续推导产生式下面的符号
                if (has_nil == false)
                {
                    break;
                }
            }

            // 如果说到最后一个符号都可以推导出ε, 那么这个产生式就 可以为空
            if (has_nil == true)
            {
                first_set.insert("ε");
            }

        }

        break;
    }


    return first_set;
}

std::set<std::string> Parser::follow(std::string A)
{
    // 一个文法符号的FOLLOW集就是 可能出现在这个文法符号后面的终结符
    // 比如 S->ABaD, 那么FOLLOW(B)的值就是a。 
    //                    FOLLOW(A)的值包含了FIRST(B)中除了ε以外的所有终结符,如果First(B)包含空的话。说明跟在B后面的终结符号就可以跟在A后面,这时我们要把FOLLOW(B)的值也添加到FOLLOW(A)中
    //                  因为D是文法符号S的最右符号,那么所有跟在S后面的符号必定跟在D后面。所以FOLLOW(S)所有的值都在FOLLOW(D)中
    //                     以下是书中的总结

    // 不断应用下面这两个规则,直到没有新的FOLLOW集 被加入
    // 规则一: FOLLOW(S)中加入$, S是文法开始符号
    // 规则二: A->CBY FOLLOW(B) 就是FIRST(Y)
    // 规则三: A->CB 或者 A->CBZ(Z可以推导出ε) 所有FOLLOW(A)的符号都在FOLLOW(B)

    std::set<std::string> follow_set;

    // 如果是文法的开始符号,那么加入$
    if (cfg_->StartSymbolic() == A)
    {
        follow_set.insert("$");
    }

    // 搜索所有包含 A 符号的产生式
    auto productions = cfg_->Productions();

    for (size_t i = 0; i < productions.size(); i++)
    {
        // 获取所有的产生式
        for (size_t t = 0; t < productions[i]->Body().size(); t++)
        {
            auto body = productions[i]->Body()[t];

            for (size_t k = 0; k < body.size(); k++)
            {
                if (A != body[k]->Name())
                {
                    continue;
                }

                // 第一种情况 符号在产生式的最末尾, 那么我们就要去获取产生式头部的FOLLOW集
                if (k == body.size() - 1)
                {
                    // 没有被FOLLOW过, 避免陷入死循环
                    if (follow_record_.end() == std::find(follow_record_.begin(), follow_record_.end(), productions[i]->Header()))
                    {
                        follow_record_.insert(productions[i]->Header());
                        auto fset = follow(productions[i]->Header());
                        follow_set.insert(fset.begin(), fset.end());
                    }
                    continue;
                }

                // 不是在最后一个符号 我们获取文法符号的下一个符号的First集合
                auto fset = First(body[k + 1]);

                bool has_nil = false;
                for (auto terminal_symbolic : fset)
                {
                    if (terminal_symbolic == "ε")
                    {
                        has_nil = true;
                        continue;
                    }

                    follow_set.insert(terminal_symbolic);
                }

                // 如果下一个First集合包含ε
                if (has_nil)
                {
                    follow_record_.insert(body[k + 1]->Name());
                    auto fset = follow(body[k + 1]->Name());
                    follow_set.insert(fset.begin(), fset.end());
                }


            }
        }
    }

    return follow_set;
}

std::set<std::string> Parser::Follow(std::string A)
{
    follow_record_.clear();
    follow_record_.insert(A);
    return follow(A);
}

LL1.h

#pragma once

#include "Parser.h"
#include <map>

struct ASTNode;

// 抽象语法树
struct AST
{
    ASTNode *root;
};

struct ASTNode
{
    CFG::GrammarSymbolic *symbolic; // 文法符号
    std::vector<ASTNode *> nodes;    // 子节点
};

// LL(1) 语法分析
class LL1 : Parser
{
public:
    LL1(CFG::CFG *cfg, Lex *lex);
    ~LL1();

private:
    void InitParser();

public:
    /// 语法分析
    AST Parse(); 

private:
    // LL(1) 预测分析表
    std::map<std::string, std::map<std::string, CFG::ProductionBody>> ll1_table_;
};

LL1.cpp

#include "LL1.h"
#include <stack>



LL1::LL1(CFG::CFG *cfg, Lex *lex) : Parser(cfg, lex)
{
    // 文法要移除左递归和提取左公因子
    cfg_->RemoveRecursive();
    cfg_->TakeLeft();

    InitParser();
}


LL1::~LL1()
{
}

void LL1::InitParser()
{
    // 生成预测分析表的目的就是让 LL(1) 文法, 向前看一个符号就可以确定使用非终结符的哪个产生式。
    // 比如:  A->cBD | bFA
    //         当我们开始推导非终结符 A 的时候, 我们向前看输入中的一个终结符号,
    //         如果终结符号是 c 我们就选择 A->cBD产生式来推导非终结符A。 如果终结符号是b那么我们就选择 A -> bFA来推导非终结符A。

    // 预测分析表有两个规则
    // 规则一: 产生式 A->B, 对于First(B)中的所有终结符号a, 把A->B这个产生式加入到 M[A, a]中。
    // 规则二: 产生式 A->B, 如果First(B)中包含nil串, 那么对Follow(A)的所有终结符号b, 把A->B这个产生式加入到 M[A, b]中。
    // 比如产生式: 
    //        A->BC
    //        F->GAc
    //        B->a | ε
    //        C->f | ε
    //     首先我们获取 FIRST(BC)的所有终结符号集合 {a, f, ε}, 我们把A->B 添加到M[A, a], M[A, f]。
    //  其次我们发现 FIRST(BC)中是包含ε的, 那么我们就获取FOLLOW(A)的值. FOLLOW(A) => {c, $}, 那么我们就把 A->B 也添加到 M[A, c], M[A, $]中。
    auto products = cfg_->Productions();

    for (size_t i = 0; i < products.size(); i++)
    {
        auto pro = products[i]->Body();
        // 获取所有的产生式右部
        for (size_t k = 0; k < pro.size(); k++)
        {
            // 对于某一条产生式右部的所有符号
            // 比如 A -> BCD
            // 从左往右的顺序是 B-C-D
            // 首先推导 FIRST(B) 如果FIRST(B)不能推导出空串那么就把 FIRST(B)中的所有终结符a。 把当前处理的产生式加入到 M[A, a]
            //         FIRST(B) 如果可以推导出空串, 那么意味着 B 这个符号是可以不存在的,那么我们就要查看 FIRST(C) 集合。依次循环这个步骤。

            // -------------------
            // 如果说 FIRST(B) FIRST(C) FIRST(D) 都包含空串那么就说明了 整个产生式都是可以推导出空串的。
            // 比如说  B -> b | ε
            //         C -> c | ε
            //         D -> d | ε
            // BCD 都可以为空, 那么 A 就可以为空了。这个时候我们就要获取 FOLLOW(A)的值来确定应用当前的产生式
            bool has_nil = false;
            for (size_t t = 0; t < pro[k].size(); t++)
            {
                auto first_set = First(pro[k][t]);

                has_nil = false;

                for (auto terminal_symbolic : first_set)
                {
                    if (terminal_symbolic == "ε")
                    {
                        has_nil = true;
                        continue;
                    }

                    ll1_table_[products[i]->Header()][terminal_symbolic] = pro[k];
                }

                // 当前符号不能推导出空串那么就结束
                if (false == has_nil)
                {
                    break;
                }
            }

            // 产生式右部所有的符号都可以为空
            if (has_nil)
            {
                auto follow_set = Follow(products[i]->Header());

                for (auto terminal_symbolic : follow_set)
                {
                    ll1_table_[products[i]->Header()][terminal_symbolic] = pro[k];
                }
            }

        }


    }
}


AST LL1::Parse()
{
    AST ast;
    ast.root = new ASTNode;
    auto cur_node = ast.root;

    cur_node->symbolic = new CFG::GrammarSymbolic(cfg_->StartSymbolic(), CFG::SYMBOLIC_TYPE_N_TERMINAL);

    std::stack<std::string> ll1_stack;
    std::vector<std::vector<void *>> ast_stack; // 语法分析栈(递归栈)

    ll1_stack.push("$"); // 结束符号
    ll1_stack.push(cfg_->StartSymbolic()); // 文法开始符号

    // 进入下一层语法栈

    ast_stack.push_back(std::vector<void *>());
    // 栈中的两个局部变量
    ast_stack.back().push_back(0);
    ast_stack.back().push_back(cur_node);


    size_t stack_deep = 0;

    auto X = ll1_stack.top();
    auto input_token = lex_->FetchNext();
    if (NULL == input_token)
    {
        throw std::runtime_error("词法分析失败");
    }

    while (X != "$")
    {
        if (cfg_->IsTerminal(X))
        {
            if (X == input_token->Name())
            {
                ll1_stack.pop(); // 当前符号出栈

                input_token = lex_->FetchNext(); // 获取下一个输入符号

                if (NULL == input_token)
                {
                    throw std::runtime_error("词法分析失败");
                }
            }
            else if (X == "ε")
            {
                ll1_stack.pop(); // 当前符号出栈
            }
            else
            {
                throw std::runtime_error("语法分析错误");
            }

            do
            {
                if (ast_stack.size() <= 1)
                {
                    break;
                }

                // 索引右移动
                ast_stack.back()[0] = (void *)((int)ast_stack.back()[0] + 1);
                int idx = (int)ast_stack.back()[0];
                int parent_idx = (int)ast_stack[ast_stack.size() - 2][0];
                ASTNode *node = (ASTNode *)ast_stack[ast_stack.size() - 2][parent_idx + 1];

                if (node->nodes.size() == idx)
                {
                    ast_stack.pop_back(); // 类似于返回
                    continue;
                }
                break;
            } while (true);


        }
        else if (cfg_->IsNTerminal(X)) // 如果是非终结符号
        {
            auto iter = ll1_table_[X].find(input_token->Name());
            if (iter == ll1_table_[X].end())
            {
                throw std::runtime_error("语法分析错误");
            }

            ll1_stack.pop(); // 弹出当前符号
            // 对于当前非终结符 应用的产生式体
            auto product_body = ll1_table_[X][input_token->Name()];

            for (int i = product_body.size() - 1; i >= 0; i--)
            {
                ll1_stack.push(product_body[i]->Name());
            }

            int idx = (int)ast_stack.back()[0];
            ASTNode *node = (ASTNode *)ast_stack.back()[idx + 1];

            // 进入下一层语法栈
            ast_stack.push_back(std::vector<void *>());

            // 栈中的两个局部变量
            ast_stack.back().push_back(0);

            for (size_t i = 0; i < product_body.size(); i++)
            {
                auto new_node = new ASTNode;
                new_node->symbolic = product_body[i];

                node->nodes.push_back(new_node);
                ast_stack.back().push_back(new_node);
            }






            stack_deep++; // 栈深度添加
        }

        X = ll1_stack.top(); // 重新设置X的值
    }

    return ast;
}

main.cpp

#include <iostream>
#include "CFG.h"
#include <stdlib.h>
#include <stdio.h>
#include "Lex.h"
#include "LL1.h"
#include <memory>
#include <Windows.h>
#include <gdiplus.h>

using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

using std::cin;
using std::cout;

void TestParse()
{
    // 产生式列表
    const char *products = "S -> E\n"
                           "E -> E + T | E - T | T\n"
                           "T -> T * F | T / F | F\n"
                           "F -> (E) | num";

    // 非终结符号集合
    std::set<std::string> n_terminal_set =  { "S", "E", "T", "F" };

    // 终结符号集合
    std::set<std::string> terminal_set = { "num", "+", "-", "*", "/", "(", ")" };

    try
    {
        CFG::CFG cfg(n_terminal_set, terminal_set, products, "S");
        std::cout << "消除左递归之前: " << std::endl << cfg << std::endl;
        std::cout << "消除左递归后: " << std::endl << cfg.RemoveRecursive();
    }
    catch (const std::exception &exp)
    {
        std::cout << exp.what() << std::endl;
    }


}

void TestFirst()
{

}

// 获取树的深度
int ASTDeep()
{
    return 0;
}


// 获取树的宽度
int ASTWidth(ASTNode *node)
{
    if (node->nodes.size() == 0)
    {
        node->width - 1;
        return 1;
    }

    int width = 0;
    for (auto n : node->nodes)
    {
        width += ASTWidth(n);
    }
    node->width = width;
    return width;
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if (size == 0)
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL)
        return -1;  // Failure

    GetImageEncoders(num, size, pImageCodecInfo);

    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }
    }

    free(pImageCodecInfo);
    return -1;  // Failure
}

const int kNodeWidth = 50;
const int kNodeHeight = 50;
const int space = 20;
const int kRadii = kNodeWidth / 2;
const int nBitmapW = 3000;
const int nBitmapH = 1080;

int AnsiToUnicode(LPWSTR wstrUnicode, LPCSTR szAnsi)
{
    DWORD dwMinSize = 0;
    dwMinSize = MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, NULL, 0);
    if (0 == dwMinSize)
    {
        return 0;
    }
    MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, wstrUnicode, dwMinSize);

    return dwMinSize;
}

int LeftWidth(ASTNode *node, int offset = 0)
{
    int left = offset;
    bool hasMiddle = node->nodes.size() % 2;
    for (size_t i = 0; i < node->nodes.size(); i++)
    {


        if (i < node->nodes.size() / 2) // 在左边
        {
            left = min(left, LeftWidth(node->nodes[i], offset - 1));

        }
        else if (i == node->nodes.size() / 2 && hasMiddle) { // 在中间
            LeftWidth(node->nodes[i], offset);
        }
        else { // 在右边
            left = min(left, LeftWidth(node->nodes[i], offset + 1));
        }
    }

    return left;
}

void MakeASTIMG(ASTNode *node, Gdiplus::Graphics &graphics, int center_x,  int deep = 0)
{
    FontFamily  fontFamily(L"Times New Roman");
    Font        font(&fontFamily, 18, FontStyleRegular, UnitPixel);
    int x = center_x - kRadii + kNodeWidth + space;


    Gdiplus::RectF       rectF(x , deep * 100, kNodeWidth, kNodeHeight);
    Gdiplus::SolidBrush  solidBrush(Color(255, 0, 0, 255));


    Pen      pen(Color::White, 1);
    graphics.DrawEllipse(&pen, rectF);

    Gdiplus::StringFormat sf;
    sf.SetAlignment(Gdiplus::StringAlignmentCenter);
    sf.SetLineAlignment(Gdiplus::StringAlignmentCenter);
    WCHAR wchar[100] = { 0 };
    AnsiToUnicode(wchar, node->symbolic->Name().c_str());

    graphics.DrawString(wchar, -1, &font, rectF, &sf, &solidBrush);

    if (node->nodes.size() == 1)
    {
        graphics.DrawLine(&pen, x + kRadii, deep * 100 + kNodeHeight, center_x + kNodeWidth + space, (deep + 1) * 100);


        MakeASTIMG(node->nodes[0], graphics, center_x, deep + 1);
        return;
    }

    int width = ASTWidth(node); // 当前节点宽度
    int sum_width = 0;
    bool lastInMid = false;
    bool hasMiddle = node->nodes.size() % 2;

    int left = abs(LeftWidth(node) * (kNodeWidth + space));
    center_x -= left;

    for (size_t i = 0; i < node->nodes.size(); i++)
    {
        int current_width = ASTWidth(node->nodes[i]);
        int cur_width = current_width * (kNodeWidth + space);
        int left = abs(LeftWidth(node->nodes[i])) * (kNodeWidth + space);
        int to_centor_x = center_x + (sum_width) + left;

        graphics.DrawLine(&pen, x + kRadii, deep * 100 + kNodeHeight, to_centor_x  + kNodeWidth + space, (deep + 1) * 100);


        MakeASTIMG(node->nodes[i], graphics, to_centor_x, deep + 1);

        sum_width += cur_width;
    }

}

// 生成语法树图片
void MakeASTIMG(ASTNode *node)
{
    ///// 生成图片太麻烦了.... 还是先打印出来树结构吧
    //for (int i = 0; i <= deep; ++i)
    //{
    //    std::cout << "\t";
    //}
    //auto str = node->symbolic->Name() == "ε" ? "@" : node->symbolic->Name();

    //std::cout << str << std::endl << std::endl;;

    //
    //
    //for (size_t i = 0; i < node->nodes.size(); i++)
    //{
    //    MakeASTIMG(node->nodes[i], deep + 1);
    //}

    // 节点的宽度 高度 和间隔


    int width = ASTWidth(node); // 树的宽度

    HDC hMemoryDC = CreateCompatibleDC(NULL);

    HBITMAP hBitMap = CreateCompatibleBitmap(hMemoryDC, nBitmapW, nBitmapH);

    SelectObject(hMemoryDC, hBitMap);

    Graphics graphics(hMemoryDC);

    Gdiplus::SolidBrush white(Color(255, 255, 255, 255));
    graphics.FillRectangle(&white, 0, 0, nBitmapW, nBitmapH);

    MakeASTIMG(node, graphics, nBitmapW / 2);

    // 生成图片
    BITMAP bm;
    GetObject(hBitMap, sizeof(BITMAP), &bm);
    WORD BitsPerPixel = bm.bmBitsPixel;

    using namespace Gdiplus;
    Bitmap* bitmap = Bitmap::FromHBITMAP(hBitMap, NULL);
    EncoderParameters encoderParameters;
    ULONG compression;
    CLSID clsid;

    if (BitsPerPixel == 1)
    {
        compression = EncoderValueCompressionCCITT4;
    }
    else
    {
        compression = EncoderValueCompressionLZW;
    }
    GetEncoderClsid(L"image/jpeg", &clsid);

    encoderParameters.Count = 1;
    encoderParameters.Parameter[0].Guid = EncoderCompression;
    encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
    encoderParameters.Parameter[0].NumberOfValues = 1;
    encoderParameters.Parameter[0].Value = &compression;

    bitmap->Save(L"1.jpeg", &clsid, &encoderParameters);
    delete bitmap;


}

void TestParseLL1()
{
    // 产生式列表
    const char *products = "S -> E\n"
        "E -> E + T | E - T | T\n"
        "T -> T * F | T / F | F\n"
        "F -> (E) | num";

    // 非终结符号集合
    std::set<std::string> n_terminal_set = { "S", "E", "T", "F" };

    // 终结符号集合
    std::set<std::string> terminal_set = { "num", "+", "-", "*", "/", "(", ")" };
    CFG::CFG cfg(n_terminal_set, terminal_set, products, "S");

    char line[255] = { 0 };
    cin.getline(line, sizeof(line));
    LL1 ll1(&cfg, new ExpLex(line));
    try
    {
        auto ast = ll1.Parse(); // 解析成功生成语法分析树
        MakeASTIMG(ast.root);
    }
    catch (std::exception &err)
    {
        std::cout << err.what() << std::endl;
    }


}

int main()
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    //TestParse();
    TestParseLL1();
    system("pause");

    return 0;
}

当我们输入错误的表达式语法时就会报错:
图片描述

 

当我们输入正确的语法时就会在程序运行目录生成一张 1.jpeg的图片。
图片描述
图片描述

 

其实多叉树生成这种图片的算法,大家有兴趣可以看一下。我修修改改花了一整天才实现的。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2019-1-17 13:19 被kanxue编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (21)
雪    币: 6086
活跃值: (1192)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
CCkicker 2018-12-24 19:06
2
0
沙发
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-24 19:32
3
0
CCkicker 沙发
我感觉我表达能力太弱了...
雪    币: 1414
活跃值: (4148)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2018-12-24 19:49
4
1
菜鸟级X 我感觉我表达能力太弱了...
这是自己动手写编译器SCC的书吧?作者的代码是用的TCC的
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-24 19:50
5
0
IamHuskar 这是自己动手写编译器SCC的书吧?作者的代码是用的TCC的
这个都是我根据编译原理书上写的代码,tcc是c语言写的好吗
雪    币: 2046
活跃值: (265)
能力值: ( LV7,RANK:104 )
在线值:
发帖
回帖
粉丝
1lu29e 2018-12-24 23:58
6
0
LZ的目的是反向编译虚拟代码?如果直接脱壳子呢?脱壳是治标不治本?
雪    币: 81
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhengsidie 2018-12-25 10:15
7
0
就喜欢有专研精神的
雪    币: 1402
活跃值: (4135)
能力值: ( LV9,RANK:195 )
在线值:
发帖
回帖
粉丝
天水姜伯约 4 2018-12-25 12:18
8
0
楼主还缺不缺端茶倒水的小弟?
雪    币: 1414
活跃值: (4148)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2018-12-25 15:20
9
0
菜鸟级X 这个都是我根据编译原理书上写的代码,tcc是c语言写的好吗
不是,我是说书有点像以前看过的那本手动写编译器SCC的某些章节。那本书的作者是基于TCC做的。
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-25 15:36
10
0
IamHuskar 不是,我是说书有点像以前看过的那本手动写编译器SCC的某些章节。那本书的作者是基于TCC做的。
那本书 基本就是把 TCC 代码搬过去了
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-25 15:37
11
0
三十二变 楼主还缺不缺端茶倒水的小弟?[em_51]
大哥,要不你收我做小弟吧
雪    币: 249
活跃值: (346)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hackevil 2018-12-25 21:50
12
0
理论上肯定没问题,vmp里面数据流和控制流的陷阱处理起来就够搞死几个人了。理想很丰满,现实很骨感,单纯一个反编译器要从理论到实际应用那是得经历多少洗礼,目前仍不能完全应对各种对抗,更何况是对vmp。反编译理论上都差不多,不分语言的,但是真正实用于对抗级别反编译器,估计就ida-xray团队吧,jeb团队,都是反编译器界的代表,好像还有一个是看雪的GDA吧
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-26 08:43
13
0
hackevil 理论上肯定没问题,vmp里面数据流和控制流的陷阱处理起来就够搞死几个人了。理想很丰满,现实很骨感,单纯一个反编译器要从理论到实际应用那是得经历多少洗礼,目前仍不能完全应对各种对抗,更何况是对vmp。反 ...
总要有人去尝试的...
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-26 09:31
14
0
hackevil 理论上肯定没问题,vmp里面数据流和控制流的陷阱处理起来就够搞死几个人了。理想很丰满,现实很骨感,单纯一个反编译器要从理论到实际应用那是得经历多少洗礼,目前仍不能完全应对各种对抗,更何况是对vmp。反 ...
而且个人认为 写VMP 反编译器,要比写 TCC语言编译器 还要来的简单的多, 毕竟词法单元和文法都不是一个数量级的。VMP的词法单元和文法结构相对来说简单的多。
雪    币: 630
活跃值: (95)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
internetH 2018-12-27 18:47
15
0
我写过词法分析器,LALR的语法分析器。做研究可以。利用所学的东西做分析其他东西也没问题,如果真要去实现编译器,估计作者不行,不是所智力不行。很多条件制约,而且性能优化要花很长时间。我的LALR语法分析器分析实际的C程序语法并生成AST要近5分钟,而别人的几秒。优化的差距不是一点点。后面的制导翻译,中间代码生成,指令选择,寄存器选择,优化问题,难度不是一点点。如果有单位支撑,如果有经费,可以搞。
当年的豪情都没了,可能我老了。希望后来者有更好的条件,更大的决心。

鼓励一下的是:我现在对源代码、二进制代码、或任一规范语法文件的分析工具编写都比较自信了。
雪    币: 2391
活跃值: (294)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
菜鸟级X 2 2018-12-27 22:51
16
0
internetH 我写过词法分析器,LALR的语法分析器。做研究可以。利用所学的东西做分析其他东西也没问题,如果真要去实现编译器,估计作者不行,不是所智力不行。很多条件制约,而且性能优化要花很长时间。我的LALR语法分 ...
老哥 你说的是没问题的, 这件事本身就是一个循序渐进的过程。学习的过程,而且我相信我可以写出来的。VMP编译器相对说C语言编译器, 词法单元少,文法简单,相对来说编译器前端的工作量很少。编译器后端可以自己写 就自己写,如果实在写不出来交给LLVM也是未尝不可的。
雪    币: 1414
活跃值: (4148)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2018-12-28 11:41
17
0
菜鸟级X 老哥 你说的是没问题的, 这件事本身就是一个循序渐进的过程。学习的过程,而且我相信我可以写出来的。VMP编译器相对说C语言编译器, 词法单元少,文法简单,相对来说编译器前端的工作量很少。编译器后端可以 ...
楼主整理一下吧。标题上加  1 2 3 4 更有顺序 
雪    币: 1414
活跃值: (4148)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2018-12-28 11:45
18
0
internetH 我写过词法分析器,LALR的语法分析器。做研究可以。利用所学的东西做分析其他东西也没问题,如果真要去实现编译器,估计作者不行,不是所智力不行。很多条件制约,而且性能优化要花很长时间。我的LALR语法分 ...
确实如此,就跟我当年写防火墙一样,写了一个80%。最后20%不搞了,实在是没有继续坚持的动力。有一种曾经梦想仗剑走天涯,后来作业太多就放弃了的感觉
雪    币: 95
活跃值: (236)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
小心情 2019-1-7 14:22
19
0
很六,我要去尝试一下上下文相关算法,实际写点代码出来分析一下
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
junkboy 2019-1-11 20:21
20
0
#寻宝大战#祝看雪19岁快乐!
雪    币: 19
活跃值: (289)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
asanzjx 2019-1-20 10:46
21
0
对于表达式求值这种,就是类似计算器这种功能,可以转成逆序波兰式,然后使用调度场算法,很容易实现的,思路也很清晰
雪    币: 219
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
长相很重要吗 2019-1-24 00:20
22
0
TL,DR
用过 ocamlyacc(LALR) 和 camlp4(自顶向下, 流匹配), 也搜过一些比如 bison, menhir(LR1), 但从未见过有 LL1 基于跳转表, 可用的成熟分析器 ,
估计是因为 LL1 要消除左递归必然要增加新的产生式, 这样就导致了产生式相关联的动作(action) 没法处理, 
LR 分析器在分析带有二义性语法时非常棘手,因此无法处理复杂性高的语言 ,
而 camlp4 要自己处理操作符优先级非常麻烦, 因此写出来的语法定义会难以理解, 

游客
登录 | 注册 方可回帖
返回