Skip to content

Commit

Permalink
lang: ast: more Stmt TimeCheck implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
gelisam authored and purpleidea committed Jan 20, 2024
1 parent e1086c5 commit 3eb3a96
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 13 deletions.
35 changes: 33 additions & 2 deletions lang/ast/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,16 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
}

func (obj *StmtRes) TimeCheck() error {
panic("TODO")
// no need to check obj.Name because that Expr is always a string, never a
// function

for _, x := range obj.Contents {
if err := x.TimeCheck(); err != nil {
return err
}
}

return nil
}

// Graph returns the reactive function graph which is expressed by this node. It
Expand Down Expand Up @@ -1407,7 +1416,29 @@ func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) {
}

func (obj *StmtResField) TimeCheck() error {
panic("TODO")
timelessFn, err := obj.Value.TimeCheck(map[string]*types.Timeless{})
if err != nil {
return err
}
typeFn, err := obj.Value.Type()
if err != nil {
return err
}
if typeFn.Kind != types.KindFunc {
// We only care about timelessness for functions
return nil
}

isCompletelyTimeless, err := types.IsCompletelyTimeless(timelessFn, typeFn)
if err != nil {
return err
}

if !isCompletelyTimeless {
return fmt.Errorf("resource field `%s` is not timeless", obj.Field)
}

return nil
}

// Graph returns the reactive function graph which is expressed by this node. It
Expand Down
105 changes: 94 additions & 11 deletions lang/types/timeless.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,96 @@ var (
}
)

func ApplyTimeless(funcT *Timeless, inputs []*Timeless) (*Timeless, error) {
if funcT.IsTimeless != nil {
return funcT, nil
} else if funcT.PropagatesTimeless != nil {
return (*funcT.PropagatesTimeless)(inputs)
func IsCompletelyTimeless(timeless *Timeless, typ *Type) (bool, error) {
if timeless.IsTimeless != nil {
return *timeless.IsTimeless, nil
}

if timeless.PropagatesTimeless != nil {
switch typ.Kind {
case KindBool:
return false, fmt.Errorf("IsCompletelyTimeless: timeless is invalid for the given type")
case KindStr:
return false, fmt.Errorf("IsCompletelyTimeless: timeless is invalid for the given type")
case KindInt:
return false, fmt.Errorf("IsCompletelyTimeless: timeless is invalid for the given type")
case KindFloat:
return false, fmt.Errorf("IsCompletelyTimeless: timeless is invalid for the given type")

case KindList:
return IsCompletelyTimeless(timeless, typ.Val)

case KindMap:
// We are assuming that maps cannot contain functions as keys.
if typ.Key == nil || typ.Val == nil {
panic("malformed map type")
}
return IsCompletelyTimeless(timeless, typ.Val)

case KindStruct: // {a bool; b int}
if typ.Map == nil {
panic("malformed struct type")
}
if len(typ.Map) != len(typ.Ord) {
panic("malformed struct length")
}
for _, k := range typ.Ord {
t, ok := typ.Map[k]
if !ok {
panic("malformed struct order")
}
if t == nil {
panic("malformed struct field")
}
r, err := IsCompletelyTimeless(timeless, t)
if err != nil {
return false, err
}
if !r {
return false, nil
}
}
return true, nil

case KindFunc:
if typ.Map == nil {
panic("malformed func type")
}
if len(typ.Map) != len(typ.Ord) {
panic("malformed func length")
}

timelessArgs := []*Timeless{}
for _ = range typ.Ord {
timelessArgs = append(timelessArgs, AlwaysTimeless)
}

timelessOutput, err := ApplyTimeless(timeless, timelessArgs)
if err != nil {
return false, err
}

return IsCompletelyTimeless(timelessOutput, typ.Out)

case KindVariant:
// Pessimistically assume that variants are timeful
return false, nil

default:
panic("unknown type kind")
}
}

return false, fmt.Errorf("IsCompletelyTimeless: timeless is invalid")
}

func ApplyTimeless(timelessFn *Timeless, inputs []*Timeless) (*Timeless, error) {
if timelessFn.IsTimeless != nil {
return timelessFn, nil
} else if timelessFn.PropagatesTimeless != nil {
return (*timelessFn.PropagatesTimeless)(inputs)
} else {
return nil, fmt.Errorf("ApplyTimeless: func1 is invalid")
return nil, fmt.Errorf("ApplyTimeless: timelessFn is invalid")
}
}

Expand All @@ -177,9 +260,9 @@ func ApplyTimeless(funcT *Timeless, inputs []*Timeless) (*Timeless, error) {
// example, the list ["1", os.system(...)] is timeful because one of its
// elements is timeful, and the function func($x) { if ... { "1" } else {
// os.system(...) } } is timeful because it sometimes returns a timeful value.
func CombineTimeless(time1, time2 *Timeless) (*Timeless, error) {
if time1.IsTimeless != nil && time2.IsTimeless != nil {
if *time1.IsTimeless && *time2.IsTimeless {
func CombineTimeless(timeless1, timeless2 *Timeless) (*Timeless, error) {
if timeless1.IsTimeless != nil && timeless2.IsTimeless != nil {
if *timeless1.IsTimeless && *timeless2.IsTimeless {
return AlwaysTimeless, nil
} else {
return AlwaysTimeful, nil
Expand All @@ -190,11 +273,11 @@ func CombineTimeless(time1, time2 *Timeless) (*Timeless, error) {
// combineTimeless(func($x) { $x }, func($x) { timeful }) => func($x) { timeful }

propagatesTimeless := func(inputs []*Timeless) (*Timeless, error) {
output1, err1 := ApplyTimeless(time1, inputs)
output1, err1 := ApplyTimeless(timeless1, inputs)
if err1 != nil {
return nil, err1
}
output2, err2 := ApplyTimeless(time2, inputs)
output2, err2 := ApplyTimeless(timeless2, inputs)
if err2 != nil {
return nil, err2
}
Expand Down

0 comments on commit 3eb3a96

Please sign in to comment.