aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/parser.rs
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-24 16:48:16 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-01-07 16:38:54 +0100
commit9b5e5aa6d36a550bed84f85df8e1e46fcf9765e1 (patch)
tree9748bd1059bb24fd5fb204ff9d91ae7531632beb /lib/xixanta/src/parser.rs
parentf3c6b02fb2f2957b9b896c34d05abe75263c3f60 (diff)
downloadtools.nes-9b5e5aa6d36a550bed84f85df8e1e46fcf9765e1.tar.gz
tools.nes-9b5e5aa6d36a550bed84f85df8e1e46fcf9765e1.zip
Add the .repeat control statement
This is a control statement which acts similarly as .proc/.macro/.scope, in which an inner block is allocated for it. Hence, all the previous work from 1f8a6becc7cd ("parser: Implement block bodies") and ec8b709fa24c ("Implement block bodies inside of the assembler") make this one out possible, as .repeat statements don't have an identifier that can be used for hashing. From the parser perspective this introduction raises two new things. First of all this control statement also needed a differentiation between the amount of required arguments, and the allowed ones, since there is a second optional argument to it. And second, even the identifier is not given, we have to generate one so to add a context for it. This was at first not needed, but introducing .repeat-only variables means that we have to have inner contexts which need to be named somehow so we can retrieve the context later when picking up the value for them again. This last thing brought the need for a new dependency: rand. This is used to generate a random string to identify the .repeat block. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src/parser.rs')
-rw-r--r--lib/xixanta/src/parser.rs98
1 files changed, 94 insertions, 4 deletions
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index 8429c2e..c171624 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -1,6 +1,7 @@
use crate::errors::ParseError;
use crate::node::{NodeBodyType, NodeType, OperationType, PNode, PString};
use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS};
+use rand::distributions::{Alphanumeric, DistString};
use std::cmp::Ordering;
use std::io::{self, BufRead, Read};
@@ -82,8 +83,10 @@ impl Parser {
}
}
+ /// Returns the first layer of nodes that have been parsed. Note that this
+ /// function only makes sense to be called whenever parsing is done,
+ /// otherwise results will be incomplete in (most probably) unexpected ways.
pub fn nodes(&self) -> Vec<PNode> {
- // println!("{:#?}", self.nodes);
self.nodes.first().unwrap().to_vec()
}
@@ -916,6 +919,11 @@ impl Parser {
}
}
+ // Generate a unique identifier with the given prefix.
+ fn unique_identifier(&self, prefix: String) -> String {
+ prefix + &String::from("-") + &Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
+ }
+
// Returns a NodeType::Control node with whatever could be parsed
// considering the given `id` and rest of the `line`.
fn parse_control(&mut self, id: PString, line: &str) -> Result<PNode, ParseError> {
@@ -933,11 +941,21 @@ impl Parser {
// If this control function has an identifier (e.g. `.macro
// Identifier(args...)`), let's parse it now.
- if control.has_identifier {
+ if control.has_identifier.is_some() {
self.skip_whitespace(line);
left = Some(Box::new(PNode {
node_type: NodeType::Value,
- value: self.parse_identifier(line)?.0,
+ // The identifier is actually there or does it have to be generated?
+ value: if control.has_identifier.unwrap() {
+ PString {
+ value: self.unique_identifier(control.control_type.to_string()),
+ line: self.line,
+ start: id.start,
+ end: id.end,
+ }
+ } else {
+ self.parse_identifier(line)?.0
+ },
left: None,
right: None,
args: None,
@@ -950,7 +968,7 @@ impl Parser {
// required by the function.
let args = self.parse_arguments(line)?;
if let Some(args_required) = control.required_args {
- if args.len() != args_required {
+ if args.len() < args_required.0 || args.len() > args_required.1 {
return Err(self.parser_error(
format!("wrong number of arguments for function '{}'", id.value).as_str(),
));
@@ -2153,6 +2171,78 @@ inc $20
}
#[test]
+ fn parse_repeat_control() {
+ let mut parser = Parser::default();
+ let err = parser.parse(".repeat\n.endrepeat".as_bytes()).unwrap_err();
+ assert_eq!(
+ err.first().unwrap().message,
+ "wrong number of arguments for function '.repeat'"
+ );
+
+ let mut parser = Parser::default();
+ let err = parser
+ .parse(".repeat 1, 2, 3\n.endrepeat".as_bytes())
+ .unwrap_err();
+ assert_eq!(
+ err.first().unwrap().message,
+ "wrong number of arguments for function '.repeat'"
+ );
+
+ // Minimum required argument.
+
+ parser = Parser::default();
+ let mut line = ".repeat 2\n.endrepeat";
+
+ assert!(parser.parse(line.as_bytes()).is_ok());
+ let mut control = parser.nodes.last().unwrap().first().unwrap();
+ assert_node(
+ control,
+ NodeType::Control(ControlType::StartRepeat),
+ line,
+ ".repeat",
+ );
+ assert!(control
+ .left
+ .as_ref()
+ .unwrap()
+ .value
+ .value
+ .starts_with(".repeat-"));
+ assert!(control.right.is_some());
+
+ let mut args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 1);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "2");
+
+ // Maximum allowed arguments.
+
+ parser = Parser::default();
+ line = ".repeat 2, I\n.endrepeat";
+
+ assert!(parser.parse(line.as_bytes()).is_ok());
+ control = parser.nodes.last().unwrap().first().unwrap();
+ assert_node(
+ control,
+ NodeType::Control(ControlType::StartRepeat),
+ line,
+ ".repeat",
+ );
+ assert!(control
+ .left
+ .as_ref()
+ .unwrap()
+ .value
+ .value
+ .starts_with(".repeat-"));
+ assert!(control.right.is_some());
+
+ args = control.args.clone().unwrap();
+ assert_eq!(args.len(), 2);
+ assert_node(args.first().unwrap(), NodeType::Value, line, "2");
+ assert_node(args.last().unwrap(), NodeType::Value, line, "I");
+ }
+
+ #[test]
fn parse_unknown_control() {
let mut parser = Parser::default();
let mut err = parser.parse(".".as_bytes()).unwrap_err();