1use std::fmt;
12
13use super::instruction::{
14 ArrayInstruction, AtomArrangementInstruction, CpuInstruction, DetectorObservableInstruction,
15 Instruction, LaneConstInstruction, MeasurementInstruction, QuantumGateInstruction,
16};
17use super::program::Program;
18use crate::version::Version;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum ParseError {
22 MissingVersion,
23 InvalidVersion(String),
24 UnknownMnemonic { line: usize, mnemonic: String },
25 MissingOperand { line: usize, mnemonic: String },
26 InvalidOperand { line: usize, message: String },
27}
28
29impl fmt::Display for ParseError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 ParseError::MissingVersion => write!(f, "missing .version directive"),
33 ParseError::InvalidVersion(s) => write!(f, "invalid version: {}", s),
34 ParseError::UnknownMnemonic { line, mnemonic } => {
35 write!(f, "line {}: unknown mnemonic '{}'", line, mnemonic)
36 }
37 ParseError::MissingOperand { line, mnemonic } => {
38 write!(f, "line {}: missing operand for '{}'", line, mnemonic)
39 }
40 ParseError::InvalidOperand { line, message } => {
41 write!(f, "line {}: {}", line, message)
42 }
43 }
44 }
45}
46
47impl std::error::Error for ParseError {}
48
49pub fn parse(source: &str) -> Result<Program, ParseError> {
51 let mut version: Option<Version> = None;
52 let mut instructions = Vec::new();
53
54 for (line_idx, raw_line) in source.lines().enumerate() {
55 let line_num = line_idx + 1;
56
57 let line = match raw_line.find(';') {
59 Some(pos) => &raw_line[..pos],
60 None => raw_line,
61 };
62 let line = line.trim();
63
64 if line.is_empty() {
65 continue;
66 }
67
68 if let Some(rest) = line.strip_prefix('.') {
70 let mut parts = rest.split_whitespace();
71 let directive = parts.next().unwrap_or("");
72 if directive == "version" {
73 let val = parts
74 .next()
75 .ok_or(ParseError::InvalidVersion("missing value".to_string()))?;
76 version = Some(parse_version(val)?);
77 }
78 continue;
79 }
80
81 let mut parts = line.split_whitespace();
83 let mnemonic = parts.next().unwrap();
84 let instr = parse_instruction(mnemonic, &mut parts, line_num)?;
85 instructions.push(instr);
86 }
87
88 let version = version.ok_or(ParseError::MissingVersion)?;
89 Ok(Program {
90 version,
91 instructions,
92 })
93}
94
95fn parse_instruction<'a>(
96 mnemonic: &str,
97 operands: &mut impl Iterator<Item = &'a str>,
98 line: usize,
99) -> Result<Instruction, ParseError> {
100 match mnemonic {
101 "const_float" => {
102 let val = require_operand(operands, line, mnemonic)?;
103 let f: f64 =
104 val.parse()
105 .map_err(|e: std::num::ParseFloatError| ParseError::InvalidOperand {
106 line,
107 message: e.to_string(),
108 })?;
109 Ok(Instruction::Cpu(CpuInstruction::ConstFloat(f)))
110 }
111 "const_int" => {
112 let val = require_operand(operands, line, mnemonic)?;
113 let n = parse_i64(val, line)?;
114 Ok(Instruction::Cpu(CpuInstruction::ConstInt(n)))
115 }
116 "const_loc" => {
117 let val = require_operand(operands, line, mnemonic)?;
118 let n = parse_hex_u32(val, line)?;
119 Ok(Instruction::LaneConst(LaneConstInstruction::ConstLoc(n)))
120 }
121 "const_lane" => {
122 let val = require_operand(operands, line, mnemonic)?;
123 let n = parse_hex_u64(val, line)?;
124 let data0 = n as u32;
125 let data1 = (n >> 32) as u32;
126 Ok(Instruction::LaneConst(LaneConstInstruction::ConstLane(
127 data0, data1,
128 )))
129 }
130 "const_zone" => {
131 let val = require_operand(operands, line, mnemonic)?;
132 let n = parse_hex_u32(val, line)?;
133 Ok(Instruction::LaneConst(LaneConstInstruction::ConstZone(n)))
134 }
135 "pop" => Ok(Instruction::Cpu(CpuInstruction::Pop)),
136 "dup" => Ok(Instruction::Cpu(CpuInstruction::Dup)),
137 "swap" => Ok(Instruction::Cpu(CpuInstruction::Swap)),
138 "initial_fill" => {
139 let val = require_operand(operands, line, mnemonic)?;
140 let arity = parse_u32(val, line)?;
141 Ok(Instruction::AtomArrangement(
142 AtomArrangementInstruction::InitialFill { arity },
143 ))
144 }
145 "fill" => {
146 let val = require_operand(operands, line, mnemonic)?;
147 let arity = parse_u32(val, line)?;
148 Ok(Instruction::AtomArrangement(
149 AtomArrangementInstruction::Fill { arity },
150 ))
151 }
152 "move" => {
153 let val = require_operand(operands, line, mnemonic)?;
154 let arity = parse_u32(val, line)?;
155 Ok(Instruction::AtomArrangement(
156 AtomArrangementInstruction::Move { arity },
157 ))
158 }
159 "local_r" => {
160 let val = require_operand(operands, line, mnemonic)?;
161 let arity = parse_u32(val, line)?;
162 Ok(Instruction::QuantumGate(QuantumGateInstruction::LocalR {
163 arity,
164 }))
165 }
166 "local_rz" => {
167 let val = require_operand(operands, line, mnemonic)?;
168 let arity = parse_u32(val, line)?;
169 Ok(Instruction::QuantumGate(QuantumGateInstruction::LocalRz {
170 arity,
171 }))
172 }
173 "global_r" => Ok(Instruction::QuantumGate(QuantumGateInstruction::GlobalR)),
174 "global_rz" => Ok(Instruction::QuantumGate(QuantumGateInstruction::GlobalRz)),
175 "cz" => Ok(Instruction::QuantumGate(QuantumGateInstruction::CZ)),
176 "measure" => {
177 let val = require_operand(operands, line, mnemonic)?;
178 let arity = parse_u32(val, line)?;
179 Ok(Instruction::Measurement(MeasurementInstruction::Measure {
180 arity,
181 }))
182 }
183 "await_measure" => Ok(Instruction::Measurement(
184 MeasurementInstruction::AwaitMeasure,
185 )),
186 "new_array" => {
187 let type_str = require_operand(operands, line, mnemonic)?;
188 let type_tag = parse_u8(type_str, line)?;
189 let dim0_str = require_operand(operands, line, "new_array dim0")?;
190 let dim0 = parse_u16(dim0_str, line)?;
191 let dim1 = match operands.next() {
192 Some(s) => parse_u16(s, line)?,
193 None => 0,
194 };
195 Ok(Instruction::Array(ArrayInstruction::NewArray {
196 type_tag,
197 dim0,
198 dim1,
199 }))
200 }
201 "get_item" => {
202 let val = require_operand(operands, line, mnemonic)?;
203 let ndims = parse_u16(val, line)?;
204 Ok(Instruction::Array(ArrayInstruction::GetItem { ndims }))
205 }
206 "set_detector" => Ok(Instruction::DetectorObservable(
207 DetectorObservableInstruction::SetDetector,
208 )),
209 "set_observable" => Ok(Instruction::DetectorObservable(
210 DetectorObservableInstruction::SetObservable,
211 )),
212 "return" => Ok(Instruction::Cpu(CpuInstruction::Return)),
213 "halt" => Ok(Instruction::Cpu(CpuInstruction::Halt)),
214 _ => Err(ParseError::UnknownMnemonic {
215 line,
216 mnemonic: mnemonic.to_string(),
217 }),
218 }
219}
220
221fn require_operand<'a>(
222 operands: &mut impl Iterator<Item = &'a str>,
223 line: usize,
224 mnemonic: &str,
225) -> Result<&'a str, ParseError> {
226 operands.next().ok_or(ParseError::MissingOperand {
227 line,
228 mnemonic: mnemonic.to_string(),
229 })
230}
231
232fn parse_hex_u32(s: &str, line: usize) -> Result<u32, ParseError> {
233 let s = s
234 .strip_prefix("0x")
235 .or_else(|| s.strip_prefix("0X"))
236 .unwrap_or(s);
237 u32::from_str_radix(s, 16).map_err(|e| ParseError::InvalidOperand {
238 line,
239 message: format!("invalid hex u32: {}", e),
240 })
241}
242
243fn parse_i64(s: &str, line: usize) -> Result<i64, ParseError> {
244 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
245 u64::from_str_radix(hex, 16).map(|v| v as i64)
246 } else {
247 s.parse::<i64>()
248 }
249 .map_err(|e| ParseError::InvalidOperand {
250 line,
251 message: format!("invalid i64: {}", e),
252 })
253}
254
255fn parse_hex_u64(s: &str, line: usize) -> Result<u64, ParseError> {
256 let s = s
257 .strip_prefix("0x")
258 .or_else(|| s.strip_prefix("0X"))
259 .unwrap_or(s);
260 u64::from_str_radix(s, 16).map_err(|e| ParseError::InvalidOperand {
261 line,
262 message: format!("invalid hex u64: {}", e),
263 })
264}
265
266fn parse_u16(s: &str, line: usize) -> Result<u16, ParseError> {
267 s.parse::<u16>().map_err(|e| ParseError::InvalidOperand {
268 line,
269 message: format!("invalid u16: {}", e),
270 })
271}
272
273fn parse_u32(s: &str, line: usize) -> Result<u32, ParseError> {
274 s.parse::<u32>().map_err(|e| ParseError::InvalidOperand {
275 line,
276 message: format!("invalid u32: {}", e),
277 })
278}
279
280fn parse_version(s: &str) -> Result<Version, ParseError> {
282 if let Some((major_str, minor_str)) = s.split_once('.') {
283 let major = major_str
284 .parse::<u16>()
285 .map_err(|e| ParseError::InvalidVersion(e.to_string()))?;
286 let minor = minor_str
287 .parse::<u16>()
288 .map_err(|e| ParseError::InvalidVersion(e.to_string()))?;
289 Ok(Version::new(major, minor))
290 } else {
291 let major = s
292 .parse::<u16>()
293 .map_err(|e| ParseError::InvalidVersion(e.to_string()))?;
294 Ok(Version::new(major, 0))
295 }
296}
297
298fn parse_u8(s: &str, line: usize) -> Result<u8, ParseError> {
299 s.parse::<u8>().map_err(|e| ParseError::InvalidOperand {
300 line,
301 message: format!("invalid u8: {}", e),
302 })
303}
304
305pub fn print(program: &Program) -> String {
307 let mut out = format!(
308 ".version {}.{}\n",
309 program.version.major, program.version.minor
310 );
311
312 for instr in &program.instructions {
313 out.push_str(&print_instruction(instr));
314 out.push('\n');
315 }
316
317 out
318}
319
320fn print_instruction(instr: &Instruction) -> String {
321 match instr {
322 Instruction::Cpu(cpu) => match cpu {
323 CpuInstruction::ConstFloat(f) => format!("const_float {}", format_float(*f)),
324 CpuInstruction::ConstInt(n) => format!("const_int {}", n),
325 CpuInstruction::Pop => "pop".to_string(),
326 CpuInstruction::Dup => "dup".to_string(),
327 CpuInstruction::Swap => "swap".to_string(),
328 CpuInstruction::Return => "return".to_string(),
329 CpuInstruction::Halt => "halt".to_string(),
330 },
331 Instruction::LaneConst(lc) => match lc {
332 LaneConstInstruction::ConstLoc(v) => format!("const_loc 0x{:08x}", v),
333 LaneConstInstruction::ConstLane(d0, d1) => {
334 let combined = (*d0 as u64) | ((*d1 as u64) << 32);
335 format!("const_lane 0x{:016x}", combined)
336 }
337 LaneConstInstruction::ConstZone(v) => format!("const_zone 0x{:08x}", v),
338 },
339 Instruction::AtomArrangement(aa) => match aa {
340 AtomArrangementInstruction::InitialFill { arity } => {
341 format!("initial_fill {}", arity)
342 }
343 AtomArrangementInstruction::Fill { arity } => format!("fill {}", arity),
344 AtomArrangementInstruction::Move { arity } => format!("move {}", arity),
345 },
346 Instruction::QuantumGate(qg) => match qg {
347 QuantumGateInstruction::LocalR { arity } => format!("local_r {}", arity),
348 QuantumGateInstruction::LocalRz { arity } => format!("local_rz {}", arity),
349 QuantumGateInstruction::GlobalR => "global_r".to_string(),
350 QuantumGateInstruction::GlobalRz => "global_rz".to_string(),
351 QuantumGateInstruction::CZ => "cz".to_string(),
352 },
353 Instruction::Measurement(m) => match m {
354 MeasurementInstruction::Measure { arity } => format!("measure {}", arity),
355 MeasurementInstruction::AwaitMeasure => "await_measure".to_string(),
356 },
357 Instruction::Array(arr) => match arr {
358 ArrayInstruction::NewArray {
359 type_tag,
360 dim0,
361 dim1,
362 } => {
363 if *dim1 == 0 {
364 format!("new_array {} {}", type_tag, dim0)
365 } else {
366 format!("new_array {} {} {}", type_tag, dim0, dim1)
367 }
368 }
369 ArrayInstruction::GetItem { ndims } => format!("get_item {}", ndims),
370 },
371 Instruction::DetectorObservable(dob) => match dob {
372 DetectorObservableInstruction::SetDetector => "set_detector".to_string(),
373 DetectorObservableInstruction::SetObservable => "set_observable".to_string(),
374 },
375 }
376}
377
378fn format_float(f: f64) -> String {
380 let s = format!("{}", f);
381 if s.contains('.') || s.contains('e') || s.contains('E') {
382 s
383 } else {
384 format!("{}.0", s)
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_parse_minimal_program() {
394 let source = ".version 1\nhalt\n";
395 let program = parse(source).unwrap();
396 assert_eq!(program.version, Version::new(1, 0));
397 assert_eq!(program.instructions.len(), 1);
398 assert_eq!(
399 program.instructions[0],
400 Instruction::Cpu(CpuInstruction::Halt)
401 );
402 }
403
404 #[test]
405 fn test_parse_with_comments_and_blanks() {
406 let source = r#"
407; This is a comment
408.version 2
409
410const_int 42 ; inline comment
411halt
412"#;
413 let program = parse(source).unwrap();
414 assert_eq!(program.version, Version::new(2, 0));
415 assert_eq!(program.instructions.len(), 2);
416 assert_eq!(
417 program.instructions[0],
418 Instruction::Cpu(CpuInstruction::ConstInt(42))
419 );
420 }
421
422 #[test]
423 fn test_parse_all_mnemonics() {
424 let source = r#".version 1
425const_float 1.5
426const_int 42
427const_loc 0x00010002
428const_lane 0x0000000080010200
429const_zone 0x00000003
430pop
431dup
432swap
433initial_fill 3
434fill 2
435move 1
436local_r 4
437local_rz 2
438global_r
439global_rz
440cz
441measure 1
442await_measure
443new_array 2 10 20
444get_item 2
445set_detector
446set_observable
447return
448halt
449"#;
450 let program = parse(source).unwrap();
451 assert_eq!(program.instructions.len(), 24);
452 }
453
454 #[test]
455 fn test_parse_new_array_1d() {
456 let source = ".version 1\nnew_array 1 10\n";
457 let program = parse(source).unwrap();
458 assert_eq!(
459 program.instructions[0],
460 Instruction::Array(ArrayInstruction::NewArray {
461 type_tag: 1,
462 dim0: 10,
463 dim1: 0,
464 })
465 );
466 }
467
468 #[test]
469 fn test_print_round_trip() {
470 let program = Program {
471 version: Version::new(1, 0),
472 instructions: vec![
473 Instruction::Cpu(CpuInstruction::ConstFloat(1.5)),
474 Instruction::Cpu(CpuInstruction::ConstInt(42)),
475 Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x00010002)),
476 Instruction::LaneConst(LaneConstInstruction::ConstLane(0x80010200, 0x00000000)),
477 Instruction::LaneConst(LaneConstInstruction::ConstZone(3)),
478 Instruction::Cpu(CpuInstruction::Pop),
479 Instruction::Cpu(CpuInstruction::Dup),
480 Instruction::Cpu(CpuInstruction::Swap),
481 Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 3 }),
482 Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 2 }),
483 Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 1 }),
484 Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity: 4 }),
485 Instruction::QuantumGate(QuantumGateInstruction::LocalRz { arity: 2 }),
486 Instruction::QuantumGate(QuantumGateInstruction::GlobalR),
487 Instruction::QuantumGate(QuantumGateInstruction::GlobalRz),
488 Instruction::QuantumGate(QuantumGateInstruction::CZ),
489 Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
490 Instruction::Measurement(MeasurementInstruction::AwaitMeasure),
491 Instruction::Array(ArrayInstruction::NewArray {
492 type_tag: 2,
493 dim0: 10,
494 dim1: 20,
495 }),
496 Instruction::Array(ArrayInstruction::GetItem { ndims: 2 }),
497 Instruction::DetectorObservable(DetectorObservableInstruction::SetDetector),
498 Instruction::DetectorObservable(DetectorObservableInstruction::SetObservable),
499 Instruction::Cpu(CpuInstruction::Return),
500 Instruction::Cpu(CpuInstruction::Halt),
501 ],
502 };
503
504 let text = print(&program);
505 let parsed = parse(&text).unwrap();
506 assert_eq!(program, parsed);
507 }
508
509 #[test]
510 fn test_text_binary_round_trip() {
511 let source = r#".version 1
512const_loc 0x00000000
513const_loc 0x00000001
514initial_fill 2
515const_lane 0x0000000000000100
516move 1
517const_float 1.5708
518global_rz
519const_zone 0x00000000
520cz
521measure 1
522await_measure
523return
524"#;
525 let program = parse(source).unwrap();
526 let binary = program.to_binary();
527 let from_binary = Program::from_binary(&binary).unwrap();
528 let text_again = print(&from_binary);
529 let reparsed = parse(&text_again).unwrap();
530 assert_eq!(program, reparsed);
531 }
532
533 #[test]
534 fn test_missing_version() {
535 let source = "halt\n";
536 assert_eq!(parse(source), Err(ParseError::MissingVersion));
537 }
538
539 #[test]
540 fn test_unknown_mnemonic() {
541 let source = ".version 1\nfoobar\n";
542 assert!(matches!(
543 parse(source),
544 Err(ParseError::UnknownMnemonic { line: 2, .. })
545 ));
546 }
547
548 #[test]
549 fn test_missing_operand() {
550 let source = ".version 1\nconst_int\n";
551 assert!(matches!(
552 parse(source),
553 Err(ParseError::MissingOperand { line: 2, .. })
554 ));
555 }
556
557 #[test]
558 fn test_invalid_operand() {
559 let source = ".version 1\nconst_int abc\n";
560 assert!(matches!(
561 parse(source),
562 Err(ParseError::InvalidOperand { line: 2, .. })
563 ));
564 }
565
566 #[test]
567 fn test_print_new_array_1d() {
568 let program = Program {
569 version: Version::new(1, 0),
570 instructions: vec![Instruction::Array(ArrayInstruction::NewArray {
571 type_tag: 1,
572 dim0: 5,
573 dim1: 0,
574 })],
575 };
576 let text = print(&program);
577 assert!(text.contains("new_array 1 5\n"));
578 assert!(!text.contains("new_array 1 5 0"));
579 }
580
581 #[test]
582 fn test_print_new_array_2d() {
583 let program = Program {
584 version: Version::new(1, 0),
585 instructions: vec![Instruction::Array(ArrayInstruction::NewArray {
586 type_tag: 2,
587 dim0: 10,
588 dim1: 20,
589 })],
590 };
591 let text = print(&program);
592 assert!(text.contains("new_array 2 10 20"));
593 }
594
595 #[test]
596 fn test_design_doc_example() {
597 let source = r#".version 1
598
599const_loc 0x00000000 ; word 0, site 0
600const_loc 0x00000001 ; word 0, site 1
601initial_fill 2
602const_lane 0x0000000000010000 ; fwd, site_bus, word 0, site 1, bus 0
603move 1
604const_loc 0x00000000 ; word 0, site 0
605const_float 1.5708 ; axis_angle
606const_float 3.14159 ; rotation_angle
607local_r 1
608const_float 0.7854 ; rotation_angle
609global_rz
610const_zone 0x00000000
611cz
612const_zone 0x00000000
613measure 1
614await_measure
615return
616"#;
617 let program = parse(source).unwrap();
618 assert_eq!(program.version, Version::new(1, 0));
619 assert_eq!(program.instructions.len(), 17);
620 }
621
622 #[test]
623 fn test_parse_version_major_minor() {
624 let source = ".version 2.3\nhalt\n";
625 let program = parse(source).unwrap();
626 assert_eq!(program.version, Version::new(2, 3));
627 }
628
629 #[test]
630 fn test_print_version_format() {
631 let program = Program {
632 version: Version::new(2, 3),
633 instructions: vec![Instruction::Cpu(CpuInstruction::Halt)],
634 };
635 let text = print(&program);
636 assert!(text.starts_with(".version 2.3\n"));
637 }
638
639 #[test]
640 fn test_const_int_negative() {
641 let source = ".version 1\nconst_int -42\nhalt\n";
642 let program = parse(source).unwrap();
643 assert_eq!(
644 program.instructions[0],
645 Instruction::Cpu(CpuInstruction::ConstInt(-42))
646 );
647 let text = print(&program);
648 assert!(text.contains("const_int -42"));
649 }
650}