aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta/src')
-rw-r--r--lib/xixanta/src/node.rs13
-rw-r--r--lib/xixanta/src/parser.rs229
2 files changed, 223 insertions, 19 deletions
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index a09568c..4281c4a 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -249,6 +249,12 @@ pub enum OperationType {
Greater,
}
+#[derive(Debug, Clone, PartialEq)]
+pub enum CommentType {
+ AsanReserve(u8),
+ AsanWeak,
+}
+
/// The PNode type.
#[derive(Debug, Clone, PartialEq)]
pub enum NodeType {
@@ -299,6 +305,9 @@ pub enum NodeType {
/// A operation expression. If the operation has two sides, then the left
/// and right arms contain the operands, otherwise only the right one.
Operation(OperationType),
+
+ /// A command which is relevant for the current session.
+ Comment(CommentType),
}
impl fmt::Display for NodeType {
@@ -338,6 +347,10 @@ impl fmt::Display for NodeType {
OperationType::Less => write!(f, "less"),
OperationType::Greater => write!(f, "greater"),
},
+ NodeType::Comment(ct) => match ct {
+ CommentType::AsanReserve(_) => write!(f, ";; asan:reserve"),
+ CommentType::AsanWeak => write!(f, ";; asan:weak"),
+ },
}
}
}
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index ba56f11..c40e0d1 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -1,10 +1,18 @@
-use crate::node::{ControlType, NodeBodyType, NodeType, OperationType, PNode, PString};
+use crate::node::{
+ CommentType, ControlType, NodeBodyType, NodeType, OperationType, PNode, PString,
+};
use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
use crate::{Error, SourceInfo};
use std::cmp::Ordering;
use std::io::{self, BufRead, Read};
use std::path;
+// Kinds of whitespace stops that can be found by `skip_whitespace`.
+enum Whitespace {
+ Empty,
+ Comment,
+}
+
/// The Parser struct holds basic data for the current parsing session.
#[derive(Default)]
pub struct Parser {
@@ -134,16 +142,125 @@ impl Parser {
self.nodes.first().unwrap().to_vec()
}
+ // Handle the comment at the `init` offset for the given `line`. That is,
+ // check on whether there is a special directive which needs to be treated
+ // differently.
+ fn handle_comment(&mut self, line: &str, init: usize) -> Result<(), Vec<Error>> {
+ let mut offset = init;
+ for c in line.get(offset..).unwrap_or("").chars() {
+ if c != ';' && !c.is_whitespace() {
+ break;
+ }
+ offset += 1;
+ }
+
+ let start = offset;
+ let mut cmd = String::from("");
+ for c in line.get(offset..).unwrap_or("").chars() {
+ if c.is_whitespace() {
+ break;
+ }
+ cmd.push(c);
+ offset += 1;
+ }
+
+ match cmd.as_str() {
+ "asan:reserve" => {
+ // Skip whitespaces.
+ for c in line.get(offset..).unwrap_or("").chars() {
+ if c != ';' && !c.is_whitespace() {
+ break;
+ }
+ offset += 1;
+ }
+
+ // Fetch the argument.
+ let mut arg = String::from("");
+ for c in line.get(offset..).unwrap_or("").chars() {
+ if c.is_whitespace() {
+ break;
+ }
+ arg.push(c);
+ }
+
+ // Argument validation.
+ if !arg.starts_with('$') || arg.len() > 3 {
+ return Err(Error {
+ line: self.line,
+ global: false,
+ source: self.sources[self.current_source].clone(),
+ message: "expecting a byte formatted with a leading '$' sign".to_string(),
+ }
+ .into());
+ }
+ let Ok(val) = u8::from_str_radix(arg.get(1..).unwrap_or("00"), 16) else {
+ return Err(Error {
+ line: self.line,
+ global: false,
+ source: self.sources[self.current_source].clone(),
+ message: "could not parse asan:reserve number".to_string(),
+ }
+ .into());
+ };
+ if val < 2 {
+ return Err(Error {
+ line: self.line,
+ global: false,
+ source: self.sources[self.current_source].clone(),
+ message: "bad asan:reserve number, should be higher than $01".to_string(),
+ }
+ .into());
+ }
+
+ // Push whatever was parsed.
+ self.nodes.last_mut().unwrap().push(PNode {
+ node_type: NodeType::Comment(CommentType::AsanReserve(val)),
+ value: PString {
+ value: cmd,
+ line: self.line,
+ start,
+ end: offset - 1,
+ },
+ left: None,
+ right: None,
+ args: None,
+ source: self.current_source,
+ });
+ }
+ "asan:weak" => {
+ self.nodes.last_mut().unwrap().push(PNode {
+ node_type: NodeType::Comment(CommentType::AsanWeak),
+ value: PString {
+ value: cmd,
+ line: self.line,
+ start,
+ end: offset - 1,
+ },
+ left: None,
+ right: None,
+ args: None,
+ source: self.current_source,
+ });
+ }
+ &_ => {}
+ }
+
+ Ok(())
+ }
+
// Parse a single `line` and push the parsed nodes into `self.nodes`.
fn parse_line(&mut self, line: &str) -> Result<(), Vec<Error>> {
self.column = 0;
self.offset = 0;
// Skip until the first non-whitespace character. If that's not
- // possible, then it's an empty line and we can return early.
- if !self.skip_whitespace(line) {
- return Ok(());
- }
+ // possible, then it's an empty line and we can return early. If the
+ // line is a comment, check if there are any special statements in it.
+ match self.skip_whitespace(line) {
+ Some(Whitespace::Empty) => return Ok(()),
+ Some(Whitespace::Comment) => return self.handle_comment(line, self.offset),
+ None => {}
+ };
// Let's pin point the last character we need to care for parsing. This
// can be either the start position of an inline comment (i.e. ';'), or
@@ -200,7 +317,8 @@ impl Parser {
}
// Returns the index to the absolute end of the semantic line (i.e.
- // everything before the end of the string or an inline comment).
+ // everything before the end of the string or an inline comment). If there
+ // was an inline comment, it will also call `handle_comment` for it.
fn find_line_end(&mut self, line: &str) -> Result<usize, Error> {
// If there is no semicolon, then we just return the end of the line.
let Some(sc) = line.find(';') else {
@@ -210,12 +328,18 @@ impl Parser {
// If there is no double quote, then the semicolon that we found is
// really an inline comment.
let Some(quote) = line.find('"') else {
+ if let Err(errors) = self.handle_comment(line, sc) {
+ return Err(errors.first().unwrap().clone());
+ }
return Ok(sc);
};
// If the quote character is after the semicolon, then it's inside of
// the comment.
if quote > sc {
+ if let Err(errors) = self.handle_comment(line, sc) {
+ return Err(errors.first().unwrap().clone());
+ }
return Ok(sc);
}
@@ -233,6 +357,9 @@ impl Parser {
if end_quote > sc {
Ok(end_quote)
} else {
+ if let Err(errors) = self.handle_comment(line, sc) {
+ return Err(errors.first().unwrap().clone());
+ }
Ok(sc)
}
}
@@ -303,7 +430,7 @@ impl Parser {
global: false,
source: self.sources[self.current_source].clone(),
message: "you cannot have a relative label inside of an identifier".to_string(),
- });
+ });
}
// Skip the ':' character for the next loop.
@@ -753,9 +880,7 @@ impl Parser {
};
if *node_type != expected_close {
return Err(self
- .parser_error(
- format!("expecting '{expected_close}', found '{node_type}'").as_str(),
- )
+ .parser_error(format!("expecting '{expected_close}', found '{node_type}'").as_str())
.into());
}
@@ -810,9 +935,7 @@ impl Parser {
line: node.value.line,
global: false,
source: current_source.clone(),
- message: format!(
- "could not find out the parent directory for file '{file_path}'"
- ),
+ message: format!("could not find out the parent directory for file '{file_path}'"),
}
.into());
};
@@ -1572,24 +1695,25 @@ impl Parser {
// Advances `self.column` and `self.offset` until a non-whitespace character
// is found. Note that the initial index is bound to `self.offset`. Returns
- // false if the line can be skipped entirely, true otherwise.
- fn skip_whitespace(&mut self, line: &str) -> bool {
+ // None if a non-whitespace character could be found, otherwise a Whitespace
+ // value on what kind of whitespace made the parsing stop.
+ fn skip_whitespace(&mut self, line: &str) -> Option<Whitespace> {
if line.is_empty() {
- return false;
+ return Some(Whitespace::Empty);
}
for c in line.get(self.offset..).unwrap_or("").chars() {
if !c.is_whitespace() {
if c == ';' {
- return false;
+ return Some(Whitespace::Comment);
}
- return true;
+ return None;
}
self.next();
}
- true
+ None
}
// Increment `self.column` and `self.offset` by one.
@@ -3113,4 +3237,71 @@ inc $20
assert_node(args.last().unwrap(), NodeType::Value, line, "arg2");
}
}
+
+ #[test]
+ fn parse_comment_asan_reserve() {
+ let code = r#";; asan:reserve $02
+VAR = $00
+
+VAR2 = $02 ;; asan:reserve $03
+"#;
+ let mut parser = Parser::default();
+ assert!(parser.parse(code.as_bytes(), SourceInfo::default()).is_ok());
+
+ let nodes = parser.nodes();
+ assert_eq!(nodes.len(), 4);
+
+ assert_node(
+ nodes.first().unwrap(),
+ NodeType::Comment(CommentType::AsanReserve(2)),
+ code,
+ "asan:reserve",
+ );
+
+ // Yeah, `assert_node` is bad at multi-line cases, let's do it
+ // manually...
+ let last = nodes.get(2).unwrap();
+ assert_eq!(
+ last.node_type,
+ NodeType::Comment(CommentType::AsanReserve(3))
+ );
+ assert_eq!(last.value.line, 3);
+ assert_eq!(last.value.start, 14);
+ assert_eq!(last.value.end, 26);
+ assert_eq!(last.value.value.as_str(), "asan:reserve");
+ }
+
+ #[test]
+ fn parse_comment_asan_reserve_no_hex() {
+ let code = r#";; asan:reserve 02
+;; asan:reserve $00
+;; asan:reserve $1
+;; asan:reserve $123
+"#;
+
+ let mut parser = Parser::default();
+ let res = parser.parse(code.as_bytes(), SourceInfo::default());
+
+ assert!(res.is_err());
+
+ let errors = res.unwrap_err();
+ assert_eq!(errors.len(), 4);
+
+ assert_eq!(
+ errors.first().unwrap().message,
+ "expecting a byte formatted with a leading '$' sign"
+ );
+ assert_eq!(
+ errors.get(1).unwrap().message,
+ "bad asan:reserve number, should be higher than $01"
+ );
+ assert_eq!(
+ errors.get(2).unwrap().message,
+ "bad asan:reserve number, should be higher than $01"
+ );
+ assert_eq!(
+ errors.last().unwrap().message,
+ "expecting a byte formatted with a leading '$' sign"
+ );
+ }
}