Skip to content

Commit 3fc7641

Browse files
Resolve labels at parse time and improve tests
1 parent 1f1bb80 commit 3fc7641

File tree

3 files changed

+103
-38
lines changed

3 files changed

+103
-38
lines changed

src/load_file/mod.rs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub use types::{AddressMode, Modifier, Opcode, Value};
88

99
pub const DEFAULT_CORE_SIZE: usize = 8000;
1010

11+
type InstructionSet = Box<[Option<Instruction>]>;
1112
type LabelMap = HashMap<String, usize>;
1213

1314
#[derive(Clone, Debug, PartialEq)]
@@ -107,12 +108,18 @@ impl Instruction {
107108
}
108109
}
109110

110-
#[derive(PartialEq)]
111111
pub struct Core {
112-
instructions: Box<[Option<Instruction>]>,
112+
instructions: InstructionSet,
113113
labels: LabelMap,
114114
}
115115

116+
impl PartialEq for Core {
117+
// TODO: should this impl resolve the instructions? Depends on the use case
118+
fn eq(&self, other: &Self) -> bool {
119+
self.instructions == other.instructions
120+
}
121+
}
122+
116123
impl Default for Core {
117124
fn default() -> Self {
118125
Core::new(DEFAULT_CORE_SIZE)
@@ -174,25 +181,39 @@ impl Core {
174181
self.labels.get(label).copied()
175182
}
176183

184+
pub fn resolve(&self) -> Result<Self, String> {
185+
let instructions = self
186+
.instructions
187+
.iter()
188+
.enumerate()
189+
.map(|(i, maybe_instruction)| {
190+
maybe_instruction
191+
.as_ref()
192+
.map(|instruction| instruction.resolve(i, &self.labels))
193+
.transpose()
194+
})
195+
.collect::<Result<InstructionSet, String>>()?;
196+
197+
Ok(Self {
198+
instructions,
199+
..Default::default()
200+
})
201+
}
202+
177203
pub fn dump(&self) -> String {
178204
// TODO: convert to fmt::Display - this will require some upfront
179205
// validation that all labels are valid, etc
180206
// It may be desirable to have Debug be a dump() of the load file and
181207
// Display show the original parsed document (or something like that)
182-
let resolve_result: Result<Vec<_>, String> = self
183-
.instructions
184-
.iter()
185-
.filter(|&maybe_instruction| maybe_instruction.is_some())
186-
.map(|instruction| instruction.as_ref().unwrap())
187-
.enumerate()
188-
.map(|(i, instruction)| instruction.resolve(i, &self.labels))
189-
.collect();
190-
191-
match resolve_result {
208+
match self.resolve() {
192209
Err(msg) => msg,
193-
Ok(resolved) => resolved.iter().fold(String::new(), |result, instruction| {
194-
result + &instruction.to_string() + "\n"
195-
}),
210+
Ok(core) => core
211+
.instructions
212+
.iter()
213+
.filter_map(Option::as_ref)
214+
.fold(String::new(), |result, instruction| {
215+
result + &instruction.to_string() + "\n"
216+
}),
196217
}
197218
}
198219
}
@@ -240,16 +261,35 @@ mod tests {
240261
assert!(core.label_address("never_mentioned").is_none());
241262
}
242263

243-
// TODO: add test for unresolvable label
244264
#[test]
245-
fn resolve_labels() {
246-
let mut core = Core::new(200);
265+
fn resolve_failure() {
266+
let mut core = Core::new(10);
247267

248-
core.add_label(123, "baz".into()).expect("Should add baz");
249268
core.add_label(0, "foo".into()).expect("Should add foo");
250269

251270
core.set(
252271
5,
272+
Instruction {
273+
field_a: Field {
274+
value: Value::Label("not_real".into()),
275+
..Default::default()
276+
},
277+
..Default::default()
278+
},
279+
);
280+
281+
core.resolve().expect_err("Should fail to resolve");
282+
}
283+
284+
#[test]
285+
fn resolve_labels() {
286+
let mut core = Core::new(10);
287+
288+
core.add_label(0, "foo".into()).expect("Should add foo");
289+
core.add_label(7, "baz".into()).expect("Should add baz");
290+
291+
core.set(
292+
3,
253293
Instruction {
254294
field_a: Field {
255295
value: Value::Label("baz".into()),
@@ -263,16 +303,19 @@ mod tests {
263303
},
264304
);
265305

266-
let resolved = core.get_resolved(5).expect("Should resolve instruction");
306+
let resolved_core = core.resolve().expect("Should resolve all labels in core");
307+
267308
assert_eq!(
268-
resolved,
309+
resolved_core
310+
.get(3)
311+
.expect("Should have instruction at pos 5"),
269312
Instruction {
270313
field_a: Field {
271-
value: Value::Literal(118),
314+
value: Value::Literal(4),
272315
..Default::default()
273316
},
274317
field_b: Field {
275-
value: Value::Literal(-5),
318+
value: Value::Literal(-3),
276319
..Default::default()
277320
},
278321
..Default::default()

src/parser.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ pub fn parse(file_contents: &str) -> Result<Core, Error> {
9595
}
9696
}
9797

98-
Ok(core)
98+
// TODO: keep the original core or use the resolved one?
99+
// Probably it should keep a resolved copy in itself
100+
Ok(core.resolve()?)
99101
}
100102

101103
fn parse_instruction(mut instruction_pairs: Pairs<Rule>) -> Instruction {
@@ -239,6 +241,38 @@ mod tests {
239241
}
240242
}
241243

244+
#[test]
245+
fn parse_expr() {
246+
// TODO: expand grammar for math operations, parens, etc.
247+
// Then test it here. Possibly worth breaking into its own module
248+
parses_to! {
249+
parser: RedcodeParser,
250+
input: "123",
251+
rule: Rule::Expr,
252+
tokens: [
253+
Expr(0, 3, [
254+
Number(0, 3)
255+
]),
256+
]
257+
};
258+
}
259+
260+
#[test]
261+
fn parse_label_expr() {
262+
for test_input in ["foo", "fo2", "f_2"].iter() {
263+
parses_to! {
264+
parser: RedcodeParser,
265+
input: test_input,
266+
rule: Rule::Expr,
267+
tokens: [
268+
Expr(0, 3, [
269+
Label(0, 3)
270+
]),
271+
]
272+
};
273+
}
274+
}
275+
242276
#[test]
243277
fn parse_opcode_modifier() {
244278
for test_input in [
@@ -360,26 +394,14 @@ mod tests {
360394
);
361395
expected_core.set(
362396
4,
363-
Instruction::new(
364-
Opcode::Jmp,
365-
Field {
366-
address_mode: AddressMode::Direct,
367-
value: Value::Label(String::from("begin")),
368-
},
369-
Field::immediate(0),
370-
),
397+
Instruction::new(Opcode::Jmp, Field::direct(-4), Field::immediate(0)),
371398
);
372399
expected_core.set(
373400
5,
374401
Instruction::new(Opcode::Jmp, Field::direct(-1), Field::immediate(0)),
375402
);
376403

377-
expected_core.add_label(0, "preload".into()).unwrap();
378-
expected_core.add_label(0, "begin".into()).unwrap();
379-
expected_core.add_label(2, "loop".into()).unwrap();
380-
expected_core.add_label(2, "main".into()).unwrap();
381-
382-
let parsed = parse(simple_input).unwrap();
404+
let parsed = parse(simple_input).expect("Should parse simple file");
383405

384406
assert_eq!(parsed, expected_core);
385407
}
File renamed without changes.

0 commit comments

Comments
 (0)