Implement rudimentary control flow

main
Elnu 1 year ago
parent c0c8153cbe
commit abc17c7c3c

@ -1,13 +1,14 @@
"Bob sat on the bench." # Control testing
"Bob" "Good morning!" "Bob" "I will not say anything"
"Bob" "I am in a block now!" if False:
"Bob" "Isn't this cool?" "Bob" "W-what? Why am I saying this?"
"Bob" "And now we're back in normal indentation." if True:
"Bob" "We can even go multiple levels in!" "Testing 123"
"Bob" "How far does the rabbit hole go?" "..."
"Bob" "rabbit hole go?" "Bob" "Good."
"Bob" "go?" "Bob" "Now I will tell you my favorite food..."
"Bob" "Not sure what came of me there." if True:
"Bob" "I suppose I should eat a potato." "Bob" "...cereal!"
eat "potato" True if False:
"Bob" "Yum!" "Bob" "But I actually hate it."
"Bob sat on the bench."

@ -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