Colin Cross | 8e0c511 | 2015-01-23 14:15:10 -0800 | [diff] [blame] | 1 | // Copyright 2014 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 15 | package parser |
| 16 | |
| 17 | import ( |
| 18 | "errors" |
| 19 | "fmt" |
| 20 | "io" |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 21 | "sort" |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 22 | "strconv" |
| 23 | "strings" |
| 24 | "text/scanner" |
| 25 | ) |
| 26 | |
| 27 | var errTooManyErrors = errors.New("too many errors") |
| 28 | |
| 29 | const maxErrors = 1 |
| 30 | |
| 31 | type ParseError struct { |
| 32 | Err error |
| 33 | Pos scanner.Position |
| 34 | } |
| 35 | |
| 36 | func (e *ParseError) Error() string { |
| 37 | return fmt.Sprintf("%s: %s", e.Pos, e.Err) |
| 38 | } |
| 39 | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 40 | type File struct { |
Colin Cross | 23d7aa1 | 2015-06-30 16:05:22 -0700 | [diff] [blame] | 41 | Name string |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 42 | Defs []Definition |
Colin Cross | 1e73794 | 2016-06-10 17:27:12 -0700 | [diff] [blame] | 43 | Comments []*CommentGroup |
Colin Cross | 1ead645 | 2016-06-09 17:40:13 -0700 | [diff] [blame] | 44 | } |
| 45 | |
| 46 | func (f *File) Pos() scanner.Position { |
| 47 | return scanner.Position{ |
| 48 | Filename: f.Name, |
| 49 | Line: 1, |
| 50 | Column: 1, |
| 51 | Offset: 0, |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | func (f *File) End() scanner.Position { |
| 56 | if len(f.Defs) > 0 { |
| 57 | return f.Defs[len(f.Defs)-1].End() |
| 58 | } |
| 59 | return noPos |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 60 | } |
| 61 | |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 62 | func parse(p *parser) (file *File, errs []error) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 63 | defer func() { |
| 64 | if r := recover(); r != nil { |
| 65 | if r == errTooManyErrors { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 66 | errs = p.errors |
| 67 | return |
| 68 | } |
| 69 | panic(r) |
| 70 | } |
| 71 | }() |
| 72 | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 73 | defs := p.parseDefinitions() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 74 | p.accept(scanner.EOF) |
| 75 | errs = p.errors |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 76 | comments := p.comments |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 77 | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 78 | return &File{ |
Colin Cross | 23d7aa1 | 2015-06-30 16:05:22 -0700 | [diff] [blame] | 79 | Name: p.scanner.Filename, |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 80 | Defs: defs, |
| 81 | Comments: comments, |
| 82 | }, errs |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 83 | |
| 84 | } |
| 85 | |
| 86 | func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) { |
| 87 | p := newParser(r, scope) |
| 88 | p.eval = true |
| 89 | p.scanner.Filename = filename |
| 90 | |
| 91 | return parse(p) |
| 92 | } |
| 93 | |
| 94 | func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) { |
| 95 | p := newParser(r, scope) |
| 96 | p.scanner.Filename = filename |
| 97 | |
| 98 | return parse(p) |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | type parser struct { |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 102 | scanner scanner.Scanner |
| 103 | tok rune |
| 104 | errors []error |
| 105 | scope *Scope |
Colin Cross | 1e73794 | 2016-06-10 17:27:12 -0700 | [diff] [blame] | 106 | comments []*CommentGroup |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 107 | eval bool |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 108 | } |
| 109 | |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 110 | func newParser(r io.Reader, scope *Scope) *parser { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 111 | p := &parser{} |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 112 | p.scope = scope |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 113 | p.scanner.Init(r) |
| 114 | p.scanner.Error = func(sc *scanner.Scanner, msg string) { |
| 115 | p.errorf(msg) |
| 116 | } |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 117 | p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 118 | scanner.ScanRawStrings | scanner.ScanComments |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 119 | p.next() |
| 120 | return p |
| 121 | } |
| 122 | |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 123 | func (p *parser) error(err error) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 124 | pos := p.scanner.Position |
| 125 | if !pos.IsValid() { |
| 126 | pos = p.scanner.Pos() |
| 127 | } |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 128 | err = &ParseError{ |
| 129 | Err: err, |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 130 | Pos: pos, |
| 131 | } |
| 132 | p.errors = append(p.errors, err) |
| 133 | if len(p.errors) >= maxErrors { |
| 134 | panic(errTooManyErrors) |
| 135 | } |
| 136 | } |
| 137 | |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 138 | func (p *parser) errorf(format string, args ...interface{}) { |
| 139 | p.error(fmt.Errorf(format, args...)) |
| 140 | } |
| 141 | |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 142 | func (p *parser) accept(toks ...rune) bool { |
| 143 | for _, tok := range toks { |
| 144 | if p.tok != tok { |
| 145 | p.errorf("expected %s, found %s", scanner.TokenString(tok), |
| 146 | scanner.TokenString(p.tok)) |
| 147 | return false |
| 148 | } |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 149 | p.next() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 150 | } |
| 151 | return true |
| 152 | } |
| 153 | |
| 154 | func (p *parser) next() { |
| 155 | if p.tok != scanner.EOF { |
| 156 | p.tok = p.scanner.Scan() |
Colin Cross | 1e73794 | 2016-06-10 17:27:12 -0700 | [diff] [blame] | 157 | if p.tok == scanner.Comment { |
| 158 | var comments []*Comment |
| 159 | for p.tok == scanner.Comment { |
| 160 | lines := strings.Split(p.scanner.TokenText(), "\n") |
| 161 | if len(comments) > 0 && p.scanner.Position.Line > comments[len(comments)-1].End().Line+1 { |
| 162 | p.comments = append(p.comments, &CommentGroup{Comments: comments}) |
| 163 | comments = nil |
| 164 | } |
| 165 | comments = append(comments, &Comment{lines, p.scanner.Position}) |
| 166 | p.tok = p.scanner.Scan() |
| 167 | } |
| 168 | p.comments = append(p.comments, &CommentGroup{Comments: comments}) |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 169 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 170 | } |
| 171 | return |
| 172 | } |
| 173 | |
| 174 | func (p *parser) parseDefinitions() (defs []Definition) { |
| 175 | for { |
| 176 | switch p.tok { |
| 177 | case scanner.Ident: |
| 178 | ident := p.scanner.TokenText() |
| 179 | pos := p.scanner.Position |
| 180 | |
| 181 | p.accept(scanner.Ident) |
| 182 | |
| 183 | switch p.tok { |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 184 | case '+': |
| 185 | p.accept('+') |
| 186 | defs = append(defs, p.parseAssignment(ident, pos, "+=")) |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 187 | case '=': |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 188 | defs = append(defs, p.parseAssignment(ident, pos, "=")) |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 189 | case '{', '(': |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 190 | defs = append(defs, p.parseModule(ident, pos)) |
| 191 | default: |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 192 | p.errorf("expected \"=\" or \"+=\" or \"{\" or \"(\", found %s", |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 193 | scanner.TokenString(p.tok)) |
| 194 | } |
| 195 | case scanner.EOF: |
| 196 | return |
| 197 | default: |
| 198 | p.errorf("expected assignment or module definition, found %s", |
| 199 | scanner.TokenString(p.tok)) |
| 200 | return |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 205 | func (p *parser) parseAssignment(name string, namePos scanner.Position, |
| 206 | assigner string) (assignment *Assignment) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 207 | |
| 208 | assignment = new(Assignment) |
| 209 | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 210 | pos := p.scanner.Position |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 211 | if !p.accept('=') { |
| 212 | return |
| 213 | } |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 214 | value := p.parseExpression() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 215 | |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 216 | assignment.Name = name |
| 217 | assignment.NamePos = namePos |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 218 | assignment.Value = value |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 219 | assignment.OrigValue = value |
Colin Cross | b3d0b8d | 2016-06-09 17:03:57 -0700 | [diff] [blame] | 220 | assignment.EqualsPos = pos |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 221 | assignment.Assigner = assigner |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 222 | |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 223 | if p.scope != nil { |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 224 | if assigner == "+=" { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 225 | if old, local := p.scope.Get(assignment.Name); old == nil { |
| 226 | p.errorf("modified non-existent variable %q with +=", assignment.Name) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 227 | } else if !local { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 228 | p.errorf("modified non-local variable %q with +=", assignment.Name) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 229 | } else if old.Referenced { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 230 | p.errorf("modified variable %q with += after referencing", assignment.Name) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 231 | } else { |
Colin Cross | b3d0b8d | 2016-06-09 17:03:57 -0700 | [diff] [blame] | 232 | val, err := p.evaluateOperator(old.Value, assignment.Value, '+', assignment.EqualsPos) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 233 | if err != nil { |
| 234 | p.error(err) |
| 235 | } else { |
| 236 | old.Value = val |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 237 | } |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 238 | } |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 239 | } else { |
| 240 | err := p.scope.Add(assignment) |
| 241 | if err != nil { |
| 242 | p.error(err) |
| 243 | } |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 244 | } |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 245 | } |
| 246 | |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 247 | return |
| 248 | } |
| 249 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 250 | func (p *parser) parseModule(typ string, typPos scanner.Position) *Module { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 251 | |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 252 | compat := false |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 253 | lbracePos := p.scanner.Position |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 254 | if p.tok == '{' { |
| 255 | compat = true |
| 256 | } |
| 257 | |
| 258 | if !p.accept(p.tok) { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 259 | return nil |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 260 | } |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 261 | properties := p.parsePropertyList(true, compat) |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 262 | rbracePos := p.scanner.Position |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 263 | if !compat { |
| 264 | p.accept(')') |
| 265 | } else { |
| 266 | p.accept('}') |
| 267 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 268 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 269 | return &Module{ |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 270 | Type: typ, |
| 271 | TypePos: typPos, |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 272 | Map: Map{ |
| 273 | Properties: properties, |
| 274 | LBracePos: lbracePos, |
| 275 | RBracePos: rbracePos, |
| 276 | }, |
| 277 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 278 | } |
| 279 | |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 280 | func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 281 | for p.tok == scanner.Ident { |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 282 | property := p.parseProperty(isModule, compat) |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 283 | properties = append(properties, property) |
| 284 | |
| 285 | if p.tok != ',' { |
| 286 | // There was no comma, so the list is done. |
| 287 | break |
| 288 | } |
| 289 | |
| 290 | p.accept(',') |
| 291 | } |
| 292 | |
| 293 | return |
| 294 | } |
| 295 | |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 296 | func (p *parser) parseProperty(isModule, compat bool) (property *Property) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 297 | property = new(Property) |
| 298 | |
| 299 | name := p.scanner.TokenText() |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 300 | namePos := p.scanner.Position |
| 301 | p.accept(scanner.Ident) |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 302 | pos := p.scanner.Position |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 303 | |
| 304 | if isModule { |
Logan Chien | 3deba3d | 2018-06-25 11:52:48 +0800 | [diff] [blame] | 305 | if compat { |
| 306 | if !p.accept(':') { |
| 307 | return |
| 308 | } |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 309 | } else { |
| 310 | if !p.accept('=') { |
| 311 | return |
| 312 | } |
| 313 | } |
| 314 | } else { |
| 315 | if !p.accept(':') { |
| 316 | return |
| 317 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 318 | } |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 319 | |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 320 | value := p.parseExpression() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 321 | |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 322 | property.Name = name |
| 323 | property.NamePos = namePos |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 324 | property.Value = value |
Colin Cross | b3d0b8d | 2016-06-09 17:03:57 -0700 | [diff] [blame] | 325 | property.ColonPos = pos |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 326 | |
| 327 | return |
| 328 | } |
| 329 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 330 | func (p *parser) parseExpression() (value Expression) { |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 331 | value = p.parseValue() |
| 332 | switch p.tok { |
| 333 | case '+': |
| 334 | return p.parseOperator(value) |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 335 | case '-': |
| 336 | p.errorf("subtraction not supported: %s", p.scanner.String()) |
| 337 | return value |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 338 | default: |
| 339 | return value |
| 340 | } |
| 341 | } |
| 342 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 343 | func (p *parser) evaluateOperator(value1, value2 Expression, operator rune, |
| 344 | pos scanner.Position) (*Operator, error) { |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 345 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 346 | value := value1 |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 347 | |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 348 | if p.eval { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 349 | e1 := value1.Eval() |
| 350 | e2 := value2.Eval() |
| 351 | if e1.Type() != e2.Type() { |
| 352 | return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator, |
| 353 | e1.Type(), e2.Type()) |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 354 | } |
| 355 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 356 | value = e1.Copy() |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 357 | |
| 358 | switch operator { |
| 359 | case '+': |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 360 | switch v := value.(type) { |
| 361 | case *String: |
| 362 | v.Value += e2.(*String).Value |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 363 | case *Int64: |
| 364 | v.Value += e2.(*Int64).Value |
Colin Cross | fdeaf88 | 2018-03-21 17:00:39 -0700 | [diff] [blame] | 365 | v.Token = "" |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 366 | case *List: |
| 367 | v.Values = append(v.Values, e2.(*List).Values...) |
| 368 | case *Map: |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 369 | var err error |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 370 | v.Properties, err = p.addMaps(v.Properties, e2.(*Map).Properties, pos) |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 371 | if err != nil { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 372 | return nil, err |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 373 | } |
| 374 | default: |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 375 | return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type()) |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 376 | } |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 377 | default: |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 378 | panic("unknown operator " + string(operator)) |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 379 | } |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 380 | } |
| 381 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 382 | return &Operator{ |
| 383 | Args: [2]Expression{value1, value2}, |
| 384 | Operator: operator, |
| 385 | OperatorPos: pos, |
| 386 | Value: value, |
| 387 | }, nil |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 388 | } |
| 389 | |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 390 | func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) { |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 391 | ret := make([]*Property, 0, len(map1)) |
Colin Cross | 969c703 | 2015-03-10 14:37:27 -0700 | [diff] [blame] | 392 | |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 393 | inMap1 := make(map[string]*Property) |
| 394 | inMap2 := make(map[string]*Property) |
| 395 | inBoth := make(map[string]*Property) |
Colin Cross | 969c703 | 2015-03-10 14:37:27 -0700 | [diff] [blame] | 396 | |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 397 | for _, prop1 := range map1 { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 398 | inMap1[prop1.Name] = prop1 |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 399 | } |
| 400 | |
| 401 | for _, prop2 := range map2 { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 402 | inMap2[prop2.Name] = prop2 |
| 403 | if _, ok := inMap1[prop2.Name]; ok { |
| 404 | inBoth[prop2.Name] = prop2 |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 405 | } |
| 406 | } |
Colin Cross | 969c703 | 2015-03-10 14:37:27 -0700 | [diff] [blame] | 407 | |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 408 | for _, prop1 := range map1 { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 409 | if prop2, ok := inBoth[prop1.Name]; ok { |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 410 | var err error |
| 411 | newProp := *prop1 |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 412 | newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos) |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 413 | if err != nil { |
| 414 | return nil, err |
| 415 | } |
| 416 | ret = append(ret, &newProp) |
| 417 | } else { |
| 418 | ret = append(ret, prop1) |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | for _, prop2 := range map2 { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 423 | if _, ok := inBoth[prop2.Name]; !ok { |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 424 | ret = append(ret, prop2) |
| 425 | } |
| 426 | } |
Colin Cross | 969c703 | 2015-03-10 14:37:27 -0700 | [diff] [blame] | 427 | |
Colin Cross | 542fd55 | 2015-02-12 10:12:10 -0800 | [diff] [blame] | 428 | return ret, nil |
| 429 | } |
| 430 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 431 | func (p *parser) parseOperator(value1 Expression) *Operator { |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 432 | operator := p.tok |
| 433 | pos := p.scanner.Position |
| 434 | p.accept(operator) |
| 435 | |
| 436 | value2 := p.parseExpression() |
| 437 | |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 438 | value, err := p.evaluateOperator(value1, value2, operator, pos) |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 439 | if err != nil { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 440 | p.error(err) |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 441 | return nil |
Colin Cross | b274e6c | 2015-02-04 10:50:22 -0800 | [diff] [blame] | 442 | } |
| 443 | |
Colin Cross | d1facc1 | 2015-01-08 14:56:03 -0800 | [diff] [blame] | 444 | return value |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 445 | |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 446 | } |
| 447 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 448 | func (p *parser) parseValue() (value Expression) { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 449 | switch p.tok { |
| 450 | case scanner.Ident: |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 451 | return p.parseVariable() |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 452 | case '-', scanner.Int: // Integer might have '-' sign ahead ('+' is only treated as operator now) |
| 453 | return p.parseIntValue() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 454 | case scanner.String: |
| 455 | return p.parseStringValue() |
| 456 | case '[': |
| 457 | return p.parseListValue() |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 458 | case '{': |
| 459 | return p.parseMapValue() |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 460 | default: |
| 461 | p.errorf("expected bool, list, or string value; found %s", |
| 462 | scanner.TokenString(p.tok)) |
| 463 | return |
| 464 | } |
| 465 | } |
| 466 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 467 | func (p *parser) parseVariable() Expression { |
| 468 | var value Expression |
| 469 | |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 470 | switch text := p.scanner.TokenText(); text { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 471 | case "true", "false": |
| 472 | value = &Bool{ |
| 473 | LiteralPos: p.scanner.Position, |
| 474 | Value: text == "true", |
Colin Cross | fdeaf88 | 2018-03-21 17:00:39 -0700 | [diff] [blame] | 475 | Token: text, |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 476 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 477 | default: |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 478 | if p.eval { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 479 | if assignment, local := p.scope.Get(text); assignment == nil { |
| 480 | p.errorf("variable %q is not set", text) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 481 | } else { |
| 482 | if local { |
| 483 | assignment.Referenced = true |
| 484 | } |
| 485 | value = assignment.Value |
Colin Cross | 96e5670 | 2015-03-19 17:28:06 -0700 | [diff] [blame] | 486 | } |
Sasha Smundak | 77418b7 | 2020-01-21 13:31:06 -0800 | [diff] [blame] | 487 | } else { |
| 488 | value = &NotEvaluated{} |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 489 | } |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 490 | value = &Variable{ |
| 491 | Name: text, |
| 492 | NamePos: p.scanner.Position, |
| 493 | Value: value, |
| 494 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 495 | } |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 496 | |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 497 | p.accept(scanner.Ident) |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 498 | return value |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 499 | } |
| 500 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 501 | func (p *parser) parseStringValue() *String { |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 502 | str, err := strconv.Unquote(p.scanner.TokenText()) |
| 503 | if err != nil { |
| 504 | p.errorf("couldn't parse string: %s", err) |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 505 | return nil |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 506 | } |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 507 | |
| 508 | value := &String{ |
| 509 | LiteralPos: p.scanner.Position, |
| 510 | Value: str, |
| 511 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 512 | p.accept(scanner.String) |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 513 | return value |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 514 | } |
| 515 | |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 516 | func (p *parser) parseIntValue() *Int64 { |
| 517 | var str string |
| 518 | literalPos := p.scanner.Position |
| 519 | if p.tok == '-' { |
| 520 | str += string(p.tok) |
| 521 | p.accept(p.tok) |
| 522 | if p.tok != scanner.Int { |
| 523 | p.errorf("expected int; found %s", scanner.TokenString(p.tok)) |
| 524 | return nil |
| 525 | } |
| 526 | } |
| 527 | str += p.scanner.TokenText() |
| 528 | i, err := strconv.ParseInt(str, 10, 64) |
| 529 | if err != nil { |
| 530 | p.errorf("couldn't parse int: %s", err) |
| 531 | return nil |
| 532 | } |
| 533 | |
| 534 | value := &Int64{ |
| 535 | LiteralPos: literalPos, |
| 536 | Value: i, |
Colin Cross | fdeaf88 | 2018-03-21 17:00:39 -0700 | [diff] [blame] | 537 | Token: str, |
Nan Zhang | f586544 | 2017-11-01 14:03:28 -0700 | [diff] [blame] | 538 | } |
| 539 | p.accept(scanner.Int) |
| 540 | return value |
| 541 | } |
| 542 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 543 | func (p *parser) parseListValue() *List { |
| 544 | lBracePos := p.scanner.Position |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 545 | if !p.accept('[') { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 546 | return nil |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 547 | } |
| 548 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 549 | var elements []Expression |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 550 | for p.tok != ']' { |
Colin Cross | 82b7d51 | 2015-01-02 15:47:54 -0800 | [diff] [blame] | 551 | element := p.parseExpression() |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 552 | elements = append(elements, element) |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 553 | |
| 554 | if p.tok != ',' { |
| 555 | // There was no comma, so the list is done. |
| 556 | break |
| 557 | } |
| 558 | |
| 559 | p.accept(',') |
| 560 | } |
| 561 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 562 | rBracePos := p.scanner.Position |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 563 | p.accept(']') |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 564 | |
| 565 | return &List{ |
| 566 | LBracePos: lBracePos, |
| 567 | RBracePos: rBracePos, |
| 568 | Values: elements, |
| 569 | } |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 570 | } |
| 571 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 572 | func (p *parser) parseMapValue() *Map { |
| 573 | lBracePos := p.scanner.Position |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 574 | if !p.accept('{') { |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 575 | return nil |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 576 | } |
| 577 | |
Colin Cross | 6bb4af9 | 2015-01-14 17:04:13 -0800 | [diff] [blame] | 578 | properties := p.parsePropertyList(false, false) |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 579 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 580 | rBracePos := p.scanner.Position |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 581 | p.accept('}') |
Romain Guy | 2476f81 | 2014-07-25 17:01:20 -0700 | [diff] [blame] | 582 | |
Colin Cross | e32cc80 | 2016-06-07 12:28:16 -0700 | [diff] [blame] | 583 | return &Map{ |
| 584 | LBracePos: lBracePos, |
| 585 | RBracePos: rBracePos, |
| 586 | Properties: properties, |
Jamie Gennis | 1bc967e | 2014-05-27 16:34:41 -0700 | [diff] [blame] | 587 | } |
| 588 | } |
| 589 | |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 590 | type Scope struct { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 591 | vars map[string]*Assignment |
| 592 | inheritedVars map[string]*Assignment |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 593 | } |
| 594 | |
| 595 | func NewScope(s *Scope) *Scope { |
| 596 | newScope := &Scope{ |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 597 | vars: make(map[string]*Assignment), |
| 598 | inheritedVars: make(map[string]*Assignment), |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 599 | } |
| 600 | |
| 601 | if s != nil { |
| 602 | for k, v := range s.vars { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 603 | newScope.inheritedVars[k] = v |
| 604 | } |
| 605 | for k, v := range s.inheritedVars { |
| 606 | newScope.inheritedVars[k] = v |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 607 | } |
| 608 | } |
| 609 | |
| 610 | return newScope |
| 611 | } |
| 612 | |
| 613 | func (s *Scope) Add(assignment *Assignment) error { |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 614 | if old, ok := s.vars[assignment.Name]; ok { |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 615 | return fmt.Errorf("variable already set, previous assignment: %s", old) |
| 616 | } |
| 617 | |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 618 | if old, ok := s.inheritedVars[assignment.Name]; ok { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 619 | return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old) |
| 620 | } |
| 621 | |
Colin Cross | c32c479 | 2016-06-09 15:52:30 -0700 | [diff] [blame] | 622 | s.vars[assignment.Name] = assignment |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 623 | |
| 624 | return nil |
| 625 | } |
| 626 | |
| 627 | func (s *Scope) Remove(name string) { |
| 628 | delete(s.vars, name) |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 629 | delete(s.inheritedVars, name) |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 630 | } |
| 631 | |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 632 | func (s *Scope) Get(name string) (*Assignment, bool) { |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 633 | if a, ok := s.vars[name]; ok { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 634 | return a, true |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 635 | } |
| 636 | |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 637 | if a, ok := s.inheritedVars[name]; ok { |
| 638 | return a, false |
| 639 | } |
| 640 | |
| 641 | return nil, false |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 642 | } |
| 643 | |
| 644 | func (s *Scope) String() string { |
| 645 | vars := []string{} |
| 646 | |
| 647 | for k := range s.vars { |
| 648 | vars = append(vars, k) |
| 649 | } |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 650 | for k := range s.inheritedVars { |
| 651 | vars = append(vars, k) |
| 652 | } |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 653 | |
| 654 | sort.Strings(vars) |
| 655 | |
| 656 | ret := []string{} |
| 657 | for _, v := range vars { |
Colin Cross | 6d8780f | 2015-07-10 17:51:55 -0700 | [diff] [blame] | 658 | if assignment, ok := s.vars[v]; ok { |
| 659 | ret = append(ret, assignment.String()) |
| 660 | } else { |
| 661 | ret = append(ret, s.inheritedVars[v].String()) |
| 662 | } |
Colin Cross | c0dbc55 | 2015-01-02 15:19:28 -0800 | [diff] [blame] | 663 | } |
| 664 | |
| 665 | return strings.Join(ret, "\n") |
| 666 | } |