1use midnight_proofs::{
99 circuit::{Layouter, Region, Value},
100 plonk::Error,
101};
102
103use super::{
104 varlen::ScannerVec, NativeAutomaton, ScannerChip, ALPHABET_MAX_SIZE, AUTOMATON_PARALLELISM,
105 NB_AUTOMATON_COLS,
106};
107use crate::{
108 field::AssignedNative, instructions::AssignmentInstructions, parsing::scanner::AutomatonParser,
109 types::AssignedByte, vec::AssignedVector, CircuitField,
110};
111
112impl<F> NativeAutomaton<F>
113where
114 F: CircuitField + Ord,
115{
116 fn next_transition(
119 &self,
120 state: &AssignedNative<F>,
121 letter: &AssignedNative<F>,
122 ) -> Result<(Value<F>, Value<F>), Error> {
123 let target_opt = state.value().zip(letter.value()).map(|(s, l)| self.get_transition(s, l));
124 target_opt.error_if_known_and(|o| o.is_none())?;
125 let target = target_opt.map(|o| o.unwrap());
126 Ok((target.map(|t| t.0), target.map(|t| t.1)))
127 }
128}
129
130impl<F> ScannerChip<F>
131where
132 F: CircuitField + Ord,
133{
134 pub(super) fn parse_automaton(
147 &self,
148 layouter: &mut impl Layouter<F>,
149 automaton: &NativeAutomaton<F>,
150 input: &[AssignedNative<F>],
151 ) -> Result<Vec<AssignedNative<F>>, Error> {
152 let init_state: AssignedNative<F> =
153 self.native_gadget.assign_fixed(layouter, automaton.initial_state)?;
154 let invalid_letter: AssignedNative<F> =
155 self.native_gadget.assign_fixed(layouter, F::from(ALPHABET_MAX_SIZE as u64))?;
156 let zero: AssignedNative<F> = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
157
158 layouter.assign_region(
159 || "parsing layout",
160 |mut region| {
161 let mut offset = 0;
162 let mut outputs = Vec::with_capacity(input.len());
163
164 let mut state = init_state.copy_advice(
166 || "initial state",
167 &mut region,
168 self.config.advice_cols[0],
169 offset,
170 )?;
171
172 for chunk in input.chunks(AUTOMATON_PARALLELISM) {
174 for (batch, letter) in chunk.iter().enumerate() {
175 self.apply_one_transition(
176 &mut region,
177 automaton,
178 &mut state,
179 letter,
180 batch,
181 &mut outputs,
182 &mut offset,
183 )?;
184 }
185 }
186
187 #[allow(clippy::modulo_one)]
189 self.assert_final_state(
190 &mut region,
191 &invalid_letter,
192 &zero,
193 input.len() % AUTOMATON_PARALLELISM,
194 &mut offset,
195 )?;
196
197 Ok(outputs)
198 },
199 )
200 }
201
202 #[allow(clippy::too_many_arguments)]
203 fn apply_one_transition(
212 &self,
213 region: &mut Region<'_, F>,
214 automaton: &NativeAutomaton<F>,
215 state: &mut AssignedNative<F>,
216 letter: &AssignedNative<F>,
217 batch: usize,
218 outputs: &mut Vec<AssignedNative<F>>,
219 offset: &mut usize,
220 ) -> Result<(), Error> {
221 self.config.q_automaton.enable(region, *offset)?;
222
223 let base = NB_AUTOMATON_COLS * batch;
224 letter.copy_advice(
225 || format!("letter batch {batch}"),
226 region,
227 self.config.advice_cols[base + 1],
228 *offset,
229 )?;
230
231 let (next_state_val, output_val) = automaton.next_transition(state, letter)?;
232
233 let output = region.assign_advice(
234 || format!("output batch {batch}"),
235 self.config.advice_cols[base + 2],
236 *offset,
237 || output_val,
238 )?;
239 outputs.push(output);
240
241 let target_col = if batch == AUTOMATON_PARALLELISM - 1 {
242 *offset += 1;
243 0
244 } else {
245 base + NB_AUTOMATON_COLS
246 };
247 *state = region.assign_advice(
248 || format!("next state batch {batch}"),
249 self.config.advice_cols[target_col],
250 *offset,
251 || next_state_val,
252 )?;
253
254 Ok(())
255 }
256
257 fn assert_final_state(
262 &self,
263 region: &mut Region<'_, F>,
264 invalid_letter: &AssignedNative<F>,
265 zero: &AssignedNative<F>,
266 batch: usize,
267 offset: &mut usize,
268 ) -> Result<(), Error> {
269 self.config.q_automaton.enable(region, *offset)?;
270
271 let base = NB_AUTOMATON_COLS * batch;
274 invalid_letter.copy_advice(
275 || format!("final check letter ({ALPHABET_MAX_SIZE})"),
276 region,
277 self.config.advice_cols[base + 1],
278 *offset,
279 )?;
280
281 for col in (base + 2)..(NB_AUTOMATON_COLS * AUTOMATON_PARALLELISM) {
283 zero.copy_advice(
284 || "parsing trailing 0",
285 region,
286 self.config.advice_cols[col],
287 *offset,
288 )?;
289 }
290 *offset += 1;
291 zero.copy_advice(
292 || "parsing trailing 0",
293 region,
294 self.config.advice_cols[0],
295 *offset,
296 )?;
297
298 Ok(())
299 }
300}
301
302impl<F> ScannerChip<F>
303where
304 F: CircuitField + Ord,
305{
306 pub(crate) fn load_automata_table(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
319 let cache = self.automaton_cache.borrow();
320 layouter.assign_table(
321 || "automaton table",
322 |mut table| {
323 let mut offset = 0;
324 let mut add_entry =
325 |source: F, letter: F, target: F, output: F| -> Result<(), Error> {
326 table.assign_cell(
327 || "t_source",
328 self.config.t_source,
329 offset,
330 || Value::known(source),
331 )?;
332 table.assign_cell(
333 || "t_letter",
334 self.config.t_letter,
335 offset,
336 || Value::known(letter),
337 )?;
338 table.assign_cell(
339 || "t_target",
340 self.config.t_target,
341 offset,
342 || Value::known(target),
343 )?;
344 table.assign_cell(
345 || "t_output",
346 self.config.t_output,
347 offset,
348 || Value::known(output),
349 )?;
350 offset += 1;
351 Ok(())
352 };
353
354 add_entry(F::ZERO, F::ZERO, F::ZERO, F::ZERO)?;
356
357 let filler = F::from(ALPHABET_MAX_SIZE as u64);
358 for automaton in cache.values() {
363 for (source, inner) in automaton.transitions.iter() {
364 for (letter, (target, output_extr)) in inner.iter() {
365 assert!(
366 *source != F::ZERO && *target != F::ZERO,
367 "sanity check failed: the circuit requires that state 0 \
368 is not used, but the automaton generation failed to \
369 ensure it."
370 );
371 add_entry(*source, *letter, *target, *output_extr)?
372 }
373 }
374 for state in automaton.final_states.iter() {
375 add_entry(*state, filler, F::ZERO, F::ZERO)?
378 }
379 }
380 Ok(())
381 },
382 )
383 }
384}
385
386impl<F> ScannerChip<F>
387where
388 F: CircuitField + Ord,
389{
390 fn resolve_automaton(&self, parser: &AutomatonParser) -> NativeAutomaton<F> {
395 if let Some(aut) = self.automaton_cache.borrow().get(parser) {
396 return aut.clone();
397 }
398
399 let raw_automaton = match parser {
400 AutomatonParser::Static(spec) => self.config.static_library[spec].clone().1,
401 AutomatonParser::Dynamic(regex) => regex.to_automaton(),
402 };
403
404 let offset = {
405 let mut ms = self.max_state.borrow_mut();
406 let o = *ms;
407 *ms += raw_automaton.nb_states;
408 o
409 };
410 let native: NativeAutomaton<F> = raw_automaton.offset_states(offset).into();
411 self.automaton_cache.borrow_mut().insert(parser.clone(), native.clone());
412 native
413 }
414
415 pub fn parse(
421 &self,
422 layouter: &mut impl Layouter<F>,
423 parser: AutomatonParser,
424 input: &[AssignedByte<F>],
425 ) -> Result<Vec<AssignedNative<F>>, Error> {
426 let automaton = self.resolve_automaton(&parser);
427 let native_input: Vec<AssignedNative<F>> = input.iter().map(AssignedNative::from).collect();
428 self.parse_automaton(layouter, &automaton, &native_input)
429 }
430
431 pub fn parse_varlen<const M: usize, const A: usize>(
441 &self,
442 layouter: &mut impl Layouter<F>,
443 parser: AutomatonParser,
444 input: &ScannerVec<F, M, A>,
445 ) -> Result<AssignedVector<F, AssignedNative<F>, M, A>, Error> {
446 let automaton = self.resolve_automaton(&parser);
447
448 let buffer = self.parse_automaton(layouter, &automaton, &*input.buffer)?;
452 Ok(AssignedVector {
453 buffer: Box::new(buffer.try_into().unwrap()),
454 len: input.len().clone(),
455 })
456 }
457}
458
459#[cfg(test)]
460mod test {
461 use itertools::Itertools;
462 use midnight_proofs::{
463 circuit::{Layouter, SimpleFloorPlanner, Value},
464 dev::MockProver,
465 plonk::{Circuit, ConstraintSystem, Error},
466 };
467
468 use super::{
469 super::{regex::Regex, AutomatonParser},
470 ScannerChip,
471 };
472 use crate::{
473 field::AssignedNative,
474 instructions::{AssertionInstructions, AssignmentInstructions},
475 testing_utils::FromScratch,
476 types::AssignedByte,
477 utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
478 CircuitField,
479 };
480
481 #[derive(Clone, Debug)]
482 struct RegexCircuit<F> {
483 input: Vec<Value<u8>>,
484 output: Vec<Value<F>>,
485 regex: Regex,
486 }
487
488 impl<F: CircuitField> RegexCircuit<F> {
489 fn new(s: &str, output: &[usize], regex: Regex) -> Self {
490 let input = s.bytes().map(Value::known).collect::<Vec<_>>();
491 let output =
492 output.iter().map(|&x| Value::known(F::from(x as u64))).collect::<Vec<_>>();
493 RegexCircuit {
494 input,
495 output,
496 regex,
497 }
498 }
499 }
500
501 impl<F> Circuit<F> for RegexCircuit<F>
502 where
503 F: CircuitField + Ord,
504 {
505 type Config = <ScannerChip<F> as FromScratch<F>>::Config;
506
507 type FloorPlanner = SimpleFloorPlanner;
508
509 type Params = ();
510
511 fn without_witnesses(&self) -> Self {
512 unreachable!()
513 }
514
515 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
516 let committed_instance_column = meta.instance_column();
517 let instance_column = meta.instance_column();
518 ScannerChip::configure_from_scratch(
519 meta,
520 &mut vec![],
521 &mut vec![],
522 &[committed_instance_column, instance_column],
523 )
524 }
525
526 fn synthesize(
527 &self,
528 config: Self::Config,
529 mut layouter: impl Layouter<F>,
530 ) -> Result<(), Error> {
531 let scanner_chip = ScannerChip::<F>::new_from_scratch(&config);
532
533 let input: Vec<AssignedByte<F>> =
534 scanner_chip.native_gadget.assign_many(&mut layouter, &self.input.clone())?;
535 let output: Vec<AssignedNative<F>> =
536 scanner_chip.native_gadget.assign_many(&mut layouter, &self.output)?;
537
538 cost_measure_start(&mut layouter);
539 let parsed_output = scanner_chip.parse(
540 &mut layouter,
541 AutomatonParser::Dynamic(self.regex.clone()),
542 &input,
543 )?;
544 cost_measure_end(&mut layouter);
545 assert!(
546 parsed_output.len() == output.len(),
547 "test failed: the lengths of the
548 parsed output (len = {}) and of the expected output (len = {}) are
549 different",
550 parsed_output.len(),
551 output.len()
552 );
553 parsed_output.iter().zip_eq(output.iter()).try_for_each(|(o1, o2)| {
554 scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
555 })?;
556
557 scanner_chip.load_from_scratch(&mut layouter)
558 }
559 }
560
561 fn parsing_one_test(
562 test_index: usize,
563 cost_model: bool,
564 input: &str,
565 output: &[usize],
566 circuit: &RegexCircuit<midnight_curves::Fq>,
567 must_pass: bool,
568 ) {
569 assert!(
570 !cost_model || must_pass,
571 ">> [test {test_index}] (bug) if cost_model is set to true, must_pass should be set to true"
572 );
573 let prover = MockProver::<midnight_curves::Fq>::run(circuit, vec![vec![], vec![]]);
574 if must_pass {
575 println!(
576 ">> [test {test_index}] Parsing input {}, which should pass (output: {:?})",
577 input, output
578 );
579 prover.unwrap().assert_satisfied()
580 } else {
581 match prover {
582 Ok(prover) => {
583 if let Ok(()) = prover.verify() {
584 panic!(
585 ">> [test {test_index}] (bug) input {} is incorrectly accepted (output {:?})",
586 input, output
587 )
588 } else {
589 println!(
590 ">> [test {test_index}] The verifier failed on input {}, which is expected",
591 input
592 )
593 }
594 }
595 Err(_) => println!(
596 ">> [test {test_index}] The prover failed on input {}, which is (supposedly) expected",
597 input
598 ),
599 }
600 }
601
602 if cost_model {
603 circuit_to_json::<midnight_curves::Fq>(
604 "Scanner",
605 &format!(
606 "automaton parsing perf (input length = {})",
607 circuit.input.len()
608 ),
609 circuit.clone(),
610 );
611 }
612 }
613
614 fn basic_test(test_index: usize, input: &str, output: &[usize], regex: Regex, must_pass: bool) {
616 parsing_one_test(
617 test_index,
618 false,
619 input,
620 output,
621 &RegexCircuit::new(input, output, regex),
622 must_pass,
623 )
624 }
625
626 fn basic_fail_test(test_index: usize, input: &str, regex: Regex) {
628 basic_test(test_index, input, &vec![0; input.len()], regex, false)
629 }
630
631 fn perf_test(test_index: usize, input: &str, regex: Regex) {
633 println!("\n>> Performance test, input size {}:", input.len());
634 let output = vec![0; input.len()];
635 parsing_one_test(
636 test_index,
637 true,
638 input,
639 &output,
640 &RegexCircuit::new(input, &output, regex),
641 true,
642 )
643 }
644
645 #[test]
646 fn parsing_test() {
648 let regex0 = Regex::hard_coded_example0();
649 let regex1 = Regex::hard_coded_example1();
650
651 basic_test(0, "hello (world)!!!!!", &[0; 18], regex0.clone(), true);
653 basic_test(0, "hello (world)!!!!!", &[1; 18], regex0.clone(), false); basic_test(
655 1,
656 "hello (world)!!!!!oipdsfihs32,;'p'';@",
657 &[0; 37],
658 regex0.clone(),
659 true,
660 );
661 basic_test(2, "hello (world) !!!!!", &[0; 20], regex0.clone(), true);
662 basic_test(2, "hello (world) !!!!!", &[1; 20], regex0.clone(), false); basic_test(3, "hello (world )!!!!!", &[0; 20], regex0.clone(), true);
664 basic_test(4, "hello ( world)!!!!!", &[0; 20], regex0.clone(), true);
665 basic_test(
666 5,
667 "hello hello hello (world , world ) !!!!!",
668 &[0; 42],
669 regex0.clone(),
670 true,
671 );
672 basic_test(
673 6,
674 "hello hello hello (world , world ) !!!!! ;'{][0(*&6235% /.,><",
675 &[0; 65],
676 regex0.clone(),
677 true,
678 );
679 basic_test(
680 7,
681 "hello hello hello ( world,world , world )!!!!!",
682 &[0; 50],
683 regex0.clone(),
684 true,
685 );
686
687 basic_fail_test(8, "hello (world)!!!!", regex0.clone());
690 basic_fail_test(9, "hello (world)!!!!!!", regex0.clone());
692 basic_fail_test(10, "hello world)!!!!!", regex0.clone());
694 basic_fail_test(11, "hello (warudo)!!!!!", regex0.clone());
696 basic_fail_test(12, "hello hello hello(world)!!!!!", regex0.clone());
698 basic_fail_test(
700 13,
701 "hello hello hello (world world ) !!!!!",
702 regex0.clone(),
703 );
704 basic_fail_test(14, "hello hellohello ( world,world )!!!!!", regex0.clone());
706 basic_fail_test(15, "hello hellohello ( world,world )!!! !!", regex0.clone());
708
709 basic_test(
711 16,
712 "holy hell !!!",
713 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
714 regex1.clone(),
715 true,
716 );
717 basic_test(16, "holy hell !!!", &[0; 13], regex1.clone(), false); basic_test(
719 17,
720 "holy hell !!!!!!",
721 &[
722 0, 1, 2, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
723 ],
724 regex1.clone(),
725 true,
726 );
727 basic_test(17, "holy hell !!!!!!", &[0; 21], regex1.clone(), false); basic_test(
729 18,
730 "holyyyy hell !!!",
731 &[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
732 regex1.clone(),
733 true,
734 );
735 basic_test(
736 19,
737 "holyyyy hell !!!!!!",
738 &[
739 0, 1, 2, 1, 1, 1, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
740 ],
741 regex1.clone(),
742 true,
743 );
744
745 basic_fail_test(20, "holy hell!!!", regex1.clone());
748 basic_fail_test(21, "holyhell !!!", regex1.clone());
749 basic_fail_test(22, "holyhell!!!", regex1.clone());
750 basic_fail_test(23, "holyyyy hell!!!", regex1.clone());
751 basic_fail_test(24, "holyyyyhell !!!!!!", regex1.clone());
752 basic_fail_test(25, "holy hell ", regex1.clone());
754 basic_fail_test(26, "holyyyy hell ", regex1.clone());
755 basic_fail_test(27, "holy hellllll !!!", regex1.clone());
757
758 perf_test(
761 28,
762 "hello hello hello (world, world , world ) !!!!!",
763 regex0,
764 );
765 }
766
767 #[derive(Clone, Debug)]
773 struct DynamicRegexCircuit<F: CircuitField> {
774 regex1: Regex,
775 input1: Vec<Value<u8>>,
776 output1: Vec<Value<F>>,
777 regex2: Regex,
778 input2: Vec<Value<u8>>,
779 output2: Vec<Value<F>>,
780 must_cache: bool,
781 }
782
783 impl<F: CircuitField> DynamicRegexCircuit<F> {
784 fn new(
785 regex1: Regex,
786 input1: &str,
787 output1: &[usize],
788 regex2: Regex,
789 input2: &str,
790 output2: &[usize],
791 must_cache: bool,
792 ) -> Self {
793 Self {
794 regex1,
795 input1: input1.bytes().map(Value::known).collect(),
796 output1: output1.iter().map(|&x| Value::known(F::from(x as u64))).collect(),
797 regex2,
798 input2: input2.bytes().map(Value::known).collect(),
799 output2: output2.iter().map(|&x| Value::known(F::from(x as u64))).collect(),
800 must_cache,
801 }
802 }
803 }
804
805 impl<F> Circuit<F> for DynamicRegexCircuit<F>
806 where
807 F: CircuitField + Ord,
808 {
809 type Config = <ScannerChip<F> as FromScratch<F>>::Config;
810 type FloorPlanner = SimpleFloorPlanner;
811 type Params = ();
812
813 fn without_witnesses(&self) -> Self {
814 unreachable!()
815 }
816
817 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
818 let committed_instance_column = meta.instance_column();
819 let instance_column = meta.instance_column();
820 ScannerChip::configure_from_scratch(
821 meta,
822 &mut vec![],
823 &mut vec![],
824 &[committed_instance_column, instance_column],
825 )
826 }
827
828 fn synthesize(
829 &self,
830 config: Self::Config,
831 mut layouter: impl Layouter<F>,
832 ) -> Result<(), Error> {
833 let scanner_chip = ScannerChip::<F>::new_from_scratch(&config);
834
835 let input1: Vec<AssignedByte<F>> =
837 scanner_chip.native_gadget.assign_many(&mut layouter, &self.input1)?;
838 let output1: Vec<AssignedNative<F>> =
839 scanner_chip.native_gadget.assign_many(&mut layouter, &self.output1)?;
840 let parsed1 = scanner_chip.parse(
841 &mut layouter,
842 AutomatonParser::Dynamic(self.regex1.clone()),
843 &input1,
844 )?;
845 assert_eq!(parsed1.len(), output1.len(), "first output length mismatch");
846 parsed1.iter().zip_eq(output1.iter()).try_for_each(|(o1, o2)| {
847 scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
848 })?;
849
850 let input2: Vec<AssignedByte<F>> =
852 scanner_chip.native_gadget.assign_many(&mut layouter, &self.input2)?;
853 let output2: Vec<AssignedNative<F>> =
854 scanner_chip.native_gadget.assign_many(&mut layouter, &self.output2)?;
855 let parsed2 = scanner_chip.parse(
856 &mut layouter,
857 AutomatonParser::Dynamic(self.regex2.clone()),
858 &input2,
859 )?;
860 assert_eq!(
861 parsed2.len(),
862 output2.len(),
863 "second output length mismatch"
864 );
865 parsed2.iter().zip_eq(output2.iter()).try_for_each(|(o1, o2)| {
866 scanner_chip.native_gadget.assert_equal(&mut layouter, o1, o2)
867 })?;
868
869 let cache_size = scanner_chip.automaton_cache.borrow().len();
872 if self.must_cache {
873 assert_eq!(cache_size, 1, "expected 1 cached regex, got {cache_size}");
874 } else {
875 assert_eq!(cache_size, 2, "expected 2 cached regexes, got {cache_size}");
876 }
877
878 scanner_chip.load_from_scratch(&mut layouter)
879 }
880 }
881
882 fn dynamic_basic_test(
883 test_index: usize,
884 cost_model: bool,
885 entry1: (Regex, &str, &[usize]),
886 entry2: (Regex, &str, &[usize]),
887 must_pass: bool,
888 must_cache: bool,
889 ) {
890 assert!(
891 !cost_model || must_pass,
892 ">> [dynamic test {test_index}] (bug) if cost_model is set to true, must_pass should be set to true"
893 );
894 let circuit = DynamicRegexCircuit::<midnight_curves::Fq>::new(
895 entry1.0, entry1.1, entry1.2, entry2.0, entry2.1, entry2.2, must_cache,
896 );
897 let prover = MockProver::<midnight_curves::Fq>::run(&circuit, vec![vec![], vec![]]);
898 if must_pass {
899 println!(
900 ">> [dynamic test {test_index}] Parsing inputs '{}' and '{}', which should pass (cache: {must_cache})",
901 entry1.1, entry2.1
902 );
903 prover.unwrap().assert_satisfied()
904 } else {
905 match prover {
906 Ok(prover) => {
907 if let Ok(()) = prover.verify() {
908 panic!(
909 ">> [dynamic test {test_index}] inputs '{}' / '{}' incorrectly accepted",
910 entry1.1, entry2.1
911 )
912 } else {
913 println!(">> [dynamic test {test_index}] verifier failed (expected)",)
914 }
915 }
916 Err(_) => println!(">> [dynamic test {test_index}] prover failed (expected)",),
917 }
918 }
919
920 if cost_model {
921 circuit_to_json::<midnight_curves::Fq>(
922 "Scanner",
923 &format!(
924 "multi-regex parsing perf (input length = {})",
925 entry1.1.len()
926 ),
927 circuit,
928 );
929 }
930 }
931
932 #[test]
933 fn dynamic_parsing_test() {
934 let regex1 = Regex::hard_coded_example1();
935 let regex2 = Regex::hard_coded_example0();
936
937 dynamic_basic_test(
939 0,
940 false,
941 (
942 regex1.clone(),
943 "holy hell !!!",
944 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
945 ),
946 (
947 regex1.clone(),
948 "holyyyy hell !!!",
949 &[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
950 ),
951 true,
952 true,
953 );
954
955 dynamic_basic_test(
957 1,
958 false,
959 (
960 regex1.clone(),
961 "holy hell !!!",
962 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
963 ),
964 (regex1.clone(), "holy hell !!!", &[0; 13]),
965 false,
966 true,
967 );
968
969 dynamic_basic_test(
971 2,
972 false,
973 (
974 regex1.clone(),
975 "holy hell !!!",
976 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
977 ),
978 (regex1.clone(), "holy hell!!!", &[0; 12]),
979 false,
980 true,
981 );
982
983 dynamic_basic_test(
985 3,
986 false,
987 (
988 regex1.clone(),
989 "holy hell !!!",
990 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
991 ),
992 (regex2, "hello (world)!!!!!", &[0; 18]),
993 true,
994 false,
995 );
996
997 let perf_input = "holyyyyyyyyy hell !!!!!!!!!!!!!!!!!!!!!!!!!!!";
999 #[rustfmt::skip]
1000 let perf_output: &[usize] = &[
1001 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0,
1002 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,
1003 ];
1004 dynamic_basic_test(
1005 4,
1006 true,
1007 (regex1.clone(), perf_input, perf_output),
1008 (regex1, perf_input, perf_output),
1009 true,
1010 true,
1011 );
1012 }
1013
1014 #[derive(Clone, Debug)]
1017 struct VarlenParseCircuit<F: CircuitField> {
1018 input: Value<Vec<u8>>,
1019 expected_buffer: [Value<F>; 32],
1022 regex: Regex,
1023 }
1024
1025 impl<F: CircuitField> VarlenParseCircuit<F> {
1026 fn new(input: &[u8], payload_output: &[usize], regex: Regex) -> Self {
1027 use crate::vec::get_lims;
1028
1029 let range = get_lims::<32, 1>(input.len());
1031 assert_eq!(
1032 payload_output.len(),
1033 range.len(),
1034 "payload_output length must match input length"
1035 );
1036 let mut buffer = [Value::known(F::ZERO); 32];
1037 for (pos, &marker) in range.zip(payload_output.iter()) {
1038 buffer[pos] = Value::known(F::from(marker as u64));
1039 }
1040
1041 Self {
1042 input: Value::known(input.to_vec()),
1043 expected_buffer: buffer,
1044 regex,
1045 }
1046 }
1047 }
1048
1049 impl<F> Circuit<F> for VarlenParseCircuit<F>
1050 where
1051 F: CircuitField + Ord,
1052 {
1053 type Config = <ScannerChip<F> as FromScratch<F>>::Config;
1054 type FloorPlanner = SimpleFloorPlanner;
1055 type Params = ();
1056
1057 fn without_witnesses(&self) -> Self {
1058 unreachable!()
1059 }
1060
1061 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
1062 let instance_columns = [meta.instance_column(), meta.instance_column()];
1063 ScannerChip::configure_from_scratch(meta, &mut vec![], &mut vec![], &instance_columns)
1064 }
1065
1066 fn synthesize(
1067 &self,
1068 config: Self::Config,
1069 mut layouter: impl Layouter<F>,
1070 ) -> Result<(), Error> {
1071 let scanner = ScannerChip::<F>::new_from_scratch(&config);
1072 let ng = &scanner.native_gadget;
1073
1074 let input = scanner.assign_scanner_vec::<32, 1>(&mut layouter, self.input.clone())?;
1075 let expected: Vec<AssignedNative<F>> =
1076 ng.assign_many(&mut layouter, &self.expected_buffer)?;
1077
1078 let parsed = scanner.parse_varlen(
1079 &mut layouter,
1080 AutomatonParser::Dynamic(self.regex.clone()),
1081 &input,
1082 )?;
1083
1084 for (out_cell, exp_cell) in parsed.buffer.iter().zip(expected.iter()) {
1086 ng.assert_equal(&mut layouter, out_cell, exp_cell)?;
1087 }
1088
1089 scanner.load_from_scratch(&mut layouter)
1090 }
1091 }
1092
1093 fn varlen_parse_test(input: &[u8], output: &[usize], regex: Regex, must_pass: bool) {
1094 type F = midnight_curves::Fq;
1095 let circuit = VarlenParseCircuit::<F>::new(input, output, regex);
1096 println!(
1097 ">> [varlen_parse] [must{} pass] input len={}",
1098 if must_pass { "" } else { " not" },
1099 input.len(),
1100 );
1101 let result = MockProver::run(&circuit, vec![vec![], vec![]]);
1102 match result {
1103 Ok(p) => {
1104 let verified = p.verify();
1105 if must_pass {
1106 verified.expect("should have passed")
1107 } else {
1108 assert!(verified.is_err(), "should have failed");
1109 }
1110 }
1111 Err(e) => assert!(!must_pass, "Prover failed unexpectedly: {:?}", e),
1112 }
1113 println!("... ok!");
1114 }
1115
1116 #[test]
1117 fn parse_varlen_test() {
1118 let regex1 = Regex::hard_coded_example1();
1119
1120 varlen_parse_test(
1122 b"holy hell !!!",
1123 &[0, 1, 2, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
1124 regex1.clone(),
1125 true,
1126 );
1127
1128 varlen_parse_test(b"holy hell !!!", &[0; 13], regex1.clone(), false);
1130
1131 varlen_parse_test(b"holy hell!!!", &[0; 12], regex1.clone(), false);
1133
1134 varlen_parse_test(
1136 b"holyyyy hell !!!",
1137 &[0, 1, 2, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 1, 1, 1],
1138 regex1,
1139 true,
1140 );
1141 }
1142}