Skip to content

Commit

Permalink
Merge pull request #151 from kyleect/instanceof
Browse files Browse the repository at this point in the history
Implement `instanceof` function
  • Loading branch information
kyleect authored Jan 9, 2024
2 parents 3614a63 + cbbd2bf commit 60e039f
Show file tree
Hide file tree
Showing 20 changed files with 246 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ With the syntax and implementation changes so far the Locks language has divered
- Add the `len` native function for lists and strings
- Change `print` from a statement to a function: `print`, `println`
- Add [`typeof`](https://kyleect.github.io/locks/#/docs#typeof) native function to return a value's type as string
- Add [`instanceof`](https://kyleect.github.io/locks/#/docs#instanceof)
- 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
32 changes: 32 additions & 0 deletions playground/src/pages/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ const Docs: React.FC = () => (
typeof
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="#instanceof ">
instanceof
</Link>
</li>
</ul>
<DocCard
title="Example"
Expand Down Expand Up @@ -581,6 +586,33 @@ const Docs: React.FC = () => (
<code>list</code> | <code>nil</code>
</DocCard>

<DocCard
title="instanceof"
anchor="instanceof"
code={[
'class GrandParent {}',
'',
'class Parent extends GrandParent {}',
'',
'class Child extends Parent {}',
'',
'class OtherChild extends Parent {}',
'',
'class Grandchild extends Child {}',
'',
'let instance = Grandchild();',
'',
'println(instanceof(instance, Grandchild)); // out: true',
'println(instanceof(instance, Child)); // out: true',
'println(instanceof(instance, Parent)); // out: true',
'println(instanceof(instance, OtherChild)); // out: false',
]}
height="400px"
>
Return <code>bool</code> if the value is an instance of the
class/super class.
</DocCard>

<div className="shadow rounded p-3 vstack gap-3">
<h2 id="errors">
Errors{' '}
Expand Down
15 changes: 15 additions & 0 deletions res/examples/instanceof/closure_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Test {}

let f;

let value = 100;

{
fn add (n) {
return a + value;
}

f = add;
}

println(instanceof(f, Test)); // out: TypeError: expected type "instance" but got "function"
7 changes: 7 additions & 0 deletions res/examples/instanceof/false_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof(false, Test)); // out: TypeError: expected type "instance" but got "bool"
7 changes: 7 additions & 0 deletions res/examples/instanceof/function_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {}

fn test () {

}

println(instanceof(test, Test)); // out: TypeError: expected type "instance" but got "function"
7 changes: 7 additions & 0 deletions res/examples/instanceof/instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof(test, Test)); // out: true
24 changes: 24 additions & 0 deletions res/examples/instanceof/instance_of_super_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class GrandParent {}

class Parent extends GrandParent {

}

class Child extends Parent {

}

class OtherChild extends Parent {

}

class Grandchild extends Child {

}

let instance = Grandchild();

println(instanceof(instance, Grandchild)); // out: true
println(instanceof(instance, Child)); // out: true
println(instanceof(instance, Parent)); // out: true
println(instanceof(instance, OtherChild)); // out: false
11 changes: 11 additions & 0 deletions res/examples/instanceof/instance_of_wrong_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Test {

}

class Wrong {

}

let test = Test();

println(instanceof(test, Wrong)); // out: false
7 changes: 7 additions & 0 deletions res/examples/instanceof/list_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof([], Test)); // out: TypeError: expected type "instance" but got "list"
9 changes: 9 additions & 0 deletions res/examples/instanceof/method_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Test {
fn test (a, b) {
return a + b;
}
}

let test = Test();

println(instanceof(test.test, Test)); // out: TypeError: expected type "instance" but got "function"
3 changes: 3 additions & 0 deletions res/examples/instanceof/native_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Test {}

println(instanceof(clock, Test)); // out: TypeError: expected type "instance" but got "function"
7 changes: 7 additions & 0 deletions res/examples/instanceof/nil_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof(nil, Test)); // out: TypeError: expected type "instance" but got "nil"
7 changes: 7 additions & 0 deletions res/examples/instanceof/number_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof(123, Test)); // out: TypeError: expected type "instance" but got "number"
7 changes: 7 additions & 0 deletions res/examples/instanceof/string_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof("test", Test)); // out: TypeError: expected type "instance" but got "string"
7 changes: 7 additions & 0 deletions res/examples/instanceof/true_instance_of_class.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Test {

}

let test = Test();

