Commit Graph

68 Commits

Author SHA1 Message Date
ef83796ccc Implement BaseObjInst::eq and ::neq
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 12:38:13 -07:00
8f9d634a15 Reorder derives, I guess
Formatters do the darnedest things

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 12:37:46 -07:00
56001856be Manually implement Debug for MethodInst
MethodInst's self_binding was causing endless recursion issues, this
just skips over it and uses the normal formatting for it

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 12:30:26 -07:00
3e769e9c48 Compile RHS of binary expressions (oopsie)
Not sure how this happened besides being a gigantic moron, I completely
forgot to do `self.compile_expr(&expr.rhs)`.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 12:27:53 -07:00
4b5d2af117 Move builtin object methods from src/obj.rs to src/builtins.rs
Builtin functions are now living in the builtins file. They're still
part of BaseObjInst but they just are in a different file. Also,
implement the base "not" function.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:50:51 -07:00
3a7c04686a Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).

Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
d69a60f42c Fix tests that were broken from the last commit
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 09:05:31 -07:00
078aef70ea Split up src/obj.rs
* common macros are in their own private module
* functions are in their own obj::function module

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 09:03:34 -07:00
d699ad2ff5 Move MethodInst stack mangling to MethodInst::call
When a MethodInst is called as a function in the VM, it needs to push
its `self_binding` member to the stack. Previously, we were downcasting
(if possible) to MethodInst in the VM, but really, we are calling
`MethodInst::call` anyway, so it makes more sense to do
MethodInst-specific stuff in the MethodInst-specific function.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 08:44:05 -07:00
3545488ef8 Add TODO notice on a sorta-important scoping bug
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 08:37:29 -07:00
fe586526df Fix get_attr_lazy
I had misunderstood/misused the ? suffix operator for the Option type.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 21:48:44 -07:00
9c4898ff8d Add base object function stubs(!)
Big step here, we have function stubs available for everybody. Most of
them panic. Each type will eventually have its own implementations for
different operators.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 21:34:10 -07:00
0d04090a99 Implement vtables and method resolution
Types now can have vtable elements which are used by instances to bind
themselves to methods. When Op::GetAttr is executed, it calls a new
function, Obj::get_attr_lazy. This will search:

* attributes on the object
* vtable on the object's type
* vtable on the object's type's type,
* etc.

This searches up the type tree for a named value. If it exists as an
attribute, it will be returned immediately. If it exists in the type's
vtable, then it will be inserted as an attribute. If the vtable value is
a function, the object that it is being called on will be bound to that
method as the `self` parameter.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 20:59:00 -07:00
8b931e9d12 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>
2024-09-23 18:12:32 -07:00
24b06851c7 WIP: move mutability to be internal to the object instead of the pointer
I'm not super happy with this. But, the RwLock has been moved to the
`BaseObjInst::attrs` member. Although this is not exactly how it appears
in code, it basically does this:

    type Ptr<T> = Arc<RwLock<T>>;

    struct BaseObjInst {
        attr: HashMap<String, Ptr<dyn Obj>>,
        // etc
    }

becomes

    type Ptr<T> = Arc<T>;

    struct BaseObjInst {
        attr: RwLock<HashMap<String, ObjP>>,
        // etc
    }

This makes things a lot more ergonomic (don't have to use try_read() and
try_write() everywhere), but it also eliminates compile-time errors that
would catch mutability errors. This is currently rearing its ugly head
when initializing the typesystem, since `Type` needs to hold a circular
reference itself (which it already shouldn't be doing since it's a
reference-counted pointer!). Currently, all tests are failing because of
this limitation.

There are a couple of ways around this limitation.

The first solution would be just copying  all of the object
instantiation code into the `init_types` function and avoid calling
`some_base_type.instantiate()`. This would probably be literal
copy-pasting, or maybe an (ugly) macro, and probably a nightmare to
maintain long-term. I don't like this option, but it would make
everything "just work" with reference-counted pointers.

The second solution would be to write our own garbage collector, which
would allow for circular references and (hypothetically) mutably
updating these references. This is something that I am looking into,
because I really want a RefCell that you can pass around in a more
ergonomic way.

I think the fundamental error that I'm running into is trying to borrow
the same value multiple times mutably, which you *really* shouldn't be
doing. I believe I need to write better code and does the same thing.

The only unsolved problem is circular references. This is not a problem
right now because I'm not writing code that has circular references
besides the base typesystem (which is not a problem because they need to
live the entire lifetime of the program), but it will be a latent
problem until it gets fixed.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-22 20:40:15 -07:00
16f3dc960c Base initial commit
Still WIP, working on object system still, which in Rust, makes me want
to kill myself

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-20 16:04:30 -07:00
ccf6c9e939 Update visitor methods to return a Result<(), Box<dyn Error>>
Visitors for AST members may be fallible now

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-20 16:03:37 -07:00
0c3e8bd4c0 Add tools and .gitignore
Mostly going to be changing up how the AST is generated so I want to get
it into git now before I screw too much up.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-16 14:07:53 -07:00