Skip to content

Commit 80579cb

Browse files
committed
refactor: move various Go syntax related structs to library; add tests
This commit moves the various Go syntax related structs to a `go` module in a library, adds unit tests for them, and refactors the `main.rs` file to use the library. It doesn't yet do anything with the 'Func' struct or a couple of others, but it's a step in the direction of a more succinct `main.rs` file.
1 parent 4e01932 commit 80579cb

9 files changed

Lines changed: 728 additions & 349 deletions

File tree

cmd/gravity/src/go/comment.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use genco::{
2+
prelude::*,
3+
tokens::{ItemStr, static_literal},
4+
};
5+
6+
/// Format a comment where each line is preceeded by `//`.
7+
/// Based on https://github.com/udoprog/genco/blob/1ec4869f458cf71d1d2ffef77fe051ea8058b391/src/lang/csharp/comment.rs
8+
pub struct Comment<T>(T);
9+
10+
impl<T> FormatInto<Go> for Comment<T>
11+
where
12+
T: IntoIterator,
13+
T::Item: Into<ItemStr>,
14+
{
15+
fn format_into(self, tokens: &mut Tokens<Go>) {
16+
for line in self.0 {
17+
tokens.push();
18+
tokens.append(static_literal("//"));
19+
tokens.space();
20+
tokens.append(line.into());
21+
}
22+
}
23+
}
24+
25+
/// Helper function to create a Go comment.
26+
pub fn comment<T>(comment: T) -> Comment<T>
27+
where
28+
T: IntoIterator,
29+
T::Item: Into<ItemStr>,
30+
{
31+
Comment(comment)
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use genco::{prelude::*, tokens::Tokens};
37+
38+
use crate::go::comment;
39+
40+
#[test]
41+
fn test_comment() {
42+
let comment = comment(&["hello", "world"]);
43+
let mut tokens = Tokens::<Go>::new();
44+
comment.format_into(&mut tokens);
45+
assert_eq!(tokens.to_string().unwrap(), "// hello\n// world");
46+
}
47+
}

cmd/gravity/src/go/embed.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use genco::{
2+
prelude::*,
3+
tokens::{ItemStr, static_literal},
4+
};
5+
6+
/// Type for generating Go embed directives (//go:embed)
7+
pub struct Embed<T>(pub T);
8+
9+
impl<T> FormatInto<Go> for Embed<T>
10+
where
11+
T: Into<ItemStr>,
12+
{
13+
fn format_into(self, tokens: &mut Tokens<Go>) {
14+
// TODO(#13): Submit patch to genco that will allow aliases for go imports
15+
// tokens.register(go::import("embed", ""));
16+
tokens.push();
17+
tokens.append(static_literal("//go:embed"));
18+
tokens.space();
19+
tokens.append(self.0.into());
20+
}
21+
}
22+
23+
/// Helper function to create an embed directive.
24+
pub fn embed<T>(path: T) -> Embed<T>
25+
where
26+
T: Into<ItemStr>,
27+
{
28+
Embed(path)
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::*;
34+
35+
#[test]
36+
fn test_embed_directive() {
37+
let mut tokens = Tokens::<Go>::new();
38+
let embed = embed("module.wasm");
39+
40+
quote_in! { tokens =>
41+
$(embed)
42+
};
43+
44+
let output = tokens.to_string().unwrap();
45+
assert!(output.contains("//go:embed module.wasm"));
46+
}
47+
48+
#[test]
49+
fn test_embed_with_variable() {
50+
let mut tokens = Tokens::<Go>::new();
51+
52+
quote_in! { tokens =>
53+
$(embed("app.wasm"))
54+
var wasmFile []byte
55+
};
56+
57+
let output = tokens.to_string().unwrap();
58+
assert!(output.contains("//go:embed app.wasm"));
59+
assert!(output.contains("var wasmFile []byte"));
60+
}
61+
}

