Files
not-python-rust/src/obj/ty.rs

324 lines
12 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use std::fmt::{self, Debug, Display};
use std::rc::Rc;
use gc::{Finalize, Trace};
use crate::obj::macros::*;
use crate::obj::prelude::*;
use crate::obj::{BaseObj, BUILTINS};
use crate::vm::{Argc, Vm};
#[derive(Trace, Finalize)]
pub struct Ty {
base: BaseObj,
#[unsafe_ignore_trace]
name: Rc<String>,
vtable: HashMap<String, ObjP>,
}
impl Ty {
pub fn new(name: impl ToString) -> Self {
Self {
name: Rc::new(name.to_string()),
base: Default::default(),
vtable: Default::default(),
}
}
pub fn name(&self) -> &Rc<String> {
&self.name
}
pub fn vtable(&self) -> &HashMap<String, ObjP> {
&self.vtable
}
impl_create!(name: impl ToString);
}
impl Debug for Ty {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"<Ty {} at {:#x}>",
self.name,
(self as *const _ as usize)
)
}
}
impl Display for Ty {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self, fmt)
}
}
impl Object for Ty {
fn equals(&self, other: &dyn Object) -> bool {
if let Some(other) = other.as_any().downcast_ref::<Ty>() {
// TODO Ty::equals : something more robust than this
// Tys should hold equality if they have the same name
// the problem is that Ty.get_attr("__ty__") is going to return itself, so we have
// to go through attributes to specially exclude to the __ty__ attribute if it points
// to ourself.
// How do we detect that it's pointing to ourself? I suppose pointers are the way
self.name == other.name
} else {
false
}
}
fn call(&self, vm: &mut Vm, argc: Argc) {
// TODO Object::call - need to handle "this object cannot be called" errors
// BLOCKED-ON: exceptions
// I don't think there's any way we could call this *without* it being a method.
// If you do e.g. `Int.__call__`, Int is an object, so it should be doing `__call__` as a
// vtable value.
if cfg!(debug_assertions) {
let index = vm.stack().len() - 1 - argc as usize;
let this = vm.stack()[index].clone();
assert!(
std::ptr::addr_eq(&*this.borrow(), self),
"calling {}.__call__ on type that is not ourselves",
self.ty_name()
);
}
let function = self
.vtable
.get("__call__")
.expect("Why does a type not have a __call__ member?");
function.borrow().call(vm, argc);
}
fn arity(&self) -> Option<Argc> {
// HACK XXX NOTE Ty __call__ arity :
// We need to tread carefully here. Normally, `__call__` would be wrapped as a method.
// However, we have to get the `__call__` member directly from the vtable.
// We are subtracting 1 from the arity, because whenever it *does* become a method, the
// arity will match when we call `Ty` directly.
self.vtable
.get("__call__")
.and_then(|function| function.borrow().arity())
.map(|n| n - 1)
}
impl_base_obj!(Ty);
}
pub fn init_types() {
#![allow(non_snake_case)]
macro_rules! types {
(
base_type: $base_type:ident,
$(
$name:ident {
$( $vtable_name:ident => $vtable_value:expr ),* $(,)?
}
),* $(,)?
) => {{
$(
let $name = make_ptr(Ty::new(stringify!($name)));
BUILTINS.with_borrow_mut(|builtins| builtins.insert(stringify!($name).to_string(), $name.clone()));
)*
// We have to instantiate these objects all by hand. This is because the `instantiate`
// function does some stuff that may accidentally cause infinite recursion while we are
// setting up these fundamental types.
$({
let base_type = $base_type.clone();
$name.borrow_mut().set_attr("__ty__", base_type);
with_obj_downcast_mut($name.clone(), |ty: &mut Ty| { ty.base.is_instantiated = true; });
})*
$({
$(
let vtable_name = stringify!($vtable_name);
let vtable_value = $vtable_value;
with_obj_downcast_mut($name.clone(), |ty: &mut Ty| {
ty.vtable.insert(vtable_name.to_string(), vtable_value);
});
)*
})*
}};
}
types! {
// base type
base_type: Ty,
// type definitions
Ty {
// Conversion methods
to_str => BuiltinFunction::create("to_str", BaseObj::to_str, 1),
to_repr => BuiltinFunction::create("to_repr", BaseObj::to_repr, 1),
to_bool => BuiltinFunction::create("to_bool", BaseObj::to_bool, 1),
to_int => BuiltinFunction::create("to_int", BaseObj::not_implemented_un, 1),
to_float => BuiltinFunction::create("to_float", BaseObj::not_implemented_un, 1),
to_list => BuiltinFunction::create("to_list", BaseObj::not_implemented_un, 1),
// Constructor
// TODO Ty::do_call, Ty::init - implement these methods
__call__ => BuiltinFunction::create("__call__", BaseObj::not_implemented_un, 1),
__init__ => BuiltinFunction::create("__init__", BaseObj::not_implemented_un, 1),
// Operators
__add__ => BuiltinFunction::create("__add__", BaseObj::not_implemented_bin, 2),
__sub__ => BuiltinFunction::create("__sub__", BaseObj::not_implemented_bin, 2),
__mul__ => BuiltinFunction::create("__mul__", BaseObj::not_implemented_bin, 2),
__div__ => BuiltinFunction::create("__div__", BaseObj::not_implemented_bin, 2),
__and__ => BuiltinFunction::create("__and__", BaseObj::and, 2),
__or__ => BuiltinFunction::create("__or__", BaseObj::or, 2),
__ne__ => BuiltinFunction::create("__ne__", BaseObj::ne, 2),
__eq__ => BuiltinFunction::create("__eq__", BaseObj::eq, 2),
__gt__ => BuiltinFunction::create("__gt__", BaseObj::not_implemented_bin, 2),
__ge__ => BuiltinFunction::create("__ge__", BaseObj::not_implemented_bin, 2),
__lt__ => BuiltinFunction::create("__lt__", BaseObj::not_implemented_bin, 2),
__le__ => BuiltinFunction::create("__le__", BaseObj::not_implemented_bin, 2),
__pos__ => BuiltinFunction::create("__pos__", BaseObj::not_implemented_un, 1),
__neg__ => BuiltinFunction::create("__neg__", BaseObj::not_implemented_un, 1),
__not__ => BuiltinFunction::create("__not__", BaseObj::not, 1),
// Methods
len => BuiltinFunction::create("len", BaseObj::not_implemented_un, 1),
},
Obj {
//__call__ => BuiltinFunction::create("__call__",
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
// Methods
},
List {
// Conversion methods
to_repr => BuiltinFunction::create("to_repr", List::to_repr, 1),
to_list => BuiltinFunction::create("to_list", List::to_list, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", List::do_call, 2),
__init__ => BuiltinFunction::create("__init__", List::init, 2),
// Operators
__index__ => BuiltinFunction::create("__index__", List::index, 2),
// Methods
len => BuiltinFunction::create("len", List::len, 1),
push => BuiltinFunction::create("push", List::push, 2),
pop => BuiltinFunction::create("pop", List::pop, 1),
extend => BuiltinFunction::create("extend", List::extend, 2),
},
Str {
// Conversion methods
to_str => BuiltinFunction::create("to_str", Str::to_str, 1),
to_int => BuiltinFunction::create("to_int", Str::to_int, 1),
to_float => BuiltinFunction::create("to_float", Str::to_float, 1),
to_list => BuiltinFunction::create("to_list", Str::to_list, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", Str::do_call, 2),
__init__ => BuiltinFunction::create("__init__", Str::init, 2),
// Operators
__add__ => BuiltinFunction::create("__add__", Str::add, 2),
__mul__ => BuiltinFunction::create("__mul__", Str::mul, 2),
__index__ => BuiltinFunction::create("__index__", Str::index, 2),
// Methods
len => BuiltinFunction::create("len", Str::len, 1),
// TODO Str methods - .lower, .upper, .slice, etc
},
Int {
// Conversion methods
to_int => BuiltinFunction::create("to_int", Int::to_int, 1),
to_float => BuiltinFunction::create("to_float", Int::to_float, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", Int::do_call, 2),
__init__ => BuiltinFunction::create("__init__", Int::init, 2),
// Operators
__add__ => BuiltinFunction::create("__add__", Int::add, 2),
__sub__ => BuiltinFunction::create("__sub__", Int::sub, 2),
__mul__ => BuiltinFunction::create("__mul__", Int::mul, 2),
__div__ => BuiltinFunction::create("__div__", Int::div, 2),
//__eq__ => BuiltinFunction::create("__eq__", Int::eq, 2),
__gt__ => BuiltinFunction::create("__gt__", Int::gt, 2),
__ge__ => BuiltinFunction::create("__ge__", Int::ge, 2),
__lt__ => BuiltinFunction::create("__lt__", Int::lt, 2),
__le__ => BuiltinFunction::create("__le__", Int::le, 2),
__pos__ => BuiltinFunction::create("__pos__", Int::pos, 1),
__neg__ => BuiltinFunction::create("__neg__", Int::neg, 1),
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
// Methods
},
Float {
// Conversion methods
to_int => BuiltinFunction::create("to_int", Float::to_int, 1),
to_float => BuiltinFunction::create("to_float", Float::to_float, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", Float::do_call, 2),
__init__ => BuiltinFunction::create("__init__", Float::init, 2),
// Operators
__add__ => BuiltinFunction::create("__add__", Float::add, 2),
__sub__ => BuiltinFunction::create("__sub__", Float::sub, 2),
__mul__ => BuiltinFunction::create("__mul__", Float::mul, 2),
__div__ => BuiltinFunction::create("__div__", Float::div, 2),
__gt__ => BuiltinFunction::create("__gt__", Float::gt, 2),
__ge__ => BuiltinFunction::create("__ge__", Float::ge, 2),
__lt__ => BuiltinFunction::create("__lt__", Float::lt, 2),
__le__ => BuiltinFunction::create("__le__", Float::le, 2),
__pos__ => BuiltinFunction::create("__pos__", Float::pos, 1),
__neg__ => BuiltinFunction::create("__neg__", Float::neg, 1),
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
// Methods
},
Bool {
// Conversion methods
to_int => BuiltinFunction::create("to_int", Bool::to_int, 1),
to_float => BuiltinFunction::create("to_float", Bool::to_float, 1),
// Constructor
__call__ => BuiltinFunction::create("__call__", Bool::do_call, 2),
__init__ => BuiltinFunction::create("__init__", Bool::init, 2),
// Operators
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
// Methods
},
Nil {
// Conversion methods
// Constructor
__call__ => BuiltinFunction::create("__call__", Nil::do_call, 1),
__init__ => BuiltinFunction::create("__init__", Nil::init, 1),
// Operators
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
// Methods
},
BuiltinFunction {
// Conversion methods
// Constructor
// Operators
// Methods
},
UserFunction {
// Conversion methods
// Constructor
// Operators
// Methods
},
Method {
// Conversion methods
// Constructor
// Operators
// Methods
},
Module {
// Conversion methods
// Constructor
// Operators
// Methods
},
}
}