Created
June 30, 2019 15:02
-
-
Save zesterer/e92b4f8c1cd5ee25dc72eb74a6c7015a to your computer and use it in GitHub Desktop.
Natter Interpreter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{ | |
sync::mpsc, | |
thread::{self, JoinHandle}, | |
collections::{HashMap, HashSet}, | |
}; | |
// Error | |
#[derive(Debug)] | |
pub enum Error { | |
ParseError(ParseError), | |
} | |
impl From<ParseError> for Error { | |
fn from(err: ParseError) -> Self { | |
Error::ParseError(err) | |
} | |
} | |
#[derive(Clone, Debug)] | |
pub enum ParseError { | |
Misc(usize, String), | |
UnexpectedEof, | |
NoError, | |
First, | |
} | |
impl ParseError { | |
fn max(&self, other: Self) -> Self { | |
match (self, &other) { | |
(ParseError::Misc(a, _), ParseError::Misc(b, _)) => if a > b { | |
self.clone() | |
} else { | |
other | |
}, | |
(First, _) => other, | |
(ParseError::UnexpectedEof, _) => self.clone(), | |
(_, First) => self.clone(), | |
(_, ParseError::UnexpectedEof) => other, | |
_ => other, | |
} | |
} | |
} | |
// I/O | |
pub enum Input { | |
Choice(usize), | |
Text(String), | |
} | |
pub enum Output { | |
Speech { name: String, text: String }, | |
ChoiceRequest(Vec<String>), | |
TextRequest, | |
Finish, | |
} | |
// Engine | |
pub struct Engine { | |
send_input: Option<mpsc::SyncSender<Input>>, | |
recv_output: mpsc::Receiver<Output>, | |
worker_handle: Option<JoinHandle<()>>, | |
} | |
impl Engine { | |
pub fn new(module: Module, initial_frame: String) -> Self { | |
let (send_input, recv_input) = mpsc::sync_channel(1); | |
let (send_output, recv_output) = mpsc::sync_channel(1); | |
let wh = thread::spawn(move || | |
Self::worker(recv_input, send_output, module, initial_frame) | |
); | |
Self { | |
send_input: Some(send_input), | |
recv_output, | |
worker_handle: Some(wh), | |
} | |
} | |
pub fn next_output(&mut self) -> Output { | |
self.recv_output.recv().unwrap_or(Output::Finish) | |
} | |
pub fn input(&mut self, input: Input) { | |
self.send_input | |
.as_ref() | |
.and_then(|si| si.send(input).ok()) | |
.unwrap(); | |
} | |
fn worker( | |
recv_input: mpsc::Receiver<Input>, | |
send_output: mpsc::SyncSender<Output>, | |
module: Module, | |
initial_frame: String, | |
) { | |
struct Io<'a> { | |
recv: &'a mpsc::Receiver<Input>, | |
send: &'a mpsc::SyncSender<Output>, | |
} | |
type Context = HashSet<String>; | |
fn exec_block(block: &Block, ctx: &mut Context, io: &Io, module: &Module) -> bool { | |
for s in &block.statements { | |
match s { | |
Statement::Speech { name, text } => { | |
io.send.send(Output::Speech { name: name.clone(), text: text.clone() }).unwrap(); | |
}, | |
Statement::Choice(choices) => { | |
let choice_strs = choices.iter().map(|(s, _)| s.clone()).collect(); | |
io.send.send(Output::ChoiceRequest(choice_strs)).unwrap(); | |
if let Input::Choice(n) = io.recv.recv().unwrap() { | |
if let Some((_, block)) = choices.get(n) { | |
if exec_block(block, ctx, io, module) { | |
return true; | |
} | |
} else { | |
panic!("Choice was not in range!"); | |
} | |
} else { | |
panic!("Requested a choice, but didn't receive one"); | |
} | |
}, | |
Statement::Set(tag) => { | |
ctx.insert(tag.clone()); | |
}, | |
Statement::Unset(tag) => { | |
ctx.remove(tag); | |
}, | |
Statement::Call(frame) => { | |
if exec_block( | |
if let Some(block) = module.frames.get(frame.as_str()) { | |
block | |
} else { | |
panic!("Frame does not exist!"); | |
}, | |
ctx, | |
io, | |
module, | |
) { | |
return true; | |
} | |
}, | |
Statement::If { predicate, true_block, false_block } => { | |
if ctx.contains(predicate.as_str()) { | |
if exec_block(true_block, ctx, io, module) { | |
return true; | |
} | |
} else { | |
if exec_block(false_block, ctx, io, module) { | |
return true; | |
} | |
} | |
}, | |
Statement::While { predicate, block } => { | |
while ctx.contains(predicate.as_str()) { | |
if exec_block(block, ctx, io, module) { | |
return true; | |
} | |
} | |
}, | |
Statement::Loop { block } => { | |
loop { | |
if exec_block(block, ctx, io, module) { | |
return true; | |
} | |
} | |
}, | |
Statement::Finish => { | |
return true; | |
}, | |
s => panic!("Unimplemented statement: {:?}", s), | |
} | |
} | |
false | |
} | |
let mut ctx = Context::new(); | |
exec_block( | |
if let Some(block) = module.frames.get(&initial_frame) { | |
block | |
} else { | |
panic!("Initial frame does not exist!"); | |
}, | |
&mut ctx, | |
&Io { recv: &recv_input, send: &send_output }, | |
&module, | |
); | |
// End the dialogue | |
send_output.send(Output::Finish).unwrap(); | |
} | |
} | |
impl Drop for Engine { | |
fn drop(&mut self) { | |
self.send_input.take(); | |
self.worker_handle.take().map(|wh| wh.join()); | |
} | |
} | |
// Module | |
#[derive(Clone, Debug)] | |
enum BoolExpr { | |
State(String), | |
Not(Box<BoolExpr>), | |
Or(Box<BoolExpr>, Box<BoolExpr>), | |
And(Box<BoolExpr>, Box<BoolExpr>), | |
} | |
#[derive(Clone, Debug)] | |
enum Statement { | |
Speech { | |
name: String, | |
text: String, | |
}, | |
If { | |
predicate: String, | |
true_block: Box<Block>, | |
false_block: Box<Block>, | |
}, | |
While { | |
predicate: String, | |
block: Box<Block>, | |
}, | |
Loop { block: Box<Block> }, | |
Choice(Vec<(String, Box<Block>)>), | |
Set(String), | |
Unset(String), | |
Call(String), | |
Finish, | |
} | |
#[derive(Clone, Debug)] | |
struct Block { | |
statements: Vec<Statement>, | |
} | |
#[derive(Clone)] | |
pub struct Module { | |
frames: HashMap<String, Block>, | |
} | |
#[derive(Clone, Debug)] | |
enum Line { | |
FrameDecl(String), | |
Speech(String, String), | |
Choice, | |
Branch(String), | |
Call(String), | |
Set(String), | |
Unset(String), | |
If(String), | |
While(String), | |
Loop, | |
Else, | |
End, | |
Finish, | |
Comment, | |
} | |
impl Module { | |
fn lex_lines(code: &str) -> Result<Vec<(usize, Line)>, Error> { | |
Ok(code | |
.lines() | |
.enumerate() | |
.map(|(num, l)| (num + 1, l.trim_start())) | |
.filter(|(_, l)| l.len() > 0) | |
.map(|(num, l)| match l.chars().next() { | |
Some(':') => if l[1..].len() > 0 && l[1..].chars().all(|c| c.is_alphanumeric() || c == '_') { | |
Ok((num, Line::FrameDecl(l[1..].into()))) | |
} else { | |
Err(ParseError::Misc(num, format!("Invalid frame identifier '{}'", &l[1..]))) | |
}, | |
Some('@') => { | |
let space_idx = l.find(' ').unwrap_or(1); | |
if space_idx > 1 && l[1..space_idx].chars().all(|c| c.is_alphanumeric() || c == '_') { | |
Ok((num, Line::Speech(l[1..space_idx].into(), l[space_idx..].trim_start().into()))) | |
} else { | |
Err(ParseError::Misc(num, format!("Invalid speaker identifier '{}'", &l[0..space_idx]))) | |
} | |
}, | |
Some('>') => if l[1..].trim_start().len() > 0 { | |
Ok((num, Line::Branch(l[1..].into()))) | |
} else { | |
Err(ParseError::Misc(num, "Choice branch must have identifying text".into())) | |
}, | |
Some('#') => Ok((num, Line::Comment)), | |
Some(c) if c.is_alphanumeric() => match l.split(' ').next().unwrap_or("").trim() { | |
"choice" => Ok((num, Line::Choice)), | |
"loop" => Ok((num, Line::Loop)), | |
"else" => Ok((num, Line::Else)), | |
"end" => Ok((num, Line::End)), | |
"finish" => Ok((num, Line::Finish)), | |
op @ "call" | op @ "set" | op @ "unset" | op @ "if" | op @ "while" => if l.split(' ').count() == 2 { | |
let arg = l.split(' ').nth(1).unwrap(); | |
if arg.len() > 0 && arg.chars().all(|c| c.is_alphanumeric() || c == '_') { | |
match op { | |
"call" => Ok((num, Line::Call(arg.into()))), | |
"set" => Ok((num, Line::Set(arg.into()))), | |
"unset" => Ok((num, Line::Unset(arg.into()))), | |
"if" => Ok((num, Line::If(arg.into()))), | |
"while" => Ok((num, Line::While(arg.into()))), | |
_ => panic!("This isn't supposed to be possible"), | |
} | |
} else { | |
Err(ParseError::Misc(num, format!("Invalid frame target identifier '{}'", arg))) | |
} | |
} else { | |
Err(ParseError::Misc(num, "Too many arguments for goto flow control instruction".into())) | |
}, | |
s => Err(ParseError::Misc(num, format!("Unknown flow control instruction '{}'", s))), | |
}, | |
_ => Err(ParseError::Misc(num, "Invalid syntax".into())), | |
}) | |
.collect::<Result<Vec<_>, _>>()?) | |
} | |
pub fn parse_from_code(code: &str) -> Result<Self, Error> { | |
type LineIter<'a> = std::slice::Iter<'a, (usize, Line)>; | |
fn read_line<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, usize, Line), ParseError> { | |
match iter.next().cloned() { | |
Some((num, line)) => Ok((iter, num, line)), | |
None => return Err(ParseError::UnexpectedEof), | |
} | |
} | |
fn read_speech_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
match read_line(iter)? { | |
(iter, _, Line::Speech(name, text)) => Ok((iter, Statement::Speech { name, text })), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected speech statement".into())), | |
} | |
} | |
fn read_call_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
match read_line(iter)? { | |
(iter, _, Line::Call(arg)) => Ok((iter, Statement::Call(arg))), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected call statement".into())), | |
} | |
} | |
fn read_set_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
match read_line(iter)? { | |
(iter, _, Line::Set(arg)) => Ok((iter, Statement::Set(arg))), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected set statement".into())), | |
} | |
} | |
fn read_unset_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
match read_line(iter)? { | |
(iter, _, Line::Unset(arg)) => Ok((iter, Statement::Unset(arg))), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected unset statement".into())), | |
} | |
} | |
fn read_finish_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
match read_line(iter)? { | |
(iter, _, Line::Finish) => Ok((iter, Statement::Finish)), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected finish statement".into())), | |
} | |
} | |
fn read_if_statement<'a>(iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
let (iter, predicate) = match read_line(iter)? { | |
(iter, _, Line::If(predicate)) => (iter, predicate), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected 'if'".into())), | |
}; | |
let (iter, true_block) = read_block(iter.clone())?; | |
let iter = match read_line(iter.clone()) { | |
Ok((iter, _, Line::Else)) => iter, | |
Ok((iter, _, Line::End)) => { | |
return Ok((iter, Statement::If { predicate, true_block: Box::new(true_block), false_block: Box::new(Block { statements: vec![] }) })); | |
}, | |
Ok((_, num, _)) => return Err(ParseError::Misc(num, "Expected 'else' or 'end'".into())), | |
Err(err) => return Err(err), | |
}; | |
let (iter, false_block) = read_block(iter.clone())?; | |
match read_line(iter.clone()) { | |
Ok((iter, _, Line::End)) => { | |
return Ok((iter, Statement::If { predicate, true_block: Box::new(true_block), false_block: Box::new(false_block) })); | |
}, | |
Ok((_, num, _)) => return Err(ParseError::Misc(num, "Expected 'end' to end if".into())), | |
Err(err) => return Err(err), | |
}; | |
} | |
fn read_while_statement<'a>(iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
let (iter, predicate) = match read_line(iter)? { | |
(iter, _, Line::While(predicate)) => (iter, predicate), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected 'while'".into())), | |
}; | |
let (iter, block) = read_block(iter.clone())?; | |
let iter = match read_line(iter.clone()) { | |
Ok((iter, _, Line::End)) => { | |
return Ok((iter, Statement::While { predicate, block: Box::new(block) })); | |
}, | |
Ok((_, num, _)) => return Err(ParseError::Misc(num, "Expected 'end' to end while".into())), | |
Err(err) => return Err(err), | |
}; | |
} | |
fn read_loop_statement<'a>(iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
let iter = match read_line(iter)? { | |
(iter, _, Line::Loop) => iter, | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected 'loop'".into())), | |
}; | |
let (iter, block) = read_block(iter.clone())?; | |
let iter = match read_line(iter.clone()) { | |
Ok((iter, _, Line::End)) => { | |
return Ok((iter, Statement::Loop { block: Box::new(block) })); | |
}, | |
Ok((_, num, _)) => return Err(ParseError::Misc(num, "Expected 'end' to end loop".into())), | |
Err(err) => return Err(err), | |
}; | |
} | |
fn read_choice_statement<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Statement), ParseError> { | |
iter = match read_line(iter)? { | |
(iter, _, Line::Choice) => iter, | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected 'choice'".into())), | |
}; | |
let mut branches = vec![]; | |
let mut max_err = ParseError::First; | |
loop { | |
let branch_tag = match read_line(iter.clone()) { | |
Ok((new_iter, _, Line::Branch(tag))) => { | |
iter = new_iter; | |
tag | |
}, | |
Ok((iter, _, Line::End)) => return Ok((iter, Statement::Choice(branches))), | |
Ok((_, num, _)) => return Err(ParseError::Misc(num, "Expected choice branch or 'end'".into())), | |
Err(err) => return Err(max_err.max(err)), | |
}; | |
max_err = max_err.max(match read_block(iter.clone()) { | |
Ok((new_iter, block)) => { | |
branches.push((branch_tag, Box::new(block))); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
} | |
} | |
fn read_block<'a>(mut iter: LineIter<'a>) -> Result<(LineIter<'a>, Block), ParseError> { | |
let mut statements = vec![]; | |
let mut max_err = ParseError::First; | |
loop { | |
max_err = max_err.max(match read_speech_statement(iter.clone()) { | |
Ok((new_iter, speech)) => { | |
statements.push(speech); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_choice_statement(iter.clone()) { | |
Ok((new_iter, choice)) => { | |
statements.push(choice); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_if_statement(iter.clone()) { | |
Ok((new_iter, if_s)) => { | |
statements.push(if_s); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_while_statement(iter.clone()) { | |
Ok((new_iter, while_s)) => { | |
statements.push(while_s); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_loop_statement(iter.clone()) { | |
Ok((new_iter, loop_s)) => { | |
statements.push(loop_s); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_call_statement(iter.clone()) { | |
Ok((new_iter, choice)) => { | |
statements.push(choice); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_set_statement(iter.clone()) { | |
Ok((new_iter, set)) => { | |
statements.push(set); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_unset_statement(iter.clone()) { | |
Ok((new_iter, unset)) => { | |
statements.push(unset); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
max_err = max_err.max(match read_finish_statement(iter.clone()) { | |
Ok((new_iter, finish)) => { | |
statements.push(finish); | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => err, | |
}); | |
match read_line(iter.clone()) { | |
Ok((new_iter, _, Line::Comment)) => { | |
iter = new_iter; | |
continue; | |
}, | |
Err(err) => max_err = max_err.max(err), | |
_ => {}, | |
} | |
return Ok((iter, Block { statements })); | |
} | |
} | |
fn read_frame<'a>(iter: LineIter<'a>) -> Result<(LineIter<'a>, String, Block), ParseError> { | |
let (iter, name) = match read_line(iter).map_err(|_| ParseError::NoError)? { | |
(iter, _, Line::FrameDecl(name)) => (iter, name), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected frame declaration".into())), | |
}; | |
let (iter, block) = read_block(iter.clone())?; | |
let (iter, name) = match read_line(iter)? { | |
(iter, _, Line::End) => (iter, name), | |
(_, num, _) => return Err(ParseError::Misc(num, "Expected 'end'".into())), | |
}; | |
Ok((iter, name, block)) | |
} | |
let lines = Self::lex_lines(code)?; | |
let mut iter = lines.iter(); | |
let mut frames = HashMap::new(); | |
loop { | |
match read_frame(iter) { | |
Ok((new_iter, name, block)) => { | |
frames.insert(name, block); | |
iter = new_iter; | |
}, | |
Err(ParseError::NoError) => break, | |
Err(err) => return Err(err.into()), | |
} | |
} | |
Ok(Self { | |
frames, | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment