lang: Add a for loop statement for iterating over a list
This adds a for statement which is used to iterate over a list with a body of statements. This is an important data transformation tool which should be used sparingly, but is important to have. An import statement inside of a for loop is not currently supported. We have a simple hack to detect the obvious cases, but more deeply nested scenarios probably won't be caught, and you'll get an obscure error message if you try to do this. This was incredibly challenging to get right, and it's all thanks to Sam for his brilliance. Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
@@ -85,8 +85,9 @@ type Stmt interface {
|
||||
// child statements, and Infer/Check for child expressions.
|
||||
TypeCheck() ([]*UnificationInvariant, error)
|
||||
|
||||
// Graph returns the reactive function graph expressed by this node.
|
||||
Graph() (*pgraph.Graph, error)
|
||||
// Graph returns the reactive function graph expressed by this node. It
|
||||
// takes in the environment of any functions in scope.
|
||||
Graph(env *Env) (*pgraph.Graph, error)
|
||||
|
||||
// Output returns the output that this "program" produces. This output
|
||||
// is what is used to build the output graph. It requires the input
|
||||
@@ -148,7 +149,7 @@ type Expr interface {
|
||||
// Graph returns the reactive function graph expressed by this node. It
|
||||
// takes in the environment of any functions in scope. It also returns
|
||||
// the function for this node.
|
||||
Graph(env map[string]Func) (*pgraph.Graph, Func, error)
|
||||
Graph(env *Env) (*pgraph.Graph, Func, error)
|
||||
|
||||
// SetValue stores the result of the last computation of this expression
|
||||
// node.
|
||||
@@ -277,9 +278,21 @@ func (obj *Data) AbsFilename() string {
|
||||
// An interesting note about these is that they exist in a distinct namespace
|
||||
// from the variables, which could actually contain lambda functions.
|
||||
type Scope struct {
|
||||
// Variables maps the scope of name to Expr.
|
||||
Variables map[string]Expr
|
||||
Functions map[string]Expr // the Expr will usually be an *ExprFunc (actually it's usually (or always) an *ExprSingleton, which wraps an *ExprFunc now)
|
||||
Classes map[string]Stmt
|
||||
|
||||
// Functions is the scope of functions.
|
||||
//
|
||||
// The Expr will usually be an *ExprFunc. (Actually it's usually or
|
||||
// always an *ExprSingleton, which wraps an *ExprFunc now.)
|
||||
Functions map[string]Expr
|
||||
|
||||
// Classes map the name of the class to the class.
|
||||
Classes map[string]Stmt
|
||||
|
||||
// Iterated is a flag that is true if this scope is inside of a for
|
||||
// loop.
|
||||
Iterated bool
|
||||
|
||||
Chain []Node // chain of previously seen node's
|
||||
}
|
||||
@@ -291,6 +304,7 @@ func EmptyScope() *Scope {
|
||||
Variables: make(map[string]Expr),
|
||||
Functions: make(map[string]Expr),
|
||||
Classes: make(map[string]Stmt),
|
||||
Iterated: false,
|
||||
Chain: []Node{},
|
||||
}
|
||||
}
|
||||
@@ -307,6 +321,7 @@ func (obj *Scope) Copy() *Scope {
|
||||
variables := make(map[string]Expr)
|
||||
functions := make(map[string]Expr)
|
||||
classes := make(map[string]Stmt)
|
||||
iterated := obj.Iterated
|
||||
chain := []Node{}
|
||||
|
||||
for k, v := range obj.Variables { // copy
|
||||
@@ -326,6 +341,7 @@ func (obj *Scope) Copy() *Scope {
|
||||
Variables: variables,
|
||||
Functions: functions,
|
||||
Classes: classes,
|
||||
Iterated: iterated,
|
||||
Chain: chain,
|
||||
}
|
||||
}
|
||||
@@ -377,6 +393,10 @@ func (obj *Scope) Merge(scope *Scope) error {
|
||||
obj.Classes[name] = scope.Classes[name]
|
||||
}
|
||||
|
||||
if scope.Iterated { // XXX: how should we merge this?
|
||||
obj.Iterated = scope.Iterated
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -398,6 +418,94 @@ func (obj *Scope) IsEmpty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Env is an environment which contains the relevant mappings. This is used at
|
||||
// the Graph(...) stage of the compiler. It does not contain classes.
|
||||
type Env struct {
|
||||
// Variables map and Expr to a *FuncSingleton which deduplicates the
|
||||
// use of a function.
|
||||
Variables map[Expr]*FuncSingleton
|
||||
|
||||
// Functions contains the captured environment, because when we're
|
||||
// recursing into a StmtFunc which is defined inside a for loop, we can
|
||||
// use that to get the right Env.Variables map. As for the function
|
||||
// itself, it's the same in each loop iteration, therefore, we find it
|
||||
// in obj.expr of ExprCall. (Functions map[string]*Env) But actually,
|
||||
// our new version is now this:
|
||||
Functions map[Expr]*Env
|
||||
}
|
||||
|
||||
// EmptyEnv returns the zero, empty value for the scope, with all the internal
|
||||
// lists initialized appropriately.
|
||||
func EmptyEnv() *Env {
|
||||
return &Env{
|
||||
Variables: make(map[Expr]*FuncSingleton),
|
||||
Functions: make(map[Expr]*Env),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a copy of the Env struct. This ensures that if the internal maps
|
||||
// are changed, it doesn't affect other copies of the Env. It does *not* copy or
|
||||
// change the pointers contained within, since these are references, and we need
|
||||
// those to be consistently pointing to the same things after copying.
|
||||
func (obj *Env) Copy() *Env {
|
||||
if obj == nil { // allow copying nil envs
|
||||
return EmptyEnv()
|
||||
}
|
||||
|
||||
variables := make(map[Expr]*FuncSingleton)
|
||||
functions := make(map[Expr]*Env)
|
||||
|
||||
for k, v := range obj.Variables { // copy
|
||||
variables[k] = v // we don't copy the func's!
|
||||
}
|
||||
for k, v := range obj.Functions { // copy
|
||||
functions[k] = v // we don't copy the generator func's
|
||||
}
|
||||
|
||||
return &Env{
|
||||
Variables: variables,
|
||||
Functions: functions,
|
||||
}
|
||||
}
|
||||
|
||||
// FuncSingleton is a singleton system for storing a singleton func and its
|
||||
// corresponding graph. You must pass in a `MakeFunc` builder method to generate
|
||||
// these. The graph which is returned from this must contain that Func as a
|
||||
// node.
|
||||
type FuncSingleton struct {
|
||||
// MakeFunc builds and returns a Func and a graph that it must be
|
||||
// contained within.
|
||||
// XXX: Add Txn as an input arg?
|
||||
MakeFunc func() (*pgraph.Graph, Func, error)
|
||||
|
||||
g *pgraph.Graph
|
||||
f Func
|
||||
}
|
||||
|
||||
// GraphFunc returns the previously saved graph and func if they exist. If they
|
||||
// do not, then it calls the MakeFunc method to get them, and saves a copy for
|
||||
// next time.
|
||||
// XXX: Add Txn as an input arg?
|
||||
func (obj *FuncSingleton) GraphFunc() (*pgraph.Graph, Func, error) {
|
||||
// If obj.f already exists, just use that.
|
||||
if obj.f != nil { // && obj.g != nil
|
||||
return obj.g, obj.f, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
obj.g, obj.f, err = obj.MakeFunc() // XXX: Add Txn as an input arg?
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if obj.g == nil {
|
||||
return nil, nil, fmt.Errorf("unexpected nil graph")
|
||||
}
|
||||
if obj.f == nil {
|
||||
return nil, nil, fmt.Errorf("unexpected nil function")
|
||||
}
|
||||
return obj.g, obj.f, nil
|
||||
}
|
||||
|
||||
// Arg represents a name identifier for a func or class argument declaration and
|
||||
// is sometimes accompanied by a type. This does not satisfy the Expr interface.
|
||||
type Arg struct {
|
||||
|
||||
Reference in New Issue
Block a user