generated from ElnuDev/rust-project
Split source file
This commit is contained in:
parent
fcfe7171b5
commit
08635610cc
9 changed files with 442 additions and 422 deletions
|
@ -41,7 +41,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self) {
|
fn next(&mut self) {
|
||||||
if let Some(renrs::Event::Say { name, text }) = self.state.next() {
|
if let Some(renrs::parser::event::Event::Say { name, text }) = self.state.next() {
|
||||||
self.text = match name {
|
self.text = match name {
|
||||||
Some(name) => format!("{name}: {text}"),
|
Some(name) => format!("{name}: {text}"),
|
||||||
None => text,
|
None => text,
|
||||||
|
|
426
renrs/src/lib.rs
426
renrs/src/lib.rs
|
@ -1,424 +1,10 @@
|
||||||
use core::panic;
|
use std::{path::PathBuf, rc::Rc, cell::RefCell};
|
||||||
use std::{fs, path::PathBuf, collections::HashMap, rc::Rc, cell::RefCell, hash::Hash};
|
|
||||||
|
|
||||||
use pest::Parser;
|
pub mod parser;
|
||||||
use pest_derive::Parser;
|
use parser::block::CommandBlock;
|
||||||
use regex::Regex;
|
use parser::command::Command;
|
||||||
|
use parser::event::Event;
|
||||||
#[derive(Parser)]
|
use parser::parse_file;
|
||||||
#[grammar = "rpy.pest"]
|
|
||||||
struct RpyParser;
|
|
||||||
|
|
||||||
// Raw script tokens
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum Token {
|
|
||||||
Keyword(String),
|
|
||||||
Str(String),
|
|
||||||
Array(Vec<Token>),
|
|
||||||
Boolean(bool),
|
|
||||||
Number(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
fn print(&self) -> &str {
|
|
||||||
use Token::*;
|
|
||||||
match &self {
|
|
||||||
Keyword(keyword) => keyword,
|
|
||||||
Str(_) => "String",
|
|
||||||
Array(_) => "Array",
|
|
||||||
Boolean(_) => "Boolean",
|
|
||||||
Number(_) => "Number",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for Token {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
use Token::*;
|
|
||||||
match self {
|
|
||||||
Keyword(_) => panic!("keywords should not be used as values"),
|
|
||||||
Str(string) => string.to_owned(),
|
|
||||||
Array(array) => format!(
|
|
||||||
"[{}]",
|
|
||||||
array
|
|
||||||
.iter()
|
|
||||||
.map(|token| token.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ")
|
|
||||||
),
|
|
||||||
Boolean(boolean) => match boolean {
|
|
||||||
true => "True",
|
|
||||||
false => "False",
|
|
||||||
}.to_owned(),
|
|
||||||
Number(number) => number.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indented command block
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CommandBlock {
|
|
||||||
parent: Option<Rc<RefCell<CommandBlock>>>,
|
|
||||||
control: Option<Control>,
|
|
||||||
elements: Vec<BlockElement>,
|
|
||||||
next: Option<usize>,
|
|
||||||
variables: Option<Rc<RefCell<HashMap<String, Token>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandBlock {
|
|
||||||
fn next(&mut self) -> Option<Command> {
|
|
||||||
let mut next = match self.next {
|
|
||||||
Some(next) => next,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
if !self.evaluate_control() {
|
|
||||||
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.clone());
|
|
||||||
next += 1;
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
BlockElement::Block(block) => {
|
|
||||||
match block.borrow_mut().next() {
|
|
||||||
Some(command) => {
|
|
||||||
result = Some(command.clone());
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
next += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self.next = if count >= next {
|
|
||||||
Some(next)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_control(&self) -> bool {
|
|
||||||
use Control::*;
|
|
||||||
self.control.as_ref().map_or(true, |control| match control {
|
|
||||||
If { condition } => *condition,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_variables(&self) -> HashMap<String, Token> {
|
|
||||||
match &self.variables {
|
|
||||||
Some(variables) => {
|
|
||||||
let variables = variables.borrow().clone();
|
|
||||||
if let Some(parent) = &self.parent {
|
|
||||||
let mut parent_variables = parent.borrow().get_variables();
|
|
||||||
parent_variables.extend(variables.into_iter());
|
|
||||||
parent_variables
|
|
||||||
} else {
|
|
||||||
variables
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CommandBlock {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
parent: None,
|
|
||||||
control: None,
|
|
||||||
elements: Vec::new(),
|
|
||||||
next: Some(0),
|
|
||||||
variables: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum BlockElement {
|
|
||||||
Command(Command),
|
|
||||||
Block(Rc<RefCell<CommandBlock>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
enum Control {
|
|
||||||
If { condition: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Control {
|
|
||||||
fn has_variable_scope(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsed script commands
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
enum Command {
|
|
||||||
Say { name: Option<String>, text: String },
|
|
||||||
Eat { food: String, politely: bool },
|
|
||||||
Define { variable: String, value: Token },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {
|
|
||||||
fn execute(&self, context: &Rc<RefCell<CommandBlock>>) -> Option<Event> {
|
|
||||||
use Command::*;
|
|
||||||
Some(match self {
|
|
||||||
Say { name, text } => Event::Say {
|
|
||||||
name: name.clone(),
|
|
||||||
text: text.clone(),
|
|
||||||
},
|
|
||||||
Define { .. } => panic!("define command should not be executed at runtime!"),
|
|
||||||
Eat { .. } => return None,
|
|
||||||
}.process(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Event {
|
|
||||||
Say { name: Option<String>, text: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Event {
|
|
||||||
fn is_blocking(&self) -> bool {
|
|
||||||
use Event::*;
|
|
||||||
match self {
|
|
||||||
Say { .. } => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process(mut self, context: &Rc<RefCell<CommandBlock>>) -> Self {
|
|
||||||
use Event::*;
|
|
||||||
match &mut self {
|
|
||||||
Say { name, text } => {
|
|
||||||
let context = context.borrow();
|
|
||||||
let variables = context.get_variables();
|
|
||||||
*name = name.as_deref().map(|name| interpolate_string(&name, &variables));
|
|
||||||
*text = interpolate_string(&text, &variables);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interpolate_string(input: &str, variables: &HashMap<String, Token>) -> String {
|
|
||||||
let re = Regex::new(r"\[(\w+)\]").unwrap();
|
|
||||||
let interpolated_string = re.replace_all(input, |caps: ®ex::Captures| {
|
|
||||||
let var_name = &caps[1];
|
|
||||||
if let Some(value) = variables.get(var_name) {
|
|
||||||
value.to_string()
|
|
||||||
} else {
|
|
||||||
format!("[{}]", var_name)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interpolated_string.into_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======
|
|
||||||
// Parser
|
|
||||||
// ======
|
|
||||||
|
|
||||||
type Pair<'a> = pest::iterators::Pair<'a, Rule>;
|
|
||||||
|
|
||||||
// Read file into commands
|
|
||||||
fn parse_file(file_path: &PathBuf) -> Rc<RefCell<CommandBlock>> {
|
|
||||||
let unparsed_file = fs::read_to_string(file_path).expect("cannot find file");
|
|
||||||
parse(&unparsed_file)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(script: &str) -> Rc<RefCell<CommandBlock>> {
|
|
||||||
let file = RpyParser::parse(Rule::File, script)
|
|
||||||
.expect("unsuccessful parse")
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
parse_block(file, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_block(pair: Pair, definitions: Option<Rc<RefCell<HashMap<String, Token>>>>) -> Rc<RefCell<CommandBlock>> {
|
|
||||||
//let variables: HashMap<String, Token> = HashMap::new();
|
|
||||||
let is_root = definitions.is_none();
|
|
||||||
let definitions = definitions.unwrap_or(Rc::new(RefCell::new(HashMap::new())));
|
|
||||||
let block_rc = Rc::new(RefCell::new(CommandBlock {
|
|
||||||
variables: if is_root { Some(definitions.clone()) } else { None },
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
{
|
|
||||||
let mut block = block_rc.borrow_mut();
|
|
||||||
block.elements = {
|
|
||||||
let mut control = None;
|
|
||||||
let mut elements = Vec::new();
|
|
||||||
for pair in pair.into_inner() {
|
|
||||||
elements.push(match pair.as_rule() {
|
|
||||||
Rule::Control => {
|
|
||||||
if control.is_some() {
|
|
||||||
panic!("control statement should not be empty");
|
|
||||||
}
|
|
||||||
control = Some(parse_control(pair));
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
Rule::Line => BlockElement::Command(match parse_command(pair) {
|
|
||||||
Command::Define { variable, value } => {
|
|
||||||
let mut value = value;
|
|
||||||
if let Token::Keyword(keyword) = value {
|
|
||||||
value = definitions
|
|
||||||
.borrow()
|
|
||||||
.get(&keyword)
|
|
||||||
.unwrap_or_else(|| panic!("undefined variable `{keyword}`"))
|
|
||||||
.clone();
|
|
||||||
}
|
|
||||||
definitions.borrow_mut().insert(variable, value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
command => command,
|
|
||||||
}),
|
|
||||||
Rule::Block => BlockElement::Block({
|
|
||||||
let subblock_rc = parse_block(pair, Some(definitions.clone()));
|
|
||||||
{
|
|
||||||
let mut subblock = subblock_rc.borrow_mut();
|
|
||||||
if let Some(control) = control.as_ref() {
|
|
||||||
if control.has_variable_scope() {
|
|
||||||
subblock.variables = Some(Rc::new(RefCell::new(HashMap::new())));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("block should have control");
|
|
||||||
}
|
|
||||||
subblock.parent = Some(block_rc.clone());
|
|
||||||
subblock.control = control;
|
|
||||||
control = None;
|
|
||||||
}
|
|
||||||
subblock_rc
|
|
||||||
}),
|
|
||||||
Rule::EOI => break, // end
|
|
||||||
_ => unreachable!(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
elements
|
|
||||||
};
|
|
||||||
if is_root {
|
|
||||||
block.variables = Some(definitions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
block_rc
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_line(pair: Pair) -> Vec<Token> {
|
|
||||||
pair
|
|
||||||
.into_inner()
|
|
||||||
.map(|pair| parse_token(pair))
|
|
||||||
.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 {
|
|
||||||
() => {
|
|
||||||
panic!("Unknown command {}", describe_line(&line))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
match line.as_slice() {
|
|
||||||
[Str(text)] => Say {
|
|
||||||
name: None,
|
|
||||||
text: text.to_owned(),
|
|
||||||
},
|
|
||||||
[Str(name), Str(text)] => Say {
|
|
||||||
name: Some(name.to_owned()),
|
|
||||||
text: text.to_owned(),
|
|
||||||
},
|
|
||||||
[Keyword(keyword), Keyword(variable), Keyword(equals), value] if keyword.eq("define") && equals.eq("=") => Define {
|
|
||||||
variable: variable.to_owned(),
|
|
||||||
value: value.clone(),
|
|
||||||
},
|
|
||||||
[Keyword(keyword), Str(food), tail @ ..] if keyword.eq("eat") => Eat {
|
|
||||||
food: food.to_owned(),
|
|
||||||
politely: match tail {
|
|
||||||
[Boolean(politely)] => *politely,
|
|
||||||
_ => unknown!(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => unknown!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line description e.g. [String, Keyword, Array]
|
|
||||||
// Used in parse_command as feedback for invalid commands
|
|
||||||
fn describe_line(line: &[Token]) -> 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// =====
|
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
script: Rc<RefCell<CommandBlock>>,
|
script: Rc<RefCell<CommandBlock>>,
|
||||||
|
|
162
renrs/src/parser/block.rs
Normal file
162
renrs/src/parser/block.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use std::{rc::Rc, cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
|
use super::{command::{Command, parse_command}, Pair, token::Token, Rule, control::{parse_control, Control}};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandBlock {
|
||||||
|
parent: Option<Rc<RefCell<CommandBlock>>>,
|
||||||
|
control: Option<Control>,
|
||||||
|
elements: Vec<BlockElement>,
|
||||||
|
next: Option<usize>,
|
||||||
|
variables: Option<Rc<RefCell<HashMap<String, Token>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandBlock {
|
||||||
|
pub fn next(&mut self) -> Option<Command> {
|
||||||
|
let mut next = match self.next {
|
||||||
|
Some(next) => next,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
if !self.evaluate_control() {
|
||||||
|
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.clone());
|
||||||
|
next += 1;
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
BlockElement::Block(block) => {
|
||||||
|
match block.borrow_mut().next() {
|
||||||
|
Some(command) => {
|
||||||
|
result = Some(command.clone());
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
next += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.next = if count >= next {
|
||||||
|
Some(next)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_control(&self) -> bool {
|
||||||
|
use Control::*;
|
||||||
|
self.control.as_ref().map_or(true, |control| match control {
|
||||||
|
If { condition } => *condition,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_variables(&self) -> HashMap<String, Token> {
|
||||||
|
match &self.variables {
|
||||||
|
Some(variables) => {
|
||||||
|
let variables = variables.borrow().clone();
|
||||||
|
if let Some(parent) = &self.parent {
|
||||||
|
let mut parent_variables = parent.borrow().get_variables();
|
||||||
|
parent_variables.extend(variables.into_iter());
|
||||||
|
parent_variables
|
||||||
|
} else {
|
||||||
|
variables
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CommandBlock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
parent: None,
|
||||||
|
control: None,
|
||||||
|
elements: Vec::new(),
|
||||||
|
next: Some(0),
|
||||||
|
variables: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum BlockElement {
|
||||||
|
Command(Command),
|
||||||
|
Block(Rc<RefCell<CommandBlock>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_block(pair: Pair, definitions: Option<Rc<RefCell<HashMap<String, Token>>>>) -> Rc<RefCell<CommandBlock>> {
|
||||||
|
//let variables: HashMap<String, Token> = HashMap::new();
|
||||||
|
let is_root = definitions.is_none();
|
||||||
|
let definitions = definitions.unwrap_or(Rc::new(RefCell::new(HashMap::new())));
|
||||||
|
let block_rc = Rc::new(RefCell::new(CommandBlock {
|
||||||
|
variables: if is_root { Some(definitions.clone()) } else { None },
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
{
|
||||||
|
let mut block = block_rc.borrow_mut();
|
||||||
|
block.elements = {
|
||||||
|
let mut control = None;
|
||||||
|
let mut elements = Vec::new();
|
||||||
|
for pair in pair.into_inner() {
|
||||||
|
elements.push(match pair.as_rule() {
|
||||||
|
Rule::Control => {
|
||||||
|
if control.is_some() {
|
||||||
|
panic!("control statement should not be empty");
|
||||||
|
}
|
||||||
|
control = Some(parse_control(pair));
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Rule::Line => BlockElement::Command(match parse_command(pair) {
|
||||||
|
Command::Define { variable, value } => {
|
||||||
|
let mut value = value;
|
||||||
|
if let Token::Keyword(keyword) = value {
|
||||||
|
value = definitions
|
||||||
|
.borrow()
|
||||||
|
.get(&keyword)
|
||||||
|
.unwrap_or_else(|| panic!("undefined variable `{keyword}`"))
|
||||||
|
.clone();
|
||||||
|
}
|
||||||
|
definitions.borrow_mut().insert(variable, value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
command => command,
|
||||||
|
}),
|
||||||
|
Rule::Block => BlockElement::Block({
|
||||||
|
let subblock_rc = parse_block(pair, Some(definitions.clone()));
|
||||||
|
{
|
||||||
|
let mut subblock = subblock_rc.borrow_mut();
|
||||||
|
if let Some(control) = control.as_ref() {
|
||||||
|
if control.has_variable_scope() {
|
||||||
|
// TODO: Sublock-scoped variables
|
||||||
|
subblock.variables = Some(Rc::new(RefCell::new(HashMap::new())));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("block should have control");
|
||||||
|
}
|
||||||
|
subblock.parent = Some(block_rc.clone());
|
||||||
|
subblock.control = control;
|
||||||
|
control = None;
|
||||||
|
}
|
||||||
|
subblock_rc
|
||||||
|
}),
|
||||||
|
Rule::EOI => break, // end
|
||||||
|
_ => unreachable!(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
elements
|
||||||
|
};
|
||||||
|
if is_root {
|
||||||
|
block.variables = Some(definitions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block_rc
|
||||||
|
}
|
58
renrs/src/parser/command.rs
Normal file
58
renrs/src/parser/command.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
|
use super::{token::Token, block::CommandBlock, event::Event, Pair, utils::{describe_line, parse_line}};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum Command {
|
||||||
|
Say { name: Option<String>, text: String },
|
||||||
|
Eat { food: String, politely: bool },
|
||||||
|
Define { variable: String, value: Token },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn execute(&self, context: &Rc<RefCell<CommandBlock>>) -> Option<Event> {
|
||||||
|
use Command::*;
|
||||||
|
Some(match self {
|
||||||
|
Say { name, text } => Event::Say {
|
||||||
|
name: name.clone(),
|
||||||
|
text: text.clone(),
|
||||||
|
},
|
||||||
|
Define { .. } => panic!("define command should not be executed at runtime!"),
|
||||||
|
Eat { .. } => return None,
|
||||||
|
}.process(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_command(pair: Pair) -> Command {
|
||||||
|
use Command::*;
|
||||||
|
use Token::*;
|
||||||
|
let line = parse_line(pair);
|
||||||
|
macro_rules! unknown {
|
||||||
|
() => {
|
||||||
|
panic!("Unknown command {}", describe_line(&line))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match line.as_slice() {
|
||||||
|
[Str(text)] => Say {
|
||||||
|
name: None,
|
||||||
|
text: text.to_owned(),
|
||||||
|
},
|
||||||
|
[Str(name), Str(text)] => Say {
|
||||||
|
name: Some(name.to_owned()),
|
||||||
|
text: text.to_owned(),
|
||||||
|
},
|
||||||
|
[Keyword(keyword), Keyword(variable), Keyword(equals), value] if keyword.eq("define") && equals.eq("=") => Define {
|
||||||
|
variable: variable.to_owned(),
|
||||||
|
value: value.clone(),
|
||||||
|
},
|
||||||
|
[Keyword(keyword), Str(food), tail @ ..] if keyword.eq("eat") => Eat {
|
||||||
|
food: food.to_owned(),
|
||||||
|
politely: match tail {
|
||||||
|
[Boolean(politely)] => *politely,
|
||||||
|
_ => unknown!(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => unknown!(),
|
||||||
|
}
|
||||||
|
}
|
32
renrs/src/parser/control.rs
Normal file
32
renrs/src/parser/control.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use super::{token::Token, Pair, utils::{describe_line, parse_line}};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum Control {
|
||||||
|
If { condition: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Control {
|
||||||
|
pub fn has_variable_scope(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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!(),
|
||||||
|
}
|
||||||
|
}
|
46
renrs/src/parser/event.rs
Normal file
46
renrs/src/parser/event.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::{rc::Rc, cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use super::{block::CommandBlock, token::Token};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Event {
|
||||||
|
Say { name: Option<String>, text: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn is_blocking(&self) -> bool {
|
||||||
|
use Event::*;
|
||||||
|
match self {
|
||||||
|
Say { .. } => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(mut self, context: &Rc<RefCell<CommandBlock>>) -> Self {
|
||||||
|
use Event::*;
|
||||||
|
match &mut self {
|
||||||
|
Say { name, text } => {
|
||||||
|
let context = context.borrow();
|
||||||
|
let variables = context.get_variables();
|
||||||
|
*name = name.as_deref().map(|name| interpolate_string(&name, &variables));
|
||||||
|
*text = interpolate_string(&text, &variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate_string(input: &str, variables: &HashMap<String, Token>) -> String {
|
||||||
|
let re = Regex::new(r"\[(\w+)\]").unwrap();
|
||||||
|
let interpolated_string = re.replace_all(input, |caps: ®ex::Captures| {
|
||||||
|
let var_name = &caps[1];
|
||||||
|
if let Some(value) = variables.get(var_name) {
|
||||||
|
value.to_string()
|
||||||
|
} else {
|
||||||
|
format!("[{}]", var_name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interpolated_string.into_owned()
|
||||||
|
}
|
33
renrs/src/parser/mod.rs
Normal file
33
renrs/src/parser/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
pub mod token;
|
||||||
|
pub mod command;
|
||||||
|
pub mod control;
|
||||||
|
pub mod event;
|
||||||
|
pub mod block;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub type Pair<'a> = pest::iterators::Pair<'a, Rule>;
|
||||||
|
|
||||||
|
use std::{path::PathBuf, rc::Rc, cell::RefCell, fs};
|
||||||
|
|
||||||
|
pub use pest::Parser;
|
||||||
|
use pest_derive::Parser;
|
||||||
|
|
||||||
|
use block::{CommandBlock, parse_block};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[grammar = "rpy.pest"]
|
||||||
|
pub struct RpyParser;
|
||||||
|
|
||||||
|
// Read file into commands
|
||||||
|
pub fn parse_file(file_path: &PathBuf) -> Rc<RefCell<CommandBlock>> {
|
||||||
|
let unparsed_file = fs::read_to_string(file_path).expect("cannot find file");
|
||||||
|
parse(&unparsed_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(script: &str) -> Rc<RefCell<CommandBlock>> {
|
||||||
|
let file = RpyParser::parse(Rule::File, script)
|
||||||
|
.expect("unsuccessful parse")
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
parse_block(file, None)
|
||||||
|
}
|
82
renrs/src/parser/token.rs
Normal file
82
renrs/src/parser/token.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use super::{Rule, Pair};
|
||||||
|
|
||||||
|
// Raw script tokens
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Token {
|
||||||
|
Keyword(String),
|
||||||
|
Str(String),
|
||||||
|
Array(Vec<Token>),
|
||||||
|
Boolean(bool),
|
||||||
|
Number(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub fn print(&self) -> &str {
|
||||||
|
use Token::*;
|
||||||
|
match &self {
|
||||||
|
Keyword(keyword) => keyword,
|
||||||
|
Str(_) => "String",
|
||||||
|
Array(_) => "Array",
|
||||||
|
Boolean(_) => "Boolean",
|
||||||
|
Number(_) => "Number",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Token {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
use Token::*;
|
||||||
|
match self {
|
||||||
|
Keyword(_) => panic!("keywords should not be used as values"),
|
||||||
|
Str(string) => string.to_owned(),
|
||||||
|
Array(array) => format!(
|
||||||
|
"[{}]",
|
||||||
|
array
|
||||||
|
.iter()
|
||||||
|
.map(|token| token.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
),
|
||||||
|
Boolean(boolean) => match boolean {
|
||||||
|
true => "True",
|
||||||
|
false => "False",
|
||||||
|
}.to_owned(),
|
||||||
|
Number(number) => number.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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!(),
|
||||||
|
}
|
||||||
|
}
|
21
renrs/src/parser/utils.rs
Normal file
21
renrs/src/parser/utils.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use super::{Pair, token::{Token, parse_token}};
|
||||||
|
|
||||||
|
pub fn parse_line(pair: Pair) -> Vec<Token> {
|
||||||
|
pair
|
||||||
|
.into_inner()
|
||||||
|
.map(|pair| parse_token(pair))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line description e.g. [String, Keyword, Array]
|
||||||
|
// Used in parse_command as feedback for invalid commands
|
||||||
|
pub fn describe_line(line: &[Token]) -> 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
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue