generated from ElnuDev/rust-project
Implement define statements
This commit is contained in:
parent
abc17c7c3c
commit
fcfe7171b5
5 changed files with 176 additions and 48 deletions
9
demo/Cargo.lock
generated
9
demo/Cargo.lock
generated
|
@ -1739,9 +1739,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -1750,9 +1750,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||
|
||||
[[package]]
|
||||
name = "renrs"
|
||||
|
@ -1760,6 +1760,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Control testing
|
||||
"Bob" "I will not say anything"
|
||||
"Bob" "I will not say anything, [foo]"
|
||||
define foo = "bar"
|
||||
if False:
|
||||
"Bob" "W-what? Why am I saying this?"
|
||||
if True:
|
||||
|
@ -11,4 +12,6 @@ if True:
|
|||
"Bob" "...cereal!"
|
||||
if False:
|
||||
"Bob" "But I actually hate it."
|
||||
define bar = "potato"
|
||||
define foo = bar
|
||||
"Bob sat on the bench."
|
|
@ -9,3 +9,4 @@ edition = "2021"
|
|||
[dependencies]
|
||||
pest = "2.6.0"
|
||||
pest_derive = "2.6.0"
|
||||
regex = "1.8.3"
|
||||
|
|
207
renrs/src/lib.rs
207
renrs/src/lib.rs
|
@ -1,8 +1,9 @@
|
|||
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_derive::Parser;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[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
|
||||
#[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> {
|
||||
fn next(&mut self) -> Option<Command> {
|
||||
let mut next = match self.next {
|
||||
Some(next) => next,
|
||||
None => return None,
|
||||
|
@ -53,14 +79,14 @@ impl CommandBlock {
|
|||
for element in &mut self.elements[next..] {
|
||||
match element {
|
||||
BlockElement::Command(command) => {
|
||||
result = Some(&*command);
|
||||
result = Some(command.clone());
|
||||
next += 1;
|
||||
break;
|
||||
},
|
||||
BlockElement::Block(block) => {
|
||||
match block.next() {
|
||||
match block.borrow_mut().next() {
|
||||
Some(command) => {
|
||||
result = Some(command);
|
||||
result = Some(command.clone());
|
||||
break;
|
||||
},
|
||||
None => {
|
||||
|
@ -84,14 +110,32 @@ impl CommandBlock {
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,32 +143,43 @@ impl Default for CommandBlock {
|
|||
#[derive(Debug)]
|
||||
enum BlockElement {
|
||||
Command(Command),
|
||||
Block(CommandBlock),
|
||||
Block(Rc<RefCell<CommandBlock>>),
|
||||
}
|
||||
|
||||
type Script = 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)]
|
||||
#[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 is_blocking(&self) -> bool {
|
||||
fn execute(&self, context: &Rc<RefCell<CommandBlock>>) -> Option<Event> {
|
||||
use Command::*;
|
||||
match self {
|
||||
Say { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +188,42 @@ 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
|
||||
// ======
|
||||
|
@ -140,25 +231,33 @@ pub enum Event {
|
|||
type Pair<'a> = pest::iterators::Pair<'a, Rule>;
|
||||
|
||||
// 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");
|
||||
parse(&unparsed_file)
|
||||
}
|
||||
|
||||
fn parse(script: &str) -> CommandBlock {
|
||||
fn parse(script: &str) -> Rc<RefCell<CommandBlock>> {
|
||||
let file = RpyParser::parse(Rule::File, script)
|
||||
.expect("unsuccessful parse")
|
||||
.next()
|
||||
.unwrap();
|
||||
parse_block(file)
|
||||
parse_block(file, None)
|
||||
}
|
||||
|
||||
fn parse_block(block: Pair) -> CommandBlock {
|
||||
CommandBlock {
|
||||
elements: {
|
||||
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 block.into_inner() {
|
||||
for pair in pair.into_inner() {
|
||||
elements.push(match pair.as_rule() {
|
||||
Rule::Control => {
|
||||
if control.is_some() {
|
||||
|
@ -167,24 +266,49 @@ fn parse_block(block: Pair) -> CommandBlock {
|
|||
control = Some(parse_control(pair));
|
||||
continue;
|
||||
},
|
||||
Rule::Line => BlockElement::Command(parse_command(pair)),
|
||||
Rule::Block => BlockElement::Block({
|
||||
let mut block = parse_block(pair);
|
||||
if control.is_none() {
|
||||
panic!("block should have control");
|
||||
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;
|
||||
}
|
||||
block.control = control;
|
||||
control = None;
|
||||
block
|
||||
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
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
if is_root {
|
||||
block.variables = Some(definitions);
|
||||
}
|
||||
}
|
||||
block_rc
|
||||
}
|
||||
|
||||
fn parse_line(pair: Pair) -> Vec<Token> {
|
||||
|
@ -229,6 +353,10 @@ fn parse_command(pair: Pair) -> Command {
|
|||
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 {
|
||||
|
@ -293,7 +421,7 @@ fn parse_token(pair: Pair) -> Token {
|
|||
// =====
|
||||
|
||||
pub struct State {
|
||||
script: Script,
|
||||
script: Rc<RefCell<CommandBlock>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -305,20 +433,15 @@ impl State {
|
|||
|
||||
pub fn next(&mut self) -> Option<Event> {
|
||||
while let Some(command) = self.next_command() {
|
||||
if command.is_blocking() {
|
||||
return Some(match command {
|
||||
Command::Say { name, text } => Event::Say {
|
||||
name: name.clone(),
|
||||
text: text.clone()
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
});
|
||||
let event = command.execute(&self.script);
|
||||
if event.as_ref().map_or(false, |event| event.is_blocking()) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn next_command(&mut self) -> Option<&Command> {
|
||||
self.script.next()
|
||||
fn next_command(&mut self) -> Option<Command> {
|
||||
self.script.borrow_mut().next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ Number = @{
|
|||
|
||||
// comments are a # followed by
|
||||
// any Number of non-newline characters
|
||||
// TODO: Inline comments broken by no implicit whitespace
|
||||
// FIXME: Inline comments broken by no implicit whitespace
|
||||
COMMENT = _{ "#" ~ char* }
|
||||
|
||||
Colon = { ":" }
|
||||
|
|
Loading…
Add table
Reference in a new issue