hboard/src/keyboard/script.rs

518 lines
16 KiB
Rust

use std::collections::HashMap;
use crate::{
graphics, image,
keyboard::Method,
script::{Args, Engine, ErrorTrait, Func, Value},
};
use super::Keyboard;
fn register<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 3], _>([cls, name, func]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/register"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{name:?}"),
));
};
let Value::Func(mut func) = func else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"function",
&format!("{func:?}"),
));
};
func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}");
let meth = vm
.classes
.entry(cls.into_owned())
.or_default()
.methods
.entry(name.into_owned())
.or_default();
meth.func = Some(func);
println!("done");
Ok(Value::Null)
}
fn class_define<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 2], _>([cls, supers]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/class-define"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"symbol",
&format!("{cls:?}"),
));
};
let supers = match supers {
Value::Null => vec![],
Value::RevArray(supers) => supers,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list",
&format!("{supers:?}"),
));
}
};
let supers = supers
.into_iter()
.rev()
.map(|sup| {
let Value::Symbol(sup) = sup else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list of symbols",
&format!("list containing {sup:?}"),
));
};
Ok(sup.into_owned())
})
.collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {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>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
args.reverse();
let cls = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let name = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
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(
"kbd/call",
"symbol",
&format!("{name:?}"),
));
};
let is_super = matches!(func_idx, Some((x, _)) if x == &name);
let func = vm
.classes
.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(cls);
args.reverse();
call_func(&mut func, vm, args)
} else {
Err(ErrorTrait::from_str(
"kbd/call",
&if is_super {
format!("super for {cls_s}/{name} not found")
} else {
format!("{cls_s}/{name} is not defined")
},
))
}
}
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], _>([cls, name]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/super"));
};
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/super",
"symbol",
&format!("{name:?}"),
));
};
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> {
Ok(match (a, b) {
(Value::Int(a), Value::Int(b)) => a.saturating_add(b),
(Value::Float(a), Value::Float(b)) => (a + b) as i64,
(Value::Int(a), Value::Float(b)) => (a as f64 + b) as i64,
(Value::Float(a), Value::Int(b)) => (a + b as f64) as i64,
x => {
return Err(E::Error::invalid_argt(
name,
"int or float",
&format!("{:?}", x.1),
))
}
})
}
fn to_i64<E: Engine>(name: &str, x: Value<E>) -> Result<i64, E::Error> {
add_to_i64(name, x, Value::Int(0))
}
fn make_text<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 5], _>([xy, wh, text, font, font_size]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-text"));
};
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{wh:?}"),
));
};
let Value::String(text) = text else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{text:?}"),
));
};
let Value::String(font) = font else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font:?}"),
));
};
let font_size = match font_size {
Value::Int(x) => x as f64,
Value::Float(x) => x,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font_size:?}"),
))
}
};
let Some(font_handle) = vm.font_db.font_handle(&font) else {
return Err(ErrorTrait::from_str("kbd/make-text", "font not found"));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-text", x.clone())?;
let y1 = to_i64("kbd/make-text", y.clone())?;
let x_w = add_to_i64("kbd/make-text", x, w)?;
let y_h = add_to_i64("kbd/make-text", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let halign = graphics::Alignment::Middle;
let valign = graphics::Alignment::Middle;
let direction = swash::shape::Direction::LeftToRight;
let script = swash::text::Script::Latin;
let color = image::ColorRgba([255, 0, 0, 255]);
// [xy, wh, text, font, font_size]
// println!("rendering text {text} of size {w}/{h} at {x}/{y} with font {font} {font_size}, color {color:?}, alignment {halign:?}/{valign:?}, script {script:?}, direction {direction:?}");
let text = graphics::WidgetEnum::Text(graphics::Text {
direction,
font_size: font_size as f32,
font_handle,
color,
halign,
valign,
script,
text: text.into(),
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
height: h as u32,
width: w as u32,
},
});
Value::from_serde(&text).map_err(|err| ErrorTrait::from_str("kbd/make-text", &err.to_string()))
}
fn make_rect<E: Engine>(
_vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let mut args = args.into_iter();
let Some(xy) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let Some(wh) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let mut round = 0.0f64;
while let Some(arg) = args.next() {
match arg {
Value::Symbol(x) if x == ":round-corners" => {
round = 1.0;
if let Some(arg) = args.next() {
let Value::Float(arg) = arg else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"float",
&format!("{arg:?}"),
));
};
round = arg;
}
}
x => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"make-rect keyword arguments",
&format!("{x:?}"),
))
}
}
}
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{wh:?}"),
));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-rect", x.clone())?;
let y1 = to_i64("kbd/make-rect", y.clone())?;
let x_w = add_to_i64("kbd/make-rect", x, w)?;
let y_h = add_to_i64("kbd/make-rect", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let color = image::ColorRgba([0, 0, 0, 255]);
// [xy, wh, text, font, font_size]
println!("rendering rect of size {w}/{h} at {x}/{y} with rounding {round}, color {color:?}");
let rect = graphics::WidgetEnum::Shape(graphics::SimpleShape {
col: color,
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
width: w as u32,
height: h as u32,
},
round_corners_ratio: round as f32,
});
Value::from_serde(&rect).map_err(|err| ErrorTrait::from_str("kbd/make-rect", &err.to_string()))
}
fn make_layout<E: Engine>(
_vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([items]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-layout"));
};
let Value::RevArray(items) = items else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list",
&format!("{items:?}"),
));
};
let mut layout = graphics::Layout::new();
for item in items.into_iter().rev() {
let Value::RevArray(mut item) = item else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list of lists",
&format!("list with {item:?}"),
));
};
let Some(Value::String(a)) = item.pop() else {
return Err(ErrorTrait::from_str(
"kbd/make-layout",
"each item's first element must be the item id",
));
};
let item = Value::RevArray(item)
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))?;
// println!("{item:#?}");
layout.add_child(item, &a, None);
}
Value::from_serde(&layout)
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))
}
fn width<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 0], _>([]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/width"));
};
println!("getting keyboard width");
Ok(Value::Int(vm.width.into()))
}
fn load_font<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([path]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/load-font"));
};
let Value::String(path) = path else {
return Err(ErrorTrait::invalid_argt(
"kbd/load-font",
"string",
&format!("{path:?}"),
));
};
println!("loading font {path:?}");
vm.font_db
.load_font(path)
.map(|()| Value::Null)
.map_err(|err| ErrorTrait::from_str("kbd/load-font", &err.to_string()))
}
fn set_layout<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([layout]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-layout"));
};
let layout: graphics::Layout = layout
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/set-layout", &err.to_string()))?;
vm.layout = Some(layout);
Ok(Value::Null)
}
pub fn init_builtins<E: 'static + Engine>(engine: &mut Keyboard<E>) {
const DEFS: &str = include_str!("../../defs.scm");
engine.eval_t(DEFS).unwrap();
engine.gc();
engine.register_func("kbd/register", register);
engine.register_func("kbd/class-define", class_define);
engine.register_func("kbd/call", call);
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);
engine.register_func("kbd/make-layout", make_layout);
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();
}