aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/rosa
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-30 22:30:58 +0900
committerOphestra <cat@gensokyo.uk>2026-10-30 22:30:58 +0900
commit1b9124e88ab86e8a91384066cd96adbdfcf8bc4a (patch)
tree97416b4fd4cefe27fd3ab051c79fc968abd50fd9 /internal/rosa
parent8969642abedfdc2d7e70fa695709b891ff631bae (diff)
internal/rosa/azalea: re-construct expr text
This significantly helps with debugging: the %#v text is incredibly unhelpful to read, and just ends up filling the terminal with useless information. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/rosa')
-rw-r--r--internal/rosa/azalea/azalea.go92
-rw-r--r--internal/rosa/azalea/evaluate.go33
2 files changed, 122 insertions, 3 deletions
diff --git a/internal/rosa/azalea/azalea.go b/internal/rosa/azalea/azalea.go
index 5e044214..94e54585 100644
--- a/internal/rosa/azalea/azalea.go
+++ b/internal/rosa/azalea/azalea.go
@@ -6,6 +6,8 @@ import (
"errors"
"io"
"strconv"
+ "strings"
+ "sync/atomic"
"text/scanner"
)
@@ -72,6 +74,7 @@ func (p *parser) scanAs(expects rune) { p.scan(); p.expects(expects) }
// An Int is the value represented by an integer literal.
type Int int64
+func (v Int) String() string { return strconv.FormatInt(int64(v), 10) }
func (v Int) GoString() string {
return "azalea.Int(" + strconv.FormatInt(int64(v), 10) + ")"
}
@@ -86,6 +89,7 @@ func (p *parser) parseInt() Int {
// A String holds the unquoted content of a string literal.
type String string
+func (v String) String() string { return strconv.Quote(string(v)) }
func (v String) GoString() string {
return "azalea.String(" + strconv.Quote(string(v)) + ")"
}
@@ -99,6 +103,7 @@ func (p *parser) parseString() String {
// An Ident holds the name of an identifier.
type Ident string
+func (v Ident) String() string { return string(v) }
func (v Ident) GoString() string {
return "azalea.Ident(" + strconv.Quote(string(v)) + ")"
}
@@ -107,6 +112,18 @@ func (v Ident) GoString() string {
// supports concatenation.
type Val []any
+func (v Val) String() string {
+ if len(v) == 0 {
+ return `""`
+ }
+
+ f := make([]string, len(v))
+ for i, e := range v {
+ f[i] = exprTextIndent(e)
+ }
+ return strings.Join(f, "+\n\t")
+}
+
// parseVal parses until the end of the [Val].
func (p *parser) parseVal() (v Val) {
v = append(v, p.parseExpr())
@@ -120,9 +137,30 @@ func (p *parser) parseVal() (v Val) {
// An Array holds statements in an array.
type Array []Val
+func (v Array) String() string {
+ if len(v) == 0 {
+ return "[]"
+ } else if len(v) == 1 {
+ return "[ " + ExprText(v[0]) + " ]"
+ }
+
+ f := make([]string, len(v))
+ for i, e := range v {
+ f[i] = exprTextIndent(e)
+ }
+ return "[\n\t" + strings.Join(f, ",\n\t") + "\n]"
+}
+
// A KV holds a key/value pair.
type KV struct{ K, V Val }
+func (v KV) String() string {
+ if len(v.V) == 0 {
+ return v.K.String() + ";"
+ }
+ return v.K.String() + ": " + v.V.String() + ";"
+}
+
// An Arg represents an argument of [Func].
type Arg struct {
K []Ident
@@ -130,6 +168,24 @@ type Arg struct {
R bool
}
+func (v Arg) String() string {
+ k := make([]string, len(v.K))
+ for i, e := range v.K {
+ k[i] = string(e)
+ }
+ var r string
+ if v.R {
+ r = "#"
+ }
+
+ return strings.Join(k, ", ") + r + " = " + v.V.String() + ";"
+}
+
+// Nil represents an untyped nil.
+type Nil struct{}
+
+func (Nil) String() string { return "nil" }
+
// Func is a function call or package declaration.
type Func struct {
// Function or package identifier.
@@ -140,6 +196,42 @@ type Func struct {
Args []Arg
}
+// fullFuncString controls whether [Func.String] includes the function body.
+var fullFuncString atomic.Bool
+
+// SetFuncString configures the verbosity of the [fmt.Stringer] string of the
+// [Func] structure. This mainly affects error messages.
+func SetFuncString(full bool) (old bool) {
+ return fullFuncString.Swap(full)
+}
+
+func (v Func) String() string {
+ var buf strings.Builder
+ if v.Package {
+ buf.WriteString("package ")
+ }
+ buf.WriteString(string(v.Ident))
+
+ if !fullFuncString.Load() {
+ buf.WriteString(" ...")
+ return buf.String()
+ }
+
+ if len(v.Args) == 0 {
+ buf.WriteString(" {}")
+ return buf.String()
+ }
+
+ buf.WriteString(" {\n")
+ for _, a := range v.Args {
+ buf.WriteByte('\t')
+ buf.WriteString(a.String())
+ buf.WriteByte('\n')
+ }
+ buf.WriteByte('}')
+ return buf.String()
+}
+
// parseExpr parses the current expression.
func (p *parser) parseExpr() any {
switch p.tok {
diff --git a/internal/rosa/azalea/evaluate.go b/internal/rosa/azalea/evaluate.go
index 1fb730f3..3b55f863 100644
--- a/internal/rosa/azalea/evaluate.go
+++ b/internal/rosa/azalea/evaluate.go
@@ -6,6 +6,7 @@ import (
"maps"
"reflect"
"slices"
+ "strconv"
"strings"
"unique"
)
@@ -34,9 +35,6 @@ type (
V map[unique.Handle[Ident]]any
}
- // Nil represents an untyped nil.
- Nil struct{}
-
// AString represents an argument string that may be nil.
AString struct {
V string
@@ -184,6 +182,35 @@ func store[T Value](rp any, r T) {
}
}
+// ExprText is like the %#v printf verb, but for azalea expressions.
+func ExprText(expr any) string {
+ switch v := expr.(type) {
+ case fmt.Stringer:
+ return v.String()
+
+ case bool:
+ return strconv.FormatBool(v)
+
+ case []KV:
+ if len(v) == 0 {
+ return "{}"
+ }
+ s := make([]string, len(v))
+ for i, kv := range v {
+ s[i] = kv.String()
+ }
+ return "{\n\t" + strings.Join(s, "\n\t") + "\n}"
+
+ default:
+ return fmt.Sprint(expr)
+ }
+}
+
+// exprTextIndent is like [ExprText], but advances indentation by one level.
+func exprTextIndent(expr any) string {
+ return strings.ReplaceAll(ExprText(expr), "\n", "\n\t")
+}
+
// EvaluationError is an error and the expression it occurred in.
type EvaluationError struct {
Expr any