github.com/aretext/aretext@v1.3.0/input/engine/compile_test.go (about) 1 package engine 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 ) 9 10 func TestCompile(t *testing.T) { 11 testCases := []struct { 12 name string 13 cmdExprs []CmdExpr 14 expected *StateMachine 15 }{ 16 { 17 name: "empty", 18 cmdExprs: []CmdExpr{}, 19 expected: &StateMachine{ 20 numStates: 1, 21 acceptCmd: map[stateId]CmdId{}, 22 transitions: map[stateId][]transition{}, 23 }, 24 }, 25 { 26 name: "EventExpr", 27 cmdExprs: []CmdExpr{ 28 { 29 CmdId: 0, 30 Expr: EventExpr{Event: 99}, 31 }, 32 }, 33 expected: &StateMachine{ 34 numStates: 2, 35 acceptCmd: map[stateId]CmdId{ 36 1: 0, 37 }, 38 transitions: map[stateId][]transition{ 39 0: { 40 { 41 eventRange: eventRange{start: 99, end: 99}, 42 nextState: 1, 43 }, 44 }, 45 }, 46 }, 47 }, 48 { 49 name: "EventRangeExpr", 50 cmdExprs: []CmdExpr{ 51 { 52 CmdId: 0, 53 Expr: EventRangeExpr{ 54 StartEvent: 23, 55 EndEvent: 79, 56 }, 57 }, 58 }, 59 expected: &StateMachine{ 60 numStates: 2, 61 acceptCmd: map[stateId]CmdId{ 62 1: 0, 63 }, 64 transitions: map[stateId][]transition{ 65 0: { 66 { 67 eventRange: eventRange{start: 23, end: 79}, 68 nextState: 1, 69 }, 70 }, 71 }, 72 }, 73 }, 74 { 75 name: "ConcatExpr", 76 cmdExprs: []CmdExpr{ 77 { 78 CmdId: 0, 79 Expr: ConcatExpr{ 80 Children: []Expr{ 81 EventExpr{Event: 12}, 82 EventExpr{Event: 34}, 83 }, 84 }, 85 }, 86 }, 87 expected: &StateMachine{ 88 numStates: 3, 89 acceptCmd: map[stateId]CmdId{ 90 1: 0, 91 }, 92 transitions: map[stateId][]transition{ 93 0: { 94 { 95 eventRange: eventRange{start: 12, end: 12}, 96 nextState: 2, 97 }, 98 }, 99 2: { 100 { 101 eventRange: eventRange{start: 34, end: 34}, 102 nextState: 1, 103 }, 104 }, 105 }, 106 }, 107 }, 108 { 109 name: "AltExpr", 110 cmdExprs: []CmdExpr{ 111 { 112 CmdId: 0, 113 Expr: AltExpr{ 114 Children: []Expr{ 115 EventExpr{Event: 12}, 116 EventExpr{Event: 34}, 117 }, 118 }, 119 }, 120 }, 121 expected: &StateMachine{ 122 numStates: 2, 123 acceptCmd: map[stateId]CmdId{ 124 1: 0, 125 }, 126 transitions: map[stateId][]transition{ 127 0: { 128 { 129 eventRange: eventRange{start: 12, end: 12}, 130 nextState: 1, 131 }, 132 { 133 eventRange: eventRange{start: 34, end: 34}, 134 nextState: 1, 135 }, 136 }, 137 }, 138 }, 139 }, 140 { 141 name: "OptionExpr", 142 cmdExprs: []CmdExpr{ 143 { 144 CmdId: 0, 145 Expr: OptionExpr{ 146 Child: EventExpr{Event: 99}, 147 }, 148 }, 149 }, 150 expected: &StateMachine{ 151 numStates: 2, 152 acceptCmd: map[stateId]CmdId{ 153 0: 0, 154 1: 0, 155 }, 156 transitions: map[stateId][]transition{ 157 0: { 158 { 159 eventRange: eventRange{start: 99, end: 99}, 160 nextState: 1, 161 }, 162 }, 163 }, 164 }, 165 }, 166 { 167 name: "StarExpr", 168 cmdExprs: []CmdExpr{ 169 { 170 CmdId: 0, 171 Expr: StarExpr{ 172 Child: EventExpr{Event: 99}, 173 }, 174 }, 175 }, 176 expected: &StateMachine{ 177 numStates: 1, 178 acceptCmd: map[stateId]CmdId{ 179 0: 0, 180 }, 181 transitions: map[stateId][]transition{ 182 0: { 183 { 184 eventRange: eventRange{start: 99, end: 99}, 185 nextState: 0, 186 }, 187 }, 188 }, 189 }, 190 }, 191 { 192 name: "CaptureExpr", 193 cmdExprs: []CmdExpr{ 194 { 195 CmdId: 0, 196 Expr: CaptureExpr{ 197 Child: EventExpr{Event: 99}, 198 }, 199 }, 200 }, 201 expected: &StateMachine{ 202 numStates: 2, 203 acceptCmd: map[stateId]CmdId{ 204 1: 0, 205 }, 206 transitions: map[stateId][]transition{ 207 0: { 208 { 209 eventRange: eventRange{start: 99, end: 99}, 210 nextState: 1, 211 captures: map[CmdId]CaptureId{ 212 0: 0, 213 }, 214 }, 215 }, 216 }, 217 }, 218 }, 219 { 220 name: "Multiple commands with overlapping transitions", 221 cmdExprs: []CmdExpr{ 222 { 223 CmdId: 0, 224 Expr: EventRangeExpr{ 225 StartEvent: 2, 226 EndEvent: 10, 227 }, 228 }, 229 { 230 CmdId: 1, 231 Expr: EventRangeExpr{ 232 StartEvent: 0, 233 EndEvent: 3, 234 }, 235 }, 236 { 237 CmdId: 2, 238 Expr: EventRangeExpr{ 239 StartEvent: 8, 240 EndEvent: 13, 241 }, 242 }, 243 }, 244 expected: &StateMachine{ 245 numStates: 4, 246 acceptCmd: map[stateId]CmdId{ 247 1: 1, 248 2: 0, 249 3: 2, 250 }, 251 transitions: map[stateId][]transition{ 252 0: { 253 { 254 eventRange: eventRange{start: 0, end: 1}, 255 nextState: 1, 256 }, 257 { 258 eventRange: eventRange{start: 2, end: 3}, 259 nextState: 2, 260 }, 261 { 262 eventRange: eventRange{start: 4, end: 7}, 263 nextState: 2, 264 }, 265 { 266 eventRange: eventRange{start: 8, end: 10}, 267 nextState: 2, 268 }, 269 { 270 eventRange: eventRange{start: 11, end: 13}, 271 nextState: 3, 272 }, 273 }, 274 }, 275 }, 276 }, 277 { 278 name: "Multiple commands with same suffix", 279 cmdExprs: []CmdExpr{ 280 { 281 CmdId: 0, 282 Expr: ConcatExpr{ 283 Children: []Expr{ 284 EventExpr{Event: 1}, 285 EventExpr{Event: 2}, 286 EventExpr{Event: 3}, 287 }, 288 }, 289 }, 290 { 291 CmdId: 1, 292 Expr: ConcatExpr{ 293 Children: []Expr{ 294 EventExpr{Event: 4}, 295 EventExpr{Event: 5}, 296 EventExpr{Event: 3}, 297 }, 298 }, 299 }, 300 }, 301 expected: &StateMachine{ 302 numStates: 7, 303 acceptCmd: map[stateId]CmdId{ 304 1: 1, 305 2: 0, 306 }, 307 transitions: map[stateId][]transition{ 308 0: { 309 { 310 eventRange: eventRange{start: 1, end: 1}, 311 nextState: 3, 312 }, 313 { 314 eventRange: eventRange{start: 4, end: 4}, 315 nextState: 4, 316 }, 317 }, 318 3: { 319 { 320 eventRange: eventRange{start: 2, end: 2}, 321 nextState: 6, 322 }, 323 }, 324 4: { 325 { 326 eventRange: eventRange{start: 5, end: 5}, 327 nextState: 5, 328 }, 329 }, 330 5: { 331 { 332 eventRange: eventRange{start: 3, end: 3}, 333 nextState: 1, 334 }, 335 }, 336 6: { 337 { 338 eventRange: eventRange{start: 3, end: 3}, 339 nextState: 2, 340 }, 341 }, 342 }, 343 }, 344 }, 345 { 346 name: "Sequential overlapping transitions", 347 cmdExprs: []CmdExpr{ 348 { 349 CmdId: 0, 350 Expr: EventRangeExpr{ 351 StartEvent: 0, 352 EndEvent: 1, 353 }, 354 }, 355 { 356 CmdId: 1, 357 Expr: EventRangeExpr{ 358 StartEvent: 1, 359 EndEvent: 2, 360 }, 361 }, 362 { 363 CmdId: 2, 364 Expr: EventRangeExpr{ 365 StartEvent: 2, 366 EndEvent: 3, 367 }, 368 }, 369 }, 370 expected: &StateMachine{ 371 numStates: 4, 372 acceptCmd: map[stateId]CmdId{ 373 1: 0, 374 2: 1, 375 3: 2, 376 }, 377 transitions: map[stateId][]transition{ 378 0: { 379 { 380 eventRange: eventRange{start: 0, end: 0}, 381 nextState: 1, 382 }, 383 { 384 eventRange: eventRange{start: 1, end: 1}, 385 nextState: 1, 386 }, 387 { 388 eventRange: eventRange{start: 2, end: 2}, 389 nextState: 2, 390 }, 391 { 392 eventRange: eventRange{start: 3, end: 3}, 393 nextState: 3, 394 }, 395 }, 396 }, 397 }, 398 }, 399 { 400 name: "Overlapping transitions same start, first is longer", 401 cmdExprs: []CmdExpr{ 402 { 403 CmdId: 0, 404 Expr: EventRangeExpr{ 405 StartEvent: 0, 406 EndEvent: 7, 407 }, 408 }, 409 { 410 CmdId: 1, 411 Expr: EventRangeExpr{ 412 StartEvent: 0, 413 EndEvent: 2, 414 }, 415 }, 416 }, 417 expected: &StateMachine{ 418 numStates: 2, 419 acceptCmd: map[stateId]CmdId{ 420 1: 0, 421 }, 422 transitions: map[stateId][]transition{ 423 0: { 424 { 425 eventRange: eventRange{start: 0, end: 2}, 426 nextState: 1, 427 }, 428 { 429 eventRange: eventRange{start: 3, end: 7}, 430 nextState: 1, 431 }, 432 }, 433 }, 434 }, 435 }, 436 } 437 438 for _, tc := range testCases { 439 t.Run(tc.name, func(t *testing.T) { 440 sm, err := Compile(tc.cmdExprs) 441 require.NoError(t, err) 442 assert.Equal(t, tc.expected, sm) 443 }) 444 } 445 } 446 447 func TestCompileValidation(t *testing.T) { 448 testCases := []struct { 449 name string 450 cmdExprs []CmdExpr 451 errMsg string 452 }{ 453 { 454 name: "valid program", 455 cmdExprs: []CmdExpr{ 456 { 457 CmdId: 0, 458 Expr: EventExpr{Event: 1}, 459 }, 460 }, 461 errMsg: "", 462 }, 463 { 464 name: "nil expression", 465 cmdExprs: []CmdExpr{ 466 { 467 CmdId: 0, 468 Expr: nil, 469 }, 470 }, 471 errMsg: "Invalid expression for cmd 0: Invalid expression type <nil>", 472 }, 473 { 474 name: "invalid event range", 475 cmdExprs: []CmdExpr{ 476 { 477 CmdId: 0, 478 Expr: EventRangeExpr{ 479 StartEvent: 5, 480 EndEvent: 4, 481 }, 482 }, 483 }, 484 errMsg: "Invalid expression for cmd 0: Invalid event range [5, 4]", 485 }, 486 { 487 name: "duplicate command IDs", 488 cmdExprs: []CmdExpr{ 489 { 490 CmdId: 0, 491 Expr: EventExpr{Event: 1}, 492 }, 493 { 494 CmdId: 0, 495 Expr: EventExpr{Event: 2}, 496 }, 497 }, 498 errMsg: "Duplicate command ID detected: 0", 499 }, 500 { 501 name: "two commands with the same capture ID", 502 cmdExprs: []CmdExpr{ 503 { 504 CmdId: 0, 505 Expr: CaptureExpr{ 506 CaptureId: 1, 507 Child: EventExpr{Event: 1}, 508 }, 509 }, 510 { 511 CmdId: 1, 512 Expr: CaptureExpr{ 513 CaptureId: 1, 514 Child: EventExpr{Event: 1}, 515 }, 516 }, 517 }, 518 errMsg: "", 519 }, 520 { 521 name: "nested capture ID", 522 cmdExprs: []CmdExpr{ 523 { 524 CmdId: 0, 525 Expr: CaptureExpr{ 526 CaptureId: 1, 527 Child: AltExpr{ 528 Children: []Expr{ 529 CaptureExpr{ 530 CaptureId: 2, 531 Child: EventExpr{Event: 1}, 532 }, 533 EventExpr{Event: 2}, 534 }, 535 }, 536 }, 537 }, 538 }, 539 errMsg: "Invalid expression for cmd 0: Nested capture detected: 2", 540 }, 541 } 542 543 for _, tc := range testCases { 544 t.Run(tc.name, func(t *testing.T) { 545 _, err := Compile(tc.cmdExprs) 546 if tc.errMsg == "" { 547 assert.NoError(t, err) 548 } else { 549 assert.EqualError(t, err, tc.errMsg) 550 } 551 }) 552 } 553 }