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}