mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
413 lines
22 KiB
Rust
413 lines
22 KiB
Rust
//! Promise 构造与内建安装:`Construct(C, args)` 通用路径(native /
|
||
//! bytecode 构造器)与 %Promise% 内建对象初始化。
|
||
//!
|
||
//! `init_promise_intrinsics` 在 VM 创建与 `full_reset` 后调用:原型继承
|
||
//! session 的 Object/Function 原型,session 重建后须重挂并重绑 global 槽。
|
||
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_runtime_api::NativeResult;
|
||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||
use oxide_types::value::JsValue;
|
||
|
||
use crate::vm::{FrameArgs, FrameContinuation, Vm};
|
||
|
||
use super::aggregate::{
|
||
promise_static_all, promise_static_all_settled, promise_static_any, promise_static_race, promise_static_reject,
|
||
promise_static_resolve, promise_static_try, promise_static_with_resolvers,
|
||
};
|
||
use super::reactions::{promise_catch, promise_finally, promise_then};
|
||
use super::{is_constructor_value, PromiseState, PromiseStateKind};
|
||
|
||
impl Vm {
|
||
/// 创建空 Promise 对象(proto = `%Promise.prototype%`),状态盒为 Pending。
|
||
pub(super) fn create_promise_object(&mut self) -> JsValue {
|
||
let proto_val = JsValue::from_js_object(self.promise_proto.as_ptr() as *mut JsObject);
|
||
let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||
// SAFETY: `alloc_object` 返回非空 arena 指针;此处写 type_tag 与 native_data
|
||
// (Box<PromiseState> 由 Box::into_raw 分配,随对象释放),无别名。
|
||
let obj = unsafe { &mut *ptr };
|
||
obj.type_tag = JsObject::OBJ_TYPE_PROMISE;
|
||
let state = Box::new(PromiseState {
|
||
state: PromiseStateKind::Pending,
|
||
result: JsValue::undefined(),
|
||
reactions: Vec::new(),
|
||
resolve_fn: JsValue::undefined(),
|
||
reject_fn: JsValue::undefined(),
|
||
already_resolved: false,
|
||
promoted_clone: std::ptr::null_mut(),
|
||
});
|
||
obj.set_native_data(Box::into_raw(state) as *mut u8);
|
||
JsValue::from_js_object(ptr)
|
||
}
|
||
|
||
/// 构造调用(Construct(C, args)):native 构造器值传递调用,bytecode 构造器
|
||
/// 压构造帧执行(含 derived 构造器 super() 语义),返回值非对象时回退到新对象。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `ctor` 非可构造值(箭头 / 非构造 native / 普通值)返回 TypeError 的 `Err`;
|
||
/// 消费 `last_uncaught_value`,调用方不得再取槽。
|
||
pub(crate) fn construct_ctor(&mut self, ctor: JsValue, args: &[JsValue]) -> Result<JsValue, JsValue> {
|
||
self.construct_with(ctor, ctor, args)
|
||
}
|
||
|
||
/// 带 newTarget 覆写的构造调用(Construct(C, args, newTarget)):this 与
|
||
/// new.target 均按 `new_target` 推导——this = OrdinaryCreateFromConstructor(newTarget),
|
||
/// 构造帧 new.target 槽 = newTarget。Reflect.construct 面的入口;newTarget 与
|
||
/// 构造器本身同值时与 `construct_ctor` 逐位等价。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `ctor` 非可构造值返回 TypeError 的 `Err`;`new_target` 的可构造性由调用方
|
||
/// 入口校验。失败时消费 `last_uncaught_value`,`Err` 携带原始抛出值,
|
||
/// 调用方不得再取槽。
|
||
pub(crate) fn construct_with(
|
||
&mut self, ctor: JsValue, new_target: JsValue, args: &[JsValue],
|
||
) -> Result<JsValue, JsValue> {
|
||
// IsConstructor 校验:arrow / 非构造 native / 普通值拒绝。
|
||
if !is_constructor_value(ctor) {
|
||
return Err(oxide_builtins::error::create_type_error(self, "constructor is not a constructor"));
|
||
}
|
||
// SAFETY: `is_constructor_value` 已含对象校验,指针非空且指向存活对象;
|
||
// 此处只读 native_fn() 判定分支,不跨 GC/reset。
|
||
let ctor_obj = unsafe { &*ctor.as_js_object_ptr() };
|
||
// SAFETY: 调用方入口已对 new_target 做过同谓词 IsConstructor 校验,
|
||
// 指针非空且指向存活对象。
|
||
let nt_obj = unsafe { &*new_target.as_js_object_ptr() };
|
||
// native 构造器:receiver 为新对象(this 取 newTarget.prototype),值传递调用。
|
||
if ctor_obj.native_fn().is_some() {
|
||
// DataView 构造器不预分配 this:其体内先做全部校验、后读 newTarget.prototype
|
||
// (抛错的 prototype getter 不得先于越界 RangeError 触发),占位 receiver
|
||
// 不可观测(构造器恒返回自建对象);原型取 %DataView.prototype% 使构造器
|
||
// 的 receiver 原型链判定通过。
|
||
let is_data_view = std::ptr::eq(
|
||
ctor.as_js_object_ptr(),
|
||
self.session.builtin_world().data_view_constructor.as_ptr() as *mut JsObject,
|
||
);
|
||
let this_val = if is_data_view {
|
||
let data_view_proto_val =
|
||
JsValue::from_js_object(self.session.builtin_world().data_view_proto.as_ptr() as *mut JsObject);
|
||
JsValue::from_js_object(self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, data_view_proto_val)))
|
||
} else {
|
||
let this_ptr = self.alloc_ctor_this(nt_obj, ctor_obj, new_target)?;
|
||
JsValue::from_js_object(this_ptr)
|
||
};
|
||
// newTarget 经 reg(255) 暴露给 native 构造器(快照/恢复:call 窗口
|
||
// 不触及 255);构造形态标记同窗夹持(调用后恢复)。
|
||
let saved_nt = self.regs[255];
|
||
let saved_constructing = self.constructing_native;
|
||
self.regs[255] = new_target;
|
||
self.constructing_native = true;
|
||
let result = self.call_function_sync(ctor, this_val, args);
|
||
self.constructing_native = saved_constructing;
|
||
self.regs[255] = saved_nt;
|
||
return match result {
|
||
Ok(ret) if ret.is_object() => Ok(ret),
|
||
Ok(_) => Ok(this_val),
|
||
Err(e) => Err(self
|
||
.last_uncaught_value
|
||
.take()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e))),
|
||
};
|
||
}
|
||
self.call_constructor_bytecode_inline(ctor, ctor_obj, nt_obj, new_target, args)
|
||
}
|
||
|
||
/// 分配构造 this:proto = newTarget.prototype(缺省 Object.prototype)。
|
||
///
|
||
/// 按**构造器**(非 newTarget)种类分读形:native 构造器保持裸存储读
|
||
/// (占位 receiver 的原型不可观测,可观测面由各构造体自重读覆盖,native
|
||
/// 面的 getter 触发权归构造体的规范步序位);字节码构造器走传播读
|
||
/// (Ordinary [[Construct]] 的 GpFC 先于函数体求值且读体是唯一读点,
|
||
/// 访问器 getter 的异常须原值上抛,非对象结果回落 Object.prototype)。
|
||
fn alloc_ctor_this(
|
||
&mut self, nt_obj: &JsObject, ctor_obj: &JsObject, nt_val: JsValue,
|
||
) -> Result<*mut JsObject, JsValue> {
|
||
let proto_si = self.kernel_core.perm_interner().intern("prototype").0;
|
||
let object_proto = JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
|
||
let is_native_ctor = ctor_obj.native_fn().is_some() && ctor_obj.type_tag == JsObject::OBJ_TYPE_CONSTRUCTOR;
|
||
let proto_val = if is_native_ctor {
|
||
match self.resolve_property(nt_obj, proto_si) {
|
||
Some(p) if p.is_object() => p,
|
||
_ => object_proto,
|
||
}
|
||
} else {
|
||
let pc_before = self.pc;
|
||
match self.ordinary_get(nt_obj, proto_si, nt_val) {
|
||
Ok(v) if self.pc == pc_before => {
|
||
if v.is_object() {
|
||
v
|
||
} else {
|
||
object_proto
|
||
}
|
||
}
|
||
Ok(_) => {
|
||
// depth-0 getter 抛已就地展开跳走(构造入口恒在 native 上下文,
|
||
// 结构性不可达):停掉构造流,不续行。
|
||
return Err(JsValue::undefined());
|
||
}
|
||
Err(err) => {
|
||
// 重入路径:恢复原始异常值,按 construct_with 契约携带原值。
|
||
return Err(self
|
||
.last_uncaught_value
|
||
.take()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &err)));
|
||
}
|
||
}
|
||
};
|
||
Ok(self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)))
|
||
}
|
||
|
||
/// bytecode 构造器构造调用:压构造帧内嵌 dispatch 执行(derived 构造器
|
||
/// super() 前 this = undefined,new.target = newTarget),this 与构造 this
|
||
/// 均按 newTarget 分配,结果经 `do_return` 交付 regs[0](非对象回退构造 this)。
|
||
///
|
||
/// # 副作用
|
||
/// - 经 `save_inline_state` / `restore_inline_state` 保存恢复调用方执行状态。
|
||
/// - `construct_dispatch` 标志令构造帧弹出时交付结果而非继续执行。
|
||
fn call_constructor_bytecode_inline(
|
||
&mut self, ctor: JsValue, ctor_obj: &JsObject, nt_obj: &JsObject, new_target: JsValue, args: &[JsValue],
|
||
) -> Result<JsValue, JsValue> {
|
||
// 按构造器自身记录的表代际解析:代际表缺失(已回收)或下标越界与
|
||
// 原生函数哨兵同口径报 TypeError。
|
||
let callee_module = match self.callee_module(ctor_obj) {
|
||
Some(m) => m,
|
||
None => {
|
||
return Err(oxide_builtins::error::create_type_error(self, "constructor is not a constructor"));
|
||
}
|
||
};
|
||
if callee_module.is_generator || callee_module.is_async {
|
||
return Err(oxide_builtins::error::create_type_error(self, "constructor is not a constructor"));
|
||
}
|
||
// 先拷出寄存器数,释放对代际表的借用后再进入可变借用区。
|
||
let callee_reg_count = callee_module.n_registers;
|
||
let new_obj_ptr = self.alloc_ctor_this(nt_obj, ctor_obj, new_target)?;
|
||
let new_obj_val = JsValue::from_js_object(new_obj_ptr);
|
||
// derived 构造器 super() 前 this 为 undefined,基类 this = 新对象。
|
||
let this_value = if ctor_obj.is_derived_constructor() {
|
||
JsValue::undefined()
|
||
} else {
|
||
new_obj_val
|
||
};
|
||
// 入口已解析代际表:读 n_registers 定保存窗口。
|
||
let window = self.active_reg_limit.max(callee_reg_count).max(1) as usize;
|
||
let saved = self.save_inline_state(window);
|
||
let prev_construct = self.construct_dispatch;
|
||
self.construct_dispatch = true;
|
||
let result = self
|
||
.push_bytecode_frame(
|
||
ctor,
|
||
this_value,
|
||
FrameArgs::Slice(args),
|
||
Some(0),
|
||
Some(new_obj_val),
|
||
new_target,
|
||
FrameContinuation::None,
|
||
0,
|
||
)
|
||
.and_then(|_| self.dispatch());
|
||
self.construct_dispatch = prev_construct;
|
||
self.restore_inline_state(saved);
|
||
match result {
|
||
Ok(v) => Ok(v),
|
||
Err(e) => Err(self
|
||
.last_uncaught_value
|
||
.take()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e))),
|
||
}
|
||
}
|
||
|
||
/// 初始化/重建 Promise 内建对象:`%Promise%` 构造器与 `%Promise.prototype%`,
|
||
/// 绑定到 global 的 `Promise` 槽。
|
||
///
|
||
/// 在 VM 创建与 `full_reset` 后调用——原型继承自 session 的 Object/Function
|
||
/// 原型,session 重建后须重挂并重绑 global 槽。
|
||
pub(crate) fn init_promise_intrinsics(&mut self) {
|
||
let sf = self.kernel_core.perm_interner().as_ref();
|
||
let sh = self.kernel_core.shape_forge().as_ref();
|
||
let fn_proto_val = self.session.builtin_world().fn_proto_val();
|
||
let world = self.session.builtin_world();
|
||
let object_proto_val =
|
||
JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
|
||
|
||
// %Promise.prototype%:proto = Object.prototype,方法 then/catch/finally + @@toStringTag。
|
||
let mut proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto_val));
|
||
let ctor_si = sf.intern("constructor").0;
|
||
let ctor_shape = sh.make_shape(proto.shape_id(), ctor_si);
|
||
proto.set_shape_id(ctor_shape);
|
||
proto.push_prop(JsValue::undefined());
|
||
proto.set_data_meta(0u32, PropAttributes::new(true, false, true));
|
||
oxide_kernel::bind_methods_static!(
|
||
&mut proto,
|
||
sf,
|
||
sh,
|
||
world,
|
||
("then", promise_then as *const (), 2),
|
||
("catch", promise_catch as *const (), 1),
|
||
("finally", promise_finally as *const (), 1),
|
||
);
|
||
let tag_key = oxide_types::private_key::make_well_known_symbol_key(
|
||
oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG,
|
||
);
|
||
let tag_shape = sh.make_shape(proto.shape_id(), tag_key);
|
||
proto.set_shape_id(tag_shape);
|
||
let tag_pos = proto.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("Promise").0)));
|
||
proto.set_data_meta(tag_pos, PropAttributes::new(false, false, true));
|
||
|
||
// %Promise% 构造器:proto = Function.prototype,静态方法 resolve/reject。
|
||
// 自身属性顺序:length、name、prototype(CreateBuiltinFunction 顺序)。
|
||
let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
|
||
ctor.set_function(true);
|
||
ctor.set_native_arg_count(1);
|
||
// SAFETY: promise_constructor 是 NativeFn 函数项。
|
||
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(promise_constructor as *const ()) }));
|
||
ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR;
|
||
let length_si = sf.intern("length").0;
|
||
let ctor_shape1 = sh.make_shape(ctor.shape_id(), length_si);
|
||
ctor.set_shape_id(ctor_shape1);
|
||
let lpos = ctor.push_prop(JsValue::int(1));
|
||
ctor.set_data_meta(lpos, PropAttributes::new(false, false, true));
|
||
let name_si = sf.intern("name").0;
|
||
let ctor_shape2 = sh.make_shape(ctor.shape_id(), name_si);
|
||
ctor.set_shape_id(ctor_shape2);
|
||
let npos = ctor.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("Promise").0)));
|
||
ctor.set_data_meta(npos, PropAttributes::new(false, false, true));
|
||
let proto2_si = sf.intern("prototype").0;
|
||
let ctor_shape3 = sh.make_shape(ctor.shape_id(), proto2_si);
|
||
ctor.set_shape_id(ctor_shape3);
|
||
ctor.push_prop(JsValue::undefined());
|
||
ctor.set_data_meta(2u32, PropAttributes::new(false, false, false));
|
||
oxide_kernel::bind_methods_static!(
|
||
&mut ctor,
|
||
sf,
|
||
sh,
|
||
world,
|
||
("resolve", promise_static_resolve as *const (), 1),
|
||
("reject", promise_static_reject as *const (), 1),
|
||
("all", promise_static_all as *const (), 1),
|
||
("race", promise_static_race as *const (), 1),
|
||
("allSettled", promise_static_all_settled as *const (), 1),
|
||
("any", promise_static_any as *const (), 1),
|
||
("withResolvers", promise_static_with_resolvers as *const (), 0),
|
||
("try", promise_static_try as *const (), 1),
|
||
);
|
||
|
||
// 固定地址后互相接线:proto.constructor ↔ ctor.prototype。
|
||
Self::swap_intrinsic_proto(&mut self.promise_proto, *proto);
|
||
Self::swap_intrinsic_proto(&mut self.promise_constructor, *ctor);
|
||
// SAFETY: `promise_proto` 为 `P<JsObject>`(Arc 透明包装),堆址固定且本行前刚经
|
||
// swap_intrinsic_proto 落地;与下一处 ctor_mut 指向不同对象,无别名。
|
||
let proto_mut = unsafe { &mut *self.promise_proto.as_mut_ptr() };
|
||
proto_mut.set_prop_at(0u32, JsValue::from_js_object(self.promise_constructor.as_ptr() as *mut JsObject));
|
||
// SAFETY: `promise_constructor` 同为堆址固定的存活 `P<JsObject>`(Arc 透明包装);
|
||
// 此处写 prototype 槽位(下标 2),与 proto_mut 分属不同对象,无别名。
|
||
let ctor_mut = unsafe { &mut *self.promise_constructor.as_mut_ptr() };
|
||
// prototype 槽位在 length/name 之后(下标 2)。
|
||
ctor_mut.set_prop_at(2u32, JsValue::from_js_object(self.promise_proto.as_ptr() as *mut JsObject));
|
||
|
||
// Promise[Symbol.species] 访问器:getter 返回 receiver,派生类沿静态原型链
|
||
// 解析 @@species 得自身构造器。ctor 经本函数每次调用全新构造,同键槽不累积。
|
||
crate::bindings::bind_accessor_getter_key(
|
||
&self.kernel_core,
|
||
&self.session,
|
||
ctor_mut,
|
||
oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_SPECIES),
|
||
"get [Symbol.species]",
|
||
oxide_builtins::array::array_species_get::<crate::vm::Vm> as *const (),
|
||
);
|
||
|
||
// 绑定 global:槽已存在则更新(full_reset 未重建 global 时旧槽指向已弃 ctor)。
|
||
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
|
||
// SAFETY: global 对象由 session 持有,存活整个 session;本函数内只改其
|
||
// shape/属性区,期间无 reset 或对象搬移。
|
||
let global = unsafe { &mut *global_ptr };
|
||
let si = self.kernel_core.perm_interner().intern("Promise").0;
|
||
let ctor_val = JsValue::from_js_object(self.promise_constructor.as_ptr() as *mut JsObject);
|
||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||
global.set_prop_at(pos, ctor_val);
|
||
} else {
|
||
let shape = self.kernel_core.shape_forge().make_shape(global.shape_id(), si);
|
||
global.set_shape_id(shape);
|
||
let pos = global.push_prop(ctor_val);
|
||
// global 数据属性:writable:true、enumerable:false、configurable:true。
|
||
global.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||
global.bump_generation();
|
||
}
|
||
|
||
// Promise.any 的拒绝路径依赖 AggregateError 内建。
|
||
self.init_aggregate_error_intrinsics();
|
||
}
|
||
}
|
||
|
||
/// `Promise` 构造器:设置状态盒并同步调用 executor(resolve, reject)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. NewTarget 缺失形态(普通调用 / .call 入口)→ TypeError。
|
||
/// 2. executor 可读性检查(步序先于 GpFC 原型读)。
|
||
/// 3. GetPrototypeFromConstructor:传播式读 new.target 的 "prototype"
|
||
/// (访问器副作用与异常原值传播);非对象结果回落 %Promise.prototype%。
|
||
/// 4. 物化 receiver(原型重设 + type_tag + 状态盒)。
|
||
fn promise_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
// NewTarget 缺失 = 非构造形态:构造入口置位、普通调用入口清零,据此拒绝。
|
||
if !vm.constructing_native {
|
||
return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise must be called with new"));
|
||
}
|
||
let executor = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
if !oxide_builtins::iterator::is_callable(executor) {
|
||
return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise executor is not a function"));
|
||
}
|
||
// GetPrototypeFromConstructor:异常原值上抛;非对象结果回落内建原型。
|
||
let promise_proto_val = JsValue::from_js_object(vm.promise_proto.as_ptr() as *mut JsObject);
|
||
let new_target = vm.reg(255);
|
||
let proto = if new_target.is_object() {
|
||
let nt_ptr = new_target.as_js_object_ptr();
|
||
// SAFETY: is_object 保证指针非空且对象本 session 存活。
|
||
let nt_obj = unsafe { &*nt_ptr };
|
||
let proto_si = vm.kernel_core.perm_interner().intern("prototype").0;
|
||
match vm.ordinary_get(nt_obj, proto_si, new_target) {
|
||
Ok(v) if v.is_object() => v,
|
||
Ok(_) => promise_proto_val,
|
||
Err(err) => {
|
||
return NativeResult::Err(
|
||
vm.last_uncaught_value
|
||
.take()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, &err)),
|
||
);
|
||
}
|
||
}
|
||
} else {
|
||
promise_proto_val
|
||
};
|
||
// receiver 原型重设为 GpFC 结果(恒等重设是 no-op,非对象回落重设是语义修正),
|
||
// 随后物化状态盒。
|
||
// SAFETY: this 为构造帧分配的存活对象;此处写 type_tag 后即新建状态盒,无别名。
|
||
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
||
if let Err(msg) = obj.set_proto(proto) {
|
||
return NativeResult::Err(oxide_builtins::error::create_type_error(vm, msg));
|
||
}
|
||
obj.type_tag = JsObject::OBJ_TYPE_PROMISE;
|
||
let resolve = vm.make_resolve_reject_fn(this_val, false, false);
|
||
let reject = vm.make_resolve_reject_fn(this_val, true, false);
|
||
let state = Box::new(PromiseState {
|
||
state: PromiseStateKind::Pending,
|
||
result: JsValue::undefined(),
|
||
reactions: Vec::new(),
|
||
resolve_fn: resolve,
|
||
reject_fn: reject,
|
||
already_resolved: false,
|
||
promoted_clone: std::ptr::null_mut(),
|
||
});
|
||
obj.set_native_data(Box::into_raw(state) as *mut u8);
|
||
match vm.call_function_sync(executor, JsValue::undefined(), &[resolve, reject]) {
|
||
Ok(_) => NativeResult::Ok(this_val),
|
||
Err(e) => {
|
||
// executor 抛错:以抛出的值为拒绝原因(原值经 last_uncaught_value 保留)。
|
||
let exc = vm
|
||
.last_uncaught_value
|
||
.take()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, &e));
|
||
let _ = vm.reject_promise(this_val, exc);
|
||
NativeResult::Ok(this_val)
|
||
}
|
||
}
|
||
}
|