语义分析

语义分析是编译过程的一个逻辑阶段,语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查,进行类型审查。语义分析是审查源程序有无语义错误,为代码生成阶段收集类型信息。比如语义分析的一个工作是进行类型审查,审查每个算符是否具有语言规范允许的运算对象,当不符合语言规范时,编译程序应报告错误。如有的编译程序要对实数用作数组下标的情况报告错误。又比如某些某些程序规定运算对象可被强制,那么当二目运算施于一整型和一实型对象时,编译程序应将整型转换为实型而不能认为是源程序的错误。

符号表

符号表是用来存放源程序中出现的有关名字的属性信息,这些信息集中反映了名字的语义特征属性。符号表在编译全过程的地位和作用非常重要,是进行上下文合法性检查和语义处理及代码生成的依据。符号表总体结构的设计和实现是与源语言的复杂性(包括词法结构、语法结构的复杂性)有关,还与对于编译系统在时间效率和空间效率方面的要求有关。

符号表有多种表示方式,而该程序需要用到嵌套作用于,所以符号表应该如下:

public class Scope {
	public Scope parent;
	
	private HashMap<String, Type> map;
	
	public Scope(Scope parent) {
		this.parent = parent;
		this.map = new HashMap<String, Type>();
	}
	
	public void put(String name, Type type) {
		this.map.put(name, type);
	}
	
	public Type findInCurrent(String name) {
		if (!this.map.containsKey(name)) {
			return Type.NOT_FOUND;
		} else {
			return this.map.get(name);
		}
	}
	
	public Type find(String name) {
		Type type = this.findInCurrent(name);
		if (type == Type.NOT_FOUND) {
			if (this.parent != null) {
				return parent.find(name);
			}
		}
		return type;
	}
	
	public enum Type {
		ID,
		INT,
		CHAR,
		FLOAT,
		STRING,
		ARRAY,
		FUNCTION,
		NOT_FOUND
	}
}

整个符号表呈现树形状,不过其中通过 parent 与父节点建立连接,这也方便遍时后回溯。这样,在每次定义变量、函数时将其名称及相关数据记录进符号表:

public void visit(VarDecl s) {
	s.exp.accept(this);
	scope.put(s.id, Type.ID);
}

其中如果当前scope中s.id的值已经定义则报错。每次使用时查找是否进行定义:

public void visit(Id id) {
	Type type = scope.find(id.id);
	if (type == Type.NOT_FOUND) {
		Error.instance().PrintMsg("var " + id.id + " not defined!");
	}
	this.type = type;
}

作用域

按照上面的符号表构建,当查找变量时,首先在当前作用于中遍历一次,没有找到则遍历父节点。通过这种方式,可以实现作用域屏蔽:

var x = 1;
function func() {
	var x = 1.0f;
}
x == 1;

在内层作用域中,并不会对外部数据进行覆盖。

类型检查

类型检查主要在两个部分:语义分析、运行时类型检查。语义分析部分主要针对的是常量部分的类型检查如"string" + 1这样的用法错误。而变量等存在如下情况:

var x = 1;
if (condition) {
	x = "string";
}
func(x);

在调用func(x)时,无法得知当前的x的具体类型,所以这部分需要交给运行时类型检查完成。而对于如下的运算,需要进行类型转换:

var x = 1 + 0.5;
var y = 'c' + 1;

因为只有数值类型可以进行类型相互转换,所以在判断时:

private boolean numberic(Type type) {
	return (type == Type.CHAR || type == Type.INT || type == Type.FLOAT);
}

private void needType(Type type) {
	if (this.type != type && this.type != Type.ID) {
		if (!numberic(type) || !numberic(this.type)) {
			Error.instance().PrintMsg("need " + type.toString() + " but get " + this.type.toString());
		}
	}
}

private Type maxType(Type left, Type right) {
	if (left == right) {
		return left;
	} else if (left == Type.FLOAT || right == Type.FLOAT) {
		return Type.FLOAT;
	} else if (left == Type.INT || right == Type.INT) {
		return Type.INT;
	} else {
		return Type.CHAR;
	}
}

在判断是否指定类型(needType)时,如果不是相同类型、并且当前类型并不是ID(即不能判断),且双方都不是数值类型,那么肯定错误。当双方都是数值类型时,可以通过maxType计算返回值类型(其中有类型提升)。