diff --git a/README.md b/README.md index 24010e3..102387a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Zoom Quilt Machine -A [game-engine](https://en.wikipedia.org/wiki/Game_engine)-like system +A [game-engine](https://en.wikipedia.org/wiki/Game_engine)-like system written in [Rust](https://www.rust-lang.org/), for the [Internet Computer](https://dfinity.org/faq). @@ -21,12 +21,43 @@ Initially, we focus on [PoC](https://en.wikipedia.org/wiki/Proof_of_concept) con - [ ] HTTP-client support: Connect to an IC canister holding saved media; send/receive media data to/from that canister. -### See also: Existing Zoom Quilts: +### See also: + +This project draws inspiration from many sources. + +#### Computer graphics standards (2D) + +At its core, ZQM is an experimental computer graphics project +that draws inspiration from existing standards for 2D computer graphics. + +For instance, the [SVG standard](https://www.w3.org/TR/SVGTiny12/) shares some goals and has some +comparable concepts and parts. + +As a shared goal, both ZQM and SVG offer a human-computer language +for authoring portable 2D graphics with interaction (scripting). + +##### SVG standard details: + +- [Scalable Vector Graphics (SVG) Tiny 1.2 Specification](https://www.w3.org/TR/SVGTiny12/) + - [Basic data types](https://www.w3.org/TR/SVGTiny12/types.html) + - [DOM IDL, for interactive scripts](https://www.w3.org/TR/SVGTiny12/svgudomidl.html) + +ZQM aspires to produce SVG media artifacts from its own media. + +#### Media/content editors/IDEs + + - https://p5stamper.com/ + + +#### Existing "Zoom Quilts" (art projects): + +The name _"Zoom Quilt Machine"_ was inspired by the experiences +of watching these art projects, +and imagining an answer to the question: +_"What tools could express the authorship of these quilts, as living data structures?"_: - https://www.zoomquilt.org/ - https://zoomquilt2.com/ - https://arkadia.xyz/ - https://www.adultswim.com/etcetera/zoom/ -#### Media/content editors/IDEs - - https://p5stamper.com/ diff --git a/engine/Cargo.toml b/engine/Cargo.toml index c382c5e..b75c900 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -13,7 +13,14 @@ serde_bytes = "0.11" serde_cbor = "0.9" serde_json = "1.0" hashcons = "0.1" +tokio = "0.2.10" #sdl2 = "0.32" +delay = "0.1.0" + +serde-idl = { path = "/Users/matthew/dfn/sdk/src/serde_idl" } +dfx_derive = { path = "/Users/matthew/dfn/sdk/src/dfx_derive" } +dfx_info = { path = "/Users/matthew/dfn/sdk/src/dfx_info" } +ic-agent = { path = "/Users/matthew/dfn/sdk/src/agent/rust" } [lib] name = "zqm_engine" diff --git a/engine/src/lib/bitmap.rs b/engine/src/lib/bitmap.rs index 2ae93ec..bb58da2 100644 --- a/engine/src/lib/bitmap.rs +++ b/engine/src/lib/bitmap.rs @@ -48,7 +48,7 @@ pub enum AutoCommand { // Define a canonical editor for the structure in question. Again, use simplified, affine Rust. /// the history-_independent_ state of the editor -#[derive(Debug, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct EditorState { /// created by an Init command; affected by Auto and Edit commands pub bitmap: Bitmap, @@ -62,7 +62,7 @@ pub struct EditorState { // includes the command history, and any "pre-states" before initialization completes. /// the history-_dependent_ state of the editoro -#[derive(Debug, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct Editor { /// full linear history of this bitmap's evolution, as a sequence of commands pub history: Vec<Command>, diff --git a/engine/src/lib/candid.rs b/engine/src/lib/candid.rs new file mode 100644 index 0000000..7929b19 --- /dev/null +++ b/engine/src/lib/candid.rs @@ -0,0 +1,955 @@ +use serde::{Deserialize, Serialize}; +extern crate serde_idl; + +use menu; +use menu::MenuType; +use render; +use types::lang::{Atom, Name}; + +use ic_agent::{Agent, AgentConfig, Blob, CanisterId}; +use serde_idl::grammar::IDLProgParser; +use serde_idl::lexer::Lexer; +use serde_idl::{ + types::{Dec, IDLProg, IDLType, Label, PrimType}, + value::IDLArgs, + value::IDLField, + value::IDLValue, +}; + + +use std::collections::HashMap; +pub type Env = HashMap<String, MenuType>; + +pub fn parse_idl(input: &str) -> IDLProg { + let lexer = Lexer::new(input); + IDLProgParser::new().parse(lexer).unwrap() +} + +fn name_of_idllabel(l: &Label) -> Name { + match l { + Label::Id(n) => Name::Atom(Atom::Usize(*n as usize)), + Label::Named(n) => Name::Atom(Atom::String(n.clone())), + Label::Unnamed(_) => Name::Void, + } +} + +fn menutype_of_idltype(env: &Env, t: &IDLType) -> MenuType { + match t { + IDLType::RecordT(fields) => { + let mut out = vec![]; + for field in fields.iter() { + let n = name_of_idllabel(&field.label); + let t = menutype_of_idltype(env, &field.typ); + out.push((n, t)) + } + MenuType::Product(out) + } + IDLType::VariantT(fields) => { + let mut out = vec![]; + for field in fields.iter() { + let n = name_of_idllabel(&field.label); + let t = menutype_of_idltype(env, &field.typ); + out.push((n, t)) + } + MenuType::Variant(out) + } + IDLType::OptT(t) => { + let mt = menutype_of_idltype(env, t); + MenuType::Option(Box::new(mt)) + } + IDLType::VarT(v) => menutype_resolve_var(env, v, 0), + IDLType::VecT(t) => { + let t = menutype_of_idltype(env, &*t); + MenuType::Vec(Box::new(t)) + } + IDLType::FuncT(ft) => { + let args = ft + .args + .iter() + .map(|t| menutype_of_idltype(env, t)) + .collect(); + let rets = ft + .rets + .iter() + .map(|t| menutype_of_idltype(env, t)) + .collect(); + MenuType::Func(menu::FuncType { args, rets }) + } + IDLType::PrimT(PrimType::Nat) => MenuType::Prim(menu::PrimType::Nat), + IDLType::PrimT(PrimType::Int) => MenuType::Prim(menu::PrimType::Int), + IDLType::PrimT(PrimType::Nat8) => MenuType::Prim(menu::PrimType::Nat), + IDLType::PrimT(PrimType::Text) => MenuType::Prim(menu::PrimType::Text), + IDLType::PrimT(PrimType::Bool) => MenuType::Prim(menu::PrimType::Bool), + IDLType::PrimT(PrimType::Null) => MenuType::Prim(menu::PrimType::Null), + IDLType::ServT(_) => { + // To do (!): + MenuType::Prim(menu::PrimType::Null) + } + _ => unimplemented!("{:?}", t), + } +} + +pub fn string_of_field_id(id: u32) -> Option<String> { + match id { + 24860 => Some("ok".to_string()), + 1269255460 => Some("rect".to_string()), + 4996424 => Some("dim".to_string()), + 5594516 => Some("pos".to_string()), + 38537191 => Some("height".to_string()), + 3395466758 => Some("width".to_string()), + 120 => Some("x".to_string()), + 121 => Some("y".to_string()), + 240232876 => Some("closed".to_string()), + _ => None, + } +} + +pub fn get_nat(v: &IDLValue) -> Option<usize> { + match v { + IDLValue::Nat(n) => Some(*n as usize), + _ => { + error!("expected nat: {:?}", v); + None + } + } +} + +pub fn get_text(v: &IDLValue) -> Option<String> { + match v { + IDLValue::Text(t) => Some(t.clone()), + _ => { + error!("expected text: {:?}", v); + None + } + } +} + +pub fn get_pos(pos: &IDLValue) -> Option<render::Pos> { + match pos { + IDLValue::Record(fields) => match (get_nat(&fields[0].val), get_nat(&fields[1].val)) { + (Some(x), Some(y)) => { + let x = x as isize; + let y = y as isize; + Some(render::Pos { x, y }) + } + _ => None, + }, + _ => None, + } +} + +pub fn get_dim(dim: &IDLValue) -> Option<render::Dim> { + match dim { + IDLValue::Record(fields) => match (get_nat(&fields[1].val), get_nat(&fields[0].val)) { + (Some(width), Some(height)) => Some(render::Dim { width, height }), + _ => None, + }, + _ => None, + } +} + +pub fn get_rect(rect: &IDLValue) -> Option<render::Rect> { + match rect { + IDLValue::Record(fields) => { + if fields.len() != 2 { + return None; + }; + match (get_pos(&fields[1].val), get_dim(&fields[0].val)) { + (Some(pos), Some(dim)) => Some(render::Rect { pos, dim }), + _ => None, + } + } + _ => None, + } +} + +pub fn get_color(color: &IDLValue) -> Option<render::Color> { + match color { + IDLValue::Record(fields) => { + if fields.len() != 3 { + return None; + }; + match ( + get_nat(&fields[0].val), + get_nat(&fields[1].val), + get_nat(&fields[2].val), + ) { + (Some(r), Some(g), Some(b)) => Some(render::Color::RGB(r, g, b)), + _ => None, + } + } + _ => None, + } +} + +pub fn get_color_nat(color_nat: &IDLValue) -> Option<(render::Color, usize)> { + match color_nat { + IDLValue::Record(fields) => { + if fields.len() != 2 { + return None; + }; + match (get_color(&fields[0].val), get_nat(&fields[1].val)) { + (Some(c), Some(n)) => Some((c, n)), + _ => None, + } + } + _ => None, + } +} + +pub fn get_fill_(f: &IDLField) -> Option<render::Fill> { + match f.id { + // #closed : Color + 240232876 => match get_color(&f.val) { + Some(c) => Some(Fill::Closed(c)), + None => None, + }, + // #open : (Color, Nat) + 1236534218 => match get_color_nat(&f.val) { + Some((c, n)) => Some(Fill::Open(c, n)), + None => None, + }, + // #none + 1225396920 => Some(Fill::None), + _ => { + error!("unrecognized fill tag: {:?}", f); + None + } + } +} + +pub fn get_fill(v: &IDLValue) -> Option<render::Fill> { + match v { + IDLValue::Variant(v) => get_fill_(&*v), + _ => { + error!("unrecognized fill: {:?}", v); + None + } + } +} + +pub fn get_dir2d_(f: &IDLField) -> Option<Dir2D> { + match f.id { + 3915647964 => Some(Dir2D::Right), + _ => { + error!("unrecognized dir2d tag: {:?}", f.id); + None + } + } +} + +pub fn get_dir2d(v: &IDLValue) -> Option<Dir2D> { + match v { + IDLValue::Variant(v) => get_dir2d_(&*v), + _ => { + error!("unrecognized dir2d: {:?}", v); + None + } + } +} + +pub fn get_flow_atts(v: &IDLValue) -> Option<render::FlowAtts> { + match v { + IDLValue::Record(fields) => { + if fields.len() != 3 { + return None; + }; + match ( + get_dir2d(&fields[0].val), + get_nat(&fields[2].val), + get_nat(&fields[1].val), + ) { + (Some(dir), Some(intra_pad), Some(inter_pad)) => Some(FlowAtts { + dir, + intra_pad, + inter_pad, + }), + _ => { + error!("unrecognized flow_atts: {:?}", v); + None + } + } + } + _ => None, + } +} + +pub fn get_text_atts(v: &IDLValue) -> Option<render::TextAtts> { + match v { + IDLValue::Record(fields) => { + if fields.len() != 5 { + return None; + }; + // to do -- fix these indices; + // get indicies from field names somehow. + match ( + get_nat(&fields[1].val), + get_fill(&fields[0].val), + get_fill(&fields[3].val), + get_dim(&fields[4].val), + get_flow_atts(&fields[2].val), + ) { + (Some(zoom), Some(fg_fill), Some(bg_fill), Some(glyph_dim), Some(glyph_flow)) => { + Some(TextAtts { + zoom, + fg_fill, + bg_fill, + glyph_dim, + glyph_flow, + }) + } + _ => { + error!("could not recognize text_atts {:?}", v); + None + } + } + } + _ => { + error!("could not recognize text_atts {:?}", v); + None + } + } +} + +pub fn get_render_text(elm: &IDLValue) -> Option<render::Elm> { + match elm { + IDLValue::Record(fields) => { + if fields.len() != 2 { + return None; + }; + // to do -- decompose these text atts and use them! + match (get_text(&fields[0].val), get_text_atts(&fields[1].val)) { + (Some(t), Some(ta)) => { + let mut r = Render::new(); + r.text(&t, &ta); + Some(r.into_elms()[0].clone()) + } + _ => None, + } + } + _ => None, + } +} + +pub fn get_render_rect_fill(elm: &IDLValue) -> Option<render::Elm> { + match elm { + IDLValue::Record(fields) => { + if fields.len() != 2 { + return None; + }; + match (get_rect(&fields[0].val), get_fill(&fields[1].val)) { + (Some(r), Some(f)) => Some(render::Elm::Rect(r, f)), + _ => None, + } + } + _ => None, + } +} + +pub fn get_render_node(elm: &IDLValue) -> Option<render::Elm> { + match elm { + IDLValue::Record(fields) => { + if fields.len() != 3 { + return None; + }; + let rect = get_rect(&fields[2].val); + let fill = get_fill(&fields[1].val); + // to do -- decompose these text atts and use them! + match &fields[0].val { + IDLValue::Vec(vals) => match (rect, fill, get_render_elms(&vals)) { + (None, _, _) => { + warn!("failed to parse rect of node elm"); + None + } + (_, None, _) => { + warn!("failed to parse fill of node elm"); + None + } + (Some(rect), Some(fill), Some(node_elms)) => { + trace!("node with elements: {:?}", node_elms); + Some(render::Elm::Node(Box::new(render::Node { + name: Name::Void, + children: node_elms, + rect: rect, + fill: fill, + }))) + } + _ => None, + }, + _ => None, + } + } + _ => None, + } +} + +/* +Variant( + // node + IDLField { id: 1225394690, val: + Record( + [ + // elms + IDLField { + id: 1125441421, val: + // elms vector + Vec( + [ + // #rect + Variant(IDLField { id: 1269255460, + // (rect, fill) + val: Record([ + // 0: rect + IDLField { id: 0, val: Record([ + IDLField { id: 4996424, val: Record([ + IDLField { id: 38537191, val: Nat(10) }, + IDLField { id: 3395466758, val: Nat(10) }]) }, + IDLField { id: 5594516, val: Record([ + IDLField { id: 120, val: Nat(2) }, + IDLField { id: 121, val: Nat(2) }]) }]) }, + // 1: fill + IDLField { id: 1, val: Variant( + IDLField { id: 240232876, val: Record([ + IDLField { id: 0, val: Nat(0) }, + IDLField { id: 1, val: Nat(0) }, + IDLField { id: 2, val: Nat(0) }]) }) } + ]) + }) + ] + ) + }, + // fill + IDLField { id: 1136381571, val: Variant(IDLField { id: 1225396920, val: Null }) }, + // rect + IDLField { id: 1269255460, val: + Record([ + IDLField { id: 4996424, val: Record([ + IDLField { id: 38537191, val: Nat(0) }, + IDLField { id: 3395466758, val: Nat(0) } + ]) + }, + IDLField { id: 5594516, val: Record([ + IDLField { id: 120, val: Nat(2) }, + IDLField { id: 121, val: Nat(2) }]) }]) + } + ] + ) + }) +*/ + +pub fn get_render_elm_(elm: &IDLField) -> Option<render::Elm> { + match elm.id { + // #node : Node + 1225394690 => get_render_node(&elm.val), + // #text : (String, TextAtts) + 1291439277 => get_render_text(&elm.val), + // #rect : (Rect, Fill) + 1269255460 => get_render_rect_fill(&elm.val), + tag => { + info!( + "warning: recognized element tag {}, for element {:?}", + tag, elm + ); + None + } + } +} + +pub fn get_render_elm(v: &IDLValue) -> Option<render::Elm> { + // extra debugger messages here: + match v { + IDLValue::Variant(f) => match get_render_elm_(f) { + None => { + warn!("failed to parse element {:?}", v); + return None; + } + Some(elm) => Some(elm), + }, + _ => { + warn!("unrecognized element {:?}", v); + return None; + } + } +} + +pub fn get_render_elms(vals: &Vec<IDLValue>) -> Option<render::Elms> { + let mut out: render::Elms = vec![]; + for v in vals.iter() { + match get_render_elm(v) { + Some(elm) => out.push(elm), + None => return None, + } + } + Some(out) +} + +pub fn get_render_named_elms_(vals: &Vec<IDLValue>) -> Option<render::NamedElms> { + let mut out: render::NamedElms = vec![]; + for v in vals.iter() { + match v { + IDLValue::Record(fields) => { + if fields.len() != 2 { + return None; + }; + match (get_text(&fields[0].val), get_render_elm(&fields[1].val)) { + (Some(name), Some(elm)) => out.push((Name::Atom(Atom::String(name)), elm)), + _ => return None, + }; + } + _ => return None, + } + } + Some(out) +} + +pub fn get_render_named_elms(vals: &IDLValue) -> Option<render::NamedElms> { + match vals { + IDLValue::Vec(vals) => get_render_named_elms_(vals), + _ => None, + } +} + +pub fn get_render_out(v: &IDLValue) -> Option<render::Out> { + match v { + IDLValue::Variant(field) => match field.id { + // #draw + 1114647556 => match get_render_elm(&field.val) { + Some(elm) => Some(render::Out::Draw(elm)), + None => None, + }, + // #redraw + 4271367479 => match get_render_named_elms(&field.val) { + Some(named_elms) => Some(render::Out::Redraw(named_elms)), + None => None, + }, + // #renderStreams -- to do + // otherwise + id => { + warn!("unexpected render_out id {:?}", id); + None + } + }, + _ => None, + } +} + +pub fn get_result_render_out(v: &IDLValue) -> Option<render::Out> { + // todo: add rendering for Ok/Err themselves + match v { + IDLValue::Variant(f) => match f.id { + // #ok + 24860 => get_render_out(&f.val), + // #err + 5048165 => get_render_out(&f.val), + _ => { + warn!("unexpected field {:?}", f); + None + } + }, + _ => None, + } +} + +pub fn find_render_out(vs: &Vec<IDLValue>) -> Option<render::Out> { + let mut outs: Vec<render::Out> = vec![]; + for v in vs.iter() { + match get_result_render_out(v) { + None => {} + Some(o) => outs.push(o), + } + } + if outs.len() == 0 { + None + } else if outs.len() == 1 { + outs.pop() + } else { + error!("unexpected: multiple rendering outputs; using the last"); + outs.pop() + } +} + +pub fn idlargs_of_menutree(mt: &menu::MenuTree) -> String { + format!("{}", mt) +} + +pub fn blob_of_menutree(mt: &menu::MenuTree) -> Blob { + let args: String = idlargs_of_menutree(mt); + let args: IDLArgs = args.parse().unwrap(); + Blob(args.to_bytes().unwrap()) +} + +pub fn menutype_resolve_var(env: &Env, v: &String, depth: usize) -> MenuType { + // to do -- what is the real threshold in the def again? + if depth > 100 { + MenuType::Var(Name::Atom(Atom::String(v.clone()))) + } else { + match env.get(v) { + None => MenuType::Var(Name::Atom(Atom::String(v.clone()))), + Some(MenuType::Var(Name::Atom(Atom::String(v)))) => { + menutype_resolve_var(env, v, depth + 1) + } + Some(MenuType::Var(_)) => unreachable!(), + Some(t) => t.clone(), + } + } +} + +pub fn tuple_of_product(t: &menu::MenuTree) -> menu::MenuTree { + // to do + t.clone() +} + +pub fn menutype_of_idlprog(p: &IDLProg) -> menu::MenuType { + let mut emp = HashMap::new(); + let mut env = HashMap::new(); + for dec in p.decs.iter() { + match dec { + Dec::TypD(ref b) => { + let t = menutype_of_idltype(&emp, &b.typ); + //print!("{:?}", b); + drop(env.insert(b.id.clone(), t)) + } + _ => unimplemented!(), + } + } + match p.actor { + Some(IDLType::ServT(ref methods)) => { + let mut choices = vec![]; + for method in methods.iter() { + //eprint!("method {:?} : {:?}", method.id, method.typ); + let i = method.id.clone(); + let t = match method.typ { + IDLType::FuncT(ref ft) => { + if ft.args.len() > 0 { + let arg_types: Vec<MenuType> = ft + .args + .iter() + .map(|a| menutype_of_idltype(&env, a)) + .collect(); + let mut fields = vec![]; + for i in 0..arg_types.len() { + fields.push(arg_types[i].clone()) + } + MenuType::Tup(fields) + } else { + MenuType::Prim(menu::PrimType::Unit) + } + } + _ => unreachable!(), + }; + choices.push((Name::Atom(Atom::String(i)), t)); + } + MenuType::Variant(choices) + } + _ => panic!("expected a service type"), + } +} + +pub fn agent(url: &str) -> Result<Agent, ic_agent::AgentError> { + Agent::new(AgentConfig { + url: format!("http://{}", url).as_str(), + ..AgentConfig::default() + }) +} + +use std::time::{Duration, SystemTime}; + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub struct Call { + pub method: String, + pub args: menu::MenuTree, + pub args_idl: String, + pub rets_idl: Result<String, String>, + pub render_out: Option<render::Out>, + pub timestamp: SystemTime, + pub duration: Duration, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub struct Repl { + pub config: Config, + pub display: Vec<(Name, render::Elm)>, + pub history: Vec<Call>, + // todo: log of results, parsed into MenuTree's according to the MenuType of the result type +} + +impl Repl { + pub fn update_display(&mut self, out: &Option<render::Out>) { + match out { + None => (), // nothing + Some(render::Out::Draw(named_elms)) => { + // nothing + } + Some(render::Out::Redraw(named_elms)) => { + let mut cells = std::collections::HashMap::new(); + for (name, elm) in self.display.iter() { + cells.insert(name, elm); + } + for (name, elm) in named_elms.iter() { + cells.insert(name, elm); + } + let mut display: render::NamedElms = vec![]; + for (name, elm) in cells.drain().take(1) { + display.push((name.clone(), elm.clone())) + } + self.display = display; + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub struct Config { + //idl_prog: IDLProg, + pub replica_url: String, + pub canister_id: String, + pub menu_type: MenuType, +} + +use types::lang::{Command, Editor, Frame, State}; +pub fn init(url: &str, cid_text: &str, p: &IDLProg) -> Result<State, String> { + use eval; + let cid: CanisterId = CanisterId::from_text(cid_text).unwrap(); + assert_eq!(cid.to_text(), cid_text); + let mt: menu::MenuType = menutype_of_idlprog(p); + let mut st = State { + stack: vec![Frame::from_editor(Editor::CandidRepl(Box::new(Repl { + history: vec![], + display: vec![], + config: Config { + //idl_prog: p.clone(), + menu_type: mt.clone(), + replica_url: url.to_string(), + canister_id: cid.to_string(), + }, + })))], + frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor { + state: None, + history: vec![], + }))), + }; + let cmd = Command::Menu(menu::Command::Init(menu::InitCommand::Default( + menu::MenuTree::Blank(mt.clone()), + mt, + ))); + eval::command_eval(&mut st, None, &cmd)?; + Ok(st) +} + +use crate::render::{FlowAtts, FrameType, Render, TextAtts}; +use types::{ + lang::Dir2D, + render::{Color, Dim, Elms, Fill}, +}; + +fn text_zoom() -> usize { + 2 +} + +fn horz_flow() -> FlowAtts { + FlowAtts { + dir: Dir2D::Right, + intra_pad: 2, + inter_pad: 2, + } +} + +fn vert_flow() -> FlowAtts { + FlowAtts { + dir: Dir2D::Down, + intra_pad: 2, + inter_pad: 2, + } +} + +fn glyph_padding() -> usize { + 1 +} + +// eventaually we get these atts from +// some environment-determined settings +fn glyph_flow() -> FlowAtts { + FlowAtts { + dir: Dir2D::Right, + intra_pad: glyph_padding(), + inter_pad: glyph_padding(), + } +} + +fn glyph_dim() -> Dim { + Dim { + width: 5, + height: 5, + } +} + +fn kw_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(255, 230, 255)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn dim_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(150, 100, 150)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn dim2_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(200, 180, 200)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn data_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(230, 230, 230)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn err_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(255, 100, 100)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn msg_atts() -> TextAtts { + TextAtts { + zoom: text_zoom(), + fg_fill: Fill::Closed(Color::RGB(200, 200, 255)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn big_msg_atts() -> TextAtts { + TextAtts { + zoom: text_zoom() * 2, + fg_fill: Fill::Closed(Color::RGB(220, 220, 255)), + bg_fill: Fill::Closed(Color::RGB(50, 50, 50)), + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } +} + +fn box_fill() -> Fill { + Fill::Open(Color::RGB(50, 100, 50), 1) +} + +pub fn render_elms(repl: &Repl, r: &mut Render) { + if repl.display.len() > 0 { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.str("Render cells:", &msg_atts()); + for (name, elm) in repl.display.iter() { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.fill(Fill::Open(Color::RGB(255, 255, 255), 1)); + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.name(name, &data_atts()); + r.str("= ", &data_atts()); + r.end(); + r.elm(elm.clone()); + r.end(); + } + r.end() + } + if repl.history.len() > 0 { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.str("Message log:", &msg_atts()); + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.fill(box_fill()); + for call in repl.history.iter().rev().take(10) { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.str(&call.method, &msg_atts()); + r.text(&call.args_idl, &data_atts()); + r.end(); + match call.rets_idl { + Ok(ref rets_idl) => { + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.text(&format!(" {:?}", &call.duration), &dim_atts()); + r.str("━━►", &dim2_atts()); + // to do -- if len of text overflows, then wrap it + match call.render_out { + None => { + if rets_idl.len() > 80 { + let mut rets_idl = rets_idl.clone(); + rets_idl.truncate(80); + r.text(&rets_idl, &data_atts()); + r.str("...", &data_atts()) + } else { + r.text(rets_idl, &data_atts()) + } + } + Some(ref out) => match out { + render::Out::Draw(elm) => r.elm(elm.clone()), + render::Out::Redraw(named_elms) => { + for (name, elm) in named_elms.iter() { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.fill(Fill::Open(Color::RGB(255, 255, 255), 1)); + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.name(name, &data_atts()); + r.str("= ", &data_atts()); + r.end(); + r.elm(elm.clone()); + r.end(); + } + } + }, + }; + r.end() + } + Err(ref msg) => { + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.text(&format!(" {:?}", &call.duration), &dim_atts()); + r.str("━━► ", &err_atts()); + r.end(); + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.str(&" ", &err_atts()); + // to do -- if len of text overflows, then wrap it + r.text(msg, &err_atts()); + r.end(); + r.end(); + } + } + r.end() + } + r.end(); + r.end() + } +} + +#[test] +fn doit() { + let prog = r#" +service server : { + f : (a: nat, b:nat) -> () oneway; + g : (a: text, b:nat) -> () oneway; + h : (a: nat, b:record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }) -> () oneway; +} + "#; + let ast = parse_idl(&prog); + let menu = menutype_of_idlprog_service(&ast); + drop(menu); +} diff --git a/engine/src/lib/chain.rs b/engine/src/lib/chain.rs index f0150ee..f84f331 100644 --- a/engine/src/lib/chain.rs +++ b/engine/src/lib/chain.rs @@ -131,7 +131,7 @@ pub struct EditorState { pub tail: Chain, } -#[derive(Debug, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct Editor { pub history: Vec<Command>, pub state: Option<EditorState>, diff --git a/engine/src/lib/eval.rs b/engine/src/lib/eval.rs index 98619e2..e1b54a5 100644 --- a/engine/src/lib/eval.rs +++ b/engine/src/lib/eval.rs @@ -1,17 +1,30 @@ // to-do/question: rename this module to 'engine'? use bitmap; +use candid; use menu; +use delay::Delay; +use std::time::Duration; + +const RETRY_PAUSE: Duration = Duration::from_millis(100); +const REQUEST_TIMEOUT: Duration = Duration::from_secs(60); + +pub enum LoadState { + CandidFile { file: String }, + Resume, +} + pub use super::types::{ event::Event, - lang::{Command, Editor, State}, + lang::{Command, Editor, Media, State}, render, }; pub fn commands_of_event(state: &mut State, event: &Event) -> Result<Vec<Command>, ()> { debug!("commands_of_event {:?}", event); - let res = match &mut state.editor { + let res = match &mut state.frame.editor { + &mut Editor::CandidRepl(ref _repl) => unimplemented!(), &mut Editor::Bitmap(ref _ed) => { // to do -- insert a name into each command that is unique, // but whose structure encodes a wallclock timestamp, among other sequence numbers. @@ -45,45 +58,204 @@ pub fn commands_of_event(state: &mut State, event: &Event) -> Result<Vec<Command res } -pub fn command_eval(state: &mut State, command: &Command) -> Result<(), String> { - debug!("command_eval {:?}", command); - let res = match (command, &mut state.editor) { - (&Command::Bitmap(ref bc), &mut Editor::Bitmap(ref mut be)) => { - super::bitmap::semantics::editor_eval(be, bc) - } - (&Command::Bitmap(ref _bc), _) => Err("bitmap editor expected bitmap command".to_string()), - (_, &mut Editor::Bitmap(ref mut _be)) => { - Err("bitmap command for non-bitmap editor".to_string()) +use serde_idl::value::{IDLArgs, IDLField, IDLValue}; + +pub fn command_eval( + state: &mut State, + orig_event: Option<Event>, + command: &Command, +) -> Result<(), String> { + if let &Command::Return(ref media) = command { + debug!("command_eval: return..."); + + if state.stack.len() == 0 { + Err(format!("cannot return; empty stack")) + } else { + let old_top = state.frame.clone(); + state.frame = state.stack.pop().unwrap(); + let resume = Command::Resume(media.clone(), old_top); + command_eval(state, None, &resume) } + } else if let &Command::Resume(Media::MenuTree(ref mt), ref old_frame) = command { + // to do: clean, refactor and move this logic (e.g., into the candid module) + match &**mt { + &menu::MenuTree::Variant(ref ch) => match &ch.choice { + None => { + warn!("no choice selected; try again..."); + } + &Some((ref lab, ref tree, ref typ)) => { + let method = format!("{}", lab); + let str = format!("{}", tree); + info!("Sending message \"{}\", with args {}", method, str); + if let Ok(args) = &str.parse::<IDLArgs>() { + debug!("...as {}({})", method, args); + + if let &mut Editor::CandidRepl(ref mut repl) = &mut state.frame.editor { + use ic_agent::{Blob, CanisterId}; + use std::time::Duration; + use tokio::runtime::Runtime; - (&Command::Menu(ref c), &mut Editor::Menu(ref mut e)) => { - super::menu::semantics::editor_eval(e, c) + info!( + "...to canister_id {:?} at replica_url {:?}", + repl.config.canister_id, repl.config.replica_url + ); + + let mut runtime = Runtime::new().expect("Unable to create a runtime"); + let delay = Delay::builder() + .throttle(RETRY_PAUSE) + .timeout(REQUEST_TIMEOUT) + .build(); + let agent = candid::agent(&repl.config.replica_url).unwrap(); + let canister_id = + CanisterId::from_text(repl.config.canister_id.clone()).unwrap(); + let timestamp = std::time::SystemTime::now(); + let blob_res = runtime.block_on(agent.call_and_wait( + &canister_id, + &method, + &Blob(args.to_bytes().unwrap()), + delay, + )); + + let elapsed = timestamp.elapsed().unwrap(); + if let Ok(blob_res) = blob_res { + let result = + serde_idl::IDLArgs::from_bytes(&(*blob_res.unwrap().0)); + let idl_rets = result.unwrap().args; + let render_out = candid::find_render_out(&idl_rets); + repl.update_display(&render_out); + let res = format!("{:?}", &idl_rets); + let mut res_log = res.clone(); + if res_log.len() > 80 { + res_log.truncate(80); + res_log.push_str("...(truncated)"); + } + info!("..successful result {:?}", res_log); + trace!("..render out {:?}", render_out); + let call = candid::Call { + timestamp: timestamp, + duration: elapsed, + method: method, + args: tree.clone(), + args_idl: str.to_string(), + rets_idl: Ok(res), + render_out, + }; + repl.history.push(call) + } else { + let res = format!("{:?}", blob_res); + info!("..error result {:?}", res); + let call = candid::Call { + timestamp: timestamp, + duration: elapsed, + method: method, + args: tree.clone(), + args_idl: str.to_string(), + rets_idl: Err(res), + render_out: None, + }; + repl.history.push(call) + } + } else { + unreachable!() + } + } else { + error!("Cannot send incomplete message; please fill remaining blanks.") + } + } + }, + _ => unreachable!("broken invariants"), } - (&Command::Menu(ref _c), _) => Err("menu editor expected menu command".to_string()), - (_, &mut Editor::Menu(ref mut _e)) => Err("menu command for non-menu editor".to_string()), + state.stack.push(state.frame.clone()); + state.frame = old_frame.clone(); + Ok(()) + } else { + debug!("command_eval {:?}", command); + match orig_event { + Some(ev) => info!("event {:?} ==> command {:?}", ev, command), + None => info!("unknown event ==> command {:?}", command), + }; + let res = match (command, &mut state.frame.editor) { + (&Command::Bitmap(ref bc), &mut Editor::Bitmap(ref mut be)) => { + super::bitmap::semantics::editor_eval(be, bc) + } + (&Command::Bitmap(ref _bc), _) => { + Err("bitmap editor expected bitmap command".to_string()) + } + (_, &mut Editor::Bitmap(ref mut _be)) => { + Err("bitmap command for non-bitmap editor".to_string()) + } - (&Command::Chain(ref _ch), _) => unimplemented!(), - (&Command::Grid(ref _gr), _) => unimplemented!(), - }; - debug!("command_eval {:?} ==> {:?}", command, res); - res + (&Command::Menu(ref c), &mut Editor::Menu(ref mut e)) => { + match super::menu::semantics::editor_eval(e, c) { + Ok(()) => Ok(()), + Err(menu::Halt::Commit(mt)) => { + command_eval(state, None, &Command::Return(Media::MenuTree(Box::new(mt)))) + } + Err(menu::Halt::Message(m)) => Err(m), + } + } + (&Command::Menu(ref _c), _) => Err("menu editor expected menu command".to_string()), + (_, &mut Editor::Menu(ref mut _e)) => { + Err("menu command for non-menu editor".to_string()) + } + + (&Command::Chain(ref _ch), _) => unimplemented!(), + (&Command::Grid(ref _gr), _) => unimplemented!(), + (&Command::Return(_), _) => unimplemented!(), + (&Command::Resume(_, _), _) => unimplemented!(), + }; + debug!("command_eval {:?} ==> {:?}", command, res); + res + } } -pub fn render_elms(state: &State) -> Result<render::Elms, String> { - match &state.editor { +use crate::render::{FlowAtts, FrameType, Render, TextAtts}; +use types::lang::{Dir2D, Name}; + +pub fn render_elms_of_editor(editor: &Editor, r: &mut Render) { + match editor { + &Editor::CandidRepl(ref repl) => candid::render_elms(repl, r), &Editor::Bitmap(ref ed) => match ed.state { - None => Ok(vec![]), - Some(ref ed) => super::bitmap::io::render_elms(ed), + None => warn!("to do: render empty bitmap editor?"), + Some(ref ed) => unimplemented!(), //super::bitmap::io::render_elms(ed, r), }, &Editor::Menu(ref ed) => match ed.state { - None => Ok(vec![]), - Some(ref st) => super::menu::io::render_elms(st), + None => warn!("to do: render empty bitmap editor?"), + Some(ref st) => menu::io::render_elms(st, r), }, &Editor::Chain(ref _ch) => unimplemented!(), &Editor::Grid(ref _gr) => unimplemented!(), } } +pub fn render_elms(state: &State) -> Result<render::Elms, String> { + let mut r = Render::new(); + fn vert_flow() -> FlowAtts { + FlowAtts { + dir: Dir2D::Down, + intra_pad: 2, + inter_pad: 2, + } + } + fn horz_flow() -> FlowAtts { + FlowAtts { + dir: Dir2D::Right, + intra_pad: 2, + inter_pad: 2, + } + } + // to do: acquire and use the screen dimension for "clip flows". + r.begin(&Name::Void, FrameType::Flow(horz_flow())); + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + for frame in state.stack.iter() { + render_elms_of_editor(&frame.editor, &mut r); + } + r.end(); + render_elms_of_editor(&state.frame.editor, &mut r); + r.end(); + Ok(r.into_elms()) +} + pub fn get_persis_state_path() -> String { let dir: String = std::env::current_dir().unwrap().to_str().unwrap().into(); format!("{}/zqm.json", dir) diff --git a/engine/src/lib/glyph.rs b/engine/src/lib/glyph.rs index bd1b16b..2e3c505 100644 --- a/engine/src/lib/glyph.rs +++ b/engine/src/lib/glyph.rs @@ -22,6 +22,20 @@ pub mod cap5x5 { // the first set of glyphs to bootstrap zqm's interface // editor: emacs (Rust major mode, in ovewrite minor mode) glyph_map! { + "☺2" => [ + 0 8 8 8 0 ; + 8 0 0 0 8 ; + 8 0 0 0 8 ; + 8 0 0 0 8 ; + 0 8 8 8 0 + ], + "☺" => [ + 0 8 8 8 0 ; + 8 0 8 0 8 ; + 0 8 8 8 0 ; + 8 0 0 0 8 ; + 0 8 8 8 0 + ], " " => [ 0 0 0 0 0 ; 0 0 0 0 0 ; @@ -29,6 +43,78 @@ pub mod cap5x5 { 0 0 0 0 0 ; 0 0 0 0 0 ], + // hack: lock glyph for mazeGame + "ļ" => [ + 8 8 8 8 8 ; + 8 8 0 8 8 ; + 8 0 8 0 8 ; + 8 8 0 8 8 ; + 8 8 8 8 8 + ], + // hack: key glyph for mazeGame + "ķ" => [ + 0 0 8 0 0 ; + 0 8 0 8 0 ; + 0 0 8 0 0 ; + 0 0 8 8 0 ; + 0 0 8 8 0 + ], + "◊" => [ + 0 0 8 0 0 ; + 0 8 0 8 0 ; + 8 0 0 0 8 ; + 0 8 0 8 0 ; + 0 0 8 0 0 + ], + "⇲" => [ + 8 0 8 0 0 ; + 0 8 8 0 8 ; + 8 8 8 0 8 ; + 0 0 0 0 8 ; + 0 8 8 8 8 + ], + "█" => [ + 8 8 8 8 8 ; + 8 8 8 8 8 ; + 8 8 8 8 8 ; + 8 8 8 8 8 ; + 8 8 8 8 8 + ], + "░" => [ + 8 0 8 0 8 ; + 0 8 0 8 0 ; + 8 0 8 0 8 ; + 0 8 0 8 0 ; + 8 0 8 0 8 + ], + "━" => [ + 0 0 0 0 0 ; + 0 0 0 0 0 ; + 8 0 8 0 8 ; + 0 0 0 0 0 ; + 0 0 0 0 0 + ], + "►" => [ + 0 0 8 0 0 ; + 0 0 8 8 0 ; + 8 0 8 8 8 ; + 0 0 8 8 0 ; + 0 0 8 0 0 + ], + "→" => [ + 0 0 8 0 0 ; + 0 0 0 8 0 ; + 8 8 8 8 8 ; + 0 0 0 8 0 ; + 0 0 8 0 0 + ], + "?" => [ + 0 8 8 8 0 ; + 8 0 0 0 8 ; + 0 0 8 8 0 ; + 0 0 0 0 0 ; + 0 0 8 0 0 + ], "**" => [ 0 0 8 0 0 ; 0 8 8 8 0 ; diff --git a/engine/src/lib/init.rs b/engine/src/lib/init.rs index 9beb6ca..098f410 100644 --- a/engine/src/lib/init.rs +++ b/engine/src/lib/init.rs @@ -1,17 +1,52 @@ -use eval; +extern crate serde_idl; + +use candid; use menu; -use types::lang::{Atom, Command, Editor, Name, State}; + +use eval; +use types::lang::{Atom, Command, Editor, Frame, FrameCont, Name, State}; pub fn init_state() -> State { let (mut state_init, init_command) = { - if false { - use crate::bitmap; + if true { + let idl_spec = r#" +service server : { + f : (a: nat, b:nat) -> () oneway; + g : (a: text, b:nat) -> () oneway; + h : (a: nat, b:record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }) -> () oneway; +} + "#; + let idlprog = candid::parse_idl(&idl_spec); + let menu_type = candid::menutype_of_idlprog(&idlprog); ( State { - editor: Editor::Bitmap(Box::new(bitmap::Editor { + stack: vec![], + frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor { state: None, history: vec![], - })), + }))), + }, + Command::Menu(menu::Command::Init(menu::InitCommand::Default( + menu::MenuTree::Blank(menu_type.clone()), + menu_type, + ))), + ) + } else if false { + use crate::bitmap; + ( + State { + stack: vec![], + frame: Frame { + name: Name::Void, + editor: Editor::Bitmap(Box::new(bitmap::Editor { + state: None, + history: vec![], + })), + cont: FrameCont { + var: Name::Void, + commands: vec![], + }, + }, }, Command::Bitmap(bitmap::Command::Init(bitmap::InitCommand::Make16x16)), ) @@ -79,10 +114,11 @@ pub fn init_state() -> State { ]); ( State { - editor: Editor::Menu(Box::new(menu::Editor { + stack: vec![], + frame: Frame::from_editor(Editor::Menu(Box::new(menu::Editor { state: None, history: vec![], - })), + }))), }, Command::Menu(menu::Command::Init(menu::InitCommand::Default( menu::MenuTree::Blank(root.clone()), @@ -91,7 +127,7 @@ pub fn init_state() -> State { ) } }; - let r = eval::command_eval(&mut state_init, &init_command); + let r = eval::command_eval(&mut state_init, None, &init_command); match r { Ok(()) => {} Err(err) => eprintln!("Failed to initialize the editor: {:?}", err), diff --git a/engine/src/lib/menu.rs b/engine/src/lib/menu.rs index 2fdf1e2..810fa38 100644 --- a/engine/src/lib/menu.rs +++ b/engine/src/lib/menu.rs @@ -4,6 +4,7 @@ use types::lang::{Atom, Dir1D, Name}; pub type Text = String; pub type Nat = usize; // todo -- use a bignum rep +pub type Int = isize; // todo -- use a bignum rep pub type Label = Name; #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] @@ -14,16 +15,26 @@ pub enum MenuType { Option(Box<MenuType>), Vec(Box<MenuType>), Tup(Vec<MenuType>), + Var(Name), + Func(FuncType), } #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] pub enum PrimType { + Null, Unit, Nat, + Int, Text, Bool, } +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] +pub struct FuncType { + pub args: Vec<MenuType>, + pub rets: Vec<MenuType>, +} + #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub enum MenuTree { Product(Vec<(Label, MenuTree, MenuType)>), @@ -33,31 +44,33 @@ pub enum MenuTree { Tup(Vec<(MenuTree, MenuType)>), Blank(MenuType), Nat(Nat), + Int(Int), Text(Text), Bool(bool), Unit, + Null, } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct LabelSelect { - before: Vec<(Label, MenuTree, MenuType)>, - ctx: MenuCtx, - label: Label, - after: Vec<(Label, MenuTree, MenuType)>, + pub before: Vec<(Label, MenuTree, MenuType)>, + pub ctx: MenuCtx, + pub label: Label, + pub after: Vec<(Label, MenuTree, MenuType)>, } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct PosSelect { - before: Vec<(Label, MenuTree, MenuType)>, - ctx: MenuCtx, - after: Vec<(Label, MenuTree, MenuType)>, + pub before: Vec<(MenuTree, MenuType)>, + pub ctx: MenuCtx, + pub after: Vec<(MenuTree, MenuType)>, } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct LabelChoice { - before: Vec<(Label, MenuTree, MenuType)>, - choice: Option<(Label, MenuTree, MenuType)>, - after: Vec<(Label, MenuTree, MenuType)>, + pub before: Vec<(Label, MenuTree, MenuType)>, + pub choice: Option<(Label, MenuTree, MenuType)>, + pub after: Vec<(Label, MenuTree, MenuType)>, } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] @@ -68,6 +81,7 @@ pub enum Error { #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)] pub enum Tag { + Root, Prim(PrimType), Variant, Product, @@ -75,6 +89,8 @@ pub enum Tag { Vec, Tup, Blank, + Var, + Func, } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] @@ -91,22 +107,23 @@ pub enum AutoCommand { #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub enum EditCommand { + Commit, Descend, Ascend, PrevSibling, NextSibling, - GotoRoot, // ---? - AutoFill, // Tab - Clear, // Backspace - NextTree, // ArrowRight - PrevTree, // ArrowLeft - NextBlank, // ---? - PrevBlank, // ---? - NextVariant, // ArrowRight - PrevVariant, // ArrowLefet - AcceptVariant, // Enter - VecInsertBlank, // ---? - VecInsertAuto, // ---? + GotoRoot, + AutoFill, + Clear, + NextTree, + PrevTree, + NextBlank, + PrevBlank, + NextVariant, + PrevVariant, + AcceptVariant, + VecInsertBlank, + VecInsertAuto, } #[derive(Debug, Clone, Serialize, Deserialize, Hash)] @@ -119,11 +136,11 @@ pub enum Command { #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub enum MenuCtx { Root(MenuType), + Tup(Box<PosSelect>), Product(Box<LabelSelect>), Variant(Box<LabelSelect>), Option(bool, Box<MenuCtx>), Vec(Box<PosSelect>), - Tup(Box<PosSelect>), } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] @@ -140,11 +157,16 @@ pub struct Editor { pub history: Vec<Command>, } +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub enum Halt { + Commit(MenuTree), + Message(String), +} + pub mod semantics { use super::*; - pub type Err = String; - pub type Res = Result<(), Err>; + pub type Res = Result<(), Halt>; pub fn editor_eval(menu: &mut Editor, command: &Command) -> Res { trace!("editor_eval({:?}) begin", command); @@ -159,7 +181,7 @@ pub mod semantics { Ok(()) } Command::Edit(ref c) => match menu.state { - None => Err("Invalid editor state".to_string()), + None => Err(Halt::Message("Invalid editor state".to_string())), Some(ref mut st) => state_eval_command(st, c), }, Command::Auto(ref _c) => unimplemented!(), @@ -171,6 +193,10 @@ pub mod semantics { pub fn state_eval_command(menu: &mut MenuState, command: &EditCommand) -> Res { match command { + &EditCommand::Commit => { + goto_root(menu)?; + Err(Halt::Commit(menu.tree.clone())) + } &EditCommand::GotoRoot => goto_root(menu), &EditCommand::AutoFill => { let tree = auto_fill(&menu.tree_typ, 1); @@ -222,15 +248,17 @@ pub mod semantics { MenuTree::Tup(_) => Tag::Tup, MenuTree::Blank(_) => Tag::Blank, MenuTree::Nat(_) => Tag::Prim(PrimType::Nat), + MenuTree::Int(_) => Tag::Prim(PrimType::Int), MenuTree::Bool(_) => Tag::Prim(PrimType::Bool), MenuTree::Text(_) => Tag::Prim(PrimType::Text), MenuTree::Unit => Tag::Prim(PrimType::Unit), + MenuTree::Null => Tag::Prim(PrimType::Null), } } pub fn ctx_tag(ctx: &MenuCtx) -> Tag { match ctx { - MenuCtx::Root(typ) => typ_tag(typ), + MenuCtx::Root(typ) => Tag::Root, MenuCtx::Product(_) => Tag::Product, MenuCtx::Variant(_) => Tag::Variant, MenuCtx::Option(_, _) => Tag::Option, @@ -247,6 +275,8 @@ pub mod semantics { MenuType::Option(_) => Tag::Option, MenuType::Tup(_) => Tag::Tup, MenuType::Vec(_) => Tag::Vec, + MenuType::Var(_) => Tag::Var, + MenuType::Func(_) => Tag::Func, } } @@ -255,7 +285,10 @@ pub mod semantics { if &tt == tag { Ok(()) } else { - Err(format!("expected {:?} but found {:?}: {:?}", tag, tt, tree)) + Err(Halt::Message(format!( + "expected {:?} but found {:?}: {:?}", + tag, tt, tree + ))) } } @@ -269,7 +302,7 @@ pub mod semantics { } } - pub fn next_blank(menu: &mut MenuState) -> Result<MenuType, Err> { + pub fn next_blank(menu: &mut MenuState) -> Result<MenuType, Halt> { match menu.tree { MenuTree::Blank(ref t) => Ok(t.clone()), _ => { @@ -279,7 +312,7 @@ pub mod semantics { } } - pub fn prev_blank(menu: &mut MenuState) -> Result<MenuType, Err> { + pub fn prev_blank(menu: &mut MenuState) -> Result<MenuType, Halt> { match menu.tree { MenuTree::Blank(ref t) => Ok(t.clone()), _ => { @@ -317,7 +350,26 @@ pub mod semantics { pub fn ascend(menu: &mut MenuState) -> Res { match menu.ctx.clone() { - MenuCtx::Root(_) => Err("cannot ascend: already at root".to_string()), + MenuCtx::Root(_) => Err(Halt::Message("cannot ascend: already at root".to_string())), + MenuCtx::Tup(sel) => { + let mut arms: Vec<(MenuTree, MenuType)> = sel + .before + .iter() + .map(|(t, tt)| (t.clone(), tt.clone())) + .collect(); + arms.push((menu.tree.clone(), menu.tree_typ.clone())); + let mut after = sel + .after + .iter() + .map(|(t, tt)| (t.clone(), tt.clone())) + .collect(); + arms.append(&mut after); + let fields: Vec<MenuType> = arms.iter().map(|(_, t)| t.clone()).collect(); + menu.tree = MenuTree::Tup(arms); + menu.tree_typ = MenuType::Tup(fields); + menu.ctx = sel.ctx; + Ok(()) + } MenuCtx::Product(mut sel) => { let mut arms = sel.before; arms.push((sel.label, menu.tree.clone(), menu.tree_typ.clone())); @@ -352,14 +404,45 @@ pub mod semantics { } MenuCtx::Option(_flag, _menu) => unimplemented!(), MenuCtx::Vec(_sel) => unimplemented!(), - MenuCtx::Tup(_sel) => unimplemented!(), } } pub fn descend(menu: &mut MenuState, dir: Dir1D) -> Res { // navigate product field structure; ignore unchosen variant options. match menu.tree { - MenuTree::Blank(_) => Err("no subtrees".to_string()), + MenuTree::Blank(_) => Err(Halt::Message("no subtrees".to_string())), + MenuTree::Tup(ref trees) => { + let mut trees = trees.clone(); + if trees.len() > 0 { + match dir { + Dir1D::Forward => { + trees.rotate_left(1); + let (tree, tree_t) = trees.pop().unwrap(); + menu.tree = tree; + menu.tree_typ = tree_t; + menu.ctx = MenuCtx::Tup(Box::new(PosSelect { + before: vec![], + ctx: menu.ctx.clone(), + after: trees, + })); + Ok(()) + } + Dir1D::Backward => { + let (tree, tree_t) = trees.pop().unwrap(); + menu.tree = tree; + menu.tree_typ = tree_t; + menu.ctx = MenuCtx::Tup(Box::new(PosSelect { + before: trees, + ctx: menu.ctx.clone(), + after: vec![], + })); + Ok(()) + } + } + } else { + Err(Halt::Message("no subtrees".to_string())) + } + } MenuTree::Product(ref trees) => { let mut trees = trees.clone(); if trees.len() > 0 { @@ -391,9 +474,14 @@ pub mod semantics { } } } else { - Err("no subtrees".to_string()) + Err(Halt::Message("no subtrees".to_string())) } } + MenuTree::Nat(ref n) => { + // to do -- move this logic + menu.tree = MenuTree::Nat(*n + 1); + Ok(()) + } MenuTree::Variant(ref trees) => { let trees = trees.clone(); match trees.choice { @@ -410,12 +498,12 @@ pub mod semantics { Ok(()) } }, - None => Err("no choice subtree".to_string()), + None => Err(Halt::Message("no choice subtree".to_string())), } } _ => { // to do - Err("not implemented".to_string()) + Err(Halt::Message("not implemented".to_string())) } } } @@ -470,12 +558,26 @@ pub mod semantics { } } } - _ => Err("expected tree to be a variant".to_string()), + _ => Err(Halt::Message("expected tree to be a variant".to_string())), } } pub fn next_sibling(menu: &mut MenuState) -> Res { match menu.ctx.clone() { + MenuCtx::Tup(mut sel) => { + sel.before.push((menu.tree.clone(), menu.tree_typ.clone())); + if sel.after.len() > 0 { + sel.after.rotate_left(1); + let (tree, tree_typ) = sel.after.pop().unwrap(); + menu.tree = tree; + menu.tree_typ = tree_typ; + menu.ctx = MenuCtx::Tup(sel); + Ok(()) + } else { + ascend(menu)?; + descend(menu, Dir1D::Forward) + } + } MenuCtx::Product(mut sel) => { sel.before .push((sel.label, menu.tree.clone(), menu.tree_typ.clone())); @@ -519,6 +621,20 @@ pub mod semantics { descend(menu, Dir1D::Backward) } } + MenuCtx::Tup(mut sel) => { + sel.after.push((menu.tree.clone(), menu.tree_typ.clone())); + sel.after.rotate_left(1); + if sel.before.len() > 0 { + let (tree, tree_typ) = sel.before.pop().unwrap(); + menu.tree = tree; + menu.tree_typ = tree_typ; + menu.ctx = MenuCtx::Tup(sel); + Ok(()) + } else { + ascend(menu)?; + descend(menu, Dir1D::Backward) + } + } MenuCtx::Root(_) => Ok(()), MenuCtx::Variant(_) => { ascend(menu)?; @@ -562,9 +678,13 @@ pub mod semantics { } else { match typ { &MenuType::Prim(PrimType::Unit) => MenuTree::Unit, + &MenuType::Prim(PrimType::Null) => MenuTree::Null, &MenuType::Prim(PrimType::Nat) => MenuTree::Nat(0), + &MenuType::Prim(PrimType::Int) => MenuTree::Int(0), &MenuType::Prim(PrimType::Text) => MenuTree::Text("".to_string()), &MenuType::Prim(PrimType::Bool) => MenuTree::Bool(false), + &MenuType::Var(ref n) => MenuTree::Blank(typ.clone()), + &MenuType::Func(ref f) => MenuTree::Blank(typ.clone()), &MenuType::Variant(ref labtyps) => { let after_choices: Vec<(Label, MenuTree, MenuType)> = labtyps .iter() @@ -623,6 +743,8 @@ pub mod io { (&Event::Quit { .. }, _, _) => Err(()), (&Event::KeyDown(ref kei), ref ctx, ref tree) => match (kei.key.as_str(), ctx, tree) { ("Escape", _, _) => Err(()), + //("Enter", Tag::Root, _) => Ok(vec![EditCommand::Commit]), + ("Enter", _, _) => Ok(vec![EditCommand::Commit]), ("Backspace", _, _) => Ok(vec![EditCommand::Clear]), ("Tab", _, Tag::Blank) => Ok(vec![EditCommand::AutoFill]), @@ -631,13 +753,14 @@ pub mod io { ("ArrowLeft", _, _) => Ok(vec![EditCommand::Ascend]), ("ArrowRight", _, _) => Ok(vec![EditCommand::Descend]), - ("ArrowUp", Tag::Variant, _) => Ok(vec![EditCommand::PrevVariant]), - ("ArrowDown", Tag::Variant, _) => Ok(vec![EditCommand::NextVariant]), + ("ArrowUp", _, Tag::Variant) => Ok(vec![EditCommand::PrevVariant]), + ("ArrowDown", _, Tag::Variant) => Ok(vec![EditCommand::NextVariant]), ("ArrowUp", Tag::Product, _) => Ok(vec![EditCommand::PrevSibling]), ("ArrowDown", Tag::Product, _) => Ok(vec![EditCommand::NextSibling]), - ("Enter", _, _) => Ok(vec![EditCommand::Descend]), + ("ArrowUp", Tag::Tup, _) => Ok(vec![EditCommand::PrevSibling]), + ("ArrowDown", Tag::Tup, _) => Ok(vec![EditCommand::NextSibling]), (key, ctx, tree) => { warn!( @@ -654,9 +777,9 @@ pub mod io { } } - pub fn render_elms(menu: &MenuState) -> Result<Elms, String> { - use crate::render::{FlowAtts, FrameType, TextAtts}; + use crate::render::{FlowAtts, FrameType, TextAtts}; + pub fn render_elms(menu: &MenuState, r: &mut Render) { fn black_fill() -> Fill { //Fill::Closed(Color::RGB(0, 0, 0)) Fill::None @@ -736,7 +859,7 @@ pub mod io { glyph_flow: glyph_flow(), } }; - fn typ_atts() -> TextAtts { + fn typ_lab_atts() -> TextAtts { TextAtts { zoom: 2, fg_fill: Fill::Closed(Color::RGB(200, 255, 255)), @@ -745,6 +868,15 @@ pub mod io { glyph_flow: glyph_flow(), } }; + fn typ_sym_atts() -> TextAtts { + TextAtts { + zoom: 2, + fg_fill: Fill::Closed(Color::RGB(180, 200, 200)), + bg_fill: Fill::None, + glyph_dim: glyph_dim(), + glyph_flow: glyph_flow(), + } + }; fn typ_vflow() -> FlowAtts { FlowAtts { dir: Dir2D::Down, @@ -845,6 +977,29 @@ pub mod io { r_out.end(); return; } + &MenuCtx::Tup(ref sel) => { + next_ctx = Some(sel.ctx.clone()); + r.begin(&Name::Void, FrameType::Flow(vert_flow())); + for (t, _ty) in sel.before.iter() { + begin_item(&mut r); + //render_product_label(l, &mut r); + render_tree(t, false, &ctx_box_fill(), &mut r); + r.end(); + } + { + begin_item(&mut r); + //render_product_label(&sel.label, &mut r); + r.nest(&Name::Void, r_tree); + r.end(); + } + for (t, _ty) in sel.after.iter() { + begin_item(&mut r); + //render_product_label(&l, &mut r); + render_tree(t, false, &ctx_box_fill(), &mut r); + r.end(); + } + r.end(); + } &MenuCtx::Product(ref sel) => { next_ctx = Some(sel.ctx.clone()); r.begin(&Name::Void, FrameType::Flow(vert_flow())); @@ -877,7 +1032,6 @@ pub mod io { } &MenuCtx::Option(_flag, ref _body) => unimplemented!(), &MenuCtx::Vec(ref _ch) => unimplemented!(), - &MenuCtx::Tup(ref _ch) => unimplemented!(), }; r.end(); // continue rendering the rest of the context, in whatever flow we are using for that purpose. @@ -893,6 +1047,7 @@ pub mod io { fn render_type( typ: &MenuType, text: &TextAtts, + text2: &TextAtts, vflow: &FlowAtts, hflow: &FlowAtts, r: &mut Render, @@ -900,7 +1055,9 @@ pub mod io { let mut first = true; match typ { MenuType::Prim(PrimType::Unit) => r.str("()", text), + MenuType::Prim(PrimType::Null) => r.str("null", text), MenuType::Prim(PrimType::Nat) => r.str("nat", text), + MenuType::Prim(PrimType::Int) => r.str("int", text), MenuType::Prim(PrimType::Text) => r.str("text", text), MenuType::Prim(PrimType::Bool) => r.str("bool", text), MenuType::Variant(fields) => { @@ -910,16 +1067,18 @@ pub mod io { if first { first = false; begin_flow(r, hflow); - r.str("{", text); + r.str("{ ", text2); } else { r.end(); begin_flow(r, hflow); - r.str("; ", text); + r.str("; ", text2); }; - r.str(&format!("#{}: ", l), text); - render_type(t, text, vflow, hflow, r); + r.str("#", text2); + r.str(&format!("{}", l), text); + r.str(": ", text2); + render_type(t, text, text2, vflow, hflow, r); } - r.str("}", text); + r.str(" }", text2); r.end(); } else { unimplemented!() @@ -933,25 +1092,76 @@ pub mod io { if first { first = false; begin_flow(r, hflow); - r.str("{", text); + r.str("{ ", text2); } else { r.end(); begin_flow(r, hflow); - r.str("; ", text); + r.str("; ", text2); }; - r.str(&format!("{}: ", l), text); - render_type(t, text, vflow, hflow, r); + r.str(&format!("{}", l), text); + r.str(":", text2); + render_type(t, text, text2, vflow, hflow, r); } - r.str("}", text); + r.str(" }", text2); r.end(); } else { - unimplemented!() + r.str("{ }", text2); + } + r.end() + } + MenuType::Var(n) => r.name(n, text), + MenuType::Option(t) => { + begin_flow(r, hflow); + r.str("?", text2); + render_type(t, text, text2, vflow, hflow, r); + r.end() + } + MenuType::Vec(t) => { + begin_flow(r, hflow); + r.str("[", text2); + render_type(t, text, text2, vflow, hflow, r); + r.str("]", text2); + r.end() + } + MenuType::Tup(typs) => { + begin_flow(r, hflow); + r.str("(", text2); + let mut not_first = false; + for t in typs.iter() { + if not_first { + r.str(", ", text2); + } + render_type(t, text, text2, vflow, hflow, r); + not_first = true; + } + r.str(")", text2); + r.end() + } + MenuType::Func(ft) => { + begin_flow(r, hflow); + r.str("(", text2); + let mut not_first = false; + for t in ft.args.iter() { + if not_first { + r.str(", ", text2); + } + render_type(t, text, text2, vflow, hflow, r); + not_first = true; } + r.str(")", text2); + r.str("→", text2); + r.str("(", text2); + let mut not_first = false; + for t in ft.rets.iter() { + if not_first { + r.str(", ", text2); + } + render_type(t, text, text2, vflow, hflow, r); + not_first = true; + } + r.str(")", text2); r.end() } - MenuType::Option(_t) => unimplemented!(), - MenuType::Vec(_t) => unimplemented!(), - MenuType::Tup(_fields) => unimplemented!(), } } @@ -974,7 +1184,7 @@ pub mod io { r.begin(&Name::Void, FrameType::Flow(vert_flow())); if show_detailed { begin_item(r); - r.text(&format!("Choice:"), &msg_atts()); + r.text(&format!("choice:"), &msg_atts()); if let Some(_) = ch.choice { // nothing } else { @@ -1048,19 +1258,21 @@ pub mod io { } &MenuTree::Blank(ref _typ) => r.text(&format!("___"), &blank_atts()), &MenuTree::Nat(n) => r.text(&format!("{}", n), &text_atts()), + &MenuTree::Int(i) => r.text(&format!("{}", i), &text_atts()), &MenuTree::Bool(b) => r.text(&format!("{}", b), &text_atts()), &MenuTree::Text(ref t) => r.text(&format!("{:?}", t), &text_atts()), &MenuTree::Unit => r.str("()", &text_atts()), + &MenuTree::Null => r.str("null", &text_atts()), }; r.end(); }; - let mut r = Render::new(); r.begin(&Name::Void, FrameType::Flow(vert_flow())); if true { - r.str("hello world!", &text_atts()); - r.str(" please, enter a value to submit:", &msg_atts()); - r.str(" (Auto-fill and navigate with arrow keys)", &msg_atts()); - + if false { + r.str("hello world!", &text_atts()); + r.str(" please, enter a value to submit:", &msg_atts()); + r.str(" (Auto-fill and navigate with arrow keys)", &msg_atts()); + } let r_tree = { let mut r_tree = Render::new(); r_tree.begin(&Name::Void, FrameType::Flow(vert_flow())); @@ -1068,7 +1280,8 @@ pub mod io { render_tree(&menu.tree, true, &detailed_tree_box_fill(), &mut r_tree); render_type( &menu.tree_typ, - &typ_atts(), + &typ_lab_atts(), + &typ_sym_atts(), &typ_vflow(), &typ_hflow(), &mut r_tree, @@ -1076,15 +1289,78 @@ pub mod io { r_tree.end(); r_tree }; - render_ctx(&menu.ctx, &mut r, r_tree); + render_ctx(&menu.ctx, r, r_tree); } r.end(); - Ok(r.into_elms()) } } use std::fmt; +impl fmt::Display for MenuTree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut first = true; + match self { + MenuTree::Product(ref fields) => { + write!(f, "record {{")?; + for (l, t, _) in fields.iter() { + if !first { + write!(f, "; ")?; + }; + first = false; + write!(f, "{}={}", l, t)?; + } + write!(f, "}}") + } + MenuTree::Variant(ref choice) => { + write!(f, "variant {{")?; + if let Some((ref l, ref t, _)) = choice.choice { + write!(f, "{}={}", l, t)?; + } else { + // nothing + } + write!(f, "}}") + } + MenuTree::Option(ref b, ref t, _) => { + if *b { + write!(f, "opt {}", t) + } else { + write!(f, "opt null") + } + } + MenuTree::Vec(ref ts, _) => { + write!(f, "vec{{")?; + for t in ts.iter() { + if !first { + write!(f, "; ")?; + }; + first = false; + write!(f, "{}", t)?; + } + write!(f, "}}") + } + MenuTree::Tup(ref ts) => { + write!(f, "(")?; + for (t, _) in ts.iter() { + if !first { + write!(f, ", ")?; + }; + first = false; + write!(f, "{}", t)?; + } + write!(f, ")") + } + MenuTree::Blank(_) => write!(f, "variant {{BLANK=()}}"), + MenuTree::Nat(n) => write!(f, "{}", n), + MenuTree::Int(i) => write!(f, "{}", i), + MenuTree::Text(t) => write!(f, "{:?}", t), + MenuTree::Bool(b) => write!(f, "{}", b), + MenuTree::Unit => write!(f, "()"), + MenuTree::Null => write!(f, "null"), + } + } +} + // move elsewhere impl fmt::Display for Name { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1114,8 +1390,11 @@ impl fmt::Display for MenuType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut first = true; match self { + MenuType::Var(ref n) => write!(f, "{:?}", n), MenuType::Prim(PrimType::Unit) => write!(f, "()"), + MenuType::Prim(PrimType::Null) => write!(f, "null"), MenuType::Prim(PrimType::Nat) => write!(f, "nat"), + MenuType::Prim(PrimType::Int) => write!(f, "int"), MenuType::Prim(PrimType::Text) => write!(f, "text"), MenuType::Prim(PrimType::Bool) => write!(f, "bool"), MenuType::Variant(fields) => { @@ -1153,6 +1432,25 @@ impl fmt::Display for MenuType { } write!(f, ")") } + MenuType::Func(ft) => { + write!(f, "(")?; + for t in ft.args.iter() { + if !first { + write!(f, ", ")?; + }; + write!(f, "{}", t)?; + first = false; + } + write!(f, ") -> ("); + for t in ft.rets.iter() { + if !first { + write!(f, ", ")?; + }; + write!(f, "{}", t)?; + first = false; + } + write!(f, ")") + } } } } diff --git a/engine/src/lib/mod.rs b/engine/src/lib/mod.rs index e3e4f47..d165591 100644 --- a/engine/src/lib/mod.rs +++ b/engine/src/lib/mod.rs @@ -7,7 +7,11 @@ extern crate hashcons; extern crate serde; extern crate serde_bytes; -// -------- eval semantics --------- +extern crate ic_agent; +extern crate serde_idl; + +extern crate tokio; +extern crate delay; pub mod eval; pub mod init; @@ -34,3 +38,7 @@ pub type GlyphMap = std::collections::HashMap<types::lang::Name, Glyph>; pub mod glyph; pub mod render; + +// -------- Candid (IC input/output) --------- + +pub mod candid; diff --git a/engine/src/lib/render.rs b/engine/src/lib/render.rs index cedd4ea..64a1e5d 100644 --- a/engine/src/lib/render.rs +++ b/engine/src/lib/render.rs @@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize}; use bitmap; use glyph; -use types::{ +pub use types::{ lang::{Atom, Dir2D, Name}, - render::{Dim, Elm, Elms, Fill, Node, Pos, Rect}, + render::{Color, Dim, Elm, Elms, Fill, NamedElms, Node, Out, Pos, Rect}, }; #[derive(Clone, Debug, Serialize, Deserialize, Hash)] @@ -99,6 +99,10 @@ impl Render { self.frame.elms.push(Elm::Rect(r.clone(), f)) } + pub fn elm(&mut self, e: Elm) { + self.frame.elms.push(e) + } + pub fn bitmap(&mut self, bm: &bitmap::Bitmap, ba: &BitmapAtts) { let (width, height) = bitmap::semantics::bitmap_get_size(bm); for y in 0..height { @@ -160,6 +164,11 @@ impl Render { self.end(); } + pub fn add(&mut self, elms: Elms) { + let mut elms = elms; + self.frame.elms.append(&mut elms) + } + pub fn into_elms(self) -> Elms { assert_eq!(self.stack.len(), 0); self.frame.elms diff --git a/engine/src/lib/types.rs b/engine/src/lib/types.rs index 23d2f9f..f6cf8c5 100644 --- a/engine/src/lib/types.rs +++ b/engine/src/lib/types.rs @@ -1,6 +1,6 @@ /// The ZQM language: abstract syntax pub mod lang { - use crate::{bitmap, chain, grid, menu}; + use crate::{bitmap, candid, chain, grid, menu}; use hashcons::merkle::Merkle; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub mod lang { Atom(Atom), Name(Name), Location(Location), + MenuTree(Box<menu::MenuTree>), Bitmap(Box<bitmap::Bitmap>), Chain(Box<chain::Chain>), Grid(Box<grid::Grid>), @@ -100,18 +101,34 @@ pub mod lang { pub content: Media, } - #[derive(Debug, Serialize, Deserialize, Hash)] + #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub enum Editor { + CandidRepl(Box<candid::Repl>), Bitmap(Box<bitmap::Editor>), Menu(Box<menu::Editor>), Chain(Box<chain::Editor>), Grid(Box<grid::Editor>), } + #[derive(Clone, Debug, Serialize, Deserialize, Hash)] + pub struct Frame { + pub name: Name, + pub editor: Editor, + pub cont: FrameCont, + } + + #[derive(Clone, Debug, Serialize, Deserialize, Hash)] + pub struct FrameCont { + pub var: Name, + pub commands: Vec<Command>, + } + // to do -- eventually, we may want these to be "open" wrt the exp environment; // for expressing scripts, etc; then we'd need to do substitution, or more env-passing, or both. #[derive(Debug, Clone, Serialize, Deserialize, Hash)] pub enum Command { + Return(Media), + Resume(Media, Frame), Menu(menu::Command), Bitmap(bitmap::Command), Chain(chain::Command), @@ -134,7 +151,8 @@ pub mod lang { #[derive(Debug, Serialize, Deserialize, Hash)] pub struct State { - pub editor: Editor, + pub stack: Vec<Frame>, + pub frame: Frame, } pub type Hash = u64; @@ -163,6 +181,19 @@ pub mod lang { pub time: Name, pub place: Name, } + + impl Frame { + pub fn from_editor(editor: Editor) -> Frame { + Frame { + name: Name::Void, + editor: editor, + cont: FrameCont { + var: Name::Void, + commands: vec![], + }, + } + } + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -314,6 +345,7 @@ pub mod event { Quit, KeyDown(KeyEventInfo), KeyUp(KeyEventInfo), + WindowSizeChange(super::render::Dim), } #[derive(Clone, Debug, Serialize, Deserialize, Hash)] pub struct KeyEventInfo { @@ -379,6 +411,13 @@ pub mod render { Node(Box<Node>), } pub type Elms = Vec<Elm>; + pub type NamedElms = Vec<(Name, Elm)>; + + #[derive(Clone, Debug, Serialize, Deserialize, Hash)] + pub enum Out { + Draw(Elm), + Redraw(NamedElms), + } } /// Deprecated? @@ -406,143 +445,3 @@ pub mod util { Name::Atom(atom) } } - -/* - -/// a state store tree cursor -/// -/// the "cursor position" is a distinct subtree; -/// together with the tree context, the pair forms a "tree zipper" -/// that represents a cursor in a tree, permitting O(1) local navigation. -#[derive(Debug, Serialize, Deserialize)] -pub struct StateStoreTreeCursor { - pub ctx: StateStoreTreeCtx, - pub pos: StateStoreTree, -} - -/// historical tree of state stores -/// -/// a state store tree organizes a set of meta commands' state stores into a -/// historical tree of concurrent evolution; each "node" is a store of -/// states; each "edge" is a MetaCommand that relates two nodes' state stores. -#[derive(Debug, Serialize, Deserialize)] -pub enum StateStoreTree { - Empty, - Singleton(StateStore), - Linear(StateStore, Vec<LinearStateTrans>, Box<StateStoreTree>), - Branching(StateStore, Vec<BranchingStateTrans>), -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum StateStoreTreeCtx { - Linear(StateStore, Vec<LinearStateTrans>, Vec<LinearStateTrans>), - Branching(StateStore, Vec<BranchingStateTransCtx>), -} - -/// a linear state transition consists of a `Command` and target `StateStore` -#[derive(Debug, Serialize, Deserialize)] -pub struct LinearStateTrans { - command: Command, - target: StateStore, -} - -/// a branching state transition consists of a `Command` and target `StateStoreTree` -/// -/// two sister state transitions, each of type `BranchingStateTrans`, -/// arise together when a `Command` is undone, and a distinct `Command` is issued; -/// this pattern can repeat, giving rise to more siblings. -#[derive(Debug, Serialize, Deserialize)] -pub struct BranchingStateTrans { - command: Command, - subtree: Box<StateStoreTree>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BranchingStateTransCtx { - ctx: StateStoreTreeCtx, - command: Command, -} -*/ - -/* - -/// A `History` generalizes the relationship between interactive -/// `Command`s and the `State`s that they describe and inter-relate. -/// -/// - Commands -/// -/// - Each "non-historical" Command's semantics (e.g., in bitmap -/// editor: move the cursor, toggle bits, etc), -/// -/// - their "History" (the ambient stack or tree of past editor -/// states), -/// -/// - Each "historical" Command's semantics, e.g., push/pop editor -/// states; undo/redo editor states, including the _mathematical_ -/// meaning of "undo/redo", which generalizes and nests. -/// - -use std::hash::Hash; -use std::fmt::Debug; - -trait History : Eq + Hash + Debug { - /// State of an abstracted "editor" - type State : Eq + Hash + Debug + Clone; - /// Commands of an abstracted "editor" - type Command : Eq + Hash + Debug; - /// Historical (re-)focusing commands of an abstracted "editor" - type HCommand : Eq + Hash + Debug; - /// Ok responses for HCommand evaluation - type Resp : Eq + Hash + Debug; - /// Err responses for HCommand evaluation - type Error : Eq + Hash + Debug; - - /// project/translate any historical command from the (more general) command type - /// - /// the environment uses this operation to test whether the - /// command is recognized/defined, and should be evaluated here. - /// - /// to do: enforce "well-formed ensembles", where no two distinct - /// command histories translate the same command; "well-formed - /// ensembles" enjoy nice properties: composition order does not - /// matter, and there are no (potentially very strange) "double - /// effects" of any one command. - fn translate_command(&mut self, &Self::Command) -> Option<Self::HCommand>; - - /// internal semantics of this command history. - /// - /// this method gives the "historical semantics" for the given "historical command" - fn intern_eval(&mut self, - state: &mut Self::State, - command: &Self::HCommand) - -> Result<Self::Resp, Self::Error>; - - /// external semantics of this command history - /// - /// this method records a state-transition triple in its representation, which - /// happens when the environment changes the editor state. - /// - /// Q: What assumptions can we make about the state triples? - /// e.g., do we ever miss state changes, or do they always sequence entirely? - /// - /// provisionally: the sequence of triples is always "well - /// formed", where each "after" state becomes the "before" state - /// in the subsequent triple, if any. - /// - fn extern_eval(&mut self, - before: &Self::State, - command: &Self::Command, - result: Result<Self::Resp, Self::Error>, - after: &Self::State); -} -*/ - -///////////////////////////////// - -/* -impl FromStr for Name { - fn from_str(s:& str) -> Name { - Name::Atom(NameAtom::String(s.to_string())) - } -} -*/ diff --git a/shell/Cargo.toml b/shell/Cargo.toml index 499b28e..419542f 100644 --- a/shell/Cargo.toml +++ b/shell/Cargo.toml @@ -16,6 +16,11 @@ hashcons = "0.1" sdl2 = "0.32" zqm-engine = { path = "../engine" } +serde-idl = { path = "/Users/matthew/dfn/sdk/src/serde_idl" } +dfx_derive = { path = "/Users/matthew/dfn/sdk/src/dfx_derive" } +dfx_info = { path = "/Users/matthew/dfn/sdk/src/dfx_info" } +ic-agent = { path = "/Users/matthew/dfn/sdk/src/agent/rust" } + [[bin]] name = "zqm-shell" path = "src/bin/zqm.rs" diff --git a/shell/src/bin/zqm.rs b/shell/src/bin/zqm.rs index 3ba05f8..71ddcc0 100644 --- a/shell/src/bin/zqm.rs +++ b/shell/src/bin/zqm.rs @@ -15,13 +15,14 @@ extern crate structopt; use structopt::StructOpt; use sdl2::event::Event as SysEvent; +use sdl2::event::WindowEvent; use sdl2::keyboard::Keycode; use std::io; // ZQM: extern crate zqm_engine; use zqm_engine::{ - eval, + candid, eval, init, types::{self, event, render}, }; @@ -44,21 +45,19 @@ struct CliOpt { #[derive(StructOpt, Debug)] enum CliCommand { + #[structopt(name = "candid", about = "Start an interactive Candid session.")] + Candid { + replica_url: String, + canister_id: String, + did_file: String, + }, + #[structopt(name = "start", about = "Start interactively.")] Start, #[structopt(name = "resume", about = "Resume last interaction.")] Resume, - #[structopt(name = "replay", about = "Replay last interaction.")] - Replay, - - #[structopt( - name = "history", - about = "Interact with history, the list of all prior interactions." - )] - History, - #[structopt(name = "version", about = "Display version.")] Version, @@ -157,6 +156,16 @@ pub fn draw_elms<T: RenderTarget>( fn translate_system_event(event: SysEvent) -> Option<event::Event> { match &event { + SysEvent::Window { + win_event: WindowEvent::SizeChanged(w, h), + .. + } => { + let dim = render::Dim { + width: *w as usize, + height: *h as usize, + }; + Some(event::Event::WindowSizeChange(dim)) + } SysEvent::Quit { .. } | SysEvent::KeyDown { keycode: Some(Keycode::Escape), @@ -191,22 +200,32 @@ fn translate_system_event(event: SysEvent) -> Option<event::Event> { } } +pub fn redraw<T: RenderTarget>( + canvas: &mut Canvas<T>, + state: &mut types::lang::State, + dim: &render::Dim, +) -> Result<(), String> { + let pos = render::Pos { x: 0, y: 0 }; + let fill = render::Fill::Closed(render::Color::RGB(0, 0, 0)); + let elms = eval::render_elms(state)?; + draw_elms(canvas, &pos, dim, &fill, &elms)?; + canvas.present(); + drop(elms); + Ok(()) +} + pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> { use sdl2::event::EventType; - - let pos = render::Pos { x: 0, y: 0 }; - let dim = render::Dim { - width: 888, + let mut dim = render::Dim { + width: 1000, height: 666, }; - let fill = render::Fill::Closed(render::Color::RGB(0, 0, 0)); - let sdl_context = sdl2::init()?; let video_subsystem = sdl_context.video()?; let window = video_subsystem - .window("zoom-quilt-machine", dim.width as u32, dim.height as u32) + .window("thin-ic-agent", dim.width as u32, dim.height as u32) .position_centered() - //.resizable() + .resizable() //.input_grabbed() //.fullscreen() //.fullscreen_desktop() @@ -223,10 +242,7 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> { { // draw initial frame, before waiting for any events - let elms = eval::render_elms(state)?; - draw_elms(&mut canvas, &pos, &dim, &fill, &elms)?; - canvas.present(); - drop(elms); + redraw(&mut canvas, state, &dim); } let mut event_pump = sdl_context.event_pump()?; @@ -242,12 +258,22 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> { None => continue 'running, Some(event) => event, }; + // catch window resize event: redraw and loop: + match event { + event::Event::WindowSizeChange(new_dim) => { + dim = new_dim.clone(); + redraw(&mut canvas, state, &dim)?; + continue 'running; + } + _ => (), + } match eval::commands_of_event(state, &event) { Ok(commands) => { for c in commands.iter() { // note: we borrow the command here, possibly requiring some cloning when it is evaluated. // todo -- we do nothing with the result; we should log it. - match eval::command_eval(state, c) { + let event = event.clone(); + match eval::command_eval(state, Some(event), c) { Ok(()) => {} Err(msg) => { warn!("Command {:?} lead to an error:", c); @@ -255,10 +281,7 @@ pub fn do_event_loop(state: &mut types::lang::State) -> Result<(), String> { } } } - let elms = eval::render_elms(state)?; - draw_elms(&mut canvas, &pos, &dim, &fill, &elms)?; - canvas.present(); - drop(elms); + redraw(&mut canvas, state, &dim)?; } Err(()) => break 'running, } @@ -276,12 +299,32 @@ fn main() { (_, _, _) => log::LevelFilter::Info, }, ); - - let mut state = eval::load_state(); - info!("Evaluating CLI command: {:?} ...", &cliopt.command); - + // - - - - - - - - - - - match cliopt.command { + CliCommand::Candid { + replica_url, + canister_id, + did_file, + } => { + use std::fs; + let contents = fs::read_to_string(&did_file).expect("reading candid file"); + let ast = candid::parse_idl(&contents); + //let menu = candid::menutype_of_idlprog_service(&ast); + let mut state = candid::init(replica_url.as_str(), canister_id.as_str(), &ast).unwrap(); + do_event_loop(&mut state).unwrap(); + eval::save_state(&state); + } + CliCommand::Start => { + let mut state = init::init_state(); + do_event_loop(&mut state).unwrap(); + eval::save_state(&state); + } + CliCommand::Resume => { + let mut state = eval::load_state(); + do_event_loop(&mut state).unwrap(); + eval::save_state(&state); + } CliCommand::Version => { const VERSION: &'static str = env!("CARGO_PKG_VERSION"); println!("{}", VERSION); @@ -292,15 +335,5 @@ fn main() { CliOpt::clap().gen_completions_to("zqm", s, &mut io::stdout()); info!("done") } - CliCommand::Start => { - do_event_loop(&mut state).unwrap(); - eval::save_state(&state); - } - CliCommand::Resume => { - do_event_loop(&mut state).unwrap(); - eval::save_state(&state); - } - CliCommand::Replay => unimplemented!(), - CliCommand::History => unimplemented!(), } } diff --git a/shell/tic-onto b/shell/tic-onto new file mode 100755 index 0000000..4742cfa --- /dev/null +++ b/shell/tic-onto @@ -0,0 +1,3 @@ +#!/bin/sh +echo Connecting to $1 +cargo run $2 candid localhost:8000 ic:$1 ../mohammer/canisters/idl/$1.did