Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/gravity/src/go/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use genco::{
prelude::*,
tokens::{ItemStr, static_literal},
};

/// Format a comment where each line is preceeded by `//`.
/// Based on https://github.com/udoprog/genco/blob/1ec4869f458cf71d1d2ffef77fe051ea8058b391/src/lang/csharp/comment.rs
pub struct Comment<T>(T);

impl<T> FormatInto<Go> for Comment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Go>) {
for line in self.0 {
tokens.push();
tokens.append(static_literal("//"));
tokens.space();
tokens.append(line.into());
}
}
}

/// Helper function to create a Go comment.
pub fn comment<T>(comment: T) -> Comment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
Comment(comment)
}

#[cfg(test)]
mod tests {
use genco::{prelude::*, tokens::Tokens};

use crate::go::comment;

#[test]
fn test_comment() {
let comment = comment(&["hello", "world"]);
let mut tokens = Tokens::<Go>::new();
comment.format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "// hello\n// world");
}
}
61 changes: 61 additions & 0 deletions cmd/gravity/src/go/embed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use genco::{
prelude::*,
tokens::{ItemStr, static_literal},
};

/// Type for generating Go embed directives (//go:embed)
pub struct Embed<T>(pub T);

impl<T> FormatInto<Go> for Embed<T>
where
T: Into<ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Go>) {
// TODO(#13): Submit patch to genco that will allow aliases for go imports
// tokens.register(go::import("embed", ""));
tokens.push();
tokens.append(static_literal("//go:embed"));
tokens.space();
tokens.append(self.0.into());
}
}

/// Helper function to create an embed directive.
pub fn embed<T>(path: T) -> Embed<T>
where
T: Into<ItemStr>,
{
Embed(path)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_embed_directive() {
let mut tokens = Tokens::<Go>::new();
let embed = embed("module.wasm");

quote_in! { tokens =>
$(embed)
};

let output = tokens.to_string().unwrap();
assert!(output.contains("//go:embed module.wasm"));
}

#[test]
fn test_embed_with_variable() {
let mut tokens = Tokens::<Go>::new();

quote_in! { tokens =>
$(embed("app.wasm"))
var wasmFile []byte
};

let output = tokens.to_string().unwrap();
assert!(output.contains("//go:embed app.wasm"));
assert!(output.contains("var wasmFile []byte"));
}
}
122 changes: 122 additions & 0 deletions cmd/gravity/src/go/identifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::str::Chars;

use genco::{prelude::*, tokens::ItemStr};

/// Represents a Go identifier with appropriate casing rules.
///
/// Go identifiers follow specific naming conventions:
/// - Public identifiers start with uppercase (exported)
/// - Private identifiers start with lowercase (unexported)
/// - Local identifiers are used as-is without transformation
#[derive(Debug, Clone, Copy)]
pub enum GoIdentifier<'a> {
/// Public/exported identifier (will be converted to UpperCamelCase)
Public { name: &'a str },
/// Private/unexported identifier (will be converted to lowerCamelCase)
Private { name: &'a str },
/// Local identifier (will be converted to lowerCamelCase)
Local { name: &'a str },
}

impl<'a> GoIdentifier<'a> {
/// Creates a new public identifier.
pub fn public(name: &'a str) -> Self {
Self::Public { name }
}

/// Creates a new private identifier.
pub fn private(name: &'a str) -> Self {
Self::Private { name }
}

/// Creates a new local identifier.
pub fn local(name: &'a str) -> Self {
Self::Local { name }
}

/// Returns an iterator over the characters of the underlying name.
///
/// This provides access to the raw name without case transformations.
///
/// # Returns
/// An iterator over the characters of the identifier's name.
pub fn chars(&self) -> Chars<'a> {
match self {
GoIdentifier::Public { name } => name.chars(),
GoIdentifier::Private { name } => name.chars(),
GoIdentifier::Local { name } => name.chars(),
}
}
}

impl From<GoIdentifier<'_>> for String {
fn from(value: GoIdentifier) -> Self {
let mut tokens: Tokens<Go> = Tokens::new();
value.format_into(&mut tokens);
tokens.to_string().expect("to format correctly")
}
}

