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) 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 (kbd/load-font font-path) #f)
(define-syntax self (syntax-rules () ((self func arg ...) (kbd/call kbd/this 'func arg ...)))) (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 (define-syntax kbd/defmember
(syntax-rules () (syntax-rules ()

View file

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

View file

@ -1,5 +1,8 @@
use std::collections::HashMap;
use crate::{ use crate::{
graphics, image, graphics, image,
keyboard::Method,
script::{Args, Engine, ErrorTrait, Func, Value}, script::{Args, Engine, ErrorTrait, Func, Value},
}; };
@ -35,11 +38,14 @@ fn register<E: Engine>(
}; };
func.add_ref(vm); func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}"); println!("register {cls:?} {name:?} {func:?}");
vm.classes let meth = vm
.classes
.entry(cls.into_owned()) .entry(cls.into_owned())
.or_default() .or_default()
.methods .methods
.insert(name.into_owned(), func); .entry(name.into_owned())
.or_default();
meth.func = Some(func);
println!("done"); println!("done");
Ok(Value::Null) Ok(Value::Null)
@ -86,15 +92,36 @@ fn class_define<E: Engine>(
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {supers:?}"); println!("register {cls:?} {supers:?}");
vm.classes let mut meths = HashMap::<_, Method<_>>::new();
.entry(cls.into_owned()) // 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() .or_default()
.supers .super_funcs
.extend(supers); .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"); println!("done");
Ok(Value::Null) 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>( fn call<E: Engine>(
vm: &mut Keyboard<E>, vm: &mut Keyboard<E>,
mut args: Args<Keyboard<E>>, mut args: Args<Keyboard<E>>,
@ -106,12 +133,27 @@ fn call<E: Engine>(
let name = args let name = args
.pop() .pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?; .ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let Value::Symbol(cls) = cls else { 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( return Err(ErrorTrait::invalid_argt(
"kbd/call", "kbd/call",
"symbol", "symbol",
&format!("{cls:?}"), &format!("{cls:?}"),
)); ));
}
},
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
}
}; };
let Value::Symbol(name) = name else { let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt( return Err(ErrorTrait::invalid_argt(
@ -120,72 +162,79 @@ fn call<E: Engine>(
&format!("{name:?}"), &format!("{name:?}"),
)); ));
}; };
fn call_func<E: Engine>( let is_super = matches!(func_idx, Some((x, _)) if x == &name);
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
let func = vm let func = vm
.classes .classes
.get_mut(cls.as_ref()) .get_mut(cls_s.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned()); .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 { if let Some(mut func) = func {
args.push(Value::Symbol(cls)); args.push(cls);
args.reverse(); args.reverse();
call_func(&mut func, vm, args) call_func(&mut func, vm, args)
} else { } else {
Err(ErrorTrait::from_str( Err(ErrorTrait::from_str(
"kbd/call", "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>( fn super_fn<E: Engine>(
vm: &mut Keyboard<E>, _vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>, args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> { ) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else { let Ok::<[Value<_>; 2], _>([cls, name]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-user-data")); return Err(ErrorTrait::invalid_argc("kbd/super"));
}; };
let Value::Symbol(k) = k else { let (cls, func_idx) = match cls {
return Err(ErrorTrait::invalid_argt( Value::Symbol(cls) => (cls, None),
"kbd/set-user-data", Value::RevArray(cls1) => match <[_; 3]>::try_from(cls1) {
"symbol", Ok([Value::Int(idx), Value::Symbol(super_name), Value::Symbol(cls)]) => {
&format!("{k:?}"), (cls, Some((super_name, idx)))
));
};
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) return Err(ErrorTrait::from_str("kbd/super", "invalid class"));
} }
},
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( return Err(ErrorTrait::invalid_argt(
"kbd/get-user-data", "kbd/super",
"symbol", "symbol",
&format!("{k:?}"), &format!("{cls:?}"),
));
}
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/super",
"symbol",
&format!("{name:?}"),
)); ));
}; };
println!("getting user data {k:?}"); Ok(match func_idx {
Ok(vm Some((func, idx)) if func == name => Value::RevArray(vec![
.user_data Value::Int(idx + 1),
.get(k.as_ref()) Value::Symbol(func),
.cloned() Value::Symbol(cls),
.unwrap_or(Value::Bool(false))) ]),
_ => 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> { 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/register", register);
engine.register_func("kbd/class-define", class_define); engine.register_func("kbd/class-define", class_define);
engine.register_func("kbd/call", call); 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_func("kbd/make-text", make_text);
engine.register(":round-corners", Value::new_symbol(":round-corners")); engine.register(":round-corners", Value::new_symbol(":round-corners"));
engine.register_func("kbd/make-rect", make_rect); 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/width", width);
engine.register_func("kbd/load-font", load_font); engine.register_func("kbd/load-font", load_font);
engine.register_func("kbd/set-layout", set_layout); engine.register_func("kbd/set-layout", set_layout);
engine.register_func("kbd/super", super_fn);
engine.gc(); engine.gc();
} }

View file

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

View file

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