Skip to content

Commit

Permalink
Merge pull request #167 from kyleect/base-class
Browse files Browse the repository at this point in the history
Implement a base class of `Object`
  • Loading branch information
kyleect authored Jan 9, 2024
2 parents 6994b9b + 640e603 commit 81479db
Show file tree
Hide file tree
Showing 14 changed files with 108 additions and 72 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ With the syntax and implementation changes so far the Locks language has divered
- 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) 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.
- 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
19 changes: 18 additions & 1 deletion playground/src/pages/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,24 @@ const Docs: React.FC = () => (
'println(greeter.greet("World")); // out: Hello World!!!',
]}
height="425px"
/>
>
Classes can extend other classes. All classes extend or decend from
the base class `Object`.
</DocCard>

<DocCard
title="Object Base Class"
anchor="classes-object"
code={[
'class Example {}',
'let example = Example();',
'println(instanceof(example, Example)); // out: true',
'println(instanceof(example, Object)); // out: true',
]}
height="100px"
>
All classes extend or descend from the base class `Object`.
</DocCard>

<DocCard
title="Class Field/Method Index Access"
Expand Down
6 changes: 3 additions & 3 deletions res/examples/field/index_access_get_nested.locks
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Object {
class Container {
let box;

fn init(value) {
Expand All @@ -18,6 +18,6 @@ class Box {
}
}

let obj = Object(123);
let container = Container(123);

println(obj["box"]["get"]()); // out: 123
println(container["box"]["get"]()); // out: 123
6 changes: 3 additions & 3 deletions res/examples/field/index_access_get_undefined.locks
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Object {
class Test {
}

let obj = Object();
let test = Test();

println(obj["key"]); // out: AttributeError: "Object" object has no attribute "key"
println(test["key"]); // out: AttributeError: "Test" object has no attribute "key"
6 changes: 3 additions & 3 deletions res/examples/field/index_access_set_undefined.locks
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Object {
class Test {
}

let obj = Object();
let test = Test();

obj["key"] = "new value"; // out: AttributeError: "Object" object has no attribute "key"
test["key"] = "new value"; // out: AttributeError: "Test" object has no attribute "key"
1 change: 1 addition & 0 deletions res/examples/instanceof/instance_of_super_class.locks
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ println(instanceof(instance, Child)); // out: true
println(instanceof(instance, Parent)); // out: true
println(instanceof(instance, GrandParent)); // out: true
println(instanceof(instance, OtherChild)); // out: false
println(instanceof(instance, Object)); // out: true
8 changes: 0 additions & 8 deletions res/examples/super/no_superclass_bind.locks

This file was deleted.

8 changes: 0 additions & 8 deletions res/examples/super/no_superclass_call.locks

This file was deleted.

4 changes: 3 additions & 1 deletion res/examples/variable/local_from_method.locks
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
let foo = "variable";

class Foo {
class A {}

class Foo extends A {
fn method() {
println(foo);
}
Expand Down
3 changes: 3 additions & 0 deletions res/lib/locks.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Object {

}
6 changes: 3 additions & 3 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ impl Cmd {
Cmd::Run { path } => {
let source = fs::read_to_string(path)
.with_context(|| format!("could not read file: {path}"))?;
let mut vm = VM::default();
let stdout = &mut io::stdout().lock();
let mut vm = VM::new();
if let Err(e) = vm.run(&source, stdout) {
report_err(&source, e);
bail!("program exited with errors");
Expand All @@ -46,8 +46,8 @@ impl Cmd {

Cmd::Exec { source } => match source {
Some(source) => {
let mut vm = VM::default();
let stdout = &mut io::stdout().lock();
let mut vm = VM::new();

if let Err(e) = vm.run(source, stdout) {
report_err(source, e);
Expand All @@ -61,8 +61,8 @@ impl Cmd {
.lines()
.fold("".to_string(), |acc, line| acc + &line.unwrap() + "\n");

let mut vm = VM::default();
let stdout = &mut io::stdout().lock();
let mut vm = VM::new();

if let Err(e) = vm.run(&source, stdout) {
report_err(&source, e);
Expand Down
34 changes: 32 additions & 2 deletions src/vm/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,33 @@ impl Compiler {
)?;
// This will consume both values just pushed on VM's stack
self.emit_u8(op::INHERIT, span);
} else {
let base_class = String::from("Object");

if class.name != base_class {
if let Some(last) = self.class_ctx.last_mut() {
last.has_super = true;
}

self.begin_scope();
self.declare_local("super", &NO_SPAN)?;
self.define_local();

self.get_variable(
&Identifier { name: base_class, package: None, depth: None },
span,
gc,
)?;

self.get_variable(
&Identifier { name: class.name.clone(), package: None, depth: None },
span,
gc,
)?;

// This will consume both values just pushed on VM's stack
self.emit_u8(op::INHERIT, span);
}
}

// Initialize class fields, if they exist
Expand Down Expand Up @@ -205,9 +232,12 @@ impl Compiler {
self.emit_u8(op::POP, span);
}

if has_super {
self.end_scope(&NO_SPAN);
if let Some(x) = self.class_ctx.last() {
if x.has_super {
self.end_scope(&NO_SPAN);
}
}

self.class_ctx.pop().expect("attempted to pop the global context");
}
Stmt::Error => panic!("tried to compile despite parser errors"),
Expand Down
39 changes: 1 addition & 38 deletions src/vm/disassembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,44 +446,7 @@ mod tests {
println(greeter.greet(\"World\")); // out: Hello World",
concat!(
"0000 OP_CLASS 0 == 'Greeter'\n",
"0002 OP_DEFINE_GLOBAL 0 == 'Greeter'\n",
"0004 OP_GET_GLOBAL 0 == 'Greeter'\n",
"0006 OP_NIL\n",
"0007 OP_FIELD 1 == 'greeting'\n",
"0009 OP_POP\n",
"0010 OP_GET_GLOBAL 0 == 'Greeter'\n",
"0012 OP_CLOSURE 2 == '<fn init arity=1>'\n",
"| 0000 OP_GET_LOCAL 1\n",
"| 0002 OP_GET_LOCAL 0\n",
"| 0004 OP_SET_PROPERTY 0 == 'greeting'\n",
"| 0006 OP_POP\n",
"| 0007 OP_GET_LOCAL 0\n",
"| 0009 OP_RETURN\n",
"0014 OP_METHOD 3 == 'init'\n",
"0016 OP_CLOSURE 4 == '<fn greet arity=1>'\n",
"| 0000 OP_GET_LOCAL 0\n",
"| 0002 OP_GET_PROPERTY 0 == 'greeting'\n",
"| 0004 OP_CONSTANT 1 == ' '\n",
"| 0006 OP_ADD\n",
"| 0007 OP_GET_LOCAL 1\n",
"| 0009 OP_ADD\n",
"| 0010 OP_RETURN\n",
"0018 OP_METHOD 5 == 'greet'\n",
"0020 OP_POP\n",
"0021 OP_GET_GLOBAL 0 == 'Greeter'\n",
"0023 OP_CONSTANT 6 == 'Hello'\n",
"0025 OP_CALL 1\n",
"0027 OP_DEFINE_GLOBAL 7 == 'greeter'\n",
"0029 OP_GET_GLOBAL 8 == 'println'\n",
"0031 OP_GET_GLOBAL 7 == 'greeter'\n",
"0033 OP_GET_PROPERTY 5 == 'greet'\n",
"0035 OP_CONSTANT 9 == 'World'\n",
"0037 OP_CALL 1\n",
"0039 OP_CALL 1\n",
"0041 OP_POP\n",
"0042 OP_NIL\n",
"0043 OP_RETURN\n",
"0000 OP_CLASS 0 == 'Greeter'\n0002 OP_DEFINE_GLOBAL 0 == 'Greeter'\n0004 OP_GET_GLOBAL 1 == 'Object'\n0006 OP_GET_GLOBAL 0 == 'Greeter'\n0008 OP_INHERIT\n0009 OP_GET_GLOBAL 0 == 'Greeter'\n0011 OP_NIL\n0012 OP_FIELD 2 == 'greeting'\n0014 OP_POP\n0015 OP_GET_GLOBAL 0 == 'Greeter'\n0017 OP_CLOSURE 3 == '<fn init arity=1>'\n| 0000 OP_GET_LOCAL 1\n| 0002 OP_GET_LOCAL 0\n| 0004 OP_SET_PROPERTY 0 == 'greeting'\n| 0006 OP_POP\n| 0007 OP_GET_LOCAL 0\n| 0009 OP_RETURN\n0019 OP_METHOD 4 == 'init'\n0021 OP_CLOSURE 5 == '<fn greet arity=1>'\n| 0000 OP_GET_LOCAL 0\n| 0002 OP_GET_PROPERTY 0 == 'greeting'\n| 0004 OP_CONSTANT 1 == ' '\n| 0006 OP_ADD\n| 0007 OP_GET_LOCAL 1\n| 0009 OP_ADD\n| 0010 OP_RETURN\n0023 OP_METHOD 6 == 'greet'\n0025 OP_POP\n0026 OP_POP\n0027 OP_GET_GLOBAL 0 == 'Greeter'\n0029 OP_CONSTANT 7 == 'Hello'\n0031 OP_CALL 1\n0033 OP_DEFINE_GLOBAL 8 == 'greeter'\n0035 OP_GET_GLOBAL 9 == 'println'\n0037 OP_GET_GLOBAL 8 == 'greeter'\n0039 OP_GET_PROPERTY 6 == 'greet'\n0041 OP_CONSTANT 10 == 'World'\n0043 OP_CALL 1\n0045 OP_CALL 1\n0047 OP_POP\n0048 OP_NIL\n0049 OP_RETURN\n"
)
),
}
Expand Down
38 changes: 36 additions & 2 deletions src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ mod value;

use std::hash::BuildHasherDefault;
use std::io::Write;
use std::{mem, ptr};
use std::{fs, mem, ptr};

use anyhow::Context;
use arrayvec::ArrayVec;
pub use compiler::Compiler;
pub use disassembler::Disassembler;
Expand All @@ -36,6 +37,8 @@ const FRAMES_MAX: usize = 64;
const STACK_MAX: usize = FRAMES_MAX * STACK_MAX_PER_FRAME;
const STACK_MAX_PER_FRAME: usize = u8::MAX as usize + 1;

const PRELOADED_LOCKS_LIBS: &'static [&'static str] = &["res/lib/locks.locks"];

#[derive(Debug)]
pub struct VM {
pub globals: HashMap<*mut ObjectString, Value, BuildHasherDefault<FxHasher>>,
Expand Down Expand Up @@ -69,12 +72,43 @@ pub struct VM {
}

impl VM {
pub fn new() -> VM {
let vm = VM::default();

vm
}

pub fn run(&mut self, source: &str, stdout: &mut impl Write) -> Result<(), Vec<ErrorS>> {
let mut errors: Vec<ErrorS> = vec![];

for &path in PRELOADED_LOCKS_LIBS.into_iter() {
let source =
fs::read_to_string(&path).with_context(|| format!("could not read file: {path}"));

if let Ok(source) = source {
if let Err(mut errs) = self.load(&source, stdout) {
errors.append(&mut errs);
}
}
}

if let Err(mut errs) = self.load(&source, stdout) {
errors.append(&mut errs);
}

if errors.len() > 0 {
return Err(errors);
}

Ok(())
}

fn load(&mut self, source: &str, stdout: &mut impl Write) -> Result<(), Vec<ErrorS>> {
// This will change with each call to `run`
let offset = self.source.len();

// Add current source to self.source
// This helps us keep track of what the offset should be on future calls to `run`
// This helps us keep track of what the offset should be on future calls to `load`
self.source.reserve(source.len() + 1);
self.source.push_str(source);
self.source.push('\n');
Expand Down

0 comments on commit 81479db

Please sign in to comment.