diff --git a/expand.go b/expand.go index eb06dc5..494da80 100644 --- a/expand.go +++ b/expand.go @@ -10,18 +10,24 @@ import ( // Expand a word. This includes substituting variables and handling quotes. -func expand(input string, vars map[string][]string, expandBackticks bool) string { - expanded := make([]byte, 0) +func expand(input string, vars map[string][]string, expandBackticks bool) []string { + parts := make([]string, 0) + expanded := "" var i, j int for i = 0; i < len(input); { j = i + strings.IndexAny(input[i:], "\"'`$\\") if j < 0 { - expanded = append(expanded, input[i:]...) + expanded += input[i:] break } - expanded = append(expanded, input[i:j]...) + println("-------------------") + println(len(input)) + println(i) + println(j) + + expanded += input[i:j] c, w := utf8.DecodeRuneInString(input[j:]) i = j + w @@ -30,12 +36,15 @@ func expand(input string, vars map[string][]string, expandBackticks bool) string switch c { case '\\': out, off = expandEscape(input[i:]) + expanded += out case '"': out, off = expandDoubleQuoted(input[i:], vars, expandBackticks) + expanded += out case '\'': out, off = expandSingleQuoted(input[i:]) + expanded += out case '`': if expandBackticks { @@ -44,16 +53,26 @@ func expand(input string, vars map[string][]string, expandBackticks bool) string out = input off = len(input) } + expanded += out case '$': - out, off = expandSigil(input[i:], vars) + var outparts []string + outparts, off = expandSigil(input[i:], vars) + if len(outparts) > 0 { + outparts[0] = expanded + outparts[0] + expanded = outparts[len(outparts)-1] + parts = append(parts, outparts[:len(outparts)-1]...) + } } - expanded = append(expanded, out...) i += off } - return string(expanded) + if len(expanded) > 0 { + parts = append(parts, expanded) + } + + return parts } // Expand following a '\\' @@ -79,7 +98,7 @@ func expandDoubleQuoted(input string, vars map[string][]string, expandBackticks j += w if c == '"' { - return expand(input[:j], vars, expandBackticks), (j + w) + return strings.Join(expand(input[:j], vars, expandBackticks), " "), (j + w) } if c == '\\' { @@ -106,21 +125,21 @@ func expandSingleQuoted(input string) (string, int) { } // Expand something starting with at '$'. -func expandSigil(input string, vars map[string][]string) (string, int) { +func expandSigil(input string, vars map[string][]string) ([]string, int) { c, w := utf8.DecodeRuneInString(input) var offset int var varname string if c == '{' { j := strings.IndexRune(input[w:], '}') if j < 0 { - return input, len(input) + return []string{"$" + input}, len(input) } varname = input[w:j] offset = j + 1 } else { // try to match a variable name - i := w + i := 0 j := i for j < len(input) { c, w = utf8.DecodeRuneInString(input) @@ -132,38 +151,48 @@ func expandSigil(input string, vars map[string][]string) (string, int) { if j > i { varname = input[i:j] + offset = j } else { - return input, len(input) + return []string{"$" + input}, len(input) } } if isValidVarName(varname) { varvals, ok := vars[varname] if ok { - return strings.Join(varvals, " "), offset + return varvals, offset } } - return input, len(input) + return []string{"$" + input}, len(input) } // Find and expand all sigils. -func expandSigils(input string, vars map[string][]string) string { - expanded := make([]byte, 0) +func expandSigils(input string, vars map[string][]string) []string { + parts := make([]string, 0) + expanded := "" for i := 0; i < len(input); { j := strings.IndexRune(input[i:], '$') if j < 0 { - expanded = append(expanded, input[i:]...) + expanded += input[i:] break } ex, k := expandSigil(input[j+1:], vars) - expanded = append(expanded, ex...) + if len(ex) > 0 { + ex[0] = expanded + ex[0] + expanded = ex[len(ex)-1] + parts = append(parts, ex[:len(ex)-1]...) + } i = k } - return string(expanded) + if len(expanded) > 0 { + parts = append(parts, expanded) + } + + return parts } @@ -212,3 +241,39 @@ func expandBackQuoted(input string, vars map[string][]string) (string, int) { } +// Split a string on whitespace taking into account escaping and quoting. +//func splitQuoted(input string) []string { + //parts := make([]string, 0) + //var i, j int + //i = 0 + //for { + //// skip all unescaped whitespace + //for i < len(input) { + //c, w := utf8.DecodeRuneInString(input[i:]) + //if strings.IndexRune(" \t", c) < 0 { + //break + //} + //i += w + //} + + //if i >= len(input) { + //break + //} + + //// Ugh. Will this take into account quoting in variables? + + //switch c { + //case '"': + //case '\'': + //default: + + //} + //} + + //return parts +//} + + + + + diff --git a/parse.go b/parse.go index 16f336d..6c950b5 100644 --- a/parse.go +++ b/parse.go @@ -269,9 +269,10 @@ func parseRecipe(p *parser, t token) parserStateFun { // rule has attributes if j < len(p.tokenbuf) { - attribs := make([]string, j-i-1) + attribs := make([]string, 0) for k := i + 1; k < j; k++ { - attribs[k-i-1] = expand(p.tokenbuf[k].val, p.rules.vars, true) + exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) + attribs = append(attribs, exparts...) } err := r.parseAttribs(attribs) if err != nil { @@ -287,46 +288,50 @@ func parseRecipe(p *parser, t token) parserStateFun { } // targets - r.targets = make([]pattern, i) + r.targets = make([]pattern, 0) for k := 0; k < i; k++ { - targetstr := expand(p.tokenbuf[k].val, p.rules.vars, true) - r.targets[k].spat = targetstr + exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) + for i := range exparts { + targetstr := exparts[i] + r.targets = append(r.targets, pattern{spat: targetstr}) - if r.attributes.regex { - rpat, err := regexp.Compile(targetstr) - if err != nil { - msg := fmt.Sprintf("invalid regular expression: %q", err) - p.basicErrorAtToken(msg, p.tokenbuf[k]) - } - r.targets[k].rpat = rpat - } else { - idx := strings.IndexRune(targetstr, '%') - if idx >= 0 { - var left, right string - if idx > 0 { - left = regexp.QuoteMeta(targetstr[:idx]) - } - if idx < len(targetstr) - 1 { - right = regexp.QuoteMeta(targetstr[idx+1:]) - } - - patstr := fmt.Sprintf("^%s(.*)%s$", left, right) - rpat, err := regexp.Compile(patstr) + if r.attributes.regex { + rpat, err := regexp.Compile(targetstr) if err != nil { - msg := fmt.Sprintf("error compiling suffix rule. This is a bug.", err) + msg := fmt.Sprintf("invalid regular expression: %q", err) p.basicErrorAtToken(msg, p.tokenbuf[k]) } - r.targets[k].rpat = rpat - r.targets[k].issuffix = true - r.ismeta = true + r.targets[len(r.targets)-1].rpat = rpat + } else { + idx := strings.IndexRune(targetstr, '%') + if idx >= 0 { + var left, right string + if idx > 0 { + left = regexp.QuoteMeta(targetstr[:idx]) + } + if idx < len(targetstr) - 1 { + right = regexp.QuoteMeta(targetstr[idx+1:]) + } + + patstr := fmt.Sprintf("^%s(.*)%s$", left, right) + rpat, err := regexp.Compile(patstr) + if err != nil { + msg := fmt.Sprintf("error compiling suffix rule. This is a bug.", err) + p.basicErrorAtToken(msg, p.tokenbuf[k]) + } + r.targets[len(r.targets)-1].rpat = rpat + r.targets[len(r.targets)-1].issuffix = true + r.ismeta = true + } } } } // prereqs - r.prereqs = make([]string, len(p.tokenbuf)-j-1) + r.prereqs = make([]string, 0) for k := j + 1; k < len(p.tokenbuf); k++ { - r.prereqs[k-j-1] = expand(p.tokenbuf[k].val, p.rules.vars, true) + exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) + r.prereqs = append(r.prereqs, exparts...) } if t.typ == tokenRecipe { diff --git a/ruleset.go b/ruleset.go index 562cb98..b781d0d 100644 --- a/ruleset.go +++ b/ruleset.go @@ -173,12 +173,14 @@ func (rs *ruleSet) executeAssignment(ts []token) *assignmentError { ts[0]} } + // expanded variables - vals := make([]string, len(ts)-1) - for i := 0; i < len(vals); i++ { - vals[i] = expand(ts[i+1].val, rs.vars, true) + vals := make([]string, 0) + for i := 1; i < len(ts); i++ { + vals = append(vals, expand(ts[i].val, rs.vars, true)...) } + rs.vars[assignee] = vals return nil }