制作解释器:静态类型变量和操作

时间:2019-05-02 19:17:51

标签: c++ interpreter static-typing

我正在为类似c的自定义语言(我们称其为mylang)进行解释。 另外,我知道“编译”是一个错误的词,可用于解释器,但在引用文本解析和稍后执行的抽象语法树的构造时,我会使用它。

我已经制作了分词器,有一个生成ast的解析器,最终会出现解析错误。 现在,所有ast语句类都继承一个“ exec”函数,而所有与表达式相关的类(运算符,操作数,变量名,函数调用)都继承一个“ eval”函数,该函数返回一个“变量”类实例。 变量分为两部分:包含其类型的枚举(该语言将具有许多c ++类型,它们都将在枚举中以“ t_”命名),例如t_int,t_float,t_string,t_unsigned_long等;以及该类型的实际值。

运行mylang程序分为2个步骤: -解析文件(随着解析器的进行,逐个令牌地使用令牌生成器)。 -调用exec作为ast的根。包含表达式的语句将调用其持有的表达式的eval。

这很简单,但是它会导致很多错误,而这些错误只会在运行时发生,而我宁愿在编译时进行。 现在,二进制操作元素的“ eval”函数是一个很长的“ if-else”链,它调用其操作数的exec,然后检查返回变量的类型以查看它们是否相同,static_cast对该类型执行操作,或者如果它们不同,则抛出错误,这是我希望在编译时遇到的错误。当前的工作方式也迫使我首先对两个操作数求值,以便知道我应该执行哪个操作,而这又使我无法对布尔表达式执行延迟求值。

//inte namespace has interpreter classes
//var namespace contains the virtual var::root, from which a class for each type inherits. var::_float has type = t_float and space for a float.
//bin is the binary operator class
//function scope is passed to all evals so that should a variable name appear, that element of the ast can retrive that variable's value from the current scope.
inte::var::root * bin::eval(inte::function_scope * fs)
    {
    //here i'm evaluating the 2 operands
    inte::var::root* ll = left->eval(fs);
    inte::var::root* rr = right->eval(fs);

    //if both operands are of type int
    if ((ll->t == inte::var::t_int) && (rr->t == inte::var::t_int))
        {
        //cast the operands to int type value class (based on var::root, with an added int value).
        auto lln = static_cast<inte::var::_int*>(ll);
        auto rrn = static_cast<inte::var::_int*>(rr);
        //the operation will return an int type value
        auto ret = new inte::var::_int();

        //check the operation's operand, perform the operation and return
        switch (op.t)
            {
            case tok::sum:      ret->value = lln->value + rrn->value; return ret;
            case tok::mul:      ret->value = lln->value * rrn->value; return ret;
            case tok::mod:      ret->value = lln->value % rrn->value; return ret;
            case tok::assign:   ret->value = lln->value = rrn->value; return ret;
            ...
            }
        }
    else if (ll->t == inte::var::t_float && rr->t == inte::var::t_float)
        {...}
    else if (ll->t == inte::var::t_str && rr->t == inte::var::t_str)
        {...}

    //if the operands are of different type, OR, given these types the switch doesn't have that operation for these types (for instance t_float's switch doesn't have % operation)
    //runtime error (which i'd rather have at compile time)
    mylang::inte::error_op(op, ll, rr, nullptr);
    return nullptr;
    }

现在有实际问题:

  1. 这种设计有多糟糕?如此重复使得我认为应该有更好的方法来处理多个变量类型的多个运算符。现在我只有3个人,我什至无法想象当我有3个人时会变成什么样。
  2. 我想在编译时为所有表达式元素调用另一个函数,该函数返回树下直到表达式根的所有实例的类型。这样做会让我在编译时抛出不兼容的类型错误,也让我在树的中间推动转换,同时仍然提示警告,以使int和float之间的总和仍然编译(隐式转换)。但是问题是在编译时,我不知道存在哪些变量以及它们是什么类型,因为仅当“变量声明”的{ {1}}在运行时发生。关于如何解决这个问题的任何想法吗?我应该只在编译时“模拟”所有代码的执行而没有实际值来检查一切是否正常吗?还是有更好的方法?

0 个答案:

没有答案