aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/src/parser.rs86
1 files changed, 67 insertions, 19 deletions
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index 322bd76..d88a8a2 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -159,7 +159,7 @@ impl Parser {
// followed by more code. Hence, push it first, then fetch the next
// identifier and finally fall through.
self.offset = 0;
- let (mut id, mut nt) = self.parse_identifier(l)?;
+ let (mut id, mut nt) = self.parse_identifier(l, true)?;
if nt == NodeType::Label {
self.nodes.last_mut().unwrap().push(PNode {
node_type: nt,
@@ -181,7 +181,7 @@ impl Parser {
// The label is followed by a statement. Let's parse the identifier
// for it and fall through.
self.offset = 0;
- (id, nt) = self.parse_identifier(l)?;
+ (id, nt) = self.parse_identifier(l, true)?;
if nt == NodeType::Label {
return Err(self
.parser_error("cannot have multiple labels at the same location")
@@ -232,15 +232,16 @@ impl Parser {
}
}
- // Given a `line` parses an identifier if possible. This identifier is not
- // necessary an "identifier" per se, but rather a first identifier-like
- // string which can be used to determine which kind of expression we are
- // dealing with. Returns a PString representing this identifier on success,
+ // Given a `line` parses an identifier if possible. This identifier can
+ // optionally allow a '.' character at the first position if `dot` is set to
+ // true. This is usually a good idea if we can have a label or a control
+ // statement. Returns a PString representing this identifier on success,
// plus a hint on whether the identifier belongs to a label or not.
- fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType), Error> {
+ fn parse_identifier(&mut self, line: &str, dot: bool) -> Result<(PString, NodeType), Error> {
let start = self.column;
let base_offset = self.offset;
let mut nt = NodeType::Value;
+ let mut first_seen = false;
// For the general case we just need to iterate until a whitespace
// character or an inline comment is found. Then our PString object is
@@ -254,6 +255,12 @@ impl Parser {
.chars()
.peekable();
while let Some(c) = chars.next() {
+ // Error out if '.' is not allowed from now on.
+ if c == '.' && (!dot || first_seen) {
+ return Err(self.parser_error("cannot have a '.' in this context"));
+ }
+ first_seen = true;
+
// Check for the end of the identifier. For this, it's easier to
// simply list what is allowed and negate it.
if !(c.is_ascii_alphanumeric() || c == '@' || c == '_' || c == '.') {
@@ -352,6 +359,9 @@ impl Parser {
// The value of the identifier is whatever we have picked up
// along the parsing.
let value = String::from(line.get(base_offset..self.offset).unwrap_or("").trim());
+ if value == "." {
+ return Err(self.parser_error("empty identifier"));
+ }
// The end of the identifier range has to be shortened for
// regular labels because we want to ignore to extra ':'
@@ -382,6 +392,9 @@ impl Parser {
// The line is merely the identifier (e.g. instruction with implied
// addressing).
let id = String::from(line.get(base_offset..).unwrap_or_default().trim());
+ if id == "." {
+ return Err(self.parser_error("empty identifier"));
+ }
Ok((
PString {
value: id,
@@ -550,12 +563,13 @@ impl Parser {
// Parse the given `line` as an assignment statement which declares a
// variable at `id`.
fn parse_assignment(&mut self, line: &str, id: PString) -> Result<(), Error> {
- // Notice that `parse_identifier` pretty much swallows any kind of
- // identifier without doing any sanity checks. Now it's the time to do
- // so.
+ // Ensure that the parsed identifier is a valid one on assignments.
if let Err(msg) = id.is_valid_identifier(false) {
return Err(self.parser_error(&msg));
}
+ if id.value.contains('.') {
+ return Err(self.parser_error("'.' characters are not allowed for variables"));
+ }
// Skip the '=' sign and any possible whitespaces.
self.next();
@@ -1210,7 +1224,7 @@ impl Parser {
if first == '(' {
self.extract_paren_expression(line, level)
} else if first == '.' {
- let (id, _) = self.parse_identifier(line)?;
+ let (id, _) = self.parse_identifier(line, true)?;
self.parse_control(id, line, level)
} else if first == '\'' {
self.parse_char(line)
@@ -1248,7 +1262,7 @@ impl Parser {
// Like `parse_identifier` but it returns an error if a label was found.
fn parse_value(&mut self, line: &str) -> Result<PNode, Error> {
- let (id, nt) = self.parse_identifier(line)?;
+ let (id, nt) = self.parse_identifier(line, true)?;
if id.is_empty() {
return Err(self.parser_error("invalid identifier"));
}
@@ -1342,7 +1356,14 @@ impl Parser {
let control = match CONTROL_FUNCTIONS.get(&id.value.to_lowercase()) {
Some(control) => control,
None => {
- return Err(self.parser_error(format!("unknown function '{}'", id.value).as_str()))
+ return Ok(PNode {
+ node_type: NodeType::Value,
+ value: id,
+ left: None,
+ right: None,
+ args: None,
+ source: self.current_source,
+ });
}
};
@@ -1361,7 +1382,7 @@ impl Parser {
end: id.end,
}
} else {
- self.parse_identifier(line)?.0
+ self.parse_identifier(line, false)?.0
},
left: None,
right: None,
@@ -1661,6 +1682,25 @@ mod tests {
assert_node(nodes.last().unwrap(), NodeType::Instruction, line, "dex")
}
+ #[test]
+ fn dot_label() {
+ let line = ".L1: dex";
+
+ let mut parser = Parser::default();
+ assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok());
+
+ let nodes = parser.nodes();
+ assert_eq!(nodes.len(), 2);
+
+ // Label.
+ assert_eq!(nodes.first().unwrap().value.value, ".L1");
+ assert_eq!(nodes.first().unwrap().value.start, 0);
+ assert_eq!(nodes.first().unwrap().value.end, 3);
+
+ // Instruction
+ assert_node(nodes.last().unwrap(), NodeType::Instruction, line, "dex")
+ }
+
// Literals
#[test]
@@ -2196,6 +2236,15 @@ mod tests {
parser = Parser::default();
err = parser
+ .parse(".var = 1".as_bytes(), SourceInfo::default())
+ .unwrap_err();
+ assert_eq!(
+ err.first().unwrap().message,
+ "'.' characters are not allowed for variables"
+ );
+
+ parser = Parser::default();
+ err = parser
.parse("var =".as_bytes(), SourceInfo::default())
.unwrap_err();
assert_eq!(err.first().unwrap().message, "incomplete assignment");
@@ -2860,16 +2909,15 @@ inc $20
#[test]
fn parse_unknown_control() {
let mut parser = Parser::default();
- let mut err = parser
+ let err = parser
.parse(".".as_bytes(), SourceInfo::default())
.unwrap_err();
- assert_eq!(err.first().unwrap().message, "unknown function '.'");
+ assert_eq!(err.first().unwrap().message, "empty identifier");
parser = Parser::default();
- err = parser
+ assert!(parser
.parse(".whatever".as_bytes(), SourceInfo::default())
- .unwrap_err();
- assert_eq!(err.first().unwrap().message, "unknown function '.whatever'");
+ .is_ok());
}
// Macro calls.