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  }