diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-05-16 21:54:40 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-05-17 12:44:34 +0900 |
| commit | c2ff9c9fa55ea569a5e70ee90d2eb0b2ee2a0540 (patch) | |
| tree | fa1eaf0fcbd8bbd29fb3eb156a1c2fd8d35c66f8 /internal/rosa/azalea/evaluate.go | |
| parent | d38d306147979cbc7f161d1150d6786572598497 (diff) | |
internal/rosa/azalea: evaluator
Performance is sufficient for the use case, despite the fact that I could not even think of a lower-effort way to do this:
BenchmarkParse-128 55100 21494 ns/op
BenchmarkEvaluate-128 131670 9248 ns/op
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/rosa/azalea/evaluate.go')
| -rw-r--r-- | internal/rosa/azalea/evaluate.go | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/internal/rosa/azalea/evaluate.go b/internal/rosa/azalea/evaluate.go new file mode 100644 index 00000000..431d6944 --- /dev/null +++ b/internal/rosa/azalea/evaluate.go @@ -0,0 +1,308 @@ +package azalea + +import ( + "errors" + "fmt" + "maps" + "reflect" + "unique" +) + +// Value are types supported by the language. +type Value interface { + bool | int64 | string | []string | [][2]string +} + +type ( + // FArg is an argument passed to [F]. + FArg struct { + K unique.Handle[Ident] + V any + R bool + } + // FArgs are arguments passed to [F]. + FArgs []FArg + + // F is the implementation of a [Func]. + F struct { + F func(isPackage bool, args FArgs) (v any, set bool, err error) + V map[unique.Handle[Ident]]any + } +) + +// Apply applies named arguments and rejects unused arguments. +func (args FArgs) Apply(v map[unique.Handle[Ident]]any) error { + for _, arg := range args { + if arg.V == nil { + // unset + continue + } + + r, ok := v[arg.K] + if !ok { + if arg.R { + continue + } + return UndefinedError(arg.K.Value()) + } + err := storeE(r, arg.V) + if err != nil { + return err + } + } + return nil +} + +// A Frame refers to local variables and debugging information. +type Frame struct { + // Local constants. + Val map[unique.Handle[Ident]]any + // Functions. + Func map[unique.Handle[Ident]]F +} + +// UnsupportedExprError is an expression with invalid concrete type. +type UnsupportedExprError struct{ E any } + +func (e UnsupportedExprError) Error() string { + return fmt.Sprintf("unsupported expression %#v", e.E) +} + +// UndefinedError is an identifier not defined in any stack frame visible to the +// expression containing it. +type UndefinedError Ident + +func (e UndefinedError) Error() string { + return "undefined: " + string(e) +} + +// evaluate is evaluateAny with a type parameter. +func evaluate[T Value](s []Frame, expr any, rp *T) bool { + return evaluateAny(s, expr, rp) +} + +// TypeError is an unexpected type during evaluation. +type TypeError struct { + Concrete, Asserted reflect.Type +} + +func (e TypeError) Error() string { + return "expected " + e.Asserted.String() + ", got " + e.Concrete.String() +} + +func (e TypeError) Is(err error) bool { + var v TypeError + return errors.As(err, &v) && + e.Asserted == v.Asserted && + e.Concrete == v.Concrete +} + +// storeE is a convenience function to set the value of a result pointer. +func storeE(rp any, r any) error { + pv := reflect.ValueOf(rp).Elem() + v := reflect.ValueOf(r) + pt, vt := pv.Type(), v.Type() + if !vt.AssignableTo(pt) { + return TypeError{vt, pt} + } + pv.Set(v) + return nil +} + +// store is like storeE, but panics if error is non-nil. +func store[T Value](rp any, r T) { + err := storeE(rp, r) + if err != nil { + panic(err) + } +} + +// EvaluationError is an error and the expression it occurred in. +type EvaluationError struct { + Expr any + Err error +} + +// Unwrap returns the underlying error. +func (e EvaluationError) Unwrap() error { return e.Err } + +// Error returns a very long error description that should not be presented +// to the user directly. +func (e EvaluationError) Error() string { + return fmt.Sprintf("expression %#v: %v", e.Expr, e.Err) +} + +// evaluateAny implements [Evaluate]. +func evaluateAny(s []Frame, expr, rp any) bool { + defer func() { + r := recover() + if r == nil { + return + } + + err, ok := r.(error) + if !ok { + panic(r) + } + + if _, ok = err.(EvaluationError); ok { + panic(err) + } + panic(EvaluationError{expr, err}) + }() + + switch e := expr.(type) { + case Int: + store(rp, int64(e)) + return true + + case String: + store(rp, string(e)) + return true + + case Ident: + var ( + v any + ok bool + ) + for i := range s { + v, ok = s[len(s)-1-i].Val[unique.Make(e)] + if ok { + break + } + } + if !ok { + panic(UndefinedError(e)) + } + if err := storeE(rp, v); err != nil { + panic(err) + } + return true + + case Val: + if len(e) == 1 { + switch v := e[0].(type) { + case Ident: + switch v { + case "unset": + return false + case "true": + store(rp, true) + return true + case "false": + store(rp, false) + return true + default: + return evaluateAny(s, v, rp) + } + + default: + return evaluateAny(s, e[0], rp) + } + } + var v string + for i := range e { + var _r string + if evaluate(s, e[i], &_r) { + v += _r + } + } + store(rp, v) + return true + + case Array: + r := make([]string, 0, len(e)) + for i := range e { + var _r string + if evaluate(s, e[i], &_r) { + r = append(r, _r) + } + } + store(rp, r) + return true + + case []KV: + r := make([][2]string, 0, len(e)) + for i := range e { + var _r string + if e[i].V == nil || evaluate(s, e[i].V, &_r) { + r = append(r, [2]string{string(e[i].K), _r}) + } + } + store(rp, r) + return true + + case Func: + var ( + f F + ok bool + ) + for i := range s { + f, ok = s[len(s)-1-i].Func[unique.Make(e.Ident)] + if ok { + break + } + } + if !ok { + panic(UndefinedError(e.Ident)) + } + + argc := len(e.Args) + for _, arg := range e.Args { + argc += len(arg.K) - 1 + } + fargs := make([]FArg, 0, len(e.Args)) + s = append(s, Frame{Val: maps.Clone(f.V)}) + fp := &s[len(s)-1] + + for _, arg := range e.Args { + farg := FArg{R: arg.R} + if !evaluateAny(s, arg.V, &farg.V) { + farg.V = nil + } + for _, name := range arg.K { + h := unique.Make(name) + farg.K = h + fargs = append(fargs, farg) + + if arg.R && farg.V != nil { + if fp.Val == nil { + fp.Val = make(map[unique.Handle[Ident]]any) + } + (*fp).Val[h] = farg.V + } + } + } + + v, set, err := f.F(e.Package, fargs) + if err != nil { + panic(err) + } else if v != nil { + if err = storeE(rp, v); err != nil { + panic(err) + } + } + return set + + default: + panic(UnsupportedExprError{expr}) + } +} + +// Evaluate evaluates a statement and returns its value. +func Evaluate[T Value](s []Frame, expr any) (v T, set bool, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + _err, ok := r.(error) + if !ok { + panic(r) + } + err = _err + }() + set = evaluate[T](s, expr, &v) + return +} |
