自定义解释型语言中this/self关键字的实现方案咨询
C# 实现自定义解释器
self关键字与OOP访问控制方案 你最初的两个思路方向没有问题,但全局存储实例的设计会引入内存泄漏、上下文串扰问题,不需要绕这个弯路,主流语言实现实例上下文的核心逻辑是方法调用时隐式注入实例引用到当前栈帧,整套实现可以完全对齐C#本身的OOP运行逻辑,步骤如下:
1. 先定义核心运行时元数据结构
直接用C#类定义解释器运行时需要的核心类型,不需要额外复杂设计:
// 访问修饰符枚举,对应你语法里的pub/prv public enum AccessLevel { Public, Private } // 类/结构体的元数据定义,编译阶段生成 public class ClassMeta { public string ClassName { get; set; } // 方法元数据表:key为方法名,存储方法体AST、形参列表、访问级别 public Dictionary<string, MethodMeta> MethodTable { get; } = new(); // 字段元数据表:key为字段名,存储字段访问级别 public Dictionary<string, AccessLevel> FieldAccessTable { get; } = new(); } // 方法元数据,编译阶段生成 public class MethodMeta { public IAstNode Body { get; set; } // 方法对应的AST语法树节点 public List<string> ParamNames { get; set; } = new(); // 形参名列表 public AccessLevel Access { get; set; } } // 运行时类实例,解释执行阶段每次new生成 public class RuntimeInstance { public ClassMeta Class { get; init; } // 实例所属的类元数据 // 实例字段值存储:key为字段名,value为实际存储的值 public Dictionary<string, object> Fields { get; } = new(); } // 调用栈帧,解释执行时每次函数/方法调用生成 public class StackFrame { // 局部变量存储:包含形参、方法内定义的变量、隐式注入的self public Dictionary<string, object> Locals { get; } = new(); // 当前栈帧所属的类元数据:只有实例方法调用时会赋值,全局函数为null public ClassMeta CurrentClass { get; set; } }
2. 实现self的隐式注入逻辑
不需要全局维护实例字典,self的注入完全在方法调用的逻辑里完成:
- 当解释到实例方法调用逻辑(比如示例中的
$dog.run 30)时,按以下步骤处理:- 先做第一层访问校验:如果是类外部调用(即当前执行的栈帧
CurrentClass为null,或者和被调用方法所属类不一致),只有方法访问级别是Public才允许调用,否则抛出权限错误。 - 新建本次方法调用的栈帧,给新栈帧的
CurrentClass赋值为实例对应的ClassMeta。 - 往新栈帧的
Locals里注入一个特殊变量:键名为self,值为当前调用的RuntimeInstance对象,这个变量不需要在方法参数里声明,解释器自动注入。 - 把调用传入的实参按形参顺序绑定到新栈帧的
Locals里,推入调用栈开始执行方法体。
- 先做第一层访问校验:如果是类外部调用(即当前执行的栈帧
- 注意:全局函数(比如示例里的
main,定义在struct外部)调用时,新栈帧的CurrentClass设为null,不需要注入self变量。 - 当解释器在方法体内解析到
$self标识符时,直接从当前栈帧的Locals里取提前注入的实例值即可,不需要做任何特殊处理。
3. 调整访问控制校验逻辑,支持私有成员内部访问
不需要给self关键字开跳过校验的特殊后门,把访问校验逻辑统一成判断「当前执行上下文是否属于目标成员的所属类」即可,逻辑严谨无漏洞:
- 所有实例成员(字段、方法)访问前,统一执行以下校验:
- 取出当前调用栈顶的栈帧,拿到
CurrentClass值 - 取出目标成员所属的类元数据
- 如果当前栈帧的
CurrentClass和目标成员所属类是同一个对象(即引用相等),无论成员是Public还是Private,直接允许访问 - 如果不是同一个类,只有目标成员访问级别为
Public时允许访问,否则抛出非法访问错误
- 取出当前调用栈顶的栈帧,拿到
- 这套逻辑不仅支持
$self调用私有方法,后续如果要实现同类实例互访私有成员、嵌套类访问外部类私有成员等特性,都可以直接扩展,和C#、Java等主流OOP语言的访问控制逻辑完全对齐。
4. 对应你给出的示例的执行流程验证
按上述逻辑跑你写的Dog示例,完全符合预期:
- 执行
new Dog时,生成一个RuntimeInstance,关联Dog对应的ClassMeta,赋值给$dog变量 - 执行
7 -> $dog.age时,当前栈帧是main函数的栈帧,CurrentClass为null,检查age字段是Public,允许赋值 - 执行
$dog.run 30时,检查run方法是Public,允许外部调用;新建run方法的栈帧,注入self为dog实例,设置栈帧CurrentClass为Dog的元数据,绑定形参dist=30,开始执行run方法体 - run方法内循环执行到
$self.bark()时,取当前栈帧CurrentClass是Dog,bark方法所属类也是Dog,虽然bark是Private,直接允许调用;调用bark时同样新建栈帧,注入同一个self实例,执行吠叫的打印逻辑 - 所有逻辑执行完成,不会出现权限错误,也不会有实例生命周期问题。
不推荐全局实例字典方案的原因
- 全局字典持有所有实例引用,会导致实例失去引用后无法被垃圾回收,存在内存泄漏问题
- 多线程并行解释执行时,容易出现实例上下文串扰
- 实例作为参数、返回值跨作用域传递时,需要额外维护实例唯一ID和生命周期,增加不必要的复杂度
内容的提问来源于stack exchange,提问作者kamkow1
相关产品推荐
相关产品推荐

