From abc17c7c3c01210f846d1ab543d2c56f0a433217 Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Sat, 27 May 2023 20:34:01 -0700 Subject: [PATCH] Implement rudimentary control flow --- demo/demo.rpy | 27 +++++++------- renrs/src/lib.rs | 90 ++++++++++++++++++++++++++++++++++++++-------- renrs/src/rpy.pest | 6 ++-- 3 files changed, 93 insertions(+), 30 deletions(-) diff --git a/demo/demo.rpy b/demo/demo.rpy index 99c42ad..d5f20ac 100644 --- a/demo/demo.rpy +++ b/demo/demo.rpy @@ -1,13 +1,14 @@ -"Bob sat on the bench." -"Bob" "Good morning!" - "Bob" "I am in a block now!" - "Bob" "Isn't this cool?" -"Bob" "And now we're back in normal indentation." - "Bob" "We can even go multiple levels in!" - "Bob" "How far does the rabbit hole go?" - "Bob" "rabbit hole go?" - "Bob" "go?" -"Bob" "Not sure what came of me there." -"Bob" "I suppose I should eat a potato." -eat "potato" True -"Bob" "Yum!" \ No newline at end of file +# Control testing +"Bob" "I will not say anything" +if False: + "Bob" "W-what? Why am I saying this?" + if True: + "Testing 123" +"..." +"Bob" "Good." +"Bob" "Now I will tell you my favorite food..." +if True: + "Bob" "...cereal!" + if False: + "Bob" "But I actually hate it." +"Bob sat on the bench." \ No newline at end of file diff --git a/renrs/src/lib.rs b/renrs/src/lib.rs index 4351603..f9df96b 100644 --- a/renrs/src/lib.rs +++ b/renrs/src/lib.rs @@ -20,6 +20,7 @@ enum Token { impl Token { fn print(&self) -> &str { + use Token::*; match &self { Keyword(keyword) => keyword, Str(_) => "String", @@ -30,11 +31,10 @@ impl Token { } } -use Token::*; - // Indented command block #[derive(Debug)] struct CommandBlock { + control: Option, elements: Vec, next: Option, } @@ -45,6 +45,9 @@ impl CommandBlock { Some(next) => next, None => return None, }; + if !self.evaluate_control() { + return None; + } let mut result = None; let count = self.elements.len(); for element in &mut self.elements[next..] { @@ -74,11 +77,19 @@ impl CommandBlock { }; result } + + fn evaluate_control(&self) -> bool { + use Control::*; + self.control.as_ref().map_or(true, |control| match control { + If { condition } => *condition, + }) + } } impl Default for CommandBlock { fn default() -> Self { Self { + control: None, elements: Vec::new(), next: Some(0), } @@ -93,6 +104,12 @@ enum BlockElement { type Script = CommandBlock; +#[derive(Debug)] +#[allow(dead_code)] +enum Control { + If { condition: bool }, +} + // Parsed script commands #[derive(Debug)] #[allow(dead_code)] @@ -101,10 +118,9 @@ enum Command { Eat { food: String, politely: bool }, } -use Command::*; - impl Command { fn is_blocking(&self) -> bool { + use Command::*; match self { Say { .. } => true, _ => false, @@ -139,22 +155,66 @@ fn parse(script: &str) -> CommandBlock { fn parse_block(block: Pair) -> CommandBlock { CommandBlock { - elements: block.into_inner().filter_map(|pair| match pair.as_rule() { - Rule::Line => Some(BlockElement::Command(parse_command(pair))), - Rule::Block => Some(BlockElement::Block(parse_block(pair))), - Rule::EOI => None, // end - _ => unreachable!(), - }).collect(), + elements: { + let mut control = None; + let mut elements = Vec::new(); + for pair in block.into_inner() { + elements.push(match pair.as_rule() { + Rule::Control => { + if control.is_some() { + panic!("control statement should not be empty"); + } + control = Some(parse_control(pair)); + continue; + }, + Rule::Line => BlockElement::Command(parse_command(pair)), + Rule::Block => BlockElement::Block({ + let mut block = parse_block(pair); + if control.is_none() { + panic!("block should have control"); + } + block.control = control; + control = None; + block + }), + Rule::EOI => break, // end + _ => unreachable!(), + }); + } + elements + }, ..Default::default() } } -fn parse_command(pair: Pair) -> Command { - use Token::*; - let line: Vec = pair +fn parse_line(pair: Pair) -> Vec { + pair .into_inner() .map(|pair| parse_token(pair)) - .collect(); + .collect() +} + +fn parse_control(pair: Pair) -> Control { + use Control::*; + use Token::*; + let line = parse_line(pair); + macro_rules! unknown { + () => { + panic!("Unknown control {}", describe_line(&line)) + }; + } + match line.as_slice() { + [Keyword(control), Boolean(condition)] if control.eq("if") => If { + condition: *condition, + }, + _ => unknown!(), + } +} + +fn parse_command(pair: Pair) -> Command { + use Command::*; + use Token::*; + let line = parse_line(pair); macro_rules! unknown { () => { panic!("Unknown command {}", describe_line(&line)) @@ -247,7 +307,7 @@ impl State { while let Some(command) = self.next_command() { if command.is_blocking() { return Some(match command { - Say { name, text } => Event::Say { + Command::Say { name, text } => Event::Say { name: name.clone(), text: text.clone() }, diff --git a/renrs/src/rpy.pest b/renrs/src/rpy.pest index b50e52f..6f2371f 100644 --- a/renrs/src/rpy.pest +++ b/renrs/src/rpy.pest @@ -43,12 +43,14 @@ Number = @{ // comments are a # followed by // any Number of non-newline characters +// TODO: Inline comments broken by no implicit whitespace COMMENT = _{ "#" ~ char* } Colon = { ":" } // lines are comprised of a statement -Line = @{ (token ~ whitespace+)* ~ token ~ Colon? } +Line = @{ (token ~ whitespace+)* ~ token } +Control = @{ Line ~ Colon } File = { SOI ~ NEWLINE* ~ block_content* ~ NEWLINE* ~ EOI } @@ -66,5 +68,5 @@ Block = { whitespace = _{ " " } block_content = _{ - Line ~ (whitespace+ ~ Line)* ~ (NEWLINE | EOI) ~ Block* + (Control | Line) ~ (whitespace+ ~ Line)* ~ (NEWLINE | EOI) ~ Block* } \ No newline at end of file