use pretty_assertions::assert_eq;
use crate::{
AddressingMode, Assembler, AssemblyError, AssemblyLocation, DropKind,
Function, Immediate, Instruction, RegisterIndex, RollingRecordIndex,
support::{compile_valid, read_compilation_test_cases}
};
#[test]
fn test_text_round_trip_full_optimization()
{
let corpus = include_str!("../../tests/test_full_optimization.txt");
for (index, (source, expected)) in
read_compilation_test_cases(corpus).iter().enumerate()
{
let function = match Assembler::assemble(expected)
{
Ok(f) => f,
Err(e) =>
{
panic!(
"case {}: assemble failed for source `{}`:\n{}\nexpected:\n{}",
index + 1,
source,
e,
expected
);
}
};
let actual = format!("{}", function);
assert_eq!(
actual.trim(),
*expected,
"text round-trip mismatch for case {} source `{}`",
index + 1,
source
);
}
}
#[test]
fn test_struct_round_trip_full_optimization()
{
let corpus = include_str!("../../tests/test_full_optimization.txt");
for (index, (source, _expected)) in
read_compilation_test_cases(corpus).iter().enumerate()
{
let compiled = compile_valid(source);
let text = format!("{}", compiled);
let reassembled = match Assembler::assemble(&text)
{
Ok(f) => f,
Err(e) => panic!(
"case {} source `{}`: re-assemble failed: {}\ntext:\n{}",
index + 1,
source,
e,
text
)
};
assert_eq!(
reassembled,
compiled,
"struct round-trip mismatch for case {} source `{}`",
index + 1,
source
);
}
}
#[test]
fn test_text_round_trip_compile_unoptimized()
{
let corpus = include_str!("../../tests/test_compile_unoptimized.txt");
for (index, (source, expected)) in
read_compilation_test_cases(corpus).iter().enumerate()
{
let function = match Assembler::assemble(expected)
{
Ok(f) => f,
Err(e) => panic!(
"case {} source `{}`: assemble failed: {}\nexpected:\n{}",
index + 1,
source,
e,
expected
)
};
let actual = format!("{}", function);
assert_eq!(
actual.trim(),
*expected,
"text round-trip mismatch for case {} source `{}`",
index + 1,
source
);
}
}
#[test]
fn test_assemble_constant_return()
{
let text = "\
Function() r#0 ⚅#0
\textern[]
\tbody:
\t\treturn 0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(function.parameters, Vec::<String>::new());
assert_eq!(function.externals, Vec::<String>::new());
assert_eq!(function.register_count, 0);
assert_eq!(function.rolling_record_count, 0);
assert_eq!(
function.instructions,
vec![Instruction::r#return(AddressingMode::Immediate(Immediate(
0
)))]
);
}
#[test]
fn test_assemble_single_parameter()
{
let text = "\
Function(x@0) r#1 ⚅#0
\textern[]
\tbody:
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(function.parameters, vec!["x".to_string()]);
assert_eq!(function.register_count, 1);
}
#[test]
fn test_assemble_single_extern()
{
let text = "\
Function() r#1 ⚅#0
\textern[y@0]
\tbody:
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(function.parameters, Vec::<String>::new());
assert_eq!(function.externals, vec!["y".to_string()]);
assert_eq!(function.register_count, 1);
}
#[test]
fn test_assemble_unicode_and_whitespace_names()
{
let text = "\
Function(an argument@0, qualified.access@1, kebab-case@2) r#4 ⚅#0
\textern[выражение в кости@3]
\tbody:
\t\t@3 <- @0 + @3
\t\treturn @3
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.parameters,
vec![
"an argument".to_string(),
"qualified.access".to_string(),
"kebab-case".to_string()
]
);
assert_eq!(function.externals, vec!["выражение в кости".to_string()]);
assert_eq!(function.register_count, 4);
}
#[test]
fn test_assemble_binary_arithmetic()
{
for (sigil, constructor) in [
(
"+",
Instruction::add
as fn(
RegisterIndex,
AddressingMode,
AddressingMode
) -> Instruction
),
("-", Instruction::sub),
("*", Instruction::mul),
("/", Instruction::div),
("%", Instruction::r#mod),
("^", Instruction::exp)
]
{
let text = format!(
"\
Function() r#1 ⚅#0
\textern[]
\tbody:
\t\t@0 <- 3 {} 4
\t\treturn @0
",
sigil
);
let function = Assembler::assemble(&text).unwrap();
assert_eq!(
function.instructions[0],
constructor(
RegisterIndex(0),
AddressingMode::Immediate(Immediate(3)),
AddressingMode::Immediate(Immediate(4))
),
"binary op {} mis-parsed",
sigil
);
}
}
#[test]
fn test_assemble_unary_negation()
{
let text = "\
Function(x@0) r#2 ⚅#0
\textern[]
\tbody:
\t\t@1 <- -@0
\t\treturn @1
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::neg(
RegisterIndex(1),
AddressingMode::Register(RegisterIndex(0))
)
);
let text = "\
Function() r#1 ⚅#0
\textern[]
\tbody:
\t\t@0 <- -5
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::neg(
RegisterIndex(0),
AddressingMode::Immediate(Immediate(5))
)
);
}
#[test]
fn test_assemble_roll_range()
{
let text = "\
Function() r#1 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll range -10:10
\t\t@0 <- sum rolling record ⚅0
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::roll_range(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(-10)),
AddressingMode::Immediate(Immediate(10))
)
);
let text = "\
Function(a@0, b@1) r#3 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll range @0:@1
\t\t@2 <- sum rolling record ⚅0
\t\treturn @2
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::roll_range(
RollingRecordIndex(0),
AddressingMode::Register(RegisterIndex(0)),
AddressingMode::Register(RegisterIndex(1))
)
);
}
#[test]
fn test_assemble_roll_standard_dice()
{
let text = "\
Function() r#1 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll standard dice 3D6
\t\t@0 <- sum rolling record ⚅0
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::roll_standard_dice(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(3)),
AddressingMode::Immediate(Immediate(6))
)
);
}
#[test]
fn test_assemble_roll_custom_dice()
{
let text = "\
Function() r#1 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll custom dice 1D[-1, 0, 1]
\t\t@0 <- sum rolling record ⚅0
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::roll_custom_dice(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(1)),
vec![-1, 0, 1]
)
);
let text = "\
Function() r#1 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll custom dice 2D[42]
\t\t@0 <- sum rolling record ⚅0
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::roll_custom_dice(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(2)),
vec![42]
)
);
}
#[test]
fn test_assemble_drop_clauses()
{
let text = "\
Function() r#1 ⚅#1
\textern[]
\tbody:
\t\t⚅0 <- roll standard dice 3D6
\t\t⚅0 <- drop lowest 1 from ⚅0
\t\t⚅0 <- drop highest 1 from ⚅0
\t\t@0 <- sum rolling record ⚅0
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[1],
Instruction::drop_lowest(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(1))
)
);
assert_eq!(
function.instructions[2],
Instruction::drop_highest(
RollingRecordIndex(0),
AddressingMode::Immediate(Immediate(1))
)
);
}
#[test]
fn test_assemble_negative_operands()
{
let text = "\
Function() r#1 ⚅#0
\textern[]
\tbody:
\t\t@0 <- -5 + -3
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::add(
RegisterIndex(0),
AddressingMode::Immediate(Immediate(-5)),
AddressingMode::Immediate(Immediate(-3))
)
);
let text = "\
Function() r#1 ⚅#0
\textern[]
\tbody:
\t\t@0 <- 10 - -3
\t\treturn @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::sub(
RegisterIndex(0),
AddressingMode::Immediate(Immediate(10)),
AddressingMode::Immediate(Immediate(-3))
)
);
}
#[test]
fn test_assemble_permissive_whitespace()
{
let text = "\
Function(x@0, y@1) r#2 ⚅#0
extern[]
body:
@0 <- @0 + @1
return @0
";
let function = Assembler::assemble(text).unwrap();
assert_eq!(function.parameters, vec!["x".to_string(), "y".to_string()]);
assert_eq!(
function.instructions[0],
Instruction::add(
RegisterIndex(0),
AddressingMode::Register(RegisterIndex(0)),
AddressingMode::Register(RegisterIndex(1))
)
);
}
#[test]
fn test_assemble_crlf_line_endings()
{
let text =
"Function() r#0 ⚅#0\r\n\textern[]\r\n\tbody:\r\n\t\treturn 1\r\n";
let function = Assembler::assemble(text).unwrap();
assert_eq!(
function.instructions[0],
Instruction::r#return(AddressingMode::Immediate(Immediate(1)))
);
}
#[test]
fn test_assemble_surrounding_blank_lines()
{
let text = "\n\n\
Function() r#0 ⚅#0
\textern[]
\tbody:
\t\treturn 0
\n\n";
let function = Assembler::assemble(text).unwrap();
assert_eq!(function.register_count, 0);
}
#[track_caller]
fn assert_rejects(input: &str) -> AssemblyError
{
match Assembler::assemble(input)
{
Ok(function) => panic!(
"expected rejection but assembled successfully:\n{:#?}",
function
),
Err(e) => e
}
}
#[test]
fn test_reject_missing_function_keyword()
{
assert!(matches!(
assert_rejects(
"NotFunction() r#0 ⚅#0\n\textern[]\n\tbody:\n\t\treturn 0\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_missing_register_count_prefix()
{
assert!(matches!(
assert_rejects("Function() 0 ⚅#0\n\textern[]\n\tbody:\n\t\treturn 0\n"),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_missing_rolling_record_count_prefix()
{
assert!(matches!(
assert_rejects("Function() r#0 0\n\textern[]\n\tbody:\n\t\treturn 0\n"),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_missing_extern_line()
{
assert!(matches!(
assert_rejects("Function() r#0 ⚅#0\n\tbody:\n\t\treturn 0\n"),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_missing_body_header()
{
assert!(matches!(
assert_rejects("Function() r#0 ⚅#0\n\textern[]\n\t\treturn 0\n"),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_unknown_opcode()
{
assert!(matches!(
assert_rejects(
"Function() r#1 ⚅#0\n\textern[]\n\tbody:\n\t\t@0 <- wibble 1 + 2\n\t\treturn @0\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_wrong_destination_kind()
{
assert!(matches!(
assert_rejects(
"Function() r#1 ⚅#1\n\textern[]\n\tbody:\n\t\t@0 <- roll range 0:5\n\t\treturn @0\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_rolling_record_as_arithmetic_operand()
{
assert!(matches!(
assert_rejects(
"Function() r#1 ⚅#1\n\textern[]\n\tbody:\n\t\t⚅0 <- roll standard dice 3D6\n\t\t@0 <- ⚅0 + 1\n\t\treturn @0\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_register_out_of_bounds()
{
let e = assert_rejects(
"Function() r#1 ⚅#0\n\textern[]\n\tbody:\n\t\t@5 <- 1 + 2\n\t\treturn @5\n"
);
let AssemblyError::RegisterOutOfBounds {
index,
register_count,
..
} = e
else
{
panic!("expected RegisterOutOfBounds, got: {:?}", e);
};
assert_eq!(index, 5);
assert_eq!(register_count, 1);
}
#[test]
fn test_reject_rolling_record_out_of_bounds()
{
let e = assert_rejects(
"Function() r#1 ⚅#1\n\textern[]\n\tbody:\n\t\t⚅5 <- roll standard dice 3D6\n\t\t@0 <- sum rolling record ⚅5\n\t\treturn @0\n"
);
let AssemblyError::RollingRecordOutOfBounds {
index,
rolling_record_count,
..
} = e
else
{
panic!("expected RollingRecordOutOfBounds, got: {:?}", e);
};
assert_eq!(index, 5);
assert_eq!(rolling_record_count, 1);
}
#[test]
fn test_reject_register_gap()
{
let e = assert_rejects(
"Function() r#3 ⚅#0\n\textern[]\n\tbody:\n\t\t@0 <- 1 + 2\n\t\t@2 <- @0 + 3\n\t\treturn @2\n"
);
let AssemblyError::RegisterGap {
index,
register_count,
..
} = e
else
{
panic!("expected RegisterGap, got: {:?}", e);
};
assert_eq!(index, 1);
assert_eq!(register_count, 3);
}
#[test]
fn test_reject_rolling_record_gap()
{
let e = assert_rejects(
"Function() r#1 ⚅#3\n\textern[]\n\tbody:\n\t\t⚅0 <- roll standard dice 3D6\n\t\t⚅2 <- roll standard dice 3D6\n\t\t@0 <- sum rolling record ⚅2\n\t\treturn @0\n"
);
let AssemblyError::RollingRecordGap {
index,
rolling_record_count,
..
} = e
else
{
panic!("expected RollingRecordGap, got: {:?}", e);
};
assert_eq!(index, 1);
assert_eq!(rolling_record_count, 3);
}
#[test]
fn test_reject_non_contiguous_parameter_indices()
{
let e = assert_rejects(
"Function(x@0, y@2) r#2 ⚅#0\n\textern[]\n\tbody:\n\t\t@0 <- @0 + @1\n\t\treturn @0\n"
);
let AssemblyError::NonContiguousParameter {
name,
index,
position,
..
} = e
else
{
panic!("expected NonContiguousParameter, got: {:?}", e);
};
assert_eq!(name, "y");
assert_eq!(index, 2);
assert_eq!(position, 1);
}
#[test]
fn test_reject_non_contiguous_extern_indices()
{
let e = assert_rejects(
"Function(x@0) r#3 ⚅#0\n\textern[a@1, b@5]\n\tbody:\n\t\t@2 <- @1 + @0\n\t\treturn @2\n"
);
let AssemblyError::NonContiguousExternal {
name,
index,
expected,
arity,
..
} = e
else
{
panic!("expected NonContiguousExternal, got: {:?}", e);
};
assert_eq!(name, "b");
assert_eq!(index, 5);
assert_eq!(expected, 2);
assert_eq!(arity, 1);
}
#[test]
fn test_reject_parameter_index_disagrees_with_position()
{
let e = assert_rejects(
"Function(x@1) r#1 ⚅#0\n\textern[]\n\tbody:\n\t\treturn @0\n"
);
let AssemblyError::NonContiguousParameter {
name,
index,
position,
..
} = e
else
{
panic!("expected NonContiguousParameter, got: {:?}", e);
};
assert_eq!(name, "x");
assert_eq!(index, 1);
assert_eq!(position, 0);
}
#[test]
fn test_reject_faceless_custom_dice()
{
assert!(matches!(
assert_rejects(
"Function() r#1 ⚅#1\n\textern[]\n\tbody:\n\t\t⚅0 <- roll custom dice 1D[]\n\t\t@0 <- sum rolling record ⚅0\n\t\treturn @0\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_drop_source_disagrees_with_destination()
{
let e = assert_rejects(
"Function() r#1 ⚅#2\n\textern[]\n\tbody:\n\t\t⚅0 <- roll standard dice 3D6\n\t\t⚅1 <- roll standard dice 3D6\n\t\t⚅0 <- drop lowest 1 from ⚅1\n\t\t@0 <- sum rolling record ⚅0\n\t\treturn @0\n"
);
let AssemblyError::DropSourceMismatch {
kind,
destination,
source,
..
} = e
else
{
panic!("expected DropSourceMismatch, got: {:?}", e);
};
assert_eq!(kind, DropKind::Lowest);
assert_eq!(destination, 0);
assert_eq!(source, 1);
}
#[test]
fn test_reject_drop_highest_source_disagrees_with_destination()
{
let e = assert_rejects(
"Function() r#1 ⚅#2\n\textern[]\n\tbody:\n\t\t⚅0 <- roll standard dice 3D6\n\t\t⚅1 <- roll standard dice 3D6\n\t\t⚅0 <- drop highest 1 from ⚅1\n\t\t@0 <- sum rolling record ⚅0\n\t\treturn @0\n"
);
let AssemblyError::DropSourceMismatch { kind, .. } = e
else
{
panic!("expected DropSourceMismatch, got: {:?}", e);
};
assert_eq!(kind, DropKind::Highest);
}
#[test]
fn test_reject_trailing_garbage()
{
assert!(matches!(
assert_rejects(
"Function() r#0 ⚅#0\n\textern[]\n\tbody:\n\t\treturn 0\nxxx\n"
),
AssemblyError::Syntax { .. }
));
}
#[test]
fn test_reject_declared_args_exceed_register_count()
{
let e = assert_rejects(
"Function(x@0, y@1) r#1 ⚅#0\n\textern[]\n\tbody:\n\t\treturn @0\n"
);
let AssemblyError::InsufficientRegisterCount {
register_count,
required,
..
} = e
else
{
panic!("expected InsufficientRegisterCount, got: {:?}", e);
};
assert_eq!(register_count, 1);
assert_eq!(required, 2);
}
#[test]
fn test_error_location_points_at_offending_line()
{
let e = assert_rejects(
"Function() r#1 ⚅#0\n\textern[]\n\tbody:\n\t\t@9 <- 1 + 2\n\t\treturn @9\n"
);
let location = e.location();
assert_eq!(
location.line, 4,
"expected line 4 (body instruction), got {}",
location.line
);
assert!(matches!(e, AssemblyError::RegisterOutOfBounds { .. }));
}
#[test]
fn test_display_renders_location_and_description()
{
let e = assert_rejects(
"Function() r#1 ⚅#0\n\textern[]\n\tbody:\n\t\t@5 <- 1 + 2\n\t\treturn @5\n"
);
let rendered = format!("{}", e);
assert!(
rendered.contains("line 4"),
"missing line number: {}",
rendered
);
assert!(
rendered.contains("register @5"),
"missing register token: {}",
rendered
);
assert!(
rendered.contains("exceeds declared register count r#1"),
"missing descriptive tail: {}",
rendered
);
}
#[test]
fn test_from_str_delegates_to_assembler()
{
let text = "\
Function() r#0 ⚅#0
\textern[]
\tbody:
\t\treturn 42
";
let function: Function = text.parse().unwrap();
assert_eq!(
function.instructions[0],
Instruction::r#return(AddressingMode::Immediate(Immediate(42)))
);
}
#[test]
fn test_from_str_propagates_errors()
{
let result: Result<Function, _> = "not a function".parse();
assert!(result.is_err());
}
#[test]
fn test_assembly_error_display_and_location()
{
let loc = |n: usize| AssemblyLocation {
offset: n,
line: n as u32,
column: n
};
let cases: Vec<(AssemblyError, &str)> = vec![
(
AssemblyError::Syntax {
description: "bad token".to_string(),
location: loc(1)
},
"assembly error at line 1, column 1 (byte 1): syntax error (bad \
token)"
),
(
AssemblyError::NonContiguousParameter {
name: "x".to_string(),
index: 3,
position: 1,
location: loc(2)
},
"assembly error at line 2, column 2 (byte 2): parameter `x` has \
index 3 but appears in position 1; parameters must be contiguous \
from @0"
),
(
AssemblyError::NonContiguousExternal {
name: "y".to_string(),
index: 5,
expected: 2,
arity: 1,
location: loc(3)
},
"assembly error at line 3, column 3 (byte 3): external `y` has \
index 5 but expected @2 (externs must be contiguous, starting at \
@1)"
),
(
AssemblyError::InsufficientRegisterCount {
register_count: 1,
required: 3,
location: loc(4)
},
"assembly error at line 4, column 4 (byte 4): header declares r#1 \
registers but parameters and externs together require at least @2"
),
(
AssemblyError::RegisterOutOfBounds {
index: 5,
register_count: 1,
location: loc(5)
},
"assembly error at line 5, column 5 (byte 5): register @5 exceeds \
declared register count r#1"
),
(
AssemblyError::RollingRecordOutOfBounds {
index: 4,
rolling_record_count: 2,
location: loc(6)
},
"assembly error at line 6, column 6 (byte 6): rolling record ⚅4 \
exceeds declared rolling record count ⚅#2"
),
(
AssemblyError::RegisterGap {
index: 2,
register_count: 4,
location: loc(7)
},
"assembly error at line 7, column 7 (byte 7): register @2 is \
declared by r#4 but is never referenced (no gaps are permitted in \
the register file)"
),
(
AssemblyError::RollingRecordGap {
index: 1,
rolling_record_count: 3,
location: loc(8)
},
"assembly error at line 8, column 8 (byte 8): rolling record ⚅1 is \
declared by ⚅#3 but is never referenced (no gaps are permitted in \
the rolling record file)"
),
(
AssemblyError::FacelessCustomDice { location: loc(9) },
"assembly error at line 9, column 9 (byte 9): custom dice must have \
at least one face"
),
(
AssemblyError::DropSourceMismatch {
kind: DropKind::Lowest,
destination: 1,
source: 2,
location: loc(10)
},
"assembly error at line 10, column 10 (byte 10): drop lowest \
destination ⚅1 disagrees with its `from` source ⚅2"
),
(
AssemblyError::DropSourceMismatch {
kind: DropKind::Highest,
destination: 3,
source: 4,
location: loc(11)
},
"assembly error at line 11, column 11 (byte 11): drop highest \
destination ⚅3 disagrees with its `from` source ⚅4"
),
(
AssemblyError::UnexpectedRollingRecordOperand { location: loc(12) },
"assembly error at line 12, column 12 (byte 12): rolling record \
operand is not permitted here"
),
];
for (err, expected) in cases
{
let n = err.location().offset;
assert_eq!(
err.to_string(),
expected,
"Display mismatch for variant at loc {}",
n
);
assert_eq!(err.location(), loc(n), "location() mismatch for {:?}", err);
}
}
#[test]
fn test_reject_operand_register_out_of_bounds()
{
let e = assert_rejects(
"Function() r#1 ⚅#0\n\textern[]\n\t\
body:\n\t\t@0 <- @5 + 2\n\t\treturn @0\n"
);
let AssemblyError::RegisterOutOfBounds {
index,
register_count,
..
} = e
else
{
panic!("expected RegisterOutOfBounds, got: {:?}", e);
};
assert_eq!(index, 5);
assert_eq!(register_count, 1);
}
#[test]
fn test_assemble_integer_saturation()
{
let max = Assembler::assemble(
"Function() r#0 ⚅#0\n\textern[]\n\tbody:\n\t\t\
return 99999999999999\n"
)
.unwrap();
assert_eq!(
max.instructions[0],
Instruction::r#return(AddressingMode::Immediate(Immediate(i32::MAX)))
);
let min = Assembler::assemble(
"Function() r#0 ⚅#0\n\textern[]\n\tbody:\n\t\t\
return -99999999999999\n"
)
.unwrap();
assert_eq!(
min.instructions[0],
Instruction::r#return(AddressingMode::Immediate(Immediate(i32::MIN)))
);
}
#[test]
fn test_reject_blank_named_register()
{
let e = assert_rejects(
"Function(\u{A0}@0) r#1 ⚅#0\n\textern[]\n\tbody:\n\t\treturn @0\n"
);
assert!(
matches!(e, AssemblyError::Syntax { .. }),
"expected Syntax, got: {:?}",
e
);
}