println(instanceof(true, Test)); // out: TypeError: expected type "instance" but got "bool"
4 changes: 2 additions & 2 deletions res/examples/typeof/boolean.locks
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
println(typeof(true)); // out: boolean
println(typeof(false)); // out: boolean
println(typeof(true)); // out: bool
println(typeof(false)); // out: bool
File renamed without changes.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ pub enum TypeError {
InvalidMethodAssignment { name: String, type_: String },
#[error("{type_:?} object has no length")]
NoLength { type_: String },
#[error(r#"expected type "{expected_type}" but got "{actual_type}""#)]
InvalidType { expected_type: String, actual_type: String },
}

impl AsDiagnostic for TypeError {
Expand Down
76 changes: 75 additions & 1 deletion src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ impl VM {

let obj_type = match value.type_() {
value::ValueType::Nil => "nil",
value::ValueType::Bool => "boolean",
value::ValueType::Bool => "bool",
value::ValueType::Number => "number",
value::ValueType::Object(type_) => match type_ {
ObjectType::BoundMethod => "function",
Expand All @@ -1072,6 +1072,78 @@ impl VM {

type_str
}
Native::InstanceOf => {
if arg_count != 2 {
return self.err(TypeError::ArityMismatch {
name: "instanceof".to_string(),
exp_args: 2,
got_args: arg_count,
});
}

let test = self.pop();
let value = self.pop();
self.pop();

let test_type = match test.type_() {
value::ValueType::Nil => "nil",
value::ValueType::Bool => "bool",
value::ValueType::Number => "number",
value::ValueType::Object(type_) => match type_ {
ObjectType::BoundMethod => "function",
ObjectType::Class => "class",
ObjectType::Closure => "function",
ObjectType::Function => "function",
ObjectType::Native => "function",
ObjectType::Instance => "instance",
ObjectType::String => "string",
ObjectType::List => "list",
ObjectType::Package => "package",
ObjectType::Upvalue => "upvalue",
},
};

if test_type != "class" {
return self.err(TypeError::InvalidType {
expected_type: "class".to_owned(),
actual_type: test_type.to_owned(),
});
}

let test_class = unsafe { test.as_object().class };

let value_type = match value.type_() {
value::ValueType::Nil => "nil",
value::ValueType::Bool => "bool",
value::ValueType::Number => "number",
value::ValueType::Object(type_) => match type_ {
ObjectType::BoundMethod => "function",
ObjectType::Class => "class",
ObjectType::Closure => "function",
ObjectType::Function => "function",
ObjectType::Native => "function",
ObjectType::Instance => "instance",
ObjectType::String => "string",
ObjectType::List => "list",
ObjectType::Package => "package",
ObjectType::Upvalue => "upvalue",
},
};

if value_type != "instance" {
return self.err(TypeError::InvalidType {
expected_type: "instance".to_owned(),
actual_type: value_type.to_owned(),
});
}

let value_class = unsafe { (*value.as_object().instance).class };

let same_class = test_class == value_class
|| unsafe { (*value_class).get_super_classes().contains(&test_class) };

Value::from(same_class)
}
};

self.push(value);
Expand Down Expand Up @@ -1206,6 +1278,8 @@ impl Default for VM {
globals.insert(gc.alloc("print"), gc.alloc(ObjectNative::new(Native::Print)).into());
globals.insert(gc.alloc("println"), gc.alloc(ObjectNative::new(Native::PrintLn)).into());
globals.insert(gc.alloc("typeof"), gc.alloc(ObjectNative::new(Native::TypeOf)).into());
globals
.insert(gc.alloc("instanceof"), gc.alloc(ObjectNative::new(Native::InstanceOf)).into());

let vm = Self {
globals,
Expand Down
17 changes: 16 additions & 1 deletion src/vm/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,20 @@ impl ObjectClass {
Self { common, name, super_: None, methods: HashMap::default(), fields: HashMap::default() }
}

/// Try to get method from class then tries it's parent/super class if it exists
/// Get a list of parent/super classes the class extends
pub fn get_super_classes(&self) -> Vec<*mut ObjectClass> {
let mut super_classes: Vec<*mut ObjectClass> = vec![];

if let Some(super_) = self.super_ {
let sc = unsafe { (*super_).get_super_classes() };
super_classes.extend(sc);
super_classes.push(super_);
}

super_classes
}

/// Try to get 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_method(&self, name: *mut ObjectString) -> Option<&*mut ObjectClosure> {
Expand Down Expand Up @@ -346,6 +359,7 @@ pub enum Native {
Print,
PrintLn,
TypeOf,
InstanceOf,
}

impl Display for Native {
Expand All @@ -356,6 +370,7 @@ impl Display for Native {
Native::Print => write!(f, "print"),
Native::PrintLn => write!(f, "println"),
Native::TypeOf => write!(f, "typeof"),
Native::InstanceOf => write!(f, "instanceof"),
}
}
}
Expand Down

0 comments on commit 60e039f

Please sign in to comment.