乐趣区

关于go:手写编程语言实现运算符重载

前言

先带来日常的 GScript 更新:新增了可变参数的个性,语法如下:

int add(string s, int ...num){println(s);
    int sum = 0;
    for(int i=0;i<len(num);i++){int v = num[i];
        sum = sum+v;
    }
    return sum;
}
int x = add("abc", 1,2,3,4);
println(x);
assertEqual(x, 10);

得益于可变参数,所以新增了格式化字符串的内置函数:

//formats according to a format specifier and writes to standard output.
printf(string format, any ...a){}

//formats according to a format specifier and returns the resulting string.
string sprintf(string format, any ...a){}

上面重点看看 GScript 所反对的运算符重载是如何实现的。

应用

运算符重载其实也是多态的一种表现形式,咱们能够重写运算符的重载函数,从而扭转他们的计算规定。

println(100+2*2);

以这段代码的运算符为例,输入的后果天然是:104.

但如果咱们是对两个对象进行计算呢,举个例子:

class Person{
    int age;
    Person(int a){age = a;}
}
Person p1 = Person(10);
Person p2 = Person(20);
Person p3 = p1+p2;

这样的写法在 Java/Go 中都会报编译谬误,这是因为他们两者都不反对运算符重载;

Python/C# 是反对的,相比之下我感觉 C# 的实现形式更合乎 GScript 语法,所以参考 C# 实现了以下的语法规定。

Person operator + (Person p1, Person p2){Person pp = Person(p1.age+p2.age);
    return pp;
}
Person p3 = p1+p2;
println("p3.age="+p3.age);
assertEqual(p3.age, 30);

有几个硬性条件:

  • 函数名必须是 operator
  • 名称后跟上运算符即可。

目前反对的运算符有:+-*/ == != < <= > >=

实现

以前在应用 Python 运算符重载时就有想过它是如何实现的?但没有深究,这次借着本人实现相干性能从而须要深刻了解。

其中重点就为两步:

  1. 编译期间:记录所有的重载函数和运算符的关系。
  2. 运行期:依据以后的运算找到申明的函数,间接运行即可。

第一步的重点是扫描所有的重载函数,将重载函数与运算符寄存起来,须要关注的是函数的返回值与运算符类型。

// OpOverload 重载符
type OpOverload struct {
    function  *Func
    tokenType int
}

// 运算符重载自定义函数
opOverloads []*symbol.OpOverload

在编译器中应用一个切片寄存。

而在运行期中当两个入参类型雷同时,则须要查找重载函数。

// GetOpFunction 获取运算符重载函数
// 通过返回值以及运算符号 (+-*/) 匹配重载函数
func (a *AnnotatedTree) GetOpFunction(returnType symbol.Type, tokenType int) *symbol.Func {
    for _, overload := range a.opOverloads {isType := overload.GetFunc().GetReturnType().IsType(returnType)
        if isType && overload.GetTokenType() == tokenType {return overload.GetFunc()
        }
    }
    return nil
}

查找形式就是通过编译期寄存的数据进行匹配,拿到重载函数后主动调用便实现了重载。

感兴趣的敌人能够查看相干代码:

  • 编译期:https://github.com/crossoverJie/gscript/blob/ae729ce7d4cf39fe115121993fcd2222716755e5/resolver/type_scope_resolver.go#L127
  • 运行期:https://github.com/crossoverJie/gscript/blob/499236af549be47ff827c6d55de1fc8e5600b9b3/visitor.go#L387

总结

运算符重载其实并不是一个罕用的性能;因为会扭转运算符的语义,比方明明是加法却在重载函数中写为减法。

这会使得代码浏览起来艰难,但在某些状况下咱们又十分心愿语言自身能反对运算符重载。

比方在 Go 中罕用的一个第三方精度库 decimal.Decimal,进行运算时只能应用 d1.Add(d2) 这样的函数,当运算简单时:

a5 = (a1.Add(a2).Add(a3)).Mul(a4);
a5 = (a1+a2+a3)*a4;

就不如上面这种直观,所以有利有弊吧,多一个选项总不是好事。

GScript 源码:
https://github.com/crossoverJie/gscript

退出移动版