diff --git a/examples/ski/.gitignore b/examples/ski/.gitignore new file mode 100644 index 00000000..0be6e726 --- /dev/null +++ b/examples/ski/.gitignore @@ -0,0 +1,16 @@ +# Cargo build +**/target + +# Cargo config +.cargo + +# Profile-guided optimization +/tmp +pgo-data.profdata + +# MacOS nuisances +.DS_Store + +# Proofs +**/proof-with-pis.json +**/proof-with-io.json diff --git a/examples/ski/program/Cargo.toml b/examples/ski/program/Cargo.toml new file mode 100644 index 00000000..81b1bd50 --- /dev/null +++ b/examples/ski/program/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +[package] +version = "0.1.0" +name = "ski-program" +edition = "2021" + +[dependencies] +wp1-zkvm = { git = "ssh://git@github.com/wormhole-foundation/wp1.git", branch = "memoset" } +ski = { path = "../ski" } + diff --git a/examples/ski/program/elf/riscv32im-succinct-zkvm-elf b/examples/ski/program/elf/riscv32im-succinct-zkvm-elf new file mode 100755 index 00000000..f61421c9 Binary files /dev/null and b/examples/ski/program/elf/riscv32im-succinct-zkvm-elf differ diff --git a/examples/ski/program/src/main.rs b/examples/ski/program/src/main.rs new file mode 100644 index 00000000..450fb6ec --- /dev/null +++ b/examples/ski/program/src/main.rs @@ -0,0 +1,18 @@ +//! A simple program to be proven inside the zkVM. + +#![no_main] +wp1_zkvm::entrypoint!(main); + +use ski::{ITerm, Mem, SKI}; + +// INFO summary: cycles=254197, e2e=4552, khz=55.84, proofSize=1.24 MiB +// (S(K(SI))K)KI evaled to K +#[allow(dead_code)] +pub fn main() { + let ski = wp1_zkvm::io::read::(); + let mem = &mut Mem::new(); + let term = ITerm::try_from_ski(mem, &ski).unwrap(); + let evaled = term.eval(mem, 0).to_ski(mem); + + wp1_zkvm::io::write(&evaled); +} diff --git a/examples/ski/script/Cargo.toml b/examples/ski/script/Cargo.toml new file mode 100644 index 00000000..c4ad2794 --- /dev/null +++ b/examples/ski/script/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +[package] +version = "0.1.0" +name = "ski-script" +edition = "2021" + +[dependencies] +wp1-sdk = { path = "../../../sdk" } +ski = { path = "../ski" } + +[build-dependencies] +wp1-helper = { path = "../../../helper" } diff --git a/examples/ski/script/build.rs b/examples/ski/script/build.rs new file mode 100644 index 00000000..3705d9ef --- /dev/null +++ b/examples/ski/script/build.rs @@ -0,0 +1,5 @@ +use wp1_helper::build_program; + +fn main() { + build_program("../program") +} diff --git a/examples/ski/script/src/main.rs b/examples/ski/script/src/main.rs new file mode 100644 index 00000000..4006d468 --- /dev/null +++ b/examples/ski/script/src/main.rs @@ -0,0 +1,35 @@ +//! A simple script to generate and verify the proof of a given program. +use std::str::FromStr; + +const ELF: &[u8] = include_bytes!("../../program/elf/riscv32im-succinct-zkvm-elf"); + +use wp1_sdk::{utils, SP1Prover, SP1Stdin, SP1Verifier}; + +use ski::SKI; + +fn main() { + // Setup a tracer for logging. + utils::setup_logger(); + + // Generate proof. + let mut stdin = SP1Stdin::new(); + let ski = SKI::from_str(&"(S(K(SI))K)KI").unwrap(); + stdin.write(&ski); + + let mut proof = SP1Prover::prove(ELF, stdin).expect("proving failed"); + + // Read output. + let evaled = proof.stdout.read::(); + assert_eq!("K", evaled.to_string()); + println!("{ski} evaled to {evaled}"); + + // Verify proof. + SP1Verifier::verify(ELF, &proof).expect("verification failed"); + + // Save proof. + proof + .save("proof-with-io.json") + .expect("saving proof failed"); + + println!("successfully generated and verified proof for the program!") +} diff --git a/examples/ski/ski/Cargo.toml b/examples/ski/ski/Cargo.toml new file mode 100644 index 00000000..c4df5960 --- /dev/null +++ b/examples/ski/ski/Cargo.toml @@ -0,0 +1,37 @@ +[workspace] +[package] +version = "0.1.0" +name = "ski" +edition = "2021" + +[dependencies] +itertools = "0.12.0" +serde = { features = ["derive"] } +p3-air = { git = "https://github.com/Plonky3/Plonky3.git" } +p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3.git" } +p3-field = { git = "https://github.com/Plonky3/Plonky3.git" } +p3-matrix = { git = "https://github.com/Plonky3/Plonky3.git" } +wp1-core = { git = "ssh://git@github.com/wormhole-foundation/wp1.git", branch = "memoset" } +wp1-derive = { git = "ssh://git@github.com/wormhole-foundation/wp1.git" } + +[patch."https://github.com/Plonky3/Plonky3.git"] +p3-air = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-field = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-commit = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-matrix = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-baby-bear = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-util = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-challenger = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-dft = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-fri = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-goldilocks = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-keccak = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-keccak-air = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-blake3 = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-mds = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-merkle-tree = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-poseidon2 = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-symmetric = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-uni-stark = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-maybe-rayon = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } +p3-bn254-fr = { git = "ssh://git@github.com/Plonky3/Plonky3.git", rev = "2871dfe257a49583c763c94a879785188716f006" } diff --git a/examples/ski/ski/src/air/builder.rs b/examples/ski/ski/src/air/builder.rs new file mode 100644 index 00000000..77c448ea --- /dev/null +++ b/examples/ski/ski/src/air/builder.rs @@ -0,0 +1,131 @@ +use crate::air::pointer::Pointer; +use itertools::chain; +use wp1_core::air::{ + AirInteraction, BaseAirBuilder, ByteAirBuilder, ExtensionAirBuilder, WordAirBuilder, +}; +use wp1_core::lookup::InteractionKind; + +/// A custom AirBuilder trait that only includes +pub trait LurkAirBuilder: + BaseAirBuilder + + MemoSetBuilder + + ConsBuilder + + RelationBuilder + + ByteAirBuilder + + WordAirBuilder + + ExtensionAirBuilder +{ +} + +pub trait MemoSetBuilder: BaseAirBuilder { + /// Make a MemoSet query + fn memoset_query( + &mut self, + tag: impl Into, + values: impl IntoIterator, + is_real: impl Into, + ) { + self.send(AirInteraction::new( + chain!([tag.into()], values.into_iter()).collect(), + is_real.into(), + InteractionKind::MemoSet, + )); + } + + /// Prove a MemoSet query (once!) + fn memoset_prove( + &mut self, + tag: impl Into, + values: impl IntoIterator, + multiplicity: impl Into, + ) { + self.receive(AirInteraction::new( + chain!([tag.into()], values.into_iter()).collect(), + multiplicity.into(), + InteractionKind::MemoSet, + )); + } +} + +//impl LurkAirBuilder for AB {} +impl MemoSetBuilder for AB {} + +//pub trait LurkAirBuilder: BaseAirBuilder + ConsBuilder + RelationBuilder {} + +pub trait ConsBuilder: BaseAirBuilder { + /// Sends a byte operation to be processed. + fn query_cons( + &mut self, + a: Pointer, + b: Pointer, + c: Pointer, + is_cons: E, + ) where + E: Into, + { + todo!("add cons domain"); + self.send(AirInteraction::new( + chain!(a.into_exprs(), b.into_exprs(), c.into_exprs()).collect(), + is_cons.into(), + InteractionKind::MemoSet, + )); + } + + /// Receives a byte operation to be processed. + fn prove_cons( + &mut self, + a: Pointer, + b: Pointer, + c: Pointer, + multiplicity: E, + ) where + E: Into, + { + todo!("add cons domain"); + self.receive(AirInteraction::new( + chain!(a.into_exprs(), b.into_exprs(), c.into_exprs()).collect(), + multiplicity.into(), + InteractionKind::MemoSet, + )); + } +} + +pub trait RelationBuilder: BaseAirBuilder { + /// Sends a byte operation to be processed. + fn query_relation( + &mut self, + tag: Etag, + a: Pointer, + b: Pointer, + is_real: EReal, + ) where + Etag: Into, + EReal: Into, + { + todo!("add relation domain"); + self.send(AirInteraction::new( + chain!(a.into_exprs(), b.into_exprs(), [tag.into()]).collect(), + is_real.into(), + InteractionKind::MemoSet, + )); + } + + /// Receives a byte operation to be processed. + fn prove_relation( + &mut self, + tag: Etag, + a: Pointer, + b: Pointer, + multiplicity: EMult, + ) where + Etag: Into, + EMult: Into, + { + todo!("add relation domain"); + self.receive(AirInteraction::new( + chain!(a.into_exprs(), b.into_exprs(), [tag.into()]).collect(), + multiplicity.into(), + InteractionKind::MemoSet, + )); + } +} diff --git a/examples/ski/ski/src/air/mod.rs b/examples/ski/ski/src/air/mod.rs new file mode 100644 index 00000000..d65d86db --- /dev/null +++ b/examples/ski/ski/src/air/mod.rs @@ -0,0 +1,2 @@ +pub mod builder; +pub mod pointer; diff --git a/examples/ski/ski/src/air/pointer.rs b/examples/ski/ski/src/air/pointer.rs new file mode 100644 index 00000000..9558d7f0 --- /dev/null +++ b/examples/ski/ski/src/air/pointer.rs @@ -0,0 +1,16 @@ +use wp1_derive::AlignedBorrow; + +#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] +pub struct Pointer { + pub idx: T, + pub tag: T, +} + +impl Pointer { + pub fn into_exprs(self) -> impl IntoIterator + where + T: Into, + { + [self.tag.into(), self.idx.into()] + } +} diff --git a/examples/ski/ski/src/chips/mod.rs b/examples/ski/ski/src/chips/mod.rs new file mode 100644 index 00000000..2701b57b --- /dev/null +++ b/examples/ski/ski/src/chips/mod.rs @@ -0,0 +1,76 @@ +use p3_air::{Air, AirBuilder, BaseAir}; +use p3_field::{AbstractField, Field}; +use p3_matrix::dense::RowMajorMatrix; +use wp1_core::stark::SP1AirBuilder; +use wp1_derive::AlignedBorrow; + +use std::mem::size_of; + +struct EvalCols {} +struct ApplyCols {} +struct Eval1Cols {} +struct SCols {} +struct S1Cols {} +struct S2Cols {} +struct S3Cols {} +struct KCols {} +struct K1Cols {} +struct K2Cols {} +struct ICols {} +struct I1Cols {} + +#[derive(AlignedBorrow)] +struct CpuCols { + x: T, +} + +struct CpuChip {} + +const NUM_CPU_COLS: usize = size_of::>(); + +impl BaseAir for CpuChip { + fn width(&self) -> usize { + NUM_CPU_COLS + } +} + +impl CpuChip { + #[allow(dead_code)] + fn generate_trace() -> RowMajorMatrix { + todo!() + } +} + +impl Air for CpuChip { + fn eval(&self, builder: &mut AB) { + todo!() + } +} + +#[cfg(test)] +mod tests { + use p3_baby_bear::BabyBear; + use p3_field::{AbstractField, Field}; + use p3_matrix::dense::RowMajorMatrix; + use wp1_core::{ + stark::StarkGenericConfig, + utils::{uni_stark_prove as prove, uni_stark_verify as verify, BabyBearPoseidon2}, + }; + + use super::*; + + fn prove_trace() { + let config = BabyBearPoseidon2::new(); + let mut challenger = config.challenger(); + + let f = BabyBear::from_canonical_usize; + + let trace: RowMajorMatrix = CpuChip::generate_trace(); + + let chip = CpuChip {}; + let proof = prove::(&config, &chip, &mut challenger, trace); + + let mut challenger = config.challenger(); + verify(&config, &chip, &mut challenger, &proof).unwrap(); + } +} diff --git a/examples/ski/ski/src/cons/air.rs b/examples/ski/ski/src/cons/air.rs new file mode 100644 index 00000000..708a9bf9 --- /dev/null +++ b/examples/ski/ski/src/cons/air.rs @@ -0,0 +1,51 @@ +use crate::air::builder::LurkAirBuilder; +use p3_air::{Air, AirBuilder, BaseAir}; +use p3_field::AbstractField; +use p3_matrix::MatrixRowSlices; +use std::{borrow::Borrow, mem::size_of}; + +use crate::cons::columns::ConsCols; +use crate::cons::{ConsChip, CAR_TAG, CDR_TAG, CONST_PTR_TAG}; + +pub const NUM_CONS_COLS: usize = size_of::>(); + +impl BaseAir for ConsChip { + fn width(&self) -> usize { + NUM_CONS_COLS + } +} + +impl Air for ConsChip +where + AB: LurkAirBuilder, +{ + fn eval(&self, builder: &mut AB) { + let main = builder.main(); + let local: &ConsCols = main.row_slice(0).borrow(); + let next: &ConsCols = main.row_slice(1).borrow(); + + // Ensure the first pointer has index 0 + builder.when_first_row().assert_zero(local.c_ptr.idx); + + // Ensure the pointer index increases at every row + builder + .when_transition() + .assert_eq(local.c_ptr.idx + AB::F::one(), next.c_ptr.idx); + + builder.assert_eq(local.c_ptr.tag, AB::F::from_canonical_u16(CONST_PTR_TAG)); + + builder.prove_cons(local.a_ptr, local.b_ptr, local.c_ptr, local.mult_cons); + builder.prove_relation( + AB::F::from_canonical_u16(CAR_TAG), + local.a_ptr, + local.c_ptr, + local.mult_car, + ); + builder.prove_relation( + AB::F::from_canonical_u16(CDR_TAG), + local.b_ptr, + local.c_ptr, + local.mult_car, + ); + } +} diff --git a/examples/ski/ski/src/cons/columns.rs b/examples/ski/ski/src/cons/columns.rs new file mode 100644 index 00000000..63992315 --- /dev/null +++ b/examples/ski/ski/src/cons/columns.rs @@ -0,0 +1,15 @@ +use wp1_derive::AlignedBorrow; + +use crate::air::pointer::Pointer; + +/// The column layout for the chip. +#[derive(AlignedBorrow, Default, Clone, Debug)] +#[repr(C)] +pub struct ConsCols { + pub a_ptr: Pointer, + pub b_ptr: Pointer, + pub c_ptr: Pointer, + pub mult_cons: T, + pub mult_car: T, + pub mult_cdr: T, +} diff --git a/examples/ski/ski/src/cons/mod.rs b/examples/ski/ski/src/cons/mod.rs new file mode 100644 index 00000000..0aa457de --- /dev/null +++ b/examples/ski/ski/src/cons/mod.rs @@ -0,0 +1,11 @@ +pub mod air; +pub mod columns; + +const CONST_PTR_TAG: u16 = 42; + +const CAR_TAG: u16 = 101; +const CDR_TAG: u16 = 102; + +/// A chip that implements the CPU. +#[derive(Default)] +pub struct ConsChip; diff --git a/examples/ski/ski/src/cpu/mod.rs b/examples/ski/ski/src/cpu/mod.rs new file mode 100644 index 00000000..e810de57 --- /dev/null +++ b/examples/ski/ski/src/cpu/mod.rs @@ -0,0 +1,262 @@ +use core::mem::size_of; +use itertools::Itertools; +use p3_air::{Air, AirBuilder, BaseAir}; +use p3_field::{AbstractField, Field}; +use p3_matrix::{dense::RowMajorMatrix, MatrixRowSlices}; +use std::borrow::{Borrow, BorrowMut}; +use wp1_core::stark::SP1AirBuilder; +use wp1_derive::AlignedBorrow; + +#[derive(AlignedBorrow, Default, Clone, Debug)] +#[repr(C)] +struct OpCols { + is_add: T, + is_sub: T, + is_mul: T, + is_div: T, +} + +/// The column layout for the chip. +#[derive(AlignedBorrow, Default, Clone, Debug)] +#[repr(C)] +struct CpuCols { + op: OpCols, + + /// Unconstrained in the first row + stack_top: T, + /// Unconstrained in rows that perform operations and consume the stack + stack_next: T, + + /// When performing a division, we need to provide the inverse + /// of stack_next. Can be 0 whenever op.is_div = 0 + stack_next_inv: T, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum Operation { + Add, + Sub, + Mul, + Div, +} + +#[derive(Default)] +struct CpuChip { + _phantom: std::marker::PhantomData, +} + +const NUM_CPU_COLS: usize = size_of::>(); + +impl CpuChip { + /// Creates a trace matrix from a sequence of n operations and a stack with n+1 values. + /// For example, providing the operations [+, -] and the values [1, 2, 3] will result on the + /// trace for 3 + 2 - 1 + #[allow(dead_code)] + fn generate_trace(ops: Vec, mut stack: Vec) -> RowMajorMatrix { + let trace_height = (ops.len() + 1).next_power_of_two(); + + let trace = ops + .into_iter() + .map(Some) + .pad_using(trace_height, |_| None) + .flat_map(|op| { + let mut row = [F::zero(); NUM_CPU_COLS]; + let cols: &mut CpuCols = row.as_mut_slice().borrow_mut(); + + match op { + None => { + assert_eq!(stack.len(), 1); + cols.stack_top = stack[0]; + } + Some(op) => { + let stack_top = stack.pop().unwrap(); + let stack_next = stack.pop().unwrap(); + + let result = match op { + Operation::Add => { + cols.op.is_add = F::one(); + stack_top + stack_next + } + Operation::Sub => { + cols.op.is_sub = F::one(); + stack_top - stack_next + } + Operation::Mul => { + cols.op.is_mul = F::one(); + stack_top * stack_next + } + Operation::Div => { + cols.op.is_div = F::one(); + let inv = stack_next.inverse(); + cols.stack_next_inv = inv; + + stack_top * inv + } + }; + stack.push(result); + cols.stack_top = stack_top; + cols.stack_next = stack_next + } + } + row + }) + .collect(); + + RowMajorMatrix::new(trace, NUM_CPU_COLS) + } +} + +impl BaseAir for CpuChip { + fn width(&self) -> usize { + NUM_CPU_COLS + } +} + +impl Air for CpuChip +where + AB: SP1AirBuilder, +{ + fn eval(&self, builder: &mut AB) { + let main = builder.main(); + let local: &CpuCols = main.row_slice(0).borrow(); + let next: &CpuCols = main.row_slice(1).borrow(); + + let op_is_some = local.op.is_add + local.op.is_sub + local.op.is_mul + local.op.is_div; + + // Constrain op flags + { + // Ensure all flags are boolean + builder.assert_bool(local.op.is_add); + builder.assert_bool(local.op.is_sub); + builder.assert_bool(local.op.is_mul); + builder.assert_bool(local.op.is_div); + + // Ensure only 1 or 0 flags are set + builder.assert_bool(op_is_some.clone()); + } + + // op ADD + { + let result = local.stack_top + local.stack_next; + + // We need `when_transition` to skip the constraint in the last row, for which + // `next` roundtrips and points to the first row. + // Also, we should avoid degrees higher than 3 for efficiency purposes. The following + // reaches degree 3 with two "when" followed by an "assert" + builder + .when(local.op.is_add) + .when_transition() + .assert_eq(result, next.stack_top); + } + + // op SUB + { + let result = local.stack_top - local.stack_next; + + builder + .when(local.op.is_sub) + .when_transition() + .assert_eq(result, next.stack_top); + } + + // op MUL + { + let result = local.stack_top * local.stack_next; + + builder + .when(local.op.is_mul) + .when_transition() + .assert_eq(result, next.stack_top); + } + + // op DIV + { + // Check that stack_next has an inverse stack_next_inv + builder + .when(local.op.is_div) + .assert_eq(local.stack_next * local.stack_next_inv, AB::F::one()); + + let result = local.stack_top * local.stack_next_inv; + + builder + .when(local.op.is_div) + .when_transition() + .assert_eq(result, next.stack_top); + } + + // no op + { + // When there's no operation to be performed, the the stack can't change + builder + .when_not(op_is_some.clone()) + .when_transition() + .assert_eq(local.stack_top, next.stack_top); + + builder + .when_not(op_is_some) + .when_transition() + .assert_eq(local.stack_next, next.stack_next) + } + } +} + +#[cfg(test)] +mod tests { + use p3_baby_bear::BabyBear; + use p3_field::{AbstractField, Field}; + use p3_matrix::dense::RowMajorMatrix; + use wp1_core::{ + stark::StarkGenericConfig, + utils::{uni_stark_prove as prove, uni_stark_verify as verify, BabyBearPoseidon2}, + }; + + use super::*; + + #[test] + fn prove_trace() { + let config = BabyBearPoseidon2::new(); + let mut challenger = config.challenger(); + + let f = BabyBear::from_canonical_u16; + + let ops = vec![ + Operation::Add, + Operation::Sub, + Operation::Mul, + Operation::Div, + ]; + let stack = vec![f(3), f(7), f(3), f(4), f(2)]; + + let trace: RowMajorMatrix = CpuChip::generate_trace(ops, stack); + + let trace_expected = { + let inv = f(3).inverse(); + assert_eq!(f(21) * inv, f(7)); + let trace_expected = [ + // stack_top─┐ ┌─stack_next + // ┌─add ┌─sub ┌─mul ┌─div │ │ ┌─stack_next_inv + [f(1), f(0), f(0), f(0), f(2), f(4), f(0)], // 2 + 4 => 6 + [f(0), f(1), f(0), f(0), f(6), f(3), f(0)], // 6 - 3 => 3 + [f(0), f(0), f(1), f(0), f(3), f(7), f(0)], // 3 * 7 => 21 + [f(0), f(0), f(0), f(1), f(21), f(3), inv], // 21 / 3 => 7 + [f(0), f(0), f(0), f(0), f(7), f(0), f(0)], // 7 + // fill rows until we reach the next power of two + [f(0), f(0), f(0), f(0), f(7), f(0), f(0)], // 7 + [f(0), f(0), f(0), f(0), f(7), f(0), f(0)], // 7 + [f(0), f(0), f(0), f(0), f(7), f(0), f(0)], // 7 + ] + .into_iter() + .flatten() + .collect(); + RowMajorMatrix::new(trace_expected, NUM_CPU_COLS) + }; + assert_eq!(trace, trace_expected); + + let chip = CpuChip::default(); + let proof = prove::(&config, &chip, &mut challenger, trace); + + let mut challenger = config.challenger(); + verify(&config, &chip, &mut challenger, &proof).unwrap(); + } +} diff --git a/examples/ski/ski/src/interned.rs b/examples/ski/ski/src/interned.rs new file mode 100644 index 00000000..248d4ae5 --- /dev/null +++ b/examples/ski/ski/src/interned.rs @@ -0,0 +1,816 @@ +#![allow(non_snake_case)] + +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use std::collections::HashMap; +use std::io; + +use crate::{ParseTermError, SKI}; + +type Addr = usize; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ITerm { + S(Addr, Option, Option, Option), + K(Addr, Option, Option), + I(Addr, Option), + Seq(Addr, Addr, Addr), + Nil, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Op { + Eval1(ITerm), + Apply(ITerm, ITerm), + Eval(ITerm), +} + +#[derive(Debug)] +pub struct Step { + op: Op, + out: ITerm, + depth: usize, +} + +impl Step { + pub fn fmt_to_string(&self, mem: &Mem) -> String { + let mut out = Vec::new(); + self.fmt(mem, &mut out).unwrap(); + String::from_utf8(out).unwrap() + } + + pub fn fmt(&self, mem: &Mem, w: &mut W) -> Result<(), io::Error> { + write!(w, "[{}]", self.depth)?; + for _ in 0..self.depth { + write!(w, " ")?; + } + + match &self.op { + Op::Eval1(term) => { + write!(w, "Eval1 ")?; + term.fmt(mem, w)?; + } + Op::Eval(term) => { + write!(w, "Eval ")?; + term.fmt(mem, w)?; + } + Op::Apply(left, right) => { + write!(w, "Apply ")?; + left.fmt(mem, w)?; + write!(w, "<-[")?; + right.fmt(mem, w)?; + write!(w, "] ")?; + } + } + write!(w, " => ")?; + self.out.fmt(mem, w) + } + + fn fmt_steps>, W: io::Write>( + steps: I, + mem: &Mem, + w: &mut W, + ) -> Result<(), io::Error> { + for step in steps { + step.borrow().fmt(mem, w)?; + write!(w, "\n")?; + } + Ok(()) + } + + pub fn fmt_steps_to_string>>( + steps: I, + mem: &Mem, + ) -> String { + let mut out = Vec::new(); + Self::fmt_steps(steps, mem, &mut out).unwrap(); + String::from_utf8(out).unwrap() + } +} +const NIL_ADDR: usize = 3; + +impl ITerm { + pub fn is_s(&self) -> bool { + matches!(self, Self::S(_, None, None, None)) + } + pub fn is_s1(&self) -> bool { + matches!(self, Self::S(_, Some(_), None, None)) + } + pub fn is_s2(&self) -> bool { + matches!(self, Self::S(_, Some(_), Some(_), None)) + } + pub fn is_s3(&self) -> bool { + matches!(self, Self::S(_, Some(_), Some(_), Some(_))) + } + pub fn is_k(&self) -> bool { + matches!(self, Self::K(_, None, None)) + } + pub fn is_k1(&self) -> bool { + matches!(self, Self::K(_, Some(_), None)) + } + pub fn is_k2(&self) -> bool { + matches!(self, Self::K(_, Some(_), Some(_))) + } + pub fn is_i(&self) -> bool { + matches!(self, Self::I(_, None)) + } + pub fn is_i1(&self) -> bool { + matches!(self, Self::I(_, Some(_),)) + } + + pub fn try_from_ski(mem: &mut Mem, ski: &SKI) -> Result { + Self::from_str(mem, &ski.src) + } + + pub fn to_ski(&self, mem: &Mem) -> SKI { + SKI { + src: self.fmt_to_string(mem), + } + } + + pub fn addr(&self) -> Addr { + match self { + Self::S(addr, _, _, _) => *addr, + Self::K(addr, _, _) => *addr, + Self::I(addr, _) => *addr, + Self::Seq(addr, _, _) => *addr, + Self::Nil => 3, + } + } + + fn first(&self, mem: &mut Mem) -> (Self, Option) { + match self { + Self::Seq(_, first, rest) => { + let rest_term = mem.get_term(*rest); + let tail = match rest_term { + Self::Nil => None, + _ => Some(rest_term), + }; + let first_term = mem.get_term(*first); + assert!(first_term != ITerm::Nil); + (first_term, tail) + } + _ => { + panic!("xxxx") + } + } + } + + pub fn from_str(mem: &mut Mem, s: &str) -> Result { + if s.is_empty() { + Err(ParseTermError) + } else { + let mut stack = Vec::new(); + let mut inner = Vec::new(); + + for c in s.chars() { + match c { + 'S' | 's' => inner.push(mem.S().addr()), + 'K' | 'k' => inner.push(mem.K().addr()), + 'I' | 'i' => inner.push(mem.I().addr()), + '(' => { + stack.push(inner); + inner = Vec::new(); + } + ')' => { + let term = mem.seq(&inner); + inner = stack.pop().ok_or(ParseTermError)?; + inner.push(term.addr()); + } + _ => Err(ParseTermError)?, + }; + } + if inner.len() == 1 { + Ok(mem.get_term(inner[0])) + } else { + Ok(mem.seq(&inner)) + } + } + } + pub fn fmt_to_string(&self, mem: &Mem) -> String { + let mut out = Vec::new(); + self.fmt(mem, &mut out).unwrap(); + String::from_utf8(out).unwrap() + } + + pub fn fmt(&self, mem: &Mem, w: &mut W) -> Result<(), io::Error> { + match self { + Self::S(_, None, None, None) => { + write!(w, "S") + } + Self::S(_, Some(x), None, None) => { + write!(w, "(S")?; + mem.borrow_term(*x).fmt(mem, w)?; + write!(w, ")") + } + Self::S(_, Some(x), Some(y), None) => { + write!(w, "(S")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w)?; + write!(w, ")") + } + Self::S(_, Some(x), Some(y), Some(z)) => { + write!(w, "(S")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w)?; + mem.borrow_term(*z).fmt(mem, w)?; + write!(w, ")") + } + Self::K(_, None, None) => { + write!(w, "K") + } + Self::K(_, Some(x), None) => { + write!(w, "(K")?; + mem.borrow_term(*x).fmt(mem, w)?; + write!(w, ")") + } + Self::K(_, Some(x), Some(y)) => { + write!(w, "(K")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w)?; + write!(w, ")") + } + Self::I(_, None) => { + write!(w, "I") + } + Self::I(_, Some(x)) => { + write!(w, "(I")?; + mem.borrow_term(*x).fmt(mem, w)?; + write!(w, ")") + } + Self::S(_, _, _, _) => write!(w, ""), + Self::K(_, _, _) => write!(w, ""), + Self::Seq(_, first, rest) => { + write!(w, "(")?; + mem.borrow_term(*first).fmt(mem, w)?; + + let mut tail = rest.clone(); + loop { + match mem.borrow_term(tail) { + ITerm::Seq(_, first, rest) => { + mem.borrow_term(*first).fmt(mem, w)?; + tail = *rest; + } + last => { + last.fmt(mem, w)?; + write!(w, ")")?; + break; + } + } + } + Ok(()) + } + Self::Nil => Ok(()), + } + } + pub fn fmt3(&self, mem: &Mem, w: &mut W) -> Result<(), io::Error> { + match self { + Self::S(_, None, None, None) => { + write!(w, "S") + } + Self::S(_, Some(x), None, None) => { + write!(w, "S")?; + mem.borrow_term(*x).fmt(mem, w) + } + Self::S(_, Some(x), Some(y), None) => { + write!(w, "S")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w) + } + Self::S(_, Some(x), Some(y), Some(z)) => { + write!(w, "S")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w)?; + mem.borrow_term(*z).fmt(mem, w) + } + Self::K(_, None, None) => { + write!(w, "K") + } + Self::K(_, Some(x), None) => { + write!(w, "K")?; + mem.borrow_term(*x).fmt(mem, w) + } + Self::K(_, Some(x), Some(y)) => { + write!(w, "K")?; + mem.borrow_term(*x).fmt(mem, w)?; + mem.borrow_term(*y).fmt(mem, w) + } + Self::I(_, None) => { + write!(w, "I") + } + Self::I(_, Some(x)) => { + write!(w, "I")?; + mem.borrow_term(*x).fmt(mem, w) + } + Self::S(_, _, _, _) => write!(w, ""), + Self::K(_, _, _) => write!(w, ""), + Self::Seq(_, first, rest) => { + write!(w, "(")?; + mem.borrow_term(*first).fmt(mem, w)?; + + let mut tail = rest.clone(); + loop { + match mem.borrow_term(tail) { + ITerm::Seq(_, first, rest) => { + mem.borrow_term(*first).fmt(mem, w)?; + tail = *rest; + } + last => { + last.fmt(mem, w)?; + write!(w, ")")?; + break; + } + } + } + Ok(()) + } + Self::Nil => Ok(()), + } + } + + pub fn fmt2_to_string(&self, mem: &Mem) -> String { + let mut out = Vec::new(); + self.fmt(mem, &mut out).unwrap(); + String::from_utf8(out).unwrap() + } + + pub fn fmt2(&self, mem: &Mem, w: &mut W) -> Result<(), io::Error> { + match self { + Self::S(_, None, None, None) => { + write!(w, "S") + } + Self::S(_, Some(x), None, None) => { + write!(w, "(S1")?; + mem.borrow_term(*x).fmt2(mem, w)?; + write!(w, ")") + } + Self::S(_, Some(x), Some(y), None) => { + write!(w, "(S2")?; + mem.borrow_term(*x).fmt2(mem, w)?; + mem.borrow_term(*y).fmt2(mem, w)?; + write!(w, ")") + } + Self::S(_, Some(x), Some(y), Some(z)) => { + write!(w, "(S3")?; + mem.borrow_term(*x).fmt2(mem, w)?; + mem.borrow_term(*y).fmt2(mem, w)?; + mem.borrow_term(*z).fmt2(mem, w)?; + write!(w, ")") + } + Self::K(_, None, None) => { + write!(w, "K~") + } + Self::K(_, Some(x), None) => { + write!(w, "(K1")?; + mem.borrow_term(*x).fmt2(mem, w)?; + write!(w, ")") + } + Self::K(_, Some(x), Some(y)) => { + write!(w, "(K2")?; + mem.borrow_term(*x).fmt2(mem, w)?; + mem.borrow_term(*y).fmt2(mem, w)?; + write!(w, ")") + } + Self::I(_, None) => { + write!(w, "I") + } + Self::I(_, Some(x)) => { + write!(w, "(I")?; + mem.borrow_term(*x).fmt2(mem, w)?; + write!(w, ")") + } + Self::S(_, _, _, _) => write!(w, ""), + Self::K(_, _, _) => write!(w, ""), + Self::Seq(_, first, rest) => { + write!(w, "(")?; + mem.borrow_term(*first).fmt2(mem, w)?; + + let mut tail = rest.clone(); + loop { + match mem.borrow_term(tail) { + ITerm::Seq(_, first, rest) => { + mem.borrow_term(*first).fmt2(mem, w)?; + tail = *rest; + } + last => { + last.fmt2(mem, w)?; + write!(w, ")")?; + break; + } + } + } + Ok(()) + } + Self::Nil => Ok(()), + } + } + + fn eval1(self, mem: &mut Mem, depth: usize) -> Self { + let op = Op::Eval1(self.clone()); + + if let Some(found) = mem.memo.get(&op) { + return found.clone(); + } + + let step = Step { + op: op.clone(), + // Placeholder: this will be updated when output is known. + out: self.clone(), + depth: depth, + }; + mem.steps.push(step); + let step_index = mem.steps.len() - 1; + + let result = match self { + Self::S(_, Some(x), Some(y), Some(z)) => { + let ix = mem.get_term(x); + let iy = mem.get_term(y); + let iz = mem.get_term(z); + let xz = ix + .apply(mem, iz.clone(), 1 + depth) + .eval1(mem, 1 + depth) + .clone(); + let yz = iy.apply(mem, iz, depth + 1).eval1(mem, depth + 1).clone(); + xz.apply(mem, yz, depth + 1) + } + Self::S(_, _, _, _) => self, + Self::K(_, Some(x), Some(_)) => mem.get_term(x), + Self::K(_, _, _) => self, + Self::I(_, Some(x)) => mem.get_term(x), + Self::I(_, None) => self, + Self::Seq(_, first, rest) => { + let first = mem.get_term(first); + let first_evaled = first.clone().eval(mem, depth + 1); + if mem.get_term(rest) == ITerm::Nil { + if first_evaled == first { + first_evaled + } else { + mem.cons(first_evaled, Self::Nil) + } + } else { + let rest = mem.get_term(rest); + let (second, tail) = rest.first(mem); + let second_evaled = second.clone().eval(mem, depth + 1); + let applied = first_evaled.clone().apply(mem, second_evaled, depth + 1); + + if let Some(tail) = tail { + mem.cons(applied, tail) + } else { + mem.cons(applied, Self::Nil) + } + } + } + Self::Nil => unreachable!(), + }; + + mem.steps[step_index].out = result.clone(); + mem.memo.insert(op, result.clone()); + result + } + + pub fn eval(self, mem: &mut Mem, depth: usize) -> Self { + let op = Op::Eval(self.clone()); + let step = Step { + op: op.clone(), + out: self.clone(), + depth, + }; + mem.steps.push(step); + let step_index = mem.steps.len() - 1; + + if let Some(found) = mem.memo.get(&op) { + return found.clone(); + } + + let mut prev_addr; + let mut term = self.clone(); + + loop { + prev_addr = term.addr(); + term = term.eval1(mem, depth + 1); + + if term.addr() == prev_addr { + break; + }; + } + mem.steps[step_index].out = term.clone(); + mem.memo.insert(op, term.clone()); + + term + } + + fn apply(self, mem: &mut Mem, a: Self, depth: usize) -> Self { + let op = Op::Apply(self.clone(), a.clone()); + let step = Step { + op: op.clone(), + out: self.clone(), + depth, + }; + mem.steps.push(step); + let step_index = mem.steps.len() - 1; + + if let Some(found) = mem.memo.get(&op) { + return found.clone(); + } + + let result = match self { + Self::S(_, None, None, None) => mem.S1(a.addr()), + Self::S(_, Some(x), None, None) => mem.S2(x, a.addr()), + Self::S(_, Some(x), Some(y), None) => mem.S3(x, y, a.addr()), + Self::K(_, None, None) => mem.K1(a.addr()), + Self::K(_, Some(x), None) => mem.K2(x, a.addr()), + Self::I(_, None) => mem.I1(a.addr()), + Self::I(_, Some(x)) => mem.get_term(x).apply(mem, a, depth + 1), + Self::Seq(_, _, _) => { + let applied = self.clone().eval(mem, depth + 1).apply(mem, a, depth + 1); + applied //mem.cons(applied, Self::Nil) + } + Self::S(_, _, _, _) => panic!("malformed S"), + Self::K(_, _, _) => panic!("malformed K"), + Self::Nil => unreachable!(), + }; + if let Some(found) = mem.memo.get(&op) { + assert_eq!(found, &result); + } + + mem.steps[step_index].out = result.clone(); + mem.memo.insert(op, result.clone()); + result + } +} + +#[derive(Debug, Default)] +pub struct Mem { + terms: Vec, + steps: Vec, + memo: HashMap, + s1: HashMap, + s2: HashMap<(Addr, Addr), Addr>, + s3: HashMap<(Addr, Addr, Addr), Addr>, + k1: HashMap, + k2: HashMap<(Addr, Addr), Addr>, + i1: HashMap, + seqs: HashMap<(Addr, Addr), Addr>, +} + +impl Mem { + pub fn new() -> Self { + let mut mem = Mem::default(); + mem.terms = vec![ + ITerm::S(0, None, None, None), + ITerm::K(1, None, None), + ITerm::I(2, None), + ITerm::Nil, + ]; + + mem + } + + pub fn input(&self) -> ITerm { + match &self.steps[0].op { + Op::Eval(input) => input.clone(), + _ => panic!("Mem does not describe a toplevel evaluation."), + } + } + + pub fn output(&self) -> ITerm { + self.steps[0].out.clone() + } + + pub fn borrow_term(&self, addr: Addr) -> &ITerm { + &self.terms[addr] + } + + pub fn get_term(&self, addr: Addr) -> ITerm { + self.terms[addr].clone() + } + + // NOTE: The clones are shallow. + pub fn S(&mut self) -> ITerm { + self.terms[0].clone() + } + pub fn K(&mut self) -> ITerm { + self.terms[1].clone() + } + pub fn I(&mut self) -> ITerm { + self.terms[2].clone() + } + + pub fn S1(&mut self, x_addr: Addr) -> ITerm { + if let Some(found) = self.s1.get(&x_addr) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::S(addr, Some(x_addr), None, None); + self.s1.insert(x_addr, addr); + self.terms.push(new.clone()); + new + } + } + pub fn S2(&mut self, x_addr: Addr, y_addr: Addr) -> ITerm { + if let Some(found) = self.s2.get(&(x_addr, y_addr)) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::S(addr, Some(x_addr), Some(y_addr), None); + self.s2.insert((x_addr, y_addr), addr); + self.terms.push(new.clone()); + new + } + } + pub fn S3(&mut self, x_addr: Addr, y_addr: Addr, z_addr: Addr) -> ITerm { + if let Some(found) = self.s3.get(&(x_addr, y_addr, z_addr)) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::S(addr, Some(x_addr), Some(y_addr), Some(z_addr)); + self.s3.insert((x_addr, y_addr, z_addr), addr); + self.terms.push(new.clone()); + new + } + } + pub fn K1(&mut self, x_addr: Addr) -> ITerm { + if let Some(found) = self.k1.get(&x_addr) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::K(addr, Some(x_addr), None); + self.k1.insert(x_addr, addr); + self.terms.push(new.clone()); + new + } + } + pub fn K2(&mut self, x_addr: Addr, y_addr: Addr) -> ITerm { + if let Some(found) = self.k2.get(&(x_addr, y_addr)) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::K(addr, Some(x_addr), Some(y_addr)); + self.k2.insert((x_addr, y_addr), addr); + self.terms.push(new.clone()); + new + } + } + pub fn I1(&mut self, x_addr: Addr) -> ITerm { + if let Some(found) = self.i1.get(&x_addr) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::I(addr, Some(x_addr)); + self.i1.insert(x_addr, addr); + self.terms.push(new.clone()); + new + } + } + pub fn seq(&mut self, addrs: &[Addr]) -> ITerm { + if addrs.is_empty() { + return ITerm::Nil; + } + let first_addr = addrs[0]; + let rest_seqs = self.seq(&addrs[1..]); + + if let Some(found) = self.seqs.get(&(first_addr, rest_seqs.addr())) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + if addrs.len() > 1 { + self.seqs.insert((first_addr, rest_seqs.addr()), addr); + let new = ITerm::Seq(addr, addrs[0], rest_seqs.addr()); + self.terms.push(new.clone()); + new + } else { + let new = ITerm::Seq(addr, addrs[0], NIL_ADDR); + self.terms.push(new.clone()); + new + } + } + } + + pub fn cons(&mut self, first: ITerm, rest: ITerm) -> ITerm { + if let Some(found) = self.seqs.get(&(first.addr(), rest.addr())) { + self.terms[*found].clone() + } else { + let addr = self.terms.len(); + let new = ITerm::Seq(addr, first.addr(), rest.addr()); + self.terms.push(new.clone()); + new + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn eval_test(input: &str, expected: &str) { + let mem = &mut Mem::new(); + let term = ITerm::from_str(mem, input).unwrap(); + let evaled = term.clone().eval(mem, 0); + + if expected != evaled.fmt2_to_string(mem) { + let _ = dbg!(&input); + let _ = dbg!(Step::fmt_steps(&mem.steps, mem, &mut io::stdout())); + // dbg!(&mem); + } + assert_eq!(expected, evaled.fmt_to_string(mem)); + assert_eq!(term, mem.input()); + assert_eq!(evaled, mem.output()); + } + + #[test] + // cargo test debug -- --nocapture + fn test_debug_reduction() { + eval_test("(S(K(SI)))", "(S(K(SI)))"); + eval_test("(K(SI))", "(K(SI))"); + eval_test("(S(K(SI))K)", "(S(K(SI))K)"); + eval_test("((K(SI))K)KKI", "(KK)"); + eval_test("((K(SI))K)KK", "(K(KK))"); + eval_test("((((K(SI))K)K)K)", "(K(KK))"); + eval_test(&"K((SII)(SII))", "(K(SII(SII)))"); + eval_test("SIKKI", "(KK)"); + } + + #[test] + fn test_iterm() { + let test = |input: &str, expected: &str| { + let mem = &mut Mem::new(); + let term = ITerm::from_str(mem, input).unwrap(); + assert_eq!(expected, term.fmt_to_string(mem)); + }; + + test("K", "K"); + test("(S)", "(S)"); + test("(SI)", "(SI)"); + test("KIS(S)", "(KIS(S))"); + test("S(K(IS)S)KI", "(S(K(IS)S)KI)"); + + eval_test("SK(KS)", "(SK(KS))"); + + eval_test("SII(SII)", "(SII(SII))"); + eval_test("((SII)(SII))", "(SII(SII))"); + eval_test(&"K((SII)(SII))", "(K(SII(SII)))"); + + eval_test("K", "K"); + eval_test("KK", "(KK)"); + eval_test("SKSK", "K"); + + eval_test("SII(I)", "I"); + + eval_test("SIKKI", "(KK)"); + eval_test("((K(SI))K)", "(SI)"); + + eval_test("(K(SI))", "(K(SI))"); + eval_test("(S(K(SI)))", "(S(K(SI)))"); + eval_test("(S(K(SI))K)", "(S(K(SI))K)"); + + eval_test("(S(K(SI))K)KI", "K"); + eval_test("((K(SI))K)KKI", "(KK)"); + + eval_test("((K(SI))K)KK", "(K(KK))"); + + eval_test("(((K(SI))K)K)", "(SIK)"); + eval_test("((((K(SI))K)K)K)", "(K(KK))"); + + eval_test("((SII)(SII))", "(SII(SII))"); + + // these pass + eval_test("SIK(K)", "(K(KK))"); + eval_test("K(K)K", "K"); + eval_test("((K(SI))K)K", "(SIK)"); + eval_test("SIKK", "(K(KK))"); + eval_test("SIKKK", "(KK)"); + eval_test("(K(SI))K", "(SI)"); + eval_test("(SI)KKI", "(KK)"); + eval_test("(KK)KKI", "K"); + + // boolean + let t = &"K"; + let f = &"(SK)"; + let not = format!("({f})({t})"); + let not_t = format!("({t}){not}"); + let not_f = format!("({f}){not}"); + let or = format!("{t}"); + let and = format!("{f}"); + + eval_test(¬_t, &format!("{f}")); + eval_test(¬_f, &format!("{t}")); + + let t_and_t = format!("({t})({t})({and})"); + let t_and_f = format!("({t})({f})({and})"); + let f_and_f = format!("({f})({f})({and})"); + let f_and_t = format!("({f})({t})({and})"); + eval_test(&t_and_t, t); + eval_test(&t_and_f, f); + eval_test(&f_and_f, f); + eval_test(&f_and_t, f); + + let t_or_t = format!("({t})({or})({t})"); + let t_or_f = format!("({t})({or})({f})"); + let f_or_f = format!("({f})({or})({f})"); + let f_or_t = format!("({f})({or})({t})"); + eval_test(&t_or_t, t); + eval_test(&t_or_f, t); + eval_test(&f_or_f, f); + eval_test(&f_or_t, t); + } +} diff --git a/examples/ski/ski/src/lib.rs b/examples/ski/ski/src/lib.rs new file mode 100644 index 00000000..cfb89444 --- /dev/null +++ b/examples/ski/ski/src/lib.rs @@ -0,0 +1,9 @@ +pub mod air; +pub mod chips; +pub mod cons; +pub mod cpu; +pub mod interned; +pub mod terms; + +pub use crate::interned::{ITerm, Mem}; +pub use crate::terms::{ParseTermError, SKI}; diff --git a/examples/ski/ski/src/terms.rs b/examples/ski/ski/src/terms.rs new file mode 100644 index 00000000..9dffc30a --- /dev/null +++ b/examples/ski/ski/src/terms.rs @@ -0,0 +1,27 @@ +#![allow(non_snake_case)] +use serde::{Deserialize, Serialize}; +//use std::convert::TryFrom; +use std::fmt::Formatter; +use std::str::FromStr; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SKI { + pub src: String, +} + +#[derive(Debug)] +pub struct ParseTermError; + +impl FromStr for SKI { + type Err = ParseTermError; + + fn from_str(s: &str) -> Result { + Ok(Self { src: s.into() }) + } +} + +impl std::fmt::Display for SKI { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", self.src) + } +}