generated from ElnuDev/rust-project
Implement rudimentary control flow
This commit is contained in:
parent
c0c8153cbe
commit
abc17c7c3c
3 changed files with 93 additions and 30 deletions
|
@ -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…
Add table
Reference in a new issue