mod block_comment;
use crate as genco;
use crate::{quote_in, Formatter, ItemStr, Lang, LangItem};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt;
pub use self::block_comment::BlockComment;
pub type Tokens = crate::Tokens<Csharp>;
impl_dynamic_types! { Csharp =>
pub trait TypeTrait {
fn name(&self) -> &str;
fn namespace(&self) -> Option<&str> {
None
}
fn is_nullable(&self) -> bool;
fn type_imports(&self, _: &mut BTreeSet<(ItemStr, ItemStr)>) {}
}
pub trait TypeArgs;
pub struct TypeBox;
pub enum TypeEnum;
impl TypeTrait for Simple {
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<(ItemStr, ItemStr)>) {
modules.insert((SYSTEM.into(), self.alias.into()));
}
}
impl TypeTrait for Optional {
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<(ItemStr, ItemStr)>) {
self.inner.type_imports(modules)
}
}
impl TypeTrait for Type {
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<(ItemStr, ItemStr)>) {
for argument in &self.arguments {
argument.type_imports(modules);
}
if let Some(namespace) = &self.namespace {
modules.insert((namespace.clone(), self.name.clone()));
}
}
}
impl TypeTrait for Array {
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<(ItemStr, ItemStr)>) {
self.inner.type_imports(modules);
}
}
impl TypeTrait for Void {
fn name(&self) -> &str {
"void"
}
fn is_nullable(&self) -> bool {
false
}
}
}
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(());
impl_modifier! {
pub enum Modifier<Csharp> {
Public => "public",
Private => "private",
Internal => "internal",
Protected => "protected",
Abstract => "abstract",
Async => "async",
Const => "const",
Event => "event",
Extern => "extern",
New => "new",
Override => "override",
Partial => "partial",
Readonly => "readonly",
Sealed => "sealed",
Static => "static",
Unsafe => "unsafe",
Virtual => "virtual",
Volatile => "volatile",
}
}
#[derive(Debug, Default)]
pub struct Config {
namespace: Option<ItemStr>,
imported_names: HashMap<String, String>,
}
impl Config {
pub fn with_namespace<N>(self, namespace: N) -> Self
where
N: Into<ItemStr>,
{
Self {
namespace: Some(namespace.into()),
..self
}
}
}
#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
pub struct Optional {
inner: TypeBox,
}
impl_lang_item! {
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<ItemStr>,
name: ItemStr,
path: Vec<ItemStr>,
arguments: Vec<TypeBox>,
qualified: bool,
kind: Kind,
}
impl Type {
pub fn path<P: Into<ItemStr>>(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_lang_item! {
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_lang_item! {
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_lang_item! {
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_lang_item! {
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(tokens: &Tokens, output: &mut Tokens, config: &mut Config) {
let mut modules = BTreeSet::new();
for import in tokens.walk_imports() {
import.type_imports(&mut modules);
}
if modules.is_empty() {
return;
}
let mut imported = HashSet::new();
for (namespace, name) in modules {
if Some(&*namespace) == config.namespace.as_deref() {
continue;
}
match config.imported_names.get(&*name) {
Some(existing) if existing == &*namespace => continue,
Some(_) => continue,
_ => {}
}
if !imported.contains(&*namespace) {
quote_in!(output => using #(&namespace););
output.push();
imported.insert(namespace.to_string());
}
config
.imported_names
.insert(name.to_string(), namespace.to_string());
}
output.line();
}
}
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();
Self::imports(&tokens, &mut toks, config);
if let Some(namespace) = &config.namespace {
quote_in! { toks =>
namespace #namespace {
#tokens
}
}
toks.push()
} else {
toks.append(tokens);
}
toks.format(out, config, level)
}
}
pub fn using<P: Into<ItemStr>, N: Into<ItemStr>>(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<ItemStr>>(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(),
}
}
pub fn block_comment<T>(comment: T) -> BlockComment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
BlockComment(comment)
}