From 630470394eaff86df7cfd2c2d7b6567ea7b37eae Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Fri, 9 Oct 2020 22:05:36 +0100 Subject: [PATCH] zcoord own module Signed-off-by: Ian Jackson --- Cargo.lock.example | 6 + Cargo.toml | 3 +- src/bigfloat.rs | 402 -------------------------------------------- src/commands.rs | 2 +- src/gamestate.rs | 2 - src/imports.rs | 4 +- src/lib.rs | 1 - zcoord/Cargo.toml | 5 + zcoord/src/lib.rs | 410 ++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 426 insertions(+), 409 deletions(-) delete mode 100644 src/bigfloat.rs diff --git a/Cargo.lock.example b/Cargo.lock.example index 16a6616f..eddddac4 100644 --- a/Cargo.lock.example +++ b/Cargo.lock.example @@ -1095,6 +1095,7 @@ dependencies = [ "nix", "num-traits", "ordered-float", + "otter-zcoord", "percent-encoding 2.1.0", "pwd", "rand", @@ -1119,6 +1120,11 @@ dependencies = [ [[package]] name = "otter-zcoord" version = "0.0.1" +dependencies = [ + "fehler", + "serde", + "thiserror", +] [[package]] name = "pear" diff --git a/Cargo.toml b/Cargo.toml index 7aeb854e..0ce65fa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ version = "0.0.1" edition = "2018" [workspace] -members = ["zcoord"] [dependencies] #lazy_static = "1.0.0" @@ -23,6 +22,8 @@ members = ["zcoord"] # ^ this strange version + Cargo seems to make me have to specify it # exactly. futures-rs/Cargo.toml does it this way, so ok ? +otter-zcoord = { path = "zcoord" } + anyhow = "1" thiserror = "1" diff --git a/src/bigfloat.rs b/src/bigfloat.rs deleted file mode 100644 index f3588b8e..00000000 --- a/src/bigfloat.rs +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright 2020 Ian Jackson -// SPDX-License-Identifier: AGPL-3.0-or-later -// There is NO WARRANTY. - -// See bigfloat.ts - -use crate::imports::*; - -const BITS_PER_DIGIT : usize = 5; -const DIGITS_PER_LIMB : usize = 10; -const DEFAULT_TEXT : &[u8] = b"gggggggggg"; - -const DELTA : LimbVal = Wrapping(0x4000_0000); -const ZERO : LimbVal = Wrapping(0); -const ONE : LimbVal = Wrapping(1); - -const BITS_PER_LIMB : usize = BITS_PER_DIGIT * DIGITS_PER_LIMB; -const DIGIT_MASK : LimbVal = Wrapping((1u64 << BITS_PER_DIGIT) - 1); -const TEXT_PER_LIMB : usize = DIGITS_PER_LIMB + 1; -const LIMB_MODULUS : LimbVal = Wrapping(1u64 << BITS_PER_LIMB); -const LIMB_MASK : LimbVal = Wrapping((1u64 << BITS_PER_LIMB)-1); - -#[derive(Deserialize)] -#[serde(try_from="&str")] -pub struct Bigfloat(innards::Innards); - -type RawLimbVal = u64; -type LimbVal = Wrapping; - -mod innards { - use super::*; - use std::mem::{self, align_of, size_of}; - use std::ptr::{self, NonNull}; - use std::alloc::{self, Layout}; - use std::slice; - - unsafe impl Send for Bigfloat { } - unsafe impl Sync for Bigfloat { } - - pub(in super) type Innards = NonNull; - type Taillen = u16; - type Tail1 = u8; - - pub(in super) - struct Header { - pub taillen: u16, - } - - #[repr(C)] - #[allow(dead_code)] // this is for documentation purposes - struct Repr { - h: Header, - d: [Tail1], - } - - const OFFSET : usize = { - let h_size = size_of::
(); - let l_align = align_of::(); - l_align * ((h_size + l_align - 1) / l_align) - }; - - fn layout(len: Taillen) -> (usize, Layout) { - let tail_nbytes : usize = size_of::() * (len as usize); - let all_nbytes = OFFSET + tail_nbytes; - let align = max(align_of::
(), align_of::()); - (all_nbytes, Layout::from_size_align(all_nbytes, align).unwrap()) - } - - fn ptrs(p: *mut u8) -> (*mut Header, *mut Tail1) { unsafe { - let p_header : *mut Header = mem::transmute(p); - let p_tail : *mut Tail1 = mem::transmute(p.add(OFFSET)); - (p_header, p_tail) - } } - - impl Bigfloat { - unsafe fn alloc_unsafe(taillen: Taillen, f:F) -> Bigfloat - where F: FnOnce(*mut Tail1) - { - #[allow(unused_unsafe)] // unsafe block in unsafe fn - unsafe { - let p = alloc::alloc(layout(taillen).1); - let (p_header, p_tail) = ptrs(p); - ptr::write(p_header, Header { taillen }); - f(p_tail); - Bigfloat(NonNull::new(p).unwrap()) - } - } - - pub(in super) - fn alloc(taillen: Taillen) -> Bigfloat { - unsafe { - Bigfloat::alloc_unsafe(taillen, |nt : *mut Tail1| { - ptr::write_bytes(nt, 0, taillen as usize); - }) - } - } - - #[throws(Overflow)] - pub(in super) - fn alloc_copy(tail: &[Tail1]) -> Bigfloat { - let taillen = tail.len().try_into()?; - unsafe { - Bigfloat::alloc_unsafe(taillen, |nt : *mut Tail1| { - ptr::copy_nonoverlapping(tail.as_ptr(), nt, taillen as usize); - }) - } - } - - pub(in super) - fn tail(&self) -> &[Tail1] { - unsafe { - let (h, t) = ptrs(self.0.as_ptr()); - let h = h.as_ref().unwrap(); - slice::from_raw_parts(t, h.taillen as usize) - } - } - - pub(in super) - fn tail_mut(&mut self) -> &mut [Tail1] { - unsafe { - let (h, t) = ptrs(self.0.as_ptr()); - let h = h.as_ref().unwrap(); - slice::from_raw_parts_mut(t, h.taillen as usize) - } - } - - fn layout(&self) -> (usize, Layout) { - unsafe { - let (h, _) = ptrs(self.0.as_ptr()); - let h = h.as_ref().unwrap(); - let taillen = h.taillen; - layout(taillen) - } - } - } - - impl Drop for Bigfloat { - fn drop(&mut self) { - let layout = self.layout().1; - unsafe { - alloc::dealloc(self.0.as_mut(), layout); - } - } - } - - impl Clone for Bigfloat { - fn clone(&self) -> Bigfloat { - let (all_bytes, layout) = self.layout(); - unsafe { - let p = alloc::alloc(layout); - ptr::copy_nonoverlapping(self.0.as_ptr(), p, all_bytes); - Bigfloat(NonNull::new(p).unwrap()) - } - } - } - -} - -impl Default for Bigfloat { - fn default() -> Bigfloat { - Bigfloat::alloc_copy(DEFAULT_TEXT).unwrap() - } -} - -#[derive(Clone,Debug)] -pub struct Mutable { - limbs: Vec, -} - -impl TryFrom<&Mutable> for Bigfloat { - type Error = Overflow; - #[throws(Overflow)] - fn try_from(m: &Mutable) -> Bigfloat { - let taillen = (m.limbs.len() * TEXT_PER_LIMB - 1).try_into()?; - let mut bf = Bigfloat::alloc(taillen); - let mut w = bf.tail_mut(); - for mut l in m.limbs.iter().cloned() { - if l >= LIMB_MODULUS { throw!(Overflow) }; - for p in w[0..DIGITS_PER_LIMB].rchunks_exact_mut(1) { - let v = (l & DIGIT_MASK).0 as u8; - p[0] = if v < 10 { b'0' + v } else { (b'a' - 10) + v }; - l >>= BITS_PER_DIGIT; - } - if let Some(p) = w.get_mut(DIGITS_PER_LIMB) { - *p = b'_'; - } else { - break; - } - w = &mut w[TEXT_PER_LIMB..]; - } - bf - } -} - -impl Bigfloat { - #[throws(as Option)] - pub fn from_str(s: &str) -> Self { - let s = s.as_bytes(); - let nomlen = s.len() + 1; - if nomlen % TEXT_PER_LIMB !=0 { None? } - for lt in s.chunks(TEXT_PER_LIMB) { - if !lt[0..DIGITS_PER_LIMB].iter().all( - |c: &u8| { - (b'0'..=b'9').contains(&c) || - (b'a'..=b'v').contains(&c) - }) { None? } - match lt[DIGITS_PER_LIMB..] { [] | [b'_'] => (), _ => None? }; - } - if &s[s.len() - DIGITS_PER_LIMB.. ] == b"0000000000" { None? } - Bigfloat::alloc_copy(s).ok()? - } - - pub fn clone_mut(&self) -> Mutable { - let tail = self.tail(); - let nlimbs = (tail.len() + 1) / TEXT_PER_LIMB; - let mut limbs = Vec::with_capacity(nlimbs+2); - for lt in tail.chunks(TEXT_PER_LIMB) { - let s = str::from_utf8(<[0..DIGITS_PER_LIMB]).unwrap(); - let v = RawLimbVal::from_str_radix(s, 1 << BITS_PER_DIGIT).unwrap(); - limbs.push(Wrapping(v)); - } - Mutable { limbs } - } - - pub fn as_str(&self) -> &str { - let tail = self.tail(); - str::from_utf8(tail).unwrap() - } - - pub fn to_string(&self) -> String { - self.as_str().to_string() - } -} - -#[derive(Error,Debug,Copy,Clone,Serialize,Deserialize)] -pub struct Overflow; -display_as_debug!(Overflow); - -impl From for Overflow { - fn from(_: TryFromIntError) -> Overflow { Overflow } -} - -impl Mutable { - #[throws(Overflow)] - pub fn increment(&mut self) -> Bigfloat { - 'attempt: loop { - let mut i = self.limbs.len() - 1; - let mut delta = DELTA; - - if (||{ - loop { - let nv = self.limbs[i] + delta; - self.limbs[i] = nv & LIMB_MASK; - if nv < LIMB_MODULUS { return Some(()) } - if i == 0 { return None } - i -= 1; - delta = ONE; - } - })() == Some(()) { break 'attempt } - - // undo - loop { - if i >= self.limbs.len() { break } - else if i == self.limbs.len()-1 { delta = DELTA; } - let nv = self.limbs[i] - delta; - self.limbs[i] = nv & LIMB_MASK; - i += 1; - } - self.limbs.push(ZERO); - self.limbs.push(ZERO); - } - self.repack()? - } - - #[throws(Overflow)] - pub fn repack(&self) -> Bigfloat { self.try_into()? } -} - -impl Display for Bigfloat { - #[throws(fmt::Error)] - fn fmt(&self, f: &mut Formatter) { - write!(f, "{}", self.as_str())? - } -} -impl Debug for Bigfloat { - #[throws(fmt::Error)] - fn fmt(&self, f: &mut Formatter) { - write!(f, r#"Bf""#)?; - ::fmt(self, f)?; - write!(f, r#"""#)?; - } -} - -impl Ord for Bigfloat { - fn cmp(&self, other: &Bigfloat) -> Ordering { - let at = self.tail(); - let bt = other.tail(); - at.cmp(bt) - } -} -impl PartialOrd for Bigfloat { - fn partial_cmp(&self, other: &Bigfloat) -> Option { - Some(self.cmp(other)) - } -} -impl Eq for Bigfloat { -} -impl PartialEq for Bigfloat { - fn eq(&self, other: &Bigfloat) -> bool { - self.cmp(other) == Ordering::Equal - } -} - -#[derive(Error,Clone,Copy,Debug)] -#[error("error parsing bigfloat (z value)")] -pub struct ParseError; - -impl TryFrom<&str> for Bigfloat { - type Error = ParseError; - #[throws(ParseError)] - fn try_from(s: &str) -> Bigfloat { - Bigfloat::from_str(s).ok_or(ParseError)? - } -} - -impl Serialize for Bigfloat { - fn serialize(&self, s: S) -> Result { - s.serialize_str(self.as_str()) - } -} - -#[cfg(test)] -mod test { - // everything from here on is seded by the js test extractor! - use super::*; - - fn bf(s: &str) -> Bigfloat { - Bigfloat::from_str(s).unwrap() - } - - #[test] - fn bfparse() { - let s = "gg0123abcd_0123456789"; - let b = Bigfloat::from_str(s).unwrap(); - let b2 = b.clone(); - assert_eq!(format!("{}", &b), s); - assert_eq!(format!("{}", &b.clone_mut().repack().unwrap()), s); - mem::drop(b); - assert_eq!(format!("{}", &b2), s); - assert_eq!(format!("{:?}", &b2), - format!(r#"Bf"{}""#, &b2)); - fn bad(s: &str) { assert_eq!(None, Bigfloat::from_str(s)); } - bad(""); - bad("0"); - bad("0000000000"); - bad("0000000000_0000000000"); - bad("aaaaaaaa0_aaaaaaaa00"); - bad("aaaaaaaa0_aaaaaaaa00"); - bad("aaaaaaaa00_aaaaaaaa0"); - bad("#aaaaaaaa0_aaaaaaaa00"); - bad("aaaaaaaa0#_aaaaaaaa00"); - bad("aaaaaaaa00#aaaaaaaa00"); - bad("aaaaaaaa00_aaaaaaaa0#"); - bad("Zaaaaaaaa0_#aaaaaaaa0"); - bad("Aaaaaaaaa0_#aaaaaaaa0"); - bad("waaaaaaaa0_#aaaaaaaa0"); - bad("/aaaaaaaa0_#aaaaaaaa0"); - bad(":aaaaaaaa0_#aaaaaaaa0"); - bad("`aaaaaaaa0_#aaaaaaaa0"); - } - - #[test] - fn equality() { - assert!( bf("gg0123abcd_0123456789") < - bf("gg0123abcd_012345678a") ); - - assert!( bf("gg0123abcd") < - bf("gg0123abcd_012345678a") ); - } - - #[test] - fn addition() { - fn mk(s: &str) -> super::Mutable { bf(s).clone_mut() } - impl Mutable { - fn tinc(mut self, exp: &str) -> Self { - let got = self.increment().unwrap(); - assert_eq!(got.to_string(), exp); - self - } - }/* - mk("000000000a") - .tinc("000100000a") - .tinc("000200000a") - ;*/ - mk("vvvvvvvvvv") - .tinc("vvvvvvvvvv_0000000000_0001000000") - ; - mk("vvvvvvvvvv_vvvvvvvvvv_vvvvv01234") - .tinc("vvvvvvvvvv_vvvvvvvvvv_vvvvv01234_0000000000_0001000000") - ; - } -} diff --git a/src/commands.rs b/src/commands.rs index f2bf55a6..f39e038a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -92,7 +92,7 @@ pub enum MgmtError { PieceNotFound, LimitExceeded, ServerFailure(String), - ZCoordinateOverflow(#[from] bigfloat::Overflow), + ZCoordinateOverflow(#[from] zcoord::Overflow), BadGlob { pat: String, msg: String }, BadSpec(#[from] SpecError), } diff --git a/src/gamestate.rs b/src/gamestate.rs index 15d5f106..9446a4e5 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -19,8 +19,6 @@ pub struct Generation (pub u64); visible_slotmap_key!{ VisiblePieceId('.') } -pub type ZCoord = Bigfloat; - #[derive(Clone,Serialize,Deserialize,Eq,Ord,PartialEq,PartialOrd)] #[serde(transparent)] pub struct Html (pub String); diff --git a/src/imports.rs b/src/imports.rs index 6d93599b..8173b201 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -111,7 +111,9 @@ pub use crate::utils::*; pub use crate::spec::*; pub use crate::debugreader::DebugReader; pub use crate::shapelib; -pub use crate::bigfloat::{self, Bigfloat}; + +pub use otter_zcoord as zcoord; +pub use zcoord::ZCoord; pub use nix::unistd::Uid; diff --git a/src/lib.rs b/src/lib.rs index fd0b9ec5..e69492ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,5 +24,4 @@ pub mod utils; pub mod mgmtchannel; pub mod debugreader; pub mod shapelib; -pub mod bigfloat; #[path="slotmap-slot-idx.rs"] pub mod slotmap_slot_idx; diff --git a/zcoord/Cargo.toml b/zcoord/Cargo.toml index 9da063f6..d23b6ce3 100644 --- a/zcoord/Cargo.toml +++ b/zcoord/Cargo.toml @@ -7,3 +7,8 @@ license = "AGPL-3.0-or-later" version = "0.0.1" edition = "2018" +[dependencies] + +serde = { version = "1", features = ["derive","rc"] } +fehler = "1" +thiserror = "1" diff --git a/zcoord/src/lib.rs b/zcoord/src/lib.rs index 8337712e..3fe70962 100644 --- a/zcoord/src/lib.rs +++ b/zcoord/src/lib.rs @@ -1 +1,409 @@ -// +// Copyright 2020 Ian Jackson +// SPDX-License-Identifier: AGPL-3.0-or-later +// There is NO WARRANTY. + +// See zcoord.ts + +use std::cmp::{Ordering, max}; +use std::convert::{TryFrom, TryInto}; +use std::fmt::{self, Debug, Display, Formatter}; +use std::num::{TryFromIntError, Wrapping}; +use std::str; +use fehler::{throw, throws}; +use serde::{Serialize, Serializer, Deserialize}; +use thiserror::Error; + +const BITS_PER_DIGIT : usize = 5; +const DIGITS_PER_LIMB : usize = 10; +const DEFAULT_TEXT : &[u8] = b"gggggggggg"; + +const DELTA : LimbVal = Wrapping(0x4000_0000); +const ZERO : LimbVal = Wrapping(0); +const ONE : LimbVal = Wrapping(1); + +const BITS_PER_LIMB : usize = BITS_PER_DIGIT * DIGITS_PER_LIMB; +const DIGIT_MASK : LimbVal = Wrapping((1u64 << BITS_PER_DIGIT) - 1); +const TEXT_PER_LIMB : usize = DIGITS_PER_LIMB + 1; +const LIMB_MODULUS : LimbVal = Wrapping(1u64 << BITS_PER_LIMB); +const LIMB_MASK : LimbVal = Wrapping((1u64 << BITS_PER_LIMB)-1); + +#[derive(Deserialize)] +#[serde(try_from="&str")] +pub struct ZCoord(innards::Innards); + +type RawLimbVal = u64; +type LimbVal = Wrapping; + +mod innards { + use super::*; + use std::mem::{self, align_of, size_of}; + use std::ptr::{self, NonNull}; + use std::alloc::{self, Layout}; + use std::slice; + + unsafe impl Send for ZCoord { } + unsafe impl Sync for ZCoord { } + + pub(in super) type Innards = NonNull; + type Taillen = u16; + type Tail1 = u8; + + pub(in super) + struct Header { + pub taillen: u16, + } + + #[repr(C)] + #[allow(dead_code)] // this is for documentation purposes + struct Repr { + h: Header, + d: [Tail1], + } + + const OFFSET : usize = { + let h_size = size_of::
(); + let l_align = align_of::(); + l_align * ((h_size + l_align - 1) / l_align) + }; + + fn layout(len: Taillen) -> (usize, Layout) { + let tail_nbytes : usize = size_of::() * (len as usize); + let all_nbytes = OFFSET + tail_nbytes; + let align = max(align_of::
(), align_of::()); + (all_nbytes, Layout::from_size_align(all_nbytes, align).unwrap()) + } + + fn ptrs(p: *mut u8) -> (*mut Header, *mut Tail1) { unsafe { + let p_header : *mut Header = mem::transmute(p); + let p_tail : *mut Tail1 = mem::transmute(p.add(OFFSET)); + (p_header, p_tail) + } } + + impl ZCoord { + unsafe fn alloc_unsafe(taillen: Taillen, f:F) -> ZCoord + where F: FnOnce(*mut Tail1) + { + #[allow(unused_unsafe)] // unsafe block in unsafe fn + unsafe { + let p = alloc::alloc(layout(taillen).1); + let (p_header, p_tail) = ptrs(p); + ptr::write(p_header, Header { taillen }); + f(p_tail); + ZCoord(NonNull::new(p).unwrap()) + } + } + + pub(in super) + fn alloc(taillen: Taillen) -> ZCoord { + unsafe { + ZCoord::alloc_unsafe(taillen, |nt : *mut Tail1| { + ptr::write_bytes(nt, 0, taillen as usize); + }) + } + } + + #[throws(Overflow)] + pub(in super) + fn alloc_copy(tail: &[Tail1]) -> ZCoord { + let taillen = tail.len().try_into()?; + unsafe { + ZCoord::alloc_unsafe(taillen, |nt : *mut Tail1| { + ptr::copy_nonoverlapping(tail.as_ptr(), nt, taillen as usize); + }) + } + } + + pub(in super) + fn tail(&self) -> &[Tail1] { + unsafe { + let (h, t) = ptrs(self.0.as_ptr()); + let h = h.as_ref().unwrap(); + slice::from_raw_parts(t, h.taillen as usize) + } + } + + pub(in super) + fn tail_mut(&mut self) -> &mut [Tail1] { + unsafe { + let (h, t) = ptrs(self.0.as_ptr()); + let h = h.as_ref().unwrap(); + slice::from_raw_parts_mut(t, h.taillen as usize) + } + } + + fn layout(&self) -> (usize, Layout) { + unsafe { + let (h, _) = ptrs(self.0.as_ptr()); + let h = h.as_ref().unwrap(); + let taillen = h.taillen; + layout(taillen) + } + } + } + + impl Drop for ZCoord { + fn drop(&mut self) { + let layout = self.layout().1; + unsafe { + alloc::dealloc(self.0.as_mut(), layout); + } + } + } + + impl Clone for ZCoord { + fn clone(&self) -> ZCoord { + let (all_bytes, layout) = self.layout(); + unsafe { + let p = alloc::alloc(layout); + ptr::copy_nonoverlapping(self.0.as_ptr(), p, all_bytes); + ZCoord(NonNull::new(p).unwrap()) + } + } + } + +} + +impl Default for ZCoord { + fn default() -> ZCoord { + ZCoord::alloc_copy(DEFAULT_TEXT).unwrap() + } +} + +#[derive(Clone,Debug)] +pub struct Mutable { + limbs: Vec, +} + +impl TryFrom<&Mutable> for ZCoord { + type Error = Overflow; + #[throws(Overflow)] + fn try_from(m: &Mutable) -> ZCoord { + let taillen = (m.limbs.len() * TEXT_PER_LIMB - 1).try_into()?; + let mut bf = ZCoord::alloc(taillen); + let mut w = bf.tail_mut(); + for mut l in m.limbs.iter().cloned() { + if l >= LIMB_MODULUS { throw!(Overflow) }; + for p in w[0..DIGITS_PER_LIMB].rchunks_exact_mut(1) { + let v = (l & DIGIT_MASK).0 as u8; + p[0] = if v < 10 { b'0' + v } else { (b'a' - 10) + v }; + l >>= BITS_PER_DIGIT; + } + if let Some(p) = w.get_mut(DIGITS_PER_LIMB) { + *p = b'_'; + } else { + break; + } + w = &mut w[TEXT_PER_LIMB..]; + } + bf + } +} + +impl ZCoord { + #[throws(as Option)] + pub fn from_str(s: &str) -> Self { + let s = s.as_bytes(); + let nomlen = s.len() + 1; + if nomlen % TEXT_PER_LIMB !=0 { None? } + for lt in s.chunks(TEXT_PER_LIMB) { + if !lt[0..DIGITS_PER_LIMB].iter().all( + |c: &u8| { + (b'0'..=b'9').contains(&c) || + (b'a'..=b'v').contains(&c) + }) { None? } + match lt[DIGITS_PER_LIMB..] { [] | [b'_'] => (), _ => None? }; + } + if &s[s.len() - DIGITS_PER_LIMB.. ] == b"0000000000" { None? } + ZCoord::alloc_copy(s).ok()? + } + + pub fn clone_mut(&self) -> Mutable { + let tail = self.tail(); + let nlimbs = (tail.len() + 1) / TEXT_PER_LIMB; + let mut limbs = Vec::with_capacity(nlimbs+2); + for lt in tail.chunks(TEXT_PER_LIMB) { + let s = str::from_utf8(<[0..DIGITS_PER_LIMB]).unwrap(); + let v = RawLimbVal::from_str_radix(s, 1 << BITS_PER_DIGIT).unwrap(); + limbs.push(Wrapping(v)); + } + Mutable { limbs } + } + + pub fn as_str(&self) -> &str { + let tail = self.tail(); + str::from_utf8(tail).unwrap() + } + + pub fn to_string(&self) -> String { + self.as_str().to_string() + } +} + +#[derive(Error,Debug,Copy,Clone,Serialize,Deserialize)] +#[error("Z coordinate overflow")] +pub struct Overflow; + +impl From for Overflow { + fn from(_: TryFromIntError) -> Overflow { Overflow } +} + +impl Mutable { + #[throws(Overflow)] + pub fn increment(&mut self) -> ZCoord { + 'attempt: loop { + let mut i = self.limbs.len() - 1; + let mut delta = DELTA; + + if (||{ + loop { + let nv = self.limbs[i] + delta; + self.limbs[i] = nv & LIMB_MASK; + if nv < LIMB_MODULUS { return Some(()) } + if i == 0 { return None } + i -= 1; + delta = ONE; + } + })() == Some(()) { break 'attempt } + + // undo + loop { + if i >= self.limbs.len() { break } + else if i == self.limbs.len()-1 { delta = DELTA; } + let nv = self.limbs[i] - delta; + self.limbs[i] = nv & LIMB_MASK; + i += 1; + } + self.limbs.push(ZERO); + self.limbs.push(ZERO); + } + self.repack()? + } + + #[throws(Overflow)] + pub fn repack(&self) -> ZCoord { self.try_into()? } +} + +impl Display for ZCoord { + #[throws(fmt::Error)] + fn fmt(&self, f: &mut Formatter) { + write!(f, "{}", self.as_str())? + } +} +impl Debug for ZCoord { + #[throws(fmt::Error)] + fn fmt(&self, f: &mut Formatter) { + write!(f, r#"Bf""#)?; + ::fmt(self, f)?; + write!(f, r#"""#)?; + } +} + +impl Ord for ZCoord { + fn cmp(&self, other: &ZCoord) -> Ordering { + let at = self.tail(); + let bt = other.tail(); + at.cmp(bt) + } +} +impl PartialOrd for ZCoord { + fn partial_cmp(&self, other: &ZCoord) -> Option { + Some(self.cmp(other)) + } +} +impl Eq for ZCoord { +} +impl PartialEq for ZCoord { + fn eq(&self, other: &ZCoord) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +#[derive(Error,Clone,Copy,Debug)] +#[error("error parsing zcoord (z value)")] +pub struct ParseError; + +impl TryFrom<&str> for ZCoord { + type Error = ParseError; + #[throws(ParseError)] + fn try_from(s: &str) -> ZCoord { + ZCoord::from_str(s).ok_or(ParseError)? + } +} + +impl Serialize for ZCoord { + fn serialize(&self, s: S) -> Result { + s.serialize_str(self.as_str()) + } +} + +#[cfg(test)] +mod test { + // everything from here on is seded by the js test extractor! + use super::*; + + fn bf(s: &str) -> ZCoord { + ZCoord::from_str(s).unwrap() + } + + #[test] + fn bfparse() { + let s = "gg0123abcd_0123456789"; + let b = ZCoord::from_str(s).unwrap(); + let b2 = b.clone(); + assert_eq!(format!("{}", &b), s); + assert_eq!(format!("{}", &b.clone_mut().repack().unwrap()), s); + mem::drop(b); + assert_eq!(format!("{}", &b2), s); + assert_eq!(format!("{:?}", &b2), + format!(r#"Bf"{}""#, &b2)); + fn bad(s: &str) { assert_eq!(None, ZCoord::from_str(s)); } + bad(""); + bad("0"); + bad("0000000000"); + bad("0000000000_0000000000"); + bad("aaaaaaaa0_aaaaaaaa00"); + bad("aaaaaaaa0_aaaaaaaa00"); + bad("aaaaaaaa00_aaaaaaaa0"); + bad("#aaaaaaaa0_aaaaaaaa00"); + bad("aaaaaaaa0#_aaaaaaaa00"); + bad("aaaaaaaa00#aaaaaaaa00"); + bad("aaaaaaaa00_aaaaaaaa0#"); + bad("Zaaaaaaaa0_#aaaaaaaa0"); + bad("Aaaaaaaaa0_#aaaaaaaa0"); + bad("waaaaaaaa0_#aaaaaaaa0"); + bad("/aaaaaaaa0_#aaaaaaaa0"); + bad(":aaaaaaaa0_#aaaaaaaa0"); + bad("`aaaaaaaa0_#aaaaaaaa0"); + } + + #[test] + fn equality() { + assert!( bf("gg0123abcd_0123456789") < + bf("gg0123abcd_012345678a") ); + + assert!( bf("gg0123abcd") < + bf("gg0123abcd_012345678a") ); + } + + #[test] + fn addition() { + fn mk(s: &str) -> super::Mutable { bf(s).clone_mut() } + impl Mutable { + fn tinc(mut self, exp: &str) -> Self { + let got = self.increment().unwrap(); + assert_eq!(got.to_string(), exp); + self + } + }/* + mk("000000000a") + .tinc("000100000a") + .tinc("000200000a") + ;*/ + mk("vvvvvvvvvv") + .tinc("vvvvvvvvvv_0000000000_0001000000") + ; + mk("vvvvvvvvvv_vvvvvvvvvv_vvvvv01234") + .tinc("vvvvvvvvvv_vvvvvvvvvv_vvvvv01234_0000000000_0001000000") + ; + } +} -- 2.30.2