···2121 - `error`: crash on overflow/underflow
22222323## better diagnostics
2424-- [ ] **errors**: switch to `miette` for richer error display with source snippets
2424+- [x] **errors**: switch to `miette` for richer error display with source snippets
2525- [x] **tracing**: cli flag to print state of vm/tape after every instruction
26262727## visualization
+176-100
src/evaluator.rs
···11use crate::lexer::Token;
22use clap::ValueEnum;
33+use miette::{Diagnostic, NamedSource, SourceSpan};
34use std::fmt;
45use std::io::Write;
66+use std::sync::Arc;
57use std::thread;
68use std::time;
7988-#[derive(Debug)]
1010+#[derive(Debug, Diagnostic)]
911pub enum RuntimeError {
1010- DataPointerOutsideTapeLength(usize, usize, usize),
1111- DataPointerNegative(usize, usize),
1212- CellUnderflow(usize, usize),
1313- CellOverflow(usize, usize),
1414- InvalidToken(u8, usize, usize),
1515- IoReadError(std::io::Error),
1616- IoWriteError(std::io::Error),
1212+ #[diagnostic(
1313+ code(tachyon::runtime::data_pointer_oob_right),
1414+ help("remove the '>' instruction that triggers this")
1515+ )]
1616+ DataPointerOutsideTapeLength {
1717+ #[source_code]
1818+ src: NamedSource<Arc<str>>,
1919+ #[label("offending instruction here")]
2020+ span: SourceSpan,
2121+ },
2222+2323+ #[diagnostic(
2424+ code(tachyon::runtime::data_pointer_oob_left),
2525+ help("remove the '<' that triggers this")
2626+ )]
2727+ DataPointerNegative {
2828+ #[source_code]
2929+ src: NamedSource<Arc<str>>,
3030+ #[label("offending instruction here")]
3131+ span: SourceSpan,
3232+ },
3333+3434+ #[diagnostic(
3535+ code(tachyon::runtime::cell_underflow),
3636+ help("remove the '-' that triggers this")
3737+ )]
3838+ CellUnderflow {
3939+ #[source_code]
4040+ src: NamedSource<Arc<str>>,
4141+ #[label("offending instruction here")]
4242+ span: SourceSpan,
4343+ },
4444+4545+ #[diagnostic(
4646+ code(tachyon::runtime::cell_overflow),
4747+ help("remove the '+' that triggers this")
4848+ )]
4949+ CellOverflow {
5050+ #[source_code]
5151+ src: NamedSource<Arc<str>>,
5252+ #[label("offending instruction here")]
5353+ span: SourceSpan,
5454+ },
5555+5656+ #[diagnostic(
5757+ code(tachyon::runtime::invalid_token),
5858+ help("this is a bug with the tokenizer. please report it!"),
5959+ url("https://tangled.org/digi.rip/tachyon/issues")
6060+ )]
6161+ InvalidToken {
6262+ #[source_code]
6363+ src: NamedSource<Arc<str>>,
6464+ #[label("offending token here")]
6565+ span: SourceSpan,
6666+ },
6767+6868+ #[diagnostic(
6969+ code(tachyon::runtime::io_read),
7070+ help("check that stdin is available and not closed")
7171+ )]
7272+ IoReadError { err: std::io::Error },
7373+7474+ #[diagnostic(
7575+ code(tachyon::runtime::io_write),
7676+ help("check that stdout is available and not closed")
7777+ )]
7878+ IoWriteError { err: std::io::Error },
1779}
18801981impl fmt::Display for RuntimeError {
2082 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2183 match self {
2222- RuntimeError::DataPointerOutsideTapeLength(tape_length, line, col) => {
2323- write!(
2424- f,
2525- "Data pointer moved past position {} (end of tape) at line {}, col {}",
2626- tape_length, line, col
2727- )
8484+ RuntimeError::DataPointerOutsideTapeLength { .. } => {
8585+ write!(f, "data pointer moved past end of tape")
2886 }
2929- RuntimeError::DataPointerNegative(line, col) => {
3030- write!(
3131- f,
3232- "Data pointer went left of position 0 at line {}, col {}",
3333- line, col
3434- )
8787+ RuntimeError::DataPointerNegative { .. } => {
8888+ write!(f, "data pointer moved past start of tape")
3589 }
3636- RuntimeError::CellUnderflow(line, col) => {
3737- write!(f, "Cell value went below 0 at line {}, col {}", line, col)
9090+ RuntimeError::CellUnderflow { .. } => {
9191+ write!(f, "cell value went below 0")
3892 }
3939- RuntimeError::CellOverflow(line, col) => {
4040- write!(f, "Cell value exceeded 255 at line {}, col {}", line, col)
9393+ RuntimeError::CellOverflow { .. } => {
9494+ write!(f, "cell value went over 255")
4195 }
4242- RuntimeError::InvalidToken(token, line, col) => {
4343- write!(
4444- f,
4545- "Invalid token {} at line {}, col {}",
4646- char::from(*token),
4747- line,
4848- col
4949- )
9696+ RuntimeError::InvalidToken { .. } => {
9797+ write!(f, "invalid token found")
5098 }
5151- RuntimeError::IoReadError(err) => {
5252- write!(f, "Failed while reading stdin: {}", err)
9999+ RuntimeError::IoReadError { err, .. } => {
100100+ write!(f, "failed to read input: {}", err)
53101 }
5454- RuntimeError::IoWriteError(err) => {
5555- write!(f, "Failed while writing stdout: {}", err)
102102+ RuntimeError::IoWriteError { err, .. } => {
103103+ write!(f, "failed to write output: {}", err)
56104 }
57105 }
58106 }
59107}
601086161-impl std::error::Error for RuntimeError {
6262- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6363- match self {
6464- RuntimeError::IoReadError(e) | RuntimeError::IoWriteError(e) => Some(e),
6565- _ => None,
6666- }
6767- }
6868-}
109109+impl std::error::Error for RuntimeError {}
6911070111#[derive(Clone, ValueEnum, Debug, PartialEq)]
71112pub enum OOBBehavior {
···80121}
8112282123pub struct VM {
124124+ filename: Arc<str>,
125125+ source: Arc<str>,
83126 tape: Vec<u8>,
84127 data_pointer: usize,
85128 inst_pointer: usize,
···92135}
9313694137pub struct VMBuilder {
138138+ filename: Arc<str>,
139139+ source: Arc<str>,
95140 tape: Vec<u8>,
96141 data_oob: OOBBehavior,
97142 cell_oob: OOBBehavior,
···104149impl VMBuilder {
105150 fn new() -> Self {
106151 VMBuilder {
152152+ filename: Arc::from(""),
153153+ source: Arc::from(""),
107154 tape: vec![0; 30_000],
108155 data_oob: OOBBehavior::Error,
109156 cell_oob: OOBBehavior::Wrap,
···114161 }
115162 }
116163164164+ pub fn source(mut self, filename: String, source: String) -> Self {
165165+ self.filename = Arc::from(filename.as_str());
166166+ self.source = Arc::from(source.as_str());
167167+ self
168168+ }
169169+117170 pub fn tape(mut self, length: usize) -> Self {
118171 self.tape = vec![0; length];
119172 self
···151204152205 pub fn build(self) -> VM {
153206 VM {
207207+ filename: self.filename,
208208+ source: self.source,
154209 tape: self.tape,
155210 data_pointer: 0,
156211 inst_pointer: 0,
···172227 fn move_right(&mut self, current: &Token) -> Result<(), RuntimeError> {
173228 if self.data_pointer + 1 >= self.tape.len() {
174229 if self.data_oob == OOBBehavior::Error {
175175- return Err(RuntimeError::DataPointerOutsideTapeLength(
176176- self.tape.len(),
177177- current.line,
178178- current.col,
179179- ));
230230+ return Err(RuntimeError::DataPointerOutsideTapeLength {
231231+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
232232+ span: (current.offset, 1).into(),
233233+ });
180234 } else if self.data_oob == OOBBehavior::Wrap {
181235 self.data_pointer = 0;
182236 }
···190244 fn move_left(&mut self, current: &Token) -> Result<(), RuntimeError> {
191245 if self.data_pointer == 0 {
192246 if self.data_oob == OOBBehavior::Error {
193193- return Err(RuntimeError::DataPointerNegative(current.line, current.col));
247247+ return Err(RuntimeError::DataPointerNegative {
248248+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
249249+ span: (current.offset, 1).into(),
250250+ });
194251 } else if self.data_oob == OOBBehavior::Wrap {
195252 self.data_pointer = self.tape.len() - 1;
196253 }
···208265 self.tape[self.data_pointer] = val;
209266 }
210267 None => {
211211- return Err(RuntimeError::CellOverflow(current.line, current.col));
268268+ return Err(RuntimeError::CellOverflow {
269269+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
270270+ span: (current.offset, 1).into(),
271271+ });
212272 }
213273 }
214274 } else if self.cell_oob == OOBBehavior::Wrap {
···225285 self.tape[self.data_pointer] = val;
226286 }
227287 None => {
228228- return Err(RuntimeError::CellUnderflow(current.line, current.col));
288288+ return Err(RuntimeError::CellUnderflow {
289289+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
290290+ span: (current.offset, 1).into(),
291291+ });
229292 }
230293 }
231294 } else if self.cell_oob == OOBBehavior::Wrap {
···240303241304 input
242305 .read_exact(&mut buffer)
243243- .map_err(RuntimeError::IoReadError)?;
306306+ .map_err(|e| RuntimeError::IoReadError { err: e })?;
244307 self.tape[self.data_pointer] = buffer[0];
245308246309 loop {
247310 match input.fill_buf() {
248248- Err(e) => return Err(RuntimeError::IoReadError(e)),
311311+ Err(e) => {
312312+ return Err(RuntimeError::IoReadError { err: e });
313313+ }
249314 Ok([]) => break,
250315 Ok(r) if r[0].is_ascii_alphanumeric() => input.consume(1),
251316 Ok(_) => {
···261326 fn write_ascii<W: std::io::Write>(&mut self, output: &mut W) -> Result<(), RuntimeError> {
262327 output
263328 .write_all(&[self.tape[self.data_pointer]])
264264- .map_err(RuntimeError::IoWriteError)?;
329329+ .map_err(|e| RuntimeError::IoWriteError { err: e })?;
265330266331 Ok(())
267332 }
···280345 Ok(0) => break, // eof
281346 Ok(_) if single[0].is_ascii_digit() => buffer.push(single[0] as char),
282347 Ok(_) => break,
283283- Err(e) => return Err(RuntimeError::IoReadError(e)),
348348+ Err(e) => return Err(RuntimeError::IoReadError { err: e }),
284349 }
285350 }
286351···289354 loop {
290355 match input.fill_buf() {
291356 Err(e) => {
292292- return Err(RuntimeError::IoReadError(e));
357357+ return Err(RuntimeError::IoReadError { err: e });
293358 }
294359 Ok([]) => break,
295360 Ok(r) if r[0].is_ascii_digit() => input.consume(1),
···303368304369 match buffer.parse::<u16>() {
305370 Ok(val) if val > 255 && self.cell_oob == OOBBehavior::Error => {
306306- return Err(RuntimeError::CellOverflow(current.line, current.col));
371371+ return Err(RuntimeError::CellOverflow {
372372+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
373373+ span: (current.offset, 1).into(),
374374+ });
307375 }
308376 Ok(val) => self.tape[self.data_pointer] = val as u8,
309377 Err(_) => self.tape[self.data_pointer] = 0,
···313381 }
314382315383 fn write_raw<W: std::io::Write>(&mut self, output: &mut W) -> Result<(), RuntimeError> {
316316- write!(output, "{} ", self.tape[self.data_pointer]).map_err(RuntimeError::IoWriteError)?;
384384+ write!(output, "{} ", self.tape[self.data_pointer])
385385+ .map_err(|e| RuntimeError::IoWriteError { err: e })?;
317386318387 Ok(())
319388 }
···392461 }
393462 }
394463395395- _ => Err(RuntimeError::InvalidToken(
396396- current.byte,
397397- current.line,
398398- current.col,
399399- ))?,
464464+ _ => Err(RuntimeError::InvalidToken {
465465+ src: NamedSource::new(Arc::clone(&self.filename), Arc::clone(&self.source)),
466466+ span: (current.offset, 1).into(),
467467+ })?,
400468 }
401469402470 if self.tracing {
···420488 fn data_pointer() {
421489 let source = "+>++>+++-<-<-";
422490 let tokens = tokenizer(source);
423423- let map = parse_brackets(&tokens, source).unwrap();
491491+ let map = parse_brackets(&tokens, "test", source).unwrap();
424492 let mut machine = VM::builder().tape(3).build();
425493426494 machine
···435503 fn data_pointer_oob_left() {
436504 let source = "<";
437505 let tokens = tokenizer(source);
438438- let map = parse_brackets(&tokens, source).unwrap();
439439- let mut machine = VM::builder().tape(1).build();
506506+ let map = parse_brackets(&tokens, "test", source).unwrap();
507507+ let mut machine = VM::builder()
508508+ .source("test".to_string(), source.to_string())
509509+ .tape(1)
510510+ .build();
440511 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
441441-442442- eprintln!("{}", result.as_ref().unwrap_err());
443443- assert!(result.is_err());
512512+ eprintln!("{:?}", miette::Report::new(result.unwrap_err()));
444513 }
445514446515 #[test]
447516 fn data_pointer_oob_right() {
448517 let source = ">>>>";
449518 let tokens = tokenizer(source);
450450- let map = parse_brackets(&tokens, source).unwrap();
451451- let mut machine = VM::builder().tape(1).build();
519519+ let map = parse_brackets(&tokens, "test", source).unwrap();
520520+ let mut machine = VM::builder()
521521+ .source("test".to_string(), source.to_string())
522522+ .tape(1)
523523+ .build();
452524 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
453453-454454- eprintln!("{}", result.as_ref().unwrap_err());
455455- assert!(result.is_err());
525525+ eprintln!("{:?}", miette::Report::new(result.unwrap_err()));
456526 }
457527458528 #[test]
459529 fn increment() {
460530 let source = "++++";
461531 let tokens = tokenizer(source);
462462- let map = parse_brackets(&tokens, source).unwrap();
532532+ let map = parse_brackets(&tokens, "test", source).unwrap();
463533 let mut machine = VM::builder().tape(1).build();
464534465535 machine
···472542 fn decrement() {
473543 let source = "++++---";
474544 let tokens = tokenizer(source);
475475- let map = parse_brackets(&tokens, source).unwrap();
545545+ let map = parse_brackets(&tokens, "test", source).unwrap();
476546 let mut machine = VM::builder().tape(1).build();
477547478548 machine
···488558 // from 255 back to 0
489559 let source = "->-+";
490560 let tokens = tokenizer(source);
491491- let map = parse_brackets(&tokens, source).unwrap();
561561+ let map = parse_brackets(&tokens, "test", source).unwrap();
492562 let mut machine = VM::builder().tape(2).build();
493563494564 machine
···502572 fn input() {
503573 let source = ",";
504574 let tokens = tokenizer(source);
505505- let map = parse_brackets(&tokens, source).unwrap();
575575+ let map = parse_brackets(&tokens, "test", source).unwrap();
506576 let mut machine = VM::builder().tape(1).build();
507577 let fake_input = std::io::Cursor::new(b"A");
508578···516586 fn input_overflow() {
517587 let source = ",";
518588 let tokens = tokenizer(source);
519519- let map = parse_brackets(&tokens, source).unwrap();
589589+ let map = parse_brackets(&tokens, "test", source).unwrap();
520590 let mut machine = VM::builder().tape(1).build();
521591 let fake_input = std::io::Cursor::new(b"AB");
522592···530600 fn input_zero() {
531601 let source = ",";
532602 let tokens = tokenizer(source);
533533- let map = parse_brackets(&tokens, source).unwrap();
603603+ let map = parse_brackets(&tokens, "test", source).unwrap();
534604 let mut machine = VM::builder().tape(1).build();
535605 let fake_input = std::io::Cursor::new(b"0");
536606···544614 fn output() {
545615 let source = ",.";
546616 let tokens = tokenizer(source);
547547- let map = parse_brackets(&tokens, source).unwrap();
617617+ let map = parse_brackets(&tokens, "test", source).unwrap();
548618 let mut machine = VM::builder().tape(1).build();
549619 let fake_input = std::io::Cursor::new(b"1");
550620 let mut capture_buffer: Vec<u8> = Vec::new();
···559629 fn looping() {
560630 let source = "+++[>+<-]>";
561631 let tokens = tokenizer(source);
562562- let map = parse_brackets(&tokens, source).unwrap();
632632+ let map = parse_brackets(&tokens, "test", source).unwrap();
563633 let mut machine = VM::builder().tape(3).build();
564634565635 machine
···574644 // making sure loop code isn't skipping
575645 let source = "++[-]>++";
576646 let tokens = tokenizer(source);
577577- let map = parse_brackets(&tokens, source).unwrap();
647647+ let map = parse_brackets(&tokens, "test", source).unwrap();
578648 let mut machine = VM::builder().tape(2).build();
579649580650 machine
···589659 fn hello_world() {
590660 let source = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
591661 let tokens = tokenizer(source);
592592- let map = parse_brackets(&tokens, source).unwrap();
662662+ let map = parse_brackets(&tokens, "test", source).unwrap();
593663 let mut machine = VM::builder().build();
594664 let mut capture_buffer: Vec<u8> = Vec::new();
595665···604674 fn repeat() {
605675 let source = ",>,[<.>-]";
606676 let tokens = tokenizer(source);
607607- let map = parse_brackets(&tokens, source).unwrap();
677677+ let map = parse_brackets(&tokens, "test", source).unwrap();
608678 let mut machine = VM::builder().build();
609679 let mut capture_buffer: Vec<u8> = Vec::new();
610680 let fake_input = std::io::Cursor::new(b"A\nB");
···621691 fn nesting_loops() {
622692 let source = "[[][]]";
623693 let tokens = tokenizer(source);
624624- let map = parse_brackets(&tokens, source).unwrap();
694694+ let map = parse_brackets(&tokens, "test", source).unwrap();
625695 let mut machine = VM::builder().tape(1).build();
626696 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
627697···632702 fn data_pointer_wrap_right() {
633703 let source = ">";
634704 let tokens = tokenizer(source);
635635- let map = parse_brackets(&tokens, source).unwrap();
705705+ let map = parse_brackets(&tokens, "test", source).unwrap();
636706 let mut machine = VM::builder().tape(1).data_oob(OOBBehavior::Wrap).build();
637707 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
638708 assert!(result.is_ok());
···643713 fn data_pointer_wrap_left() {
644714 let source = "<";
645715 let tokens = tokenizer(source);
646646- let map = parse_brackets(&tokens, source).unwrap();
716716+ let map = parse_brackets(&tokens, "test", source).unwrap();
647717 let mut machine = VM::builder().tape(1).data_oob(OOBBehavior::Wrap).build();
648718 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
649719 assert!(result.is_ok());
···654724 fn cell_oob_error_decrement() {
655725 let source = "-+";
656726 let tokens = tokenizer(source);
657657- let map = parse_brackets(&tokens, source).unwrap();
658658- let mut machine = VM::builder().tape(1).cell_oob(OOBBehavior::Error).build();
727727+ let map = parse_brackets(&tokens, "test", source).unwrap();
728728+ let mut machine = VM::builder()
729729+ .source("test".to_string(), source.to_string())
730730+ .tape(1)
731731+ .cell_oob(OOBBehavior::Error)
732732+ .build();
659733 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
660660- eprintln!("{}", result.as_ref().unwrap_err());
661661- assert!(result.is_err());
734734+ eprintln!("{:?}", miette::Report::new(result.unwrap_err()));
662735 }
663736664737 #[test]
665738 fn cell_oob_error_increment() {
666739 let source = &"+".repeat(256);
667740 let tokens = tokenizer(source);
668668- let map = parse_brackets(&tokens, source).unwrap();
669669- let mut machine = VM::builder().tape(1).cell_oob(OOBBehavior::Error).build();
741741+ let map = parse_brackets(&tokens, "test", source).unwrap();
742742+ let mut machine = VM::builder()
743743+ .source("test".to_string(), source.to_string())
744744+ .tape(1)
745745+ .cell_oob(OOBBehavior::Error)
746746+ .build();
670747 let result = machine.run(&tokens, &map, std::io::empty(), std::io::empty());
671671- eprintln!("{}", result.as_ref().unwrap_err());
672672- assert!(result.is_err());
748748+ eprintln!("{:?}", miette::Report::new(result.unwrap_err()));
673749 }
674750675751 #[test]
676752 fn read_raw() {
677753 let source = ",";
678754 let tokens = tokenizer(source);
679679- let map = parse_brackets(&tokens, source).unwrap();
755755+ let map = parse_brackets(&tokens, "test", source).unwrap();
680756 let mut machine = VM::builder().tape(1).input_type(IOFormat::Raw).build();
681757 let fake_input = std::io::Cursor::new("1");
682758 machine
···689765 fn write_raw() {
690766 let source = ",.";
691767 let tokens = tokenizer(source);
692692- let map = parse_brackets(&tokens, source).unwrap();
768768+ let map = parse_brackets(&tokens, "test", source).unwrap();
693769 let mut machine = VM::builder()
694770 .tape(1)
695771 .input_type(IOFormat::Raw)
···710786 fn read_raw_discard() {
711787 let source = ",>,<.>.";
712788 let tokens = tokenizer(source);
713713- let map = parse_brackets(&tokens, source).unwrap();
789789+ let map = parse_brackets(&tokens, "test", source).unwrap();
714790 let mut machine = VM::builder()
715791 .input_type(IOFormat::Raw)
716792 .output_type(IOFormat::Raw)