]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // Package token defines constants representing the lexical tokens for HCL |
2 | // (HashiCorp Configuration Language) | |
3 | package token | |
4 | ||
5 | import ( | |
6 | "fmt" | |
7 | "strconv" | |
8 | "strings" | |
9 | ||
10 | hclstrconv "github.com/hashicorp/hcl/hcl/strconv" | |
11 | ) | |
12 | ||
13 | // Token defines a single HCL token which can be obtained via the Scanner | |
14 | type Token struct { | |
15 | Type Type | |
16 | Pos Pos | |
17 | Text string | |
18 | JSON bool | |
19 | } | |
20 | ||
21 | // Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language) | |
22 | type Type int | |
23 | ||
24 | const ( | |
25 | // Special tokens | |
26 | ILLEGAL Type = iota | |
27 | EOF | |
28 | COMMENT | |
29 | ||
30 | identifier_beg | |
31 | IDENT // literals | |
32 | literal_beg | |
33 | NUMBER // 12345 | |
34 | FLOAT // 123.45 | |
35 | BOOL // true,false | |
36 | STRING // "abc" | |
37 | HEREDOC // <<FOO\nbar\nFOO | |
38 | literal_end | |
39 | identifier_end | |
40 | ||
41 | operator_beg | |
42 | LBRACK // [ | |
43 | LBRACE // { | |
44 | COMMA // , | |
45 | PERIOD // . | |
46 | ||
47 | RBRACK // ] | |
48 | RBRACE // } | |
49 | ||
50 | ASSIGN // = | |
51 | ADD // + | |
52 | SUB // - | |
53 | operator_end | |
54 | ) | |
55 | ||
56 | var tokens = [...]string{ | |
57 | ILLEGAL: "ILLEGAL", | |
58 | ||
59 | EOF: "EOF", | |
60 | COMMENT: "COMMENT", | |
61 | ||
62 | IDENT: "IDENT", | |
63 | NUMBER: "NUMBER", | |
64 | FLOAT: "FLOAT", | |
65 | BOOL: "BOOL", | |
66 | STRING: "STRING", | |
67 | ||
68 | LBRACK: "LBRACK", | |
69 | LBRACE: "LBRACE", | |
70 | COMMA: "COMMA", | |
71 | PERIOD: "PERIOD", | |
72 | HEREDOC: "HEREDOC", | |
73 | ||
74 | RBRACK: "RBRACK", | |
75 | RBRACE: "RBRACE", | |
76 | ||
77 | ASSIGN: "ASSIGN", | |
78 | ADD: "ADD", | |
79 | SUB: "SUB", | |
80 | } | |
81 | ||
82 | // String returns the string corresponding to the token tok. | |
83 | func (t Type) String() string { | |
84 | s := "" | |
85 | if 0 <= t && t < Type(len(tokens)) { | |
86 | s = tokens[t] | |
87 | } | |
88 | if s == "" { | |
89 | s = "token(" + strconv.Itoa(int(t)) + ")" | |
90 | } | |
91 | return s | |
92 | } | |
93 | ||
94 | // IsIdentifier returns true for tokens corresponding to identifiers and basic | |
95 | // type literals; it returns false otherwise. | |
96 | func (t Type) IsIdentifier() bool { return identifier_beg < t && t < identifier_end } | |
97 | ||
98 | // IsLiteral returns true for tokens corresponding to basic type literals; it | |
99 | // returns false otherwise. | |
100 | func (t Type) IsLiteral() bool { return literal_beg < t && t < literal_end } | |
101 | ||
102 | // IsOperator returns true for tokens corresponding to operators and | |
103 | // delimiters; it returns false otherwise. | |
104 | func (t Type) IsOperator() bool { return operator_beg < t && t < operator_end } | |
105 | ||
106 | // String returns the token's literal text. Note that this is only | |
107 | // applicable for certain token types, such as token.IDENT, | |
108 | // token.STRING, etc.. | |
109 | func (t Token) String() string { | |
110 | return fmt.Sprintf("%s %s %s", t.Pos.String(), t.Type.String(), t.Text) | |
111 | } | |
112 | ||
113 | // Value returns the properly typed value for this token. The type of | |
114 | // the returned interface{} is guaranteed based on the Type field. | |
115 | // | |
116 | // This can only be called for literal types. If it is called for any other | |
117 | // type, this will panic. | |
118 | func (t Token) Value() interface{} { | |
119 | switch t.Type { | |
120 | case BOOL: | |
121 | if t.Text == "true" { | |
122 | return true | |
123 | } else if t.Text == "false" { | |
124 | return false | |
125 | } | |
126 | ||
127 | panic("unknown bool value: " + t.Text) | |
128 | case FLOAT: | |
129 | v, err := strconv.ParseFloat(t.Text, 64) | |
130 | if err != nil { | |
131 | panic(err) | |
132 | } | |
133 | ||
134 | return float64(v) | |
135 | case NUMBER: | |
136 | v, err := strconv.ParseInt(t.Text, 0, 64) | |
137 | if err != nil { | |
138 | panic(err) | |
139 | } | |
140 | ||
141 | return int64(v) | |
142 | case IDENT: | |
143 | return t.Text | |
144 | case HEREDOC: | |
145 | return unindentHeredoc(t.Text) | |
146 | case STRING: | |
147 | // Determine the Unquote method to use. If it came from JSON, | |
148 | // then we need to use the built-in unquote since we have to | |
149 | // escape interpolations there. | |
150 | f := hclstrconv.Unquote | |
151 | if t.JSON { | |
152 | f = strconv.Unquote | |
153 | } | |
154 | ||
155 | // This case occurs if json null is used | |
156 | if t.Text == "" { | |
157 | return "" | |
158 | } | |
159 | ||
160 | v, err := f(t.Text) | |
161 | if err != nil { | |
162 | panic(fmt.Sprintf("unquote %s err: %s", t.Text, err)) | |
163 | } | |
164 | ||
165 | return v | |
166 | default: | |
167 | panic(fmt.Sprintf("unimplemented Value for type: %s", t.Type)) | |
168 | } | |
169 | } | |
170 | ||
171 | // unindentHeredoc returns the string content of a HEREDOC if it is started with << | |
172 | // and the content of a HEREDOC with the hanging indent removed if it is started with | |
173 | // a <<-, and the terminating line is at least as indented as the least indented line. | |
174 | func unindentHeredoc(heredoc string) string { | |
175 | // We need to find the end of the marker | |
176 | idx := strings.IndexByte(heredoc, '\n') | |
177 | if idx == -1 { | |
178 | panic("heredoc doesn't contain newline") | |
179 | } | |
180 | ||
181 | unindent := heredoc[2] == '-' | |
182 | ||
183 | // We can optimize if the heredoc isn't marked for indentation | |
184 | if !unindent { | |
185 | return string(heredoc[idx+1 : len(heredoc)-idx+1]) | |
186 | } | |
187 | ||
188 | // We need to unindent each line based on the indentation level of the marker | |
189 | lines := strings.Split(string(heredoc[idx+1:len(heredoc)-idx+2]), "\n") | |
190 | whitespacePrefix := lines[len(lines)-1] | |
191 | ||
192 | isIndented := true | |
193 | for _, v := range lines { | |
194 | if strings.HasPrefix(v, whitespacePrefix) { | |
195 | continue | |
196 | } | |
197 | ||
198 | isIndented = false | |
199 | break | |
200 | } | |
201 | ||
202 | // If all lines are not at least as indented as the terminating mark, return the | |
203 | // heredoc as is, but trim the leading space from the marker on the final line. | |
204 | if !isIndented { | |
205 | return strings.TrimRight(string(heredoc[idx+1:len(heredoc)-idx+1]), " \t") | |
206 | } | |
207 | ||
208 | unindentedLines := make([]string, len(lines)) | |
209 | for k, v := range lines { | |
210 | if k == len(lines)-1 { | |
211 | unindentedLines[k] = "" | |
212 | break | |
213 | } | |
214 | ||
215 | unindentedLines[k] = strings.TrimPrefix(v, whitespacePrefix) | |
216 | } | |
217 | ||
218 | return strings.Join(unindentedLines, "\n") | |
219 | } |