feat: add managed types, source classes and scoped native calls

This commit is contained in:
2026-10-21 01:47:08 +08:00
parent 7c842789c0
commit a970fd7707
48 changed files with 2069 additions and 134 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
| 文件 | 职责 |
| --- | --- |
| [lib.rs](lib.rs) | CompileOptions、公开分析/编译入口、AST 原文范围检查与带来源诊断 |
| [analysis.rs](analysis.rs) | 模块/函数/表达式/单次交互作用域、局部槽、全局名称、逐引用绑定和初始化检查 |
| [codegen.rs](codegen.rs) | AST 到已有指令、嵌套代码模板、EndorCode 验证、显式 VM 装载与临时根释放 |
| [analysis.rs](analysis.rs) | 模块/函数/类方法/表达式/单次交互作用域、局部槽、全局名称、逐引用绑定和初始化检查 |
| [codegen.rs](codegen.rs) | AST 到函数与类创建指令、嵌套代码模板、EndorCode 验证、显式 VM 装载与临时根释放 |
节点定义在 frontend/Endor.asdl;这里匹配生成的 AST,不复制节点类型。源码和调用方提供的 AST 都先分析后生成,不执行 AST。修改名称语义、代码布局或所有权时同步 [编译契约](../../../docs/runtime/compiler.md)、[检查](../tests/README.md) 与 [示例](../examples/README.md)。返回 [compiler](../README.md)。
+66 -17
View File
@@ -39,7 +39,7 @@ pub struct Scope {
#[derive(Debug, Clone)]
pub struct SymbolTable {
pub root: Scope,
/// Top-level function scopes in declaration order. Nested declarations
/// Function and class-method scopes in declaration order. Nested declarations
/// are rejected until source closure semantics are implemented.
pub functions: Vec<Scope>,
}
@@ -48,9 +48,11 @@ fn error(span: Span, message: impl Into<String>) -> Diagnostic {
diagnostic(DiagnosticKind::Semantic, span, message)
}
fn definition(declaration: &Decl) -> &Function {
let DeclKind::Function(function) = &declaration.kind;
function
fn declaration_name(declaration: &Decl) -> &frontend::ast::Name {
match &declaration.kind {
DeclKind::Function(function) => &function.name,
DeclKind::Class(class) => &class.name,
}
}
/// Resolve a closed source unit plus explicitly declared host globals.
@@ -69,21 +71,25 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
if globals.contains("") {
return Err(error(root.span(), "host global names must not be empty"));
}
let functions: Vec<&Function> = match root {
AstRoot::Module(program) => program.declarations.iter().map(definition).collect(),
let declarations: Vec<&Decl> = match root {
AstRoot::Module(program) => program.declarations.iter().collect(),
AstRoot::Interactive(input) => input
.statements
.iter()
.filter_map(|statement| {
if let StmtKind::Declaration(declaration) = &statement.kind {
Some(definition(declaration))
} else {
None
}
.filter_map(|statement| match &statement.kind {
StmtKind::Declaration(declaration) => Some(declaration),
_ => None,
})
.collect(),
AstRoot::Expression(_) => Vec::new(),
};
let functions: Vec<&Function> = declarations
.iter()
.flat_map(|declaration| match &declaration.kind {
DeclKind::Function(function) => vec![function],
DeclKind::Class(class) => class.methods.iter().collect(),
})
.collect();
let (kind, name) = match root {
AstRoot::Module(_) => (ScopeKind::Module, "<module>"),
AstRoot::Expression(_) => (ScopeKind::Expression, "<expression>"),
@@ -91,10 +97,43 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
};
let mut root_scope = Builder::new(kind, name, root.span());
let mut declared = BTreeSet::new();
for function in &functions {
declare(&mut declared, &function.name.text, function.name.span)?;
globals.insert(function.name.text.clone());
root_scope.intern(&function.name.text);
for declaration in &declarations {
let name = declaration_name(declaration);
declare(&mut declared, &name.text, name.span)?;
globals.insert(name.text.clone());
root_scope.intern(&name.text);
}
for declaration in &declarations {
if let DeclKind::Class(class) = &declaration.kind {
let base_name = class
.base
.as_ref()
.map_or("Object", |base| base.text.as_str());
if !globals.contains(base_name) {
return Err(error(
class.span,
format!("class base is not supplied: {base_name}"),
));
}
root_scope.intern(base_name);
let mut members = BTreeSet::new();
for field in &class.fields {
declare(&mut members, &field.name.text, field.span)?;
}
for method in &class.methods {
declare(&mut members, &method.name.text, method.span)?;
if method
.parameters
.first()
.is_none_or(|parameter| parameter.name.text != "self")
{
return Err(error(
method.span,
"class method requires an explicit first self parameter",
));
}
}
}
}
// Analyze all bodies after registering all functions, enabling forward
// references and recursion without instantiating function objects here.
@@ -119,6 +158,11 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result<SymbolTable,
globals.insert(assignment.name.text.clone());
root_scope.intern(&assignment.name.text);
}
StmtKind::MemberAssignment(assignment) => {
root_scope.expression(&assignment.object, &globals)?;
root_scope.expression(&assignment.value, &globals)?;
root_scope.intern(&assignment.name.text);
}
StmtKind::Declaration(_) => {}
StmtKind::Expression(expression) => {
root_scope.expression(expression, &globals)?
@@ -181,7 +225,7 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
StmtKind::Declaration(_) => {
return Err(error(
statement.span,
"nested function declarations are not compiled yet",
"nested declarations are not compiled yet",
));
}
_ => {}
@@ -210,6 +254,11 @@ fn function_scope(function: &Function, globals: &BTreeSet<String>) -> Result<Sco
}
returned = true;
}
StmtKind::MemberAssignment(assignment) => {
builder.expression(&assignment.object, globals)?;
builder.expression(&assignment.value, globals)?;
builder.intern(&assignment.name.text);
}
StmtKind::Expression(expression) => builder.expression(expression, globals)?,
StmtKind::Declaration(_) => unreachable!("nested declaration rejected above"),
}
+39 -2
View File
@@ -150,7 +150,39 @@ impl<'a> Emitter<'a> {
}
fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> {
let DeclKind::Function(function) = &declaration.kind;
match &declaration.kind {
DeclKind::Function(function) => {
self.function_value(function, symbols)?;
self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text))
}
DeclKind::Class(class) => {
self.constant(Value::Text(class.name.text.clone()))?;
let base = class
.base
.as_ref()
.map_or("Object", |base| base.text.as_str());
self.emit(Opcode::LoadGlobal, self.name_index(base))?;
for field in &class.fields {
self.constant(Value::Text(field.name.text.clone()))?;
self.constant(Value::Text(field.type_name.text.clone()))?;
}
self.emit(Opcode::BuildMap, class.fields.len())?;
for method in &class.methods {
self.constant(Value::Text(method.name.text.clone()))?;
self.function_value(method, symbols)?;
}
self.emit(Opcode::BuildMap, class.methods.len())?;
self.simple(Opcode::BuildClass);
self.emit(Opcode::StoreGlobal, self.name_index(&class.name.text))
}
}
}
fn function_value(
&mut self,
function: &frontend::ast::Function,
symbols: &SymbolTable,
) -> Result<(), Diagnostic> {
let scope = symbols
.functions
.iter()
@@ -176,7 +208,7 @@ impl<'a> Emitter<'a> {
self.children.push((index, child));
self.constant(Value::Nil)?; // Replaced by a managed code object during load().
self.instructions.push(Instruction::make_function(0, 0));
self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text))
Ok(())
}
fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> {
@@ -202,6 +234,11 @@ impl<'a> Emitter<'a> {
StmtKind::Assignment(assignment) => {
self.store_binding(&assignment.name.text, &assignment.value)?
}
StmtKind::MemberAssignment(assignment) => {
self.expression(&assignment.value)?;
self.expression(&assignment.object)?;
self.emit(Opcode::StoreAttr, self.name_index(&assignment.name.text))?;
}
StmtKind::Return(statement) => {
if let Some(value) = &statement.value {
self.expression(value)?;
+2
View File
@@ -1,5 +1,7 @@
# 编译链路检查
[classes.rs](classes.rs) 覆盖源码 class、继承字段构造、动态方法覆盖、绑定接收者保活/回收、绑定后参数数量和重复成员/缺少 self 的预执行诊断。只编译,不在本地运行。
[pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。
裸赋值检查覆盖跨交互输入创建/更新 globals、参数重赋值、局部槽复用及未初始化读取;执行结束检查空堆。
+115
View File
@@ -0,0 +1,115 @@
use compiler::{CompileOptions, compile_source};
use endor_core::{ObjectRef, Value, Vm};
use frontend::{EndorReader, ParseMode};
fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value {
let names = vm
.dict_items(globals)
.unwrap()
.iter()
.filter_map(|(name, _)| {
if let Value::Text(name) = name {
Some(name.clone())
} else {
None
}
})
.collect();
let source = EndorReader::from_text("<classes>", source);
let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap();
let code = template.load(vm).unwrap();
let result = vm.execute(code, globals).unwrap();
vm.release(code).unwrap();
result
}
#[test]
fn source_inheritance_binding_and_dynamic_dispatch_keep_the_real_receiver_alive() {
let mut vm = Vm::new().unwrap();
let globals = vm.new_dict().unwrap();
let roots = vm.bootstrap_types().unwrap();
for (name, object) in [("Object", roots.object), ("Type", roots.type_type)] {
vm.dict_set(globals, Value::Text(name.into()), Value::Object(object))
.unwrap();
vm.release(object).unwrap();
}
let source = r#"
class Base {
value: Number
function read(self: Base): Number { return self.value }
function via(self: Base): Number { return self.read() }
function withEvent(self: Base, event: Object): Object { return event }
}
class Child: Base {
label: String
function read(self: Child): Number { return 12 }
}
"#;
assert_eq!(
execute(&mut vm, globals, source, ParseMode::Module),
Value::Nil
);
let callback = execute(
&mut vm,
globals,
"Child(9, \"ok\").via",
ParseMode::Expression,
);
let Value::Object(callback) = callback else {
panic!("bound callback")
};
assert_eq!(
vm.object_store()
.positional_parameter_count(callback)
.unwrap(),
Some(0)
);
let with_event = execute(
&mut vm,
globals,
"Child(9, \"ok\").withEvent",
ParseMode::Expression,
);
let Value::Object(with_event) = with_event else {
panic!("bound callback")
};
assert_eq!(
vm.object_store()
.positional_parameter_count(with_event)
.unwrap(),
Some(1)
);
vm.release(with_event).unwrap();
vm.dict_clear(globals).unwrap();
vm.release(globals).unwrap();
vm.collect_cycles();
// No global name or external instance root remains. The bound callback
// alone retains its receiver and can still dispatch to Child.read.
assert_eq!(
vm.call(callback, &[]).unwrap(),
Value::Number(12_i64.into())
);
vm.release(callback).unwrap();
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn duplicate_members_and_missing_receivers_fail_before_execution() {
for text in [
"class Bad { value: Number value: String }",
"class Bad { function read(): Number { return 1 } }",
] {
let source = EndorReader::from_text("<invalid-class>", text);
assert!(
compile_source(
&source,
ParseMode::Module,
&CompileOptions {
globals: vec!["Object".into()]
}
)
.is_err()
);
}
}
+1 -1
View File
@@ -10,6 +10,6 @@ Endor VM 包,仅负责 Vm、执行帧、调用协议和 wordcode 求值循环
Vm::new 创建独立 ObjectStore 和空执行状态;代码对象、全局字典和函数仍按宿主需要显式创建,再 execute/call。EndorObject、Number、ObjectRef、内置载荷、代码对象/指令定义与 VmError 在 objects 中实现;endor_core 保留这些类型的兼容重导出,既有 Vm 对象方法委托到 ObjectStore。
没有 Python type/元类或语言根类。网络、数据库和进程环境属于宿主,源码阅读器与 lexer/parser 属于 [frontend](../frontend/README.md),AST 名称分析与字节码生成属于独立 [compiler](../compiler/README.md),见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。
宿主可显式引导托管 Object/Type 和名义继承,见[类型机制](../../docs/runtime/types.md);普通源码 class 和继承方法分派已经接入,trait、原生类分配继承与完整元类尚未接入。网络、数据库和进程环境属于宿主,源码阅读器与 lexer/parser 属于 [frontend](../frontend/README.md),AST 名称分析与字节码生成属于独立 [compiler](../compiler/README.md),见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。
返回值所有权见 [生命周期](../../docs/runtime/lifecycle.md),执行契约见 [字节码 VM](../../docs/runtime/bytecode.md)。本地不构建或测试,见 [验证](../../docs/operations/validation.md)。返回 [工作区](../README.md)。
+141 -47
View File
@@ -4,6 +4,12 @@ use crate::{
EndorStringObject, ExecutionLimits, ExecutionLocation, Instruction, ObjectRef, Opcode, Value,
Vm, VmError,
};
use crate::{NativeContext, NativeResult};
enum PreparedCall {
Value(Value),
Frame(EndorFrame),
}
fn invalid(offset: usize, reason: &'static str) -> VmError {
VmError::InvalidBytecode { offset, reason }
@@ -49,20 +55,17 @@ impl Vm {
limits: ExecutionLimits,
) -> Result<Value, VmError> {
self.last_traceback.clear();
if !self
.object(function)?
.payload()
.as_any()
.is::<EndorFunctionObject>()
{
return self.invoke_native(function, arguments);
}
let mut executed = 0;
let frame = match self.prepare_call(function, arguments.to_vec(), &mut executed, limits)? {
PreparedCall::Value(value) => return Ok(value),
PreparedCall::Frame(frame) => frame,
};
if limits.max_frames == 0 {
self.clear_frame(frame);
return Err(VmError::ExecutionLimit("frames"));
}
let frame = self.prepare_frame(function, arguments)?;
let mut frames = vec![frame];
let result = self.eval_frames(&mut frames, limits);
let result = self.eval_frames(&mut frames, limits, &mut executed);
if result.is_err() {
self.last_traceback = frames
.iter()
@@ -196,14 +199,68 @@ impl Vm {
&mut self,
function: ObjectRef,
arguments: &[Value],
) -> Result<Value, VmError> {
) -> Result<NativeResult, VmError> {
self.object(function)?;
for argument in arguments {
self.validate_value(argument)?;
}
if let Some(callable) = self.object(function)?.payload().native_callable() {
return NativeContext::execute(&mut self.objects, function, arguments, callable);
}
let result = self.object(function)?.payload().call(arguments)?;
self.retain_value(&result)?;
Ok(result)
Ok(NativeResult::Return(result))
}
fn prepare_call(
&mut self,
mut function: ObjectRef,
mut arguments: Vec<Value>,
executed: &mut usize,
limits: ExecutionLimits,
) -> Result<PreparedCall, VmError> {
let mut owned = false;
let result = (|| loop {
if self
.object(function)?
.payload()
.as_any()
.is::<EndorFunctionObject>()
{
return self
.prepare_frame(function, &arguments)
.map(PreparedCall::Frame);
}
if *executed >= limits.max_instructions {
return Err(VmError::ExecutionLimit("instructions"));
}
*executed += 1;
match self.invoke_native(function, &arguments)? {
NativeResult::Return(value) => return Ok(PreparedCall::Value(value)),
NativeResult::Invoke {
function: next,
arguments: values,
} => {
if owned {
self.release(function)?;
for value in arguments.drain(..) {
self.release_value(value)?;
}
}
function = next;
arguments = values;
owned = true;
}
}
})();
if owned {
self.release(function)
.expect("delegated call owns its function");
for value in arguments {
self.drop_root(value);
}
}
result
}
fn stack_value(frame: &EndorFrame, index: usize) -> Result<Value, VmError> {
@@ -220,16 +277,15 @@ impl Vm {
Self::stack_value(frame, frame.stack.len() - 1)
}
fn assignment_value(&self, previous: Option<Value>, incoming: Value) -> Result<Value, VmError> {
if let (Some(Value::Object(old)), Value::Object(new)) = (&previous, &incoming) {
if old == new
|| self
.object(*old)?
.payload()
.assign_from(self.object(*new)?.payload())?
{
return Ok(Value::Object(*old));
}
fn assignment_value(
&mut self,
previous: Option<Value>,
incoming: Value,
) -> Result<Value, VmError> {
if let (Some(Value::Object(old)), Value::Object(new)) = (&previous, &incoming)
&& (old == new || self.objects.assign_from(*old, *new)?)
{
return Ok(Value::Object(*old));
}
Ok(incoming)
}
@@ -356,17 +412,53 @@ impl Vm {
self.new_function(name, code, frame.globals, defaults, closure)
}
fn build_class(&mut self, frame: &EndorFrame) -> Result<ObjectRef, VmError> {
let start = frame.stack.len() - 4;
let Value::Text(name) = Self::stack_value(frame, start)? else {
return Err(VmError::ObjectKindMismatch("class name"));
};
let Value::Object(base) = Self::stack_value(frame, start + 1)? else {
return Err(VmError::ObjectKindMismatch("base Type"));
};
let Value::Object(fields) = Self::stack_value(frame, start + 2)? else {
return Err(VmError::ObjectKindMismatch("field map"));
};
let Value::Object(methods) = Self::stack_value(frame, start + 3)? else {
return Err(VmError::ObjectKindMismatch("method map"));
};
let fields = self
.dict_items(fields)?
.into_iter()
.map(|(name, type_name)| match (name, type_name) {
(Value::Text(name), Value::Text(type_name)) => Ok(crate::ClassField {
name: name.clone(),
type_name: type_name.clone(),
}),
_ => Err(VmError::ObjectKindMismatch("class field annotation")),
})
.collect::<Result<Vec<_>, _>>()?;
let methods = self
.dict_items(methods)?
.into_iter()
.map(|(name, function)| match (name, function) {
(Value::Text(name), Value::Object(function)) => Ok((name.clone(), *function)),
_ => Err(VmError::ObjectKindMismatch("class method")),
})
.collect::<Result<std::collections::BTreeMap<_, _>, _>>()?;
self.objects.new_class(name, base, fields, methods)
}
fn eval_frames(
&mut self,
frames: &mut Vec<EndorFrame>,
limits: ExecutionLimits,
executed: &mut usize,
) -> Result<Value, VmError> {
let mut executed = 0;
loop {
if executed >= limits.max_instructions {
if *executed >= limits.max_instructions {
return Err(VmError::ExecutionLimit("instructions"));
}
executed += 1;
*executed += 1;
let frame = frames.last_mut().expect("an evaluation frame is active");
let decoded = frame.executable.instruction_at(frame.instruction_offset)?;
frame.current_offset = decoded.offset;
@@ -485,27 +577,24 @@ impl Vm {
for index in start + 2..frame.stack.len() {
arguments.push(Self::stack_value(frame, index)?);
}
let language_function = self
.object(function)?
.payload()
.as_any()
.is::<EndorFunctionObject>();
if language_function {
if frames.len() >= limits.max_frames {
return Err(VmError::ExecutionLimit("frames"));
match self.prepare_call(function, arguments, executed, limits)? {
PreparedCall::Frame(mut callee) => {
if frames.len() >= limits.max_frames {
self.clear_frame(callee);
return Err(VmError::ExecutionLimit("frames"));
}
callee.previous = Some(frames.len() - 1);
self.discard(frames.last_mut().unwrap(), argument + 2);
frames.push(callee);
}
PreparedCall::Value(value) => {
self.discard(frames.last_mut().unwrap(), argument + 2);
frames
.last_mut()
.unwrap()
.stack
.push(StackEntry::Value(value));
}
let mut callee = self.prepare_frame(function, &arguments)?;
callee.previous = Some(frames.len() - 1);
self.discard(frames.last_mut().unwrap(), argument + 2);
frames.push(callee);
} else {
let value = self.invoke_native(function, &arguments)?;
self.discard(frames.last_mut().unwrap(), argument + 2);
frames
.last_mut()
.unwrap()
.stack
.push(StackEntry::Value(value));
}
}
Opcode::ReturnValue => {
@@ -554,11 +643,16 @@ impl Vm {
let Value::Object(owner) = Self::top_value(frame)? else {
return Err(VmError::ObjectKindMismatch("object"));
};
let value = self.get_attr(owner, &frame.executable.layout().names[argument])?;
self.retain_value(&value)?;
let value =
self.bind_attr(owner, &frame.executable.layout().names[argument])?;
self.discard(frame, 1);
frame.stack.push(StackEntry::Value(value));
}
Opcode::BuildClass => {
let class = self.build_class(frame)?;
self.discard(frame, 4);
frame.stack.push(StackEntry::Value(Value::Object(class)));
}
Opcode::StoreAttr => {
let Value::Object(owner) = Self::top_value(frame)? else {
return Err(VmError::ObjectKindMismatch("object"));
+9 -5
View File
@@ -44,11 +44,15 @@ mod vm;
pub use frame::{EndorFrame, ExecutionLimits, ExecutionLocation};
// Preserve existing embedding imports; objects owns these definitions.
pub use objects::ObjectStore;
pub use objects::{
CodeLayout, CompareOp, DecodedInstruction, EndorBaseExceptionObject, EndorCellObject,
EndorCode, EndorCodeObject, EndorDecimal, EndorDictObject, EndorFunctionObject, EndorInteger,
EndorNativeCall, EndorNativeFunctionObject, EndorNumber, EndorNumberObject, EndorObject,
EndorRational, EndorReal, EndorStringObject, EndorTracebackEntry, Instruction, NativeCall,
NativeFunction, ObjectHeader, ObjectPayload, ObjectRef, Opcode, Record, Value, VmError,
ChangeBatch, ChangeKey, CodeLayout, CompareOp, ContextNativeFunction, DecodedInstruction,
EndorBaseExceptionObject, EndorCellObject, EndorCode, EndorCodeObject, EndorDecimal,
EndorDictObject, EndorFunctionObject, EndorInteger, EndorNativeCall, EndorNativeFunctionObject,
EndorNumber, EndorNumberObject, EndorObject, EndorRational, EndorReal, EndorStringObject,
EndorTracebackEntry, Instruction, NativeCall, NativeCallable, NativeContext, NativeFunction,
NativeResult, ObjectChange, ObjectHeader, ObjectPayload, ObjectRef, Opcode, Record, Value,
VmError, WeakObjectRef,
};
pub use objects::{ClassField, EndorTypeObject, RootTypes, TypeConstructor};
pub use vm::Vm;
+81 -1
View File
@@ -9,11 +9,91 @@ use crate::{
/// reference accounting and builtin operations live in the objects crate.
#[derive(Debug)]
pub struct Vm {
objects: ObjectStore,
pub(crate) objects: ObjectStore,
pub(crate) last_traceback: Vec<ExecutionLocation>,
}
impl Vm {
pub fn define_method(
&mut self,
type_object: ObjectRef,
name: impl Into<String>,
function: ObjectRef,
) -> Result<(), VmError> {
self.objects.define_method(type_object, name, function)
}
/// Owned result; release_value when finished. Unlike get_attr, methods bind
/// their receiver and retain it until the bound method itself is released.
pub fn bind_attr(&mut self, object: ObjectRef, name: &str) -> Result<Value, VmError> {
self.objects.bind_attr(object, name)
}
pub fn bootstrap_types(&mut self) -> Result<crate::RootTypes, VmError> {
self.objects.bootstrap_types()
}
pub fn new_type(
&mut self,
name: impl Into<String>,
base: ObjectRef,
constructor: crate::TypeConstructor,
) -> Result<ObjectRef, VmError> {
self.objects.new_type(name, base, constructor)
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.objects.attach_type(object, type_object)
}
pub fn type_of(&self, object: ObjectRef) -> Result<Option<ObjectRef>, VmError> {
self.objects.type_of(object)
}
pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.objects.is_subtype(candidate, expected)
}
pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.objects.is_instance(object, expected)
}
/// Read-only access for embedding protocols sharing the same object graph.
pub fn object_store(&self) -> &ObjectStore {
&self.objects
}
pub fn downgrade(&self, reference: ObjectRef) -> Result<crate::WeakObjectRef, VmError> {
self.objects.downgrade(reference)
}
pub fn upgrade(
&mut self,
reference: crate::WeakObjectRef,
) -> Result<Option<ObjectRef>, VmError> {
self.objects.upgrade(reference)
}
pub fn take_changes(&mut self) -> crate::ChangeBatch {
self.objects.take_changes()
}
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
self.objects.change_version(reference)
}
pub fn replace_payload(
&mut self,
reference: ObjectRef,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
self.objects.replace_payload(reference, payload)
}
pub fn new() -> Result<Self, VmError> {
Ok(Self {
objects: ObjectStore::new()?,
+2
View File
@@ -7,6 +7,8 @@
| [runtime.rs](runtime.rs) | 空堆启动、默认操作、引用/借用、属性原子性、稳定地址、长链/循环、原生回调、VM 销毁 |
| [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 |
| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 |
| [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄;仅远端执行 |
| [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、委托、弱引用过期与有界变更;仅远端执行 |
核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。
+150
View File
@@ -0,0 +1,150 @@
//! Remote-only regression coverage. Do not execute locally.
use endor_core::{
ChangeKey, ContextNativeFunction, ExecutionLimits, NativeCallable, NativeContext, NativeResult,
ObjectRef, Value, Vm, VmError,
};
#[derive(Debug)]
struct Allocate(bool);
impl NativeCallable for Allocate {
fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result<NativeResult, VmError> {
let value = cx.new_object()?;
cx.set_attr(value, "label", Value::Text("scoped".into()))?;
let _temporary = cx.new_object()?;
if self.0 {
return Err(VmError::InvalidArgument("intentional failure"));
}
Ok(NativeResult::Return(Value::Object(value)))
}
}
#[test]
fn scoped_allocations_return_one_root_and_clean_up_errors() {
let mut vm = Vm::new().unwrap();
let function = vm
.allocate(ContextNativeFunction::new(
"allocate",
Allocate(false),
vec![],
))
.unwrap();
let value = vm.call(function, &[]).unwrap();
let Value::Object(object) = value else {
panic!("expected object")
};
assert_eq!(vm.ref_count(object).unwrap(), 1);
assert_eq!(vm.object_count(), 2);
vm.release_value(value).unwrap();
vm.replace_payload(
function,
ContextNativeFunction::new("fail", Allocate(true), vec![]),
)
.unwrap();
assert!(vm.call(function, &[]).is_err());
assert_eq!(vm.object_count(), 1);
vm.release(function).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[derive(Debug)]
struct Delegate(ObjectRef);
impl NativeCallable for Delegate {
fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
Ok(NativeResult::Invoke {
function: self.0,
arguments: args.to_vec(),
})
}
}
#[test]
fn native_delegation_shares_budget_and_cleans_owned_arguments() {
let mut vm = Vm::new().unwrap();
let target = vm
.allocate(ContextNativeFunction::new(
"target",
Allocate(false),
vec![],
))
.unwrap();
let delegate = vm
.allocate(ContextNativeFunction::new(
"delegate",
Delegate(target),
vec![target],
))
.unwrap();
let arg = vm.new_object().unwrap();
assert!(
vm.call_with_limits(
delegate,
&[Value::Object(arg)],
ExecutionLimits {
max_frames: 1,
max_instructions: 1
}
)
.is_err()
);
assert_eq!(vm.ref_count(arg).unwrap(), 1);
assert_eq!(vm.ref_count(target).unwrap(), 2);
let value = vm.call(delegate, &[]).unwrap();
vm.release_value(value).unwrap();
vm.release(delegate).unwrap();
vm.release(target).unwrap();
vm.release(arg).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn weak_references_expire_without_retaining_reused_slots() {
let mut vm = Vm::new().unwrap();
let object = vm.new_object().unwrap();
let weak = vm.downgrade(object).unwrap();
let upgraded = vm.upgrade(weak).unwrap().unwrap();
vm.release(upgraded).unwrap();
vm.release(object).unwrap();
let replacement = vm.new_object().unwrap();
assert!(vm.upgrade(weak).unwrap().is_none());
let mut foreign = Vm::new().unwrap();
assert_eq!(foreign.upgrade(weak), Err(VmError::ForeignHandle));
vm.release(replacement).unwrap();
}
#[test]
fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() {
let mut vm = Vm::new().unwrap();
let object = vm.new_object().unwrap();
vm.set_attr(object, "value", Value::Bool(false)).unwrap();
vm.set_attr(object, "value", Value::Bool(true)).unwrap();
let version = vm.change_version(object).unwrap();
let changes = vm.take_changes();
assert!(!changes.rescan_required);
assert_eq!(changes.changes.len(), 1);
assert_eq!(changes.changes[0].key, ChangeKey::Attribute("value".into()));
assert_eq!(changes.changes[0].version, version);
let cell = vm.new_cell(None).unwrap();
vm.cell_set(cell, Value::Object(object)).unwrap();
vm.cell_clear(cell).unwrap();
assert_eq!(vm.take_changes().changes[0].key, ChangeKey::Contents);
vm.set_attr(object, "self", Value::Object(object)).unwrap();
vm.release(object).unwrap();
assert_eq!(vm.collect_cycles(), 1);
vm.release(cell).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() {
let mut vm = Vm::new().unwrap();
let object = vm.new_object().unwrap();
for index in 0..4100 {
vm.set_attr(object, index.to_string(), Value::Nil).unwrap();
}
let changes = vm.take_changes();
assert!(changes.rescan_required);
assert!(changes.changes.is_empty());
vm.set_attr(object, "after", Value::Nil).unwrap();
assert_eq!(vm.take_changes().changes.len(), 1);
vm.release(object).unwrap();
}
+65
View File
@@ -0,0 +1,65 @@
use endor_core::{TypeConstructor, Value, Vm, VmError};
#[test]
fn nominal_types_have_distinct_inheritance_and_instance_relations() {
let mut vm = Vm::new().unwrap();
assert_eq!(vm.object_count(), 0);
let roots = vm.bootstrap_types().unwrap();
let block = vm
.new_type("Block", roots.object, TypeConstructor::Abstract)
.unwrap();
let page = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
let other = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
assert!(vm.is_subtype(page, block).unwrap());
assert!(!vm.is_subtype(block, page).unwrap());
assert!(!vm.is_subtype(page, other).unwrap());
assert!(vm.is_instance(page, roots.type_type).unwrap());
assert!(vm.is_instance(roots.object, roots.type_type).unwrap());
assert!(vm.is_instance(roots.type_type, roots.type_type).unwrap());
assert!(!vm.is_subtype(roots.object, roots.type_type).unwrap());
let Value::Object(instance) = vm.call(page, &[]).unwrap() else {
panic!("instance required")
};
assert!(vm.is_instance(instance, block).unwrap());
assert!(!vm.is_instance(instance, roots.type_type).unwrap());
assert_eq!(vm.get_attr(instance, "type").unwrap(), Value::Object(page));
assert_eq!(vm.get_attr(page, "base").unwrap(), Value::Object(block));
assert!(vm.set_attr(instance, "type", Value::Object(other)).is_err());
assert!(vm.attach_type(instance, other).is_err());
assert!(vm.replace_payload(page, endor_core::Record).is_err());
for object in [roots.object, roots.type_type, block, page, other] {
vm.release(object).unwrap();
}
vm.collect_cycles();
// The instance keeps its definition, base and metatype reachable without
// any host-owned type registry. The unrelated same-name type was collected.
assert_eq!(vm.object_count(), 5);
assert!(vm.is_instance(instance, block).unwrap());
vm.release(instance).unwrap();
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn foreign_type_edges_fail_without_changing_the_target() {
let mut first = Vm::new().unwrap();
let mut second = Vm::new().unwrap();
let roots = first.bootstrap_types().unwrap();
let object = second.new_object().unwrap();
assert_eq!(
second.attach_type(object, roots.object),
Err(VmError::ForeignHandle)
);
assert_eq!(second.type_of(object).unwrap(), None);
assert_eq!(second.ref_count(object).unwrap(), 1);
second
.set_attr(object, "type", Value::Text("existing field".into()))
.unwrap();
let local = second.bootstrap_types().unwrap();
assert!(second.attach_type(object, local.object).is_err());
assert_eq!(
second.get_attr(object, "type").unwrap(),
Value::Text("existing field".into())
);
assert_eq!(second.type_of(object).unwrap(), None);
}
+4
View File
@@ -12,17 +12,21 @@ module Endor {
Program = (Decl* declarations, span span)
Decl = (DeclKind kind, span span)
DeclKind = Function(Function definition)
| Class(Class definition)
Class = (Name name, Name? base, Parameter* fields, Function* methods, span span)
Function = (Name name, Parameter* parameters, Name return_type, Block body, span span)
Parameter = (Name name, Name type_name, span span)
Block = (Stmt* statements, span span)
Stmt = (StmtKind kind, span span)
StmtKind = Binding(Binding binding)
| Assignment(Assignment assignment)
| MemberAssignment(MemberAssignment assignment)
| Return(ReturnStatement statement)
| Expression(Expr expression)
| Declaration(Decl declaration)
Binding = (Name name, Expr value, span span)
Assignment = (Name name, Expr value, span span)
MemberAssignment = (Expr object, Name name, Expr value, span span)
ReturnStatement = (Expr? value, span span)
InteractiveInput = (Stmt* statements, span span)
+98 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b",
"$id": "urn:endor:ast:sha256:ad2ac52943dd26e08395c0d2698861da98bb89c360241c4712c769de86709fd0",
"$ref": "#/$defs/AstDocument",
"$defs": {
"Span": {
@@ -27,7 +27,7 @@
"type": "object",
"properties": {
"schema": {
"const": "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b"
"const": "urn:endor:ast:sha256:ad2ac52943dd26e08395c0d2698861da98bb89c360241c4712c769de86709fd0"
},
"root": {
"$ref": "#/$defs/AstRoot"
@@ -159,9 +159,65 @@
"data"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": {
"const": "Class"
},
"data": {
"$ref": "#/$defs/Class"
}
},
"required": [
"kind",
"data"
],
"additionalProperties": false
}
]
},
"Class": {
"type": "object",
"properties": {
"name": {
"$ref": "#/$defs/Name"
},
"base": {
"anyOf": [
{
"$ref": "#/$defs/Name"
},
{
"type": "null"
}
]
},
"fields": {
"type": "array",
"items": {
"$ref": "#/$defs/Parameter"
}
},
"methods": {
"type": "array",
"items": {
"$ref": "#/$defs/Function"
}
},
"span": {
"$ref": "#/$defs/Span"
}
},
"required": [
"name",
"fields",
"methods",
"span"
],
"additionalProperties": false
},
"Function": {
"type": "object",
"properties": {
@@ -282,6 +338,22 @@
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"kind": {
"const": "MemberAssignment"
},
"data": {
"$ref": "#/$defs/MemberAssignment"
}
},
"required": [
"kind",
"data"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
@@ -372,6 +444,30 @@
],
"additionalProperties": false
},
"MemberAssignment": {
"type": "object",
"properties": {
"object": {
"$ref": "#/$defs/Expr"
},
"name": {
"$ref": "#/$defs/Name"
},
"value": {
"$ref": "#/$defs/Expr"
},
"span": {
"$ref": "#/$defs/Span"
}
},
"required": [
"object",
"name",
"value",
"span"
],
"additionalProperties": false
},
"ReturnStatement": {
"type": "object",
"properties": {
+28 -3
View File
@@ -1,20 +1,22 @@
// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.
export const AST_SCHEMA_ID = "urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b" as const;
export const AST_SCHEMA_ID = "urn:endor:ast:sha256:ad2ac52943dd26e08395c0d2698861da98bb89c360241c4712c769de86709fd0" as const;
export interface Span { start: number; end: number; }
export type AstDocument = { schema: typeof AST_SCHEMA_ID; root: AstRoot; };
export type AstRoot = { kind: "Module"; data: Program; } | { kind: "Expression"; data: Expr; } | { kind: "Interactive"; data: InteractiveInput; };
export type Name = { text: string; span: Span; };
export type Program = { declarations: Array<Decl>; span: Span; };
export type Decl = { kind: DeclKind; span: Span; };
export type DeclKind = { kind: "Function"; data: Function; };
export type DeclKind = { kind: "Function"; data: Function; } | { kind: "Class"; data: Class; };
export type Class = { name: Name; base?: Name | null; fields: Array<Parameter>; methods: Array<Function>; span: Span; };
export type Function = { name: Name; parameters: Array<Parameter>; return_type: Name; body: Block; span: Span; };
export type Parameter = { name: Name; type_name: Name; span: Span; };
export type Block = { statements: Array<Stmt>; span: Span; };
export type Stmt = { kind: StmtKind; span: Span; };
export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; };
export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "MemberAssignment"; data: MemberAssignment; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; };
export type Binding = { name: Name; value: Expr; span: Span; };
export type Assignment = { name: Name; value: Expr; span: Span; };
export type MemberAssignment = { object: Expr; name: Name; value: Expr; span: Span; };
export type ReturnStatement = { value?: Expr | null; span: Span; };
export type InteractiveInput = { statements: Array<Stmt>; span: Span; };
export type Expr = { kind: ExprKind; span: Span; };
@@ -29,6 +31,7 @@ visitName?(node: Name): boolean | void;
visitProgram?(node: Program): boolean | void;
visitDecl?(node: Decl): boolean | void;
visitDeclKind?(node: DeclKind): boolean | void;
visitClass?(node: Class): boolean | void;
visitFunction?(node: Function): boolean | void;
visitParameter?(node: Parameter): boolean | void;
visitBlock?(node: Block): boolean | void;
@@ -36,6 +39,7 @@ visitStmt?(node: Stmt): boolean | void;
visitStmtKind?(node: StmtKind): boolean | void;
visitBinding?(node: Binding): boolean | void;
visitAssignment?(node: Assignment): boolean | void;
visitMemberAssignment?(node: MemberAssignment): boolean | void;
visitReturnStatement?(node: ReturnStatement): boolean | void;
visitInteractiveInput?(node: InteractiveInput): boolean | void;
visitExpr?(node: Expr): boolean | void;
@@ -82,8 +86,19 @@ switch (node.kind) {
case "Function":
walkFunction(node.data, visitor);
break;
case "Class":
walkClass(node.data, visitor);
break;
}
}
export function walkClass(node: Class, visitor: Visitor): void {
if (visitor.visitClass?.(node) === false) return;
walkName(node.name, visitor);
if (node.base != null) walkName(node.base, visitor);
for (const value of node.fields) walkParameter(value, visitor);
for (const value of node.methods) walkFunction(value, visitor);
walkSpan(node.span, visitor);
}
export function walkFunction(node: Function, visitor: Visitor): void {
if (visitor.visitFunction?.(node) === false) return;
walkName(node.name, visitor);
@@ -117,6 +132,9 @@ break;
case "Assignment":
walkAssignment(node.data, visitor);
break;
case "MemberAssignment":
walkMemberAssignment(node.data, visitor);
break;
case "Return":
walkReturnStatement(node.data, visitor);
break;
@@ -140,6 +158,13 @@ walkName(node.name, visitor);
walkExpr(node.value, visitor);
walkSpan(node.span, visitor);
}
export function walkMemberAssignment(node: MemberAssignment, visitor: Visitor): void {
if (visitor.visitMemberAssignment?.(node) === false) return;
walkExpr(node.object, visitor);
walkName(node.name, visitor);
walkExpr(node.value, visitor);
walkSpan(node.span, visitor);
}
export function walkReturnStatement(node: ReturnStatement, visitor: Visitor): void {
if (visitor.visitReturnStatement?.(node) === false) return;
if (node.value != null) walkExpr(node.value, visitor);
+2
View File
@@ -14,4 +14,6 @@
| [parser.rs](parser.rs) | 统一根/ParseMode 与具体解析 API、错误映射、AST 辅助函数,以及独立语法文件的编译期引入 |
| [grammar.peg](grammar.peg) | token 输入的 PEG 规则、运算符优先级与 AST 构造动作;保留 peg::parser! 外层声明 |
函数体允许独立方法调用等表达式语句;末尾 return 可省略,已有编译器隐式返回 Nil。
改节点先编辑 ../Endor.asdl,再用 scripts/generate_ast.py 更新 Rust/编辑器产物,契约见 [AST 模型](../../../docs/runtime/ast.md)。改输入适配时同步 [阅读器契约](../../../docs/runtime/reader.md) 与读取错误检查;改词法从 lexer.peg 进入,并同步 token、诊断和错误检查;改语法从 grammar.peg 进入,并同步 AST、[测试](../tests/README.md)、样例与 [前端契约](../../../docs/runtime/frontend.md)。词法与语法两份 PEG 动作均不做 VM 分配、I/O 或修改外部状态,避免回溯造成副作用。返回 [frontend](../README.md)。
+6 -4
View File
@@ -26,9 +26,11 @@ pub trait Spanned {
impl Program {
/// Iterate the currently supported top-level function declarations.
pub fn functions(&self) -> impl Iterator<Item = &Function> {
self.declarations.iter().map(|declaration| {
let DeclKind::Function(function) = &declaration.kind;
function
})
self.declarations
.iter()
.filter_map(|declaration| match &declaration.kind {
DeclKind::Function(function) => Some(function),
DeclKind::Class(_) => None,
})
}
}
+32 -1
View File
@@ -3,7 +3,7 @@
use super::Spanned;
use crate::Span;
pub const AST_SCHEMA_ID: &str =
"urn:endor:ast:sha256:8c89fde6c029357a0809751c41cb46d59972b987bbe1b25c0cced4bfcdbe2a7b";
"urn:endor:ast:sha256:ad2ac52943dd26e08395c0d2698861da98bb89c360241c4712c769de86709fd0";
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AstDocument {
@@ -63,16 +63,32 @@ impl Spanned for Decl {
#[serde(tag = "kind", content = "data", deny_unknown_fields)]
pub enum DeclKind {
Function(Function),
Class(Class),
}
impl Spanned for DeclKind {
fn span(&self) -> Span {
match self {
Self::Function(node) => node.span(),
Self::Class(node) => node.span(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Class {
pub name: Name,
pub base: Option<Name>,
pub fields: Vec<Parameter>,
pub methods: Vec<Function>,
pub span: Span,
}
impl Spanned for Class {
fn span(&self) -> Span {
self.span
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Function {
pub name: Name,
pub parameters: Vec<Parameter>,
@@ -124,6 +140,7 @@ impl Spanned for Stmt {
pub enum StmtKind {
Binding(Binding),
Assignment(Assignment),
MemberAssignment(MemberAssignment),
Return(ReturnStatement),
Expression(Expr),
Declaration(Decl),
@@ -133,6 +150,7 @@ impl Spanned for StmtKind {
match self {
Self::Binding(node) => node.span(),
Self::Assignment(node) => node.span(),
Self::MemberAssignment(node) => node.span(),
Self::Return(node) => node.span(),
Self::Expression(node) => node.span(),
Self::Declaration(node) => node.span(),
@@ -165,6 +183,19 @@ impl Spanned for Assignment {
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemberAssignment {
pub object: Expr,
pub name: Name,
pub value: Expr,
pub span: Span,
}
impl Spanned for MemberAssignment {
fn span(&self) -> Span {
self.span
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReturnStatement {
pub value: Option<Expr>,
pub span: Span,
+45 -3
View File
@@ -1,9 +1,9 @@
// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.
use super::{
Assignment, AstDocument, AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind,
Function, InteractiveInput, Name, Parameter, Program, ReturnStatement, Spanned, Stmt, StmtKind,
UnaryOp,
Assignment, AstDocument, AstRoot, BinaryOp, Binding, Block, Class, Decl, DeclKind, Expr,
ExprKind, Function, InteractiveInput, MemberAssignment, Name, Parameter, Program,
ReturnStatement, Spanned, Stmt, StmtKind, UnaryOp,
};
use crate::Span;
pub trait Visitor {
@@ -48,6 +48,12 @@ pub trait Visitor {
self.leave_node();
result
}
fn visit_class(&mut self, node: &Class) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_class(self, node);
self.leave_node();
result
}
fn visit_function(&mut self, node: &Function) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_function(self, node);
@@ -90,6 +96,12 @@ pub trait Visitor {
self.leave_node();
result
}
fn visit_member_assignment(&mut self, node: &MemberAssignment) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_member_assignment(self, node);
self.leave_node();
result
}
fn visit_return_statement(&mut self, node: &ReturnStatement) -> Result<(), Self::Error> {
self.enter_node(node.span())?;
let result = walk_return_statement(self, node);
@@ -163,9 +175,26 @@ pub fn walk_decl_kind<V: Visitor + ?Sized>(
DeclKind::Function(value) => {
visitor.visit_function(value)?;
}
DeclKind::Class(value) => {
visitor.visit_class(value)?;
}
}
Ok(())
}
pub fn walk_class<V: Visitor + ?Sized>(visitor: &mut V, node: &Class) -> Result<(), V::Error> {
visitor.visit_name(&node.name)?;
if let Some(value) = &node.base {
visitor.visit_name(value)?;
}
for value in &node.fields {
visitor.visit_parameter(value)?;
}
for value in &node.methods {
visitor.visit_function(value)?;
}
visitor.visit_span(&node.span)?;
Ok(())
}
pub fn walk_function<V: Visitor + ?Sized>(
visitor: &mut V,
node: &Function,
@@ -211,6 +240,9 @@ pub fn walk_stmt_kind<V: Visitor + ?Sized>(
StmtKind::Assignment(value) => {
visitor.visit_assignment(value)?;
}
StmtKind::MemberAssignment(value) => {
visitor.visit_member_assignment(value)?;
}
StmtKind::Return(value) => {
visitor.visit_return_statement(value)?;
}
@@ -238,6 +270,16 @@ pub fn walk_assignment<V: Visitor + ?Sized>(
visitor.visit_span(&node.span)?;
Ok(())
}
pub fn walk_member_assignment<V: Visitor + ?Sized>(
visitor: &mut V,
node: &MemberAssignment,
) -> Result<(), V::Error> {
visitor.visit_expr(&node.object)?;
visitor.visit_name(&node.name)?;
visitor.visit_expr(&node.value)?;
visitor.visit_span(&node.span)?;
Ok(())
}
pub fn walk_return_statement<V: Visitor + ?Sized>(
visitor: &mut V,
node: &ReturnStatement,
+35 -2
View File
@@ -51,6 +51,23 @@ peg::parser! {
= function:function() {
Decl { span: function.span, kind: DeclKind::Function(function) }
}
/ start:keyword("class") name:name() base:(colon() base:name() { base })?
lbrace() members:class_member()* end:rbrace() {
let mut fields = Vec::new();
let mut methods = Vec::new();
for member in members {
match member {
ClassMember::Field(field) => fields.push(field),
ClassMember::Method(method) => methods.push(method),
}
}
let span = start.through(end);
Decl { span, kind: DeclKind::Class(Class { name, base, fields, methods, span }) }
}
rule class_member() -> ClassMember
= function:function() { ClassMember::Method(function) }
/ field:parameter() { ClassMember::Field(field) }
pub rule interactive() -> InteractiveInput
= statement:interactive_statement() {
@@ -62,6 +79,7 @@ peg::parser! {
Stmt { span: declaration.span, kind: StmtKind::Declaration(declaration) }
}
/ binding:binding() { binding_statement(binding) }
/ assignment:member_assignment() { assignment }
/ assignment:assignment() { assignment }
/ expression:expression() {
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
@@ -79,15 +97,19 @@ peg::parser! {
}
rule block() -> Block
= start:lbrace() locals:local_statement()* returned:return_statement() end:rbrace() {
= start:lbrace() locals:local_statement()* returned:return_statement()? end:rbrace() {
let mut statements = locals;
statements.push(returned);
if let Some(returned) = returned { statements.push(returned); }
Block { statements, span: start.through(end) }
}
rule local_statement() -> Stmt
= binding:binding() { binding_statement(binding) }
/ assignment:member_assignment() { assignment }
/ assignment:assignment() { assignment }
/ !return_keyword() expression:expression() {
Stmt { span: expression.span, kind: StmtKind::Expression(expression) }
}
rule assignment() -> Stmt
= name:name() equal() value:expression() {
@@ -95,6 +117,17 @@ peg::parser! {
Stmt { span, kind: StmtKind::Assignment(Assignment { name, value, span }) }
}
rule member_assignment() -> Stmt
= target:postfix_expression() equal() value:expression() {?
let span = target.span.through(value.span);
match target.kind {
ExprKind::Member { object, name } => Ok(Stmt {
span, kind: StmtKind::MemberAssignment(MemberAssignment { object: *object, name, value, span }),
}),
_ => Err("member assignment target"),
}
}
rule binding() -> Binding
= start:let_keyword() name:name() equal() value:expression() {
Binding { span: start.through(value.span), name, value }
+9 -3
View File
@@ -1,6 +1,7 @@
use crate::ast::{
Assignment, AstRoot, BinaryOp, Binding, Block, Decl, DeclKind, Expr, ExprKind, Function,
InteractiveInput, Name, Parameter, Program, ReturnStatement, Stmt, StmtKind, UnaryOp,
Assignment, AstRoot, BinaryOp, Binding, Block, Class, Decl, DeclKind, Expr, ExprKind, Function,
InteractiveInput, MemberAssignment, Name, Parameter, Program, ReturnStatement, Stmt, StmtKind,
UnaryOp,
};
use crate::lexer::decode_string;
use crate::{Diagnostic, DiagnosticKind, Span, Token, TokenKind, lex};
@@ -44,7 +45,7 @@ pub fn parse_source_expression(
parse_expression(source.text()).map_err(|error| source.annotate(error))
}
/// Parse one or more basic typed function declarations. This is a syntax
/// Parse one or more typed function or class declarations. This is a syntax
/// operation: type names and name references are retained without resolution.
pub fn parse(source: &str) -> Result<Program, Diagnostic> {
let tokens = lex(source)?;
@@ -93,6 +94,11 @@ enum Suffix {
Call(Vec<Expr>, Span),
}
enum ClassMember {
Field(Parameter),
Method(Function),
}
fn postfix(expression: Expr, suffix: Suffix) -> Expr {
match suffix {
Suffix::Member(name) => Expr {
+1 -1
View File
@@ -2,7 +2,7 @@
[syntax.rs](syntax.rs) 覆盖 UTF-8 token 范围、中文/日文名称、数字原文、负号、字符串转义与错误、算术优先级和结合性、成员/调用链、函数与绑定、上下文名称、完整输入、非法/未支持语法以及 CRLF 和 EOF 诊断位置。
[ast.rs](ast.rs) 覆盖根模式、声明/语句结构、生成 Visitor 与错误退出、Spanned 位置、带 schema 的 JSON 往返及错误输入;同时核对编辑器产物标识,并验证裸名称赋值的独立节点、Unicode 范围、JSON 往返和不支持的赋值目标。
[ast.rs](ast.rs) 覆盖根模式、声明/语句结构、生成 Visitor 与错误退出、Spanned 位置、带 schema 的 JSON 往返及错误输入;同时核对编辑器产物标识,并验证裸名称赋值的独立节点、Unicode 范围、JSON 往返和属性赋值目标和不支持的调用结果赋值目标。
[lexical.rs](lexical.rs) 专门覆盖字符 PEG 迁移后的原文位置换算、首个错误及详细诊断、Unicode 非法转义、未闭合字符串、注释/除法/成员/数字/字符串边界和空输入/尾随注释。
+38 -1
View File
@@ -4,6 +4,35 @@ use frontend::ast::{
};
use frontend::{EndorReader, ParseMode, Span, parse_ast, parse_source_ast};
#[test]
fn function_bodies_accept_method_statements_and_optional_trailing_return() {
let root = parse_ast(
"function click(): Unit { title.setText(\"changed\") card.setColor(\"#FFEEDD\") }",
ParseMode::Module,
)
.unwrap();
let AstRoot::Module(program) = root else {
panic!("expected module")
};
let function = program.functions().next().unwrap();
assert_eq!(function.body.statements.len(), 2);
assert!(
function
.body
.statements
.iter()
.all(|s| matches!(s.kind, StmtKind::Expression(_)))
);
assert!(parse_ast("function empty(): Unit {}", ParseMode::Module).is_ok());
assert!(
parse_ast(
"function invalid(): Unit { return value other() }",
ParseMode::Module
)
.is_err()
);
}
#[test]
fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() {
let source = "function echo(value: String): String { let copy = value return copy }";
@@ -198,5 +227,13 @@ fn bare_assignment_has_its_own_ast_and_unicode_source_ranges() {
let json = document.to_json().unwrap();
assert_eq!(AstDocument::from_json(&json).unwrap(), document);
assert!(parse_ast("value = 1 = 2", ParseMode::Interactive).is_err());
assert!(parse_ast("object.field = 1", ParseMode::Interactive).is_err());
let member = parse_ast("object.field = 1", ParseMode::Interactive).unwrap();
let AstRoot::Interactive(member) = member else {
panic!("interactive")
};
assert!(matches!(
&member.statements[0].kind,
StmtKind::MemberAssignment(_)
));
assert!(parse_ast("object() = 1", ParseMode::Interactive).is_err());
}
+3 -4
View File
@@ -4,7 +4,9 @@ use frontend::ast::{
use frontend::{DiagnosticKind, Span, TokenKind, lex, parse, parse_expression};
fn function(program: &Program, index: usize) -> &Function {
let DeclKind::Function(function) = &program.declarations[index].kind;
let DeclKind::Function(function) = &program.declarations[index].kind else {
panic!("expected function")
};
function
}
@@ -258,14 +260,11 @@ fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax(
"// empty",
"let x = 1",
"use atelier:shop.data",
"class Product {}",
"agent Worker {}",
"function f() { return 1 }",
"function f(x): Number { return x }",
"function f(x: Number,): Number { return x }",
"function f(): Number {}",
"function f(): Number { return 1 let x = 2 }",
"function f(): Number { let x = 1 }",
"function f(): Number { return 1 } extra",
"function f(): Number { function g(): Number { return 1 } return 2 }",
] {
+2 -2
View File
@@ -1,6 +1,6 @@
# objects
Endor 核心对象与对象存储包,零外部依赖,不依赖 core。包含 EndorObject、ObjectPayload、Value、ObjectRef、Number、字符串/字典/Cell/函数/异常载荷,以及代码对象与 wordcode 数据定义、编解码和静态验证。没有 Python type/元类或语言根类。
Endor 核心对象与对象存储包,零外部依赖,不依赖 core。包含 EndorObject、ObjectPayload、Value、ObjectRef、Number、字符串/字典/Cell/函数/异常载荷,以及代码对象与 wordcode 数据定义、编解码和静态验证。提供显式引导的 Object/Type 名义类型,已接入普通源码 class 和继承方法分派,trait、原生分配继承与完整元类行为仍待实现。
| 内容 | 职责 |
| --- | --- |
@@ -8,7 +8,7 @@ Endor 核心对象与对象存储包,零外部依赖,不依赖 core。包含
| [src](src/README.md) | 对象定义、私有堆、ObjectStore、对象工厂、字节码数据与共享错误 |
| [tests](tests/README.md) | 不依赖 VM 的对象存储、事务失败、回收和共享代码检查 |
ObjectStore 独立拥有空堆,负责对象分配、代际校验、引用计数、循环回收、属性与内置容器操作;底层 Heap、句柄字段和可变载荷替换仍为私有实现。代码对象只保存及验证执行数据,求值循环与调用帧属于 [core](../core/README.md)。共享 VmError 定义在本包,保留现有名称以避免对象操作接口依赖 VM。
ObjectStore 独立拥有空堆,负责对象分配、代际校验、引用计数、循环回收、属性与内置容器操作;底层 Heap 与句柄字段保持私有。replace_payload 通过公开的受校验事务替换载荷及所有托管边;NativeContext 提供受限原生分配与变更能力。代码对象只保存及验证执行数据,求值循环与调用帧属于 [core](../core/README.md)。共享 VmError 定义在本包,保留现有名称以避免对象操作接口依赖 VM。
新代码可以直接从 objects 引用对象类型;endor_core 继续重导出现有类型,Vm 的对象方法委托给本包,原有宿主入口保持可用。复制句柄不增加引用;独立 ObjectStore 的对象句柄不能交给另一存储或 VM 使用。
+3
View File
@@ -6,6 +6,9 @@
| --- | --- |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出与独立存储 rustdoc 示例 |
| [object.rs](object.rs) | Value、CompareOp、ObjectPayload、ObjectHeader、EndorObject、Record、NativeFunction |
| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段与原生构造委托 |
| [native.rs](native.rs) | NativeContext、上下文原生函数、作用域根与非递归委托调用 |
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收及私有边界测试 |
+26
View File
@@ -0,0 +1,26 @@
use crate::ObjectRef;
/// A precise field change or a replacement of an object's native contents.
/// Entries are notifications, never roots; a source can be dead when consumed.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChangeKey {
Attribute(String),
Contents,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectChange {
pub object: ObjectRef,
pub key: ChangeKey,
pub version: u64,
}
/// Coalesced changes since the previous drain. Overflow requires the consumer
/// to rescan its dependencies, rather than silently missing notifications.
#[derive(Debug, Default)]
pub struct ChangeBatch {
pub changes: Vec<ObjectChange>,
pub rescan_required: bool,
}
pub(crate) const MAX_PENDING_CHANGES: usize = 4096;
+1
View File
@@ -344,6 +344,7 @@ fn verify(
let consumed = defaults + closure + 1;
(consumed, consumed, 1)
}
Opcode::BuildClass => (4, 4, 1),
Opcode::ReturnValue => (1, 1, 0),
_ => (0, 0, 0),
};
+83 -1
View File
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::changes::MAX_PENDING_CHANGES;
use crate::{ChangeBatch, ChangeKey, ObjectChange};
use crate::{EndorObject, ObjectHeader, ObjectPayload, Value, VmError};
static NEXT_HEAP_ID: AtomicU64 = AtomicU64::new(1);
@@ -21,6 +23,11 @@ impl fmt::Display for ObjectRef {
}
}
/// A non-owning reference. Only the originating store can upgrade it; slot
/// reuse never makes a dead weak reference point at a different object.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WeakObjectRef(pub(crate) ObjectRef);
#[derive(Debug)]
struct Slot {
generation: u64,
@@ -33,6 +40,8 @@ pub(crate) struct Heap {
slots: Vec<Slot>,
free: Vec<usize>,
live: usize,
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
changes_overflow: bool,
}
impl Heap {
@@ -45,6 +54,8 @@ impl Heap {
slots: Vec::new(),
free: Vec::new(),
live: 0,
changes: BTreeMap::new(),
changes_overflow: false,
})
}
@@ -72,6 +83,8 @@ impl Heap {
identity: reference,
strong_refs: 1,
external_refs: 1,
change_version: 0,
type_object: None,
},
payload,
attributes: BTreeMap::new(),
@@ -92,6 +105,27 @@ impl Heap {
.ok_or(VmError::InvalidHandle)
}
pub(crate) fn attach_type(
&mut self,
reference: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
if self.get(reference)?.header.type_object.is_some() {
return Err(VmError::InvalidArgument("object already has a type"));
}
if self.get(reference)?.attributes.contains_key("type") {
return Err(VmError::InvalidArgument(
"type attribute conflicts with managed type",
));
}
let increments = self.prepare_increments(&[type_object])?;
self.prepare_change(reference)?;
self.apply_increments(increments);
self.get_mut(reference)?.header.type_object = Some(type_object);
self.record_change(reference, ChangeKey::Contents);
Ok(())
}
/// Transactional copy-on-update for built-in containers. The EndorObject
/// box and identity stay stable; only its payload and edge snapshot change.
/// All new edges are validated/retained before any old edge is released.
@@ -104,10 +138,12 @@ impl Heap {
let mut edges = Vec::new();
payload.trace(&mut |target| edges.push(target));
let increments = self.prepare_increments(&edges)?;
self.prepare_change(reference)?;
self.apply_increments(increments);
let object = self.get_mut(reference)?;
let old_edges = std::mem::replace(&mut object.payload_edges, edges);
let old_payload = std::mem::replace(&mut object.payload, Box::new(payload));
self.record_change(reference, ChangeKey::Contents);
self.release_edges(old_edges);
drop(old_payload);
Ok(())
@@ -194,8 +230,13 @@ impl Heap {
_ => Vec::new(),
};
let increments = self.prepare_increments(&edges)?;
self.prepare_change(reference)?;
self.apply_increments(increments);
let old = self.get_mut(reference)?.attributes.insert(name, value);
let old = self
.get_mut(reference)?
.attributes
.insert(name.clone(), value);
self.record_change(reference, ChangeKey::Attribute(name));
if let Some(Value::Object(target)) = old {
self.release_edges(vec![target]);
}
@@ -213,17 +254,58 @@ impl Heap {
pub(crate) fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
self.check_attributes(reference)?;
self.prepare_change(reference)?;
let old = self
.get_mut(reference)?
.attributes
.remove(name)
.ok_or_else(|| VmError::AttributeNotFound(name.into()))?;
self.record_change(reference, ChangeKey::Attribute(name.into()));
if let Value::Object(target) = old {
self.release_edges(vec![target]);
}
Ok(())
}
pub(crate) fn prepare_change(&self, reference: ObjectRef) -> Result<(), VmError> {
self.get(reference)?
.header
.change_version
.checked_add(1)
.ok_or(VmError::ExecutionLimit("object change version"))?;
Ok(())
}
pub(crate) fn record_change(&mut self, reference: ObjectRef, key: ChangeKey) {
let object = self.get_mut(reference).expect("validated change target");
object.header.change_version += 1;
let version = object.header.change_version;
if self.changes_overflow {
return;
}
let entry = (reference, key);
if self.changes.len() >= MAX_PENDING_CHANGES && !self.changes.contains_key(&entry) {
self.changes.clear();
self.changes_overflow = true;
} else {
self.changes.insert(entry, version);
}
}
pub(crate) fn take_changes(&mut self) -> ChangeBatch {
ChangeBatch {
changes: std::mem::take(&mut self.changes)
.into_iter()
.map(|((object, key), version)| ObjectChange {
object,
key,
version,
})
.collect(),
rescan_required: std::mem::take(&mut self.changes_overflow),
}
}
fn check_attributes(&self, reference: ObjectRef) -> Result<(), VmError> {
if !self.get(reference)?.payload.allows_attributes() {
return Err(VmError::UnsupportedOperation("attributes"));
+3
View File
@@ -34,6 +34,7 @@ pub enum Opcode {
StoreAttr = 26,
BuildMap = 27,
MakeFunction = 28,
BuildClass = 29,
ExtendedArg = 255,
}
@@ -69,6 +70,7 @@ impl Opcode {
26 => Self::StoreAttr,
27 => Self::BuildMap,
28 => Self::MakeFunction,
29 => Self::BuildClass,
255 => Self::ExtendedArg,
_ => return None,
})
@@ -83,6 +85,7 @@ impl Opcode {
| Self::ReturnValue
| Self::ToBool
| Self::UnaryNot
| Self::BuildClass
)
}
}
+7 -1
View File
@@ -14,23 +14,28 @@
//! ```
mod builtins;
mod changes;
mod code;
mod error;
mod heap;
mod instruction;
mod native;
mod number;
mod object;
mod store;
mod store_builtins;
mod types;
pub use builtins::{
EndorBaseExceptionObject, EndorCellObject, EndorDictObject, EndorFunctionObject,
EndorStringObject, EndorTracebackEntry,
};
pub use changes::{ChangeBatch, ChangeKey, ObjectChange};
pub use code::{CodeLayout, EndorCode, EndorCodeObject};
pub use error::VmError;
pub use heap::ObjectRef;
pub use heap::{ObjectRef, WeakObjectRef};
pub use instruction::{DecodedInstruction, Instruction, Opcode};
pub use native::{ContextNativeFunction, NativeCallable, NativeContext, NativeResult};
pub use number::{
EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational, EndorReal,
};
@@ -39,3 +44,4 @@ pub use object::{
ObjectHeader, ObjectPayload, Record, Value,
};
pub use store::ObjectStore;
pub use types::{ClassField, EndorTypeObject, RootTypes, TypeConstructor};
+206
View File
@@ -0,0 +1,206 @@
use std::{fmt, sync::Arc};
use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef};
/// Borrowed results produced inside a native call. Invoke delegates to another
/// callable through the VM's existing frame loop, without recursively entering it.
#[derive(Debug)]
pub enum NativeResult {
Return(Value),
Invoke {
function: ObjectRef,
arguments: Vec<Value>,
},
}
pub trait NativeCallable: fmt::Debug + Send + Sync + 'static {
fn invoke(
&self,
context: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError>;
}
/// Native allocation/mutation scope. Allocations and upgraded weak references
/// are temporary owned roots. Return/Invoke promotes only its output roots;
/// Drop releases everything else, including on error or unwinding.
pub struct NativeContext<'a> {
store: &'a mut ObjectStore,
roots: Vec<ObjectRef>,
callee: ObjectRef,
}
impl<'a> NativeContext<'a> {
/// Callable currently dispatched by the VM, borrowed for this scope.
pub fn callee(&self) -> ObjectRef {
self.callee
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.store.attach_type(object, type_object)
}
pub fn store(&self) -> &ObjectStore {
self.store
}
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
let object = self.store.allocate(payload)?;
self.roots.push(object);
Ok(object)
}
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
self.allocate(crate::Record)
}
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
let object = self.store.new_dict()?;
self.roots.push(object);
Ok(object)
}
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
let object = self.store.new_cell(value)?;
self.roots.push(object);
Ok(object)
}
pub fn set_attr(
&mut self,
object: ObjectRef,
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
self.store.set_attr(object, name, value)
}
pub fn del_attr(&mut self, object: ObjectRef, name: &str) -> Result<(), VmError> {
self.store.del_attr(object, name)
}
pub fn dict_set(&mut self, object: ObjectRef, key: Value, value: Value) -> Result<(), VmError> {
self.store.dict_set(object, key, value)
}
pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result<bool, VmError> {
self.store.dict_delete(object, key)
}
pub fn cell_set(&mut self, object: ObjectRef, value: Value) -> Result<(), VmError> {
self.store.cell_set(object, value)
}
pub fn replace_payload(
&mut self,
object: ObjectRef,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
self.store.replace_payload(object, payload)
}
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
let object = self.store.upgrade(weak)?;
if let Some(object) = object {
self.roots.push(object);
}
Ok(object)
}
fn protect(&mut self, value: &Value) -> Result<(), VmError> {
if let Value::Object(object) = value {
self.store.retain(*object)?;
self.roots.push(*object);
}
Ok(())
}
/// Internal execution bridge. Every returned object reference owns a root;
/// consumers must release Return values or all Invoke arguments/function.
pub fn execute(
store: &'a mut ObjectStore,
function: ObjectRef,
arguments: &[Value],
callable: Arc<dyn NativeCallable>,
) -> Result<NativeResult, VmError> {
let mut context = Self {
store,
roots: Vec::new(),
callee: function,
};
context.protect(&Value::Object(function))?;
for argument in arguments {
context.protect(argument)?;
}
let result = callable.invoke(&mut context, arguments)?;
let transferred_from = context.roots.len();
match &result {
NativeResult::Return(value) => context.protect(value)?,
NativeResult::Invoke {
function,
arguments,
} => {
context.protect(&Value::Object(*function))?;
for argument in arguments {
context.protect(argument)?;
}
}
}
// Transfer exactly the output roots; all other roots remain scoped.
context.roots.truncate(transferred_from);
Ok(result)
}
}
impl Drop for NativeContext<'_> {
fn drop(&mut self) {
for root in self.roots.drain(..).rev() {
self.store
.release(root)
.expect("native scope owns its roots");
}
}
}
/// A context-aware function with explicit, immutable captured object edges.
#[derive(Debug)]
pub struct ContextNativeFunction {
name: &'static str,
callable: Arc<dyn NativeCallable>,
captures: Vec<ObjectRef>,
}
impl ContextNativeFunction {
pub fn new(
name: &'static str,
callable: impl NativeCallable,
captures: Vec<ObjectRef>,
) -> Self {
Self {
name,
callable: Arc::new(callable),
captures,
}
}
}
impl ObjectPayload for ContextNativeFunction {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
self.name
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
for capture in &self.captures {
visit(*capture);
}
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(self.callable.clone())
}
}
+40 -2
View File
@@ -51,6 +51,19 @@ impl CompareOp {
/// Rust Drop releases host resources when the payload is destroyed.
/// Equal payloads must provide compatible hashes, or reject hashing explicitly.
pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
/// Shared-reference facades may expose the current referent's type. The
/// referent must also be held by traced managed edges. Ordinary objects
/// keep their immutable header type; this does not change inheritance.
fn type_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
Ok(None)
}
/// Optional context-aware native entry. The VM clones this dispatcher before
/// borrowing the store mutably; callbacks never borrow a live payload.
fn native_callable(&self) -> Option<std::sync::Arc<dyn crate::NativeCallable>> {
None
}
fn as_any(&self) -> &dyn Any;
/// A diagnostic label only, with no language type or inheritance semantics.
@@ -104,6 +117,18 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
fn assign_from(&self, _source: &dyn ObjectPayload) -> Result<bool, VmError> {
Ok(false)
}
/// Optional managed shared-reference assignment. Return a built-in Cell
/// owned by this payload after validating the source and any domain cycles.
/// The store writes the source object to the Cell transactionally. This
/// keeps mutable edges inside the GC graph, unlike native assign_from.
fn assignment_cell(
&self,
_source: ObjectRef,
_store: &crate::ObjectStore,
) -> Result<Option<ObjectRef>, VmError> {
Ok(None)
}
}
#[derive(Debug, Default)]
@@ -166,15 +191,25 @@ impl ObjectPayload for EndorNativeFunctionObject {
}
}
/// Common runtime state. There is deliberately no language type pointer.
/// Common runtime state. Embedders may attach an immutable managed type.
#[derive(Debug)]
pub struct ObjectHeader {
pub(crate) identity: ObjectRef,
pub(crate) strong_refs: usize,
pub(crate) external_refs: usize,
pub(crate) change_version: u64,
pub(crate) type_object: Option<ObjectRef>,
}
impl ObjectHeader {
pub fn type_object(&self) -> Option<ObjectRef> {
self.type_object
}
pub fn change_version(&self) -> u64 {
self.change_version
}
pub fn identity(&self) -> ObjectRef {
self.identity
}
@@ -190,7 +225,7 @@ impl ObjectHeader {
}
/// Runtime object foundation, analogous in role to CPython's common object
/// structure, without its type system or binary layout. The heap boxes this
/// structure, without its binary layout. The heap boxes this
/// structure so its address remains stable throughout its lifetime.
#[derive(Debug)]
pub struct EndorObject {
@@ -213,6 +248,9 @@ impl EndorObject {
/// allocation and refreshed on VM-managed payload replacement; attributes
/// are maintained by the VM's reference accounting.
pub fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(reference) = self.header.type_object {
visit(reference);
}
for reference in &self.payload_edges {
visit(*reference);
}
+91 -3
View File
@@ -1,5 +1,8 @@
use crate::heap::Heap;
use crate::{CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value, VmError};
use crate::{
ChangeBatch, ChangeKey, CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value,
VmError, WeakObjectRef,
};
/// Object storage independent of the VM evaluator. Construction creates an empty
/// heap; dropping it releases all objects and their host resources. Mutable
@@ -10,6 +13,80 @@ pub struct ObjectStore {
}
impl ObjectStore {
pub fn downgrade(&self, reference: ObjectRef) -> Result<WeakObjectRef, VmError> {
self.object(reference)?;
Ok(WeakObjectRef(reference))
}
/// Returns a new owned root, or None when the original object was released.
/// A foreign store is an error, not an expired reference.
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
match self.object(weak.0) {
Ok(_) => {
self.retain(weak.0)?;
Ok(Some(weak.0))
}
Err(VmError::InvalidHandle) => Ok(None),
Err(error) => Err(error),
}
}
pub fn take_changes(&mut self) -> ChangeBatch {
self.heap.take_changes()
}
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
Ok(self.object(reference)?.header().change_version())
}
/// Replace a host payload transactionally, including all owned GC edges.
pub fn replace_payload(
&mut self,
reference: ObjectRef,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
if self
.object(reference)?
.payload()
.as_any()
.is::<crate::EndorTypeObject>()
{
return Err(VmError::UnsupportedOperation(
"replace immutable type definition",
));
}
self.validate_builtin(&payload)?;
self.heap.replace_payload(reference, payload)
}
pub fn assign_from(&mut self, target: ObjectRef, source: ObjectRef) -> Result<bool, VmError> {
self.heap.prepare_change(target)?;
self.object(source)?;
if let Some(cell) = self
.object(target)?
.payload()
.assignment_cell(source, self)?
{
// Replacing the Cell edge may otherwise reclaim a target which is
// reachable only through the edge being removed.
self.retain(target)?;
let result = self.cell_set(cell, Value::Object(source));
if result.is_ok() {
self.heap.record_change(target, ChangeKey::Contents);
}
self.release(target)?;
return result.map(|()| true);
}
let changed = self
.object(target)?
.payload()
.assign_from(self.object(source)?.payload())?;
if changed {
self.heap.record_change(target, ChangeKey::Contents);
}
Ok(changed)
}
pub fn new() -> Result<Self, VmError> {
Ok(Self { heap: Heap::new()? })
}
@@ -91,7 +168,11 @@ impl ObjectStore {
/// Reads a borrowed value. An Object result needs retain() if the host
/// wants it to survive removal of the attribute that currently owns it.
pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
self.heap.get_attr(reference, name)
self.lookup_attr(reference, name)
.map(|attribute| match attribute {
crate::types::AttributeLookup::Value(value) => value,
crate::types::AttributeLookup::Method { function, .. } => Value::Object(function),
})
}
/// Object-valued attributes own internal strong references independently
@@ -102,10 +183,17 @@ impl ObjectStore {
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
self.heap.set_attr(reference, name.into(), value)
let name = name.into();
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("write object type"));
}
self.heap.set_attr(reference, name, value)
}
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("delete object type"));
}
self.heap.del_attr(reference, name)
}
+474
View File
@@ -0,0 +1,474 @@
//! Nominal type identities in the same managed heap as their instances.
use std::{collections::BTreeMap, sync::Arc};
use crate::{
NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef, ObjectStore, Value,
VmError,
};
#[derive(Debug, Clone, Copy)]
pub enum TypeConstructor {
/// Allocate a plain, attribute-bearing instance of this type.
Record,
/// Delegate to a managed callable. It must return an instance of this type.
Native(ObjectRef),
Abstract,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassField {
pub name: String,
/// Preserved source annotation, like current function annotations.
pub type_name: String,
}
/// Nominal definition with immutable identity/base and a managed method table.
/// Its base, constructor and metatype are traced
/// edges; no registry outside the heap keeps unused definitions alive.
#[derive(Debug)]
pub struct EndorTypeObject {
name: String,
base: Option<ObjectRef>,
constructor: TypeConstructor,
methods: BTreeMap<String, ObjectRef>,
fields: Vec<ClassField>,
}
impl EndorTypeObject {
pub fn name(&self) -> &str {
&self.name
}
pub fn base(&self) -> Option<ObjectRef> {
self.base
}
}
impl ObjectPayload for EndorTypeObject {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"type"
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(base) = self.base {
visit(base);
}
if let TypeConstructor::Native(function) = self.constructor {
visit(function);
}
for method in self.methods.values() {
visit(*method);
}
}
fn repr(&self, _: ObjectRef) -> Result<String, VmError> {
Ok(format!("<type {}>", self.name))
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(Arc::new(Construct))
}
}
#[derive(Debug)]
struct Construct;
impl NativeCallable for Construct {
fn invoke(
&self,
cx: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError> {
let type_object = cx.callee();
let definition = cx.store().payload::<EndorTypeObject>(type_object, "Type")?;
match definition.constructor {
TypeConstructor::Native(function) => Ok(NativeResult::Invoke {
function,
arguments: arguments.to_vec(),
}),
TypeConstructor::Abstract => {
Err(VmError::UnsupportedOperation("abstract type construction"))
}
TypeConstructor::Record => {
if arguments.len() != definition.fields.len() {
return Err(VmError::ArgumentCount {
name: definition.name.clone(),
required: definition.fields.len(),
maximum: definition.fields.len(),
provided: arguments.len(),
});
}
let fields = definition.fields.clone();
let instance = cx.new_object()?;
cx.attach_type(instance, type_object)?;
for (field, value) in fields.iter().zip(arguments) {
cx.set_attr(instance, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(instance)))
}
}
}
}
/// Each field owns one external root; the caller must release both.
#[derive(Debug, Clone, Copy)]
pub struct RootTypes {
pub object: ObjectRef,
pub type_type: ObjectRef,
}
impl ObjectStore {
/// Explicit bootstrap preserves the empty-store contract. Inheritance:
/// Type -> Object; instance relations: Object : Type and Type : Type.
pub fn bootstrap_types(&mut self) -> Result<RootTypes, VmError> {
let object = self.allocate(EndorTypeObject {
name: "Object".into(),
base: None,
constructor: TypeConstructor::Record,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
let mut allocated_type = None;
let result = (|| {
let type_type = self.allocate(EndorTypeObject {
name: "Type".into(),
base: Some(object),
constructor: TypeConstructor::Abstract,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
allocated_type = Some(type_type);
self.attach_type(object, type_type)?;
self.attach_type(type_type, type_type)?;
Ok(RootTypes { object, type_type })
})();
if result.is_err() {
if let Some(type_type) = allocated_type {
self.release(type_type)?;
}
self.release(object)?;
self.collect_cycles();
}
result
}
/// Base links are fixed at allocation, preventing inheritance cycles.
pub fn new_type(
&mut self,
name: impl Into<String>,
base: ObjectRef,
constructor: TypeConstructor,
) -> Result<ObjectRef, VmError> {
self.payload::<EndorTypeObject>(base, "Type")?;
let metatype = self
.type_of(base)?
.ok_or(VmError::InvalidArgument("base type has no metatype"))?;
let name = name.into();
if name.is_empty() {
return Err(VmError::InvalidArgument("type name must not be empty"));
}
let definition = self.allocate(EndorTypeObject {
name,
base: Some(base),
constructor,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
if let Err(error) = self.attach_type(definition, metatype) {
self.release(definition)?;
return Err(error);
}
Ok(definition)
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.payload::<EndorTypeObject>(type_object, "Type")?;
self.heap.attach_type(object, type_object)
}
/// Borrowed type reference; legacy/untyped host objects return None.
pub fn type_of(&self, object: ObjectRef) -> Result<Option<ObjectRef>, VmError> {
let mut current = object;
let mut visited = std::collections::BTreeSet::new();
loop {
if !visited.insert(current) {
return Err(VmError::InvalidArgument(
"cyclic shared-reference type target",
));
}
let instance = self.object(current)?;
match instance.payload().type_target(self)? {
Some(target) => current = target,
None => return Ok(instance.header().type_object()),
}
}
}
pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.payload::<EndorTypeObject>(expected, "Type")?;
let mut current = Some(candidate);
while let Some(type_object) = current {
let definition = self.payload::<EndorTypeObject>(type_object, "Type")?;
if type_object == expected {
return Ok(true);
}
current = definition.base;
}
Ok(false)
}
pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.payload::<EndorTypeObject>(expected, "Type")?;
match self.type_of(object)? {
Some(type_object) => self.is_subtype(type_object, expected),
None => Ok(false),
}
}
/// Updating the managed method table preserves nominal identity and base.
pub fn define_method(
&mut self,
type_object: ObjectRef,
name: impl Into<String>,
function: ObjectRef,
) -> Result<(), VmError> {
self.object(function)?;
let name = name.into();
if name.is_empty() || name == "type" {
return Err(VmError::InvalidArgument("reserved or empty method name"));
}
let definition = self.payload::<EndorTypeObject>(type_object, "Type")?;
if definition.fields.iter().any(|field| field.name == name) {
return Err(VmError::InvalidArgument(
"method conflicts with class field",
));
}
let mut methods = definition.methods.clone();
methods.insert(name, function);
let updated = EndorTypeObject {
name: definition.name.clone(),
base: definition.base,
constructor: definition.constructor,
methods,
fields: definition.fields.clone(),
};
self.heap.replace_payload(type_object, updated)
}
/// Atomically build a source class, including inherited constructor fields.
/// Native allocation needs its own protocol; never forge a Record payload
/// claiming to be a native visual instance.
pub fn new_class(
&mut self,
name: impl Into<String>,
base: ObjectRef,
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
) -> Result<ObjectRef, VmError> {
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
if !matches!(definition.constructor, TypeConstructor::Record) {
return Err(VmError::UnsupportedOperation(
"source subclass of native or abstract allocator",
));
}
let mut fields = definition.fields.clone();
for field in own_fields {
if field.name.is_empty()
|| field.name == "type"
|| fields.iter().any(|old| old.name == field.name)
|| self.lookup_method(base, &field.name)?.is_some()
{
return Err(VmError::InvalidArgument(
"duplicate or reserved class field",
));
}
fields.push(field);
}
for (name, method) in &methods {
self.object(*method)?;
if name.is_empty() || name == "type" || fields.iter().any(|field| field.name == *name) {
return Err(VmError::InvalidArgument(
"method conflicts with class field",
));
}
}
let metatype = self
.type_of(base)?
.ok_or(VmError::InvalidArgument("base type has no metatype"))?;
let name = name.into();
if name.is_empty() {
return Err(VmError::InvalidArgument("type name must not be empty"));
}
let class = self.allocate(EndorTypeObject {
name,
base: Some(base),
constructor: TypeConstructor::Record,
methods,
fields,
})?;
if let Err(error) = self.attach_type(class, metatype) {
self.release(class)?;
return Err(error);
}
Ok(class)
}
pub fn lookup_method(
&self,
type_object: ObjectRef,
name: &str,
) -> Result<Option<ObjectRef>, VmError> {
let mut current = Some(type_object);
while let Some(type_object) = current {
let definition = self.payload::<EndorTypeObject>(type_object, "Type")?;
if let Some(function) = definition.methods.get(name) {
return Ok(Some(*function));
}
current = definition.base;
}
Ok(None)
}
/// Source positional parameters after receiver binding. None means the
/// callable uses a native protocol without a source signature.
pub fn positional_parameter_count(
&self,
function: ObjectRef,
) -> Result<Option<usize>, VmError> {
let mut current = function;
let mut receivers = 0usize;
loop {
let payload = self.object(current)?.payload();
if let Some(bound) = payload.as_any().downcast_ref::<BoundMethod>() {
receivers = receivers
.checked_add(1)
.ok_or(VmError::ExecutionLimit("bound receivers"))?;
current = bound.function;
} else if let Some(function) = payload
.as_any()
.downcast_ref::<crate::EndorFunctionObject>()
{
let count = self
.payload::<crate::EndorCodeObject>(function.code(), "code")?
.code()
.parameters()
.len();
return count
.checked_sub(receivers)
.map(Some)
.ok_or(VmError::InvalidArgument(
"method receiver exceeds parameter count",
));
} else {
return Ok(None);
}
}
}
/// Returns one owned root, including for a newly bound inherited method.
pub fn bind_attr(&mut self, receiver: ObjectRef, name: &str) -> Result<Value, VmError> {
match self.lookup_attr(receiver, name)? {
AttributeLookup::Value(value) => {
if let Value::Object(object) = value {
self.retain(object)?;
}
Ok(value)
}
AttributeLookup::Method { function, receiver } => self
.allocate(BoundMethod { function, receiver })
.map(Value::Object),
}
}
pub(crate) fn lookup_attr(
&self,
receiver: ObjectRef,
name: &str,
) -> Result<AttributeLookup, VmError> {
let object = self.object(receiver)?;
if name == "type"
&& let Some(type_object) = self.type_of(receiver)?
{
return Ok(AttributeLookup::Value(Value::Object(type_object)));
}
if let Some(definition) = object.payload().as_any().downcast_ref::<EndorTypeObject>() {
let metadata = match name {
"name" => Some(Value::Text(definition.name.clone())),
"base" => Some(definition.base.map(Value::Object).unwrap_or(Value::Nil)),
_ => None,
};
if let Some(value) = metadata {
return Ok(AttributeLookup::Value(value));
}
if let Some(function) = self.lookup_method(receiver, name)? {
return Ok(AttributeLookup::Value(Value::Object(function)));
}
}
match self.heap.get_attr(receiver, name) {
Ok(value) => return Ok(AttributeLookup::Value(value)),
Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {}
Err(error) => return Err(error),
}
if let Some(type_object) = self.type_of(receiver)? {
if let Some(function) = self.lookup_method(type_object, name)? {
return Ok(AttributeLookup::Method { function, receiver });
}
return Err(VmError::AttributeNotFound(name.into()));
}
self.heap
.get_attr(receiver, name)
.map(AttributeLookup::Value)
}
}
pub(crate) enum AttributeLookup {
Value(Value),
Method {
function: ObjectRef,
receiver: ObjectRef,
},
}
#[derive(Debug)]
struct BoundMethod {
receiver: ObjectRef,
function: ObjectRef,
}
impl ObjectPayload for BoundMethod {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"bound-method"
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.receiver);
visit(self.function);
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(Arc::new(CallBoundMethod))
}
}
#[derive(Debug)]
struct CallBoundMethod;
impl NativeCallable for CallBoundMethod {
fn invoke(
&self,
cx: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError> {
let bound = cx
.store()
.payload::<BoundMethod>(cx.callee(), "bound method")?;
let mut values = Vec::with_capacity(arguments.len() + 1);
values.push(Value::Object(bound.receiver));
values.extend_from_slice(arguments);
Ok(NativeResult::Invoke {
function: bound.function,
arguments: values,
})
}
}
+2
View File
@@ -5,6 +5,8 @@
| 引擎与宿主边界 | [架构](architecture/README.md) | [crates](../crates/README.md) |
| 解释执行、资源修订与引擎升级 | [执行与版本](architecture/execution-and-versions.md) | frontend/compiler/core |
| EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src |
| 原生可变上下文、委托、弱引用与变更通知 | [原生调用](runtime/native-calls.md) | objects/src/native.rs、changes.rs、core/src/eval.rs |
| 托管类型、继承关系与构造 | [名义类型](runtime/types.md) | objects/src/types.rs |
| Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src |
| 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs |
| 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
+1 -1
View File
@@ -19,6 +19,6 @@ Objects 拥有对象、稳定 Box、代际句柄、引用事务、私有堆和
宿主以子模块固定 Aster 提交,并通过 Cargo 路径依赖编译到宿主进程中。每个 Vm 拥有独立 ObjectStore。ObjectRef 不跨 VM 或进程使用;EndorFrame 是语言调用帧,不是 GUI 页面。宿主决定线程、进程、输入、显示与执行任务之间的边界。
引擎从空堆启动,不预设 Python type/元类或语言根类。代码共享不可变对象,函数用显式帧向量执行;对象返回值拥有根引用,借用读取不自动 retain。Number 保留无损表示,数学运算语义尚未实现。现有帧数/指令数限制不构成内存配额或沙箱。
引擎从空堆启动,Object/Type 名义类型由宿主显式引导;普通源码 class 已接入,完整元类和 trait 尚未接入。代码共享不可变对象,函数用显式帧向量执行;对象返回值拥有根引用,借用读取不自动 retain。Number 保留无损表示,数学运算语义尚未实现。现有帧数/指令数限制不构成内存配额或沙箱。
宿主文件信息和数据库适配现归 [Hestia](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server),不保存 VM 堆。原生产服务、ASTER_* 配置、数据库、卷和路径由 Hestia 保持兼容;拆仓本身不迁移或删除这些资源。Aster 不设置 CI/CD;Lyra CI 显式验证固定引擎版本及 CLI,数据库和 HTTP 检查由 Hestia CI 承担。返回 [文档索引](../README.md)。
+1 -1
View File
@@ -10,7 +10,7 @@ Aster 是可嵌入引擎,只保留 objects、core、frontend、compiler。Obje
## 已实现
- EndorObject、稳定 Box、代际句柄、显式强引用及手动循环回收;VM 从空堆启动,没有 Python type/元类或语言根类。
- EndorObject、稳定 Box、代际句柄、显式强引用及手动循环回收;VM 从空堆启动,宿主可显式引导托管 Object/Type 和名义继承;普通源码 class、字段构造和继承方法分派已接入,trait 和原生类分配继承仍待完成,见[类型机制](../runtime/types.md)。
- Function、NativeFunction、Dict、Cell、异常数据、UTF-8 String 和无损表示优先的 Number。
- 小型 wordcode、共享代码对象、操作数/控制流/栈校验、显式执行帧及 execute/call;MakeFunction 可在执行中创建带默认参数与共享闭包 Cell 的函数。对象返回值持有根,用后 release_value;借用读取不自动 retain。
- EndorReader 在 lexer 前提供字符串/流/文件/宿主 URI provider 输入,产出不可变 SourceUnit,保存来源与不透明修订标签;带来源的词法/语法错误保留行列,Lyra parse 支持文件和 stdin。没有平台 URI 后端或版本锁实现,见 [阅读器](../runtime/reader.md)。
+2
View File
@@ -2,7 +2,9 @@
| 文档 | 内容 |
| --- | --- |
| [types.md](types.md) | 托管 Object/Type、名义继承、构造与类型边生命周期 |
| [objects.md](objects.md) | 对象、句柄与默认操作 |
| [native-calls.md](native-calls.md) | 原生上下文、非递归委托、弱引用、托管赋值与变更批次 |
| [builtins.md](builtins.md) | Number、String、Dict、Cell、Function、异常数据 |
| [reader.md](reader.md) | 输入、来源和修订标签 |
| [frontend.md](frontend.md) | 词法、语法和诊断 |
+5 -4
View File
@@ -30,17 +30,18 @@ Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图
| 层级 | 当前数据模型 |
| --- | --- |
| AstRoot | Module(Program)、Expression(Expr)、Interactive(InteractiveInput) |
| Program | declarations: Vec<Decl> 与 span;当前声明种类为 Function |
| Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block |
| Program | declarations: Vec<Decl> 与 span;声明种类为 Function、Class |
| Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block;Class 保存名称、可选基类、字段及方法 |
| Block | statements: Vec<Stmt> 与 span,不再用固定的 bindings/result 字段表示 |
| Stmt/StmtKind | Binding、Assignment、Return、Expression、Declaration;每条语句有自身范围 |
| Stmt/StmtKind | Binding、Assignment、MemberAssignment、Return、Expression、Declaration;每条语句有自身范围 |
| MemberAssignment | 对象表达式、属性名称、右侧表达式与原文范围 |
| Assignment | 名称目标、右侧表达式及原文范围;独立于 let 的 Binding 节点 |
| ReturnStatement | 可选表达式与范围;当前源码中的 return 仍要求表达式 |
| Expr/ExprKind | 名称、数字原文、字符串、符号、一元/二元运算、成员和调用 |
参数与返回类型仍为 Name,只保留语法,不实施类型或名称检查。Program::functions() 提供函数迭代便捷接口。旧的 Program.functions、Block.bindings/result/return_span 字段已移除;源码构造和使用方改为声明/语句列表。ExprKind 的既有表达式结构保留。
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块仍要求一个或多个函数;函数体接受 let/裸赋值序列后跟一个带值 return。Interactive 模式接受一次函数声明、let 绑定、裸名称赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、类等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句,以及可选的末尾带值 return。Interactive 模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、trait 等尚未实现,也不加入 FunctionType 这种 Python 专用入口。
## 解析与位置
+1 -1
View File
@@ -1,6 +1,6 @@
# Endor 基本对象与统一 Number
这些对象统一定义在独立 [objects 包](../../crates/objects/README.md),属于实现层的数据结构与宿主 API,不定义 Python type、语言根类或解析器;语言函数已接入 [小型字节码执行器](bytecode.md)。它们通过 `ObjectPayload` 接入现有 `EndorObject`,共同使用对象头、稳定 Box、代际句柄、引用计数和循环回收。不是每种对象重新实现一套堆管理。
这些对象统一定义在独立 [objects 包](../../crates/objects/README.md),属于实现层的数据结构与宿主 API,尚未自动注册到[托管名义类型](types.md),不定义解析器;语言函数已接入 [小型字节码执行器](bytecode.md)。它们通过 `ObjectPayload` 接入现有 `EndorObject`,共同使用对象头、稳定 Box、代际句柄、引用计数和循环回收。不是每种对象重新实现一套堆管理。
## 对应关系
+11 -4
View File
@@ -26,7 +26,7 @@ Endor 是解释型语言,字节码是内部执行表示;从 AST 生成字节
所有执行位置和跳转距离以**两字节代码单元**计数,包含扩展参数单元;不是字节偏移,也不一定等于逻辑指令下标。前跳从当前逻辑指令之后的位置加距离;后跳从该位置减距离。目标必须是完整逻辑指令的开头,不能跳进扩展参数中间。
当前有 29 个可执行操作码和一个由编解码器处理的 EXTENDED_ARG:
当前有 30 个可执行操作码和一个由编解码器处理的 EXTENDED_ARG:
| 指令组 | 内容 |
| --- | --- |
@@ -37,7 +37,7 @@ Endor 是解释型语言,字节码是内部执行表示;从 AST 生成字节
| 控制流 | `JumpForward`、`JumpBackward`、`PopJumpIfFalse`、`PopJumpIfTrue` |
| 真值与比较 | `ToBool`、`UnaryNot`、`CompareOp` |
| 闭包 | `LoadDeref`、`StoreDeref`、`DeleteDeref`、`LoadClosure` |
| 容器 | `BuildMap` |
| 容器与类型 | `BuildMap`、`BuildClass` |
没有把 Number 转为 f64 来补运算。数字仍以无损表示为主;真假可以根据是否为零判断,数值比较和算术尚未定义。文本可作内容比较,普通对象使用现有比较接口。
@@ -152,11 +152,11 @@ fn main() -> Result<(), endor_core::VmError> {
}
```
该示例直接提供指令,不经过源码前端;当前 Endor 前端可生成 AST,但尚不能从源码生成字节码,也不解析 Python 源码。宿主仍负责准备代码对象和执行环境;函数既可由宿主 new_function 工厂创建,也可由上述 MakeFunction 在字节码运行期间创建。
该示例直接提供指令,不经过源码前端;当前 Endor 的 frontend/compiler 已能从源码生成 AST 和字节码,也不解析 Python 源码。宿主仍负责准备代码对象和执行环境;函数既可由宿主 new_function 工厂创建,也可由上述 MakeFunction 在字节码运行期间创建。
## 限额、诊断与边界
`ExecutionLimits` 默认限制 1000 个活动帧,指令配额为 `usize::MAX`,正常使用不设置有限的指令预算。`execute_with_limits`/`call_with_limits` 可显式指定限额,所有嵌套语言调用共享计步器;扩展参数不单独计步。可选指令预算是 Endor 的宿主运行边界,不是 CPython 默认行为,也不会抢占正在执行的宿主回调。
`ExecutionLimits` 默认限制 1000 个活动帧,指令配额为 `usize::MAX`。`execute_with_limits`/`call_with_limits` 可显式指定限额,嵌套语言调用和原生分派共享计步器;每次原生分派额外计一步,扩展参数不单独计步。原生 Invoke 委托通过同一帧循环推进,不重置预算;详见 [原生调用](native-calls.md)。指令预算不会抢占正在执行的宿主回调。
运行错误仍返回 `VmError`。`last_traceback()` 另提供从外到内的函数名、文件、代码首行和出错指令偏移;它保存独立元数据,不保活已经清理的帧。首行是代码声明的 first_line,不是由行号表推导出的实际错误源码行。成功的新一次调用会清空旧记录。尚无语言异常对象传播、raise/try/except 或完整源码行号表。
@@ -175,3 +175,10 @@ fn main() -> Result<(), endor_core::VmError> {
`ObjectPayload::assign_from(source)` 是可选协议,默认返回 false,普通值与对象仍替换变量绑定。StoreGlobal、StoreFast、StoreDeref 在既有值和新值均为对象时调用此协议;返回 true 表示宿主对象已替换自己的目标,绑定保留旧对象身份,其他别名随共享引用观察更新。相同句柄赋值为无操作。此协议同时适用于这些存储指令实现的声明初始化/赋值;字典 API 与 StoreAttr 不隐式调用它。
协议不传递变量名字或 UI 元数据,也不引入渲染器。实现必须先验证再修改,拒绝替换时保留旧目标;不得通过可变 payload 悄悄增加未跟踪的 VM 对象边。宿主原生关系如持有受管理对象,必须通过 VM retain/release 登记根。原生资源的内部引用可以由宿主维护,但其管理对象身份和存活根仍由 Aster 校验和计数。StoreFast 会先持有选择后的值,再释放栈值与旧槽,失败执行仍走统一帧清理。
## 类创建与方法读取
BuildClass(编号 29,无参数)消费四个栈值 `[name, baseType, fieldsDict, methodsDict]`,产生一个受管类对象;验证器按 4 入 1 出检查栈。字段字典按声明顺序保存字段名与注解名,方法字典保存 MakeFunction 创建的函数。VM 先验证基类、字段与方法关系,再创建完整类型;输入根在成功前保持在执行栈上,失败由帧统一清理。
LoadAttr 对实例自有属性返回原值;缺失时沿类型父链找方法并创建绑定方法。绑定方法持有 receiver 和选定的函数,调用时经已有非递归委托路径补入 self。类对象上的方法读取提供未绑定函数;type/name/base 元数据优先。宿主读取继承方法应使用返回拥有式结果的 bind_attr;get_attr 保持借用式读取,类型方法结果为未绑定函数。所有委托消耗同一指令预算。
+8 -3
View File
@@ -15,7 +15,7 @@
## 名称与作用域
首版是模块作用域和函数作用域。模块先登记全部函数名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 与首次赋值目标按出现顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。
首版是模块作用域和函数作用域。模块先登记全部函数/类名,再分析函数体,因此支持前向引用、自递归和函数作为普通值传递。函数参数占据最前面的局部槽,let 与首次赋值目标按出现顺序分配后续槽;引用优先使用函数局部,其次使用已声明全局。
函数内先收集全部局部名称,再按语句顺序检查初始化。后面声明的 let 会遮蔽整个函数中的同名全局,但提前读取报错;let x = x 也会报未初始化。参数、局部绑定和同一模块的函数不允许在各自作用域内重复声明;参数或局部可以遮蔽模块函数。裸名称赋值 `x = expression` 可在交互全局创建或更新名字;函数内赋值复用已有参数/局部槽,不存在时分配局部槽。赋值右侧先求值再写入,失败不会覆盖该目标;函数内赋值目标仍遮蔽同名全局,首次初始化前读取报错。`let` 的重复声明检查不变。嵌套函数/闭包分析和块级作用域尚未实现。
@@ -31,10 +31,12 @@ CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称
| 全局读取/赋值 | LoadGlobal/StoreGlobal,使用该代码对象的 names 表 |
| 函数声明 | 子代码模板 → LoadConst(code) → MakeFunction(0, 0) → StoreGlobal |
| 普通调用 | 求值 callee → PushNull → 从左到右求值位置参数 → Call(argc) |
| 成员读取 | 求值对象 → LoadAttr;沿用 VM 属性协议,无自动 self 绑定 |
| 成员读取 | 求值对象 → LoadAttr;沿用 VM 属性协议;类型方法返回持有接收者的绑定方法 |
| 属性赋值 | 求值右侧,再求值目标对象,使用 StoreAttr 维护托管引用与变更 |
| 类声明 | 名称、基类、字段映射、MakeFunction 构造的方法映射 → BuildClass → StoreGlobal |
| return | 求值结果 → ReturnValue;无值 return 或 AST 函数末尾无 return 时生成 nil |
源码 parser 仍要求函数体为 let/裸赋值序列加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。
源码 parser 接受绑定/名称及属性赋值/表达式语句与可选末尾带值 return;空 return 仍仅由 AST 调用方构造。模块初始化发布函数/类并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。
二元算术、非字面量的一元负号和符号值 .name 目前产生明确 Compilation 诊断,不伪造运行结果。导入、类、Agent、条件/循环、关键字参数等仍未实现。函数参数个数和对象是否可调用由 VM 在运行时检查。
@@ -53,3 +55,6 @@ Expression 模式返回表达式值;Interactive 模式严格编译一次声明
lyra run [--entry name] <file|-> 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。lyra eval <file|-> 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 VM str;失败仅输出 stderr 并返回非零退出码。文件和 stdin 严格 UTF-8,stdin 到 EOF 后才处理。Lyra 在成功或失败后释放代码/结果并清空自己创建的命名空间。
可执行文件见 [示例目录](../../crates/compiler/examples/README.md),API/所有权与错误路径见 [pipeline.rs](../../crates/compiler/tests/pipeline.rs),进程入口见 [CLI 检查](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli/src/branch/main/crates/cli/tests/README.md)。引擎和进程入口检查由独立 Lyra CI 分别显式执行;本地只做静态检查,不据此宣称实际执行通过,见 [验证约定](../operations/validation.md)。返回 [运行时索引](README.md)。
类方法使用显式首参数 self;编译器检查本类重复字段/方法和缺少 self。方法代码仍是共享的普通代码对象,参数类型名目前不实施运行时类型检查。BuildClass 在 VM 中检查继承字段冲突和原生分配边界;构造失败不发布类型到目标全局名。默认基类 Object 须由宿主通过 CompileOptions 声明并在运行全局中提供,编译器不自行引导类型。普通类构造和继承分派见[类型机制](types.md)。
+7 -4
View File
@@ -52,12 +52,12 @@ AST 结构及 Visitor、Spanned 和跨工具 JSON 契约见 [AST 模型](ast.md)
program = function+
function = "function" name "(" parameters? ")" ":" name block
parameter = name ":" name
block = "{" (binding | assignment)* "return" expression "}"
block = "{" (binding | assignment | expression)* ("return" expression)? "}"
binding = "let" name "=" expression
assignment = name "=" expression
```
parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函数体要求一个末尾 return;函数体尚不支持独立表达式语句、属性赋值、嵌套函数声明或默认参数源码写法。新增 Interactive 解析模式接受一次函数声明、let 绑定、裸名称赋值或表达式,完整消费输入,但不创建 REPL 或执行代码;AST 可表示的语句组合比当前源码子集更广。参数和返回类型目前只是名称节点;并不据此创建语言 type 对象或检查类型。
parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函数体支持独立表达式语句(包括方法调用)和可选的末尾 return;省略 return 时编译器生成 Nil 结果。函数体支持属性赋值,尚不支持嵌套声明或默认参数源码写法。Interactive 解析模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,完整消费输入,但不创建 REPL 或执行代码。参数和返回类型目前只是名称节点;并不据此创建语言 type 对象或检查类型,省略 return 不代表已实施 Unit 类型检查。
表达式包括名称、数字、字符串、`.name` 符号、括号分组、成员访问与位置参数调用、一元负号以及四则运算。优先级从高到低为:
@@ -68,7 +68,7 @@ parameters 和调用参数列表使用逗号分隔,不接受尾随逗号。函
符号字面量、名称与成员分别保留为独立 AST 结构。true、false、Nil 等词目前只是普通名称,没有额外的源码字面量或内建值绑定。
use、namespace、class、agent、完整类型语法、条件/循环、比较/布尔运算和闭包声明等尚未加入首版。当前语法可修改;扩展时同步 AST、PEG 规则、样例和测试,不将旧草案视作不可修改的 ABI。
use、namespace、trait、agent、完整类型语法、条件/循环、比较/布尔运算和闭包声明等尚未加入首版。当前语法可修改;扩展时同步 AST、PEG 规则、样例和测试,不将旧草案视作不可修改的 ABI。
## 诊断和命令
@@ -78,4 +78,7 @@ use、namespace、class、agent、完整类型语法、条件/循环、比较
当前 Compiler 将 AST 经模块/函数名称分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。MakeFunction 已接入源码函数编译,但解析接受的二元算术和符号值仍没有执行语义;编译器会给出明确诊断,见 [编译链路](compiler.md)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。
裸赋值 `name = expression` 创建或更新当前作用域中的名称;它不是表达式,不能链式赋值,当前也不接受属性/索引目标。`let` 仍表示声明,保留重复声明检查;名称与初始化规则见 [Compiler](compiler.md)。两种源码写法产生独立的 Binding/Assignment 节点,宿主不改写输入。
裸赋值 `name = expression` 创建或更新当前作用域中的名称;它不是表达式,不能链式赋值,属性目标使用独立 MemberAssignment 节点;索引目标尚不支持。`let` 仍表示声明,保留重复声明检查;名称与初始化规则见 [Compiler](compiler.md)。两种源码写法产生独立的 Binding/Assignment 节点,宿主不改写输入。
源码类实现子集采用 `class Name: Base { field: TypeName function method(self: Name): ResultType { ... } }`;基类可省略,执行环境须提供 Object。成员函数沿用既有 function 语法,接收者 self 为显式首参数。普通类字段按基类字段、再本类字段的顺序接收位置构造实参。这里记录实现范围,不确认语言文档中的全部候选构造语法;详细限制见[名义类型](types.md)。
+2
View File
@@ -29,6 +29,8 @@ Rust panic 尚无专门的捕获恢复或执行根 RAII 清理机制。宿主捕
## 回收和整体销毁
NativeContext 的分配与弱引用升级使用作用域根,成功时仅移交 Return 或 Invoke 结果,失败时释放临时持有。WeakObjectRef 和变更通知不保活对象;具体接口见 [原生调用](native-calls.md)。
通常释放在总引用数归零时立即执行。堆使用显式工作列表递减出边,长链不会递归析构。不可达循环通过手动 collect_cycles 从根标记后批量移除;没有自动阈值、分代或增量 GC。
Vm 没有独立后台线程、暂停状态或显式 shutdown 状态机。Rust 所有权销毁它所拥有的堆和剩余载荷,包括没有先做循环回收的对象;原生共享资源继续遵循各自的 Rust 所有权。外部 ObjectRef 不保活 Vm。
+55
View File
@@ -0,0 +1,55 @@
# 原生对象调用、弱引用与变更记录
本页定义宿主扩展使用的通用接口,不引入渲染或窗口语义。托管名义类型见[类型机制](types.md)。
## 有上下文的调用
旧 ObjectPayload::call 保留借用参数和借用结果协议。新载荷可通过 native_callable 返回
Arc<dyn NativeCallable>;VM 先取得独立分派器,再可变访问 ObjectStore,避免同时借用
堆中载荷与可变对象存储。ContextNativeFunction 提供名称、分派器和不可变捕获边列表。
捕获中的 ObjectRef 必须全部在 captures 中登记,未登记句柄不能保活对象。
NativeCallable::invoke 接收 NativeContext 和借用参数。context 提供对象、字典、Cell
分配、托管属性和容器写入、载荷事务替换及弱引用升级。store() 仅暴露只读访问;
callee() 返回当前可调用对象的借用句柄;attach_type 通过受校验的类型边事务附加实例类型。
没有不受控的 &mut Vm、手动释放根或重入执行器入口。
context 内分配或升级得到的对象由本次作用域临时持有。NativeResult::Return(value)
返回借用结果;作用域为结果额外持有一份根,再释放其余临时根。出错或 unwind 也会
清理本作用域根,但此前完成的业务写入不会回滚;这不承诺整个执行帧的 panic 恢复。
NativeResult::Invoke { function, arguments } 将调用委托给另一个原生或语言函数。
function 和每个对象参数在移交前取得根;VM 完成调用准备后释放这些临时输入。
委托通过原有帧循环推进,不递归调用 Vm::call;语言函数使用原有帧限额。每次原生
分派计入同一指令预算,避免无界原生委托链。预算仍不能抢占一次长时间原生操作。
委托函数的结果就是该次调用结果,当前不提供任意原生调用后续 continuation。
## 托管共享引用赋值
assignment_cell(source, store) 是与原生 assign_from 并存的可选协议。实现先只读验证
目标及组合环,返回自身持有的内建 Cell;对象存储再事务性地将 source 写入 Cell。
修改的引用属于可追踪堆边,旧对象身份与方法不变。默认返回 None,沿用既有赋值规则。
Cell 的持有边也必须通过 trace 或托管字段登记;字典 API 和属性写入不隐式执行此协议。
## 弱引用
downgrade 验证对象后产生 WeakObjectRef,不增加强引用。upgrade 在同一个对象存储中
重新验证堆、槽位与代次;存活时返回一份新的拥有式根,过期返回 None,其他存储返回
ForeignHandle。释放后复用槽位不会使旧弱引用指向新对象。弱引用不保活 VM 本身。
宿主使用升级结果后须 release;NativeContext::upgrade 的升级根归当前调用作用域。
## 对象变更
每个对象头有从 0 开始的 change_version。成功的属性写入或删除产生 Attribute(name),
字典、Cell、通用载荷替换及共享引用赋值产生 Contents;失败写入不产生变更。
同值写入仍视为成功写入,消费方可自行比较派生结果。版本耗尽拒绝修改。
take_changes 按对象与变更键合并同一批写入,返回最终版本。记录不持有对象,消费时
对象可能已经回收,应重新验证身份。通知只保证修改信息,不包含旧值或回滚日志。
最多保留 4096 个不同变更键,超额后清空细项并设置 rescan_required,直到下一次提取;
消费方必须完整重查依赖,不能把溢出当成没有变化。版本仍继续递增。
引擎只记录通用对象变化,依赖订阅、视觉失效和事件提交由宿主负责。托管原生状态须
通过 replace_payload、属性或容器事务更新;载荷内部自行变动不会自动通知。
远端回归用例见 [native_context.rs](../../crates/core/tests/native_context.rs);本地不执行测试。
+8 -8
View File
@@ -1,6 +1,6 @@
# EndorObject 代码说明
当前实现的是 Endor 的 VM 运行时基础对象,角色类似 CPython 的公共对象结构。这里没有语言根类、元类、继承或 Python type 系统。标量仍然可以直接保存在 Value 中,不要求一切装箱。
当前实现的是 Endor 的 VM 运行时基础对象,角色类似 CPython 的公共对象结构。宿主可显式引导托管 Object/Type 与名义继承,见[类型机制](types.md);普通源码类和继承方法分派已实现,trait 和原生类分配继承尚待接入。标量仍然可以直接保存在 Value 中,不要求一切装箱。
基本对象及统一数字表示已在此基础上加入,详见 [基本对象与 Number](builtins.md)。`builtins.rs` 定义字符串、字典、Cell、函数和异常数据;`number.rs` 定义无损数字表示;`store_builtins.rs` 提供容器操作及关联对象校验。
@@ -12,15 +12,15 @@
```text
EndorObject
header 身份、总强引用数、外部强引用数
header 身份、总强引用数、外部强引用数、变更版本、可选类型引用
payload 原生 Rust 数据及操作接口
attributes VM 管理的可变属性表
payload_edges 分配时记录的载荷引用快照
```
`ObjectHeader` 不保存语言类型指针。`identity` 是 ObjectRef,用于识别同一个对象。`strong_refs` 是所有强引用的总数,`external_refs` 统计宿主持有和执行帧持有的根引用,也是循环回收的根来源。
`ObjectHeader.type_object` 是可选的、受 GC 追踪的类型对象引用,普通实例附加后不可更改。`identity` 是 ObjectRef,用于识别同一个对象。`strong_refs` 是所有强引用的总数,`external_refs` 统计宿主持有和执行帧持有的根引用,也是循环回收的根来源。
`payload` 是 `Box<dyn ObjectPayload>`。Box 保存原生数据,dyn trait 让运行时通过统一接口调用不同 Rust 实现。Rust 会为 trait 分发生成所需机制;不需要创建一个 Python 式的 Type 对象。`kind_name` 只用于诊断,`as_any` 只用于原生 Rust 扩展判断自己能否处理另一份载荷。
`payload` 是 `Box<dyn ObjectPayload>`。Box 保存原生数据,dyn trait 让运行时通过统一接口调用不同 Rust 实现。Rust 会为 trait 分发生成所需机制;其原生操作接口与托管 Type 身份分别保存。`kind_name` 只用于诊断,`as_any` 只用于原生 Rust 扩展判断自己能否处理另一份载荷。
`attributes` 保存字段名和 Value。对象值会被 VM 计为内部强引用;字符串和整数等直接保存。载荷可选择是否允许属性,例如 Record 允许,NativeFunction 默认不允许。
@@ -77,7 +77,7 @@ ObjectPayload 提供以下默认行为,载荷可以逐项覆盖:
Record 使用身份语义并开启属性。NativeFunction 保存名称和 Rust 函数指针,覆盖 repr 和 call。原生函数收到 Value 参数并返回 Result<Value, VmError>;VM 在调用前后检查参数和返回值中的对象句柄。
原生回调当前不能修改 VM 或直接在该 VM 中分配新对象;其参数和原始返回对象值是借用句柄。Vm::call 会验证并 retain 对象结果,对外统一返回一份持有式结果。语言函数已通过 [wordcode 执行器](bytecode.md) 使用显式帧、参数槽、闭包和求值栈执行;call/execute 的结果完成使用后须 release_value。
旧 call 回调不能修改 VM 或分配新对象;需要这些能力的原生方法可使用 NativeCallable 与受限 NativeContext。两种原生入口都接受借用参数,Vm::call 对外统一返回一份持有式结果。上下文临时根、非递归委托与捕获规则见 [原生调用](native-calls.md)。语言函数通过 [wordcode 执行器](bytecode.md) 使用显式帧、参数槽、闭包和求值栈执行;call/execute 的结果完成使用后须 release_value。
compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。它不实现 Python 的子类型优先规则。内容比较由扩展定义;如果两个对象内容相等,它们必须给出相同哈希,或者明确拒绝哈希。
@@ -92,7 +92,7 @@ compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。
引用计数无法自行释放 A → B → A,所以提供显式 `collect_cycles()`:
1. 把 external_refs 大于零的对象作为根。
2. 沿托管属性和载荷边标记所有可达对象。
2. 沿类型、托管属性和载荷边标记所有可达对象。
3. 先从槽位中摘除全部不可达对象。
4. 扣减垃圾指向幸存对象的引用,再 Drop 全部垃圾。
@@ -100,8 +100,8 @@ compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。
## 当前边界
已实现:统一对象结构、稳定地址、代际句柄、身份、显式引用计数、托管属性、原生操作覆盖、比较与哈希、原生调用、资源清理和显式循环回收。
已实现:统一对象结构、稳定地址、代际句柄、身份、显式引用计数、托管属性、原生操作覆盖、比较与哈希、原生调用、资源清理和显式循环回收。另有受限原生可变上下文、安全弱引用、托管 Cell 赋值及有界对象变更记录,见 [原生调用](native-calls.md)。
尚未实现:Python type 系统、语言根类、继承和描述符、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,基础源码执行由独立 [Compiler](compiler.md) 接入,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server/src/branch/main/docs/runtime/database-objects.md)。
尚未实现:Python type 兼容、trait、原生类分配继承和描述符、任意 VM 重入调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,基础源码执行由独立 [Compiler](compiler.md) 接入,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server/src/branch/main/docs/runtime/database-objects.md)。
验证范围与执行约定见 [验证](../operations/validation.md)。
+56
View File
@@ -0,0 +1,56 @@
# 托管名义类型
类型与实例保存在同一个 ObjectStore 中。此处是已实现的运行时机制;源码普通 `class`、字段构造和继承方法分派已经接入;trait、原生类分配继承和完整初始化协议尚未接入,不把 Rust 的 ObjectPayload 当成 Endor 的类定义。
## 创建与关系
ObjectStore/Vm 仍从空堆启动。宿主显式调用 `bootstrap_types()` 得到 Object 和 Type 两个类型对象,每个返回字段各拥有一份外部根,需要分别释放。
- Object 没有父类型。
- Type 的父类型是 Object。
- Object 的实例类型是 Type。
- Type 的实例类型也是 Type。
`new_type(name, base, constructor)` 创建一份新的名义定义,并沿 base 取得元类型。名字相同不代表类型相同。base 在创建时固定,不能通过替换载荷或修改属性改变,因此继承链不会因后续修改出现循环。`attach_type(instance, definition)` 只允许给尚未附加类型的对象设置一次类型,验证同堆、存活状态和 Type 载荷。已有 `type` 普通字段时拒绝附加,避免遮蔽数据。旧的原生对象可以继续保持未分类。
`type_of` 返回借用式类型引用;`is_subtype(candidate, expected)` 检查名义父链,含自身;`is_instance(instance, expected)` 先取得实例类型,再检查父链。非法句柄和非 Type 参数报错,不返回假值来掩盖错误。这些接口目前面向堆对象,直接保存在 Value 中的标量尚未统一接入。
## 源码可见操作与构造
带类型的对象通过 `get_attr(object, "type")` 暴露类型对象,不能写入或删除。Type 对象额外提供只读的 `name`、`base`;根 Object 的 base 为 Nil。源码前端已有的成员读取和调用表达式可以直接使用这些对象。类型名称由宿主注册到全局字典,Aster 不硬编码 Block 或 Page。
TypeConstructor 三种入口:
- Record:按声明字段接收位置参数,创建带此类型的普通 Record 实例;无字段时不接收参数。
- Native(callable):通过现有显式 VM 调用循环委托给托管可调用对象;宿主构造器负责返回该类型的实例。
- Abstract:拒绝直接构造。
原生 Type 调用通过 NativeContext::callee 取得当前定义,委托计入现有指令限额,不递归调用 Rust 求值器。尚不提供构造后初始化 continuation。
共享引用载荷可显式实现 `type_target(store)`:外壳身份保持稳定,类型查询沿当前被引用对象解析。目标必须由已登记的托管边保活;查询验证句柄并检测循环。普通实例使用固定 header 类型。Tessera 的 Block 共享赋值选择此协议,使 TextBlock 引用换成 RectBlock 后,类型查询与实际展示一致。这不是修改类继承链。
## 生命周期
header 的类型引用、Type 的 base 和原生构造器都是计数和循环回收可见的强边。实例保活其定义;类型对象及元类型没有隐藏的全局根。释放宿主根后,仍有实例的类型保留,无人引用的定义、Object/Type 环和原生构造器捕获环可以一起回收。
远端回归用例见 [types.rs](../../crates/core/tests/types.rs),覆盖名义身份、关系区分、类型边保活与回收、只读元数据及外来句柄失败原子性。本地仅编译检查,不执行测试。
## 源码普通类与方法
```endor
class Counter {
value: Number
function read(self: Counter): Number { return self.value }
}
class DetailedCounter: Counter {
label: String
function read(self: DetailedCounter): Number { return 12 }
}
```
当前可用 `DetailedCounter(9, "demo")` 构造,位置参数依次对应继承字段及本类字段;`object.field = value` 经 StoreAttr 更新同一对象并记录属性变更。方法需显式声明 self;`object.read` 创建持有该接收者的绑定方法,保存后不因原变量重新赋值而失效。方法体通过 self 再次取方法时按实例实际类型分派,子类可覆盖方法。字段与方法不能相互重名,子类不能重新声明继承字段;类型身份仍按定义对象区分。
方法表本身是类型对象拥有的托管边。宿主 define_method 可显式替换条目,不改变类型身份、基类或已有绑定方法选中的函数。bind_attr 返回一份拥有式根;get_attr 返回借用值和未绑定方法,宿主不能把两者的调用/释放协议混用。positional_parameter_count 会扣除绑定的接收者,供宿主判断事件回调的剩余源码参数。
这是一份实现子集说明:构造器只按字段赋值,没有默认值、命名字段构造或隐式 init;字段/参数/返回注解保留名称,尚未实施类型检查。普通类当前只能继承普通 Record 分配的类,继承原生或抽象分配器明确报错,不能伪造带 Block 名义身份的普通 Record。trait、原生视觉子类和用户自定义测量/绘制协议仍待实现。正式语言文档的候选语法不因这一实现被自动确认为规范。