diff --git a/README.md b/README.md index af15582..f9d8723 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ With the syntax and implementation changes so far the Locks language has divered - Add [`instanceof`](https://kyleect.github.io/locks/#/docs#instanceof) native function to return `boolean` if the value is an instance of the class or super class. - Add the base class [`Object`](https://kyleect.github.io/locks/#/docs#classes-object) class that all classes extend from. - The file `res/lib/locks.locks` is loaded by the VM before running user code. This is where the base class `Object` is defined. + - Implement [`static class fields`](https://kyleect.github.io/locks/#/docs#classes-static-fields) - Bug Fixes - Add `#[repr(C)]` to `ObjectNative`. This fixes a segfault that occurred when there were multiple entries in the `Native` enum. - [Remove an OP transformation the compiler](https://github.com/kyleect/locks/pull/135/files#diff-23c5734d7de815d5e64ad2291873d96e9f686a8b11d76481f3d02c905c53341dL403) was doing that would cause a segfault when bound methods were passed to functions e.g. `function(instance.method)` diff --git a/playground/src/pages/docs.tsx b/playground/src/pages/docs.tsx index 0d74201..3eae287 100644 --- a/playground/src/pages/docs.tsx +++ b/playground/src/pages/docs.tsx @@ -153,6 +153,11 @@ const Docs: React.FC = () => ( Class Field/Method Index Access +
  • + + Static Class Fields + +
  • ( height="300px" /> + + => ast::Stmt::Class(<>); StmtClass: ast::StmtClass = "class" >)?> "{" + >)*> >)*> >)*> "}" => @@ -395,5 +396,6 @@ extern { "while" => lexer::Token::While, "extends" => lexer::Token::Extends, "package" => lexer::Token::Package, + "static" => lexer::Token::Static } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index a631a74..d99d8f6 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -53,6 +53,7 @@ pub struct StmtClass { pub super_: Option, pub methods: Vec>, pub fields: Vec>, + pub static_fields: Vec>, } /// An expression statement evaluates an expression and discards the result. diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 1595dcf..0752406 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -158,6 +158,8 @@ pub enum Token { Extends, #[token("package")] Package, + #[token("static")] + Static, #[regex(r"//.*", logos::skip)] #[regex(r"[ \r\n\t\f]+", logos::skip)] diff --git a/src/vm/compiler.rs b/src/vm/compiler.rs index 0b13f9b..f744fe1 100644 --- a/src/vm/compiler.rs +++ b/src/vm/compiler.rs @@ -210,6 +210,36 @@ impl Compiler { self.emit_u8(op::POP, span); } + // Initialize class fields, if they exist + if !class.static_fields.is_empty() { + // Get class `Value` by name and push it on to the VM's stack + self.get_variable( + &Identifier { name: class.name.clone(), package: None, depth: None }, + span, + gc, + )?; + + for (field_assign, span) in &class.static_fields { + let name = &field_assign.identifier.name; + + // Compile value expression if it exists and push it on the VM's stack. + // + // This value is the field's default value. + // Otherwise its initalized to `nil`. + match &field_assign.value { + Some(value) => self.compile_expr(value, gc)?, + None => self.emit_u8(op::NIL, span), + } + + self.emit_u8(op::STATIC_FIELD, span); + // Emit field name's constant index + let name = gc.alloc(name).into(); + self.emit_constant(name, span)?; + } + + self.emit_u8(op::POP, span); + } + // Initialize class methods, if they exist if !class.methods.is_empty() { self.get_variable( diff --git a/src/vm/gc.rs b/src/vm/gc.rs index bdca9bb..632bfd4 100644 --- a/src/vm/gc.rs +++ b/src/vm/gc.rs @@ -49,6 +49,11 @@ impl Gc { self.mark(name); self.mark(method); } + + for (&name, &static_field) in unsafe { &(*class).static_fields } { + self.mark(name); + self.mark(static_field); + } } ObjectType::Closure => { let closure = unsafe { object.closure }; diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 38b9556..4806bdf 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -222,6 +222,7 @@ impl VM { op::GET_INDEX => self.op_get_index(), op::SET_INDEX => self.op_set_index(), op::PACKAGE => self.op_package(), + op::STATIC_FIELD => self.op_static_field(), _ => util::unreachable(), }?; @@ -447,13 +448,58 @@ impl VM { fn op_get_property(&mut self) -> Result<()> { let name = unsafe { self.read_value().as_object().string }; - let instance = { - let value = unsafe { *self.peek(0) }; - let object = value.as_object(); + let value = unsafe { *self.peek(0) }; - if value.is_object() && object.type_() == ObjectType::Instance { - unsafe { object.instance } - } else { + if !value.is_object() { + return self.err(AttributeError::NoSuchAttribute { + type_: value.type_().to_string(), + name: unsafe { (*name).value.to_string() }, + }); + } + + let object = value.as_object(); + + match object.type_() { + ObjectType::Class => { + let class = unsafe { object.class }; + + match unsafe { (*class).static_fields.get(&name) } { + Some(&field) => { + self.pop(); + self.push(field); + } + None => { + return self.err(AttributeError::NoSuchAttribute { + type_: unsafe { (*(*class).name).value.to_string() }, + name: unsafe { (*name).value.to_string() }, + }); + } + } + } + ObjectType::Instance => { + let instance = unsafe { object.instance }; + + match unsafe { (*instance).fields.get(&name) } { + Some(&field) => { + self.pop(); + self.push(field); + } + None => match unsafe { (*(*instance).class).get_method(name) } { + Some(&method) => { + let bound_method = self.alloc(ObjectBoundMethod::new(instance, method)); + self.pop(); + self.push(bound_method.into()); + } + None => { + return self.err(AttributeError::NoSuchAttribute { + type_: unsafe { (*(*(*instance).class).name).value.to_string() }, + name: unsafe { (*name).value.to_string() }, + }); + } + }, + } + } + _ => { return self.err(AttributeError::NoSuchAttribute { type_: value.type_().to_string(), name: unsafe { (*name).value.to_string() }, @@ -461,65 +507,78 @@ impl VM { } }; - match unsafe { (*instance).fields.get(&name) } { - Some(&field) => { - self.pop(); - self.push(field); - } - None => match unsafe { (*(*instance).class).get_method(name) } { - Some(&method) => { - let bound_method = self.alloc(ObjectBoundMethod::new(instance, method)); - self.pop(); - self.push(bound_method.into()); - } - None => { - return self.err(AttributeError::NoSuchAttribute { - type_: unsafe { (*(*(*instance).class).name).value.to_string() }, - name: unsafe { (*name).value.to_string() }, - }); - } - }, - } - Ok(()) } fn op_set_property(&mut self) -> Result<()> { let name = unsafe { self.read_value().as_object().string }; - let instance = { - let value = self.pop(); - let object = value.as_object(); + let value = self.pop(); - if value.is_object() && object.type_() == ObjectType::Instance { - unsafe { object.instance } - } else { - return self.err(AttributeError::NoSuchAttribute { - type_: value.type_().to_string(), + if !value.is_object() { + return self.err(AttributeError::NoSuchAttribute { + type_: value.type_().to_string(), + name: unsafe { (*name).value.to_string() }, + }); + } + + let object = value.as_object(); + + match object.type_() { + ObjectType::Class => { + let class = unsafe { object.class }; + let value = unsafe { *self.peek(0) }; + let has_field = unsafe { (*class).static_fields.get(&name) }; + + if let Some(_) = has_field { + unsafe { (*class).static_fields.insert(name, value) }; + return Ok(()); + } + + self.err(AttributeError::NoSuchAttribute { + type_: unsafe { (*(*class).name).value.to_string() }, name: unsafe { (*name).value.to_string() }, - }); + }) } - }; - let value = unsafe { *self.peek(0) }; - let has_field = unsafe { (*instance).fields.get(&name) }; + ObjectType::Instance => { + let instance = { + if value.is_object() && object.type_() == ObjectType::Instance { + unsafe { object.instance } + } else { + return self.err(AttributeError::NoSuchAttribute { + type_: value.type_().to_string(), + name: unsafe { (*name).value.to_string() }, + }); + } + }; + let value = unsafe { *self.peek(0) }; + let has_field = unsafe { (*instance).fields.get(&name) }; - if let Some(_) = has_field { - unsafe { (*instance).fields.insert(name, value) }; - return Ok(()); - } + if let Some(_) = has_field { + unsafe { (*instance).fields.insert(name, value) }; + return Ok(()); + } - let class_has_method = unsafe { (*(*instance).class).methods.get(&name) }; + let class_has_method = unsafe { (*(*instance).class).methods.get(&name) }; - if let Some(_) = class_has_method { - return self.err(TypeError::InvalidMethodAssignment { - name: unsafe { (*name).value.to_owned() }, - type_: unsafe { (*(*(*instance).class).name).value.to_owned() }, - }); - } + if let Some(_) = class_has_method { + return self.err(TypeError::InvalidMethodAssignment { + name: unsafe { (*name).value.to_owned() }, + type_: unsafe { (*(*(*instance).class).name).value.to_owned() }, + }); + } - self.err(AttributeError::NoSuchAttribute { - type_: unsafe { (*(*(*instance).class).name).value.to_string() }, - name: unsafe { (*name).value.to_string() }, - }) + self.err(AttributeError::NoSuchAttribute { + type_: unsafe { (*(*(*instance).class).name).value.to_string() }, + name: unsafe { (*name).value.to_string() }, + }) + } + _ => { + return self.err(AttributeError::NoSuchAttribute { + type_: value.type_().to_string(), + name: unsafe { (*name).value.to_string() }, + }); + } + } } fn op_get_super(&mut self) -> Result<()> { @@ -795,6 +854,19 @@ impl VM { Ok(()) } + /// Define a static field on the [`ObjectClass`] on the top of the VM's stack + /// + /// This consumes 2 byte ops for field name, & access modifier + /// + /// This pop's the [`Value`] from the VM's stack for the field value. + fn op_static_field(&mut self) -> Result<()> { + let name = unsafe { self.read_value().as_object().string }; + let value = self.pop(); + let class = unsafe { (*self.peek(0)).as_object().class }; + unsafe { (*class).static_fields.insert(name, value) }; + Ok(()) + } + /// Define a method on the [`ObjectClass`] on the top of the VM's stack /// /// This consumes 1 byte op for method name. diff --git a/src/vm/object.rs b/src/vm/object.rs index 48f9ca4..b39323c 100644 --- a/src/vm/object.rs +++ b/src/vm/object.rs @@ -250,12 +250,20 @@ pub struct ObjectClass { pub super_: Option<*mut ObjectClass>, pub methods: HashMap<*mut ObjectString, *mut ObjectClosure, BuildHasherDefault>, pub fields: HashMap<*mut ObjectString, Value, BuildHasherDefault>, + pub static_fields: HashMap<*mut ObjectString, Value, BuildHasherDefault>, } impl ObjectClass { pub fn new(name: *mut ObjectString) -> Self { let common = ObjectCommon { type_: ObjectType::Class, is_marked: false }; - Self { common, name, super_: None, methods: HashMap::default(), fields: HashMap::default() } + Self { + common, + name, + super_: None, + methods: HashMap::default(), + fields: HashMap::default(), + static_fields: HashMap::default(), + } } /// Get a list of parent/super classes the class extends diff --git a/src/vm/op.rs b/src/vm/op.rs index b1307f4..2d164fa 100644 --- a/src/vm/op.rs +++ b/src/vm/op.rs @@ -87,5 +87,6 @@ iota! { GET_INDEX, SET_INDEX, // Reads a 1 byte offset for the package name - PACKAGE + PACKAGE, + STATIC_FIELD }