Implement define statements

main
Elnu 1 year ago
parent abc17c7c3c
commit fcfe7171b5

9
demo/Cargo.lock generated

@ -1739,9 +1739,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.1" version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1750,9 +1750,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.1" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]] [[package]]
name = "renrs" name = "renrs"
@ -1760,6 +1760,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"pest", "pest",
"pest_derive", "pest_derive",
"regex",
] ]
[[package]] [[package]]

@ -1,5 +1,6 @@
# Control testing # Control testing
"Bob" "I will not say anything" "Bob" "I will not say anything, [foo]"
define foo = "bar"
if False: if False:
"Bob" "W-what? Why am I saying this?" "Bob" "W-what? Why am I saying this?"
if True: if True:
@ -11,4 +12,6 @@ if True:
"Bob" "...cereal!" "Bob" "...cereal!"
if False: if False:
"Bob" "But I actually hate it." "Bob" "But I actually hate it."
define bar = "potato"
define foo = bar
"Bob sat on the bench." "Bob sat on the bench."

@ -9,3 +9,4 @@ edition = "2021"
[dependencies] [dependencies]
pest = "2.6.0" pest = "2.6.0"
pest_derive = "2.6.0" pest_derive = "2.6.0"
regex = "1.8.3"

@ -1,8 +1,9 @@
use core::panic; use core::panic;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf, collections::HashMap, rc::Rc, cell::RefCell, hash::Hash};
use pest::Parser; use pest::Parser;
use pest_derive::Parser; use pest_derive::Parser;
use regex::Regex;
#[derive(Parser)] #[derive(Parser)]
#[grammar = "rpy.pest"] #[grammar = "rpy.pest"]
@ -31,16 +32,41 @@ impl Token {
} }
} }
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 // Indented command block
#[derive(Debug)] #[derive(Debug)]
struct CommandBlock { struct CommandBlock {
parent: Option<Rc<RefCell<CommandBlock>>>,
control: Option<Control>, control: Option<Control>,
elements: Vec<BlockElement>, elements: Vec<BlockElement>,
next: Option<usize>, next: Option<usize>,
variables: Option<Rc<RefCell<HashMap<String, Token>>>>,
} }
impl CommandBlock { impl CommandBlock {
fn next(&mut self) -> Option<&Command> { fn next(&mut self) -> Option<Command> {
let mut next = match self.next { let mut next = match self.next {
Some(next) => next, Some(next) => next,
None => return None, None => return None,
@ -53,14 +79,14 @@ impl CommandBlock {
for element in &mut self.elements[next..] { for element in &mut self.elements[next..] {
match element { match element {
BlockElement::Command(command) => { BlockElement::Command(command) => {
result = Some(&*command); result = Some(command.clone());
next += 1; next += 1;
break; break;
}, },
BlockElement::Block(block) => { BlockElement::Block(block) => {
match block.next() { match block.borrow_mut().next() {
Some(command) => { Some(command) => {
result = Some(command); result = Some(command.clone());
break; break;
}, },
None => { None => {
@ -84,14 +110,32 @@ impl CommandBlock {
If { condition } => *condition, 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 { impl Default for CommandBlock {
fn default() -> Self { fn default() -> Self {
Self { Self {
parent: None,
control: None, control: None,
elements: Vec::new(), elements: Vec::new(),
next: Some(0), next: Some(0),
variables: None,
} }
} }
} }
@ -99,32 +143,43 @@ impl Default for CommandBlock {
#[derive(Debug)] #[derive(Debug)]
enum BlockElement { enum BlockElement {
Command(Command), Command(Command),
Block(CommandBlock), Block(Rc<RefCell<CommandBlock>>),
} }
type Script = CommandBlock;
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
enum Control { enum Control {
If { condition: bool }, If { condition: bool },
} }
impl Control {
fn has_variable_scope(&self) -> bool {
match self {
_ => false,
}
}
}
// Parsed script commands // Parsed script commands
#[derive(Debug)] #[derive(Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
enum Command { enum Command {
Say { name: Option<String>, text: String }, Say { name: Option<String>, text: String },
Eat { food: String, politely: bool }, Eat { food: String, politely: bool },
Define { variable: String, value: Token },
} }
impl Command { impl Command {
fn is_blocking(&self) -> bool { fn execute(&self, context: &Rc<RefCell<CommandBlock>>) -> Option<Event> {
use Command::*; use Command::*;
match self { Some(match self {
Say { .. } => true, Say { name, text } => Event::Say {
_ => false, name: name.clone(),
} text: text.clone(),
},
Define { .. } => panic!("define command should not be executed at runtime!"),
Eat { .. } => return None,
}.process(context))
} }
} }
@ -133,6 +188,42 @@ pub enum Event {
Say { name: Option<String>, text: String }, 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 // Parser
// ====== // ======
@ -140,25 +231,33 @@ pub enum Event {
type Pair<'a> = pest::iterators::Pair<'a, Rule>; type Pair<'a> = pest::iterators::Pair<'a, Rule>;
// Read file into commands // Read file into commands
fn parse_file(file_path: &PathBuf) -> CommandBlock { fn parse_file(file_path: &PathBuf) -> Rc<RefCell<CommandBlock>> {
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");
parse(&unparsed_file) parse(&unparsed_file)
} }
fn parse(script: &str) -> CommandBlock { fn parse(script: &str) -> Rc<RefCell<CommandBlock>> {
let file = RpyParser::parse(Rule::File, script) let file = RpyParser::parse(Rule::File, script)
.expect("unsuccessful parse") .expect("unsuccessful parse")
.next() .next()
.unwrap(); .unwrap();
parse_block(file) parse_block(file, None)
} }
fn parse_block(block: Pair) -> CommandBlock { fn parse_block(pair: Pair, definitions: Option<Rc<RefCell<HashMap<String, Token>>>>) -> Rc<RefCell<CommandBlock>> {
CommandBlock { //let variables: HashMap<String, Token> = HashMap::new();
elements: { 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 control = None;
let mut elements = Vec::new(); let mut elements = Vec::new();
for pair in block.into_inner() { for pair in pair.into_inner() {
elements.push(match pair.as_rule() { elements.push(match pair.as_rule() {
Rule::Control => { Rule::Control => {
if control.is_some() { if control.is_some() {
@ -167,24 +266,49 @@ fn parse_block(block: Pair) -> CommandBlock {
control = Some(parse_control(pair)); control = Some(parse_control(pair));
continue; continue;
}, },
Rule::Line => BlockElement::Command(parse_command(pair)), 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({ Rule::Block => BlockElement::Block({
let mut block = parse_block(pair); let subblock_rc = parse_block(pair, Some(definitions.clone()));
if control.is_none() { {
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"); panic!("block should have control");
} }
block.control = control; subblock.parent = Some(block_rc.clone());
subblock.control = control;
control = None; control = None;
block }
subblock_rc
}), }),
Rule::EOI => break, // end Rule::EOI => break, // end
_ => unreachable!(), _ => unreachable!(),
}); });
} }
elements elements
}, };
..Default::default() if is_root {
block.variables = Some(definitions);
} }
}
block_rc
} }
fn parse_line(pair: Pair) -> Vec<Token> { fn parse_line(pair: Pair) -> Vec<Token> {
@ -229,6 +353,10 @@ fn parse_command(pair: Pair) -> Command {
name: Some(name.to_owned()), name: Some(name.to_owned()),
text: text.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 { [Keyword(keyword), Str(food), tail @ ..] if keyword.eq("eat") => Eat {
food: food.to_owned(), food: food.to_owned(),
politely: match tail { politely: match tail {
@ -293,7 +421,7 @@ fn parse_token(pair: Pair) -> Token {
// ===== // =====
pub struct State { pub struct State {
script: Script, script: Rc<RefCell<CommandBlock>>,
} }
impl State { impl State {
@ -305,20 +433,15 @@ impl State {
pub fn next(&mut self) -> Option<Event> { pub fn next(&mut self) -> Option<Event> {
while let Some(command) = self.next_command() { while let Some(command) = self.next_command() {
if command.is_blocking() { let event = command.execute(&self.script);
return Some(match command { if event.as_ref().map_or(false, |event| event.is_blocking()) {
Command::Say { name, text } => Event::Say { return event;
name: name.clone(),
text: text.clone()
},
_ => unimplemented!(),
});
} }
} }
None None
} }
fn next_command(&mut self) -> Option<&Command> { fn next_command(&mut self) -> Option<Command> {
self.script.next() self.script.borrow_mut().next()
} }
} }

@ -43,7 +43,7 @@ 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 // FIXME: Inline comments broken by no implicit whitespace
COMMENT = _{ "#" ~ char* } COMMENT = _{ "#" ~ char* }
Colon = { ":" } Colon = { ":" }

Loading…
Cancel
Save