forked from rosa/hakurei
Supported fields are still rather minimal, but evaluation works, and resulting artifacts cure correctly. Signed-off-by: Ophestra <cat@gensokyo.uk>
362 lines
6.9 KiB
Go
362 lines
6.9 KiB
Go
package azalea
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"reflect"
|
|
"slices"
|
|
"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
|
|
|
|
// PF implements the package declaration function.
|
|
PF func(name Ident, args FArgs) (v any, set bool, err error)
|
|
|
|
// F is the implementation of a [Func].
|
|
F struct {
|
|
F func(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](d PF, s []Frame, expr any, rp *T) bool {
|
|
return evaluateAny(d, 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)
|
|
}
|
|
|
|
var (
|
|
// IdentInputs is a special array argument in a package declaration whose
|
|
// values of [Ident] are kept as is when passed to a [PF].
|
|
IdentInputs = unique.Make(Ident("inputs"))
|
|
// IdentRuntime has the same semantics as [IdentInputs].
|
|
IdentRuntime = unique.Make(Ident("runtime"))
|
|
|
|
// ErrInvalidInputs is panicked for an [IdentInputs] argument to [PF]
|
|
// sharing its value or set for R.
|
|
ErrInvalidInputs = errors.New("inputs must not be common or bound to scope")
|
|
)
|
|
|
|
// evaluateAny implements [Evaluate].
|
|
func evaluateAny(d PF, 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(d, s, v, rp)
|
|
}
|
|
|
|
default:
|
|
return evaluateAny(d, s, e[0], rp)
|
|
}
|
|
}
|
|
var v string
|
|
for i := range e {
|
|
var _r string
|
|
if evaluate(d, 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(d, 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(d, 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
|
|
)
|
|
if !e.Package {
|
|
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{})
|
|
fp := &s[len(s)-1]
|
|
if !e.Package {
|
|
fp.Val = maps.Clone(f.V)
|
|
}
|
|
|
|
args:
|
|
for _, arg := range e.Args {
|
|
names := make([]unique.Handle[Ident], len(arg.K))
|
|
for i, name := range arg.K {
|
|
names[i] = unique.Make(name)
|
|
}
|
|
|
|
farg := FArg{R: arg.R}
|
|
if e.Package {
|
|
for _, special := range [...]unique.Handle[Ident]{
|
|
IdentInputs,
|
|
IdentRuntime,
|
|
} {
|
|
if slices.Contains(names, special) {
|
|
if len(names) != 1 || len(arg.V) != 1 || arg.R {
|
|
panic(ErrInvalidInputs)
|
|
}
|
|
farg.K = names[0]
|
|
if err := storeE(&farg.V, arg.V[0]); err != nil {
|
|
panic(err)
|
|
}
|
|
fargs = append(fargs, farg)
|
|
continue args
|
|
}
|
|
}
|
|
}
|
|
|
|
if !evaluateAny(d, s, arg.V, &farg.V) {
|
|
farg.V = nil
|
|
}
|
|
for _, name := range names {
|
|
farg.K = name
|
|
fargs = append(fargs, farg)
|
|
|
|
if arg.R && farg.V != nil {
|
|
if fp.Val == nil {
|
|
fp.Val = make(map[unique.Handle[Ident]]any)
|
|
}
|
|
(*fp).Val[name] = farg.V
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
v any
|
|
err error
|
|
)
|
|
if !e.Package {
|
|
v, ok, err = f.F(fargs)
|
|
} else {
|
|
v, ok, err = d(e.Ident, fargs)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
} else if v != nil {
|
|
if err = storeE(rp, v); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return ok
|
|
|
|
default:
|
|
panic(UnsupportedExprError{expr})
|
|
}
|
|
}
|
|
|
|
// Evaluate evaluates a statement and returns its value.
|
|
func Evaluate[T any](d PF, 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 = evaluateAny(d, s, expr, &v)
|
|
return
|
|
}
|