use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Language {
Rust,
Cpp,
C,
Java,
CSharp,
Python,
TypeScript,
Go,
OCaml,
Ada,
D,
Elixir,
Julia,
Lua,
Nim,
Swift,
Zig,
Node,
Ada83,
FSharp,
Kotlin,
}
impl Language {
pub const ALL: [Self; 21] = [
Self::Rust,
Self::Cpp,
Self::C,
Self::Java,
Self::CSharp,
Self::Python,
Self::TypeScript,
Self::Go,
Self::OCaml,
Self::Ada,
Self::D,
Self::Elixir,
Self::Julia,
Self::Lua,
Self::Nim,
Self::Swift,
Self::Zig,
Self::Node,
Self::Ada83,
Self::FSharp,
Self::Kotlin,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Rust => "rust",
Self::Cpp => "c++",
Self::C => "c",
Self::Java => "java",
Self::CSharp => "c#",
Self::Python => "python",
Self::TypeScript => "typescript",
Self::Go => "go",
Self::OCaml => "ocaml",
Self::Ada => "ada",
Self::D => "d",
Self::Elixir => "elixir",
Self::Julia => "julia",
Self::Lua => "lua",
Self::Nim => "nim",
Self::Swift => "swift",
Self::Zig => "zig",
Self::Node => "node",
Self::Ada83 => "ada83",
Self::FSharp => "f#",
Self::Kotlin => "kotlin",
}
}
#[must_use]
pub const fn extension(self) -> &'static str {
match self {
Self::Rust => "rs",
Self::Cpp => "hpp",
Self::C => "h",
Self::Java => "java",
Self::CSharp => "cs",
Self::Python => "py",
Self::TypeScript => "ts",
Self::Go => "go",
Self::OCaml => "ml",
Self::Ada => "ads",
Self::D => "d",
Self::Elixir => "ex",
Self::Julia => "jl",
Self::Lua => "lua",
Self::Nim => "nim",
Self::Swift => "swift",
Self::Zig => "zig",
Self::Node => "js",
Self::Ada83 => "ads",
Self::FSharp => "fs",
Self::Kotlin => "kt",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Ident(String);
impl Ident {
#[must_use]
pub fn new(text: &str) -> Self {
Self(text.to_string())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Path {
segments: Vec<Ident>,
rooted: bool,
}
impl Path {
#[must_use]
pub fn new(segments: &[&str]) -> Self {
Self {
segments: segments.iter().map(|s| Ident::new(s)).collect(),
rooted: false,
}
}
#[must_use]
pub fn rooted(segments: &[&str]) -> Self {
Self {
segments: segments.iter().map(|s| Ident::new(s)).collect(),
rooted: true,
}
}
#[must_use]
pub fn single(name: &str) -> Self {
Self::new(&[name])
}
#[must_use]
pub fn segments(&self) -> &[Ident] {
&self.segments
}
#[must_use]
pub const fn is_rooted(&self) -> bool {
self.rooted
}
#[must_use]
pub fn last(&self) -> Option<&Ident> {
self.segments.last()
}
#[must_use]
pub fn join(&self, name: &str) -> Self {
let mut out = self.clone();
out.segments.push(Ident::new(name));
out
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Doc {
lines: Vec<String>,
}
impl Doc {
#[must_use]
pub fn none() -> Self {
Self::default()
}
#[must_use]
pub fn line(text: &str) -> Self {
Self {
lines: alloc::vec![text.to_string()],
}
}
#[must_use]
pub fn lines(lines: &[&str]) -> Self {
Self {
lines: lines.iter().map(|line| (*line).to_string()).collect(),
}
}
pub fn push(&mut self, text: &str) {
self.lines.push(text.to_string());
}
#[must_use]
pub fn as_lines(&self) -> &[String] {
&self.lines
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BodyText {
pub begin: Vec<String>,
pub end: Vec<String>,
}
impl BodyText {
#[must_use]
pub fn none() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Literal {
Int(String),
Float(String),
Bool(bool),
Char(char),
Str(String),
Bytes(Vec<u8>),
Null,
}
impl Literal {
#[must_use]
pub fn int(value: i128) -> Self {
Self::Int(alloc::format!("{value}"))
}
#[must_use]
pub fn string(text: &str) -> Self {
Self::Str(text.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Visibility {
Public,
Internal,
#[default]
Private,
}
impl Visibility {
pub const ALL: [Self; 3] = [Self::Public, Self::Internal, Self::Private];
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn a_path_keeps_its_segments_in_order_and_knows_whether_it_is_rooted() {
let path = Path::rooted(&["Robot", "Pose"]);
assert!(path.is_rooted());
assert_eq!(path.segments().len(), 2);
assert_eq!(path.last().map(Ident::as_str), Some("Pose"));
let joined = path.join("Inner");
assert_eq!(joined.segments().len(), 3);
assert!(joined.is_rooted(), "joining lost the root");
}
#[test]
fn an_identifier_is_written_exactly_as_it_was_given() {
for name in ["Pose", "type", "class", "_reserved"] {
assert_eq!(Ident::new(name).as_str(), name);
}
}
#[test]
fn every_language_has_its_own_name_and_extension() {
let mut seen = alloc::vec::Vec::new();
for language in Language::ALL {
assert!(!seen.contains(&language.as_str()));
seen.push(language.as_str());
assert!(!language.extension().is_empty());
}
assert_eq!(seen.len(), Language::ALL.len());
}
#[test]
fn a_doc_comment_keeps_its_lines_apart() {
let doc = Doc::lines(&["first", "second"]);
assert_eq!(doc.as_lines().len(), 2);
assert!(!doc.is_empty());
assert!(Doc::none().is_empty());
}
}