cmd/gravity/src/go/identifier.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::str::Chars;
2+
3+
use genco::{prelude::*, tokens::ItemStr};
4+
5+
/// Represents a Go identifier with appropriate casing rules.
6+
///
7+
/// Go identifiers follow specific naming conventions:
8+
/// - Public identifiers start with uppercase (exported)
9+
/// - Private identifiers start with lowercase (unexported)
10+
/// - Local identifiers are used as-is without transformation
11+
#[derive(Debug, Clone, Copy)]
12+
pub enum GoIdentifier<'a> {
13+
/// Public/exported identifier (will be converted to UpperCamelCase)
14+
Public { name: &'a str },
15+
/// Private/unexported identifier (will be converted to lowerCamelCase)
16+
Private { name: &'a str },
17+
/// Local identifier (will be converted to lowerCamelCase)
18+
Local { name: &'a str },
19+
}
20+
21+
impl<'a> GoIdentifier<'a> {
22+
/// Creates a new public identifier.
23+
pub fn public(name: &'a str) -> Self {
24+
Self::Public { name }
25+
}
26+
27+
/// Creates a new private identifier.
28+
pub fn private(name: &'a str) -> Self {
29+
Self::Private { name }
30+
}
31+
32+
/// Creates a new local identifier.
33+
pub fn local<'b: 'a>(name: &'a str) -> Self {
34+
Self::Local { name }
35+
}
36+
37+
/// Returns an iterator over the characters of the underlying name.
38+
///
39+
/// This provides access to the raw name without case transformations.
40+
///
41+
/// # Returns
42+
/// An iterator over the characters of the identifier's name.
43+
pub fn chars(&self) -> Chars<'a> {
44+
match self {
45+
GoIdentifier::Public { name } => name.chars(),
46+
GoIdentifier::Private { name } => name.chars(),
47+
GoIdentifier::Local { name } => name.chars(),
48+
}
49+
}
50+
}
51+
52+
impl From<GoIdentifier<'_>> for String {
53+
fn from(value: GoIdentifier) -> Self {
54+
let mut tokens: Tokens<Go> = Tokens::new();
55+
value.format_into(&mut tokens);
56+
tokens.to_string().expect("to format correctly")
57+
}
58+
}
59+
60+
impl FormatInto<Go> for &GoIdentifier<'_> {
61+
fn format_into(self, tokens: &mut Tokens<Go>) {
62+
let mut chars = self.chars();
63+
64+
// TODO(#12): Check for invalid first character
65+
66+
if let GoIdentifier::Public { .. } = self {
67+
// https://stackoverflow.com/a/38406885
68+
match chars.next() {
69+
Some(c) => tokens.append(ItemStr::from(c.to_uppercase().to_string())),
70+
None => panic!("No function name"),
71+
};
72+
};
73+
74+
while let Some(c) = chars.next() {
75+
match c {
76+
' ' | '-' | '_' => {
77+
if let Some(c) = chars.next() {
78+
tokens.append(ItemStr::from(c.to_uppercase().to_string()));
79+
}
80+
}
81+
_ => tokens.append(ItemStr::from(c.to_string())),
82+
}
83+
}
84+
}
85+
}
86+
impl FormatInto<Go> for GoIdentifier<'_> {
87+
fn format_into(self, tokens: &mut Tokens<Go>) {
88+
(&self).format_into(tokens)
89+
}
90+
}
91+
92+
#[cfg(test)]
93+
mod tests {
94+
95+
use genco::{prelude::*, tokens::Tokens};
96+
97+
use crate::go::GoIdentifier;
98+
99+
#[test]
100+
fn test_public_identifier() {
101+
let id = GoIdentifier::public("hello-world");
102+
let mut tokens = Tokens::<Go>::new();
103+
(&id).format_into(&mut tokens);
104+
assert_eq!(tokens.to_string().unwrap(), "HelloWorld");
105+
}
106+
107+
#[test]
108+
fn test_private_identifier() {
109+
let id = GoIdentifier::private("hello-world");
110+
let mut tokens = Tokens::<Go>::new();
111+
(&id).format_into(&mut tokens);
112+
assert_eq!(tokens.to_string().unwrap(), "helloWorld");
113+
}
114+
115+
#[test]
116+
fn test_local_identifier() {
117+
let id = GoIdentifier::local("hello-world");
118+
let mut tokens = Tokens::<Go>::new();
119+
(&id).format_into(&mut tokens);
120+
assert_eq!(tokens.to_string().unwrap(), "helloWorld");
121+
}
122+
}

