Split source file

main
Elnu 2 years ago
parent fcfe7171b5
commit 08635610cc

@ -41,7 +41,7 @@ impl App {
}
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 {
Some(name) => format!("{name}: {text}"),
None => text,

@ -1,424 +1,10 @@
use core::panic;
use std::{fs, path::PathBuf, collections::HashMap, rc::Rc, cell::RefCell, hash::Hash};
use std::{path::PathBuf, rc::Rc, cell::RefCell};
use pest::Parser;
use pest_derive::Parser;
use regex::Regex;
#[derive(Parser)]
#[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: &regex::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 mod parser;
use parser::block::CommandBlock;
use parser::command::Command;
use parser::event::Event;
use parser::parse_file;
pub struct State {
script: Rc<RefCell<CommandBlock>>,

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

@ -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!(),
}
}

@ -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!(),
}
}

@ -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: &regex::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()
}

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

@ -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!(),
}
}

@ -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…
Cancel
Save