tis_100/
lex.rs

1//! Functions for splitting TIS-100 assembly code into lexemes.
2
3/// The maximum number of characters per line.
4const NUM_CHARS: usize = 18;
5
6/// The maximum number of lines per program.
7const NUM_LINES: usize = 16;
8
9/// A label and the index of the instruction that it refers to.
10#[derive(Debug, PartialEq)]
11pub struct Label(pub String, pub usize);
12
13/// A lexed source line, consisting of its line number, an optional label,
14/// and one or more lexemes that form an instruction.
15#[derive(Debug, PartialEq)]
16pub struct Line(pub usize, pub Option<Label>, pub Vec<String>);
17
18/// Split the source code into lines of labels and lexemes.
19pub fn lex_program(src: &str) -> Vec<Line> {
20    let mut next_op = 0;
21    let mut lines = Vec::new();
22
23    for (index, line) in src.lines().take(NUM_LINES).enumerate() {
24        let (maybe_label, words) = lex_line(line);
25        let label = if let Some(label) = maybe_label {
26            Some(Label(label, next_op))
27        } else {
28            None
29        };
30
31        if words.len() > 0 {
32            next_op += 1;
33        }
34
35        lines.push(Line(index, label, words));
36    }
37
38    lines
39}
40
41/// Lex a single line of source code.
42fn lex_line(line: &str) -> (Option<String>, Vec<String>) {
43    let mut label = None;
44    let mut words = Vec::new();
45    let mut word = String::new();
46
47    for c in line.to_uppercase().chars().take(NUM_CHARS) {
48        if is_comment_delimiter(c) {
49            break;
50        } else if is_whitespace(c) {
51            if word.len() > 0 {
52                words.push(word.clone());
53                word.clear();
54            }
55        } else if label.is_some() || !is_label_delimiter(c) {
56            word.push(c)
57        } else {
58            label = Some(word.clone());
59            word.clear();
60        }
61    }
62
63    if word.len() > 0 {
64        words.push(word.clone());
65    }
66
67    (label, words)
68}
69
70/// Check if a character is whitespace.
71fn is_whitespace(c: char) -> bool {
72    c == ' ' || c == ','
73}
74
75/// Check if a character is a comment delimiter.
76fn is_comment_delimiter(c: char) -> bool {
77    c == '#'
78}
79
80/// Check if a character is a label delimter.
81fn is_label_delimiter(c: char) -> bool {
82    c == ':'
83}
84
85#[test]
86fn test_is_whitespace() {
87    assert!(is_whitespace(' '));
88    assert!(is_whitespace(','));
89    assert!(!is_whitespace('1'));
90    assert!(!is_whitespace('A'));
91}
92
93#[test]
94fn test_is_comment_delimiter() {
95    assert!(is_comment_delimiter('#'));
96    assert!(!is_comment_delimiter('1'));
97    assert!(!is_comment_delimiter('A'));
98}
99
100#[test]
101fn test_is_label_delimiter() {
102    assert!(is_label_delimiter(':'));
103    assert!(!is_label_delimiter('1'));
104    assert!(!is_label_delimiter('A'));
105}
106
107#[test]
108fn test_lex_line() {
109    let (lbl, lex) = lex_line("LABEL: MOV UP ACC # comment");
110    assert_eq!(lbl, Some("LABEL".to_string()));
111    assert_eq!(lex.len(), 3);
112    assert_eq!(lex[0], "MOV");
113    assert_eq!(lex[1], "UP");
114    assert_eq!(lex[2], "ACC");
115
116    let (lbl, lex) = lex_line("ADD 1");
117    assert_eq!(lbl, None);
118    assert_eq!(lex.len(), 2);
119    assert_eq!(lex[0], "ADD");
120    assert_eq!(lex[1], "1");
121
122    let (lbl, lex) = lex_line(":ADD 1 2 3");
123    assert_eq!(lbl, Some("".to_string()));
124    assert_eq!(lex.len(), 4);
125    assert_eq!(lex[0], "ADD");
126    assert_eq!(lex[1], "1");
127    assert_eq!(lex[2], "2");
128    assert_eq!(lex[3], "3");
129
130    let (lbl, lex) = lex_line(",,LABEL:,,ADD,1,,,,,");
131    assert_eq!(lbl, Some("LABEL".to_string()));
132    assert_eq!(lex.len(), 2);
133    assert_eq!(lex[0], "ADD");
134    assert_eq!(lex[1], "1");
135
136    let (lbl, lex) = lex_line("# LABEL: MOV UP ACC");
137    assert_eq!(lbl, None);
138    assert_eq!(lex.len(), 0);
139
140    let (lbl, lex) = lex_line("LABEL: MOV LEFT RIGHT");
141    assert_eq!(lbl, Some("LABEL".to_string()));
142    assert_eq!(lex.len(), 3);
143    assert_eq!(lex[0], "MOV");
144    assert_eq!(lex[1], "LEFT");
145    assert_eq!(lex[2], "RI");
146}
147
148#[test]
149fn test_lex_program() {
150    let lines = lex_program("MOV UP ACC\nADD 1\nMOV ACC DOWN");
151    assert_eq!(lines.len(), 3);
152
153    let lines = lex_program("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n");
154    assert_eq!(lines.len(), 16);
155
156    let lines = lex_program("1:\n2:\n3: ADD 1\n4: ADD 1\n");
157    assert_eq!(lines.len(), 4);
158    assert_eq!(lines[0].1, Some(Label("1".to_string(), 0)));
159    assert_eq!(lines[1].1, Some(Label("2".to_string(), 0)));
160    assert_eq!(lines[2].1, Some(Label("3".to_string(), 0)));
161    assert_eq!(lines[3].1, Some(Label("4".to_string(), 1)));
162}
163