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  }