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
64 changes: 61 additions & 3 deletions poet/code.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package poet

import (
"fmt"
"regexp"
"strings"
)

const (
replaceTypeLiteral rune = 'L'
replaceTypeString rune = 'S'
replaceTypeType rune = 'T'
)

var stringFormatRegex = regexp.MustCompile(`\${1,2}\d*[LST]`)

type Code struct {
RawCode string
IsFlow bool
Expand All @@ -15,8 +25,56 @@ type Code struct {
Statements []Code
}

func stringify(raw any) string {
if raw == nil {
return "null"
}

return fmt.Sprintf("%v", raw)
}

func formatRawCode(ctx *Context, rawCode string, arguments []any) string {
return ""
matchIndex := 0

return stringFormatRegex.ReplaceAllStringFunc(rawCode, func(match string) string {
// if the pattern is escaped
if strings.HasPrefix(match, "$$") {
return match[1:]
}

argumentIndex, replaceType := 0, rune(match[len(match)-1])
for i := 1; i < len(match)-1; i++ {
argumentIndex = (argumentIndex * 10) + int(match[i]-'0')
}

argumentIndex -= 1
if argumentIndex < 0 {
argumentIndex = matchIndex
}

if argumentIndex > len(arguments)-1 {
// tried to access an argument that is not there - TODO allow errors to be returned from formatting
return match
}

replacement := match
switch replaceType {
case replaceTypeLiteral:
replacement = stringify(arguments[argumentIndex])
case replaceTypeString:
replacement = fmt.Sprintf("%q", stringify(arguments[argumentIndex]))
case replaceTypeType:
// it is unlikely that a user will ever want to include the generic constraint in the formatted
// value - in the future could make this configurable through extended replace type codes
replacement = arguments[argumentIndex].(TypeName).Format(ctx, ExcludeConstraints)
}

if len(match) == 2 {
matchIndex += 1
}

return replacement
})
}

func formatStatements(ctx *Context, statements []Code) string {
Expand Down Expand Up @@ -47,7 +105,7 @@ func (c *Code) Format(ctx *Context) string {

if c.IsFlow {
// Control flow statement
sb.WriteString(c.RawCode)
sb.WriteString(formatRawCode(ctx, c.RawCode, c.Arguments))
sb.WriteString(" {\n")
sb.WriteString(ctx.indent(formatStatements(ctx, c.Statements)))
sb.WriteString("}")
Expand All @@ -57,7 +115,7 @@ func (c *Code) Format(ctx *Context) string {

if c.RawCode != "" && !c.IsFlow {
// Simple statement
sb.WriteString(c.RawCode)
sb.WriteString(formatRawCode(ctx, c.RawCode, c.Arguments))
if !strings.HasSuffix(c.RawCode, ";") {
sb.WriteRune(';')
}
Expand Down
104 changes: 104 additions & 0 deletions poet/code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package poet

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestCodeBuilder_StringFormatting(t *testing.T) {
tests := []struct {
name string
code string
expected string
args []any
}{
{
name: "simple replacement",
code: "$T $L = $S",
expected: `String test = "a value"`,
args: []any{String, "test", "a value"},
},
{
name: "complex replacement",
code: "$T $3L = $2S + $3S",
expected: `String test = "a value" + "test"`,
args: []any{String, "a value", "test"},
},
{
name: "nil values",
code: "$1L $1S",
expected: `null "null"`,
args: []any{nil},
},
{
name: "bool values",
code: "$1L $1S",
expected: `true "true"`,
args: []any{true},
},
{
name: "int values",
code: "$1L $1S",
expected: `1 "1"`,
args: []any{1},
},
{
name: "float values",
code: "$1L $1S",
expected: `1.1 "1.1"`,
args: []any{1.1},
},
{
name: "string values",
code: "$1L $1S",
expected: `hello "hello"`,
args: []any{"hello"},
},
{
name: "quoted strings",
code: "$1L $1S",
expected: `"hello" "\"hello\""`,
args: []any{`"hello"`},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

assert := assert.New(t)

ctx := NewContext("io.github.tandemdude")

code := NewCodeBuilder().
AddStatement(tt.code, tt.args...).
Build()

assert.Equal(tt.expected+";\n", code.Format(ctx))
})
}
}

func TestCodeBuilder_MultipleStatements(t *testing.T) {
assert := assert.New(t)

ctx := NewContext("io.github.tandemdude")

code := NewCodeBuilder().
AddStatement("$L $S", false, true).
AddStatement("$L $S", true, false).
Build()

assert.Equal("false \"true\";\ntrue \"false\";\n", code.Format(ctx))
}

func TestCodeBuilder_NoStatements(t *testing.T) {
assert := assert.New(t)

ctx := NewContext("io.github.tandemdude")

code := NewCodeBuilder().Build()

assert.Equal("", code.Format(ctx))
}
Loading