github.com/aretext/aretext@v1.3.0/syntax/parser/computation_test.go (about) 1 package parser 2 3 import ( 4 "math" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 ) 9 10 type stubState struct{ x int } 11 12 func (s stubState) Equals(other State) bool { 13 otherStubState, ok := other.(stubState) 14 return ok && s.x == otherStubState.x 15 } 16 17 func TestComputationLargestMatchingSubComputation(t *testing.T) { 18 testCases := []struct { 19 name string 20 builder func() *computation 21 readStartPos uint64 22 readEndPos uint64 23 state State 24 expectedReadLength uint64 25 }{ 26 { 27 name: "single computation, start does not match", 28 builder: func() *computation { 29 return newComputation(2, 2, EmptyState{}, EmptyState{}, nil) 30 }, 31 readStartPos: 2, 32 readEndPos: 5, 33 state: EmptyState{}, 34 expectedReadLength: 0, 35 }, 36 { 37 name: "single computation, smaller than range", 38 builder: func() *computation { 39 return newComputation(2, 2, EmptyState{}, EmptyState{}, nil) 40 }, 41 readStartPos: 0, 42 readEndPos: 5, 43 state: EmptyState{}, 44 expectedReadLength: 2, 45 }, 46 { 47 name: "single computation, one less than end of range", 48 builder: func() *computation { 49 return newComputation(2, 2, EmptyState{}, EmptyState{}, nil) 50 }, 51 readStartPos: 0, 52 readEndPos: 3, 53 state: EmptyState{}, 54 expectedReadLength: 2, 55 }, 56 { 57 name: "single computation, equal to range", 58 builder: func() *computation { 59 return newComputation(2, 2, EmptyState{}, EmptyState{}, nil) 60 }, 61 readStartPos: 0, 62 readEndPos: 2, 63 state: EmptyState{}, 64 expectedReadLength: 2, 65 }, 66 { 67 name: "single computation, greater than range", 68 builder: func() *computation { 69 return newComputation(5, 5, EmptyState{}, EmptyState{}, nil) 70 }, 71 readStartPos: 0, 72 readEndPos: 4, 73 state: EmptyState{}, 74 expectedReadLength: 0, 75 }, 76 { 77 name: "multiple computations, match left child", 78 builder: func() *computation { 79 left := newComputation(3, 3, EmptyState{}, EmptyState{}, nil) 80 right := newComputation(5, 5, EmptyState{}, EmptyState{}, nil) 81 return left.Append(right) 82 }, 83 readStartPos: 0, 84 readEndPos: 4, 85 state: EmptyState{}, 86 expectedReadLength: 3, 87 }, 88 { 89 name: "multiple computations, match right child", 90 builder: func() *computation { 91 left := newComputation(3, 3, EmptyState{}, EmptyState{}, nil) 92 right := newComputation(5, 5, EmptyState{}, EmptyState{}, nil) 93 return left.Append(right) 94 }, 95 readStartPos: 3, 96 readEndPos: 9, 97 state: EmptyState{}, 98 expectedReadLength: 5, 99 }, 100 { 101 name: "multiple computations, match left child with lookahead", 102 builder: func() *computation { 103 left := newComputation(10, 3, EmptyState{}, EmptyState{}, nil) 104 right := newComputation(5, 5, EmptyState{}, EmptyState{}, nil) 105 return left.Append(right) 106 }, 107 readStartPos: 0, 108 readEndPos: 15, 109 state: EmptyState{}, 110 expectedReadLength: 10, 111 }, 112 { 113 name: "multiple computations, match right child with lookahead", 114 builder: func() *computation { 115 left := newComputation(10, 3, EmptyState{}, EmptyState{}, nil) 116 right := newComputation(9, 8, EmptyState{}, EmptyState{}, nil) 117 return left.Append(right) 118 }, 119 readStartPos: 3, 120 readEndPos: 20, 121 state: EmptyState{}, 122 expectedReadLength: 9, 123 }, 124 { 125 name: "match state left child", 126 builder: func() *computation { 127 left := newComputation(3, 3, stubState{1}, stubState{2}, nil) 128 right := newComputation(5, 5, stubState{3}, stubState{4}, nil) 129 return left.Append(right) 130 }, 131 readStartPos: 0, 132 readEndPos: 4, 133 state: stubState{1}, 134 expectedReadLength: 3, 135 }, 136 { 137 name: "mismatch state left child", 138 builder: func() *computation { 139 left := newComputation(3, 3, stubState{1}, stubState{2}, nil) 140 right := newComputation(5, 5, stubState{3}, stubState{4}, nil) 141 return left.Append(right) 142 }, 143 readStartPos: 0, 144 readEndPos: 3, 145 state: stubState{99}, 146 expectedReadLength: 0, 147 }, 148 { 149 name: "match state parent", 150 builder: func() *computation { 151 left := newComputation(3, 3, stubState{1}, stubState{2}, nil) 152 right := newComputation(5, 5, stubState{3}, stubState{4}, nil) 153 return left.Append(right) 154 }, 155 readStartPos: 0, 156 readEndPos: 9, 157 state: stubState{1}, 158 expectedReadLength: 8, 159 }, 160 { 161 name: "mismatch state parent", 162 builder: func() *computation { 163 left := newComputation(3, 3, stubState{1}, stubState{2}, nil) 164 right := newComputation(5, 5, stubState{3}, stubState{4}, nil) 165 return left.Append(right) 166 }, 167 readStartPos: 0, 168 readEndPos: 9, 169 state: stubState{99}, 170 expectedReadLength: 0, 171 }, 172 } 173 174 for _, tc := range testCases { 175 t.Run(tc.name, func(t *testing.T) { 176 c := tc.builder() 177 sub := c.LargestMatchingSubComputation(tc.readStartPos, tc.readEndPos, tc.state) 178 assert.Equal(t, tc.expectedReadLength, sub.ReadLength()) 179 }) 180 } 181 } 182 183 func TestComputationTokensIntersectingRange(t *testing.T) { 184 testCases := []struct { 185 name string 186 builder func() *computation 187 startPos uint64 188 endPos uint64 189 expectedTokens []Token 190 }{ 191 { 192 name: "single computation, no tokens", 193 builder: func() *computation { 194 return newComputation(1, 1, EmptyState{}, EmptyState{}, nil) 195 }, 196 startPos: 0, 197 endPos: 100, 198 expectedTokens: nil, 199 }, 200 { 201 name: "single computation, single token equals range", 202 builder: func() *computation { 203 return newComputation(2, 2, EmptyState{}, EmptyState{}, []ComputedToken{ 204 {Offset: 0, Length: 2}, 205 }) 206 }, 207 startPos: 0, 208 endPos: 2, 209 expectedTokens: []Token{ 210 {StartPos: 0, EndPos: 2}, 211 }, 212 }, 213 { 214 name: "single computation, multiple tokens in range", 215 builder: func() *computation { 216 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 217 {Offset: 0, Length: 3}, 218 {Offset: 3, Length: 1}, 219 }) 220 }, 221 startPos: 0, 222 endPos: 4, 223 expectedTokens: []Token{ 224 {StartPos: 0, EndPos: 3}, 225 {StartPos: 3, EndPos: 4}, 226 }, 227 }, 228 { 229 name: "single computation, token ending before range", 230 builder: func() *computation { 231 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 232 {Offset: 0, Length: 1}, 233 }) 234 }, 235 startPos: 2, 236 endPos: 4, 237 expectedTokens: nil, 238 }, 239 { 240 name: "single computation, token ending at range start", 241 builder: func() *computation { 242 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 243 {Offset: 0, Length: 1}, 244 }) 245 }, 246 startPos: 1, 247 endPos: 4, 248 expectedTokens: nil, 249 }, 250 { 251 name: "single computation, token starting at range end", 252 builder: func() *computation { 253 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 254 {Offset: 2, Length: 1}, 255 }) 256 }, 257 startPos: 0, 258 endPos: 2, 259 expectedTokens: nil, 260 }, 261 { 262 name: "single computation, token starting after range end", 263 builder: func() *computation { 264 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 265 {Offset: 3, Length: 1}, 266 }) 267 }, 268 startPos: 0, 269 endPos: 2, 270 expectedTokens: nil, 271 }, 272 { 273 name: "append two computations, all tokens intersect range", 274 builder: func() *computation { 275 return newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 276 {Offset: 0, Length: 4}, 277 }).Append( 278 newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 279 {Offset: 0, Length: 3}, 280 })) 281 }, 282 startPos: 0, 283 endPos: 7, 284 expectedTokens: []Token{ 285 {StartPos: 0, EndPos: 4}, 286 {StartPos: 4, EndPos: 7}, 287 }, 288 }, 289 { 290 name: "append many computations in sequence, all tokens intersect range", 291 builder: func() *computation { 292 var c *computation 293 for i := 0; i < 10; i++ { 294 c = c.Append(newComputation(1, 1, EmptyState{}, EmptyState{}, []ComputedToken{ 295 {Offset: 0, Length: 1}, 296 })) 297 } 298 return c 299 }, 300 startPos: 0, 301 endPos: 10, 302 expectedTokens: []Token{ 303 {StartPos: 0, EndPos: 1}, 304 {StartPos: 1, EndPos: 2}, 305 {StartPos: 2, EndPos: 3}, 306 {StartPos: 3, EndPos: 4}, 307 {StartPos: 4, EndPos: 5}, 308 {StartPos: 5, EndPos: 6}, 309 {StartPos: 6, EndPos: 7}, 310 {StartPos: 7, EndPos: 8}, 311 {StartPos: 8, EndPos: 9}, 312 {StartPos: 9, EndPos: 10}, 313 }, 314 }, 315 { 316 name: "prepend many computations in sequence, all tokens intersect range", 317 builder: func() *computation { 318 var c *computation 319 for i := 0; i < 10; i++ { 320 c = newComputation(1, 1, EmptyState{}, EmptyState{}, []ComputedToken{ 321 {Offset: 0, Length: 1}, 322 }).Append(c) 323 } 324 return c 325 }, 326 startPos: 0, 327 endPos: 10, 328 expectedTokens: []Token{ 329 {StartPos: 0, EndPos: 1}, 330 {StartPos: 1, EndPos: 2}, 331 {StartPos: 2, EndPos: 3}, 332 {StartPos: 3, EndPos: 4}, 333 {StartPos: 4, EndPos: 5}, 334 {StartPos: 5, EndPos: 6}, 335 {StartPos: 6, EndPos: 7}, 336 {StartPos: 7, EndPos: 8}, 337 {StartPos: 8, EndPos: 9}, 338 {StartPos: 9, EndPos: 10}, 339 }, 340 }, 341 { 342 name: "append two computations each with many sub-computations, all tokens intersect range", 343 builder: func() *computation { 344 var c1, c2 *computation 345 for i := 0; i < 5; i++ { 346 c1 = c1.Append(newComputation(1, 1, EmptyState{}, EmptyState{}, []ComputedToken{ 347 {Offset: 0, Length: 1}, 348 })) 349 c2 = c2.Append(newComputation(1, 1, EmptyState{}, EmptyState{}, []ComputedToken{ 350 {Offset: 0, Length: 1}, 351 })) 352 } 353 return c1.Append(c2) 354 }, 355 startPos: 0, 356 endPos: 10, 357 expectedTokens: []Token{ 358 {StartPos: 0, EndPos: 1}, 359 {StartPos: 1, EndPos: 2}, 360 {StartPos: 2, EndPos: 3}, 361 {StartPos: 3, EndPos: 4}, 362 {StartPos: 4, EndPos: 5}, 363 {StartPos: 5, EndPos: 6}, 364 {StartPos: 6, EndPos: 7}, 365 {StartPos: 7, EndPos: 8}, 366 {StartPos: 8, EndPos: 9}, 367 {StartPos: 9, EndPos: 10}, 368 }, 369 }, 370 } 371 372 for _, tc := range testCases { 373 t.Run(tc.name, func(t *testing.T) { 374 c := tc.builder() 375 tokens := c.TokensIntersectingRange(tc.startPos, tc.endPos) 376 assert.Equal(t, tc.expectedTokens, tokens) 377 }) 378 } 379 } 380 381 func TestTokenAtPosition(t *testing.T) { 382 testCases := []struct { 383 name string 384 builder func() *computation 385 pos uint64 386 expectFound bool 387 expectedToken Token 388 }{ 389 { 390 name: "single computation, no tokens", 391 builder: func() *computation { 392 return newComputation(1, 1, EmptyState{}, EmptyState{}, nil) 393 }, 394 pos: 0, 395 expectedToken: Token{}, 396 }, 397 { 398 name: "single computation, single token containing position at start", 399 builder: func() *computation { 400 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 401 {Offset: 0, Length: 3}, 402 }) 403 }, 404 pos: 0, 405 expectedToken: Token{StartPos: 0, EndPos: 3}, 406 }, 407 { 408 name: "single computation, single token containing position in middle", 409 builder: func() *computation { 410 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 411 {Offset: 0, Length: 3}, 412 }) 413 }, 414 pos: 1, 415 expectedToken: Token{StartPos: 0, EndPos: 3}, 416 }, 417 { 418 name: "single computation, single token containing position at end", 419 builder: func() *computation { 420 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 421 {Offset: 0, Length: 3}, 422 }) 423 }, 424 pos: 2, 425 expectedToken: Token{StartPos: 0, EndPos: 3}, 426 }, 427 { 428 name: "single computation, single token position just past end", 429 builder: func() *computation { 430 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 431 {Offset: 0, Length: 3}, 432 }) 433 }, 434 pos: 3, 435 expectedToken: Token{}, 436 }, 437 { 438 name: "single computation, multiple tokens, one contains position", 439 builder: func() *computation { 440 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 441 {Offset: 0, Length: 1}, 442 {Offset: 1, Length: 1}, 443 {Offset: 2, Length: 1}, 444 }) 445 }, 446 pos: 1, 447 expectedToken: Token{StartPos: 1, EndPos: 2}, 448 }, 449 { 450 name: "single computation, multiple tokens, none contain position", 451 builder: func() *computation { 452 return newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 453 {Offset: 0, Length: 1}, 454 {Offset: 2, Length: 1}, 455 }) 456 }, 457 pos: 1, 458 expectedToken: Token{}, 459 }, 460 { 461 name: "multiple computations, token in left child", 462 builder: func() *computation { 463 c := newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 464 {Offset: 0, Length: 3}, 465 }) 466 return c.Append(newComputation(1, 1, EmptyState{}, EmptyState{}, []ComputedToken{})) 467 }, 468 pos: 1, 469 expectedToken: Token{StartPos: 0, EndPos: 3}, 470 }, 471 { 472 name: "multiple computations, token in right child", 473 builder: func() *computation { 474 c := newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{}) 475 return c.Append(newComputation(4, 4, EmptyState{}, EmptyState{}, []ComputedToken{ 476 {Offset: 0, Length: 4}, 477 })) 478 }, 479 pos: 4, 480 expectedToken: Token{StartPos: 3, EndPos: 7}, 481 }, 482 } 483 484 for _, tc := range testCases { 485 t.Run(tc.name, func(t *testing.T) { 486 c := tc.builder() 487 token := c.TokenAtPosition(tc.pos) 488 assert.Equal(t, tc.expectedToken, token) 489 }) 490 } 491 } 492 493 func TestConcatLeafComputations(t *testing.T) { 494 testCases := []struct { 495 name string 496 computations []*computation 497 }{ 498 { 499 name: "empty", 500 computations: nil, 501 }, 502 { 503 name: "single computation", 504 computations: []*computation{ 505 newComputation(5, 5, EmptyState{}, EmptyState{}, []ComputedToken{ 506 {Length: 3}, 507 }), 508 }, 509 }, 510 { 511 name: "two computations", 512 computations: []*computation{ 513 newComputation(5, 5, EmptyState{}, EmptyState{}, []ComputedToken{ 514 {Length: 3}, 515 }), 516 newComputation(8, 8, EmptyState{}, EmptyState{}, []ComputedToken{ 517 {Length: 8}, 518 }), 519 }, 520 }, 521 { 522 name: "many computations", 523 computations: []*computation{ 524 newComputation(5, 5, EmptyState{}, EmptyState{}, []ComputedToken{ 525 {Length: 3}, 526 }), 527 newComputation(8, 8, EmptyState{}, EmptyState{}, []ComputedToken{ 528 {Length: 8}, 529 }), 530 newComputation(2, 2, EmptyState{}, EmptyState{}, []ComputedToken{ 531 {Length: 2}, 532 }), 533 newComputation(7, 7, EmptyState{}, EmptyState{}, []ComputedToken{ 534 {Length: 7}, 535 }), 536 newComputation(3, 3, EmptyState{}, EmptyState{}, []ComputedToken{ 537 {Length: 3}, 538 }), 539 }, 540 }, 541 } 542 543 for _, tc := range testCases { 544 t.Run(tc.name, func(t *testing.T) { 545 c1 := concatLeafComputations(tc.computations) 546 547 var c2 *computation 548 for _, leaf := range tc.computations { 549 c2 = c2.Append(leaf) 550 } 551 552 assert.Equal(t, c1.ReadLength(), c2.ReadLength()) 553 assert.Equal(t, c1.ConsumedLength(), c2.ConsumedLength()) 554 assert.Equal(t, c1.TreeHeight(), c2.TreeHeight()) 555 556 actualTokens := c1.TokensIntersectingRange(0, math.MaxUint64) 557 expectedTokens := c2.TokensIntersectingRange(0, math.MaxUint64) 558 assert.Equal(t, actualTokens, expectedTokens) 559 }) 560 } 561 }