Revised some names in parse::line and added some unit tests.

This commit is contained in:
Filipe Rodrigues 2021-04-29 12:06:25 +01:00
parent b572f1d922
commit c211a2fc86
5 changed files with 186 additions and 126 deletions

View File

@ -2,14 +2,15 @@
// Modules
pub mod error;
use std::str::FromStr;
#[cfg(test)]
pub mod test;
// Exports
pub use error::{ParseLineError, ReadArgError, ReadFuncError, ReadLiteralError, ReadNameError};
pub use error::{ParseArgError, ParseFuncError, ParseLineError, ParseLiteralError, ParseNameError};
// Imports
use crate::inst::Register;
use std::str::FromStr;
/// A line
#[derive(PartialEq, Clone, Debug)]
@ -26,7 +27,7 @@ impl Line {
pub fn parse(line: &str) -> Result<Self, ParseLineError> {
let mut line = line.trim();
// Read all labels and then the mnemonic
// Parse all labels and then the mnemonic
let mut labels = vec![];
let mnemonic = loop {
// If the line starts with a comment or is empty, return all labels
@ -34,8 +35,8 @@ impl Line {
return Ok(Self { labels, inst: None });
}
// Read a name
let (name, rest) = self::read_name(line)?;
// Parse a name
let (name, rest) = self::parse_name(line)?;
// Check the character after the name
let mut rest = rest.chars();
@ -60,7 +61,7 @@ impl Line {
},
// If we got a space or eof, we found the mnemonic.
// On a space, break and read arguments
// On a space, break and parse arguments
Some(' ') => {
line = rest.as_str().trim_start();
break name.to_owned();
@ -69,18 +70,18 @@ impl Line {
}
};
// Then read all arguments
// Then parse all arguments
let mut args = vec![];
loop {
// Read an argument
let (arg, rest) = self::read_arg(line)?;
// Parse an argument
let (arg, rest) = self::parse_arg(line)?;
args.push(arg);
// Check the character after the argument
let rest = rest.trim_start();
let mut rest = rest.chars();
match rest.next() {
// If we got ',', continue reading
// If we got ',', continue parsing
Some(',') => {
line = rest.as_str().trim_start();
continue;
@ -164,6 +165,54 @@ pub enum LineArgExpr {
},
}
impl LineArgExpr {
/// Parses an expression
pub fn parse(s: &str) -> Result<(LineArgExpr, &str), ParseArgError> {
let mut chars = s.char_indices();
match chars.next() {
// If it's numeric, 0..9 or '+' / '-', it's a simple literal
Some((_, '0'..='9' | '+' | '-')) => self::parse_literal(s)
.map(|(num, rest)| (LineArgExpr::Literal(num), rest))
.map_err(ParseArgError::Literal),
// If it starts with a label char, it's a label
Some((_, c)) if self::is_valid_first_name_char(c) => {
// Parse the label
let (label, rest) = self::parse_name(s).map_err(ParseArgError::Label)?;
// If there's a '+' after, parse an offset too
let (offset, rest) = match rest.strip_prefix('+') {
Some(rest) => self::parse_literal(rest)
.map(|(num, rest)| (Some(num), rest))
.map_err(ParseArgError::LabelOffset)?,
None => (None, rest),
};
// If there's a '@' after, parse a function too
let (func, rest) = match rest.strip_prefix('@') {
Some(rest) => self::parse_func(rest)
.map(|(func, rest)| (Some(func), rest))
.map_err(ParseArgError::LabelFunc)?,
None => (None, rest),
};
let label = LineArgExpr::Label {
label: label.to_owned(),
offset,
func,
};
Ok((label, rest))
},
// Else it's an invalid char
Some(_) => Err(ParseArgError::InvalidStartChar),
None => Err(ParseArgError::Empty),
}
}
}
/// Line label functions
#[allow(clippy::pub_enum_variant_names)] // We'll have other functions eventually
#[derive(PartialEq, Clone, Debug)]
@ -175,14 +224,14 @@ pub enum LineLabelFunc {
AddrHi,
}
/// Reads a name
fn read_name(s: &str) -> Result<(&str, &str), ReadNameError> {
/// Parses a name
pub fn parse_name(s: &str) -> Result<(&str, &str), ParseNameError> {
// Make sure the first character is valid
let mut chars = s.char_indices();
match chars.next() {
Some((_, c)) if self::is_valid_first_name_char(c) => (),
Some(_) => return Err(ReadNameError::StartChar),
None => return Err(ReadNameError::Empty),
Some(_) => return Err(ParseNameError::StartChar),
None => return Err(ParseNameError::Empty),
}
// Then keep consuming until we get a non-valid continuation character
@ -197,38 +246,38 @@ fn read_name(s: &str) -> Result<(&str, &str), ReadNameError> {
Ok((&s[..idx], &s[idx..]))
}
/// Reads an argument
fn read_arg(s: &str) -> Result<(LineArg, &str), ReadArgError> {
/// Parses an argument
pub fn parse_arg(s: &str) -> Result<(LineArg, &str), ParseArgError> {
let mut chars = s.char_indices();
match chars.next() {
// If we got '$', it's a register
Some((_, '$')) => self::read_reg(s).map(|(reg, rest)| (LineArg::Register(reg), rest)),
Some((_, '$')) => self::parse_reg(s).map(|(reg, rest)| (LineArg::Register(reg), rest)),
// If we got '"', it's a string
Some((_, '"')) => self::read_string(s).map(|(string, rest)| (LineArg::String(string), rest)),
Some((_, '"')) => self::parse_string(s).map(|(string, rest)| (LineArg::String(string), rest)),
// If we got '^', it's a mnemonic
Some((_, '^')) => self::read_name(chars.as_str())
Some((_, '^')) => self::parse_name(chars.as_str())
.map(|(name, rest)| (LineArg::Mnemonic(name.to_owned()), rest))
.map_err(ReadArgError::ReadLabel),
.map_err(ParseArgError::Label),
// Else try to read an expression
// Else try to parse an expression
Some(_) => {
// Read the expression
let (expr, rest) = self::read_expr(s)?;
// Parse the expression
let (expr, rest) = LineArgExpr::parse(s)?;
// Then check if we have a register
let rest = rest.trim_start();
match rest.strip_prefix('(') {
// If the rest starts with '(', read it as a register offset
// If the rest starts with '(', parse it as a register offset
Some(rest) => match rest.split_once(')') {
Some((reg, rest)) => {
// Parse the register
// If we have leftover tokens after reading it, return Err
// If we have leftover tokens after parsing it, return Err
let reg = reg.trim();
let (reg, reg_rest) = self::read_reg(reg)?;
let (reg, reg_rest) = self::parse_reg(reg)?;
if !reg_rest.is_empty() {
return Err(ReadArgError::RegisterOffsetLeftoverTokens);
return Err(ParseArgError::RegisterOffsetLeftoverTokens);
}
Ok((
@ -239,82 +288,38 @@ fn read_arg(s: &str) -> Result<(LineArg, &str), ReadArgError> {
rest,
))
},
None => Err(ReadArgError::MissingRegisterOffsetDelimiter),
None => Err(ParseArgError::MissingRegisterOffsetDelimiter),
},
None => Ok((LineArg::Expr(expr), rest)),
}
},
None => Err(ReadArgError::Empty),
None => Err(ParseArgError::Empty),
}
}
/// Reads an expression
pub fn read_expr(s: &str) -> Result<(LineArgExpr, &str), ReadArgError> {
let mut chars = s.char_indices();
match chars.next() {
// If it's numeric, 0..9 or '+' / '-', it's a simple literal
Some((_, '0'..='9' | '+' | '-')) => self::read_literal(s)
.map(|(num, rest)| (LineArgExpr::Literal(num), rest))
.map_err(ReadArgError::ReadLiteral),
// If it starts with a label char, it's a label
Some((_, c)) if self::is_valid_first_name_char(c) => {
// Read the label
let (label, rest) = self::read_name(s).map_err(ReadArgError::ReadLabel)?;
// If there's a '+' after, read an offset too
let (offset, rest) = match rest.strip_prefix('+') {
Some(rest) => self::read_literal(rest)
.map(|(num, rest)| (Some(num), rest))
.map_err(ReadArgError::ReadLabelOffset)?,
None => (None, rest),
};
// If there's a '@' after, read a function too
let (func, rest) = match rest.strip_prefix('@') {
Some(rest) => self::read_func(rest)
.map(|(func, rest)| (Some(func), rest))
.map_err(ReadArgError::ReadLabelFunc)?,
None => (None, rest),
};
let label = LineArgExpr::Label {
label: label.to_owned(),
offset,
func,
};
Ok((label, rest))
},
// Else it's an invalid char
Some(_) => Err(ReadArgError::InvalidStartChar),
None => Err(ReadArgError::Empty),
}
}
/// Reads a register
fn read_reg(s: &str) -> Result<(Register, &str), ReadArgError> {
/// Parse a register
pub fn parse_reg(s: &str) -> Result<(Register, &str), ParseArgError> {
match s.get(..3) {
Some(reg) => match Register::from_str(reg) {
Ok(reg) => Ok((reg, &s[3..])),
Err(()) => Err(ReadArgError::UnknownRegister),
Err(()) => Err(ParseArgError::UnknownRegister),
},
None => Err(ReadArgError::ExpectedRegister),
None => Err(ParseArgError::ExpectedRegister),
}
}
/// Reads a func
fn read_func(s: &str) -> Result<(LineLabelFunc, &str), ReadFuncError> {
/// Parses a func
pub fn parse_func(s: &str) -> Result<(LineLabelFunc, &str), ParseFuncError> {
None.or_else(|| s.strip_prefix("addr_hi").map(|rest| (LineLabelFunc::AddrHi, rest)))
.or_else(|| s.strip_prefix("addr_lo").map(|rest| (LineLabelFunc::AddrLo, rest)))
.ok_or(ReadFuncError::Unknown)
.ok_or(ParseFuncError::Unknown)
}
/// Reads a string
fn read_string(s: &str) -> Result<(String, &str), ReadArgError> {
/// Parses a string
///
/// # Panics if `s[0]` isn't '"'.
pub fn parse_string(s: &str) -> Result<(String, &str), ParseArgError> {
let mut is_escaping = false;
let mut in_multi_escape = false;
let mut chars = s.char_indices();
@ -344,7 +349,7 @@ fn read_string(s: &str) -> Result<(String, &str), ReadArgError> {
let (string, rest) = s.split_at(idx + 1);
// Note: For whatever reason 'snailquote' requires the quotes to be included in `string`
let string = snailquote::unescape(string).map_err(ReadArgError::UnescapeString)?;
let string = snailquote::unescape(string).map_err(ParseArgError::UnescapeString)?;
break Ok((string, rest));
},
@ -352,13 +357,13 @@ fn read_string(s: &str) -> Result<(String, &str), ReadArgError> {
// Else just continue
Some(_) => continue,
None => break Err(ReadArgError::MissingClosingDelimiterString),
None => break Err(ParseArgError::MissingClosingDelimiterString),
};
}
}
/// Reads a literal from a string and returns the rest
fn read_literal(s: &str) -> Result<(i64, &str), ReadLiteralError> {
/// Parses a literal from a string and returns the rest
pub fn parse_literal(s: &str) -> Result<(i64, &str), ParseLiteralError> {
// Check if it's negative
let (is_neg, num) = match s.chars().next() {
Some('+') => (false, &s[1..]),
@ -390,7 +395,7 @@ fn read_literal(s: &str) -> Result<(i64, &str), ReadLiteralError> {
};
// Parse it
let num = i64::from_str_radix(num, base).map_err(ReadLiteralError::Parse)?;
let num = i64::from_str_radix(num, base).map_err(ParseLiteralError::Parse)?;
let num = match is_neg {
true => -num,
false => num,
@ -399,12 +404,14 @@ fn read_literal(s: &str) -> Result<(i64, &str), ReadLiteralError> {
Ok((num, rest))
}
/// Returns if `c` is a valid mnemonic first character
/// Returns if `c` is a valid name first character
#[must_use]
fn is_valid_first_name_char(c: char) -> bool {
c.is_ascii_alphabetic() || ['.', '_'].contains(&c)
}
/// Returns if `c` is a valid mnemonic continuation character
/// Returns if `c` is a valid name continuation character
#[must_use]
fn is_valid_cont_name_char(c: char) -> bool {
c.is_ascii_alphanumeric() || ['.', '_'].contains(&c)
}

View File

@ -4,28 +4,28 @@ use snailquote::UnescapeError;
/// Error for [`Line::parse`](super::Line::parse)
#[derive(Debug, thiserror::Error)]
#[derive(PartialEq, Debug, thiserror::Error)]
pub enum ParseLineError {
/// Unable to read name
/// Unable to parse name
#[error("Expected name")]
ReadName(#[from] ReadNameError),
ParseName(#[from] ParseNameError),
/// Invalid name suffix
#[error("Invalid name suffix")]
InvalidNameSuffix,
/// Unable to read argument
/// Unable to parse argument
#[error("Expected argument")]
ReadArg(#[from] ReadArgError),
ParseArg(#[from] ParseArgError),
/// Invalid argument suffix
#[error("Invalid argument suffix")]
InvalidArgSuffix,
}
/// Name reading error
#[derive(Debug, thiserror::Error)]
pub enum ReadNameError {
/// Name parsing error
#[derive(PartialEq, Clone, Debug, thiserror::Error)]
pub enum ParseNameError {
/// Name was empty
#[error("Name was empty")]
Empty,
@ -35,25 +35,25 @@ pub enum ReadNameError {
StartChar,
}
/// Literal reading error
#[derive(Debug, thiserror::Error)]
pub enum ReadLiteralError {
/// Literal parsing error
#[derive(PartialEq, Clone, Debug, thiserror::Error)]
pub enum ParseLiteralError {
/// Parse
#[error("Unable to parse literal")]
Parse(#[from] std::num::ParseIntError),
}
/// Func reading error
#[derive(Debug, thiserror::Error)]
pub enum ReadFuncError {
/// Func parsing error
#[derive(PartialEq, Clone, Debug, thiserror::Error)]
pub enum ParseFuncError {
/// Parse
#[error("Unknown functions")]
Unknown,
}
/// Argument reading error
#[derive(Debug, thiserror::Error)]
pub enum ReadArgError {
/// Argument parsing error
#[derive(PartialEq, Debug, thiserror::Error)]
pub enum ParseArgError {
/// Empty
#[error("Argument was empty")]
Empty,
@ -62,25 +62,25 @@ pub enum ReadArgError {
#[error("Invalid starting char")]
InvalidStartChar,
/// Read Literal
#[error("Unable to read literal")]
ReadLiteral(#[source] ReadLiteralError),
/// Parse Literal
#[error("Unable to parse literal")]
Literal(#[source] ParseLiteralError),
/// Read mnemonic
#[error("Unable to read mnemonic")]
ReadMnemonic(#[source] ReadNameError),
/// Parse mnemonic
#[error("Unable to parse mnemonic")]
ParseMnemonic(#[source] ParseNameError),
/// Read label
#[error("Unable to read label")]
ReadLabel(#[source] ReadNameError),
/// Parse label
#[error("Unable to parse label")]
Label(#[source] ParseNameError),
/// Read label offset
#[error("Unable to read label offset")]
ReadLabelOffset(#[source] ReadLiteralError),
/// Parse label offset
#[error("Unable to parse label offset")]
LabelOffset(#[source] ParseLiteralError),
/// Read label func
#[error("Unable to read label func")]
ReadLabelFunc(#[source] ReadFuncError),
/// Parse label func
#[error("Unable to parse label func")]
LabelFunc(#[source] ParseFuncError),
/// Expected register
#[error("Expected register")]

View File

@ -0,0 +1,47 @@
//! Tests
// Imports
use super::*;
#[test]
fn test_parse_literal() {
// Oks
assert_matches!(self::parse_literal("0"), Ok((0, "")));
assert_matches!(self::parse_literal("123"), Ok((123, "")));
assert_matches!(self::parse_literal("123abc"), Ok((123, "abc")));
assert_matches!(self::parse_literal("+1"), Ok((1, "")));
assert_matches!(self::parse_literal("-1"), Ok((-1, "")));
assert_matches!(self::parse_literal("0x100"), Ok((0x100, "")));
assert_matches!(self::parse_literal("0b100"), Ok((0b100, "")));
assert_matches!(self::parse_literal("0o100"), Ok((0o100, "")));
assert_matches!(self::parse_literal("-0x100"), Ok((-0x100, "")));
assert_matches!(self::parse_literal("-0b100"), Ok((-0b100, "")));
assert_matches!(self::parse_literal("-0o100"), Ok((-0o100, "")));
assert_matches!(self::parse_literal("0x123abc"), Ok((0x123abc, "")));
assert_matches!(self::parse_literal("0b123abc"), Ok((0b1, "23abc")));
assert_matches!(self::parse_literal("0o123abc"), Ok((0o123, "abc")));
// Errors
assert_matches!(self::parse_literal(""), Err(ParseLiteralError::Parse(_)));
assert_matches!(self::parse_literal("abc"), Err(ParseLiteralError::Parse(_)));
assert_matches!(self::parse_literal("0xg"), Err(ParseLiteralError::Parse(_)));
assert_matches!(self::parse_literal("0b2"), Err(ParseLiteralError::Parse(_)));
assert_matches!(self::parse_literal("0o8"), Err(ParseLiteralError::Parse(_)));
}
#[test]
fn test_parse_string() {
// Oks
assert_eq!(
self::parse_string(r#""Hello,\n World""#),
Ok(("Hello,\n World".to_owned(), ""))
);
}
#[test]
#[should_panic]
fn parse_string_panic() {
match self::parse_string("a") {
Ok(_) | Err(_) => (),
}
}

View File

@ -17,7 +17,7 @@ mod cli;
// Imports
use anyhow::Context;
use dcb_exe::{
inst::{parse::line, DisplayCtx, Inst, InstDisplay, InstFmtArg, ParseCtx},
inst::{parse::LineArgExpr, DisplayCtx, Inst, InstDisplay, InstFmtArg, ParseCtx},
reader::iter::ExeItem,
ExeReader, Func, Pos,
};
@ -248,7 +248,7 @@ pub fn inst_display<'a>(
// Validator
let validate = || -> Result<(), anyhow::Error> {
// Parse the override
let (expr, rest) = line::read_expr(&value).context("Unable to parse override")?;
let (expr, rest) = LineArgExpr::parse(&value).context("Unable to parse override")?;
let rest = rest.trim_start();
if !rest.is_empty() {

View File

@ -404,6 +404,12 @@
start_pos: 0x80049e80
end_pos: 0x80049ef8
- name: something42
start_pos: 0x8004b2f0
end_pos: 0x8004b394
inline_comments:
0x8004b2f0: "Division by 0"
# A functions
- name: InitHeap
signature: "fn(addr: *u32, size: u32)"