wstl1/mapping_language/transpiler/env.go (141 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package transpiler
import (
"fmt"
"github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/util/jsonutil" /* copybara-comment: jsonutil */
"bitbucket.org/creachadair/stringset" /* copybara-comment: stringset */
mpb "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/proto" /* copybara-comment: mapping_go_proto */
)
// env represents a lexical variable/input/target binding environment. This is a component of a
// closure, though not the whole closure itself (closures do not currently exist in Whistle).
type env struct {
name string
parent *env
vars stringset.Set
targets stringset.Set
args map[string]int
inputsFromParent map[string]int
// requiredArgs stores the names of arguments that are required for the projector. It is a subset of the projector arguments.
requiredArgs []string
mapping []*mpb.FieldMapping
}
// newEnv creates a new environment with the given name and registers/binds the given arguments.
func newEnv(name string, args []string, requiredArgs []string) *env {
am := make(map[string]int)
for i, a := range args {
am[a] = i
}
return &env{
name: name,
args: am,
inputsFromParent: make(map[string]int),
requiredArgs: requiredArgs,
}
}
// declareVar checks if the given var is already declared as an arg, and if not binds it in the
// environment.
func (n *env) declareVar(v string) error {
if _, ok := n.args[v]; ok {
return fmt.Errorf("variable %s has the same name as a function argument, it must be unique", v)
}
n.vars.Add(v)
return nil
}
// declareTarget binds the given target in the environment.
func (n *env) declareTarget(target string) {
n.targets.Add(target)
}
// readVar
func (n *env) readVar(input, field string) *mpb.ValueSource {
if n.vars.Contains(input) {
return &mpb.ValueSource{
Source: &mpb.ValueSource_FromLocalVar{
FromLocalVar: jsonutil.JoinPath(input, field),
},
}
}
return nil
}
// readInput checks the environment (vars, args, targets, parent environment in that order) for the
// given input, and produces a ValueSource to read it. Inputs read from the parent environment will
// be accounted for when the callsite is generated.
func (n *env) readInput(input, field string) *mpb.ValueSource {
if v := n.readVar(input, field); v != nil {
return v
}
if i, ok := n.args[input]; ok {
return &mpb.ValueSource{
Source: &mpb.ValueSource_FromInput{
FromInput: &mpb.ValueSource_InputSource{
Arg: int32(i + 1),
Field: field,
},
},
}
}
if n.targets.Contains(input) {
return &mpb.ValueSource{
Source: &mpb.ValueSource_FromDestination{
FromDestination: jsonutil.JoinPath(input, field),
},
}
}
if n.parent == nil {
return nil
}
// Check to see if we've already read this input from the parent environment. If not, attempt to
// read it.
if _, ok := n.inputsFromParent[input]; !ok {
if n.parent.readInput(input, field) != nil {
n.inputsFromParent[input] = len(n.inputsFromParent)
}
}
// At this point, if the parent environment had the input, we will have read it.
if i, ok := n.inputsFromParent[input]; ok {
return &mpb.ValueSource{
Source: &mpb.ValueSource_FromInput{
FromInput: &mpb.ValueSource_InputSource{
Arg: int32(len(n.args) + i + 1),
Field: field,
},
},
}
}
return nil
}
// generateProjector creates a ProjectorDefinition from the current environment.
func (n *env) generateProjector() *mpb.ProjectorDefinition {
result := &mpb.ProjectorDefinition{
Name: n.name,
Mapping: n.mapping,
}
requiredArgStack := &valueStack{}
for _, k := range n.requiredArgs {
requiredArgStack.push(
&mpb.ValueSource{
Source: &mpb.ValueSource_ProjectedValue{
ProjectedValue: &mpb.ValueSource{
Source: n.readInput(k, "").GetSource(),
Projector: "$IsNotNil",
},
},
})
}
for i := range result.Mapping {
if result.Mapping[i].Condition == nil {
result.Mapping[i].Condition = requiredArgStack.and()
} else {
result.Mapping[i].Condition = requiredArgStack.and(result.Mapping[i].Condition)
}
}
return result
}
// addMapping adds the given mapping to the environment and subsequently the projector definition
// registered by it.
func (n *env) addMapping(mapping *mpb.FieldMapping) {
n.mapping = append(n.mapping, mapping)
}
func (n *env) newChild(name string, args []string, requiredArgs []string) *env {
child := newEnv(name, args, requiredArgs)
child.parent = n
return child
}
// generateCallsite generates a callsite, intended to be placed in the parent environment's projector,
// for calling this environment's projector, passing along any inputs pulled from the parent env in
// addition to any arguments required by this environment's projector itself (i.e. those declared in
// newChild)
func (n *env) generateCallsite(args ...*mpb.ValueSource) (*mpb.ValueSource, error) {
vs := &mpb.ValueSource{
Projector: n.name,
}
if len(args) != len(n.args) {
return nil, fmt.Errorf("wrong number of arguments - %s expects %d %v but got %d", n.name, len(n.args), n.args, len(args))
}
addArgs(vs, args...)
inputsFromParents := make([]string, len(n.inputsFromParent))
for a, i := range n.inputsFromParent {
inputsFromParents[i] = a
}
for _, a := range inputsFromParents {
addArgs(vs, n.parent.readInput(a, ""))
}
return vs, nil
}