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:
Moe Charm
2025-08-21 03:08:13 +09:00
parent 2200312f09
commit 55777a0735
12 changed files with 208 additions and 201 deletions

View File

@ -949,203 +949,16 @@ impl NyashInterpreter {
arguments: &[ASTNode],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
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();
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();
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
for arg in arguments {
arg_values.push(self.execute_expression(arg)?);
}
// Encode arguments using TLV (plugin's expected format)
let mut tlv_data = Vec::new();
// Header: version(2 bytes) + argc(2 bytes)
tlv_data.extend_from_slice(&1u16.to_le_bytes()); // version = 1
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()))
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)) => Ok(result_box),
Ok(None) => Ok(Box::new(VoidBox::new())),
Err(e) => Err(RuntimeError::RuntimeFailure { message: format!("Plugin method {} failed: {:?}", method, e) }),
}
}
}