mod modifier;
mod utils;
pub use self::modifier::Modifier;
pub use self::utils::BlockComment;
use crate::{Cons, Formatter, Lang, LangItem};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt;
pub type Tokens<'el> = crate::Tokens<'el, Csharp>;
impl_type_basics!(Csharp, TypeEnum<'a>, TypeTrait, TypeBox, TypeArgs, {Simple, Optional, Type, Array, Void});
pub trait TypeTrait: 'static + fmt::Debug + LangItem<Csharp> {
fn as_enum(&self) -> TypeEnum<'_>;
fn name(&self) -> &str;
fn namespace(&self) -> Option<&str> {
None
}
fn is_nullable(&self) -> bool;
fn type_imports(&self, _: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {}
}
static SYSTEM: &'static str = "System";
static SEP: &'static str = ".";
pub const BOOLEAN: Simple = Simple {
name: "bool",
alias: "Boolean",
};
pub const BYTE: Simple = Simple {
name: "byte",
alias: "Byte",
};
pub const SBYTE: Simple = Simple {
name: "sbyte",
alias: "SByte",
};
pub const DECIMAL: Simple = Simple {
name: "decimal",
alias: "Decimal",
};
pub const SINGLE: Simple = Simple {
name: "float",
alias: "Single",
};
pub const DOUBLE: Simple = Simple {
name: "double",
alias: "Double",
};
pub const INT16: Simple = Simple {
name: "short",
alias: "Int16",
};
pub const UINT16: Simple = Simple {
name: "ushort",
alias: "UInt16",
};
pub const INT32: Simple = Simple {
name: "int",
alias: "Int32",
};
pub const UINT32: Simple = Simple {
name: "uint",
alias: "UInt32",
};
pub const INT64: Simple = Simple {
name: "long",
alias: "Int64",
};
pub const UINT64: Simple = Simple {
name: "ulong",
alias: "UInt64",
};
pub const VOID: Void = Void(());
#[derive(Debug, Default)]
pub struct Config {
namespace: Option<Cons<'static>>,
imported_names: HashMap<String, String>,
}
impl Config {
pub fn with_namespace<N>(self, namespace: N) -> Self
where
N: Into<Cons<'static>>,
{
Self {
namespace: Some(namespace.into()),
..self
}
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Optional {
inner: TypeBox,
}
impl TypeTrait for Optional {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Optional(self)
}
fn name(&self) -> &str {
self.inner.name()
}
fn namespace(&self) -> Option<&str> {
self.inner.namespace()
}
fn is_nullable(&self) -> bool {
false
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
self.inner.type_imports(modules)
}
}
impl LangItem<Csharp> for Optional {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
self.inner.format(out, config, level)?;
if !self.inner.is_nullable() {
out.write_str("?")?;
}
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub enum Kind {
Enum,
Class,
Struct,
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Type {
namespace: Option<Cons<'static>>,
name: Cons<'static>,
path: Vec<Cons<'static>>,
arguments: Vec<TypeBox>,
qualified: bool,
kind: Kind,
}
impl Type {
pub fn path<P: Into<Cons<'static>>>(self, part: P) -> Self {
let mut path = self.path;
path.push(part.into());
Self {
path: path,
arguments: vec![],
..self
}
}
pub fn with_arguments(self, args: impl TypeArgs) -> Self {
Self {
arguments: args.into_args(),
..self
}
}
pub fn qualified(self) -> Self {
Self {
qualified: true,
..self
}
}
pub fn into_struct(self) -> Self {
Self {
kind: Kind::Struct,
arguments: vec![],
..self
}
}
pub fn into_enum(self) -> Self {
Self {
kind: Kind::Enum,
arguments: vec![],
..self
}
}
}
impl TypeTrait for Type {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Type(self)
}
fn name(&self) -> &str {
&*self.name
}
fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
fn is_nullable(&self) -> bool {
match self.kind {
Kind::Enum | Kind::Struct => false,
_ => true,
}
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
for argument in &self.arguments {
argument.type_imports(modules);
}
if let Some(namespace) = &self.namespace {
modules.insert((namespace.clone(), self.name.clone()));
}
}
}
impl LangItem<Csharp> for Type {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
{
let qualified = match self.qualified {
true => true,
false => {
let file_namespace = config.namespace.as_ref().map(|p| p.as_ref());
let imported = config
.imported_names
.get(self.name.as_ref())
.map(String::as_str);
let pkg = self.namespace.as_deref();
imported != pkg && file_namespace != pkg
}
};
if let Some(namespace) = &self.namespace {
if qualified {
out.write_str(namespace)?;
out.write_str(SEP)?;
}
}
}
{
out.write_str(self.name.as_ref())?;
let mut it = self.path.iter();
while let Some(n) = it.next() {
out.write_str(".")?;
out.write_str(n.as_ref())?;
}
}
if !self.arguments.is_empty() {
out.write_str("<")?;
let mut it = self.arguments.iter().peekable();
while let Some(argument) = it.next() {
argument.format(out, config, level + 1usize)?;
if it.peek().is_some() {
out.write_str(", ")?;
}
}
out.write_str(">")?;
}
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Simple {
name: &'static str,
alias: &'static str,
}
impl TypeTrait for Simple {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Simple(self)
}
fn name(&self) -> &str {
self.name
}
fn namespace(&self) -> Option<&str> {
Some(SYSTEM)
}
fn is_nullable(&self) -> bool {
false
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
modules.insert((SYSTEM.into(), self.alias.into()));
}
}
impl LangItem<Csharp> for Simple {
fn format(&self, out: &mut Formatter, _: &mut Config, _: usize) -> fmt::Result {
out.write_str(self.alias)?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Array {
inner: TypeBox,
}
impl TypeTrait for Array {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Array(self)
}
fn name(&self) -> &str {
self.inner.name()
}
fn namespace(&self) -> Option<&str> {
self.inner.namespace()
}
fn is_nullable(&self) -> bool {
true
}
fn type_imports(&self, modules: &mut BTreeSet<(Cons<'static>, Cons<'static>)>) {
self.inner.type_imports(modules);
}
}
impl LangItem<Csharp> for Array {
fn format(&self, out: &mut Formatter, config: &mut Config, level: usize) -> fmt::Result {
self.inner.format(out, config, level)?;
out.write_str("[]")?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Void(());
impl TypeTrait for Void {
fn as_enum(&self) -> TypeEnum<'_> {
TypeEnum::Void(self)
}
fn name(&self) -> &str {
"void"
}
fn is_nullable(&self) -> bool {
false
}
}
impl LangItem<Csharp> for Void {
fn format(&self, out: &mut Formatter, _: &mut Config, _: usize) -> fmt::Result {
out.write_str("void")?;
Ok(())
}
fn as_import(&self) -> Option<&dyn TypeTrait> {
Some(self)
}
}
pub struct Csharp(());
impl Csharp {
fn imports<'el>(tokens: &Tokens<'el>, config: &mut Config) -> Option<Tokens<'el>> {
let mut modules = BTreeSet::new();
let file_namespace = config.namespace.as_ref().map(|p| p.as_ref());
for custom in tokens.walk_custom() {
if let Some(import) = custom.as_import() {
import.type_imports(&mut modules);
}
}
if modules.is_empty() {
return None;
}
let mut out = Tokens::new();
let mut imported = HashSet::new();
for (namespace, name) in modules {
if Some(&*namespace) == file_namespace.as_deref() {
continue;
}
match config.imported_names.get(&*name) {
Some(existing) if existing == &*namespace => continue,
Some(_) => continue,
_ => {}
}
if !imported.contains(&*namespace) {
out.push(toks!("using ", namespace.clone(), ";"));
imported.insert(namespace.to_string());
}
config
.imported_names
.insert(name.to_string(), namespace.to_string());
}
Some(out)
}
}
impl Lang for Csharp {
type Config = Config;
type Import = dyn TypeTrait;
fn quote_string(out: &mut Formatter, input: &str) -> fmt::Result {
use std::fmt::Write as _;
out.write_char('"')?;
for c in input.chars() {
match c {
'\t' => out.write_str("\\t")?,
'\u{0007}' => out.write_str("\\b")?,
'\n' => out.write_str("\\n")?,
'\r' => out.write_str("\\r")?,
'\u{0014}' => out.write_str("\\f")?,
'\'' => out.write_str("\\'")?,
'"' => out.write_str("\\\"")?,
'\\' => out.write_str("\\\\")?,
c => out.write_char(c)?,
}
}
out.write_char('"')?;
Ok(())
}
fn write_file(
tokens: Tokens<'_>,
out: &mut Formatter,
config: &mut Self::Config,
level: usize,
) -> fmt::Result {
let mut toks: Tokens = Tokens::new();
if let Some(imports) = Self::imports(&tokens, config) {
toks.push(imports);
toks.line_spacing();
}
if let Some(ref namespace) = config.namespace {
toks.push(toks!["namespace ", namespace.clone(), " {"]);
toks.indent();
toks.append(tokens);
toks.unindent();
toks.push("}");
} else {
toks.append(tokens);
}
toks.format(out, config, level)
}
}
pub fn using<P: Into<Cons<'static>>, N: Into<Cons<'static>>>(namespace: P, name: N) -> Type {
Type {
namespace: Some(namespace.into()),
name: name.into(),
path: vec![],
arguments: vec![],
qualified: false,
kind: Kind::Class,
}
}
pub fn local<N: Into<Cons<'static>>>(name: N) -> Type {
Type {
namespace: None,
name: name.into(),
path: vec![],
arguments: vec![],
qualified: false,
kind: Kind::Class,
}
}
pub fn array<I: Into<TypeBox>>(value: I) -> Array {
Array {
inner: value.into(),
}
}
pub fn optional<I: Into<TypeBox>>(value: I) -> Optional {
Optional {
inner: value.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate as genco;
use crate::{quote, Csharp, Quoted, Tokens};
#[test]
fn test_string() {
let mut toks: Tokens<Csharp> = Tokens::new();
toks.append("hello \n world".quoted());
assert_eq!("\"hello \\n world\"", toks.to_string().unwrap().as_str());
}
#[ignore]
#[test]
fn test_using() {
let a = using("Foo.Bar", "A");
let b = using("Foo.Bar", "B");
let ob = using("Foo.Baz", "B");
let ob_a = ob.clone().with_arguments(a.clone());
let toks: Tokens<Csharp> = quote!(#a #b #ob #ob_a);
assert_eq!(
vec!["using Foo.Bar;", "", "A B Foo.Baz.B Foo.Baz.B<A>", ""],
toks.to_file_vec().unwrap()
);
}
}