|
|
|
@ -32,19 +32,15 @@ impl Token {
|
|
|
|
|
|
|
|
|
|
use Token::*;
|
|
|
|
|
|
|
|
|
|
// Parsing types
|
|
|
|
|
|
|
|
|
|
type Line = Vec<Token>;
|
|
|
|
|
|
|
|
|
|
// Indented command block
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct Block<T> {
|
|
|
|
|
elements: Vec<BlockElement<T>>,
|
|
|
|
|
struct CommandBlock {
|
|
|
|
|
elements: Vec<BlockElement>,
|
|
|
|
|
next: Option<usize>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> Block<T> {
|
|
|
|
|
fn next(&mut self) -> Option<&T> {
|
|
|
|
|
impl CommandBlock {
|
|
|
|
|
fn next(&mut self) -> Option<&Command> {
|
|
|
|
|
let mut next = match self.next {
|
|
|
|
|
Some(next) => next,
|
|
|
|
|
None => return None,
|
|
|
|
@ -80,7 +76,7 @@ impl<T> Block<T> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> Default for Block<T> {
|
|
|
|
|
impl Default for CommandBlock {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
elements: Vec::new(),
|
|
|
|
@ -90,13 +86,11 @@ impl<T> Default for Block<T> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum BlockElement<T> {
|
|
|
|
|
Command(T),
|
|
|
|
|
Block(Block<T>),
|
|
|
|
|
enum BlockElement {
|
|
|
|
|
Command(Command),
|
|
|
|
|
Block(CommandBlock),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LineBlock = Block<Line>;
|
|
|
|
|
type CommandBlock = Block<Command>;
|
|
|
|
|
type Script = CommandBlock;
|
|
|
|
|
|
|
|
|
|
// Parsed script commands
|
|
|
|
@ -123,116 +117,44 @@ pub enum Event {
|
|
|
|
|
Say { name: Option<String>, text: String },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
// Step 1 parsing
|
|
|
|
|
// converting from pest pairs to Token blocks
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
|
|
// Tokenize raw script string
|
|
|
|
|
fn parse(script: &str) -> LineBlock {
|
|
|
|
|
let file = RpyParser::parse(Rule::file, script)
|
|
|
|
|
.expect("unsuccessful parse")
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap();
|
|
|
|
|
parse_block(file)
|
|
|
|
|
}
|
|
|
|
|
// ======
|
|
|
|
|
// Parser
|
|
|
|
|
// ======
|
|
|
|
|
|
|
|
|
|
type Pair<'a> = pest::iterators::Pair<'a, 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 => {
|
|
|
|
|
let line = parse_line(element);
|
|
|
|
|
// TODO: For some reason a blank final line is always parsed
|
|
|
|
|
if line.len() == 0 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
BlockElement::Command(line)
|
|
|
|
|
},
|
|
|
|
|
Rule::EOI => break, // end
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
block
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tokenize line
|
|
|
|
|
fn parse_line(pair: Pair) -> Line {
|
|
|
|
|
let mut tokens = Vec::new();
|
|
|
|
|
for token in pair.into_inner() {
|
|
|
|
|
tokens.push(parse_token(token));
|
|
|
|
|
}
|
|
|
|
|
tokens
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tokenize token
|
|
|
|
|
fn parse_token(pair: Pair) -> Token {
|
|
|
|
|
let token = pair.as_rule();
|
|
|
|
|
macro_rules! contents {
|
|
|
|
|
() => {
|
|
|
|
|
pair.into_inner().next().unwrap()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
match token {
|
|
|
|
|
Rule::String => {
|
|
|
|
|
let contents = contents!();
|
|
|
|
|
Token::Str(match contents.as_rule() {
|
|
|
|
|
Rule::SingleQuoteStringData => contents.as_str().replace("\\'", "'"),
|
|
|
|
|
Rule::DoubleQuoteStringData => contents.as_str().replace("\\\"", "\""),
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
Rule::Array => {
|
|
|
|
|
let contents = contents!();
|
|
|
|
|
let mut array = Vec::new();
|
|
|
|
|
for token in contents.into_inner() {
|
|
|
|
|
array.push(parse_token(token));
|
|
|
|
|
}
|
|
|
|
|
Token::Array(array)
|
|
|
|
|
}
|
|
|
|
|
Rule::Boolean => Token::Boolean(match pair.as_str() {
|
|
|
|
|
"True" => true,
|
|
|
|
|
"False" => false,
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
}),
|
|
|
|
|
Rule::Number => Token::Number(pair.as_str().parse().unwrap()),
|
|
|
|
|
Rule::Keyword => Token::Keyword(pair.as_str().to_owned()),
|
|
|
|
|
__ => unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==============================================
|
|
|
|
|
// Step 2 reading
|
|
|
|
|
// converting from Token blocks to Command blocks
|
|
|
|
|
// ==============================================
|
|
|
|
|
|
|
|
|
|
// Read file into commands
|
|
|
|
|
fn read_file(file_path: &PathBuf) -> CommandBlock {
|
|
|
|
|
let line_block = {
|
|
|
|
|
fn parse_file(file_path: &PathBuf) -> CommandBlock {
|
|
|
|
|
let unparsed_file = fs::read_to_string(file_path).expect("cannot find file");
|
|
|
|
|
parse(&unparsed_file)
|
|
|
|
|
};
|
|
|
|
|
read_block(&line_block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read line block into command block
|
|
|
|
|
fn read_block(block: &LineBlock) -> CommandBlock {
|
|
|
|
|
fn parse(script: &str) -> CommandBlock {
|
|
|
|
|
let file = RpyParser::parse(Rule::File, script)
|
|
|
|
|
.expect("unsuccessful parse")
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap();
|
|
|
|
|
parse_block(file)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_block(block: Pair) -> CommandBlock {
|
|
|
|
|
CommandBlock {
|
|
|
|
|
elements: block.elements.iter().map(|element| match element {
|
|
|
|
|
BlockElement::Command(line) => BlockElement::Command(read_command(&line)),
|
|
|
|
|
BlockElement::Block(block) => BlockElement::Block(read_block(&block)),
|
|
|
|
|
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(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read token array to command
|
|
|
|
|
fn read_command(line: &Line) -> Command {
|
|
|
|
|
fn parse_command(pair: Pair) -> Command {
|
|
|
|
|
use Token::*;
|
|
|
|
|
let line: Vec<Token> = pair
|
|
|
|
|
.into_inner()
|
|
|
|
|
.map(|pair| parse_token(pair))
|
|
|
|
|
.collect();
|
|
|
|
|
macro_rules! unknown {
|
|
|
|
|
() => {
|
|
|
|
|
panic!("Unknown command {}", describe_line(&line))
|
|
|
|
@ -260,7 +182,7 @@ fn read_command(line: &Line) -> Command {
|
|
|
|
|
|
|
|
|
|
// Line description e.g. [String, Keyword, Array]
|
|
|
|
|
// Used in parse_command as feedback for invalid commands
|
|
|
|
|
fn describe_line(line: &Line) -> String {
|
|
|
|
|
fn describe_line(line: &[Token]) -> String {
|
|
|
|
|
let mut description = "[".to_owned();
|
|
|
|
|
let mut iter = line.iter();
|
|
|
|
|
description.push_str(&format!("{}", iter.next().unwrap().print()));
|
|
|
|
@ -271,6 +193,41 @@ fn describe_line(line: &Line) -> String {
|
|
|
|
|
description
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_token(pair: Pair) -> Token {
|
|
|
|
|
let token = pair.as_rule();
|
|
|
|
|
macro_rules! contents {
|
|
|
|
|
() => {
|
|
|
|
|
pair.into_inner().next().unwrap()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
match token {
|
|
|
|
|
Rule::String => {
|
|
|
|
|
let contents = contents!();
|
|
|
|
|
Token::Str(match contents.as_rule() {
|
|
|
|
|
Rule::SingleQuoteStringData => contents.as_str().replace("\\'", "'"),
|
|
|
|
|
Rule::DoubleQuoteStringData => contents.as_str().replace("\\\"", "\""),
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
Rule::Array => {
|
|
|
|
|
let contents = contents!();
|
|
|
|
|
let mut array = Vec::new();
|
|
|
|
|
for token in contents.into_inner() {
|
|
|
|
|
array.push(parse_token(token));
|
|
|
|
|
}
|
|
|
|
|
Token::Array(array)
|
|
|
|
|
}
|
|
|
|
|
Rule::Boolean => Token::Boolean(match pair.as_str() {
|
|
|
|
|
"True" => true,
|
|
|
|
|
"False" => false,
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
}),
|
|
|
|
|
Rule::Number => Token::Number(pair.as_str().parse().unwrap()),
|
|
|
|
|
Rule::Keyword => Token::Keyword(pair.as_str().to_owned()),
|
|
|
|
|
__ => unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =====
|
|
|
|
|
// State
|
|
|
|
|
// =====
|
|
|
|
@ -282,7 +239,7 @@ pub struct State {
|
|
|
|
|
impl State {
|
|
|
|
|
pub fn from_file(file: PathBuf) -> State {
|
|
|
|
|
State {
|
|
|
|
|
script: read_file(&file),
|
|
|
|
|
script: parse_file(&file),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|