1use std::cell::Cell;
10use std::collections::BTreeMap;
11use std::fmt;
12use std::str::FromStr;
13
14use bloq_circuit::{
15 BodyId, CircuitBody, ConditionalCorrection, CoordCircuit, DetectorParity, DetectorTerm, Flow,
16 FlowMarker, GateType, LoopStateId, MeasRecord, Op, Pauli, PauliBasis, PauliMap,
17 checked_translate_coordinate,
18};
19use glam::{IVec2, IVec3};
20use rustc_hash::FxHashMap;
21
22use crate::{
23 Basis, Bloq, BloqEdge, BloqNode, BloqNodeId, BloqNodeKind, BloqTemplate, BloqTemplatePool,
24 BoundaryFace, BundleDetector, BundleMeasurement, ClassicalExpr, ClassicalNode, DetectorBundle,
25 DetectorBundleId, DetectorBundlePool, DetectorBundleUse, InstanceBoundaryOperator,
26 InstanceMeasurement, MetadataValue, NodeDetector, NodeProvenance, NodeRestart,
27 ObservableOutput, PipePadding, PipeSeam, QuantumEdge, QuantumNode, QuantumTimeline, RegionNode,
28 SourceBlockRef, SubGraph, TemplateDetector, TemplateDetectorScope, TemplateId,
29 TemplateInstance, TemplateInstanceId, TemplateRepeatState, TemplateRestart, TemporalPipeRef,
30 ValueRef, ValueRole,
31};
32
33use super::{BLOQ_TEXT_VERSION, decode_metadata_token};
34
35#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
37#[error("bloq text line {line}: {message}")]
38pub struct TextParseError {
39 pub line: usize,
41 message: String,
42}
43
44impl TextParseError {
45 fn new(line: usize, message: impl fmt::Display) -> Self {
46 Self {
47 line,
48 message: message.to_string(),
49 }
50 }
51}
52
53type Result<T> = std::result::Result<T, TextParseError>;
54
55pub(super) fn parse_bloq_text(text: &str) -> Result<Bloq> {
56 Parser::new(text).parse_program()
57}
58
59pub(super) fn implied_measurement_qubits<'a>(
64 ops: impl Iterator<Item = &'a Op>,
65) -> FxHashMap<u32, IVec2> {
66 let mut implied = FxHashMap::default();
67 for op in ops {
68 for_each_implied_measurement(op, |measurement, qubit| {
69 implied.insert(measurement, qubit);
70 });
71 }
72 implied
73}
74
75fn for_each_implied_measurement(op: &Op, mut f: impl FnMut(u32, IVec2)) {
76 match op {
77 Op::Measure {
78 qubits,
79 measurements,
80 ..
81 } => {
82 for (&qubit, &measurement) in qubits.iter().zip(measurements) {
83 f(measurement, qubit);
84 }
85 }
86 Op::MPP {
87 products,
88 measurements,
89 } => {
90 for (product, &measurement) in products.iter().zip(measurements) {
91 if let Some(qubit) = product.representative_coord() {
92 f(measurement, qubit);
93 }
94 }
95 }
96 Op::Gate { .. }
97 | Op::Tick
98 | Op::Repeat { .. }
99 | Op::ConditionalPauli(_)
100 | Op::Depolarize1 { .. }
101 | Op::Depolarize2 { .. }
102 | Op::PauliError { .. } => {}
103 }
104}
105
106fn register_implied_measurements(
107 op: &Op,
108 line: usize,
109 implied: &mut FxHashMap<u32, IVec2>,
110) -> Result<()> {
111 let mut conflict = None;
112 for_each_implied_measurement(op, |measurement, qubit| match implied.get(&measurement) {
113 Some(&previous) if previous != qubit => {
114 conflict.get_or_insert((measurement, previous, qubit));
115 }
116 Some(_) => {}
117 None => {
118 implied.insert(measurement, qubit);
119 }
120 });
121 let Some((measurement, first, second)) = conflict else {
122 return Ok(());
123 };
124 Err(TextParseError::new(
125 line,
126 format!(
127 "measurement m{measurement} is bound to two different qubits: \
128 ({},{}) and ({},{})",
129 first.x, first.y, second.x, second.y
130 ),
131 ))
132}
133
134fn validate_text_coordinate_translations(bloq: &Bloq, line: usize) -> Result<()> {
138 for (_, level) in bloq.levels() {
139 for (_, quantum) in level.quantum_nodes() {
140 for instance in &quantum.instances {
141 validate_template_translation(bloq, instance.template_id, instance.offset, line)?;
142 }
143 }
144 }
145 for padding in bloq.pipe_padding() {
146 for template in [padding.one_round, padding.looped] {
147 validate_template_translation(bloq, template, padding.offset, line)?;
148 }
149 }
150 Ok(())
151}
152
153fn validate_template_translation(
154 bloq: &Bloq,
155 template_id: TemplateId,
156 offset: IVec2,
157 line: usize,
158) -> Result<()> {
159 let Some(template) = bloq.templates().get(template_id) else {
160 return Ok(());
162 };
163 for &coord in template.qubits() {
164 validate_translated_coord(coord, offset, template_id, line)?;
165 }
166 for flow in &template.boundary_flows {
167 for (coord, _) in flow.start.iter().chain(flow.end.iter()) {
168 validate_translated_coord(*coord, offset, template_id, line)?;
169 }
170 if let Some(center) = flow.center {
171 validate_translated_coord(center, offset, template_id, line)?;
172 }
173 }
174 Ok(())
175}
176
177fn validate_translated_coord(
178 coord: IVec2,
179 offset: IVec2,
180 template: TemplateId,
181 line: usize,
182) -> Result<()> {
183 if checked_translate_coordinate(coord, offset).is_err() {
184 return Err(TextParseError::new(
185 line,
186 format!(
187 "template t{} coordinate ({},{}) overflows after offset ({},{})",
188 template.0, coord.x, coord.y, offset.x, offset.y
189 ),
190 ));
191 }
192 Ok(())
193}
194
195struct Parser<'a> {
200 lines: Vec<&'a str>,
202 pos: usize,
204}
205
206struct Stmt<'a> {
210 line: usize,
211 tokens: Vec<&'a str>,
212 next: Cell<usize>,
213}
214
215impl<'a> Parser<'a> {
216 fn new(text: &'a str) -> Self {
217 Self {
218 lines: text.split('\n').collect(),
219 pos: 0,
220 }
221 }
222
223 fn next_stmt(&mut self) -> Option<Stmt<'a>> {
226 while self.pos < self.lines.len() {
227 let line_number = self.pos + 1;
228 let line = self.lines[self.pos];
229 self.pos += 1;
230 let code = line.split('#').next().unwrap_or("");
231 let tokens: Vec<&str> = code.split_whitespace().collect();
232 if !tokens.is_empty() {
233 return Some(Stmt {
234 line: line_number,
235 tokens,
236 next: Cell::new(0),
237 });
238 }
239 }
240 None
241 }
242
243 fn expect_stmt(&mut self, expected: &str) -> Result<Stmt<'a>> {
244 let end = self.lines.len();
245 self.next_stmt().ok_or_else(|| {
246 TextParseError::new(end, format!("expected {expected}, found end of input"))
247 })
248 }
249}
250
251impl<'a> Stmt<'a> {
252 fn err(&self, message: impl fmt::Display) -> TextParseError {
253 TextParseError::new(self.line, message)
254 }
255
256 fn peek(&self) -> Option<&'a str> {
257 self.tokens.get(self.next.get()).copied()
258 }
259
260 fn advance(&self) {
261 self.next.set(self.next.get() + 1);
262 }
263
264 fn next_token(&self, expected: &str) -> Result<&'a str> {
265 let token = self
266 .peek()
267 .ok_or_else(|| self.err(format!("expected {expected}, found end of line")))?;
268 self.advance();
269 Ok(token)
270 }
271
272 fn expect(&self, literal: &str) -> Result<()> {
273 let token = self.next_token(&format!("`{literal}`"))?;
274 if token != literal {
275 return Err(self.err(format!("expected `{literal}`, found `{token}`")));
276 }
277 Ok(())
278 }
279
280 fn finish(&self) -> Result<()> {
281 if let Some(extra) = self.peek() {
282 return Err(self.err(format!("unexpected trailing `{extra}`")));
283 }
284 Ok(())
285 }
286
287 fn is_close(&self) -> bool {
289 self.tokens == ["}"]
290 }
291}
292
293impl<'a> Parser<'a> {
298 fn parse_program(&mut self) -> Result<Bloq> {
299 let header = self.expect_stmt("`BLOQIR <version>` header")?;
300 header.expect("BLOQIR")?;
301 let version: u32 = parse_int(&header, header.next_token("format version")?)?;
302 header.finish()?;
303 if version != BLOQ_TEXT_VERSION {
304 return Err(header.err(format!(
305 "unsupported bloq text version {version} (this reader supports only \
306 version {BLOQ_TEXT_VERSION}; there is no cross-version compatibility)"
307 )));
308 }
309
310 let mut metadata = BTreeMap::new();
311 let mut logical_inputs = Vec::new();
312 let mut logical_outputs = Vec::new();
313 let mut templates = BloqTemplatePool::new();
314 let mut detector_bundles = DetectorBundlePool::new();
315 let mut top = None;
316
317 while let Some(stmt) = self.next_stmt() {
318 match stmt.next_token(
319 "`metadata`, `logical-input`, `logical-output`, `template`, `bundle`, or `graph`",
320 )? {
321 "metadata" => {
322 let key_token = stmt.next_token("metadata key")?;
323 let key = decode_metadata_token(key_token).map_err(|message| {
324 stmt.err(format!("invalid metadata key `{key_token}`: {message}"))
325 })?;
326 let kind = stmt.next_token("metadata value type")?;
327 let value_token = stmt.next_token("metadata value")?;
328 let value = match kind {
329 "u64" => MetadataValue::U64(parse_int(&stmt, value_token)?),
330 "string" => MetadataValue::String(
331 decode_metadata_token(value_token).map_err(|message| {
332 stmt.err(format!(
333 "invalid metadata string `{value_token}`: {message}"
334 ))
335 })?,
336 ),
337 other => {
338 return Err(stmt.err(format!("unknown metadata value type `{other}`")));
339 }
340 };
341 stmt.finish()?;
342 if metadata.insert(key.clone(), value).is_some() {
343 return Err(stmt.err(format!("duplicate metadata key `{key}`")));
344 }
345 }
346 "logical-input" => {
347 let port = parse_coord3(&stmt, stmt.next_token("input port")?)?;
348 stmt.expect("instance")?;
349 let instance = crate::lowering::TemplateInstanceId(parse_prefixed(
350 &stmt,
351 stmt.next_token("input Port instance")?,
352 'i',
353 )?);
354 stmt.expect("x")?;
355 let x = parse_pauli_map(&stmt, stmt.next_token("logical X operator")?)?;
356 stmt.expect("z")?;
357 let z = parse_pauli_map(&stmt, stmt.next_token("logical Z operator")?)?;
358 stmt.finish()?;
359 logical_inputs.push(crate::LogicalInput {
360 port,
361 instance,
362 x,
363 z,
364 });
365 }
366 "logical-output" => {
367 let port = parse_coord3(&stmt, stmt.next_token("output port")?)?;
368 stmt.expect("instance")?;
369 let instance = crate::lowering::TemplateInstanceId(parse_prefixed(
370 &stmt,
371 stmt.next_token("output instance")?,
372 'i',
373 )?);
374 stmt.expect("x")?;
375 let x = parse_pauli_map(&stmt, stmt.next_token("logical X operator")?)?;
376 stmt.expect("z")?;
377 let z = parse_pauli_map(&stmt, stmt.next_token("logical Z operator")?)?;
378 stmt.finish()?;
379 logical_outputs.push(crate::LogicalOutput {
380 port,
381 instance,
382 x,
383 z,
384 });
385 }
386 "template" => {
387 let id = parse_prefixed(&stmt, stmt.next_token("template id")?, 't')?;
388 if id as usize != templates.len() {
389 return Err(stmt.err(format!(
390 "template t{id} out of order: expected t{}",
391 templates.len()
392 )));
393 }
394 stmt.expect("{")?;
395 stmt.finish()?;
396 templates.insert(self.parse_template()?);
397 }
398 "bundle" => {
399 let id = parse_prefixed(&stmt, stmt.next_token("bundle id")?, 'b')?;
400 if id as usize != detector_bundles.len() {
401 return Err(stmt.err(format!(
402 "bundle b{id} out of order: expected b{}",
403 detector_bundles.len()
404 )));
405 }
406 stmt.expect("owners")?;
407 let mut owner_templates = Vec::new();
408 while stmt.peek() != Some("{") {
409 owner_templates.push(TemplateId(parse_prefixed(
410 &stmt,
411 stmt.next_token("owner template or `{`")?,
412 't',
413 )?));
414 }
415 stmt.expect("{")?;
416 stmt.finish()?;
417 detector_bundles.insert(self.parse_bundle(owner_templates)?);
418 }
419 "graph" => {
420 if top.is_some() {
421 return Err(stmt.err("duplicate graph block"));
422 }
423 stmt.expect("{")?;
424 stmt.finish()?;
425 top = Some(self.parse_level()?);
426 }
427 other => return Err(stmt.err(format!("unknown section `{other}`"))),
428 }
429 }
430
431 let top =
432 top.ok_or_else(|| TextParseError::new(self.lines.len(), "missing `graph` section"))?;
433 let bloq = Bloq::from_parts(
434 templates,
435 detector_bundles,
436 top,
437 metadata,
438 logical_inputs,
439 logical_outputs,
440 );
441 validate_text_coordinate_translations(&bloq, self.lines.len())?;
442 Ok(bloq)
443 }
444}
445
446impl<'a> Parser<'a> {
447 fn parse_bundle(&mut self, owner_templates: Vec<TemplateId>) -> Result<DetectorBundle> {
448 let mut detectors = Vec::new();
449 loop {
450 let stmt = self.expect_stmt("a bundle detector or `}`")?;
451 if stmt.is_close() {
452 break;
453 }
454 stmt.expect("detector")?;
455 let parity = parse_parity(
456 &stmt,
457 stmt.next_token("bundle detector parity")?,
458 parse_bundle_measurement,
459 )?;
460 let coords = parse_opt_detector_coords(&stmt)?;
461 stmt.finish()?;
462 detectors.push(BundleDetector { parity, coords });
463 }
464 Ok(DetectorBundle::new(owner_templates, detectors))
465 }
466}
467
468impl<'a> Parser<'a> {
473 fn parse_template(&mut self) -> Result<BloqTemplate> {
474 let mut entry = None;
476 let mut bodies: FxHashMap<u32, (Vec<Op>, usize)> = FxHashMap::default();
477 let mut extra_measurements: Vec<(MeasRecord, usize)> = Vec::new();
479 let mut implied_measurements = FxHashMap::default();
480 let mut detectors = Vec::new();
481 let mut repeat_states = Vec::new();
482 let mut boundary_flows = Vec::new();
483 let mut restarts = Vec::new();
484
485 loop {
486 let stmt = self.expect_stmt("a template statement or `}`")?;
487 if stmt.is_close() {
488 break;
489 }
490 let keyword = stmt.next_token("template statement")?;
491 match keyword {
492 "circuit" | "body" => {
493 let body = if keyword == "circuit" && stmt.peek() == Some("{") {
494 0
495 } else {
496 parse_prefixed(&stmt, stmt.next_token("body id")?, 'b')?
497 };
498 if keyword == "circuit" && entry.replace(BodyId(body)).is_some() {
499 return Err(stmt.err("duplicate circuit entry"));
500 }
501 stmt.expect("{")?;
502 stmt.finish()?;
503 if bodies
504 .insert(
505 body,
506 (self.parse_ops(&mut implied_measurements)?, stmt.line),
507 )
508 .is_some()
509 {
510 return Err(stmt.err(format!("duplicate body b{body}")));
511 }
512 }
513 "meas" => {
514 let id = parse_measurement_output(&stmt, stmt.next_token("measurement id")?)?;
515 stmt.expect("@")?;
516 let qubit = parse_coord2(&stmt, stmt.next_token("qubit coordinate")?)?;
517 stmt.finish()?;
518 extra_measurements.push((MeasRecord { id, qubit }, stmt.line));
519 }
520 "detector" => {
521 let scope = if let Some(token) = stmt.peek()
522 && let Some(args) = call_args(token, "body")
523 {
524 stmt.advance();
525 let [body] = parse_call_args(&stmt, args)?;
526 TemplateDetectorScope::RepeatBody {
527 body: BodyId(parse_prefixed(&stmt, body, 'b')?),
528 }
529 } else {
530 TemplateDetectorScope::TopLevel
531 };
532 let parity =
533 parse_parity(&stmt, stmt.next_token("detector parity")?, parse_meas_term)?;
534 let coords = parse_opt_detector_coords(&stmt)?;
535 stmt.finish()?;
536 detectors.push(TemplateDetector {
537 scope,
538 parity,
539 coords,
540 });
541 }
542 token if call_args(token, "loop").is_some() => {
543 let args = call_args(token, "loop").expect("guard matched");
544 let [body, state] = parse_call_args(&stmt, args)?;
545 let body = BodyId(parse_prefixed(&stmt, body, 'b')?);
546 let state = LoopStateId(parse_prefixed(&stmt, state, 's')?);
547 stmt.expect("init")?;
548 let initial =
549 parse_parity(&stmt, stmt.next_token("initial parity")?, parse_meas_term)?;
550 stmt.expect("next")?;
551 let next =
552 parse_parity(&stmt, stmt.next_token("next parity")?, parse_meas_term)?;
553 stmt.finish()?;
554 repeat_states.push(TemplateRepeatState {
555 body,
556 state,
557 initial,
558 next,
559 });
560 }
561 "flow" => {
562 boundary_flows.push(parse_flow(&stmt)?);
563 }
564 "restart" => {
565 let parity =
566 parse_parity(&stmt, stmt.next_token("restart parity")?, parse_meas_term)?;
567 stmt.finish()?;
568 restarts.push(TemplateRestart { parity });
569 }
570 other => return Err(stmt.err(format!("unknown template statement `{other}`"))),
571 }
572 }
573
574 Ok(BloqTemplate::with_parts(
575 build_circuit(
576 bodies,
577 entry.unwrap_or(BodyId(0)),
578 implied_measurements,
579 extra_measurements,
580 )?,
581 detectors,
582 repeat_states,
583 boundary_flows,
584 restarts,
585 ))
586 }
587
588 fn parse_ops(&mut self, implied: &mut FxHashMap<u32, IVec2>) -> Result<Vec<Op>> {
589 let mut ops = Vec::new();
590 loop {
591 let stmt = self.expect_stmt("a circuit op or `}`")?;
592 if stmt.is_close() {
593 return Ok(ops);
594 }
595 let op = parse_op(&stmt)?;
596 register_implied_measurements(&op, stmt.line, implied)?;
597 ops.push(op);
598 }
599 }
600}
601
602fn build_circuit(
607 bodies: FxHashMap<u32, (Vec<Op>, usize)>,
608 entry: BodyId,
609 implied: FxHashMap<u32, IVec2>,
610 extra: Vec<(MeasRecord, usize)>,
611) -> Result<CoordCircuit> {
612 if let Some((max_body, (_, line))) = bodies.iter().max_by_key(|(body, _)| **body)
613 && *max_body as usize >= bodies.len()
614 {
615 return Err(TextParseError::new(
616 *line,
617 format!("body b{max_body} is sparse: body ids must be contiguous from b0"),
618 ));
619 }
620
621 let mut circuit = CoordCircuit::new();
622 for _ in 1..bodies.len() {
623 circuit.add_body(CircuitBody::new());
624 }
625 let mut seen = implied.clone();
630 for (record, line) in &extra {
631 match seen.insert(record.id, record.qubit) {
632 Some(qubit) if qubit != record.qubit => {
633 return Err(TextParseError::new(
634 *line,
635 format!(
636 "measurement m{} is bound to two different qubits: \
637 ({},{}) and ({},{})",
638 record.id, qubit.x, qubit.y, record.qubit.x, record.qubit.y
639 ),
640 ));
641 }
642 _ => {}
643 }
644 }
645
646 let mut records: Vec<MeasRecord> = implied
647 .into_iter()
648 .map(|(id, qubit)| MeasRecord { id, qubit })
649 .collect();
650 records.extend(extra.into_iter().map(|(record, _)| record));
651 for (body_id, (ops, _)) in bodies {
652 *circuit
653 .body_mut(BodyId(body_id))
654 .expect("dense body ids were allocated above")
655 .ops_mut() = ops;
656 }
657 circuit.register_measurement_records(&mut records);
658 circuit
659 .set_entry_body(entry)
660 .expect("dense parsed bodies contain the entry");
661 Ok(circuit)
662}
663
664fn parse_op(stmt: &Stmt<'_>) -> Result<Op> {
665 let token = stmt.next_token("circuit op")?;
666 let (head, probability) = parse_instruction_head(stmt, token)?;
667 let op = match head {
668 "TICK" if probability.is_none() => Op::Tick,
669 "REPEAT" => {
670 reject_probability_argument(stmt, token, probability)?;
671 let repetitions = parse_int(stmt, stmt.next_token("repetition count")?)?;
672 let body = BodyId(parse_prefixed(stmt, stmt.next_token("body id")?, 'b')?);
673 Op::Repeat { body, repetitions }
674 }
675 "CPAULI" => {
676 reject_probability_argument(stmt, token, probability)?;
677 let mut corrections = Vec::new();
678 while let Some(token) = stmt.peek() {
679 stmt.advance();
680 corrections.push(parse_correction(stmt, token)?);
681 }
682 Op::ConditionalPauli(corrections)
683 }
684 "MPP" => {
685 reject_probability_argument(stmt, token, probability)?;
686 let mut products = Vec::new();
687 let mut measurements = Vec::new();
688 while let Some(token) = stmt.peek() {
689 stmt.advance();
690 let (product, measurement) = token.rsplit_once(":").ok_or_else(|| {
691 stmt.err(format!("MPP product `{token}` is missing its `:mN` record"))
692 })?;
693 let product = parse_pauli_map(stmt, product)?;
694 if product.is_empty() {
695 return Err(stmt.err("MPP product must contain a non-identity Pauli target"));
696 }
697 products.push(product);
698 measurements.push(parse_measurement_output(stmt, measurement)?);
699 }
700 if products.is_empty() {
701 return Err(stmt.err("MPP requires at least one product"));
702 }
703 Op::MPP {
704 products,
705 measurements,
706 }
707 }
708 "M" | "MX" | "MY" => {
709 let basis = match head {
710 "MX" => PauliBasis::X,
711 "MY" => PauliBasis::Y,
712 _ => PauliBasis::Z,
713 };
714 let mut qubits = Vec::new();
715 let mut measurements = Vec::new();
716 while let Some(token) = stmt.peek() {
717 stmt.advance();
718 let (qubit, measurement) = token.split_once(":").ok_or_else(|| {
719 stmt.err(format!(
720 "measure target `{token}` is missing its `:mN` record"
721 ))
722 })?;
723 qubits.push(parse_coord2(stmt, qubit)?);
724 measurements.push(parse_measurement_output(stmt, measurement)?);
725 }
726 Op::Measure {
727 basis,
728 qubits,
729 measurements,
730 flip_probability: probability.unwrap_or(0.0),
731 }
732 }
733 "DEPOLARIZE1" => Op::Depolarize1 {
734 probability: require_probability_argument(stmt, token, probability)?,
735 qubits: parse_qubit_targets(stmt)?,
736 },
737 "DEPOLARIZE2" => {
738 let qubits = parse_qubit_targets(stmt)?;
739 if qubits.len() % 2 != 0 {
740 return Err(stmt.err(format!(
741 "DEPOLARIZE2 requires an even target count, got {}",
742 qubits.len()
743 )));
744 }
745 Op::Depolarize2 {
746 probability: require_probability_argument(stmt, token, probability)?,
747 qubits,
748 }
749 }
750 "X_ERROR" | "Y_ERROR" | "Z_ERROR" => Op::PauliError {
751 probability: require_probability_argument(stmt, token, probability)?,
752 pauli: match head {
753 "X_ERROR" => PauliBasis::X,
754 "Y_ERROR" => PauliBasis::Y,
755 _ => PauliBasis::Z,
756 },
757 qubits: parse_qubit_targets(stmt)?,
758 },
759 gate => {
760 reject_probability_argument(stmt, token, probability)?;
761 let gate =
762 GateType::from_str(gate).map_err(|_| stmt.err(format!("unknown gate `{gate}`")))?;
763 let mut qubits = Vec::new();
764 while let Some(token) = stmt.peek() {
765 stmt.advance();
766 qubits.push(parse_coord2(stmt, token)?);
767 }
768 Op::Gate { gate, qubits }
769 }
770 };
771 stmt.finish()?;
772 Ok(op)
773}
774
775fn parse_instruction_head<'a>(stmt: &Stmt<'_>, token: &'a str) -> Result<(&'a str, Option<f64>)> {
776 let Some((name, argument)) = token.split_once('(') else {
777 return Ok((token, None));
778 };
779 let argument = argument
780 .strip_suffix(')')
781 .ok_or_else(|| stmt.err(format!("malformed instruction argument in `{token}`")))?;
782 if name.is_empty() || argument.is_empty() || argument.contains(['(', ')']) {
783 return Err(stmt.err(format!("malformed instruction argument in `{token}`")));
784 }
785 let probability = argument
786 .parse::<f64>()
787 .map_err(|_| stmt.err(format!("invalid probability `{argument}`")))?;
788 if !(0.0..=1.0).contains(&probability) {
789 return Err(stmt.err(format!(
790 "probability must be finite and in [0, 1], got `{argument}`"
791 )));
792 }
793 Ok((name, Some(probability)))
794}
795
796fn reject_probability_argument(
797 stmt: &Stmt<'_>,
798 token: &str,
799 probability: Option<f64>,
800) -> Result<()> {
801 if probability.is_some() {
802 return Err(stmt.err(format!("instruction `{token}` does not take a probability")));
803 }
804 Ok(())
805}
806
807fn require_probability_argument(
808 stmt: &Stmt<'_>,
809 token: &str,
810 probability: Option<f64>,
811) -> Result<f64> {
812 probability.ok_or_else(|| stmt.err(format!("instruction `{token}` requires a probability")))
813}
814
815fn parse_qubit_targets(stmt: &Stmt<'_>) -> Result<Vec<IVec2>> {
816 let mut qubits = Vec::new();
817 while let Some(token) = stmt.peek() {
818 stmt.advance();
819 qubits.push(parse_coord2(stmt, token)?);
820 }
821 Ok(qubits)
822}
823
824fn parse_correction(stmt: &Stmt<'_>, token: &str) -> Result<ConditionalCorrection> {
826 let error = || {
827 stmt.err(format!(
828 "malformed correction `{token}`, expected `P[c]@(x,y)`"
829 ))
830 };
831 let (pauli, rest) = token.split_once('[').ok_or_else(error)?;
832 let (control, target) = rest.split_once("]@").ok_or_else(error)?;
833 let control = parse_prefixed(stmt, control, 'm')?;
834 Ok(ConditionalCorrection {
835 pauli: parse_pauli_basis(stmt, pauli)?,
836 control,
837 target: parse_coord2(stmt, target)?,
838 })
839}
840
841fn parse_flow(stmt: &Stmt<'_>) -> Result<Flow> {
842 let start = parse_pauli_map(stmt, stmt.next_token("flow start")?)?;
843 stmt.expect("->")?;
844 let end = parse_pauli_map(stmt, stmt.next_token("flow end")?)?;
845 let mut flow = Flow::new(start, end);
846 while let Some(token) = stmt.peek() {
847 stmt.advance();
848 match token {
849 "meas" => {
850 let list = stmt.next_token("flow measurements")?;
851 let mut measurements = Vec::new();
852 for entry in list.split('*') {
853 measurements.push(parse_prefixed(stmt, entry, 'm')?);
854 }
855 flow = flow.with_measurements(measurements);
856 }
857 "center" => {
858 let center = parse_coord2(stmt, stmt.next_token("flow center")?)?;
859 flow = flow.with_center(center);
860 }
861 "sign" => {
862 stmt.expect("-1")?;
863 flow = flow.with_sign(true);
864 }
865 "discard" => flow = flow.with_marker(FlowMarker::Discard),
866 "restart" => flow = flow.with_marker(FlowMarker::Restart),
867 other => return Err(stmt.err(format!("unknown flow attribute `{other}`"))),
868 }
869 }
870 Ok(flow)
871}
872
873impl<'a> Parser<'a> {
878 fn parse_level(&mut self) -> Result<SubGraph> {
882 let mut nodes: Vec<(u32, BloqNode)> = Vec::new();
883 let mut node_lines: Vec<(u32, usize)> = Vec::new();
885 let mut edges: Vec<(u32, u32, BloqEdge)> = Vec::new();
886 let mut edge_lines: Vec<usize> = Vec::new();
887 let mut value_output = None;
888 let mut boundary_outputs = None;
889
890 loop {
891 let stmt = self.expect_stmt("a node, an edge, or `}`")?;
892 if stmt.is_close() {
893 break;
894 }
895 let head = stmt.next_token("node or edge")?;
896 if head == "result" {
897 if value_output.is_some() {
898 return Err(stmt.err("duplicate result declaration"));
899 }
900 let output = stmt.next_token("result node or false")?;
901 value_output = Some(if output == "false" {
902 None
903 } else {
904 Some(parse_value_ref(&stmt, output)?)
905 });
906 stmt.finish()?;
907 continue;
908 }
909 if head == "bindings" {
910 if boundary_outputs.is_some() {
911 return Err(stmt.err("duplicate bindings declaration"));
912 }
913 let mut outputs = Vec::new();
914 while let Some(output) = stmt.peek() {
915 outputs.push(BloqNodeId(parse_prefixed(&stmt, output, 'n')?));
916 stmt.advance();
917 }
918 boundary_outputs = Some(outputs);
919 continue;
920 }
921 let id = parse_prefixed(&stmt, head, 'n')?;
922 if stmt.peek() == Some("->") {
923 stmt.advance();
924 let target = parse_prefixed(&stmt, stmt.next_token("edge target")?, 'n')?;
925 edge_lines.push(stmt.line);
926 edges.push((id, target, parse_edge(&stmt)?));
927 } else {
928 node_lines.push((id, stmt.line));
929 let node = self.parse_node(&stmt)?;
930 nodes.push((id, node));
931 }
932 }
933
934 let mut level = crate::ir::rebuild_level(nodes, edges).map_err(|error| {
937 use crate::ir::LevelRebuildError;
938 let line = match error {
939 LevelRebuildError::DuplicateNode(id) => node_lines
941 .iter()
942 .filter(|(node, _)| *node == id)
943 .map(|&(_, line)| line)
944 .nth(1),
945 LevelRebuildError::MissingEndpoint { index, .. } => edge_lines.get(index).copied(),
946 LevelRebuildError::ExcessiveVacancy { .. } => node_lines
948 .iter()
949 .max_by_key(|(node, _)| *node)
950 .map(|&(_, line)| line),
951 };
952 let line = line.unwrap_or(self.lines.len());
953 TextParseError::new(line, error)
954 })?;
955 level.set_value_output(value_output.flatten());
956 level.set_boundary_outputs(boundary_outputs.unwrap_or_default());
957 Ok(level)
958 }
959
960 fn parse_node(&mut self, stmt: &Stmt<'_>) -> Result<BloqNode> {
961 let kind = stmt.next_token("node kind")?;
962 match kind {
963 "quantum" => {
964 stmt.expect("{")?;
965 stmt.finish()?;
966 self.parse_quantum_node()
967 }
968 "rus" => self.parse_region_node(stmt),
969 _ => parse_classical_node(stmt, kind),
970 }
971 }
972
973 fn parse_quantum_node(&mut self) -> Result<BloqNode> {
974 let mut quantum = QuantumNode::default();
975 let mut provenance = NodeProvenance::None;
976 loop {
977 let stmt = self.expect_stmt("a quantum-node statement or `}`")?;
978 if stmt.is_close() {
979 break;
980 }
981 let keyword = stmt.next_token("quantum-node statement")?;
982 match keyword {
983 "instance" => {
984 let id = parse_prefixed(&stmt, stmt.next_token("instance id")?, 'i')?;
985 let template = parse_prefixed(&stmt, stmt.next_token("template id")?, 't')?;
986 stmt.expect("@")?;
987 let offset = parse_coord2(&stmt, stmt.next_token("instance offset")?)?;
988 let provenance = if stmt.peek() == Some("from") {
989 stmt.advance();
990 match stmt.next_token("instance source")? {
991 "block" => crate::InstanceProvenance::Block {
992 source: parse_coord3(&stmt, stmt.next_token("source block")?)?,
993 },
994 "pipe" => crate::InstanceProvenance::Pipe {
995 src: parse_coord3(&stmt, stmt.next_token("pipe source")?)?,
996 dst: parse_coord3(&stmt, stmt.next_token("pipe destination")?)?,
997 },
998 "spatial-port" => {
999 let role = parse_port_role(&stmt)?;
1000 let part = match stmt
1001 .next_token("spatial Port substitution part")?
1002 {
1003 "cube" => crate::SpatialPortPart::Cube,
1004 "temporal-port" => crate::SpatialPortPart::TemporalPort,
1005 other => {
1006 return Err(stmt
1007 .err(format!("unknown spatial Port part `{other}`")));
1008 }
1009 };
1010 let source =
1011 parse_coord3(&stmt, stmt.next_token("source spatial Port")?)?;
1012 crate::InstanceProvenance::SpatialPortSubstitution {
1013 source,
1014 role,
1015 part,
1016 }
1017 }
1018 other => {
1019 return Err(stmt.err(format!("unknown instance source `{other}`")));
1020 }
1021 }
1022 } else {
1023 crate::InstanceProvenance::Source
1024 };
1025 stmt.finish()?;
1026 quantum.instances.push(TemplateInstance {
1027 id: TemplateInstanceId(id),
1028 template_id: TemplateId(template),
1029 offset,
1030 provenance,
1031 });
1032 }
1033 "detector" => {
1034 let parity = parse_parity(
1035 &stmt,
1036 stmt.next_token("detector parity")?,
1037 parse_instance_measurement,
1038 )?;
1039 let coords = parse_opt_detector_coords(&stmt)?;
1040 stmt.finish()?;
1041 quantum.detectors.push(NodeDetector { parity, coords });
1042 }
1043 "use" => {
1044 let bundle = DetectorBundleId(parse_prefixed(
1045 &stmt,
1046 stmt.next_token("bundle id")?,
1047 'b',
1048 )?);
1049 let mut instances = Vec::new();
1050 while stmt.peek() != Some("@") {
1051 instances.push(TemplateInstanceId(parse_prefixed(
1052 &stmt,
1053 stmt.next_token("owner instance or `@`")?,
1054 'i',
1055 )?));
1056 }
1057 stmt.expect("@")?;
1058 let offset = parse_coord2(&stmt, stmt.next_token("bundle offset")?)?;
1059 stmt.finish()?;
1060 quantum.detector_bundles.push(DetectorBundleUse {
1061 bundle,
1062 instances,
1063 offset,
1064 });
1065 }
1066 "restart" => {
1067 let parity = parse_parity(
1068 &stmt,
1069 stmt.next_token("restart parity")?,
1070 parse_instance_measurement,
1071 )?;
1072 stmt.finish()?;
1073 quantum.restarts.push(NodeRestart { parity });
1074 }
1075 "timeline" => {
1076 stmt.expect("rounds")?;
1077 let mut layer_round_ends = Vec::new();
1078 while let Some(end) = stmt.peek() {
1079 stmt.advance();
1080 layer_round_ends.push(parse_int(&stmt, end)?);
1081 }
1082 if quantum
1083 .timeline
1084 .replace(QuantumTimeline { layer_round_ends })
1085 .is_some()
1086 {
1087 return Err(stmt.err("duplicate quantum timeline"));
1088 }
1089 stmt.finish()?;
1090 }
1091 "guard" => {
1092 let input = parse_int(&stmt, stmt.next_token("membership input slot")?)?;
1093 let mut guard = crate::QuantumGuard {
1094 input,
1095 instances: Vec::new(),
1096 ..Default::default()
1097 };
1098 while let Some(token) = stmt.peek() {
1099 stmt.advance();
1100 match token.as_bytes().first() {
1101 Some(b'i') => guard
1102 .instances
1103 .push(TemplateInstanceId(parse_prefixed(&stmt, token, 'i')?)),
1104 Some(b'd') => guard.detectors.push(parse_prefixed(&stmt, token, 'd')?),
1105 Some(b'b') => guard
1106 .detector_bundles
1107 .push(parse_prefixed(&stmt, token, 'b')?),
1108 Some(b'r') => guard.restarts.push(parse_prefixed(&stmt, token, 'r')?),
1109 Some(b'x') => {
1110 let (target, parity) = token[1..]
1111 .split_once('=')
1112 .ok_or_else(|| stmt.err("expected xdN=parity or xrN=parity"))?;
1113 let parity =
1114 parse_parity(&stmt, parity, parse_instance_measurement)?;
1115 if target.starts_with('d') {
1116 guard
1117 .detector_parities
1118 .push((parse_prefixed(&stmt, target, 'd')?, parity));
1119 } else {
1120 guard
1121 .restart_parities
1122 .push((parse_prefixed(&stmt, target, 'r')?, parity));
1123 }
1124 }
1125 _ => {
1126 return Err(stmt.err(
1127 "expected guarded instance iN, detector dN, bundle use bN, or restart rN",
1128 ));
1129 }
1130 }
1131 }
1132 stmt.finish()?;
1133 quantum.guards.push(guard);
1134 }
1135 "from" => {
1136 provenance = parse_provenance(&stmt)?;
1137 stmt.finish()?;
1138 }
1139 other => {
1140 return Err(stmt.err(format!("unknown quantum-node statement `{other}`")));
1141 }
1142 }
1143 }
1144 Ok(BloqNode::quantum(quantum).with_provenance(provenance))
1145 }
1146
1147 fn parse_region_node(&mut self, stmt: &Stmt<'_>) -> Result<BloqNode> {
1148 let expr = parse_expr_until(stmt, &["source", "when", "{"])?;
1151 let restart_source = if stmt.peek() == Some("source") {
1152 stmt.advance();
1153 Some(parse_value_ref(
1154 stmt,
1155 stmt.next_token("restart source node")?,
1156 )?)
1157 } else {
1158 None
1159 };
1160 let activation = if stmt.peek() == Some("when") {
1161 stmt.advance();
1162 Some(parse_prefixed(
1163 stmt,
1164 stmt.next_token("activation input")?,
1165 'v',
1166 )?)
1167 } else {
1168 None
1169 };
1170 stmt.expect("{")?;
1171 stmt.finish()?;
1172
1173 let mut provenance = NodeProvenance::None;
1174 let mut next = self.expect_stmt("a region body or `}`")?;
1175 if next.peek() == Some("from") {
1176 next.advance();
1177 provenance = parse_provenance(&next)?;
1178 next.finish()?;
1179 next = self.expect_stmt("a region body or `}`")?;
1180 }
1181
1182 let region = RegionNode::RepeatUntilSuccess {
1183 body: self.parse_body_block(&next, "body")?,
1184 restart_condition: expr,
1185 restart_source,
1186 };
1187
1188 let close = self.expect_stmt("`}` closing the region node")?;
1189 if !close.is_close() {
1190 return Err(close.err("expected `}` closing the region node"));
1191 }
1192 Ok(BloqNode {
1193 activation,
1194 kind: BloqNodeKind::Region(region),
1195 provenance,
1196 })
1197 }
1198
1199 fn parse_body_block(&mut self, stmt: &Stmt<'_>, name: &str) -> Result<SubGraph> {
1201 stmt.expect(name)?;
1202 stmt.expect("{")?;
1203 stmt.finish()?;
1204 self.parse_level()
1205 }
1206}
1207
1208fn parse_classical_node(stmt: &Stmt<'_>, kind: &str) -> Result<BloqNode> {
1209 let classical = match kind {
1210 "compute" => ClassicalNode::Compute {
1211 expr: parse_expr_until(stmt, &["when", "from"])?,
1212 },
1213 "observable" => {
1214 let token = stmt.next_token("observable index or fragment")?;
1215 let index = if token == "fragment" {
1216 None
1217 } else {
1218 Some(parse_int(stmt, token)?)
1219 };
1220 let measurements = if stmt.peek() == Some("measurements") {
1221 stmt.advance();
1222 parse_classical_measurements(stmt)?
1223 } else {
1224 Vec::new()
1225 };
1226 let operators = if stmt.peek() == Some("operators") {
1227 stmt.advance();
1228 parse_classical_operators(stmt)?
1229 } else {
1230 Vec::new()
1231 };
1232 ClassicalNode::Observable {
1233 index,
1234 measurements,
1235 operators,
1236 }
1237 }
1238 "discard" => ClassicalNode::Discard {
1239 condition: parse_expr_until(stmt, &["when", "from"])?,
1240 },
1241 other => return Err(stmt.err(format!("unknown node kind `{other}`"))),
1242 };
1243
1244 let activation = if stmt.peek() == Some("when") {
1245 stmt.advance();
1246 Some(parse_prefixed(stmt, stmt.next_token("guard input")?, 'v')?)
1247 } else {
1248 None
1249 };
1250 let provenance = if stmt.peek() == Some("from") {
1251 stmt.advance();
1252 parse_provenance(stmt)?
1253 } else {
1254 NodeProvenance::None
1255 };
1256 stmt.finish()?;
1257 Ok(BloqNode {
1258 activation,
1259 kind: BloqNodeKind::Classical(classical.into()),
1260 provenance,
1261 })
1262}
1263
1264fn parse_classical_measurements(stmt: &Stmt<'_>) -> Result<Vec<InstanceMeasurement>> {
1265 let Some(token) = stmt.peek() else {
1266 return Ok(Vec::new());
1267 };
1268 if matches!(token, "operators" | "from" | "when") {
1269 return Ok(Vec::new());
1270 }
1271 stmt.advance();
1272 token
1273 .split('*')
1274 .map(|entry| parse_instance_measurement(stmt, entry))
1275 .collect()
1276}
1277
1278fn parse_classical_operators(stmt: &Stmt<'_>) -> Result<Vec<InstanceBoundaryOperator>> {
1279 let mut operators = Vec::new();
1281 while !matches!(stmt.peek(), None | Some("from" | "when")) {
1282 let instance =
1283 TemplateInstanceId(parse_prefixed(stmt, stmt.next_token("instance id")?, 'i')?);
1284 let face = match stmt.next_token("`input` or `output`")? {
1285 "input" => BoundaryFace::Input,
1286 "output" => BoundaryFace::Output,
1287 other => return Err(stmt.err(format!("unknown boundary face `{other}`"))),
1288 };
1289 let token = stmt.next_token("boundary operator")?;
1290 let (map_token, more) = token
1291 .strip_suffix(',')
1292 .map_or((token, false), |rest| (rest, true));
1293 operators.push(InstanceBoundaryOperator {
1294 instance,
1295 face,
1296 operator: parse_pauli_map(stmt, map_token)?,
1297 });
1298 if !more {
1299 break;
1300 }
1301 }
1302 Ok(operators)
1303}
1304
1305fn parse_edge(stmt: &Stmt<'_>) -> Result<BloqEdge> {
1306 let edge = match stmt.next_token("edge kind")? {
1307 "order" => BloqEdge::Order,
1308 kind @ ("value" | "compose") => {
1309 let slot = parse_int(stmt, stmt.next_token("input slot")?)?;
1310 let output = if kind == "value" && matches!(stmt.peek(), Some("corrected" | "flip")) {
1311 parse_output(stmt, stmt.next_token("output port")?)?
1312 } else {
1313 ObservableOutput::Corrected
1314 };
1315 let role = match stmt.peek() {
1316 Some("feedback") => {
1317 stmt.advance();
1318 ValueRole::FeedbackFold {
1319 action: parse_int(stmt, stmt.next_token("feedback action")?)?,
1320 }
1321 }
1322 Some("readout") => {
1323 stmt.advance();
1324 ValueRole::ReadoutFold
1325 }
1326 _ => ValueRole::Data,
1327 };
1328 if kind == "compose" {
1329 BloqEdge::Compose { slot, role }
1330 } else {
1331 BloqEdge::Value { slot, role, output }
1332 }
1333 }
1334 "quantum" => {
1335 let mut pipes = Vec::new();
1336 while let Some(token) = stmt.peek() {
1337 if token == "when" {
1338 break;
1339 }
1340 stmt.advance();
1341 let pipe = parse_pipe(stmt, token)?;
1342 let padding = if stmt.peek() == Some("padding") {
1344 stmt.advance();
1345 stmt.expect("offset")?;
1346 let offset = parse_coord2(stmt, stmt.next_token("padding offset")?)?;
1347 stmt.expect("one")?;
1348 let one_round = parse_prefixed(stmt, stmt.next_token("template id")?, 't')?;
1349 stmt.expect("loop")?;
1350 let looped = parse_prefixed(stmt, stmt.next_token("template id")?, 't')?;
1351 Some(PipePadding {
1352 offset,
1353 one_round: TemplateId(one_round),
1354 looped: TemplateId(looped),
1355 })
1356 } else {
1357 None
1358 };
1359 pipes.push(PipeSeam { pipe, padding });
1360 }
1361 let guard = if stmt.peek() == Some("when") {
1362 stmt.advance();
1363 Some(parse_value_ref(
1364 stmt,
1365 stmt.next_token("seam guard producer")?,
1366 )?)
1367 } else {
1368 None
1369 };
1370 BloqEdge::Quantum(Box::new(QuantumEdge { pipes, guard }))
1371 }
1372 other => return Err(stmt.err(format!("unknown edge kind `{other}`"))),
1373 };
1374 stmt.finish()?;
1375 Ok(edge)
1376}
1377
1378fn parse_output(stmt: &Stmt<'_>, token: &str) -> Result<ObservableOutput> {
1379 match token {
1380 "corrected" => Ok(ObservableOutput::Corrected),
1381 "flip" => Ok(ObservableOutput::Flip),
1382 _ => Err(stmt.err(format!("unknown output port `{token}`"))),
1383 }
1384}
1385
1386fn parse_value_ref(stmt: &Stmt<'_>, token: &str) -> Result<ValueRef> {
1387 let (node, output) = match token.split_once(':') {
1388 Some((node, output)) => (node, parse_output(stmt, output)?),
1389 None => (token, ObservableOutput::Corrected),
1390 };
1391 Ok(ValueRef {
1392 node: BloqNodeId(parse_prefixed(stmt, node, 'n')?),
1393 output,
1394 })
1395}
1396
1397fn parse_provenance(stmt: &Stmt<'_>) -> Result<NodeProvenance> {
1402 let provenance = match stmt.next_token("provenance kind")? {
1403 "blocks" => {
1404 let mut members = Vec::new();
1405 while let Some(token) = stmt.peek() {
1406 stmt.advance();
1407 members.push(SourceBlockRef {
1408 pos: parse_coord3(stmt, token)?,
1409 });
1410 }
1411 NodeProvenance::BlockComponent { members }
1412 }
1413 "pipe" => NodeProvenance::TemporalPipe {
1414 pipe: parse_pipe(stmt, stmt.next_token("pipe")?)?,
1415 },
1416 "spatial-port" => {
1417 let role = parse_port_role(stmt)?;
1418 let source = parse_coord3(stmt, stmt.next_token("source spatial Port")?)?;
1419 NodeProvenance::SpatialPortSubstitution { source, role }
1420 }
1421 "padding" => {
1422 let pipe = parse_pipe(stmt, stmt.next_token("pipe")?)?;
1423 stmt.expect("rounds")?;
1424 let rounds = parse_int(stmt, stmt.next_token("round count")?)?;
1425 NodeProvenance::MemoryPadding { pipe, rounds }
1426 }
1427 "generator" => NodeProvenance::Generator {
1428 ordinal: parse_int(stmt, stmt.next_token("generator ordinal")?)?,
1429 },
1430 "action" => NodeProvenance::Action {
1431 ordinal: parse_int(stmt, stmt.next_token("action ordinal")?)?,
1432 },
1433 "selector" => NodeProvenance::BranchSelector {
1434 name: decode_metadata_token(stmt.next_token("branch name")?)
1435 .map_err(|message| stmt.err(message))?,
1436 },
1437 "frame" => {
1438 let basis = match stmt.next_token("frame basis")? {
1439 "x" => Basis::X,
1440 "z" => Basis::Z,
1441 other => return Err(stmt.err(format!("unknown frame basis `{other}`"))),
1442 };
1443 let port = parse_coord3(stmt, stmt.next_token("frame output port")?)?;
1444 NodeProvenance::OutputFrame { port, basis }
1445 }
1446 other => return Err(stmt.err(format!("unknown provenance kind `{other}`"))),
1447 };
1448 Ok(provenance)
1449}
1450
1451fn call_args<'a>(token: &'a str, name: &str) -> Option<&'a str> {
1458 token
1459 .strip_prefix(name)?
1460 .strip_prefix('(')?
1461 .strip_suffix(')')
1462}
1463
1464fn parse_call_args<'a, const N: usize>(stmt: &Stmt<'_>, args: &'a str) -> Result<[&'a str; N]> {
1466 let pieces: Vec<&str> = args.split(',').collect();
1467 <[&str; N]>::try_from(pieces).map_err(|pieces: Vec<&str>| {
1468 stmt.err(format!("expected {N} argument(s), found {}", pieces.len()))
1469 })
1470}
1471
1472fn parse_int<T: FromStr>(stmt: &Stmt<'_>, token: &str) -> Result<T> {
1473 token
1474 .parse()
1475 .map_err(|_| stmt.err(format!("invalid number `{token}`")))
1476}
1477
1478fn parse_prefixed(stmt: &Stmt<'_>, token: &str, prefix: char) -> Result<u32> {
1482 token
1483 .strip_prefix(prefix)
1484 .filter(|digits| !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()))
1485 .and_then(|digits| digits.parse().ok())
1486 .ok_or_else(|| stmt.err(format!("expected `{prefix}<N>`, found `{token}`")))
1487}
1488
1489fn parse_port_role(stmt: &Stmt<'_>) -> Result<crate::PortRole> {
1492 stmt.next_token("spatial Port role")?
1493 .parse()
1494 .map_err(|_| stmt.err("invalid spatial Port role"))
1495}
1496
1497fn parse_measurement_output(stmt: &Stmt<'_>, token: &str) -> Result<u32> {
1498 let measurement = parse_prefixed(stmt, token, 'm')?;
1499 if measurement == u32::MAX {
1500 return Err(stmt.err(format!(
1501 "measurement id m{measurement} exhausts the measurement id space"
1502 )));
1503 }
1504 Ok(measurement)
1505}
1506
1507fn parse_coord2(stmt: &Stmt<'_>, token: &str) -> Result<IVec2> {
1508 let [x, y] = parse_int_tuple(stmt, token)?;
1509 Ok(IVec2::new(x, y))
1510}
1511
1512fn parse_coord3(stmt: &Stmt<'_>, token: &str) -> Result<IVec3> {
1513 let [x, y, z] = parse_int_tuple(stmt, token)?;
1514 Ok(IVec3::new(x, y, z))
1515}
1516
1517fn parse_int_tuple<const N: usize>(stmt: &Stmt<'_>, token: &str) -> Result<[i32; N]> {
1518 let inner = token
1519 .strip_prefix('(')
1520 .and_then(|rest| rest.strip_suffix(')'))
1521 .ok_or_else(|| stmt.err(format!("expected a coordinate, found `{token}`")))?;
1522 let mut out = [0i32; N];
1523 let mut pieces = inner.split(',');
1524 for slot in &mut out {
1525 let piece = pieces
1526 .next()
1527 .ok_or_else(|| stmt.err(format!("coordinate `{token}` has too few components")))?;
1528 *slot = parse_int(stmt, piece)?;
1529 }
1530 if pieces.next().is_some() {
1531 return Err(stmt.err(format!("coordinate `{token}` has too many components")));
1532 }
1533 Ok(out)
1534}
1535
1536fn parse_pipe(stmt: &Stmt<'_>, token: &str) -> Result<TemporalPipeRef> {
1538 let (token, hadamard) = token
1539 .strip_suffix('h')
1540 .map_or((token, false), |rest| (rest, true));
1541 let (src, dst) = token
1542 .split_once('>')
1543 .ok_or_else(|| stmt.err(format!("expected a pipe `(..)>(..)`, found `{token}`")))?;
1544 Ok(TemporalPipeRef {
1545 src: parse_coord3(stmt, src)?,
1546 dst: parse_coord3(stmt, dst)?,
1547 hadamard,
1548 })
1549}
1550
1551fn parse_pauli(stmt: &Stmt<'_>, token: &str) -> Result<Pauli> {
1552 Pauli::from_str(token).map_err(|_| stmt.err(format!("invalid Pauli `{token}`")))
1553}
1554
1555fn parse_pauli_basis(stmt: &Stmt<'_>, token: &str) -> Result<PauliBasis> {
1556 PauliBasis::from_str(token).map_err(|_| stmt.err(format!("invalid Pauli basis `{token}`")))
1557}
1558
1559fn parse_pauli_map(stmt: &Stmt<'_>, token: &str) -> Result<PauliMap> {
1561 if token == "_" {
1562 return Ok(PauliMap::empty());
1563 }
1564 let mut map = PauliMap::empty();
1565 for entry in token.split('*') {
1566 let (pauli, coord) = entry
1567 .split_at_checked(1)
1568 .ok_or_else(|| stmt.err(format!("malformed Pauli map entry `{entry}`")))?;
1569 map.insert(parse_coord2(stmt, coord)?, parse_pauli(stmt, pauli)?);
1570 }
1571 Ok(map)
1572}
1573
1574fn parse_meas_term(stmt: &Stmt<'_>, token: &str) -> Result<u32> {
1575 parse_prefixed(stmt, token, 'm')
1576}
1577
1578fn parse_instance_measurement(stmt: &Stmt<'_>, token: &str) -> Result<InstanceMeasurement> {
1580 let (instance, measurement) = token
1581 .split_once(':')
1582 .ok_or_else(|| stmt.err(format!("expected `iN:mK`, found `{token}`")))?;
1583 Ok(InstanceMeasurement {
1584 instance: TemplateInstanceId(parse_prefixed(stmt, instance, 'i')?),
1585 measurement: parse_prefixed(stmt, measurement, 'm')?,
1586 })
1587}
1588
1589fn parse_bundle_measurement(stmt: &Stmt<'_>, token: &str) -> Result<BundleMeasurement> {
1591 let (owner, measurement) = token
1592 .split_once(':')
1593 .ok_or_else(|| stmt.err(format!("expected `oN:mK`, found `{token}`")))?;
1594 Ok(BundleMeasurement {
1595 owner: parse_prefixed(stmt, owner, 'o')?,
1596 measurement: parse_prefixed(stmt, measurement, 'm')?,
1597 })
1598}
1599
1600fn parse_parity<M: Copy + Ord>(
1603 stmt: &Stmt<'_>,
1604 token: &str,
1605 measurement: impl Fn(&Stmt<'_>, &str) -> Result<M>,
1606) -> Result<DetectorParity<M>> {
1607 let (sign, token) = token
1608 .strip_prefix('-')
1609 .map_or((false, token), |token| (true, token));
1610 if token == "0" {
1611 return Ok(DetectorParity::default().with_sign(sign));
1612 }
1613 let mut terms = Vec::new();
1614 for entry in token.split('*') {
1615 if entry.starts_with('s') {
1618 terms.push(DetectorTerm::LoopState(LoopStateId(parse_prefixed(
1619 stmt, entry, 's',
1620 )?)));
1621 } else {
1622 terms.push(DetectorTerm::Measurement(measurement(stmt, entry)?));
1623 }
1624 }
1625 Ok(DetectorParity::from_terms(terms).with_sign(sign))
1626}
1627
1628fn parse_opt_detector_coords(stmt: &Stmt<'_>) -> Result<Option<bloq_circuit::DetectorCoords>> {
1630 if stmt.peek() != Some("@") {
1631 return Ok(None);
1632 }
1633 stmt.advance();
1634 let token = stmt.next_token("detector coordinates")?;
1635 let inner = token
1636 .strip_prefix('(')
1637 .and_then(|rest| rest.strip_suffix(')'))
1638 .ok_or_else(|| stmt.err(format!("expected coordinates, found `{token}`")))?;
1639 let mut coords = bloq_circuit::DetectorCoords::new();
1640 if !inner.is_empty() {
1641 for piece in inner.split(',') {
1642 coords.push(
1643 piece
1644 .parse()
1645 .map_err(|_| stmt.err(format!("invalid coordinate `{piece}`")))?,
1646 );
1647 }
1648 }
1649 Ok(Some(coords))
1650}
1651
1652fn parse_expr_until(stmt: &Stmt<'_>, terminators: &[&str]) -> Result<ClassicalExpr> {
1659 let mut source = String::new();
1660 while let Some(token) = stmt.peek() {
1661 if terminators.contains(&token) {
1662 break;
1663 }
1664 stmt.advance();
1665 source.push_str(token);
1666 source.push(' ');
1667 }
1668 parse_expr(stmt, &source)
1669}
1670
1671#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1672enum ExprToken {
1673 In(u32),
1674 Const(bool),
1675 Not,
1676 Xor,
1677 And,
1678 Or,
1679 XorList,
1680 AndList,
1681 OrList,
1682 Select,
1683 Parity,
1684 Comma,
1685 Open,
1686 Close,
1687}
1688
1689fn lex_expr(stmt: &Stmt<'_>, source: &str) -> Result<Vec<ExprToken>> {
1690 let mut tokens = Vec::new();
1691 let bytes = source.as_bytes();
1692 let mut index = 0;
1693 while index < bytes.len() {
1694 let rest = &source[index..];
1695 let (token, width) = match bytes[index] {
1696 b' ' => {
1697 index += 1;
1698 continue;
1699 }
1700 b'!' => (ExprToken::Not, 1),
1701 b'^' => (ExprToken::Xor, 1),
1702 b'&' => (ExprToken::And, 1),
1703 b'|' => (ExprToken::Or, 1),
1704 b',' => (ExprToken::Comma, 1),
1705 b'x' if rest.starts_with("xor") => (ExprToken::XorList, 3),
1706 b'a' if rest.starts_with("and") => (ExprToken::AndList, 3),
1707 b'o' if rest.starts_with("or") => (ExprToken::OrList, 2),
1708 b's' if rest.starts_with("select") => (ExprToken::Select, 6),
1709 b'p' if rest.starts_with("parity") => (ExprToken::Parity, 6),
1710 b'(' => (ExprToken::Open, 1),
1711 b')' => (ExprToken::Close, 1),
1712 b'0' => (ExprToken::Const(false), 1),
1713 b'1' => (ExprToken::Const(true), 1),
1714 _ => {
1715 let digits = rest
1716 .strip_prefix("in")
1717 .map(|after| after.bytes().take_while(u8::is_ascii_digit).count())
1718 .filter(|&len| len > 0)
1719 .ok_or_else(|| stmt.err(format!("invalid expression at `{rest}`")))?;
1720 let slot = rest[2..2 + digits]
1721 .parse()
1722 .map_err(|_| stmt.err(format!("invalid input slot in `{rest}`")))?;
1723 (ExprToken::In(slot), 2 + digits)
1724 }
1725 };
1726 tokens.push(token);
1727 index += width;
1728 }
1729 Ok(tokens)
1730}
1731
1732struct ExprParser<'s, 'a> {
1735 stmt: &'s Stmt<'a>,
1736 tokens: Vec<ExprToken>,
1737 next: usize,
1738}
1739
1740fn parse_expr(stmt: &Stmt<'_>, source: &str) -> Result<ClassicalExpr> {
1741 let mut parser = ExprParser {
1742 stmt,
1743 tokens: lex_expr(stmt, source)?,
1744 next: 0,
1745 };
1746 let expr = parser.parse_or()?;
1747 if parser.next != parser.tokens.len() {
1748 return Err(stmt.err("trailing tokens in expression"));
1749 }
1750 Ok(expr)
1751}
1752
1753impl ExprParser<'_, '_> {
1754 fn peek(&self) -> Option<ExprToken> {
1755 self.tokens.get(self.next).copied()
1756 }
1757
1758 fn parse_or(&mut self) -> Result<ClassicalExpr> {
1759 self.parse_binop(ExprToken::Or, ClassicalExpr::Or, Self::parse_xor)
1760 }
1761
1762 fn parse_xor(&mut self) -> Result<ClassicalExpr> {
1763 self.parse_binop(ExprToken::Xor, ClassicalExpr::Xor, Self::parse_and)
1764 }
1765
1766 fn parse_and(&mut self) -> Result<ClassicalExpr> {
1767 self.parse_binop(ExprToken::And, ClassicalExpr::And, Self::parse_not)
1768 }
1769
1770 fn parse_binop(
1772 &mut self,
1773 token: ExprToken,
1774 build: fn(Box<[ClassicalExpr]>) -> ClassicalExpr,
1775 next: fn(&mut Self) -> Result<ClassicalExpr>,
1776 ) -> Result<ClassicalExpr> {
1777 let first = next(self)?;
1778 if self.peek() != Some(token) {
1779 return Ok(first);
1780 }
1781 let mut operands = vec![first];
1782 while self.peek() == Some(token) {
1783 self.next += 1;
1784 operands.push(next(self)?);
1785 }
1786 Ok(build(operands.into_boxed_slice()))
1787 }
1788
1789 fn parse_not(&mut self) -> Result<ClassicalExpr> {
1790 if self.peek() == Some(ExprToken::Not) {
1791 self.next += 1;
1792 return Ok(ClassicalExpr::Not(Box::new(self.parse_not()?)));
1793 }
1794 self.parse_atom()
1795 }
1796
1797 fn parse_atom(&mut self) -> Result<ClassicalExpr> {
1798 let token = self
1799 .peek()
1800 .ok_or_else(|| self.stmt.err("expression ended unexpectedly"))?;
1801 self.next += 1;
1802 match token {
1803 ExprToken::In(slot) => Ok(ClassicalExpr::In(slot)),
1804 ExprToken::Const(bit) => Ok(ClassicalExpr::Const(bit)),
1805 ExprToken::XorList
1806 | ExprToken::AndList
1807 | ExprToken::OrList
1808 | ExprToken::Select
1809 | ExprToken::Parity => {
1810 if self.peek() != Some(ExprToken::Open) {
1811 return Err(self.stmt.err("expected `(` after expression function"));
1812 }
1813 self.next += 1;
1814 let mut operands = Vec::new();
1815 if self.peek() != Some(ExprToken::Close) {
1816 loop {
1817 operands.push(self.parse_or()?);
1818 if self.peek() != Some(ExprToken::Comma) {
1819 break;
1820 }
1821 self.next += 1;
1822 }
1823 }
1824 if self.peek() != Some(ExprToken::Close) {
1825 return Err(self.stmt.err("unclosed expression function"));
1826 }
1827 self.next += 1;
1828 Ok(match token {
1829 ExprToken::XorList => ClassicalExpr::Xor(operands.into_boxed_slice()),
1830 ExprToken::AndList => ClassicalExpr::And(operands.into_boxed_slice()),
1831 ExprToken::OrList => ClassicalExpr::Or(operands.into_boxed_slice()),
1832 ExprToken::Parity => {
1833 let mut operands = operands.into_iter();
1834 let Some(ClassicalExpr::Const(constant)) = operands.next() else {
1835 return Err(self
1836 .stmt
1837 .err("parity expects a constant followed by input slots"));
1838 };
1839 let inputs = operands
1840 .map(|operand| match operand {
1841 ClassicalExpr::In(slot) => Ok(slot),
1842 _ => Err(self.stmt.err("parity operands must be input slots")),
1843 })
1844 .collect::<Result<Box<[_]>>>()?;
1845 ClassicalExpr::Parity { inputs, constant }
1846 }
1847 _ => ClassicalExpr::Select(Box::new(operands.try_into().map_err(|_| {
1848 self.stmt
1849 .err("select expects condition, when_false, when_true")
1850 })?)),
1851 })
1852 }
1853 ExprToken::Open => {
1854 let expr = self.parse_or()?;
1855 if self.peek() != Some(ExprToken::Close) {
1856 return Err(self.stmt.err("unclosed `(` in expression"));
1857 }
1858 self.next += 1;
1859 Ok(expr)
1860 }
1861 _ => Err(self.stmt.err("expected an expression atom")),
1862 }
1863 }
1864}