diff --git a/README.md b/README.md index 78a44cd..e62b9aa 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/defs.scm b/defs.scm index 4c7a2ce..6eb2f50 100644 --- a/defs.scm +++ b/defs.scm @@ -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 () diff --git a/src/keyboard.rs b/src/keyboard.rs index 16c4e59..45c16a8 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -24,10 +24,25 @@ pub struct PopupInfo<'b> { font_db: &'b mut FontDb, } +#[derive(Clone, Debug)] +struct Method { + func: Option, + super_funcs: Vec, +} + +impl Default for Method { + fn default() -> Self { + Self { + func: None, + super_funcs: Vec::new(), + } + } +} + #[derive(Clone, Debug)] pub struct Class { pub supers: Vec, - pub methods: HashMap, + pub methods: HashMap>, } impl Default for Class { @@ -219,7 +234,7 @@ impl Keyboard { .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(); } diff --git a/src/keyboard/script.rs b/src/keyboard/script.rs index 9b2d337..eb4cc89 100644 --- a/src/keyboard/script.rs +++ b/src/keyboard/script.rs @@ -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( }; 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( .collect::, _>>()?; 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( + func: &mut E::Func, + engine: &mut E, + args: Vec>, +) -> Result, E::Error> { + func.call(engine, args) +} + fn call( vm: &mut Keyboard, mut args: Args>, @@ -106,12 +133,27 @@ fn call( 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( &format!("{name:?}"), )); }; - fn call_func( - func: &mut E::Func, - engine: &mut E, - args: Vec>, - ) -> Result, 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( - vm: &mut Keyboard, +fn super_fn( + _vm: &mut Keyboard, args: Args>, ) -> Result>, 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( - vm: &mut Keyboard, - args: Args>, -) -> Result>, 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(name: &str, a: Value, b: Value) -> Result { @@ -456,8 +505,6 @@ pub fn init_builtins(engine: &mut Keyboard) { 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(engine: &mut Keyboard) { 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(); } diff --git a/src/script.rs b/src/script.rs index e95cc3a..5e6e545 100644 --- a/src/script.rs +++ b/src/script.rs @@ -104,6 +104,9 @@ impl Value { } 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); } diff --git a/tmp.scm b/tmp.scm index 5ea247f..c82e4a9 100644 --- a/tmp.scm +++ b/tmp.scm @@ -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))))