#![deny(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_import_braces,
unused_qualifications
)]
pub use crate::{evaluator::Evaluator, incconsumer::IncConsumer};
use crate::{
incconsumer::{Consumption, Process, Validation},
types::Type,
};
use cmdmat::RegError;
pub use cmdmat::{self, Spec};
pub use metac::{Evaluate, PartialParse, PartialParseOp};
use std::{
io::{Read, Write},
str::from_utf8,
};
#[cfg(feature = "with-tokio")]
mod applicator;
pub mod evaluator;
mod incconsumer;
pub mod predicates;
pub mod types;
#[cfg(feature = "with-tokio")]
pub use applicator::tokio_apply;
pub type Feedback = Result<String, String>;
pub struct GameShell<'a, C, R: Read, W: Write> {
evaluator: Evaluator<'a, C>,
parser: PartialParse,
reader: R,
writer: W,
}
impl<'a, C, R: Read, W: Write> GameShell<'a, C, R, W> {
pub fn new(context: C, reader: R, writer: W) -> Self {
Self {
evaluator: Evaluator::new(context),
parser: PartialParse::default(),
reader,
writer,
}
}
pub fn evaluator(&mut self) -> &mut Evaluator<'a, C> {
&mut self.evaluator
}
pub fn context(&self) -> &C {
self.evaluator.context()
}
pub fn context_mut(&mut self) -> &mut C {
self.evaluator.context_mut()
}
pub fn register(&mut self, spec: Spec<'_, 'a, Type, String, C>) -> Result<(), RegError> {
self.evaluator.register(spec)
}
pub fn register_many(
&mut self,
spec: &[Spec<'_, 'a, Type, String, C>],
) -> Result<(), RegError> {
self.evaluator.register_many(spec)
}
}
impl<'a, C, R: Read, W: Write> IncConsumer for GameShell<'a, C, R, W> {
fn consume(&mut self, output: &mut [u8]) -> Consumption {
if output.is_empty() {
let _ = self
.writer
.write(b"DecodeError(\"Internal buffer is full, disconnecting\")");
return Consumption::Stop;
}
match self.reader.read(output) {
Ok(0) => Consumption::Stop,
Ok(count) => Consumption::Consumed(count),
Err(_) => Consumption::Stop,
}
}
fn validate(&mut self, input: u8) -> Validation {
match self.parser.parse_increment(input) {
PartialParseOp::Ready => Validation::Ready,
PartialParseOp::Unready => Validation::Unready,
PartialParseOp::Discard => Validation::Discard,
}
}
fn process(&mut self, input: &[u8]) -> Process {
let string = from_utf8(input);
if let Ok(string) = string {
let result = self.evaluator.interpret_single(string);
match result {
Ok(result) => {
match result {
Feedback::Ok(res) => {
if self
.writer
.write_all(format!("Ok({:?})", res).as_bytes())
.is_err()
{
return Process::Stop;
}
}
Feedback::Err(res) => {
if self
.writer
.write_all(format!("Err({:?})", res).as_bytes())
.is_err()
{
return Process::Stop;
}
}
}
if self.writer.flush().is_err() {
return Process::Stop;
}
}
Err(parse_error) => {
if self
.writer
.write_all(
format!("ParseError(\"Unable to parse input: {:?}\")", parse_error)
.as_bytes(),
)
.is_err()
{
return Process::Stop;
}
if self.writer.flush().is_err() {
return Process::Stop;
}
}
}
Process::Continue
} else {
if self
.writer
.write_all(b"DecodeError(\"Received invalid UTF-8 input, disconnecting\")")
.is_err()
{
return Process::Stop;
}
if self.writer.flush().is_err() {
return Process::Stop;
}
Process::Stop
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::predicates::*;
#[test]
fn basic_case() {
let read = b"Lorem ipsum";
let mut write = [0u8; 10];
let mut shell = GameShell::new(0u8, &read[..], &mut write[..]);
assert_eq!(&mut 0u8, shell.context_mut());
}
#[test]
fn basic_byte_stream() {
let read = b"call 1.23\n";
let mut write = [0u8; 10];
let mut eval = GameShell::new(0u8, &read[..], &mut write[..]);
fn handler(context: &mut u8, _args: &[Type]) -> Result<String, String> {
*context += 1;
Ok("".into())
}
eval.register((&[("call", ANY_F32)], handler)).unwrap();
let buffer = &mut [0u8; 1024];
eval.run(buffer);
assert_eq!(1, *eval.context());
}
#[test]
fn discard_stream_first_command_too_big() {
let read = b"call 1.2\ncall 3.1\ncall 99.9999\ncall 1.0";
let mut write = [0u8; 1024];
let mut eval = GameShell::new(0u8, &read[..], &mut write[..]);
fn handler(context: &mut u8, _args: &[Type]) -> Result<String, String> {
*context += 1;
Ok("".into())
}
eval.register((&[("call", ANY_F32)], handler)).unwrap();
let buffer = &mut [0u8; 10];
eval.run(buffer);
assert_eq!(2, *eval.context());
let index = write
.iter()
.enumerate()
.find(|(_, &byte)| byte == b'\0')
.map(|(idx, _)| idx)
.unwrap();
assert_eq!(
"Ok(\"\")Ok(\"\")DecodeError(\"Internal buffer is full, disconnecting\")",
from_utf8(&write[0..index]).unwrap()
);
}
#[test]
fn partial_read_succeeds() {
let read = b"call 1.2\nrock 3.1\n";
let mut write = [0u8; 1024];
let mut eval = GameShell::new(0f32, &read[..], &mut write[..]);
fn handler(context: &mut f32, args: &[Type]) -> Result<String, String> {
match args[0] {
Type::F32(number) => *context += number,
_ => panic!(),
}
Ok("".into())
}
eval.register((&[("call", ANY_F32)], handler)).unwrap();
eval.register((&[("rock", ANY_F32)], handler)).unwrap();
let buffer = &mut [0u8; 12];
eval.run(buffer);
assert_eq!(4.3, *eval.context());
}
}