mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
feat(verifier): goja runtime hosting the spec API
Installs globalThis.__uatu__ with extract, always, actions, weighted, tap, inputText, and stub taps/swipes. Load runs the bundled spec, then pulls properties + actions out of globalThis. PushSnapshot rebuilds state.snapshots and refreshes every extractor handle's current/previous in registration order so chained extractors observe up-to-date values. Properties are wired through internal/ltl as Always(Thunk(...)), so verdicts latch to violated as soon as a predicate returns false. NextAction resolves actions/weighted recursively with a seedable rand source for reproducible runs.
This commit is contained in:
1 parent
29c6ea4842
commit
72175bb13c
5 files changed
+501
No files matched your search
@@ -0,0 +1,169 @@
|
||||
package verifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type ActionKind string
|
||||
|
||||
const (
|
||||
ActionKindTap ActionKind = "Tap"
|
||||
ActionKindInputText ActionKind = "InputText"
|
||||
)
|
||||
|
||||
type Action struct {
|
||||
Kind ActionKind
|
||||
On string
|
||||
Text string
|
||||
}
|
||||
|
||||
type extractorState struct {
|
||||
getter goja.Callable
|
||||
handle *goja.Object
|
||||
}
|
||||
|
||||
type formulaState struct {
|
||||
predicate goja.Callable
|
||||
}
|
||||
|
||||
const (
|
||||
tagFormula = "__uatuFormula"
|
||||
tagActionGenerator = "__uatuActionGenerator"
|
||||
tagInternalKind = "__uatuKind"
|
||||
internalKindActions = "actions"
|
||||
internalKindWeighted = "weighted"
|
||||
internalKindBuiltinTaps = "taps"
|
||||
internalKindBuiltinSwipes = "swipes"
|
||||
)
|
||||
|
||||
// installRuntimeBindings exposes globalThis.__uatu__ to the loaded spec.
|
||||
func (v *Verifier) installRuntimeBindings() error {
|
||||
uatu := v.runtime.NewObject()
|
||||
|
||||
if err := uatu.Set("extract", v.bindExtract); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("always", v.bindAlways); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("actions", v.bindActions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("weighted", v.bindWeighted); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("tap", v.bindTap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("inputText", v.bindInputText); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("taps", v.builtinGenerator(internalKindBuiltinTaps)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("swipes", v.builtinGenerator(internalKindBuiltinSwipes)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.runtime.GlobalObject().Set("__uatu__", uatu)
|
||||
}
|
||||
|
||||
func (v *Verifier) bindExtract(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
panic(v.runtime.NewTypeError("extract requires exactly one argument"))
|
||||
}
|
||||
getter, ok := goja.AssertFunction(call.Arguments[0])
|
||||
if !ok {
|
||||
panic(v.runtime.NewTypeError("extract argument must be a function"))
|
||||
}
|
||||
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set("current", goja.Undefined())
|
||||
_ = handle.Set("previous", goja.Undefined())
|
||||
|
||||
v.extractors = append(v.extractors, &extractorState{getter: getter, handle: handle})
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) bindAlways(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
panic(v.runtime.NewTypeError("always requires exactly one argument"))
|
||||
}
|
||||
predicate, ok := goja.AssertFunction(call.Arguments[0])
|
||||
if !ok {
|
||||
panic(v.runtime.NewTypeError("always argument must be a function"))
|
||||
}
|
||||
|
||||
formula := &formulaState{predicate: predicate}
|
||||
v.formulas = append(v.formulas, formula)
|
||||
formulaIndex := len(v.formulas) - 1
|
||||
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set(tagFormula, true)
|
||||
_ = handle.Set("__uatuIndex", formulaIndex)
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) bindActions(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
panic(v.runtime.NewTypeError("actions requires a single generator argument"))
|
||||
}
|
||||
if _, ok := goja.AssertFunction(call.Arguments[0]); !ok {
|
||||
panic(v.runtime.NewTypeError("actions argument must be a function"))
|
||||
}
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set(tagActionGenerator, true)
|
||||
_ = handle.Set(tagInternalKind, internalKindActions)
|
||||
_ = handle.Set("generate", call.Arguments[0])
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) bindWeighted(call goja.FunctionCall) goja.Value {
|
||||
entries := v.runtime.NewArray()
|
||||
for index, argument := range call.Arguments {
|
||||
object := argument.ToObject(v.runtime)
|
||||
if object == nil {
|
||||
panic(v.runtime.NewTypeError(fmt.Sprintf("weighted entry %d must be a [number, generator] tuple", index)))
|
||||
}
|
||||
if err := entries.Set(fmt.Sprintf("%d", index), object); err != nil {
|
||||
panic(v.runtime.NewGoError(err))
|
||||
}
|
||||
}
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set(tagActionGenerator, true)
|
||||
_ = handle.Set(tagInternalKind, internalKindWeighted)
|
||||
_ = handle.Set("entries", entries)
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) bindTap(call goja.FunctionCall) goja.Value {
|
||||
parameters := call.Argument(0).ToObject(v.runtime)
|
||||
if parameters == nil {
|
||||
panic(v.runtime.NewTypeError("Tap requires {on}"))
|
||||
}
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set("kind", "Tap")
|
||||
_ = handle.Set("on", parameters.Get("on"))
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) bindInputText(call goja.FunctionCall) goja.Value {
|
||||
parameters := call.Argument(0).ToObject(v.runtime)
|
||||
if parameters == nil {
|
||||
panic(v.runtime.NewTypeError("InputText requires {into, text}"))
|
||||
}
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set("kind", "InputText")
|
||||
_ = handle.Set("into", parameters.Get("into"))
|
||||
_ = handle.Set("text", parameters.Get("text"))
|
||||
return handle
|
||||
}
|
||||
|
||||
func (v *Verifier) builtinGenerator(kind string) *goja.Object {
|
||||
handle := v.runtime.NewObject()
|
||||
_ = handle.Set(tagActionGenerator, true)
|
||||
_ = handle.Set(tagInternalKind, kind)
|
||||
return handle
|
||||
}
|
||||
Reference in new issue
Block a user