类型系统

类型系统是一门语言所有的类型的集合,操作这些类型的规则,以及类型之间怎么相互作用

  • 根据领域的需求,设计自己的类型系统的特征
  • 在编译器中支持类型检查、类型推导和类型转换

    设计类型系统

    类型:是针对一组数值,以及在这组数值之上的一组操作
    定规矩:可以检查施加在数据上的操作是否合法,通过类型检查降低计算出错的概率

    取舍和权衡

  • 类型都是对象/支持非对象化的基础数据类型

  • 字符串是原生数据类型/是一个普通的类
  • 是静态类型语言/是动态类型语言
    • 静态类型语言(全部或者几乎全部的类型检查是在编译期进行的)
      • 程序错误较少(检查类型是否匹配,以及进行缺省的类型转换)
      • 性能更高(不需要在运行时再去做类型检查和转换)
    • 动态类型语言(类型的检查是在运行期进行的)
      • 语言太严格
      • 编程效率低(要一遍遍编译)
    • 强类型
    • 弱类型
  • 是否符合这门语言想解决的问题

    类型检查、类型推导和类型转换

    1. a = b + 10

    类型推导(Type Inference)

    如果 b 是一个浮点型,b+10 的结果也是浮点型。如果 b 是字符串型,实际的结果是字符串的连接 ```javascript case ScriptParser.ADD: if (type1 == PrimitiveType.String || type2 == PrimitiveType.String){
    1. type = PrimitiveType.String;
    } else if (type1 instanceof PrimitiveType && type2 instanceof PrimitiveType){
    1. // 类型“向上”对齐,比如一个int和一个float,取float
    2. type = PrimitiveType.getUpperType(type1,type2);
    } else {
    1. console.log("operand should be PrimitiveType for additive operation");
    } break;

private Object add(Object obj1, Object obj2, Type targetType) { Object rtn = null; if (targetType == PrimitiveType.String) { result = String.valueOf(obj1) + String.valueOf(obj2); } else if (targetType == PrimitiveType.Integer) { result = ((Number)obj1).intValue() + ((Number)obj2).intValue(); } else if (targetType == PrimitiveType.Float) { result = ((Number)obj1).floatValue()+ ((Number)obj2).floatValue(); } … return result; }

  1. <a name="lbS8M"></a>
  2. #### S 属性(Synthesized Attribute)
  3. 如果一种属性能够从下级节点推导出来,那么这种属性就叫做 S 属性,是通过下级节点和自身来确定的<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/155952/1644497749131-161042a8-8bbf-45df-9f58-76a1dd0937c8.png#clientId=ue5b26832-0c31-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=418&id=uf4e0390b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=1108&originalType=binary&ratio=1&rotation=0&showTitle=false&size=255754&status=done&style=none&taskId=u7a0a02e7-3b5d-4dcf-91a1-dbbc98f8d43&title=&width=1108)
  4. <a name="KUJSr"></a>
  5. #### I 属性(Inherited Attribute)
  6. AST 中某个节点的属性是由上级节点、兄弟节点和它自身来决定的
  7. ```javascript
  8. int a;
  1. variableDeclarators
  2. : typeType variableDeclarator (',' variableDeclarator)*
  3. ;
  4. variableDeclarator
  5. : variableDeclaratorId ('=' variableInitializer)?
  6. ;
  7. variableDeclaratorId
  8. : IDENTIFIER ('[' ']')*
  9. ;
  10. typeType
  11. : (classOrInterfaceType| functionType | primitiveType) ('[' ']')*
  12. ;

image.png

  1. // Go 语言两种声明变量的方式
  2. var a int = 10 //第一种
  3. a := 10 //第二种

类型检查(Type Checking)

