YOUD2UWO6XXL3CV6UCF7VDY2SV4VZOALPQNUCQMK37ITYO6YEYAAC
delimiter $ ( [ { ~ $
$ } ] ) , $;
--| Well formed formulas, or propositions - true or false.
strict provable sort wff;
--| Implication: if p, then q.
term im (p q: wff): wff; infixr im: $->$ prec 25;
--| Negation: p is false.
term not (p: wff): wff; prefix not: $~$ prec 41;
--| Axiom 1 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_1 (a b: wff): $ a -> b -> a $;
--| Axiom 2 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_2 (a b c: wff): $ (a -> b -> c) -> (a -> b) -> a -> c $;
--| Axiom 3 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_3 (a b: wff): $ (~a -> ~b) -> b -> a $;
--| Modus ponens: from `a -> b` and `a`, infer `b`.
axiom ax_mp (a b: wff): $ a -> b $ > $ a $ > $ b $;
delimiter $ ( ) ~ { } , $;
--| Well formed formulas, or propositions - true or false.
strict provable sort wff;
--| Implication: if p, then q.
term im (p q: wff): wff; infixr im: $->$ prec 25;
--| Negation: p is false.
term not (p: wff): wff; prefix not: $~$ prec 41;
--| Axiom 1 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_1 (a b: wff): $ a -> b -> a $;
--| Axiom 2 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_2 (a b c: wff): $ (a -> b -> c) -> (a -> b) -> a -> c $;
--| Axiom 3 of Lukasiewicz' axioms for classical propositional logic.
axiom ax_3 (a b: wff): $ (~a -> ~b) -> b -> a $;
--| Modus ponens: from `a -> b` and `a`, infer `b`.
axiom ax_mp (a b: wff): $ a -> b $ > $ a $ > $ b $;
macro_rules! bin_parser {
( $(($name: ident, $t:ident))* ) => {
$(
pub fn $name(source: &[u8]) -> Res<($t, &[u8])> {
let int_bytes =
source
.get(0..std::mem::size_of::<$t>())
.ok_or_else(|| {
VerifErr::Msg(format!("binary parser ran out of input @ {}: {}", file!(), line!()))
})?;
let new_pos = std::mem::size_of::<$t>();
Ok(
($t::from_le_bytes(int_bytes.try_into().unwrap()), &source[new_pos..])
)
}
)*
};
}
bin_parser! {
(parse_u8, u8)
(parse_u16, u16)
(parse_u32, u32)
(parse_u64, u64)
}
macro_rules! bitwise_inner {
( $($t:ident),* ) => {
$(
impl std::ops::BitAnd<$t> for $t {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
$t { inner: self.inner & rhs.inner }
}
}
impl std::ops::BitAndAssign<$t> for $t {
fn bitand_assign(&mut self, other: Self) {
self.inner &= other.inner
}
}
impl std::ops::BitOr<$t> for $t {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
$t { inner: self.inner | rhs.inner }
}
}
impl std::ops::BitOrAssign<$t> for $t {
fn bitor_assign(&mut self, other: Self) {
self.inner |= other.inner
}
}
impl std::ops::Not for $t {
type Output = $t;
fn not(self) -> Self::Output {
$t { inner: !self.inner }
}
}
)*
}
}
bitwise_inner!(Type, Mods);
}
pub type Arg = Type;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Type {
pub inner: u64
}
impl std::default::Default for Type {
fn default() -> Self {
Type { inner: 0 }
}
}
impl Type {
pub fn high_bit(self) -> Self {
Type { inner: self.inner & (!crate::mmb::TYPE_DEPS_MASK) }
}
pub fn disjoint(self, other: Self) -> bool {
(self.inner & other.inner) == 0
}
pub fn new_bound() -> Self {
Type { inner: 1 << 63 }
}
pub fn new(bound: bool) -> Self {
if bound {
Type { inner: 1 << 63 }
} else {
Type { inner: 0 }
}
}
pub fn new_with_sort(sort_num: u8) -> Self {
Type { inner: (sort_num as u64) << 56 }
}
#[inline]
pub fn is_bound(self) -> bool {
self.inner & (1 << 63) != 0
}
pub fn add_sort(&mut self, sort_id: u8) {
// clear existing sort if any;
self.inner &= TYPE_SORT_MASK;
// Add new sort
self.inner |= ((sort_id as u64) << 56);
}
/// Add a dependency on bv_idx
pub fn add_dep(&mut self, bv_idx: u64) {
self.inner |= 1 << bv_idx.checked_sub(1).expect("No 0th bv to depend on")
}
pub fn sort(self) -> u8 {
u8::try_from((self.inner >> 56) & 0x7F).unwrap()
}
pub fn depends_on_(self, j: u64) -> bool {
self.inner & (1u64 << j.checked_sub(1).expect("No 0th to depend on")) != 0
}
/// If this is the type of a bound variable, return a u64
/// whose only activated bit is the bit indicating which bv
/// it is.
pub fn bound_digit(self) -> Res<u64> {
if self.is_bound() {
Ok(self.inner & !(0xFF << 56))
} else {
Err(VerifErr::Msg(format!("Not bound")))
}
}
/// The POSITION (1 - 55) of this bound variable, NOT the literal u64.
pub fn bound_pos(self) -> Res<u64> {
if self.is_bound() {
for i in 0..56 {
if ((1 << i) & self.inner) != 0 {
return Ok(i + 1)
}
}
Err(VerifErr::Msg(format!("should be unreachable")))
} else {
Err(VerifErr::Msg(format!("Not bound")))
}
}
pub fn has_deps(self) -> bool {
if self.is_bound() {
false
} else {
(self.inner & crate::mmb::TYPE_DEPS_MASK) > 0
}
}
/// If this is a regular/not-bound variable, return a u64
/// whose only activated bits are the ones marking the bvs
/// on which it depends.
pub fn deps(self) -> Res<u64> {
if !self.is_bound() {
Ok(self.inner & !(0xFF << 56))
} else {
Err(VerifErr::Msg(format!("No deps")))
}
}
}
#[test]
fn bound1() {
let mut t1 = Type::new_bound();
t1 |= Type { inner: (1 << 15) };
assert_eq!(t1.bound_pos().unwrap(), 16);
}
#[test]
fn bound2() {
let mut t1 = Type::new_bound();
t1 |= Type { inner: (1 << 0) };
assert_eq!(t1.bound_pos().unwrap(), 1);
}
#[test]
fn bound3() {
let mut t1 = Type::new_bound();
t1 |= Type { inner: (1 << 55) };
assert_eq!(t1.bound_pos().unwrap(), 56);
}
#[test]
fn bound_err1() {
let t1 = Type::new_bound();
assert!(t1.bound_pos().is_err())
}
#[test]
fn bound_err2() {
let t1 = Type::new(false);
assert!(t1.bound_pos().is_err())
assert!(!t0.has_deps());
assert!(!t1.has_deps());
assert!(t2.has_deps());
assert!(!t3.has_deps());
assert_eq!(t2.deps().unwrap(), 1);
t1.add_dep(1);
t1.add_dep(2);
t1.add_dep(4);
assert!(t1.has_deps());
assert_eq!(t1.deps().unwrap(), 11);
t1_v.add_dep(4);
t1_v.add_dep(1);
t1_v.add_dep(2);
assert!(t1_v.has_deps());
assert_eq!(t1_v.deps().unwrap(), 11);
println!("t3: {:?}", t3);
let bound_1 = Type { inner: Type::new_bound().inner | (1 << 8) };
println!("bound idx: {:?}", bound_1.bound_digit());
assert!(bound_1.is_bound());
}
}
}
}
/// A reference to an entry in the term table.
#[derive(Debug, Clone, Copy)]
pub struct Term<'a> {
/// The sort of the term.
pub term_num: u32,
pub sort: u8,
/// Number of args NOT including `ret`
pub args_start: &'a [u8],
// The pointer to the start of the unify stream.
pub unify: UnifyIter<'a>,
}
/// An iterator over the arguments of a term/assertion that doesn't require allocation
/// of a vector, instead reading them from the source file each time.
#[derive(Debug, Clone, Copy)]
pub struct Args<'a> {
source: &'a [u8],
}
impl<'a> DoubleEndedIterator for Args<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.source.is_empty() {
None
} else {
let split = self.source.len().checked_sub(8)?;
let (lhs, rhs) = self.source.split_at(split);
self.source = lhs;
match parse_u64(rhs) {
Err(_) => unreachable!("Should have been guaranteed by check in `prefix_args`"),
Ok((parsed, rest)) => {
assert!(rest.is_empty());
Some(Type { inner: parsed })
}
}
}
}
}
impl<'a> ExactSizeIterator for Args<'a> {
fn len(&self) -> usize {
self.source.len() / std::mem::size_of::<u64>()
}
}
impl<'a> Iterator for Args<'a> {
type Item = Arg;
fn next(&mut self) -> Option<Self::Item> {
if self.source.is_empty() {
return None
}
match parse_u64(self.source) {
Err(_) => unreachable!("Should have been guaranteed by check in `prefix_args`"),
Ok((parsed, rest)) => {
self.source = rest;
Some(Type { inner: parsed })
}
}
}
// Needed for `len` to work correctly.
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.source.len() / std::mem::size_of::<u64>(),
Some(self.source.len() / std::mem::size_of::<u64>())
)
}
}
#[test]
fn args_back1() {
let s1 = &[
10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33
];
let s2 = &[
10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25,
];
let s3 = &[
10, 11, 12, 13, 14, 15, 16, 17,
];
let tgt1 = [26, 27, 28, 29, 30, 31, 32, 33];
let tgt2 = [18, 19, 20, 21, 22, 23, 24, 25];
let tgt3 = [10, 11, 12, 13, 14, 15, 16, 17];
let mut args = Args { source: s1 };
let back = args.next_back().unwrap();
assert_eq!(back.inner, u64::from_le_bytes(tgt1));
assert_eq!(args.source, s2);
let back = args.next_back().unwrap();
assert_eq!(back.inner, u64::from_le_bytes(tgt2));
assert_eq!(args.source, s3);
let back = args.next_back().unwrap();
assert_eq!(back.inner, u64::from_le_bytes(tgt3));
assert!(args.source.is_empty());
}
#[test]
fn args_back_err1() {
let s1 = &[10, 11, 12, 13, 14, 15, 16];
let mut args = Args { source: s1 };
let back = args.next_back();
assert!(back.is_none());
}
#[test]
fn args_no_ret1() {
let fake_unify = UnifyIter { buf: &[], pos: 0 };
let s1 = &[
10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33
];
let t = Term { term_num: 0, sort: 0, args_start: s1, unify: fake_unify };
let s2 = &[
10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25,
];
let args_no_ret = t.args_no_ret();
assert_eq!(args_no_ret.source, s2);
}
#[test]
fn args_no_ret2() {
let fake_unify = UnifyIter { buf: &[], pos: 0 };
let s1 = &[
10, 11, 12, 13, 14, 15, 16, 17,
];
let t = Term { term_num: 0, sort: 0, args_start: s1, unify: fake_unify };
let args_no_ret = t.args_no_ret();
assert_eq!(args_no_ret.source, &[]);
}
impl<'a> Term<'a> {
/// Returns true if this is a `def`, false for a `term`.
#[inline]
pub fn is_def(&self) -> bool {
self.sort & 0x80 != 0
}
/// The return sort of this term/def.
#[inline]
pub fn sort(&self) -> SortNum {
self.sort & 0x7F
}
/// args; including the `ret` element at the end.
#[inline]
pub fn args(&self) -> Args<'a> {
Args {
source: self.args_start,
}
}
/// args without the `ret` element at the end.
/// This will panic if there are less than 8 bytes in `source`,
/// which seems fine for now, since that would mean we had an invalid `Args`
/// to start with.
#[inline]
pub fn args_no_ret(&self) -> Args<'a> {
Args {
source: &self.args_start[..(self.args_start.len() - std::mem::size_of::<u64>())],
}
}
pub fn num_args_no_ret(&self) -> u16 {
u16::try_from(self.args().len().checked_sub(1).unwrap()).unwrap()
}
/// The return sort and dependencies.
pub fn ret(&self) -> Type {
self.args().last().unwrap()
}
/// The beginning of the unify stream for the term.
#[inline]
pub fn unify(&self) -> UnifyIter<'_> {
self.unify.clone()
}
}
/// A reference to an entry in the theorem table.
#[derive(Debug, Clone, Copy)]
pub struct Assert<'a> {
/// The array of arguments.
// `Arg` is a u64
pub assert_num: u32,
pub args_start: &'a [u8],
/// The pointer to the start of the unify stream.
pub unify: UnifyIter<'a>,
}
impl<'a> Assert<'a> {
/// The list of arguments of this axiom/theorem.
#[inline]
pub fn args(&self) -> Args<'a> {
Args {
source: self.args_start,
}
}
/// The beginning of the unify stream.
#[inline]
pub fn unify(&self) -> UnifyIter<'a> {
self.unify.clone()
}
pub fn num_args(&self) -> u16 {
u16::try_from(self.args().len()).unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Mods {
pub inner: u8,
}
#[test]
fn test_mods1() {
let m = Mods { inner: 1 };
assert!(!m.is_provable());
let m = Mods { inner: 2 };
assert!(!m.is_provable());
let m = Mods { inner: 4 };
assert!(m.is_provable());
let m = Mods { inner: 8 };
assert!(!m.is_provable());
let m = Mods { inner: 3 };
assert!(!m.is_provable());
let m = Mods { inner: 12 };
assert!(m.is_provable());
let m = Mods { inner: 7 };
assert!(m.is_provable());
}
impl Mods {
pub fn new() -> Self {
Mods { inner: 0u8 }
}
pub fn is_provable(self) -> bool {
(self.inner & Mods::provable().inner) != 0
}
pub fn pure() -> Self {
Mods { inner: 1 }
}
pub fn strict() -> Self {
Mods { inner: 2 }
}
pub fn provable() -> Self {
Mods { inner: 4 }
}
pub fn free() -> Self {
Mods { inner: 8 }
}
}
/// An iterator over the declaration stream. This is the main provider of
/// verification obligations for mmb items, giving you both the target (for example,
/// `public term, number 13`) as well as the proof stream. The unification stream
/// Is later looked up using the item number when it's needed.
#[derive(Debug, Clone)]
pub struct DeclIter<'a> {
/// The full source file.
pub mmb: &'a [u8],
/// The index of the current declaration in the file.
/// Starts at `proof_stream_start` from the header file.
pub pos: usize,
pub next_sort_num: u8,
pub next_termdef_num: u32,
pub next_assert_num: u32,
}
/// We pair the item number (sortnum, termnum, assertnum) with the statement
/// So that during parallel checking of mmb declarations, we know which item is
/// which, since they may be checked out of order.
impl<'a> Iterator for DeclIter<'a> {
type Item = Result<(StmtCmd, ProofIter<'a>), VerifErr>;
fn next(&mut self) -> Option<Self::Item> {
match try_next_decl(self.mmb, self.pos)? {
Err(e) => Some(Err(e)),
Ok((stmt, pr, rest)) => {
let stmt = match stmt {
StmtCmd::Sort {..} => {
let num = self.next_sort_num;
self.next_sort_num += 1;
StmtCmd::Sort { num: Some(num) }
}
StmtCmd::TermDef {local, ..} => {
let num = self.next_termdef_num;
self.next_termdef_num += 1;
StmtCmd::TermDef { num: Some(num), local }
}
StmtCmd::Axiom {..} => {
let num = self.next_assert_num;
self.next_assert_num += 1;
StmtCmd::Axiom { num: Some(num) }
}
StmtCmd::Thm {local, ..} => {
let num = self.next_assert_num;
self.next_assert_num += 1;
StmtCmd::Thm { num: Some(num), local }
}
};
self.pos = rest;
Some(Ok((stmt, pr)))
}
}
}
}
/// is only used for `DeclIter::next`.
/// This always gets the full mmb file
fn try_next_decl(mmb: &[u8], pos: usize) -> Option<Res<(StmtCmd, ProofIter<'_>, usize)>> {
let (cmd, data, rest) = match try_next_cmd(mmb, pos) {
// Means cmd == 0, but here is unreachable
Ok(None) => return None,
// Means there was an error slicing in `try_next_cmd`
Err(e) => return Some(Err(e)),
Ok(Some(((cmd, data), rest))) => (cmd, data, rest)
};
let next2 = pos + data as usize;
let pr = ProofIter {
buf: mmb,
pos: rest,
ends_at: pos + (data as usize)
};
Some(Ok((
StmtCmd::try_from(cmd).ok()?,
pr,
next2
)))
}
// used by proof and unify
// This now always gets the full mmb file.
pub fn try_next_cmd<T>(mmb: &[u8], pos: usize) -> Res<Option<(T, usize)>>
where T: TryFrom<(u8, u32)> {
let (cmd, data, new_pos) = parse_cmd(mmb, pos)?;
if cmd == 0 {
return Ok(None);
}
Ok(Some((
T::try_from((cmd, data)).map_err(|_| VerifErr::Msg(format!("try_next_cmd failed @ pos {}", pos)))?,
new_pos
)))
}
/// Constants used in the MMB specification.
pub mod cmd {
/// `DATA_8 = 0x40`, used as a command mask for an 8 bit data field
pub const DATA_8: u8 = 0x40;
/// `DATA_16 = 0x80`, used as a command mask for a 16 bit data field
pub const DATA_16: u8 = 0x80;
/// `DATA_32 = 0xC0`, used as a command mask for a 32 bit data field
pub const DATA_32: u8 = 0xC0;
/// `DATA_MASK = 0xC0`, selects one of `DATA_8`, `DATA_16`, or `DATA_32` for data size
pub const DATA_MASK: u8 = 0xC0;
}
fn parse_cmd(mmb: &[u8], start_at: usize) -> Res<(u8, u32, usize)> {
match mmb.get(start_at..) {
None | Some([]) => Err(VerifErr::Msg("Parse cmd exhausted".to_string())),
Some([n, tl @ ..]) => {
let cmd_kind = n & !cmd::DATA_MASK;
let cmd_size = n & cmd::DATA_MASK;
match cmd_size {
0 => Ok((cmd_kind, 0, mmb.len() - tl.len())),
cmd::DATA_8 => {
let (data, rest) = parse_u8(tl)?;
Ok((cmd_kind, data as u32, mmb.len() - rest.len()))
}
cmd::DATA_16 => {
let (data, rest) = parse_u16(tl)?;
Ok((cmd_kind, data as u32, mmb.len() - rest.len()))
}
cmd::DATA_32 => {
let (data, rest) = parse_u32(tl)?;
Ok((cmd_kind, data, mmb.len() - rest.len()))
}
_ => Err(VerifErr::Msg("Parse cmd got unknown size".to_string())),
}
pub fn new_from(file_data: &'a crate::fs::FileData) -> Res<Self> {
let header = crate::mmb::parse_header(file_data.mmb_file.as_slice())?;
let index = crate::mmb::index::parse_index(file_data.mmb_file.as_slice(), header)?;
let declars = DeclIter {
mmb: file_data.mmb_file.as_slice(),
pos: header.proof_stream_start as usize,
next_sort_num: 0,
next_termdef_num: 0,
next_assert_num: 0,
};
pub fn mmb_num_sorts_done(&self) -> u8 {
self.mmb_num_sorts_done.load(Relaxed)
}
pub fn mmb_num_termdefs_done(&self) -> u32 {
self.mmb_num_termdefs_done.load(Relaxed)
}
pub fn mmb_num_asserts_done(&self) -> u32 {
self.mmb_num_asserts_done.load(Relaxed)
}
pub fn new_from(file_data: &'a crate::fs::FileView<'a>) -> Res<Self> {
let declars : Result<Vec<(NumdStmtCmd, ProofIter)>, ParseError> = file_data.mmb.proof().collect();
StmtCmd::Sort {..} => { self.next_sort(); },
StmtCmd::TermDef {..} => { self.next_termdef(); },
StmtCmd::Axiom {..} | StmtCmd::Thm {..} => { self.next_assert(); },
NumdStmtCmd::Sort {..} => { self.mmb_num_sorts_done.fetch_add(1, Relaxed); },
NumdStmtCmd::TermDef {..} => { self.mmb_num_termdefs_done.fetch_add(1, Relaxed); },
NumdStmtCmd::Axiom {..} | NumdStmtCmd::Thm {..} => { self.mmb_num_asserts_done.fetch_add(1, Relaxed); },
pub fn assert_mmz_done(&self, mmz: &crate::mmz::MmzMem<'a>, errs: &mut Vec<VerifErr>) {
if mmz.num_sorts_done() != self.header.num_sorts {
errs.push(VerifErr::Msg(format!(
pub fn assert_mmz_done(&self, mmz: &crate::mmz::MmzMem<'a>) -> Res<()> {
if mmz.num_sorts_done().into_inner() != self.file_view.mmb.header.num_sorts {
return Err(VerifErr::Msg(format!(
pub fn assert_mmb_done(&self, errs: &mut Vec<VerifErr>) {
if self.mmb_num_sorts_done() != self.header.num_sorts {
errs.push(VerifErr::Msg(format!(
pub fn assert_mmb_done(&self) -> Res<()> {
if self.mmb_num_sorts_done() != self.file_view.mmb.header.num_sorts {
return Err(VerifErr::Msg(format!(
}
}
impl<'a> Outline<'a> {
pub fn mmb(&self) -> &'a [u8] {
self.file_data.mmb_file.as_slice()
}
pub fn mmb_num_sorts_done(&self) -> u8 {
self.mmb_num_sorts_done.load(Relaxed)
}
pub fn mmb_num_termdefs_done(&self) -> u32 {
self.mmb_num_termdefs_done.load(Relaxed)
}
pub fn mmb_num_asserts_done(&self) -> u32 {
self.mmb_num_asserts_done.load(Relaxed)
}
fn next_sort(&self) -> u8 {
self.mmb_num_sorts_done.fetch_add(1, Relaxed)
}
fn next_termdef(&self) -> u32 {
self.mmb_num_termdefs_done.fetch_add(1, Relaxed)
}
fn next_assert(&self) -> u32 {
self.mmb_num_asserts_done.fetch_add(1, Relaxed)
}
pub fn get_sort_mods(&self, n: usize) -> Res<Mods> {
none_err! {
self
.mmb()
.get(self.header.sort_data_start as usize + n)
.map(|byte| Mods { inner: *byte })
}
}
/// Get a term (by number) from the mmb file
pub fn get_term_by_num(&self, term_num: u32) -> Res<Term<'a>> {
let start_point = (self.header.terms_start as usize) + ((term_num as usize) * 8);
let source = none_err!(self.mmb().get(start_point..))?;
let (num_args, source) = parse_u16(source)?;
let (sort, source) = parse_u8(source)?;
let (_reserved, source) = parse_u8(source)?;
let (term_args_start, _) = parse_u32(source)?;
let args_start = none_err!(self.mmb().get(term_args_start as usize..))?;
let split_point = none_err!{ std::mem::size_of::<u64>().checked_mul((num_args + 1) as usize) }?;
let (args_start, unify_start) = args_start.split_at(split_point);
let unify = UnifyIter {
buf: self.mmb(),
pos: self.mmb().len() - unify_start.len(),
};
Ok(Term { term_num, sort, args_start, unify })
}
/// Get an assertion (by number) from the mmb file
pub fn get_assert_by_num(&self, assert_num: u32) -> Res<Assert<'a>> {
let thm_start = (self.header.thms_start + (assert_num * 8)) as usize;
let source = self.mmb().get(thm_start..).ok_or(VerifErr::Msg(format!("Bad index")))?;
let (num_args, source) = parse_u16(source)?;
let (_reserved, source) = parse_u16(source)?;
let (args_start, _) = parse_u32(source)?;
let args_slice = none_err!(self.mmb().get(args_start as usize..))?;
let split_point = none_err! { std::mem::size_of::<u64>().checked_mul(num_args as usize) }?;
let (args_start, unify_start) = args_slice.split_at(split_point);
let unify = UnifyIter {
buf: self.mmb(),
pos: self.mmb().len() - unify_start.len(),
};
Ok(Assert { assert_num, args_start, unify })
}
}
/// These are helper functions for rendering unsigned integers
/// as a sequence of bits. For example, `view64(TYPE_SORT_MASK)` renders as:
///
/// 10000000_11111111_11111111_11111111_11111111_11111111_11111111_11111111
///
/// They're not efficient, but they're for debugging and I'm too lazy to write efficient
/// ones right now.
pub fn view64(n: u64) -> String {
let s = format!("{:#066b}", n);
let mut acc = String::new();
for (idx, c) in s.chars().skip(2).enumerate() {
if idx % 8 == 0 && idx != 0 {
acc.push('_');
}
acc.push(c);
Ok(())
pub fn view32(n: u32) -> String {
let s = format!("{:#034b}", n);
let mut acc = String::new();
for (idx, c) in s.chars().skip(2).enumerate() {
if idx % 8 == 0 && idx != 0 {
acc.push('_');
}
acc.push(c);
}
acc
}
///// These are helper functions for rendering unsigned integers
///// as a sequence of bits. For example, `view64(TYPE_SORT_MASK)` renders as:
/////
///// 10000000_11111111_11111111_11111111_11111111_11111111_11111111_11111111
/////
///// They're not efficient, but they're for debugging and I'm too lazy to write efficient
///// ones right now.
//pub fn view64(n: u64) -> String {
// let s = format!("{:#066b}", n);
// let mut acc = String::new();
// for (idx, c) in s.chars().skip(2).enumerate() {
// if idx % 8 == 0 && idx != 0 {
// acc.push('_');
// }
// acc.push(c);
// }
// acc
//}
pub fn view16(n: u16) -> String {
let s = format!("{:#018b}", n);
let mut acc = String::new();
for (idx, c) in s.chars().skip(2).enumerate() {
if idx % 8 == 0 && idx != 0 {
acc.push('_');
}
acc.push(c);
}
acc
}
//pub fn view32(n: u32) -> String {
// let s = format!("{:#034b}", n);
// let mut acc = String::new();
// for (idx, c) in s.chars().skip(2).enumerate() {
// if idx % 8 == 0 && idx != 0 {
// acc.push('_');
// }
// acc.push(c);
// }
// acc
//}
pub fn view8(n: u8) -> String {
let s = format!("{:#010b}", n);
let mut acc = String::new();
for (idx, c) in s.chars().skip(2).enumerate() {
if idx % 8 == 0 && idx != 0 {
acc.push('_');
}
acc.push(c);
}
acc
}
//pub fn view16(n: u16) -> String {
// let s = format!("{:#018b}", n);
// let mut acc = String::new();
// for (idx, c) in s.chars().skip(2).enumerate() {
// if idx % 8 == 0 && idx != 0 {
// acc.push('_');
// }
// acc.push(c);
// }
// acc
//}
impl Debug for Type {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", view64(self.inner))
}
}
//pub fn view8(n: u8) -> String {
// let s = format!("{:#010b}", n);
// let mut acc = String::new();
// for (idx, c) in s.chars().skip(2).enumerate() {
// if idx % 8 == 0 && idx != 0 {
// acc.push('_');
// }
// acc.push(c);
// }
// acc
//}
for (s1, m) in self.mem.coes.iter() {
for s2 in m.keys() {
if self.mem.outline.get_sort_mods(*s2 as usize)?.is_provable() {
if let Some(_s2) = provs.insert(*s1, *s2) {
println!("Coercion diamond to provable detected");
panic!();
for (&s1, m) in self.mem.coes.iter() {
for s2 in m.keys().copied() {
if self.mem.outline.file_view.mmb_sort_mods(s2)?.contains(Modifiers::PROVABLE) {
if let Some(_s2) = provs.insert(s1, s2) {
return Err(VerifErr::Msg(format!("Coercion diamond to provable detected. from: {:?}, to: {:?}", s1, s2)))
// unneeded
//pub declared_notations: FxIndexMap<Str<'a>, DeclaredNotation<'a>>,
pub nonlocal_termdefs: HashMap<TermIdent<'a>, TermNum>,
pub nonlocal_asserts: HashMap<AssertIdent<'a>, AssertNum>,
pub nonlocal_termdefs: HashMap<TermIdent<'a>, TermId>,
pub nonlocal_asserts: HashMap<AssertIdent<'a>, ThmId>,
StmtCmd::Sort {..} => None,
StmtCmd::TermDef {..} => {
let term = self.outline.get_term_by_num(self.num_termdefs_done())?;
NumdStmtCmd::Sort {..} => None,
NumdStmtCmd::TermDef {..} => {
let term = none_err!(self.outline.file_view.mmb.term(TermId(self.num_termdefs_done())))?;
StmtCmd::Axiom {..} | StmtCmd::Thm {..} => {
let assert = self.outline.get_assert_by_num(self.num_asserts_done())?;
NumdStmtCmd::Axiom {..} | NumdStmtCmd::Thm {..} => {
let assert = none_err!(self.outline.file_view.mmb.thm(ThmId(self.num_asserts_done())))?;
let sort_num = u8::try_from(idx).unwrap();
let mods = self.outline.get_sort_mods(idx).unwrap();
assert!(self.provs.insert(sort_num, mods.is_provable()).is_none());
sort_num
let sort_id = SortId(u8::try_from(idx).unwrap());
let is_provable = self.outline.file_view.mmb_sort_mods(sort_id)?.contains(Modifiers::PROVABLE);
assert!(self.provs.insert(sort_id, is_provable).is_none());
Ok(sort_id)
StmtCmd::Sort {..} => self.num_sorts += 1,
StmtCmd::TermDef {..} => self.num_termdefs += 1,
StmtCmd::Axiom {..} | StmtCmd::Thm {..} => self.num_asserts += 1,
NumdStmtCmd::Sort {..} => self.num_sorts += 1,
NumdStmtCmd::TermDef {..} => self.num_termdefs += 1,
NumdStmtCmd::Axiom {..} | NumdStmtCmd::Thm {..} => self.num_asserts += 1,
/// `STMT_AXIOM = 0x02`, starts an `axiom` declaration
pub const STMT_AXIOM: u8 = 0x02;
/// `STMT_SORT = 0x04`, starts a `sort` declaration
pub const STMT_SORT: u8 = 0x04;
// `STMT_TERM = 0x05`, starts a `term` declaration
//pub const STMT_TERM: u8 = 0x05;
/// `STMT_DEF = 0x05`, starts a `def` declaration. (This is the same as
/// `STMT_TERM` because the actual indication of whether this is a
/// def is in the term header)
pub const STMT_DEF: u8 = 0x05;
/// `STMT_THM = 0x06`, starts a `theorem` declaration
pub const STMT_THM: u8 = 0x06;
/// `STMT_LOCAL = 0x08`, starts a `local` declaration
/// (a bit mask to be combined with `STMT_THM` or `STMT_DEF`)
pub const STMT_LOCAL: u8 = 0x08;
/// `STMT_LOCAL_DEF = 0x0D`
pub const STMT_LOCAL_DEF: u8 = STMT_LOCAL | STMT_DEF;
/// `STMT_LOCAL_THM = 0x0E`
pub const STMT_LOCAL_THM: u8 = STMT_LOCAL | STMT_THM;
/// The main part of the proof consists of a sequence of declarations,
/// and these commands denote the different kind of declaration that can
/// be introduced.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StmtCmd {
/// A new sort. Equivalent to `sort foo;`. This is followed by no data,
/// as the sort data is stored in the header.
Sort { num: Option<u8> },
/// A new axiom. Equivalent to `axiom foo ...`. This is followed by a proof
/// sequence, that should unify with the unify sequence in the header.
Axiom { num: Option<u32> },
/// A new term or def. Equivalent to `term/def foo ...`.
/// If `local` is true, then this is `local def foo`. This is followed by
/// no data, as the header contains the unify sequence and can be checked on its own.
TermDef { num: Option<u32>, local: bool },
/// A new theorem. Equivalent to `(pub) theorem foo ...`, where `local` means
/// that the theorem is not `pub`. This is followed by a proof sequence,
/// that will construct the statement and proof, and should unify
/// with the unify sequence in the header.
Thm { num: Option<u32>, local: bool },
}
impl StmtCmd {
pub fn is_local(self) -> bool {
match self {
| StmtCmd::TermDef { local: true, .. }
| StmtCmd::Thm { local: true, .. } => true,
_ => false
}
}
}
impl std::convert::TryFrom<u8> for StmtCmd {
type Error = ();
fn try_from(cmd: u8) -> Result<Self, ()> {
Ok(match cmd {
STMT_SORT => StmtCmd::Sort { num: None },
STMT_AXIOM => StmtCmd::Axiom { num: None },
STMT_DEF => StmtCmd::TermDef { num: None, local: false },
STMT_LOCAL_DEF => StmtCmd::TermDef { num: None, local: true },
STMT_THM => StmtCmd::Thm { num: None, local: false },
STMT_LOCAL_THM => StmtCmd::Thm { num: None, local: true },
_ => return Err(()),
})
}
}
use std::fmt::{ Debug, Formatter, Result as FmtResult };
use crate::mmb::Header;
use crate::util::{
parse_u8,
parse_u32,
parse_u64,
Res,
VerifErr
};
use crate::none_err;
/// A parsed `MMB` file index.
pub struct Index<'a> {
/// The full file
pub mmb: &'a [u8],
/// A pointer to the root of the binary search tree, for searching based on name.
pub root: u64,
/// Pointers to the index entries for the sorts
pub sorts: Vec<u64>,
/// Pointers to the index entries for the terms
// The u64 you get back is to an `indexentry`.
pub terms: Vec<u64>,
/// Pointers to the index entries for the theorems
pub thms: Vec<u64>,
}
impl<'a> Debug for Index<'a> {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let mut d = f.debug_struct("Index");
d.field("root", &self.root);
d.field("sorts", &self.sorts);
d.field("terms", &self.terms);
d.field("thms", &self.thms);
d.finish()
}
}
impl<'a> std::default::Default for Index<'a> {
fn default() -> Index<'a> {
Index {
mmb: b"",
root: 0,
sorts: Vec::new(),
terms: Vec::new(),
thms: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct IndexEntry<'a> {
// pointer to left subchild (for binary searching by strings)
pub left: u64,
// pointer to right subchild
pub right: u64,
// For locating in the source file
pub row: u32,
// For locating in the source file
pub col: u32,
// pointer to the command that declares this item
pub proof: u64,
// Index of the object in the relevant table
pub ix: u32,
// sort, term, thm, var
pub kind: u8,
// zero-terminated char* buffer
pub charbuff: &'a [u8],
}
use crate::Outline;
impl<'a> Outline<'a> {
#[inline]
pub fn term_index_entry(&self, term_num: u32) -> Option<IndexEntry<'a>> {
let entry = *&self.index.terms[term_num as usize];
self.index_entry(entry as usize)
}
#[inline]
pub fn assert_index_entry(&self, assert_num: u32) -> Option<IndexEntry<'a>> {
let entry = *&self.index.thms[assert_num as usize];
self.index_entry(entry as usize)
}
#[inline]
pub fn index_entry(&self, start_at: usize) -> Option<IndexEntry<'a>> {
let (left, rest) = parse_u64(&self.mmb()[start_at..]).unwrap();
let (right, rest) = parse_u64(rest).unwrap();
let (row, rest) = parse_u32(rest).unwrap();
let (col, rest) = parse_u32(rest).unwrap();
let (proof, rest) = parse_u64(rest).unwrap();
let (ix, rest) = parse_u32(rest).unwrap();
let (kind, rest) = parse_u8(rest).unwrap();
let charbuff = parse_cstr(rest)?;
Some(IndexEntry {
left,
right,
row,
col,
proof,
ix,
kind,
charbuff,
})
}
}
fn parse_cstr<'a>(source: &'a [u8]) -> Option<&'a [u8]> {
let null = source.iter().position(|c| *c == 0)?;
let (s, _) = source.split_at(null);
Some(s)
}
pub fn parse_index<'a>(mmb: &'a [u8], header: Header) -> Res<Index<'a>> {
let (root, rest) = parse_u64(&mmb[header.index_start as usize..])
.expect("Failed to get u64 for index root");
let (sorts, rest) = prefix_u64(rest, header.num_sorts as usize)
.expect("Failed to get sorts in index");
let (terms, rest) = prefix_u64(rest, header.num_terms as usize)
.expect("Failed to get num terms");
let (thms, _) = prefix_u64(rest, header.num_thms as usize)
.expect("Failed to get num thms in index");
Ok(Index {
mmb,
root,
sorts,
terms,
thms,
})
}
#[inline]
pub fn prefix_u64(mut bytes: &[u8], num_elems: usize) -> Res<(Vec<u64>, &[u8])> {
let split_point = none_err!{ std::mem::size_of::<u64>().checked_mul(num_elems) }?;
if split_point <= bytes.len() {
let mut v = Vec::with_capacity(num_elems);
for _ in 0..num_elems {
let (inner, rest_) = parse_u64(bytes)?;
bytes = rest_;
v.push(inner);
}
Ok((v, bytes))
} else {
Err(VerifErr::Msg(format!("Bad prefix arg")))
}
}
/// `UNIFY_TERM = 0x30`: See [`UnifyCmd`](super::UnifyCmd).
pub const UNIFY_TERM: u8 = 0x30;
/// `UNIFY_TERM_SAVE = 0x31`: See [`UnifyCmd`](super::UnifyCmd).
pub const UNIFY_TERM_SAVE: u8 = 0x31;
/// `UNIFY_REF = 0x32`: See [`UnifyCmd`](super::UnifyCmd).
pub const UNIFY_REF: u8 = 0x32;
/// `UNIFY_DUMMY = 0x33`: See [`UnifyCmd`](super::UnifyCmd).
pub const UNIFY_DUMMY: u8 = 0x33;
/// `UNIFY_HYP = 0x36`: See [`UnifyCmd`](super::UnifyCmd).
pub const UNIFY_HYP: u8 = 0x36;
/// Unify commands appear in the header data for a `def` or `axiom`/`theorem`.
/// They are executed by the [`ProofCmd::Thm`] command in order to perform
/// substitutions. The state of the unify stack machine is:
///
/// * `MS: Stack<StackEl>`: The main stack, called `S` in the [`ProofCmd`]
/// documentation.
/// Since a unification is called as a subroutine during a proof command,
/// the main stack is available, but most operations don't use it.
/// * `S: Stack<Expr>`: The unify stack, which contains expressions
/// from the target context that are being destructured.
/// * `H: Vec<Expr>: The unify heap, also known as the substitution. This is
/// initialized with the expressions that the target context would like to
/// substitute for the variable names in the theorem being applied, but
/// it can be extended in order to support substitutions with sharing
/// as well as dummy variables.
#[derive(Debug, Clone, Copy)]
pub enum UnifyCmd {
/// ```text
/// UTerm t: S, (t e1 ... en) --> S, en, ..., e1
/// USave: H; S, e --> H, e; S, e
/// UTermSave t = USave; UTerm t:
/// H; S, (t e1 ... en) --> H, (t e1 ... en); S, en, ..., e1
/// ```
/// Pop an element from the stack, ensure that the head is `t`, then
/// push the `n` arguments to the term (in reverse order, so that they
/// are dealt with in the correct order in the command stream).
/// `UTermSave` does the same thing but saves the term to the unify heap
/// before the destructuring.
Term {
/** The term that should be at the head */
term_num: u32,
/** True if we want to recall this substitution for later */
save: bool,
},
/// ```text
/// URef i: H; S, Hi --> H; S
/// ```
/// Get the `i`-th element from the unify heap (the substitution),
/// and match it against the head of the unify stack.
Ref(u32),
/// ```text
/// UDummy s: H; S, x --> H, x; S (where x:s)
/// ```
/// Pop a variable from the unify stack (ensure that it is a name of
/// the appropriate sort) and push it to the heap (ensure that it is
/// distinct from everything else in the substitution).
Dummy { sort_id: u8 },
/// ```text
/// UHyp (UThm mode): MS, |- e; S --> MS; S, e
/// UHyp (UThmEnd mode): HS, e; S --> HS; S, e
/// ```
/// `UHyp` is a command that is used in theorem declarations to indicate that
/// we are about to read a hypothesis. There are two contexts where we read
/// this, when we are first declaring the theorem and check the statement (`UThmEnd` mode),
/// and later when we are applying the theorem and have to apply a substitution (`UThm` mode).
/// When we are applying the theorem, we have `|- e` on the main stack, and we
/// pop that and load the expression onto the unify stack.
/// When we are checking a theorem, we have been pushing hypotheses onto the
/// hypothesis stack, so we pop it from there instead.
Hyp,
}
impl std::convert::TryFrom<(u8, u32)> for UnifyCmd {
type Error = VerifErr;
fn try_from((cmd, data): (u8, u32)) -> Result<Self, VerifErr> {
Ok(match cmd {
UNIFY_TERM => UnifyCmd::Term {
term_num: data,
save: false,
},
UNIFY_TERM_SAVE => UnifyCmd::Term {
term_num: data,
save: true,
},
UNIFY_REF => UnifyCmd::Ref(data),
UNIFY_DUMMY => {
let sort_id = u8::try_from(data).map_err(|_| {
VerifErr::Msg("Failure to shrink u32 to u8 in proof_cmd try from".to_string())
})?;
UnifyCmd::Dummy { sort_id }
}
UNIFY_HYP => UnifyCmd::Hyp,
_ => return Err(VerifErr::Msg("Unrecognized try_form in try from unifycmd".to_string())),
})
}
}
/// An iterator over a unify command stream.
#[derive(Debug, Clone, Copy)]
pub struct UnifyIter<'a> {
/// The full source file.
pub buf: &'a [u8],
/// The index of the current declaration in the file.
pub pos: usize,
}
impl<'a> Iterator for UnifyIter<'a> {
type Item = Result<UnifyCmd, VerifErr>;
fn next(&mut self) -> Option<Self::Item> {
match try_next_cmd(self.buf, self.pos) {
// err
Err(e) => Some(Err(e)),
// Exhausted
Ok(None) => None,
// next
Ok(Some((stmt, rest))) => {
self.pos = rest;
Some(Ok(stmt))
}
}
}
}
UnifyCmd::Term { term_num, save } => self.unify_term(term_num, save)?,
UnifyCmd::Dummy { sort_id } => self.unify_dummy(mode, sort_id)?,
UnifyCmd::Term { tid, save } => self.unify_term(tid, save)?,
UnifyCmd::Dummy(sort_id) => self.unify_dummy(mode, sort_id)?,
/// `PROOF_TERM = 0x10`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_TERM: u8 = 0x10;
/// `PROOF_TERM_SAVE = 0x11`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_TERM_SAVE: u8 = 0x11;
/// `PROOF_REF = 0x12`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_REF: u8 = 0x12;
/// `PROOF_DUMMY = 0x13`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_DUMMY: u8 = 0x13;
/// `PROOF_THM = 0x14`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_THM: u8 = 0x14;
/// `PROOF_THM_SAVE = 0x15`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_THM_SAVE: u8 = 0x15;
/// `PROOF_HYP = 0x16`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_HYP: u8 = 0x16;
/// `PROOF_CONV = 0x17`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_CONV: u8 = 0x17;
/// `PROOF_REFL = 0x18`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_REFL: u8 = 0x18;
/// `PROOF_SYMM = 0x19`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_SYMM: u8 = 0x19;
/// `PROOF_CONG = 0x1A`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_CONG: u8 = 0x1A;
/// `PROOF_UNFOLD = 0x1B`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_UNFOLD: u8 = 0x1B;
/// `PROOF_CONV_CUT = 0x1C`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_CONV_CUT: u8 = 0x1C;
/// `PROOF_CONV_REF = 0x1D`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_CONV_REF: u8 = 0x1D;
/// `PROOF_CONV_SAVE = 0x1E`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_CONV_SAVE: u8 = 0x1E;
/// `PROOF_SAVE = 0x1F`: See [`ProofCmd`](super::ProofCmd).
pub const PROOF_SAVE: u8 = 0x1F;
/// A proof command, which acts on a stack machine with the following components:
///
/// * `H: Vec<StackEl>`: a "heap" consisting of indexable elements that can be copied
/// onto the stack using [`Ref`](ProofCmd::Ref)
/// * `S: Stack<StackEl>`: The main stack, which most operations push and pop from.
/// * `HS: Vec<Expr>`: The hypothesis list, which grows only on [`Hyp`](ProofCmd::Hyp)
/// operations and collects the hypotheses of the theorem.
pub enum ProofCmd {
/// ```text
/// Term t: H; S, e1, ..., en --> H; S, (t e1 .. en)
/// Save: H; S, e --> H, e; S, e
/// TermSave t = Term t; Save:
/// H; S, e1, ..., en --> H, (t e1 .. en); S, (t e1 .. en)
/// ```
///
/// Pop `n` elements from the stack and allocate a new term `t` applied to those
/// expressions. When `save` is used, the new term is also saved to the heap
/// (this is used if the term will ever be needed more than once).
Term {
/** The term to construct */
term_num: u32,
/** True if we should save to the heap */
save: bool,
},
/// ```text
/// Ref i: H; S --> H; S, Hi
/// ```
/// Get the `i`-th heap element and push it on the stack.
Ref(u32),
/// ```text
/// Dummy s: H; S --> H, x; S, x alloc(x:s)
/// ```
/// Allocate a new variable `x` of sort `s`, and push it to the stack and the heap.
Dummy { sort_num: u8 },
/// ```text
/// Thm T: H; S, e1, ..., en, e --> H; S', |- e
/// (where Unify(T): S; e1, ... en; e --> S'; H'; .)
/// Save: H; S, |- e --> H, |- e; S, |- e
/// ```
/// Pop `n` elements from the stack and put them on the unify heap, then call the
/// unifier for `T` with `e` as the target. The unifier will pop additional
/// proofs from the stack if the UHyp command is used, and when it is done,
/// the conclusion is pushed as a proven statement.
///
/// When Save is used, the proven statement is also saved to the heap.
Thm {
/** The theorem to apply */
thm_num: u32,
/** True if we should save to the heap */
save: bool,
},
/// ```text
/// Hyp: HS; H; S, e --> HS, e; H, |- e; S
/// ```
/// This command means that we are finished constructing the expression `e`
/// which denotes a statement, and wish to assume it as a hypothesis.
/// Push `e` to the hypothesis stack, and push `|- e` to the heap.
Hyp,
/// ```text
/// Conv: S, e1, |- e2 --> S, |- e1, e1 =?= e2
/// ```
/// Pop `e1` and `|- e2`, and push `|- e1`, guarded by a convertibility obligation
/// `e1 =?= e2`.
Conv,
/// ```text
/// Refl: S, e =?= e --> S
/// ```
/// Pop a convertibility obligation where the two sides are equal.
Refl,
/// ```text
/// Symm: S, e1 =?= e2 --> S, e2 =?= e1
/// ```
/// Swap the direction of a convertibility obligation.
Sym,
/// ```text
/// Cong: S, (t e1 ... en) =?= (t e1' ... en') --> S, en =?= en', ..., e1 =?= e1'
/// ```
/// Pop a convertibility obligation for two term expressions, and
/// push convertibility obligations for all the parts.
/// The parts are pushed in reverse order so that they are dealt with
/// in declaration order in the proof stream.
Cong,
/// ```text
/// Unfold: S, (t e1 ... en) =?= e', (t e1 ... en), e --> S, e =?= e'
/// (where Unify(t): e1, ..., en; e --> H'; .)
/// ```
/// Pop terms `(t e1 ... en)`, `e` from the stack and run the unifier for `t`
/// (which should be a definition) to make sure that `(t e1 ... en)` unfolds to `e`.
/// Then pop `(t e1 ... en) =?= e'` and push `e =?= e'`.
Unfold,
/// ```text
/// ConvCut: S, e1 =?= e2 --> S, e1 = e2, e1 =?= e2
/// ```
/// Pop a convertibility obligation `e1 =?= e2`, and
/// push a convertability assertion `e1 = e2` guarded by `e1 =?= e2`.
ConvCut,
/// ```text
/// ConvRef i: H; S, e1 =?= e2 --> H; S (where Hi is e1 = e2)
/// ```
/// Pop a convertibility obligation `e1 =?= e2`, where `e1 = e2` is
/// `i`-th on the heap.
ConvRef(u32),
/// ```text
/// ConvSave: H; S, e1 = e2 --> H, e1 = e2; S
/// ```
/// Pop a convertibility proof `e1 = e2` and save it to the heap.
ConvSave,
/// ```text
/// Save: H; S, s --> H, s; S, s
/// ```
/// Save the outline of the stack to the heap, without popping it.
Save,
}
impl std::convert::TryFrom<(u8, u32)> for ProofCmd {
type Error = VerifErr;
fn try_from((cmd, data): (u8, u32)) -> Result<Self, Self::Error> {
Ok(match cmd {
PROOF_TERM => ProofCmd::Term {
term_num: data,
save: false,
},
PROOF_TERM_SAVE => ProofCmd::Term {
term_num: data,
save: true,
},
PROOF_REF => ProofCmd::Ref(data),
PROOF_DUMMY => {
let sort_num = u8::try_from(data).map_err(|_| {
VerifErr::Msg("Failure to shrink u32 to u8 in proof_cmd try from".to_string())
})?;
ProofCmd::Dummy { sort_num }
}
PROOF_THM => ProofCmd::Thm {
thm_num: data,
save: false,
},
PROOF_THM_SAVE => ProofCmd::Thm {
thm_num: data,
save: true,
},
PROOF_HYP => ProofCmd::Hyp,
PROOF_CONV => ProofCmd::Conv,
PROOF_REFL => ProofCmd::Refl,
PROOF_SYMM => ProofCmd::Sym,
PROOF_CONG => ProofCmd::Cong,
PROOF_UNFOLD => ProofCmd::Unfold,
PROOF_CONV_CUT => ProofCmd::ConvCut,
PROOF_CONV_REF => ProofCmd::ConvRef(data),
PROOF_CONV_SAVE => ProofCmd::ConvSave,
PROOF_SAVE => ProofCmd::Save,
owise => {
println!("Failed to convert {:?}\n", owise);
return Err(VerifErr::Msg("try_from for ProofCmd failed match".to_string()));
}
})
}
}
/// An iterator over a proof command stream.
#[derive(Debug, Clone, Copy)]
pub struct ProofIter<'a> {
/// The full source file, but trimmed such that the end
/// is the expected end of the proof stream. The final call to `next`
/// will fail if it does not hit the expected end when the
/// proof stream runs out.
pub buf: &'a [u8],
/// The index of the current proof command in the file.
pub pos: usize,
/// Mark `ends_at` instead of giving ProofIter a truncated slice just so
/// the behvaior wrt `try_next_cmd` is identical.
pub ends_at: usize,
}
impl<'a> ProofIter<'a> {
/// True if this iterator is "null", meaning that it has zero commands.
/// This is not the same as being empty, which happens when there is one command
/// which is the terminating `CMD_END` command.
pub fn is_null(&self) -> bool {
//self.buf.len() == self.pos
self.pos == self.ends_at
}
}
impl<'a> Iterator for ProofIter<'a> {
type Item = Result<ProofCmd, VerifErr>;
fn next(&mut self) -> Option<Self::Item> {
match try_next_cmd(self.buf, self.pos) {
// An actual error.
Err(e) => Some(Err(e)),
// `try_next_cmd` got `Ok(None)` by receiving a 0 command at the correct position
Ok(None) if self.ends_at == self.pos + 1 => None,
// `try_next_cmd` got `Ok(None)` by receiving a 0 command at the WRONG position
Ok(None) => Some(Err(VerifErr::Msg(format!("Proof iter err @ {}", self.pos)))),
// `try_next_cmd` parsed a new command.
Ok(Some((stmt, rest))) => {
self.pos = rest;
Some(Ok(stmt))
}
}
}
pub enum Mode {
Def,
Thm,
ProofCmd::Dummy { sort_num } => self.proof_dummy(sort_num)?,
ProofCmd::Term { term_num, save } => self.proof_term(mode, term_num, save)?,
ProofCmd::Thm { thm_num, save } => self.proof_thm(thm_num, save)?,
ProofCmd::Dummy(sort_num) => self.proof_dummy(sort_num)?,
ProofCmd::Term { tid, save } => self.proof_term(mode, tid, save)?,
ProofCmd::Thm { tid, save } => self.proof_thm(tid, save)?,
let heap_elem = *&self.heap[i as usize];
Ok(self.stack.push(heap_elem))
match none_err!(self.heap.get(i as usize).copied())? {
MmbItem::Conv(c1, c2) => {
let stack_coconv = none_err!(self.stack.pop())?;
if let MmbItem::CoConv(cc1, cc2) = stack_coconv {
make_sure!(c1 == cc1);
Ok(make_sure!(c2 == cc2))
} else {
return Err(VerifErr::Unreachable(file!(), line!()));
}
}
heap_elem => Ok(self.stack.push(heap_elem))
}
fn proof_dummy(&mut self, sort_num: u8) -> Res<()> {
make_sure!(sort_num < self.outline.header.num_sorts);
make_sure!(self.outline.get_sort_mods(sort_num as usize).unwrap().inner & crate::mmb::SORT_STRICT == 0);
fn proof_dummy(&mut self, sort_num: SortId) -> Res<()> {
make_sure!(sort_num.into_inner() < self.outline.file_view.mmb.header.num_sorts);
make_sure!(!(self.outline.file_view.mmb_sort_mods(sort_num)?.contains(Modifiers::STRICT)));
make_sure!(term_num < self.outline.header.num_terms);
let termref = self.outline.get_term_by_num(term_num)?;
make_sure!(term_num.into_inner() < self.outline.file_view.mmb.header.num_terms.get());
let termref = none_err!(self.outline.file_view.mmb.term(term_num))?;
make_sure!(thm_num < self.outline.header.num_thms);
let thmref = self.outline.get_assert_by_num(thm_num)?;
make_sure!(thm_num.into_inner() < self.outline.file_view.mmb.header.num_thms.get());
let thmref = none_err!(self.outline.file_view.mmb.thm(thm_num))?;
let e_sort_numx = e.get_ty()?.sort();
let e_sort_mods = self.outline.get_sort_mods(e_sort_numx as usize).unwrap().inner;
make_sure!(e_sort_mods & crate::mmb::SORT_PROVABLE != 0);
make_sure!(self.outline.file_view.mmb_sort_mods(e.get_ty()?.sort())?.contains(Modifiers::PROVABLE));
let f_ebar = none_err!(self.stack.pop())?;
let (term_num, ebar) = match f_ebar {
MmbItem::Expr(MmbExpr::App{ term_num, args, .. }) => (term_num, args.clone()),
let cc = none_err!(self.stack.pop())?;
let (term_num, ebar, e_doubleprime) = match cc {
MmbItem::CoConv(MmbItem::Expr(MmbExpr::App{ term_num, args, .. }), e_doubleprime) => {
(term_num, args.clone(), e_doubleprime)
}
let cc = none_err!(self.stack.pop())?;
if let MmbItem::CoConv(f_ebar2, e_doubleprime) = cc {
make_sure!(f_ebar == *f_ebar2);
let coconv = self.alloc(MmbItem::CoConv(e_prime, e_doubleprime));
Ok(self.stack.push(coconv))
} else {
return Err(VerifErr::Unreachable(file!(), line!()));
}
let coconv = self.alloc(MmbItem::CoConv(e_prime, e_doubleprime));
Ok(self.stack.push(coconv))
fn proof_conv_ref(&mut self, i: u32) -> Res<()> {
let heap_conv = none_err!(self.heap.get(i as usize).copied())?;
let stack_coconv = none_err!(self.stack.pop())?;
if let (MmbItem::Conv(c1, c2), MmbItem::CoConv(cc1, cc2)) = (heap_conv, stack_coconv) {
make_sure!(c1 == cc1);
Ok(make_sure!(c2 == cc2))
} else {
return Err(VerifErr::Unreachable(file!(), line!()));
}
}
fn proof_sorry(&mut self) -> Res<()> {
match none_err!(self.stack.pop())? {
e @ MmbItem::Expr(_) => {
let proof = self.alloc(MmbItem::Proof(e));
Ok(self.stack.push(proof))
},
MmbItem::Conv(..) => Ok(()),
owise => Err(VerifErr::Msg(format!("ProofCmd::Sorry is only valid when the stack has an Expr or Conv on top. Top element was {:?}", owise)))
}
}
const MM0B_MAGIC: u32 = 0x42304D4D;
// Each sort has one byte associated to it, which
// contains flags for the sort modifiers.
// The high four bits are unused.
pub const SORT_PURE : u8 = 1;
pub const SORT_STRICT : u8 = 2;
pub const SORT_PROVABLE : u8 = 4;
pub const SORT_FREE : u8 = 8;
/// bound mask: 10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000
pub const TYPE_BOUND_MASK: u64 = 1 << 63;
/// deps mask: 00000000_11111111_11111111_11111111_11111111_11111111_11111111_11111111
pub const TYPE_DEPS_MASK: u64 = (1 << 56) - 1;
#[derive(Debug, Clone, Copy)]
pub struct Header {
/// "= MM0B_VERSION"
pub magic: u32,
pub version: u8,
/// Number of declared sorts
pub num_sorts: u8,
pub reserved: u16,
/// Number of terms and defs
pub num_terms: u32,
/// Number of axioms and theorems
pub num_thms: u32,
/// Pointer to start of term table
pub terms_start: u32,
/// Pointer to start of theorem table
pub thms_start: u32,
/// Pointer to start of proof section
pub proof_stream_start: u32,
pub reserved2: u32,
/// Pointer to start of index, or 0
pub index_start: u64,
// The list of all sorts. The number of sorts is
// limited to 128 because of the data layout.
// So don't monomorphize too much.
pub sort_data_start: u32,
}
impl std::default::Default for Header {
fn default() -> Header {
Header {
magic: 0,
version: 0,
num_sorts: 0,
reserved: 0,
num_terms: 0,
num_thms: 0,
terms_start: 0,
thms_start: 0,
proof_stream_start: 0,
reserved2: 0,
index_start: 0,
sort_data_start: 0
}
}
}
pub fn parse_header(mmb: &[u8]) -> Res<Header> {
let (magic, source) = parse_u32(mmb)?;
assert_eq!(magic, MM0B_MAGIC);
let (version, source) = parse_u8(source)?;
let (num_sorts, source) = parse_u8(source)?;
let (reserved, source) = parse_u16(source)?;
let (num_terms, source) = parse_u32(source)?;
let (num_thms, source) = parse_u32(source)?;
let (terms_start, source) = parse_u32(source)?;
let (thms_start, source) = parse_u32(source)?;
let (proof_stream_start, source) = parse_u32(source)?;
let (reserved2, source) = parse_u32(source)?;
let (index_start, source) = parse_u64(source)?;
let sort_data_start = conv_err!(u32::try_from(mmb.len() - source.len()))?;
Ok(Header {
magic,
version,
num_sorts,
reserved,
num_terms,
num_thms,
terms_start,
thms_start,
proof_stream_start,
reserved2,
index_start,
sort_data_start
})
}
//#[derive(Debug)]
StmtCmd::TermDef { num, .. } => {
let term = outline.get_term_by_num(num.unwrap())?;
if !term.is_def() && !proof.is_null() {
NumdStmtCmd::TermDef { term_id, .. } => {
let term = none_err!(outline.file_view.mmb.term(term_id))?;
if !term.def() && !proof.is_null() {
let raw_file = match RawFileData::new_from(mmb_path, mmz_path) {
Ok(data) => data,
Err(e) => {
eprintln!("Failed to obtain file data: {:?}\n", e);
panic!()
}
};
let file_view = match raw_file.view() {
Ok(v) => v,
Err(e) => {
eprintln!("mm0-rs mm0b_parser failed to parse mmb file: {:?}\n", e);
panic!()
}
};
let file_data = FileData::new_from(mmb_path, mmz_path).unwrap();
let outline = Outline::new_from(&file_data).unwrap();
let outline = match Outline::new_from(&file_view) {
Ok(ol) => ol,
Err(e) => {
eprintln!("failed to create verification outline: {:?}\n", e);
panic!()
}
};
if let Some((e, es)) = errs.split_last() {
println!("verification was unsuccessful. Terminated with error {:?}\n + {} other errors", e, es.len());
if let Err(e) = verif_result {
eprintln!("verification was unsuccessful. Terminated with error {:?}\n", e)
let (mut errs) = verify_mmz(outline);
let mut mmb_errs = verify_mmb(outline, &task_counter);
errs.append(&mut mmb_errs);
outline.assert_mmb_done(&mut errs);
errs
verify_mmz(outline)?;
verify_mmb(outline, &task_counter)?;
outline.assert_mmb_done()
fn verify_par<'a>(outline: &'a Outline<'a>, num_threads: usize) -> Vec<VerifErr> {
// If any of the threads panic, just panic the whole thing since
// the error is something to do with this tool rather than a user-provided input.
fn verify_par<'a>(outline: &'a Outline<'a>, num_threads: usize) -> Res<()> {
let mut errs = match sco.spawn(|_| verify_mmz(outline)).join() {
Err(_) => vec![VerifErr::Msg(format!("mmz thread panicked!"))],
Ok(errs) => errs,
};
sco
.spawn(|_| verify_mmz(outline))
.join()
.unwrap()
.map_err(|_| VerifErr::Msg(format!("mmz thread panicked!")))?;
match mmb_task.join() {
Err(_) => { errs.push(VerifErr::Msg(format!("mmb thread {} panicked", idx))); },
Ok(mut mmb_errs) => errs.append(&mut mmb_errs),
}
mmb_task
.join()
.unwrap()
.map_err(|_| VerifErr::Msg(format!("mmb thread # {} panicked", idx)))?;
pub mmz_hierarchy: ImportGraph,
#[allow(dead_code)]
pub(crate) mmz_hierarchy: ImportGraph,
}
#[derive(Debug)]
pub struct FileView<'a> {
pub mmb: BasicMmbFile<'a>,
pub mmz: &'a [String]
}
impl<'a> FileView<'a> {
pub(crate) fn mmb_sort_mods(&self, id: SortId) -> Res<Modifiers> {
use std::convert::TryFrom;
none_err!(self.mmb.sort(id))
.and_then(|id| conv_err!(Modifiers::try_from(id)))
}
}
pub struct MmzImporter {
impl std::default::Default for FileData {
fn default() -> Self {
FileData {
mmb_file: Vec::new(),
mmz_files: Vec::new(),
root_mmz_path: PathBuf::from(""),
mmz_hierarchy: ImportGraph::NoImports(PathBuf::from("")),
impl MmzImporter {
pub fn new_from(root_mmz_path: PathBuf) -> Self {
MmzImporter {
root_mmz_path,
mmz_strings: Vec::new(),
}
pub fn new_from(mmb_path: impl Into<PathBuf>, root_mmz_path: Option<impl Into<PathBuf>>) -> Res<Self> {
let mmb_path = mmb_path.into().canonicalize().unwrap();
let root_mmz_path = match root_mmz_path {
None => {
let mut base = mmb_path.clone();
base.set_extension("mm0");
base
},
Some(p) => p.into().canonicalize().unwrap()
};
let mut mmb_handle = OpenOptions::new()
.read(true)
.truncate(false)
.open(&mmb_path)
.map_err(|_| VerifErr::Msg(format!("IO err in add_file mmb")))
.unwrap();
let mut mmb_file = Vec::<u8>::with_capacity(mmb_handle.metadata().unwrap().len() as usize);
mmb_handle.read_to_end(&mut mmb_file).unwrap();
let mut data = FileData::default();
data.mmb_file = mmb_file;
data.root_mmz_path = root_mmz_path.clone();
data.mmz_hierarchy = data.add_mmz_aux(root_mmz_path)?;
Ok(data)
let s = std::fs::read_to_string(&mmz_path).unwrap();
// Read the current file so we can find the import statements, but don't
// push it to the list of mmz files yet so that we maintain the
// primtive >> uses imports
// directional ordering.
let s = io_err!(std::fs::read_to_string(&mmz_path))?;
let imports = self.find_imports(s.split_whitespace())?;
self.mmz_files.push(s);
let popped = self.todos.pop().unwrap();
// Find any imports in `s` and add them (and their imports) to the accumulator
let imports = self.add_mmz_aux(s.split_whitespace())?;
// NOW we can push it.
self.mmz_strings.push(s);
// Take this file off the stack of `todos` now that we've collected its imports
let popped = none_err!(self.todos.pop())?;
/// Form the import graph by mutual recursion with `add_mmz_aux`.
fn find_imports(&mut self, mut ws: std::str::SplitWhitespace) -> Res<Vec<ImportGraph>> {
/// Form the import graph by mutual recursion with `add_mmz`.
fn add_mmz_aux(
&mut self,
mut ws: std::str::SplitWhitespace,
) -> Res<Vec<ImportGraph>> {
Some(path) if path.starts_with("\"") => {
let end_pos = path.chars().skip(1).position(|c| c == '"').unwrap();
let pathbuf = PathBuf::from(&path[1..end_pos + 1]);
Some(path) if path.starts_with("\"") && path.ends_with("\";") => {
// Get the path without the quotes and semicolon
let pathbuf = PathBuf::from(&path[1..path.len() - 2]);
impl Debug for FileData {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let mut d = f.debug_struct("FileData");
d.field("mmz_files", &"<omitted>");
d.field("mmz_hierarchy", &self.mmz_hierarchy);
d.field("todos", &self.todos);
d.field("done", &self.done);
d.field("root_mmz_file", &self.root_mmz_path);
d.finish()
pub fn new_from(mmb_path: impl Into<PathBuf>, root_mmz_path: Option<impl Into<PathBuf>>) -> Res<Self> {
let mmb_path = io_err!(mmb_path.into().canonicalize())?;
// If there was no mmz_path specified, look for an mm0 file
// in the same location as the mmb file.
let root_mmz_path = match root_mmz_path {
None => {
let mut base = mmb_path.clone();
base.set_extension("mm0");
base
},
Some(p) => io_err!(p.into().canonicalize())?
};
let mut mmb_handle = io_err! {
OpenOptions::new()
.read(true)
.truncate(false)
.open(&mmb_path)
}?;
let mut mmb_bytes = Vec::<u8>::with_capacity(io_err!(mmb_handle.metadata())?.len() as usize);
io_err!(mmb_handle.read_to_end(&mut mmb_bytes))?;
let mut mmz_import_garbage = MmzImporter::new_from(root_mmz_path.clone());
let mmz_hierarchy = mmz_import_garbage.add_mmz(root_mmz_path)?;
let data = RawFileData {
mmb_bytes,
mmz_strings: mmz_import_garbage.mmz_strings,
mmz_hierarchy,
};
Ok(data)
let file_data = FileData::new_from("./test_resources/a.mmb", Some("./test_resources/a.mm0")).unwrap();
let file_data2 = FileData::new_from("./test_resources/a.mmb", None::<String>).unwrap();
assert_eq!(file_data.mmz_hierarchy, file_data2.mmz_hierarchy);
let file_data1 = RawFileData::new_from("./test_resources/a.mmb", Some("./test_resources/a.mm0")).unwrap();
let file_data2 = RawFileData::new_from("./test_resources/a.mmb", None::<String>).unwrap();
assert_eq!(file_data1.mmz_hierarchy, file_data2.mmz_hierarchy);
## 0.1.3 -> 2.0
+ Remove native mmb parsing facilities in favor of the ones in mm0-rs/components/mmb_parser; the version of `second_opinion` that relies on native implementations will remain available in the `native` fork.
+ Remove ConvRef, change conv implementation
+ The main verificaiton only returns one (the first) error instead of accumulating any and all errors. Since there's a backtrace provided, I didn't find the multiple error reporting to be helpful.
+ The FileData struct is now a RawFileData struct that owns the underlying data, and FileView is passed around since we need to show that the data backing the parsed MmbFile lives as long as the verification is going on.
+ Break out MmzImporter so we can discard the garbage collected while traversing the mmz import graph.
+ Most methods and structs now use the native SortId, TermId, ThmId instead of u8/u32s.
+ The native Mods was replaced with the upstream Modifiers.
+ Removed most of the outstanding unwraps which were in the `fs` module. They now return a proper error.
+ Fix bug in bound variable counter
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e84f8531413398f7110dbcc126428ed097b51efaa59cafb626130a736daee6f"
dependencies = [
"mm0_deepsize_derive",
]
[[package]]
name = "mm0_deepsize_derive"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d347f3395472eb8f5de502facb8c0bcccb73269d257c32c69e6bafad5246cb1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mm0_util"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cee3d13265fcdecc6fb1dd05d6a0e516e7f3bdc223b57d21109686aca19190"
dependencies = [
"bitflags",
"lazy_static",
"libc",
"mm0_deepsize",
"mm0_deepsize_derive",
"pathdiff",
"procinfo",
]
[[package]]
name = "mm0b_parser"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "908327387657a2adc5231e72c7873ae4aa6e35281185ef146a750e173fda65b5"
dependencies = [
"byteorder",
"memchr",
"mm0_util",
"zerocopy",
]
[[package]]
name = "nom"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff"
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "procinfo"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c"
dependencies = [
"byteorder",
"libc",
"nom",
"rustc_version",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "second_opinion"
version = "0.2.1"
dependencies = [
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "syn"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
[[package]]
name = "zerocopy"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e59ec1d2457bd6c0dd89b50e7d9d6b0b647809bf3f0a59ac85557046950b7b2"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f"
dependencies = [
"proc-macro2",
"syn",
"synstructure",
]