繁体   English   中英

构建口译员:设计AST

[英]Building an interpreter: designing an AST

因此,我正在为一种与Python类似的语言进行解释。 现在,我了解到这不是一项艰巨的任务,我并不希望它能很好地工作或做很多事情,但我希望它具有一些基本功能(变量,函数,循环,if语句等)。

因此,目前我处于解释器获取文件并将其拆分为令牌列表的阶段,现在我准备将这些令牌转换为AST。 我打算使用递归下降解析器来做到这一点,我相信我理解这一点,但这就是问题所在。 假设我有以下输入

1 + 2 * 3

这将输出7,因为首先使用BIDMAS进行了乘法

2 * 3 = 6

然后加法之后

1 + 6 = 7

我有一个简单的语法,所以我知道如何获得此顺序,但是我不知道如何将其存储为AST。 为了简化答案,让我们假设这是您将收到的唯一输入,并且语法可以是

program = add
add = mul {"+" mul}
mul = NUM {"*" NUM}

因此,基本上,您如何制作一个数据结构来存储AST?

PS我在用C做这个。

免责声明:此表示是主观的,仅用于说明。

从根本上来说,您的AST就像二叉树一样构造,其中每个AST节点都是一个“ C”结构,既包含“左”指针又包含“右”指针。 AST的其他元素通常是上下文相关的。 例如,变量声明与函数定义或函数中的表达式的比较。 对于您引用的示例,粗糙的树将反映以下情况:

   +
 /   \
1     *
      /\
     2  3 

因此,通过将上述节点1 +(2 * 3)替换为您的AST构造将类似于:

                 -----------------
                | type: ADDOP   |
                | left: struct* |
                | right: struct*|
                -----------------
              /                   \
             /                     \
 (ADDOP left points to)         (ADDOP right points to)
------------------------       --------------------------  
| type: NUMLITERAL     |       | type: MULTOP           |
| value: 1             |       | left: struct*          |
| left: struct* (null) |       | right: struct*         |
| right: struct*(null) |       --------------------------
------------------------              /               \
                                     /                 \

                    (MULTOP left points to)         (MULTOP right points to)
                    ------------------------       --------------------------  
                    | type: NUMLITERAL     |       | type: NUMLITERAL       |
                    | value: 2             |       | value: 3               |
                    | left: struct* (null) |       | left: struct* (null)   |
                    | right: struct*(null) |       | right: struct* (null)  |
                    ------------------------       --------------------------

我假设您对“ C”以及如何malloc节点和分配左/右指针足够了解。

现在剩下的活动将是对树进行后顺序遍历,以评估表达式并产生结果,或者发出与编译结果对齐的适当的中间代码/机器代码。 任何一种选择都会带来大量的思想和计划。

顺便说一句:如前所述,AST节点通常将具有基于您要表示的抽象级别的属性。 另请注意,典型的编译器可能出于不同的原因利用多个AST。 是的,请多多阅读/研究。

注意:这说明了AST的数据结构,但是@mikeb答案对于如何将字符串“ 1 + 2 * 3”放入这种结构的节点来说是坚如磐石。

我会使用“调车场”算法-> https://en.wikipedia.org/wiki/Shunting-yard_algorithm

那里也有伪代码。

自由贸易协定:

在计算机科学中,调车场算法是一种解析以中缀符号指定的数学表达式的方法。 它可以用于生成后缀表示法字符串(也称为反向波兰表示法(RPN))或抽象语法树(AST)。 该算法是由Edsger Dijkstra发明的,并被称为“调车场”算法,因为其操作类似于铁路调车场。 Dijkstra首先在Mathematisch Centrum报告MR 34/61中描述了Shunting Yard算法。

像RPN的评估一样,调车场算法是基于堆栈的。 中缀表达式是大多数人习惯的数学符号形式,例如“ 3 + 4”或“ 3 + 4 *(2-1)”。 为了进行转换,有两个文本变量(字符串),输入和输出。 还有一个堆栈,用于保存尚未添加到输出队列的运算符。 要进行转换,程序将按顺序读取每个符号并根据该符号执行某些操作。 以上示例的结果为“ 3 4 +”或“ 3 4 2 1-* +”。

调车场算法后来被推广到操作员优先级解析中。

