tis_100/
parse.rs

1//! Functions for parsing TIS-100 assembly code into instructions.
2
3use std::str::FromStr;
4use std::fmt::{Display, Formatter, Error};
5use std::collections::HashMap;
6use core::*;
7use core::Instruction::*;
8use lex::{lex_program, Label, Line};
9
10/// An error that can be returned while parsing a TIS-100 assembly program.
11#[derive(Debug, PartialEq)]
12pub enum ParseProgramError {
13    InvalidLabel,
14    UndefinedLabel(String),
15    DuplicateLabel(String),
16    InvalidOpcode(String),
17    InvalidExpression(String),
18    InvalidRegister(String),
19    MissingOperand(String),
20    TooManyOperands(String),
21}
22
23impl Display for ParseProgramError {
24    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
25        match self {
26            &InvalidLabel => f.write_str("Invalid label"),
27            &UndefinedLabel(ref lbl) => f.write_fmt(format_args!("Undefined label: '{}'", lbl)),
28            &DuplicateLabel(ref lbl) => f.write_fmt(format_args!("Label is already defined: '{}'", lbl)),
29            &InvalidOpcode(ref op) => f.write_fmt(format_args!("Invalid opcode: '{}'", op)),
30            &InvalidExpression(ref expr) => f.write_fmt(format_args!("Invalid expression: '{}'", expr)),
31            &InvalidRegister(ref reg) => f.write_fmt(format_args!("Invalid register: '{}'", reg)),
32            &MissingOperand(ref op) => f.write_fmt(format_args!("Missing operand: '{}'", op)),
33            &TooManyOperands(ref ops) => f.write_fmt(format_args!("Too many operands: '{}'", ops)),
34        }
35    }
36}
37
38/// All errors discovered while parsing a TIS-100 assembly program.
39pub type ProgramErrors = Vec<(usize, ParseProgramError)>;
40
41use self::ParseProgramError::*;
42
43/// A result that can be returned intermediate phases of the parsing process.
44type ParseResult<T> = Result<T, ParseProgramError>;
45
46/// Parse the program source code into a list of instructions. If one or more errors are
47/// encountered during parsing, then the list of errors will be returned instead.
48///
49/// # Example
50///
51/// ```
52/// use tis_100::core::Instruction::*;
53/// use tis_100::core::Source::*;
54/// use tis_100::core::Register::*;
55/// use tis_100::core::IoRegister::*;
56/// use tis_100::core::Port::*;
57/// use tis_100::parse::parse_program;
58///
59/// let src = "MOV UP ACC\nADD 1\nMOV ACC DOWN\n";
60/// let prog = parse_program(src).unwrap();
61/// assert_eq!(prog[0], Mov(REG(IO(DIR(UP))), ACC));
62/// assert_eq!(prog[1], Add(VAL(1)));
63/// assert_eq!(prog[2], Mov(REG(ACC), IO(DIR(DOWN))));
64/// ```
65pub fn parse_program(src: &str) -> Result<Program, ProgramErrors> {
66    // The basic parsing process is:
67    // 1. Tokenize the source into labels, opcodes, and operands
68    // 2. Create a mapping of labels to instruction indices
69    // 3. Parse opcodes and operands line-by-line to generate instructions
70
71    let mut label_map = HashMap::new();
72    let mut instructions = Vec::new();
73    let mut errors = Vec::new();
74
75    let lines = lex_program(src);
76
77    // Lable mapping pass
78    for &Line(line_num, ref maybe_label, _) in lines.iter() {
79        if let &Some(Label(ref name, index)) = maybe_label {
80            if name.len() == 0 {
81                errors.push((line_num, InvalidLabel));
82            } else if let None = label_map.get(name) {
83                label_map.insert(name.clone(), index as isize);
84            } else {
85                errors.push((line_num, DuplicateLabel(name.clone())));
86            }
87        }
88    }
89
90    // Instruction pass
91    for &Line(line_num, _, ref lexemes) in lines.iter() {
92        if lexemes.len() > 0 {
93            match parse_instruction(&lexemes[0], &lexemes[1..], &label_map) {
94                Ok(instruction) => instructions.push(instruction),
95                Err(err) => errors.push((line_num, err)),
96            }
97        }
98    }
99
100    if errors.len() > 0 {
101        Err(errors)
102    } else {
103        Ok(instructions)
104    }
105}
106
107/// Attempt to parse a single TIS-100 assembly instruction.
108fn parse_instruction(opcode: &str, operands: &[String], labels: &HashMap<String, isize>) -> ParseResult<Instruction> {
109    match str::parse::<Opcode>(opcode) {
110        Ok(NOP) => parse_no_operands(Nop, operands),
111        Ok(MOV) => parse_two_operands(Mov, opcode, operands),
112        Ok(SWP) => parse_no_operands(Swp, operands),
113        Ok(SAV) => parse_no_operands(Sav, operands),
114        Ok(ADD) => parse_one_operand(Add, opcode, operands),
115        Ok(SUB) => parse_one_operand(Sub, opcode, operands),
116        Ok(NEG) => parse_no_operands(Neg, operands),
117        Ok(JMP) => parse_jump(Jmp, opcode, operands, labels),
118        Ok(JEZ) => parse_jump(Jez, opcode, operands, labels),
119        Ok(JNZ) => parse_jump(Jnz, opcode, operands, labels),
120        Ok(JGZ) => parse_jump(Jgz, opcode, operands, labels),
121        Ok(JLZ) => parse_jump(Jlz, opcode, operands, labels),
122        Ok(JRO) => parse_one_operand(Jro, opcode, operands),
123        _ => Err(InvalidOpcode(opcode.to_string())),
124    }
125}
126
127/// Attempt to resolve a jump label to an instruction index.
128fn resolve_label<'a>(label: &str, labels: &'a HashMap<String, isize>) -> ParseResult<&'a isize> {
129    labels.get(label).ok_or(UndefinedLabel(label.to_string()))
130}
131
132/// Parse a jump opcode and label into a jump instruction.
133fn parse_jump<F: Fn(isize) -> Instruction>(f: F, opcode: &str, operands: &[String], labels: &HashMap<String, isize>) -> ParseResult<Instruction> {
134    if operands.len() < 1 {
135        Err(MissingOperand(opcode.to_string()))
136    } else if operands.len() == 1 {
137        resolve_label(&operands[0], labels).map(|&i| f(i))
138    } else {
139        Err(TooManyOperands(operands[1..].join(" ")))
140    }
141}
142
143/// Parse an opcode into an instruction.
144fn parse_no_operands(instruction: Instruction, operands: &[String]) -> ParseResult<Instruction> {
145    if operands.len() == 0 {
146        Ok(instruction)
147    } else {
148        Err(TooManyOperands(operands.join(" ")))
149    }
150}
151
152/// Parse an opcode and one operand into an instruction.
153fn parse_one_operand<T: FromStr, F: Fn(T) -> Instruction>(f: F, opcode: &str, operands: &[String]) -> ParseResult<Instruction> {
154    if operands.len() < 1 {
155        Err(MissingOperand(opcode.to_string()))
156    } else if operands.len() == 1 {
157        match str::parse::<T>(&operands[0]) {
158            Ok(op) => Ok(f(op)),
159            Err(_) => Err(InvalidExpression(operands[0].clone())),
160        }
161    } else {
162        Err(TooManyOperands(operands[1..].join(" ")))
163    }
164}
165
166/// Parse an opcode and two operands into an instruction.
167fn parse_two_operands<T: FromStr, U: FromStr, F: Fn(T, U) -> Instruction>(f: F, opcode: &str, operands: &[String]) -> ParseResult<Instruction> {
168    if operands.len() < 2 {
169        Err(MissingOperand(opcode.to_string() + " " + &operands.join(" ")))
170    } else if operands.len() == 2 {
171        match str::parse::<T>(&operands[0]) {
172            Ok(op1) => match str::parse::<U>(&operands[1]) {
173                Ok(op2) => Ok(f(op1, op2)),
174                Err(_) => Err(InvalidRegister(operands[1].clone())),
175            },
176            Err(_) => Err(InvalidExpression(operands[0].clone())),
177        }
178    } else {
179        Err(TooManyOperands(operands[2..].join(" ")))
180    }
181}
182
183/// The opcode component of a TIS-100 instruction.
184#[derive(Debug, PartialEq, Copy, Clone)]
185pub enum Opcode {
186    NOP,
187    MOV,
188    SWP,
189    SAV,
190    ADD,
191    SUB,
192    NEG,
193    JMP,
194    JEZ,
195    JNZ,
196    JGZ,
197    JLZ,
198    JRO,
199}
200
201use self::Opcode::*;
202
203/// An error which can be returned when parsing an opcode.
204#[derive(Debug, PartialEq)]
205pub struct ParseOpcodeError;
206
207impl FromStr for Opcode {
208    type Err = ParseOpcodeError;
209
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        match s {
212            "NOP" => Ok(NOP),
213            "MOV" => Ok(MOV),
214            "SWP" => Ok(SWP),
215            "SAV" => Ok(SAV),
216            "ADD" => Ok(ADD),
217            "SUB" => Ok(SUB),
218            "NEG" => Ok(NEG),
219            "JMP" => Ok(JMP),
220            "JEZ" => Ok(JEZ),
221            "JNZ" => Ok(JNZ),
222            "JGZ" => Ok(JGZ),
223            "JLZ" => Ok(JLZ),
224            "JRO" => Ok(JRO),
225            _ => Err(ParseOpcodeError),
226        }
227    }
228}
229
230#[test]
231fn test_parse_opcode() {
232    assert_eq!(str::parse::<Opcode>("NOP"), Ok(NOP));
233    assert_eq!(str::parse::<Opcode>("MOV"), Ok(MOV));
234    assert_eq!(str::parse::<Opcode>("SWP"), Ok(SWP));
235    assert_eq!(str::parse::<Opcode>("SAV"), Ok(SAV));
236    assert_eq!(str::parse::<Opcode>("ADD"), Ok(ADD));
237    assert_eq!(str::parse::<Opcode>("SUB"), Ok(SUB));
238    assert_eq!(str::parse::<Opcode>("NEG"), Ok(NEG));
239    assert_eq!(str::parse::<Opcode>("JMP"), Ok(JMP));
240    assert_eq!(str::parse::<Opcode>("JEZ"), Ok(JEZ));
241    assert_eq!(str::parse::<Opcode>("JNZ"), Ok(JNZ));
242    assert_eq!(str::parse::<Opcode>("JGZ"), Ok(JGZ));
243    assert_eq!(str::parse::<Opcode>("JLZ"), Ok(JLZ));
244    assert_eq!(str::parse::<Opcode>("JRO"), Ok(JRO));
245    assert_eq!(str::parse::<Opcode>("nop"), Err(ParseOpcodeError));
246    assert_eq!(str::parse::<Opcode>("bad"), Err(ParseOpcodeError));
247}