Some more work on string expansion.
This commit is contained in:
parent
084a45fc74
commit
f812efe3ad
4 changed files with 90 additions and 35 deletions
34
README.md
34
README.md
|
|
@ -11,42 +11,44 @@ of it. Put simply, mk takes make, keeps its simple direct syntax, but fixes
|
||||||
basically everything that's annoyed you over the years. To name a few things:
|
basically everything that's annoyed you over the years. To name a few things:
|
||||||
|
|
||||||
1. Recipes are delimited by any indentation, not tab characters in particular.
|
1. Recipes are delimited by any indentation, not tab characters in particular.
|
||||||
2. Phony targets are handled separately from file targets. Your mkfile won't
|
1. Phony targets are handled separately from file targets. Your mkfile won't
|
||||||
be broken by having a file named 'clean'.
|
be broken by having a file named 'clean'.
|
||||||
2. Attributes instead of weird special targets like `.SECONDARY:`.
|
1. Attributes instead of weird special targets like `.SECONDARY:`.
|
||||||
5. Special variables like `$target`, `$prereq`, and `$stem` in place of
|
1. Special variables like `$target`, `$prereq`, and `$stem` in place of
|
||||||
make's pointlessly cryptic `$@`, `$^`, and `$*`.
|
make's pointlessly cryptic `$@`, `$^`, and `$*`.
|
||||||
3. In addition to suffix rules (e.g. `%.o: %.c`), mk has more powerful regular
|
1. In addition to suffix rules (e.g. `%.o: %.c`), mk has more powerful regular
|
||||||
expression rules.
|
expression rules.
|
||||||
4. Sane handling of rules with multiple targets.
|
1. Sane handling of rules with multiple targets.
|
||||||
5. An optional attribute to delete targets when a recipe fails, so you aren't
|
1. An optional attribute to delete targets when a recipe fails, so you aren't
|
||||||
left with corrupt output.
|
left with corrupt output.
|
||||||
6. Plan 9 mkfiles can not only include other mkfiles, but pipe in the output of
|
1. Plan 9 mkfiles can not only include other mkfiles, but pipe in the output of
|
||||||
recipes. Your mkfile can configure itself by doing something like
|
recipes. Your mkfile can configure itself by doing something like
|
||||||
`<|sh config.sh`.
|
`<|sh config.sh`.
|
||||||
7. A generalized mechanism to determine if a target is out of date, for when
|
1. A generalized mechanism to determine if a target is out of date, for when
|
||||||
timestamps won't cut it.
|
timestamps won't cut it.
|
||||||
|
1. Variables are expanded in recipes only if they are defined. They way you
|
||||||
|
usually don't have to escape `$`.
|
||||||
|
|
||||||
And much more! For more, read the original mk paper: ["Mk: a successor to
|
And much more! For more, read the original mk paper: ["Mk: a successor to
|
||||||
make"](#).
|
make"](#).
|
||||||
|
|
||||||
# Improvements over Plan 9 mk
|
# Improvements over Plan 9 mk
|
||||||
|
|
||||||
This mk stays mostly faithful to Plan 9, but makes a few minor (in my opinion)
|
This mk stays mostly faithful to Plan 9, but makes a few (in my opinion)
|
||||||
improvements.
|
improvements.
|
||||||
|
|
||||||
|
1. A clean, modern implementation in go, that doesn't depend on the whole plan
|
||||||
|
9 for userspace stack.
|
||||||
|
1. Use go regular expressions, which are perl-like. The original mk used plan9
|
||||||
|
regex, which few people know or care to learn.
|
||||||
1. Allow blank lines in recipes. A recipe is any indented block of text, and
|
1. Allow blank lines in recipes. A recipe is any indented block of text, and
|
||||||
continues until a non-indented character or the end of the file.
|
continues until a non-indented character or the end of the file.
|
||||||
2. Add an 'S' attribute to execute recipes with programs other than sh. This
|
1. Add an 'S' attribute to execute recipes with programs other than sh. This
|
||||||
way, you don't have to separate your six line python script into its own
|
way, you don't have to separate your six line python script into its own
|
||||||
file. Just stick it in the mkfile.
|
file. Just stick it in the mkfile.
|
||||||
3. Use a perl-compatible regular expressions. The original mk used plan9
|
1. Use sh syntax for command insertion (i.e. backticks) rather than rc shell
|
||||||
regex, which few people know or care to learn.
|
syntax.
|
||||||
4. A clean, modern implementation in go, that doesn't depend on the whole plan
|
|
||||||
9 for userspace stack.
|
|
||||||
|
|
||||||
Most Plan 9 mkfiles should remain backwards compatible, but strict backwards
|
|
||||||
compatibility isn't the goal.
|
|
||||||
|
|
||||||
# Current State
|
# Current State
|
||||||
|
|
||||||
|
|
|
||||||
16
mk.go
16
mk.go
|
|
@ -1,13 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"fmt"
|
"fmt"
|
||||||
//"io/ioutil"
|
"io/ioutil"
|
||||||
//"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//input, _ := ioutil.ReadAll(os.Stdin)
|
input, _ := ioutil.ReadAll(os.Stdin)
|
||||||
|
|
||||||
// TEST LEXING
|
// TEST LEXING
|
||||||
//_, tokens := lex(string(input))
|
//_, tokens := lex(string(input))
|
||||||
|
|
@ -16,10 +16,10 @@ func main() {
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// TEST PARSING
|
// TEST PARSING
|
||||||
//rs := parse(string(input), "<stdin>")
|
rs := parse(string(input), "<stdin>")
|
||||||
//fmt.Println(rs)
|
fmt.Println(rs)
|
||||||
|
|
||||||
// TEST STRING EXPANSION
|
// TEST STRING EXPANSION
|
||||||
rules := &ruleSet{make(map[string][]string), make([]rule, 0)}
|
//rules := &ruleSet{make(map[string][]string), make([]rule, 0)}
|
||||||
println(rules.expand("\"This is a quote: \\\"\""))
|
//println(rules.expand("\"This is a quote: \\\"\""))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
parse.go
7
parse.go
|
|
@ -272,10 +272,11 @@ func parseRecipe(p *parser, t token) parserStateFun {
|
||||||
// targets
|
// targets
|
||||||
r.targets = make([]string, i)
|
r.targets = make([]string, i)
|
||||||
for k := 0; k < i; k++ {
|
for k := 0; k < i; k++ {
|
||||||
r.targets[k] = p.tokenbuf[k].val
|
r.targets[k] = p.rules.expand(p.tokenbuf[k].val, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rule has attributes
|
// rule has attributes
|
||||||
|
// TODO: should we be expanding the attribute strings?
|
||||||
if j < len(p.tokenbuf) {
|
if j < len(p.tokenbuf) {
|
||||||
attribs := make([]string, j-i-1)
|
attribs := make([]string, j-i-1)
|
||||||
for k := i + 1; k < j; k++ {
|
for k := i + 1; k < j; k++ {
|
||||||
|
|
@ -293,11 +294,11 @@ func parseRecipe(p *parser, t token) parserStateFun {
|
||||||
// prereqs
|
// prereqs
|
||||||
r.prereqs = make([]string, len(p.tokenbuf)-j-1)
|
r.prereqs = make([]string, len(p.tokenbuf)-j-1)
|
||||||
for k := j + 1; k < len(p.tokenbuf); k++ {
|
for k := j + 1; k < len(p.tokenbuf); k++ {
|
||||||
r.prereqs[k-j-1] = p.tokenbuf[k].val
|
r.prereqs[k-j-1] = p.rules.expand(p.tokenbuf[k].val, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.typ == tokenRecipe {
|
if t.typ == tokenRecipe {
|
||||||
r.recipe = t.val
|
r.recipe = p.rules.expand(t.val, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.rules.push(r)
|
p.rules.push(r)
|
||||||
|
|
|
||||||
68
ruleset.go
68
ruleset.go
|
|
@ -95,7 +95,7 @@ func (rs *ruleSet) push(r rule) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand a word. This includes substituting variables and handling quotes.
|
// Expand a word. This includes substituting variables and handling quotes.
|
||||||
func (rs *ruleSet) expand(input string) string {
|
func (rs *ruleSet) expand(input string, expandBackticks bool) string {
|
||||||
expanded := make([]byte, 0)
|
expanded := make([]byte, 0)
|
||||||
var i, j int
|
var i, j int
|
||||||
for i = 0; i < len(input); {
|
for i = 0; i < len(input); {
|
||||||
|
|
@ -117,16 +117,21 @@ func (rs *ruleSet) expand(input string) string {
|
||||||
out, off = rs.expandEscape(input[i:])
|
out, off = rs.expandEscape(input[i:])
|
||||||
|
|
||||||
case '"':
|
case '"':
|
||||||
out, off = rs.expandDoubleQuoted(input[i:])
|
out, off = rs.expandDoubleQuoted(input[i:], expandBackticks)
|
||||||
|
|
||||||
case '\'':
|
case '\'':
|
||||||
out, off = rs.expandSingleQuoted(input[i:])
|
out, off = rs.expandSingleQuoted(input[i:])
|
||||||
|
|
||||||
case '`':
|
case '`':
|
||||||
out, off = rs.expandBackQuoted(input[i:])
|
if expandBackticks {
|
||||||
|
out, off = rs.expandBackQuoted(input[i:])
|
||||||
|
} else {
|
||||||
|
out = input
|
||||||
|
off = len(input)
|
||||||
|
}
|
||||||
|
|
||||||
case '$':
|
case '$':
|
||||||
// TODO: recursive call: expandSigil
|
out, off = rs.expandSigil(input[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
expanded = append(expanded, []byte(out)...)
|
expanded = append(expanded, []byte(out)...)
|
||||||
|
|
@ -143,7 +148,7 @@ func (rs *ruleSet) expandEscape(input string) (string, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand a double quoted string starting after a '\"'
|
// Expand a double quoted string starting after a '\"'
|
||||||
func (rs *ruleSet) expandDoubleQuoted(input string) (string, int) {
|
func (rs *ruleSet) expandDoubleQuoted(input string, expandBackticks bool) (string, int) {
|
||||||
// find the first non-escaped "
|
// find the first non-escaped "
|
||||||
j := 0
|
j := 0
|
||||||
for {
|
for {
|
||||||
|
|
@ -159,7 +164,7 @@ func (rs *ruleSet) expandDoubleQuoted(input string) (string, int) {
|
||||||
j += w
|
j += w
|
||||||
|
|
||||||
if c == '"' {
|
if c == '"' {
|
||||||
return rs.expand(input[:j]), (j + w)
|
return rs.expand(input[:j], expandBackticks), (j + w)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == '\\' {
|
if c == '\\' {
|
||||||
|
|
@ -185,6 +190,48 @@ func (rs *ruleSet) expandSingleQuoted(input string) (string, int) {
|
||||||
return input[:j], (j + 1)
|
return input[:j], (j + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand something starting with at '$'.
|
||||||
|
func (rs *ruleSet) expandSigil(input string) (string, int) {
|
||||||
|
c, w := utf8.DecodeRuneInString(input)
|
||||||
|
var offset int
|
||||||
|
var varname string
|
||||||
|
if c == '{' {
|
||||||
|
j := strings.Index(input[w:], "}")
|
||||||
|
if j < 0 {
|
||||||
|
return input, len(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
varname = input[w:j]
|
||||||
|
offset = j + 1
|
||||||
|
} else {
|
||||||
|
// try to match a variable name
|
||||||
|
i := w
|
||||||
|
j := i
|
||||||
|
for j < len(input) {
|
||||||
|
c, w = utf8.DecodeRuneInString(input)
|
||||||
|
if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j += w
|
||||||
|
}
|
||||||
|
|
||||||
|
if j > i {
|
||||||
|
varname = input[i:j]
|
||||||
|
} else {
|
||||||
|
return input, len(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValidVarName(varname) {
|
||||||
|
varvals, ok := rs.vars[varname]
|
||||||
|
if ok {
|
||||||
|
return strings.Join(varvals, " "), offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input, len(input)
|
||||||
|
}
|
||||||
|
|
||||||
// Expand a backtick quoted string, by executing the contents.
|
// Expand a backtick quoted string, by executing the contents.
|
||||||
func (rs *ruleSet) expandBackQuoted(input string) (string, int) {
|
func (rs *ruleSet) expandBackQuoted(input string) (string, int) {
|
||||||
j := strings.Index(input, "`")
|
j := strings.Index(input, "`")
|
||||||
|
|
@ -209,12 +256,17 @@ func isValidVarName(v string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isdigit(c rune) bool {
|
||||||
|
return '0' <= c && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func isalpha(c rune) bool {
|
func isalpha(c rune) bool {
|
||||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|
||||||
}
|
}
|
||||||
|
|
||||||
func isalnum(c rune) bool {
|
func isalnum(c rune) bool {
|
||||||
return isalpha(c) || ('0' <= c && c <= '9')
|
return isalpha(c) || isdigit(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
type assignmentError struct {
|
type assignmentError struct {
|
||||||
|
|
@ -234,7 +286,7 @@ func (rs *ruleSet) executeAssignment(ts []token) *assignmentError {
|
||||||
// expanded variables
|
// expanded variables
|
||||||
vals := make([]string, len(ts)-1)
|
vals := make([]string, len(ts)-1)
|
||||||
for i := 0; i < len(vals); i++ {
|
for i := 0; i < len(vals); i++ {
|
||||||
vals[i] = rs.expand(ts[i+1].val)
|
vals[i] = rs.expand(ts[i+1].val, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.vars[assignee] = vals
|
rs.vars[assignee] = vals
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue