wstl1/mapping_language/transpiler/path.go (114 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" "regexp" "strings" "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_language/parser" /* copybara-comment: parser */ "github.com/antlr/antlr4/runtime/Go/antlr" /* copybara-comment: antlr */ ) // identifierEscape is the quote escape character to use to indicate that an indentifier has special // characters in it. const identifierEscape = "'" type pathSpec struct { arg, field, index string } // VisitTargetPath returns a pathSpec for the given TargetPathContext. func (t *transpiler) VisitTargetPath(ctx *parser.TargetPathContext) interface{} { p := ctx.TargetPathHead().Accept(t).(pathSpec) for i := range ctx.AllTargetPathSegment() { p.field += ctx.TargetPathSegment(i).Accept(t).(string) } if ctx.OWMOD() != nil && ctx.OWMOD().GetText() != "" { p.field += ctx.OWMOD().GetText() } // Only one of p.arg and p.index can be filled. if (p.arg == "") == (p.index == "") { t.fail(ctx, fmt.Errorf("invalid target path - expected arg xor index but got both or neither (arg %s and index %s)", p.arg, p.index)) } return p } // VisitTargetPathHead returns a partially filled pathSpec for the given TargetPathHeadContext. // Either the arg or index field will be filled, as appropriate. func (t *transpiler) VisitTargetPathHead(ctx *parser.TargetPathHeadContext) interface{} { if ctx.TOKEN() != nil && ctx.TOKEN().GetText() != "" { return pathSpec{ arg: getTokenText(ctx.TOKEN()), } } // ROOT_INPUT is a special case path segment since normally they cannot contain $. if ctx.ROOT_INPUT() != nil && ctx.ROOT_INPUT().GetText() != "" { return pathSpec{ arg: ctx.ROOT_INPUT().GetText(), } } // ROOT is a special case path segment since it is a keyword and does not get tokenized as a TOKEN // TODO(): Remove after sunset. if ctx.ROOT() != nil && ctx.ROOT().GetText() != "" { return pathSpec{ arg: ctx.ROOT().GetText(), } } if ctx.Index() != nil && ctx.Index().GetText() != "" { return pathSpec{ index: ctx.Index().GetText(), } } if ctx.WILDCARD() != nil && ctx.WILDCARD().GetText() != "" { return pathSpec{ index: ctx.WILDCARD().GetText(), } } if ctx.ArrayMod() != nil && ctx.ArrayMod().GetText() != "" { return pathSpec{ index: ctx.ArrayMod().GetText(), } } t.fail(ctx, fmt.Errorf("invalid target path head - no token, index, arraymod, or wildcard")) return nil } // VisitTargetPathSegment returns a string of the TargetPathSegmentContext contents. func (t *transpiler) VisitTargetPathSegment(ctx *parser.TargetPathSegmentContext) interface{} { if ctx.TOKEN() != nil && ctx.TOKEN().GetText() != "" { delim := "" if ctx.DELIM() != nil { delim = ctx.DELIM().GetText() } return delim + getTokenText(ctx.TOKEN()) } return ctx.GetText() } // VisitSourcePath returns a pathSpec for the given SourcePathContext. func (t *transpiler) VisitSourcePath(ctx *parser.SourcePathContext) interface{} { p := ctx.SourcePathHead().Accept(t).(pathSpec) for i := range ctx.AllSourcePathSegment() { p.field += ctx.SourcePathSegment(i).Accept(t).(string) } // Only one of p.arg and p.index can be filled. if (p.arg == "") == (p.index == "") { t.fail(ctx, fmt.Errorf("invalid source path - expected arg xor index but got both or neither (arg %s and index %s)", p.arg, p.index)) } return p } // VisitSourcePathHead returns a partially filled pathSpec for the given SourcePathHeadContext. // Either the arg or index field will be filled, as appropriate. func (t *transpiler) VisitSourcePathHead(ctx *parser.SourcePathHeadContext) interface{} { if ctx.TOKEN() != nil && ctx.TOKEN().GetText() != "" { return pathSpec{ arg: getTokenText(ctx.TOKEN()), } } // ROOT_INPUT is a special case path segment since normally they cannot contain $. if ctx.ROOT_INPUT() != nil && ctx.ROOT_INPUT().GetText() != "" { return pathSpec{ arg: ctx.ROOT_INPUT().GetText(), } } // ROOT is a special case path segment since it is a keyword and does not get tokenized as a TOKEN // TODO(): Remove after sunset. if ctx.ROOT() != nil && ctx.ROOT().GetText() != "" { return pathSpec{ arg: ctx.ROOT().GetText(), } } t.fail(ctx, fmt.Errorf("invalid source path head - no token or %s", rootEnvInputName)) return nil } // VisitSourcePathSegment returns a string of the SourcePathSegmentContext contents. func (t *transpiler) VisitSourcePathSegment(ctx *parser.SourcePathSegmentContext) interface{} { if ctx.TOKEN() != nil && ctx.TOKEN().GetText() != "" { delim := "" if ctx.DELIM() != nil { delim = ctx.DELIM().GetText() } return delim + getTokenText(ctx.TOKEN()) } return ctx.GetText() } var anyChar = regexp.MustCompile(".") func getTokenText(node antlr.TerminalNode) string { nodeText := node.GetText() if strings.HasPrefix(nodeText, identifierEscape) && strings.HasSuffix(nodeText, identifierEscape) { return anyChar.ReplaceAllStringFunc(strings.Trim(nodeText, identifierEscape), func(s string) string { return `\` + s }) } return nodeText }