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 15package parser 16 17import ( 18 "bytes" 19 "reflect" 20 "testing" 21 "text/scanner" 22) 23 24func mkpos(offset, line, column int) scanner.Position { 25 return scanner.Position{ 26 Offset: offset, 27 Line: line, 28 Column: column, 29 } 30} 31 32var validParseTestCases = []struct { 33 input string 34 defs []Definition 35 comments []Comment 36}{ 37 {` 38 foo {} 39 `, 40 []Definition{ 41 &Module{ 42 Type: Ident{"foo", mkpos(3, 2, 3)}, 43 LbracePos: mkpos(7, 2, 7), 44 RbracePos: mkpos(8, 2, 8), 45 }, 46 }, 47 nil, 48 }, 49 50 {` 51 foo { 52 name: "abc", 53 } 54 `, 55 []Definition{ 56 &Module{ 57 Type: Ident{"foo", mkpos(3, 2, 3)}, 58 LbracePos: mkpos(7, 2, 7), 59 RbracePos: mkpos(27, 4, 3), 60 Properties: []*Property{ 61 { 62 Name: Ident{"name", mkpos(12, 3, 4)}, 63 Pos: mkpos(16, 3, 8), 64 Value: Value{ 65 Type: String, 66 Pos: mkpos(18, 3, 10), 67 StringValue: "abc", 68 }, 69 }, 70 }, 71 }, 72 }, 73 nil, 74 }, 75 76 {` 77 foo { 78 isGood: true, 79 } 80 `, 81 []Definition{ 82 &Module{ 83 Type: Ident{"foo", mkpos(3, 2, 3)}, 84 LbracePos: mkpos(7, 2, 7), 85 RbracePos: mkpos(28, 4, 3), 86 Properties: []*Property{ 87 { 88 Name: Ident{"isGood", mkpos(12, 3, 4)}, 89 Pos: mkpos(18, 3, 10), 90 Value: Value{ 91 Type: Bool, 92 Pos: mkpos(20, 3, 12), 93 BoolValue: true, 94 }, 95 }, 96 }, 97 }, 98 }, 99 nil, 100 }, 101 102 {` 103 foo { 104 stuff: ["asdf", "jkl;", "qwert", 105 "uiop", "bnm,"] 106 } 107 `, 108 []Definition{ 109 &Module{ 110 Type: Ident{"foo", mkpos(3, 2, 3)}, 111 LbracePos: mkpos(7, 2, 7), 112 RbracePos: mkpos(67, 5, 3), 113 Properties: []*Property{ 114 { 115 Name: Ident{"stuff", mkpos(12, 3, 4)}, 116 Pos: mkpos(17, 3, 9), 117 Value: Value{ 118 Type: List, 119 Pos: mkpos(19, 3, 11), 120 EndPos: mkpos(63, 4, 19), 121 ListValue: []Value{ 122 Value{ 123 Type: String, 124 Pos: mkpos(20, 3, 12), 125 StringValue: "asdf", 126 }, 127 Value{ 128 Type: String, 129 Pos: mkpos(28, 3, 20), 130 StringValue: "jkl;", 131 }, 132 Value{ 133 Type: String, 134 Pos: mkpos(36, 3, 28), 135 StringValue: "qwert", 136 }, 137 Value{ 138 Type: String, 139 Pos: mkpos(49, 4, 5), 140 StringValue: "uiop", 141 }, 142 Value{ 143 Type: String, 144 Pos: mkpos(57, 4, 13), 145 StringValue: "bnm,", 146 }, 147 }, 148 }, 149 }, 150 }, 151 }, 152 }, 153 nil, 154 }, 155 156 {` 157 foo { 158 stuff: { 159 isGood: true, 160 name: "bar" 161 } 162 } 163 `, 164 []Definition{ 165 &Module{ 166 Type: Ident{"foo", mkpos(3, 2, 3)}, 167 LbracePos: mkpos(7, 2, 7), 168 RbracePos: mkpos(62, 7, 3), 169 Properties: []*Property{ 170 { 171 Name: Ident{"stuff", mkpos(12, 3, 4)}, 172 Pos: mkpos(17, 3, 9), 173 Value: Value{ 174 Type: Map, 175 Pos: mkpos(19, 3, 11), 176 EndPos: mkpos(58, 6, 4), 177 MapValue: []*Property{ 178 { 179 Name: Ident{"isGood", mkpos(25, 4, 5)}, 180 Pos: mkpos(31, 4, 11), 181 Value: Value{ 182 Type: Bool, 183 Pos: mkpos(33, 4, 13), 184 BoolValue: true, 185 }, 186 }, 187 { 188 Name: Ident{"name", mkpos(43, 5, 5)}, 189 Pos: mkpos(47, 5, 9), 190 Value: Value{ 191 Type: String, 192 Pos: mkpos(49, 5, 11), 193 StringValue: "bar", 194 }, 195 }, 196 }, 197 }, 198 }, 199 }, 200 }, 201 }, 202 nil, 203 }, 204 205 {` 206 // comment1 207 foo { 208 // comment2 209 isGood: true, // comment3 210 } 211 `, 212 []Definition{ 213 &Module{ 214 Type: Ident{"foo", mkpos(17, 3, 3)}, 215 LbracePos: mkpos(21, 3, 7), 216 RbracePos: mkpos(70, 6, 3), 217 Properties: []*Property{ 218 { 219 Name: Ident{"isGood", mkpos(41, 5, 4)}, 220 Pos: mkpos(47, 5, 10), 221 Value: Value{ 222 Type: Bool, 223 Pos: mkpos(49, 5, 12), 224 BoolValue: true, 225 }, 226 }, 227 }, 228 }, 229 }, 230 []Comment{ 231 Comment{ 232 Comment: []string{"// comment1"}, 233 Pos: mkpos(3, 2, 3), 234 }, 235 Comment{ 236 Comment: []string{"// comment2"}, 237 Pos: mkpos(26, 4, 4), 238 }, 239 Comment{ 240 Comment: []string{"// comment3"}, 241 Pos: mkpos(56, 5, 19), 242 }, 243 }, 244 }, 245 246 {` 247 foo { 248 name: "abc", 249 } 250 251 bar { 252 name: "def", 253 } 254 `, 255 []Definition{ 256 &Module{ 257 Type: Ident{"foo", mkpos(3, 2, 3)}, 258 LbracePos: mkpos(7, 2, 7), 259 RbracePos: mkpos(27, 4, 3), 260 Properties: []*Property{ 261 { 262 Name: Ident{"name", mkpos(12, 3, 4)}, 263 Pos: mkpos(16, 3, 8), 264 Value: Value{ 265 Type: String, 266 Pos: mkpos(18, 3, 10), 267 StringValue: "abc", 268 }, 269 }, 270 }, 271 }, 272 &Module{ 273 Type: Ident{"bar", mkpos(32, 6, 3)}, 274 LbracePos: mkpos(36, 6, 7), 275 RbracePos: mkpos(56, 8, 3), 276 Properties: []*Property{ 277 { 278 Name: Ident{"name", mkpos(41, 7, 4)}, 279 Pos: mkpos(45, 7, 8), 280 Value: Value{ 281 Type: String, 282 Pos: mkpos(47, 7, 10), 283 StringValue: "def", 284 }, 285 }, 286 }, 287 }, 288 }, 289 nil, 290 }, 291 {` 292 foo = "stuff" 293 bar = foo 294 baz = foo + bar 295 boo = baz 296 boo += foo 297 `, 298 []Definition{ 299 &Assignment{ 300 Name: Ident{"foo", mkpos(3, 2, 3)}, 301 Pos: mkpos(7, 2, 7), 302 Value: Value{ 303 Type: String, 304 Pos: mkpos(9, 2, 9), 305 StringValue: "stuff", 306 }, 307 OrigValue: Value{ 308 Type: String, 309 Pos: mkpos(9, 2, 9), 310 StringValue: "stuff", 311 }, 312 Assigner: "=", 313 Referenced: true, 314 }, 315 &Assignment{ 316 Name: Ident{"bar", mkpos(19, 3, 3)}, 317 Pos: mkpos(23, 3, 7), 318 Value: Value{ 319 Type: String, 320 Pos: mkpos(25, 3, 9), 321 StringValue: "stuff", 322 Variable: "foo", 323 }, 324 OrigValue: Value{ 325 Type: String, 326 Pos: mkpos(25, 3, 9), 327 StringValue: "stuff", 328 Variable: "foo", 329 }, 330 Assigner: "=", 331 Referenced: true, 332 }, 333 &Assignment{ 334 Name: Ident{"baz", mkpos(31, 4, 3)}, 335 Pos: mkpos(35, 4, 7), 336 Value: Value{ 337 Type: String, 338 Pos: mkpos(37, 4, 9), 339 StringValue: "stuffstuff", 340 Expression: &Expression{ 341 Args: [2]Value{ 342 { 343 Type: String, 344 Pos: mkpos(37, 4, 9), 345 StringValue: "stuff", 346 Variable: "foo", 347 }, 348 { 349 Type: String, 350 Pos: mkpos(43, 4, 15), 351 StringValue: "stuff", 352 Variable: "bar", 353 }, 354 }, 355 Operator: '+', 356 Pos: mkpos(41, 4, 13), 357 }, 358 }, 359 OrigValue: Value{ 360 Type: String, 361 Pos: mkpos(37, 4, 9), 362 StringValue: "stuffstuff", 363 Expression: &Expression{ 364 Args: [2]Value{ 365 { 366 Type: String, 367 Pos: mkpos(37, 4, 9), 368 StringValue: "stuff", 369 Variable: "foo", 370 }, 371 { 372 Type: String, 373 Pos: mkpos(43, 4, 15), 374 StringValue: "stuff", 375 Variable: "bar", 376 }, 377 }, 378 Operator: '+', 379 Pos: mkpos(41, 4, 13), 380 }, 381 }, 382 Assigner: "=", 383 Referenced: true, 384 }, 385 &Assignment{ 386 Name: Ident{"boo", mkpos(49, 5, 3)}, 387 Pos: mkpos(53, 5, 7), 388 Value: Value{ 389 Type: String, 390 Pos: mkpos(55, 5, 9), 391 StringValue: "stuffstuffstuff", 392 Expression: &Expression{ 393 Args: [2]Value{ 394 { 395 Type: String, 396 Pos: mkpos(55, 5, 9), 397 StringValue: "stuffstuff", 398 Variable: "baz", 399 Expression: &Expression{ 400 Args: [2]Value{ 401 { 402 Type: String, 403 Pos: mkpos(37, 4, 9), 404 StringValue: "stuff", 405 Variable: "foo", 406 }, 407 { 408 Type: String, 409 Pos: mkpos(43, 4, 15), 410 StringValue: "stuff", 411 Variable: "bar", 412 }, 413 }, 414 Operator: '+', 415 Pos: mkpos(41, 4, 13), 416 }, 417 }, 418 { 419 Variable: "foo", 420 Type: String, 421 Pos: mkpos(68, 6, 10), 422 StringValue: "stuff", 423 }, 424 }, 425 Pos: mkpos(66, 6, 8), 426 Operator: '+', 427 }, 428 }, 429 OrigValue: Value{ 430 Type: String, 431 Pos: mkpos(55, 5, 9), 432 StringValue: "stuffstuff", 433 Variable: "baz", 434 Expression: &Expression{ 435 Args: [2]Value{ 436 { 437 Type: String, 438 Pos: mkpos(37, 4, 9), 439 StringValue: "stuff", 440 Variable: "foo", 441 }, 442 { 443 Type: String, 444 Pos: mkpos(43, 4, 15), 445 StringValue: "stuff", 446 Variable: "bar", 447 }, 448 }, 449 Operator: '+', 450 Pos: mkpos(41, 4, 13), 451 }, 452 }, 453 Assigner: "=", 454 }, 455 &Assignment{ 456 Name: Ident{"boo", mkpos(61, 6, 3)}, 457 Pos: mkpos(66, 6, 8), 458 Value: Value{ 459 Type: String, 460 Pos: mkpos(68, 6, 10), 461 StringValue: "stuff", 462 Variable: "foo", 463 }, 464 OrigValue: Value{ 465 Type: String, 466 Pos: mkpos(68, 6, 10), 467 StringValue: "stuff", 468 Variable: "foo", 469 }, 470 Assigner: "+=", 471 }, 472 }, 473 nil, 474 }, 475} 476 477func TestParseValidInput(t *testing.T) { 478 for _, testCase := range validParseTestCases { 479 r := bytes.NewBufferString(testCase.input) 480 file, errs := ParseAndEval("", r, NewScope(nil)) 481 if len(errs) != 0 { 482 t.Errorf("test case: %s", testCase.input) 483 t.Errorf("unexpected errors:") 484 for _, err := range errs { 485 t.Errorf(" %s", err) 486 } 487 t.FailNow() 488 } 489 490 if len(file.Defs) == len(testCase.defs) { 491 for i := range file.Defs { 492 if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) { 493 t.Errorf("test case: %s", testCase.input) 494 t.Errorf("incorrect defintion %d:", i) 495 t.Errorf(" expected: %s", testCase.defs[i]) 496 t.Errorf(" got: %s", file.Defs[i]) 497 } 498 } 499 } else { 500 t.Errorf("test case: %s", testCase.input) 501 t.Errorf("length mismatch, expected %d definitions, got %d", 502 len(testCase.defs), len(file.Defs)) 503 } 504 505 if len(file.Comments) == len(testCase.comments) { 506 for i := range file.Comments { 507 if !reflect.DeepEqual(file.Comments, testCase.comments) { 508 t.Errorf("test case: %s", testCase.input) 509 t.Errorf("incorrect comment %d:", i) 510 t.Errorf(" expected: %s", testCase.comments[i]) 511 t.Errorf(" got: %s", file.Comments[i]) 512 } 513 } 514 } else { 515 t.Errorf("test case: %s", testCase.input) 516 t.Errorf("length mismatch, expected %d comments, got %d", 517 len(testCase.comments), len(file.Comments)) 518 } 519 } 520} 521 522// TODO: Test error strings 523