diff --git a/expand.go b/expand.go index 1ffd616..4ef896c 100644 --- a/expand.go +++ b/expand.go @@ -3,6 +3,7 @@ package main import ( + "regexp" "strings" "unicode/utf8" ) @@ -136,18 +137,45 @@ func expandSigil(input string, vars map[string][]string) ([]string, int) { c, w := utf8.DecodeRuneInString(input) var offset int var varname string + var namelist_pattern = regexp.MustCompile(`^\s*([^:]+)\s*:\s*([^%]*)%([^=]*)\s*=\s*([^%]*)%([^%]*)\s*`) // escaping of "$" with "$$" if c == '$' { return []string{"$"}, 2 + // match bracketed expansions: ${foo}, or ${foo:a%b=c%d} } else if c == '{' { j := strings.IndexRune(input[w:], '}') if j < 0 { return []string{"$" + input}, len(input) } - varname = input[w : w+j] offset = w + j + 1 + + // is this a namelist? + mat := namelist_pattern.FindStringSubmatch(varname) + if mat != nil && isValidVarName(mat[1]) { + // ${varname:a%b=c%d} + varname = mat[1] + a, b, c, d := mat[2], mat[3], mat[4], mat[5] + values, ok := vars[varname] + if !ok { + return []string{}, offset + } + + pat := regexp.MustCompile(strings.Join([]string{`^\Q`, a, `\E(.*)\Q`, b, `\E$`}, "")) + expanded_values := make([]string, len(values)) + for i, value := range values { + value_match := pat.FindStringSubmatch(value) + if value_match != nil { + expanded_values[i] = strings.Join([]string{c, value_match[1], d}, "") + } else { + expanded_values[i] = value + } + } + + return expanded_values, offset + } + // bare variables: $foo } else { // try to match a variable name i := 0 diff --git a/lex.go b/lex.go index 011ac53..6ba7e20 100644 --- a/lex.go +++ b/lex.go @@ -11,7 +11,7 @@ type tokenType int const eof rune = '\000' // Rune's that cannot be part of a bare (unquoted) string. -const nonBareRunes = " \t\n\r\\=:#'\"" +const nonBareRunes = " \t\n\r\\=:#'\"$" // Return true if the string contains whitespace only. func onlyWhitespace(s string) bool { @@ -383,6 +383,14 @@ func lexBareWord(l *lexer) lexerStateFun { l.next() return lexBareWord } + } else if c == '$' { + c1 := l.peekN(1) + if c1 == '{' { + return lexBracketExpansion + } else { + l.next() + return lexBareWord + } } if l.start < l.pos { @@ -391,3 +399,11 @@ func lexBareWord(l *lexer) lexerStateFun { return lexTopLevel } + +func lexBracketExpansion(l *lexer) lexerStateFun { + l.next() // '$' + l.next() // '{' + l.acceptUntil("}") + l.next() // '}' + return lexBareWord +}