refactor: unify string helpers and pattern2 derived slot
This commit is contained in:
@ -169,6 +169,7 @@ pub enum CoreMethodId {
|
||||
StringConcat,
|
||||
StringSubstring,
|
||||
StringIndexOf,
|
||||
StringIndexOfFrom,
|
||||
StringReplace,
|
||||
StringTrim,
|
||||
StringSplit,
|
||||
@ -210,156 +211,446 @@ pub enum CoreMethodId {
|
||||
ResultGetValue,
|
||||
}
|
||||
|
||||
/// SSOT for CoreMethodId metadata (name/arity/return types and policy flags).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct CoreMethodSpec {
|
||||
id: CoreMethodId,
|
||||
box_id: CoreBoxId,
|
||||
name: &'static str,
|
||||
arity: usize,
|
||||
return_type_name: &'static str,
|
||||
is_pure: bool,
|
||||
allowed_in_condition: bool,
|
||||
allowed_in_init: bool,
|
||||
vtable_slot: Option<u16>,
|
||||
}
|
||||
|
||||
const CORE_METHOD_SPECS: &[CoreMethodSpec] = &[
|
||||
// StringBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringLength,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "length",
|
||||
arity: 0,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: true,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(300),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringUpper,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "toUpper",
|
||||
arity: 0,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringLower,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "toLower",
|
||||
arity: 0,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringConcat,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "concat",
|
||||
arity: 1,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(302),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringSubstring,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "substring",
|
||||
arity: 2,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(301),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringIndexOf,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "indexOf",
|
||||
arity: 1,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(303),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringIndexOfFrom,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "indexOf",
|
||||
arity: 2,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(303),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringReplace,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "replace",
|
||||
arity: 2,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(304),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringTrim,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "trim",
|
||||
arity: 0,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(305),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::StringSplit,
|
||||
box_id: CoreBoxId::String,
|
||||
name: "split",
|
||||
arity: 1,
|
||||
return_type_name: "Unknown",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
// IntegerBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::IntegerAbs,
|
||||
box_id: CoreBoxId::Integer,
|
||||
name: "abs",
|
||||
arity: 0,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::IntegerMin,
|
||||
box_id: CoreBoxId::Integer,
|
||||
name: "min",
|
||||
arity: 1,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::IntegerMax,
|
||||
box_id: CoreBoxId::Integer,
|
||||
name: "max",
|
||||
arity: 1,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
// BoolBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::BoolNot,
|
||||
box_id: CoreBoxId::Bool,
|
||||
name: "not",
|
||||
arity: 0,
|
||||
return_type_name: "BoolBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::BoolAnd,
|
||||
box_id: CoreBoxId::Bool,
|
||||
name: "and",
|
||||
arity: 1,
|
||||
return_type_name: "BoolBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::BoolOr,
|
||||
box_id: CoreBoxId::Bool,
|
||||
name: "or",
|
||||
arity: 1,
|
||||
return_type_name: "BoolBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
// ArrayBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ArrayLength,
|
||||
box_id: CoreBoxId::Array,
|
||||
name: "length",
|
||||
arity: 0,
|
||||
return_type_name: "IntegerBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: true,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(102),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ArrayPush,
|
||||
box_id: CoreBoxId::Array,
|
||||
name: "push",
|
||||
arity: 1,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(103),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ArrayPop,
|
||||
box_id: CoreBoxId::Array,
|
||||
name: "pop",
|
||||
arity: 0,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(104),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ArrayGet,
|
||||
box_id: CoreBoxId::Array,
|
||||
name: "get",
|
||||
arity: 1,
|
||||
return_type_name: "Unknown",
|
||||
is_pure: true,
|
||||
allowed_in_condition: true,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(100),
|
||||
},
|
||||
// MapBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::MapGet,
|
||||
box_id: CoreBoxId::Map,
|
||||
name: "get",
|
||||
arity: 1,
|
||||
return_type_name: "Unknown",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(203),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::MapSet,
|
||||
box_id: CoreBoxId::Map,
|
||||
name: "set",
|
||||
arity: 2,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(204),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::MapHas,
|
||||
box_id: CoreBoxId::Map,
|
||||
name: "has",
|
||||
arity: 1,
|
||||
return_type_name: "BoolBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: true,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(202),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::MapKeys,
|
||||
box_id: CoreBoxId::Map,
|
||||
name: "keys",
|
||||
arity: 0,
|
||||
return_type_name: "Unknown",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: Some(206),
|
||||
},
|
||||
// ConsoleBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ConsolePrintln,
|
||||
box_id: CoreBoxId::Console,
|
||||
name: "println",
|
||||
arity: 1,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(400),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ConsoleLog,
|
||||
box_id: CoreBoxId::Console,
|
||||
name: "log",
|
||||
arity: 1,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(400),
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ConsoleError,
|
||||
box_id: CoreBoxId::Console,
|
||||
name: "error",
|
||||
arity: 1,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: Some(402),
|
||||
},
|
||||
// FileBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::FileRead,
|
||||
box_id: CoreBoxId::File,
|
||||
name: "read",
|
||||
arity: 1,
|
||||
return_type_name: "StringBox",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::FileWrite,
|
||||
box_id: CoreBoxId::File,
|
||||
name: "write",
|
||||
arity: 1,
|
||||
return_type_name: "Void",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::FileOpen,
|
||||
box_id: CoreBoxId::File,
|
||||
name: "open",
|
||||
arity: 1,
|
||||
return_type_name: "FileBox",
|
||||
is_pure: false,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: false,
|
||||
vtable_slot: None,
|
||||
},
|
||||
// ResultBox methods
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ResultIsOk,
|
||||
box_id: CoreBoxId::Result,
|
||||
name: "isOk",
|
||||
arity: 0,
|
||||
return_type_name: "BoolBox",
|
||||
is_pure: true,
|
||||
allowed_in_condition: true,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
CoreMethodSpec {
|
||||
id: CoreMethodId::ResultGetValue,
|
||||
box_id: CoreBoxId::Result,
|
||||
name: "getValue",
|
||||
arity: 0,
|
||||
return_type_name: "Unknown",
|
||||
is_pure: true,
|
||||
allowed_in_condition: false,
|
||||
allowed_in_init: true,
|
||||
vtable_slot: None,
|
||||
},
|
||||
];
|
||||
|
||||
impl CoreMethodId {
|
||||
fn spec(&self) -> &'static CoreMethodSpec {
|
||||
CORE_METHOD_SPECS
|
||||
.iter()
|
||||
.find(|spec| spec.id == *self)
|
||||
.expect("CoreMethodSpec missing for CoreMethodId")
|
||||
}
|
||||
|
||||
/// メソッドが属する Box ID
|
||||
pub fn box_id(&self) -> CoreBoxId {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
StringLength | StringUpper | StringLower | StringConcat | StringSubstring
|
||||
| StringIndexOf | StringReplace | StringTrim | StringSplit => CoreBoxId::String,
|
||||
|
||||
IntegerAbs | IntegerMin | IntegerMax => CoreBoxId::Integer,
|
||||
|
||||
BoolNot | BoolAnd | BoolOr => CoreBoxId::Bool,
|
||||
|
||||
ArrayLength | ArrayPush | ArrayPop | ArrayGet => CoreBoxId::Array,
|
||||
|
||||
MapGet | MapSet | MapHas | MapKeys => CoreBoxId::Map,
|
||||
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => CoreBoxId::Console,
|
||||
|
||||
FileRead | FileWrite | FileOpen => CoreBoxId::File,
|
||||
|
||||
ResultIsOk | ResultGetValue => CoreBoxId::Result,
|
||||
}
|
||||
self.spec().box_id
|
||||
}
|
||||
|
||||
/// メソッド名(例: "length")
|
||||
pub fn name(&self) -> &'static str {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
StringLength => "length",
|
||||
StringUpper => "upper",
|
||||
StringLower => "lower",
|
||||
StringConcat => "concat",
|
||||
StringSubstring => "substring",
|
||||
StringIndexOf => "indexOf",
|
||||
StringReplace => "replace",
|
||||
StringTrim => "trim",
|
||||
StringSplit => "split",
|
||||
|
||||
IntegerAbs => "abs",
|
||||
IntegerMin => "min",
|
||||
IntegerMax => "max",
|
||||
|
||||
BoolNot => "not",
|
||||
BoolAnd => "and",
|
||||
BoolOr => "or",
|
||||
|
||||
ArrayLength => "length",
|
||||
ArrayPush => "push",
|
||||
ArrayPop => "pop",
|
||||
ArrayGet => "get",
|
||||
|
||||
MapGet => "get",
|
||||
MapSet => "set",
|
||||
MapHas => "has",
|
||||
MapKeys => "keys",
|
||||
|
||||
ConsolePrintln => "println",
|
||||
ConsoleLog => "log",
|
||||
ConsoleError => "error",
|
||||
|
||||
FileRead => "read",
|
||||
FileWrite => "write",
|
||||
FileOpen => "open",
|
||||
|
||||
ResultIsOk => "isOk",
|
||||
ResultGetValue => "getValue",
|
||||
}
|
||||
self.spec().name
|
||||
}
|
||||
|
||||
/// 引数の数
|
||||
pub fn arity(&self) -> usize {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
StringLength | StringUpper | StringLower | StringTrim | IntegerAbs | BoolNot
|
||||
| ArrayLength | ArrayPop | MapKeys | ResultIsOk | ResultGetValue => 0,
|
||||
|
||||
StringConcat | StringIndexOf | StringReplace | StringSplit | IntegerMin
|
||||
| IntegerMax | BoolAnd | BoolOr | ArrayGet | ArrayPush | MapGet | MapHas
|
||||
| ConsolePrintln | ConsoleLog | ConsoleError | FileRead | FileWrite | FileOpen => 1,
|
||||
|
||||
StringSubstring | MapSet => 2,
|
||||
}
|
||||
self.spec().arity
|
||||
}
|
||||
|
||||
/// Phase 84-4-B: 戻り値型(型推論用)
|
||||
pub fn return_type_name(&self) -> &'static str {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
StringLength | StringIndexOf | ArrayLength => "IntegerBox",
|
||||
self.spec().return_type_name
|
||||
}
|
||||
|
||||
StringUpper | StringLower | StringConcat | StringSubstring | StringReplace
|
||||
| StringTrim => "StringBox",
|
||||
|
||||
IntegerAbs | IntegerMin | IntegerMax => "IntegerBox",
|
||||
|
||||
BoolNot | BoolAnd | BoolOr | MapHas | ResultIsOk => "BoolBox",
|
||||
|
||||
ArrayPush | ArrayPop | MapSet | ConsolePrintln | ConsoleLog | ConsoleError
|
||||
| FileWrite => "Void",
|
||||
|
||||
ArrayGet | MapGet | MapKeys | StringSplit => "Unknown",
|
||||
|
||||
FileRead => "StringBox",
|
||||
FileOpen => "FileBox",
|
||||
ResultGetValue => "Unknown",
|
||||
}
|
||||
/// VTable slot for TypeRegistry (None when not exposed via vtable).
|
||||
pub fn vtable_slot(&self) -> Option<u16> {
|
||||
self.spec().vtable_slot
|
||||
}
|
||||
|
||||
/// 全CoreMethodIdを反復
|
||||
pub fn iter() -> impl Iterator<Item = CoreMethodId> {
|
||||
use CoreMethodId::*;
|
||||
[
|
||||
StringLength,
|
||||
StringUpper,
|
||||
StringLower,
|
||||
StringConcat,
|
||||
StringSubstring,
|
||||
StringIndexOf,
|
||||
StringReplace,
|
||||
StringTrim,
|
||||
StringSplit,
|
||||
IntegerAbs,
|
||||
IntegerMin,
|
||||
IntegerMax,
|
||||
BoolNot,
|
||||
BoolAnd,
|
||||
BoolOr,
|
||||
ArrayLength,
|
||||
ArrayPush,
|
||||
ArrayPop,
|
||||
ArrayGet,
|
||||
MapGet,
|
||||
MapSet,
|
||||
MapHas,
|
||||
MapKeys,
|
||||
ConsolePrintln,
|
||||
ConsoleLog,
|
||||
ConsoleError,
|
||||
FileRead,
|
||||
FileWrite,
|
||||
FileOpen,
|
||||
ResultIsOk,
|
||||
ResultGetValue,
|
||||
]
|
||||
.into_iter()
|
||||
CORE_METHOD_SPECS.iter().map(|spec| spec.id)
|
||||
}
|
||||
|
||||
/// Box名とメソッド名から CoreMethodId を取得
|
||||
pub fn from_box_and_method(box_id: CoreBoxId, method: &str) -> Option<CoreMethodId> {
|
||||
Self::iter().find(|m| m.box_id() == box_id && m.name() == method)
|
||||
let canonical = crate::runtime::core_method_aliases::canonical_method_name(method);
|
||||
CORE_METHOD_SPECS
|
||||
.iter()
|
||||
.find(|spec| spec.box_id == box_id && spec.name == canonical)
|
||||
.map(|spec| spec.id)
|
||||
}
|
||||
|
||||
/// メソッド名とアリティから CoreMethodId を解決
|
||||
pub fn resolve_by_name_and_arity(
|
||||
method_name: &str,
|
||||
arg_len: usize,
|
||||
) -> Result<CoreMethodId, Vec<usize>> {
|
||||
let canonical = crate::runtime::core_method_aliases::canonical_method_name(method_name);
|
||||
let mut expected = Vec::new();
|
||||
for spec in CORE_METHOD_SPECS.iter().filter(|spec| spec.name == canonical) {
|
||||
expected.push(spec.arity);
|
||||
if spec.arity == arg_len {
|
||||
return Ok(spec.id);
|
||||
}
|
||||
}
|
||||
expected.sort_unstable();
|
||||
expected.dedup();
|
||||
Err(expected)
|
||||
}
|
||||
|
||||
/// Phase 224-B: Pure function (no side effects, deterministic)
|
||||
@ -373,28 +664,7 @@ impl CoreMethodId {
|
||||
/// - `StringLength`: Pure - always returns same length for same string
|
||||
/// - `ArrayPush`: Not pure - mutates the array (side effect)
|
||||
pub fn is_pure(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String methods (pure - return new values, don't mutate)
|
||||
StringLength | StringUpper | StringLower | StringConcat | StringSubstring
|
||||
| StringIndexOf | StringReplace | StringTrim | StringSplit => true,
|
||||
|
||||
// Integer/Bool methods (pure - mathematical operations)
|
||||
IntegerAbs | IntegerMin | IntegerMax | BoolNot | BoolAnd | BoolOr => true,
|
||||
|
||||
// Array/Map read operations (pure - don't mutate)
|
||||
ArrayLength | ArrayGet => true,
|
||||
MapGet | MapHas => true,
|
||||
MapKeys => true,
|
||||
|
||||
// ResultBox read operations (pure)
|
||||
ResultIsOk | ResultGetValue => true,
|
||||
|
||||
// Impure - mutate state or have side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
}
|
||||
self.spec().is_pure
|
||||
}
|
||||
|
||||
/// Phase 224-B: Allowed in loop condition expressions
|
||||
@ -406,36 +676,7 @@ impl CoreMethodId {
|
||||
///
|
||||
/// This is a whitelist approach - default to false for safety.
|
||||
pub fn allowed_in_condition(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String read operations - allowed
|
||||
StringLength => true,
|
||||
|
||||
// Array read operations - allowed
|
||||
ArrayLength | ArrayGet => true,
|
||||
|
||||
// Map read operations - allowed
|
||||
MapHas => true,
|
||||
|
||||
// ResultBox operations - allowed
|
||||
ResultIsOk => true,
|
||||
|
||||
// Not yet whitelisted - be conservative
|
||||
StringUpper | StringLower | StringConcat | StringSubstring | StringIndexOf
|
||||
| StringReplace | StringTrim | StringSplit => false,
|
||||
|
||||
IntegerAbs | IntegerMin | IntegerMax => false,
|
||||
BoolNot | BoolAnd | BoolOr => false,
|
||||
|
||||
MapGet => false,
|
||||
MapKeys => false,
|
||||
ResultGetValue => false,
|
||||
|
||||
// Obviously disallowed - side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
}
|
||||
self.spec().allowed_in_condition
|
||||
}
|
||||
|
||||
/// Phase 224-B: Allowed in loop body init expressions
|
||||
@ -443,43 +684,14 @@ impl CoreMethodId {
|
||||
/// Methods allowed for LoopBodyLocal initialization.
|
||||
/// Similar to condition requirements but slightly more permissive.
|
||||
pub fn allowed_in_init(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String operations - allowed
|
||||
StringLength | StringSubstring | StringIndexOf => true,
|
||||
|
||||
// String transformations - allowed for init
|
||||
StringUpper | StringLower | StringTrim => true,
|
||||
|
||||
// Array operations - allowed
|
||||
ArrayLength | ArrayGet => true,
|
||||
|
||||
// Map operations - allowed
|
||||
MapGet | MapHas | MapKeys => true,
|
||||
|
||||
// ResultBox operations - allowed
|
||||
ResultIsOk | ResultGetValue => true,
|
||||
|
||||
// String operations that create new strings - allowed
|
||||
StringConcat | StringReplace | StringSplit => true,
|
||||
|
||||
// Math operations - allowed
|
||||
IntegerAbs | IntegerMin | IntegerMax => true,
|
||||
|
||||
// Not allowed - side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
|
||||
// Bool operations - technically pure but unusual in init
|
||||
BoolNot | BoolAnd | BoolOr => false,
|
||||
}
|
||||
self.spec().allowed_in_init
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
// ===== CoreBoxId tests =====
|
||||
|
||||
@ -573,6 +785,7 @@ mod tests {
|
||||
fn test_core_method_id_arity() {
|
||||
assert_eq!(CoreMethodId::StringLength.arity(), 0);
|
||||
assert_eq!(CoreMethodId::StringConcat.arity(), 1);
|
||||
assert_eq!(CoreMethodId::StringIndexOfFrom.arity(), 2);
|
||||
assert_eq!(CoreMethodId::MapSet.arity(), 2);
|
||||
}
|
||||
|
||||
@ -606,6 +819,22 @@ mod tests {
|
||||
assert!(count >= 27); // Phase 87: 27個以上のメソッド
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_method_spec_uniqueness() {
|
||||
let mut ids = HashSet::new();
|
||||
let mut signatures = HashSet::new();
|
||||
for spec in CORE_METHOD_SPECS {
|
||||
assert!(ids.insert(spec.id), "duplicate CoreMethodId in specs");
|
||||
let key = (spec.box_id, spec.name, spec.arity);
|
||||
assert!(
|
||||
signatures.insert(key),
|
||||
"duplicate CoreMethodSpec signature: {:?}",
|
||||
key
|
||||
);
|
||||
}
|
||||
assert_eq!(ids.len(), CoreMethodId::iter().count());
|
||||
}
|
||||
|
||||
// ===== Phase 224-B tests =====
|
||||
|
||||
#[test]
|
||||
|
||||
43
src/runtime/core_method_aliases.rs
Normal file
43
src/runtime/core_method_aliases.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! Core method alias table (SSOT).
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CoreMethodAlias {
|
||||
pub alias: &'static str,
|
||||
pub canonical: &'static str,
|
||||
}
|
||||
|
||||
const CORE_METHOD_ALIASES: &[CoreMethodAlias] = &[
|
||||
CoreMethodAlias {
|
||||
alias: "toUpperCase",
|
||||
canonical: "toUpper",
|
||||
},
|
||||
CoreMethodAlias {
|
||||
alias: "toLowerCase",
|
||||
canonical: "toLower",
|
||||
},
|
||||
CoreMethodAlias {
|
||||
alias: "find",
|
||||
canonical: "indexOf",
|
||||
},
|
||||
];
|
||||
|
||||
pub fn canonical_method_name(method_name: &str) -> &str {
|
||||
CORE_METHOD_ALIASES
|
||||
.iter()
|
||||
.find(|alias| alias.alias == method_name)
|
||||
.map(|alias| alias.canonical)
|
||||
.unwrap_or(method_name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_canonical_method_name_aliases() {
|
||||
assert_eq!(canonical_method_name("toUpperCase"), "toUpper");
|
||||
assert_eq!(canonical_method_name("toLowerCase"), "toLower");
|
||||
assert_eq!(canonical_method_name("find"), "indexOf");
|
||||
assert_eq!(canonical_method_name("indexOf"), "indexOf");
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
pub mod box_registry;
|
||||
pub mod core_box_ids; // Phase 87: CoreBoxId/CoreMethodId 型安全enum
|
||||
pub mod core_method_aliases; // Phase 29ab: Core method alias SSOT
|
||||
pub mod core_services; // Phase 91: CoreServices trait 定義
|
||||
pub mod deprecations;
|
||||
pub mod gc;
|
||||
|
||||
@ -23,16 +23,46 @@
|
||||
* - This enables unified dispatch for both VMValue::String and VMValue::BoxRef(StringBox)
|
||||
*/
|
||||
|
||||
use super::core_box_ids::{CoreBoxId, CoreMethodId};
|
||||
use super::type_box_abi::{MethodEntry, TypeBox};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// 最小サンプル: MapBox の TypeBox を事前登録(Tier-1 PoC 用)
|
||||
// --- ArrayBox ---
|
||||
const ARRAY_METHODS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "get",
|
||||
arity: 1,
|
||||
slot: 100,
|
||||
},
|
||||
fn core_method_entries_for_box(box_id: CoreBoxId) -> Vec<MethodEntry> {
|
||||
CoreMethodId::iter()
|
||||
.filter(|method_id| method_id.box_id() == box_id)
|
||||
.filter_map(|method_id| {
|
||||
method_id.vtable_slot().map(|slot| MethodEntry {
|
||||
name: method_id.name(),
|
||||
arity: method_id.arity() as u8,
|
||||
slot,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn core_method_entries_for_box_signatures(
|
||||
box_id: CoreBoxId,
|
||||
allowed: &[(&'static str, usize)],
|
||||
) -> Vec<MethodEntry> {
|
||||
let core = core_method_entries_for_box(box_id);
|
||||
core.into_iter()
|
||||
.filter(|entry| {
|
||||
allowed
|
||||
.iter()
|
||||
.any(|(name, arity)| entry.name == *name && entry.arity as usize == *arity)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn merge_method_entries(mut entries: Vec<MethodEntry>, extras: &[MethodEntry]) -> &'static [MethodEntry] {
|
||||
entries.extend_from_slice(extras);
|
||||
let mut seen = HashSet::new();
|
||||
entries.retain(|method| seen.insert((method.name, method.arity)));
|
||||
Box::leak(entries.into_boxed_slice())
|
||||
}
|
||||
|
||||
const ARRAY_METHOD_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "set",
|
||||
arity: 2,
|
||||
@ -43,22 +73,6 @@ const ARRAY_METHODS: &[MethodEntry] = &[
|
||||
arity: 0,
|
||||
slot: 102,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "length",
|
||||
arity: 0,
|
||||
slot: 102,
|
||||
},
|
||||
// P0: vtable coverage extension
|
||||
MethodEntry {
|
||||
name: "push",
|
||||
arity: 1,
|
||||
slot: 103,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "pop",
|
||||
arity: 0,
|
||||
slot: 104,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "clear",
|
||||
arity: 0,
|
||||
@ -97,10 +111,8 @@ const ARRAY_METHODS: &[MethodEntry] = &[
|
||||
slot: 111,
|
||||
},
|
||||
];
|
||||
static ARRAYBOX_TB: TypeBox = TypeBox::new_with("ArrayBox", ARRAY_METHODS);
|
||||
|
||||
// --- MapBox ---
|
||||
const MAP_METHODS: &[MethodEntry] = &[
|
||||
const MAP_METHOD_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "size",
|
||||
arity: 0,
|
||||
@ -111,21 +123,6 @@ const MAP_METHODS: &[MethodEntry] = &[
|
||||
arity: 0,
|
||||
slot: 201,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "has",
|
||||
arity: 1,
|
||||
slot: 202,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "get",
|
||||
arity: 1,
|
||||
slot: 203,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "set",
|
||||
arity: 2,
|
||||
slot: 204,
|
||||
},
|
||||
// Extended
|
||||
MethodEntry {
|
||||
name: "delete",
|
||||
@ -137,11 +134,6 @@ const MAP_METHODS: &[MethodEntry] = &[
|
||||
arity: 1,
|
||||
slot: 205,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "keys",
|
||||
arity: 0,
|
||||
slot: 206,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "values",
|
||||
arity: 0,
|
||||
@ -153,41 +145,13 @@ const MAP_METHODS: &[MethodEntry] = &[
|
||||
slot: 208,
|
||||
},
|
||||
];
|
||||
static MAPBOX_TB: TypeBox = TypeBox::new_with("MapBox", MAP_METHODS);
|
||||
|
||||
// --- StringBox ---
|
||||
const STRING_METHODS: &[MethodEntry] = &[
|
||||
const STRING_METHOD_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "len",
|
||||
arity: 0,
|
||||
slot: 300,
|
||||
},
|
||||
// P1: extend String vtable
|
||||
MethodEntry {
|
||||
name: "substring",
|
||||
arity: 2,
|
||||
slot: 301,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "concat",
|
||||
arity: 1,
|
||||
slot: 302,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "indexOf",
|
||||
arity: 1,
|
||||
slot: 303,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "replace",
|
||||
arity: 2,
|
||||
slot: 304,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "trim",
|
||||
arity: 0,
|
||||
slot: 305,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "toUpper",
|
||||
arity: 0,
|
||||
@ -199,40 +163,58 @@ const STRING_METHODS: &[MethodEntry] = &[
|
||||
slot: 307,
|
||||
},
|
||||
];
|
||||
static STRINGBOX_TB: TypeBox = TypeBox::new_with("StringBox", STRING_METHODS);
|
||||
|
||||
// --- ConsoleBox --- (WASM v2 unified dispatch 用の雛形)
|
||||
// 400: log(..), 401: warn(..), 402: error(..), 403: clear()
|
||||
const CONSOLE_METHODS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "log",
|
||||
arity: 1,
|
||||
slot: 400,
|
||||
},
|
||||
const CONSOLE_METHOD_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "warn",
|
||||
arity: 1,
|
||||
slot: 401,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "error",
|
||||
arity: 1,
|
||||
slot: 402,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "clear",
|
||||
arity: 0,
|
||||
slot: 403,
|
||||
},
|
||||
// Phase 122: println は log のエイリアス
|
||||
// JSON v0/selfhost が println を吐いても log と同じスロットを使うための alias
|
||||
MethodEntry {
|
||||
name: "println",
|
||||
arity: 1,
|
||||
slot: 400,
|
||||
},
|
||||
];
|
||||
static CONSOLEBOX_TB: TypeBox = TypeBox::new_with("ConsoleBox", CONSOLE_METHODS);
|
||||
|
||||
static ARRAYBOX_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
static MAPBOX_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
static STRINGBOX_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
static CONSOLEBOX_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
|
||||
fn arraybox_typebox() -> &'static TypeBox {
|
||||
ARRAYBOX_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box(CoreBoxId::Array);
|
||||
let methods = merge_method_entries(core, ARRAY_METHOD_EXTRAS);
|
||||
TypeBox::new_with("ArrayBox", methods)
|
||||
})
|
||||
}
|
||||
|
||||
fn mapbox_typebox() -> &'static TypeBox {
|
||||
MAPBOX_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box(CoreBoxId::Map);
|
||||
let methods = merge_method_entries(core, MAP_METHOD_EXTRAS);
|
||||
TypeBox::new_with("MapBox", methods)
|
||||
})
|
||||
}
|
||||
|
||||
fn stringbox_typebox() -> &'static TypeBox {
|
||||
STRINGBOX_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box(CoreBoxId::String);
|
||||
let methods = merge_method_entries(core, STRING_METHOD_EXTRAS);
|
||||
TypeBox::new_with("StringBox", methods)
|
||||
})
|
||||
}
|
||||
|
||||
fn consolebox_typebox() -> &'static TypeBox {
|
||||
CONSOLEBOX_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box(CoreBoxId::Console);
|
||||
let methods = merge_method_entries(core, CONSOLE_METHOD_EXTRAS);
|
||||
TypeBox::new_with("ConsoleBox", methods)
|
||||
})
|
||||
}
|
||||
|
||||
// --- InstanceBox ---
|
||||
// Representative methods exposed via unified slots for field access and diagnostics.
|
||||
@ -268,48 +250,27 @@ static INSTANCEBOX_TB: TypeBox = TypeBox::new_with("InstanceBox", INSTANCE_METHO
|
||||
// Primitive types (String, Integer, Array) share the same slot numbers as their Box variants
|
||||
// This enables unified dispatch for both primitives and boxes
|
||||
|
||||
// Primitive String uses same slots as StringBox (300+)
|
||||
const PRIMITIVE_STRING_METHODS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "length",
|
||||
arity: 0,
|
||||
slot: 300,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "substring",
|
||||
arity: 2,
|
||||
slot: 301,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "concat",
|
||||
arity: 1,
|
||||
slot: 302,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "indexOf",
|
||||
arity: 1,
|
||||
slot: 303,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "replace",
|
||||
arity: 2,
|
||||
slot: 304,
|
||||
},
|
||||
const PRIMITIVE_STRING_ALLOWED_SIGNATURES: &[(&str, usize)] = &[
|
||||
("length", 0),
|
||||
("substring", 2),
|
||||
("concat", 1),
|
||||
("indexOf", 1),
|
||||
("replace", 2),
|
||||
];
|
||||
const PRIMITIVE_STRING_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "lastIndexOf",
|
||||
arity: 1,
|
||||
slot: 308,
|
||||
},
|
||||
];
|
||||
static PRIMITIVE_STRING_TB: TypeBox = TypeBox::new_with("String", PRIMITIVE_STRING_METHODS);
|
||||
|
||||
// Primitive Array uses same slots as ArrayBox (100+)
|
||||
const PRIMITIVE_ARRAY_METHODS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "get",
|
||||
arity: 1,
|
||||
slot: 100,
|
||||
},
|
||||
const PRIMITIVE_ARRAY_ALLOWED_SIGNATURES: &[(&str, usize)] = &[
|
||||
("get", 1),
|
||||
("length", 0),
|
||||
("push", 1),
|
||||
];
|
||||
const PRIMITIVE_ARRAY_EXTRAS: &[MethodEntry] = &[
|
||||
MethodEntry {
|
||||
name: "set",
|
||||
arity: 2,
|
||||
@ -320,30 +281,44 @@ const PRIMITIVE_ARRAY_METHODS: &[MethodEntry] = &[
|
||||
arity: 0,
|
||||
slot: 102,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "length",
|
||||
arity: 0,
|
||||
slot: 102,
|
||||
},
|
||||
MethodEntry {
|
||||
name: "push",
|
||||
arity: 1,
|
||||
slot: 103,
|
||||
},
|
||||
];
|
||||
static PRIMITIVE_ARRAY_TB: TypeBox = TypeBox::new_with("Array", PRIMITIVE_ARRAY_METHODS);
|
||||
|
||||
static PRIMITIVE_STRING_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
static PRIMITIVE_ARRAY_TB: OnceLock<TypeBox> = OnceLock::new();
|
||||
|
||||
fn primitive_string_typebox() -> &'static TypeBox {
|
||||
PRIMITIVE_STRING_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box_signatures(
|
||||
CoreBoxId::String,
|
||||
PRIMITIVE_STRING_ALLOWED_SIGNATURES,
|
||||
);
|
||||
let methods = merge_method_entries(core, PRIMITIVE_STRING_EXTRAS);
|
||||
TypeBox::new_with("String", methods)
|
||||
})
|
||||
}
|
||||
|
||||
fn primitive_array_typebox() -> &'static TypeBox {
|
||||
PRIMITIVE_ARRAY_TB.get_or_init(|| {
|
||||
let core = core_method_entries_for_box_signatures(
|
||||
CoreBoxId::Array,
|
||||
PRIMITIVE_ARRAY_ALLOWED_SIGNATURES,
|
||||
);
|
||||
let methods = merge_method_entries(core, PRIMITIVE_ARRAY_EXTRAS);
|
||||
TypeBox::new_with("Array", methods)
|
||||
})
|
||||
}
|
||||
|
||||
/// 型名から TypeBox を解決(雛形)。現在は常に None。
|
||||
pub fn resolve_typebox_by_name(type_name: &str) -> Option<&'static TypeBox> {
|
||||
match type_name {
|
||||
"MapBox" => Some(&MAPBOX_TB),
|
||||
"ArrayBox" => Some(&ARRAYBOX_TB),
|
||||
"StringBox" => Some(&STRINGBOX_TB),
|
||||
"ConsoleBox" => Some(&CONSOLEBOX_TB),
|
||||
"MapBox" => Some(mapbox_typebox()),
|
||||
"ArrayBox" => Some(arraybox_typebox()),
|
||||
"StringBox" => Some(stringbox_typebox()),
|
||||
"ConsoleBox" => Some(consolebox_typebox()),
|
||||
"InstanceBox" => Some(&INSTANCEBOX_TB),
|
||||
// Phase 124: Primitive types
|
||||
"String" => Some(&PRIMITIVE_STRING_TB),
|
||||
"Array" => Some(&PRIMITIVE_ARRAY_TB),
|
||||
"String" => Some(primitive_string_typebox()),
|
||||
"Array" => Some(primitive_array_typebox()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -368,3 +343,27 @@ pub fn known_methods_for(type_name: &str) -> Option<Vec<&'static str>> {
|
||||
v.dedup();
|
||||
Some(v)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_core_method_vtable_slots_match_registry() {
|
||||
for method_id in CoreMethodId::iter() {
|
||||
let Some(expected_slot) = method_id.vtable_slot() else {
|
||||
continue;
|
||||
};
|
||||
let type_name = method_id.box_id().name();
|
||||
let resolved = resolve_slot_by_name(type_name, method_id.name(), method_id.arity());
|
||||
assert_eq!(
|
||||
resolved,
|
||||
Some(expected_slot),
|
||||
"vtable slot mismatch: {}.{}({})",
|
||||
type_name,
|
||||
method_id.name(),
|
||||
method_id.arity()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user