Get code blocks working

main
Elnu 1 year ago
parent 59f0f8258a
commit f178baf1e6

@ -1,3 +1,13 @@
"Bob sat on the bench." "Bob sat on the bench."
"Bob" "Good morning!" "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 eat "potato" True
"Bob" "Yum!"

@ -81,7 +81,7 @@ impl App {
thread::spawn(move || { thread::spawn(move || {
let mut complete = false; let mut complete = false;
for i in 0..len { for i in 0..(len + 1) {
if *kill.lock().unwrap() { if *kill.lock().unwrap() {
break; break;
} }

@ -1,3 +1,4 @@
use core::panic;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use pest::Parser; use pest::Parser;
@ -8,8 +9,8 @@ use pest_derive::Parser;
struct RpyParser; struct RpyParser;
// Raw script tokens // Raw script tokens
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Token { enum Token {
Keyword(String), Keyword(String),
Str(String), Str(String),
Array(Vec<Token>), Array(Vec<Token>),
@ -31,6 +32,73 @@ impl Token {
use Token::*; use Token::*;
// Parsing types
type Line = Vec<Token>;
// Indented command block
#[derive(Debug)]
struct Block<T> {
elements: Vec<BlockElement<T>>,
next: Option<usize>,
}
impl<T> Block<T> {
fn next(&mut self) -> Option<&T> {
let mut next = match self.next {
Some(next) => next,
None => return None,
};
let mut result = None;
let count = self.elements.len();
for element in &mut self.elements[next..] {
match element {
BlockElement::Command(command) => {
result = Some(&*command);
next += 1;
break;
},
BlockElement::Block(block) => {
match block.next() {
Some(command) => {
result = Some(command);
break;
},
None => {
next += 1;
}
}
}
};
}
self.next = if count >= next {
Some(next)
} else {
None
};
result
}
}
impl<T> Default for Block<T> {
fn default() -> Self {
Self {
elements: Vec::new(),
next: Some(0),
}
}
}
#[derive(Debug)]
enum BlockElement<T> {
Command(T),
Block(Block<T>),
}
type LineBlock = Block<Line>;
type CommandBlock = Block<Command>;
type Script = CommandBlock;
// Parsed script commands // Parsed script commands
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -55,97 +123,122 @@ pub enum Event {
Say { name: Option<String>, text: String }, Say { name: Option<String>, text: String },
} }
// ==========================================
// Step 1 parsing
// converting from pest pairs to Token blocks
// ==========================================
// Tokenize raw script string // Tokenize raw script string
fn tokenize(script: &str) -> Vec<Vec<Token>> { fn parse(script: &str) -> LineBlock {
let file = RpyParser::parse(Rule::file, script) let file = RpyParser::parse(Rule::file, script)
.expect("unsuccessful parse") .expect("unsuccessful parse")
.next() .next()
.unwrap(); .unwrap();
// TODO: Init with capacity parse_block(file)
let mut lines = Vec::new(); }
for line in file.into_inner() {
let mut tokens = Vec::new(); type Pair<'a> = pest::iterators::Pair<'a, Rule>;
match line.as_rule() {
// Tokenize block
fn parse_block(pair: Pair) -> LineBlock {
let mut block = LineBlock::default();
for element in pair.into_inner() {
block.elements.push(match element.as_rule() {
Rule::block => BlockElement::Block(parse_block(element)),
Rule::line => { Rule::line => {
for token in line.into_inner() { let line = parse_line(element);
tokens.push(parse_pair(token)); // TODO: For some reason a blank final line is always parsed
if line.len() == 0 {
continue;
} }
} BlockElement::Command(line)
Rule::EOI => (), },
Rule::EOI => break, // end
_ => unreachable!(), _ => unreachable!(),
});
} }
// TODO: For some a blank final line is always parsed block
if tokens.len() > 0 { }
lines.push(tokens);
} // Tokenize line
fn parse_line(pair: Pair) -> Line {
let mut tokens = Vec::new();
for token in pair.into_inner() {
tokens.push(parse_token(token));
} }
lines tokens
} }
// Parse raw pest data into Token // Tokenize token
fn parse_pair(pair: pest::iterators::Pair<Rule>) -> Token { fn parse_token(pair: Pair) -> Token {
let token = pair.as_rule(); let token = pair.as_rule();
match token { macro_rules! contents {
Rule::token => {} () => {
_ => panic!("Not a token!"), pair.into_inner().next().unwrap()
}; };
let contents = pair.into_inner().next().unwrap(); }
let contents_rule = contents.as_rule(); match token {
match contents_rule {
Rule::string => { Rule::string => {
let data = contents.into_inner().next().unwrap(); let contents = contents!();
Token::Str(match data.as_rule() { Token::Str(match contents.as_rule() {
Rule::single_quote_string_data => data.as_str().replace("\\'", "'"), Rule::single_quote_string_data => contents.as_str().replace("\\'", "'"),
Rule::double_quote_string_data => data.as_str().replace("\\\"", "\""), Rule::double_quote_string_data => contents.as_str().replace("\\\"", "\""),
_ => unreachable!(), _ => unreachable!(),
}) })
} }
Rule::array => { Rule::array => {
let contents = contents!();
let mut array = Vec::new(); let mut array = Vec::new();
for token in contents.into_inner() { for token in contents.into_inner() {
array.push(parse_pair(token)); array.push(parse_token(token));
} }
Token::Array(array) Token::Array(array)
} }
Rule::boolean => Token::Boolean(match contents.as_str() { Rule::boolean => Token::Boolean(match pair.as_str() {
"True" => true, "True" => true,
"False" => false, "False" => false,
_ => unreachable!(), _ => unreachable!(),
}), }),
Rule::number => Token::Number(contents.as_str().parse().unwrap()), Rule::number => Token::Number(pair.as_str().parse().unwrap()),
Rule::keyword => Token::Keyword(contents.as_str().to_owned()), Rule::keyword => Token::Keyword(pair.as_str().to_owned()),
__ => unreachable!(), __ => unreachable!(),
} }
} }
// Tokenize file
fn tokenize_file(file_path: PathBuf) -> Vec<Vec<Token>> { // ==============================================
// Step 2 reading
// converting from Token blocks to Command blocks
// ==============================================
// Read file into commands
fn read_file(file_path: &PathBuf) -> CommandBlock {
let line_block = {
let unparsed_file = fs::read_to_string(file_path).expect("cannot find file"); let unparsed_file = fs::read_to_string(file_path).expect("cannot find file");
tokenize(&unparsed_file) parse(&unparsed_file)
};
read_block(&line_block)
} }
fn describe_line(line: &Vec<Token>) -> String { // Read line block into command block
let mut description = "[".to_owned(); fn read_block(block: &LineBlock) -> CommandBlock {
let mut iter = line.iter(); CommandBlock {
description.push_str(&format!("{}", iter.next().unwrap().print())); elements: block.elements.iter().map(|element| match element {
for token in iter { BlockElement::Command(line) => BlockElement::Command(read_command(&line)),
description.push_str(&format!(", {}", token.print())); BlockElement::Block(block) => BlockElement::Block(read_block(&block)),
}).collect(),
..Default::default()
} }
description.push_str("]");
description
} }
// Parse file into commands // Read token array to command
fn parse_file(file_path: PathBuf) -> Vec<Command> { fn read_command(line: &Line) -> Command {
let token_lines = tokenize_file(file_path);
let mut commands = Vec::new();
for line in token_lines {
macro_rules! unknown { macro_rules! unknown {
() => { () => {
panic!("Unknown command {}", describe_line(&line)) panic!("Unknown command {}", describe_line(&line))
}; };
} }
commands.push(match line.as_slice() { match line.as_slice() {
[Str(text)] => Say { [Str(text)] => Say {
name: None, name: None,
text: text.to_owned(), text: text.to_owned(),
@ -162,23 +255,34 @@ fn parse_file(file_path: PathBuf) -> Vec<Command> {
}, },
}, },
_ => unknown!(), _ => unknown!(),
});
} }
commands
} }
// Line description e.g. [String, Keyword, Array]
// Used in parse_command as feedback for invalid commands
fn describe_line(line: &Line) -> String {
let mut description = "[".to_owned();
let mut iter = line.iter();
description.push_str(&format!("{}", iter.next().unwrap().print()));
for token in iter {
description.push_str(&format!(", {}", token.print()));
}
description.push_str("]");
description
}
// =====
// State
// =====
pub struct State { pub struct State {
command_queue: Vec<Command>, script: Script,
} }
impl State { impl State {
pub fn from_file(file: PathBuf) -> State { pub fn from_file(file: PathBuf) -> State {
State { State {
command_queue: { script: read_file(&file),
let mut q = parse_file(file);
q.reverse();
q
},
} }
} }
@ -186,19 +290,18 @@ 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 { name, text }, Say { name, text } => Event::Say {
name: name.clone(),
text: text.clone()
},
_ => unimplemented!(), _ => unimplemented!(),
}) });
} }
} }
None None
} }
fn next_command(&mut self) -> Option<Command> { fn next_command(&mut self) -> Option<&Command> {
if self.command_queue.len() == 0 { self.script.next()
None
} else {
Some(self.command_queue.remove(self.command_queue.len() - 1))
}
} }
} }

