mipsasm/
error.rs

1use std::fmt::Write;
2use std::{cmp, fmt};
3
4#[macro_export]
5macro_rules! error {
6    ($self:ident, MultipleLabelDefinition, $label:expr, $first:expr) => {
7        ParserError::MultipleLabelDefinition {
8            line: Line::new(
9                $self.line_num,
10                $self.input.get($self.line_num - 1).unwrap().to_string(),
11            ),
12            label: $label.to_string(),
13            first: Line::new($first + 1, $self.input.get($first).unwrap().to_string()),
14        }
15    };
16    ($self:ident, InvalidLabel, $label:expr) => {
17        ParserError::InvalidLabel {
18            line: Line::new(
19                $self.line_num,
20                $self.input.get($self.line_num - 1).unwrap().to_string(),
21            ),
22            label: $label.to_string(),
23        }
24    };
25    ($self:ident, InvalidInstruction) => {
26        ParserError::InvalidInstruction {
27            line: Line::new(
28                $self.line_num,
29                $self.input.get($self.line_num - 1).unwrap().to_string(),
30            ),
31        }
32    };
33    ($self:ident, InvalidOperandCount, $ops:expr, $expected:expr, $found:expr) => {
34        ParserError::InvalidOperandCount {
35            line: Line::new(
36                $self.line_num,
37                $self.input.get($self.line_num - 1).unwrap().to_string(),
38            ),
39            ops: $ops.to_string(),
40            expected: $expected,
41            found: $found,
42        }
43    };
44    ($self:ident, InvalidOpcode, $opcode:expr) => {
45        ParserError::InvalidOpcode {
46            line: Line::new(
47                $self.line_num,
48                $self.input.get($self.line_num - 1).unwrap().to_string(),
49            ),
50            opcode: $opcode.to_string(),
51        }
52    };
53    ($self:ident, InvalidRegister, $register:expr) => {
54        ParserError::InvalidRegister {
55            line: Line::new(
56                $self.line_num,
57                $self.input.get($self.line_num - 1).unwrap().to_string(),
58            ),
59            register: $register.to_string(),
60        }
61    };
62    ($self:ident, InvalidTargetAddress, $target:expr) => {
63        ParserError::InvalidTargetAddress {
64            line: Line::new(
65                $self.line_num,
66                $self.input.get($self.line_num - 1).unwrap().to_string(),
67            ),
68            address: $target.to_string(),
69        }
70    };
71    ($self:ident, InvalidImmediate, $immediate:expr) => {
72        ParserError::InvalidImmediate {
73            line: Line::new(
74                $self.line_num,
75                $self.input.get($self.line_num - 1).unwrap().to_string(),
76            ),
77            immediate: $immediate.to_string(),
78        }
79    };
80    ($self:ident, InvalidFloatCond, $cond:expr) => {
81        ParserError::InvalidFloatCond {
82            line: Line::new(
83                $self.line_num,
84                $self.input.get($self.line_num - 1).unwrap().to_string(),
85            ),
86            cond: $cond.to_string(),
87        }
88    };
89    ($self:ident, BranchOutOfBounds, $line:expr, $target:expr, $bounds:expr) => {
90        ParserError::BranchOutOfBounds {
91            line: Line::new(
92                $self.line_num,
93                $self.input.get($self.line_num - 1).unwrap().to_string(),
94            ),
95            branch: $target,
96            bounds: $bounds,
97        }
98    };
99    ($self:ident, LocalLabelOutOfScope, $line_num:expr, $label:expr) => {
100        ParserError::LocalLabelOutOfScope {
101            line: Line::new(
102                $line_num,
103                $self.input.get($line_num - 1).unwrap().to_string(),
104            ),
105            label: $label.to_string(),
106        }
107    };
108    ($self:ident, UndefinedLabel, $line_num:expr, $label:expr) => {
109        ParserError::UndefinedLabel {
110            line: Line::new(
111                $line_num,
112                $self.input.get($line_num - 1).unwrap().to_string(),
113            ),
114            label: $label.to_string(),
115        }
116    };
117}
118
119#[macro_export]
120macro_rules! warning {
121    ($self:ident, InvalidInstructionInDelaySlot) => {
122        ParserWarning::InvalidInstructionInDelaySlot {
123            delay_slot_inst: Line::new(
124                $self.line_num,
125                $self.input.get($self.line_num - 1).unwrap().to_string(),
126            ),
127            line: Line::new(
128                $self.line_num - 1,
129                $self.input.get($self.line_num - 2).unwrap().to_string(),
130            ),
131        }
132    };
133    ($self:ident, UnalignedBranch, $target:expr) => {
134        ParserWarning::UnalignedBranch {
135            line: Line::new(
136                $self.line_num,
137                $self.input.get($self.line_num - 1).unwrap().to_string(),
138            ),
139            offset: $target,
140        }
141    };
142    ($self:ident, UnalignedJump, $target:expr) => {
143        ParserWarning::UnalignedJump {
144            line: Line::new(
145                $self.line_num,
146                $self.input.get($self.line_num - 1).unwrap().to_string(),
147            ),
148            target: $target,
149        }
150    };
151}
152
153#[derive(Debug)]
154pub struct Line {
155    num: usize,
156    content: String,
157}
158
159impl Line {
160    pub fn new(num: usize, content: String) -> Self {
161        Self { num, content }
162    }
163}
164
165#[derive(Debug)]
166pub enum ParserWarning {
167    InvalidInstructionInDelaySlot { line: Line, delay_slot_inst: Line },
168    UnalignedBranch { line: Line, offset: String },
169    UnalignedJump { line: Line, target: String },
170}
171
172impl fmt::Display for ParserWarning {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        match self {
175            Self::InvalidInstructionInDelaySlot {
176                line: Line { num, content },
177                delay_slot_inst:
178                    Line {
179                        num: delay_num,
180                        content: delay_content,
181                    },
182            } => {
183                let margin = num.to_string().len();
184                writeln!(
185                    f,
186                    "warning: instruction `{}` cannot be in a delay slot",
187                    delay_content.trim()
188                )?;
189                writeln!(
190                    f,
191                    "{}",
192                    fmt_line(
193                        *delay_num,
194                        delay_content,
195                        margin,
196                        false,
197                        "this instruction cannot be in a delay slot",
198                        true,
199                        delay_content.trim()
200                    )
201                )?;
202                writeln!(f, "\x1b[94m...\x1b[0m")?;
203                writeln!(
204                    f,
205                    "{}",
206                    fmt_line(
207                        *num,
208                        content,
209                        margin,
210                        true,
211                        "delay slot occurs after this instruction",
212                        false,
213                        content.trim()
214                    )
215                )
216            }
217            Self::UnalignedBranch {
218                line: Line { num, content },
219                offset,
220            } => {
221                let margin = num.to_string().len();
222                writeln!(
223                    f,
224                    "warning: branch offset `{}` is not aligned to a 4-byte boundary",
225                    offset
226                )?;
227                writeln!(
228                    f,
229                    "{}",
230                    fmt_line(
231                        *num,
232                        content,
233                        margin,
234                        true,
235                        "offset is not divisible by 4",
236                        false,
237                        offset
238                    )
239                )
240            }
241            Self::UnalignedJump {
242                line: Line { num, content },
243                target,
244            } => {
245                let margin = num.to_string().len();
246                writeln!(
247                    f,
248                    "warning: jump target `{}` is not aligned to a 4-byte boundary",
249                    target
250                )?;
251                writeln!(
252                    f,
253                    "{}",
254                    fmt_line(
255                        *num,
256                        content,
257                        margin,
258                        true,
259                        "target is not divisible by 4",
260                        false,
261                        target
262                    )
263                )
264            }
265        }
266    }
267}
268
269#[derive(Debug)]
270pub enum ParserError {
271    MultipleLabelDefinition {
272        line: Line,
273        label: String,
274        first: Line,
275    },
276    InvalidLabel {
277        line: Line,
278        label: String,
279    },
280    InvalidInstruction {
281        line: Line,
282    },
283    InvalidOperandCount {
284        line: Line,
285        expected: usize,
286        found: usize,
287        ops: String,
288    },
289    InvalidOpcode {
290        line: Line,
291        opcode: String,
292    },
293    InvalidRegister {
294        line: Line,
295        register: String,
296    },
297    InvalidTargetAddress {
298        line: Line,
299        address: String,
300    },
301    InvalidImmediate {
302        line: Line,
303        immediate: String,
304    },
305    InvalidFloatCond {
306        line: Line,
307        cond: String,
308    },
309    BranchOutOfBounds {
310        line: Line,
311        branch: String,
312        bounds: (u32, u32),
313    },
314    LocalLabelOutOfScope {
315        line: Line,
316        label: String,
317    },
318    UndefinedLabel {
319        line: Line,
320        label: String,
321    },
322}
323
324impl fmt::Display for ParserError {
325    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326        match self {
327            Self::MultipleLabelDefinition {
328                line: Line { num, content },
329                label,
330                first:
331                    Line {
332                        num: first_num,
333                        content: first_content,
334                    },
335            } => {
336                let margin = cmp::max(num.to_string().len(), first_num.to_string().len());
337                writeln!(
338                    f,
339                    "\x1b[91merror\x1b[0m: label `{}` defined multiple times",
340                    label
341                )?;
342                writeln!(
343                    f,
344                    "{}",
345                    fmt_line(
346                        *first_num,
347                        first_content,
348                        margin,
349                        true,
350                        "first defined here",
351                        true,
352                        label
353                    )
354                )?;
355                writeln!(f, "\x1b[94m...\x1b[0m")?;
356                writeln!(
357                    f,
358                    "{}",
359                    fmt_line(*num, content, margin, false, "redefined here", false, label)
360                )
361            }
362            Self::InvalidLabel {
363                line: Line { num, content },
364                label,
365            } => {
366                let margin = num.to_string().len();
367                writeln!(
368                    f,
369                    "\x1b[91merror\x1b[0m: label `{}` must start with a letter",
370                    label
371                )?;
372                writeln!(
373                    f,
374                    "{}",
375                    fmt_line(*num, content, margin, false, "defined here", true, label)
376                )
377            }
378            Self::InvalidInstruction {
379                line: Line { num, content },
380            } => {
381                let margin = num.to_string().len();
382                writeln!(
383                    f,
384                    "\x1b[91merror\x1b[0m: invalid instruction `{}`",
385                    content.trim()
386                )?;
387                writeln!(
388                    f,
389                    "{}",
390                    fmt_line(*num, content, margin, false, "", true, content.trim())
391                )
392            }
393            Self::InvalidOperandCount {
394                line: Line { num, content },
395                ops,
396                expected,
397                found,
398            } => {
399                let margin = num.to_string().len();
400                writeln!(
401                    f,
402                    "\x1b[91merror\x1b[0m: invalid number of operands `{}`",
403                    content
404                )?;
405                writeln!(
406                    f,
407                    "{}",
408                    fmt_line(
409                        *num,
410                        content,
411                        margin,
412                        false,
413                        format!("expected {} operands, found {}", expected, found).as_str(),
414                        true,
415                        ops
416                    )
417                )
418            }
419            Self::InvalidOpcode {
420                line: Line { num, content },
421                opcode,
422            } => {
423                let margin = num.to_string().len();
424                writeln!(f, "\x1b[91merror\x1b[0m: invalid opcode `{}`", opcode)?;
425                writeln!(
426                    f,
427                    "{}",
428                    fmt_line(*num, content, margin, false, "", true, opcode)
429                )
430            }
431            Self::InvalidRegister {
432                line: Line { num, content },
433                register,
434            } => {
435                let margin = num.to_string().len();
436                writeln!(f, "\x1b[91merror\x1b[0m: invalid register `{}`", register)?;
437                writeln!(
438                    f,
439                    "{}",
440                    fmt_line(*num, content, margin, false, "", true, register)
441                )
442            }
443            Self::InvalidTargetAddress {
444                line: Line { num, content },
445                address,
446            } => {
447                let margin = num.to_string().len();
448                writeln!(
449                    f,
450                    "\x1b[91merror\x1b[0m: invalid target address `{}`",
451                    address
452                )?;
453                writeln!(
454                    f,
455                    "{}",
456                    fmt_line(*num, content, margin, false, "", true, address)
457                )
458            }
459            Self::InvalidImmediate {
460                line: Line { num, content },
461                immediate,
462            } => {
463                let margin = num.to_string().len();
464                writeln!(f, "\x1b[91merror\x1b[0m: invalid immediate `{}`", immediate)?;
465                writeln!(
466                    f,
467                    "{}",
468                    fmt_line(*num, content, margin, false, "", true, immediate)
469                )
470            }
471            Self::InvalidFloatCond {
472                line: Line { num, content },
473                cond,
474            } => {
475                let margin = num.to_string().len();
476                writeln!(
477                    f,
478                    "\x1b[91merror\x1b[0m: invalid float compare condition `{}`",
479                    cond
480                )?;
481                writeln!(
482                    f,
483                    "{}",
484                    fmt_line(*num, content, margin, false, "", true, cond)
485                )
486            }
487            Self::BranchOutOfBounds {
488                line: Line { num, content },
489                branch,
490                bounds,
491            } => {
492                let margin = num.to_string().len();
493                writeln!(f, "\x1b[91merror\x1b[0m: branch `{}` out of bounds", branch)?;
494                writeln!(
495                    f,
496                    "{}",
497                    fmt_line(
498                        *num,
499                        content,
500                        margin,
501                        false,
502                        &format!(
503                            "should be between 0x{:08x?} and 0x{:08x?}",
504                            bounds.0, bounds.1
505                        ),
506                        true,
507                        branch
508                    )
509                )
510            }
511            Self::LocalLabelOutOfScope {
512                line: Line { num, content },
513                label,
514            } => {
515                let margin = num.to_string().len();
516                writeln!(
517                    f,
518                    "\x1b[91merror\x1b[0m: local label `{}` used outside of its scope",
519                    label
520                )?;
521                writeln!(
522                    f,
523                    "{}",
524                    fmt_line(*num, content, margin, false, "", true, label)
525                )
526            }
527            Self::UndefinedLabel {
528                line: Line { num, content },
529                label,
530            } => {
531                let margin = num.to_string().len();
532                writeln!(f, "\x1b[91merror\x1b[0m: label `{}` is not defined", label)?;
533                writeln!(
534                    f,
535                    "{}",
536                    fmt_line(*num, content, margin, false, "used here", true, label)
537                )
538            }
539        }
540    }
541}
542
543fn fmt_line(
544    num: usize,
545    content: &str,
546    margin: usize,
547    err_underline: bool,
548    msg: &str,
549    first_space: bool,
550    underline: &str,
551) -> String {
552    let mut s = String::new();
553    let underline_start = content
554        .find(underline)
555        .unwrap_or_else(|| panic!("{}", content));
556    if first_space {
557        writeln!(s, "\x1b[94m{:>margin$} |\x1b[0m", "").unwrap();
558    }
559    writeln!(s, "\x1b[94m{:>margin$} |\x1b[0m {}", num, content).unwrap();
560    if err_underline {
561        writeln!(
562            s,
563            "\x1b[94m{:>margin$} | {: <start$}{:-<len$} {msg}\x1b[0m",
564            "",
565            "",
566            "",
567            len = underline.len(),
568            start = underline_start
569        )
570        .unwrap();
571    } else {
572        writeln!(
573            s,
574            "\x1b[94m{:>margin$} | {: <start$}\x1b[91m{:^<len$} {msg}\x1b[0m",
575            "",
576            "",
577            "",
578            len = underline.len(),
579            start = underline_start
580        )
581        .unwrap();
582    }
583    write!(s, "\x1b[94m{:>margin$} |\x1b[0m", "").unwrap();
584    s
585}