Skip to content

Commit

Permalink
Implement static methods
Browse files Browse the repository at this point in the history
  • Loading branch information
kyleect committed Jan 9, 2024
1 parent c6b7843 commit a646550
Show file tree
Hide file tree
Showing 24 changed files with 226 additions and 17 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ With the syntax and implementation changes so far the Locks language has divered
- 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)
- Implement [`static class methods`](https://kyleect.github.io/locks/#/docs#classes-static-methods)
- 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)`
Expand Down
18 changes: 18 additions & 0 deletions playground/src/pages/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ const Docs: React.FC = () => (
Static Class Fields
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="#classes-static-methods">
Static Class Methods
</Link>
</li>
<li className="nav-item">
<Link
className="nav-link"
Expand Down Expand Up @@ -555,6 +560,19 @@ const Docs: React.FC = () => (
height="200px"
/>

<DocCard
title="Static Class Methods"
anchor="classes-static-methods"
code={[
'class Test {',
' static fn value () => 123;',
'}',
'',
'println(Test.value()); // out: 123',
]}
height="175px"
/>

<DocCard
title="Single Expression Method Bodies"
anchor="classes-single-expression-method-bodies"
Expand Down
4 changes: 2 additions & 2 deletions res/examples/static_field/get_static_field.locks
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Tests {
class Test {
static let value = 123;
}

println(Tests.value); // out: 123
println(Test.value); // out: 123
9 changes: 9 additions & 0 deletions res/examples/static_field/get_static_field_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class A {
static let value = 100;
}

class B extends A {

}

println(B.value); // out: 100
13 changes: 13 additions & 0 deletions res/examples/static_field/override_static_field_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class A {
static let value = 100;
}

class B extends A {

}

class C extends B {
static let value = 123;
}

println(C.value); // out: 123
6 changes: 3 additions & 3 deletions res/examples/static_field/set_static_field.locks
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Tests {
class Test {
static let value = 123;
}

Tests.value = 1000;
Test.value = 1000;

println(Tests.value); // out: 1000
println(Test.value); // out: 1000
11 changes: 11 additions & 0 deletions res/examples/static_field/set_static_field_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class A {
static let value = 100;
}

class B extends A {

}

B.value = 123;

println(B.value); // out: 123
5 changes: 5 additions & 0 deletions res/examples/static_method/get_static_method.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Test {
static fn value () => 123;
}

println(Test.value()); // out: 123
10 changes: 10 additions & 0 deletions res/examples/static_method/get_static_method_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class A {
static fn value () => 100;
}

class B extends A {

}

println(B.value()); // out: 100
println(A.value == B.value); // out: true
16 changes: 16 additions & 0 deletions res/examples/static_method/override_static_method_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class A {
static fn value () => 100;
}

class B extends A {

}

class C extends B {
static fn value () => 123;
}

println(C.value()); // out: 123
println(A.value == B.value); // out: true
println(B.value == C.value); // out: false
println(A.value == C.value); // out: false
9 changes: 9 additions & 0 deletions res/examples/static_method/set_static_method.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Test {
static fn value () => 123;
}

fn newFunction () => 1000;

Test.value = newFunction; // out: TypeError: static methods on classes can not be reassigned (e.g. class<Test>.value = "...")

println(Test.value());
13 changes: 13 additions & 0 deletions res/examples/static_method/set_static_method_from_super.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class A {
static fn value () => 100;
}

class B extends A {

}

fn test () {

}

B.value = test; // out: TypeError: static methods on classes can not be reassigned (e.g. class<B>.value = "...")
5 changes: 5 additions & 0 deletions res/examples/typeof/static_field.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Class {
static let value = "test value";
}

println(typeof(Class.value)); // out: string
7 changes: 7 additions & 0 deletions res/examples/typeof/static_method.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Class {
static fn test () {

}
}

println(typeof(Class.test)); // out: function
1 change: 1 addition & 0 deletions res/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DeclClass: ast::Stmt = <class:StmtClass> => ast::Stmt::Class(<>);
StmtClass: ast::StmtClass =
"class" <name:identifier> <super_:("extends" <Spanned<ExprIdentifier>>)?> "{"
<static_fields:("static" <Spanned<StmtAssign>>)*>
<static_methods:("static" <Spanned<StmtFn>>)*>
<fields:(<Spanned<StmtAssign>>)*>
<methods:(<Spanned<StmtFn>>)*>
"}" =>
Expand Down
4 changes: 1 addition & 3 deletions res/lib/locks.locks
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
class Object {

}
class Object {}
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ pub enum TypeError {
"methods on instances can not be reassigned (e.g. instance<{type_}>.{name} = \"...\")"
)]
InvalidMethodAssignment { name: String, type_: String },
#[error(
"static methods on classes can not be reassigned (e.g. class<{type_}>.{name} = \"...\")"
)]
InvalidStaticMethodAssignment { name: String, type_: String },
#[error("{type_:?} object has no length")]
NoLength { type_: String },
#[error(r#"expected type "{expected_type}" but got "{actual_type}""#)]
Expand Down
1 change: 1 addition & 0 deletions src/syntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct StmtClass {
pub methods: Vec<Spanned<StmtFn>>,
pub fields: Vec<Spanned<StmtAssign>>,
pub static_fields: Vec<Spanned<StmtAssign>>,
pub static_methods: Vec<Spanned<StmtFn>>,
}

/// An expression statement evaluates an expression and discards the result.
Expand Down
17 changes: 17 additions & 0 deletions src/vm/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,23 @@ impl Compiler {
self.emit_u8(op::POP, span);
}

// Initialize class methods, if they exist
if !class.static_methods.is_empty() {
self.get_variable(
&Identifier { name: class.name.clone(), package: None, depth: None },
span,
gc,
)?;
for (method, span) in &class.static_methods {
self.compile_function(method, span, FunctionType::Method, gc)?;

let name = gc.alloc(&method.name).into();
self.emit_u8(op::STATIC_METHOD, span);
self.emit_constant(name, span)?;
}
self.emit_u8(op::POP, span);
}

if let Some(x) = self.class_ctx.last() {
if x.has_super {
self.end_scope(&NO_SPAN);
Expand Down
1 change: 1 addition & 0 deletions src/vm/disassembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ impl<'a> Disassembler<'a> {
op::PACKAGE => self.disassemble_op_constant("OP_PACKAGE", op_idx),
op::FIELD => self.disassemble_op_constant("OP_FIELD", op_idx),
op::STATIC_FIELD => self.disassemble_op_constant("OP_STATIC_FIELD", op_idx),
op::STATIC_METHOD => self.disassemble_op_constant("OP_STATIC_METHOD", op_idx),
byte => self.disassemble_op_simple(&format!("OP_UNKNOWN({byte:#X})")),
};

Expand Down
5 changes: 5 additions & 0 deletions src/vm/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ impl Gc {
self.mark(name);
self.mark(static_field);
}

for (&name, &method) in unsafe { &(*class).static_methods } {
self.mark(name);
self.mark(method);
}
}
ObjectType::Closure => {
let closure = unsafe { object.closure };
Expand Down
47 changes: 39 additions & 8 deletions src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ impl VM {
op::SET_INDEX => self.op_set_index(),
op::PACKAGE => self.op_package(),
op::STATIC_FIELD => self.op_static_field(),
op::STATIC_METHOD => self.op_static_method(),
_ => util::unreachable(),
}?;

Expand Down Expand Up @@ -463,17 +464,23 @@ impl VM {
ObjectType::Class => {
let class = unsafe { object.class };

match unsafe { (*class).static_fields.get(&name) } {
match unsafe { (*class).get_static_field(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() },
});
}
None => match unsafe { (*class).get_static_method(name) } {
Some(&method) => {
self.pop();
self.push(method.into());
}
None => {
return self.err(AttributeError::NoSuchAttribute {
type_: unsafe { (*(*class).name).value.to_string() },
name: unsafe { (*name).value.to_string() },
});
}
},
}
}
ObjectType::Instance => {
Expand Down Expand Up @@ -527,13 +534,22 @@ impl VM {
ObjectType::Class => {
let class = unsafe { object.class };
let value = unsafe { *self.peek(0) };
let has_field = unsafe { (*class).static_fields.get(&name) };
let has_field = unsafe { (*class).get_static_field(name) };

if let Some(_) = has_field {
unsafe { (*class).static_fields.insert(name, value) };
return Ok(());
}

let class_has_method = unsafe { (*class).get_static_method(name) };

if let Some(_) = class_has_method {
return self.err(TypeError::InvalidStaticMethodAssignment {
name: unsafe { (*name).value.to_owned() },
type_: unsafe { (*(*class).name).value.to_owned() },
});
}

self.err(AttributeError::NoSuchAttribute {
type_: unsafe { (*(*class).name).value.to_string() },
name: unsafe { (*name).value.to_string() },
Expand Down Expand Up @@ -867,6 +883,21 @@ impl VM {
Ok(())
}

/// Define a static method on the [`ObjectClass`] on the top of the VM's stack
///
/// This consumes 1 byte op for method name.
///
/// This pop's the [`ObjectClosure`] from the VM's stack for the field value.
///
/// The next [`Value`] on the VM's stack is used as the [`ObjectClass`] for the method.
fn op_static_method(&mut self) -> Result<()> {
let name = unsafe { self.read_value().as_object().string };
let method = unsafe { self.pop().as_object().closure };
let class = unsafe { (*self.peek(0)).as_object().class };
unsafe { (*class).static_methods.insert(name, method) };
Ok(())
}

/// Define a method on the [`ObjectClass`] on the top of the VM's stack
///
/// This consumes 1 byte op for method name.
Expand Down
33 changes: 33 additions & 0 deletions src/vm/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ pub struct ObjectClass {
pub methods: HashMap<*mut ObjectString, *mut ObjectClosure, BuildHasherDefault<FxHasher>>,
pub fields: HashMap<*mut ObjectString, Value, BuildHasherDefault<FxHasher>>,
pub static_fields: HashMap<*mut ObjectString, Value, BuildHasherDefault<FxHasher>>,
pub static_methods:
HashMap<*mut ObjectString, *mut ObjectClosure, BuildHasherDefault<FxHasher>>,
}

impl ObjectClass {
Expand All @@ -263,6 +265,7 @@ impl ObjectClass {
methods: HashMap::default(),
fields: HashMap::default(),
static_fields: HashMap::default(),
static_methods: HashMap::default(),
}
}

Expand Down Expand Up @@ -293,6 +296,36 @@ impl ObjectClass {

method
}

/// Try to get static method from the class then tries it's parent/super class if it exists
///
/// This happens recursively until there isn't a parent/super class to try
pub fn get_static_method(&self, name: *mut ObjectString) -> Option<&*mut ObjectClosure> {
let method = self.static_methods.get(&name).or_else(|| {
self.super_.and_then(|super_| {
let method = unsafe { (*super_).get_static_method(name) };

method
})
});

method
}

/// Try to get static field from the class then tries it's parent/super class if it exists
///
/// This happens recursively until there isn't a parent/super class to try
pub fn get_static_field(&self, name: *mut ObjectString) -> Option<&Value> {
let method = self.static_fields.get(&name).or_else(|| {
self.super_.and_then(|super_| {
let method = unsafe { (*super_).get_static_field(name) };

method
})
});

method
}
}

#[derive(Debug)]
Expand Down
3 changes: 2 additions & 1 deletion src/vm/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@ iota! {
SET_INDEX,
// Reads a 1 byte offset for the package name
PACKAGE,
STATIC_FIELD
STATIC_FIELD,
STATIC_METHOD
}

0 comments on commit a646550

Please sign in to comment.