@ -1,6 +1,3 @@
// underscores mark are silent rules, are ignored
WHITESPACE = _{ " " }
// characters are anything but newlines // characters are anything but newlines
char = { !NEWLINE ~ ANY } char = { !NEWLINE ~ ANY }
@ -12,7 +9,7 @@ token = { string | array | boolean | number | keyword }
// KEYWORDS // KEYWORDS
// has to be atomic for no implicit separate (spaces) // has to be atomic for no implicit separate (spaces)
keyword = @{ (!(WHITESPACE | NEWLINE) ~ ANY)+ } keyword = ${ (!(whitespace | NEWLINE) ~ ANY)+ }
// STRING // STRING
single_quote_string_data = @{ ( single_quote_string_data = @{ (
@ -29,13 +26,13 @@ string = ${
} }
// ARRAY // ARRAY
array = { array = ${
"[" ~ "]" "[" ~ "]"
| "[" ~ NEWLINE* ~ token ~ ("," ~ NEWLINE* ~ token)* ~ NEWLINE* ~ "]" | "[" ~ whitespace* ~ NEWLINE* ~ whitespace* ~ token ~ ("," ~ whitespace* ~ NEWLINE* ~ whitespace* ~ token)* ~ NEWLINE* ~ "]"
} }
// BOOLEAN // BOOLEAN
boolean = { "True" | "False" } boolean = ${ "True" | "False" }
// NUMBER // NUMBER
number = @{ number = @{
@ -49,6 +46,23 @@ number = @{
COMMENT = _{ "#" ~ char* } COMMENT = _{ "#" ~ char* }
// lines are comprised of a statement // lines are comprised of a statement
line = { token+ } line = @{ (token ~ whitespace+)* ~ token }
file = { SOI ~ NEWLINE* ~ block_content* ~ NEWLINE* ~ EOI }
block = {
// The first line in the block
PEEK_ALL ~ PUSH(" "+ | "\t"+) ~ block_content ~
// Subsequent lines in the block
(PEEK_ALL ~ block_content)* ~
// Remove the last layer of indentation from the stack when exiting the block
DROP
}
// can't be called WHITESPACE
// as this would mess up NewBlock
whitespace = _{ " " }
file = { SOI ~ (line ~ (NEWLINE+ ~ line)*)? ~ NEWLINE* ~ EOI } block_content = _{
line ~ (whitespace+ ~ line)* ~ (NEWLINE | EOI) ~ block*
}
Loading…
Cancel
Save