implement basic inheritance

This commit is contained in:
chayleaf 2024-09-04 09:00:54 +07:00
parent dfcc06fb31
commit 318abf6c6d
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
6 changed files with 145 additions and 69 deletions

View file

@ -23,3 +23,12 @@ popup.setLayout(layout: Layout | null)
keyboard.setLayout(layout: Layout | null)
TODO: multiple inheritance, diamond problem
(it's technically allowed but the semantics aren't very good)
MAYBE PLANNED: associated data (currently classes instances are
singletons, which really simplifies ffi)
NOT PLANNED: dynamic classes (i.e. updating classes after their
definition). You can probably hack something up if you really need it.

View file

@ -10,7 +10,7 @@
; (define (kbd/load-font font-path) #f)
(define-syntax self (syntax-rules () ((self func arg ...) (kbd/call kbd/this 'func arg ...))))
(define-syntax super (syntax-rules () ((super func arg ...) (kbd/call (kbd/super kbd/this) 'func arg ...))))
(define-syntax super (syntax-rules () ((super func arg ...) (kbd/call (kbd/super kbd/this 'func) 'func arg ...))))
(define-syntax kbd/defmember
(syntax-rules ()

View file

@ -24,10 +24,25 @@ pub struct PopupInfo<'b> {
font_db: &'b mut FontDb,
}
#[derive(Clone, Debug)]
struct Method<E: Engine> {
func: Option<E::Func>,
super_funcs: Vec<E::Func>,
}
impl<E: Engine> Default for Method<E> {
fn default() -> Self {
Self {
func: None,
super_funcs: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Class<E: Engine> {
pub supers: Vec<String>,
pub methods: HashMap<String, E::Func>,
pub methods: HashMap<String, Method<E>>,
}
impl<E: Engine> Default for Class<E> {
@ -219,7 +234,7 @@ impl<E: Engine> Keyboard<E> {
.classes
.get_mut("en")
.and_then(|cls| cls.methods.get_mut("kbd/width-changed"))
.cloned()
.and_then(|x| x.func.clone())
{
func.call(self, vec![Value::Symbol("en".into())]).unwrap();
}

View file

@ -1,5 +1,8 @@
use std::collections::HashMap;
use crate::{
graphics, image,
keyboard::Method,
script::{Args, Engine, ErrorTrait, Func, Value},
};
@ -35,11 +38,14 @@ fn register<E: Engine>(
};
func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}");
vm.classes
let meth = vm
.classes
.entry(cls.into_owned())
.or_default()
.methods
.insert(name.into_owned(), func);
.entry(name.into_owned())
.or_default();
meth.func = Some(func);
println!("done");
Ok(Value::Null)
@ -86,15 +92,36 @@ fn class_define<E: Engine>(
.collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {supers:?}");
vm.classes
.entry(cls.into_owned())
.or_default()
.supers
.extend(supers);
let mut meths = HashMap::<_, Method<_>>::new();
// TODO: multiple inheritance ideally needs bfs rather than dfs
// and resolving diamonds requires deduplicating bases, and ideally toposorting them
// this is fine for when a single method has only a single super tho
for sup in &supers {
if let Some(sup) = vm.classes.get(sup) {
for (name, meth) in &sup.methods {
meths
.entry(name.clone())
.or_default()
.super_funcs
.extend(meth.super_funcs.iter().chain(meth.func.iter()).cloned());
}
}
}
let entry = vm.classes.entry(cls.into_owned()).or_default();
entry.supers.extend(supers);
entry.methods.extend(meths);
println!("done");
Ok(Value::Null)
}
fn call_func<E: Engine>(
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
fn call<E: Engine>(
vm: &mut Keyboard<E>,
mut args: Args<Keyboard<E>>,
@ -106,12 +133,27 @@ fn call<E: Engine>(
let name = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
let (cls_s, func_idx) = match &cls {
Value::Symbol(cls) => (cls, None),
Value::RevArray(cls1) => match <&[_; 3]>::try_from(&cls1[..]) {
Ok([Value::Int(idx), Value::Symbol(super_name), Value::Symbol(cls)]) => {
(cls, Some((super_name, idx)))
}
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
}
},
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
}
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
@ -120,72 +162,79 @@ fn call<E: Engine>(
&format!("{name:?}"),
));
};
fn call_func<E: Engine>(
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
let is_super = matches!(func_idx, Some((x, _)) if x == &name);
let func = vm
.classes
.get_mut(cls.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned());
.get_mut(cls_s.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()))
.and_then(|x| match func_idx {
Some((_, idx)) if is_super => usize::try_from(*idx)
.ok()
.and_then(|idx| x.super_funcs.iter().rev().nth(idx))
.cloned(),
_ => x
.func
.iter()
.chain(x.super_funcs.iter().rev())
.next()
.cloned(),
});
if let Some(mut func) = func {
args.push(Value::Symbol(cls));
args.push(cls);
args.reverse();
call_func(&mut func, vm, args)
} else {
Err(ErrorTrait::from_str(
"kbd/call",
&format!("{cls}/{name} is not defined"),
&if is_super {
format!("super for {cls_s}/{name} not found")
} else {
format!("{cls_s}/{name} is not defined")
},
))
}
}
fn set_user_data<E: Engine>(
vm: &mut Keyboard<E>,
fn super_fn<E: Engine>(
_vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-user-data"));
let Ok::<[Value<_>; 2], _>([cls, name]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/super"));
};
let Value::Symbol(k) = k else {
let (cls, func_idx) = match cls {
Value::Symbol(cls) => (cls, None),
Value::RevArray(cls1) => match <[_; 3]>::try_from(cls1) {
Ok([Value::Int(idx), Value::Symbol(super_name), Value::Symbol(cls)]) => {
(cls, Some((super_name, idx)))
}
_ => {
return Err(ErrorTrait::from_str("kbd/super", "invalid class"));
}
},
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/super",
"symbol",
&format!("{cls:?}"),
));
}
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/set-user-data",
"kbd/super",
"symbol",
&format!("{k:?}"),
&format!("{name:?}"),
));
};
println!("setting user data {k:?} to {v:?}");
if let Some(mut val) = vm.user_data.insert(k.into_owned(), v.clone()) {
// FIXME: theoretically this can unref data that's referenced elsewhere
val.unref(vm);
}
v.add_ref(vm);
Ok(Value::Null)
}
fn get_user_data<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([k]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/get-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/get-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("getting user data {k:?}");
Ok(vm
.user_data
.get(k.as_ref())
.cloned()
.unwrap_or(Value::Bool(false)))
Ok(match func_idx {
Some((func, idx)) if func == name => Value::RevArray(vec![
Value::Int(idx + 1),
Value::Symbol(func),
Value::Symbol(cls),
]),
_ => Value::RevArray(vec![Value::Int(0), Value::Symbol(name), Value::Symbol(cls)]),
})
}
fn add_to_i64<E: Engine>(name: &str, a: Value<E>, b: Value<E>) -> Result<i64, E::Error> {
@ -456,8 +505,6 @@ pub fn init_builtins<E: 'static + Engine>(engine: &mut Keyboard<E>) {
engine.register_func("kbd/register", register);
engine.register_func("kbd/class-define", class_define);
engine.register_func("kbd/call", call);
engine.register_func("kbd/set-user-data", set_user_data);
engine.register_func("kbd/get-user-data", get_user_data);
engine.register_func("kbd/make-text", make_text);
engine.register(":round-corners", Value::new_symbol(":round-corners"));
engine.register_func("kbd/make-rect", make_rect);
@ -465,5 +512,6 @@ pub fn init_builtins<E: 'static + Engine>(engine: &mut Keyboard<E>) {
engine.register_func("kbd/width", width);
engine.register_func("kbd/load-font", load_font);
engine.register_func("kbd/set-layout", set_layout);
engine.register_func("kbd/super", super_fn);
engine.gc();
}

View file

@ -104,6 +104,9 @@ impl<E: Engine> Value<E> {
}
pub fn add_car(&mut self, car: Self) {
match self {
Self::Null => {
*self = Self::RevArray(vec![car]);
}
Self::RevArray(ref mut x) => {
x.push(car);
}

View file

@ -1,8 +1,10 @@
(kbd/load-font "/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf")
(kbd/defclass en ()
((init) (kbd/set-user-data 'state 'lower))
((state) (display (or (kbd/get-user-data 'state) 'lower)) (or (kbd/get-user-data 'state) 'lower))
(define state #f)
(kbd/defclass en (latin)
((init) (set! state 'lower))
((state) (or 'state 'lower))
((show-upper) (or (symbol=? (self state) 'upper) (symbol=? (self state) 'capslock)))
((show-lower) (symbol=? (self state) 'lower))
((show-symbols) (symbol=? (self state) 'symbols))
@ -87,5 +89,4 @@
; signals
((kbd/width-changed)
(display "a !!!")
(kbd/set-layout (self kbd-layout))))