Implement rudimentary control flow

main
Elnu 1 year ago
parent c0c8153cbe
commit abc17c7c3c

@ -1,13 +1,14 @@
# 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." "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!"

@ -20,6 +20,7 @@ enum Token {
impl Token { impl Token {
fn print(&self) -> &str { fn print(&self) -> &str {
use Token::*;
match &self { match &self {
Keyword(keyword) => keyword, Keyword(keyword) => keyword,
Str(_) => "String", Str(_) => "String",
@ -30,11 +31,10 @@ impl Token {
} }
} }
use Token::*;
// Indented command block // Indented command block
#[derive(Debug)] #[derive(Debug)]
struct CommandBlock { struct CommandBlock {
control: Option<Control>,
elements: Vec<BlockElement>, elements: Vec<BlockElement>,
next: Option<usize>, next: Option<usize>,
} }
@ -45,6 +45,9 @@ impl CommandBlock {
Some(next) => next, Some(next) => next,
None => return None, None => return None,
}; };
if !self.evaluate_control() {
return None;
}
let mut result = None; let mut result = None;
let count = self.elements.len(); let count = self.elements.len();
for element in &mut self.elements[next..] { for element in &mut self.elements[next..] {
@ -74,11 +77,19 @@ impl CommandBlock {
}; };
result 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 { impl Default for CommandBlock {
fn default() -> Self { fn default() -> Self {
Self { Self {
control: None,
elements: Vec::new(), elements: Vec::new(),
next: Some(0), next: Some(0),
} }
@ -93,6 +104,12 @@ enum BlockElement {
type Script = CommandBlock; type Script = CommandBlock;
#[derive(Debug)]
#[allow(dead_code)]
enum Control {
If { condition: bool },
}
// Parsed script commands // Parsed script commands
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -101,10 +118,9 @@ enum Command {
Eat { food: String, politely: bool }, Eat { food: String, politely: bool },
} }
use Command::*;
impl Command { impl Command {
fn is_blocking(&self) -> bool { fn is_blocking(&self) -> bool {
use Command::*;
match self { match self {
Say { .. } => true, Say { .. } => true,
_ => false, _ => false,
@ -139,22 +155,66 @@ fn parse(script: &str) -> CommandBlock {
fn parse_block(block: Pair) -> CommandBlock { fn parse_block(block: Pair) -> CommandBlock {
CommandBlock { CommandBlock {
elements: block.into_inner().filter_map(|pair| match pair.as_rule() { elements: {
Rule::Line => Some(BlockElement::Command(parse_command(pair))), let mut control = None;
Rule::Block => Some(BlockElement::Block(parse_block(pair))), let mut elements = Vec::new();
Rule::EOI => None, // end for pair in block.into_inner() {
_ => unreachable!(), elements.push(match pair.as_rule() {
}).collect(), 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() ..Default::default()
} }
} }
fn parse_command(pair: Pair) -> Command { fn parse_line(pair: Pair) -> Vec<Token> {
use Token::*; pair
let line: Vec<Token> = pair
.into_inner() .into_inner()
.map(|pair| parse_token(pair)) .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 { macro_rules! unknown {
() => { () => {
panic!("Unknown command {}", describe_line(&line)) panic!("Unknown command {}", describe_line(&line))
@ -247,7 +307,7 @@ impl State {
while let Some(command) = self.next_command() { while let Some(command) = self.next_command() {
if command.is_blocking() { if command.is_blocking() {
return Some(match command { return Some(match command {
Say { name, text } => Event::Say { Command::Say { name, text } => Event::Say {
name: name.clone(), name: name.clone(),
text: text.clone() text: text.clone()
}, },

@ -43,12 +43,14 @@ Number = @{
// comments are a # followed by // comments are a # followed by
// any Number of non-newline characters // any Number of non-newline characters
// TODO: Inline comments broken by no implicit whitespace
COMMENT = _{ "#" ~ char* } COMMENT = _{ "#" ~ char* }
Colon = { ":" } Colon = { ":" }
// lines are comprised of a statement // 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 } File = { SOI ~ NEWLINE* ~ block_content* ~ NEWLINE* ~ EOI }
@ -66,5 +68,5 @@ Block = {
whitespace = _{ " " } whitespace = _{ " " }
block_content = _{ block_content = _{
Line ~ (whitespace+ ~ Line)* ~ (NEWLINE | EOI) ~ Block* (Control | Line) ~ (whitespace+ ~ Line)* ~ (NEWLINE | EOI) ~ Block*
} }
Loading…
Cancel
Save