当右边的值计算完,赋值给 a 的时候,要检查左右两边的类型是否匹配

  • 赋值语句(检查赋值操作左边和右边的类型是否匹配)
  • 变量声明语句(因为变量声明语句中也会有初始化部分,所以也需要类型匹配)
  • 函数传参(调用函数的时候,传入的参数要符合形参的要求)
  • 函数返回值(从函数中返回一个值的时候,要符合函数返回值的规定)

    类型转换(Type Conversion)

    如果 a 的类型是浮点型,而右边传过来的是整型,一般要进行缺省的类型转换
    1. // MySQL 自动将'2'转换成了数字
    2. select 1 + '2';
    强制做类型转换,只有到运行期才能检查出错误

    引用消解

    在程序里使用变量、函数、类等符号时,我们需要知道它们指的是谁,要能对应到定义它们的地方 ```javascript

    include

int a = 1;

void main() { a = 2; int a = 3; int b = a; printf(“in func: a=%d b=%d \n”, a, b); }

// output in func: a=3 b=3

  1. <a name="Q0UT0"></a>
  2. ### 变量的引用消解
  3. 比对变量
  4. <a name="TnTb2"></a>
  5. ### 函数的引用消解
  6. 比对函数名称,参数和返回值(可以叫函数原型,或者函数的类型)<br />需要返回值、参数个数、每个参数的类型都能匹配
  7. 面向对象编程语言:当一个参数需要一个对象时,程序中提供其子类的一个实例也可以
  8. ```javascript
  9. class MyClass1{} //父类
  10. class MyClass2 extends MyClass1{} //子类
  11. MyClass1 obj1;
  12. MyClass2 obj2;
  13. function fun(MyClass1 obj){} //参数需要父类的实例
  14. fun(obj2); //提供子类的实例

强类型编程语言:考虑某个实参是否能够被自动转换成形参所要求的类型(需要 double 类型的地方,传一个 int 也可以)
命名空间:

  1. play.PlayScriptCompiler.Compile() //Java语言
  2. play::PlayScriptCompiler.Compile() //C++语言

左值和右值

左值

  1. a + 3;
  2. a = 3;

第3行应该取出 a 的地址,或者说 a 的引用,然后用赋值操作把 3 这个值写到 a 的内存地址。这时,我们说取出来的是 a 的左值(L-value)

  • 赋值表达式的左边
  • 带有初始化的变量声明语句中的变量
  • 当给函数形参赋值的时候
  • 一元操作符: ++ 和 ––
  • 其他需要改变变量内容的操作

判断表达式是否能生成合格的左值:
出现在赋值语句左边的,必须是能够获得左值的表达式

  • 一个变量
  • 一个类的属性
  • 2+3

    右值

    就是我们通常所说的值,不是地址

    属性计算

    属性计算是做上下文分析,或者说语义分析的一种算法

  • 它的变量定义是哪个(这就引用到定义该变量的 Symbol)

  • 它的类型是什么
  • 它的作用域是什么
  • 这个节点求值时,是否该返回左值
  • 能否正确地返回一个左值
  • 它的值是什么

    属性文法(Attribute Grammar)

    正则文法,上下文无关文法
    属性文法:在上下文无关文法的基础上做了一些增强,使之能够计算属性值 ```javascript // 上下文无关文法表达加法和乘法运算的例子 add → add + mul add → mul mul → mul * primary mul → primary primary → “(“ add “)” primary → integer

// 对 value 属性进行计算的属性文法 add1 → add1 + mul [ add1.value = add2.value + mul.value ] add → mul [ add.value = mul.value ] mul1 → mul2 primary [ mul1.value = mul2.value primary.value ] mul → primary [ mul.value = primary.value ] primary → “(“ add “)” [ primary.value = add.value ] primary → integer [ primary.value = strToInt(integer.str) ] ``` 特点:它会基于语法规则,增加一些与语义处理有关的规则

语义分析过程

类型和作用域解析

把自定义类、函数和和作用域的树都分析出来
可以使用在前,声明在后

类型的消解

把所有出现引用到类型的地方都消解掉
变量声明、函数参数声明、类的继承等等

引用的消解和 S 属性的类型的推导

对所有的变量、函数调用,都跟它的定义关联起来,并完成所有的类型计算

做类型检查

当赋值语句左右两边的类型不兼容的时候报错

做一些语义合法性的检查

  • break 只能出现在循环语句中
  • 某个函数声明了返回值一定要有 return 语句