diff --git a/Cargo.lock b/Cargo.lock index 1c751d6..2326e46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "argh" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" +checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" [[package]] name = "encode_unicode" @@ -529,9 +529,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -676,9 +676,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -687,18 +687,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9207952ae1a003f42d3d5e892dac3c6ba42aa6ac0c79a6a91a2b5cb4253e75c" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1728216d3244de4f14f14f8c15c79be1a7c67867d28d69b719690e2a19fb445" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", diff --git a/src/core/mod.rs b/src/core/mod.rs index 06c3e5a..f56e54a 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -11,7 +11,8 @@ pub use self::dictionary::{Dictionaries, Dictionary, DictionaryEntry}; pub use self::env::{Environment, SourceBlock}; pub use self::lexer::Lexer; pub use self::stack::{ - OwnedCellSlice, SharedBox, Stack, StackTuple, StackValue, StackValueType, WordList, + HashMapTreeKey, HashMapTreeNode, OwnedCellSlice, SharedBox, Stack, StackTuple, StackValue, + StackValueType, WordList, }; pub mod cont; diff --git a/src/core/stack.rs b/src/core/stack.rs index b38090f..7fde62e 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -102,7 +102,7 @@ impl Stack { } } - pub fn push_opt_raw(&mut self, value: Option>) -> Result<()> { + pub fn push_opt_raw(&mut self, value: Option>) -> Result<()> { match value { None => self.push_null(), Some(value) => self.push_raw(value), @@ -217,6 +217,13 @@ impl Stack { self.pop()?.into_cont() } + pub fn pop_cont_owned(&mut self) -> Result { + Ok(match Rc::try_unwrap(self.pop()?.into_cont()?) { + Ok(inner) => inner, + Err(rc) => rc.as_ref().clone(), + }) + } + pub fn pop_word_list(&mut self) -> Result> { self.pop()?.into_word_list() } @@ -240,7 +247,7 @@ impl Stack { self.pop()?.into_atom() } - pub fn pop_hashmap_owned(&mut self) -> Result>> { + pub fn pop_hashmap(&mut self) -> Result>> { let value = self.pop()?; if value.is_null() { Ok(None) @@ -425,7 +432,7 @@ define_stack_value! { if v.is_empty() { return f.write_str("[]"); } - f.write_str("[")?; + f.write_str("[ ")?; let mut first = true; for item in v { if !std::mem::take(&mut first) { @@ -433,7 +440,7 @@ define_stack_value! { } StackValue::fmt_dump(item.as_ref(), f)?; } - f.write_str("]") + f.write_str(" ]") }, as_tuple(v): &StackTuple = Ok(v), into_tuple, @@ -799,6 +806,12 @@ impl HashMapTreeNode { self.into_iter() } + pub fn owned_iter(self: Rc) -> HashMapTreeOwnedIter { + HashMapTreeOwnedIter { + stack: vec![(self, None)], + } + } + pub fn lookup(root_opt: &Option>, key: K) -> Option<&'_ Rc> where K: AsHashMapTreeKeyRef, @@ -1079,12 +1092,12 @@ impl<'a> Iterator for HashMapTreeIter<'a> { } } -#[derive(Default)] -pub struct HashMapTreeIntoIter { +#[derive(Default, Clone)] +pub struct HashMapTreeOwnedIter { stack: Vec<(Rc, Option)>, } -impl Iterator for HashMapTreeIntoIter { +impl Iterator for HashMapTreeOwnedIter { type Item = Rc; fn next(&mut self) -> Option { @@ -1117,6 +1130,13 @@ pub trait AsHashMapTreeKeyRef { fn as_equivalent(&self) -> HashMapTreeKeyRef<'_>; } +impl AsHashMapTreeKeyRef for &T { + #[inline] + fn as_equivalent(&self) -> HashMapTreeKeyRef<'_> { + ::as_equivalent(self) + } +} + #[derive(Clone)] pub struct HashMapTreeKey { pub hash: u64, diff --git a/src/modules/control.rs b/src/modules/control.rs index 2ca1622..347e4ca 100644 --- a/src/modules/control.rs +++ b/src/modules/control.rs @@ -47,7 +47,7 @@ impl Control { #[cmd(name = "times", tail)] fn interpret_execute_times(ctx: &mut Context) -> Result> { let count = ctx.stack.pop_smallint_range(0, 1000000000)? as usize; - let body = ctx.stack.pop_cont()?.as_ref().clone(); + let body = ctx.stack.pop_cont_owned()?; Ok(match count { 0 => None, 1 => Some(body), @@ -95,8 +95,8 @@ impl Control { #[cmd(name = "while", tail)] fn interpret_while(ctx: &mut Context) -> Result> { - let body = ctx.stack.pop_cont()?.as_ref().clone(); - let cond = ctx.stack.pop_cont()?.as_ref().clone(); + let body = ctx.stack.pop_cont_owned()?; + let cond = ctx.stack.pop_cont_owned()?; ctx.next = Some(Rc::new(cont::WhileCont { condition: Some(cond.clone()), body: Some(body), @@ -108,7 +108,7 @@ impl Control { #[cmd(name = "until", tail)] fn interpret_until(ctx: &mut Context) -> Result> { - let body = ctx.stack.pop_cont()?.as_ref().clone(); + let body = ctx.stack.pop_cont_owned()?; ctx.next = Some(Rc::new(cont::UntilCont { body: Some(body.clone()), after: ctx.next.take(), @@ -226,7 +226,7 @@ impl Control { } }; let word = ctx.stack.pop_string_owned()?; - let cont = ctx.stack.pop_cont()?.as_ref().clone(); + let cont = ctx.stack.pop_cont_owned()?; define_word(&mut ctx.dicts.current, word, cont, mode) } diff --git a/src/modules/dict_utils.rs b/src/modules/dict_utils.rs index e850859..7a3966e 100644 --- a/src/modules/dict_utils.rs +++ b/src/modules/dict_utils.rs @@ -169,7 +169,7 @@ impl DictUtils { #[cmd(name = "dictmapext", tail, args(ext = true, s = false))] #[cmd(name = "idictmapext", tail, args(ext = true, s = true))] fn interpret_dict_map(ctx: &mut Context, ext: bool, s: bool) -> Result> { - let func = ctx.stack.pop_cont()?.as_ref().clone(); + let func = ctx.stack.pop_cont_owned()?; let bits = ctx.stack.pop_smallint_range(0, MAX_KEY_BITS)? as u16; let cell = pop_maybe_cell(&mut ctx.stack)?; Ok(Some(Rc::new(LoopCont::new( @@ -190,7 +190,7 @@ impl DictUtils { #[cmd(name = "dictforeachrev", tail, args(r = true, s = false))] #[cmd(name = "idictforeachrev", tail, args(r = true, s = true))] fn interpret_dict_foreach(ctx: &mut Context, r: bool, s: bool) -> Result> { - let func = ctx.stack.pop_cont()?.as_ref().clone(); + let func = ctx.stack.pop_cont_owned()?; let bits = ctx.stack.pop_smallint_range(0, MAX_KEY_BITS)? as u16; let cell = pop_maybe_cell(&mut ctx.stack)?; Ok(Some(Rc::new(LoopCont::new( @@ -206,7 +206,7 @@ impl DictUtils { #[cmd(name = "dictmerge", tail)] fn interpret_dict_merge(ctx: &mut Context) -> Result> { - let func = ctx.stack.pop_cont()?.as_ref().clone(); + let func = ctx.stack.pop_cont_owned()?; let bits = ctx.stack.pop_smallint_range(0, MAX_KEY_BITS)? as u16; let right = pop_maybe_cell(&mut ctx.stack)?; let left = pop_maybe_cell(&mut ctx.stack)?; @@ -224,7 +224,7 @@ impl DictUtils { #[cmd(name = "dictdiff", tail)] fn interpret_dict_diff(ctx: &mut Context) -> Result> { - let func = ctx.stack.pop_cont()?.as_ref().clone(); + let func = ctx.stack.pop_cont_owned()?; let bits = ctx.stack.pop_smallint_range(0, MAX_KEY_BITS)? as u16; let right = pop_maybe_cell(&mut ctx.stack)?; let left = pop_maybe_cell(&mut ctx.stack)?; diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 4e9f111..342eece 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,3 +1,4 @@ +use std::iter::Peekable; use std::rc::Rc; use anyhow::{Context as _, Result}; @@ -250,6 +251,107 @@ impl FiftModule for BaseModule { stack.push(tuple) } + // === Hashmaps === + + #[cmd(name = "hmapnew", stack)] + fn interpret_hmap_new(stack: &mut Stack) -> Result<()> { + stack.push_null() + } + + #[cmd(name = "hmap@", stack, args(chk = false))] + #[cmd(name = "hmap@?", stack, args(chk = true))] + fn interpret_hmap_fetch(stack: &mut Stack, chk: bool) -> Result<()> { + let map = stack.pop_hashmap()?; + let key = HashMapTreeKey::new(stack.pop()?)?; + let value = HashMapTreeNode::lookup(&map, key).map(|node| node.value.clone()); + + let found = value.is_some(); + match value { + Some(value) => stack.push_raw(value)?, + None if !chk => stack.push_null()?, + _ => {} + } + if chk { + stack.push_bool(found)?; + } + Ok(()) + } + + #[cmd(name = "hmap-", stack, args(chk = false, read = false))] + #[cmd(name = "hmap-?", stack, args(chk = true, read = false))] + #[cmd(name = "hmap@-", stack, args(chk = false, read = true))] + fn interpret_hmap_delete(stack: &mut Stack, chk: bool, read: bool) -> Result<()> { + let mut map = stack.pop_hashmap()?; + let key = HashMapTreeKey::new(stack.pop()?)?; + let value = HashMapTreeNode::remove(&mut map, key); + stack.push_opt_raw(map)?; + + let exists = value.is_some(); + match value { + Some(value) if read => stack.push_raw(value)?, + None if read && !chk => stack.push_null()?, + _ => {} + } + if chk { + stack.push_bool(exists)?; + } + Ok(()) + } + + #[cmd(name = "hmap!", stack, args(add = false))] + #[cmd(name = "hmap!+", stack, args(add = true))] + fn interpret_hmap_store(stack: &mut Stack, add: bool) -> Result<()> { + let mut map = stack.pop_hashmap()?; + let key = HashMapTreeKey::new(stack.pop()?)?; + let value = stack.pop()?; + + if add { + HashMapTreeNode::set(&mut map, &key, &value); + } else { + HashMapTreeNode::replace(&mut map, key, &value); + } + stack.push_opt_raw(map) + } + + #[cmd(name = "hmapempty?", stack)] + fn interpret_hmap_is_empty(stack: &mut Stack) -> Result<()> { + let map = stack.pop_hashmap()?; + stack.push_bool(map.is_none()) + } + + #[cmd(name = "hmapunpack", stack)] + fn interpret_hmap_decompose(stack: &mut Stack) -> Result<()> { + let map = stack.pop_hashmap()?; + let not_empty = map.is_some(); + + if let Some(map) = map { + stack.push_raw(map.key.stack_value.clone())?; + stack.push_raw(map.value.clone())?; + stack.push_opt_raw(map.left.clone())?; + stack.push_opt_raw(map.right.clone())?; + } + + stack.push_bool(not_empty) + } + + #[cmd(name = "hmapforeach", tail)] + fn interpret_hmap_foreach(ctx: &mut Context) -> Result> { + let func = ctx.stack.pop_cont_owned()?; + let Some(map) = ctx.stack.pop_hashmap()? else { + return Ok(None); + }; + Ok(Some(Rc::new(cont::LoopCont::new( + HmapIterCont { + iter: map.owned_iter().peekable(), + ok: true, + }, + func, + ctx.next.take(), + )))) + } + + // === Environment === + #[cmd(name = "now")] fn interpret_now(ctx: &mut Context) -> Result<()> { ctx.stack.push_int(ctx.env.now_ms() / 1000) @@ -311,3 +413,32 @@ impl FiftModule for BaseModule { ctx.stack.push_bool(exists) } } + +#[derive(Clone)] +struct HmapIterCont { + iter: Peekable, + ok: bool, +} + +impl cont::LoopContImpl for HmapIterCont { + fn pre_exec(&mut self, ctx: &mut Context) -> Result { + let entry = match self.iter.next() { + Some(entry) => entry, + None => return Ok(false), + }; + + ctx.stack.push_raw(entry.key.stack_value.clone())?; + ctx.stack.push_raw(entry.value.clone())?; + Ok(true) + } + + fn post_exec(&mut self, ctx: &mut Context) -> Result { + self.ok = ctx.stack.pop_bool()?; + Ok(self.ok && self.iter.peek().is_some()) + } + + fn finalize(&mut self, ctx: &mut Context) -> Result { + ctx.stack.push_bool(self.ok)?; + Ok(true) + } +}