use midnight_proofs::{
circuit::{Layouter, Region, Value},
plonk::Error,
};
use super::{
varlen::ScannerVec, NativeAutomaton, ScannerChip, ALPHABET_MAX_SIZE, AUTOMATON_PARALLELISM,
NB_AUTOMATON_COLS,
};
use crate::{
field::AssignedNative, instructions::AssignmentInstructions, parsing::scanner::AutomatonParser,
types::AssignedByte, vec::AssignedVector, CircuitField,
};
impl<F> NativeAutomaton<F>
where
F: CircuitField + Ord,
{
fn next_transition(
&self,
state: &AssignedNative<F>,
letter: &AssignedNative<F>,
) -> Result<(Value<F>, Value<F>), Error> {
let target_opt = state.value().zip(letter.value()).map(|(s, l)| self.get_transition(s, l));
target_opt.error_if_known_and(|o| o.is_none())?;
let target = target_opt.map(|o| o.unwrap());
Ok((target.map(|t| t.0), target.map(|t| t.1)))
}
}
impl<F> ScannerChip<F>
where
F: CircuitField + Ord,
{
pub(super) fn parse_automaton(
&self,
layouter: &mut impl Layouter<F>,
automaton: &NativeAutomaton<F>,
input: &[AssignedNative<F>],
) -> Result<Vec<AssignedNative<F>>, Error> {
let init_state: AssignedNative<F> =
self.native_gadget.assign_fixed(layouter, automaton.initial_state)?;
let invalid_letter: AssignedNative<F> =
self.native_gadget.assign_fixed(layouter, F::from(ALPHABET_MAX_SIZE as u64))?;
let zero: AssignedNative<F> = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
layouter.assign_region(
|| "parsing layout",
|mut region| {
let mut offset = 0;
let mut outputs = Vec::with_capacity(input.len());
let mut state = init_state.copy_advice(
|| "initial state",
&mut region,
self.config.advice_cols[0],
offset,
)?;
for chunk in input.chunks(AUTOMATON_PARALLELISM) {
for (batch, letter) in chunk.iter().enumerate() {
self.apply_one_transition(
&mut region,
automaton,
&mut state,
letter,
batch,
&mut outputs,
&mut offset,
)?;
}
}
#[allow(clippy::modulo_one)]
self.assert_final_state(
&mut region,
&invalid_letter,
&zero,
input.len() % AUTOMATON_PARALLELISM,
&mut offset,
)?;
Ok(outputs)
},
)
}
#[allow(clippy::too_many_arguments)]
fn apply_one_transition(
&self,
region: &mut Region<'_, F>,
automaton: &NativeAutomaton<F>,
state: &mut AssignedNative<F>,
letter: &AssignedNative<F>,
batch: usize,
outputs: &mut Vec<AssignedNative<F>>,
offset: &mut usize,
) -> Result<(), Error> {
self.config.q_automaton.enable(region, *offset)?;
let base = NB_AUTOMATON_COLS * batch;
letter.copy_advice(
|| format!("letter batch {batch}"),
region,
self.config.advice_cols[base + 1],
*offset,
)?;
let (next_state_val, output_val) = automaton.next_transition(state, letter)?;
let output = region.assign_advice(
|| format!("output batch {batch}"),
self.config.advice_cols[base + 2],
*offset,
|| output_val,
)?;
outputs.push(output);
let target_col = if batch == AUTOMATON_PARALLELISM - 1 {
*offset += 1;
0
} else {
base + NB_AUTOMATON_COLS
};
*state = region.assign_advice(
|| format!("next state batch {batch}"),
self.config.advice_cols[target_col],
*offset,
|| next_state_val,
)?;
Ok(())
}
fn assert_final_state(
&self,
region: &mut Region<'_, F>,
invalid_letter: &AssignedNative<F>,
zero: &AssignedNative<F>,
batch: usize,
offset: &mut usize,
) -> Result<(), Error> {
self.config.q_automaton.enable(region, *offset)?;
let base = NB_AUTOMATON_COLS * batch;
invalid_letter.copy_advice(
|| format!("final check letter ({ALPHABET_MAX_SIZE})"),
region,
self.config.advice_cols[base + 1],
*offset,
)?;
for col in (base + 2)..(NB_AUTOMATON_COLS * AUTOMATON_PARALLELISM) {
zero.copy_advice(
|| "parsing trailing 0",
region,
self.config.advice_cols[col],
*offset,
)?;
}
*offset += 1;
zero.copy_advice(
|| "parsing trailing 0",
region,
self.config.advice_cols[0],
*offset,
)?;
Ok(())
}
}
impl<F> ScannerChip<F>
where
F: CircuitField + Ord,
{
pub(crate) fn load_automata_table(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
let cache = self.automaton_cache.borrow();
layouter.assign_table(
|| "automaton table",
|mut table| {
let mut offset = 0;
let mut add_entry =
|source: F, letter: F, target: F, output: F| -> Result<(), Error> {
table.assign_cell(
|| "t_source",
self.config.t_source,
offset,
|| Value::known(source),
)?;
table.assign_cell(
|| "t_letter",
self.config.t_letter,
offset,
|| Value::known(letter),
)?;
table.assign_cell(
|| "t_target",
self.config.t_target,
offset,
|| Value::known(target),
)?;
table.assign_cell(
|| "t_output",
self.config.t_output,
offset,
|| Value::known(output),
)?;
offset += 1;
Ok(())
};
add_entry(F::ZERO, F::ZERO, F::ZERO, F::ZERO)?;
let filler = F::from(ALPHABET_MAX_SIZE as u64);
for automaton in cache.values() {
for (source, inner) in automaton.transitions.iter() {
for (letter, (target, output_extr)) in inner.iter() {
assert!(
*source != F::ZERO && *target != F::ZERO,
"sanity check failed: the circuit requires that state 0 \
is not used, but the automaton generation failed to \
ensure it."
);
add_entry(*source, *letter, *target, *output_extr)?
}
}
for state in automaton.final_states.iter() {
add_entry(*state, filler, F::ZERO, F::ZERO)?
}
}
Ok(())
},
)
}
}
impl<F> ScannerChip<F>
where
F: CircuitField + Ord,
{
fn resolve_automaton(&self, parser: &AutomatonParser) -> NativeAutomaton<F> {
if let Some(aut) = self.automaton_cache.borrow().get(parser) {
return aut.clone();
}
let raw_automaton = match parser {
AutomatonParser::Static(spec) => self.config.static_library[spec].clone().1,
AutomatonParser::Dynamic(regex) => regex.to_automaton(),
};
let offset = {
let mut ms = self.max_state.borrow_mut();
let o = *ms;
*ms += raw_automaton.nb_states;
o
};
let native: NativeAutomaton<F> = raw_automaton.offset_states(offset).into();
self.automaton_cache.borrow_mut().insert(parser.clone(), native.clone());
native
}
pub fn parse(
&self,
layouter: &mut impl Layouter<F>,
parser: AutomatonParser,
input: &[AssignedByte<F>],
) -> Result<Vec<AssignedNative<F>>, Error> {
let automaton = self.resolve_automaton(&parser);
let native_input: Vec<AssignedNative<F>> = input.iter().map(AssignedNative::from).collect();
self.parse_automaton(layouter, &automaton, &native_input)
}
pub fn parse_varlen<const M: usize, const A: usize>(
&self,
layouter: &mut impl Layouter<F>,
parser: AutomatonParser,
input: &ScannerVec<F, M, A>,
) -> Result<AssignedVector<F, AssignedNative<F>, M, A>, Error> {
let automaton = self.resolve_automaton(&parser);
let buffer = self.parse_automaton(layouter, &automaton, &*input.buffer)?;
Ok(AssignedVector {
buffer: Box::new(buffer.try_into().unwrap()),
len: input.len().clone(),
})
}
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use midnight_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use super::{
super::{regex::Regex, AutomatonParser},
ScannerChip,
};
use crate::{
field::AssignedNative,
instructions::{AssertionInstructions, AssignmentInstructions},
testing_utils::FromScratch,
types::AssignedByte,
utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
CircuitField,
};
#[derive(Clone, Debug)]
struct RegexCircuit<F> {
input: Vec<Value<u8>>,
output: Vec<Value<F>>,
regex: Regex,
}
impl<F: CircuitField> RegexCircuit<F> {
fn new(s: &str, output: &[usize], regex: Regex) -> Self {
let input = s.bytes().map(Value::known).collect::<Vec<_>>();
let output =
output.iter().map(|&x| Value::known(F::from(x as u64))).collect::<Vec<_>>();
RegexCircuit {
input,
output,
regex,
}
}
}
impl<F> Circuit<F> for RegexCircuit<F>
where
F: CircuitField + Ord,
{
type Config = <ScannerChip<F> as FromScratch<F>>::Config;
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let committed_instance_column = meta.instance_column();
let instance_column = meta.instance_column();
ScannerChip::configure_from_scratch(
meta,
&mut vec![],
&mut vec![],
&[committed_instance_column, instance_column],
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let scanner_chip = ScannerChip::<F>::new_from_scratch(&config);
let input: Vec<AssignedByte<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.input.clone())?;
let output: Vec<AssignedNative<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.output)?;
cost_measure_start(&mut layouter);
let parsed_output = scanner_chip.parse(
&mut layouter,
AutomatonParser::Dynamic(self.regex.clone()),
&input,
)?;
cost_measure_end(&mut layouter);
assert!(
parsed_output.len() == output.len(),
"test failed: the lengths of the
parsed output (len = {}) and of the expected output (len = {}) are
different",
parsed_output.len(),
output.len()
);
parsed_output.iter().zip_eq(output.iter()).try_for_each(|(o1, o2)| {
scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
})?;
scanner_chip.load_from_scratch(&mut layouter)
}
}
fn parsing_one_test(
test_index: usize,
cost_model: bool,
input: &str,
output: &[usize],
circuit: &RegexCircuit<midnight_curves::Fq>,
must_pass: bool,
) {
assert!(
!cost_model || must_pass,
">> [test {test_index}] (bug) if cost_model is set to true, must_pass should be set to true"
);
let prover = MockProver::<midnight_curves::Fq>::run(circuit, vec![vec![], vec![]]);
if must_pass {
println!(
">> [test {test_index}] Parsing input {}, which should pass (output: {:?})",
input, output
);
prover.unwrap().assert_satisfied()
} else {
match prover {
Ok(prover) => {
if let Ok(()) = prover.verify() {
panic!(
">> [test {test_index}] (bug) input {} is incorrectly accepted (output {:?})",
input, output
)
} else {
println!(
">> [test {test_index}] The verifier failed on input {}, which is expected",
input
)
}
}
Err(_) => println!(
">> [test {test_index}] The prover failed on input {}, which is (supposedly) expected",
input
),
}
}
if cost_model {
circuit_to_json::<midnight_curves::Fq>(
"Scanner",
&format!(
"automaton parsing perf (input length = {})",
circuit.input.len()
),
circuit.clone(),
);
}
}
fn basic_test(test_index: usize, input: &str, output: &[usize], regex: Regex, must_pass: bool) {
parsing_one_test(
test_index,
false,
input,
output,
&RegexCircuit::new(input, output, regex),
must_pass,
)
}
fn basic_fail_test(test_index: usize, input: &str, regex: Regex) {
basic_test(test_index, input, &vec![0; input.len()], regex, false)
}
fn perf_test(test_index: usize, input: &str, regex: Regex) {
println!("\n>> Performance test, input size {}:", input.len());
let output = vec![0; input.len()];
parsing_one_test(
test_index,
true,
input,
&output,
&RegexCircuit::new(input, &output, regex),
true,
)
}
#[test]
fn parsing_test() {
let regex0 = Regex::hard_coded_example0();
let regex1 = Regex::hard_coded_example1();
basic_test(0, "hello (world)!!!!!", &[0; 18], regex0.clone(), true);
basic_test(0, "hello (world)!!!!!", &[1; 18], regex0.clone(), false); basic_test(
1,
"hello (world)!!!!!oipdsfihs32,;'p'';@",
&[0; 37],
regex0.clone(),
true,
);
basic_test(2, "hello (world) !!!!!", &[0; 20], regex0.clone(), true);
basic_test(2, "hello (world) !!!!!", &[1; 20], regex0.clone(), false); basic_test(3, "hello (world )!!!!!", &[0; 20], regex0.clone(), true);
basic_test(4, "hello ( world)!!!!!", &[0; 20], regex0.clone(), true);
basic_test(
5,
"hello hello hello (world , world ) !!!!!",
&[0; 42],
regex0.clone(),
true,
);
basic_test(
6,
"hello hello hello (world , world ) !!!!! ;'{][0(*&6235% /.,><",
&[0; 65],
regex0.clone(),
true,
);
basic_test(
7,
"hello hello hello ( world,world , world )!!!!!",
&[0; 50],
regex0.clone(),
true,
);
basic_fail_test(8, "hello (world)!!!!", regex0.clone());
basic_fail_test(9, "hello (world)!!!!!!", regex0.clone());
basic_fail_test(10, "hello world)!!!!!", regex0.clone());
basic_fail_test(11, "hello (warudo)!!!!!", regex0.clone());
basic_fail_test(12, "hello hello hello(world)!!!!!", regex0.clone());
basic_fail_test(
13,
"hello hello hello (world world ) !!!!!",
regex0.clone(),
);
basic_fail_test(14, "hello hellohello ( world,world )!!!!!", regex0.clone());
basic_fail_test(15, "hello hellohello ( world,world )!!! !!", regex0.clone());
basic_test(
16,
"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
regex1.clone(),
true,
);
basic_test(16, "holy hell !!!", &[0; 13], regex1.clone(), false); basic_test(
17,
"holy hell !!!!!!",
&[
0, 1, 2, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
],
regex1.clone(),
true,
);
basic_test(17, "holy hell !!!!!!", &[0; 21], regex1.clone(), false); basic_test(
18,
"holyyyy hell !!!",
&[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
regex1.clone(),
true,
);
basic_test(
19,
"holyyyy hell !!!!!!",
&[
0, 1, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
],
regex1.clone(),
true,
);
basic_fail_test(20, "holy hell!!!", regex1.clone());
basic_fail_test(21, "holyhell !!!", regex1.clone());
basic_fail_test(22, "holyhell!!!", regex1.clone());
basic_fail_test(23, "holyyyy hell!!!", regex1.clone());
basic_fail_test(24, "holyyyyhell !!!!!!", regex1.clone());
basic_fail_test(25, "holy hell ", regex1.clone());
basic_fail_test(26, "holyyyy hell ", regex1.clone());
basic_fail_test(27, "holy hellllll !!!", regex1.clone());
perf_test(
28,
"hello hello hello (world, world , world ) !!!!!",
regex0,
);
}
#[derive(Clone, Debug)]
struct DynamicRegexCircuit<F: CircuitField> {
regex1: Regex,
input1: Vec<Value<u8>>,
output1: Vec<Value<F>>,
regex2: Regex,
input2: Vec<Value<u8>>,
output2: Vec<Value<F>>,
must_cache: bool,
}
impl<F: CircuitField> DynamicRegexCircuit<F> {
fn new(
regex1: Regex,
input1: &str,
output1: &[usize],
regex2: Regex,
input2: &str,
output2: &[usize],
must_cache: bool,
) -> Self {
Self {
regex1,
input1: input1.bytes().map(Value::known).collect(),
output1: output1.iter().map(|&x| Value::known(F::from(x as u64))).collect(),
regex2,
input2: input2.bytes().map(Value::known).collect(),
output2: output2.iter().map(|&x| Value::known(F::from(x as u64))).collect(),
must_cache,
}
}
}
impl<F> Circuit<F> for DynamicRegexCircuit<F>
where
F: CircuitField + Ord,
{
type Config = <ScannerChip<F> as FromScratch<F>>::Config;
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let committed_instance_column = meta.instance_column();
let instance_column = meta.instance_column();
ScannerChip::configure_from_scratch(
meta,
&mut vec![],
&mut vec![],
&[committed_instance_column, instance_column],
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let scanner_chip = ScannerChip::<F>::new_from_scratch(&config);
let input1: Vec<AssignedByte<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.input1)?;
let output1: Vec<AssignedNative<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.output1)?;
let parsed1 = scanner_chip.parse(
&mut layouter,
AutomatonParser::Dynamic(self.regex1.clone()),
&input1,
)?;
assert_eq!(parsed1.len(), output1.len(), "first output length mismatch");
parsed1.iter().zip_eq(output1.iter()).try_for_each(|(o1, o2)| {
scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
})?;
let input2: Vec<AssignedByte<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.input2)?;
let output2: Vec<AssignedNative<F>> =
scanner_chip.native_gadget.assign_many(&mut layouter, &self.output2)?;
let parsed2 = scanner_chip.parse(
&mut layouter,
AutomatonParser::Dynamic(self.regex2.clone()),
&input2,
)?;
assert_eq!(
parsed2.len(),
output2.len(),
"second output length mismatch"
);
parsed2.iter().zip_eq(output2.iter()).try_for_each(|(o1, o2)| {
scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
})?;
let cache_size = scanner_chip.automaton_cache.borrow().len();
if self.must_cache {
assert_eq!(cache_size, 1, "expected 1 cached regex, got {cache_size}");
} else {
assert_eq!(cache_size, 2, "expected 2 cached regexes, got {cache_size}");
}
scanner_chip.load_from_scratch(&mut layouter)
}
}
fn dynamic_basic_test(
test_index: usize,
cost_model: bool,
entry1: (Regex, &str, &[usize]),
entry2: (Regex, &str, &[usize]),
must_pass: bool,
must_cache: bool,
) {
assert!(
!cost_model || must_pass,
">> [dynamic test {test_index}] (bug) if cost_model is set to true, must_pass should be set to true"
);
let circuit = DynamicRegexCircuit::<midnight_curves::Fq>::new(
entry1.0, entry1.1, entry1.2, entry2.0, entry2.1, entry2.2, must_cache,
);
let prover = MockProver::<midnight_curves::Fq>::run(&circuit, vec![vec![], vec![]]);
if must_pass {
println!(
">> [dynamic test {test_index}] Parsing inputs '{}' and '{}', which should pass (cache: {must_cache})",
entry1.1, entry2.1
);
prover.unwrap().assert_satisfied()
} else {
match prover {
Ok(prover) => {
if let Ok(()) = prover.verify() {
panic!(
">> [dynamic test {test_index}] inputs '{}' / '{}' incorrectly accepted",
entry1.1, entry2.1
)
} else {
println!(">> [dynamic test {test_index}] verifier failed (expected)",)
}
}
Err(_) => println!(">> [dynamic test {test_index}] prover failed (expected)",),
}
}
if cost_model {
circuit_to_json::<midnight_curves::Fq>(
"Scanner",
&format!(
"multi-regex parsing perf (input length = {})",
entry1.1.len()
),
circuit,
);
}
}
#[test]
fn dynamic_parsing_test() {
let regex1 = Regex::hard_coded_example1();
let regex2 = Regex::hard_coded_example0();
dynamic_basic_test(
0,
false,
(
regex1.clone(),
"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
),
(
regex1.clone(),
"holyyyy hell !!!",
&[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
),
true,
true,
);
dynamic_basic_test(
1,
false,
(
regex1.clone(),
"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
),
(regex1.clone(), "holy hell !!!", &[0; 13]),
false,
true,
);
dynamic_basic_test(
2,
false,
(
regex1.clone(),
"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
),
(regex1.clone(), "holy hell!!!", &[0; 12]),
false,
true,
);
dynamic_basic_test(
3,
false,
(
regex1.clone(),
"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
),
(regex2, "hello (world)!!!!!", &[0; 18]),
true,
false,
);
let perf_input = "holyyyyyyyyy hell !!!!!!!!!!!!!!!!!!!!!!!!!!!";
#[rustfmt::skip]
let perf_output: &[usize] = &[
0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
dynamic_basic_test(
4,
true,
(regex1.clone(), perf_input, perf_output),
(regex1, perf_input, perf_output),
true,
true,
);
}
#[derive(Clone, Debug)]
struct VarlenParseCircuit<F: CircuitField> {
input: Value<Vec<u8>>,
expected_buffer: [Value<F>; 32],
regex: Regex,
}
impl<F: CircuitField> VarlenParseCircuit<F> {
fn new(input: &[u8], payload_output: &[usize], regex: Regex) -> Self {
use crate::vec::get_lims;
let range = get_lims::<32, 1>(input.len());
assert_eq!(
payload_output.len(),
range.len(),
"payload_output length must match input length"
);
let mut buffer = [Value::known(F::ZERO); 32];
for (pos, &marker) in range.zip(payload_output.iter()) {
buffer[pos] = Value::known(F::from(marker as u64));
}
Self {
input: Value::known(input.to_vec()),
expected_buffer: buffer,
regex,
}
}
}
impl<F> Circuit<F> for VarlenParseCircuit<F>
where
F: CircuitField + Ord,
{
type Config = <ScannerChip<F> as FromScratch<F>>::Config;
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let instance_columns = [meta.instance_column(), meta.instance_column()];
ScannerChip::configure_from_scratch(meta, &mut vec![], &mut vec![], &instance_columns)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let scanner = ScannerChip::<F>::new_from_scratch(&config);
let ng = &scanner.native_gadget;
let input = scanner.assign_scanner_vec::<32, 1>(&mut layouter, self.input.clone())?;
let expected: Vec<AssignedNative<F>> =
ng.assign_many(&mut layouter, &self.expected_buffer)?;
let parsed = scanner.parse_varlen(
&mut layouter,
AutomatonParser::Dynamic(self.regex.clone()),
&input,
)?;
for (out_cell, exp_cell) in parsed.buffer.iter().zip(expected.iter()) {
ng.assert_equal(&mut layouter, out_cell, exp_cell)?;
}
scanner.load_from_scratch(&mut layouter)
}
}
fn varlen_parse_test(input: &[u8], output: &[usize], regex: Regex, must_pass: bool) {
type F = midnight_curves::Fq;
let circuit = VarlenParseCircuit::<F>::new(input, output, regex);
println!(
">> [varlen_parse] [must{} pass] input len={}",
if must_pass { "" } else { " not" },
input.len(),
);
let result = MockProver::run(&circuit, vec![vec![], vec![]]);
match result {
Ok(p) => {
let verified = p.verify();
if must_pass {
verified.expect("should have passed")
} else {
assert!(verified.is_err(), "should have failed");
}
}
Err(e) => assert!(!must_pass, "Prover failed unexpectedly: {:?}", e),
}
println!("... ok!");
}
#[test]
fn parse_varlen_test() {
let regex1 = Regex::hard_coded_example1();
varlen_parse_test(
b"holy hell !!!",
&[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
regex1.clone(),
true,
);
varlen_parse_test(b"holy hell !!!", &[0; 13], regex1.clone(), false);
varlen_parse_test(b"holy hell!!!", &[0; 12], regex1.clone(), false);
varlen_parse_test(
b"holyyyy hell !!!",
&[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
regex1,
true,
);
}
}