use crate::{
syntax::{EnumSubstitutions, MaybeBool, lex},
types::{self, ApiType, EnumType, Field, RecordType, enum_type::EnumVariant},
};
use std::fmt::Write as _;
use super::SyntaxElement;
#[macro_export]
macro_rules! bufwriteln {
($dst:expr, :>$offset:expr, $($args:tt)*) => {
write!($dst, "{0:>1$}", " ", $offset).unwrap();
writeln!($dst, $($args)*).unwrap();
};
($dst:expr, $($args:tt)*) => {
writeln!($dst, $($args)*).unwrap();
};
}
pub trait Interpretable {
fn command_syntax_impl(&self) -> Result<Option<String>, String>;
}
impl Interpretable for ApiType {
fn command_syntax_impl(&self) -> Result<Option<String>, String> {
match self {
ApiType::Record(record_type) => record_type.command_syntax_impl(),
ApiType::DiscriminatedUnion(_) => Ok(None),
ApiType::Enum(enum_type) => enum_type.command_syntax_impl(),
}
}
}
impl Interpretable for EnumType {
fn command_syntax_impl(&self) -> Result<Option<String>, String> {
self.syntax
.bind(EnumInterpreter::with_offset(self, 8))
.prelude(|_, code_buffer| {
bufwriteln!(code_buffer, "impl CommandSyntax for {} {{", self.name);
bufwriteln!(code_buffer, :>4, "const COMMAND_BUF_SIZE: usize = 16;",);
bufwriteln!(code_buffer,);
bufwriteln!(code_buffer, :>4, "fn append_command_syntax(&self, buf: &mut String) {{",);
Ok(())
})
.postlude(|_, code_buffer| {
bufwriteln!(code_buffer, :>4, "}}",);
bufwriteln!(code_buffer, "}}",);
Ok(())
})
.generate()
.map_err(|e| format!("{e} in syntax {} of the enum type\n{}", self.syntax, self))
}
}
impl Interpretable for RecordType {
fn command_syntax_impl(&self) -> Result<Option<String>, String> {
self.syntax
.bind(RecordInterpreter::with_offset(self, 8))
.prelude(|_, out| {
bufwriteln!(out, "impl CommandSyntax for {} {{", self.name);
if self.syntax.contains("json(") {
bufwriteln!(out, :>4, "const COMMAND_BUF_SIZE: usize = 1024;");
} else if self.fields.len() > 2 || self.syntax.contains("[0]>") {
bufwriteln!(out, :>4, "const COMMAND_BUF_SIZE: usize = 256;");
} else if self.fields.is_empty() {
bufwriteln!(out, :>4, "const COMMAND_BUF_SIZE: usize = 0;");
} else {
bufwriteln!(out, :>4, "const COMMAND_BUF_SIZE: usize = 64;");
}
bufwriteln!(out,);
bufwriteln!(out, :>4, "fn append_command_syntax(&self, buf: &mut String) {{",);
Ok(())
})
.postlude(|_, out| {
bufwriteln!(out, :>4, "}}");
bufwriteln!(out, "}}");
Ok(())
})
.generate()
.map_err(|e| format!("{e} in syntax {} of the record type\n{}", self.syntax, self))
}
}
pub type SyntaxInterpreterResult<'a, T, E> = Result<T, SyntaxInterpreterError<'a, E>>;
pub trait SyntaxInterpreter {
type ContextData;
type Error;
fn interpret_literal<'a>(
&mut self,
el: SyntaxElement<'a>,
_lit: &'a str,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_bool<'a>(
&mut self,
el: SyntaxElement<'a>,
_maybe_bool: MaybeBool,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_enum_substitutions<'a>(
&mut self,
el: SyntaxElement<'a>,
_enum_subs: EnumSubstitutions<'a>,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_trivial_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
_member_to_sub: &'a str,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_delegate_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
_member_to_sub: &'a str,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_json_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
_member_to_sub: &'a str,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_vec_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
_member_to_sub: &'a str,
_delim: &'a str,
_ctx: Option<&mut Self::ContextData>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_optional_context_enter<'a>(
&mut self,
el: SyntaxElement<'a>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, Self::ContextData, Self::Error> {
Err(SyntaxInterpreterError::Unexpected(el))
}
fn interpret_optional_context_exit<'a>(
&mut self,
_el: SyntaxElement<'a>,
_ctx: Self::ContextData,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
Ok(())
}
fn interpret_syntax<'a>(
&mut self,
syntax: &'a str,
mut ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
for tok in lex(syntax) {
let el = tok.map_err(SyntaxInterpreterError::Lexer)?;
match el {
SyntaxElement::Literal(lit) => {
self.interpret_literal(el, lit, ctx.as_deref_mut(), out)?
}
SyntaxElement::EnumSubstitutions(enum_subs) => {
self.interpret_enum_substitutions(el, enum_subs, ctx.as_deref_mut(), out)?
}
SyntaxElement::MaybeBool(maybe_bool) => {
self.interpret_bool(el, maybe_bool, ctx.as_deref_mut(), out)?
}
SyntaxElement::TrivialMemberSubstitution { member_name } => {
self.interpret_trivial_substitution(el, member_name, ctx.as_deref_mut(), out)?
}
SyntaxElement::DelegateMemberSubstitution { member_name } => {
self.interpret_delegate_substitution(el, member_name, ctx.as_deref_mut(), out)?
}
SyntaxElement::JsonMemberSubstitution { member_name } => {
self.interpret_json_substitution(el, member_name, ctx.as_deref_mut(), out)?
}
SyntaxElement::VecMemberSubstitution { member_name, delim } => self
.interpret_vec_substitution(el, member_name, delim, ctx.as_deref_mut(), out)?,
SyntaxElement::Optional { unparsed } => {
let mut new_ctx = self.interpret_optional_context_enter(el, out)?;
self.interpret_syntax(unparsed, Some(&mut new_ctx), out)?;
self.interpret_optional_context_exit(el, new_ctx, out)?;
}
}
}
Ok(())
}
}
pub enum SyntaxInterpreterError<'a, E: 'a> {
Unexpected(SyntaxElement<'a>),
Lexer(String),
Custom(E),
}
impl<'a, E: 'a + std::fmt::Display> std::fmt::Display for SyntaxInterpreterError<'a, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unexpected(el) => writeln!(f, "Unexpected syntax element {el:?}"),
Self::Lexer(err) => writeln!(f, "Lexer error: {err}"),
Self::Custom(err) => writeln!(f, "{err}"),
}
}
}
pub type DeferredGeneration<'s, SI> = Box<
dyn 's
+ FnOnce(
&mut SI,
&mut String,
) -> SyntaxInterpreterResult<'s, (), <SI as SyntaxInterpreter>::Error>,
>;
pub struct Generator<'s, SI: SyntaxInterpreter> {
syntax: &'s str,
interpreter: SI,
make_prelude: Option<DeferredGeneration<'s, SI>>,
make_postlude: Option<DeferredGeneration<'s, SI>>,
}
impl<'s, SI: SyntaxInterpreter> Generator<'s, SI> {
pub fn new(syntax: &'s str, interpreter: SI) -> Self {
Self {
syntax,
interpreter,
make_prelude: None,
make_postlude: None,
}
}
pub fn prelude<F>(mut self, f: F) -> Self
where
F: 's + FnOnce(&mut SI, &mut String) -> SyntaxInterpreterResult<'s, (), SI::Error>,
{
self.make_prelude = Some(Box::new(f));
self
}
pub fn postlude<F>(mut self, f: F) -> Self
where
F: 's + FnOnce(&mut SI, &mut String) -> SyntaxInterpreterResult<'s, (), SI::Error>,
{
self.make_postlude = Some(Box::new(f));
self
}
pub fn generate(self) -> SyntaxInterpreterResult<'s, Option<String>, SI::Error> {
if self.syntax.is_empty() {
return Ok(None);
}
let mut code_buffer = String::with_capacity(4096);
let Self {
syntax,
mut interpreter,
make_prelude,
make_postlude,
} = self;
if let Some(f) = make_prelude {
f(&mut interpreter, &mut code_buffer)?;
}
interpreter.interpret_syntax(syntax, None, &mut code_buffer)?;
if let Some(f) = make_postlude {
f(&mut interpreter, &mut code_buffer)?;
}
Ok(Some(code_buffer))
}
}
pub trait BindExt<SI: SyntaxInterpreter> {
fn bind(&self, interpreter: SI) -> Generator<'_, SI>;
}
impl<SI: SyntaxInterpreter> BindExt<SI> for str {
fn bind(&self, interpreter: SI) -> Generator<'_, SI> {
Generator::new(self, interpreter)
}
}
type VariantName<'a> = &'a str;
type VariantLiteral<'a> = &'a str;
struct EnumInterpreter<'a> {
typ: &'a EnumType,
offset: usize,
}
impl<'a> EnumInterpreter<'a> {
fn with_offset(typ: &'a EnumType, offset: usize) -> Self {
Self { typ, offset }
}
fn interpret_subs(
&self,
subs: &EnumSubstitutions,
from_field: Option<&Field>,
code_buffer: &mut String,
) -> Result<(), EnumInterpreterErr> {
let (accessor, enum_ref) = match from_field {
Some(field) => (format!(".{}", field.rust_name), self.typ.name.as_str()),
None => (String::new(), "Self"),
};
let offset = self.offset;
bufwriteln!(code_buffer, :>offset, "match self{accessor} {{");
for (var_name, literal) in self.variant_substitutions(subs)? {
bufwriteln!(code_buffer, :>offset + 4, "{enum_ref}::{var_name} => {{");
interpret_literal(literal, offset + 8, code_buffer);
bufwriteln!(code_buffer, :>offset + 4, "}}");
}
bufwriteln!(code_buffer, :>offset, "}}");
Ok(())
}
fn variant_substitutions<'s>(
&self,
enum_subs: &'s EnumSubstitutions<'s>,
) -> Result<impl Iterator<Item = (VariantName<'a>, VariantLiteral<'s>)>, EnumInterpreterErr>
{
let literals_count = enum_subs.iter().count();
if self.typ.variants.len() != enum_subs.iter().count() {
return Err(EnumInterpreterErr {
expected: self.typ.variants.len(),
got: literals_count,
});
}
Ok(self
.typ
.variants
.iter()
.map(|var| var.rust_name.as_str())
.zip(enum_subs.iter()))
}
}
impl<'this> SyntaxInterpreter for EnumInterpreter<'this> {
type Error = EnumInterpreterErr;
type ContextData = ();
fn interpret_literal<'a>(
&mut self,
_el: SyntaxElement<'a>,
lit: &'a str,
_ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
interpret_literal(lit, self.offset, out);
Ok(())
}
fn interpret_enum_substitutions<'a>(
&mut self,
_el: SyntaxElement<'a>,
enum_subs: EnumSubstitutions<'a>,
_ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
self.interpret_subs(&enum_subs, None, out)
.map_err(SyntaxInterpreterError::Custom)?;
Ok(())
}
}
pub struct EnumInterpreterErr {
expected: usize,
got: usize,
}
impl std::fmt::Display for EnumInterpreterErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"The enum has only {} variants, while the syntax defines {}",
self.expected, self.got
)
}
}
struct RecordInterpreter<'a> {
typ: &'a RecordType,
curr_field_ix: usize,
offset: usize,
}
impl<'a> RecordInterpreter<'a> {
fn with_offset(typ: &'a RecordType, offset: usize) -> Self {
Self {
typ,
curr_field_ix: 0,
offset,
}
}
fn current_field<'el>(
&self,
el: SyntaxElement<'el>,
) -> SyntaxInterpreterResult<'el, &'a Field, String> {
self
.typ
.fields
.get(self.curr_field_ix)
.ok_or_else(|| {
SyntaxInterpreterError::Custom(format!("Expected a field while processing an element {el:?} but found None(current_field_ix={})", self.curr_field_ix))
})
}
fn field_by_api_name<'el>(
&mut self,
api_name: &str,
el: SyntaxElement<'el>,
) -> SyntaxInterpreterResult<'el, &'a Field, String> {
loop {
let field = self.typ.fields.get(self.curr_field_ix).ok_or_else(|| {
SyntaxInterpreterError::Custom(format!("Failed to find a struct field with the name: {api_name:?} while trying to match element {el:?}"))
})?;
if field.api_name == api_name {
return Ok(field);
}
self.curr_field_ix += 1;
}
}
}
impl<'this> SyntaxInterpreter for RecordInterpreter<'this> {
type Error = String;
type ContextData = RecordContextData<'this>;
fn interpret_literal<'a>(
&mut self,
_el: SyntaxElement<'a>,
lit: &'a str,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
match ctx {
Some(frame) => interpret_literal(lit, self.offset, &mut frame.buffer),
None => interpret_literal(lit, self.offset, out),
}
Ok(())
}
fn interpret_bool<'a>(
&mut self,
el: SyntaxElement<'a>,
maybe_bool: MaybeBool,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
match maybe_bool {
MaybeBool::On | MaybeBool::Off => {
if let Some(frame) = ctx {
let maybe_bool_field = self.current_field(el)?;
if !maybe_bool_field.is_bool() {
return Err(SyntaxInterpreterError::Custom(format!(
"Expected a regular bool field but got {maybe_bool:?} while processing an element {el:?} in the optional span"
)));
}
if maybe_bool == MaybeBool::On {
interpret_literal("on", self.offset, &mut frame.buffer);
frame.cond_kind = CondKind::Bool(maybe_bool_field);
} else {
interpret_literal("off", self.offset, &mut frame.buffer);
frame.cond_kind = CondKind::NotBool(maybe_bool_field);
}
} else {
return Err(SyntaxInterpreterError::Custom(
"Unexpexted non-optional '{val:?}' literal that doesn't provide a choice. Expected (on|off)"
.to_owned(),
));
}
}
MaybeBool::Either => {
let bool_field = self.current_field(el)?;
let (self_str, deref, dest) = if let Some(frame) = ctx {
if !bool_field.is_optional() {
return Err(SyntaxInterpreterError::Custom(format!(
"Expected an optional bool field but got {bool_field:?} while processing an element {el:?}"
)));
}
frame.cond_kind = CondKind::Opt(bool_field);
("", "*", &mut frame.buffer)
} else {
if !bool_field.is_bool() {
return Err(SyntaxInterpreterError::Custom(format!(
"The current field must be a bool, but instead it's a {bool_field:?} for element {el:?}",
)));
}
("self.", "", out)
};
bufwriteln!(dest, :> self.offset, "if {deref}{self_str}{} {{", bool_field.rust_name);
interpret_literal("on", self.offset + 4, dest);
bufwriteln!(dest, :> self.offset, "}} else {{");
interpret_literal("off", self.offset + 4, dest);
bufwriteln!(dest, :> self.offset, "}}");
}
}
self.curr_field_ix += 1;
Ok(())
}
fn interpret_enum_substitutions<'a>(
&mut self,
el: SyntaxElement<'a>,
enum_subs: EnumSubstitutions<'a>,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
if ctx.is_some() {
return Err(SyntaxInterpreterError::Custom(format!(
"Enum substitutions are unsupported in optional contexts, but got {el:?}",
)));
}
let field = self.current_field(el)?;
if !field.is_compound() {
return Err(SyntaxInterpreterError::Custom(format!(
"Expected a enum field but got a {:?} field while processing {el:?}",
field.typ,
)));
}
let ad_hoc_enum = EnumType::new(
field.typ.clone(),
enum_subs.iter().map(EnumVariant::from_api_name).collect(),
);
let interpreter = EnumInterpreter::with_offset(&ad_hoc_enum, self.offset);
interpreter
.interpret_subs(&enum_subs, Some(field), out)
.map_err(|e| SyntaxInterpreterError::Custom(e.to_string()))?;
self.curr_field_ix += 1;
Ok(())
}
fn interpret_trivial_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
member_to_sub: &'a str,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
let field = self.field_by_api_name(member_to_sub, el)?;
let (self_str, unwrap, dest) = match ctx {
Some(frame) => {
frame.cond_kind = CondKind::Opt(field);
("", "", &mut frame.buffer)
}
None => ("self.", maybe_unwrap(field), out),
};
bufwriteln!(dest, :> self.offset, "write!(buf, \"{{}}\", {self_str}{}{unwrap}).unwrap();", field.rust_name);
self.curr_field_ix += 1;
Ok(())
}
fn interpret_delegate_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
member_to_sub: &'a str,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
let field = self.field_by_api_name(member_to_sub, el)?;
let (self_str, dest) = match ctx {
Some(frame) => {
frame.cond_kind = CondKind::Opt(field);
("", &mut frame.buffer)
}
None => ("self.", out),
};
bufwriteln!(dest, :> self.offset, "{self_str}{}.append_command_syntax(buf);", field.rust_name);
self.curr_field_ix += 1;
Ok(())
}
fn interpret_json_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
member_to_sub: &'a str,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
let field = self.field_by_api_name(member_to_sub, el)?;
let (self_str, dest) = match ctx {
Some(frame) => {
frame.cond_kind = CondKind::Opt(field);
("", &mut frame.buffer)
}
None => ("self.", out),
};
bufwriteln!(
dest,
:> self.offset, "// SAFETY: serde_json guarantees to produce valid UTF-8 sequences",
);
bufwriteln!(
dest,
:> self.offset, "unsafe {{ serde_json::to_writer(buf.as_mut_vec(), &{self_str}{}).unwrap(); }}",
field.rust_name,
);
self.curr_field_ix += 1;
Ok(())
}
fn interpret_vec_substitution<'a>(
&mut self,
el: SyntaxElement<'a>,
member_to_sub: &'a str,
delim: &'a str,
ctx: Option<&mut Self::ContextData>,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
let field = self.field_by_api_name(member_to_sub, el)?;
let (self_str, unwrap, dest) = match ctx {
Some(frame) => {
frame.cond_kind = CondKind::Opt(field);
("", "", &mut frame.buffer)
}
None => ("self.", maybe_unwrap(field), out),
};
bufwriteln!(dest, :> self.offset, "let mut iter = {self_str}{}{unwrap}.iter();", field.rust_name);
bufwriteln!(dest, :> self.offset, "if let Some(el) = iter.next() {{");
bufwriteln!(dest, :> self.offset + 4, "write!(buf, \"{{el}}\").unwrap();");
bufwriteln!(dest, :> self.offset, "}}");
bufwriteln!(dest, :> self.offset, "for el in iter {{");
interpret_literal(delim, self.offset + 4, dest);
bufwriteln!(dest, :> self.offset + 4, "write!(buf, \"{{el}}\").unwrap();");
bufwriteln!(dest, :> self.offset, "}}");
self.curr_field_ix += 1;
Ok(())
}
fn interpret_optional_context_enter<'a>(
&mut self,
_el: SyntaxElement<'a>,
_out: &mut String,
) -> SyntaxInterpreterResult<'a, Self::ContextData, Self::Error> {
self.offset += 4;
Ok(RecordContextData {
buffer: String::with_capacity(256),
cond_kind: CondKind::None,
})
}
fn interpret_optional_context_exit<'a>(
&mut self,
el: SyntaxElement<'a>,
ctx: Self::ContextData,
out: &mut String,
) -> SyntaxInterpreterResult<'a, (), Self::Error> {
self.offset -= 4;
match ctx.cond_kind {
CondKind::Bool(field) => {
bufwriteln!(out, :> self.offset, "if self.{} {{", field.rust_name);
}
CondKind::NotBool(field) => {
bufwriteln!(out, :> self.offset, "if !self.{} {{", field.rust_name);
}
CondKind::Opt(field) => {
bufwriteln!(out, :> self.offset, "if let Some({0}) = &self.{0} {{", field.rust_name);
}
CondKind::None => {
return Err(SyntaxInterpreterError::Custom(format!(
"Failed to deduce a field type in optional context while processing {el:?}"
)));
}
}
out.push_str(&ctx.buffer);
bufwriteln!(out, :> self.offset, "}}");
Ok(())
}
}
pub struct RecordContextData<'a> {
buffer: String,
cond_kind: CondKind<'a>,
}
enum CondKind<'a> {
Bool(&'a Field),
NotBool(&'a Field),
Opt(&'a Field),
None,
}
fn interpret_literal(literal: &str, offset: usize, code_buffer: &mut String) {
if literal.len() == 1 {
bufwriteln!(code_buffer, :>offset, "buf.push('{literal}');");
} else {
bufwriteln!(code_buffer, :>offset, "buf.push_str(\"{literal}\");");
}
}
fn maybe_unwrap(field: &Field) -> &'static str {
if let Some(inner) = field.inner_type() {
if field.is_optional() && types::is_string_type(inner) {
".as_deref().unwrap_or_default()"
} else if field.is_vec() {
""
} else {
".unwrap_or_default()"
}
} else {
""
}
}
#[cfg(test)]
mod tests {
use super::*;
use expect_test::expect;
#[test]
fn enum_interpreter() {
let mut test_enum = EnumType::new(
"Greeting".to_owned(),
vec!["\"hi\"".parse().unwrap(), "\"bye\"".parse().unwrap()],
);
test_enum.syntax = "Hello,|Goodbye,| World!".to_owned();
let interpreter_impl = test_enum.command_syntax_impl().unwrap().unwrap();
expect![[r#"
impl CommandSyntax for Greeting {
const COMMAND_BUF_SIZE: usize = 16;
fn append_command_syntax(&self, buf: &mut String) {
match self {
Self::Hi => {
buf.push_str("Hello,");
}
Self::Bye => {
buf.push_str("Goodbye,");
}
}
buf.push(' ');
buf.push_str("World!");
}
}
"#]]
.assert_eq(&interpreter_impl);
}
trait CommandSyntax {
const COMMAND_BUF_SIZE: usize;
fn to_command_string(&self) -> String {
let mut buf = String::with_capacity(Self::COMMAND_BUF_SIZE);
self.append_command_syntax(&mut buf);
buf
}
fn append_command_syntax(&self, buf: &mut String);
}
enum Greeting {
Hi,
Bye,
}
impl CommandSyntax for Greeting {
const COMMAND_BUF_SIZE: usize = 16;
fn append_command_syntax(&self, buf: &mut String) {
match self {
Self::Hi => {
buf.push_str("Hello,");
}
Self::Bye => {
buf.push_str("Goodbye,");
}
}
buf.push(' ');
buf.push_str("World!");
}
}
#[test]
fn real_greeting() {
assert_eq!(Greeting::Hi.to_command_string(), "Hello, World!");
assert_eq!(Greeting::Bye.to_command_string(), "Goodbye, World!");
}
}