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 {
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
/// 公開フィールド(public { ... })
|
||||
public_fields: Vec<String>,
|
||||
/// 非公開フィールド(private { ... })
|
||||
private_fields: Vec<String>,
|
||||
methods: HashMap<String, ASTNode>, // method_name -> FunctionDeclaration
|
||||
constructors: HashMap<String, ASTNode>, // constructor_key -> FunctionDeclaration
|
||||
init_fields: Vec<String>, // initブロック内のフィールド定義
|
||||
|
||||
@ -173,6 +173,8 @@ pub struct VM {
|
||||
last_result: Option<VMValue>,
|
||||
/// Simple field storage for objects (maps reference -> field -> value)
|
||||
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: LoopExecutor,
|
||||
/// Shared runtime for box creation and declarations
|
||||
@ -205,6 +207,7 @@ impl VM {
|
||||
pc: 0,
|
||||
last_result: None,
|
||||
object_fields: HashMap::new(),
|
||||
object_class: HashMap::new(),
|
||||
loop_executor: LoopExecutor::new(),
|
||||
runtime: NyashRuntime::new(),
|
||||
scope_tracker: ScopeTracker::new(),
|
||||
@ -228,6 +231,7 @@ impl VM {
|
||||
pc: 0,
|
||||
last_result: None,
|
||||
object_fields: HashMap::new(),
|
||||
object_class: HashMap::new(),
|
||||
loop_executor: LoopExecutor::new(),
|
||||
runtime,
|
||||
scope_tracker: ScopeTracker::new(),
|
||||
@ -611,6 +615,8 @@ impl VM {
|
||||
// Register for scope-based finalization (share; keep same instance)
|
||||
let reg_arc = std::sync::Arc::from(b.share_box());
|
||||
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
|
||||
self.set_value(*dst, VMValue::from_nyash_box(b));
|
||||
Ok(ControlFlow::Continue)
|
||||
@ -696,6 +702,17 @@ impl VM {
|
||||
},
|
||||
|
||||
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
|
||||
let field_value = if let Some(fields) = self.object_fields.get(reference) {
|
||||
if let Some(value) = fields.get(field) {
|
||||
@ -716,6 +733,17 @@ impl VM {
|
||||
MirInstruction::RefSet { reference, field, value } => {
|
||||
// Get the value to set
|
||||
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
|
||||
if !self.object_fields.contains_key(reference) {
|
||||
@ -1127,10 +1155,12 @@ mod tests {
|
||||
crate::ast::ASTNode::Program { statements, .. } => {
|
||||
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 {
|
||||
name: name.clone(),
|
||||
fields: fields.clone(),
|
||||
public_fields: public_fields.clone(),
|
||||
private_fields: private_fields.clone(),
|
||||
methods: methods.clone(),
|
||||
constructors: constructors.clone(),
|
||||
init_fields: init_fields.clone(),
|
||||
|
||||
@ -15,6 +15,8 @@ use crate::ast::ASTNode;
|
||||
pub struct BoxDeclaration {
|
||||
pub name: String,
|
||||
pub fields: Vec<String>,
|
||||
pub public_fields: Vec<String>,
|
||||
pub private_fields: Vec<String>,
|
||||
pub methods: HashMap<String, ASTNode>,
|
||||
pub constructors: HashMap<String, ASTNode>,
|
||||
pub init_fields: Vec<String>,
|
||||
@ -26,4 +28,3 @@ pub struct BoxDeclaration {
|
||||
/// Generic type parameters
|
||||
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);
|
||||
|
||||
@ -41,6 +48,20 @@ impl NyashInterpreter {
|
||||
|
||||
// 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は何回呼ばれてもエラーにしない(ユーザー要求)
|
||||
// is_finalized()チェックを削除
|
||||
|
||||
|
||||
@ -949,8 +949,6 @@ 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)?);
|
||||
@ -958,194 +956,9 @@ impl NyashInterpreter {
|
||||
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 {
|
||||
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()))
|
||||
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) }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -958,6 +958,8 @@ impl NyashInterpreter {
|
||||
&mut self,
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
public_fields: Vec<String>,
|
||||
private_fields: Vec<String>,
|
||||
methods: HashMap<String, ASTNode>,
|
||||
constructors: HashMap<String, ASTNode>,
|
||||
init_fields: Vec<String>,
|
||||
@ -991,6 +993,8 @@ impl NyashInterpreter {
|
||||
let box_decl = super::BoxDeclaration {
|
||||
name: name.clone(),
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
|
||||
@ -64,7 +64,7 @@ impl NyashInterpreter {
|
||||
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 {
|
||||
// 🔥 Static Box宣言の処理
|
||||
self.register_static_box_declaration(
|
||||
@ -83,6 +83,8 @@ impl NyashInterpreter {
|
||||
self.register_box_declaration(
|
||||
name.clone(),
|
||||
fields.clone(),
|
||||
public_fields.clone(),
|
||||
private_fields.clone(),
|
||||
methods.clone(),
|
||||
constructors.clone(),
|
||||
init_fields.clone(),
|
||||
@ -286,9 +288,28 @@ impl NyashInterpreter {
|
||||
|
||||
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)?;
|
||||
|
||||
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は何回呼ばれてもエラーにしない(ユーザー要求)
|
||||
// is_finalized()チェックを削除
|
||||
|
||||
|
||||
@ -137,6 +137,8 @@ impl NyashParser {
|
||||
|
||||
let mut fields = Vec::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 init_fields = Vec::new();
|
||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
||||
@ -405,6 +407,35 @@ impl NyashParser {
|
||||
let field_or_method = field_or_method.clone();
|
||||
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) {
|
||||
// メソッド定義
|
||||
@ -473,6 +504,8 @@ impl NyashParser {
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
@ -571,6 +604,8 @@ impl NyashParser {
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields: vec![], // インターフェースはフィールドなし
|
||||
public_fields: vec![],
|
||||
private_fields: vec![],
|
||||
methods,
|
||||
constructors: HashMap::new(), // インターフェースにコンストラクタなし
|
||||
init_fields: vec![], // インターフェースにinitブロックなし
|
||||
|
||||
@ -273,6 +273,8 @@ impl NyashParser {
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields: vec![],
|
||||
private_fields: vec![],
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
|
||||
@ -365,10 +365,23 @@ impl NyashRunner {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
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 {
|
||||
name: name.clone(),
|
||||
fields: fields.clone(),
|
||||
public_fields: public_fields.clone(),
|
||||
private_fields: private_fields.clone(),
|
||||
methods: methods.clone(),
|
||||
constructors: constructors.clone(),
|
||||
init_fields: init_fields.clone(),
|
||||
|
||||
Reference in New Issue
Block a user