用Python+Sly开发脚本解释器:如何为赋值左值建立引用变量?
解决赋值左值统一处理的方案
针对你用Python+Sly开发解释器时遇到的赋值左值冗余问题,核心思路是把所有左值解析为「可变容器+访问键/索引」的组合,通过这一组信息直接完成赋值,彻底避免重复遍历左值的流程。
具体实现思路
不管是普通变量还是嵌套对象/数组字段,最终都可以拆解为「存储值的可变容器」和「访问该值的键/索引」。比如:
bob→ 容器是当前作用域的变量字典,键是"bob"this.speed→ 容器是this对象的字段字典,键是"speed"eachthing.child[prevRef+2].name→ 容器是eachthing.child[prevRef+2]对应的对象字段字典,键是"name"
基于这个逻辑,你可以在Sly中写递归的左值解析规则,一次性完成左值的遍历、验证,并返回最终的容器和键/索引:
# 自定义对象和数组类(用可变结构存字段/元素) class ScriptObject: def __init__(self): self.fields = {} # 可变字典,存对象字段 class ScriptArray: def __init__(self, elements): self.elements = list(elements) # 可变列表,存数组元素 # Sly的左值解析规则 @_('ID') def lvalue(self, p): # 普通变量:容器是当前作用域的变量字典 return (self.current_scope.vars, p.ID) @_('lvalue "." ID') def lvalue(self, p): # 对象字段访问:递归解析前面的左值,拿到父容器和键 parent_container, parent_key = p.lvalue parent_obj = parent_container[parent_key] # 验证父对象类型合法 if not isinstance(parent_obj, ScriptObject): raise SyntaxError(f"{parent_key} is not an object") # 返回当前字段的容器(对象的字段字典)和键 return (parent_obj.fields, p.ID) @_('lvalue "[" expr "]"') def lvalue(self, p): # 数组索引访问:递归解析前面的左值,拿到父容器和键 parent_container, parent_key = p.lvalue parent_arr = parent_container[parent_key] # 验证父对象是数组 if not isinstance(parent_arr, ScriptArray): raise SyntaxError(f"{parent_key} is not an array") # 计算索引值并验证合法性 index = self.evaluate(p.expr) if not isinstance(index, int) or index < 0 or index >= len(parent_arr.elements): raise IndexError(f"Invalid index {index}") # 返回数组元素的容器(数组的元素列表)和索引 return (parent_arr.elements, index)
然后处理赋值语句时,流程就完全统一了:
@_('lvalue "=" expr') def assignment(self, p): # 1. 从左值解析结果拿到容器和键/索引(已完成所有合法性验证) target_container, target_key = p.lvalue # 2. 计算右值 right_value = self.evaluate(p.expr) # 3. 类型校验(根据你的语言规则实现,比如变量的预期类型) expected_type = self.get_expected_type(target_container, target_key) if expected_type and not isinstance(right_value, expected_type): raise TypeError(f"Expected {expected_type.__name__}, got {type(right_value).__name__}") # 4. 直接赋值,无需重复遍历左值 target_container[target_key] = right_value
为什么这个方案可行?
Python的可变对象(字典、列表、自定义类的可变属性)本身就支持这种"间接修改"——你拿到的容器是原对象的引用,修改容器里的键/索引值,就是直接修改原变量/对象字段的值,完全不需要额外的指针或索引存储机制。
这种方式把左值的遍历、验证和赋值彻底解耦,既消除了流程冗余,又符合解释器的执行逻辑。
内容的提问来源于stack exchange,提问作者Sean Duggan
相关产品推荐
相关产品推荐

