diff options
| -rw-r--r-- | internal/rosa/azalea/azalea.go | 92 | ||||
| -rw-r--r-- | internal/rosa/azalea/evaluate.go | 33 |
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 |