该代码,因为有人指出,这不是存储它的方法(如果您不喜欢C,请从http://rosettacode.org/wiki/Parsing/Shunting-yard_algorithm中选择 ):

#include <sys/types.h>
#include <regex.h>
#include <stdio.h>

typedef struct {
    const char *s;
    int len, prec, assoc;
} str_tok_t;

typedef struct {
    const char * str;
    int assoc, prec;
    regex_t re;
} pat_t;

enum assoc { A_NONE, A_L, A_R };
pat_t pat_eos = {"", A_NONE, 0};

pat_t pat_ops[] = {
    {"^\\)",    A_NONE, -1},
    {"^\\*\\*", A_R, 3},
    {"^\\^",    A_R, 3},
    {"^\\*",    A_L, 2},
    {"^/",      A_L, 2},
    {"^\\+",    A_L, 1},
    {"^-",      A_L, 1},
    {0}
};

pat_t pat_arg[] = {
    {"^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?"},
    {"^[a-zA-Z_][a-zA-Z_0-9]*"},
    {"^\\(", A_L, -1},
    {0}
};

str_tok_t stack[256]; /* assume these are big enough */
str_tok_t queue[256];
int l_queue, l_stack;
#define qpush(x) queue[l_queue++] = x
#define spush(x) stack[l_stack++] = x
#define spop()   stack[--l_stack]

void display(const char *s)
{
    int i;
    printf("\033[1;1H\033[JText | %s", s);
    printf("\nStack| ");
    for (i = 0; i < l_stack; i++)
        printf("%.*s ", stack[i].len, stack[i].s); // uses C99 format strings
    printf("\nQueue| ");
    for (i = 0; i < l_queue; i++)
        printf("%.*s ", queue[i].len, queue[i].s);
    puts("\n\n<press enter>");
    getchar();
}

int prec_booster;

#define fail(s1, s2) {fprintf(stderr, "[Error %s] %s\n", s1, s2); return 0;}

int init(void)
{
    int i;
    pat_t *p;

    for (i = 0, p = pat_ops; p[i].str; i++)
        if (regcomp(&(p[i].re), p[i].str, REG_NEWLINE|REG_EXTENDED))
            fail("comp", p[i].str);

    for (i = 0, p = pat_arg; p[i].str; i++)
        if (regcomp(&(p[i].re), p[i].str, REG_NEWLINE|REG_EXTENDED))
            fail("comp", p[i].str);

    return 1;
}

pat_t* match(const char *s, pat_t *p, str_tok_t * t, const char **e)
{
    int i;
    regmatch_t m;

    while (*s == ' ') s++;
    *e = s;

    if (!*s) return &pat_eos;

    for (i = 0; p[i].str; i++) {
        if (regexec(&(p[i].re), s, 1, &m, REG_NOTEOL))
            continue;
        t->s = s;
        *e = s + (t->len = m.rm_eo - m.rm_so);
        return p + i;
    }
    return 0;
}

int parse(const char *s) {
    pat_t *p;
    str_tok_t *t, tok;

    prec_booster = l_queue = 0;
    display(s);
    while (*s) {
        p = match(s, pat_arg, &tok, &s);
        if (!p || p == &pat_eos) fail("parse arg", s);

        /* Odd logic here. Don't actually stack the parens: don't need to. */
        if (p->prec == -1) {
            prec_booster += 100;
            continue;
        }
        qpush(tok);
        display(s);

re_op:      p = match(s, pat_ops, &tok, &s);
        if (!p) fail("parse op", s);

        tok.assoc = p->assoc;
        tok.prec = p->prec;

        if (p->prec > 0)
            tok.prec = p->prec + prec_booster;
        else if (p->prec == -1) {
            if (prec_booster < 100)
                fail("unmatched )", s);
            tok.prec = prec_booster;
        }

        while (l_stack) {
            t = stack + l_stack - 1;
            if (!(t->prec == tok.prec && t->assoc == A_L)
                    && t->prec <= tok.prec)
                break;
            qpush(spop());
            display(s);
        }

        if (p->prec == -1) {
            prec_booster -= 100;
            goto re_op;
        }

        if (!p->prec) {
            display(s);
            if (prec_booster)
                fail("unmatched (", s);
            return 1;
        }

        spush(tok);
        display(s);
    }

    return 1;
}

int main()
{
    int i;
    const char *tests[] = { 
        "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3",    /* RC mandated: OK */
        "123",                  /* OK */
        "3+4 * 2 / ( 1 - 5 ) ^ 2 ^ 3.14",   /* OK */
        "(((((((1+2+3**(4 + 5))))))",       /* bad parens */
        "a^(b + c/d * .1e5)!",          /* unknown op */
        "(1**2)**3",                /* OK */
        0
    };

    if (!init()) return 1;
    for (i = 0; tests[i]; i++) {
        printf("Testing string `%s'   <enter>\n", tests[i]);
        getchar();

        printf("string `%s': %s\n\n", tests[i],
            parse(tests[i]) ? "Ok" : "Error");
    }

    return 0;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM