Revamp object system, start using gc crate

Wow, what a ride. I think everything should be working now. In short:

* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
  type. I may choose to implement my own in contiguous memory in the
  future. We will see.
* The type system is no longer global. This is a bit of a burden,
  because now, whenever you want to create a new object, you need to
  pass its type object into the `Obj::instantiate` method, as well as
  its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
  helps create new objects as long as you have access to a `builtins`
  hashmap. So something that would normally look like this:

    fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
        let print_builtin = upcast_obj(BuiltinFunctionInst::create(
            ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
            "print",
            print,
            1
        );
        builtins.insert("print".to_string(), print_builtin)
        // other builtins inserted here...
    }

  now looks like this:

    fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
        let print_builtin = builtins.create_builtin_function("print", print, 1);
        builtins.insert("print".to_string(), print_builtin);
    }

(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))

Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.

Next up is vtables.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2024-09-23 18:12:32 -07:00
parent 24b06851c7
commit 8b931e9d12
8 changed files with 502 additions and 356 deletions

View File

@@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display};
use std::sync::{Arc, LazyLock};
use std::rc::Rc;
use std::sync::LazyLock;
use assert_matches::assert_matches;
use common_macros::hash_map;
@@ -405,25 +406,25 @@ impl Display for CompileError {
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct Compiler {
pub struct Compiler<'b> {
chunks: Vec<Chunk>,
scopes: Vec<Scope>,
constants: Vec<ObjP>,
globals: Vec<String>,
builtins: &'b HashMap<String, ObjP>,
}
impl Default for Compiler {
fn default() -> Self {
impl<'b> Compiler<'b> {
pub fn new(builtins: &'b HashMap<String, ObjP>) -> Self {
Compiler {
chunks: Default::default(),
scopes: Default::default(),
constants: Default::default(),
globals: vec!["print".to_string(), "println".to_string()],
globals: builtins.keys().map(ToString::to_string).collect(),
builtins,
}
}
}
impl Compiler {
fn chunk(&self) -> &Chunk {
self.chunks.last().expect("no chunk")
}
@@ -478,7 +479,7 @@ impl Compiler {
// simple interning - try to find a constant that is exactly equal to this one and just
// return its value instead
for (index, interned) in self.constants.iter().enumerate() {
if constant.equals(interned.as_ref()) {
if constant.borrow().equals(&*interned.borrow()) {
return Ok(index as ConstantId);
}
}
@@ -491,6 +492,8 @@ impl Compiler {
}
.into());
}
// convert this to a pointer, upcast, and then re-GC
self.constants.push(constant);
Ok(index as ConstantId)
}
@@ -629,7 +632,7 @@ impl Compiler {
}
}
impl StmtVisitor for Compiler {
impl StmtVisitor for Compiler<'_> {
fn visit_expr_stmt(&mut self, stmt: &ExprStmt) -> Result<()> {
self.compile_expr(&stmt.expr)?;
self.emit(stmt_line_number(stmt), Op::Pop);
@@ -661,9 +664,13 @@ impl StmtVisitor for Compiler {
// TODO - maybe this would be smarter to set up in the AST. I'm 99% sure that the last
// object created, if it were a function object, will be what we're assigning it to, but I
// want to be 100% sure instead of 99%.
let obj = Arc::get_mut(self.constants.last_mut().unwrap()).unwrap();
if let Some(fun) = obj.as_any_mut().downcast_mut::<UserFunctionInst>() {
fun.set_name(Arc::new(name.to_string()));
let obj = self.constants.last().unwrap().as_ref();
if let Some(fun) = obj
.borrow_mut()
.as_any_mut()
.downcast_mut::<UserFunctionInst>()
{
fun.set_name(Rc::new(name.to_string()));
}
Ok(())
@@ -671,7 +678,7 @@ impl StmtVisitor for Compiler {
fn visit_set_stmt(&mut self, stmt: &SetStmt) -> Result<()> {
self.compile_expr(&stmt.expr)?;
let name = self.insert_constant(StrInst::create(&stmt.name.text))?;
let name = self.insert_constant(self.create_str(&stmt.name.text))?;
self.compile_expr(&stmt.rhs)?;
self.emit(stmt_line_number(stmt), Op::SetAttr(name));
Ok(())
@@ -690,7 +697,7 @@ impl StmtVisitor for Compiler {
if let Some(expr) = &stmt.expr {
self.compile_expr(expr)?;
} else {
let nil = self.insert_constant(NilInst::create())?;
let nil = self.insert_constant(self.create_nil())?;
self.emit(stmt_line_number(stmt), Op::PushConstant(nil));
}
Ok(())
@@ -699,7 +706,7 @@ impl StmtVisitor for Compiler {
// condition
self.compile_expr(&stmt.condition)?;
// call obj.__bool__()
let bool_attr = self.insert_constant(StrInst::create("__bool__"))?;
let bool_attr = self.insert_constant(self.create_str("__bool__"))?;
self.emit(expr_line_number(&*stmt.condition), Op::GetAttr(bool_attr));
self.emit(expr_line_number(&*stmt.condition), Op::Call(0));
let condition_patch_index = self.chunk().code.len();
@@ -746,7 +753,7 @@ impl StmtVisitor for Compiler {
}
}
impl ExprVisitor for Compiler {
impl ExprVisitor for Compiler<'_> {
fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> Result<()> {
static OP_NAMES: LazyLock<HashMap<TokenKind, &'static str>> = LazyLock::new(|| {
hash_map! {
@@ -771,7 +778,7 @@ impl ExprVisitor for Compiler {
let mut exit_patch_index = 0;
if let TokenKind::And | TokenKind::Or = expr.op.kind {
let constant_id = self.insert_constant(StrInst::create("__bool__"))?;
let constant_id = self.insert_constant(self.create_str("__bool__"))?;
self.emit(expr_line_number(&*expr.lhs), Op::GetAttr(constant_id));
self.emit(expr_line_number(&*expr.lhs), Op::Call(0));
exit_patch_index = self.chunk().code.len();
@@ -785,12 +792,12 @@ impl ExprVisitor for Compiler {
let name = OP_NAMES
.get(&expr.op.kind)
.expect("invalid binary operator");
let constant_id = self.insert_constant(StrInst::create(name))?;
let constant_id = self.insert_constant(self.create_str(name))?;
self.emit(expr_line_number(expr), Op::GetAttr(constant_id));
// convert RHS to a bool if we're doing AND or OR
if let TokenKind::And | TokenKind::Or = expr.op.kind {
let constant_id = self.insert_constant(StrInst::create("__bool__"))?;
let constant_id = self.insert_constant(self.create_str("__bool__"))?;
self.emit(expr_line_number(&*expr.rhs), Op::GetAttr(constant_id));
self.emit(expr_line_number(&*expr.rhs), Op::Call(0));
}
@@ -828,7 +835,7 @@ impl ExprVisitor for Compiler {
});
self.compile_expr(&expr.expr)?;
let name = OP_NAMES.get(&expr.op.kind).expect("invalid unary operator");
let constant_id = self.insert_constant(StrInst::create(name))?;
let constant_id = self.insert_constant(self.create_str(name))?;
self.emit(expr_line_number(expr), Op::GetAttr(constant_id));
self.emit(expr_line_number(expr), Op::Call(0));
Ok(())
@@ -852,7 +859,7 @@ impl ExprVisitor for Compiler {
fn visit_get_expr(&mut self, expr: &GetExpr) -> Result<()> {
self.compile_expr(&expr.expr)?;
let constant_id = self.insert_constant(StrInst::create(&expr.name.text))?;
let constant_id = self.insert_constant(self.create_str(&expr.name.text))?;
self.emit(expr_line_number(expr), Op::GetAttr(constant_id));
Ok(())
}
@@ -878,25 +885,25 @@ impl ExprVisitor for Compiler {
}
TokenKind::Number => {
let obj = if expr.token.text.contains('.') {
FloatInst::create(expr.token.text.parse().unwrap()) as ObjP
self.create_float(expr.token.text.parse().unwrap())
} else {
IntInst::create(expr.token.text.parse().unwrap()) as ObjP
self.create_int(expr.token.text.parse().unwrap())
};
let constant_id = self.insert_constant(obj)?;
self.emit(expr_line_number(expr), Op::PushConstant(constant_id));
}
TokenKind::String => {
let constant_id =
self.insert_constant(StrInst::create(unescape(&expr.token.text)))?;
self.insert_constant(self.create_str(unescape(&expr.token.text)))?;
self.emit(expr_line_number(expr), Op::PushConstant(constant_id));
}
TokenKind::True | TokenKind::False => {
let constant_id =
self.insert_constant(BoolInst::create(expr.token.kind == TokenKind::True))?;
self.insert_constant(self.create_bool(expr.token.kind == TokenKind::True))?;
self.emit(expr_line_number(expr), Op::PushConstant(constant_id));
}
TokenKind::Nil => {
let constant_id = self.insert_constant(NilInst::create())?;
let constant_id = self.insert_constant(self.create_nil())?;
self.emit(expr_line_number(expr), Op::PushConstant(constant_id));
}
_ => unreachable!(),
@@ -954,7 +961,7 @@ impl ExprVisitor for Compiler {
}
// always end with a "return nil"
let nil = self.insert_constant(NilInst::create())?;
let nil = self.insert_constant(self.create_nil())?;
self.emit(end_line, Op::PushConstant(nil));
self.emit(end_line, Op::Return);
@@ -962,7 +969,7 @@ impl ExprVisitor for Compiler {
// create the function
let chunk = self.chunks.pop().unwrap();
let fun = UserFunctionInst::create(chunk, expr.params.len() as Argc);
let fun = self.create_user_function(chunk, expr.params.len() as Argc);
// register the function as a constant
let fun_constant = self.insert_constant(fun)?;
@@ -982,3 +989,9 @@ impl ExprVisitor for Compiler {
Ok(())
}
}
impl ObjFactory for Compiler<'_> {
fn builtins(&self) -> &HashMap<String, ObjP> {
&self.builtins
}
}