aboutsummaryrefslogtreecommitdiffstats
path: root/rule_parser.go
blob: 729f2d32f437513e3657c609e7577c79e80e9ffb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package main

import (
	"errors"
	"strings"
)

type Rule struct {
	outputs         []string
	inputs          []string
	orderOnlyInputs []string
	outputPatterns  []string
	isDoubleColon   bool
	isSuffixRule    bool
	vars            *VarTab
	cmds            []string
	filename        string
	lineno          int
	cmdLineno       int
}

func isPatternRule(s string) bool {
	return strings.IndexByte(s, '%') >= 0
}

func (r *Rule) parseInputs(s string) {
	inputs := splitSpaces(s)
	isOrderOnly := false
	for _, input := range inputs {
		if input == "|" {
			isOrderOnly = true
			continue
		}
		if isOrderOnly {
			r.orderOnlyInputs = append(r.orderOnlyInputs, input)
		} else {
			r.inputs = append(r.inputs, input)
		}
	}
}

func (r *Rule) parseVar(s string) *AssignAST {
	eq := strings.IndexByte(s, '=')
	if eq <= 0 {
		return nil
	}
	assign := &AssignAST{
		rhs: strings.TrimLeft(s[eq+1:], " \t"),
	}
	assign.filename = r.filename
	assign.lineno = r.lineno
	// TODO(ukai): support override, export.
	switch s[eq-1 : eq] {
	case ":=":
		assign.lhs = strings.TrimSpace(s[:eq-1])
		assign.op = ":="
	case "+=":
		assign.lhs = strings.TrimSpace(s[:eq-1])
		assign.op = "+="
	case "?=":
		assign.lhs = strings.TrimSpace(s[:eq-1])
		assign.op = "?="
	default:
		assign.lhs = strings.TrimSpace(s[:eq])
		assign.op = "="
	}
	return assign
}

func (r *Rule) parse(line string) (*AssignAST, error) {
	index := strings.IndexByte(line, ':')
	if index < 0 {
		return nil, errors.New("*** missing separator.")
	}

	first := line[:index]
	outputs := splitSpaces(first)
	isFirstPattern := isPatternRule(first)
	if isFirstPattern {
		if len(outputs) > 1 {
			return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
		}
		r.outputPatterns = outputs
	} else {
		r.outputs = outputs
	}

	index++
	if index < len(line) && line[index] == ':' {
		r.isDoubleColon = true
		index++
	}

	rest := line[index:]
	if assign := r.parseVar(rest); assign != nil {
		return assign, nil
	}
	index = strings.IndexByte(rest, ':')
	if index < 0 {
		r.parseInputs(rest)
		return nil, nil
	}

	// %.x: %.y: %.z
	if isFirstPattern {
		return nil, errors.New("*** mixed implicit and normal rules: deprecated syntax")
	}

	second := rest[:index]
	third := rest[index+1:]

	r.outputs = outputs
	r.outputPatterns = splitSpaces(second)
	if len(r.outputPatterns) == 0 {
		return nil, errors.New("*** missing target pattern.")
	}
	if len(r.outputPatterns) > 1 {
		return nil, errors.New("*** multiple target patterns.")
	}
	if !isPatternRule(r.outputPatterns[0]) {
		return nil, errors.New("*** target pattern contains no '%'.")
	}
	r.parseInputs(third)

	return nil, nil
}