解释器中的运算符

Operators in an interpreter

我做翻译只是为了好玩。首先,我正在尝试评估表达式。评估 returns 一个 Value 对象,每个类型都有自己的 Value 结构。例如:

struct Value  // This is the abstract base class for every value type
{
     int type;
};

struct IntegerValue : public Value
{
     int value;

     IntegerValue(int value) : value(value), type(VALUE_INTEGER) {}
};

我不知道这是否是一个不错的设计(可能不是),但目前可以使用。但是当我定义新的类型和运算符时,评估方法变得庞大。例如,在运算符“==”中,左侧和右侧可以是字符串、整数、浮点数等等……所以我想我需要为值结构定义运算符而不是在 eval 方法中检查它们(甚至可能允许像 c++ 中那样的用户定义运算符),但我想不出一个快速、优雅且易于扩展的设计。有什么想法吗?

在我的 postscript 解释器(用 C 编写)中,我将我的类型化对象定义为联合,这样我就可以仔细安排成员覆盖相同的内存。

union {
    word tag;
    struct { word tag; word pad0; int val; } _int;
    struct { word tag; word pad0; float val; } _real;
    //...
} object;

你可能不需要在你的项目中如此注重记忆,但我已经从这个结构中得到了很多好处。


至于处理可能发生的类型组合的爆炸式增长。我最近在我的 APL 解释器中实现了几种数字类型,并借助宏(隐藏了更多代码),3 种可能的类型变成了 9 种不同的情况:

/* apply binary math op to nums, yielding num
TODO: additional numeric types.
    configurable overflow promotion handling.
 */
#define BIN_MATH_FUNC(func,z,x,y,overflow,domainI,domainD) \
     switch(NUMERIC_TYPES(x,y)){ \
     case TYPEPAIR(IMM,IMM): DOM(domainI,z,numimm(x),numimm(y)) \
                             if (overflow(numimm(x),numimm(y))) \
                                 z=flo((D)numimm(x) func (D)numimm(y)); \
                             else z=num(numimm(x) func numimm(y)); break; \
     case TYPEPAIR(IMM,FIX): DOM(domainI,z,numimm(x),numint(y)) \
                             if (overflow(numimm(x),numint(y))) \
                                 z=flo((D)numimm(x) func (D)numint(y)); \
                             else z=num(numimm(x) func numint(y)); break; \
     case TYPEPAIR(IMM,FLO): DOM(domainD,z,numimm(x),numdbl(y)) \
                             z=flo(numimm(x) func numdbl(y)); break; \
     case TYPEPAIR(FIX,IMM): DOM(domainI,z,numint(x),numimm(y)) \
                             if (overflow(numint(x),numimm(y))) \
                                 z=flo((D)numint(x) func (D)numimm(y)); \
                             else z=num(numint(x) func numimm(y)); break; \
     case TYPEPAIR(FIX,FIX): DOM(domainI,z,numint(x),numint(y)) \
                             if (overflow(numint(x),numint(y))) \
                                 z=flo((D)numint(x) func (D)numint(y)); \
                             else z=num(numint(x) func numint(y)); break; \
     case TYPEPAIR(FIX,FLO): DOM(domainD,z,numint(x),numdbl(y)) \
                             z=flo(numint(x) func numdbl(y)); break; \
     case TYPEPAIR(FLO,IMM): DOM(domainD,z,numdbl(x),numimm(y)) \
                             z=flo(numdbl(x) func numimm(y)); break; \
     case TYPEPAIR(FLO,FIX): DOM(domainD,z,numdbl(x),numint(y)) \
                             z=flo(numdbl(x) func numint(y)); break; \
     case TYPEPAIR(FLO,FLO): DOM(domainD,z,numdbl(x),numdbl(y)) \
                             z=flo(numdbl(x) func numdbl(y)); break; \
     }    

宏的 func 参数是一个 C 数学运算符,如 +*%。所以我只需要在需要浮点数学的地方提供浮点数,或者在需要整数数学的地方提供整数。 domain? 函数仅用于检测被零除。

TYPEPAIR 辅助宏非常有用,但可能不太清楚它应该如何工作。参数是 enum 值,C 版本的原子符号,表示为小整数。所以这里我只需要区分3种数字类型,所以我为它们做了一个enum

enum { IMM = 1, FIX, FLO, NTYPES };

通常 enum 值从 0 开始分配,但对于数学技巧,我希望这些值从 1 开始。然后我可以将这些值视为 数字系统 NTYPES 作为基数或基数。使用这样定义的符号,我可以将 2-digit 类型计算为单个数值。这是一个常量值,所以如果通过宏计算,可以作为switch case。

#define TYPEPAIR(a,b)  ((a)*NTYPES+(b))

它也可以组成一个更大的类型模式数字表示。

TYPEPAIR(TYPEPAIR(IMM,FIX),FLO)

扩展为

((((IMM)*NTYPES+(FIX)))*NTYPES+(FLO))

这种东西让我可以用更少的代码匹配更大的事物模式。而不是像

if (TYPE(x)==IMM && TYPE(y)==FIX) //...
//...

无法表示为 switch