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")) // 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) } 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 && slices.Contains(names, IdentInputs) { 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 } 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 Value](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 = evaluate[T](d, s, expr, &v) return }