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
}