cmd/gravity/src/go/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Representations of Go types, and implementations for formatting them.
2+
3+
mod comment;
4+
mod embed;
5+
#[path = "./type.rs"]
6+
mod go_type;
7+
mod identifier;
8+
mod operand;
9+
mod result;
10+
11+
pub use comment::*;
12+
pub use embed::*;
13+
pub use go_type::*;
14+
pub use identifier::*;
15+
pub use operand::*;
16+
pub use result::*;

cmd/gravity/src/go/operand.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use genco::{
2+
prelude::*,
3+
tokens::{ItemStr, static_literal},
4+
};
5+
6+
/// Represents an operand in Go code generation.
7+
///
8+
/// Operands can be literals, single values (variables), or multi-value tuples
9+
/// (used for functions returning multiple values).
10+
#[derive(Debug, Clone, PartialEq)]
11+
pub enum Operand {
12+
/// A literal value (e.g., "0", "true", "\"hello\"")
13+
Literal(String),
14+
/// A single variable or expression
15+
SingleValue(String),
16+
/// A tuple of two values (for multi-value returns)
17+
MultiValue((String, String)),
18+
}
19+
20+
impl Operand {
21+
/// Returns the primary value of the operand.
22+
///
23+
/// For single values and literals, returns the value itself.
24+
/// For multi-value tuples, returns the first value.
25+
///
26+
/// # Returns
27+
/// A string representation of the primary value.
28+
pub fn as_string(&self) -> String {
29+
match self {
30+
Operand::Literal(s) => s.clone(),
31+
Operand::SingleValue(s) => s.clone(),
32+
Operand::MultiValue((s1, _)) => s1.clone(),
33+
}
34+
}
35+
}
36+
37+
// Implement genco's FormatInto for Operand so it can be used in quote! macros
38+
impl FormatInto<Go> for &Operand {
39+
fn format_into(self, tokens: &mut Tokens<Go>) {
40+
match self {
41+
Operand::Literal(val) => tokens.append(ItemStr::from(val)),
42+
Operand::SingleValue(val) => tokens.append(ItemStr::from(val)),
43+
Operand::MultiValue((val1, val2)) => {
44+
tokens.append(ItemStr::from(val1));
45+
tokens.append(static_literal(","));
46+
tokens.space();
47+
tokens.append(ItemStr::from(val2));
48+
}
49+
}
50+
}
51+
}
52+
53+
impl FormatInto<Go> for Operand {
54+
fn format_into(self, tokens: &mut Tokens<Go>) {
55+
(&self).format_into(tokens)
56+
}
57+
}
58+
59+
impl FormatInto<Go> for &mut Operand {
60+
fn format_into(self, tokens: &mut Tokens<Go>) {
61+
let op: &Operand = self;
62+
op.format_into(tokens)
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use genco::{prelude::*, tokens::Tokens};
69+
70+
use crate::go::Operand;
71+
72+
#[test]
73+
fn test_operand_literal() {
74+
let op = Operand::Literal("42".to_string());
75+
let mut tokens = Tokens::<Go>::new();
76+
op.format_into(&mut tokens);
77+
assert_eq!(tokens.to_string().unwrap(), "42");
78+
}
79+
80+
#[test]
81+
fn test_operand_single_value() {
82+
let op = Operand::SingleValue("myVar".to_string());
83+
let mut tokens = Tokens::<Go>::new();
84+
op.format_into(&mut tokens);
85+
assert_eq!(tokens.to_string().unwrap(), "myVar");
86+
}
87+
88+
#[test]
89+
fn test_operand_multi_value() {
90+
let op = Operand::MultiValue(("val1".to_string(), "val2".to_string()));
91+
let mut tokens = Tokens::<Go>::new();
92+
op.format_into(&mut tokens);
93+
assert_eq!(tokens.to_string().unwrap(), "val1, val2");
94+
}
95+
}

0 commit comments

Comments
 (0)