feat: Implement field visibility (public/private) system
## Major Features Added
### Field Visibility System
- Added `private { ... }` and `public { ... }` blocks in box declarations
- Default visibility is now handled explicitly (fields must be in either block)
- Visibility checks enforced at both interpreter and VM levels
### Parser Enhancements
- Extended AST with public_fields and private_fields vectors
- Added parsing for visibility blocks in box definitions
- Maintained backward compatibility with existing `init { ... }` syntax
### Interpreter Implementation
- Added visibility checks in field access (get_field/set_field)
- External access to private fields now throws appropriate errors
- Internal access (within methods) always allowed
### VM Implementation
- Extended VM with object_class tracking for visibility checks
- RefGet/RefSet instructions now enforce field visibility
- Fixed nested box declaration collection (boxes defined inside methods)
### Test Examples Added
- docs/examples/visibility_ok.nyash - demonstrates correct usage
- docs/examples/visibility_error.nyash - tests private field access errors
## Technical Details
### Error Messages
- Interpreter: "Field 'X' is private in Y"
- VM: Same error message for consistency
### Current Limitations
- All RefGet/RefSet treated as external access in VM (internal flag future work)
- Legacy `init { ... }` fields treated as having unspecified visibility
## Test Results
✅ Interpreter: Both test cases pass correctly
✅ VM: Both test cases pass correctly after nested declaration fix
This implements the foundation for proper encapsulation in Nyash,
following the "explicit is better than implicit" philosophy.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
27
docs/examples/visibility_error.nyash
Normal file
27
docs/examples/visibility_error.nyash
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
static box Main {
|
||||||
|
init { console }
|
||||||
|
|
||||||
|
main() {
|
||||||
|
me.console = new ConsoleBox()
|
||||||
|
|
||||||
|
box User {
|
||||||
|
private { age, passwordHash }
|
||||||
|
public { name }
|
||||||
|
|
||||||
|
init { name, age }
|
||||||
|
|
||||||
|
birth(n, a) {
|
||||||
|
me.name = n
|
||||||
|
me.age = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local u = new User("Alice", 20)
|
||||||
|
me.console.log("name(public)=" + u.name) # OK: public
|
||||||
|
|
||||||
|
# 以下は外部からprivateフィールドへのアクセス → エラーになる想定
|
||||||
|
u.age = 30 # 外部からの代入(NG)
|
||||||
|
me.console.log(u.age) # 外部からの参照(NG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
36
docs/examples/visibility_ok.nyash
Normal file
36
docs/examples/visibility_ok.nyash
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
static box Main {
|
||||||
|
init { console }
|
||||||
|
|
||||||
|
main() {
|
||||||
|
me.console = new ConsoleBox()
|
||||||
|
|
||||||
|
box User {
|
||||||
|
private { age, passwordHash }
|
||||||
|
public { name }
|
||||||
|
|
||||||
|
init { name, age }
|
||||||
|
|
||||||
|
birth(n, a) {
|
||||||
|
me.name = n
|
||||||
|
me.age = a
|
||||||
|
}
|
||||||
|
|
||||||
|
setAge(a) {
|
||||||
|
me.age = a # private だが内部からなのでOK
|
||||||
|
}
|
||||||
|
|
||||||
|
getAge() {
|
||||||
|
return me.age # private を内部参照(OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local u = new User("Alice", 20)
|
||||||
|
me.console.log("name(public)=" + u.name) # OK: public
|
||||||
|
u.name = "Bob" # OK: public set
|
||||||
|
|
||||||
|
me.console.log("age(private, internal)=" + u.getAge()) # OK: 内部アクセス
|
||||||
|
u.setAge(21) # OK: 内部でprivate set
|
||||||
|
me.console.log("done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -460,6 +460,10 @@ pub enum ASTNode {
|
|||||||
BoxDeclaration {
|
BoxDeclaration {
|
||||||
name: String,
|
name: String,
|
||||||
fields: Vec<String>,
|
fields: Vec<String>,
|
||||||
|
/// 公開フィールド(public { ... })
|
||||||
|
public_fields: Vec<String>,
|
||||||
|
/// 非公開フィールド(private { ... })
|
||||||
|
private_fields: Vec<String>,
|
||||||
methods: HashMap<String, ASTNode>, // method_name -> FunctionDeclaration
|
methods: HashMap<String, ASTNode>, // method_name -> FunctionDeclaration
|
||||||
constructors: HashMap<String, ASTNode>, // constructor_key -> FunctionDeclaration
|
constructors: HashMap<String, ASTNode>, // constructor_key -> FunctionDeclaration
|
||||||
init_fields: Vec<String>, // initブロック内のフィールド定義
|
init_fields: Vec<String>, // initブロック内のフィールド定義
|
||||||
|
|||||||
@ -173,6 +173,8 @@ pub struct VM {
|
|||||||
last_result: Option<VMValue>,
|
last_result: Option<VMValue>,
|
||||||
/// Simple field storage for objects (maps reference -> field -> value)
|
/// Simple field storage for objects (maps reference -> field -> value)
|
||||||
object_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
object_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
||||||
|
/// Class name mapping for objects (for visibility checks)
|
||||||
|
object_class: HashMap<ValueId, String>,
|
||||||
/// Loop executor for handling phi nodes and loop-specific logic
|
/// Loop executor for handling phi nodes and loop-specific logic
|
||||||
loop_executor: LoopExecutor,
|
loop_executor: LoopExecutor,
|
||||||
/// Shared runtime for box creation and declarations
|
/// Shared runtime for box creation and declarations
|
||||||
@ -205,6 +207,7 @@ impl VM {
|
|||||||
pc: 0,
|
pc: 0,
|
||||||
last_result: None,
|
last_result: None,
|
||||||
object_fields: HashMap::new(),
|
object_fields: HashMap::new(),
|
||||||
|
object_class: HashMap::new(),
|
||||||
loop_executor: LoopExecutor::new(),
|
loop_executor: LoopExecutor::new(),
|
||||||
runtime: NyashRuntime::new(),
|
runtime: NyashRuntime::new(),
|
||||||
scope_tracker: ScopeTracker::new(),
|
scope_tracker: ScopeTracker::new(),
|
||||||
@ -228,6 +231,7 @@ impl VM {
|
|||||||
pc: 0,
|
pc: 0,
|
||||||
last_result: None,
|
last_result: None,
|
||||||
object_fields: HashMap::new(),
|
object_fields: HashMap::new(),
|
||||||
|
object_class: HashMap::new(),
|
||||||
loop_executor: LoopExecutor::new(),
|
loop_executor: LoopExecutor::new(),
|
||||||
runtime,
|
runtime,
|
||||||
scope_tracker: ScopeTracker::new(),
|
scope_tracker: ScopeTracker::new(),
|
||||||
@ -611,6 +615,8 @@ impl VM {
|
|||||||
// Register for scope-based finalization (share; keep same instance)
|
// Register for scope-based finalization (share; keep same instance)
|
||||||
let reg_arc = std::sync::Arc::from(b.share_box());
|
let reg_arc = std::sync::Arc::from(b.share_box());
|
||||||
self.scope_tracker.register_box(reg_arc);
|
self.scope_tracker.register_box(reg_arc);
|
||||||
|
// Record class name for visibility checks
|
||||||
|
self.object_class.insert(*dst, box_type.clone());
|
||||||
// Store value in VM
|
// Store value in VM
|
||||||
self.set_value(*dst, VMValue::from_nyash_box(b));
|
self.set_value(*dst, VMValue::from_nyash_box(b));
|
||||||
Ok(ControlFlow::Continue)
|
Ok(ControlFlow::Continue)
|
||||||
@ -696,6 +702,17 @@ impl VM {
|
|||||||
},
|
},
|
||||||
|
|
||||||
MirInstruction::RefGet { dst, reference, field } => {
|
MirInstruction::RefGet { dst, reference, field } => {
|
||||||
|
// Visibility check (if class known and visibility declared)
|
||||||
|
if let Some(class_name) = self.object_class.get(reference) {
|
||||||
|
if let Ok(decls) = self.runtime.box_declarations.read() {
|
||||||
|
if let Some(decl) = decls.get(class_name) {
|
||||||
|
let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty();
|
||||||
|
if has_vis && !decl.public_fields.contains(field) {
|
||||||
|
return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Get field value from object
|
// Get field value from object
|
||||||
let field_value = if let Some(fields) = self.object_fields.get(reference) {
|
let field_value = if let Some(fields) = self.object_fields.get(reference) {
|
||||||
if let Some(value) = fields.get(field) {
|
if let Some(value) = fields.get(field) {
|
||||||
@ -716,6 +733,17 @@ impl VM {
|
|||||||
MirInstruction::RefSet { reference, field, value } => {
|
MirInstruction::RefSet { reference, field, value } => {
|
||||||
// Get the value to set
|
// Get the value to set
|
||||||
let new_value = self.get_value(*value)?;
|
let new_value = self.get_value(*value)?;
|
||||||
|
// Visibility check (treat all RefSet as external writes)
|
||||||
|
if let Some(class_name) = self.object_class.get(reference) {
|
||||||
|
if let Ok(decls) = self.runtime.box_declarations.read() {
|
||||||
|
if let Some(decl) = decls.get(class_name) {
|
||||||
|
let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty();
|
||||||
|
if has_vis && !decl.public_fields.contains(field) {
|
||||||
|
return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure object has field storage
|
// Ensure object has field storage
|
||||||
if !self.object_fields.contains_key(reference) {
|
if !self.object_fields.contains_key(reference) {
|
||||||
@ -1127,10 +1155,12 @@ mod tests {
|
|||||||
crate::ast::ASTNode::Program { statements, .. } => {
|
crate::ast::ASTNode::Program { statements, .. } => {
|
||||||
for st in statements { walk(st, runtime); }
|
for st in statements { walk(st, runtime); }
|
||||||
}
|
}
|
||||||
crate::ast::ASTNode::BoxDeclaration { name, fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
crate::ast::ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
||||||
let decl = CoreBoxDecl {
|
let decl = CoreBoxDecl {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
fields: fields.clone(),
|
fields: fields.clone(),
|
||||||
|
public_fields: public_fields.clone(),
|
||||||
|
private_fields: private_fields.clone(),
|
||||||
methods: methods.clone(),
|
methods: methods.clone(),
|
||||||
constructors: constructors.clone(),
|
constructors: constructors.clone(),
|
||||||
init_fields: init_fields.clone(),
|
init_fields: init_fields.clone(),
|
||||||
|
|||||||
@ -15,6 +15,8 @@ use crate::ast::ASTNode;
|
|||||||
pub struct BoxDeclaration {
|
pub struct BoxDeclaration {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub fields: Vec<String>,
|
pub fields: Vec<String>,
|
||||||
|
pub public_fields: Vec<String>,
|
||||||
|
pub private_fields: Vec<String>,
|
||||||
pub methods: HashMap<String, ASTNode>,
|
pub methods: HashMap<String, ASTNode>,
|
||||||
pub constructors: HashMap<String, ASTNode>,
|
pub constructors: HashMap<String, ASTNode>,
|
||||||
pub init_fields: Vec<String>,
|
pub init_fields: Vec<String>,
|
||||||
@ -26,4 +28,3 @@ pub struct BoxDeclaration {
|
|||||||
/// Generic type parameters
|
/// Generic type parameters
|
||||||
pub type_parameters: Vec<String>,
|
pub type_parameters: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,13 @@ impl NyashInterpreter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 外からのフィールドアクセスか(me/this以外)を判定
|
||||||
|
let is_internal_access = match object {
|
||||||
|
ASTNode::This { .. } | ASTNode::Me { .. } => true,
|
||||||
|
ASTNode::Variable { name, .. } if name == "me" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
// オブジェクトを評価(通常のフィールドアクセス)
|
// オブジェクトを評価(通常のフィールドアクセス)
|
||||||
let obj_value = self.execute_expression(object);
|
let obj_value = self.execute_expression(object);
|
||||||
|
|
||||||
@ -41,6 +48,20 @@ impl NyashInterpreter {
|
|||||||
|
|
||||||
// InstanceBoxにキャスト
|
// InstanceBoxにキャスト
|
||||||
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
||||||
|
// 可視性チェック(互換性: public/privateのどちらかが定義されていれば強制)
|
||||||
|
if !is_internal_access {
|
||||||
|
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||||
|
if let Some(box_decl) = box_decls.get(&instance.class_name) {
|
||||||
|
let has_visibility = !box_decl.public_fields.is_empty() || !box_decl.private_fields.is_empty();
|
||||||
|
if has_visibility {
|
||||||
|
if !box_decl.public_fields.contains(&field.to_string()) {
|
||||||
|
return Err(RuntimeError::InvalidOperation {
|
||||||
|
message: format!("Field '{}' is private in {}", field, instance.class_name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 🔥 finiは何回呼ばれてもエラーにしない(ユーザー要求)
|
// 🔥 finiは何回呼ばれてもエラーにしない(ユーザー要求)
|
||||||
// is_finalized()チェックを削除
|
// is_finalized()チェックを削除
|
||||||
|
|
||||||
@ -145,4 +166,4 @@ impl NyashInterpreter {
|
|||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -949,203 +949,16 @@ impl NyashInterpreter {
|
|||||||
arguments: &[ASTNode],
|
arguments: &[ASTNode],
|
||||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
eprintln!("🔍 execute_plugin_box_v2_method called: {}.{}", plugin_box.box_type, method);
|
eprintln!("🔍 execute_plugin_box_v2_method called: {}.{}", plugin_box.box_type, method);
|
||||||
// Route via loader for proper TLV/Handle handling (early return)
|
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||||
{
|
|
||||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
|
||||||
for arg in arguments {
|
|
||||||
arg_values.push(self.execute_expression(arg)?);
|
|
||||||
}
|
|
||||||
let loader_guard = crate::runtime::plugin_loader_v2::get_global_loader_v2();
|
|
||||||
let loader = loader_guard.read().map_err(|_| RuntimeError::RuntimeFailure { message: "Plugin loader lock poisoned".into() })?;
|
|
||||||
match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id, &arg_values) {
|
|
||||||
Ok(Some(result_box)) => return Ok(result_box),
|
|
||||||
Ok(None) => return Ok(Box::new(VoidBox::new())),
|
|
||||||
Err(e) => return Err(RuntimeError::RuntimeFailure { message: format!("Plugin method {} failed: {:?}", method, e) }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get global loader to access configuration
|
|
||||||
let loader = crate::runtime::plugin_loader_v2::get_global_loader_v2();
|
|
||||||
let loader = loader.read().unwrap();
|
|
||||||
|
|
||||||
// Get method_id from configuration
|
|
||||||
let method_id = if let Some(config) = &loader.config {
|
|
||||||
// Find library that provides this box type
|
|
||||||
let (lib_name, _) = config.find_library_for_box(&plugin_box.box_type)
|
|
||||||
.ok_or_else(|| RuntimeError::InvalidOperation {
|
|
||||||
message: format!("No plugin provides box type: {}", plugin_box.box_type)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get method_id from toml
|
|
||||||
if let Ok(toml_content) = std::fs::read_to_string("nyash.toml") {
|
|
||||||
if let Ok(toml_value) = toml::from_str::<toml::Value>(&toml_content) {
|
|
||||||
if let Some(box_config) = config.get_box_config(lib_name, &plugin_box.box_type, &toml_value) {
|
|
||||||
if let Some(method_config) = box_config.methods.get(method) {
|
|
||||||
eprintln!("🔍 Found method {} with id: {}", method, method_config.method_id);
|
|
||||||
method_config.method_id
|
|
||||||
} else {
|
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: format!("Unknown method '{}' for {}", method, plugin_box.box_type)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: format!("No configuration for box type: {}", plugin_box.box_type)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: "Failed to parse nyash.toml".into()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: "Failed to read nyash.toml".into()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: "No configuration loaded".into()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Evaluate arguments
|
|
||||||
let mut arg_values = Vec::new();
|
|
||||||
for arg in arguments {
|
for arg in arguments {
|
||||||
arg_values.push(self.execute_expression(arg)?);
|
arg_values.push(self.execute_expression(arg)?);
|
||||||
}
|
}
|
||||||
|
let loader_guard = crate::runtime::plugin_loader_v2::get_global_loader_v2();
|
||||||
// Encode arguments using TLV (plugin's expected format)
|
let loader = loader_guard.read().map_err(|_| RuntimeError::RuntimeFailure { message: "Plugin loader lock poisoned".into() })?;
|
||||||
let mut tlv_data = Vec::new();
|
match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id, &arg_values) {
|
||||||
|
Ok(Some(result_box)) => Ok(result_box),
|
||||||
// Header: version(2 bytes) + argc(2 bytes)
|
Ok(None) => Ok(Box::new(VoidBox::new())),
|
||||||
tlv_data.extend_from_slice(&1u16.to_le_bytes()); // version = 1
|
Err(e) => Err(RuntimeError::RuntimeFailure { message: format!("Plugin method {} failed: {:?}", method, e) }),
|
||||||
tlv_data.extend_from_slice(&(arg_values.len() as u16).to_le_bytes()); // argc
|
|
||||||
|
|
||||||
// Encode each argument
|
|
||||||
for arg in arg_values.iter() {
|
|
||||||
// For now, convert all arguments to strings
|
|
||||||
let arg_str = arg.to_string_box().value;
|
|
||||||
let arg_bytes = arg_str.as_bytes();
|
|
||||||
|
|
||||||
// TLV entry: tag(1) + reserved(1) + size(2) + data
|
|
||||||
tlv_data.push(6); // tag = 6 (String)
|
|
||||||
tlv_data.push(0); // reserved
|
|
||||||
tlv_data.extend_from_slice(&(arg_bytes.len() as u16).to_le_bytes()); // size
|
|
||||||
tlv_data.extend_from_slice(arg_bytes); // data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare output buffer
|
|
||||||
let mut output_buffer = vec![0u8; 4096]; // 4KB buffer
|
|
||||||
let mut output_len = output_buffer.len();
|
|
||||||
|
|
||||||
eprintln!("🔍 Calling plugin invoke_fn: type_id={}, method_id={}, instance_id={}",
|
|
||||||
plugin_box.type_id, method_id, plugin_box.instance_id);
|
|
||||||
|
|
||||||
// Call plugin method
|
|
||||||
let result = unsafe {
|
|
||||||
(plugin_box.invoke_fn)(
|
|
||||||
plugin_box.type_id, // type_id from PluginBoxV2
|
|
||||||
method_id, // method_id
|
|
||||||
plugin_box.instance_id, // instance_id
|
|
||||||
tlv_data.as_ptr(), // arguments
|
|
||||||
tlv_data.len(), // arguments length
|
|
||||||
output_buffer.as_mut_ptr(), // output buffer
|
|
||||||
&mut output_len, // output length
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!("🔍 Plugin method returned: {}", result);
|
|
||||||
|
|
||||||
if result != 0 {
|
|
||||||
return Err(RuntimeError::RuntimeFailure {
|
|
||||||
message: format!("Plugin method {} failed with code: {}", method, result)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse TLV output dynamically
|
|
||||||
if output_len >= 4 {
|
|
||||||
// Parse TLV header
|
|
||||||
let version = u16::from_le_bytes([output_buffer[0], output_buffer[1]]);
|
|
||||||
let argc = u16::from_le_bytes([output_buffer[2], output_buffer[3]]);
|
|
||||||
|
|
||||||
eprintln!("🔍 TLV response: version={}, argc={}", version, argc);
|
|
||||||
|
|
||||||
if version == 1 && argc > 0 && output_len >= 8 {
|
|
||||||
// Parse first TLV entry
|
|
||||||
let tag = output_buffer[4];
|
|
||||||
let _reserved = output_buffer[5];
|
|
||||||
let size = u16::from_le_bytes([output_buffer[6], output_buffer[7]]) as usize;
|
|
||||||
|
|
||||||
eprintln!("🔍 TLV entry: tag={}, size={}", tag, size);
|
|
||||||
|
|
||||||
if output_len >= 8 + size {
|
|
||||||
match tag {
|
|
||||||
2 => {
|
|
||||||
// I32 type
|
|
||||||
if size == 4 {
|
|
||||||
let value = i32::from_le_bytes([
|
|
||||||
output_buffer[8], output_buffer[9],
|
|
||||||
output_buffer[10], output_buffer[11]
|
|
||||||
]);
|
|
||||||
Ok(Box::new(IntegerBox::new(value as i64)))
|
|
||||||
} else {
|
|
||||||
Ok(Box::new(StringBox::new("ok")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6 | 7 => {
|
|
||||||
// String or Bytes type
|
|
||||||
let data = &output_buffer[8..8+size];
|
|
||||||
let string = String::from_utf8_lossy(data).to_string();
|
|
||||||
Ok(Box::new(StringBox::new(string)))
|
|
||||||
}
|
|
||||||
8 => {
|
|
||||||
// Handle type - contains type_id and instance_id
|
|
||||||
if size == 8 {
|
|
||||||
let type_id = u32::from_le_bytes([
|
|
||||||
output_buffer[8], output_buffer[9],
|
|
||||||
output_buffer[10], output_buffer[11]
|
|
||||||
]);
|
|
||||||
let instance_id = u32::from_le_bytes([
|
|
||||||
output_buffer[12], output_buffer[13],
|
|
||||||
output_buffer[14], output_buffer[15]
|
|
||||||
]);
|
|
||||||
eprintln!("🔍 Received Handle: type_id={}, instance_id={}", type_id, instance_id);
|
|
||||||
|
|
||||||
// Create a new PluginBoxV2 instance with the returned handle
|
|
||||||
let new_plugin_box = PluginBoxV2 {
|
|
||||||
box_type: plugin_box.box_type.clone(),
|
|
||||||
type_id: plugin_box.type_id,
|
|
||||||
invoke_fn: plugin_box.invoke_fn,
|
|
||||||
instance_id: instance_id,
|
|
||||||
fini_method_id: plugin_box.fini_method_id,
|
|
||||||
};
|
|
||||||
Ok(Box::new(new_plugin_box))
|
|
||||||
} else {
|
|
||||||
eprintln!("🔍 Invalid Handle size: {} (expected 8)", size);
|
|
||||||
Ok(Box::new(VoidBox::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9 => {
|
|
||||||
// Void type
|
|
||||||
Ok(Box::new(StringBox::new("ok")))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Unknown type, treat as string
|
|
||||||
eprintln!("🔍 Unknown TLV tag: {}", tag);
|
|
||||||
Ok(Box::new(StringBox::new("ok")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Box::new(StringBox::new("ok")))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No output, return void
|
|
||||||
Ok(Box::new(VoidBox::new()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No output, return void
|
|
||||||
Ok(Box::new(VoidBox::new()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -958,6 +958,8 @@ impl NyashInterpreter {
|
|||||||
&mut self,
|
&mut self,
|
||||||
name: String,
|
name: String,
|
||||||
fields: Vec<String>,
|
fields: Vec<String>,
|
||||||
|
public_fields: Vec<String>,
|
||||||
|
private_fields: Vec<String>,
|
||||||
methods: HashMap<String, ASTNode>,
|
methods: HashMap<String, ASTNode>,
|
||||||
constructors: HashMap<String, ASTNode>,
|
constructors: HashMap<String, ASTNode>,
|
||||||
init_fields: Vec<String>,
|
init_fields: Vec<String>,
|
||||||
@ -991,6 +993,8 @@ impl NyashInterpreter {
|
|||||||
let box_decl = super::BoxDeclaration {
|
let box_decl = super::BoxDeclaration {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
fields,
|
fields,
|
||||||
|
public_fields,
|
||||||
|
private_fields,
|
||||||
methods,
|
methods,
|
||||||
constructors,
|
constructors,
|
||||||
init_fields,
|
init_fields,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ impl NyashInterpreter {
|
|||||||
self.execute_using_statement(namespace_name)
|
self.execute_using_statement(namespace_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
ASTNode::BoxDeclaration { name, fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, .. } => {
|
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, .. } => {
|
||||||
if *is_static {
|
if *is_static {
|
||||||
// 🔥 Static Box宣言の処理
|
// 🔥 Static Box宣言の処理
|
||||||
self.register_static_box_declaration(
|
self.register_static_box_declaration(
|
||||||
@ -83,6 +83,8 @@ impl NyashInterpreter {
|
|||||||
self.register_box_declaration(
|
self.register_box_declaration(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
fields.clone(),
|
fields.clone(),
|
||||||
|
public_fields.clone(),
|
||||||
|
private_fields.clone(),
|
||||||
methods.clone(),
|
methods.clone(),
|
||||||
constructors.clone(),
|
constructors.clone(),
|
||||||
init_fields.clone(),
|
init_fields.clone(),
|
||||||
@ -286,9 +288,28 @@ impl NyashInterpreter {
|
|||||||
|
|
||||||
ASTNode::FieldAccess { object, field, .. } => {
|
ASTNode::FieldAccess { object, field, .. } => {
|
||||||
// フィールドへの代入
|
// フィールドへの代入
|
||||||
|
// 内部(me/this)からの代入かどうか
|
||||||
|
let is_internal = match &**object {
|
||||||
|
ASTNode::This { .. } | ASTNode::Me { .. } => true,
|
||||||
|
ASTNode::Variable { name, .. } if name == "me" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
let obj_value = self.execute_expression(object)?;
|
let obj_value = self.execute_expression(object)?;
|
||||||
|
|
||||||
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
||||||
|
// 可視性チェック(外部アクセスの場合のみ)
|
||||||
|
if !is_internal {
|
||||||
|
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||||
|
if let Some(box_decl) = box_decls.get(&instance.class_name) {
|
||||||
|
let has_visibility = !box_decl.public_fields.is_empty() || !box_decl.private_fields.is_empty();
|
||||||
|
if has_visibility && !box_decl.public_fields.contains(&field.to_string()) {
|
||||||
|
return Err(RuntimeError::InvalidOperation {
|
||||||
|
message: format!("Field '{}' is private in {}", field, instance.class_name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 🔥 finiは何回呼ばれてもエラーにしない(ユーザー要求)
|
// 🔥 finiは何回呼ばれてもエラーにしない(ユーザー要求)
|
||||||
// is_finalized()チェックを削除
|
// is_finalized()チェックを削除
|
||||||
|
|
||||||
|
|||||||
@ -137,6 +137,8 @@ impl NyashParser {
|
|||||||
|
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
let mut methods = HashMap::new();
|
let mut methods = HashMap::new();
|
||||||
|
let mut public_fields: Vec<String> = Vec::new();
|
||||||
|
let mut private_fields: Vec<String> = Vec::new();
|
||||||
let mut constructors = HashMap::new();
|
let mut constructors = HashMap::new();
|
||||||
let mut init_fields = Vec::new();
|
let mut init_fields = Vec::new();
|
||||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
||||||
@ -404,6 +406,35 @@ impl NyashParser {
|
|||||||
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
||||||
let field_or_method = field_or_method.clone();
|
let field_or_method = field_or_method.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
|
|
||||||
|
// 可視性ブロック: public { ... } / private { ... }
|
||||||
|
if field_or_method == "public" || field_or_method == "private" {
|
||||||
|
self.consume(TokenType::LBRACE)?;
|
||||||
|
self.skip_newlines();
|
||||||
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||||
|
if let TokenType::IDENTIFIER(fname) = &self.current_token().token_type {
|
||||||
|
let fname = fname.clone();
|
||||||
|
// ブロックに追加
|
||||||
|
if field_or_method == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||||
|
// 互換性のため、全体fieldsにも追加
|
||||||
|
fields.push(fname);
|
||||||
|
self.advance();
|
||||||
|
// カンマ/改行をスキップ
|
||||||
|
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||||
|
self.skip_newlines();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 予期しないトークン
|
||||||
|
return Err(ParseError::UnexpectedToken {
|
||||||
|
expected: "identifier in visibility block".to_string(),
|
||||||
|
found: self.current_token().token_type.clone(),
|
||||||
|
line: self.current_token().line,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.consume(TokenType::RBRACE)?;
|
||||||
|
self.skip_newlines();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// メソッドかフィールドかを判定
|
// メソッドかフィールドかを判定
|
||||||
if self.match_token(&TokenType::LPAREN) {
|
if self.match_token(&TokenType::LPAREN) {
|
||||||
@ -473,6 +504,8 @@ impl NyashParser {
|
|||||||
Ok(ASTNode::BoxDeclaration {
|
Ok(ASTNode::BoxDeclaration {
|
||||||
name,
|
name,
|
||||||
fields,
|
fields,
|
||||||
|
public_fields,
|
||||||
|
private_fields,
|
||||||
methods,
|
methods,
|
||||||
constructors,
|
constructors,
|
||||||
init_fields,
|
init_fields,
|
||||||
@ -571,6 +604,8 @@ impl NyashParser {
|
|||||||
Ok(ASTNode::BoxDeclaration {
|
Ok(ASTNode::BoxDeclaration {
|
||||||
name,
|
name,
|
||||||
fields: vec![], // インターフェースはフィールドなし
|
fields: vec![], // インターフェースはフィールドなし
|
||||||
|
public_fields: vec![],
|
||||||
|
private_fields: vec![],
|
||||||
methods,
|
methods,
|
||||||
constructors: HashMap::new(), // インターフェースにコンストラクタなし
|
constructors: HashMap::new(), // インターフェースにコンストラクタなし
|
||||||
init_fields: vec![], // インターフェースにinitブロックなし
|
init_fields: vec![], // インターフェースにinitブロックなし
|
||||||
@ -584,4 +619,4 @@ impl NyashParser {
|
|||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -273,6 +273,8 @@ impl NyashParser {
|
|||||||
Ok(ASTNode::BoxDeclaration {
|
Ok(ASTNode::BoxDeclaration {
|
||||||
name,
|
name,
|
||||||
fields,
|
fields,
|
||||||
|
public_fields: vec![],
|
||||||
|
private_fields: vec![],
|
||||||
methods,
|
methods,
|
||||||
constructors,
|
constructors,
|
||||||
init_fields,
|
init_fields,
|
||||||
@ -286,4 +288,4 @@ impl NyashParser {
|
|||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -365,10 +365,23 @@ impl NyashRunner {
|
|||||||
ASTNode::Program { statements, .. } => {
|
ASTNode::Program { statements, .. } => {
|
||||||
for st in statements { walk(st, runtime); }
|
for st in statements { walk(st, runtime); }
|
||||||
}
|
}
|
||||||
ASTNode::BoxDeclaration { name, fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
ASTNode::FunctionDeclaration { body, .. } => {
|
||||||
|
// Walk into function bodies to find nested box declarations
|
||||||
|
for st in body { walk(st, runtime); }
|
||||||
|
}
|
||||||
|
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
||||||
|
// Walk into methods/constructors to find nested box declarations
|
||||||
|
for (_mname, mnode) in methods {
|
||||||
|
walk(mnode, runtime);
|
||||||
|
}
|
||||||
|
for (_ckey, cnode) in constructors {
|
||||||
|
walk(cnode, runtime);
|
||||||
|
}
|
||||||
let decl = CoreBoxDecl {
|
let decl = CoreBoxDecl {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
fields: fields.clone(),
|
fields: fields.clone(),
|
||||||
|
public_fields: public_fields.clone(),
|
||||||
|
private_fields: private_fields.clone(),
|
||||||
methods: methods.clone(),
|
methods: methods.clone(),
|
||||||
constructors: constructors.clone(),
|
constructors: constructors.clone(),
|
||||||
init_fields: init_fields.clone(),
|
init_fields: init_fields.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user