impl FormatInto<Go> for &GoIdentifier<'_> {
fn format_into(self, tokens: &mut Tokens<Go>) {
let mut chars = self.chars();

// TODO(#12): Check for invalid first character

if let GoIdentifier::Public { .. } = self {
// https://stackoverflow.com/a/38406885
match chars.next() {
Some(c) => tokens.append(ItemStr::from(c.to_uppercase().to_string())),
None => panic!("No function name"),
};
};

while let Some(c) = chars.next() {
match c {
' ' | '-' | '_' => {
if let Some(c) = chars.next() {
tokens.append(ItemStr::from(c.to_uppercase().to_string()));
}
}
_ => tokens.append(ItemStr::from(c.to_string())),
}
}
}
}
impl FormatInto<Go> for GoIdentifier<'_> {
fn format_into(self, tokens: &mut Tokens<Go>) {
(&self).format_into(tokens)
}
}

#[cfg(test)]
mod tests {

use genco::{prelude::*, tokens::Tokens};

use crate::go::GoIdentifier;

#[test]
fn test_public_identifier() {
let id = GoIdentifier::public("hello-world");
let mut tokens = Tokens::<Go>::new();
(&id).format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "HelloWorld");
}

#[test]
fn test_private_identifier() {
let id = GoIdentifier::private("hello-world");
let mut tokens = Tokens::<Go>::new();
(&id).format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "helloWorld");
}

#[test]
fn test_local_identifier() {
let id = GoIdentifier::local("hello-world");
let mut tokens = Tokens::<Go>::new();
(&id).format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "helloWorld");
}
}
16 changes: 16 additions & 0 deletions cmd/gravity/src/go/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Representations of Go types, and implementations for formatting them.

mod comment;
mod embed;
#[path = "./type.rs"]
mod go_type;
mod identifier;
mod operand;
mod result;

pub use comment::*;
pub use embed::*;
pub use go_type::*;
pub use identifier::*;
pub use operand::*;
pub use result::*;
95 changes: 95 additions & 0 deletions cmd/gravity/src/go/operand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use genco::{
prelude::*,
tokens::{ItemStr, static_literal},
};

/// Represents an operand in Go code generation.
///
/// Operands can be literals, single values (variables), or multi-value tuples
/// (used for functions returning multiple values).
#[derive(Debug, Clone, PartialEq)]
pub enum Operand {
/// A literal value (e.g., "0", "true", "\"hello\"")
Literal(String),
/// A single variable or expression
SingleValue(String),
/// A tuple of two values (for multi-value returns)
MultiValue((String, String)),
}

impl Operand {
/// Returns the primary value of the operand.
///
/// For single values and literals, returns the value itself.
/// For multi-value tuples, returns the first value.
///
/// # Returns
/// A string representation of the primary value.
pub fn as_string(&self) -> String {
match self {
Operand::Literal(s) => s.clone(),
Operand::SingleValue(s) => s.clone(),
Operand::MultiValue((s1, _)) => s1.clone(),
}
}
}

// Implement genco's FormatInto for Operand so it can be used in quote! macros
impl FormatInto<Go> for &Operand {
fn format_into(self, tokens: &mut Tokens<Go>) {
match self {
Operand::Literal(val) => tokens.append(ItemStr::from(val)),
Operand::SingleValue(val) => tokens.append(ItemStr::from(val)),
Operand::MultiValue((val1, val2)) => {
tokens.append(ItemStr::from(val1));
tokens.append(static_literal(","));
tokens.space();
tokens.append(ItemStr::from(val2));
}
}
}
}

impl FormatInto<Go> for Operand {
fn format_into(self, tokens: &mut Tokens<Go>) {
(&self).format_into(tokens)
}
}

impl FormatInto<Go> for &mut Operand {
fn format_into(self, tokens: &mut Tokens<Go>) {
let op: &Operand = self;
op.format_into(tokens)
}
}

#[cfg(test)]
mod tests {
use genco::{prelude::*, tokens::Tokens};

use crate::go::Operand;

#[test]
fn test_operand_literal() {
let op = Operand::Literal("42".to_string());
let mut tokens = Tokens::<Go>::new();
op.format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "42");
}

#[test]
fn test_operand_single_value() {
let op = Operand::SingleValue("myVar".to_string());
let mut tokens = Tokens::<Go>::new();
op.format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "myVar");
}

#[test]
fn test_operand_multi_value() {
let op = Operand::MultiValue(("val1".to_string(), "val2".to_string()));
let mut tokens = Tokens::<Go>::new();
op.format_into(&mut tokens);
assert_eq!(tokens.to_string().unwrap(), "val1, val2");
}
}
Loading