chiark / gitweb /
zcoord own module
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 9 Oct 2020 21:05:36 +0000 (22:05 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 9 Oct 2020 21:05:36 +0000 (22:05 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Cargo.lock.example
Cargo.toml
src/bigfloat.rs [deleted file]
src/commands.rs
src/gamestate.rs
src/imports.rs
src/lib.rs
zcoord/Cargo.toml
zcoord/src/lib.rs

index 16a6616f47fcdfe2e3bf64f93f33255142542176..eddddac4a9da8c2b31b9b783248d9eace895cf01 100644 (file)
@@ -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"
index 7aeb854ea0e2d02f169205c6b83be4a5f067a1ae..0ce65fa56b22fb540e7486fc6186a75add50be4d 100644 (file)
@@ -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 (file)
index f3588b8..0000000
+++ /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<RawLimbVal>;
-
-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<u8>;
-  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::<Header>();
-    let l_align = align_of::<Tail1>();
-    l_align * ((h_size + l_align - 1) / l_align)
-  };
-
-  fn layout(len: Taillen) -> (usize, Layout) {
-    let tail_nbytes : usize = size_of::<Tail1>() * (len as usize);
-    let all_nbytes = OFFSET + tail_nbytes;
-    let align = max(align_of::<Header>(), align_of::<Tail1>());
-    (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<F>(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<LimbVal>,
-}
-
-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(&lt[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<TryFromIntError> 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""#)?;
-    <Bigfloat as Display>::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<Ordering> {
-    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<S:Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
-    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")
-      ;
-  }
-}
index f2bf55a6feb94ad13b3ab5555eb993ae4c73a38b..f39e038af0933a7666ac12417f54f591d1a5b739 100644 (file)
@@ -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),
 }
index 15d5f1067f8a37985cf2050b1068c2e946bcbeae..9446a4e57fce8d5b3a0820cbe0f80fe65f091d22 100644 (file)
@@ -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);
index 6d93599b592f12794d3a94ee59f7ad5cdd8f27b0..8173b201cb8211ab5bcb64b7bfad5ec89bf37e4a 100644 (file)
@@ -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;
 
index fd0b9ec5c4d677e8fc46ebcd30d0c51f439c5a4b..e69492ef955565df7ff038386900182666f5337d 100644 (file)
@@ -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;
index 9da063f616fc0137564166cc23f380500c37b093..d23b6ce39bfab93d436eea9dfbe256e376ed806d 100644 (file)
@@ -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"
index 8337712ea57f00733b5709b27efb3de797e95575..3fe7096218f3f809b597967444679d31aec3bc59 100644 (file)
@@ -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<RawLimbVal>;
+
+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<u8>;
+  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::<Header>();
+    let l_align = align_of::<Tail1>();
+    l_align * ((h_size + l_align - 1) / l_align)
+  };
+
+  fn layout(len: Taillen) -> (usize, Layout) {
+    let tail_nbytes : usize = size_of::<Tail1>() * (len as usize);
+    let all_nbytes = OFFSET + tail_nbytes;
+    let align = max(align_of::<Header>(), align_of::<Tail1>());
+    (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<F>(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<LimbVal>,
+}
+
+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(&lt[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<TryFromIntError> 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""#)?;
+    <ZCoord as Display>::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<Ordering> {
+    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<S:Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+    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")
+      ;
+  }
+}